PluginProbe
Autoptimize / 2.1.2
Autoptimize v2.1.2
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.1.2, at classes/autoptimizeScripts.php

412 lines 16.9 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', "" );
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', '');
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 );
62 if ($excludeJS!=="") {
63 $exclJSArr = array_filter(array_map('trim',explode(",",$excludeJS)));
64 $this->dontmove = array_merge($exclJSArr,$this->dontmove);
65 }
66
67 //Should we add try-catch?
68 if($options['trycatch'] == true)
69 $this->trycatch = true;
70
71 // force js in head?
72 if($options['forcehead'] == true) {
73 $this->forcehead = true;
74 } else {
75 $this->forcehead = false;
76 }
77 $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead );
78
79 // get cdn url
80 $this->cdn_url = $options['cdn_url'];
81
82 // noptimize me
83 $this->content = $this->hide_noptimize($this->content);
84
85 // Save IE hacks
86 $this->content = $this->hide_iehacks($this->content);
87
88 // comments
89 $this->content = $this->hide_comments($this->content);
90
91 //Get script files
92 if(preg_match_all('#<script.*</script>#Usmi',$this->content,$matches)) {
93 foreach($matches[0] as $tag) {
94 // only consider aggregation whitelisted in should_aggregate-function
95 if( !$this->should_aggregate($tag) ) {
96 $tag='';
97 continue;
98 }
99 if(preg_match('#<script[^>]*src=("|\')([^>]*)("|\')#Usmi',$tag,$source)) {
100 if ($this->isremovable($tag,$this->jsremovables)) {
101 $this->content = str_replace($tag,'',$this->content);
102 continue;
103 }
104
105 // External script
106 $url = current(explode('?',$source[2],2));
107 $path = $this->getpath($url);
108 if($path !== false && preg_match('#\.js$#',$path)) {
109 //Inline
110 if($this->ismergeable($tag)) {
111 //We can merge it
112 $this->scripts[] = $path;
113 } else {
114 //No merge, but maybe we can move it
115 if($this->ismovable($tag)) {
116 //Yeah, move it
117 if($this->movetolast($tag)) {
118 $this->move['last'][] = $tag;
119 } else {
120 $this->move['first'][] = $tag;
121 }
122 } else {
123 //We shouldn't touch this
124 $tag = '';
125 }
126 }
127 } else {
128 //External script (example: google analytics)
129 //OR Script is dynamic (.php etc)
130 if($this->ismovable($tag)) {
131 if($this->movetolast($tag)) {
132 $this->move['last'][] = $tag;
133 } else {
134 $this->move['first'][] = $tag;
135 }
136 } else {
137 //We shouldn't touch this
138 $tag = '';
139 }
140 }
141 } else {
142 // Inline script
143 if ($this->isremovable($tag,$this->jsremovables)) {
144 $this->content = str_replace($tag,'',$this->content);
145 continue;
146 }
147
148 // unhide comments, as javascript may be wrapped in comment-tags for old times' sake
149 $tag = $this->restore_comments($tag);
150 if($this->ismergeable($tag) && ( $this->include_inline )) {
151 preg_match('#<script.*>(.*)</script>#Usmi',$tag,$code);
152 $code = preg_replace('#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm','$1',$code[1]);
153 $code = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/','',$code);
154 $this->scripts[] = 'INLINE;'.$code;
155 } else {
156 // Can we move this?
157 if($this->ismovable($tag)) {
158 if($this->movetolast($tag)) {
159 $this->move['last'][] = $tag;
160 } else {
161 $this->move['first'][] = $tag;
162 }
163 } else {
164 //We shouldn't touch this
165 $tag = '';
166 }
167 }
168 // re-hide comments to be able to do the removal based on tag from $this->content
169 $tag = $this->hide_comments($tag);
170 }
171
172 //Remove the original script tag
173 $this->content = str_replace($tag,'',$this->content);
174 }
175
176 return true;
177 }
178
179 // No script files, great ;-)
180 return false;
181 }
182
183 //Joins and optimizes JS
184 public function minify() {
185 foreach($this->scripts as $script) {
186 if(preg_match('#^INLINE;#',$script)) {
187 //Inline script
188 $script = preg_replace('#^INLINE;#','',$script);
189 $script = rtrim( $script, ";\n\t\r" ) . ';';
190 //Add try-catch?
191 if($this->trycatch) {
192 $script = 'try{'.$script.'}catch(e){}';
193 }
194 $tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, "" );
195 if ( has_filter('autoptimize_js_individual_script') && !empty($tmpscript) ) {
196 $script=$tmpscript;
197 $this->alreadyminified=true;
198 }
199 $this->jscode .= "\n" . $script;
200 } else {
201 //External script
202 if($script !== false && file_exists($script) && is_readable($script)) {
203 $scriptsrc = file_get_contents($script);
204 $scriptsrc = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$scriptsrc);
205 $scriptsrc = rtrim($scriptsrc,";\n\t\r").';';
206 //Add try-catch?
207 if($this->trycatch) {
208 $scriptsrc = 'try{'.$scriptsrc.'}catch(e){}';
209 }
210 $tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
211 if ( has_filter('autoptimize_js_individual_script') && !empty($tmpscriptsrc) ) {
212 $scriptsrc=$tmpscriptsrc;
213 $this->alreadyminified=true;
214 } else if ((( strpos($script,"min.js") !== false ) || ( strpos($script,"wp-includes/js/jquery/jquery.js") !== false )) && ( $this->inject_min_late === true )) {
215 $scriptsrc="%%INJECTLATER".AUTOPTIMIZE_HASH."%%".base64_encode($script)."|".md5($scriptsrc)."%%INJECTLATER%%";
216 }
217 $this->jscode .= "\n".$scriptsrc;
218 }/*else{
219 //Couldn't read JS. Maybe getpath isn't working?
220 }*/
221 }
222 }
223
224 //Check for already-minified code
225 $this->md5hash = md5($this->jscode);
226 $ccheck = new autoptimizeCache($this->md5hash,'js');
227 if($ccheck->check()) {
228 $this->jscode = $ccheck->retrieve();
229 return true;
230 }
231 unset($ccheck);
232
233 //$this->jscode has all the uncompressed code now.
234 if ($this->alreadyminified!==true) {
235 if (class_exists('JSMin') && apply_filters( 'autoptimize_js_do_minify' , true)) {
236 if (@is_callable(array("JSMin","minify"))) {
237 $tmp_jscode = trim(JSMin::minify($this->jscode));
238 if (!empty($tmp_jscode)) {
239 $this->jscode = $tmp_jscode;
240 unset($tmp_jscode);
241 }
242 $this->jscode = $this->inject_minified($this->jscode);
243 $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
244 return true;
245 } else {
246 $this->jscode = $this->inject_minified($this->jscode);
247 return false;
248 }
249 } else {
250 $this->jscode = $this->inject_minified($this->jscode);
251 return false;
252 }
253 }
254 return true;
255 }
256
257 //Caches the JS in uncompressed, deflated and gzipped form.
258 public function cache() {
259 $cache = new autoptimizeCache($this->md5hash,'js');
260 if(!$cache->check()) {
261 //Cache our code
262 $cache->cache($this->jscode,'text/javascript');
263 }
264 $this->url = AUTOPTIMIZE_CACHE_URL.$cache->getname();
265 $this->url = $this->url_replace_cdn($this->url);
266 }
267
268 // Returns the content
269 public function getcontent() {
270 // Restore the full content
271 if(!empty($this->restofcontent)) {
272 $this->content .= $this->restofcontent;
273 $this->restofcontent = '';
274 }
275
276 // Add the scripts taking forcehead/ deferred (default) into account
277 if($this->forcehead == true) {
278 $replaceTag=array("</head>","before");
279 $defer="";
280 } else {
281 $replaceTag=array("</body>","before");
282 $defer="defer ";
283 }
284
285 $defer = apply_filters( 'autoptimize_filter_js_defer', $defer );
286 $bodyreplacementpayload = '<script type="text/javascript" '.$defer.'src="'.$this->url.'"></script>';
287 $bodyreplacementpayload = apply_filters('autoptimize_filter_js_bodyreplacementpayload',$bodyreplacementpayload);
288
289 $bodyreplacement = implode('',$this->move['first']);
290 $bodyreplacement .= $bodyreplacementpayload;
291 $bodyreplacement .= implode('',$this->move['last']);
292
293 $replaceTag = apply_filters( 'autoptimize_filter_js_replacetag', $replaceTag );
294
295 if (strlen($this->jscode)>0) {
296 $this->inject_in_html($bodyreplacement,$replaceTag);
297 }
298
299 // restore comments
300 $this->content = $this->restore_comments($this->content);
301
302 // Restore IE hacks
303 $this->content = $this->restore_iehacks($this->content);
304
305 // Restore noptimize
306 $this->content = $this->restore_noptimize($this->content);
307
308 // Return the modified HTML
309 return $this->content;
310 }
311
312 // Checks against the white- and blacklists
313 private function ismergeable($tag) {
314 if (!empty($this->whitelist)) {
315 foreach ($this->whitelist as $match) {
316 if(strpos($tag,$match)!==false) {
317 return true;
318 }
319 }
320 // no match with whitelist
321 return false;
322 } else {
323 foreach($this->domove as $match) {
324 if(strpos($tag,$match)!==false) {
325 // Matched something
326 return false;
327 }
328 }
329
330 if ($this->movetolast($tag)) {
331 return false;
332 }
333
334 foreach($this->dontmove as $match) {
335 if(strpos($tag,$match)!==false) {
336 //Matched something
337 return false;
338 }
339 }
340
341 // If we're here it's safe to merge
342 return true;
343 }
344 }
345
346 // Checks againstt the blacklist
347 private function ismovable($tag) {
348 if ($this->include_inline !== true || apply_filters('autoptimize_filter_js_unmovable',true)) {
349 return false;
350 }
351
352 foreach($this->domove as $match) {
353 if(strpos($tag,$match)!==false) {
354 // Matched something
355 return true;
356 }
357 }
358
359 if ($this->movetolast($tag)) {
360 return true;
361 }
362
363 foreach($this->dontmove as $match) {
364 if(strpos($tag,$match)!==false) {
365 // Matched something
366 return false;
367 }
368 }
369
370 // If we're here it's safe to move
371 return true;
372 }
373
374 private function movetolast($tag) {
375 foreach($this->domovelast as $match) {
376 if(strpos($tag,$match)!==false) {
377 // Matched, return true
378 return true;
379 }
380 }
381
382 // Should be in 'first'
383 return false;
384 }
385
386 /**
387 * Determines wheter a <script> $tag should be aggregated or not.
388 *
389 * We consider these as "aggregation-safe" currently:
390 * - script tags without a `type` attribute
391 * - script tags with an explicit `type` of `text/javascript`, 'text/ecmascript',
392 * 'application/javascript' or 'application/ecmascript'
393 *
394 * Everything else should return false.
395 *
396 * @param string $tag
397 * @return bool
398 *
399 * original function by https://github.com/zytzagoo/ on his AO fork, thanks Tomas!
400 */
401 public function should_aggregate($tag) {
402 preg_match('#<(script[^>]*)>#i',$tag,$scripttag);
403 if ( strpos($scripttag[1], 'type')===false ) {
404 return true;
405 } else if ( preg_match('/type\s*=\s*["\']?(?:text|application)\/(?:javascript|ecmascript)["\']?/i', $scripttag[1]) ) {
406 return true;
407 } else {
408 return false;
409 }
410 }
411 }
412