Completed
Push — master ( b4100c...ed2097 )
by frank
01:49
created

autoptimizeScripts   F

Complexity

Total Complexity 116

Size/Duplication

Total Lines 758
Duplicated Lines 6.2 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 47
loc 758
rs 1.842
c 0
b 0
f 0
wmc 116
lcom 1
cbo 3

11 Methods

Rating   Name   Duplication   Size   Complexity  
F read() 41 213 50
B should_aggregate() 0 29 6
D minify() 6 73 18
A cache() 0 10 2
B getcontent() 0 48 5
B ismergeable() 0 37 11
B ismovable() 0 27 9
A movetolast() 0 16 4
A can_inject_late() 0 13 5
A aggregating() 0 4 1
A minify_single() 0 29 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like autoptimizeScripts often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use autoptimizeScripts, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Class for JS optimization.
4
 */
5
6
if ( ! defined( 'ABSPATH' ) ) {
7
    exit;
8
}
9
10
class autoptimizeScripts extends autoptimizeBase
11
{
12
13
    /**
14
     * Stores founds scripts.
15
     *
16
     * @var array
17
     */
18
    private $scripts = array();
19
20
    /**
21
     * Stores to be moved JS.
22
     *
23
     * @var array
24
     */
25
    private $move = array(
26
        'first' => array(),
27
        'last'  => array(),
28
    );
29
30
    /**
31
     * List of not to be moved JS.
32
     *
33
     * @var array
34
     */
35
    private $dontmove = array(
36
        'document.write',
37
        'html5.js',
38
        'show_ads.js',
39
        'google_ad',
40
        'histats.com/js',
41
        'statcounter.com/counter/counter.js',
42
        'ws.amazon.com/widgets',
43
        'media.fastclick.net',
44
        '/ads/',
45
        'comment-form-quicktags/quicktags.php',
46
        'edToolbar',
47
        'intensedebate.com',
48
        'scripts.chitika.net/',
49
        '_gaq.push',
50
        'jotform.com/',
51
        'admin-bar.min.js',
52
        'GoogleAnalyticsObject',
53
        'plupload.full.min.js',
54
        'syntaxhighlighter',
55
        'adsbygoogle',
56
        'gist.github.com',
57
        '_stq',
58
        'nonce',
59
        'post_id',
60
        'data-noptimize',
61
        'logHuman',
62
    );
63
64
    /**
65
     * List of to be moved JS.
66
     *
67
     * @var array
68
     */
69
    private $domove = array(
70
        'gaJsHost',
71
        'load_cmc',
72
        'jd.gallery.transitions.js',
73
        'swfobject.embedSWF(',
74
        'tiny_mce.js',
75
        'tinyMCEPreInit.go',
76
    );
77
78
    /**
79
     * List of JS that can be moved last (not used any more).
80
     *
81
     * @var array
82
     */
83
    private $domovelast = array(
84
        'addthis.com',
85
        '/afsonline/show_afs_search.js',
86
        'disqus.js',
87
        'networkedblogs.com/getnetworkwidget',
88
        'infolinks.com/js/',
89
        'jd.gallery.js.php',
90
        'jd.gallery.transitions.js',
91
        'swfobject.embedSWF(',
92
        'linkwithin.com/widget.js',
93
        'tiny_mce.js',
94
        'tinyMCEPreInit.go',
95
    );
96
97
    /**
98
     * Setting CDN base URL.
99
     *
100
     * @var string
101
     */
102
    public $cdn_url = '';
103
104
    /**
105
     * Setting; aggregate or not.
106
     *
107
     * @var bool
108
     */
109
    private $aggregate = true;
110
111
    /**
112
     * Setting; try/catch wrapping or not.
113
     *
114
     * @var bool
115
     */
116
    private $trycatch = false;
117
118
    /**
119
     * State; is JS already minified.
120
     *
121
     * @var bool
122
     */
123
    private $alreadyminified = false;
124
125
    /**
126
     * Setting; force JS in head or not.
127
     *
128
     * @var bool
129
     */
130
    private $forcehead = true;
131
132
    /**
133
     * Setting; aggregate inline JS or not.
134
     *
135
     * @var bool
136
     */
137
    private $include_inline = false;
138
139
    /**
140
     * State; holds JS code.
141
     *
142
     * @var string
143
     */
144
    private $jscode = '';
145
146
    /**
147
     * State; holds URL of JS-file.
148
     *
149
     * @var string
150
     */
151
    private $url = '';
152
153
    /**
154
     * State; stores rest of HTML if (old) option "only in head" is on.
155
     *
156
     * @var string
157
     */
158
    private $restofcontent = '';
159
160
    /**
161
     * State; holds md5-hash.
162
     *
163
     * @var string
164
     */
165
    private $md5hash = '';
166
167
    /**
168
     * Setting (filter); whitelist of to be aggregated JS.
169
     *
170
     * @var string
171
     */
172
    private $whitelist = '';
173
174
    /**
175
     * Setting (filter); holds JS that should be removed.
176
     *
177
     * @var array
178
     */
179
    private $jsremovables = array();
180
181
    /**
182
     * Setting (filter); can we inject already minified files after the
183
     * unminified aggregate JS has been minified.
184
     *
185
     * @var bool
186
     */
187
    private $inject_min_late = true;
188
189
    /**
190
     * Setting; should excluded JS be minified (if not already).
191
     *
192
     * @var bool
193
     */
194
    private $minify_excluded = true;
195
196
    /**
197
     * Reads the page and collects script tags.
198
     *
199
     * @param array $options all options.
200
     */
201
    public function read( $options )
202
    {
203
        $noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content );
204
        if ( $noptimize_js ) {
205
            return false;
206
        }
207
208
        // only optimize known good JS?
209
        $whitelist_js = apply_filters( 'autoptimize_filter_js_whitelist', '', $this->content );
210 View Code Duplication
        if ( ! empty( $whitelist_js ) ) {
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...
211
            $this->whitelist = array_filter( array_map( 'trim', explode( ', ', $whitelist_js ) ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like array_filter(array_map('...(', ', $whitelist_js))) 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...
212
        }
213
214
        // is there JS we should simply remove?
215
        $removable_js = apply_filters( 'autoptimize_filter_js_removables', '', $this->content );
216 View Code Duplication
        if ( ! empty( $removable_js ) ) {
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...
217
            $this->jsremovables = array_filter( array_map( 'trim', explode( ', ', $removable_js ) ) );
218
        }
219
220
        // only header?
221 View Code Duplication
        if ( apply_filters( 'autoptimize_filter_js_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...
222
            $content             = explode( '</head>', $this->content, 2 );
223
            $this->content       = $content[0] . '</head>';
224
            $this->restofcontent = $content[1];
225
        }
226
227
        // Determine whether we're doing JS-files aggregation or not.
228
        if ( ! $options['aggregate'] ) {
229
            $this->aggregate = false;
230
        }
231
        // Returning true for "dontaggregate" turns off aggregation.
232
        if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) {
233
            $this->aggregate = false;
234
        }
235
236
        // include inline?
237
        if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) {
238
            $this->include_inline = true;
239
        }
240
241
        // filter to "late inject minified JS", default to true for now (it is faster).
242
        $this->inject_min_late = apply_filters( 'autoptimize_filter_js_inject_min_late', true );
243
244
        // filters to override hardcoded do(nt)move(last) array contents (array in, array out!).
245
        $this->dontmove   = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove );
246
        $this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast );
247
        $this->domove     = apply_filters( 'autoptimize_filter_js_domove', $this->domove );
248
249
        // Determine whether excluded files should be minified if not yet so.
250
        if ( ! $options['minify_excluded'] && $options['aggregate'] ) {
251
            $this->minify_excluded = false;
252
        }
253
        $this->minify_excluded = apply_filters( 'autoptimize_filter_js_minify_excluded', $this->minify_excluded, '' );
254
255
        // get extra exclusions settings or filter.
256
        $exclude_js = $options['js_exclude'];
257
        $exclude_js = apply_filters( 'autoptimize_filter_js_exclude', $exclude_js, $this->content );
258
259
        if ( '' !== $exclude_js ) {
260
            if ( is_array( $exclude_js ) ) {
261
                $remove_keys = array_keys( $exclude_js, 'remove' );
262
                if ( false !== $remove_keys ) {
263
                    foreach ( $remove_keys as $remove_key ) {
264
                        unset( $exclude_js[ $remove_key ] );
265
                        $this->jsremovables[] = $remove_key;
266
                    }
267
                }
268
                $excl_js_arr = array_keys( $exclude_js );
269
            } else {
270
                $excl_js_arr = array_filter( array_map( 'trim', explode( ', ', $exclude_js ) ) );
271
            }
272
            $this->dontmove = array_merge( $excl_js_arr, $this->dontmove );
273
        }
274
275
        // Should we add try-catch?
276
        if ( $options['trycatch'] ) {
277
            $this->trycatch = true;
278
        }
279
280
        // force js in head?
281
        if ( $options['forcehead'] ) {
282
            $this->forcehead = true;
283
        } else {
284
            $this->forcehead = false;
285
        }
286
287
        $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead );
288
289
        // get cdn url.
290
        $this->cdn_url = $options['cdn_url'];
291
292
        // noptimize me.
293
        $this->content = $this->hide_noptimize( $this->content );
294
295
        // Save IE hacks.
296
        $this->content = $this->hide_iehacks( $this->content );
297
298
        // comments.
299
        $this->content = $this->hide_comments( $this->content );
300
301
        // Get script files.
302
        if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) {
303
            foreach ( $matches[0] as $tag ) {
304
                // only consider script aggregation for types whitelisted in should_aggregate-function.
305
                $should_aggregate = $this->should_aggregate( $tag );
306
                if ( ! $should_aggregate ) {
307
                    $tag = '';
308
                    continue;
309
                }
310
311
                if ( preg_match( '#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) {
312
                    // non-inline script.
313 View Code Duplication
                    if ( $this->isremovable( $tag, $this->jsremovables ) ) {
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...
314
                        $this->content = str_replace( $tag, '', $this->content );
315
                        continue;
316
                    }
317
318
                    $orig_tag = null;
0 ignored issues
show
Unused Code introduced by
$orig_tag 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...
319
                    $url      = current( explode( '?', $source[2], 2 ) );
320
                    $path     = $this->getpath( $url );
321
                    if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable( $tag ) ) {
322
                        // ok to optimize, add to array.
323
                        $this->scripts[] = $path;
324
                    } else {
325
                        $orig_tag = $tag;
326
                        $new_tag  = $tag;
327
328
                        // non-mergeable script (excluded or dynamic or external).
329
                        if ( is_array( $exclude_js ) ) {
330
                            // should we add flags?
331
                            foreach ( $exclude_js as $excl_tag => $excl_flags ) {
332
                                if ( false !== strpos( $orig_tag, $excl_tag ) && in_array( $excl_flags, array( 'async', 'defer' ) ) ) {
333
                                    $new_tag = str_replace( '<script ', '<script ' . $excl_flags . ' ', $new_tag );
334
                                }
335
                            }
336
                        }
337
338
                        // Should we minify the non-aggregated script?
339
                        // -> if aggregate is on and exclude minify is on
340
                        // -> if aggregate is off and the file is not in dontmove.
341 View Code Duplication
                        if ( $path && $this->minify_excluded ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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...
342
                            $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
343
                            if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) {
344
                                $minified_url = $this->minify_single( $path );
345
                                if ( ! empty( $minified_url ) ) {
346
                                    // Replace original URL with minified URL from cache.
347
                                    $new_tag = str_replace( $url, $minified_url, $new_tag );
348
                                } else {
349
                                    // Remove the original script tag, because cache content is empty.
350
                                    $new_tag = '';
351
                                }
352
                            }
353
                        }
354
355
                        if ( $this->ismovable( $new_tag ) ) {
356
                            // can be moved, flags and all.
357
                            if ( $this->movetolast( $new_tag ) ) {
358
                                $this->move['last'][] = $new_tag;
359
                            } else {
360
                                $this->move['first'][] = $new_tag;
361
                            }
362 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...
363
                            // cannot be moved, so if flag was added re-inject altered tag immediately.
364
                            if ( ( '' !== $new_tag && $orig_tag !== $new_tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_js_remove_empty_files', false ) ) ) {
365
                                $this->content = str_replace( $orig_tag, $new_tag, $this->content );
366
                                $orig_tag      = '';
0 ignored issues
show
Unused Code introduced by
$orig_tag 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...
367
                            }
368
                            // and forget about the $tag (not to be touched any more).
369
                            $tag = '';
370
                        }
371
                    }
372
                } else {
373
                    // Inline script.
374 View Code Duplication
                    if ( $this->isremovable( $tag, $this->jsremovables ) ) {
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...
375
                        $this->content = str_replace( $tag, '', $this->content );
376
                        continue;
377
                    }
378
379
                    // unhide comments, as javascript may be wrapped in comment-tags for old times' sake.
380
                    $tag = $this->restore_comments( $tag );
381
                    if ( $this->ismergeable( $tag ) && $this->include_inline ) {
382
                        preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $code );
383
                        $code            = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
384
                        $code            = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
385
                        $this->scripts[] = 'INLINE;' . $code;
386
                    } else {
387
                        // Can we move this?
388
                        $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
389
                        if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) {
390
                            if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) {
391
                                $this->move['last'][] = $tag;
392
                            } else {
393
                                $this->move['first'][] = $tag;
394
                            }
395
                        } else {
396
                            // We shouldn't touch this.
397
                            $tag = '';
398
                        }
399
                    }
400
                    // Re-hide comments to be able to do the removal based on tag from $this->content.
401
                    $tag = $this->hide_comments( $tag );
402
                }
403
404
                // Remove the original script tag.
405
                $this->content = str_replace( $tag, '', $this->content );
406
            }
407
408
            return true;
409
        }
410
411
        // No script files, great ;-) .
412
        return false;
413
    }
414
415
    /**
416
     * Determines wheter a certain `<script>` $tag should be aggregated or not.
417
     *
418
     * We consider these as "aggregation-safe" currently:
419
     * - script tags without a `type` attribute
420
     * - script tags with these `type` attribute values: `text/javascript`, `text/ecmascript`, `application/javascript`,
421
     * and `application/ecmascript`
422
     *
423
     * Everything else should return false.
424
     *
425
     * @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
426
     *
427
     * @param string $tag Script node & child(ren).
428
     * @return bool
429
     */
430
    public function should_aggregate( $tag )
431
    {
432
        if ( empty( $tag ) ) {
433
            return false;
434
        }
435
436
        // We're only interested in the type attribute of the <script> tag itself, not any possible
437
        // inline code that might just contain the 'type=' string...
438
        $tag_parts = array();
439
        preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts );
440
        $tag_without_contents = null;
441
        if ( ! empty( $tag_parts[1] ) ) {
442
            $tag_without_contents = $tag_parts[1];
443
        }
444
445
        $has_type = ( strpos( $tag_without_contents, 'type' ) !== false );
446
447
        $type_valid = false;
448
        if ( $has_type ) {
449
            $type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
450
        }
451
452
        $should_aggregate = false;
453
        if ( ! $has_type || $type_valid ) {
454
            $should_aggregate = true;
455
        }
456
457
        return $should_aggregate;
458
    }
459
460
    /**
461
     * Joins and optimizes JS.
462
     */
463
    public function minify()
464
    {
465
        foreach ( $this->scripts as $script ) {
466
            if ( empty( $script ) ) {
467
                continue;
468
            }
469
470
            // TODO/FIXME: some duplicate code here, can be reduced/simplified.
471
            if ( preg_match( '#^INLINE;#', $script ) ) {
472
                // Inline script.
473
                $script = preg_replace( '#^INLINE;#', '', $script );
474
                $script = rtrim( $script, ";\n\t\r" ) . ';';
475
                // Add try-catch?
476
                if ( $this->trycatch ) {
477
                    $script = 'try{' . $script . '}catch(e){}';
478
                }
479
                $tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' );
480
                if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscript ) ) {
481
                    $script                = $tmpscript;
482
                    $this->alreadyminified = true;
483
                }
484
                $this->jscode .= "\n" . $script;
485
            } else {
486
                // External script.
487
                if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) {
488
                    $scriptsrc = file_get_contents( $script );
489
                    $scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
490
                    $scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';
491
                    // Add try-catch?
492
                    if ( $this->trycatch ) {
493
                        $scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
494
                    }
495
                    $tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
496 View Code Duplication
                    if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) {
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...
497
                        $scriptsrc             = $tmpscriptsrc;
498
                        $this->alreadyminified = true;
499
                    } elseif ( $this->can_inject_late( $script ) ) {
500
                        $scriptsrc = self::build_injectlater_marker( $script, md5( $scriptsrc ) );
501
                    }
502
                    $this->jscode .= "\n" . $scriptsrc;
503
                }
504
            }
505
        }
506
507
        // Check for already-minified code.
508
        $this->md5hash = md5( $this->jscode );
509
        $ccheck        = new autoptimizeCache( $this->md5hash, 'js' );
510
        if ( $ccheck->check() ) {
511
            $this->jscode = $ccheck->retrieve();
0 ignored issues
show
Documentation Bug introduced by
It seems like $ccheck->retrieve() can also be of type false. However, the property $jscode is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
512
            return true;
513
        }
514
        unset( $ccheck );
515
516
        // $this->jscode has all the uncompressed code now.
517
        if ( true !== $this->alreadyminified ) {
518
            if ( apply_filters( 'autoptimize_js_do_minify', true ) ) {
519
                $tmp_jscode = trim( JSMin::minify( $this->jscode ) );
520
                if ( ! empty( $tmp_jscode ) ) {
521
                    $this->jscode = $tmp_jscode;
522
                    unset( $tmp_jscode );
523
                }
524
                $this->jscode = $this->inject_minified( $this->jscode );
525
                $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
526
                return true;
527
            } else {
528
                $this->jscode = $this->inject_minified( $this->jscode );
529
                return false;
530
            }
531
        }
532
533
        $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
534
        return true;
535
    }
536
537
    /**
538
     * Caches the JS in uncompressed, deflated and gzipped form.
539
     */
540
    public function cache()
541
    {
542
        $cache = new autoptimizeCache( $this->md5hash, 'js' );
543
        if ( ! $cache->check() ) {
544
            // Cache our code.
545
            $cache->cache( $this->jscode, 'text/javascript' );
546
        }
547
        $this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
548
        $this->url = $this->url_replace_cdn( $this->url );
549
    }
550
551
    /**
552
     * Returns the content.
553
     */
554
    public function getcontent()
555
    {
556
        // Restore the full content.
557
        if ( ! empty( $this->restofcontent ) ) {
558
            $this->content      .= $this->restofcontent;
559
            $this->restofcontent = '';
560
        }
561
562
        // Add the scripts taking forcehead/ deferred (default) into account.
563
        if ( $this->forcehead ) {
564
            $replace_tag = array( '</head>', 'before' );
565
            $defer       = '';
566
        } else {
567
            $replace_tag = array( '</body>', 'before' );
568
            $defer       = 'defer ';
569
        }
570
571
        $defer   = apply_filters( 'autoptimize_filter_js_defer', $defer );
572
        $type_js = '';
573
        if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
574
            $type_js = 'type="text/javascript" ';
575
        }
576
577
        $bodyreplacementpayload = '<script ' . $type_js . $defer . 'src="' . $this->url . '"></script>';
578
        $bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload );
579
580
        $bodyreplacement  = implode( '', $this->move['first'] );
581
        $bodyreplacement .= $bodyreplacementpayload;
582
        $bodyreplacement .= implode( '', $this->move['last'] );
583
584
        $replace_tag = apply_filters( 'autoptimize_filter_js_replacetag', $replace_tag );
585
586
        if ( strlen( $this->jscode ) > 0 ) {
587
            $this->inject_in_html( $bodyreplacement, $replace_tag );
588
        }
589
590
        // Restore comments.
591
        $this->content = $this->restore_comments( $this->content );
592
593
        // Restore IE hacks.
594
        $this->content = $this->restore_iehacks( $this->content );
595
596
        // Restore noptimize.
597
        $this->content = $this->restore_noptimize( $this->content );
598
599
        // Return the modified HTML.
600
        return $this->content;
601
    }
602
603
    /**
604
     * Checks against the white- and blacklists.
605
     *
606
     * @param string $tag JS tag.
607
     */
608
    private function ismergeable( $tag )
609
    {
610
        if ( empty( $tag ) || ! $this->aggregate ) {
611
            return false;
612
        }
613
614
        if ( ! empty( $this->whitelist ) ) {
615
            foreach ( $this->whitelist as $match ) {
0 ignored issues
show
Bug introduced by
The expression $this->whitelist of type string is not traversable.
Loading history...
616
                if ( false !== strpos( $tag, $match ) ) {
617
                    return true;
618
                }
619
            }
620
            // No match with whitelist.
621
            return false;
622
        } else {
623
            foreach ( $this->domove as $match ) {
624
                if ( false !== strpos( $tag, $match ) ) {
625
                    // Matched something.
626
                    return false;
627
                }
628
            }
629
630
            if ( $this->movetolast( $tag ) ) {
631
                return false;
632
            }
633
634
            foreach ( $this->dontmove as $match ) {
635
                if ( false !== strpos( $tag, $match ) ) {
636
                    // Matched something.
637
                    return false;
638
                }
639
            }
640
641
            // If we're here it's safe to merge.
642
            return true;
643
        }
644
    }
645
646
    /**
647
     * Checks agains the blacklist.
648
     *
649
     * @param string $tag tag to check for blacklist (exclusions).
650
     */
651
    private function ismovable( $tag )
652
    {
653
        if ( empty( $tag ) || true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) {
654
            return false;
655
        }
656
657
        foreach ( $this->domove as $match ) {
658
            if ( false !== strpos( $tag, $match ) ) {
659
                // Matched something.
660
                return true;
661
            }
662
        }
663
664
        if ( $this->movetolast( $tag ) ) {
665
            return true;
666
        }
667
668
        foreach ( $this->dontmove as $match ) {
669
            if ( false !== strpos( $tag, $match ) ) {
670
                // Matched something.
671
                return false;
672
            }
673
        }
674
675
        // If we're here it's safe to move.
676
        return true;
677
    }
678
679
    private function movetolast( $tag )
680
    {
681
        if ( empty( $tag ) ) {
682
            return false;
683
        }
684
685
        foreach ( $this->domovelast as $match ) {
686
            if ( false !== strpos( $tag, $match ) ) {
687
                // Matched, return true.
688
                return true;
689
            }
690
        }
691
692
        // Should be in 'first'.
693
        return false;
694
    }
695
696
    /**
697
     * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
698
     * - inject_min_late being active
699
     * - filename ending in `min.js`
700
     * - filename matching `js/jquery/jquery.js` (WordPress core jquery, is minified)
701
     * - filename matching one passed in the consider minified filter
702
     *
703
     * @param string $js_path Path to JS file.
704
     * @return bool
705
     */
706
    private function can_inject_late( $js_path ) {
707
        $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
708
        if ( true !== $this->inject_min_late ) {
709
            // late-inject turned off.
710
            return false;
711
        } elseif ( ( false === strpos( $js_path, 'min.js' ) ) && ( false === strpos( $js_path, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $js_path ) === $js_path ) ) {
712
            // file not minified based on filename & filter.
713
            return false;
714
        } else {
715
            // phew, all is safe, we can late-inject.
716
            return true;
717
        }
718
    }
719
720
    /**
721
     * Returns whether we're doing aggregation or not.
722
     *
723
     * @return bool
724
     */
725
    public function aggregating()
726
    {
727
        return $this->aggregate;
728
    }
729
730
    /**
731
     * Minifies a single local js file and returns its (cached) url.
732
     *
733
     * @param string $filepath Filepath.
734
     * @param bool   $cache_miss Optional. Force a cache miss. Default false.
735
     *
736
     * @return bool|string Url pointing to the minified js file or false.
737
     */
738
    public function minify_single( $filepath, $cache_miss = false )
739
    {
740
        $contents = $this->prepare_minify_single( $filepath );
741
742
        if ( empty( $contents ) ) {
743
            return false;
744
        }
745
746
        // Check cache.
747
        $hash  = 'single_' . md5( $contents );
748
        $cache = new autoptimizeCache( $hash, 'js' );
749
750
        // If not in cache already, minify...
751
        if ( ! $cache->check() || $cache_miss ) {
752
            $contents = trim( JSMin::minify( $contents ) );
753
754
            // Check if minified cache content is empty.
755
            if ( empty( $contents ) ) {
756
                return false;
757
            }
758
759
            // Store in cache.
760
            $cache->cache( $contents, 'text/javascript' );
761
        }
762
763
        $url = $this->build_minify_single_url( $cache );
764
765
        return $url;
766
    }
767
}
768