3 private static $instance=null;
4 private $includePath = array();
5 private $stylesheets = array();
6 private $included = array();
7 private $ignoreFiles = array();
11 $this->ignoreFiles[
$path] = 1;
12 if ( isset($this->stylesheets[
$path]) ){
13 unset($this->stylesheets[$path]);
19 return @$this->ignoreFiles[
$path];
24 if ( !isset(self::$instance) ){
27 return self::$instance;
31 return $this->stylesheets;
35 $this->includePath[
$path] = $url;
39 unset($this->includePath[
$path]);
43 return $this->includePath;
47 if ( @$this->ignoreFiles[
$path] )
return;
49 $this->stylesheets[
$path] = 1;
55 return DATAFACE_SITE_HREF.
'?-action=css&--id='.$this->generateCacheKeyForScripts(array_keys($this->stylesheets));
59 return $this->included;
64 return file_get_contents($this->getCSSCachePath(array_keys($this->stylesheets)));
67 private function generateCacheKeyForScripts($stylesheets){
69 $base = basename($stylesheets[0]);
71 $base = substr($base, 0, 10);
72 return $base.
'-'.md5(implode(PATH_SEPARATOR, $stylesheets));
75 private function writeCSS($stylesheets, $contents){
76 $path = $this->getCSSCachePath($stylesheets);
77 return file_put_contents(
$path, $contents, LOCK_EX);
80 private function getCSSCachePath($stylesheets){
81 return DATAFACE_SITE_PATH.
'/templates_c/'.$this->generateCacheKeyForScripts($stylesheets).
'.css';
84 private function getManifestPath($stylesheets){
85 return DATAFACE_SITE_PATH.
'/templates_c/'.$this->generateCacheKeyForScripts($stylesheets).
'.manifest.css';
88 private function writeManifest($stylesheets, $included){
89 $path = $this->getManifestPath($stylesheets);
90 return file_put_contents(
$path, json_encode($included), LOCK_EX);
93 private function isCacheDirty($stylesheets){
94 $jspath = $this->getCSSCachePath($stylesheets);
95 $mfpath = $this->getManifestPath($stylesheets);
97 if ( !file_exists($jspath) )
return true;
98 if ( !file_exists($mfpath) )
return true;
99 $mtime = filemtime($jspath);
101 $deps = json_decode(file_get_contents($mfpath),
true);
102 foreach ($deps as $script=>$file){
103 if ( filemtime($file) > $mtime ){
114 public function compile($stylesheets=null, $clean=
false){
115 if (!isset($stylesheets) ){
116 $stylesheets = array_keys($this->stylesheets);
119 if ( $clean or $this->isCacheDirty($stylesheets) ){
120 $this->included = array();
121 $contents = $this->_compile($stylesheets);
125 "remove-empty-blocks" =>
true,
126 "remove-empty-rulesets" =>
true,
127 "remove-last-semicolons" =>
true,
128 "convert-css3-properties" =>
true,
129 "compress-color-values" =>
true,
130 "compress-unit-values" =>
true,
131 "emulate-css3-variables" =>
true
135 file_put_contents($this->getCSSCachePath($stylesheets), $contents, LOCK_EX);
136 file_put_contents($this->getManifestPath($stylesheets), json_encode($this->included), LOCK_EX);
156 private function _compile($stylesheets){
159 if ( !is_array($stylesheets) ) $stylesheets = array($stylesheets);
160 $included =& $this->included;
163 foreach ($stylesheets as $script){
165 if ( isset($included[$script]) )
continue;
167 foreach ($this->includePath as
$path=>$url){
168 $filepath =
$path.DIRECTORY_SEPARATOR.$script;
169 $dirname = dirname($script);
170 if ( $dirname ) $url .=
'/'.$dirname;
172 if ( is_readable($filepath) ){
173 $contents = file_get_contents($filepath);
174 $contents = preg_replace(
'/url\(\s*[\'"]?\/?(.+?)[\'"]?\s*\)/i',
'url('.$url.
'/$1)', $contents);
175 $included[$script] = $filepath;
180 if ( !isset($contents) )
throw new Exception(sprintf(
"Could not find script %s", $script));
187 return implode(
"\r\n",
$out);
194 $files = glob(DATAFACE_SITE_PATH.
'/templates_c/*.css');
198 $files = glob(DATAFACE_SITE_PATH.
'/templates_c/*.manifest.css');
395 private static $transformations = array
397 "border-radius" => array(
"-moz-border-radius",
"-webkit-border-radius",
"-khtml-border-radius"),
398 "border-top-left-radius" => array(
"-moz-border-radius-topleft",
"-webkit-border-top-left-radius",
"-khtml-top-left-radius"),
399 "border-top-right-radius" => array(
"-moz-border-radius-topright",
"-webkit-border-top-right-radius",
"-khtml-top-right-radius"),
400 "border-bottom-right-radius" => array(
"-moz-border-radius-bottomright",
"-webkit-border-bottom-right-radius",
"-khtml-border-bottom-right-radius"),
401 "border-bottom-left-radius" => array(
"-moz-border-radius-bottomleft",
"-webkit-border-bottom-left-radius",
"-khtml-border-bottom-left-radius"),
402 "box-shadow" => array(
"-moz-box-shadow",
"-webkit-box-shadow",
"-khtml-box-shadow"),
403 "opacity" => array(array(
"CssMin",
"_tOpacity")),
404 "text-shadow" => array(
"-moz-text-shadow",
"-webkit-text-shadow",
"-khtml-text-shadow"),
405 "white-space" => array(array(
"CssMin",
"_tWhiteSpacePreWrap"))
414 public static function minify($css, $config = array())
417 $config = array_merge(array
419 "remove-empty-blocks" =>
true,
420 "remove-empty-rulesets" =>
true,
421 "remove-last-semicolons" =>
true,
422 "convert-css3-properties" =>
false,
423 "convert-color-values" =>
false,
424 "compress-color-values" =>
false,
425 "compress-unit-values" =>
false,
426 "emulate-css3-variables" =>
true,
429 $sRemoveEmptyBlocks = $config[
"remove-empty-blocks"];
430 $sRemoveEmptyRulesets = $config[
"remove-empty-rulesets"];
431 $sRemoveLastSemicolon = $config[
"remove-last-semicolons"];
432 $sConvertCss3Properties = $config[
"convert-css3-properties"];
433 $sCompressUnitValues = $config[
"compress-unit-values"];
434 $sConvertColorValues = $config[
"convert-color-values"];
435 $sCompressColorValues = $config[
"compress-color-values"];
436 $sEmulateCcss3Variables = $config[
"emulate-css3-variables"];
437 $sRemoveTokens = array(self::T_COMMENT);
439 if (!$sEmulateCcss3Variables)
441 $sRemoveTokens = array_merge($sRemoveTokens, array(self::T_AT_VARIABLES_START, self::T_VARIABLE_DECLARATION, self::T_AT_VARIABLES_END));
443 for($i = 0, $l = count($tokens); $i < $l; $i++)
445 if (in_array($tokens[$i][0], $sRemoveTokens))
450 $tokens = array_values($tokens);
452 if ($sRemoveEmptyRulesets)
454 for($i = 0, $l = count($tokens); $i < $l; $i++)
457 if ($tokens[$i][0] == self::T_RULESET_START && $tokens[$i+4][0] == self::T_RULESET_END)
460 unset($tokens[++$i]);
461 unset($tokens[++$i]);
462 unset($tokens[++$i]);
463 unset($tokens[++$i]);
466 $tokens = array_values($tokens);
469 if ($sRemoveEmptyBlocks)
471 for($i = 0, $l = count($tokens); $i < $l; $i++)
474 if (($tokens[$i][0] == self::T_AT_MEDIA_START && $tokens[$i+1][0] == self::T_AT_MEDIA_END)
475 || ($tokens[$i][0] == self::T_AT_FONT_FACE_START && $tokens[$i+1][0] == self::T_AT_FONT_FACE_END)
476 || ($tokens[$i][0] == self::T_AT_PAGE_START && $tokens[$i+1][0] == self::T_AT_PAGE_END))
479 unset($tokens[++$i]);
482 $tokens = array_values($tokens);
485 if ($sEmulateCcss3Variables)
488 $variables = array();
489 for($i = 0, $l = count($tokens); $i < $l; $i++)
491 if ($tokens[$i][0] == self::T_VARIABLE_DECLARATION)
493 for($i2 = 0, $l2 = count($tokens[$i][3]); $i2 < $l2; $i2++)
495 if (!isset($variables[$tokens[$i][3][$i2]]))
497 $variables[$tokens[$i][3][$i2]] = array();
499 $variables[$tokens[$i][3][$i2]][$tokens[$i][1]] = $tokens[$i][2];
505 for($i = 0, $l = count($tokens); $i < $l; $i++)
507 if ($tokens[$i][0] == self::T_DECLARATION)
510 if ($sEmulateCcss3Variables)
512 if (substr($tokens[$i][2], 0, 4) ==
"var(" && substr($tokens[$i][2], -1, 1) ==
")")
514 $tokens[$i][3][] =
"all";
515 $variable = trim(substr($tokens[$i][2], 4, -1));
516 for($i2 = 0, $l2 = count($tokens[$i][3]); $i2 < $l2; $i2++)
518 if (isset($variables[$tokens[$i][3][$i2]][$variable]))
520 $tokens[$i][2] = $variables[$tokens[$i][3][$i2]][$variable];
527 if ($sCompressUnitValues)
530 $tokens[$i][2] = preg_replace(
"/(^| |-)0\.([0-9]+)(%|em|ex|px|in|cm|mm|pt|pc)/iS",
"\${1}.\${2}\${3}", $tokens[$i][2]);
532 $tokens[$i][2] = preg_replace(
"/(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/iS",
"\${1}0", $tokens[$i][2]);
534 if ($tokens[$i][2] ==
"0 0 0 0") {$tokens[$i][2] =
"0";}
537 if ($sConvertColorValues && preg_match(
"/rgb\s*\(\s*([0-9%]+)\s*,\s*([0-9%]+)\s*,\s*([0-9%]+)\s*\)/iS", $tokens[$i][2], $m))
539 for ($i2 = 1, $l2 = count($m); $i2 < $l2; $i2++)
541 if (strpos(
"%", $m[$i2]) !==
false)
543 $m[$i2] = substr($m[$i2], 0, -1);
544 $m[$i2] = (int) (256 * ($m[$i2] / 100));
546 $m[$i2] = str_pad(dechex($m[$i2]), 2,
"0", STR_PAD_LEFT);
548 $tokens[$i][2] = str_replace($m[0],
"#" . $m[1] . $m[2] . $m[3], $tokens[$i][2]);
551 if ($sCompressColorValues && preg_match(
"/\#([0-9a-f]{6})/iS", $tokens[$i][2], $m))
553 $m[1] = strtolower($m[1]);
554 if (substr($m[1], 0, 1) == substr($m[1], 1, 1) && substr($m[1], 2, 1) == substr($m[1], 3, 1) && substr($m[1], 4, 1) == substr($m[1], 5, 1))
556 $tokens[$i][2] = str_replace($m[0],
"#" . substr($m[1], 0, 1) . substr($m[1], 2, 1) . substr($m[1], 4, 1), $tokens[$i][2]);
563 for($i = 0, $l = count($tokens); $i < $l; $i++)
566 if ($tokens[$i][0] == self::T_AT_RULE)
568 $r .=
"@" . $tokens[$i][1] .
" " . $tokens[$i][2] .
";";
571 elseif ($tokens[$i][0] == self::T_AT_MEDIA_START)
573 if (count($tokens[$i][1]) == 1 && $tokens[$i][1][0] ==
"all")
579 $r .=
"@media " . implode(
",", $tokens[$i][1]) .
"{";
583 elseif ($tokens[$i][0] == self::T_AT_FONT_FACE_START)
588 elseif ($tokens[$i][0] == self::T_FONT_FACE_DECLARATION)
590 $r .= $tokens[$i][1] .
":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_AT_FONT_FACE_END ?
"" :
";");
593 elseif ($tokens[$i][0] == self::T_AT_PAGE_START)
598 elseif ($tokens[$i][0] == self::T_PAGE_DECLARATION)
600 $r .= $tokens[$i][1] .
":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_AT_PAGE_END ?
"" :
";");
603 elseif ($tokens[$i][0] == self::T_SELECTORS)
605 $r .= implode(
",", $tokens[$i][1]);
608 elseif ($tokens[$i][0] == self::T_DECLARATIONS_START)
613 elseif ($tokens[$i][0] == self::T_DECLARATION)
615 if ($sConvertCss3Properties && isset(self::$transformations[$tokens[$i][1]]))
617 foreach (self::$transformations[$tokens[$i][1]] as $value)
619 if (!is_array($value))
621 $r .= $value .
":" . $tokens[$i][2] .
";";
623 elseif (is_array($value) && is_callable($value))
625 $r.= call_user_func_array($value, array($tokens[$i][1], $tokens[$i][2]));
630 $r .= $tokens[$i][1] .
":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_DECLARATIONS_END ?
"" :
";");
633 elseif (in_array($tokens[$i][0], array(self::T_DECLARATIONS_END, self::T_AT_MEDIA_END, self::T_AT_FONT_FACE_END, self::T_AT_PAGE_END)))
659 $sDefaultScope = array(
"all");
660 $sDefaultTrim =
" \t\n\r\0\x0B";
661 $sTokenChars =
"@{}();:\n\"'/*,";
666 $state = array(self::T_DOCUMENT);
668 $scope = $sDefaultScope;
671 $selectors = array();
674 $css = str_replace(
"\r\n",
"\n", $css);
675 $css = str_replace(
"\r",
"\n", $css);
676 while (strpos($css,
"\n\n") !==
false)
678 $css = str_replace(
"\n\n",
"\n", $css);
680 $css = str_replace(
"\t",
" ", $css);
682 for ($i = 0, $l = strlen($css); $i < $l; $i++)
684 $c = substr($css, $i, 1);
686 if ($isFilterWs && $c ==
" " && $c == $p)
691 if (strpos($sTokenChars, $c) !==
false)
694 $currentState = $state[count($state) - 1];
698 if ($p ==
"/" && $c ==
"*" && $currentState != self::T_STRING && $currentState != self::T_COMMENT)
700 $saveBuffer = substr($buffer, 0, -2);
703 array_push($state, self::T_COMMENT);
708 elseif ($p ==
"*" && $c ==
"/" && $currentState == self::T_COMMENT)
710 $r[] = array(self::T_COMMENT, trim($buffer));
711 $buffer = $saveBuffer;
718 elseif (($c ==
"\"" || $c ==
"'") && $currentState != self::T_STRING && $currentState != self::T_COMMENT && $currentState != self::T_STRING_URL)
722 array_push($state, self::T_STRING);
727 elseif ($c ==
"\n" && $p ==
"\\" && $currentState == self::T_STRING)
729 $buffer = substr($buffer, 0, -2);
734 elseif ($c === $stringChar && $currentState == self::T_STRING)
740 while (substr($css, $i2, 1) ==
"\\")
759 elseif ($c ==
"(" && ($currentState != self::T_COMMENT && $currentState != self::T_STRING) && strtolower(substr($css, $i - 3, 3) ==
"url")
760 && ($currentState == self::T_DECLARATION || $currentState == self::T_FONT_FACE_DECLARATION || $currentState == self::T_PAGE_DECLARATION || $currentState == self::T_VARIABLE_DECLARATION))
762 array_push($state, self::T_STRING_URL);
767 elseif (($c ==
")" || $c ==
"\n") && ($currentState != self::T_COMMENT && $currentState != self::T_STRING) && $currentState == self::T_STRING_URL)
778 elseif ($c ==
"@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 6)) ==
"@media")
782 array_push($state, self::T_AT_MEDIA_START);
787 elseif ($c ==
"{" && $currentState == self::T_AT_MEDIA_START)
789 $buffer = strtolower(trim($buffer, $sDefaultTrim .
"{"));
790 $scope = $buffer !=
"" ? array_filter(array_map(
"trim", explode(
",", $buffer))) : $sDefaultScope;
791 $r[] = array(self::T_AT_MEDIA_START, $scope);
795 array_push($state, self::T_AT_MEDIA);
800 elseif ($currentState == self::T_AT_MEDIA && $c ==
"}")
802 $r[] = array(self::T_AT_MEDIA_END);
803 $scope = $sDefaultScope;
810 elseif ($c ==
"@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 10)) ==
"@font-face")
812 $r[] = array(self::T_AT_FONT_FACE_START);
815 array_push($state, self::T_AT_FONT_FACE);
820 elseif ($c ==
":" && $currentState == self::T_AT_FONT_FACE)
822 $property = trim($buffer, $sDefaultTrim .
":{");
824 array_push($state, self::T_FONT_FACE_DECLARATION);
829 elseif (($c ==
";" || $c ==
"}" || $c ==
"\n") && $currentState == self::T_FONT_FACE_DECLARATION)
831 $value = trim($buffer, $sDefaultTrim .
";}");
832 $r[] = array(self::T_FONT_FACE_DECLARATION, $property, $value, $scope);
838 $r[] = array(self::T_AT_FONT_FACE_END);
844 elseif ($c ==
"}" && $currentState == self::T_AT_FONT_FACE)
846 $r[] = array(self::T_AT_FONT_FACE_END);
853 elseif ($c ==
"@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 5)) ==
"@page")
855 $r[] = array(self::T_AT_PAGE_START);
858 array_push($state, self::T_AT_PAGE);
863 elseif ($c ==
":" && $currentState == self::T_AT_PAGE)
865 $property = trim($buffer, $sDefaultTrim .
":{");
867 array_push($state, self::T_PAGE_DECLARATION);
872 elseif (($c ==
";" || $c ==
"}" || $c ==
"\n") && $currentState == self::T_PAGE_DECLARATION)
874 $value = trim($buffer, $sDefaultTrim .
";}");
875 $r[] = array(self::T_PAGE_DECLARATION, $property, $value, $scope);
881 $r[] = array(self::T_AT_PAGE_END);
887 elseif ($c ==
"}" && $currentState == self::T_AT_PAGE)
889 $r[] = array(self::T_AT_PAGE_END);
896 elseif ($c ==
"@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 10)) ==
"@variables")
900 array_push($state, self::T_AT_VARIABLES_START);
905 elseif ($c ==
"{" && $currentState == self::T_AT_VARIABLES_START)
907 $buffer = strtolower(trim($buffer, $sDefaultTrim .
"{"));
908 $r[] = array(self::T_AT_VARIABLES_START, $scope);
909 $scope = $buffer !=
"" ? array_filter(array_map(
"trim", explode(
",", $buffer))) : $sDefaultScope;
913 array_push($state, self::T_AT_VARIABLES);
918 elseif ($c ==
":" && $currentState == self::T_AT_VARIABLES)
920 $property = trim($buffer, $sDefaultTrim .
":");
922 array_push($state, self::T_VARIABLE_DECLARATION);
927 elseif (($c ==
";" || $c ==
"}" || $c ==
"\n") && $currentState == self::T_VARIABLE_DECLARATION)
929 $value = trim($buffer, $sDefaultTrim .
";}");
930 $r[] = array(self::T_VARIABLE_DECLARATION, $property, $value, $scope);
936 $r[] = array(self::T_AT_VARIABLES_END);
937 $scope = $sDefaultScope;
943 elseif ($c ==
"}" && $currentState == self::T_AT_VARIABLES)
945 $r[] = array(self::T_AT_VARIABLES_END);
946 $scope = $sDefaultScope;
953 elseif ($c ==
"@" && $currentState == self::T_DOCUMENT)
956 array_push($state, self::T_AT_RULE);
961 elseif ($c ==
";" && $currentState == self::T_AT_RULE)
963 $pos = strpos($buffer,
" ");
964 $rule = substr($buffer, 0, $pos);
965 $value = trim(substr($buffer, $pos), $sDefaultTrim .
";");
966 $r[] = array(self::T_AT_RULE, $rule, $value);
973 elseif ($c ==
"," && ($currentState == self::T_AT_MEDIA || $currentState == self::T_DOCUMENT))
975 $selectors[]= trim($buffer, $sDefaultTrim .
",");
981 elseif ($c ==
"{" && ($currentState == self::T_AT_MEDIA || $currentState == self::T_DOCUMENT))
983 $selectors[]= trim($buffer, $sDefaultTrim .
"{");
984 $selectors = array_filter(array_map(
"trim", $selectors));
985 $r[] = array(self::T_RULESET_START);
986 $r[] = array(self::T_SELECTORS, $selectors);
987 $r[] = array(self::T_DECLARATIONS_START);
989 $selectors = array();
990 array_push($state, self::T_DECLARATIONS);
995 elseif ($c ==
":" && $currentState == self::T_DECLARATIONS)
997 $property = trim($buffer, $sDefaultTrim .
":;");
999 array_push($state, self::T_DECLARATION);
1004 elseif (($c ==
";" || $c ==
"}" || $c ==
"\n") && $currentState == self::T_DECLARATION)
1006 $value = trim($buffer, $sDefaultTrim .
";}");
1007 $r[] = array(self::T_DECLARATION, $property, $value, $scope);
1013 $r[] = array(self::T_DECLARATIONS_END);
1014 $r[] = array(self::T_RULESET_END);
1020 elseif ($c ==
"}" && $currentState == self::T_DECLARATIONS)
1022 $r[] = array(self::T_DECLARATIONS_END);
1023 $r[] = array(self::T_RULESET_END);
1041 private static function _tOpacity($property, $value)
1043 $ieValue = (int) ((
float) $value * 100);
1044 $r =
"-moz-opacity:" . $value .
";";
1045 $r .=
"-ms-filter: \"alpha(opacity=" . $ieValue .
")\";";
1046 $r .=
"filter: alpha(opacity=" . $ieValue .
");zoom: 1;";
1056 private static function _tWhiteSpacePreWrap($property, $value)
1058 if (strtolower($value) ==
"pre-wrap")
1060 $r =
"white-space:-moz-pre-wrap;";
1061 $r .=
"white-space:-webkit-pre-wrap;";
1062 $r .=
"white-space:-khtml-pre-wrap;";
1063 $r .=
"white-space:-pre-wrap;";
1064 $r .=
"white-space:-o-pre-wrap;";
1065 $r .=
"word-wrap:break-word;";