PluginProbe
Autoptimize / 2.9.3
Autoptimize v2.9.3
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / autoptimizeScripts.php

autoptimizeScripts.php in Autoptimize 2.9.3, at classes/autoptimizeScripts.php

830 lines 29.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 'data-cfasync',
62 'data-pagespeed-no-defer',
63 'logHuman',
64 'amp-mobile-version-switcher',
65 'data-rocketlazyloadscript',
66 'rocket-browser-checker-js-after',
67 );
68
69 /**
70 * List of to be moved JS.
71 *
72 * @var array
73 */
74 private $domove = array(
75 'gaJsHost',
76 'load_cmc',
77 'jd.gallery.transitions.js',
78 'swfobject.embedSWF(',
79 'tiny_mce.js',
80 'tinyMCEPreInit.go',
81 );
82
83 /**
84 * List of JS that can be moved last (not used any more).
85 *
86 * @var array
87 */
88 private $domovelast = array(
89 'addthis.com',
90 '/afsonline/show_afs_search.js',
91 'disqus.js',
92 'networkedblogs.com/getnetworkwidget',
93 'infolinks.com/js/',
94 'jd.gallery.js.php',
95 'jd.gallery.transitions.js',
96 'swfobject.embedSWF(',
97 'linkwithin.com/widget.js',
98 'tiny_mce.js',
99 'tinyMCEPreInit.go',
100 );
101
102 /**
103 * Setting CDN base URL.
104 *
105 * @var string
106 */
107 public $cdn_url = '';
108
109 /**
110 * Setting; aggregate or not.
111 *
112 * @var bool
113 */
114 private $aggregate = true;
115
116 /**
117 * Setting; if not aggregated, should we defer?
118 *
119 * @var bool
120 */
121 private $defer_not_aggregate = false;
122
123 /**
124 * Setting; defer inline JS?
125 *
126 * @var bool
127 */
128 private $defer_inline = false;
129
130 /**
131 * Setting; try/catch wrapping or not.
132 *
133 * @var bool
134 */
135 private $trycatch = false;
136
137 /**
138 * State; is JS already minified.
139 *
140 * @var bool
141 */
142 private $alreadyminified = false;
143
144 /**
145 * Setting; force JS in head or not.
146 *
147 * @var bool
148 */
149 private $forcehead = true;
150
151 /**
152 * Setting; aggregate inline JS or not.
153 *
154 * @var bool
155 */
156 private $include_inline = false;
157
158 /**
159 * State; holds JS code.
160 *
161 * @var string
162 */
163 private $jscode = '';
164
165 /**
166 * State; holds URL of JS-file.
167 *
168 * @var string
169 */
170 private $url = '';
171
172 /**
173 * State; stores rest of HTML if (old) option "only in head" is on.
174 *
175 * @var string
176 */
177 private $restofcontent = '';
178
179 /**
180 * State; holds md5-hash.
181 *
182 * @var string
183 */
184 private $md5hash = '';
185
186 /**
187 * Setting (filter); allowlist of to be aggregated JS.
188 *
189 * @var string
190 */
191 private $allowlist = '';
192
193 /**
194 * Setting (filter); holds JS that should be removed.
195 *
196 * @var array
197 */
198 private $jsremovables = array();
199
200 /**
201 * Setting (filter); can we inject already minified files after the
202 * unminified aggregate JS has been minified.
203 *
204 * @var bool
205 */
206 private $inject_min_late = true;
207
208 /**
209 * Setting; should excluded JS be minified (if not already).
210 *
211 * @var bool
212 */
213 private $minify_excluded = true;
214
215 /**
216 * Reads the page and collects script tags.
217 *
218 * @param array $options all options.
219 */
220 public function read( $options )
221 {
222 $noptimize_js = false;
223
224 // If page/ post check post_meta to see if optimize is off.
225 if ( false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_js_optimize' ) ) {
226 $noptimize_js = true;
227 }
228
229 // And a filter to enforce JS noptimize.
230 $noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', $noptimize_js, $this->content );
231
232 // And finally bail if noptimize_js is true.
233 if ( $noptimize_js ) {
234 return false;
235 }
236
237 // only optimize known good JS?
238 $allowlist_js = apply_filters( 'autoptimize_filter_js_allowlist', '', $this->content );
239 if ( ! empty( $allowlist_js ) ) {
240 $this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_js ) ) );
241 }
242
243 // is there JS we should simply remove?
244 $removable_js = apply_filters( 'autoptimize_filter_js_removables', '', $this->content );
245 if ( ! empty( $removable_js ) ) {
246 $this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removable_js ) ) );
247 }
248
249 // only header?
250 if ( apply_filters( 'autoptimize_filter_js_justhead', $options['justhead'] ) ) {
251 $content = explode( '</head>', $this->content, 2 );
252 $this->content = $content[0] . '</head>';
253 $this->restofcontent = $content[1];
254 }
255
256 // Determine whether we're doing JS-files aggregation or not.
257 if ( ! $options['aggregate'] ) {
258 $this->aggregate = false;
259 }
260 // Returning true for "dontaggregate" turns off aggregation.
261 if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) {
262 $this->aggregate = false;
263 }
264
265 // Defer when not aggregating.
266 if ( false === $this->aggregate && apply_filters( 'autoptimize_filter_js_defer_not_aggregate', $options['defer_not_aggregate'] ) ) {
267 $this->defer_not_aggregate = true;
268 }
269
270 // Defer inline JS?
271 if ( ( true === $this->defer_not_aggregate && apply_filters( 'autoptimize_js_filter_defer_inline', $options['defer_inline'] ) ) || apply_filters( 'autoptimize_js_filter_force_defer_inline', false ) ) {
272 $this->defer_inline = true;
273 }
274
275 // include inline?
276 if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) {
277 $this->include_inline = true;
278 }
279
280 // filter to "late inject minified JS", default to true for now (it is faster).
281 $this->inject_min_late = apply_filters( 'autoptimize_filter_js_inject_min_late', true );
282
283 // filters to override hardcoded do(nt)move(last) array contents (array in, array out!).
284 $this->dontmove = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove );
285 $this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast );
286 $this->domove = apply_filters( 'autoptimize_filter_js_domove', $this->domove );
287
288 // Determine whether excluded files should be minified if not yet so.
289 if ( ! $options['minify_excluded'] && $options['aggregate'] ) {
290 $this->minify_excluded = false;
291 }
292 $this->minify_excluded = apply_filters( 'autoptimize_filter_js_minify_excluded', $this->minify_excluded, '' );
293
294 // get extra exclusions settings or filter.
295 $exclude_js = $options['js_exclude'];
296 $exclude_js = apply_filters( 'autoptimize_filter_js_exclude', $exclude_js, $this->content );
297
298 if ( '' !== $exclude_js ) {
299 if ( is_array( $exclude_js ) ) {
300 $remove_keys = array_keys( $exclude_js, 'remove' );
301 if ( false !== $remove_keys ) {
302 foreach ( $remove_keys as $remove_key ) {
303 unset( $exclude_js[ $remove_key ] );
304 $this->jsremovables[] = $remove_key;
305 }
306 }
307 $excl_js_arr = array_keys( $exclude_js );
308 } else {
309 $excl_js_arr = array_filter( array_map( 'trim', explode( ',', $exclude_js ) ) );
310 }
311 $this->dontmove = array_merge( $excl_js_arr, $this->dontmove );
312 }
313
314 // Should we add try-catch?
315 if ( $options['trycatch'] ) {
316 $this->trycatch = true;
317 }
318
319 // force js in head?
320 if ( $options['forcehead'] ) {
321 $this->forcehead = true;
322 } else {
323 $this->forcehead = false;
324 }
325
326 $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead );
327
328 // get cdn url.
329 $this->cdn_url = $options['cdn_url'];
330
331 // noptimize me.
332 $this->content = $this->hide_noptimize( $this->content );
333
334 // Save IE hacks.
335 $this->content = $this->hide_iehacks( $this->content );
336
337 // comments.
338 $this->content = $this->hide_comments( $this->content );
339
340 // Get script files.
341 if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) {
342 foreach ( $matches[0] as $tag ) {
343 // only consider script aggregation for types allowlisted in should_aggregate-function.
344 $should_aggregate = $this->should_aggregate( $tag );
345 if ( ! $should_aggregate ) {
346 $tag = '';
347 continue;
348 }
349
350 if ( preg_match( '#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) {
351 // non-inline script.
352 if ( $this->isremovable( $tag, $this->jsremovables ) ) {
353 $this->content = str_replace( $tag, '', $this->content );
354 continue;
355 }
356
357 $orig_tag = null;
358 $url = current( explode( '?', $source[2], 2 ) );
359 $path = $this->getpath( $url );
360 if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable( $tag ) ) {
361 // ok to optimize, add to array.
362 $this->scripts[] = $path;
363 } else {
364 $orig_tag = $tag;
365 $new_tag = $tag;
366
367 // non-mergeable script (excluded or dynamic or external).
368 if ( is_array( $exclude_js ) ) {
369 // should we add flags?
370 foreach ( $exclude_js as $excl_tag => $excl_flags ) {
371 if ( false !== strpos( $orig_tag, $excl_tag ) && in_array( $excl_flags, array( 'async', 'defer' ) ) ) {
372 $new_tag = str_replace( '<script ', '<script ' . $excl_flags . ' ', $new_tag );
373 }
374 }
375 }
376
377 // not aggregating but deferring?
378 if ( $this->defer_not_aggregate && false === $this->aggregate && ( str_replace( $this->dontmove, '', $path ) === $path || ( apply_filters( 'autoptimize_filter_js_defer_external', true ) && str_replace( $this->dontmove, '', $url ) === $url ) ) && strpos( $new_tag, ' defer' ) === false && strpos( $new_tag, ' async' ) === false ) {
379 $new_tag = str_replace( '<script ', '<script defer ', $new_tag );
380 }
381
382 // Should we minify the non-aggregated script?
383 // -> if aggregate is on and exclude minify is on
384 // -> if aggregate is off and the file is not in dontmove.
385 if ( $path && $this->minify_excluded ) {
386 $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
387 if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) {
388 $minified_url = $this->minify_single( $path );
389 if ( ! empty( $minified_url ) ) {
390 // Replace original URL with minified URL from cache.
391 $new_tag = str_replace( $url, $minified_url, $new_tag );
392 } elseif ( apply_filters( 'autoptimize_filter_ccsjs_remove_empty_minified_url', false ) ) {
393 // Remove the original script tag, because cache content is empty but only if filter
394 // is trued because $minified_url is also false if original JS is minified already.
395 $new_tag = '';
396 }
397 }
398 }
399
400 // Check if we still need to CDN (esp. for already minified resources).
401 if ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) {
402 $new_tag = str_replace( $url, $this->url_replace_cdn( $url ), $new_tag );
403 }
404
405 if ( $this->ismovable( $new_tag ) ) {
406 // can be moved, flags and all.
407 if ( $this->movetolast( $new_tag ) ) {
408 $this->move['last'][] = $new_tag;
409 } else {
410 $this->move['first'][] = $new_tag;
411 }
412 } else {
413 // cannot be moved, so if flag was added re-inject altered tag immediately.
414 if ( ( '' !== $new_tag && $orig_tag !== $new_tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_js_remove_empty_files', false ) ) ) {
415 $this->content = str_replace( $orig_tag, $new_tag, $this->content );
416 $orig_tag = '';
417 }
418 // and forget about the $tag (not to be touched any more).
419 $tag = '';
420 }
421 }
422 } else {
423 // Inline script.
424 if ( $this->isremovable( $tag, $this->jsremovables ) ) {
425 $this->content = str_replace( $tag, '', $this->content );
426 continue;
427 }
428
429 // unhide comments, as javascript may be wrapped in comment-tags for old times' sake.
430 $tag = $this->restore_comments( $tag );
431 if ( $this->ismergeable( $tag ) && $this->include_inline ) {
432 preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $code );
433 $code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
434 $code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
435 $this->scripts[] = 'INLINE;' . $code;
436 } else {
437 if ( false === $this->defer_inline ) {
438 // Can we move this?
439 $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
440 if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) {
441 if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) {
442 $this->move['last'][] = $tag;
443 } else {
444 $this->move['first'][] = $tag;
445 }
446 } else {
447 $tag = '';
448 }
449 } elseif ( str_replace( $this->dontmove, '', $tag ) === $tag ) {
450 // defer inline JS by base64 encoding it.
451 preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $match );
452 $new_tag = '<script defer src="data:text/javascript;base64,' . base64_encode( $match[1] ) . '"></script>';
453 $this->content = str_replace( $tag, $new_tag, $this->content );
454 $tag = '';
455 } else {
456 $tag = '';
457 }
458 }
459 // Re-hide comments to be able to do the removal based on tag from $this->content.
460 $tag = $this->hide_comments( $tag );
461 }
462
463 // Remove the original script tag.
464 $this->content = str_replace( $tag, '', $this->content );
465 }
466
467 return true;
468 }
469
470 // No script files, great ;-) .
471 return false;
472 }
473
474 /**
475 * Determines wheter a certain `<script>` $tag should be aggregated or not.
476 *
477 * We consider these as "aggregation-safe" currently:
478 * - script tags without a `type` attribute
479 * - script tags with these `type` attribute values: `text/javascript`, `text/ecmascript`, `application/javascript`,
480 * and `application/ecmascript`
481 *
482 * Everything else should return false.
483 *
484 * @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
485 *
486 * @param string $tag Script node & child(ren).
487 * @return bool
488 */
489 public static function should_aggregate( $tag )
490 {
491 if ( empty( $tag ) ) {
492 return false;
493 }
494
495 // We're only interested in the type attribute of the <script> tag itself, not any possible
496 // inline code that might just contain the 'type=' string...
497 $tag_parts = array();
498 preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts );
499 $tag_without_contents = null;
500 if ( ! empty( $tag_parts[1] ) ) {
501 $tag_without_contents = $tag_parts[1];
502 }
503
504 $has_type = ( strpos( $tag_without_contents, 'type' ) !== false );
505
506 $type_valid = false;
507 if ( $has_type ) {
508 $type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
509 }
510
511 $should_aggregate = false;
512 if ( ! $has_type || $type_valid ) {
513 $should_aggregate = true;
514 }
515
516 return $should_aggregate;
517 }
518
519 /**
520 * Joins and optimizes JS.
521 */
522 public function minify()
523 {
524 foreach ( $this->scripts as $script ) {
525 if ( empty( $script ) ) {
526 continue;
527 }
528
529 // TODO/FIXME: some duplicate code here, can be reduced/simplified.
530 if ( preg_match( '#^INLINE;#', $script ) ) {
531 // Inline script.
532 $script = preg_replace( '#^INLINE;#', '', $script );
533 $script = rtrim( $script, ";\n\t\r" ) . ';';
534 // Add try-catch?
535 if ( $this->trycatch ) {
536 $script = 'try{' . $script . '}catch(e){}';
537 }
538 $tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' );
539 if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscript ) ) {
540 $script = $tmpscript;
541 $this->alreadyminified = true;
542 }
543 $this->jscode .= "\n" . $script;
544 } else {
545 // External script.
546 if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) {
547 $scriptsrc = file_get_contents( $script );
548 $scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
549 $scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';
550 // Add try-catch?
551 if ( $this->trycatch ) {
552 $scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
553 }
554 $tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
555 if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) {
556 $scriptsrc = $tmpscriptsrc;
557 $this->alreadyminified = true;
558 } elseif ( $this->can_inject_late( $script ) ) {
559 $scriptsrc = self::build_injectlater_marker( $script, md5( $scriptsrc ) );
560 }
561 $this->jscode .= "\n" . $scriptsrc;
562 }
563 }
564 }
565
566 // Check for already-minified code.
567 $this->md5hash = md5( $this->jscode );
568 $ccheck = new autoptimizeCache( $this->md5hash, 'js' );
569 if ( $ccheck->check() ) {
570 $this->jscode = $ccheck->retrieve();
571 return true;
572 }
573 unset( $ccheck );
574
575 // $this->jscode has all the uncompressed code now.
576 if ( true !== $this->alreadyminified ) {
577 if ( apply_filters( 'autoptimize_js_do_minify', true ) ) {
578 $tmp_jscode = trim( JSMin::minify( $this->jscode ) );
579 if ( ! empty( $tmp_jscode ) ) {
580 $this->jscode = $tmp_jscode;
581 unset( $tmp_jscode );
582 }
583 $this->jscode = $this->inject_minified( $this->jscode );
584 $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
585 return true;
586 } else {
587 $this->jscode = $this->inject_minified( $this->jscode );
588 return false;
589 }
590 }
591
592 $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
593 return true;
594 }
595
596 /**
597 * Caches the JS in uncompressed, deflated and gzipped form.
598 */
599 public function cache()
600 {
601 $cache = new autoptimizeCache( $this->md5hash, 'js' );
602 if ( ! $cache->check() ) {
603 // Cache our code.
604 $cache->cache( $this->jscode, 'text/javascript' );
605 }
606 $this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
607 $this->url = $this->url_replace_cdn( $this->url );
608 }
609
610 /**
611 * Returns the content.
612 */
613 public function getcontent()
614 {
615 // Restore the full content.
616 if ( ! empty( $this->restofcontent ) ) {
617 $this->content .= $this->restofcontent;
618 $this->restofcontent = '';
619 }
620
621 // Add the scripts taking forcehead/ deferred (default) into account.
622 if ( $this->forcehead ) {
623 $replace_tag = array( '</head>', 'before' );
624 $defer = '';
625 } else {
626 $replace_tag = array( '</body>', 'before' );
627 $defer = 'defer ';
628 }
629
630 $defer = apply_filters( 'autoptimize_filter_js_defer', $defer );
631 $type_js = '';
632 if ( apply_filters( 'autoptimize_filter_cssjs_addtype', false ) ) {
633 $type_js = 'type="text/javascript" ';
634 }
635
636 $bodyreplacementpayload = '<script ' . $type_js . $defer . 'src="' . $this->url . '"></script>';
637 $bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload );
638
639 $bodyreplacement = implode( '', $this->move['first'] );
640 $bodyreplacement .= $bodyreplacementpayload;
641 $bodyreplacement .= implode( '', $this->move['last'] );
642
643 $replace_tag = apply_filters( 'autoptimize_filter_js_replacetag', $replace_tag );
644
645 if ( strlen( $this->jscode ) > 0 ) {
646 $this->inject_in_html( $bodyreplacement, $replace_tag );
647 }
648
649 // Restore comments.
650 $this->content = $this->restore_comments( $this->content );
651
652 // Restore IE hacks.
653 $this->content = $this->restore_iehacks( $this->content );
654
655 // Restore noptimize.
656 $this->content = $this->restore_noptimize( $this->content );
657
658 // Return the modified HTML.
659 return $this->content;
660 }
661
662 /**
663 * Checks against the allow- and blocklists.
664 *
665 * @param string $tag JS tag.
666 */
667 private function ismergeable( $tag )
668 {
669 if ( empty( $tag ) || ! $this->aggregate ) {
670 return false;
671 }
672
673 if ( ! empty( $this->allowlist ) ) {
674 foreach ( $this->allowlist as $match ) {
675 if ( false !== strpos( $tag, $match ) ) {
676 return true;
677 }
678 }
679 // No match with allowlist.
680 return false;
681 } else {
682 foreach ( $this->domove as $match ) {
683 if ( false !== strpos( $tag, $match ) ) {
684 // Matched something.
685 return false;
686 }
687 }
688
689 if ( $this->movetolast( $tag ) ) {
690 return false;
691 }
692
693 foreach ( $this->dontmove as $match ) {
694 if ( false !== strpos( $tag, $match ) ) {
695 // Matched something.
696 return false;
697 }
698 }
699
700 // If we're here it's safe to merge.
701 return true;
702 }
703 }
704
705 /**
706 * Checks agains the blocklist.
707 *
708 * @param string $tag tag to check for blocklist (exclusions).
709 */
710 private function ismovable( $tag )
711 {
712 if ( empty( $tag ) || true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) {
713 return false;
714 }
715
716 foreach ( $this->domove as $match ) {
717 if ( false !== strpos( $tag, $match ) ) {
718 // Matched something.
719 return true;
720 }
721 }
722
723 if ( $this->movetolast( $tag ) ) {
724 return true;
725 }
726
727 foreach ( $this->dontmove as $match ) {
728 if ( false !== strpos( $tag, $match ) ) {
729 // Matched something.
730 return false;
731 }
732 }
733
734 // If we're here it's safe to move.
735 return true;
736 }
737
738 private function movetolast( $tag )
739 {
740 if ( empty( $tag ) ) {
741 return false;
742 }
743
744 foreach ( $this->domovelast as $match ) {
745 if ( false !== strpos( $tag, $match ) ) {
746 // Matched, return true.
747 return true;
748 }
749 }
750
751 // Should be in 'first'.
752 return false;
753 }
754
755 /**
756 * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
757 * - inject_min_late being active
758 * - filename ending in `min.js`
759 * - filename matching `js/jquery/jquery.js` (WordPress core jquery, is minified)
760 * - filename matching one passed in the consider minified filter
761 *
762 * @param string $js_path Path to JS file.
763 * @return bool
764 */
765 private function can_inject_late( $js_path ) {
766 $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
767 if ( true !== $this->inject_min_late ) {
768 // late-inject turned off.
769 return false;
770 } 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 ) ) {
771 // file not minified based on filename & filter.
772 return false;
773 } else {
774 // phew, all is safe, we can late-inject.
775 return true;
776 }
777 }
778
779 /**
780 * Returns whether we're doing aggregation or not.
781 *
782 * @return bool
783 */
784 public function aggregating()
785 {
786 return $this->aggregate;
787 }
788
789 /**
790 * Minifies a single local js file and returns its (cached) url.
791 *
792 * @param string $filepath Filepath.
793 * @param bool $cache_miss Optional. Force a cache miss. Default false.
794 *
795 * @return bool|string Url pointing to the minified js file or false.
796 */
797 public function minify_single( $filepath, $cache_miss = false )
798 {
799 $contents = $this->prepare_minify_single( $filepath );
800
801 if ( empty( $contents ) ) {
802 return false;
803 }
804
805 // Check cache.
806 $hash = 'single_' . md5( $contents );
807 $cache = new autoptimizeCache( $hash, 'js' );
808
809 // If not in cache already, minify...
810 if ( ! $cache->check() || $cache_miss ) {
811 $contents = trim( JSMin::minify( $contents ) );
812
813 // Check if minified cache content is empty.
814 if ( empty( $contents ) ) {
815 return false;
816 }
817
818 // Filter contents of excluded minified CSS.
819 $contents = apply_filters( 'autoptimize_filter_js_single_after_minify', $contents );
820
821 // Store in cache.
822 $cache->cache( $contents, 'text/javascript' );
823 }
824
825 $url = $this->build_minify_single_url( $cache );
826
827 return $url;
828 }
829 }
830