35 const PLACEHOLDER =
"\x7fPLACEHOLDER\x7f";
50 'jpe' =>
'image/jpeg',
51 'jpeg' =>
'image/jpeg',
52 'jpg' =>
'image/jpeg',
54 'tif' =>
'image/tiff',
55 'tiff' =>
'image/tiff',
56 'xbm' =>
'image/x-xbitmap',
57 'svg' =>
'image/svg+xml',
70 $stripped = preg_replace(
'/' . self::COMMENT_REGEX .
'/s',
'',
$source );
71 $path = rtrim( $path,
'/' ) .
'/';
74 $rFlags = PREG_OFFSET_CAPTURE | PREG_SET_ORDER;
75 if ( preg_match_all(
'/' . self::getUrlRegex() .
'/', $stripped,
$matches, $rFlags ) ) {
77 self::processUrlMatch( $match, $rFlags );
78 $url = $match[
'file'][0];
83 substr( $url, 0, 2 ) ===
'//' ||
84 parse_url( $url, PHP_URL_SCHEME ) ||
85 substr( $url, 0, 9 ) ===
'#default#'
90 $files[] = $path . $url;
113 if ( $ie8Compat && filesize( $file ) >= self::DATA_URI_SIZE_LIMIT ) {
117 if (
$type ===
null ) {
118 $type = self::getMimeType( $file );
124 return self::encodeStringAsDataURI( file_get_contents( $file ),
$type, $ie8Compat );
142 if ( preg_match(
'/^[\r\n\t\x20-\x7e]+$/', $contents ) ) {
145 $encoded = rawurlencode( $contents );
147 $encoded = strtr( $encoded, [
153 $uri =
'data:' .
$type .
',' . $encoded;
154 if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
160 $uri =
'data:' .
$type .
';base64,' . base64_encode( $contents );
161 if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
178 if ( strstr(
$value,
"\0" ) ) {
179 throw new Exception(
"Invalid character in CSS string" );
181 $value = strtr(
$value, [
'\\' =>
'\\\\',
'"' =>
'\\"' ] );
182 $value = preg_replace_callback(
'/[\x01-\x1f\x7f-\x9f]/',
function ( $match ) {
183 return '\\' . base_convert( ord( $match[0] ), 10, 16 ) .
' ';
185 return '"' .
$value .
'"';
194 $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
195 if ( isset( self::$mimeTypes[
$ext] ) ) {
196 return self::$mimeTypes[
$ext];
199 return mime_content_type( realpath( $file ) );
215 if ( preg_match(
'!^[\w\d:@/~.%+;,?&=-]+$!', $url ) ) {
218 return 'url("' . strtr( $url, [
'\\' =>
'\\\\',
'"' =>
'\\"' ] ) .
'")';
233 public static function remap(
$source, $local, $remote, $embedData =
true ) {
245 if ( substr( $remote, -1 ) ==
'/' ) {
246 $remote = substr( $remote, 0, -1 );
258 $pattern =
'/(?!' . self::EMBED_REGEX .
')(' . self::COMMENT_REGEX .
')/s';
260 $source = preg_replace_callback(
262 function ( $match ) use ( &$comments ) {
263 $comments[] = $match[ 0 ];
264 return CSSMin::PLACEHOLDER . ( count( $comments ) - 1 ) .
'x';
273 $pattern =
'/(?:^|[;{])\K[^;{}]*' . self::getUrlRegex() .
'[^;}]*(?=[;}]|$)/';
275 $source = preg_replace_callback(
277 function ( $matchOuter ) use ( $local, $remote, $embedData ) {
278 $rule = $matchOuter[0];
283 $rule = preg_replace(
285 CSSMin::PLACEHOLDER .
299 $ruleWithRemapped = preg_replace_callback(
301 function ( $match ) use ( $local, $remote ) {
302 self::processUrlMatch( $match );
304 $remapped =
CSSMin::remapOne( $match[
'file'], $match[
'query'], $local, $remote,
false );
314 $ruleWithEmbedded = preg_replace_callback(
316 function ( $match ) use ( $embedAll, $local, $remote, &
$mimeTypes ) {
317 self::processUrlMatch( $match );
319 $embed = $embedAll || $match[
'embed'];
328 $url = $match[
'file'] . $match[
'query'];
329 $file =
"{$local}/{$match['file']}";
331 !self::isRemoteUrl( $url ) && !self::isLocalUrl( $url )
332 && file_exists( $file )
346 if ( !$embedData || $ruleWithEmbedded === $ruleWithRemapped ) {
348 return $ruleWithRemapped;
349 } elseif ( $embedData && $needsEmbedFallback ) {
353 return "$ruleWithEmbedded;$ruleWithRemapped!ie";
356 return $ruleWithEmbedded;
361 $pattern =
'/' . self::PLACEHOLDER .
'(\d+)x/';
362 $source = preg_replace_callback( $pattern,
function ( $match ) use ( &$comments ) {
363 return $comments[ $match[1] ];
376 if ( substr( $maybeUrl, 0, 2 ) ===
'//' || parse_url( $maybeUrl, PHP_URL_SCHEME ) ) {
389 if ( $maybeUrl !==
'' && $maybeUrl[0] ===
'/' && !self::isRemoteUrl( $maybeUrl ) ) {
400 if ( $urlRegex ===
null ) {
422 'url\(\s*(?P<file0>[^\'"][^\?\)]*?)(?P<query0>\?[^\)]*?|)\s*\)' .
424 '|url\(\s*\'(?P<file1>[^\?\']*?)(?P<query1>\?[^\']*?|)\'\s*\)' .
426 '|url\(\s*"(?P<file2>[^\?"]*?)(?P<query2>\?[^"]*?|)"\s*\)' .
433 if (
$flags & PREG_SET_ORDER ) {
437 if ( isset( $match[
'file0'] ) && $match[
'file0'][1] !== -1 ) {
438 $match[
'file'] = $match[
'file0'];
439 $match[
'query'] = $match[
'query0'];
440 } elseif ( isset( $match[
'file1'] ) && $match[
'file1'][1] !== -1 ) {
441 $match[
'file'] = $match[
'file1'];
442 $match[
'query'] = $match[
'query1'];
444 $match[
'file'] = $match[
'file2'];
445 $match[
'query'] = $match[
'query2'];
448 if ( isset( $match[
'file0'] ) && $match[
'file0'] !==
'' ) {
449 $match[
'file'] = $match[
'file0'];
450 $match[
'query'] = $match[
'query0'];
451 } elseif ( isset( $match[
'file1'] ) && $match[
'file1'] !==
'' ) {
452 $match[
'file'] = $match[
'file1'];
453 $match[
'query'] = $match[
'query1'];
455 $match[
'file'] = $match[
'file2'];
456 $match[
'query'] = $match[
'query2'];
477 if ( self::isLocalUrl( $url ) && function_exists(
'wfExpandUrl' ) ) {
485 self::isRemoteUrl( $url ) ||
486 self::isLocalUrl( $url ) ||
487 substr( $url, 0, 9 ) ===
'#default#'
492 if ( $local ===
false ) {
494 $url = $remote .
'/' . $url;
497 $url =
"{$remote}/{$file}";
499 $localFile =
"{$local}/{$file}";
500 if ( file_exists( $localFile ) ) {
502 $data = self::encodeImageAsDataURI( $localFile );
503 if ( $data !==
false ) {
507 if ( method_exists(
'OutputPage',
'transformFilePath' ) ) {
512 $url .=
'?' . substr( md5_file( $localFile ), 0, 5 );
518 if ( function_exists(
'wfRemoveDotSegments' ) ) {
533 [
'; ',
': ',
' {',
'{ ',
', ',
'} ',
';}' ],
534 [
';',
':',
'{',
'{',
',',
'}',
'}' ],
535 preg_replace( [
'/\s+/',
'/\/\*.*?\*\//s' ], [
' ',
'' ],
$css )
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfRemoveDotSegments( $urlPath)
Remove all dot-segments in the provided URL path.
static serializeStringValue( $value)
Serialize a string (escape and quote) for use as a CSS string value.
static buildUrlValue( $url)
Build a CSS 'url()' value for the given URL, quoting parentheses (and other funny characters) and esc...
static encodeImageAsDataURI( $file, $type=null, $ie8Compat=true)
Encode an image file as a data URI.
static getMimeType( $file)
static isRemoteUrl( $maybeUrl)
Is this CSS rule referencing a remote URL?
static getLocalFileReferences( $source, $path)
Get a list of local files referenced in a stylesheet (includes non-existent files).
static encodeStringAsDataURI( $contents, $type, $ie8Compat=true)
Encode file contents as a data URI with chosen MIME type.
static minify( $css)
Removes whitespace from CSS data.
static isLocalUrl( $maybeUrl)
Is this CSS rule referencing a local URL?
static array $mimeTypes
List of common image files extensions and MIME-types.
static processUrlMatch(array &$match, $flags=0)
static remapOne( $file, $query, $local, $remote, $embed)
Remap or embed a CSS URL path.
const DATA_URI_SIZE_LIMIT
Internet Explorer data URI length limit.
static remap( $source, $local, $remote, $embedData=true)
Remaps CSS URL paths and automatically embeds data URIs for CSS rules or url() values preceded by an ...
static transformFilePath( $remotePathPrefix, $localPath, $file)
Utility method for transformResourceFilePath().
it s the revision text itself In either if gzip is the revision text is gzipped $flags
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query