PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
← All changes | vendor/matthiasmullie/minify/src/CSS.php +200 -97 3.2.7 → 4.7.0 View file →
@@ -1,7 +1,8 @@
1 1 <?php
2 +
2 3 /**
3 - * CSS Minifier
4 + * CSS Minifier.
4 5 *
5 6 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6 7 *
7 8 * @author Matthias Mullie <[email protected]>
@@ -11,17 +12,17 @@
11 12
12 13 namespace MatthiasMullie\Minify;
13 14
14 15 use MatthiasMullie\Minify\Exceptions\FileImportException;
16 +use MatthiasMullie\Minify\Exceptions\PatternMatchException;
17 +use MatthiasMullie\PathConverter\Converter;
15 18 use MatthiasMullie\PathConverter\ConverterInterface;
16 -use MatthiasMullie\PathConverter\Converter;
17 19
18 20 /**
19 - * CSS minifier
21 + * CSS minifier.
20 22 *
21 23 * Please report bugs on https://github.com/matthiasmullie/minify/issues
22 24 *
23 - * @package Minify
24 25 * @author Matthias Mullie <[email protected]>
25 26 * @author Tijs Verkoyen <[email protected]>
26 27 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
27 28 * @license MIT License
@@ -96,9 +97,9 @@
96 97 $content = str_replace($import, '', $content);
97 98 }
98 99
99 100 // add to top
100 - $content = implode(';', $matches[2]).';'.trim($content, ';');
101 + $content = implode(';', $matches[2]) . ';' . trim($content, ';');
101 102 }
102 103
103 104 return $content;
104 105 }
@@ -105,13 +106,13 @@
105 106
106 107 /**
107 108 * Combine CSS from import statements.
108 109 *
109 - * @import's will be loaded and their content merged into the original file,
110 + * \@import's will be loaded and their content merged into the original file,
110 111 * to save HTTP requests.
111 112 *
112 - * @param string $source The file to combine imports for
113 - * @param string $content The CSS content to combine imports for
113 + * @param string $source The file to combine imports for
114 + * @param string $content The CSS content to combine imports for
114 115 * @param string[] $parents Parent paths, for circular reference checks
115 116 *
116 117 * @return string
117 118 *
@@ -203,9 +204,9 @@
203 204
204 205 // loop the matches
205 206 foreach ($matches as $match) {
206 207 // get the path for the file that will be imported
207 - $importPath = dirname($source).'/'.$match['path'];
208 + $importPath = dirname($source) . '/' . $match['path'];
208 209
209 210 // only replace the import with the content if we can grab the
210 211 // content of the file
211 212 if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
@@ -214,9 +215,9 @@
214 215
215 216 // check if current file was not imported previously in the same
216 217 // import chain.
217 218 if (in_array($importPath, $parents)) {
218 - throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
219 + throw new FileImportException('Failed to import file "' . $importPath . '": circular reference detected.');
219 220 }
220 221
221 222 // grab referenced file & minify it (which may include importing
222 223 // yet other @import statements recursively)
@@ -226,9 +227,9 @@
226 227 $importContent = $minifier->execute($source, $parents);
227 228
228 229 // check if this is only valid for certain media
229 230 if (!empty($match['media'])) {
230 - $importContent = '@media '.$match['media'].'{'.$importContent.'}';
231 + $importContent = '@media ' . $match['media'] . '{' . $importContent . '}';
231 232 }
232 233
233 234 // add to replacement array
234 235 $search[] = $match[0];
@@ -244,9 +245,9 @@
244 245 *
245 246 * @url(image.jpg) images will be loaded and their content merged into the
246 247 * original file, to save HTTP requests.
247 248 *
248 - * @param string $source The file to import files for
249 + * @param string $source The file to import files for
249 250 * @param string $content The CSS content to import files for
250 251 *
251 252 * @return string
252 253 */
@@ -265,9 +266,9 @@
265 266 }
266 267
267 268 // get the path for the file that will be imported
268 269 $path = $match[2];
269 - $path = dirname($source).'/'.$path;
270 + $path = dirname($source) . '/' . $path;
270 271
271 272 // only replace the import with the content if we're able to get
272 273 // the content of the file, and it's relatively small
273 274 if ($this->canImportFile($path) && $this->canImportBySize($path)) {
@@ -276,9 +277,9 @@
276 277 $importContent = base64_encode($importContent);
277 278
278 279 // build replacement
279 280 $search[] = $match[0];
280 - $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
281 + $replace[] = 'url(' . $this->importExtensions[$extension] . ';base64,' . $importContent . ')';
281 282 }
282 283 }
283 284
284 285 // replace the import statements
@@ -292,11 +293,13 @@
292 293 * Minify the data.
293 294 * Perform CSS optimizations.
294 295 *
295 296 * @param string[optional] $path Path to write the data to
296 - * @param string[] $parents Parent paths, for circular reference checks
297 + * @param string[] $parents Parent paths, for circular reference checks
297 298 *
298 299 * @return string The minified data
300 + *
301 + * @throws PatternMatchException
299 302 */
300 303 public function execute($path = null, $parents = array())
301 304 {
302 305 $content = '';
@@ -315,9 +318,11 @@
315 318 $this->extractCustomProperties();
316 319 $css = $this->replace($css);
317 320
318 321 $css = $this->stripWhitespace($css);
319 - $css = $this->shortenColors($css);
322 + $css = $this->convertLegacyColors($css);
323 + $css = $this->cleanupModernColors($css);
324 + $css = $this->shortenHEXColors($css);
320 325 $css = $this->shortenZeroes($css);
321 326 $css = $this->shortenFontWeights($css);
322 327 $css = $this->stripEmptyTags($css);
323 328
@@ -354,9 +359,9 @@
354 359 * will have to be updated when a file is being saved at another location
355 360 * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
356 361 *
357 362 * @param ConverterInterface $converter Relative path converter
358 - * @param string $content The CSS content to update relative urls for
363 + * @param string $content The CSS content to update relative urls for
359 364 *
360 365 * @return string
361 366 */
362 367 protected function move(ConverterInterface $converter, $content)
@@ -468,11 +473,11 @@
468 473
469 474 // build replacement
470 475 $search[] = $match[0];
471 476 if ($type === 'url') {
472 - $replace[] = 'url('.$url.')';
477 + $replace[] = 'url(' . $url . ')';
473 478 } elseif ($type === 'import') {
474 - $replace[] = '@import "'.$url.'"';
479 + $replace[] = '@import "' . $url . '"';
475 480 }
476 481 }
477 482
478 483 // replace urls
@@ -479,61 +484,86 @@
479 484 return str_replace($search, $replace, $content);
480 485 }
481 486
482 487 /**
483 - * Shorthand hex color codes.
484 - * #FF0000 -> #F00.
488 + * Shorthand HEX color codes.
489 + * #FF0000FF -> #f00 -> red
490 + * #FF00FF00 -> transparent.
485 491 *
486 - * @param string $content The CSS content to shorten the hex color codes for
492 + * @param string $content The CSS content to shorten the HEX color codes for
487 493 *
488 494 * @return string
489 495 */
490 - protected function shortenColors($content)
496 + protected function shortenHexColors($content)
491 497 {
492 - $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content);
498 + // shorten repeating patterns within HEX ..
499 + $content = preg_replace('/(?<=[: ])#([0-9a-f])\\1([0-9a-f])\\2([0-9a-f])\\3(?:([0-9a-f])\\4)?(?=[; }])/i', '#$1$2$3$4', $content);
493 500
494 - // remove alpha channel if it's pointless...
495 - $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content);
496 - $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content);
501 + // remove alpha channel if it's pointless ..
502 + $content = preg_replace('/(?<=[: ])#([0-9a-f]{6})ff(?=[; }])/i', '#$1', $content);
503 + $content = preg_replace('/(?<=[: ])#([0-9a-f]{3})f(?=[; }])/i', '#$1', $content);
497 504
505 + // replace `transparent` with shortcut ..
506 + $content = preg_replace('/(?<=[: ])#[0-9a-f]{6}00(?=[; }])/i', '#fff0', $content);
507 +
498 508 $colors = array(
509 + // make these more readable
510 + '#00f' => 'blue',
511 + '#dc143c' => 'crimson',
512 + '#0ff' => 'cyan',
513 + '#8b0000' => 'darkred',
514 + '#696969' => 'dimgray',
515 + '#ff69b4' => 'hotpink',
516 + '#0f0' => 'lime',
517 + '#fdf5e6' => 'oldlace',
518 + '#87ceeb' => 'skyblue',
519 + '#d8bfd8' => 'thistle',
499 520 // we can shorten some even more by replacing them with their color name
500 - '#F0FFFF' => 'azure',
501 - '#F5F5DC' => 'beige',
502 - '#A52A2A' => 'brown',
503 - '#FF7F50' => 'coral',
504 - '#FFD700' => 'gold',
521 + '#f0ffff' => 'azure',
522 + '#f5f5dc' => 'beige',
523 + '#ffe4c4' => 'bisque',
524 + '#a52a2a' => 'brown',
525 + '#ff7f50' => 'coral',
526 + '#ffd700' => 'gold',
505 527 '#808080' => 'gray',
506 528 '#008000' => 'green',
507 - '#4B0082' => 'indigo',
508 - '#FFFFF0' => 'ivory',
509 - '#F0E68C' => 'khaki',
510 - '#FAF0E6' => 'linen',
529 + '#4b0082' => 'indigo',
530 + '#fffff0' => 'ivory',
531 + '#f0e68c' => 'khaki',
532 + '#faf0e6' => 'linen',
511 533 '#800000' => 'maroon',
512 534 '#000080' => 'navy',
513 535 '#808000' => 'olive',
514 - '#CD853F' => 'peru',
515 - '#FFC0CB' => 'pink',
516 - '#DDA0DD' => 'plum',
536 + '#ffa500' => 'orange',
537 + '#da70d6' => 'orchid',
538 + '#cd853f' => 'peru',
539 + '#ffc0cb' => 'pink',
540 + '#dda0dd' => 'plum',
517 541 '#800080' => 'purple',
518 - '#F00' => 'red',
519 - '#FA8072' => 'salmon',
520 - '#A0522D' => 'sienna',
521 - '#C0C0C0' => 'silver',
522 - '#FFFAFA' => 'snow',
523 - '#D2B48C' => 'tan',
524 - '#FF6347' => 'tomato',
525 - '#EE82EE' => 'violet',
526 - '#F5DEB3' => 'wheat',
542 + '#f00' => 'red',
543 + '#fa8072' => 'salmon',
544 + '#a0522d' => 'sienna',
545 + '#c0c0c0' => 'silver',
546 + '#fffafa' => 'snow',
547 + '#d2b48c' => 'tan',
548 + '#008080' => 'teal',
549 + '#ff6347' => 'tomato',
550 + '#ee82ee' => 'violet',
551 + '#f5deb3' => 'wheat',
527 552 // or the other way around
528 - 'WHITE' => '#fff',
529 - 'BLACK' => '#000',
553 + 'black' => '#000',
554 + 'fuchsia' => '#f0f',
555 + 'magenta' => '#f0f',
556 + 'white' => '#fff',
557 + 'yellow' => '#ff0',
558 + // and also `transparent`
559 + 'transparent' => '#fff0',
530 560 );
531 561
532 562 return preg_replace_callback(
533 - '/(?<=[: ])('.implode('|', array_keys($colors)).')(?=[; }])/i',
563 + '/(?<=[: ])(' . implode('|', array_keys($colors)) . ')(?=[; }])/i',
534 564 function ($match) use ($colors) {
535 - return $colors[strtoupper($match[0])];
565 + return $colors[strtolower($match[0])];
536 566 },
537 567 $content
538 568 );
539 569 }
@@ -538,8 +568,73 @@
538 568 );
539 569 }
540 570
541 571 /**
572 + * Convert RGB|HSL color codes.
573 + * rgb(255,0,0,.5) -> rgb(255 0 0 / .5).
574 + * rgb(255,0,0) -> #f00.
575 + *
576 + * @param string $content The CSS content to shorten the RGB color codes for
577 + *
578 + * @return string
579 + */
580 + protected function convertLegacyColors($content)
581 + {
582 + /*
583 + https://drafts.csswg.org/css-color/#color-syntax-legacy
584 + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb
585 + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl
586 + */
587 +
588 + // convert legacy color syntax
589 + $content = preg_replace('/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content);
590 + $content = preg_replace('/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*\)/i', '$1($2 $3 $4)', $content);
591 + $content = preg_replace('/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content);
592 + $content = preg_replace('/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*\)/i', '$1($2 $3 $4)', $content);
593 +
594 + // convert `rgb` to `hex`
595 + $dec = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])';
596 +
597 + return preg_replace_callback(
598 + "/rgb\($dec $dec $dec\)/i",
599 + function ($match) {
600 + return sprintf('#%02x%02x%02x', $match[1], $match[2], $match[3]);
601 + },
602 + $content
603 + );
604 + }
605 +
606 + /**
607 + * Cleanup RGB|HSL|HWB|LCH|LAB
608 + * rgb(255 0 0 / 1) -> rgb(255 0 0).
609 + * rgb(255 0 0 / 0) -> transparent.
610 + *
611 + * @param string $content The CSS content to cleanup HSL|HWB|LCH|LAB
612 + *
613 + * @return string
614 + */
615 + protected function cleanupModernColors($content)
616 + {
617 + /*
618 + https://drafts.csswg.org/css-color/#color-syntax-modern
619 + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb
620 + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch
621 + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab
622 + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch
623 + https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab
624 + */
625 + $tag = '(rgb|hsl|hwb|(?:(?:ok)?(?:lch|lab)))';
626 +
627 + // remove alpha channel if it's pointless ..
628 + $content = preg_replace('/' . $tag . '\(\s*([^\s)]+)\s+([^\s)]+)\s+([^\s)]+)\s+\/\s+1(?:(?:\.\d?)*|00%)?\s*\)/i', '$1($2 $3 $4)', $content);
629 +
630 + // replace `transparent` with shortcut ..
631 + $content = preg_replace('/' . $tag . '\(\s*[^\s)]+\s+[^\s)]+\s+[^\s)]+\s+\/\s+0(?:[\.0%]*)?\s*\)/i', '#fff0', $content);
632 +
633 + return $content;
634 + }
635 +
636 + /**
542 637 * Shorten CSS font weights.
543 638 *
544 639 * @param string $content The CSS content to shorten the font weights for
545 640 *
@@ -552,12 +647,12 @@
552 647 'bold' => 700,
553 648 );
554 649
555 650 $callback = function ($match) use ($weights) {
556 - return $match[1].$weights[$match[2]];
651 + return $match[1] . $weights[$match[2]];
557 652 };
558 653
559 - return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
654 + return preg_replace_callback('/(font-weight\s*:\s*)(' . implode('|', array_keys($weights)) . ')(?=[;}])/', $callback, $content);
560 655 }
561 656
562 657 /**
563 658 * Shorthand 0 values to plain 0, instead of e.g. -0em.
@@ -587,21 +682,21 @@
587 682 // NOTE: it should be safe to remove all units for a 0 value, but in
588 683 // practice, Webkit (especially Safari) seems to stumble over at least
589 684 // 0%, potentially other units as well. Only stripping 'px' for now.
590 685 // @see https://github.com/matthiasmullie/minify/issues/60
591 - $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
686 + $content = preg_replace('/' . $before . '(-?0*(\.0+)?)(?<=0)px' . $after . '/', '\\1', $content);
592 687
593 688 // strip 0-digits (.0 -> 0)
594 - $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
689 + $content = preg_replace('/' . $before . '\.0+' . $units . '?' . $after . '/', '0\\1', $content);
595 690 // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
596 - $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
691 + $content = preg_replace('/' . $before . '(-?[0-9]+\.[0-9]+)0+' . $units . '?' . $after . '/', '\\1\\2', $content);
597 692 // strip trailing 0: 50.00 -> 50, 50.00px -> 50px
598 - $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
693 + $content = preg_replace('/' . $before . '(-?[0-9]+)\.0+' . $units . '?' . $after . '/', '\\1\\2', $content);
599 694 // strip leading 0: 0.1 -> .1, 01.1 -> 1.1
600 - $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
695 + $content = preg_replace('/' . $before . '(-?)0+([0-9]*\.[0-9]+)' . $units . '?' . $after . '/', '\\1\\2\\3', $content);
601 696
602 697 // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
603 - $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
698 + $content = preg_replace('/' . $before . '-?0+' . $units . '?' . $after . '/', '0\\1', $content);
604 699
605 700 // IE doesn't seem to understand a unitless flex-basis value (correct -
606 701 // it goes against the spec), so let's add it in again (make it `%`,
607 702 // which is only 1 char: 0%, 0px, 0 anything, it's all just the same)
@@ -631,20 +726,9 @@
631 726 * Strip comments from source code.
632 727 */
633 728 protected function stripComments()
634 729 {
635 - // PHP only supports $this inside anonymous functions since 5.4
636 - $minifier = $this;
637 - $callback = function ($match) use ($minifier) {
638 - $count = count($minifier->extracted);
639 - $placeholder = '/*'.$count.'*/';
640 - $minifier->extracted[$placeholder] = $match[0];
641 -
642 - return $placeholder;
643 - };
644 - $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
645 -
646 - $this->registerPattern('/\/\*.*?\*\//s', '');
730 + $this->stripMultilineComments();
647 731 }
648 732
649 733 /**
650 734 * Strip whitespace.
@@ -651,24 +735,26 @@
651 735 *
652 736 * @param string $content The CSS content to strip the whitespace for
653 737 *
654 738 * @return string
739 + *
740 + * @throws PatternMatchException
655 741 */
656 742 protected function stripWhitespace($content)
657 743 {
658 744 // remove leading & trailing whitespace
659 - $content = preg_replace('/^\s*/m', '', $content);
660 - $content = preg_replace('/\s*$/m', '', $content);
745 + $content = $this->pregReplace('/^\s*/m', '', $content);
746 + $content = $this->pregReplace('/\s*$/m', '', $content);
661 747
662 748 // replace newlines with a single space
663 - $content = preg_replace('/\s+/', ' ', $content);
749 + $content = $this->pregReplace('/\s+/', ' ', $content);
664 750
665 751 // remove whitespace around meta characters
666 752 // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
667 - $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
668 - $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content);
669 - $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content);
670 - $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
753 + $content = $this->pregReplace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
754 + $content = $this->pregReplace('/([\[(:>\+])\s+/', '$1', $content);
755 + $content = $this->pregReplace('/\s+([\]\)>\+])/', '$1', $content);
756 + $content = $this->pregReplace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
671 757
672 758 // whitespace around + and - can only be stripped inside some pseudo-
673 759 // classes, like `:nth-child(3+2n)`
674 760 // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
@@ -673,9 +759,9 @@
673 759 // classes, like `:nth-child(3+2n)`
674 760 // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
675 761 // selectors like `div.weird- p`
676 762 $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type');
677 - $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
763 + $content = $this->pregReplace('/:(' . implode('|', $pseudos) . ')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
678 764
679 765 // remove semicolon/whitespace followed by closing bracket
680 766 $content = str_replace(';}', '}', $content);
681 767
@@ -682,15 +768,36 @@
682 768 return trim($content);
683 769 }
684 770
685 771 /**
772 + * Perform a preg_replace and check for errors.
773 + *
774 + * @param string $pattern Pattern
775 + * @param string $replacement Replacement
776 + * @param string $subject String to process
777 + *
778 + * @return string
779 + *
780 + * @throws PatternMatchException
781 + */
782 + protected function pregReplace($pattern, $replacement, $subject)
783 + {
784 + $result = preg_replace($pattern, $replacement, $subject);
785 + if ($result === null) {
786 + throw PatternMatchException::fromLastError("Failed to replace with pattern '$pattern'");
787 + }
788 +
789 + return $result;
790 + }
791 +
792 + /**
686 793 * Replace all occurrences of functions that may contain math, where
687 - * whitespace around operators needs to be preserved (e.g. calc, clamp)
794 + * whitespace around operators needs to be preserved (e.g. calc, clamp).
688 795 */
689 796 protected function extractMath()
690 797 {
691 798 $functions = array('calc', 'clamp', 'min', 'max');
692 - $pattern = '/\b('. implode('|', $functions) .')(\(.+?)(?=$|;|})/m';
799 + $pattern = '/\b(' . implode('|', $functions) . ')(\(.+?)(?=$|;|})/m';
693 800
694 801 // PHP only supports $this inside anonymous functions since 5.4
695 802 $minifier = $this;
696 803 $callback = function ($match) use ($minifier, $pattern, &$callback) {
@@ -703,13 +810,13 @@
703 810 // it can't determine the correct closing parenthesis...
704 811 // instead, it'll match a larger portion of code to where it's certain that
705 812 // the calc() musts have ended, and we'll figure out which is the correct
706 813 // closing parenthesis here, by counting how many have opened
707 - for ($i = 0; $i < $length; $i++) {
814 + for ($i = 0; $i < $length; ++$i) {
708 815 $char = $match[2][$i];
709 816 $expr .= $char;
710 817 if ($char === '(') {
711 - $opened++;
818 + ++$opened;
712 819 } elseif ($char === ')' && --$opened === 0) {
713 820 break;
714 821 }
715 822 }
@@ -715,22 +822,18 @@
715 822 }
716 823
717 824 // now that we've figured out where the calc() starts and ends, extract it
718 825 $count = count($minifier->extracted);
719 - $placeholder = 'math('.$count.')';
720 - $minifier->extracted[$placeholder] = $function.'('.trim(substr($expr, 1, -1)).')';
826 + $placeholder = 'math(' . $count . ')';
827 + $minifier->extracted[$placeholder] = $function . '(' . trim(substr($expr, 1, -1)) . ')';
721 828
722 829 // and since we've captured more code than required, we may have some leftover
723 830 // calc() in here too - go recursive on the remaining but of code to go figure
724 831 // that out and extract what is needed
725 - $rest = "";
726 - $pos = strpos($match[0], $function.$expr);
727 - if ($pos !== false) {
728 - $rest = substr_replace($match[0], '', $pos, strlen($function.$expr));
729 - }
832 + $rest = $minifier->str_replace_first($function . $expr, '', $match[0]);
730 833 $rest = preg_replace_callback($pattern, $callback, $rest);
731 834
732 - return $placeholder.$rest;
835 + return $placeholder . $rest;
733 836 };
734 837
735 838 $this->registerPattern($pattern, $callback);
736 839 }
@@ -736,9 +839,9 @@
736 839 }
737 840
738 841 /**
739 842 * Replace custom properties, whose values may be used in scenarios where
740 - * we wouldn't want them to be minified (e.g. inside calc)
843 + * we wouldn't want them to be minified (e.g. inside calc).
741 844 */
742 845 protected function extractCustomProperties()
743 846 {
744 847 // PHP only supports $this inside anonymous functions since 5.4
@@ -743,14 +846,14 @@
743 846 {
744 847 // PHP only supports $this inside anonymous functions since 5.4
745 848 $minifier = $this;
746 849 $this->registerPattern(
747 - '/(?<=^|[;}])(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m',
850 + '/(?<=^|[;}{])\s*(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m',
748 851 function ($match) use ($minifier) {
749 - $placeholder = '--custom-'. count($minifier->extracted) . ':0';
750 - $minifier->extracted[$placeholder] = $match[1] .':'. trim($match[2]);
852 + $placeholder = '--custom-' . count($minifier->extracted) . ':0';
853 + $minifier->extracted[$placeholder] = $match[1] . ':' . trim($match[2]);
854 +
751 855 return $placeholder;
752 -
753 856 }
754 857 );
755 858 }
756 859