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