move( $this->get_converter( $source, $target ), $content, $source ), $source, $target ); } /** * Get an instance of the Converter class * * @param string $source Source filepath. * @param string $target Destination filepath. * @return Converter */ protected function get_converter( $source, $target ) { return new Converter( $source, $target ); } /** * Moving a css file should update all relative urls. * Relative references (e.g. ../images/image.gif) in a certain css file, * will have to be updated when a file is being saved at another location * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). * * Method copied from WP_Rocket\Dependencies\Minify\CSS; * * @param ConverterInterface $converter Relative path converter. * @param string $content The CSS content to update relative urls for. * @param string $source The source path or URL for the CSS file. * * @return string */ protected function move( ConverterInterface $converter, $content, $source ) { /* * Relative path references will usually be enclosed by url(). @import * is an exception, where url() is not necessary around the path (but is * allowed). * This *could* be 1 regular expression, where both regular expressions * in this array are on different sides of a |. But we're using named * patterns in both regexes, the same name on both regexes. This is only * possible with a (?J) modifier, but that only works after a fairly * recent PCRE version. That's why I'm doing 2 separate regular * expressions & combining the matches after executing of both. */ $relative_regexes = [ // url(xxx). '/ # open url() url\( \s* # open path enclosure (?P["\'])? # fetch path (?P.+?) # close path enclosure (?(quotes)(?P=quotes)) \s* # close url() \) /ix', // @import "xxx" '/ # import statement @import # whitespace \s+ # we don\'t have to check for @import url(), because the # condition above will already catch these # open path enclosure (?P["\']) # fetch path (?P.+?) # close path enclosure (?P=quotes) /ix', ]; // find all relative urls in css. $matches = []; foreach ( $relative_regexes as $relative_regex ) { if ( preg_match_all( $relative_regex, $content, $regex_matches, PREG_SET_ORDER ) ) { $matches = array_merge( $matches, $regex_matches ); } } $search = []; $replace = []; // loop all urls. foreach ( $matches as $match ) { // determine if it's a url() or an @import match. $type = ( strpos( $match[0], '@import' ) === 0 ? 'import' : 'url' ); $url = $match['path']; if ( ! preg_match( '/^(data:|https?:|\\/)/', $url ) ) { // attempting to interpret GET-params makes no sense, so let's discard them for awhile. $params = strrchr( $url, '?' ); $url = $params ? substr( $url, 0, -strlen( $params ) ) : $url; // fix relative url. $url = filter_var( $source, FILTER_VALIDATE_URL ) ? dirname( $source ) . '/' . ltrim( $url, '/' ) : $converter->convert( $url ); // now that the path has been converted, re-apply GET-params. $url .= $params; } /* * Urls with control characters above 0x7e should be quoted. * According to Mozilla's parser, whitespace is only allowed at the * end of unquoted urls. * Urls with `)` (as could happen with data: uris) should also be * quoted to avoid being confused for the url() closing parentheses. * And urls with a # have also been reported to cause issues. * Urls with quotes inside should also remain escaped. * * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 * @see https://github.com/matthiasmullie/minify/issues/193 */ $url = trim( $url ); if ( preg_match( '/[\s\)\'"#\x{7f}-\x{9f}]/u', $url ) ) { $url = $match['quotes'] . $url . $match['quotes']; } // build replacement. $search[] = $match[0]; if ( 'url' === $type ) { $replace[] = 'url(' . $url . ')'; } elseif ( 'import' === $type ) { $replace[] = '@import "' . $url . '"'; } } // replace urls. return str_replace( $search, $replace, $content ); } /** * Applies font-display:swap to all font-family rules without a previously set font-display property. * * @since 3.7 * * @param string $css_file_content CSS file content to modify. * * @return string Modified CSS content. */ private function apply_font_display_swap( $css_file_content ) { $css_file_content = (string) $css_file_content; return preg_replace_callback( '/(?:@font-face)\s*{(?[^}]+)}/', function ( $matches ) { if ( false !== strpos( $matches['value'], 'font-display' ) ) { return $matches[0]; } $swap = "font-display:swap;{$matches['value']}"; return str_replace( $matches['value'], $swap, $matches[0] ); }, $css_file_content ); } }