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

autoptimizeScripts::getcontent()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 16
nop 0
dl 0
loc 48
rs 8.8234
c 0
b 0
f 0
1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
    exit;
5
}
6
7
class autoptimizeScripts extends autoptimizeBase
8
{
9
    private $scripts = array();
10
    private $move    = array(
11
        'first' => array(),
12
        'last' => array()
13
    );
14
15
    private $dontmove = array(
16
        'document.write','html5.js','show_ads.js','google_ad','histats.com/js','statcounter.com/counter/counter.js',
17
        'ws.amazon.com/widgets','media.fastclick.net','/ads/','comment-form-quicktags/quicktags.php','edToolbar',
18
        'intensedebate.com','scripts.chitika.net/','_gaq.push','jotform.com/','admin-bar.min.js','GoogleAnalyticsObject',
19
        'plupload.full.min.js','syntaxhighlighter','adsbygoogle','gist.github.com','_stq','nonce','post_id','data-noptimize'
20
        ,'logHuman'
21
    );
22
    private $domove     = array(
23
        'gaJsHost','load_cmc','jd.gallery.transitions.js','swfobject.embedSWF(','tiny_mce.js','tinyMCEPreInit.go'
24
    );
25
    private $domovelast = array(
26
        'addthis.com','/afsonline/show_afs_search.js','disqus.js','networkedblogs.com/getnetworkwidget','infolinks.com/js/',
27
        'jd.gallery.js.php','jd.gallery.transitions.js','swfobject.embedSWF(','linkwithin.com/widget.js','tiny_mce.js','tinyMCEPreInit.go'
28
    );
29
    public $cdn_url = '';
30
31
    private $aggregate       = true;
32
    private $trycatch        = false;
33
    private $alreadyminified = false;
34
    private $forcehead       = true;
35
    private $include_inline  = false;
36
    private $jscode          = '';
37
    private $url             = '';
38
    private $restofcontent   = '';
39
    private $md5hash         = '';
40
    private $whitelist       = '';
41
    private $jsremovables    = array();
42
    private $inject_min_late = '';
43
    private $minify_excluded = true;
44
45
    // Reads the page and collects script tags.
46
    public function read( $options )
47
    {
48
        $noptimizeJS = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content );
49
        if ( $noptimizeJS ) {
50
            return false;
51
        }
52
53
        // only optimize known good JS?
54
        $whitelistJS = apply_filters( 'autoptimize_filter_js_whitelist', '', $this->content );
55 View Code Duplication
        if ( ! empty( $whitelistJS ) ) {
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...
56
            $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistJS ) ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like array_filter(array_map('...de(',', $whitelistJS))) 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...
57
        }
58
59
        // is there JS we should simply remove?
60
        $removableJS = apply_filters( 'autoptimize_filter_js_removables', '', $this->content );
61 View Code Duplication
        if ( !empty( $removableJS ) ) {
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...
62
            $this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removableJS ) ) );
63
        }
64
65
        // only header?
66 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...
67
            $content             = explode( '</head>', $this->content, 2 );
68
            $this->content       = $content[0] . '</head>';
69
            $this->restofcontent = $content[1];
70
        }
71
72
        // Determine whether we're doing JS-files aggregation or not.
73
        if ( ! $options['aggregate'] ) {
74
            $this->aggregate = false;
75
        }
76
        // Returning true for "dontaggregate" turns off aggregation.
77
        if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) {
78
            $this->aggregate = false;
79
        }
80
81
        // include inline?
82
        if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) {
83
            $this->include_inline = true;
84
        }
85
86
        // filter to "late inject minified JS", default to true for now (it is faster).
87
        $this->inject_min_late = apply_filters( 'autoptimize_filter_js_inject_min_late', true );
88
89
        // filters to override hardcoded do(nt)move(last) array contents (array in, array out!).
90
        $this->dontmove   = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove );
91
        $this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast );
92
        $this->domove     = apply_filters( 'autoptimize_filter_js_domove', $this->domove );
93
94
        // Determine whether excluded files should be minified if not yet so.
95
        if ( ! $options['minify_excluded'] && $options['aggregate'] ) {
96
            $this->minify_excluded = false;
97
        }
98
        $this->minify_excluded = apply_filters( 'autoptimize_filter_js_minify_excluded', $this->minify_excluded );
99
100
        // get extra exclusions settings or filter.
101
        $excludeJS = $options['js_exclude'];
102
        $excludeJS = apply_filters( 'autoptimize_filter_js_exclude', $excludeJS, $this->content );
103
104
        if ( '' !== $excludeJS ) {
105
            if ( is_array( $excludeJS ) ) {
106
                if ( ( $removeKeys = array_keys( $excludeJS, 'remove' ) ) !== false ) {
107
                    foreach ( $removeKeys as $removeKey ) {
108
                        unset( $excludeJS[$removeKey] );
109
                        $this->jsremovables[] = $removeKey;
110
                    }
111
                }
112
                $exclJSArr = array_keys( $excludeJS );
113
            } else {
114
                $exclJSArr = array_filter( array_map( 'trim', explode( ',', $excludeJS ) ) );
115
            }
116
            $this->dontmove = array_merge( $exclJSArr, $this->dontmove );
117
        }
118
119
        // Should we add try-catch?
120
        if ( $options['trycatch'] ) {
121
            $this->trycatch = true;
122
        }
123
124
        // force js in head?
125
        if ( $options['forcehead'] ) {
126
            $this->forcehead = true;
127
        } else {
128
            $this->forcehead = false;
129
        }
130
131
        $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead );
132
133
        // get cdn url.
134
        $this->cdn_url = $options['cdn_url'];
135
136
        // noptimize me.
137
        $this->content = $this->hide_noptimize($this->content);
138
139
        // Save IE hacks.
140
        $this->content = $this->hide_iehacks($this->content);
141
142
        // comments.
143
        $this->content = $this->hide_comments($this->content);
144
145
        // Get script files.
146
        if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) {
147
            foreach( $matches[0] as $tag ) {
148
                // only consider script aggregation for types whitelisted in should_aggregate-function.
149
                $should_aggregate = $this->should_aggregate($tag);
150
                if ( ! $should_aggregate ) {
151
                    $tag = '';
152
                    continue;
153
                }
154
155
                if ( preg_match( '#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) {
156
                    // non-inline script.
157 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...
158
                        $this->content = str_replace( $tag, '', $this->content );
159
                        continue;
160
                    }
161
162
                    $origTag = null;
0 ignored issues
show
Unused Code introduced by
$origTag 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...
163
                    $url = current( explode( '?', $source[2], 2 ) );
164
                    $path = $this->getpath($url);
165
                    if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable($tag) ) {
166
                        // ok to optimize, add to array.
167
                        $this->scripts[] = $path;
168
                    } else {
169
                        $origTag = $tag;
170
                        $newTag  = $tag;
171
172
                        // non-mergeable script (excluded or dynamic or external).
173
                        if ( is_array( $excludeJS ) ) {
174
                            // should we add flags?
175
                            foreach ( $excludeJS as $exclTag => $exclFlags) {
176
                                if ( false !== strpos( $origTag, $exclTag ) && in_array( $exclFlags, array( 'async', 'defer' ) ) ) {
177
                                    $newTag = str_replace( '<script ', '<script ' . $exclFlags . ' ', $newTag );
178
                                }
179
                            }
180
                        }
181
182
                        // Should we minify the non-aggregated script?
183
                        // -> if aggregate is on and exclude minify is on
184
                        // -> if aggregate is off and the file is not in dontmove.
185 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...
186
                            $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
187
                            if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) {
188
                                $minified_url = $this->minify_single( $path );
189
                                // replace orig URL with minified URL from cache if so.
190
                                if ( ! empty( $minified_url ) ) {
191
                                    $newTag = str_replace( $url, $minified_url, $newTag );
192
                                }
193
                            }
194
                        }
195
196
                        if ( $this->ismovable($newTag) ) {
197
                            // can be moved, flags and all.
198
                            if ( $this->movetolast($newTag) )  {
199
                                $this->move['last'][] = $newTag;
200
                            } else {
201
                                $this->move['first'][] = $newTag;
202
                            }
203
                        } else {
204
                            // cannot be moved, so if flag was added re-inject altered tag immediately.
205
                            if ( $origTag !== $newTag ) {
206
                                $this->content = str_replace( $origTag, $newTag, $this->content );
207
                                $origTag = '';
0 ignored issues
show
Unused Code introduced by
$origTag 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...
208
                            }
209
                            // and forget about the $tag (not to be touched any more).
210
                            $tag = '';
211
                        }
212
                    }
213
                } else {
214
                    // Inline script.
215 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...
216
                        $this->content = str_replace( $tag, '', $this->content );
217
                        continue;
218
                    }
219
220
                    // unhide comments, as javascript may be wrapped in comment-tags for old times' sake.
221
                    $tag = $this->restore_comments($tag);
222
                    if ( $this->ismergeable($tag) && $this->include_inline ) {
223
                        preg_match( '#<script.*>(.*)</script>#Usmi', $tag , $code );
224
                        $code = preg_replace('#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
225
                        $code = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
226
                        $this->scripts[] = 'INLINE;' . $code;
227
                    } else {
228
                        // Can we move this?
229
                        $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
230
                        if ( $this->ismovable($tag) || '' !== $autoptimize_js_moveable ) {
231
                            if ( $this->movetolast($tag) || 'last' === $autoptimize_js_moveable ) {
232
                                $this->move['last'][] = $tag;
233
                            } else {
234
                                $this->move['first'][] = $tag;
235
                            }
236
                        } else {
237
                            // We shouldn't touch this.
238
                            $tag = '';
239
                        }
240
                    }
241
                    // Re-hide comments to be able to do the removal based on tag from $this->content.
242
                    $tag = $this->hide_comments($tag);
243
                }
244
245
                //Remove the original script tag.
246
                $this->content = str_replace( $tag, '', $this->content );
247
            }
248
249
            return true;
250
        }
251
252
        // No script files, great ;-)
253
        return false;
254
    }
255
256
    /**
257
     * Determines wheter a certain `<script>` $tag should be aggregated or not.
258
     *
259
     * We consider these as "aggregation-safe" currently:
260
     * - script tags without a `type` attribute
261
     * - script tags with these `type` attribute values: `text/javascript`, `text/ecmascript`, `application/javascript`,
262
     * and `application/ecmascript`
263
     *
264
     * Everything else should return false.
265
     *
266
     * @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
267
     *
268
     * @param string $tag
269
     * @return bool
270
     */
271
    public function should_aggregate($tag)
272
    {
273
        // We're only interested in the type attribute of the <script> tag itself, not any possible
274
        // inline code that might just contain the 'type=' string...
275
        $tag_parts = array();
276
        preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts);
277
        $tag_without_contents = null;
278
        if ( ! empty( $tag_parts[1] ) ) {
279
            $tag_without_contents = $tag_parts[1];
280
        }
281
282
        $has_type = ( strpos( $tag_without_contents, 'type' ) !== false );
283
284
        $type_valid = false;
285
        if ( $has_type ) {
286
            $type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
287
        }
288
289
        $should_aggregate = false;
290
        if ( ! $has_type || $type_valid ) {
291
            $should_aggregate = true;
292
        }
293
294
        return $should_aggregate;
295
    }
296
297
    //Joins and optimizes JS
298
    public function minify()
299
    {
300
        foreach ( $this->scripts as $script ) {
301
            // TODO/FIXME: some duplicate code here, can be reduced/simplified
302
            if ( preg_match( '#^INLINE;#', $script ) ) {
303
                // Inline script
304
                $script = preg_replace( '#^INLINE;#', '', $script );
305
                $script = rtrim( $script, ";\n\t\r" ) . ';';
306
                // Add try-catch?
307
                if ( $this->trycatch ) {
308
                    $script = 'try{' . $script . '}catch(e){}';
309
                }
310
                $tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' );
311
                if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscript ) ) {
312
                    $script = $tmpscript;
313
                    $this->alreadyminified = true;
314
                }
315
                $this->jscode .= "\n" . $script;
316
            } else {
317
                // External script
318
                if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) {
319
                    $scriptsrc = file_get_contents( $script );
320
                    $scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
321
                    $scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';
322
                    // Add try-catch?
323
                    if ( $this->trycatch ) {
324
                        $scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
325
                    }
326
                    $tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
327 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...
328
                        $scriptsrc = $tmpscriptsrc;
329
                        $this->alreadyminified = true;
330
                    } else if ( $this->can_inject_late($script) ) {
331
                        $scriptsrc = self::build_injectlater_marker($script, md5($scriptsrc));
332
                    }
333
                    $this->jscode .= "\n" . $scriptsrc;
334
                }/*else{
335
                    //Couldn't read JS. Maybe getpath isn't working?
336
                }*/
337
            }
338
        }
339
340
        // Check for already-minified code
341
        $this->md5hash = md5( $this->jscode );
342
        $ccheck = new autoptimizeCache($this->md5hash, 'js');
343
        if ( $ccheck->check() ) {
344
            $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...
345
            return true;
346
        }
347
        unset( $ccheck );
348
349
        // $this->jscode has all the uncompressed code now.
350
        if ( true !== $this->alreadyminified ) {
351
            if ( apply_filters( 'autoptimize_js_do_minify', true ) ) {
352
                $tmp_jscode = trim( JSMin::minify( $this->jscode ) );
353
                if ( ! empty( $tmp_jscode ) ) {
354
                    $this->jscode = $tmp_jscode;
355
                    unset( $tmp_jscode );
356
                }
357
                $this->jscode = $this->inject_minified( $this->jscode );
358
                $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
359
                return true;
360
            } else {
361
                $this->jscode = $this->inject_minified( $this->jscode );
362
                return false;
363
            }
364
        }
365
366
        $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
367
        return true;
368
    }
369
370
    // Caches the JS in uncompressed, deflated and gzipped form.
371
    public function cache()
372
    {
373
        $cache = new autoptimizeCache($this->md5hash, 'js');
374
        if ( ! $cache->check() ) {
375
            // Cache our code
376
            $cache->cache($this->jscode, 'text/javascript');
377
        }
378
        $this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
379
        $this->url = $this->url_replace_cdn($this->url);
380
    }
381
382
    // Returns the content
383
    public function getcontent()
384
    {
385
        // Restore the full content
386
        if ( ! empty( $this->restofcontent ) ) {
387
            $this->content .= $this->restofcontent;
388
            $this->restofcontent = '';
389
        }
390
391
        // Add the scripts taking forcehead/ deferred (default) into account
392
        if ( $this->forcehead ) {
393
            $replaceTag = array( '</head>', 'before' );
394
            $defer = '';
395
        } else {
396
            $replaceTag = array( '</body>', 'before' );
397
            $defer = 'defer ';
398
        }
399
400
        $defer   = apply_filters( 'autoptimize_filter_js_defer', $defer );
401
        $type_js = '';
402
        if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
403
            $type_js = 'type="text/javascript" ';
404
        }
405
406
        $bodyreplacementpayload = '<script ' . $type_js . $defer . 'src="' . $this->url . '"></script>';
407
        $bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload );
408
409
        $bodyreplacement = implode( '', $this->move['first'] );
410
        $bodyreplacement .= $bodyreplacementpayload;
411
        $bodyreplacement .= implode( '', $this->move['last'] );
412
413
        $replaceTag = apply_filters( 'autoptimize_filter_js_replacetag', $replaceTag );
414
415
        if ( strlen( $this->jscode ) > 0 ) {
416
            $this->inject_in_html( $bodyreplacement, $replaceTag );
417
        }
418
419
        // Restore comments.
420
        $this->content = $this->restore_comments( $this->content );
421
422
        // Restore IE hacks.
423
        $this->content = $this->restore_iehacks( $this->content );
424
425
        // Restore noptimize.
426
        $this->content = $this->restore_noptimize( $this->content );
427
428
        // Return the modified HTML.
429
        return $this->content;
430
    }
431
432
    // Checks against the white- and blacklists
433
    private function ismergeable($tag)
434
    {
435
        if ( ! $this->aggregate ) {
436
            return false;
437
        }
438
439
        if ( ! empty( $this->whitelist ) ) {
440
            foreach ( $this->whitelist as $match ) {
0 ignored issues
show
Bug introduced by
The expression $this->whitelist of type string is not traversable.
Loading history...
441
                if (false !== strpos( $tag, $match ) ) {
442
                    return true;
443
                }
444
            }
445
            // no match with whitelist
446
            return false;
447
        } else {
448
            foreach($this->domove as $match) {
449
                if ( false !== strpos( $tag, $match ) ) {
450
                    // Matched something
451
                    return false;
452
                }
453
            }
454
455
            if ( $this->movetolast($tag) ) {
456
                return false;
457
            }
458
459
            foreach( $this->dontmove as $match ) {
460
                if ( false !== strpos( $tag, $match ) ) {
461
                    // Matched something
462
                    return false;
463
                }
464
            }
465
466
            // If we're here it's safe to merge
467
            return true;
468
        }
469
    }
470
471
    // Checks agains the blacklist
472
    private function ismovable($tag)
473
    {
474
        if ( true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) {
475
            return false;
476
        }
477
478
        foreach ( $this->domove as $match ) {
479
            if ( false !== strpos( $tag, $match ) ) {
480
                // Matched something
481
                return true;
482
            }
483
        }
484
485
        if ( $this->movetolast($tag) ) {
486
            return true;
487
        }
488
489
        foreach ( $this->dontmove as $match ) {
490
            if ( false !== strpos( $tag, $match ) ) {
491
                // Matched something
492
                return false;
493
            }
494
        }
495
496
        // If we're here it's safe to move
497
        return true;
498
    }
499
500
    private function movetolast($tag)
501
    {
502
        foreach ( $this->domovelast as $match ) {
503
            if ( false !== strpos( $tag, $match ) ) {
504
                // Matched, return true
505
                return true;
506
            }
507
        }
508
509
        // Should be in 'first'
510
        return false;
511
    }
512
513
    /**
514
     * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
515
     * - inject_min_late being active
516
     * - filename ending in `min.js`
517
     * - filename matching `js/jquery/jquery.js` (wordpress core jquery, is minified)
518
     * - filename matching one passed in the consider minified filter
519
     *
520
     * @param string $jsPath
521
     * @return bool
522
     */
523
    private function can_inject_late($jsPath) {
524
        $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
525
        if ( true !== $this->inject_min_late ) {
526
            // late-inject turned off
527
            return false;
528
        } else if ( ( false === strpos( $jsPath, 'min.js' ) ) && ( false === strpos( $jsPath, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $jsPath ) === $jsPath ) ) {
529
            // file not minified based on filename & filter
530
            return false;
531
        } else {
532
            // phew, all is safe, we can late-inject
533
            return true;
534
        }
535
    }
536
537
    /**
538
     * Returns whether we're doing aggregation or not.
539
     *
540
     * @return bool
541
     */
542
    public function aggregating()
543
    {
544
        return $this->aggregate;
545
    }
546
547
    /**
548
     * Minifies a single local js file and returns its (cached) url.
549
     *
550
     * @param string $filepath Filepath.
551
     * @param bool $cache_miss Optional. Force a cache miss. Default false.
552
     *
553
     * @return bool|string Url pointing to the minified js file or false.
554
     */
555
    public function minify_single( $filepath, $cache_miss = false )
556
    {
557
        $contents = $this->prepare_minify_single( $filepath );
558
559
        if ( empty( $contents ) ) {
560
            return false;
561
        }
562
563
        // Check cache.
564
        $hash  = 'single_' . md5( $contents );
565
        $cache = new autoptimizeCache( $hash, 'js' );
566
567
        // If not in cache already, minify...
568
        if ( ! $cache->check() || $cache_miss ) {
569
            $contents = trim( JSMin::minify( $contents ) );
570
            // Store in cache.
571
            $cache->cache( $contents, 'text/javascript' );
572
        }
573
574
        $url = $this->build_minify_single_url( $cache );
575
576
        return $url;
577
    }
578
}
579