Completed
Push — master ( 65039b...7edd50 )
by frank
02:19
created

autoptimizeStyles::optionally_defer_excluded()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 2
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
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
     * @font-face\s* # Match @font-face and some spaces
18
     * (             # Start group 1
19
     * \{            # Match {
20
     * (?:           # A non-capturing group
21
     * [^{}]+        # Match anything except {} one or more times
22
     * |             # Or
23
     * (?1)          # Recurse/rerun the expression of group 1
24
     * )*            # Repeat 0 or more times
25
     * \}            # Match }
26
     * )             # End group 1
27
     * ~xs';
28
     */
29
    const FONT_FACE_REGEX = '~@font-face\s*(\{(?:[^{}]+|(?1))*\})~xsi'; // added `i` flag for case-insensitivity.
30
31
    private $css             = array();
32
    private $csscode         = array();
33
    private $url             = array();
34
    private $restofcontent   = '';
35
    private $datauris        = false;
36
    private $hashmap         = array();
37
    private $alreadyminified = false;
38
    private $aggregate       = true;
39
    private $inline          = false;
40
    private $defer           = false;
41
    private $defer_inline    = false;
42
    private $whitelist       = '';
43
    private $cssinlinesize   = '';
44
    private $cssremovables   = array();
45
    private $include_inline  = false;
46
    private $inject_min_late = '';
47
48
    // public $cdn_url; // Used all over the place implicitly, so will have to be either public or protected :/ .
49
50
    // Reads the page and collects style tags.
51
    public function read( $options )
52
    {
53
        $noptimizeCSS = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
54
        if ( $noptimizeCSS ) {
55
            return false;
56
        }
57
58
        $whitelistCSS = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
59
        if ( ! empty( $whitelistCSS ) ) {
60
            $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistCSS ) ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like array_filter(array_map('...e(',', $whitelistCSS))) of type array is incompatible with the declared type string of property $whitelist.

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..

Loading history...
61
        }
62
63
        $removableCSS = apply_filters( 'autoptimize_filter_css_removables', '' );
64 View Code Duplication
        if ( ! empty( $removableCSS ) ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
65
            $this->cssremovables = array_filter( array_map( 'trim', explode( ',', $removableCSS ) ) );
66
        }
67
68
        $this->cssinlinesize = apply_filters( 'autoptimize_filter_css_inlinesize', 256 );
69
70
        // filter to "late inject minified CSS", default to true for now (it is faster).
71
        $this->inject_min_late = apply_filters( 'autoptimize_filter_css_inject_min_late', true );
72
73
        // Remove everything that's not the header.
74 View Code Duplication
        if ( apply_filters( 'autoptimize_filter_css_justhead', $options['justhead'] ) ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
75
            $content             = explode( '</head>', $this->content, 2 );
76
            $this->content       = $content[0] . '</head>';
77
            $this->restofcontent = $content[1];
78
        }
79
80
        // Determine whether we're doing CSS-files aggregation or not.
81
        if ( isset( $options['aggregate'] ) && ! $options['aggregate'] ) {
82
            $this->aggregate = false;
83
        }
84
        // Returning true for "dontaggregate" turns off aggregation.
85
        if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) {
86
            $this->aggregate = false;
87
        }
88
89
        // include inline?
90
        if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) {
91
            $this->include_inline = true;
92
        }
93
94
        // List of CSS strings which are excluded from autoptimization.
95
        $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $options['css_exclude'], $this->content );
96 View Code Duplication
        if ( '' !== $excludeCSS ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
97
            $this->dontmove = array_filter( array_map( 'trim', explode( ',', $excludeCSS ) ) );
0 ignored issues
show
Bug introduced by
The property dontmove does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
98
        } else {
99
            $this->dontmove = array();
100
        }
101
102
        // forcefully exclude CSS with data-noptimize attrib.
103
        $this->dontmove[] = 'data-noptimize';
104
105
        // Should we defer css?
106
        // value: true / false.
107
        $this->defer = $options['defer'];
108
        $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
109
110
        // Should we inline while deferring?
111
        // value: inlined CSS.
112
        $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $options['defer_inline'], $this->content );
113
114
        // Should we inline?
115
        // value: true / false.
116
        $this->inline = $options['inline'];
117
        $this->inline = apply_filters( 'autoptimize_filter_css_inline', $this->inline, $this->content );
118
119
        // Store cdn url.
120
        $this->cdn_url = $options['cdn_url'];
0 ignored issues
show
Bug introduced by
The property cdn_url does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
121
122
        // Store data: URIs setting for later use.
123
        $this->datauris = $options['datauris'];
124
125
        // noptimize me.
126
        $this->content = $this->hide_noptimize( $this->content );
127
128
        // Exclude (no)script, as those may contain CSS which should be left as is.
129
        $this->content = $this->replace_contents_with_marker_if_exists(
130
                'SCRIPT',
131
                '<script',
132
                '#<(?:no)?script.*?<\/(?:no)?script>#is',
133
                $this->content
134
        );
135
136
        // Save IE hacks.
137
        $this->content = $this->hide_iehacks( $this->content );
138
139
        // Hide HTML comments.
140
        $this->content = $this->hide_comments( $this->content );
141
142
        // Get <style> and <link>.
143
        if ( preg_match_all( '#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi', $this->content, $matches ) ) {
144
145
            foreach ( $matches[0] as $tag ) {
146
                if ( $this->isremovable( $tag, $this->cssremovables ) ) {
147
                    $this->content = str_replace( $tag, '', $this->content );
148
                } elseif ( $this->ismovable( $tag ) ) {
149
                    // Get the media.
150
                    if ( false !== strpos( $tag, 'media=' ) ) {
151
                        preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $medias );
152
                        $medias = explode( ',', $medias[1] );
153
                        $media = array();
154
                        foreach ( $medias as $elem ) {
155
                            /* $media[] = current(explode(' ',trim($elem),2)); */
156
                            if ( empty( $elem ) ) {
157
                                $elem = 'all';
158
                            }
159
160
                            $media[] = $elem;
161
                        }
162
                    } else {
163
                        // No media specified - applies to all.
164
                        $media = array( 'all' );
165
                    }
166
167
                    $media = apply_filters( 'autoptimize_filter_css_tagmedia', $media, $tag );
168
169
                    if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
170
                        // <link>.
171
                        $url  = current( explode( '?', $source[2], 2 ) );
172
                        $path = $this->getpath( $url );
173
174
                        if ( false !== $path && preg_match( '#\.css$#', $path ) ) {
175
                            // Good link.
176
                            $this->css[] = array( $media, $path );
177
                        } else {
178
                            // Link is dynamic (.php etc).
179
                            $new_tag = $this->optionally_defer_excluded( $tag, 'none' );
180
                            if ( $new_tag !== $tag ) {
181
                                $this->content = str_replace( $tag, $new_tag, $this->content );
182
                            }
183
                            $tag = '';
184
                        }
185
                    } else {
186
                        // Inline css in style tags can be wrapped in comment tags, so restore comments.
187
                        $tag = $this->restore_comments( $tag );
188
                        preg_match( '#<style.*>(.*)</style>#Usmi', $tag, $code );
189
190
                        // And re-hide them to be able to to the removal based on tag.
191
                        $tag = $this->hide_comments( $tag );
192
193
                        if ( $this->include_inline ) {
194
                            $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
195
                            $this->css[] = array( $media, 'INLINE;' . $code );
196
                        } else {
197
                            $tag = '';
198
                        }
199
                    }
200
201
                    // Remove the original style tag.
202
                    $this->content = str_replace( $tag, '', $this->content );
203
                } else {
204
                    // Excluded CSS, minify if getpath and filter says so...
205
                    if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
206
                        $exploded_url = explode( '?', $source[2], 2 );
207
                        $url          = $exploded_url[0];
208
                        $path         = $this->getpath( $url );
209
210
                        if ( $path && apply_filters( 'autoptimize_filter_css_minify_excluded', true, $url ) ) {
211
                            $minified_url = $this->minify_single( $path );
212
                            if ( ! empty( $minified_url ) ) {
213
                                // Replace orig URL with cached minified URL.
214
                                $new_tag = str_replace( $url, $minified_url, $tag );
215
                            } else {
216
                                $new_tag = $tag;
217
                            }
218
219
                            $new_tag = $this->optionally_defer_excluded( $new_tag, $url );
220
221
                            // And replace!
222
                            $this->content = str_replace( $tag, $new_tag, $this->content );
223
                        }
224
                    }
225
                }
226
            }
227
            return true;
228
        }
229
230
        // Really, no styles?
231
        return false;
232
    }
233
234
    /**
235
     * Checks if non-optimized CSS is to be preloaded and if so return
236
     * the tag with preload code.
237
     *
238
     * @param string $new_tag (required).
239
     * @param string $url (optional).
240
     *
241
     * @return string $new_tag
242
     */
243
    private function optionally_defer_excluded( $new_tag, $url = '' )
244
    {
245
        // Defer single CSS if "inline & defer" is ON and there is inline CSS.
246
        if ( $this->defer && ! empty( $this->defer_inline ) ) {
247
            // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
248
            $_preload_onload = apply_filters(
249
                'autoptimize_filter_css_preload_onload',
250
                "this.onload=null;this.rel='stylesheet'",
251
                $url
252
            );
253
            // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
254
            $new_tag = '<noscript>' . $new_tag . '</noscript>' . str_replace(
255
                array(
256
                    "rel='stylesheet'",
257
                    'rel="stylesheet"',
258
                ),
259
                "rel='preload' as='style' onload=\"" . $_preload_onload . "\"",
260
                $new_tag
261
            );
262
        }
263
        return $new_tag;
264
    }
265
266
    /**
267
     * Checks if the local file referenced by $path is a valid
268
     * candidate for being inlined into a data: URI
269
     *
270
     * @param string $path
271
     * @return boolean
272
     */
273
    private function is_datauri_candidate( $path )
274
    {
275
        // Call only once since it's called from a loop.
276
        static $max_size = null;
277
        if ( null === $max_size ) {
278
            $max_size = $this->get_datauri_maxsize();
279
        }
280
281
        if ( $path && preg_match( '#\.(jpe?g|png|gif|webp|bmp)$#i', $path ) &&
282
            file_exists( $path ) && is_readable( $path ) && filesize( $path ) <= $max_size ) {
283
284
            // Seems we have a candidate.
285
            $is_candidate = true;
286
        } else {
287
            // Filter allows overriding default decision (which checks for local file existence).
288
            $is_candidate = apply_filters( 'autoptimize_filter_css_is_datauri_candidate', false, $path );
289
        }
290
291
        return $is_candidate;
292
    }
293
294
    /**
295
     * Returns the amount of bytes that shouldn't be exceeded if a file is to
296
     * be inlined into a data: URI. Defaults to 4096, passed through
297
     * `autoptimize_filter_css_datauri_maxsize` filter.
298
     *
299
     * @return mixed
300
     */
301
    private function get_datauri_maxsize()
302
    {
303
        static $max_size = null;
304
305
        /**
306
         * No need to apply the filter multiple times in case the
307
         * method itself is invoked multiple times during a single request.
308
         * This prevents some wild stuff like having different maxsizes
309
         * for different files/site-sections etc. But if you're into that sort
310
         * of thing you're probably better of building assets completely
311
         * outside of WordPress anyway.
312
         */
313
        if ( null === $max_size ) {
314
            $max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', 4096 );
315
        }
316
317
        return $max_size;
318
    }
319
320
    private function check_datauri_exclude_list( $url )
321
    {
322
        static $exclude_list = null;
323
        $no_datauris         = array();
324
325
        // Again, skip doing certain stuff repeatedly when loop-called.
326
        if ( null === $exclude_list ) {
327
            $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
328
            $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
329
        }
330
331
        $matched = false;
332
333
        if ( ! empty( $exclude_list ) ) {
334
            foreach ( $no_datauris as $no_datauri ) {
335
                if ( false !== strpos( $url, $no_datauri ) ) {
336
                    $matched = true;
337
                    break;
338
                }
339
            }
340
        }
341
342
        return $matched;
343
    }
344
345
    private function build_or_get_datauri_image( $path )
346
    {
347
        /**
348
         * TODO/FIXME: document the required return array format, or better yet,
349
         * use a string, since we don't really need an array for this. That would, however,
350
         * require changing even more code, which is not happening right now...
351
         */
352
353
        // Allows short-circuiting datauri generation for an image.
354
        $result = apply_filters( 'autoptimize_filter_css_datauri_image', array(), $path );
355
        if ( ! empty( $result ) ) {
356
            if ( is_array( $result ) && isset( $result['full'] ) && isset( $result['base64data'] ) ) {
357
                return $result;
358
            }
359
        }
360
361
        $hash = md5( $path );
362
        $check = new autoptimizeCache( $hash, 'img' );
363
        if ( $check->check() ) {
364
            // we have the base64 image in cache.
365
            $headAndData = $check->retrieve();
366
            $_base64data = explode( ';base64,', $headAndData );
367
            $base64data  = $_base64data[1];
368
            unset( $_base64data );
369
        } else {
370
            // It's an image and we don't have it in cache, get the type by extension.
371
            $exploded_path = explode( '.', $path );
372
            $type = end( $exploded_path );
373
374
            switch ( $type ) {
375
                case 'jpg':
376
                case 'jpeg':
377
                    $dataurihead = 'data:image/jpeg;base64,';
378
                    break;
379
                case 'gif':
380
                    $dataurihead = 'data:image/gif;base64,';
381
                    break;
382
                case 'png':
383
                    $dataurihead = 'data:image/png;base64,';
384
                    break;
385
                case 'bmp':
386
                    $dataurihead = 'data:image/bmp;base64,';
387
                    break;
388
                case 'webp':
389
                    $dataurihead = 'data:image/webp;base64,';
390
                    break;
391
                default:
392
                    $dataurihead = 'data:application/octet-stream;base64,';
393
            }
394
395
            // Encode the data.
396
            $base64data  = base64_encode( file_get_contents( $path ) );
397
            $headAndData = $dataurihead . $base64data;
398
399
            // Save in cache.
400
            $check->cache( $headAndData, 'text/plain' );
401
        }
402
        unset( $check );
403
404
        return array( 'full' => $headAndData, 'base64data' => $base64data );
405
    }
406
407
    /**
408
     * Given an array of key/value pairs to replace in $string,
409
     * it does so by replacing the longest-matching strings first.
410
     *
411
     * @param string $string
412
     * @param array  $replacements
413
     *
414
     * @return string
415
     */
416
    protected static function replace_longest_matches_first( $string, $replacements = array() )
417
    {
418
        if ( ! empty( $replacements ) ) {
419
            // Sort the replacements array by key length in desc order (so that the longest strings are replaced first).
420
            $keys = array_map( 'strlen', array_keys( $replacements ) );
421
            array_multisort( $keys, SORT_DESC, $replacements );
422
            $string = str_replace( array_keys( $replacements ), array_values( $replacements ), $string );
423
        }
424
425
        return $string;
426
    }
427
428
    /**
429
     * Rewrites/Replaces any ASSETS_REGEX-matching urls in a string.
430
     * Removes quotes/cruft around each one and passes it through to
431
     * `autoptimizeBase::url_replace_cdn()`.
432
     * Replacements are performed in a `longest-match-replaced-first` way.
433
     *
434
     * @param string $code CSS code.
435
     *
436
     * @return string
437
     */
438
    public function replace_urls( $code = '' )
439
    {
440
        $replacements = array();
441
442
        preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
443
        if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
444
            foreach ( $url_src_matches[1] as $count => $original_url ) {
445
                // Removes quotes and other cruft.
446
                $url = trim( $original_url, " \t\n\r\0\x0B\"'" );
447
448
                /**
449
                 * TODO/FIXME: Add a way for other code / callable to be called here
450
                 * and provide it's own results for the $replacements array
451
                 * for the "current" key.
452
                 * If such a result is returned/provided, we sholud then avoid
453
                 * calling url_replace_cdn() here for the current iteration.
454
                 *
455
                 * This would maybe allow the inlining logic currently present
456
                 * in `autoptimizeStyles::rewrite_assets()` to be "pulled out"
457
                 * and given as a callable to this method or something... and
458
                 * then we could "just" call `replace_urls()` from within
459
                 * `autoptimizeStyles::rewrite_assets()` and avoid some
460
                 * (currently present) code/logic duplication.
461
                 */
462
463
                // Do CDN replacement if needed.
464 View Code Duplication
                if ( ! empty( $this->cdn_url ) ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
465
                    $replacement_url = $this->url_replace_cdn( $url );
466
                    // Prepare replacements array.
467
                    $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
468
                        $original_url, $replacement_url, $url_src_matches[1][$count]
469
                    );
470
                }
471
            }
472
        }
473
474
        $code = self::replace_longest_matches_first( $code, $replacements );
475
476
        return $code;
477
    }
478
479
    /**
480
     * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
481
     * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
482
     * filter is used.
483
     *
484
     * @param string $code
485
     * @return string
486
     */
487
    public function hide_fontface_and_maybe_cdn( $code )
488
    {
489
        // Proceed only if @font-face declarations exist within $code.
490
        preg_match_all( self::FONT_FACE_REGEX, $code, $fontfaces );
491
        if ( isset( $fontfaces[0] ) ) {
492
            // Check if we need to cdn fonts or not.
493
            $do_font_cdn = apply_filters( 'autoptimize_filter_css_fonts_cdn', false );
494
495
            foreach ( $fontfaces[0] as $full_match ) {
496
                // Keep original match so we can search/replace it.
497
                $match_search = $full_match;
498
499
                // Do font cdn if needed.
500
                if ( $do_font_cdn ) {
501
                    $full_match = $this->replace_urls( $full_match );
502
                }
503
504
                // Replace declaration with its base64 encoded string.
505
                $replacement = self::build_marker( 'FONTFACE', $full_match );
506
                $code = str_replace( $match_search, $replacement, $code );
507
            }
508
        }
509
510
        return $code;
511
    }
512
513
    /**
514
     * Restores original @font-face declarations that have been "hidden"
515
     * using `hide_fontface_and_maybe_cdn()`.
516
     *
517
     * @param string $code
518
     * @return string
519
     */
520
    public function restore_fontface( $code )
521
    {
522
        return $this->restore_marked_content( 'FONTFACE', $code );
523
    }
524
525
    // Re-write (and/or inline) referenced assets.
526
    public function rewrite_assets( $code )
527
    {
528
        // Handle @font-face rules by hiding and processing them separately.
529
        $code = $this->hide_fontface_and_maybe_cdn( $code );
530
531
        /**
532
         * TODO/FIXME:
533
         * Certain code parts below are kind-of repeated now in `replace_urls()`, which is not ideal.
534
         * There is maybe a way to separate/refactor things and then be able to keep
535
         * the ASSETS_REGEX rewriting/handling logic in a single place (along with removing quotes/cruft from matched urls).
536
         * See comments in `replace_urls()` regarding this. The idea is to extract the inlining
537
         * logic out (which is the only real difference between replace_urls() and the code below), but still
538
         * achieve identical results as before.
539
         */
540
541
        // Re-write (and/or inline) URLs to point them to the CDN host.
542
        $url_src_matches = array();
543
        $imgreplace = array();
544
        // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
545
        preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
546
        if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
547
            foreach ( $url_src_matches[1] as $count => $original_url ) {
548
                // Removes quotes and other cruft.
549
                $url = trim( $original_url, " \t\n\r\0\x0B\"'" );
550
551
                // If datauri inlining is turned on, do it.
552
                $inlined = false;
553
                if ( $this->datauris ) {
554
                    $iurl = $url;
555
                    if ( false !== strpos( $iurl, '?' ) ) {
556
                        $iurl = strtok( $iurl, '?' );
557
                    }
558
559
                    $ipath = $this->getpath( $iurl );
560
561
                    $excluded = $this->check_datauri_exclude_list( $ipath );
562
                    if ( ! $excluded ) {
563
                        $is_datauri_candidate = $this->is_datauri_candidate( $ipath );
0 ignored issues
show
Security Bug introduced by
It seems like $ipath defined by $this->getpath($iurl) on line 559 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 false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
564
                        if ( $is_datauri_candidate ) {
565
                            $datauri     = $this->build_or_get_datauri_image( $ipath );
566
                            $base64data  = $datauri['base64data'];
0 ignored issues
show
Unused Code introduced by
$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 $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.

Loading history...
567
                            // Add it to the list for replacement.
568
                            $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
569
                                    $original_url,
570
                                    $datauri['full'],
571
                                    $url_src_matches[1][$count]
572
                            );
573
                            $inlined = true;
574
                        }
575
                    }
576
                }
577
578
                /**
579
                 * Doing CDN URL replacement for every found match (if CDN is
580
                 * specified). This way we make sure to do it even if
581
                 * inlining isn't turned on, or if a resource is skipped from
582
                 * being inlined for whatever reason above.
583
                 */
584 View Code Duplication
                if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
585
                    // Just do the "simple" CDN replacement.
586
                    $replacement_url = $this->url_replace_cdn( $url );
587
                    $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
588
                        $original_url, $replacement_url, $url_src_matches[1][$count]
589
                    );
590
                }
591
            }
592
        }
593
594
        $code = self::replace_longest_matches_first( $code, $imgreplace );
595
596
        // Replace back font-face markers with actual font-face declarations.
597
        $code = $this->restore_fontface( $code );
598
599
        return $code;
600
    }
601
602
    // Joins and optimizes CSS.
603
    public function minify()
604
    {
605
        foreach ( $this->css as $group ) {
606
            list( $media, $css ) = $group;
607
            if ( preg_match( '#^INLINE;#', $css ) ) {
608
                // <style>.
609
                $css = preg_replace( '#^INLINE;#', '', $css );
610
                $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash.
611
                $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
612
                if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
613
                    $css = $tmpstyle;
614
                    $this->alreadyminified = true;
615
                }
616
            } else {
617
                // <link>
618
                if ( false !== $css && file_exists( $css ) && is_readable( $css ) ) {
619
                    $cssPath = $css;
620
                    $css = self::fixurls( $cssPath, file_get_contents( $cssPath ) );
621
                    $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
622
                    $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $cssPath );
623 View Code Duplication
                    if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
624
                        $css = $tmpstyle;
625
                        $this->alreadyminified = true;
626
                    } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
627
                        $css = self::build_injectlater_marker( $cssPath, md5( $css ) );
628
                    }
629
                } else {
630
                    // Couldn't read CSS. Maybe getpath isn't working?
631
                    $css = '';
632
                }
633
            }
634
635
            foreach ( $media as $elem ) {
636
                if ( ! empty( $css ) ) {
637
                    if ( ! isset( $this->csscode[$elem] ) ) {
638
                        $this->csscode[$elem] = '';
639
                    }
640
                    $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
641
                }
642
            }
643
        }
644
645
        // Check for duplicate code.
646
        $md5list = array();
647
        $tmpcss  = $this->csscode;
648
        foreach ( $tmpcss as $media => $code ) {
649
            $md5sum    = md5( $code );
650
            $medianame = $media;
651
            foreach ( $md5list as $med => $sum ) {
652
                // If same code.
653
                if ( $sum === $md5sum ) {
654
                    // Add the merged code.
655
                    $medianame                 = $med . ', ' . $media;
656
                    $this->csscode[$medianame] = $code;
657
                    $md5list[$medianame]       = $md5list[$med];
658
                    unset( $this->csscode[$med], $this->csscode[$media], $md5list[$med] );
659
                }
660
            }
661
            $md5list[$medianame] = $md5sum;
662
        }
663
        unset( $tmpcss );
664
665
        // Manage @imports, while is for recursive import management.
666
        foreach ( $this->csscode as &$thiscss ) {
667
            // Flag to trigger import reconstitution and var to hold external imports.
668
            $fiximports       = false;
669
            $external_imports = '';
670
671
            // remove comments to avoid importing commented-out imports.
672
            $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
673
            while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
674
                foreach ( $matches[0] as $import ) {
675
                    if ( $this->isremovable( $import, $this->cssremovables ) ) {
676
                        $thiscss = str_replace( $import, '', $thiscss );
677
                        $import_ok = true;
678
                    } else {
679
                        $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
680
                        $path = $this->getpath( $url );
681
                        $import_ok = false;
682
                        if ( file_exists( $path ) && is_readable( $path ) ) {
683
                            $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), "\\" );
684
                            $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code );
685
                            $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
686 View Code Duplication
                            if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
687
                                $code = $tmpstyle;
688
                                $this->alreadyminified = true;
689
                            } elseif ( $this->can_inject_late( $path, $code ) ) {
690
                                $code = self::build_injectlater_marker( $path, md5( $code ) );
0 ignored issues
show
Security Bug introduced by
It seems like $path defined by $this->getpath($url) on line 680 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 false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
691
                            }
692
693
                            if ( ! empty( $code ) ) {
694
                                $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
695
                                if ( ! empty( $tmp_thiscss ) ) {
696
                                    $thiscss = $tmp_thiscss;
697
                                    $import_ok = true;
698
                                    unset( $tmp_thiscss );
699
                                }
700
                            }
701
                            unset( $code );
702
                        }
703
                    }
704
                    if ( ! $import_ok ) {
705
                        // External imports and general fall-back.
706
                        $external_imports .= $import;
707
708
                        $thiscss    = str_replace( $import, '', $thiscss );
709
                        $fiximports = true;
710
                    }
711
                }
712
                $thiscss = preg_replace( '#/\*FILESTART\*/#', '', $thiscss );
713
                $thiscss = preg_replace( '#/\*FILESTART2\*/#', '/*FILESTART*/', $thiscss );
714
715
                // and update $thiscss_nocomments before going into next iteration in while loop.
716
                $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
717
            }
718
            unset( $thiscss_nocomments );
719
720
            // Add external imports to top of aggregated CSS.
721
            if ( $fiximports ) {
722
                $thiscss = $external_imports . $thiscss;
723
            }
724
        }
725
        unset( $thiscss );
726
727
        // $this->csscode has all the uncompressed code now.
728
        foreach ( $this->csscode as &$code ) {
729
            // Check for already-minified code.
730
            $hash = md5( $code );
731
            do_action( 'autoptimize_action_css_hash', $hash );
732
            $ccheck = new autoptimizeCache( $hash, 'css' );
733
            if ( $ccheck->check() ) {
734
                $code = $ccheck->retrieve();
735
                $this->hashmap[md5( $code )] = $hash;
736
                continue;
737
            }
738
            unset( $ccheck );
739
740
            // Rewrite and/or inline referenced assets.
741
            $code = $this->rewrite_assets( $code );
742
743
            // Minify.
744
            $code = $this->run_minifier_on( $code );
745
746
            // Bring back INJECTLATER stuff.
747
            $code = $this->inject_minified( $code );
748
749
            // Filter results.
750
            $tmp_code = apply_filters( 'autoptimize_css_after_minify', $code );
751
            if ( ! empty( $tmp_code ) ) {
752
                $code = $tmp_code;
753
                unset( $tmp_code );
754
            }
755
756
            $this->hashmap[md5( $code )] = $hash;
757
        }
758
759
        unset( $code );
760
        return true;
761
    }
762
763
    public function run_minifier_on( $code )
764
    {
765
        if ( ! $this->alreadyminified ) {
766
            $do_minify = apply_filters( 'autoptimize_css_do_minify', true );
767
768
            if ( $do_minify ) {
769
                $cssmin   = new autoptimizeCSSmin();
770
                $tmp_code = trim( $cssmin->run( $code ) );
771
772
                if ( ! empty( $tmp_code ) ) {
773
                    $code = $tmp_code;
774
                    unset( $tmp_code );
775
                }
776
            }
777
        }
778
779
        return $code;
780
    }
781
782
    // Caches the CSS in uncompressed, deflated and gzipped form.
783
    public function cache()
784
    {
785
        // CSS cache.
786
        foreach ( $this->csscode as $media => $code ) {
787
            $md5 = $this->hashmap[md5( $code )];
788
            $cache = new autoptimizeCache( $md5, 'css' );
789
            if ( ! $cache->check() ) {
790
                // Cache our code.
791
                $cache->cache( $code, 'text/css' );
792
            }
793
            $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
794
        }
795
    }
796
797
    // Returns the content.
798
    public function getcontent()
799
    {
800
        // restore comments.
801
        $this->content = $this->restore_comments( $this->content );
802
803
        // restore IE hacks.
804
        $this->content = $this->restore_iehacks( $this->content );
805
806
        // restore (no)script.
807
        $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
808
809
        // Restore noptimize.
810
        $this->content = $this->restore_noptimize( $this->content );
811
812
        // Restore the full content.
813
        if ( ! empty( $this->restofcontent ) ) {
814
            $this->content .= $this->restofcontent;
815
            $this->restofcontent = '';
816
        }
817
818
        // Inject the new stylesheets.
819
        $replaceTag = array( '<title', 'before' );
820
        $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
821
822
        if ( $this->inline ) {
823
            foreach ( $this->csscode as $media => $code ) {
824
                $this->inject_in_html( '<style type="text/css" media="' . $media . '">' . $code . '</style>', $replaceTag );
825
            }
826
        } else {
827
            if ( $this->defer ) {
828
                $preloadCssBlock = '';
829
                $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
830
831
                $defer_inline_code = $this->defer_inline;
832
                if ( ! empty( $defer_inline_code ) ) {
833
                    if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
834
                        $iCssHash = md5( $defer_inline_code );
835
                        $iCssCache = new autoptimizeCache( $iCssHash, 'css' );
836 View Code Duplication
                        if ( $iCssCache->check() ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
837
                            // we have the optimized inline CSS in cache.
838
                            $defer_inline_code = $iCssCache->retrieve();
839
                        } else {
840
                            $cssmin   = new autoptimizeCSSmin();
841
                            $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
0 ignored issues
show
Documentation introduced by
$defer_inline_code is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
842
843
                            if ( ! empty( $tmp_code ) ) {
844
                                $defer_inline_code = $tmp_code;
845
                                $iCssCache->cache( $defer_inline_code, 'text/css' );
846
                                unset( $tmp_code );
847
                            }
848
                        }
849
                    }
850
                    $code_out = '<style type="text/css" id="aoatfcss" media="all">' . $defer_inline_code . '</style>';
851
                    $this->inject_in_html( $code_out, $replaceTag );
852
                }
853
            }
854
855
            foreach ( $this->url as $media => $url ) {
856
                $url = $this->url_replace_cdn( $url );
857
858
                // Add the stylesheet either deferred (import at bottom) or normal links in head.
859
                if ( $this->defer ) {
860
                    $preloadOnLoad = autoptimizeConfig::get_ao_css_preload_onload();
861
862
                    $preloadCssBlock .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preloadOnLoad . '" />';
0 ignored issues
show
Bug introduced by
The variable $preloadCssBlock 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

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
863
                    $noScriptCssBlock .= '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
0 ignored issues
show
Bug introduced by
The variable $noScriptCssBlock 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

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
864
                } else {
865
                    // $this->inject_in_html('<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag);
866
                    if ( strlen( $this->csscode[$media] ) > $this->cssinlinesize ) {
867
                        $this->inject_in_html( '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag );
868
                    } elseif ( strlen( $this->csscode[$media] ) > 0 ) {
869
                        $this->inject_in_html( '<style type="text/css" media="' . $media . '">' . $this->csscode[$media] . '</style>', $replaceTag );
870
                    }
871
                }
872
            }
873
874
            if ( $this->defer ) {
875
                $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
876
                $noScriptCssBlock .= '</noscript>';
877
                $this->inject_in_html( $preloadCssBlock . $noScriptCssBlock, $replaceTag );
878
879
                // Adds preload polyfill at end of body tag.
880
                $this->inject_in_html(
881
                    apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
882
                    array( '</body>', 'before' )
883
                );
884
            }
885
        }
886
887
        // Return the modified stylesheet.
888
        return $this->content;
889
    }
890
891
    static function fixurls( $file, $code )
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
892
    {
893
        // Switch all imports to the url() syntax.
894
        $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
895
896
        if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
897
            $file = str_replace( WP_ROOT_DIR, '/', $file );
898
            /**
899
             * rollback as per https://github.com/futtta/autoptimize/issues/94
900
             * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
901
             */
902
            $dir = dirname( $file ); // Like /themes/expound/css.
903
904
            /**
905
             * $dir should not contain backslashes, since it's used to replace
906
             * urls, but it can contain them when running on Windows because
907
             * fixurls() is sometimes called with `ABSPATH . 'index.php'`
908
             */
909
            $dir = str_replace( '\\', '/', $dir );
910
            unset( $file ); // not used below at all.
911
912
            $replace = array();
913
            foreach ( $matches[1] as $k => $url ) {
914
                // Remove quotes.
915
                $url    = trim( $url, " \t\n\r\0\x0B\"'" );
916
                $noQurl = trim( $url, "\"'" );
917
                if ( $url !== $noQurl ) {
918
                    $removedQuotes = true;
919
                } else {
920
                    $removedQuotes = false;
921
                }
922
923
                if ( '' === $noQurl ) {
924
                    continue;
925
                }
926
927
                $url = $noQurl;
928
                if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
929
                    // URL is protocol-relative, host-relative or something we don't touch.
930
                    continue;
931
                } else {
932
                    // Relative URL.
933
                    /**
934
                     * rollback as per https://github.com/futtta/autoptimize/issues/94
935
                     * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
936
                     */
937
                    $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
938
                    $newurl = apply_filters( 'autoptimize_filter_css_fixurl_newurl', $newurl );
939
940
                    /**
941
                     * Hash the url + whatever was behind potentially for replacement
942
                     * We must do this, or different css classes referencing the same bg image (but
943
                     * different parts of it, say, in sprites and such) loose their stuff...
944
                     */
945
                    $hash = md5( $url . $matches[2][$k] );
946
                    $code = str_replace( $matches[0][$k], $hash, $code );
947
948
                    if ( $removedQuotes ) {
949
                        $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
950
                    } else {
951
                        $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
952
                    }
953
                }
954
            }
955
956
            $code = self::replace_longest_matches_first( $code, $replace );
957
        }
958
959
        return $code;
960
    }
961
962
    private function ismovable( $tag )
963
    {
964
        if ( ! $this->aggregate ) {
965
            return false;
966
        }
967
968
        if ( ! empty( $this->whitelist ) ) {
969
            foreach ( $this->whitelist as $match ) {
0 ignored issues
show
Bug introduced by
The expression $this->whitelist of type string is not traversable.
Loading history...
970
                if ( false !== strpos( $tag, $match ) ) {
971
                    return true;
972
                }
973
            }
974
            // no match with whitelist.
975
            return false;
976
        } else {
977
            if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
978
                foreach ( $this->dontmove as $match ) {
979
                    if ( false !== strpos( $tag, $match ) ) {
980
                        // Matched something.
981
                        return false;
982
                    }
983
                }
984
            }
985
986
            // If we're here it's safe to move.
987
            return true;
988
        }
989
    }
990
991
    private function can_inject_late( $cssPath, $css )
992
    {
993
        $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $cssPath );
994
        if ( true !== $this->inject_min_late ) {
995
            // late-inject turned off.
996
            return false;
997
        } elseif ( ( false === strpos( $cssPath, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $cssPath ) === $cssPath ) ) {
998
            // file not minified based on filename & filter.
999
            return false;
1000
        } elseif ( false !== strpos( $css, '@import' ) ) {
1001
            // can't late-inject files with imports as those need to be aggregated.
1002
            return false;
1003
        } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
1004
            // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
1005
            return false;
1006
        } elseif ( ( ( $this->datauris == true ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1007
            // don't late-inject CSS with images if CDN is set OR if image inlining is on.
1008
            return false;
1009
        } else {
1010
            // phew, all is safe, we can late-inject.
1011
            return true;
1012
        }
1013
    }
1014
1015
    /**
1016
     * Minifies (and cdn-replaces) a single local css file
1017
     * and returns its (cached) url.
1018
     *
1019
     * @param string $filepath Filepath.
1020
     * @param bool   $cache_miss Optional. Force a cache miss. Default false.
1021
     *
1022
     * @return bool|string Url pointing to the minified css file or false.
1023
     */
1024
    public function minify_single( $filepath, $cache_miss = false )
1025
    {
1026
        $contents = $this->prepare_minify_single( $filepath );
1027
1028
        if ( empty( $contents ) ) {
1029
            return false;
1030
        }
1031
1032
        // Check cache.
1033
        $hash  = 'single_' . md5( $contents );
1034
        $cache = new autoptimizeCache( $hash, 'css' );
1035
1036
        // If not in cache already, minify...
1037 View Code Duplication
        if ( ! $cache->check() || $cache_miss ) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
1038
            // Fixurls...
1039
            $contents = self::fixurls( $filepath, $contents );
1040
            // CDN-replace any referenced assets if needed...
1041
            $contents = $this->replace_urls( $contents );
1042
            // Now minify...
1043
            $cssmin   = new autoptimizeCSSmin();
1044
            $contents = trim( $cssmin->run( $contents ) );
1045
            // Store in cache.
1046
            $cache->cache( $contents, 'text/css' );
1047
        }
1048
1049
        $url = $this->build_minify_single_url( $cache );
1050
1051
        return $url;
1052
    }
1053
1054
    /**
1055
     * Returns whether we're doing aggregation or not.
1056
     *
1057
     * @return bool
1058
     */
1059
    public function aggregating()
1060
    {
1061
        return $this->aggregate;
1062
    }
1063
1064
    public function getOptions()
1065
    {
1066
        return $this->options;
0 ignored issues
show
Bug introduced by
The property options does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1067
    }
1068
1069
    public function replaceOptions( $options )
1070
    {
1071
        $this->options = $options;
1072
    }
1073
1074
    public function setOption( $name, $value )
1075
    {
1076
        $this->options[$name] = $value;
1077
        $this->$name = $value;
1078
    }
1079
1080
    public function getOption( $name )
1081
    {
1082
        return $this->options[$name];
1083
    }
1084
}
1085