See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 /** 3 * CSS Minifier 4 * 5 * Please report bugs on https://github.com/matthiasmullie/minify/issues 6 * 7 * @author Matthias Mullie <minify@mullie.eu> 8 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 9 * @license MIT License 10 */ 11 12 namespace MatthiasMullie\Minify; 13 14 use MatthiasMullie\Minify\Exceptions\FileImportException; 15 use MatthiasMullie\PathConverter\ConverterInterface; 16 use MatthiasMullie\PathConverter\Converter; 17 18 /** 19 * CSS minifier 20 * 21 * Please report bugs on https://github.com/matthiasmullie/minify/issues 22 * 23 * @package Minify 24 * @author Matthias Mullie <minify@mullie.eu> 25 * @author Tijs Verkoyen <minify@verkoyen.eu> 26 * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 27 * @license MIT License 28 */ 29 class CSS extends Minify 30 { 31 /** 32 * @var int maximum inport size in kB 33 */ 34 protected $maxImportSize = 5; 35 36 /** 37 * @var string[] valid import extensions 38 */ 39 protected $importExtensions = array( 40 'gif' => 'data:image/gif', 41 'png' => 'data:image/png', 42 'jpe' => 'data:image/jpeg', 43 'jpg' => 'data:image/jpeg', 44 'jpeg' => 'data:image/jpeg', 45 'svg' => 'data:image/svg+xml', 46 'woff' => 'data:application/x-font-woff', 47 'woff2' => 'data:application/x-font-woff2', 48 'avif' => 'data:image/avif', 49 'apng' => 'data:image/apng', 50 'webp' => 'data:image/webp', 51 'tif' => 'image/tiff', 52 'tiff' => 'image/tiff', 53 'xbm' => 'image/x-xbitmap', 54 ); 55 56 /** 57 * Set the maximum size if files to be imported. 58 * 59 * Files larger than this size (in kB) will not be imported into the CSS. 60 * Importing files into the CSS as data-uri will save you some connections, 61 * but we should only import relatively small decorative images so that our 62 * CSS file doesn't get too bulky. 63 * 64 * @param int $size Size in kB 65 */ 66 public function setMaxImportSize($size) 67 { 68 $this->maxImportSize = $size; 69 } 70 71 /** 72 * Set the type of extensions to be imported into the CSS (to save network 73 * connections). 74 * Keys of the array should be the file extensions & respective values 75 * should be the data type. 76 * 77 * @param string[] $extensions Array of file extensions 78 */ 79 public function setImportExtensions(array $extensions) 80 { 81 $this->importExtensions = $extensions; 82 } 83 84 /** 85 * Move any import statements to the top. 86 * 87 * @param string $content Nearly finished CSS content 88 * 89 * @return string 90 */ 91 protected function moveImportsToTop($content) 92 { 93 if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) { 94 // remove from content 95 foreach ($matches[0] as $import) { 96 $content = str_replace($import, '', $content); 97 } 98 99 // add to top 100 $content = implode(';', $matches[2]).';'.trim($content, ';'); 101 } 102 103 return $content; 104 } 105 106 /** 107 * Combine CSS from import statements. 108 * 109 * @import's will be loaded and their content merged into the original file, 110 * to save HTTP requests. 111 * 112 * @param string $source The file to combine imports for 113 * @param string $content The CSS content to combine imports for 114 * @param string[] $parents Parent paths, for circular reference checks 115 * 116 * @return string 117 * 118 * @throws FileImportException 119 */ 120 protected function combineImports($source, $content, $parents) 121 { 122 $importRegexes = array( 123 // @import url(xxx) 124 '/ 125 # import statement 126 @import 127 128 # whitespace 129 \s+ 130 131 # open url() 132 url\( 133 134 # (optional) open path enclosure 135 (?P<quotes>["\']?) 136 137 # fetch path 138 (?P<path>.+?) 139 140 # (optional) close path enclosure 141 (?P=quotes) 142 143 # close url() 144 \) 145 146 # (optional) trailing whitespace 147 \s* 148 149 # (optional) media statement(s) 150 (?P<media>[^;]*) 151 152 # (optional) trailing whitespace 153 \s* 154 155 # (optional) closing semi-colon 156 ;? 157 158 /ix', 159 160 // @import 'xxx' 161 '/ 162 163 # import statement 164 @import 165 166 # whitespace 167 \s+ 168 169 # open path enclosure 170 (?P<quotes>["\']) 171 172 # fetch path 173 (?P<path>.+?) 174 175 # close path enclosure 176 (?P=quotes) 177 178 # (optional) trailing whitespace 179 \s* 180 181 # (optional) media statement(s) 182 (?P<media>[^;]*) 183 184 # (optional) trailing whitespace 185 \s* 186 187 # (optional) closing semi-colon 188 ;? 189 190 /ix', 191 ); 192 193 // find all relative imports in css 194 $matches = array(); 195 foreach ($importRegexes as $importRegex) { 196 if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) { 197 $matches = array_merge($matches, $regexMatches); 198 } 199 } 200 201 $search = array(); 202 $replace = array(); 203 204 // loop the matches 205 foreach ($matches as $match) { 206 // get the path for the file that will be imported 207 $importPath = dirname($source).'/'.$match['path']; 208 209 // only replace the import with the content if we can grab the 210 // content of the file 211 if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) { 212 continue; 213 } 214 215 // check if current file was not imported previously in the same 216 // import chain. 217 if (in_array($importPath, $parents)) { 218 throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.'); 219 } 220 221 // grab referenced file & minify it (which may include importing 222 // yet other @import statements recursively) 223 $minifier = new self($importPath); 224 $minifier->setMaxImportSize($this->maxImportSize); 225 $minifier->setImportExtensions($this->importExtensions); 226 $importContent = $minifier->execute($source, $parents); 227 228 // check if this is only valid for certain media 229 if (!empty($match['media'])) { 230 $importContent = '@media '.$match['media'].'{'.$importContent.'}'; 231 } 232 233 // add to replacement array 234 $search[] = $match[0]; 235 $replace[] = $importContent; 236 } 237 238 // replace the import statements 239 return str_replace($search, $replace, $content); 240 } 241 242 /** 243 * Import files into the CSS, base64-ized. 244 * 245 * @url(image.jpg) images will be loaded and their content merged into the 246 * original file, to save HTTP requests. 247 * 248 * @param string $source The file to import files for 249 * @param string $content The CSS content to import files for 250 * 251 * @return string 252 */ 253 protected function importFiles($source, $content) 254 { 255 $regex = '/url\((["\']?)(.+?)\\1\)/i'; 256 if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { 257 $search = array(); 258 $replace = array(); 259 260 // loop the matches 261 foreach ($matches as $match) { 262 $extension = substr(strrchr($match[2], '.'), 1); 263 if ($extension && !array_key_exists($extension, $this->importExtensions)) { 264 continue; 265 } 266 267 // get the path for the file that will be imported 268 $path = $match[2]; 269 $path = dirname($source).'/'.$path; 270 271 // only replace the import with the content if we're able to get 272 // the content of the file, and it's relatively small 273 if ($this->canImportFile($path) && $this->canImportBySize($path)) { 274 // grab content && base64-ize 275 $importContent = $this->load($path); 276 $importContent = base64_encode($importContent); 277 278 // build replacement 279 $search[] = $match[0]; 280 $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')'; 281 } 282 } 283 284 // replace the import statements 285 $content = str_replace($search, $replace, $content); 286 } 287 288 return $content; 289 } 290 291 /** 292 * Minify the data. 293 * Perform CSS optimizations. 294 * 295 * @param string[optional] $path Path to write the data to 296 * @param string[] $parents Parent paths, for circular reference checks 297 * 298 * @return string The minified data 299 */ 300 public function execute($path = null, $parents = array()) 301 { 302 $content = ''; 303 304 // loop CSS data (raw data and files) 305 foreach ($this->data as $source => $css) { 306 /* 307 * Let's first take out strings & comments, since we can't just 308 * remove whitespace anywhere. If whitespace occurs inside a string, 309 * we should leave it alone. E.g.: 310 * p { content: "a test" } 311 */ 312 $this->extractStrings(); 313 $this->stripComments(); 314 $this->extractMath(); 315 $this->extractCustomProperties(); 316 $css = $this->replace($css); 317 318 $css = $this->stripWhitespace($css); 319 $css = $this->shortenColors($css); 320 $css = $this->shortenZeroes($css); 321 $css = $this->shortenFontWeights($css); 322 $css = $this->stripEmptyTags($css); 323 324 // restore the string we've extracted earlier 325 $css = $this->restoreExtractedData($css); 326 327 $source = is_int($source) ? '' : $source; 328 $parents = $source ? array_merge($parents, array($source)) : $parents; 329 $css = $this->combineImports($source, $css, $parents); 330 $css = $this->importFiles($source, $css); 331 332 /* 333 * If we'll save to a new path, we'll have to fix the relative paths 334 * to be relative no longer to the source file, but to the new path. 335 * If we don't write to a file, fall back to same path so no 336 * conversion happens (because we still want it to go through most 337 * of the move code, which also addresses url() & @import syntax...) 338 */ 339 $converter = $this->getPathConverter($source, $path ?: $source); 340 $css = $this->move($converter, $css); 341 342 // combine css 343 $content .= $css; 344 } 345 346 $content = $this->moveImportsToTop($content); 347 348 return $content; 349 } 350 351 /** 352 * Moving a css file should update all relative urls. 353 * Relative references (e.g. ../images/image.gif) in a certain css file, 354 * will have to be updated when a file is being saved at another location 355 * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). 356 * 357 * @param ConverterInterface $converter Relative path converter 358 * @param string $content The CSS content to update relative urls for 359 * 360 * @return string 361 */ 362 protected function move(ConverterInterface $converter, $content) 363 { 364 /* 365 * Relative path references will usually be enclosed by url(). @import 366 * is an exception, where url() is not necessary around the path (but is 367 * allowed). 368 * This *could* be 1 regular expression, where both regular expressions 369 * in this array are on different sides of a |. But we're using named 370 * patterns in both regexes, the same name on both regexes. This is only 371 * possible with a (?J) modifier, but that only works after a fairly 372 * recent PCRE version. That's why I'm doing 2 separate regular 373 * expressions & combining the matches after executing of both. 374 */ 375 $relativeRegexes = array( 376 // url(xxx) 377 '/ 378 # open url() 379 url\( 380 381 \s* 382 383 # open path enclosure 384 (?P<quotes>["\'])? 385 386 # fetch path 387 (?P<path>.+?) 388 389 # close path enclosure 390 (?(quotes)(?P=quotes)) 391 392 \s* 393 394 # close url() 395 \) 396 397 /ix', 398 399 // @import "xxx" 400 '/ 401 # import statement 402 @import 403 404 # whitespace 405 \s+ 406 407 # we don\'t have to check for @import url(), because the 408 # condition above will already catch these 409 410 # open path enclosure 411 (?P<quotes>["\']) 412 413 # fetch path 414 (?P<path>.+?) 415 416 # close path enclosure 417 (?P=quotes) 418 419 /ix', 420 ); 421 422 // find all relative urls in css 423 $matches = array(); 424 foreach ($relativeRegexes as $relativeRegex) { 425 if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) { 426 $matches = array_merge($matches, $regexMatches); 427 } 428 } 429 430 $search = array(); 431 $replace = array(); 432 433 // loop all urls 434 foreach ($matches as $match) { 435 // determine if it's a url() or an @import match 436 $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url'); 437 438 $url = $match['path']; 439 if ($this->canImportByPath($url)) { 440 // attempting to interpret GET-params makes no sense, so let's discard them for awhile 441 $params = strrchr($url, '?'); 442 $url = $params ? substr($url, 0, -strlen($params)) : $url; 443 444 // fix relative url 445 $url = $converter->convert($url); 446 447 // now that the path has been converted, re-apply GET-params 448 $url .= $params; 449 } 450 451 /* 452 * Urls with control characters above 0x7e should be quoted. 453 * According to Mozilla's parser, whitespace is only allowed at the 454 * end of unquoted urls. 455 * Urls with `)` (as could happen with data: uris) should also be 456 * quoted to avoid being confused for the url() closing parentheses. 457 * And urls with a # have also been reported to cause issues. 458 * Urls with quotes inside should also remain escaped. 459 * 460 * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation 461 * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 462 * @see https://github.com/matthiasmullie/minify/issues/193 463 */ 464 $url = trim($url); 465 if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) { 466 $url = $match['quotes'] . $url . $match['quotes']; 467 } 468 469 // build replacement 470 $search[] = $match[0]; 471 if ($type === 'url') { 472 $replace[] = 'url('.$url.')'; 473 } elseif ($type === 'import') { 474 $replace[] = '@import "'.$url.'"'; 475 } 476 } 477 478 // replace urls 479 return str_replace($search, $replace, $content); 480 } 481 482 /** 483 * Shorthand hex color codes. 484 * #FF0000 -> #F00. 485 * 486 * @param string $content The CSS content to shorten the hex color codes for 487 * 488 * @return string 489 */ 490 protected function shortenColors($content) 491 { 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); 493 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); 497 498 $colors = array( 499 // 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', 505 '#808080' => 'gray', 506 '#008000' => 'green', 507 '#4B0082' => 'indigo', 508 '#FFFFF0' => 'ivory', 509 '#F0E68C' => 'khaki', 510 '#FAF0E6' => 'linen', 511 '#800000' => 'maroon', 512 '#000080' => 'navy', 513 '#808000' => 'olive', 514 '#CD853F' => 'peru', 515 '#FFC0CB' => 'pink', 516 '#DDA0DD' => 'plum', 517 '#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', 527 // or the other way around 528 'WHITE' => '#fff', 529 'BLACK' => '#000', 530 ); 531 532 return preg_replace_callback( 533 '/(?<=[: ])('.implode('|', array_keys($colors)).')(?=[; }])/i', 534 function ($match) use ($colors) { 535 return $colors[strtoupper($match[0])]; 536 }, 537 $content 538 ); 539 } 540 541 /** 542 * Shorten CSS font weights. 543 * 544 * @param string $content The CSS content to shorten the font weights for 545 * 546 * @return string 547 */ 548 protected function shortenFontWeights($content) 549 { 550 $weights = array( 551 'normal' => 400, 552 'bold' => 700, 553 ); 554 555 $callback = function ($match) use ($weights) { 556 return $match[1].$weights[$match[2]]; 557 }; 558 559 return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content); 560 } 561 562 /** 563 * Shorthand 0 values to plain 0, instead of e.g. -0em. 564 * 565 * @param string $content The CSS content to shorten the zero values for 566 * 567 * @return string 568 */ 569 protected function shortenZeroes($content) 570 { 571 // we don't want to strip units in `calc()` expressions: 572 // `5px - 0px` is valid, but `5px - 0` is not 573 // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but 574 // `10 * 0` is invalid 575 // we've extracted calcs earlier, so we don't need to worry about this 576 577 // reusable bits of code throughout these regexes: 578 // before & after are used to make sure we don't match lose unintended 579 // 0-like values (e.g. in #000, or in http://url/1.0) 580 // units can be stripped from 0 values, or used to recognize non 0 581 // values (where wa may be able to strip a .0 suffix) 582 $before = '(?<=[:(, ])'; 583 $after = '(?=[ ,);}])'; 584 $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)'; 585 586 // strip units after zeroes (0px -> 0) 587 // NOTE: it should be safe to remove all units for a 0 value, but in 588 // practice, Webkit (especially Safari) seems to stumble over at least 589 // 0%, potentially other units as well. Only stripping 'px' for now. 590 // @see https://github.com/matthiasmullie/minify/issues/60 591 $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content); 592 593 // strip 0-digits (.0 -> 0) 594 $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content); 595 // 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); 597 // strip trailing 0: 50.00 -> 50, 50.00px -> 50px 598 $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content); 599 // 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); 601 602 // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) 603 $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content); 604 605 // IE doesn't seem to understand a unitless flex-basis value (correct - 606 // it goes against the spec), so let's add it in again (make it `%`, 607 // which is only 1 char: 0%, 0px, 0 anything, it's all just the same) 608 // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex 609 $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:$1}0%$2}', $content); 610 $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%$1}', $content); 611 612 return $content; 613 } 614 615 /** 616 * Strip empty tags from source code. 617 * 618 * @param string $content 619 * 620 * @return string 621 */ 622 protected function stripEmptyTags($content) 623 { 624 $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content); 625 $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content); 626 627 return $content; 628 } 629 630 /** 631 * Strip comments from source code. 632 */ 633 protected function stripComments() 634 { 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 // Moodle-specific change MDL-68191 starts. 645 /* This was the old code: 646 $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); 647 */ 648 // This is the new, more accurate and faster regex. 649 $this->registerPattern('/ 650 # optional newline 651 \n? 652 653 # start comment 654 \/\* 655 656 # comment content 657 (?: 658 # either starts with an ! 659 ! 660 | 661 # or, after some number of characters which do not end the comment 662 (?:(?!\*\/).)*? 663 664 # there is either a @license or @preserve tag 665 @(?:license|preserve) 666 ) 667 668 # then match to the end of the comment 669 .*?\*\/\n? 670 671 /ixs', $callback); 672 // Moodle-specific change MDL-68191. 673 674 $this->registerPattern('/\/\*.*?\*\//s', ''); 675 } 676 677 /** 678 * Strip whitespace. 679 * 680 * @param string $content The CSS content to strip the whitespace for 681 * 682 * @return string 683 */ 684 protected function stripWhitespace($content) 685 { 686 // remove leading & trailing whitespace 687 $content = preg_replace('/^\s*/m', '', $content); 688 $content = preg_replace('/\s*$/m', '', $content); 689 690 // replace newlines with a single space 691 $content = preg_replace('/\s+/', ' ', $content); 692 693 // remove whitespace around meta characters 694 // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex 695 $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); 696 $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content); 697 $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content); 698 $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content); 699 700 // whitespace around + and - can only be stripped inside some pseudo- 701 // classes, like `:nth-child(3+2n)` 702 // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or 703 // selectors like `div.weird- p` 704 $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type'); 705 $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); 706 707 // remove semicolon/whitespace followed by closing bracket 708 $content = str_replace(';}', '}', $content); 709 710 return trim($content); 711 } 712 713 /** 714 * Replace all occurrences of functions that may contain math, where 715 * whitespace around operators needs to be preserved (e.g. calc, clamp) 716 */ 717 protected function extractMath() 718 { 719 $functions = array('calc', 'clamp', 'min', 'max'); 720 $pattern = '/\b('. implode('|', $functions) .')(\(.+?)(?=$|;|})/m'; 721 722 // PHP only supports $this inside anonymous functions since 5.4 723 $minifier = $this; 724 $callback = function ($match) use ($minifier, $pattern, &$callback) { 725 $function = $match[1]; 726 $length = strlen($match[2]); 727 $expr = ''; 728 $opened = 0; 729 730 // the regular expression for extracting math has 1 significant problem: 731 // it can't determine the correct closing parenthesis... 732 // instead, it'll match a larger portion of code to where it's certain that 733 // the calc() musts have ended, and we'll figure out which is the correct 734 // closing parenthesis here, by counting how many have opened 735 for ($i = 0; $i < $length; $i++) { 736 $char = $match[2][$i]; 737 $expr .= $char; 738 if ($char === '(') { 739 $opened++; 740 } elseif ($char === ')' && --$opened === 0) { 741 break; 742 } 743 } 744 745 // now that we've figured out where the calc() starts and ends, extract it 746 $count = count($minifier->extracted); 747 $placeholder = 'math('.$count.')'; 748 $minifier->extracted[$placeholder] = $function.'('.trim(substr($expr, 1, -1)).')'; 749 750 // and since we've captured more code than required, we may have some leftover 751 // calc() in here too - go recursive on the remaining but of code to go figure 752 // that out and extract what is needed 753 $rest = $minifier->str_replace_first($function.$expr, '', $match[0]); 754 $rest = preg_replace_callback($pattern, $callback, $rest); 755 756 return $placeholder.$rest; 757 }; 758 759 $this->registerPattern($pattern, $callback); 760 } 761 762 /** 763 * Replace custom properties, whose values may be used in scenarios where 764 * we wouldn't want them to be minified (e.g. inside calc) 765 */ 766 protected function extractCustomProperties() 767 { 768 // PHP only supports $this inside anonymous functions since 5.4 769 $minifier = $this; 770 $this->registerPattern( 771 772 '/(?<=^|[;}{])\s*(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m', 773 function ($match) use ($minifier) { 774 $placeholder = '--custom-'. count($minifier->extracted) . ':0'; 775 $minifier->extracted[$placeholder] = $match[1] .':'. trim($match[2]); 776 return $placeholder; 777 778 } 779 ); 780 } 781 782 /** 783 * Check if file is small enough to be imported. 784 * 785 * @param string $path The path to the file 786 * 787 * @return bool 788 */ 789 protected function canImportBySize($path) 790 { 791 return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024; 792 } 793 794 /** 795 * Check if file a file can be imported, going by the path. 796 * 797 * @param string $path 798 * 799 * @return bool 800 */ 801 protected function canImportByPath($path) 802 { 803 return preg_match('/^(data:|https?:|\\/)/', $path) === 0; 804 } 805 806 /** 807 * Return a converter to update relative paths to be relative to the new 808 * destination. 809 * 810 * @param string $source 811 * @param string $target 812 * 813 * @return ConverterInterface 814 */ 815 protected function getPathConverter($source, $target) 816 { 817 return new Converter($source, $target); 818 } 819 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body