From: Trevor Parscal Date: Thu, 9 Sep 2010 19:57:11 +0000 (+0000) Subject: Moved stand-alone libraries to includes/libs. X-Git-Tag: 1.31.0-rc.0~35075 X-Git-Url: https://git.cyclocoop.org/%27.WWW_URL.%27admin/?a=commitdiff_plain;h=c5aa8355028a4dcb1613de57a16cbcc850cc053b;p=lhc%2Fweb%2Fwiklou.git Moved stand-alone libraries to includes/libs. --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index a37bf26765..a994880307 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -52,8 +52,8 @@ $wgAutoloadLocalClasses = array( 'ConstantDependency' => 'includes/CacheDependency.php', 'CreativeCommonsRdf' => 'includes/Metadata.php', 'Credits' => 'includes/Credits.php', - 'CSSJanus' => 'includes/CSSJanus.php', - 'CSSMin' => 'includes/CSSMin.php', + 'CSSJanus' => 'includes/libs/CSSJanus.php', + 'CSSMin' => 'includes/libs/CSSMin.php', 'DBABagOStuff' => 'includes/BagOStuff.php', 'DependencyWrapper' => 'includes/CacheDependency.php', 'DiffHistoryBlob' => 'includes/HistoryBlob.php', @@ -135,7 +135,7 @@ $wgAutoloadLocalClasses = array( 'IndexPager' => 'includes/Pager.php', 'Interwiki' => 'includes/Interwiki.php', 'IP' => 'includes/IP.php', - 'JSMin' => 'includes/JSMin.php', + 'JSMin' => 'includes/libs/JSMin.php', 'LCStore_DB' => 'includes/LocalisationCache.php', 'LCStore_CDB' => 'includes/LocalisationCache.php', 'LCStore_Null' => 'includes/LocalisationCache.php', diff --git a/includes/CSSJanus.php b/includes/CSSJanus.php deleted file mode 100644 index 08e8e0963d..0000000000 --- a/includes/CSSJanus.php +++ /dev/null @@ -1,321 +0,0 @@ - '`TMP`', - 'nonAscii' => '[\200-\377]', - 'unicode' => '(?:(?:\\[0-9a-f]{1,6})(?:\r\n|\s)?)', - 'num' => '(?:[0-9]*\.[0-9]+|[0-9]+)', - 'unit' => '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)', - 'body_selector' => 'body\s*{\s*', - 'direction' => 'direction\s*:\s*', - 'escape' => null, - 'nmstart' => null, - 'nmchar' => null, - 'ident' => null, - 'quantity' => null, - 'possibly_negative_quantity' => null, - 'color' => null, - 'url_special_chars' => '[!#$%&*-~]', - 'valid_after_uri_chars' => '[\'\"]?\s*', - 'url_chars' => null, - 'lookahead_not_open_brace' => null, - 'lookahead_not_closing_paren' => null, - 'lookahead_for_closing_paren' => null, - 'lookbehind_not_letter' => '(? '[^\}]*?', - 'noflip_annotation' => '\/\*\s*@noflip\s*\*\/', - 'noflip_single' => null, - 'noflip_class' => null, - 'comment' => '/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//', - 'body_direction_ltr' => null, - 'body_direction_rtl' => null, - 'left' => null, - 'right' => null, - 'left_in_url' => null, - 'right_in_url' => null, - 'ltr_in_url' => null, - 'rtl_in_url' => null, - 'cursor_east' => null, - 'cursor_west' => null, - 'four_notation_quantity' => null, - 'four_notation_color' => null, - 'bg_horizontal_percentage' => null, - 'bg_horizontal_percentage_x' => null, - ); - - /** - * Build patterns we can't define above because they depend on other patterns. - */ - private static function buildPatterns() { - if ( !is_null( self::$patterns['escape'] ) ) { - // Patterns have already been built - return; - } - - $patterns =& self::$patterns; - $patterns['escape'] = "(?:{$patterns['unicode']}|\\[^\r\n\f0-9a-f])"; - $patterns['nmstart'] = "(?:[_a-z]|{$patterns['nonAscii']}|{$patterns['escape']})"; - $patterns['nmchar'] = "(?:[_a-z0-9-]|{$patterns['nonAscii']}|{$patterns['escape']})"; - $patterns['ident'] = "-?{$patterns['nmstart']}{$patterns['nmchar']}*"; - $patterns['quantity'] = "{$patterns['num']}(?:\s*{$patterns['unit']}|{$patterns['ident']})?"; - $patterns['possibly_negative_quantity'] = "((?:-?{$patterns['quantity']})|(?:inherit|auto))"; - $patterns['color'] = "(#?{$patterns['nmchar']}+)"; - $patterns['url_chars'] = "(?:{$patterns['url_special_chars']}|{$patterns['nonAscii']}|{$patterns['escape']})*"; - $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>)*?{)"; - $patterns['lookahead_not_closing_paren'] = "(?!{$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))"; - $patterns['lookahead_for_closing_paren'] = "(?={$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))"; - $patterns['noflip_single'] = "/({$patterns['noflip_annotation']}{$patterns['lookahead_not_open_brace']}[^;}]+;?)/i"; - $patterns['noflip_class'] = "/({$patterns['noflip_annotation']}{$patterns['chars_within_selector']}})/i"; - $patterns['body_direction_ltr'] = "/({$patterns['body_selector']}{$patterns['chars_within_selector']}{$patterns['direction']})ltr/i"; - $patterns['body_direction_rtl'] = "/({$patterns['body_selector']}{$patterns['chars_within_selector']}{$patterns['direction']})rtl/i"; - $patterns['left'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i"; - $patterns['right'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i"; - $patterns['left_in_url'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_for_closing_paren']}/i"; - $patterns['right_in_url'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_for_closing_paren']}/i"; - $patterns['ltr_in_url'] = "/{$patterns['lookbehind_not_letter']}(ltr){$patterns['lookahead_for_closing_paren']}/i"; - $patterns['rtl_in_url'] = "/{$patterns['lookbehind_not_letter']}(rtl){$patterns['lookahead_for_closing_paren']}/i"; - $patterns['cursor_east'] = "/{$patterns['lookbehind_not_letter']}([ns]?)e-resize/"; - $patterns['cursor_west'] = "/{$patterns['lookbehind_not_letter']}([ns]?)w-resize/"; - $patterns['four_notation_quantity'] = "/{$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}/i"; - $patterns['four_notation_color'] = "/(-color\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}/i"; - // The two regexes below are parenthesized differently then in the original implementation to make the - // callback's job more straightforward - $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)({$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/"; - $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)({$patterns['num']})(%)/"; - } - - /** - * Transform an LTR stylesheet to RTL - * @param string $css Stylesheet to transform - * @param bool $swapLtrRtlInURL If true, swap 'ltr' and 'rtl' in URLs - * @param bool $swapLeftRightInURL If true, swap 'left' and 'right' in URLs - * @return Transformed stylesheet - */ - public static function transform( $css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false ) { - // We wrap tokens in ` , not ~ like the original implementation does. - // This was done because ` is not a legal character in CSS and can only - // occur in URLs, where we escape it to %60 before inserting our tokens. - $css = str_replace( '`', '%60', $css ); - - self::buildPatterns(); - - // Tokenize single line rules with /* @noflip */ - $noFlipSingle = new CSSJanus_Tokenizer( self::$patterns['noflip_single'], '`NOFLIP_SINGLE`' ); - $css = $noFlipSingle->tokenize( $css ); - - // Tokenize class rules with /* @noflip */ - $noFlipClass = new CSSJanus_Tokenizer( self::$patterns['noflip_class'], '`NOFLIP_CLASS`' ); - $css = $noFlipClass->tokenize( $css ); - - // Tokenize comments - $comments = new CSSJanus_Tokenizer( self::$patterns['comment'], '`C`' ); - $css = $comments->tokenize( $css ); - - // LTR->RTL fixes start here - $css = self::fixBodyDirection( $css ); - if ( $swapLtrRtlInURL ) { - $css = self::fixLtrRtlInURL( $css ); - } - - if ( $swapLeftRightInURL ) { - $css = self::fixLeftRightInURL( $css ); - } - $css = self::fixLeftAndRight( $css ); - $css = self::fixCursorProperties( $css ); - $css = self::fixFourPartNotation( $css ); - $css = self::fixBackgroundPosition( $css ); - - // Detokenize stuff we tokenized before - $css = $comments->detokenize( $css ); - $css = $noFlipClass->detokenize( $css ); - $css = $noFlipSingle->detokenize( $css ); - - return $css; - } - - /** - * Replace direction: ltr; with direction: rtl; and vice versa, but *only* - * those inside a body { .. } selector. - * - * Unlike the original implementation, this function doesn't suffer from - * the bug causing "body\n{\ndirection: ltr;\n}" to be missed. - * See http://code.google.com/p/cssjanus/issues/detail?id=15 - */ - private static function fixBodyDirection( $css ) { - $css = preg_replace( self::$patterns['body_direction_ltr'], - '$1' . self::$patterns['tmpToken'], $css ); - $css = preg_replace( self::$patterns['body_direction_rtl'], '$1ltr', $css ); - $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css ); - - return $css; - } - - /** - * Replace 'ltr' with 'rtl' and vice versa in background URLs - */ - private static function fixLtrRtlInURL( $css ) { - $css = preg_replace( self::$patterns['ltr_in_url'], self::$patterns['tmpToken'], $css ); - $css = preg_replace( self::$patterns['rtl_in_url'], 'ltr', $css ); - $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css ); - - return $css; - } - - /** - * Replace 'left' with 'right' and vice versa in background URLs - */ - private static function fixLeftRightInURL( $css ) { - $css = preg_replace( self::$patterns['left_in_url'], self::$patterns['tmpToken'], $css ); - $css = preg_replace( self::$patterns['right_in_url'], 'left', $css ); - $css = str_replace( self::$patterns['tmpToken'], 'right', $css ); - - return $css; - } - - /** - * Flip rules like left: , padding-right: , etc. - */ - private static function fixLeftAndRight( $css ) { - $css = preg_replace( self::$patterns['left'], self::$patterns['tmpToken'], $css ); - $css = preg_replace( self::$patterns['right'], 'left', $css ); - $css = str_replace( self::$patterns['tmpToken'], 'right', $css ); - - return $css; - } - - /** - * Flip East and West in rules like cursor: nw-resize; - */ - private static function fixCursorProperties( $css ) { - $css = preg_replace( self::$patterns['cursor_east'], - '$1' . self::$patterns['tmpToken'], $css ); - $css = preg_replace( self::$patterns['cursor_west'], '$1e-resize', $css ); - $css = str_replace( self::$patterns['tmpToken'], 'w-resize', $css ); - - return $css; - } - - /** - * Swap the second and fourth parts in four-part notation rules like - * padding: 1px 2px 3px 4px; - * - * Unlike the original implementation, this function doesn't suffer from - * the bug where whitespace is not preserved when flipping four-part rules - * and four-part color rules with multiple whitespace characters between - * colors are not recognized. - * See http://code.google.com/p/cssjanus/issues/detail?id=16 - */ - private static function fixFourPartNotation( $css ) { - $css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$7$4$5$6$3', $css ); - $css = preg_replace( self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4', $css ); - - return $css; - } - - /** - * Flip horizontal background percentages. - */ - private static function fixBackgroundPosition( $css ) { - $css = preg_replace_callback( self::$patterns['bg_horizontal_percentage'], - array( 'self', 'calculateNewBackgroundPosition' ), $css ); - $css = preg_replace_callback( self::$patterns['bg_horizontal_percentage_x'], - array( 'self', 'calculateNewBackgroundPosition' ), $css ); - - return $css; - } - - /** - * Callback for calculateNewBackgroundPosition() - */ - private static function calculateNewBackgroundPosition( $matches ) { - return $matches[1] . ( 100 - $matches[2] ) . $matches[3]; - } -} - -/** - * Utility class used by CSSJanus that tokenizes and untokenizes things we want - * to protect from being janused. - * @author Roan Kattouw - */ -class CSSJanus_Tokenizer { - private $regex, $token; - private $originals; - - /** - * Constructor - * @param $regex string Regular expression whose matches to replace by a token. - * @param $token string Token - */ - public function __construct( $regex, $token ) { - $this->regex = $regex; - $this->token = $token; - $this->originals = array(); - } - - /** - * Replace all occurrences of $regex in $str with a token and remember - * the original strings. - * @param $str string String to tokenize - * @return string Tokenized string - */ - public function tokenize( $str ) { - return preg_replace_callback( $this->regex, array( $this, 'tokenizeCallback' ), $str ); - } - - private function tokenizeCallback( $matches ) { - $this->originals[] = $matches[0]; - return $this->token; - } - - /** - * Replace tokens with their originals. If multiple strings were tokenized, it's important they be - * detokenized in exactly the SAME ORDER. - * @param string $str String previously run through tokenize() - * @return string Original string - */ - public function detokenize( $str ) { - // PHP has no function to replace only the first occurrence or to - // replace occurrences of the same string with different values, - // so we use preg_replace_callback() even though we don't really need a regex - return preg_replace_callback( '/' . preg_quote( $this->token, '/' ) . '/', - array( $this, 'detokenizeCallback' ), $str ); - } - - private function detokenizeCallback( $matches ) { - $retval = current( $this->originals ); - next( $this->originals ); - - return $retval; - } -} diff --git a/includes/CSSMin.php b/includes/CSSMin.php deleted file mode 100644 index 40f893d27a..0000000000 --- a/includes/CSSMin.php +++ /dev/null @@ -1,132 +0,0 @@ -[^\?\)\:]*)\??[^\)]*[\'"]?\)/'; - $files = array(); - - if ( preg_match_all( $pattern, $source, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER ) ) { - foreach ( $matches as $match ) { - $file = ( isset( $path ) ? rtrim( $path, '/' ) . '/' : '' ) . "{$match['file'][0]}"; - - // Only proceed if we can access the file - if ( file_exists( $file ) ) { - $files[] = $file; - } - } - } - - return $files; - } - - /** - * Remaps CSS URL paths and automatically embeds data URIs for URL rules preceded by an /* @embed * / comment - * - * @param $source string CSS data to remap - * @param $path string File path where the source was read from - * @return string Remapped CSS data - */ - public static function remap( $source, $path, $embed = true ) { - - $pattern = '/((?\s*\/\*\s*\@embed\s*\*\/)(?[^\;\}]*))?url\([\'"]?(?[^\?\)\:]*)\??[^\)]*[\'"]?\)(?[^;]*)[\;]?/'; - $offset = 0; - - while ( preg_match( $pattern, $source, $match, PREG_OFFSET_CAPTURE, $offset ) ) { - // Shortcuts - $embed = $match['embed'][0]; - $rule = $match['rule'][0]; - $extra = $match['extra'][0]; - $file = "{$path}/{$match['file'][0]}"; - - // Only proceed if we can access the file - if ( file_exists( $file ) ) { - // Add version parameter as a time-stamp in ISO 8601 format, using Z for the timezone, meaning GMT - $url = "{$file}?" . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) ); - - // Detect when URLs were preceeded with embed tags, and also verify file size is below the limit - if ( $embed && $match['embed'][1] > 0 && filesize( $file ) < self::EMBED_SIZE_LIMIT ) { - // If we ever get to PHP 5.3, we should use the Fileinfo extension instead of mime_content_type - $type = mime_content_type( $file ); - // Strip off any trailing = symbols (makes browsers freak out) - $data = base64_encode( file_get_contents( $file ) ); - // Build 2 CSS properties; one which uses a base64 encoded data URI in place of the @embed - // comment to try and retain line-number integrity , and the other with a remapped an versioned - // URL and an Internet Explorer hack making it ignored in all browsers that support data URIs - $replacement = "{$rule}url(data:{$type};base64,{$data}){$extra};{$rule}url({$url}){$extra}!ie;"; - } else { - // Build a CSS property with a remapped and versioned URL - $replacement = "{$embed}{$rule}url({$url}){$extra};"; - } - - // Perform replacement on the source - $source = substr_replace( $source, $replacement, $match[0][1], strlen( $match[0][0] ) ); - // Move the offset to the end of the replacement in the source - $offset = $match[0][1] + strlen( $replacement ); - continue; - } - // Move the offset to the end of the match, leaving it alone - $offset = $match[0][1] + strlen( $match[0][0] ); - } - - return $source; - } - - /** - * Removes whitespace from CSS data - * - * @param $source string CSS data to minify - * @return string Minified CSS data - */ - public static function minify( $css ) { - return trim( - str_replace( - array( '; ', ': ', ' {', '{ ', ', ', '} ', ';}' ), - array( ';', ':', '{', '{', ',', '}', '}' ), - preg_replace( array( '/\s+/', '/\/\*.*?\*\//s' ), array( ' ', '' ), $css ) - ) - ); - } -} diff --git a/includes/JSMin.php b/includes/JSMin.php deleted file mode 100644 index 5c8391d3db..0000000000 --- a/includes/JSMin.php +++ /dev/null @@ -1,291 +0,0 @@ - - * @copyright 2002 Douglas Crockford (jsmin.c) - * @copyright 2008 Ryan Grove (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 ); - $ret = $jsmin->min(); - return $ret; - } - - // -- 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 {} diff --git a/includes/libs/CSSJanus.php b/includes/libs/CSSJanus.php new file mode 100644 index 0000000000..08e8e0963d --- /dev/null +++ b/includes/libs/CSSJanus.php @@ -0,0 +1,321 @@ + '`TMP`', + 'nonAscii' => '[\200-\377]', + 'unicode' => '(?:(?:\\[0-9a-f]{1,6})(?:\r\n|\s)?)', + 'num' => '(?:[0-9]*\.[0-9]+|[0-9]+)', + 'unit' => '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)', + 'body_selector' => 'body\s*{\s*', + 'direction' => 'direction\s*:\s*', + 'escape' => null, + 'nmstart' => null, + 'nmchar' => null, + 'ident' => null, + 'quantity' => null, + 'possibly_negative_quantity' => null, + 'color' => null, + 'url_special_chars' => '[!#$%&*-~]', + 'valid_after_uri_chars' => '[\'\"]?\s*', + 'url_chars' => null, + 'lookahead_not_open_brace' => null, + 'lookahead_not_closing_paren' => null, + 'lookahead_for_closing_paren' => null, + 'lookbehind_not_letter' => '(? '[^\}]*?', + 'noflip_annotation' => '\/\*\s*@noflip\s*\*\/', + 'noflip_single' => null, + 'noflip_class' => null, + 'comment' => '/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//', + 'body_direction_ltr' => null, + 'body_direction_rtl' => null, + 'left' => null, + 'right' => null, + 'left_in_url' => null, + 'right_in_url' => null, + 'ltr_in_url' => null, + 'rtl_in_url' => null, + 'cursor_east' => null, + 'cursor_west' => null, + 'four_notation_quantity' => null, + 'four_notation_color' => null, + 'bg_horizontal_percentage' => null, + 'bg_horizontal_percentage_x' => null, + ); + + /** + * Build patterns we can't define above because they depend on other patterns. + */ + private static function buildPatterns() { + if ( !is_null( self::$patterns['escape'] ) ) { + // Patterns have already been built + return; + } + + $patterns =& self::$patterns; + $patterns['escape'] = "(?:{$patterns['unicode']}|\\[^\r\n\f0-9a-f])"; + $patterns['nmstart'] = "(?:[_a-z]|{$patterns['nonAscii']}|{$patterns['escape']})"; + $patterns['nmchar'] = "(?:[_a-z0-9-]|{$patterns['nonAscii']}|{$patterns['escape']})"; + $patterns['ident'] = "-?{$patterns['nmstart']}{$patterns['nmchar']}*"; + $patterns['quantity'] = "{$patterns['num']}(?:\s*{$patterns['unit']}|{$patterns['ident']})?"; + $patterns['possibly_negative_quantity'] = "((?:-?{$patterns['quantity']})|(?:inherit|auto))"; + $patterns['color'] = "(#?{$patterns['nmchar']}+)"; + $patterns['url_chars'] = "(?:{$patterns['url_special_chars']}|{$patterns['nonAscii']}|{$patterns['escape']})*"; + $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>)*?{)"; + $patterns['lookahead_not_closing_paren'] = "(?!{$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))"; + $patterns['lookahead_for_closing_paren'] = "(?={$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))"; + $patterns['noflip_single'] = "/({$patterns['noflip_annotation']}{$patterns['lookahead_not_open_brace']}[^;}]+;?)/i"; + $patterns['noflip_class'] = "/({$patterns['noflip_annotation']}{$patterns['chars_within_selector']}})/i"; + $patterns['body_direction_ltr'] = "/({$patterns['body_selector']}{$patterns['chars_within_selector']}{$patterns['direction']})ltr/i"; + $patterns['body_direction_rtl'] = "/({$patterns['body_selector']}{$patterns['chars_within_selector']}{$patterns['direction']})rtl/i"; + $patterns['left'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i"; + $patterns['right'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i"; + $patterns['left_in_url'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_for_closing_paren']}/i"; + $patterns['right_in_url'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_for_closing_paren']}/i"; + $patterns['ltr_in_url'] = "/{$patterns['lookbehind_not_letter']}(ltr){$patterns['lookahead_for_closing_paren']}/i"; + $patterns['rtl_in_url'] = "/{$patterns['lookbehind_not_letter']}(rtl){$patterns['lookahead_for_closing_paren']}/i"; + $patterns['cursor_east'] = "/{$patterns['lookbehind_not_letter']}([ns]?)e-resize/"; + $patterns['cursor_west'] = "/{$patterns['lookbehind_not_letter']}([ns]?)w-resize/"; + $patterns['four_notation_quantity'] = "/{$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}/i"; + $patterns['four_notation_color'] = "/(-color\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}/i"; + // The two regexes below are parenthesized differently then in the original implementation to make the + // callback's job more straightforward + $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)({$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/"; + $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)({$patterns['num']})(%)/"; + } + + /** + * Transform an LTR stylesheet to RTL + * @param string $css Stylesheet to transform + * @param bool $swapLtrRtlInURL If true, swap 'ltr' and 'rtl' in URLs + * @param bool $swapLeftRightInURL If true, swap 'left' and 'right' in URLs + * @return Transformed stylesheet + */ + public static function transform( $css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false ) { + // We wrap tokens in ` , not ~ like the original implementation does. + // This was done because ` is not a legal character in CSS and can only + // occur in URLs, where we escape it to %60 before inserting our tokens. + $css = str_replace( '`', '%60', $css ); + + self::buildPatterns(); + + // Tokenize single line rules with /* @noflip */ + $noFlipSingle = new CSSJanus_Tokenizer( self::$patterns['noflip_single'], '`NOFLIP_SINGLE`' ); + $css = $noFlipSingle->tokenize( $css ); + + // Tokenize class rules with /* @noflip */ + $noFlipClass = new CSSJanus_Tokenizer( self::$patterns['noflip_class'], '`NOFLIP_CLASS`' ); + $css = $noFlipClass->tokenize( $css ); + + // Tokenize comments + $comments = new CSSJanus_Tokenizer( self::$patterns['comment'], '`C`' ); + $css = $comments->tokenize( $css ); + + // LTR->RTL fixes start here + $css = self::fixBodyDirection( $css ); + if ( $swapLtrRtlInURL ) { + $css = self::fixLtrRtlInURL( $css ); + } + + if ( $swapLeftRightInURL ) { + $css = self::fixLeftRightInURL( $css ); + } + $css = self::fixLeftAndRight( $css ); + $css = self::fixCursorProperties( $css ); + $css = self::fixFourPartNotation( $css ); + $css = self::fixBackgroundPosition( $css ); + + // Detokenize stuff we tokenized before + $css = $comments->detokenize( $css ); + $css = $noFlipClass->detokenize( $css ); + $css = $noFlipSingle->detokenize( $css ); + + return $css; + } + + /** + * Replace direction: ltr; with direction: rtl; and vice versa, but *only* + * those inside a body { .. } selector. + * + * Unlike the original implementation, this function doesn't suffer from + * the bug causing "body\n{\ndirection: ltr;\n}" to be missed. + * See http://code.google.com/p/cssjanus/issues/detail?id=15 + */ + private static function fixBodyDirection( $css ) { + $css = preg_replace( self::$patterns['body_direction_ltr'], + '$1' . self::$patterns['tmpToken'], $css ); + $css = preg_replace( self::$patterns['body_direction_rtl'], '$1ltr', $css ); + $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css ); + + return $css; + } + + /** + * Replace 'ltr' with 'rtl' and vice versa in background URLs + */ + private static function fixLtrRtlInURL( $css ) { + $css = preg_replace( self::$patterns['ltr_in_url'], self::$patterns['tmpToken'], $css ); + $css = preg_replace( self::$patterns['rtl_in_url'], 'ltr', $css ); + $css = str_replace( self::$patterns['tmpToken'], 'rtl', $css ); + + return $css; + } + + /** + * Replace 'left' with 'right' and vice versa in background URLs + */ + private static function fixLeftRightInURL( $css ) { + $css = preg_replace( self::$patterns['left_in_url'], self::$patterns['tmpToken'], $css ); + $css = preg_replace( self::$patterns['right_in_url'], 'left', $css ); + $css = str_replace( self::$patterns['tmpToken'], 'right', $css ); + + return $css; + } + + /** + * Flip rules like left: , padding-right: , etc. + */ + private static function fixLeftAndRight( $css ) { + $css = preg_replace( self::$patterns['left'], self::$patterns['tmpToken'], $css ); + $css = preg_replace( self::$patterns['right'], 'left', $css ); + $css = str_replace( self::$patterns['tmpToken'], 'right', $css ); + + return $css; + } + + /** + * Flip East and West in rules like cursor: nw-resize; + */ + private static function fixCursorProperties( $css ) { + $css = preg_replace( self::$patterns['cursor_east'], + '$1' . self::$patterns['tmpToken'], $css ); + $css = preg_replace( self::$patterns['cursor_west'], '$1e-resize', $css ); + $css = str_replace( self::$patterns['tmpToken'], 'w-resize', $css ); + + return $css; + } + + /** + * Swap the second and fourth parts in four-part notation rules like + * padding: 1px 2px 3px 4px; + * + * Unlike the original implementation, this function doesn't suffer from + * the bug where whitespace is not preserved when flipping four-part rules + * and four-part color rules with multiple whitespace characters between + * colors are not recognized. + * See http://code.google.com/p/cssjanus/issues/detail?id=16 + */ + private static function fixFourPartNotation( $css ) { + $css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$7$4$5$6$3', $css ); + $css = preg_replace( self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4', $css ); + + return $css; + } + + /** + * Flip horizontal background percentages. + */ + private static function fixBackgroundPosition( $css ) { + $css = preg_replace_callback( self::$patterns['bg_horizontal_percentage'], + array( 'self', 'calculateNewBackgroundPosition' ), $css ); + $css = preg_replace_callback( self::$patterns['bg_horizontal_percentage_x'], + array( 'self', 'calculateNewBackgroundPosition' ), $css ); + + return $css; + } + + /** + * Callback for calculateNewBackgroundPosition() + */ + private static function calculateNewBackgroundPosition( $matches ) { + return $matches[1] . ( 100 - $matches[2] ) . $matches[3]; + } +} + +/** + * Utility class used by CSSJanus that tokenizes and untokenizes things we want + * to protect from being janused. + * @author Roan Kattouw + */ +class CSSJanus_Tokenizer { + private $regex, $token; + private $originals; + + /** + * Constructor + * @param $regex string Regular expression whose matches to replace by a token. + * @param $token string Token + */ + public function __construct( $regex, $token ) { + $this->regex = $regex; + $this->token = $token; + $this->originals = array(); + } + + /** + * Replace all occurrences of $regex in $str with a token and remember + * the original strings. + * @param $str string String to tokenize + * @return string Tokenized string + */ + public function tokenize( $str ) { + return preg_replace_callback( $this->regex, array( $this, 'tokenizeCallback' ), $str ); + } + + private function tokenizeCallback( $matches ) { + $this->originals[] = $matches[0]; + return $this->token; + } + + /** + * Replace tokens with their originals. If multiple strings were tokenized, it's important they be + * detokenized in exactly the SAME ORDER. + * @param string $str String previously run through tokenize() + * @return string Original string + */ + public function detokenize( $str ) { + // PHP has no function to replace only the first occurrence or to + // replace occurrences of the same string with different values, + // so we use preg_replace_callback() even though we don't really need a regex + return preg_replace_callback( '/' . preg_quote( $this->token, '/' ) . '/', + array( $this, 'detokenizeCallback' ), $str ); + } + + private function detokenizeCallback( $matches ) { + $retval = current( $this->originals ); + next( $this->originals ); + + return $retval; + } +} diff --git a/includes/libs/CSSMin.php b/includes/libs/CSSMin.php new file mode 100644 index 0000000000..40f893d27a --- /dev/null +++ b/includes/libs/CSSMin.php @@ -0,0 +1,132 @@ +[^\?\)\:]*)\??[^\)]*[\'"]?\)/'; + $files = array(); + + if ( preg_match_all( $pattern, $source, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER ) ) { + foreach ( $matches as $match ) { + $file = ( isset( $path ) ? rtrim( $path, '/' ) . '/' : '' ) . "{$match['file'][0]}"; + + // Only proceed if we can access the file + if ( file_exists( $file ) ) { + $files[] = $file; + } + } + } + + return $files; + } + + /** + * Remaps CSS URL paths and automatically embeds data URIs for URL rules preceded by an /* @embed * / comment + * + * @param $source string CSS data to remap + * @param $path string File path where the source was read from + * @return string Remapped CSS data + */ + public static function remap( $source, $path, $embed = true ) { + + $pattern = '/((?\s*\/\*\s*\@embed\s*\*\/)(?[^\;\}]*))?url\([\'"]?(?[^\?\)\:]*)\??[^\)]*[\'"]?\)(?[^;]*)[\;]?/'; + $offset = 0; + + while ( preg_match( $pattern, $source, $match, PREG_OFFSET_CAPTURE, $offset ) ) { + // Shortcuts + $embed = $match['embed'][0]; + $rule = $match['rule'][0]; + $extra = $match['extra'][0]; + $file = "{$path}/{$match['file'][0]}"; + + // Only proceed if we can access the file + if ( file_exists( $file ) ) { + // Add version parameter as a time-stamp in ISO 8601 format, using Z for the timezone, meaning GMT + $url = "{$file}?" . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) ); + + // Detect when URLs were preceeded with embed tags, and also verify file size is below the limit + if ( $embed && $match['embed'][1] > 0 && filesize( $file ) < self::EMBED_SIZE_LIMIT ) { + // If we ever get to PHP 5.3, we should use the Fileinfo extension instead of mime_content_type + $type = mime_content_type( $file ); + // Strip off any trailing = symbols (makes browsers freak out) + $data = base64_encode( file_get_contents( $file ) ); + // Build 2 CSS properties; one which uses a base64 encoded data URI in place of the @embed + // comment to try and retain line-number integrity , and the other with a remapped an versioned + // URL and an Internet Explorer hack making it ignored in all browsers that support data URIs + $replacement = "{$rule}url(data:{$type};base64,{$data}){$extra};{$rule}url({$url}){$extra}!ie;"; + } else { + // Build a CSS property with a remapped and versioned URL + $replacement = "{$embed}{$rule}url({$url}){$extra};"; + } + + // Perform replacement on the source + $source = substr_replace( $source, $replacement, $match[0][1], strlen( $match[0][0] ) ); + // Move the offset to the end of the replacement in the source + $offset = $match[0][1] + strlen( $replacement ); + continue; + } + // Move the offset to the end of the match, leaving it alone + $offset = $match[0][1] + strlen( $match[0][0] ); + } + + return $source; + } + + /** + * Removes whitespace from CSS data + * + * @param $source string CSS data to minify + * @return string Minified CSS data + */ + public static function minify( $css ) { + return trim( + str_replace( + array( '; ', ': ', ' {', '{ ', ', ', '} ', ';}' ), + array( ';', ':', '{', '{', ',', '}', '}' ), + preg_replace( array( '/\s+/', '/\/\*.*?\*\//s' ), array( ' ', '' ), $css ) + ) + ); + } +} diff --git a/includes/libs/JSMin.php b/includes/libs/JSMin.php new file mode 100644 index 0000000000..5c8391d3db --- /dev/null +++ b/includes/libs/JSMin.php @@ -0,0 +1,291 @@ + + * @copyright 2002 Douglas Crockford (jsmin.c) + * @copyright 2008 Ryan Grove (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 ); + $ret = $jsmin->min(); + return $ret; + } + + // -- 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 {}