PluginProbe
Autoptimize / 2.8.2
Autoptimize v2.8.2
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.8.2, at classes/autoptimizeScripts.php

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