PluginProbe
Autoptimize / 2.7.5
Autoptimize v2.7.5
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.5, at classes/autoptimizeScripts.php

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