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 +474 -174 2.4.3 → 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,93 +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 }
181 412 }
182 413
183 - if ( $this->ismovable($newTag) ) {
184 - // can be moved, flags and all
185 - if ( $this->movetolast($newTag) ) {
186 - $this->move['last'][] = $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 ) ) {
420 + // can be moved, flags and all.
421 + if ( $this->movetolast( $new_tag ) ) {
422 + $this->move['last'][] = $new_tag;
187 423 } else {
188 - $this->move['first'][] = $newTag;
424 + $this->move['first'][] = $new_tag;
189 425 }
190 426 } else {
191 - // cannot be moved, so if flag was added re-inject altered tag immediately
192 - if ( $origTag !== $newTag ) {
193 - $this->content = str_replace( $origTag, $newTag, $this->content );
194 - $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 = '';
195 431 }
196 - // and forget about the $tag (not to be touched any more)
432 + // and forget about the $tag (not to be touched any more).
197 433 $tag = '';
198 434 }
199 435 }
200 436 } else {
201 - // Inline script
202 - if ( $this->isremovable($tag, $this->jsremovables) ) {
437 + // Inline script.
438 + if ( $this->isremovable( $tag, $this->jsremovables ) ) {
203 439 $this->content = str_replace( $tag, '', $this->content );
204 440 continue;
205 441 }
206 442
207 - // unhide comments, as javascript may be wrapped in comment-tags for old times' sake
208 - $tag = $this->restore_comments($tag);
209 - if ( $this->ismergeable($tag) && $this->include_inline ) {
210 - preg_match( '#<script.*>(.*)</script>#Usmi', $tag , $code );
211 - $code = preg_replace('#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
212 - $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 );
213 449 $this->scripts[] = 'INLINE;' . $code;
214 450 } else {
215 - // Can we move this?
216 - $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
217 - if ( $this->ismovable($tag) || '' !== $autoptimize_js_moveable ) {
218 - if ( $this->movetolast($tag) || 'last' === $autoptimize_js_moveable ) {
219 - $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 + }
220 462 } else {
221 - $this->move['first'][] = $tag;
463 + $tag = '';
222 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 = '';
223 488 } else {
224 - // We shouldn't touch this
225 489 $tag = '';
226 490 }
227 491 }
228 - // Re-hide comments to be able to do the removal based on tag from $this->content
229 - $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 );
230 494 }
231 495
232 - //Remove the original script tag
496 + // Remove the original script tag.
233 497 $this->content = str_replace( $tag, '', $this->content );
234 498 }
235 499
236 500 return true;
@@ -235,9 +499,9 @@
235 499
236 500 return true;
237 501 }
238 502
239 - // No script files, great ;-)
503 + // No script files, great ;-) .
240 504 return false;
241 505 }
242 506
243 507 /**
@@ -251,23 +515,27 @@
251 515 * Everything else should return false.
252 516 *
253 517 * @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
254 518 *
255 - * @param string $tag
519 + * @param string $tag Script node & child(ren).
256 520 * @return bool
257 521 */
258 - public function should_aggregate($tag)
522 + public static function should_aggregate( $tag )
259 523 {
524 + if ( empty( $tag ) ) {
525 + return false;
526 + }
527 +
260 528 // We're only interested in the type attribute of the <script> tag itself, not any possible
261 529 // inline code that might just contain the 'type=' string...
262 530 $tag_parts = array();
263 - preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts);
531 + preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts );
264 532 $tag_without_contents = null;
265 533 if ( ! empty( $tag_parts[1] ) ) {
266 534 $tag_without_contents = $tag_parts[1];
267 535 }
268 536
269 - $has_type = ( strpos( $tag_without_contents, 'type' ) !== false );
537 + $has_type = ( strpos( $tag_without_contents, ' type' ) !== false );
270 538
271 539 $type_valid = false;
272 540 if ( $has_type ) {
273 541 $type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
@@ -280,15 +548,21 @@
280 548
281 549 return $should_aggregate;
282 550 }
283 551
284 - //Joins and optimizes JS
552 + /**
553 + * Joins and optimizes JS.
554 + */
285 555 public function minify()
286 556 {
287 557 foreach ( $this->scripts as $script ) {
288 - // 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.
289 563 if ( preg_match( '#^INLINE;#', $script ) ) {
290 - // Inline script
564 + // Inline script.
291 565 $script = preg_replace( '#^INLINE;#', '', $script );
292 566 $script = rtrim( $script, ";\n\t\r" ) . ';';
293 567 // Add try-catch?
294 568 if ( $this->trycatch ) {
@@ -295,14 +569,14 @@
295 569 $script = 'try{' . $script . '}catch(e){}';
296 570 }
297 571 $tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' );
298 572 if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscript ) ) {
299 - $script = $tmpscript;
573 + $script = $tmpscript;
300 574 $this->alreadyminified = true;
301 575 }
302 576 $this->jscode .= "\n" . $script;
303 577 } else {
304 - // External script
578 + // External script.
305 579 if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) {
306 580 $scriptsrc = file_get_contents( $script );
307 581 $scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
308 582 $scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';
@@ -311,23 +585,21 @@
311 585 $scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
312 586 }
313 587 $tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
314 588 if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) {
315 - $scriptsrc = $tmpscriptsrc;
589 + $scriptsrc = $tmpscriptsrc;
316 590 $this->alreadyminified = true;
317 - } else if ( $this->can_inject_late($script) ) {
318 - $scriptsrc = self::build_injectlater_marker($script, md5($scriptsrc));
591 + } elseif ( $this->can_inject_late( $script ) ) {
592 + $scriptsrc = self::build_injectlater_marker( $script, md5( $scriptsrc ) );
319 593 }
320 594 $this->jscode .= "\n" . $scriptsrc;
321 - }/*else{
322 - //Couldn't read JS. Maybe getpath isn't working?
323 - }*/
595 + }
324 596 }
325 597 }
326 598
327 - // Check for already-minified code
599 + // Check for already-minified code.
328 600 $this->md5hash = md5( $this->jscode );
329 - $ccheck = new autoptimizeCache($this->md5hash, 'js');
601 + $ccheck = new autoptimizeCache( $this->md5hash, 'js' );
330 602 if ( $ccheck->check() ) {
331 603 $this->jscode = $ccheck->retrieve();
332 604 return true;
333 605 }
@@ -353,51 +625,59 @@
353 625 $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
354 626 return true;
355 627 }
356 628
357 - // Caches the JS in uncompressed, deflated and gzipped form.
629 + /**
630 + * Caches the JS in uncompressed, deflated and gzipped form.
631 + */
358 632 public function cache()
359 633 {
360 - $cache = new autoptimizeCache($this->md5hash, 'js');
634 + $cache = new autoptimizeCache( $this->md5hash, 'js' );
361 635 if ( ! $cache->check() ) {
362 - // Cache our code
363 - $cache->cache($this->jscode, 'text/javascript');
636 + // Cache our code.
637 + $cache->cache( $this->jscode, 'text/javascript' );
364 638 }
365 639 $this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
366 - $this->url = $this->url_replace_cdn($this->url);
640 + $this->url = $this->url_replace_cdn( $this->url );
367 641 }
368 642
369 - // Returns the content
643 + /**
644 + * Returns the content.
645 + */
370 646 public function getcontent()
371 647 {
372 - // Restore the full content
648 + // Restore the full content.
373 649 if ( ! empty( $this->restofcontent ) ) {
374 - $this->content .= $this->restofcontent;
650 + $this->content .= $this->restofcontent;
375 651 $this->restofcontent = '';
376 652 }
377 653
378 - // Add the scripts taking forcehead/ deferred (default) into account
654 + // Add the scripts taking forcehead/ deferred (default) into account.
379 655 if ( $this->forcehead ) {
380 - $replaceTag = array( '</head>', 'before' );
381 - $defer = '';
656 + $replace_tag = array( '</head>', 'before' );
657 + $defer = '';
382 658 } else {
383 - $replaceTag = array( '</body>', 'before' );
384 - $defer = 'defer ';
659 + $replace_tag = array( '</body>', 'before' );
660 + $defer = 'defer ';
385 661 }
386 662
387 - $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 + }
388 668
389 - $bodyreplacementpayload = '<script type="text/javascript" ' . $defer . 'src="' . $this->url . '"></script>';
669 + $bodyreplacementpayload = '<script ' . $type_js . $defer . 'src="' . $this->url . '"></script>';
390 670 $bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload );
391 671
392 - $bodyreplacement = implode( '', $this->move['first'] );
672 + $bodyreplacement = implode( '', $this->move['first'] );
393 673 $bodyreplacement .= $bodyreplacementpayload;
394 674 $bodyreplacement .= implode( '', $this->move['last'] );
395 675
396 - $replaceTag = apply_filters( 'autoptimize_filter_js_replacetag', $replaceTag );
676 + $replace_tag = apply_filters( 'autoptimize_filter_js_replacetag', $replace_tag );
397 677
398 678 if ( strlen( $this->jscode ) > 0 ) {
399 - $this->inject_in_html( $bodyreplacement, $replaceTag );
679 + $this->inject_in_html( $bodyreplacement, $replace_tag );
400 680 }
401 681
402 682 // Restore comments.
403 683 $this->content = $this->restore_comments( $this->content );
@@ -411,86 +691,98 @@
411 691 // Return the modified HTML.
412 692 return $this->content;
413 693 }
414 694
415 - // Checks against the white- and blacklists
416 - 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 )
417 701 {
418 - if ( ! $this->aggregate ) {
702 + if ( empty( $tag ) || ! $this->aggregate ) {
419 703 return false;
420 704 }
421 705
422 - if ( ! empty( $this->whitelist ) ) {
423 - foreach ( $this->whitelist as $match ) {
424 - if (false !== strpos( $tag, $match ) ) {
706 + if ( ! empty( $this->allowlist ) ) {
707 + foreach ( $this->allowlist as $match ) {
708 + if ( false !== strpos( $tag, $match ) ) {
425 709 return true;
426 710 }
427 711 }
428 - // no match with whitelist
712 + // No match with allowlist.
429 713 return false;
430 714 } else {
431 - foreach($this->domove as $match) {
715 + foreach ( $this->domove as $match ) {
432 716 if ( false !== strpos( $tag, $match ) ) {
433 - // Matched something
717 + // Matched something.
434 718 return false;
435 719 }
436 720 }
437 721
438 - if ( $this->movetolast($tag) ) {
722 + if ( $this->movetolast( $tag ) ) {
439 723 return false;
440 724 }
441 725
442 - foreach( $this->dontmove as $match ) {
726 + foreach ( $this->dontmove as $match ) {
443 727 if ( false !== strpos( $tag, $match ) ) {
444 - // Matched something
728 + // Matched something.
445 729 return false;
446 730 }
447 731 }
448 732
449 - // If we're here it's safe to merge
733 + // If we're here it's safe to merge.
450 734 return true;
451 735 }
452 736 }
453 737
454 - // Checks agains the blacklist
455 - 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 )
456 744 {
457 - 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 ) ) {
458 746 return false;
459 747 }
460 748
461 749 foreach ( $this->domove as $match ) {
462 750 if ( false !== strpos( $tag, $match ) ) {
463 - // Matched something
751 + // Matched something.
464 752 return true;
465 753 }
466 754 }
467 755
468 - if ( $this->movetolast($tag) ) {
756 + if ( $this->movetolast( $tag ) ) {
469 757 return true;
470 758 }
471 759
472 760 foreach ( $this->dontmove as $match ) {
473 761 if ( false !== strpos( $tag, $match ) ) {
474 - // Matched something
762 + // Matched something.
475 763 return false;
476 764 }
477 765 }
478 766
479 - // If we're here it's safe to move
767 + // If we're here it's safe to move.
480 768 return true;
481 769 }
482 770
483 - private function movetolast($tag)
771 + private function movetolast( $tag )
484 772 {
773 + if ( empty( $tag ) ) {
774 + return false;
775 + }
776 +
485 777 foreach ( $this->domovelast as $match ) {
486 778 if ( false !== strpos( $tag, $match ) ) {
487 - // Matched, return true
779 + // Matched, return true.
488 780 return true;
489 781 }
490 782 }
491 783
492 - // Should be in 'first'
784 + // Should be in 'first'.
493 785 return false;
494 786 }
495 787
496 788 /**
@@ -496,24 +788,23 @@
496 788 /**
497 789 * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
498 790 * - inject_min_late being active
499 791 * - filename ending in `min.js`
500 - * - filename matching `js/jquery/jquery.js` (wordpress core jquery, is minified)
501 792 * - filename matching one passed in the consider minified filter
502 793 *
503 - * @param string $jsPath
794 + * @param string $js_path Path to JS file.
504 795 * @return bool
505 796 */
506 - private function can_inject_late($jsPath) {
797 + private function can_inject_late( $js_path ) {
507 798 $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
508 799 if ( true !== $this->inject_min_late ) {
509 - // late-inject turned off
800 + // late-inject turned off.
510 801 return false;
511 - } else if ( ( false === strpos( $jsPath, 'min.js' ) ) && ( false === strpos( $jsPath, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $jsPath ) === $jsPath ) ) {
512 - // 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.
513 804 return false;
514 805 } else {
515 - // phew, all is safe, we can late-inject
806 + // phew, all is safe, we can late-inject.
516 807 return true;
517 808 }
518 809 }
519 810
@@ -530,9 +821,9 @@
530 821 /**
531 822 * Minifies a single local js file and returns its (cached) url.
532 823 *
533 824 * @param string $filepath Filepath.
534 - * @param bool $cache_miss Optional. Force a cache miss. Default false.
825 + * @param bool $cache_miss Optional. Force a cache miss. Default false.
535 826 *
536 827 * @return bool|string Url pointing to the minified js file or false.
537 828 */
538 829 public function minify_single( $filepath, $cache_miss = false )
@@ -549,8 +840,17 @@
549 840
550 841 // If not in cache already, minify...
551 842 if ( ! $cache->check() || $cache_miss ) {
552 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 +
553 853 // Store in cache.
554 854 $cache->cache( $contents, 'text/javascript' );
555 855 }
556 856