/** * Remaps CSS URL paths and automatically embeds data URIs for CSS rules * or url() values preceded by an / * @embed * / comment. * * @param string $source CSS data to remap * @param string $local File path where the source was read from * @param string $remote URL path to the file * @param bool $embedData If false, never do any data URI embedding, * even if / * @embed * / is found. * @return string Remapped CSS data */ public static function remap($source, $local, $remote, $embedData = true) { // High-level overview: // * For each CSS rule in $source that includes at least one url() value: // * Check for an @embed comment at the start indicating that all URIs should be embedded // * For each url() value: // * Check for an @embed comment directly preceding the value // * If either @embed comment exists: // * Embedding the URL as data: URI, if it's possible / allowed // * Otherwise remap the URL to work in generated stylesheets // Guard against trailing slashes, because "some/remote/../foo.png" // resolves to "some/remote/foo.png" on (some?) clients (bug 27052). if (substr($remote, -1) == '/') { $remote = substr($remote, 0, -1); } // Disallow U+007F DELETE, which is illegal anyway, and which // we use for comment placeholders. $source = str_replace("", "?", $source); // Replace all comments by a placeholder so they will not interfere with the remapping. // Warning: This will also catch on anything looking like the start of a comment between // quotation marks (e.g. "foo /* bar"). $comments = array(); $pattern = '/(?!' . CSSMin::EMBED_REGEX . ')(' . CSSMin::COMMENT_REGEX . ')/s'; $source = preg_replace_callback($pattern, function ($match) use(&$comments) { $comments[] = $match[0]; return CSSMin::PLACEHOLDER . (count($comments) - 1) . 'x'; }, $source); // Note: This will not correctly handle cases where ';', '{' or '}' // appears in the rule itself, e.g. in a quoted string. You are advised // not to use such characters in file names. We also match start/end of // the string to be consistent in edge-cases ('@import url(…)'). $pattern = '/(?:^|[;{])\\K[^;{}]*' . CSSMin::URL_REGEX . '[^;}]*(?=[;}]|$)/'; $source = preg_replace_callback($pattern, function ($matchOuter) use($local, $remote, $embedData) { $rule = $matchOuter[0]; // Check for global @embed comment and remove it. Allow other comments to be present // before @embed (they have been replaced with placeholders at this point). $embedAll = false; $rule = preg_replace('/^((?:\\s+|' . CSSMin::PLACEHOLDER . '(\\d+)x)*)' . CSSMin::EMBED_REGEX . '\\s*/', '$1', $rule, 1, $embedAll); // Build two versions of current rule: with remapped URLs // and with embedded data: URIs (where possible). $pattern = '/(?P<embed>' . CSSMin::EMBED_REGEX . '\\s*|)' . CSSMin::URL_REGEX . '/'; $ruleWithRemapped = preg_replace_callback($pattern, function ($match) use($local, $remote) { $remapped = CSSMin::remapOne($match['file'], $match['query'], $local, $remote, false); return CSSMin::buildUrlValue($remapped); }, $rule); if ($embedData) { // Remember the occurring MIME types to avoid fallbacks when embedding some files. $mimeTypes = array(); $ruleWithEmbedded = preg_replace_callback($pattern, function ($match) use($embedAll, $local, $remote, &$mimeTypes) { $embed = $embedAll || $match['embed']; $embedded = CSSMin::remapOne($match['file'], $match['query'], $local, $remote, $embed); $url = $match['file'] . $match['query']; $file = $local . $match['file']; if (!CSSMin::isRemoteUrl($url) && !CSSMin::isLocalUrl($url) && file_exists($file)) { $mimeTypes[CSSMin::getMimeType($file)] = true; } return CSSMin::buildUrlValue($embedded); }, $rule); // Are all referenced images SVGs? $needsEmbedFallback = $mimeTypes !== array('image/svg+xml' => true); } if (!$embedData || $ruleWithEmbedded === $ruleWithRemapped) { // We're not embedding anything, or we tried to but the file is not embeddable return $ruleWithRemapped; } elseif ($embedData && $needsEmbedFallback) { // Build 2 CSS properties; one which uses a data URI in place of the @embed comment, and // the other with a remapped and versioned URL with an Internet Explorer 6 and 7 hack // making it ignored in all browsers that support data URIs return "{$ruleWithEmbedded};{$ruleWithRemapped}!ie"; } else { // Look ma, no fallbacks! This is for files which IE 6 and 7 don't support anyway: SVG. return $ruleWithEmbedded; } }, $source); // Re-insert comments $pattern = '/' . CSSMin::PLACEHOLDER . '(\\d+)x/'; $source = preg_replace_callback($pattern, function ($match) use(&$comments) { return $comments[$match[1]]; }, $source); return $source; }
/** * @dataProvider provideIsRemoteUrl * @cover CSSMin::isRemoteUrl */ public function testIsRemoteUrl($expect, $url) { $this->assertEquals(CSSMin::isRemoteUrl($url), $expect); }
public static function isRemoteUrl($maybeUrl) { return parent::isRemoteUrl($maybeUrl); }