Completed
Push — master ( 8e7dca...6ec0d0 )
by frank
01:41
created

autoptimizeBase::inject_minified_callback()   D

Complexity

Conditions 14
Paths 250

Size

Total Lines 75

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
nc 250
nop 1
dl 0
loc 75
rs 4.3921
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
 * Base class other (more-specific) classes inherit from.
4
 */
5
6
if ( ! defined( 'ABSPATH' ) ) {
7
    exit;
8
}
9
10
abstract class autoptimizeBase
11
{
12
    /**
13
     * Holds content being processed (html, scripts, styles)
14
     *
15
     * @var string
16
     */
17
    protected $content = '';
18
19
    /**
20
     * Controls debug logging.
21
     *
22
     * @var bool
23
     */
24
    public $debug_log = false;
25
26
    public function __construct( $content )
27
    {
28
        $this->content = $content;
29
    }
30
31
    /**
32
     * Reads the page and collects tags.
33
     *
34
     * @param array $options Options.
35
     *
36
     * @return bool
37
     */
38
    abstract public function read( $options );
39
40
    /**
41
     * Joins and optimizes collected things.
42
     *
43
     * @return bool
44
     */
45
    abstract public function minify();
46
47
    /**
48
     * Caches the things.
49
     *
50
     * @return void
51
     */
52
    abstract public function cache();
53
54
    /**
55
     * Returns the content
56
     *
57
     * @return string
58
     */
59
    abstract public function getcontent();
60
61
    /**
62
     * Tranfsorms a given URL to a full local filepath if possible.
63
     * Returns local filepath or false.
64
     *
65
     * @param string $url URL to transform.
66
     *
67
     * @return bool|string
68
     */
69
    public function getpath( $url )
70
    {
71
        $url = apply_filters( 'autoptimize_filter_cssjs_alter_url', $url );
72
73
        if ( false !== strpos( $url, '%' ) ) {
74
            $url = urldecode( $url );
75
        }
76
77
        $site_host    = parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST );
78
        $content_host = parse_url( AUTOPTIMIZE_WP_ROOT_URL, PHP_URL_HOST );
79
80
        // Normalizing attempts...
81
        $double_slash_position = strpos( $url, '//' );
82
        if ( 0 === $double_slash_position ) {
83
            if ( is_ssl() ) {
84
                $url = 'https:' . $url;
85
            } else {
86
                $url = 'http:' . $url;
87
            }
88
        } elseif ( ( false === $double_slash_position ) && ( false === strpos( $url, $site_host ) ) ) {
89
            if ( AUTOPTIMIZE_WP_SITE_URL === $site_host ) {
90
                $url = AUTOPTIMIZE_WP_SITE_URL . $url;
91
            } else {
92
                $url = AUTOPTIMIZE_WP_SITE_URL . autoptimizeUtils::path_canonicalize( $url );
93
            }
94
        }
95
96
        if ( $site_host !== $content_host ) {
97
            $url = str_replace( AUTOPTIMIZE_WP_CONTENT_URL, AUTOPTIMIZE_WP_SITE_URL . AUTOPTIMIZE_WP_CONTENT_NAME, $url );
98
        }
99
100
        // First check; hostname wp site should be hostname of url!
101
        $url_host = @parse_url( $url, PHP_URL_HOST ); // @codingStandardsIgnoreLine
102
        if ( $url_host !== $site_host ) {
103
            /**
104
             * First try to get all domains from WPML (if available)
105
             * then explicitely declare $this->cdn_url as OK as well
106
             * then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames
107
             * each item in that array will be considered part of the same WP multisite installation
108
             */
109
            $multidomains = array();
110
111
            $multidomains_wpml = apply_filters( 'wpml_setting', array(), 'language_domains' );
112
            if ( ! empty( $multidomains_wpml ) ) {
113
                $multidomains = array_map( array( $this, 'get_url_hostname' ), $multidomains_wpml );
114
            }
115
116
            if ( ! empty( $this->cdn_url ) ) {
117
                $multidomains[] = parse_url( $this->cdn_url, PHP_URL_HOST );
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...
118
            }
119
120
            $multidomains = apply_filters( 'autoptimize_filter_cssjs_multidomain', $multidomains );
121
122
            if ( ! empty( $multidomains ) ) {
123
                if ( in_array( $url_host, $multidomains ) ) {
124
                    $url = str_replace( $url_host, $site_host, $url );
125
                } else {
126
                    return false;
127
                }
128
            } else {
129
                return false;
130
            }
131
        }
132
133
        // Try to remove "wp root url" from url while not minding http<>https.
134
        $tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_ROOT_URL );
135
136
        if ( $site_host !== $content_host ) {
137
            // As we replaced the content-domain with the site-domain, we should match against that.
138
            $tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_SITE_URL );
139
        }
140
141
        $tmp_url = preg_replace( '/https?:/', '', $url );
142
        $path    = str_replace( $tmp_ao_root, '', $tmp_url );
143
144
        // If path starts with :// or //, this is not a URL in the WP context and
145
        // we have to assume we can't aggregate.
146
        if ( preg_match( '#^:?//#', $path ) ) {
147
            // External script/css (adsense, etc).
148
            return false;
149
        }
150
151
        // Prepend with WP_ROOT_DIR to have full path to file.
152
        $path = str_replace( '//', '/', WP_ROOT_DIR . $path );
153
154
        // Final check: does file exist and is it readable?
155
        if ( file_exists( $path ) && is_file( $path ) && is_readable( $path ) ) {
156
            return $path;
157
        } else {
158
            return false;
159
        }
160
    }
161
162
    /**
163
     * Returns the hostname part of a given $url if we're able to parse it.
164
     * If not, it returns the original url (prefixed with http:// scheme in case
165
     * it was missing).
166
     * Used as callback for WPML multidomains filter.
167
     *
168
     * @param string $url URL.
169
     *
170
     * @return string
171
     */
172
    protected function get_url_hostname( $url )
173
    {
174
        // Checking that the url starts with something vaguely resembling a protocol.
175
        if ( ( 0 !== strpos( $url, 'http' ) ) && ( 0 !== strpos( $url, '//' ) ) ) {
176
            $url = 'http://' . $url;
177
        }
178
179
        // Grab the hostname.
180
        $hostname = parse_url( $url, PHP_URL_HOST );
181
182
        // Fallback when parse_url() fails.
183
        if ( empty( $hostname ) ) {
184
            $hostname = $url;
185
        }
186
187
        return $hostname;
188
    }
189
190
    /**
191
     * Hides everything between noptimize-comment tags.
192
     *
193
     * @param string $markup Markup to process.
194
     *
195
     * @return string
196
     */
197
    protected function hide_noptimize( $markup )
198
    {
199
        return $this->replace_contents_with_marker_if_exists(
200
            'NOPTIMIZE',
201
            '/<!--\s?noptimize\s?-->/',
202
            '#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is',
203
            $markup
204
        );
205
    }
206
207
    /**
208
     * Unhide noptimize-tags.
209
     *
210
     * @param string $markup Markup to process.
211
     *
212
     * @return string
213
     */
214
    protected function restore_noptimize( $markup )
215
    {
216
        return $this->restore_marked_content( 'NOPTIMIZE', $markup );
217
    }
218
219
    /**
220
     * Hides "iehacks" content.
221
     *
222
     * @param string $markup Markup to process.
223
     *
224
     * @return string
225
     */
226
    protected function hide_iehacks( $markup )
227
    {
228
        return $this->replace_contents_with_marker_if_exists(
229
            'IEHACK', // Marker name...
230
            '<!--[if', // Invalid regex, will fallback to search using strpos()...
231
            '#<!--\[if.*?\[endif\]-->#is', // Replacement regex...
232
            $markup
233
        );
234
    }
235
236
    /**
237
     * Restores "hidden" iehacks content.
238
     *
239
     * @param string $markup Markup to process.
240
     *
241
     * @return string
242
     */
243
    protected function restore_iehacks( $markup )
244
    {
245
        return $this->restore_marked_content( 'IEHACK', $markup );
246
    }
247
248
    /**
249
     * "Hides" content within HTML comments using a regex-based replacement
250
     * if HTML comment markers are found.
251
     * `<!--example-->` becomes `%%COMMENTS%%ZXhhbXBsZQ==%%COMMENTS%%`
252
     *
253
     * @param string $markup Markup to process.
254
     *
255
     * @return string
256
     */
257
    protected function hide_comments( $markup )
258
    {
259
        return $this->replace_contents_with_marker_if_exists(
260
            'COMMENTS',
261
            '<!--',
262
            '#<!--.*?-->#is',
263
            $markup
264
        );
265
    }
266
267
    /**
268
     * Restores original HTML comment markers inside a string whose HTML
269
     * comments have been "hidden" by using `hide_comments()`.
270
     *
271
     * @param string $markup Markup to process.
272
     *
273
     * @return string
274
     */
275
    protected function restore_comments( $markup )
276
    {
277
        return $this->restore_marked_content( 'COMMENTS', $markup );
278
    }
279
280
    /**
281
     * Replaces the given URL with the CDN-version of it when CDN replacement
282
     * is supposed to be done.
283
     *
284
     * @param string $url URL to process.
285
     *
286
     * @return string
287
     */
288
    public function url_replace_cdn( $url )
289
    {
290
        // For 2.3 back-compat in which cdn-ing appeared to be automatically
291
        // including WP subfolder/subdirectory into account as part of cdn-ing,
292
        // even though it might've caused serious troubles in certain edge-cases.
293
        $cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $this->cdn_url );
294
295
        // Allows API/filter to further tweak the cdn url...
296
        $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
297
        if ( ! empty( $cdn_url ) ) {
298
            $this->debug_log( 'before=' . $url );
299
300
            // Simple str_replace-based approach fails when $url is protocol-or-host-relative.
301
            $is_protocol_relative = autoptimizeUtils::is_protocol_relative( $url );
302
            $is_host_relative     = ( ! $is_protocol_relative && ( '/' === $url{0} ) );
303
            $cdn_url              = rtrim( $cdn_url, '/' );
304
305
            if ( $is_host_relative ) {
306
                // Prepending host-relative urls with the cdn url.
307
                $url = $cdn_url . $url;
308
            } else {
309
                // Either a protocol-relative or "regular" url, replacing it either way.
310
                if ( $is_protocol_relative ) {
311
                    // Massage $site_url so that simple str_replace() still "works" by
312
                    // searching for the protocol-relative version of AUTOPTIMIZE_WP_SITE_URL.
313
                    $site_url = str_replace( array( 'http:', 'https:' ), '', AUTOPTIMIZE_WP_SITE_URL );
314
                } else {
315
                    $site_url = AUTOPTIMIZE_WP_SITE_URL;
316
                }
317
                $this->debug_log( '`' . $site_url . '` -> `' . $cdn_url . '` in `' . $url . '`' );
318
                $url = str_replace( $site_url, $cdn_url, $url );
319
            }
320
321
            $this->debug_log( 'after=' . $url );
322
        }
323
324
        // Allow API filter to take further care of CDN replacement.
325
        $url = apply_filters( 'autoptimize_filter_base_replace_cdn', $url );
326
327
        return $url;
328
    }
329
330
    /**
331
     * Injects/replaces the given payload markup into `$this->content`
332
     * at the specified location.
333
     * If the specified tag cannot be found, the payload is appended into
334
     * $this->content along with a warning wrapped inside <!--noptimize--> tags.
335
     *
336
     * @param string $payload Markup to inject.
337
     * @param array  $where   Array specifying the tag name and method of injection.
338
     *                        Index 0 is the tag name (i.e., `</body>`).
339
     *                        Index 1 specifies ˛'before', 'after' or 'replace'. Defaults to 'before'.
340
     *
341
     * @return void
342
     */
343
    protected function inject_in_html( $payload, $where )
344
    {
345
        $warned   = false;
346
        $position = autoptimizeUtils::strpos( $this->content, $where[0] );
347
        if ( false !== $position ) {
348
            // Found the tag, setup content/injection as specified.
349
            if ( 'after' === $where[1] ) {
350
                $content = $where[0] . $payload;
351
            } elseif ( 'replace' === $where[1] ) {
352
                $content = $payload;
353
            } else {
354
                $content = $payload . $where[0];
355
            }
356
            // Place where specified.
357
            $this->content = autoptimizeUtils::substr_replace(
358
                $this->content,
359
                $content,
360
                $position,
361
                // Using plain strlen() should be safe here for now, since
362
                // we're not searching for multibyte chars here still...
363
                strlen( $where[0] )
364
            );
365
        } else {
366
            // Couldn't find what was specified, just append and add a warning.
367
            $this->content .= $payload;
368
            if ( ! $warned ) {
369
                $tag_display    = str_replace( array( '<', '>' ), '', $where[0] );
370
                $this->content .= '<!--noptimize--><!-- Autoptimize found a problem with the HTML in your Theme, tag `' . $tag_display . '` missing --><!--/noptimize-->';
371
                $warned         = true;
0 ignored issues
show
Unused Code introduced by
$warned 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...
372
            }
373
        }
374
    }
375
376
    /**
377
     * Returns true if given `$tag` is found in the list of `$removables`.
378
     *
379
     * @param string $tag Tag to search for.
380
     * @param array  $removables List of things considered completely removable.
381
     *
382
     * @return bool
383
     */
384
    protected function isremovable( $tag, $removables )
385
    {
386
        foreach ( $removables as $match ) {
387
            if ( false !== strpos( $tag, $match ) ) {
388
                return true;
389
            }
390
        }
391
392
        return false;
393
    }
394
395
    /**
396
     * Callback used in `self::inject_minified()`.
397
     *
398
     * @param array $matches Regex matches.
399
     *
400
     * @return string
401
     */
402
    public function inject_minified_callback( $matches )
403
    {
404
        static $conf = null;
405
        if ( null === $conf ) {
406
            $conf = autoptimizeConfig::instance();
407
        }
408
409
        /**
410
         * $matches[1] holds the whole match caught by regex in self::inject_minified(),
411
         * so we take that and split the string on `|`.
412
         * First element is the filepath, second is the md5 hash of contents
413
         * the filepath had when it was being processed.
414
         * If we don't have those, we'll bail out early.
415
        */
416
        $filepath = null;
417
        $filehash = null;
418
419
        // Grab the parts we need.
420
        $parts = explode( $matches[1], '|' );
421
        if ( ! empty( $parts ) ) {
422
            $filepath = isset( $parts[0] ) ? $parts[0] : null;
423
            $filehash = isset( $parts[1] ) ? $parts[1] : null;
424
        }
425
426
        // Bail early if something's not right...
427
        if ( ! $filepath || ! $filehash ) {
428
            return "\n";
429
        }
430
431
        $filecontent = file_get_contents( $filepath );
432
433
        // Some things are differently handled for css/js...
434
        $is_js_file = ( '.js' === substr( $filepath, -3, 3 ) );
435
436
        $is_css_file = false;
437
        if ( ! $is_js_file ) {
438
            $is_css_file = ( '.css' === substr( $filepath, -4, 4 ) );
439
        }
440
441
        // BOMs being nuked here unconditionally (regardless of where they are)!
442
        $filecontent = preg_replace( "#\x{EF}\x{BB}\x{BF}#", '', $filecontent );
443
444
        // Remove comments and blank lines.
445
        if ( $is_js_file ) {
446
            $filecontent = preg_replace( '#^\s*\/\/.*$#Um', '', $filecontent );
447
        }
448
449
        // Nuke un-important comments.
450
        $filecontent = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Um', '', $filecontent );
451
452
        // Normalize newlines.
453
        $filecontent = preg_replace( '#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#', "\n", $filecontent );
454
455
        // JS specifics.
456
        if ( $is_js_file ) {
457
            // Append a semicolon at the end of js files if it's missing.
458
            $last_char = substr( $filecontent, -1, 1 );
459
            if ( ';' !== $last_char && '}' !== $last_char ) {
460
                $filecontent .= ';';
461
            }
462
            // Check if try/catch should be used.
463
            $opt_js_try_catch = $conf->get( 'autoptimize_js_trycatch' );
464
            if ( 'on' === $opt_js_try_catch ) {
465
                // It should, wrap in try/catch.
466
                $filecontent = 'try{' . $filecontent . '}catch(e){}';
467
            }
468
        } elseif ( $is_css_file ) {
469
            $filecontent = autoptimizeStyles::fixurls( $filepath, $filecontent );
470
        } else {
471
            $filecontent = '';
472
        }
473
474
        // Return modified (or empty!) code/content.
475
        return "\n" . $filecontent;
476
    }
477
478
    /**
479
     * Inject already minified code in optimized JS/CSS.
480
     *
481
     * @param string $in Markup.
482
     *
483
     * @return string
484
     */
485
    protected function inject_minified( $in )
486
    {
487
        $out = $in;
488
489
        if ( false !== strpos( $in, '%%INJECTLATER%%' ) ) {
490
            $out = preg_replace_callback(
491
                '#\/\*\!%%INJECTLATER' . AUTOPTIMIZE_HASH . '%%(.*?)%%INJECTLATER%%\*\/#is',
492
                array( $this, 'inject_minified_callback' ),
493
                $in
494
            );
495
        }
496
497
        return $out;
498
    }
499
500
    /**
501
     * Specialized method to create the INJECTLATER marker.
502
     * These are somewhat "special", in the sense that they're additionally wrapped
503
     * within an "exclamation mark style" comment, so that they're not stripped
504
     * out by minifiers.
505
     * They also currently contain the hash of the file's contents too (unlike other markers).
506
     *
507
     * @param string $filepath Filepath.
508
     * @param string $hash Hash.
509
     *
510
     * @return string
511
     */
512
    public static function build_injectlater_marker( $filepath, $hash )
513
    {
514
        $contents = '/*!' . self::build_marker( 'INJECTLATER', $filepath, $hash ) . '*/';
515
516
        return $contents;
517
    }
518
519
    /**
520
     * Creates and returns a `%%`-style named marker which holds
521
     * the base64 encoded `$data`.
522
     * If `$hash` is provided, it's appended to the base64 encoded string
523
     * using `|` as the separator (in order to support building the
524
     * somewhat special/different INJECTLATER marker).
525
     *
526
     * @param string      $name Marker name.
527
     * @param string      $data Marker data which will be base64-encoded.
528
     * @param string|null $hash Optional.
529
     *
530
     * @return string
531
     */
532
    public static function build_marker( $name, $data, $hash = null )
533
    {
534
        // Start the marker, add the data.
535
        $marker = '%%' . $name . AUTOPTIMIZE_HASH . '%%' . base64_encode( $data );
536
537
        // Add the hash if provided.
538
        if ( null !== $hash ) {
539
            $marker .= '|' . $hash;
540
        }
541
542
        // Close the marker.
543
        $marker .= '%%' . $name . '%%';
544
545
        return $marker;
546
    }
547
548
    /**
549
     * Searches for `$search` in `$content` (using either `preg_match()`
550
     * or `strpos()`, depending on whether `$search` is a valid regex pattern or not).
551
     * If something is found, it replaces `$content` using `$re_replace_pattern`,
552
     * effectively creating our named markers (`%%{$marker}%%`.
553
     * These are then at some point replaced back to their actual/original/modified
554
     * contents using `autoptimizeBase::restore_marked_content()`.
555
     *
556
     * @param string $marker Marker name (without percent characters).
557
     * @param string $search A string or full blown regex pattern to search for in $content. Uses `strpos()` or `preg_match()`.
558
     * @param string $re_replace_pattern Regex pattern to use when replacing contents.
559
     * @param string $content Content to work on.
560
     *
561
     * @return string
562
     */
563
    protected function replace_contents_with_marker_if_exists( $marker, $search, $re_replace_pattern, $content )
564
    {
565
        $found = false;
0 ignored issues
show
Unused Code introduced by
$found 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...
566
567
        $is_regex = autoptimizeUtils::str_is_valid_regex( $search );
568
        if ( $is_regex ) {
569
            $found = preg_match( $search, $content );
570
        } else {
571
            $found = ( false !== strpos( $content, $search ) );
572
        }
573
574
        if ( $found ) {
575
            $content = preg_replace_callback(
576
                $re_replace_pattern,
577
                function( $matches ) use ( $marker ) {
578
                    return autoptimizeBase::build_marker( $marker, $matches[0] );
579
                },
580
                $content
581
            );
582
        }
583
584
        return $content;
585
    }
586
587
    /**
588
     * Complements `autoptimizeBase::replace_contents_with_marker_if_exists()`.
589
     *
590
     * @param string $marker Marker.
591
     * @param string $content Markup.
592
     *
593
     * @return string
594
     */
595
    protected function restore_marked_content( $marker, $content )
596
    {
597
        if ( false !== strpos( $content, $marker ) ) {
598
            $content = preg_replace_callback(
599
                '#%%' . $marker . AUTOPTIMIZE_HASH . '%%(.*?)%%' . $marker . '%%#is',
600
                function ( $matches ) {
601
                    return base64_decode( $matches[1] );
602
                },
603
                $content
604
            );
605
        }
606
607
        return $content;
608
    }
609
610
    /**
611
     * Logs given `$data` for debugging purposes (when debug logging is on).
612
     *
613
     * @param mixed $data Data to log.
614
     *
615
     * @return void
616
     */
617
    protected function debug_log( $data )
618
    {
619
        if ( ! isset( $this->debug_log ) || ! $this->debug_log ) {
620
            return;
621
        }
622
623
        if ( ! is_string( $data ) && ! is_resource( $data ) ) {
624
            $data = var_export( $data, true );
625
        }
626
627
        error_log( $data );
628
    }
629
630
    /**
631
     * Checks if a single local css/js file can be minified and returns source if so.
632
     *
633
     * @param string $filepath Filepath.
634
     *
635
     * @return bool|string to be minified code or false.
636
     */
637
    protected function prepare_minify_single( $filepath )
638
    {
639
        // Decide what we're dealing with, return false if we don't know.
640
        if ( $this->str_ends_in( $filepath, '.js' ) ) {
641
            $type = 'js';
642
        } elseif ( $this->str_ends_in( $filepath, '.css' ) ) {
643
            $type = 'css';
644
        } else {
645
            return false;
646
        }
647
648
        // Bail if it looks like its already minifed (by having -min or .min
649
        // in filename) or if it looks like WP jquery.js (which is minified).
650
        $minified_variants = array(
651
            '-min.' . $type,
652
            '.min.' . $type,
653
            'js/jquery/jquery.js',
654
        );
655
        foreach ( $minified_variants as $ending ) {
656
            if ( $this->str_ends_in( $filepath, $ending ) ) {
657
                return false;
658
            }
659
        }
660
661
        // Get file contents, bail if empty.
662
        $contents = file_get_contents( $filepath );
663
664
        return $contents;
665
    }
666
667
    /**
668
     * Given an autoptimizeCache instance returns the (maybe cdn-ed) url of
669
     * the cached file.
670
     *
671
     * @param autoptimizeCache $cache autoptimizeCache instance.
672
     *
673
     * @return string
674
     */
675
    protected function build_minify_single_url( autoptimizeCache $cache )
676
    {
677
        $url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
678
679
        // CDN-replace the resulting URL if needed...
680
        $url = $this->url_replace_cdn( $url );
681
682
        return $url;
683
    }
684
685
    /**
686
     * Returns true if given $str ends with given $test.
687
     *
688
     * @param string $str String to check.
689
     * @param string $test Ending to match.
690
     *
691
     * @return bool
692
     */
693
    protected function str_ends_in( $str, $test )
694
    {
695
        // @codingStandardsIgnoreStart
696
        // substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH
697
        // return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) );
698
        // @codingStandardsIgnoreEnd
699
700
        $length = strlen( $test );
701
702
        return ( substr( $str, -$length, $length ) === $test );
703
    }
704
}
705