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 ) ) { |
|
0 ignored issues
–
show
|
|||
174 | $this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_css ) ) ); |
||
0 ignored issues
–
show
It seems like
array_filter(array_map('...(',', $allowlist_css))) of type array is incompatible with the declared type string of property $allowlist .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||
175 | } |
||
176 | |||
177 | $removable_css = apply_filters( 'autoptimize_filter_css_removables', '' ); |
||
178 | View Code Duplication | if ( ! empty( $removable_css ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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'] ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 ) ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 ); |
||
0 ignored issues
–
show
It seems like
$ipath defined by $this->getpath($iurl) on line 721 can also be of type false ; however, autoptimizeStyles::is_datauri_candidate() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
726 | if ( $is_datauri_candidate ) { |
||
727 | $datauri = $this->build_or_get_datauri_image( $ipath ); |
||
728 | $base64data = $datauri['base64data']; |
||
0 ignored issues
–
show
$base64data is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
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' ) ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 ) ), '\\' ); |
||
0 ignored issues
–
show
It seems like
$path defined by $this->getpath($url) on line 844 can also be of type false ; however, autoptimizeStyles::fixurls() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
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 ) ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
851 | $code = $tmpstyle; |
||
852 | $this->alreadyminified = true; |
||
853 | } elseif ( $this->can_inject_late( $path, $code ) ) { |
||
854 | $code = self::build_injectlater_marker( $path, md5( $code ) ); |
||
0 ignored issues
–
show
It seems like
$path defined by $this->getpath($url) on line 844 can also be of type false ; however, autoptimizeBase::build_injectlater_marker() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
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 ) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
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 . '" />'; |
||
0 ignored issues
–
show
The variable
$preload_css_block does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
![]() |
|||
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" />'; |
||
0 ignored issues
–
show
The variable
$noscript_css_block does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
![]() |
|||
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 ); |
||
0 ignored issues
–
show
The variable
$inlined_ccss_block does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
![]() |
|||
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 ) |
||
0 ignored issues
–
show
|
|||
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 ) { |
||
0 ignored issues
–
show
|
|||
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 ) ) { |
||
0 ignored issues
–
show
|
|||
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 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.