Completed
Push — master ( c92350...ab0c5b )
by frank
01:42
created

autoptimizeStyles::getcontent()   D

Complexity

Conditions 15
Paths 56

Size

Total Lines 100

Duplication

Lines 10
Ratio 10 %

Importance

Changes 0
Metric Value
cc 15
nc 56
nop 0
dl 10
loc 100
rs 4.7333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    private $dontmove        = array();
48
    private $options         = array();
49
    private $minify_excluded = true;
50
51
    // public $cdn_url; // Used all over the place implicitly, so will have to be either public or protected :/ .
52
53
    // Reads the page and collects style tags.
54
    public function read( $options )
55
    {
56
        $noptimizeCSS = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
57
        if ( $noptimizeCSS ) {
58
            return false;
59
        }
60
61
        $whitelistCSS = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
62 View Code Duplication
        if ( ! empty( $whitelistCSS ) ) {
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...
63
            $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...
64
        }
65
66
        $removableCSS = apply_filters( 'autoptimize_filter_css_removables', '' );
67 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...
68
            $this->cssremovables = array_filter( array_map( 'trim', explode( ',', $removableCSS ) ) );
69
        }
70
71
        $this->cssinlinesize = apply_filters( 'autoptimize_filter_css_inlinesize', 256 );
72
73
        // filter to "late inject minified CSS", default to true for now (it is faster).
74
        $this->inject_min_late = apply_filters( 'autoptimize_filter_css_inject_min_late', true );
75
76
        // Remove everything that's not the header.
77 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...
78
            $content             = explode( '</head>', $this->content, 2 );
79
            $this->content       = $content[0] . '</head>';
80
            $this->restofcontent = $content[1];
81
        }
82
83
        // Determine whether we're doing CSS-files aggregation or not.
84
        if ( isset( $options['aggregate'] ) && ! $options['aggregate'] ) {
85
            $this->aggregate = false;
86
        }
87
        // Returning true for "dontaggregate" turns off aggregation.
88
        if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) {
89
            $this->aggregate = false;
90
        }
91
92
        // include inline?
93
        if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) {
94
            $this->include_inline = true;
95
        }
96
97
        // List of CSS strings which are excluded from autoptimization.
98
        $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $options['css_exclude'], $this->content );
99 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...
100
            $this->dontmove = array_filter( array_map( 'trim', explode( ',', $excludeCSS ) ) );
101
        } else {
102
            $this->dontmove = array();
103
        }
104
105
        // forcefully exclude CSS with data-noptimize attrib.
106
        $this->dontmove[] = 'data-noptimize';
107
108
        // Should we defer css?
109
        // value: true / false.
110
        $this->defer = $options['defer'];
111
        $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
112
113
        // Should we inline while deferring?
114
        // value: inlined CSS.
115
        $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $options['defer_inline'], $this->content );
116
117
        // Should we inline?
118
        // value: true / false.
119
        $this->inline = $options['inline'];
120
        $this->inline = apply_filters( 'autoptimize_filter_css_inline', $this->inline, $this->content );
121
122
        // Store cdn url.
123
        $this->cdn_url = $options['cdn_url'];
124
125
        // Store data: URIs setting for later use.
126
        $this->datauris = $options['datauris'];
127
128
        // Determine whether excluded files should be minified if not yet so.
129
        if ( ! $options['minify_excluded'] && $options['aggregate'] ) {
130
            $this->minify_excluded = false;
131
        }
132
        $this->minify_excluded = apply_filters( 'autoptimize_filter_css_minify_excluded', $this->minify_excluded );
133
134
        // noptimize me.
135
        $this->content = $this->hide_noptimize( $this->content );
136
137
        // Exclude (no)script, as those may contain CSS which should be left as is.
138
        $this->content = $this->replace_contents_with_marker_if_exists(
139
                'SCRIPT',
140
                '<script',
141
                '#<(?:no)?script.*?<\/(?:no)?script>#is',
142
                $this->content
143
        );
144
145
        // Save IE hacks.
146
        $this->content = $this->hide_iehacks( $this->content );
147
148
        // Hide HTML comments.
149
        $this->content = $this->hide_comments( $this->content );
150
151
        // Get <style> and <link>.
152
        if ( preg_match_all( '#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi', $this->content, $matches ) ) {
153
154
            foreach ( $matches[0] as $tag ) {
155
                if ( $this->isremovable( $tag, $this->cssremovables ) ) {
156
                    $this->content = str_replace( $tag, '', $this->content );
157
                } elseif ( $this->ismovable( $tag ) ) {
158
                    // Get the media.
159
                    if ( false !== strpos( $tag, 'media=' ) ) {
160
                        preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $medias );
161
                        $medias = explode( ',', $medias[1] );
162
                        $media = array();
163
                        foreach ( $medias as $elem ) {
164
                            /* $media[] = current(explode(' ',trim($elem),2)); */
165
                            if ( empty( $elem ) ) {
166
                                $elem = 'all';
167
                            }
168
169
                            $media[] = $elem;
170
                        }
171
                    } else {
172
                        // No media specified - applies to all.
173
                        $media = array( 'all' );
174
                    }
175
176
                    $media = apply_filters( 'autoptimize_filter_css_tagmedia', $media, $tag );
177
178
                    if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
179
                        // <link>.
180
                        $url  = current( explode( '?', $source[2], 2 ) );
181
                        $path = $this->getpath( $url );
182
183
                        if ( false !== $path && preg_match( '#\.css$#', $path ) ) {
184
                            // Good link.
185
                            $this->css[] = array( $media, $path );
186
                        } else {
187
                            // Link is dynamic (.php etc).
188
                            $new_tag = $this->optionally_defer_excluded( $tag, 'none' );
189 View Code Duplication
                            if ( $new_tag !== '' && $new_tag !== $tag ) {
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...
190
                                $this->content = str_replace( $tag, $new_tag, $this->content );
191
                            }
192
                            $tag = '';
193
                        }
194
                    } else {
195
                        // Inline css in style tags can be wrapped in comment tags, so restore comments.
196
                        $tag = $this->restore_comments( $tag );
197
                        preg_match( '#<style.*>(.*)</style>#Usmi', $tag, $code );
198
199
                        // And re-hide them to be able to to the removal based on tag.
200
                        $tag = $this->hide_comments( $tag );
201
202
                        if ( $this->include_inline ) {
203
                            $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
204
                            $this->css[] = array( $media, 'INLINE;' . $code );
205
                        } else {
206
                            $tag = '';
207
                        }
208
                    }
209
210
                    // Remove the original style tag.
211
                    $this->content = str_replace( $tag, '', $this->content );
212
                } else {
213
                    if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
214
                        $exploded_url = explode( '?', $source[2], 2 );
215
                        $url          = $exploded_url[0];
216
                        $path         = $this->getpath( $url );
217
                        $new_tag      = $tag;
218
219
                        // Excluded CSS, minify that file:
220
                        // -> if aggregate is on and exclude minify is on
221
                        // -> if aggregate is off and the file is not in dontmove.
222 View Code Duplication
                        if ( $path && $this->minify_excluded ) {
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...
223
                            $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false );
224
                            if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) {
225
                                $minified_url = $this->minify_single( $path );
226
                                if ( ! empty( $minified_url ) ) {
227
                                    // Replace orig URL with cached minified URL.
228
                                    $new_tag = str_replace( $url, $minified_url, $tag );
229
                                }
230
                            }
231
                        }
232
233
                        // Optionally defer (preload) non-aggregated CSS.
234
                        $new_tag = $this->optionally_defer_excluded( $new_tag, $url );
235
236
                        // And replace!
237 View Code Duplication
                        if ( $new_tag !== '' && $new_tag !== $tag ) {
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...
238
                            $this->content = str_replace( $tag, $new_tag, $this->content );
239
                        }
240
                    }
241
                }
242
            }
243
            return true;
244
        }
245
246
        // Really, no styles?
247
        return false;
248
    }
249
250
    /**
251
     * Checks if non-optimized CSS is to be preloaded and if so return
252
     * the tag with preload code.
253
     *
254
     * @param string $tag (required).
255
     * @param string $url (optional).
256
     *
257
     * @return string $new_tag
258
     */
259
    private function optionally_defer_excluded( $tag, $url = '' )
260
    {
261
        // Defer single CSS if "inline & defer" is ON and there is inline CSS.
262
        if ( $this->defer && ! empty( $this->defer_inline ) ) {
263
            // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
264
            $_preload_onload = apply_filters(
265
                'autoptimize_filter_css_preload_onload',
266
                "this.onload=null;this.rel='stylesheet'",
267
                $url
268
            );
269
            // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
270
            $new_tag = '<noscript>' . autoptimizeUtils::remove_id_from_node( $tag ) . '</noscript>' . str_replace(
271
                array(
272
                    "rel='stylesheet'",
273
                    'rel="stylesheet"',
274
                ),
275
                "rel='preload' as='style' onload=\"" . $_preload_onload . "\"",
276
                $tag
277
            );
278
        } else {
279
            $new_tag = $tag;
280
        }
281
282
        return $new_tag;
283
    }
284
285
    /**
286
     * Checks if the local file referenced by $path is a valid
287
     * candidate for being inlined into a data: URI
288
     *
289
     * @param string $path
290
     * @return boolean
291
     */
292
    private function is_datauri_candidate( $path )
293
    {
294
        // Call only once since it's called from a loop.
295
        static $max_size = null;
296
        if ( null === $max_size ) {
297
            $max_size = $this->get_datauri_maxsize();
298
        }
299
300
        if ( $path && preg_match( '#\.(jpe?g|png|gif|webp|bmp)$#i', $path ) &&
301
            file_exists( $path ) && is_readable( $path ) && filesize( $path ) <= $max_size ) {
302
303
            // Seems we have a candidate.
304
            $is_candidate = true;
305
        } else {
306
            // Filter allows overriding default decision (which checks for local file existence).
307
            $is_candidate = apply_filters( 'autoptimize_filter_css_is_datauri_candidate', false, $path );
308
        }
309
310
        return $is_candidate;
311
    }
312
313
    /**
314
     * Returns the amount of bytes that shouldn't be exceeded if a file is to
315
     * be inlined into a data: URI. Defaults to 4096, passed through
316
     * `autoptimize_filter_css_datauri_maxsize` filter.
317
     *
318
     * @return mixed
319
     */
320
    private function get_datauri_maxsize()
321
    {
322
        static $max_size = null;
323
324
        /**
325
         * No need to apply the filter multiple times in case the
326
         * method itself is invoked multiple times during a single request.
327
         * This prevents some wild stuff like having different maxsizes
328
         * for different files/site-sections etc. But if you're into that sort
329
         * of thing you're probably better of building assets completely
330
         * outside of WordPress anyway.
331
         */
332
        if ( null === $max_size ) {
333
            $max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', 4096 );
334
        }
335
336
        return $max_size;
337
    }
338
339
    private function check_datauri_exclude_list( $url )
340
    {
341
        static $exclude_list = null;
342
        $no_datauris         = array();
343
344
        // Again, skip doing certain stuff repeatedly when loop-called.
345
        if ( null === $exclude_list ) {
346
            $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
347
            $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
348
        }
349
350
        $matched = false;
351
352
        if ( ! empty( $exclude_list ) ) {
353
            foreach ( $no_datauris as $no_datauri ) {
354
                if ( false !== strpos( $url, $no_datauri ) ) {
355
                    $matched = true;
356
                    break;
357
                }
358
            }
359
        }
360
361
        return $matched;
362
    }
363
364
    private function build_or_get_datauri_image( $path )
365
    {
366
        /**
367
         * TODO/FIXME: document the required return array format, or better yet,
368
         * use a string, since we don't really need an array for this. That would, however,
369
         * require changing even more code, which is not happening right now...
370
         */
371
372
        // Allows short-circuiting datauri generation for an image.
373
        $result = apply_filters( 'autoptimize_filter_css_datauri_image', array(), $path );
374
        if ( ! empty( $result ) ) {
375
            if ( is_array( $result ) && isset( $result['full'] ) && isset( $result['base64data'] ) ) {
376
                return $result;
377
            }
378
        }
379
380
        $hash = md5( $path );
381
        $check = new autoptimizeCache( $hash, 'img' );
382
        if ( $check->check() ) {
383
            // we have the base64 image in cache.
384
            $headAndData = $check->retrieve();
385
            $_base64data = explode( ';base64,', $headAndData );
386
            $base64data  = $_base64data[1];
387
            unset( $_base64data );
388
        } else {
389
            // It's an image and we don't have it in cache, get the type by extension.
390
            $exploded_path = explode( '.', $path );
391
            $type = end( $exploded_path );
392
393
            switch ( $type ) {
394
                case 'jpg':
395
                case 'jpeg':
396
                    $dataurihead = 'data:image/jpeg;base64,';
397
                    break;
398
                case 'gif':
399
                    $dataurihead = 'data:image/gif;base64,';
400
                    break;
401
                case 'png':
402
                    $dataurihead = 'data:image/png;base64,';
403
                    break;
404
                case 'bmp':
405
                    $dataurihead = 'data:image/bmp;base64,';
406
                    break;
407
                case 'webp':
408
                    $dataurihead = 'data:image/webp;base64,';
409
                    break;
410
                default:
411
                    $dataurihead = 'data:application/octet-stream;base64,';
412
            }
413
414
            // Encode the data.
415
            $base64data  = base64_encode( file_get_contents( $path ) );
416
            $headAndData = $dataurihead . $base64data;
417
418
            // Save in cache.
419
            $check->cache( $headAndData, 'text/plain' );
420
        }
421
        unset( $check );
422
423
        return array( 'full' => $headAndData, 'base64data' => $base64data );
424
    }
425
426
    /**
427
     * Given an array of key/value pairs to replace in $string,
428
     * it does so by replacing the longest-matching strings first.
429
     *
430
     * @param string $string
431
     * @param array  $replacements
432
     *
433
     * @return string
434
     */
435
    protected static function replace_longest_matches_first( $string, $replacements = array() )
436
    {
437
        if ( ! empty( $replacements ) ) {
438
            // Sort the replacements array by key length in desc order (so that the longest strings are replaced first).
439
            $keys = array_map( 'strlen', array_keys( $replacements ) );
440
            array_multisort( $keys, SORT_DESC, $replacements );
441
            $string = str_replace( array_keys( $replacements ), array_values( $replacements ), $string );
442
        }
443
444
        return $string;
445
    }
446
447
    /**
448
     * Rewrites/Replaces any ASSETS_REGEX-matching urls in a string.
449
     * Removes quotes/cruft around each one and passes it through to
450
     * `autoptimizeBase::url_replace_cdn()`.
451
     * Replacements are performed in a `longest-match-replaced-first` way.
452
     *
453
     * @param string $code CSS code.
454
     *
455
     * @return string
456
     */
457
    public function replace_urls( $code = '' )
458
    {
459
        $replacements = array();
460
461
        preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
462
        if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
463
            foreach ( $url_src_matches[1] as $count => $original_url ) {
464
                // Removes quotes and other cruft.
465
                $url = trim( $original_url, " \t\n\r\0\x0B\"'" );
466
467
                /**
468
                 * TODO/FIXME: Add a way for other code / callable to be called here
469
                 * and provide it's own results for the $replacements array
470
                 * for the "current" key.
471
                 * If such a result is returned/provided, we sholud then avoid
472
                 * calling url_replace_cdn() here for the current iteration.
473
                 *
474
                 * This would maybe allow the inlining logic currently present
475
                 * in `autoptimizeStyles::rewrite_assets()` to be "pulled out"
476
                 * and given as a callable to this method or something... and
477
                 * then we could "just" call `replace_urls()` from within
478
                 * `autoptimizeStyles::rewrite_assets()` and avoid some
479
                 * (currently present) code/logic duplication.
480
                 */
481
482
                // Do CDN replacement if needed.
483 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...
484
                    $replacement_url = $this->url_replace_cdn( $url );
485
                    // Prepare replacements array.
486
                    $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
487
                        $original_url, $replacement_url, $url_src_matches[1][$count]
488
                    );
489
                }
490
            }
491
        }
492
493
        $code = self::replace_longest_matches_first( $code, $replacements );
494
495
        return $code;
496
    }
497
498
    /**
499
     * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
500
     * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
501
     * filter is used.
502
     *
503
     * @param string $code
504
     * @return string
505
     */
506
    public function hide_fontface_and_maybe_cdn( $code )
507
    {
508
        // Proceed only if @font-face declarations exist within $code.
509
        preg_match_all( self::FONT_FACE_REGEX, $code, $fontfaces );
510
        if ( isset( $fontfaces[0] ) ) {
511
            // Check if we need to cdn fonts or not.
512
            $do_font_cdn = apply_filters( 'autoptimize_filter_css_fonts_cdn', false );
513
514
            foreach ( $fontfaces[0] as $full_match ) {
515
                // Keep original match so we can search/replace it.
516
                $match_search = $full_match;
517
518
                // Do font cdn if needed.
519
                if ( $do_font_cdn ) {
520
                    $full_match = $this->replace_urls( $full_match );
521
                }
522
523
                // Replace declaration with its base64 encoded string.
524
                $replacement = self::build_marker( 'FONTFACE', $full_match );
525
                $code = str_replace( $match_search, $replacement, $code );
526
            }
527
        }
528
529
        return $code;
530
    }
531
532
    /**
533
     * Restores original @font-face declarations that have been "hidden"
534
     * using `hide_fontface_and_maybe_cdn()`.
535
     *
536
     * @param string $code
537
     * @return string
538
     */
539
    public function restore_fontface( $code )
540
    {
541
        return $this->restore_marked_content( 'FONTFACE', $code );
542
    }
543
544
    // Re-write (and/or inline) referenced assets.
545
    public function rewrite_assets( $code )
546
    {
547
        // Handle @font-face rules by hiding and processing them separately.
548
        $code = $this->hide_fontface_and_maybe_cdn( $code );
549
550
        /**
551
         * TODO/FIXME:
552
         * Certain code parts below are kind-of repeated now in `replace_urls()`, which is not ideal.
553
         * There is maybe a way to separate/refactor things and then be able to keep
554
         * the ASSETS_REGEX rewriting/handling logic in a single place (along with removing quotes/cruft from matched urls).
555
         * See comments in `replace_urls()` regarding this. The idea is to extract the inlining
556
         * logic out (which is the only real difference between replace_urls() and the code below), but still
557
         * achieve identical results as before.
558
         */
559
560
        // Re-write (and/or inline) URLs to point them to the CDN host.
561
        $url_src_matches = array();
562
        $imgreplace = array();
563
        // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
564
        preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
565
        if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
566
            foreach ( $url_src_matches[1] as $count => $original_url ) {
567
                // Removes quotes and other cruft.
568
                $url = trim( $original_url, " \t\n\r\0\x0B\"'" );
569
570
                // If datauri inlining is turned on, do it.
571
                $inlined = false;
572
                if ( $this->datauris ) {
573
                    $iurl = $url;
574
                    if ( false !== strpos( $iurl, '?' ) ) {
575
                        $iurl = strtok( $iurl, '?' );
576
                    }
577
578
                    $ipath = $this->getpath( $iurl );
579
580
                    $excluded = $this->check_datauri_exclude_list( $ipath );
581
                    if ( ! $excluded ) {
582
                        $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 578 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...
583
                        if ( $is_datauri_candidate ) {
584
                            $datauri     = $this->build_or_get_datauri_image( $ipath );
585
                            $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...
586
                            // Add it to the list for replacement.
587
                            $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
588
                                    $original_url,
589
                                    $datauri['full'],
590
                                    $url_src_matches[1][$count]
591
                            );
592
                            $inlined = true;
593
                        }
594
                    }
595
                }
596
597
                /**
598
                 * Doing CDN URL replacement for every found match (if CDN is
599
                 * specified). This way we make sure to do it even if
600
                 * inlining isn't turned on, or if a resource is skipped from
601
                 * being inlined for whatever reason above.
602
                 */
603 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...
604
                    // Just do the "simple" CDN replacement.
605
                    $replacement_url = $this->url_replace_cdn( $url );
606
                    $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
607
                        $original_url, $replacement_url, $url_src_matches[1][$count]
608
                    );
609
                }
610
            }
611
        }
612
613
        $code = self::replace_longest_matches_first( $code, $imgreplace );
614
615
        // Replace back font-face markers with actual font-face declarations.
616
        $code = $this->restore_fontface( $code );
617
618
        return $code;
619
    }
620
621
    // Joins and optimizes CSS.
622
    public function minify()
623
    {
624
        foreach ( $this->css as $group ) {
625
            list( $media, $css ) = $group;
626
            if ( preg_match( '#^INLINE;#', $css ) ) {
627
                // <style>.
628
                $css = preg_replace( '#^INLINE;#', '', $css );
629
                $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash.
630
                $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
631
                if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
632
                    $css = $tmpstyle;
633
                    $this->alreadyminified = true;
634
                }
635
            } else {
636
                // <link>
637
                if ( false !== $css && file_exists( $css ) && is_readable( $css ) ) {
638
                    $cssPath = $css;
639
                    $css = self::fixurls( $cssPath, file_get_contents( $cssPath ) );
640
                    $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
641
                    $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $cssPath );
642 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...
643
                        $css = $tmpstyle;
644
                        $this->alreadyminified = true;
645
                    } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
646
                        $css = self::build_injectlater_marker( $cssPath, md5( $css ) );
647
                    }
648
                } else {
649
                    // Couldn't read CSS. Maybe getpath isn't working?
650
                    $css = '';
651
                }
652
            }
653
654
            foreach ( $media as $elem ) {
655
                if ( ! empty( $css ) ) {
656
                    if ( ! isset( $this->csscode[$elem] ) ) {
657
                        $this->csscode[$elem] = '';
658
                    }
659
                    $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
660
                }
661
            }
662
        }
663
664
        // Check for duplicate code.
665
        $md5list = array();
666
        $tmpcss  = $this->csscode;
667
        foreach ( $tmpcss as $media => $code ) {
668
            $md5sum    = md5( $code );
669
            $medianame = $media;
670
            foreach ( $md5list as $med => $sum ) {
671
                // If same code.
672
                if ( $sum === $md5sum ) {
673
                    // Add the merged code.
674
                    $medianame                 = $med . ', ' . $media;
675
                    $this->csscode[$medianame] = $code;
676
                    $md5list[$medianame]       = $md5list[$med];
677
                    unset( $this->csscode[$med], $this->csscode[$media], $md5list[$med] );
678
                }
679
            }
680
            $md5list[$medianame] = $md5sum;
681
        }
682
        unset( $tmpcss );
683
684
        // Manage @imports, while is for recursive import management.
685
        foreach ( $this->csscode as &$thiscss ) {
686
            // Flag to trigger import reconstitution and var to hold external imports.
687
            $fiximports       = false;
688
            $external_imports = '';
689
690
            // remove comments to avoid importing commented-out imports.
691
            $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
692
            while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
693
                foreach ( $matches[0] as $import ) {
694
                    if ( $this->isremovable( $import, $this->cssremovables ) ) {
695
                        $thiscss = str_replace( $import, '', $thiscss );
696
                        $import_ok = true;
697
                    } else {
698
                        $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
699
                        $path = $this->getpath( $url );
700
                        $import_ok = false;
701
                        if ( file_exists( $path ) && is_readable( $path ) ) {
702
                            $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), "\\" );
703
                            $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code );
704
                            $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
705 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...
706
                                $code = $tmpstyle;
707
                                $this->alreadyminified = true;
708
                            } elseif ( $this->can_inject_late( $path, $code ) ) {
709
                                $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 699 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...
710
                            }
711
712
                            if ( ! empty( $code ) ) {
713
                                $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
714
                                if ( ! empty( $tmp_thiscss ) ) {
715
                                    $thiscss = $tmp_thiscss;
716
                                    $import_ok = true;
717
                                    unset( $tmp_thiscss );
718
                                }
719
                            }
720
                            unset( $code );
721
                        }
722
                    }
723
                    if ( ! $import_ok ) {
724
                        // External imports and general fall-back.
725
                        $external_imports .= $import;
726
727
                        $thiscss    = str_replace( $import, '', $thiscss );
728
                        $fiximports = true;
729
                    }
730
                }
731
                $thiscss = preg_replace( '#/\*FILESTART\*/#', '', $thiscss );
732
                $thiscss = preg_replace( '#/\*FILESTART2\*/#', '/*FILESTART*/', $thiscss );
733
734
                // and update $thiscss_nocomments before going into next iteration in while loop.
735
                $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
736
            }
737
            unset( $thiscss_nocomments );
738
739
            // Add external imports to top of aggregated CSS.
740
            if ( $fiximports ) {
741
                $thiscss = $external_imports . $thiscss;
742
            }
743
        }
744
        unset( $thiscss );
745
746
        // $this->csscode has all the uncompressed code now.
747
        foreach ( $this->csscode as &$code ) {
748
            // Check for already-minified code.
749
            $hash = md5( $code );
750
            do_action( 'autoptimize_action_css_hash', $hash );
751
            $ccheck = new autoptimizeCache( $hash, 'css' );
752
            if ( $ccheck->check() ) {
753
                $code = $ccheck->retrieve();
754
                $this->hashmap[md5( $code )] = $hash;
755
                continue;
756
            }
757
            unset( $ccheck );
758
759
            // Rewrite and/or inline referenced assets.
760
            $code = $this->rewrite_assets( $code );
761
762
            // Minify.
763
            $code = $this->run_minifier_on( $code );
764
765
            // Bring back INJECTLATER stuff.
766
            $code = $this->inject_minified( $code );
767
768
            // Filter results.
769
            $tmp_code = apply_filters( 'autoptimize_css_after_minify', $code );
770
            if ( ! empty( $tmp_code ) ) {
771
                $code = $tmp_code;
772
                unset( $tmp_code );
773
            }
774
775
            $this->hashmap[md5( $code )] = $hash;
776
        }
777
778
        unset( $code );
779
        return true;
780
    }
781
782
    public function run_minifier_on( $code )
783
    {
784
        if ( ! $this->alreadyminified ) {
785
            $do_minify = apply_filters( 'autoptimize_css_do_minify', true );
786
787 View Code Duplication
            if ( $do_minify ) {
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...
788
                $cssmin   = new autoptimizeCSSmin();
789
                $tmp_code = trim( $cssmin->run( $code ) );
790
791
                if ( ! empty( $tmp_code ) ) {
792
                    $code = $tmp_code;
793
                    unset( $tmp_code );
794
                }
795
            }
796
        }
797
798
        return $code;
799
    }
800
801
    // Caches the CSS in uncompressed, deflated and gzipped form.
802
    public function cache()
803
    {
804
        // CSS cache.
805
        foreach ( $this->csscode as $media => $code ) {
806
            $md5 = $this->hashmap[md5( $code )];
807
            $cache = new autoptimizeCache( $md5, 'css' );
808
            if ( ! $cache->check() ) {
809
                // Cache our code.
810
                $cache->cache( $code, 'text/css' );
811
            }
812
            $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
813
        }
814
    }
815
816
    // Returns the content.
817
    public function getcontent()
818
    {
819
        // Restore the full content (only applies when "autoptimize_filter_css_justhead" filter is true).
820
        if ( ! empty( $this->restofcontent ) ) {
821
            $this->content .= $this->restofcontent;
822
            $this->restofcontent = '';
823
        }
824
825
        // type is not added by default.
826
        $type_css = '';
827
        if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
828
            $type_css = 'type="text/css" ';
829
        }
830
831
        // Inject the new stylesheets.
832
        $replaceTag = array( '<title', 'before' );
833
        $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
834
835
        if ( $this->inline ) {
836
            foreach ( $this->csscode as $media => $code ) {
837
                $this->inject_in_html( '<style ' . $type_css . 'media="' . $media . '">' . $code . '</style>', $replaceTag );
838
            }
839
        } else {
840
            if ( $this->defer ) {
841
                $preloadCssBlock    = '';
842
                $inlined_ccss_block = '';
843
                $noScriptCssBlock   = "<noscript id=\"aonoscrcss\">";
844
845
                $defer_inline_code = $this->defer_inline;
846
                if ( ! empty( $defer_inline_code ) ) {
847
                    if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
848
                        $iCssHash = md5( $defer_inline_code );
849
                        $iCssCache = new autoptimizeCache( $iCssHash, 'css' );
850
                        if ( $iCssCache->check() ) {
851
                            // we have the optimized inline CSS in cache.
852
                            $defer_inline_code = $iCssCache->retrieve();
853 View Code Duplication
                        } else {
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...
854
                            $cssmin   = new autoptimizeCSSmin();
855
                            $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...
856
857
                            if ( ! empty( $tmp_code ) ) {
858
                                $defer_inline_code = $tmp_code;
859
                                $iCssCache->cache( $defer_inline_code, 'text/css' );
860
                                unset( $tmp_code );
861
                            }
862
                        }
863
                    }
864
                    // inlined critical css set here, but injected when full CSS is injected
865
                    // to avoid CSS containing SVG with <title tag receiving the full CSS link.
866
                    $inlined_ccss_block = '<style ' . $type_css . 'id="aoatfcss" media="all">' . $defer_inline_code . '</style>';
867
                }
868
            }
869
870
            foreach ( $this->url as $media => $url ) {
871
                $url = $this->url_replace_cdn( $url );
872
873
                // Add the stylesheet either deferred (import at bottom) or normal links in head.
874
                if ( $this->defer ) {
875
                    $preloadOnLoad = autoptimizeConfig::get_ao_css_preload_onload();
876
877
                    $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...
878
                    $noScriptCssBlock .= '<link ' . $type_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...
879
                } else {
880
                    if ( strlen( $this->csscode[$media] ) > $this->cssinlinesize ) {
881
                        $this->inject_in_html( '<link ' . $type_css . 'media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag );
882
                    } elseif ( strlen( $this->csscode[$media] ) > 0 ) {
883
                        $this->inject_in_html( '<style ' . $type_css . 'media="' . $media . '">' . $this->csscode[$media] . '</style>', $replaceTag );
884
                    }
885
                }
886
            }
887
888
            if ( $this->defer ) {
889
                $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
890
                $noScriptCssBlock .= '</noscript>';
891
                // Inject inline critical CSS, the preloaded full CSS and the noscript-CSS.
892
                $this->inject_in_html( $inlined_ccss_block . $preloadCssBlock . $noScriptCssBlock, $replaceTag );
0 ignored issues
show
Bug introduced by
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

  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...
893
894
                // Adds preload polyfill at end of body tag.
895
                $this->inject_in_html(
896
                    apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
897
                    apply_filters( 'autoptimize_css_preload_polyfill_injectat', array( '</body>', 'before' ) )
898
                );
899
            }
900
        }
901
902
        // restore comments.
903
        $this->content = $this->restore_comments( $this->content );
904
905
        // restore IE hacks.
906
        $this->content = $this->restore_iehacks( $this->content );
907
908
        // restore (no)script.
909
        $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
910
911
        // Restore noptimize.
912
        $this->content = $this->restore_noptimize( $this->content );
913
914
        // Return the modified stylesheet.
915
        return $this->content;
916
    }
917
918
    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...
919
    {
920
        // Switch all imports to the url() syntax.
921
        $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
922
923
        if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
924
            $file = str_replace( WP_ROOT_DIR, '/', $file );
925
            /**
926
             * rollback as per https://github.com/futtta/autoptimize/issues/94
927
             * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
928
             */
929
            $dir = dirname( $file ); // Like /themes/expound/css.
930
931
            /**
932
             * $dir should not contain backslashes, since it's used to replace
933
             * urls, but it can contain them when running on Windows because
934
             * fixurls() is sometimes called with `ABSPATH . 'index.php'`
935
             */
936
            $dir = str_replace( '\\', '/', $dir );
937
            unset( $file ); // not used below at all.
938
939
            $replace = array();
940
            foreach ( $matches[1] as $k => $url ) {
941
                // Remove quotes.
942
                $url    = trim( $url, " \t\n\r\0\x0B\"'" );
943
                $noQurl = trim( $url, "\"'" );
944
                if ( $url !== $noQurl ) {
945
                    $removedQuotes = true;
946
                } else {
947
                    $removedQuotes = false;
948
                }
949
950
                if ( '' === $noQurl ) {
951
                    continue;
952
                }
953
954
                $url = $noQurl;
955
                if ( '/' === $url[0] || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
956
                    // URL is protocol-relative, host-relative or something we don't touch.
957
                    continue;
958
                } else {
959
                    // Relative URL.
960
                    /**
961
                     * rollback as per https://github.com/futtta/autoptimize/issues/94
962
                     * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
963
                     */
964
                    $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
965
                    $newurl = apply_filters( 'autoptimize_filter_css_fixurl_newurl', $newurl );
966
967
                    /**
968
                     * Hash the url + whatever was behind potentially for replacement
969
                     * We must do this, or different css classes referencing the same bg image (but
970
                     * different parts of it, say, in sprites and such) loose their stuff...
971
                     */
972
                    $hash = md5( $url . $matches[2][$k] );
973
                    $code = str_replace( $matches[0][$k], $hash, $code );
974
975
                    if ( $removedQuotes ) {
976
                        $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
977
                    } else {
978
                        $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
979
                    }
980
                }
981
            }
982
983
            $code = self::replace_longest_matches_first( $code, $replace );
984
        }
985
986
        return $code;
987
    }
988
989
    private function ismovable( $tag )
990
    {
991
        if ( ! $this->aggregate ) {
992
            return false;
993
        }
994
995
        if ( ! empty( $this->whitelist ) ) {
996
            foreach ( $this->whitelist as $match ) {
0 ignored issues
show
Bug introduced by
The expression $this->whitelist of type string is not traversable.
Loading history...
997
                if ( false !== strpos( $tag, $match ) ) {
998
                    return true;
999
                }
1000
            }
1001
            // no match with whitelist.
1002
            return false;
1003
        } else {
1004
            if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
1005
                foreach ( $this->dontmove as $match ) {
1006
                    if ( false !== strpos( $tag, $match ) ) {
1007
                        // Matched something.
1008
                        return false;
1009
                    }
1010
                }
1011
            }
1012
1013
            // If we're here it's safe to move.
1014
            return true;
1015
        }
1016
    }
1017
1018
    private function can_inject_late( $cssPath, $css )
1019
    {
1020
        $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $cssPath );
1021
        if ( true !== $this->inject_min_late ) {
1022
            // late-inject turned off.
1023
            return false;
1024
        } elseif ( ( false === strpos( $cssPath, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $cssPath ) === $cssPath ) ) {
1025
            // file not minified based on filename & filter.
1026
            return false;
1027
        } elseif ( false !== strpos( $css, '@import' ) ) {
1028
            // can't late-inject files with imports as those need to be aggregated.
1029
            return false;
1030
        } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
1031
            // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
1032
            return false;
1033
        } 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...
1034
            // don't late-inject CSS with images if CDN is set OR if image inlining is on.
1035
            return false;
1036
        } else {
1037
            // phew, all is safe, we can late-inject.
1038
            return true;
1039
        }
1040
    }
1041
1042
    /**
1043
     * Minifies (and cdn-replaces) a single local css file
1044
     * and returns its (cached) url.
1045
     *
1046
     * @param string $filepath Filepath.
1047
     * @param bool   $cache_miss Optional. Force a cache miss. Default false.
1048
     *
1049
     * @return bool|string Url pointing to the minified css file or false.
1050
     */
1051
    public function minify_single( $filepath, $cache_miss = false )
1052
    {
1053
        $contents = $this->prepare_minify_single( $filepath );
1054
1055
        if ( empty( $contents ) ) {
1056
            return false;
1057
        }
1058
1059
        // Check cache.
1060
        $hash  = 'single_' . md5( $contents );
1061
        $cache = new autoptimizeCache( $hash, 'css' );
1062
        do_action( 'autoptimize_action_css_hash', $hash );
1063
1064
        // If not in cache already, minify...
1065
        if ( ! $cache->check() || $cache_miss ) {
1066
            // Fixurls...
1067
            $contents = self::fixurls( $filepath, $contents );
1068
            // CDN-replace any referenced assets if needed...
1069
            $contents = $this->hide_fontface_and_maybe_cdn( $contents );
1070
            $contents = $this->replace_urls( $contents );
1071
            $contents = $this->restore_fontface( $contents );
1072
            // Now minify...
1073
            $cssmin   = new autoptimizeCSSmin();
1074
            $contents = trim( $cssmin->run( $contents ) );
1075
            // Store in cache.
1076
            $cache->cache( $contents, 'text/css' );
1077
        }
1078
1079
        $url = $this->build_minify_single_url( $cache );
1080
1081
        return $url;
1082
    }
1083
1084
    /**
1085
     * Returns whether we're doing aggregation or not.
1086
     *
1087
     * @return bool
1088
     */
1089
    public function aggregating()
1090
    {
1091
        return $this->aggregate;
1092
    }
1093
1094
    public function getOptions()
1095
    {
1096
        return $this->options;
1097
    }
1098
1099
    public function replaceOptions( $options )
1100
    {
1101
        $this->options = $options;
1102
    }
1103
1104
    public function setOption( $name, $value )
1105
    {
1106
        $this->options[$name] = $value;
1107
        $this->$name = $value;
1108
    }
1109
1110
    public function getOption( $name )
1111
    {
1112
        return $this->options[$name];
1113
    }
1114
}
1115