MediaWiki  REL1_22
CSSMin.php
Go to the documentation of this file.
00001 <?php
00030 class CSSMin {
00031 
00032     /* Constants */
00033 
00040     const EMBED_SIZE_LIMIT = 24576;
00041     const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*)(?P<query>\??[^\)\'"]*)[\'"]?\s*\)';
00042 
00043     /* Protected Static Members */
00044 
00046     protected static $mimeTypes = array(
00047         'gif' => 'image/gif',
00048         'jpe' => 'image/jpeg',
00049         'jpeg' => 'image/jpeg',
00050         'jpg' => 'image/jpeg',
00051         'png' => 'image/png',
00052         'tif' => 'image/tiff',
00053         'tiff' => 'image/tiff',
00054         'xbm' => 'image/x-xbitmap',
00055     );
00056 
00057     /* Static Methods */
00058 
00066     public static function getLocalFileReferences( $source, $path = null ) {
00067         $files = array();
00068         $rFlags = PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
00069         if ( preg_match_all( '/' . self::URL_REGEX . '/', $source, $matches, $rFlags ) ) {
00070             foreach ( $matches as $match ) {
00071                 $file = ( isset( $path )
00072                     ? rtrim( $path, '/' ) . '/'
00073                     : '' ) . "{$match['file'][0]}";
00074 
00075                 // Only proceed if we can access the file
00076                 if ( !is_null( $path ) && file_exists( $file ) ) {
00077                     $files[] = $file;
00078                 }
00079             }
00080         }
00081         return $files;
00082     }
00083 
00098     public static function encodeImageAsDataURI( $file, $type = null, $sizeLimit = self::EMBED_SIZE_LIMIT ) {
00099         if ( $sizeLimit !== false && filesize( $file ) >= $sizeLimit ) {
00100             return false;
00101         }
00102         if ( $type === null ) {
00103             $type = self::getMimeType( $file );
00104         }
00105         if ( !$type ) {
00106             return false;
00107         }
00108         $data = base64_encode( file_get_contents( $file ) );
00109         return 'data:' . $type . ';base64,' . $data;
00110     }
00111 
00116     public static function getMimeType( $file ) {
00117         $realpath = realpath( $file );
00118         // Try a couple of different ways to get the mime-type of a file, in order of
00119         // preference
00120         if (
00121             $realpath
00122             && function_exists( 'finfo_file' )
00123             && function_exists( 'finfo_open' )
00124             && defined( 'FILEINFO_MIME_TYPE' )
00125         ) {
00126             // As of PHP 5.3, this is how you get the mime-type of a file; it uses the Fileinfo
00127             // PECL extension
00128             return finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $realpath );
00129         } elseif ( function_exists( 'mime_content_type' ) ) {
00130             // Before this was deprecated in PHP 5.3, this was how you got the mime-type of a file
00131             return mime_content_type( $file );
00132         } else {
00133             // Worst-case scenario has happened, use the file extension to infer the mime-type
00134             $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
00135             if ( isset( self::$mimeTypes[$ext] ) ) {
00136                 return self::$mimeTypes[$ext];
00137             }
00138         }
00139         return false;
00140     }
00141 
00152     public static function remap( $source, $local, $remote, $embedData = true ) {
00153         $pattern = '/((?P<embed>\s*\/\*\s*\@embed\s*\*\/)(?P<pre>[^\;\}]*))?' .
00154             self::URL_REGEX . '(?P<post>[^;]*)[\;]?/';
00155         $offset = 0;
00156         while ( preg_match( $pattern, $source, $match, PREG_OFFSET_CAPTURE, $offset ) ) {
00157             // Skip fully-qualified URLs and data URIs
00158             $urlScheme = parse_url( $match['file'][0], PHP_URL_SCHEME );
00159             if ( $urlScheme ) {
00160                 // Move the offset to the end of the match, leaving it alone
00161                 $offset = $match[0][1] + strlen( $match[0][0] );
00162                 continue;
00163             }
00164             // URLs with absolute paths like /w/index.php need to be expanded
00165             // to absolute URLs but otherwise left alone
00166             if ( $match['file'][0] !== '' && $match['file'][0][0] === '/' ) {
00167                 // Replace the file path with an expanded (possibly protocol-relative) URL
00168                 // ...but only if wfExpandUrl() is even available.
00169                 // This will not be the case if we're running outside of MW
00170                 $lengthIncrease = 0;
00171                 if ( function_exists( 'wfExpandUrl' ) ) {
00172                     $expanded = wfExpandUrl( $match['file'][0], PROTO_RELATIVE );
00173                     $origLength = strlen( $match['file'][0] );
00174                     $lengthIncrease = strlen( $expanded ) - $origLength;
00175                     $source = substr_replace( $source, $expanded,
00176                         $match['file'][1], $origLength
00177                     );
00178                 }
00179                 // Move the offset to the end of the match, leaving it alone
00180                 $offset = $match[0][1] + strlen( $match[0][0] ) + $lengthIncrease;
00181                 continue;
00182             }
00183 
00184             // Guard against double slashes, because "some/remote/../foo.png"
00185             // resolves to "some/remote/foo.png" on (some?) clients (bug 27052).
00186             if ( substr( $remote, -1 ) == '/' ) {
00187                 $remote = substr( $remote, 0, -1 );
00188             }
00189 
00190             // Shortcuts
00191             $embed = $match['embed'][0];
00192             $pre = $match['pre'][0];
00193             $post = $match['post'][0];
00194             $query = $match['query'][0];
00195             $url = "{$remote}/{$match['file'][0]}";
00196             $file = "{$local}/{$match['file'][0]}";
00197 
00198             $replacement = false;
00199 
00200             if ( $local !== false && file_exists( $file ) ) {
00201                 // Add version parameter as a time-stamp in ISO 8601 format,
00202                 // using Z for the timezone, meaning GMT
00203                 $url .= '?' . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) );
00204                 // Embedding requires a bit of extra processing, so let's skip that if we can
00205                 if ( $embedData && $embed && $match['embed'][1] > 0 ) {
00206                     $data = self::encodeImageAsDataURI( $file );
00207                     if ( $data !== false ) {
00208                         // Build 2 CSS properties; one which uses a base64 encoded data URI in place
00209                         // of the @embed comment to try and retain line-number integrity, and the
00210                         // other with a remapped an versioned URL and an Internet Explorer hack
00211                         // making it ignored in all browsers that support data URIs
00212                         $replacement = "{$pre}url({$data}){$post};{$pre}url({$url}){$post}!ie;";
00213                     }
00214                 }
00215                 if ( $replacement === false ) {
00216                     // Assume that all paths are relative to $remote, and make them absolute
00217                     $replacement = "{$embed}{$pre}url({$url}){$post};";
00218                 }
00219             } elseif ( $local === false ) {
00220                 // Assume that all paths are relative to $remote, and make them absolute
00221                 $replacement = "{$embed}{$pre}url({$url}{$query}){$post};";
00222             }
00223             if ( $replacement !== false ) {
00224                 // Perform replacement on the source
00225                 $source = substr_replace(
00226                     $source, $replacement, $match[0][1], strlen( $match[0][0] )
00227                 );
00228                 // Move the offset to the end of the replacement in the source
00229                 $offset = $match[0][1] + strlen( $replacement );
00230                 continue;
00231             }
00232             // Move the offset to the end of the match, leaving it alone
00233             $offset = $match[0][1] + strlen( $match[0][0] );
00234         }
00235         return $source;
00236     }
00237 
00244     public static function minify( $css ) {
00245         return trim(
00246             str_replace(
00247                 array( '; ', ': ', ' {', '{ ', ', ', '} ', ';}' ),
00248                 array( ';', ':', '{', '{', ',', '}', '}' ),
00249                 preg_replace( array( '/\s+/', '/\/\*.*?\*\//s' ), array( ' ', '' ), $css )
00250             )
00251         );
00252     }
00253 }