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