PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.20
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.20
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
wp-optimize / vendor / matthiasmullie / minify / src / CSS.php

CSS.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.20, at vendor/matthiasmullie/minify/src/CSS.php

795 lines 26.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CSS Minifier
4 *
5 * Please report bugs on https://github.com/matthiasmullie/minify/issues
6 *
7 * @author Matthias Mullie <[email protected]>
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 <[email protected]>
25 * @author Tijs Verkoyen <[email protected]>
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 $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
645
646 $this->registerPattern('/\/\*.*?\*\//s', '');
647 }
648
649 /**
650 * Strip whitespace.
651 *
652 * @param string $content The CSS content to strip the whitespace for
653 *
654 * @return string
655 */
656 protected function stripWhitespace($content)
657 {
658 // remove leading & trailing whitespace
659 $content = preg_replace('/^\s*/m', '', $content);
660 $content = preg_replace('/\s*$/m', '', $content);
661
662 // replace newlines with a single space
663 $content = preg_replace('/\s+/', ' ', $content);
664
665 // remove whitespace around meta characters
666 // 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);
671
672 // whitespace around + and - can only be stripped inside some pseudo-
673 // classes, like `:nth-child(3+2n)`
674 // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
675 // selectors like `div.weird- p`
676 $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);
678
679 // remove semicolon/whitespace followed by closing bracket
680 $content = str_replace(';}', '}', $content);
681
682 return trim($content);
683 }
684
685 /**
686 * Replace all occurrences of functions that may contain math, where
687 * whitespace around operators needs to be preserved (e.g. calc, clamp)
688 */
689 protected function extractMath()
690 {
691 $functions = array('calc', 'clamp', 'min', 'max');
692 $pattern = '/\b('. implode('|', $functions) .')(\(.+?)(?=$|;|})/m';
693
694 // PHP only supports $this inside anonymous functions since 5.4
695 $minifier = $this;
696 $callback = function ($match) use ($minifier, $pattern, &$callback) {
697 $function = $match[1];
698 $length = strlen($match[2]);
699 $expr = '';
700 $opened = 0;
701
702 // the regular expression for extracting math has 1 significant problem:
703 // it can't determine the correct closing parenthesis...
704 // instead, it'll match a larger portion of code to where it's certain that
705 // the calc() musts have ended, and we'll figure out which is the correct
706 // closing parenthesis here, by counting how many have opened
707 for ($i = 0; $i < $length; $i++) {
708 $char = $match[2][$i];
709 $expr .= $char;
710 if ($char === '(') {
711 $opened++;
712 } elseif ($char === ')' && --$opened === 0) {
713 break;
714 }
715 }
716
717 // now that we've figured out where the calc() starts and ends, extract it
718 $count = count($minifier->extracted);
719 $placeholder = 'math('.$count.')';
720 $minifier->extracted[$placeholder] = $function.'('.trim(substr($expr, 1, -1)).')';
721
722 // and since we've captured more code than required, we may have some leftover
723 // calc() in here too - go recursive on the remaining but of code to go figure
724 // 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 }
730 $rest = preg_replace_callback($pattern, $callback, $rest);
731
732 return $placeholder.$rest;
733 };
734
735 $this->registerPattern($pattern, $callback);
736 }
737
738 /**
739 * Replace custom properties, whose values may be used in scenarios where
740 * we wouldn't want them to be minified (e.g. inside calc)
741 */
742 protected function extractCustomProperties()
743 {
744 // PHP only supports $this inside anonymous functions since 5.4
745 $minifier = $this;
746 $this->registerPattern(
747 '/(?<=^|[;}])(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m',
748 function ($match) use ($minifier) {
749 $placeholder = '--custom-'. count($minifier->extracted) . ':0';
750 $minifier->extracted[$placeholder] = $match[1] .':'. trim($match[2]);
751 return $placeholder;
752
753 }
754 );
755 }
756
757 /**
758 * Check if file is small enough to be imported.
759 *
760 * @param string $path The path to the file
761 *
762 * @return bool
763 */
764 protected function canImportBySize($path)
765 {
766 return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
767 }
768
769 /**
770 * Check if file a file can be imported, going by the path.
771 *
772 * @param string $path
773 *
774 * @return bool
775 */
776 protected function canImportByPath($path)
777 {
778 return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
779 }
780
781 /**
782 * Return a converter to update relative paths to be relative to the new
783 * destination.
784 *
785 * @param string $source
786 * @param string $target
787 *
788 * @return ConverterInterface
789 */
790 protected function getPathConverter($source, $target)
791 {
792 return new Converter($source, $target);
793 }
794 }
795