diff options
Diffstat (limited to 'include')
-rw-r--r-- | include/cssmin.class.php | 868 | ||||
-rw-r--r-- | include/jsmin.class.php | 291 | ||||
-rw-r--r-- | include/template.class.php | 60 |
3 files changed, 1194 insertions, 25 deletions
diff --git a/include/cssmin.class.php b/include/cssmin.class.php new file mode 100644 index 000000000..6b38831e5 --- /dev/null +++ b/include/cssmin.class.php @@ -0,0 +1,868 @@ +<?php +/** + * CssMin - A (simple) css minifier with benefits + * + * -- + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * -- + * + * @package CssMin + * @author Joe Scylla <joe.scylla@gmail.com> + * @copyright 2008 - 2010 Joe Scylla <joe.scylla@gmail.com> + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 2.0.1.0064 (2010-09-30) + */ +class CssMin + { + /** + * State: Is in document + * + * @var integer + */ + const T_DOCUMENT = 1; + /** + * Token: Comment + * + * @var integer + */ + const T_COMMENT = 2; + /** + * Token: Generic at-rule + * + * @var integer + */ + const T_AT_RULE = 3; + /** + * Token: Start of @media block + * + * @var integer + */ + const T_AT_MEDIA_START = 4; + /** + * State: Is in @media block + * + * @var integer + */ + const T_AT_MEDIA = 5; + /** + * Token: End of @media block + * + * @var integer + */ + const T_AT_MEDIA_END = 6; + /** + * Token: Start of @font-face block + * + * @var integer + */ + const T_AT_FONT_FACE_START = 7; + /** + * State: Is in @font-face block + * + * @var integer + */ + const T_AT_FONT_FACE = 8; + /** + * Token: @font-face declaration + * + * @var integer + */ + const T_FONT_FACE_DECLARATION = 9; + /** + * Token: End of @font-face block + * + * @var integer + */ + const T_AT_FONT_FACE_END = 10; + /** + * Token: Start of @page block + * + * @var integer + */ + const T_AT_PAGE_START = 11; + /** + * State: Is in @page block + * + * @var integer + */ + const T_AT_PAGE = 12; + /** + * Token: @page declaration + * + * @var integer + */ + const T_PAGE_DECLARATION = 13; + /** + * Token: End of @page block + * + * @var integer + */ + const T_AT_PAGE_END = 14; + /** + * Token: Start of ruleset + * + * @var integer + */ + const T_RULESET_START = 15; + /** + * Token: Ruleset selectors + * + * @var integer + */ + const T_SELECTORS = 16; + /** + * Token: Start of declarations + * + * @var integer + */ + const T_DECLARATIONS_START = 17; + /** + * State: Is in declarations + * + * @var integer + */ + const T_DECLARATIONS = 18; + /** + * Token: Declaration + * + * @var integer + */ + const T_DECLARATION = 19; + /** + * Token: End of declarations + * + * @var integer + */ + const T_DECLARATIONS_END = 20; + /** + * Token: End of ruleset + * + * @var integer + */ + const T_RULESET_END = 21; + /** + * Token: Start of @variables block + * + * @var integer + */ + const T_AT_VARIABLES_START = 100; + /** + * State: Is in @variables block + * + * @var integer + */ + const T_AT_VARIABLES = 101; + /** + * Token: @variables declaration + * + * @var integer + */ + const T_VARIABLE_DECLARATION = 102; + /** + * Token: End of @variables block + * + * @var integer + */ + const T_AT_VARIABLES_END = 103; + /** + * State: Is in string + * + * @var integer + */ + const T_STRING = 254; + /** + * State: Is in url string property + * + * @var integer + */ + const T_STRING_URL = 255; + /** + * Css transformations table + * + * @var array + */ + private static $transformations = array + ( + "border-radius" => array("-moz-border-radius", "-webkit-border-radius", "-khtml-border-radius"), + "border-top-left-radius" => array("-moz-border-radius-topleft", "-webkit-border-top-left-radius", "-khtml-top-left-radius"), + "border-top-right-radius" => array("-moz-border-radius-topright", "-webkit-border-top-right-radius", "-khtml-top-right-radius"), + "border-bottom-right-radius" => array("-moz-border-radius-bottomright", "-webkit-border-bottom-right-radius", "-khtml-border-bottom-right-radius"), + "border-bottom-left-radius" => array("-moz-border-radius-bottomleft", "-webkit-border-bottom-left-radius", "-khtml-border-bottom-left-radius"), + "box-shadow" => array("-moz-box-shadow", "-webkit-box-shadow", "-khtml-box-shadow"), + "opacity" => array(array("CssMin", "_tOpacity")), + "text-shadow" => array("-moz-text-shadow", "-webkit-text-shadow", "-khtml-text-shadow"), + "white-space" => array(array("CssMin", "_tWhiteSpacePreWrap")) + ); + /** + * Minifies the Css. + * + * @param string $css + * @param array $config [optional] + * @return string + */ + public static function minify($css, $config = array()) + { + $tokens = self::parse($css); + $config = array_merge(array + ( + "remove-empty-blocks" => true, + "remove-empty-rulesets" => true, + "remove-last-semicolons" => true, + "convert-css3-properties" => false, + "convert-color-values" => false, + "compress-color-values" => false, + "compress-unit-values" => false, + "emulate-css3-variables" => true, + ), $config); + // Minification options + $sRemoveEmptyBlocks = $config["remove-empty-blocks"]; + $sRemoveEmptyRulesets = $config["remove-empty-rulesets"]; + $sRemoveLastSemicolon = $config["remove-last-semicolons"]; + $sConvertCss3Properties = $config["convert-css3-properties"]; + $sCompressUnitValues = $config["compress-unit-values"]; + $sConvertColorValues = $config["convert-color-values"]; + $sCompressColorValues = $config["compress-color-values"]; + $sEmulateCcss3Variables = $config["emulate-css3-variables"]; + $sRemoveTokens = array(self::T_COMMENT); + // Remove tokens + if (!$sEmulateCcss3Variables) + { + $sRemoveTokens = array_merge($sRemoveTokens, array(self::T_AT_VARIABLES_START, self::T_VARIABLE_DECLARATION, self::T_AT_VARIABLES_END)); + } + for($i = 0, $l = count($tokens); $i < $l; $i++) + { + if (in_array($tokens[$i][0], $sRemoveTokens)) + { + unset($tokens[$i]); + } + } + $tokens = array_values($tokens); + // Remove empty rulesets + if ($sRemoveEmptyRulesets) + { + for($i = 0, $l = count($tokens); $i < $l; $i++) + { + // Remove empty rulesets + if ($tokens[$i][0] == self::T_RULESET_START && $tokens[$i+4][0] == self::T_RULESET_END) + { + unset($tokens[$i]); // T_RULESET_START + unset($tokens[++$i]); // T_SELECTORS + unset($tokens[++$i]); // T_DECLARATIONS_START + unset($tokens[++$i]); // T_DECLARATIONS_END + unset($tokens[++$i]); // T_RULESET_END + } + } + $tokens = array_values($tokens); + } + // Remove empty @media, @font-face or @page blocks + if ($sRemoveEmptyBlocks) + { + for($i = 0, $l = count($tokens); $i < $l; $i++) + { + // Remove empty @media, @font-face or @page blocks + if (($tokens[$i][0] == self::T_AT_MEDIA_START && $tokens[$i+1][0] == self::T_AT_MEDIA_END) + || ($tokens[$i][0] == self::T_AT_FONT_FACE_START && $tokens[$i+1][0] == self::T_AT_FONT_FACE_END) + || ($tokens[$i][0] == self::T_AT_PAGE_START && $tokens[$i+1][0] == self::T_AT_PAGE_END)) + { + unset($tokens[$i]); // T_AT_MEDIA_START, T_AT_FONT_FACE_START, T_AT_PAGE_START + unset($tokens[++$i]); // T_AT_MEDIA_END, T_AT_FONT_FACE_END, T_AT_PAGE_END + } + } + $tokens = array_values($tokens); + } + // CSS Level 3 variables: parse variables + if ($sEmulateCcss3Variables) + { + // Parse variables + $variables = array(); + for($i = 0, $l = count($tokens); $i < $l; $i++) + { + if ($tokens[$i][0] == self::T_VARIABLE_DECLARATION) + { + for($i2 = 0, $l2 = count($tokens[$i][3]); $i2 < $l2; $i2++) + { + if (!isset($variables[$tokens[$i][3][$i2]])) + { + $variables[$tokens[$i][3][$i2]] = array(); + } + $variables[$tokens[$i][3][$i2]][$tokens[$i][1]] = $tokens[$i][2]; + } + } + } + } + // Conversion and compression + for($i = 0, $l = count($tokens); $i < $l; $i++) + { + if ($tokens[$i][0] == self::T_DECLARATION) + { + // CSS Level 3 variables + if ($sEmulateCcss3Variables) + { + if (substr($tokens[$i][2], 0, 4) == "var(" && substr($tokens[$i][2], -1, 1) == ")") + { + $tokens[$i][3][] = "all"; + $variable = trim(substr($tokens[$i][2], 4, -1)); + for($i2 = 0, $l2 = count($tokens[$i][3]); $i2 < $l2; $i2++) + { + if (isset($variables[$tokens[$i][3][$i2]][$variable])) + { + $tokens[$i][2] = $variables[$tokens[$i][3][$i2]][$variable]; + break; + } + } + } + } + // Compress unit values + if ($sCompressUnitValues) + { + // Compress "0.5px" to ".5px" + $tokens[$i][2] = preg_replace("/(^| |-)0\.([0-9]+)(%|em|ex|px|in|cm|mm|pt|pc)/iS", "\${1}.\${2}\${3}", $tokens[$i][2]); + // Compress "0px" to "0" + $tokens[$i][2] = preg_replace("/(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/iS", "\${1}0", $tokens[$i][2]); + // Compress "0 0 0 0" to "0" + if ($tokens[$i][2] == "0 0 0 0") {$tokens[$i][2] = "0";} + } + // Convert RGB color values to hex ("rgb(200,60%,5)" => "#c89905") + if ($sConvertColorValues && preg_match("/rgb\s*\(\s*([0-9%]+)\s*,\s*([0-9%]+)\s*,\s*([0-9%]+)\s*\)/iS", $tokens[$i][2], $m)) + { + for ($i2 = 1, $l2 = count($m); $i2 < $l2; $i2++) + { + if (strpos("%", $m[$i2]) !== false) + { + $m[$i2] = substr($m[$i2], 0, -1); + $m[$i2] = (int) (256 * ($m[$i2] / 100)); + } + $m[$i2] = str_pad(dechex($m[$i2]), 2, "0", STR_PAD_LEFT); + } + $tokens[$i][2] = str_replace($m[0], "#" . $m[1] . $m[2] . $m[3], $tokens[$i][2]); + } + // Compress color values ("#aabbcc" to "#abc") + if ($sCompressColorValues && preg_match("/\#([0-9a-f]{6})/iS", $tokens[$i][2], $m)) + { + $m[1] = strtolower($m[1]); + 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)) + { + $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]); + } + } + } + } + // Create minified css + $r = ""; + for($i = 0, $l = count($tokens); $i < $l; $i++) + { + // T_AT_RULE + if ($tokens[$i][0] == self::T_AT_RULE) + { + $r .= "@" . $tokens[$i][1] . " " . $tokens[$i][2] . ";"; + } + // T_AT_MEDIA_START + elseif ($tokens[$i][0] == self::T_AT_MEDIA_START) + { + if (count($tokens[$i][1]) == 1 && $tokens[$i][1][0] == "all") + { + $r .= "@media{"; + } + else + { + $r .= "@media " . implode(",", $tokens[$i][1]) . "{"; + } + } + // T_AT_FONT_FACE_START + elseif ($tokens[$i][0] == self::T_AT_FONT_FACE_START) + { + $r .= "@font-face{"; + } + // T_FONT_FACE_DECLARATION + elseif ($tokens[$i][0] == self::T_FONT_FACE_DECLARATION) + { + $r .= $tokens[$i][1] . ":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_AT_FONT_FACE_END ? "" : ";"); + } + // T_AT_PAGE_START + elseif ($tokens[$i][0] == self::T_AT_PAGE_START) + { + $r .= "@page{"; + } + // T_PAGE_DECLARATION + elseif ($tokens[$i][0] == self::T_PAGE_DECLARATION) + { + $r .= $tokens[$i][1] . ":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_AT_PAGE_END ? "" : ";"); + } + // T_SELECTORS + elseif ($tokens[$i][0] == self::T_SELECTORS) + { + $r .= implode(",", $tokens[$i][1]); + } + // Start of declarations + elseif ($tokens[$i][0] == self::T_DECLARATIONS_START) + { + $r .= "{"; + } + // T_DECLARATION + elseif ($tokens[$i][0] == self::T_DECLARATION) + { + if ($sConvertCss3Properties && isset(self::$transformations[$tokens[$i][1]])) + { + foreach (self::$transformations[$tokens[$i][1]] as $value) + { + if (!is_array($value)) + { + $r .= $value . ":" . $tokens[$i][2] . ";"; + } + elseif (is_array($value) && is_callable($value)) + { + $r.= call_user_func_array($value, array($tokens[$i][1], $tokens[$i][2])); + + } + } + } + $r .= $tokens[$i][1] . ":" . $tokens[$i][2] . ($sRemoveLastSemicolon && $tokens[$i+1][0] == self::T_DECLARATIONS_END ? "" : ";"); + } + // T_DECLARATIONS_END, T_AT_MEDIA_END, T_AT_FONT_FACE_END, T_AT_PAGE_END + 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))) + { + $r .= "}"; + } + else + { + // Tokens with no output: + // T_COMMENT + // T_RULESET_START + // T_RULESET_END + // T_AT_VARIABLES_START + // T_VARIABLE_DECLARATION + // T_AT_VARIABLES_END + } + } + return $r; + } + /** + * Parses the Css and returns a array of tokens. + * + * @param string $css + * @return array + */ + public static function parse($css) + { + // Settings + $sDefaultScope = array("all"); // Default scope + $sDefaultTrim = " \t\n\r\0\x0B"; // Default trim charlist + $sTokenChars = "@{}();:\n\"'/*,"; // Tokens triggering parser processing + // Basic variables + $c = null; // Current char + $p = null; // Previous char + $buffer = ""; // Buffer + $state = array(self::T_DOCUMENT); // State stack + $currentState = self::T_DOCUMENT; // Current state + $scope = $sDefaultScope; // Current scope + $stringChar = null; // String delimiter char + $isFilterWs = true; // Filter double whitespaces? + $selectors = array(); // Array with collected selectors + $r = array(); // Return value + // Prepare css + $css = str_replace("\r\n", "\n", $css); // Windows to Unix line endings + $css = str_replace("\r", "\n", $css); // Mac to Unix line endings + while (strpos($css, "\n\n") !== false) + { + $css = str_replace("\n\n", "\n", $css); // Remove double line endings + } + $css = str_replace("\t", " ", $css); // Convert tabs to spaces + // Parse css + for ($i = 0, $l = strlen($css); $i < $l; $i++) + { + $c = substr($css, $i, 1); + // Filter out double spaces + if ($isFilterWs && $c == " " && $c == $p) + { + continue; + } + $buffer .= $c; + if (strpos($sTokenChars, $c) !== false) + { + // + $currentState = $state[count($state) - 1]; + /* + * Start of comment + */ + if ($p == "/" && $c == "*" && $currentState != self::T_STRING && $currentState != self::T_COMMENT) + { + $saveBuffer = substr($buffer, 0, -2); // save the buffer (will get restored with comment ending) + $buffer = $c; + $isFilterWs = false; + array_push($state, self::T_COMMENT); + } + /* + * End of comment + */ + elseif ($p == "*" && $c == "/" && $currentState == self::T_COMMENT) + { + $r[] = array(self::T_COMMENT, trim($buffer)); + $buffer = $saveBuffer; + $isFilterWs = true; + array_pop($state); + } + /* + * Start of string + */ + elseif (($c == "\"" || $c == "'") && $currentState != self::T_STRING && $currentState != self::T_COMMENT && $currentState != self::T_STRING_URL) + { + $stringChar = $c; + $isFilterWs = false; + array_push($state, self::T_STRING); + } + /** + * Escaped LF in string => remove escape backslash and LF + */ + elseif ($c == "\n" && $p == "\\" && $currentState == self::T_STRING) + { + $buffer = substr($buffer, 0, -2); + } + /* + * End of string + */ + elseif ($c === $stringChar && $currentState == self::T_STRING) + { + if ($p == "\\") // Previous char is a escape char + { + $count = 1; + $i2 = $i -2; + while (substr($css, $i2, 1) == "\\") + { + $count++; + $i2--; + } + // if count of escape chars is uneven => continue with string... + if ($count % 2) + { + continue; + } + } + // ...else end the string + $isFilterWs = true; + array_pop($state); + $stringChar = null; + } + /** + * Start of url string property + */ + elseif ($c == "(" && ($currentState != self::T_COMMENT && $currentState != self::T_STRING) && strtolower(substr($css, $i - 3, 3) == "url") + && ($currentState == self::T_DECLARATION || $currentState == self::T_FONT_FACE_DECLARATION || $currentState == self::T_PAGE_DECLARATION || $currentState == self::T_VARIABLE_DECLARATION)) + { + array_push($state, self::T_STRING_URL); + } + /** + * End of url string property + */ + elseif (($c == ")" || $c == "\n") && ($currentState != self::T_COMMENT && $currentState != self::T_STRING) && $currentState == self::T_STRING_URL) + { + if ($p == "\\") + { + continue; + } + array_pop($state); + } + /* + * Start of at-rule @media block + */ + elseif ($c == "@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 6)) == "@media") + { + $i = $i + 6; + $buffer = ""; + array_push($state, self::T_AT_MEDIA_START); + } + /* + * At-rule @media block media types + */ + elseif ($c == "{" && $currentState == self::T_AT_MEDIA_START) + { + $buffer = strtolower(trim($buffer, $sDefaultTrim . "{")); + $scope = $buffer != "" ? array_filter(array_map("trim", explode(",", $buffer))) : $sDefaultScope; + $r[] = array(self::T_AT_MEDIA_START, $scope); + $i = $i++; + $buffer = ""; + array_pop($state); + array_push($state, self::T_AT_MEDIA); + } + /* + * End of at-rule @media block + */ + elseif ($currentState == self::T_AT_MEDIA && $c == "}") + { + $r[] = array(self::T_AT_MEDIA_END); + $scope = $sDefaultScope; + $buffer = ""; + array_pop($state); + } + /* + * Start of at-rule @font-face block + */ + elseif ($c == "@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 10)) == "@font-face") + { + $r[] = array(self::T_AT_FONT_FACE_START); + $i = $i + 10; + $buffer = ""; + array_push($state, self::T_AT_FONT_FACE); + } + /* + * @font-face declaration: Property + */ + elseif ($c == ":" && $currentState == self::T_AT_FONT_FACE) + { + $property = trim($buffer, $sDefaultTrim . ":{"); + $buffer = ""; + array_push($state, self::T_FONT_FACE_DECLARATION); + } + /* + * @font-face declaration: Value + */ + elseif (($c == ";" || $c == "}" || $c == "\n") && $currentState == self::T_FONT_FACE_DECLARATION) + { + $value = trim($buffer, $sDefaultTrim . ";}"); + $r[] = array(self::T_FONT_FACE_DECLARATION, $property, $value, $scope); + $buffer = ""; + array_pop($state); + if ($c == "}") // @font-face declaration closed with a right curly brace => closes @font-face block + { + array_pop($state); + $r[] = array(self::T_AT_FONT_FACE_END); + } + } + /* + * End of at-rule @font-face block + */ + elseif ($c == "}" && $currentState == self::T_AT_FONT_FACE) + { + $r[] = array(self::T_AT_FONT_FACE_END); + $buffer = ""; + array_pop($state); + } + /* + * Start of at-rule @page block + */ + elseif ($c == "@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 5)) == "@page") + { + $r[] = array(self::T_AT_PAGE_START); + $i = $i + 5; + $buffer = ""; + array_push($state, self::T_AT_PAGE); + } + /* + * @page declaration: Property + */ + elseif ($c == ":" && $currentState == self::T_AT_PAGE) + { + $property = trim($buffer, $sDefaultTrim . ":{"); + $buffer = ""; + array_push($state, self::T_PAGE_DECLARATION); + } + /* + * @page declaration: Value + */ + elseif (($c == ";" || $c == "}" || $c == "\n") && $currentState == self::T_PAGE_DECLARATION) + { + $value = trim($buffer, $sDefaultTrim . ";}"); + $r[] = array(self::T_PAGE_DECLARATION, $property, $value, $scope); + $buffer = ""; + array_pop($state); + if ($c == "}") // @page declaration closed with a right curly brace => closes @font-face block + { + array_pop($state); + $r[] = array(self::T_AT_PAGE_END); + } + } + /* + * End of at-rule @page block + */ + elseif ($c == "}" && $currentState == self::T_AT_PAGE) + { + $r[] = array(self::T_AT_PAGE_END); + $buffer = ""; + array_pop($state); + } + /* + * Start of at-rule @variables block + */ + elseif ($c == "@" && $currentState == self::T_DOCUMENT && strtolower(substr($css, $i, 10)) == "@variables") + { + $i = $i + 10; + $buffer = ""; + array_push($state, self::T_AT_VARIABLES_START); + } + /* + * @variables media types + */ + elseif ($c == "{" && $currentState == self::T_AT_VARIABLES_START) + { + $buffer = strtolower(trim($buffer, $sDefaultTrim . "{")); + $r[] = array(self::T_AT_VARIABLES_START, $scope); + $scope = $buffer != "" ? array_filter(array_map("trim", explode(",", $buffer))) : $sDefaultScope; + $i = $i++; + $buffer = ""; + array_pop($state); + array_push($state, self::T_AT_VARIABLES); + } + /* + * @variables declaration: Property + */ + elseif ($c == ":" && $currentState == self::T_AT_VARIABLES) + { + $property = trim($buffer, $sDefaultTrim . ":"); + $buffer = ""; + array_push($state, self::T_VARIABLE_DECLARATION); + } + /* + * @variables declaration: Value + */ + elseif (($c == ";" || $c == "}" || $c == "\n") && $currentState == self::T_VARIABLE_DECLARATION) + { + $value = trim($buffer, $sDefaultTrim . ";}"); + $r[] = array(self::T_VARIABLE_DECLARATION, $property, $value, $scope); + $buffer = ""; + array_pop($state); + if ($c == "}") // @variable declaration closed with a right curly brace => closes @variables block + { + array_pop($state); + $r[] = array(self::T_AT_VARIABLES_END); + $scope = $sDefaultScope; + } + } + /* + * End of at-rule @variables block + */ + elseif ($c == "}" && $currentState == self::T_AT_VARIABLES) + { + $r[] = array(self::T_AT_VARIABLES_END); + $scope = $sDefaultScope; + $buffer = ""; + array_pop($state); + } + /* + * Start of document level at-rule + */ + elseif ($c == "@" && $currentState == self::T_DOCUMENT) + { + $buffer = ""; + array_push($state, self::T_AT_RULE); + } + /* + * End of document level at-rule + */ + elseif ($c == ";" && $currentState == self::T_AT_RULE) + { + $pos = strpos($buffer, " "); + $rule = substr($buffer, 0, $pos); + $value = trim(substr($buffer, $pos), $sDefaultTrim . ";"); + $r[] = array(self::T_AT_RULE, $rule, $value); + $buffer = ""; + array_pop($state); + } + /** + * Selector + */ + elseif ($c == "," && ($currentState == self::T_AT_MEDIA || $currentState == self::T_DOCUMENT)) + { + $selectors[]= trim($buffer, $sDefaultTrim . ","); + $buffer = ""; + } + /* + * Start of ruleset + */ + elseif ($c == "{" && ($currentState == self::T_AT_MEDIA || $currentState == self::T_DOCUMENT)) + { + $selectors[]= trim($buffer, $sDefaultTrim . "{"); + $selectors = array_filter(array_map("trim", $selectors)); + $r[] = array(self::T_RULESET_START); + $r[] = array(self::T_SELECTORS, $selectors); + $r[] = array(self::T_DECLARATIONS_START); + $buffer = ""; + $selectors = array(); + array_push($state, self::T_DECLARATIONS); + } + /* + * Declaration: Property + */ + elseif ($c == ":" && $currentState == self::T_DECLARATIONS) + { + $property = trim($buffer, $sDefaultTrim . ":;"); + $buffer = ""; + array_push($state, self::T_DECLARATION); + } + /* + * Declaration: Value + */ + elseif (($c == ";" || $c == "}" || $c == "\n") && $currentState == self::T_DECLARATION) + { + $value = trim($buffer, $sDefaultTrim . ";}"); + $r[] = array(self::T_DECLARATION, $property, $value, $scope); + $buffer = ""; + array_pop($state); + if ($c == "}") // declaration closed with a right curly brace => close ruleset + { + array_pop($state); + $r[] = array(self::T_DECLARATIONS_END); + $r[] = array(self::T_RULESET_END); + } + } + /* + * End of ruleset + */ + elseif ($c == "}" && $currentState == self::T_DECLARATIONS) + { + $r[] = array(self::T_DECLARATIONS_END); + $r[] = array(self::T_RULESET_END); + $buffer = ""; + array_pop($state); + } + + } + + $p = $c; + } + return $r; + } + /** + * Transforms "opacity: {value}" into browser specific counterparts. + * + * @param string $property + * @param string $value + * @return string + */ + private static function _tOpacity($property, $value) + { + $ieValue = (int) ((float) $value * 100); + $r = "-moz-opacity:" . $value . ";"; // Firefox < 3.5 + $r .= "-ms-filter: \"alpha(opacity=" . $ieValue . ")\";"; // Internet Explorer 8 + $r .= "filter: alpha(opacity=" . $ieValue . ");zoom: 1;"; // Internet Explorer 4 - 7 + return $r; + } + /** + * Transforms "white-space: pre-wrap" into browser specific counterparts. + * + * @param string $property + * @param string $value + * @return string + */ + private static function _tWhiteSpacePreWrap($property, $value) + { + if (strtolower($value) == "pre-wrap") + { + $r = "white-space:-moz-pre-wrap;"; // Mozilla + $r .= "white-space:-webkit-pre-wrap;"; // Webkit + $r .= "white-space:-khtml-pre-wrap;"; // khtml + $r .= "white-space:-pre-wrap;"; // Opera 4 - 6 + $r .= "white-space:-o-pre-wrap;"; // Opera 7+ + $r .= "word-wrap:break-word;"; // Internet Explorer 5.5+ + return $r; + } + else + { + return ""; + } + } + } +?>
\ No newline at end of file diff --git a/include/jsmin.class.php b/include/jsmin.class.php new file mode 100644 index 000000000..3c2f859df --- /dev/null +++ b/include/jsmin.class.php @@ -0,0 +1,291 @@ +<?php +/** + * jsmin.php - PHP implementation of Douglas Crockford's JSMin. + * + * This is pretty much a direct port of jsmin.c to PHP with just a few + * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and + * outputs to stdout, this library accepts a string as input and returns another + * string as output. + * + * PHP 5 or higher is required. + * + * Permission is hereby granted to use this version of the library under the + * same terms as jsmin.c, which has the following license: + * + * -- + * Copyright (c) 2002 Douglas Crockford (www.crockford.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * -- + * + * @package JSMin + * @author Ryan Grove <ryan@wonko.com> + * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c) + * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port) + * @license http://opensource.org/licenses/mit-license.php MIT License + * @version 1.1.1 (2008-03-02) + * @link http://code.google.com/p/jsmin-php/ + */ + +class JSMin { + const ORD_LF = 10; + const ORD_SPACE = 32; + + protected $a = ''; + protected $b = ''; + protected $input = ''; + protected $inputIndex = 0; + protected $inputLength = 0; + protected $lookAhead = null; + protected $output = ''; + + // -- Public Static Methods -------------------------------------------------- + + public static function minify($js) { + $jsmin = new JSMin($js); + return $jsmin->min(); + } + + // -- Public Instance Methods ------------------------------------------------ + + public function __construct($input) { + $this->input = str_replace("\r\n", "\n", $input); + $this->inputLength = strlen($this->input); + } + + // -- Protected Instance Methods --------------------------------------------- + + protected function action($d) { + switch($d) { + case 1: + $this->output .= $this->a; + + case 2: + $this->a = $this->b; + + if ($this->a === "'" || $this->a === '"') { + for (;;) { + $this->output .= $this->a; + $this->a = $this->get(); + + if ($this->a === $this->b) { + break; + } + + if (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated string literal.'); + } + + if ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + } + } + } + + case 3: + $this->b = $this->next(); + + if ($this->b === '/' && ( + $this->a === '(' || $this->a === ',' || $this->a === '=' || + $this->a === ':' || $this->a === '[' || $this->a === '!' || + $this->a === '&' || $this->a === '|' || $this->a === '?')) { + + $this->output .= $this->a . $this->b; + + for (;;) { + $this->a = $this->get(); + + if ($this->a === '/') { + break; + } elseif ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + } elseif (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated regular expression '. + 'literal.'); + } + + $this->output .= $this->a; + } + + $this->b = $this->next(); + } + } + } + + protected function get() { + $c = $this->lookAhead; + $this->lookAhead = null; + + if ($c === null) { + if ($this->inputIndex < $this->inputLength) { + $c = substr($this->input, $this->inputIndex, 1); + $this->inputIndex += 1; + } else { + $c = null; + } + } + + if ($c === "\r") { + return "\n"; + } + + if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) { + return $c; + } + + return ' '; + } + + protected function isAlphaNum($c) { + return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; + } + + protected function min() { + $this->a = "\n"; + $this->action(3); + + while ($this->a !== null) { + switch ($this->a) { + case ' ': + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } else { + $this->action(2); + } + break; + + case "\n": + switch ($this->b) { + case '{': + case '[': + case '(': + case '+': + case '-': + $this->action(1); + break; + + case ' ': + $this->action(3); + break; + + default: + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } + else { + $this->action(2); + } + } + break; + + default: + switch ($this->b) { + case ' ': + if ($this->isAlphaNum($this->a)) { + $this->action(1); + break; + } + + $this->action(3); + break; + + case "\n": + switch ($this->a) { + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case "'": + $this->action(1); + break; + + default: + if ($this->isAlphaNum($this->a)) { + $this->action(1); + } + else { + $this->action(3); + } + } + break; + + default: + $this->action(1); + break; + } + } + } + + return $this->output; + } + + protected function next() { + $c = $this->get(); + + if ($c === '/') { + switch($this->peek()) { + case '/': + for (;;) { + $c = $this->get(); + + if (ord($c) <= self::ORD_LF) { + return $c; + } + } + + case '*': + $this->get(); + + for (;;) { + switch($this->get()) { + case '*': + if ($this->peek() === '/') { + $this->get(); + return ' '; + } + break; + + case null: + throw new JSMinException('Unterminated comment.'); + } + } + + default: + return $c; + } + } + + return $c; + } + + protected function peek() { + $this->lookAhead = $this->get(); + return $this->lookAhead; + } +} + +// -- Exceptions --------------------------------------------------------------- +class JSMinException extends Exception {} +?>
\ No newline at end of file diff --git a/include/template.class.php b/include/template.class.php index 666f913b7..529601a6a 100644 --- a/include/template.class.php +++ b/include/template.class.php @@ -398,7 +398,7 @@ class Template { { $content[]= '<script type="text/javascript" src="' - . Template::make_script_src($script) + . self::make_script_src($script) .'"></script>'; } @@ -995,28 +995,28 @@ class ScriptLoader return array( self::do_combine($result[0],1), self::do_combine($result[1],2) ); } - private static function do_combine($scripts, $load_mode) - { - global $conf; - if (count($scripts)<2 or !$conf['template_combine_files']) - return $scripts; - $combiner = new FileCombiner('js'); - foreach ($scripts as $script) - { - if ($script->is_remote()) fatal_error("NOT IMPLEMENTED");// TODO - we cannot combine remote scripts - $combiner->add( $script->path, $script->version ); - } - if ( $combiner->combine( $out_file, $out_version) ) - { - return array( 'combi' => new Script($load_mode, 'combi', $out_file, $out_version, array() ) ); - } - return null; - } - - // checks that if B depends on A, then B->load_mode >= A->load_mode in order to respect execution order + private static function do_combine($scripts, $load_mode) + { + global $conf; + if (count($scripts)<2 or !$conf['template_combine_files']) + return $scripts; + $combiner = new FileCombiner('js'); + foreach ($scripts as $script) + { + if ($script->is_remote()) fatal_error("NOT IMPLEMENTED");// TODO - we cannot combine remote scripts + $combiner->add( $script->path, $script->version ); + } + if ( $combiner->combine( $out_file, $out_version) ) + { + return array( 'combi' => new Script($load_mode, 'combi', $out_file, $out_version, array() ) ); + } + return null; + } + + // checks that if B depends on A, then B->load_mode >= A->load_mode in order to respect execution order private static function check_load_dep($scripts) { - global $conf; + global $conf; do { $changed = false; @@ -1061,13 +1061,14 @@ class ScriptLoader } } - private function compute_script_topological_order($script_id) + private function compute_script_topological_order($script_id, $recursion_limiter=0) { if (!isset($this->registered_scripts[$script_id])) { trigger_error("Undefined script $script_id is required by someone", E_USER_WARNING); return 0; } + $recursion_limiter<5 or fatal_error("combined script circular dependency"); $script = & $this->registered_scripts[$script_id]; if (isset($script->extra['order'])) return $script->extra['order']; @@ -1075,7 +1076,7 @@ class ScriptLoader return ($script->extra['order'] = 0); $max = 0; foreach( $script->precedents as $precedent) - $max = max($max, $this->compute_script_topological_order($precedent) ); + $max = max($max, $this->compute_script_topological_order($precedent, $recursion_limiter+1) ); $max++; return ($script->extra['order'] = $max); } @@ -1208,13 +1209,22 @@ final class FileCombiner $js = file_get_contents(PHPWG_ROOT_PATH . $file); if (strpos($file, '.min')===false and strpos($file, '.packed')===false ) { - //TODO minify javascript with some php lib from www... + require_once(PHPWG_ROOT_PATH.'include/jsmin.class.php'); + try { $js = JSMin::minify($js); } catch(Exception $e) {} } return $js; } private static function process_css($file) { + $css = self::process_css_rec($file); + require_once(PHPWG_ROOT_PATH.'include/cssmin.class.php'); + $css = CssMin::minify($css, array('emulate-css3-variables'=>false)); + return $css; + } + + private static function process_css_rec($file) + { static $PATTERN = "#url\(\s*['|\"]{0,1}(.*?)['|\"]{0,1}\s*\)#"; $css = file_get_contents(PHPWG_ROOT_PATH . $file); if (preg_match_all($PATTERN, $css, $matches, PREG_SET_ORDER)) @@ -1239,7 +1249,7 @@ final class FileCombiner foreach ($matches as $match) { $search[] = $match[0]; - $replace[] = self::process_css(dirname($file) . "/$match[1]"); + $replace[] = self::process_css_rec(dirname($file) . "/$match[1]"); } $css = str_replace($search, $replace, $css); } |