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