PluginProbe
Autoptimize / 2.2.1
Autoptimize v2.2.1
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 2.7.8 All 107 releases
autoptimize / classes / autoptimizeScripts.php

autoptimizeScripts.php in Autoptimize 2.2.1, at classes/autoptimizeScripts.php

470 lines 19.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4 class autoptimizeScripts extends autoptimizeBase {
5 private $scripts = array();
6 private $dontmove = array('document.write','html5.js','show_ads.js','google_ad','blogcatalog.com/w','tweetmeme.com/i','mybloglog.com/','histats.com/js','ads.smowtion.com/ad.js','statcounter.com/counter/counter.js','widgets.amung.us','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');
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');
9 private $trycatch = false;
10 private $alreadyminified = false;
11 private $forcehead = true;
12 private $include_inline = false;
13 private $jscode = '';
14 private $url = '';
15 private $move = array('first' => array(), 'last' => array());
16 private $restofcontent = '';
17 private $md5hash = '';
18 private $whitelist = '';
19 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
27 // 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)));
31 }
32
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)));
37 }
38
39 // 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>';
43 $this->restofcontent = $content[1];
44 }
45
46 // include inline?
47 if( apply_filters('autoptimize_js_include_inline', $options['include_inline']) == true ) {
48 $this->include_inline = true;
49 }
50
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);
53
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 );
56 $this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast );
57 $this->domove = apply_filters( 'autoptimize_filter_js_domove', $this->domove );
58
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;
68 }
69 }
70 $exclJSArr = array_keys($excludeJS);
71 } else {
72 $exclJSArr = array_filter(array_map('trim',explode(",",$excludeJS)));
73 }
74 $this->dontmove = array_merge($exclJSArr,$this->dontmove);
75 }
76
77 //Should we add try-catch?
78 if($options['trycatch'] == true)
79 $this->trycatch = true;
80
81 // force js in head?
82 if($options['forcehead'] == true) {
83 $this->forcehead = true;
84 } else {
85 $this->forcehead = false;
86 }
87 $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead );
88
89 // get cdn url
90 $this->cdn_url = $options['cdn_url'];
91
92 // noptimize me
93 $this->content = $this->hide_noptimize($this->content);
94
95 // Save IE hacks
96 $this->content = $this->hide_iehacks($this->content);
97
98 // comments
99 $this->content = $this->hide_comments($this->content);
100
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='';
107 continue;
108 }
109
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);
114 continue;
115 }
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
121 $this->scripts[] = $path;
122 } else {
123 $origTag = $tag;
124 $newTag = $tag;
125
126 // non-mergeable script (excluded or dynamic or external)
127 if (is_array($excludeJS)) {
128 // 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);
132 }
133 }
134 }
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
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 }
150
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;
156 } else {
157 $this->move['first'][] = $newTag;
158 }
159 } 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 = '';
164 }
165 // and forget about the $tag (not to be touched any more)
166 $tag = '';
167 }
168 }
169 } else {
170 // Inline script
171 if ($this->isremovable($tag,$this->jsremovables)) {
172 $this->content = str_replace($tag,'',$this->content);
173 continue;
174 }
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;
183 } 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;
189 } else {
190 $this->move['first'][] = $tag;
191 }
192 } else {
193 //We shouldn't touch this
194 $tag = '';
195 }
196 }
197 // re-hide comments to be able to do the removal based on tag from $this->content
198 $tag = $this->hide_comments($tag);
199 }
200
201 //Remove the original script tag
202 $this->content = str_replace($tag,'',$this->content);
203 }
204
205 return true;
206 }
207
208 // No script files, great ;-)
209 return false;
210 }
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);
218 $script = rtrim( $script, ";\n\t\r" ) . ';';
219 //Add try-catch?
220 if($this->trycatch) {
221 $script = 'try{'.$script.'}catch(e){}';
222 }
223 $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;
227 }
228 $this->jscode .= "\n" . $script;
229 } 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){}';
240 }
241 $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%%*/";
247 }
248 $this->jscode .= "\n".$scriptsrc;
249 }/*else{
250 //Couldn't read JS. Maybe getpath isn't working?
251 }*/
252 }
253 }
254
255 //Check for already-minified code
256 $this->md5hash = md5($this->jscode);
257 $ccheck = new autoptimizeCache($this->md5hash,'js');
258 if($ccheck->check()) {
259 $this->jscode = $ccheck->retrieve();
260 return true;
261 }
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)) {
270 $this->jscode = $tmp_jscode;
271 unset($tmp_jscode);
272 }
273 $this->jscode = $this->inject_minified($this->jscode);
274 $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
275 return true;
276 } else {
277 $this->jscode = $this->inject_minified($this->jscode);
278 return false;
279 }
280 } else {
281 $this->jscode = $this->inject_minified($this->jscode);
282 return false;
283 }
284 }
285 $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
286 return true;
287 }
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');
295 }
296 $this->url = AUTOPTIMIZE_CACHE_URL.$cache->getname();
297 $this->url = $this->url_replace_cdn($this->url);
298 }
299
300 // Returns the content
301 public function getcontent() {
302 // Restore the full content
303 if(!empty($this->restofcontent)) {
304 $this->content .= $this->restofcontent;
305 $this->restofcontent = '';
306 }
307
308 // Add the scripts taking forcehead/ deferred (default) into account
309 if($this->forcehead == true) {
310 $replaceTag=array("</head>","before");
311 $defer="";
312 } else {
313 $replaceTag=array("</body>","before");
314 $defer="defer ";
315 }
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
321 $bodyreplacement = implode('',$this->move['first']);
322 $bodyreplacement .= $bodyreplacementpayload;
323 $bodyreplacement .= implode('',$this->move['last']);
324
325 $replaceTag = apply_filters( 'autoptimize_filter_js_replacetag', $replaceTag );
326
327 if (strlen($this->jscode)>0) {
328 $this->inject_in_html($bodyreplacement,$replaceTag);
329 }
330
331 // restore comments
332 $this->content = $this->restore_comments($this->content);
333
334 // Restore IE hacks
335 $this->content = $this->restore_iehacks($this->content);
336
337 // Restore noptimize
338 $this->content = $this->restore_noptimize($this->content);
339
340 // Return the modified HTML
341 return $this->content;
342 }
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) {
351 return true;
352 }
353 }
354 // no match with whitelist
355 return false;
356 } else {
357 foreach($this->domove as $match) {
358 if(strpos($tag,$match)!==false) {
359 // Matched something
360 return false;
361 }
362 }
363
364 if ($this->movetolast($tag)) {
365 return false;
366 }
367
368 foreach($this->dontmove as $match) {
369 if(strpos($tag,$match)!==false) {
370 //Matched something
371 return false;
372 }
373 }
374
375 // If we're here it's safe to merge
376 return true;
377 }
378 }
379
380 // Checks againstt the blacklist
381 private function ismovable($tag) {
382 if ($this->include_inline !== true || apply_filters('autoptimize_filter_js_unmovable',true)) {
383 return false;
384 }
385
386 foreach($this->domove as $match) {
387 if(strpos($tag,$match)!==false) {
388 // Matched something
389 return true;
390 }
391 }
392
393 if ($this->movetolast($tag)) {
394 return true;
395 }
396
397 foreach($this->dontmove as $match) {
398 if(strpos($tag,$match)!==false) {
399 // Matched something
400 return false;
401 }
402 }
403
404 // If we're here it's safe to move
405 return true;
406 }
407
408 private function movetolast($tag) {
409 foreach($this->domovelast as $match) {
410 if(strpos($tag,$match)!==false) {
411 // Matched, return true
412 return true;
413 }
414 }
415
416 // Should be in 'first'
417 return false;
418 }
419
420 /**
421 * Determines wheter a <script> $tag should be aggregated or not.
422 *
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
431 * @return bool
432 *
433 * original function by https://github.com/zytzagoo/ on his AO fork, thanks Tomas!
434 */
435 public function should_aggregate($tag) {
436 preg_match('#<(script[^>]*)>#i',$tag,$scripttag);
437 if ( strpos($scripttag[1], 'type')===false ) {
438 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 }
444 }
445
446 /**
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
454 * @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
460 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 }
468 }
469 }
470