PluginProbe
Autoptimize / trunk
Autoptimize vtrunk
3.1.16 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 All 108 releases
← All changes | classes/autoptimizeScripts.php +432 -149 2.6.0 → trunk View file →
@@ -1,5 +1,8 @@
1 1 <?php
2 +/**
3 + * Class for JS optimization.
4 + */
2 5
3 6 if ( ! defined( 'ABSPATH' ) ) {
4 7 exit;
5 8 }
@@ -5,62 +8,243 @@
5 8 }
6 9
7 10 class autoptimizeScripts extends autoptimizeBase
8 11 {
12 +
13 + /**
14 + * Stores founds scripts.
15 + *
16 + * @var array
17 + */
9 18 private $scripts = array();
10 - private $move = array(
19 +
20 + /**
21 + * Stores to be moved JS.
22 + *
23 + * @var array
24 + */
25 + private $move = array(
11 26 'first' => array(),
12 - 'last' => array()
27 + 'last' => array(),
13 28 );
14 29
30 + /**
31 + * List of not to be moved JS.
32 + *
33 + * @var array
34 + */
15 35 private $dontmove = array(
16 - 'document.write','html5.js','show_ads.js','google_ad','histats.com/js','statcounter.com/counter/counter.js',
17 - 'ws.amazon.com/widgets','media.fastclick.net','/ads/','comment-form-quicktags/quicktags.php','edToolbar',
18 - 'intensedebate.com','scripts.chitika.net/','_gaq.push','jotform.com/','admin-bar.min.js','GoogleAnalyticsObject',
19 - 'plupload.full.min.js','syntaxhighlighter','adsbygoogle','gist.github.com','_stq','nonce','post_id','data-noptimize'
20 - ,'logHuman'
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',
21 67 );
22 - private $domove = array(
23 - 'gaJsHost','load_cmc','jd.gallery.transitions.js','swfobject.embedSWF(','tiny_mce.js','tinyMCEPreInit.go'
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',
24 81 );
82 +
83 + /**
84 + * List of JS that can be moved last (not used any more).
85 + *
86 + * @var array
87 + */
25 88 private $domovelast = array(
26 - 'addthis.com','/afsonline/show_afs_search.js','disqus.js','networkedblogs.com/getnetworkwidget','infolinks.com/js/',
27 - 'jd.gallery.js.php','jd.gallery.transitions.js','swfobject.embedSWF(','linkwithin.com/widget.js','tiny_mce.js','tinyMCEPreInit.go'
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',
28 100 );
101 +
102 + /**
103 + * Setting CDN base URL.
104 + *
105 + * @var string
106 + */
29 107 public $cdn_url = '';
30 108
31 - private $aggregate = true;
32 - private $trycatch = false;
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 + */
33 142 private $alreadyminified = false;
34 - private $forcehead = true;
35 - private $include_inline = false;
36 - private $jscode = '';
37 - private $url = '';
38 - private $restofcontent = '';
39 - private $md5hash = '';
40 - private $whitelist = '';
41 - private $jsremovables = array();
42 - private $inject_min_late = '';
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 + */
43 213 private $minify_excluded = true;
44 214
45 - // Reads the page and collects script tags.
215 + /**
216 + * Reads the page and collects script tags.
217 + *
218 + * @param array $options all options.
219 + */
46 220 public function read( $options )
47 221 {
48 - $noptimizeJS = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content );
49 - if ( $noptimizeJS ) {
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 ) {
50 234 return false;
51 235 }
52 236
53 237 // only optimize known good JS?
54 - $whitelistJS = apply_filters( 'autoptimize_filter_js_whitelist', '', $this->content );
55 - if ( ! empty( $whitelistJS ) ) {
56 - $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistJS ) ) );
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 ) ) );
57 241 }
58 242
59 243 // is there JS we should simply remove?
60 - $removableJS = apply_filters( 'autoptimize_filter_js_removables', '', $this->content );
61 - if ( !empty( $removableJS ) ) {
62 - $this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removableJS ) ) );
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 ) ) );
63 247 }
64 248
65 249 // only header?
66 250 if ( apply_filters( 'autoptimize_filter_js_justhead', $options['justhead'] ) ) {
@@ -76,9 +260,21 @@
76 260 // Returning true for "dontaggregate" turns off aggregation.
77 261 if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) {
78 262 $this->aggregate = false;
79 263 }
264 + // and the filter that should have been there to begin with.
265 + $this->aggregate = apply_filters( 'autoptimize_filter_js_aggregate', $this->aggregate );
80 266
267 + // Defer when not aggregating.
268 + if ( false === $this->aggregate && apply_filters( 'autoptimize_filter_js_defer_not_aggregate', $options['defer_not_aggregate'] ) ) {
269 + $this->defer_not_aggregate = true;
270 + }
271 +
272 + // Defer inline JS?
273 + 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 ) ) {
274 + $this->defer_inline = true;
275 + }
276 +
81 277 // include inline?
82 278 if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) {
83 279 $this->include_inline = true;
84 280 }
@@ -94,27 +290,28 @@
94 290 // Determine whether excluded files should be minified if not yet so.
95 291 if ( ! $options['minify_excluded'] && $options['aggregate'] ) {
96 292 $this->minify_excluded = false;
97 293 }
98 - $this->minify_excluded = apply_filters( 'autoptimize_filter_js_minify_excluded', $this->minify_excluded );
294 + $this->minify_excluded = apply_filters( 'autoptimize_filter_js_minify_excluded', $this->minify_excluded, '' );
99 295
100 296 // get extra exclusions settings or filter.
101 - $excludeJS = $options['js_exclude'];
102 - $excludeJS = apply_filters( 'autoptimize_filter_js_exclude', $excludeJS, $this->content );
297 + $exclude_js = $options['js_exclude'];
298 + $exclude_js = apply_filters( 'autoptimize_filter_js_exclude', $exclude_js, $this->content );
103 299
104 - if ( '' !== $excludeJS ) {
105 - if ( is_array( $excludeJS ) ) {
106 - if ( ( $removeKeys = array_keys( $excludeJS, 'remove' ) ) !== false ) {
107 - foreach ( $removeKeys as $removeKey ) {
108 - unset( $excludeJS[$removeKey] );
109 - $this->jsremovables[] = $removeKey;
300 + if ( '' !== $exclude_js ) {
301 + if ( is_array( $exclude_js ) ) {
302 + $remove_keys = array_keys( $exclude_js, 'remove' );
303 + if ( false !== $remove_keys ) {
304 + foreach ( $remove_keys as $remove_key ) {
305 + unset( $exclude_js[ $remove_key ] );
306 + $this->jsremovables[] = $remove_key;
110 307 }
111 308 }
112 - $exclJSArr = array_keys( $excludeJS );
309 + $excl_js_arr = array_keys( $exclude_js );
113 310 } else {
114 - $exclJSArr = array_filter( array_map( 'trim', explode( ',', $excludeJS ) ) );
311 + $excl_js_arr = array_filter( array_map( 'trim', explode( ',', $exclude_js ) ) );
115 312 }
116 - $this->dontmove = array_merge( $exclJSArr, $this->dontmove );
313 + $this->dontmove = array_merge( $excl_js_arr, $this->dontmove );
117 314 }
118 315
119 316 // Should we add try-catch?
120 317 if ( $options['trycatch'] ) {
@@ -133,21 +330,21 @@
133 330 // get cdn url.
134 331 $this->cdn_url = $options['cdn_url'];
135 332
136 333 // noptimize me.
137 - $this->content = $this->hide_noptimize($this->content);
334 + $this->content = $this->hide_noptimize( $this->content );
138 335
139 336 // Save IE hacks.
140 - $this->content = $this->hide_iehacks($this->content);
337 + $this->content = $this->hide_iehacks( $this->content );
141 338
142 339 // comments.
143 - $this->content = $this->hide_comments($this->content);
340 + $this->content = $this->hide_comments( $this->content );
144 341
145 342 // Get script files.
146 343 if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) {
147 - foreach( $matches[0] as $tag ) {
148 - // only consider script aggregation for types whitelisted in should_aggregate-function.
149 - $should_aggregate = $this->should_aggregate($tag);
344 + foreach ( $matches[0] as $tag ) {
345 + // only consider script aggregation for types allowlisted in should_aggregate-function.
346 + $should_aggregate = $this->should_aggregate( $tag );
150 347 if ( ! $should_aggregate ) {
151 348 $tag = '';
152 349 continue;
153 350 }
@@ -153,33 +350,50 @@
153 350 }
154 351
155 352 if ( preg_match( '#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) {
156 353 // non-inline script.
157 - if ( $this->isremovable($tag, $this->jsremovables) ) {
354 + if ( $this->isremovable( $tag, $this->jsremovables ) ) {
158 355 $this->content = str_replace( $tag, '', $this->content );
159 356 continue;
160 357 }
161 358
162 - $origTag = null;
163 - $url = current( explode( '?', $source[2], 2 ) );
164 - $path = $this->getpath($url);
165 - if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable($tag) ) {
359 + $orig_tag = null;
360 + $url = current( explode( '?', $source[2], 2 ) );
361 + $path = $this->getpath( $url );
362 + if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable( $tag ) ) {
166 363 // ok to optimize, add to array.
167 364 $this->scripts[] = $path;
168 365 } else {
169 - $origTag = $tag;
170 - $newTag = $tag;
366 + $orig_tag = $tag;
367 + $new_tag = $tag;
171 368
172 369 // non-mergeable script (excluded or dynamic or external).
173 - if ( is_array( $excludeJS ) ) {
370 + if ( is_array( $exclude_js ) ) {
174 371 // should we add flags?
175 - foreach ( $excludeJS as $exclTag => $exclFlags) {
176 - if ( false !== strpos( $origTag, $exclTag ) && in_array( $exclFlags, array( 'async', 'defer' ) ) ) {
177 - $newTag = str_replace( '<script ', '<script ' . $exclFlags . ' ', $newTag );
372 + foreach ( $exclude_js as $excl_tag => $excl_flags ) {
373 + if ( false !== strpos( $orig_tag, $excl_tag ) && in_array( $excl_flags, array( 'async', 'defer' ) ) ) {
374 + $new_tag = str_replace( '<script ', '<script ' . $excl_flags . ' ', $new_tag );
178 375 }
179 376 }
180 377 }
181 378
379 + // not aggregating but deferring?
380 + 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, '', $orig_tag ) === $orig_tag ) ) && strpos( $new_tag, ' defer' ) === false ) {
381 + if ( false !== strpos( $new_tag, ' async' ) && true === apply_filters( 'autoptimize_filter_js_defer_trumps_async', true ) ) {
382 + // remove async flag to ensure JS is properly deferred, otherwise the asynced JS might fire
383 + // before deferred inlined JS is executed, off course except filter is set to false which
384 + // re-institutes previous behavior.
385 + $new_tag = str_replace( array( " async='async'", ' async="async"', ' async=async', ' async' ), '', $new_tag );
386 + }
387 +
388 + if ( false === strpos( $new_tag, ' async' ) ) {
389 + // either async wasn't there to begin with or it was removed.
390 + // if async is there, the autoptimize_filter_js_defer_trumps_async
391 + // filter was set to false and in that case defer should not be added.
392 + $new_tag = str_replace( '<script ', '<script defer ', $new_tag );
393 + }
394 + }
395 +
182 396 // Should we minify the non-aggregated script?
183 397 // -> if aggregate is on and exclude minify is on
184 398 // -> if aggregate is off and the file is not in dontmove.
185 399 if ( $path && $this->minify_excluded ) {
@@ -185,27 +399,36 @@
185 399 if ( $path && $this->minify_excluded ) {
186 400 $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
187 401 if ( ( false === $this->aggregate && str_replace( $this->dontmove, '', $path ) === $path ) || ( true === $this->aggregate && ( false === $consider_minified_array || str_replace( $consider_minified_array, '', $path ) === $path ) ) ) {
188 402 $minified_url = $this->minify_single( $path );
189 - // replace orig URL with minified URL from cache if so.
190 403 if ( ! empty( $minified_url ) ) {
191 - $newTag = str_replace( $url, $minified_url, $newTag );
404 + // Replace original URL with minified URL from cache.
405 + $new_tag = str_replace( $url, $minified_url, $new_tag );
406 + } elseif ( apply_filters( 'autoptimize_filter_ccsjs_remove_empty_minified_url', false ) ) {
407 + // Remove the original script tag, because cache content is empty but only if filter
408 + // is trued because $minified_url is also false if original JS is minified already.
409 + $new_tag = '';
192 410 }
193 411 }
194 412 }
195 413
196 - if ( $this->ismovable($newTag) ) {
414 + // Check if we still need to CDN (esp. for already minified resources).
415 + if ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) {
416 + $new_tag = str_replace( $url, $this->url_replace_cdn( $url ), $new_tag );
417 + }
418 +
419 + if ( $this->ismovable( $new_tag ) ) {
197 420 // can be moved, flags and all.
198 - if ( $this->movetolast($newTag) ) {
199 - $this->move['last'][] = $newTag;
421 + if ( $this->movetolast( $new_tag ) ) {
422 + $this->move['last'][] = $new_tag;
200 423 } else {
201 - $this->move['first'][] = $newTag;
424 + $this->move['first'][] = $new_tag;
202 425 }
203 426 } else {
204 427 // cannot be moved, so if flag was added re-inject altered tag immediately.
205 - if ( $origTag !== $newTag ) {
206 - $this->content = str_replace( $origTag, $newTag, $this->content );
207 - $origTag = '';
428 + if ( ( '' !== $new_tag && $orig_tag !== $new_tag ) || ( '' === $new_tag && apply_filters( 'autoptimize_filter_js_remove_empty_files', false ) ) ) {
429 + $this->content = str_replace( $orig_tag, $new_tag, $this->content );
430 + $orig_tag = '';
208 431 }
209 432 // and forget about the $tag (not to be touched any more).
210 433 $tag = '';
211 434 }
@@ -211,39 +434,67 @@
211 434 }
212 435 }
213 436 } else {
214 437 // Inline script.
215 - if ( $this->isremovable($tag, $this->jsremovables) ) {
438 + if ( $this->isremovable( $tag, $this->jsremovables ) ) {
216 439 $this->content = str_replace( $tag, '', $this->content );
217 440 continue;
218 441 }
219 442
220 443 // unhide comments, as javascript may be wrapped in comment-tags for old times' sake.
221 - $tag = $this->restore_comments($tag);
222 - if ( $this->ismergeable($tag) && $this->include_inline ) {
223 - preg_match( '#<script.*>(.*)</script>#Usmi', $tag , $code );
224 - $code = preg_replace('#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
225 - $code = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
444 + $tag = $this->restore_comments( $tag );
445 + if ( $this->ismergeable( $tag ) && $this->include_inline ) {
446 + preg_match( '#<script.*>(.*)</script>#Usmi', $tag, $code );
447 + $code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
448 + $code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
226 449 $this->scripts[] = 'INLINE;' . $code;
227 450 } else {
228 - // Can we move this?
229 - $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
230 - if ( $this->ismovable($tag) || '' !== $autoptimize_js_moveable ) {
231 - if ( $this->movetolast($tag) || 'last' === $autoptimize_js_moveable ) {
232 - $this->move['last'][] = $tag;
451 + $_inline_deferable = apply_filters( 'autoptimize_filters_js_inline_deferable', array( 'nonce', 'post_id', 'syntaxhighlighter' ) );
452 + $_inline_dontmove = array_values( array_diff( $this->dontmove, $_inline_deferable ) );
453 + if ( false === $this->defer_inline ) {
454 + // Can we move this?
455 + $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
456 + if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) {
457 + if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) {
458 + $this->move['last'][] = $tag;
459 + } else {
460 + $this->move['first'][] = $tag;
461 + }
233 462 } else {
234 - $this->move['first'][] = $tag;
463 + $tag = '';
235 464 }
465 + } else if ( str_replace( $_inline_dontmove, '', $tag ) === $tag && strlen( $tag ) < apply_filters( 'autoptimize_filter_script_defer_inline_maxsize', 200000 ) ) {
466 + // defer inline JS by base64 encoding it but only if string is not ridiculously huge (to avoid issues with below regex mainly).
467 + preg_match( '#<script(?:[^>](?!id=))*\s*(?:id=(["\'])([^"\']+)\1)*+[^>]*+>(.*?)<\/script>#is', $tag, $match );
468 + if ( $match[2] ) {
469 + $_id = 'id="' . $match[2] . '" ';
470 + } else {
471 + $_id = '';
472 + }
473 +
474 + // if "minify inline" is on and if more then 9 spaces or 4 line breaks are found
475 + // in the inline JS then it is likely not minified, so minify before base64-encoding.
476 + $_script_contents = $match[3];
477 + if ( 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_html_minify_inline', 'off' ) && substr_count( $_script_contents, ' ' ) > 9 && substr_count( $_script_contents, "\n" ) > 4 && true === apply_filters( 'autoptimize_filter_script_defer_inline_minify', true ) ) {
478 + $_tmp_script_contents = trim( JSMin::minify( $_script_contents ) );
479 + if ( ! empty( $_tmp_script_contents ) ) {
480 + $_script_contents = $_tmp_script_contents;
481 + }
482 + }
483 +
484 + // base64 and defer the lot already.
485 + $new_tag = '<script defer ' . $_id . 'src="data:text/javascript;base64,' . base64_encode( $_script_contents ) . '"></script>';
486 + $this->content = str_replace( $this->hide_comments( $tag ), $new_tag, $this->content );
487 + $tag = '';
236 488 } else {
237 - // We shouldn't touch this.
238 489 $tag = '';
239 490 }
240 491 }
241 492 // Re-hide comments to be able to do the removal based on tag from $this->content.
242 - $tag = $this->hide_comments($tag);
493 + $tag = $this->hide_comments( $tag );
243 494 }
244 495
245 - //Remove the original script tag.
496 + // Remove the original script tag.
246 497 $this->content = str_replace( $tag, '', $this->content );
247 498 }
248 499
249 500 return true;
@@ -248,9 +499,9 @@
248 499
249 500 return true;
250 501 }
251 502
252 - // No script files, great ;-)
503 + // No script files, great ;-) .
253 504 return false;
254 505 }
255 506
256 507 /**
@@ -264,23 +515,27 @@
264 515 * Everything else should return false.
265 516 *
266 517 * @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
267 518 *
268 - * @param string $tag
519 + * @param string $tag Script node & child(ren).
269 520 * @return bool
270 521 */
271 - public function should_aggregate($tag)
522 + public static function should_aggregate( $tag )
272 523 {
524 + if ( empty( $tag ) ) {
525 + return false;
526 + }
527 +
273 528 // We're only interested in the type attribute of the <script> tag itself, not any possible
274 529 // inline code that might just contain the 'type=' string...
275 530 $tag_parts = array();
276 - preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts);
531 + preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts );
277 532 $tag_without_contents = null;
278 533 if ( ! empty( $tag_parts[1] ) ) {
279 534 $tag_without_contents = $tag_parts[1];
280 535 }
281 536
282 - $has_type = ( strpos( $tag_without_contents, 'type' ) !== false );
537 + $has_type = ( strpos( $tag_without_contents, ' type' ) !== false );
283 538
284 539 $type_valid = false;
285 540 if ( $has_type ) {
286 541 $type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
@@ -293,15 +548,21 @@
293 548
294 549 return $should_aggregate;
295 550 }
296 551
297 - //Joins and optimizes JS
552 + /**
553 + * Joins and optimizes JS.
554 + */
298 555 public function minify()
299 556 {
300 557 foreach ( $this->scripts as $script ) {
301 - // TODO/FIXME: some duplicate code here, can be reduced/simplified
558 + if ( empty( $script ) ) {
559 + continue;
560 + }
561 +
562 + // TODO/FIXME: some duplicate code here, can be reduced/simplified.
302 563 if ( preg_match( '#^INLINE;#', $script ) ) {
303 - // Inline script
564 + // Inline script.
304 565 $script = preg_replace( '#^INLINE;#', '', $script );
305 566 $script = rtrim( $script, ";\n\t\r" ) . ';';
306 567 // Add try-catch?
307 568 if ( $this->trycatch ) {
@@ -308,14 +569,14 @@
308 569 $script = 'try{' . $script . '}catch(e){}';
309 570 }
310 571 $tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' );
311 572 if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscript ) ) {
312 - $script = $tmpscript;
573 + $script = $tmpscript;
313 574 $this->alreadyminified = true;
314 575 }
315 576 $this->jscode .= "\n" . $script;
316 577 } else {
317 - // External script
578 + // External script.
318 579 if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) {
319 580 $scriptsrc = file_get_contents( $script );
320 581 $scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
321 582 $scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';
@@ -324,23 +585,21 @@
324 585 $scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
325 586 }
326 587 $tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
327 588 if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) {
328 - $scriptsrc = $tmpscriptsrc;
589 + $scriptsrc = $tmpscriptsrc;
329 590 $this->alreadyminified = true;
330 - } else if ( $this->can_inject_late($script) ) {
331 - $scriptsrc = self::build_injectlater_marker($script, md5($scriptsrc));
591 + } elseif ( $this->can_inject_late( $script ) ) {
592 + $scriptsrc = self::build_injectlater_marker( $script, md5( $scriptsrc ) );
332 593 }
333 594 $this->jscode .= "\n" . $scriptsrc;
334 - }/*else{
335 - //Couldn't read JS. Maybe getpath isn't working?
336 - }*/
595 + }
337 596 }
338 597 }
339 598
340 - // Check for already-minified code
599 + // Check for already-minified code.
341 600 $this->md5hash = md5( $this->jscode );
342 - $ccheck = new autoptimizeCache($this->md5hash, 'js');
601 + $ccheck = new autoptimizeCache( $this->md5hash, 'js' );
343 602 if ( $ccheck->check() ) {
344 603 $this->jscode = $ccheck->retrieve();
345 604 return true;
346 605 }
@@ -366,36 +625,40 @@
366 625 $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
367 626 return true;
368 627 }
369 628
370 - // Caches the JS in uncompressed, deflated and gzipped form.
629 + /**
630 + * Caches the JS in uncompressed, deflated and gzipped form.
631 + */
371 632 public function cache()
372 633 {
373 - $cache = new autoptimizeCache($this->md5hash, 'js');
634 + $cache = new autoptimizeCache( $this->md5hash, 'js' );
374 635 if ( ! $cache->check() ) {
375 - // Cache our code
376 - $cache->cache($this->jscode, 'text/javascript');
636 + // Cache our code.
637 + $cache->cache( $this->jscode, 'text/javascript' );
377 638 }
378 639 $this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
379 - $this->url = $this->url_replace_cdn($this->url);
640 + $this->url = $this->url_replace_cdn( $this->url );
380 641 }
381 642
382 - // Returns the content
643 + /**
644 + * Returns the content.
645 + */
383 646 public function getcontent()
384 647 {
385 - // Restore the full content
648 + // Restore the full content.
386 649 if ( ! empty( $this->restofcontent ) ) {
387 - $this->content .= $this->restofcontent;
650 + $this->content .= $this->restofcontent;
388 651 $this->restofcontent = '';
389 652 }
390 653
391 - // Add the scripts taking forcehead/ deferred (default) into account
654 + // Add the scripts taking forcehead/ deferred (default) into account.
392 655 if ( $this->forcehead ) {
393 - $replaceTag = array( '</head>', 'before' );
394 - $defer = '';
656 + $replace_tag = array( '</head>', 'before' );
657 + $defer = '';
395 658 } else {
396 - $replaceTag = array( '</body>', 'before' );
397 - $defer = 'defer ';
659 + $replace_tag = array( '</body>', 'before' );
660 + $defer = 'defer ';
398 661 }
399 662
400 663 $defer = apply_filters( 'autoptimize_filter_js_defer', $defer );
401 664 $type_js = '';
@@ -405,16 +668,16 @@
405 668
406 669 $bodyreplacementpayload = '<script ' . $type_js . $defer . 'src="' . $this->url . '"></script>';
407 670 $bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload );
408 671
409 - $bodyreplacement = implode( '', $this->move['first'] );
672 + $bodyreplacement = implode( '', $this->move['first'] );
410 673 $bodyreplacement .= $bodyreplacementpayload;
411 674 $bodyreplacement .= implode( '', $this->move['last'] );
412 675
413 - $replaceTag = apply_filters( 'autoptimize_filter_js_replacetag', $replaceTag );
676 + $replace_tag = apply_filters( 'autoptimize_filter_js_replacetag', $replace_tag );
414 677
415 678 if ( strlen( $this->jscode ) > 0 ) {
416 - $this->inject_in_html( $bodyreplacement, $replaceTag );
679 + $this->inject_in_html( $bodyreplacement, $replace_tag );
417 680 }
418 681
419 682 // Restore comments.
420 683 $this->content = $this->restore_comments( $this->content );
@@ -428,86 +691,98 @@
428 691 // Return the modified HTML.
429 692 return $this->content;
430 693 }
431 694
432 - // Checks against the white- and blacklists
433 - private function ismergeable($tag)
695 + /**
696 + * Checks against the allow- and blocklists.
697 + *
698 + * @param string $tag JS tag.
699 + */
700 + private function ismergeable( $tag )
434 701 {
435 - if ( ! $this->aggregate ) {
702 + if ( empty( $tag ) || ! $this->aggregate ) {
436 703 return false;
437 704 }
438 705
439 - if ( ! empty( $this->whitelist ) ) {
440 - foreach ( $this->whitelist as $match ) {
441 - if (false !== strpos( $tag, $match ) ) {
706 + if ( ! empty( $this->allowlist ) ) {
707 + foreach ( $this->allowlist as $match ) {
708 + if ( false !== strpos( $tag, $match ) ) {
442 709 return true;
443 710 }
444 711 }
445 - // no match with whitelist
712 + // No match with allowlist.
446 713 return false;
447 714 } else {
448 - foreach($this->domove as $match) {
715 + foreach ( $this->domove as $match ) {
449 716 if ( false !== strpos( $tag, $match ) ) {
450 - // Matched something
717 + // Matched something.
451 718 return false;
452 719 }
453 720 }
454 721
455 - if ( $this->movetolast($tag) ) {
722 + if ( $this->movetolast( $tag ) ) {
456 723 return false;
457 724 }
458 725
459 - foreach( $this->dontmove as $match ) {
726 + foreach ( $this->dontmove as $match ) {
460 727 if ( false !== strpos( $tag, $match ) ) {
461 - // Matched something
728 + // Matched something.
462 729 return false;
463 730 }
464 731 }
465 732
466 - // If we're here it's safe to merge
733 + // If we're here it's safe to merge.
467 734 return true;
468 735 }
469 736 }
470 737
471 - // Checks agains the blacklist
472 - private function ismovable($tag)
738 + /**
739 + * Checks agains the blocklist.
740 + *
741 + * @param string $tag tag to check for blocklist (exclusions).
742 + */
743 + private function ismovable( $tag )
473 744 {
474 - if ( true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) {
745 + if ( empty( $tag ) || true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) {
475 746 return false;
476 747 }
477 748
478 749 foreach ( $this->domove as $match ) {
479 750 if ( false !== strpos( $tag, $match ) ) {
480 - // Matched something
751 + // Matched something.
481 752 return true;
482 753 }
483 754 }
484 755
485 - if ( $this->movetolast($tag) ) {
756 + if ( $this->movetolast( $tag ) ) {
486 757 return true;
487 758 }
488 759
489 760 foreach ( $this->dontmove as $match ) {
490 761 if ( false !== strpos( $tag, $match ) ) {
491 - // Matched something
762 + // Matched something.
492 763 return false;
493 764 }
494 765 }
495 766
496 - // If we're here it's safe to move
767 + // If we're here it's safe to move.
497 768 return true;
498 769 }
499 770
500 - private function movetolast($tag)
771 + private function movetolast( $tag )
501 772 {
773 + if ( empty( $tag ) ) {
774 + return false;
775 + }
776 +
502 777 foreach ( $this->domovelast as $match ) {
503 778 if ( false !== strpos( $tag, $match ) ) {
504 - // Matched, return true
779 + // Matched, return true.
505 780 return true;
506 781 }
507 782 }
508 783
509 - // Should be in 'first'
784 + // Should be in 'first'.
510 785 return false;
511 786 }
512 787
513 788 /**
@@ -513,24 +788,23 @@
513 788 /**
514 789 * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
515 790 * - inject_min_late being active
516 791 * - filename ending in `min.js`
517 - * - filename matching `js/jquery/jquery.js` (wordpress core jquery, is minified)
518 792 * - filename matching one passed in the consider minified filter
519 793 *
520 - * @param string $jsPath
794 + * @param string $js_path Path to JS file.
521 795 * @return bool
522 796 */
523 - private function can_inject_late($jsPath) {
797 + private function can_inject_late( $js_path ) {
524 798 $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
525 799 if ( true !== $this->inject_min_late ) {
526 - // late-inject turned off
800 + // late-inject turned off.
527 801 return false;
528 - } else if ( ( false === strpos( $jsPath, 'min.js' ) ) && ( false === strpos( $jsPath, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $jsPath ) === $jsPath ) ) {
529 - // file not minified based on filename & filter
802 + } elseif ( ( false === strpos( $js_path, 'min.js' ) ) && ( str_replace( $consider_minified_array, '', $js_path ) === $js_path ) ) {
803 + // file not minified based on filename & filter.
530 804 return false;
531 805 } else {
532 - // phew, all is safe, we can late-inject
806 + // phew, all is safe, we can late-inject.
533 807 return true;
534 808 }
535 809 }
536 810
@@ -547,9 +821,9 @@
547 821 /**
548 822 * Minifies a single local js file and returns its (cached) url.
549 823 *
550 824 * @param string $filepath Filepath.
551 - * @param bool $cache_miss Optional. Force a cache miss. Default false.
825 + * @param bool $cache_miss Optional. Force a cache miss. Default false.
552 826 *
553 827 * @return bool|string Url pointing to the minified js file or false.
554 828 */
555 829 public function minify_single( $filepath, $cache_miss = false )
@@ -566,8 +840,17 @@
566 840
567 841 // If not in cache already, minify...
568 842 if ( ! $cache->check() || $cache_miss ) {
569 843 $contents = trim( JSMin::minify( $contents ) );
844 +
845 + // Check if minified cache content is empty.
846 + if ( empty( $contents ) ) {
847 + return false;
848 + }
849 +
850 + // Filter contents of excluded minified CSS.
851 + $contents = apply_filters( 'autoptimize_filter_js_single_after_minify', $contents );
852 +
570 853 // Store in cache.
571 854 $cache->cache( $contents, 'text/javascript' );
572 855 }
573 856