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

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