This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Class for CSS optimization. |
||
4 | */ |
||
5 | |||
6 | if ( ! defined( 'ABSPATH' ) ) { |
||
7 | exit; |
||
8 | } |
||
9 | |||
10 | class autoptimizeStyles extends autoptimizeBase |
||
11 | { |
||
12 | const ASSETS_REGEX = '/url\s*\(\s*(?!["\']?data:)(?![\'|\"]?[\#|\%|])([^)]+)\s*\)([^;},\s]*)/i'; |
||
13 | |||
14 | /** |
||
15 | * Font-face regex-fu from HamZa at: https://stackoverflow.com/a/21395083 |
||
16 | */ |
||
17 | const FONT_FACE_REGEX = '~@font-face\s*(\{(?:[^{}]+|(?1))*\})~xsi'; // added `i` flag for case-insensitivity. |
||
18 | |||
19 | /** |
||
20 | * Store CSS. |
||
21 | * |
||
22 | * @var array |
||
23 | */ |
||
24 | private $css = array(); |
||
25 | |||
26 | /** |
||
27 | * To store CSS code |
||
28 | * |
||
29 | * @var array |
||
30 | */ |
||
31 | private $csscode = array(); |
||
32 | |||
33 | /** |
||
34 | * To store urls |
||
35 | * |
||
36 | * @var array |
||
37 | */ |
||
38 | private $url = array(); |
||
39 | |||
40 | /** |
||
41 | * String to store rest of content (when old setting "only in head" is used) |
||
42 | * |
||
43 | * @var string |
||
44 | */ |
||
45 | private $restofcontent = ''; |
||
46 | |||
47 | /** |
||
48 | * Setting to change small images to inline CSS |
||
49 | * |
||
50 | * @var bool |
||
51 | */ |
||
52 | private $datauris = false; |
||
53 | |||
54 | /** |
||
55 | * Array to store hashmap |
||
56 | * |
||
57 | * @var array |
||
58 | */ |
||
59 | private $hashmap = array(); |
||
60 | |||
61 | /** |
||
62 | * Flag to indicate if CSS is already minified |
||
63 | * |
||
64 | * @var bool |
||
65 | */ |
||
66 | private $alreadyminified = false; |
||
67 | |||
68 | /** |
||
69 | * Setting if CSS should be aggregated |
||
70 | * |
||
71 | * @var bool |
||
72 | */ |
||
73 | private $aggregate = true; |
||
74 | |||
75 | /** |
||
76 | * Setting if all CSS should be inlined |
||
77 | * |
||
78 | * @var bool |
||
79 | */ |
||
80 | private $inline = false; |
||
81 | |||
82 | /** |
||
83 | * Setting if CSS should be deferred |
||
84 | * |
||
85 | * @var bool |
||
86 | */ |
||
87 | private $defer = false; |
||
88 | |||
89 | /** |
||
90 | * Setting for to be inlined CSS. |
||
91 | * |
||
92 | * @var string |
||
93 | */ |
||
94 | private $defer_inline = ''; |
||
95 | |||
96 | /** |
||
97 | * Setting for allowlist of what should be aggregated. |
||
98 | * |
||
99 | * @var string |
||
100 | */ |
||
101 | private $allowlist = ''; |
||
102 | |||
103 | /** |
||
104 | * Setting (only filter) for size under which CSS should be inlined instead of linked. |
||
105 | * |
||
106 | * @var string |
||
107 | */ |
||
108 | private $cssinlinesize = ''; |
||
109 | |||
110 | /** |
||
111 | * Setting (only filter) of CSS that can be removed. |
||
112 | * |
||
113 | * @var array |
||
114 | */ |
||
115 | private $cssremovables = array(); |
||
116 | |||
117 | /** |
||
118 | * Setting: should inline CSS be aggregated. |
||
119 | * |
||
120 | * @var bool |
||
121 | */ |
||
122 | private $include_inline = false; |
||
123 | |||
124 | /** |
||
125 | * Setting (only filter) if minified CSS can be injected after minificatoin of aggregated CSS. |
||
126 | * |
||
127 | * @var bool |
||
128 | */ |
||
129 | private $inject_min_late = true; |
||
130 | |||
131 | /** |
||
132 | * Holds all exclusions. |
||
133 | * |
||
134 | * @var array |
||
135 | */ |
||
136 | private $dontmove = array(); |
||
137 | |||
138 | /** |
||
139 | * Holds all options. |
||
140 | * |
||
141 | * @var array |
||
142 | */ |
||
143 | private $options = array(); |
||
144 | |||
145 | /** |
||
146 | * Setting; should excluded CSS-files be minified. |
||
147 | * |
||
148 | * @var bool |
||
149 | */ |
||
150 | private $minify_excluded = true; |
||
151 | |||
152 | /** |
||
153 | * Setting (filter only); should all media-attributes be forced to "all". |
||
154 | * |
||
155 | * @var bool |
||
156 | */ |
||
157 | private $media_force_all = false; |
||
158 | |||
159 | /** |
||
160 | * Reads the page and collects style tags. |
||
161 | * |
||
162 | * @param array $options all options. |
||
163 | */ |
||
164 | public function read( $options ) |
||
165 | { |
||
166 | $noptimize_css = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content ); |
||
167 | if ( $noptimize_css ) { |
||
168 | return false; |
||
169 | } |
||
170 | |||
171 | $allowlist_css = apply_filters( 'autoptimize_filter_css_allowlist', '', $this->content ); |
||
172 | $allowlist_css = apply_filters( 'autoptimize_filter_css_whitelist', $allowlist_css, $this->content ); // fixme: to be removed in next version. |
||
173 | View Code Duplication | if ( ! empty( $allowlist_css ) ) { |
|
174 | $this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_css ) ) ); |
||
175 | } |
||
176 | |||
177 | $removable_css = apply_filters( 'autoptimize_filter_css_removables', '' ); |
||
178 | View Code Duplication | if ( ! empty( $removable_css ) ) { |
|
179 | $this->cssremovables = array_filter( array_map( 'trim', explode( ',', $removable_css ) ) ); |
||
180 | } |
||
181 | |||
182 | $this->cssinlinesize = apply_filters( 'autoptimize_filter_css_inlinesize', 256 ); |
||
183 | |||
184 | // filter to "late inject minified CSS", default to true for now (it is faster). |
||
185 | $this->inject_min_late = apply_filters( 'autoptimize_filter_css_inject_min_late', true ); |
||
186 | |||
187 | // Remove everything that's not the header. |
||
188 | View Code Duplication | if ( apply_filters( 'autoptimize_filter_css_justhead', $options['justhead'] ) ) { |
|
189 | $content = explode( '</head>', $this->content, 2 ); |
||
190 | $this->content = $content[0] . '</head>'; |
||
191 | $this->restofcontent = $content[1]; |
||
192 | } |
||
193 | |||
194 | // Determine whether we're doing CSS-files aggregation or not. |
||
195 | if ( isset( $options['aggregate'] ) && ! $options['aggregate'] ) { |
||
196 | $this->aggregate = false; |
||
197 | } |
||
198 | // Returning true for "dontaggregate" turns off aggregation. |
||
199 | if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) { |
||
200 | $this->aggregate = false; |
||
201 | } |
||
202 | |||
203 | // include inline? |
||
204 | if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) { |
||
205 | $this->include_inline = true; |
||
206 | } |
||
207 | |||
208 | // List of CSS strings which are excluded from autoptimization. |
||
209 | $exclude_css = apply_filters( 'autoptimize_filter_css_exclude', $options['css_exclude'], $this->content ); |
||
210 | View Code Duplication | if ( '' !== $exclude_css ) { |
|
211 | $this->dontmove = array_filter( array_map( 'trim', explode( ',', $exclude_css ) ) ); |
||
212 | } else { |
||
213 | $this->dontmove = array(); |
||
214 | } |
||
215 | |||
216 | // forcefully exclude CSS with data-noptimize attrib. |
||
217 | $this->dontmove[] = 'data-noptimize'; |
||
218 | |||
219 | // Should we defer css? |
||
220 | // value: true / false. |
||
221 | $this->defer = $options['defer']; |
||
222 | $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content ); |
||
223 | |||
224 | // Should we inline while deferring? |
||
225 | // value: inlined CSS. |
||
226 | $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $this->sanitize_css( $options['defer_inline'] ), $this->content ); |
||
227 | |||
228 | // Should we inline? |
||
229 | // value: true / false. |
||
230 | $this->inline = $options['inline']; |
||
231 | $this->inline = apply_filters( 'autoptimize_filter_css_inline', $this->inline, $this->content ); |
||
232 | |||
233 | // Store cdn url. |
||
234 | $this->cdn_url = $options['cdn_url']; |
||
235 | |||
236 | // Store data: URIs setting for later use. |
||
237 | $this->datauris = $options['datauris']; |
||
238 | |||
239 | // Determine whether excluded files should be minified if not yet so. |
||
240 | if ( ! $options['minify_excluded'] && $options['aggregate'] ) { |
||
241 | $this->minify_excluded = false; |
||
242 | } |
||
243 | $this->minify_excluded = apply_filters( 'autoptimize_filter_css_minify_excluded', $this->minify_excluded, '' ); |
||
244 | |||
245 | // should we force all media-attributes to all? |
||
246 | $this->media_force_all = apply_filters( 'autoptimize_filter_css_tagmedia_forceall', false ); |
||
247 | |||
248 | // noptimize me. |
||
249 | $this->content = $this->hide_noptimize( $this->content ); |
||
250 | |||
251 | // Exclude (no)script, as those may contain CSS which should be left as is. |
||
252 | $this->content = $this->replace_contents_with_marker_if_exists( |
||
253 | 'SCRIPT', |
||
254 | '<script', |
||
255 | '#<(?:no)?script.*?<\/(?:no)?script>#is', |
||
256 | $this->content |
||
257 | ); |
||
258 | |||
259 | // Save IE hacks. |
||
260 | $this->content = $this->hide_iehacks( $this->content ); |
||
261 | |||
262 | // Hide HTML comments. |
||
263 | $this->content = $this->hide_comments( $this->content ); |
||
264 | |||
265 | // Get <style> and <link>. |
||
266 | if ( preg_match_all( '#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi', $this->content, $matches ) ) { |
||
267 | |||
268 | foreach ( $matches[0] as $tag ) { |
||
269 | if ( $this->isremovable( $tag, $this->cssremovables ) ) { |
||
270 | $this->content = str_replace( $tag, '', $this->content ); |
||
271 | } elseif ( $this->ismovable( $tag ) ) { |
||
272 | // Get the media. |
||
273 | if ( false !== strpos( $tag, 'media=' ) ) { |
||
274 | preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $medias ); |
||
275 | $medias = explode( ',', $medias[1] ); |
||
276 | $media = array(); |
||
277 | foreach ( $medias as $elem ) { |
||
278 | if ( empty( $elem ) ) { |
||
279 | $elem = 'all'; |
||
280 | } |
||
281 | |||
282 | $media[] = $elem; |
||
283 | } |
||
284 | } else { |
||
285 | // No media specified - applies to all. |
||
286 | $media = array( 'all' ); |
||
287 | } |
||
288 | |||
289 | // forcing media attribute to all to merge all in one file. |
||
290 | if ( $this->media_force_all ) { |
||
291 | $media = array( 'all' ); |
||
292 | } |
||
293 | |||
294 | $media = apply_filters( 'autoptimize_filter_css_tagmedia', $media, $tag ); |
||
295 | |||
296 | if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) { |
||
297 | // <link>. |
||
298 | $url = current( explode( '?', $source[2], 2 ) ); |
||
299 | $path = $this->getpath( $url ); |
||
300 | |||
301 | if ( false !== $path && preg_match( '#\.css$#', $path ) ) { |
||
302 | // Good link. |
||
303 | $this->css[] = array( $media, $path ); |
||
304 | } else { |
||
305 | // Link is dynamic (.php etc). |
||
306 | $new_tag = $this->optionally_defer_excluded( $tag, 'none' ); |
||
307 | if ( '' !== $new_tag && $new_tag !== $tag ) { |
||
308 | $this->content = str_replace( $tag, $new_tag, $this->content ); |
||
309 | } |
||
310 | $tag = ''; |
||
311 | } |
||
312 | } else { |
||
313 | // Inline css in style tags can be wrapped in comment tags, so restore comments. |
||
314 | $tag = $this->restore_comments( $tag ); |
||
315 | preg_match( '#<style.*>(.*)</style>#Usmi', $tag, $code ); |
||
316 | |||
317 | // And re-hide them to be able to to the removal based on tag. |
||
318 | $tag = $this->hide_comments( $tag ); |
||
319 | |||
320 | if ( $this->include_inline ) { |
||
321 | $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] ); |
||
322 | $this->css[] = array( $media, 'INLINE;' . $code ); |
||
323 | } else { |
||
324 | $tag = ''; |
||
325 | } |
||
326 | } |
||
327 | |||
328 | // Remove the original style tag. |
||
329 | $this->content = str_replace( $tag, '', $this->content ); |
||
330 | } else { |
||
331 | if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) { |
||
332 | $exploded_url = explode( '?', $source[2], 2 ); |
||
333 | $url = $exploded_url[0]; |
||
334 | $path = $this->getpath( $url ); |
||
335 | $new_tag = $tag; |
||
336 | |||
337 | // Excluded CSS, minify that file: |
||
338 | // -> if aggregate is on and exclude minify is on |
||
339 | // -> if aggregate is off and the file is not in dontmove. |
||
340 | View Code Duplication | if ( $path && $this->minify_excluded ) { |
|
341 | $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false ); |
||
342 | if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) { |
||
343 | $minified_url = $this->minify_single( $path ); |
||
344 | if ( ! empty( $minified_url ) ) { |
||
345 | // Replace orig URL with cached minified URL. |
||
346 | $new_tag = str_replace( $url, $minified_url, $tag ); |
||
347 | } elseif ( apply_filters( 'autoptimize_filter_ccsjs_remove_empty_minified_url', false ) ) { |
||
348 | // Remove the original style tag, because cache content is empty but only if |
||
349 | // filter is true-ed because $minified_url is also false if file is minified already. |
||
350 | $new_tag = ''; |
||
351 | } |
||
352 | } |
||
353 | } |
||
354 | |||
355 | if ( '' !== $new_tag ) { |
||
356 | // Optionally defer (preload) non-aggregated CSS. |
||
357 | $new_tag = $this->optionally_defer_excluded( $new_tag, $url ); |
||
358 | } |
||
359 | |||
360 | // And replace! |
||
361 | View Code Duplication | if ( ( '' !== $new_tag && $new_tag !== $tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_css_remove_empty_files', false ) ) ) { |
|
362 | $this->content = str_replace( $tag, $new_tag, $this->content ); |
||
363 | } |
||
364 | } |
||
365 | } |
||
366 | } |
||
367 | return true; |
||
368 | } |
||
369 | |||
370 | // Really, no styles? |
||
371 | return false; |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Checks if non-optimized CSS is to be preloaded and if so return |
||
376 | * the tag with preload code. |
||
377 | * |
||
378 | * @param string $tag (required). |
||
379 | * @param string $url (optional). |
||
380 | * |
||
381 | * @return string $new_tag |
||
382 | */ |
||
383 | private function optionally_defer_excluded( $tag, $url = '' ) |
||
384 | { |
||
385 | // Defer single CSS if "inline & defer" is ON and there is inline CSS. |
||
386 | if ( ! empty( $tag ) && false === strpos( $tag, ' onload=' ) && $this->defer && ! empty( $this->defer_inline ) && apply_filters( 'autoptimize_filter_css_defer_excluded', true, $tag ) ) { |
||
387 | // get media attribute and based on that create onload JS attribute value. |
||
388 | if ( false === strpos( $tag, 'media=' ) ) { |
||
389 | $tag = str_replace( '<link', "<link media='all'", $tag ); |
||
390 | } |
||
391 | |||
392 | preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $_medias ); |
||
393 | $_media = $_medias[1]; |
||
394 | $_preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $_media ); |
||
395 | |||
396 | if ( 'print' !== $_media ) { |
||
397 | // If not media=print, adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback. |
||
398 | $new_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>' . str_replace( |
||
399 | $_medias[0], |
||
400 | "media='print' onload=\"" . $_preload_onload . '"', |
||
401 | $tag |
||
402 | ); |
||
403 | |||
404 | // Optionally (but default false) preload the (excluded) CSS-file. |
||
405 | if ( apply_filters( 'autoptimize_fitler_css_preload_and_print', false ) && 'none' !== $url ) { |
||
406 | $new_tag = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $new_tag; |
||
407 | } |
||
408 | } else { |
||
409 | $new_tag = $tag; |
||
410 | } |
||
411 | |||
412 | return $new_tag; |
||
413 | } |
||
414 | |||
415 | // Return unchanged $tag. |
||
416 | return $tag; |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * Checks if the local file referenced by $path is a valid |
||
421 | * candidate for being inlined into a data: URI |
||
422 | * |
||
423 | * @param string $path image path. |
||
424 | * @return boolean |
||
425 | */ |
||
426 | private function is_datauri_candidate( $path ) |
||
427 | { |
||
428 | // Call only once since it's called from a loop. |
||
429 | static $max_size = null; |
||
430 | if ( null === $max_size ) { |
||
431 | $max_size = $this->get_datauri_maxsize(); |
||
432 | } |
||
433 | |||
434 | if ( $path && preg_match( '#\.(jpe?g|png|gif|webp|bmp)$#i', $path ) && |
||
435 | file_exists( $path ) && is_readable( $path ) && filesize( $path ) <= $max_size ) { |
||
436 | |||
437 | // Seems we have a candidate. |
||
438 | $is_candidate = true; |
||
439 | } else { |
||
440 | // Filter allows overriding default decision (which checks for local file existence). |
||
441 | $is_candidate = apply_filters( 'autoptimize_filter_css_is_datauri_candidate', false, $path ); |
||
442 | } |
||
443 | |||
444 | return $is_candidate; |
||
445 | } |
||
446 | |||
447 | /** |
||
448 | * Returns the amount of bytes that shouldn't be exceeded if a file is to |
||
449 | * be inlined into a data: URI. Defaults to 4096, passed through |
||
450 | * `autoptimize_filter_css_datauri_maxsize` filter. |
||
451 | * |
||
452 | * @return mixed |
||
453 | */ |
||
454 | private function get_datauri_maxsize() |
||
455 | { |
||
456 | static $max_size = null; |
||
457 | |||
458 | /** |
||
459 | * No need to apply the filter multiple times in case the |
||
460 | * method itself is invoked multiple times during a single request. |
||
461 | * This prevents some wild stuff like having different maxsizes |
||
462 | * for different files/site-sections etc. But if you're into that sort |
||
463 | * of thing you're probably better of building assets completely |
||
464 | * outside of WordPress anyway. |
||
465 | */ |
||
466 | if ( null === $max_size ) { |
||
467 | $max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', 4096 ); |
||
468 | } |
||
469 | |||
470 | return $max_size; |
||
471 | } |
||
472 | |||
473 | private function check_datauri_exclude_list( $url ) |
||
474 | { |
||
475 | static $exclude_list = null; |
||
476 | $no_datauris = array(); |
||
477 | |||
478 | // Again, skip doing certain stuff repeatedly when loop-called. |
||
479 | if ( null === $exclude_list ) { |
||
480 | $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' ); |
||
481 | $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) ); |
||
482 | } |
||
483 | |||
484 | $matched = false; |
||
485 | |||
486 | if ( ! empty( $exclude_list ) ) { |
||
487 | foreach ( $no_datauris as $no_datauri ) { |
||
488 | if ( false !== strpos( $url, $no_datauri ) ) { |
||
489 | $matched = true; |
||
490 | break; |
||
491 | } |
||
492 | } |
||
493 | } |
||
494 | |||
495 | return $matched; |
||
496 | } |
||
497 | |||
498 | private function build_or_get_datauri_image( $path ) |
||
499 | { |
||
500 | /** |
||
501 | * TODO/FIXME: document the required return array format, or better yet, |
||
502 | * use a string, since we don't really need an array for this. That would, however, |
||
503 | * require changing even more code, which is not happening right now... |
||
504 | */ |
||
505 | |||
506 | // Allows short-circuiting datauri generation for an image. |
||
507 | $result = apply_filters( 'autoptimize_filter_css_datauri_image', array(), $path ); |
||
508 | if ( ! empty( $result ) ) { |
||
509 | if ( is_array( $result ) && isset( $result['full'] ) && isset( $result['base64data'] ) ) { |
||
510 | return $result; |
||
511 | } |
||
512 | } |
||
513 | |||
514 | $hash = md5( $path ); |
||
515 | $check = new autoptimizeCache( $hash, 'img' ); |
||
516 | if ( $check->check() ) { |
||
517 | // we have the base64 image in cache. |
||
518 | $head_and_data = $check->retrieve(); |
||
519 | $_base64data = explode( ';base64,', $head_and_data ); |
||
520 | $base64data = $_base64data[1]; |
||
521 | unset( $_base64data ); |
||
522 | } else { |
||
523 | // It's an image and we don't have it in cache, get the type by extension. |
||
524 | $exploded_path = explode( '.', $path ); |
||
525 | $type = end( $exploded_path ); |
||
526 | |||
527 | switch ( $type ) { |
||
528 | case 'jpg': |
||
529 | case 'jpeg': |
||
530 | $dataurihead = 'data:image/jpeg;base64,'; |
||
531 | break; |
||
532 | case 'gif': |
||
533 | $dataurihead = 'data:image/gif;base64,'; |
||
534 | break; |
||
535 | case 'png': |
||
536 | $dataurihead = 'data:image/png;base64,'; |
||
537 | break; |
||
538 | case 'bmp': |
||
539 | $dataurihead = 'data:image/bmp;base64,'; |
||
540 | break; |
||
541 | case 'webp': |
||
542 | $dataurihead = 'data:image/webp;base64,'; |
||
543 | break; |
||
544 | default: |
||
545 | $dataurihead = 'data:application/octet-stream;base64,'; |
||
546 | } |
||
547 | |||
548 | // Encode the data. |
||
549 | $base64data = base64_encode( file_get_contents( $path ) ); |
||
550 | $head_and_data = $dataurihead . $base64data; |
||
551 | |||
552 | // Save in cache. |
||
553 | $check->cache( $head_and_data, 'text/plain' ); |
||
554 | } |
||
555 | unset( $check ); |
||
556 | |||
557 | return array( |
||
558 | 'full' => $head_and_data, |
||
559 | 'base64data' => $base64data, |
||
560 | ); |
||
561 | } |
||
562 | |||
563 | /** |
||
564 | * Given an array of key/value pairs to replace in $string, |
||
565 | * it does so by replacing the longest-matching strings first. |
||
566 | * |
||
567 | * @param string $string string in which to replace. |
||
568 | * @param array $replacements to be replaced strings and replacement. |
||
569 | * |
||
570 | * @return string |
||
571 | */ |
||
572 | protected static function replace_longest_matches_first( $string, $replacements = array() ) |
||
573 | { |
||
574 | if ( ! empty( $replacements ) ) { |
||
575 | // Sort the replacements array by key length in desc order (so that the longest strings are replaced first). |
||
576 | $keys = array_map( 'strlen', array_keys( $replacements ) ); |
||
577 | array_multisort( $keys, SORT_DESC, $replacements ); |
||
578 | $string = str_replace( array_keys( $replacements ), array_values( $replacements ), $string ); |
||
579 | } |
||
580 | |||
581 | return $string; |
||
582 | } |
||
583 | |||
584 | /** |
||
585 | * Rewrites/Replaces any ASSETS_REGEX-matching urls in a string. |
||
586 | * Removes quotes/cruft around each one and passes it through to |
||
587 | * `autoptimizeBase::url_replace_cdn()`. |
||
588 | * Replacements are performed in a `longest-match-replaced-first` way. |
||
589 | * |
||
590 | * @param string $code CSS code. |
||
591 | * |
||
592 | * @return string |
||
593 | */ |
||
594 | public function replace_urls( $code = '' ) |
||
595 | { |
||
596 | $replacements = array(); |
||
597 | |||
598 | preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches ); |
||
599 | if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) { |
||
600 | foreach ( $url_src_matches[1] as $count => $original_url ) { |
||
601 | // Removes quotes and other cruft. |
||
602 | $url = trim( $original_url, " \t\n\r\0\x0B\"'" ); |
||
603 | |||
604 | /** |
||
605 | * TODO/FIXME: Add a way for other code / callable to be called here |
||
606 | * and provide it's own results for the $replacements array |
||
607 | * for the "current" key. |
||
608 | * If such a result is returned/provided, we sholud then avoid |
||
609 | * calling url_replace_cdn() here for the current iteration. |
||
610 | * |
||
611 | * This would maybe allow the inlining logic currently present |
||
612 | * in `autoptimizeStyles::rewrite_assets()` to be "pulled out" |
||
613 | * and given as a callable to this method or something... and |
||
614 | * then we could "just" call `replace_urls()` from within |
||
615 | * `autoptimizeStyles::rewrite_assets()` and avoid some |
||
616 | * (currently present) code/logic duplication. |
||
617 | */ |
||
618 | |||
619 | // Do CDN replacement if needed. |
||
620 | View Code Duplication | if ( ! empty( $this->cdn_url ) ) { |
|
621 | $replacement_url = $this->url_replace_cdn( $url ); |
||
622 | // Prepare replacements array. |
||
623 | $replacements[ $url_src_matches[1][ $count ] ] = str_replace( |
||
624 | $original_url, $replacement_url, $url_src_matches[1][ $count ] |
||
625 | ); |
||
626 | } |
||
627 | } |
||
628 | } |
||
629 | |||
630 | $code = self::replace_longest_matches_first( $code, $replacements ); |
||
631 | |||
632 | return $code; |
||
633 | } |
||
634 | |||
635 | /** |
||
636 | * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers. |
||
637 | * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn` |
||
638 | * filter is used. |
||
639 | * |
||
640 | * @param string $code HTML being processed to hide fonts. |
||
641 | * @return string |
||
642 | */ |
||
643 | public function hide_fontface_and_maybe_cdn( $code ) |
||
644 | { |
||
645 | // Proceed only if @font-face declarations exist within $code. |
||
646 | preg_match_all( self::FONT_FACE_REGEX, $code, $fontfaces ); |
||
647 | if ( isset( $fontfaces[0] ) ) { |
||
648 | // Check if we need to cdn fonts or not. |
||
649 | $do_font_cdn = apply_filters( 'autoptimize_filter_css_fonts_cdn', false ); |
||
650 | |||
651 | foreach ( $fontfaces[0] as $full_match ) { |
||
652 | // Keep original match so we can search/replace it. |
||
653 | $match_search = $full_match; |
||
654 | |||
655 | // Do font cdn if needed. |
||
656 | if ( $do_font_cdn ) { |
||
657 | $full_match = $this->replace_urls( $full_match ); |
||
658 | } |
||
659 | |||
660 | // Replace declaration with its base64 encoded string. |
||
661 | $replacement = self::build_marker( 'FONTFACE', $full_match ); |
||
662 | $code = str_replace( $match_search, $replacement, $code ); |
||
663 | } |
||
664 | } |
||
665 | |||
666 | return $code; |
||
667 | } |
||
668 | |||
669 | /** |
||
670 | * Restores original @font-face declarations that have been "hidden" |
||
671 | * using `hide_fontface_and_maybe_cdn()`. |
||
672 | * |
||
673 | * @param string $code HTML being processed to unhide fonts. |
||
674 | * @return string |
||
675 | */ |
||
676 | public function restore_fontface( $code ) |
||
677 | { |
||
678 | return $this->restore_marked_content( 'FONTFACE', $code ); |
||
679 | } |
||
680 | |||
681 | /** |
||
682 | * Re-write (and/or inline) referenced assets. |
||
683 | * |
||
684 | * @param string $code HTML being processed rewrite assets. |
||
685 | * @return string |
||
686 | */ |
||
687 | public function rewrite_assets( $code ) |
||
688 | { |
||
689 | // Handle @font-face rules by hiding and processing them separately. |
||
690 | $code = $this->hide_fontface_and_maybe_cdn( $code ); |
||
691 | |||
692 | /** |
||
693 | * TODO/FIXME: |
||
694 | * Certain code parts below are kind-of repeated now in `replace_urls()`, which is not ideal. |
||
695 | * There is maybe a way to separate/refactor things and then be able to keep |
||
696 | * the ASSETS_REGEX rewriting/handling logic in a single place (along with removing quotes/cruft from matched urls). |
||
697 | * See comments in `replace_urls()` regarding this. The idea is to extract the inlining |
||
698 | * logic out (which is the only real difference between replace_urls() and the code below), but still |
||
699 | * achieve identical results as before. |
||
700 | */ |
||
701 | |||
702 | // Re-write (and/or inline) URLs to point them to the CDN host. |
||
703 | $url_src_matches = array(); |
||
704 | $imgreplace = array(); |
||
705 | |||
706 | // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs. |
||
707 | preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches ); |
||
708 | if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) { |
||
709 | foreach ( $url_src_matches[1] as $count => $original_url ) { |
||
710 | // Removes quotes and other cruft. |
||
711 | $url = trim( $original_url, " \t\n\r\0\x0B\"'" ); |
||
712 | |||
713 | // If datauri inlining is turned on, do it. |
||
714 | $inlined = false; |
||
715 | if ( $this->datauris ) { |
||
716 | $iurl = $url; |
||
717 | if ( false !== strpos( $iurl, '?' ) ) { |
||
718 | $iurl = strtok( $iurl, '?' ); |
||
719 | } |
||
720 | |||
721 | $ipath = $this->getpath( $iurl ); |
||
722 | |||
723 | $excluded = $this->check_datauri_exclude_list( $ipath ); |
||
724 | if ( ! $excluded ) { |
||
725 | $is_datauri_candidate = $this->is_datauri_candidate( $ipath ); |
||
726 | if ( $is_datauri_candidate ) { |
||
727 | $datauri = $this->build_or_get_datauri_image( $ipath ); |
||
728 | $base64data = $datauri['base64data']; |
||
0 ignored issues
–
show
|
|||
729 | // Add it to the list for replacement. |
||
730 | $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace( |
||
731 | $original_url, |
||
732 | $datauri['full'], |
||
733 | $url_src_matches[1][ $count ] |
||
734 | ); |
||
735 | $inlined = true; |
||
736 | } |
||
737 | } |
||
738 | } |
||
739 | |||
740 | /** |
||
741 | * Doing CDN URL replacement for every found match (if CDN is |
||
742 | * specified). This way we make sure to do it even if |
||
743 | * inlining isn't turned on, or if a resource is skipped from |
||
744 | * being inlined for whatever reason above. |
||
745 | */ |
||
746 | View Code Duplication | if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) { |
|
747 | // Just do the "simple" CDN replacement. |
||
748 | $replacement_url = $this->url_replace_cdn( $url ); |
||
749 | $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace( |
||
750 | $original_url, $replacement_url, $url_src_matches[1][ $count ] |
||
751 | ); |
||
752 | } |
||
753 | } |
||
754 | } |
||
755 | |||
756 | $code = self::replace_longest_matches_first( $code, $imgreplace ); |
||
757 | |||
758 | // Replace back font-face markers with actual font-face declarations. |
||
759 | $code = $this->restore_fontface( $code ); |
||
760 | |||
761 | return $code; |
||
762 | } |
||
763 | |||
764 | /** |
||
765 | * Joins and optimizes CSS. |
||
766 | */ |
||
767 | public function minify() |
||
768 | { |
||
769 | foreach ( $this->css as $group ) { |
||
770 | list( $media, $css ) = $group; |
||
771 | if ( preg_match( '#^INLINE;#', $css ) ) { |
||
772 | // <style>. |
||
773 | $css = preg_replace( '#^INLINE;#', '', $css ); |
||
774 | $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash. |
||
775 | $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' ); |
||
776 | if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) { |
||
777 | $css = $tmpstyle; |
||
778 | $this->alreadyminified = true; |
||
779 | } |
||
780 | } else { |
||
781 | // <link> |
||
782 | if ( false !== $css && file_exists( $css ) && is_readable( $css ) ) { |
||
783 | $css_path = $css; |
||
784 | $css = self::fixurls( $css_path, file_get_contents( $css_path ) ); |
||
785 | $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css ); |
||
786 | $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $css_path ); |
||
787 | View Code Duplication | if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) { |
|
788 | $css = $tmpstyle; |
||
789 | $this->alreadyminified = true; |
||
790 | } elseif ( $this->can_inject_late( $css_path, $css ) ) { |
||
791 | $css = self::build_injectlater_marker( $css_path, md5( $css ) ); |
||
792 | } |
||
793 | } else { |
||
794 | // Couldn't read CSS. Maybe getpath isn't working? |
||
795 | $css = ''; |
||
796 | } |
||
797 | } |
||
798 | |||
799 | foreach ( $media as $elem ) { |
||
800 | if ( ! empty( $css ) ) { |
||
801 | if ( ! isset( $this->csscode[ $elem ] ) ) { |
||
802 | $this->csscode[ $elem ] = ''; |
||
803 | } |
||
804 | $this->csscode[ $elem ] .= "\n/*FILESTART*/" . $css; |
||
805 | } |
||
806 | } |
||
807 | } |
||
808 | |||
809 | // Check for duplicate code. |
||
810 | $md5list = array(); |
||
811 | $tmpcss = $this->csscode; |
||
812 | foreach ( $tmpcss as $media => $code ) { |
||
813 | $md5sum = md5( $code ); |
||
814 | $medianame = $media; |
||
815 | foreach ( $md5list as $med => $sum ) { |
||
816 | // If same code. |
||
817 | if ( $sum === $md5sum ) { |
||
818 | // Add the merged code. |
||
819 | $medianame = $med . ', ' . $media; |
||
820 | $this->csscode[ $medianame ] = $code; |
||
821 | $md5list[ $medianame ] = $md5list[ $med ]; |
||
822 | unset( $this->csscode[ $med ], $this->csscode[ $media ], $md5list[ $med ] ); |
||
823 | } |
||
824 | } |
||
825 | $md5list[ $medianame ] = $md5sum; |
||
826 | } |
||
827 | unset( $tmpcss ); |
||
828 | |||
829 | // Manage @imports, while is for recursive import management. |
||
830 | foreach ( $this->csscode as &$thiscss ) { |
||
831 | // Flag to trigger import reconstitution and var to hold external imports. |
||
832 | $fiximports = false; |
||
833 | $external_imports = ''; |
||
834 | |||
835 | // remove comments to avoid importing commented-out imports. |
||
836 | $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss ); |
||
837 | while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) { |
||
838 | foreach ( $matches[0] as $import ) { |
||
839 | if ( $this->isremovable( $import, $this->cssremovables ) ) { |
||
840 | $thiscss = str_replace( $import, '', $thiscss ); |
||
841 | $import_ok = true; |
||
842 | } else { |
||
843 | $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" ); |
||
844 | $path = $this->getpath( $url ); |
||
845 | $import_ok = false; |
||
846 | if ( file_exists( $path ) && is_readable( $path ) ) { |
||
847 | $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), '\\' ); |
||
848 | $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code ); |
||
849 | $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' ); |
||
850 | View Code Duplication | if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) { |
|
851 | $code = $tmpstyle; |
||
852 | $this->alreadyminified = true; |
||
853 | } elseif ( $this->can_inject_late( $path, $code ) ) { |
||
854 | $code = self::build_injectlater_marker( $path, md5( $code ) ); |
||
855 | } |
||
856 | |||
857 | if ( ! empty( $code ) ) { |
||
858 | $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss ); |
||
859 | if ( ! empty( $tmp_thiscss ) ) { |
||
860 | $thiscss = $tmp_thiscss; |
||
861 | $import_ok = true; |
||
862 | unset( $tmp_thiscss ); |
||
863 | } |
||
864 | } |
||
865 | unset( $code ); |
||
866 | } |
||
867 | } |
||
868 | if ( ! $import_ok ) { |
||
869 | // External imports and general fall-back. |
||
870 | $external_imports .= $import; |
||
871 | |||
872 | $thiscss = str_replace( $import, '', $thiscss ); |
||
873 | $fiximports = true; |
||
874 | } |
||
875 | } |
||
876 | $thiscss = preg_replace( '#/\*FILESTART\*/#', '', $thiscss ); |
||
877 | $thiscss = preg_replace( '#/\*FILESTART2\*/#', '/*FILESTART*/', $thiscss ); |
||
878 | |||
879 | // and update $thiscss_nocomments before going into next iteration in while loop. |
||
880 | $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss ); |
||
881 | } |
||
882 | unset( $thiscss_nocomments ); |
||
883 | |||
884 | // Add external imports to top of aggregated CSS. |
||
885 | if ( $fiximports ) { |
||
886 | $thiscss = $external_imports . $thiscss; |
||
887 | } |
||
888 | } |
||
889 | unset( $thiscss ); |
||
890 | |||
891 | // $this->csscode has all the uncompressed code now. |
||
892 | foreach ( $this->csscode as &$code ) { |
||
893 | // Check for already-minified code. |
||
894 | $hash = md5( $code ); |
||
895 | do_action( 'autoptimize_action_css_hash', $hash ); |
||
896 | $ccheck = new autoptimizeCache( $hash, 'css' ); |
||
897 | if ( $ccheck->check() ) { |
||
898 | $code = $ccheck->retrieve(); |
||
899 | $this->hashmap[ md5( $code ) ] = $hash; |
||
900 | continue; |
||
901 | } |
||
902 | unset( $ccheck ); |
||
903 | |||
904 | // Rewrite and/or inline referenced assets. |
||
905 | $code = $this->rewrite_assets( $code ); |
||
906 | |||
907 | // Minify. |
||
908 | $code = $this->run_minifier_on( $code ); |
||
909 | |||
910 | // Bring back INJECTLATER stuff. |
||
911 | $code = $this->inject_minified( $code ); |
||
912 | |||
913 | // Filter results. |
||
914 | $tmp_code = apply_filters( 'autoptimize_css_after_minify', $code ); |
||
915 | if ( ! empty( $tmp_code ) ) { |
||
916 | $code = $tmp_code; |
||
917 | unset( $tmp_code ); |
||
918 | } |
||
919 | |||
920 | $this->hashmap[ md5( $code ) ] = $hash; |
||
921 | } |
||
922 | |||
923 | unset( $code ); |
||
924 | return true; |
||
925 | } |
||
926 | |||
927 | public function run_minifier_on( $code ) |
||
928 | { |
||
929 | if ( ! $this->alreadyminified ) { |
||
930 | $do_minify = apply_filters( 'autoptimize_css_do_minify', true ); |
||
931 | |||
932 | View Code Duplication | if ( $do_minify ) { |
|
933 | $cssmin = new autoptimizeCSSmin(); |
||
934 | $tmp_code = trim( $cssmin->run( $code ) ); |
||
935 | |||
936 | if ( ! empty( $tmp_code ) ) { |
||
937 | $code = $tmp_code; |
||
938 | unset( $tmp_code ); |
||
939 | } |
||
940 | } |
||
941 | } |
||
942 | |||
943 | return $code; |
||
944 | } |
||
945 | |||
946 | /** |
||
947 | * Caches the CSS in uncompressed, deflated and gzipped form. |
||
948 | */ |
||
949 | public function cache() |
||
950 | { |
||
951 | // CSS cache. |
||
952 | foreach ( $this->csscode as $media => $code ) { |
||
953 | if ( empty( $code ) ) { |
||
954 | continue; |
||
955 | } |
||
956 | |||
957 | $md5 = $this->hashmap[ md5( $code ) ]; |
||
958 | $cache = new autoptimizeCache( $md5, 'css' ); |
||
959 | if ( ! $cache->check() ) { |
||
960 | // Cache our code. |
||
961 | $cache->cache( $code, 'text/css' ); |
||
962 | } |
||
963 | $this->url[ $media ] = AUTOPTIMIZE_CACHE_URL . $cache->getname(); |
||
964 | } |
||
965 | } |
||
966 | |||
967 | /** |
||
968 | * Returns the content. |
||
969 | */ |
||
970 | public function getcontent() |
||
971 | { |
||
972 | // Restore the full content (only applies when "autoptimize_filter_css_justhead" filter is true). |
||
973 | if ( ! empty( $this->restofcontent ) ) { |
||
974 | $this->content .= $this->restofcontent; |
||
975 | $this->restofcontent = ''; |
||
976 | } |
||
977 | |||
978 | // type is not added by default. |
||
979 | $type_css = ''; |
||
980 | if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) { |
||
981 | $type_css = 'type="text/css" '; |
||
982 | } |
||
983 | |||
984 | // Inject the new stylesheets. |
||
985 | $replace_tag = array( '<title', 'before' ); |
||
986 | $replace_tag = apply_filters( 'autoptimize_filter_css_replacetag', $replace_tag, $this->content ); |
||
987 | |||
988 | if ( $this->inline ) { |
||
989 | foreach ( $this->csscode as $media => $code ) { |
||
990 | $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $code . '</style>' ), $replace_tag ); |
||
991 | } |
||
992 | } else { |
||
993 | if ( $this->defer ) { |
||
994 | $preload_css_block = ''; |
||
995 | $inlined_ccss_block = ''; |
||
996 | $noscript_css_block = '<noscript id="aonoscrcss">'; |
||
997 | |||
998 | $defer_inline_code = $this->defer_inline; |
||
999 | if ( ! empty( $defer_inline_code ) ) { |
||
1000 | if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) { |
||
1001 | $icss_hash = md5( $defer_inline_code ); |
||
1002 | $icss_cache = new autoptimizeCache( $icss_hash, 'css' ); |
||
1003 | if ( $icss_cache->check() ) { |
||
1004 | // we have the optimized inline CSS in cache. |
||
1005 | $defer_inline_code = $icss_cache->retrieve(); |
||
1006 | View Code Duplication | } else { |
|
1007 | $cssmin = new autoptimizeCSSmin(); |
||
1008 | $tmp_code = trim( $cssmin->run( $defer_inline_code ) ); |
||
1009 | |||
1010 | if ( ! empty( $tmp_code ) ) { |
||
1011 | $defer_inline_code = $tmp_code; |
||
1012 | $icss_cache->cache( $defer_inline_code, 'text/css' ); |
||
1013 | unset( $tmp_code ); |
||
1014 | } |
||
1015 | } |
||
1016 | } |
||
1017 | // inlined critical css set here, but injected when full CSS is injected |
||
1018 | // to avoid CSS containing SVG with <title tag receiving the full CSS link. |
||
1019 | $inlined_ccss_block = '<style ' . $type_css . 'id="aoatfcss" media="all">' . $defer_inline_code . '</style>'; |
||
1020 | } |
||
1021 | } |
||
1022 | |||
1023 | foreach ( $this->url as $media => $url ) { |
||
1024 | $url = $this->url_replace_cdn( $url ); |
||
1025 | |||
1026 | // Add the stylesheet either deferred (import at bottom) or normal links in head. |
||
1027 | if ( $this->defer && 'print' !== $media ) { |
||
1028 | $preload_onload = autoptimizeConfig::get_ao_css_preload_onload( $media ); |
||
1029 | |||
1030 | $preload_css_block .= '<link rel="stylesheet" media="print" href="' . $url . '" onload="' . $preload_onload . '" />'; |
||
1031 | if ( apply_filters( 'autoptimize_fitler_css_preload_and_print', false ) ) { |
||
1032 | $preload_css_block = '<link rel="preload" as="stylesheet" href="' . $url . '"/>' . $preload_css_block; |
||
1033 | } |
||
1034 | $noscript_css_block .= '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />'; |
||
1035 | } else { |
||
1036 | if ( strlen( $this->csscode[ $media ] ) > $this->cssinlinesize ) { |
||
1037 | $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />' ), $replace_tag ); |
||
1038 | } elseif ( strlen( $this->csscode[ $media ] ) > 0 ) { |
||
1039 | $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', '<style ' . $type_css . 'media="' . $media . '">' . $this->csscode[ $media ] . '</style>' ), $replace_tag ); |
||
1040 | } |
||
1041 | } |
||
1042 | } |
||
1043 | |||
1044 | if ( $this->defer ) { |
||
1045 | $noscript_css_block .= '</noscript>'; |
||
1046 | // Inject inline critical CSS, the preloaded full CSS and the noscript-CSS. |
||
1047 | $this->inject_in_html( apply_filters( 'autoptimize_filter_css_bodyreplacementpayload', $inlined_ccss_block . $preload_css_block . $noscript_css_block ), $replace_tag ); |
||
1048 | } |
||
1049 | } |
||
1050 | |||
1051 | // restore comments. |
||
1052 | $this->content = $this->restore_comments( $this->content ); |
||
1053 | |||
1054 | // restore IE hacks. |
||
1055 | $this->content = $this->restore_iehacks( $this->content ); |
||
1056 | |||
1057 | // restore (no)script. |
||
1058 | $this->content = $this->restore_marked_content( 'SCRIPT', $this->content ); |
||
1059 | |||
1060 | // Restore noptimize. |
||
1061 | $this->content = $this->restore_noptimize( $this->content ); |
||
1062 | |||
1063 | // Return the modified stylesheet. |
||
1064 | return $this->content; |
||
1065 | } |
||
1066 | |||
1067 | /** |
||
1068 | * Make sure URL's are absolute iso relative to original CSS location. |
||
1069 | * |
||
1070 | * @param string $file filename of optimized CSS-file. |
||
1071 | * @param string $code CSS-code in which to fix URL's. |
||
1072 | */ |
||
1073 | static function fixurls( $file, $code ) |
||
1074 | { |
||
1075 | // Switch all imports to the url() syntax. |
||
1076 | $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code ); |
||
1077 | |||
1078 | if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) { |
||
1079 | $file = str_replace( WP_ROOT_DIR, '/', $file ); |
||
1080 | /** |
||
1081 | * Rollback as per https://github.com/futtta/autoptimize/issues/94 |
||
1082 | * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file ); |
||
1083 | */ |
||
1084 | $dir = dirname( $file ); // Like /themes/expound/css. |
||
1085 | |||
1086 | /** |
||
1087 | * $dir should not contain backslashes, since it's used to replace |
||
1088 | * urls, but it can contain them when running on Windows because |
||
1089 | * fixurls() is sometimes called with `ABSPATH . 'index.php'` |
||
1090 | */ |
||
1091 | $dir = str_replace( '\\', '/', $dir ); |
||
1092 | unset( $file ); // not used below at all. |
||
1093 | |||
1094 | $replace = array(); |
||
1095 | foreach ( $matches[1] as $k => $url ) { |
||
1096 | // Remove quotes. |
||
1097 | $url = trim( $url, " \t\n\r\0\x0B\"'" ); |
||
1098 | $no_q_url = trim( $url, "\"'" ); |
||
1099 | if ( $url !== $no_q_url ) { |
||
1100 | $removed_quotes = true; |
||
1101 | } else { |
||
1102 | $removed_quotes = false; |
||
1103 | } |
||
1104 | |||
1105 | if ( '' === $no_q_url ) { |
||
1106 | continue; |
||
1107 | } |
||
1108 | |||
1109 | $url = $no_q_url; |
||
1110 | if ( '/' === $url[0] || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) { |
||
1111 | // URL is protocol-relative, host-relative or something we don't touch. |
||
1112 | continue; |
||
1113 | } else { // Relative URL. |
||
1114 | |||
1115 | /* |
||
1116 | * rollback as per https://github.com/futtta/autoptimize/issues/94 |
||
1117 | * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) ); |
||
1118 | */ |
||
1119 | $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) ); |
||
1120 | $newurl = apply_filters( 'autoptimize_filter_css_fixurl_newurl', $newurl ); |
||
1121 | |||
1122 | /** |
||
1123 | * Hash the url + whatever was behind potentially for replacement |
||
1124 | * We must do this, or different css classes referencing the same bg image (but |
||
1125 | * different parts of it, say, in sprites and such) loose their stuff... |
||
1126 | */ |
||
1127 | $hash = md5( $url . $matches[2][ $k ] ); |
||
1128 | $code = str_replace( $matches[0][ $k ], $hash, $code ); |
||
1129 | |||
1130 | if ( $removed_quotes ) { |
||
1131 | $replace[ $hash ] = "url('" . $newurl . "')" . $matches[2][ $k ]; |
||
1132 | } else { |
||
1133 | $replace[ $hash ] = 'url(' . $newurl . ')' . $matches[2][ $k ]; |
||
1134 | } |
||
1135 | } |
||
1136 | } |
||
1137 | |||
1138 | $code = self::replace_longest_matches_first( $code, $replace ); |
||
1139 | } |
||
1140 | |||
1141 | return $code; |
||
1142 | } |
||
1143 | |||
1144 | private function ismovable( $tag ) |
||
1145 | { |
||
1146 | if ( ! $this->aggregate ) { |
||
1147 | return false; |
||
1148 | } |
||
1149 | |||
1150 | if ( ! empty( $this->allowlist ) ) { |
||
1151 | foreach ( $this->allowlist as $match ) { |
||
1152 | if ( false !== strpos( $tag, $match ) ) { |
||
1153 | return true; |
||
1154 | } |
||
1155 | } |
||
1156 | // no match with allowlist. |
||
1157 | return false; |
||
1158 | } else { |
||
1159 | if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) { |
||
1160 | foreach ( $this->dontmove as $match ) { |
||
1161 | if ( false !== strpos( $tag, $match ) ) { |
||
1162 | // Matched something. |
||
1163 | return false; |
||
1164 | } |
||
1165 | } |
||
1166 | } |
||
1167 | |||
1168 | // If we're here it's safe to move. |
||
1169 | return true; |
||
1170 | } |
||
1171 | } |
||
1172 | |||
1173 | private function can_inject_late( $css_path, $css ) |
||
1174 | { |
||
1175 | $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $css_path ); |
||
1176 | if ( true !== $this->inject_min_late ) { |
||
1177 | // late-inject turned off. |
||
1178 | return false; |
||
1179 | } elseif ( ( false === strpos( $css_path, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $css_path ) === $css_path ) ) { |
||
1180 | // file not minified based on filename & filter. |
||
1181 | return false; |
||
1182 | } elseif ( false !== strpos( $css, '@import' ) ) { |
||
1183 | // can't late-inject files with imports as those need to be aggregated. |
||
1184 | return false; |
||
1185 | } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) { |
||
1186 | // don't late-inject CSS with font-src's if fonts are set to be CDN'ed. |
||
1187 | return false; |
||
1188 | } elseif ( ( ( true == $this->datauris ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) { |
||
1189 | // don't late-inject CSS with images if CDN is set OR if image inlining is on. |
||
1190 | return false; |
||
1191 | } else { |
||
1192 | // phew, all is safe, we can late-inject. |
||
1193 | return true; |
||
1194 | } |
||
1195 | } |
||
1196 | |||
1197 | /** |
||
1198 | * Minifies (and cdn-replaces) a single local css file |
||
1199 | * and returns its (cached) url. |
||
1200 | * |
||
1201 | * @param string $filepath Filepath. |
||
1202 | * @param bool $cache_miss Optional. Force a cache miss. Default false. |
||
1203 | * |
||
1204 | * @return bool|string Url pointing to the minified css file or false. |
||
1205 | */ |
||
1206 | public function minify_single( $filepath, $cache_miss = false ) |
||
1207 | { |
||
1208 | $contents = $this->prepare_minify_single( $filepath ); |
||
1209 | |||
1210 | if ( empty( $contents ) ) { |
||
1211 | return false; |
||
1212 | } |
||
1213 | |||
1214 | // Check cache. |
||
1215 | $hash = 'single_' . md5( $contents ); |
||
1216 | $cache = new autoptimizeCache( $hash, 'css' ); |
||
1217 | do_action( 'autoptimize_action_css_hash', $hash ); |
||
1218 | |||
1219 | // If not in cache already, minify... |
||
1220 | if ( ! $cache->check() || $cache_miss ) { |
||
1221 | // Fixurls... |
||
1222 | $contents = self::fixurls( $filepath, $contents ); |
||
1223 | // CDN-replace any referenced assets if needed... |
||
1224 | $contents = $this->hide_fontface_and_maybe_cdn( $contents ); |
||
1225 | $contents = $this->replace_urls( $contents ); |
||
1226 | $contents = $this->restore_fontface( $contents ); |
||
1227 | // Now minify... |
||
1228 | $cssmin = new autoptimizeCSSmin(); |
||
1229 | $contents = trim( $cssmin->run( $contents ) ); |
||
1230 | |||
1231 | // Check if minified cache content is empty. |
||
1232 | if ( empty( $contents ) ) { |
||
1233 | return false; |
||
1234 | } |
||
1235 | |||
1236 | // Filter contents of excluded minified CSS. |
||
1237 | $contents = apply_filters( 'autoptimize_filter_css_single_after_minify', $contents ); |
||
1238 | |||
1239 | // Store in cache. |
||
1240 | $cache->cache( $contents, 'text/css' ); |
||
1241 | } |
||
1242 | |||
1243 | $url = $this->build_minify_single_url( $cache ); |
||
1244 | |||
1245 | return $url; |
||
1246 | } |
||
1247 | |||
1248 | /** |
||
1249 | * Returns whether we're doing aggregation or not. |
||
1250 | * |
||
1251 | * @return bool |
||
1252 | */ |
||
1253 | public function aggregating() |
||
1254 | { |
||
1255 | return $this->aggregate; |
||
1256 | } |
||
1257 | |||
1258 | public function getOptions() |
||
1259 | { |
||
1260 | return $this->options; |
||
1261 | } |
||
1262 | |||
1263 | public function replaceOptions( $options ) |
||
1264 | { |
||
1265 | $this->options = $options; |
||
1266 | } |
||
1267 | |||
1268 | public function setOption( $name, $value ) |
||
1269 | { |
||
1270 | $this->options[ $name ] = $value; |
||
1271 | $this->$name = $value; |
||
1272 | } |
||
1273 | |||
1274 | public function getOption( $name ) |
||
1275 | { |
||
1276 | return $this->options[ $name ]; |
||
1277 | } |
||
1278 | |||
1279 | /** |
||
1280 | * Sanitize user-provided CSS. |
||
1281 | * |
||
1282 | * For now just strip_tags (the WordPress way) and preg_replace to escape < in certain cases but might do full CSS escaping in the future, see: |
||
1283 | * https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-4-css-encode-and-strictly-validate-before-inserting-untrusted-data-into-html-style-property-values |
||
1284 | * https://github.com/twigphp/Twig/blob/3.x/src/Extension/EscaperExtension.php#L300-L319 |
||
1285 | * https://github.com/laminas/laminas-escaper/blob/2.8.x/src/Escaper.php#L205-L221 |
||
1286 | * |
||
1287 | * @param string $css the to be sanitized CSS |
||
1288 | * @return string sanitized CSS. |
||
1289 | */ |
||
1290 | public static function sanitize_css( $css ) |
||
1291 | { |
||
1292 | $css = wp_strip_all_tags( $css ); |
||
1293 | if ( strpos( $css, '<' ) !== false ) { |
||
1294 | $css = preg_replace( '#<(\/?\w+)#', '\00003C$1', $css ); |
||
1295 | } |
||
1296 | return $css; |
||
1297 | } |
||
1298 | } |
||
1299 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.