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