PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/libs/CSSMin.php

https://github.com/daevid/MWFork
PHP | 222 lines | 120 code | 11 blank | 91 comment | 22 complexity | c98f094b38e3cb42327980b48861833c MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright 2010 Wikimedia Foundation
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. * not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software distributed
  12. * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
  13. * OF ANY KIND, either express or implied. See the License for the
  14. * specific language governing permissions and limitations under the License.
  15. */
  16. /**
  17. * Transforms CSS data
  18. *
  19. * This class provides minification, URL remapping, URL extracting, and data-URL embedding.
  20. *
  21. * @file
  22. * @version 0.1.1 -- 2010-09-11
  23. * @author Trevor Parscal <tparscal@wikimedia.org>
  24. * @copyright Copyright 2010 Wikimedia Foundation
  25. * @license http://www.apache.org/licenses/LICENSE-2.0
  26. */
  27. class CSSMin {
  28. /* Constants */
  29. /**
  30. * Maximum file size to still qualify for in-line embedding as a data-URI
  31. *
  32. * 24,576 is used because Internet Explorer has a 32,768 byte limit for data URIs,
  33. * which when base64 encoded will result in a 1/3 increase in size.
  34. */
  35. const EMBED_SIZE_LIMIT = 24576;
  36. const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*)(?P<query>\??[^\)\'"]*)[\'"]?\s*\)';
  37. /* Protected Static Members */
  38. /** @var array List of common image files extensions and mime-types */
  39. protected static $mimeTypes = array(
  40. 'gif' => 'image/gif',
  41. 'jpe' => 'image/jpeg',
  42. 'jpeg' => 'image/jpeg',
  43. 'jpg' => 'image/jpeg',
  44. 'png' => 'image/png',
  45. 'tif' => 'image/tiff',
  46. 'tiff' => 'image/tiff',
  47. 'xbm' => 'image/x-xbitmap',
  48. );
  49. /* Static Methods */
  50. /**
  51. * Gets a list of local file paths which are referenced in a CSS style sheet
  52. *
  53. * @param $source string CSS data to remap
  54. * @param $path string File path where the source was read from (optional)
  55. * @return array List of local file references
  56. */
  57. public static function getLocalFileReferences( $source, $path = null ) {
  58. $files = array();
  59. $rFlags = PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
  60. if ( preg_match_all( '/' . self::URL_REGEX . '/', $source, $matches, $rFlags ) ) {
  61. foreach ( $matches as $match ) {
  62. $file = ( isset( $path )
  63. ? rtrim( $path, '/' ) . '/'
  64. : '' ) . "{$match['file'][0]}";
  65. // Only proceed if we can access the file
  66. if ( !is_null( $path ) && file_exists( $file ) ) {
  67. $files[] = $file;
  68. }
  69. }
  70. }
  71. return $files;
  72. }
  73. protected static function getMimeType( $file ) {
  74. $realpath = realpath( $file );
  75. // Try a couple of different ways to get the mime-type of a file, in order of
  76. // preference
  77. if (
  78. $realpath
  79. && function_exists( 'finfo_file' )
  80. && function_exists( 'finfo_open' )
  81. && defined( 'FILEINFO_MIME_TYPE' )
  82. ) {
  83. // As of PHP 5.3, this is how you get the mime-type of a file; it uses the Fileinfo
  84. // PECL extension
  85. return finfo_file( finfo_open( FILEINFO_MIME_TYPE ), $realpath );
  86. } elseif ( function_exists( 'mime_content_type' ) ) {
  87. // Before this was deprecated in PHP 5.3, this was how you got the mime-type of a file
  88. return mime_content_type( $file );
  89. } else {
  90. // Worst-case scenario has happened, use the file extension to infer the mime-type
  91. $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
  92. if ( isset( self::$mimeTypes[$ext] ) ) {
  93. return self::$mimeTypes[$ext];
  94. }
  95. }
  96. return false;
  97. }
  98. /**
  99. * Remaps CSS URL paths and automatically embeds data URIs for URL rules
  100. * preceded by an /* @embed * / comment
  101. *
  102. * @param $source string CSS data to remap
  103. * @param $local string File path where the source was read from
  104. * @param $remote string URL path to the file
  105. * @param $embed ???
  106. * @return string Remapped CSS data
  107. */
  108. public static function remap( $source, $local, $remote, $embed = true ) {
  109. $pattern = '/((?P<embed>\s*\/\*\s*\@embed\s*\*\/)(?P<pre>[^\;\}]*))?' .
  110. self::URL_REGEX . '(?P<post>[^;]*)[\;]?/';
  111. $offset = 0;
  112. while ( preg_match( $pattern, $source, $match, PREG_OFFSET_CAPTURE, $offset ) ) {
  113. // Skip fully-qualified URLs and data URIs
  114. $urlScheme = parse_url( $match['file'][0], PHP_URL_SCHEME );
  115. if ( $urlScheme ) {
  116. // Move the offset to the end of the match, leaving it alone
  117. $offset = $match[0][1] + strlen( $match[0][0] );
  118. continue;
  119. }
  120. // URLs with absolute paths like /w/index.php need to be expanded
  121. // to absolute URLs but otherwise left alone
  122. if ( $match['file'][0] !== '' && $match['file'][0][0] === '/' ) {
  123. // Replace the file path with an expanded (possibly protocol-relative) URL
  124. // ...but only if wfExpandUrl() is even available.
  125. // This will not be the case if we're running outside of MW
  126. $lengthIncrease = 0;
  127. if ( function_exists( 'wfExpandUrl' ) ) {
  128. $expanded = wfExpandUrl( $match['file'][0], PROTO_RELATIVE );
  129. $origLength = strlen( $match['file'][0] );
  130. $lengthIncrease = strlen( $expanded ) - $origLength;
  131. $source = substr_replace( $source, $expanded,
  132. $match['file'][1], $origLength
  133. );
  134. }
  135. // Move the offset to the end of the match, leaving it alone
  136. $offset = $match[0][1] + strlen( $match[0][0] ) + $lengthIncrease;
  137. continue;
  138. }
  139. // Shortcuts
  140. $embed = $match['embed'][0];
  141. $pre = $match['pre'][0];
  142. $post = $match['post'][0];
  143. $query = $match['query'][0];
  144. $url = "{$remote}/{$match['file'][0]}";
  145. $file = "{$local}/{$match['file'][0]}";
  146. // bug 27052 - Guard against double slashes, because foo//../bar
  147. // apparently resolves to foo/bar on (some?) clients
  148. $url = preg_replace( '#([^:])//+#', '\1/', $url );
  149. $replacement = false;
  150. if ( $local !== false && file_exists( $file ) ) {
  151. // Add version parameter as a time-stamp in ISO 8601 format,
  152. // using Z for the timezone, meaning GMT
  153. $url .= '?' . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) );
  154. // Embedding requires a bit of extra processing, so let's skip that if we can
  155. if ( $embed ) {
  156. $type = self::getMimeType( $file );
  157. // Detect when URLs were preceeded with embed tags, and also verify file size is
  158. // below the limit
  159. if (
  160. $type
  161. && $match['embed'][1] > 0
  162. && filesize( $file ) < self::EMBED_SIZE_LIMIT
  163. ) {
  164. // Strip off any trailing = symbols (makes browsers freak out)
  165. $data = base64_encode( file_get_contents( $file ) );
  166. // Build 2 CSS properties; one which uses a base64 encoded data URI in place
  167. // of the @embed comment to try and retain line-number integrity, and the
  168. // other with a remapped an versioned URL and an Internet Explorer hack
  169. // making it ignored in all browsers that support data URIs
  170. $replacement = "{$pre}url(data:{$type};base64,{$data}){$post};";
  171. $replacement .= "{$pre}url({$url}){$post}!ie;";
  172. }
  173. }
  174. if ( $replacement === false ) {
  175. // Assume that all paths are relative to $remote, and make them absolute
  176. $replacement = "{$embed}{$pre}url({$url}){$post};";
  177. }
  178. } elseif ( $local === false ) {
  179. // Assume that all paths are relative to $remote, and make them absolute
  180. $replacement = "{$embed}{$pre}url({$url}{$query}){$post};";
  181. }
  182. if ( $replacement !== false ) {
  183. // Perform replacement on the source
  184. $source = substr_replace(
  185. $source, $replacement, $match[0][1], strlen( $match[0][0] )
  186. );
  187. // Move the offset to the end of the replacement in the source
  188. $offset = $match[0][1] + strlen( $replacement );
  189. continue;
  190. }
  191. // Move the offset to the end of the match, leaving it alone
  192. $offset = $match[0][1] + strlen( $match[0][0] );
  193. }
  194. return $source;
  195. }
  196. /**
  197. * Removes whitespace from CSS data
  198. *
  199. * @param $css string CSS data to minify
  200. * @return string Minified CSS data
  201. */
  202. public static function minify( $css ) {
  203. return trim(
  204. str_replace(
  205. array( '; ', ': ', ' {', '{ ', ', ', '} ', ';}' ),
  206. array( ';', ':', '{', '{', ',', '}', '}' ),
  207. preg_replace( array( '/\s+/', '/\/\*.*?\*\//s' ), array( ' ', '' ), $css )
  208. )
  209. );
  210. }
  211. }