Completed
Push — master ( 9a01d9...c990d6 )
by frank
08:32
created

classes/autoptimizeBase.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4
abstract class autoptimizeBase {
5
    protected $content = '';
6
    protected $tagWarning = false;
7
    
8
    public function __construct($content) {
9
        $this->content = $content;
10
    }
11
    
12
    //Reads the page and collects tags
13
    abstract public function read($justhead);
14
    
15
    //Joins and optimizes collected things
16
    abstract public function minify();
17
    
18
    //Caches the things
19
    abstract public function cache();
20
    
21
    //Returns the content
22
    abstract public function getcontent();
23
    
24
    //Converts an URL to a full path
25
    protected function getpath($url) {
26
        $url=apply_filters( 'autoptimize_filter_cssjs_alter_url', $url);
27
        
28
        if (strpos($url,'%')!==false) {
29
            $url=urldecode($url);
30
        }
31
32
        $siteHost=parse_url(AUTOPTIMIZE_WP_SITE_URL,PHP_URL_HOST);
33
        $contentHost=parse_url(AUTOPTIMIZE_WP_ROOT_URL,PHP_URL_HOST);
34
        
35
        // normalize
36
        if (strpos($url,'//')===0) {
37
            if (is_ssl()) {
38
                $url = "https:".$url;
39
            } else {
40
                $url = "http:".$url;
41
            }
42
        } else if ((strpos($url,'//')===false) && (strpos($url,$siteHost)===false)) {
43
            if (AUTOPTIMIZE_WP_SITE_URL === $siteHost) {
44
                $url = AUTOPTIMIZE_WP_SITE_URL.$url;
45
            } else {
46
                $subdir_levels=substr_count(preg_replace("/https?:\/\//","",AUTOPTIMIZE_WP_SITE_URL),"/");
47
                $url = AUTOPTIMIZE_WP_SITE_URL.str_repeat("/..",$subdir_levels).$url;
48
            }
49
        }
50
        
51
        if ($siteHost !== $contentHost) {
52
            $url=str_replace(AUTOPTIMIZE_WP_CONTENT_URL,AUTOPTIMIZE_WP_SITE_URL.AUTOPTIMIZE_WP_CONTENT_NAME,$url);
53
        }
54
55
        // first check; hostname wp site should be hostname of url
56
        $thisHost=@parse_url($url,PHP_URL_HOST);
57
        if ($thisHost !== $siteHost) {
58
            /* 
59
            * first try to get all domains from WPML (if available)
60
            * then explicitely declare $this->cdn_url as OK as well
61
            * then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames
62
            * each item in that array will be considered part of the same WP multisite installation
63
            */
64
            $multidomains = array();
65
            
66
            $multidomainsWPML = apply_filters('wpml_setting', array(), 'language_domains');
67
            if (!empty($multidomainsWPML)) {
68
                $multidomains = array_map(array($this,"ao_getDomain"),$multidomainsWPML);
69
            }
70
            
71
            if (!empty($this->cdn_url)) {
72
                $multidomains[]=parse_url($this->cdn_url,PHP_URL_HOST);
73
            }
74
            
75
            $multidomains = apply_filters('autoptimize_filter_cssjs_multidomain', $multidomains);
76
            
77
            if (!empty($multidomains)) {
78
                if (in_array($thisHost,$multidomains)) {
79
                    $url=str_replace($thisHost, parse_url(AUTOPTIMIZE_WP_SITE_URL,PHP_URL_HOST), $url);
80
                } else {
81
                    return false;
82
                }
83
            } else {
84
                return false;
85
            }
86
        }
87
        
88
        // try to remove "wp root url" from url while not minding http<>https
89
        $tmp_ao_root = preg_replace('/https?:/','',AUTOPTIMIZE_WP_ROOT_URL);
90
        if ($siteHost !== $contentHost) {
91
            // as we replaced the content-domain with the site-domain, we should match against that 
92
            $tmp_ao_root = preg_replace('/https?:/','',AUTOPTIMIZE_WP_SITE_URL);
93
        }
94
        $tmp_url = preg_replace('/https?:/','',$url);
95
        $path = str_replace($tmp_ao_root,'',$tmp_url);
96
        
97
        // if path starts with :// or //, this is not a URL in the WP context and we have to assume we can't aggregate
98
        if (preg_match('#^:?//#',$path)) {
99
            /** External script/css (adsense, etc) */
100
            return false;
101
        }
102
103
        // prepend with WP_ROOT_DIR to have full path to file
104
        $path = str_replace('//','/',WP_ROOT_DIR.$path);
105
        
106
        // final check: does file exist and is it readable
107
        if (file_exists($path) && is_file($path) && is_readable($path)) {
108
            return $path;
109
        } else {
110
            return false;
111
        }
112
    }
113
114
    // needed for WPML-filter
115
    protected function ao_getDomain($in) {
116
        // make sure the url starts with something vaguely resembling a protocol
117
        if ((strpos($in,"http")!==0) && (strpos($in,"//")!==0)) {
118
            $in="http://".$in;
119
        }
120
        
121
        // do the actual parse_url
122
        $out = parse_url($in,PHP_URL_HOST);
123
        
124
        // fallback if parse_url does not understand the url is in fact a url
125
        if (empty($out)) $out=$in;
126
        
127
        return $out;
128
    }
129
130
131
    // logger
132
    protected function ao_logger($logmsg,$appendHTML=true) {
133
        if ($appendHTML) {
134
            $logmsg="<!--noptimize--><!-- ".$logmsg." --><!--/noptimize-->";
135
            $this->content.=$logmsg;
136
        } else {
137
            error_log("Autoptimize: ".$logmsg);
138
        }
139
    }
140
141
    // hide everything between noptimize-comment tags
142
    protected function hide_noptimize($noptimize_in) {
143
        if ( preg_match( '/<!--\s?noptimize\s?-->/', $noptimize_in ) ) { 
144
            $noptimize_out = preg_replace_callback(
145
                '#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is',
146
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
147
                    '$matches',
148
                    'return "%%NOPTIMIZE".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%NOPTIMIZE%%";'
149
                ),
150
                $noptimize_in
151
            );
152
        } else {
153
            $noptimize_out = $noptimize_in;
154
        }
155
        return $noptimize_out;
156
    }
157
    
158
    // unhide noptimize-tags
159 View Code Duplication
    protected function restore_noptimize($noptimize_in) {
160
        if ( strpos( $noptimize_in, '%%NOPTIMIZE%%' ) !== false ) { 
161
            $noptimize_out = preg_replace_callback(
162
                '#%%NOPTIMIZE'.AUTOPTIMIZE_HASH.'%%(.*?)%%NOPTIMIZE%%#is',
163
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
164
                    '$matches',
165
                    'return base64_decode($matches[1]);'
166
                ),
167
                $noptimize_in
168
            );
169
        } else {
170
            $noptimize_out = $noptimize_in;
171
        }
172
        return $noptimize_out;
173
    }
174
175 View Code Duplication
    protected function hide_iehacks($iehacks_in) {
176
        if ( strpos( $iehacks_in, '<!--[if' ) !== false ) { 
177
            $iehacks_out = preg_replace_callback(
178
                '#<!--\[if.*?\[endif\]-->#is',
179
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
180
                    '$matches',
181
                    'return "%%IEHACK".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%IEHACK%%";'
182
                ),
183
                $iehacks_in
184
            );
185
        } else {
186
            $iehacks_out = $iehacks_in;
187
        }
188
        return $iehacks_out;
189
    }
190
191 View Code Duplication
    protected function restore_iehacks($iehacks_in) {
192
        if ( strpos( $iehacks_in, '%%IEHACK%%' ) !== false ) { 
193
            $iehacks_out = preg_replace_callback(
194
                '#%%IEHACK'.AUTOPTIMIZE_HASH.'%%(.*?)%%IEHACK%%#is',
195
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
196
                    '$matches',
197
                    'return base64_decode($matches[1]);'
198
                ),
199
                $iehacks_in
200
            );
201
        } else {
202
            $iehacks_out=$iehacks_in;
203
        }
204
        return $iehacks_out;
205
    }
206
207 View Code Duplication
    protected function hide_comments($comments_in) {
208
        if ( strpos( $comments_in, '<!--' ) !== false ) {
209
            $comments_out = preg_replace_callback(
210
                '#<!--.*?-->#is',
211
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
212
                    '$matches',
213
                    'return "%%COMMENTS".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%COMMENTS%%";'
214
                ),
215
                $comments_in
216
            );
217
        } else {
218
            $comments_out = $comments_in;
219
        }
220
        return $comments_out;
221
    }
222
223 View Code Duplication
    protected function restore_comments($comments_in) {
224
        if ( strpos( $comments_in, '%%COMMENTS%%' ) !== false ) {
225
            $comments_out = preg_replace_callback(
226
                '#%%COMMENTS'.AUTOPTIMIZE_HASH.'%%(.*?)%%COMMENTS%%#is',
227
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
228
                    '$matches',
229
                    'return base64_decode($matches[1]);'
230
                ),
231
                $comments_in
232
            );
233
        } else {
234
            $comments_out=$comments_in;
235
        }
236
        return $comments_out;
237
    }
238
239
    protected function url_replace_cdn( $url ) {
240
        // API filter to change base CDN URL
241
        $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $this->cdn_url );
242
243
        if ( !empty($cdn_url) )  {
244
            // prepend domain-less absolute URL's
245
            if ( ( substr( $url, 0, 1 ) === '/' ) && ( substr( $url, 1, 1 ) !== '/' ) ) {
246
                $url = rtrim( $cdn_url, '/' ) . $url;
247
            } else {
248
                // get wordpress base URL
249
                $WPSiteBreakdown = parse_url( AUTOPTIMIZE_WP_SITE_URL );
250
                $WPBaseUrl       = $WPSiteBreakdown['scheme'] . '://' . $WPSiteBreakdown['host'];
251
                if ( ! empty( $WPSiteBreakdown['port'] ) ) {
252
                    $WPBaseUrl .= ":" . $WPSiteBreakdown['port'];
253
                }
254
                // replace full url's with scheme
255
                $tmp_url = str_replace( $WPBaseUrl, rtrim( $cdn_url, '/' ), $url );
256
                if ( $tmp_url === $url ) {
257
                    // last attempt; replace scheme-less URL's
258
                    $url = str_replace( preg_replace( '/https?:/', '', $WPBaseUrl ), rtrim( $cdn_url, '/' ), $url );
259
                } else {
260
                    $url = $tmp_url;
261
                }
262
            }
263
        }
264
265
        // allow API filter to alter URL after CDN replacement
266
        $url = apply_filters( 'autoptimize_filter_base_replace_cdn', $url );
267
        return $url;
268
    }
269
270
    protected function inject_in_html($payload,$replaceTag) {
271
        if (strpos($this->content,$replaceTag[0])!== false) {
272
            if ($replaceTag[1]==="after") {
273
                $replaceBlock=$replaceTag[0].$payload;
274
            } else if ($replaceTag[1]==="replace"){
275
                $replaceBlock=$payload;
276
            } else {
277
                $replaceBlock=$payload.$replaceTag[0];
278
            }
279
            $this->content = substr_replace($this->content,$replaceBlock,strpos($this->content,$replaceTag[0]),strlen($replaceTag[0]));
280
        } else {
281
            $this->content .= $payload;
282
            if (!$this->tagWarning) {
283
                $this->content .= "<!--noptimize--><!-- Autoptimize found a problem with the HTML in your Theme, tag \"".str_replace(array("<",">"),"",$replaceTag[0])."\" missing --><!--/noptimize-->";
284
                $this->tagWarning=true;
285
            }
286
        }
287
    }
288
    
289
    protected function isremovable($tag, $removables) {
290
        foreach ($removables as $match) {
291
            if (strpos($tag,$match)!==false) {
292
                return true;
293
            }
294
        }
295
        return false;
296
    }
297
    
298
    // inject already minified code in optimized JS/CSS
299 View Code Duplication
    protected function inject_minified($in) {
300
        if ( strpos( $in, '%%INJECTLATER%%' ) !== false ) {
301
            $out = preg_replace_callback(
302
                '#\/\*\!%%INJECTLATER'.AUTOPTIMIZE_HASH.'%%(.*?)%%INJECTLATER%%\*\/#is',
303
                create_function(
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
304
                    '$matches',
305
                    '$filepath=base64_decode(strtok($matches[1],"|"));
306
                    $filecontent=file_get_contents($filepath);
307
                    
308
                    // remove BOM
309
                    $filecontent = preg_replace("#\x{EF}\x{BB}\x{BF}#","",$filecontent);
310
311
                    // remove comments and blank lines
312
                    if (substr($filepath,-3,3)===".js") {
313
                        $filecontent=preg_replace("#^\s*\/\/.*$#Um","",$filecontent);
314
                    }
315
316
                    $filecontent=preg_replace("#^\s*\/\*[^!].*\*\/\s?#Um","",$filecontent);
317
                    $filecontent=preg_replace("#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $filecontent);
318
319
                    // differentiate between JS, CSS and other files
320
                    if (substr($filepath,-3,3)===".js") {
321
                        if ((substr($filecontent,-1,1)!==";")&&(substr($filecontent,-1,1)!=="}")) {
322
                            $filecontent.=";";
323
                        }
324
325
                        if (get_option("autoptimize_js_trycatch")==="on") {
326
                            $filecontent="try{".$filecontent."}catch(e){}";
327
                        }
328
                    } else if ((substr($filepath,-4,4)===".css")) {
329
                        $filecontent=autoptimizeStyles::fixurls($filepath,$filecontent);
330
                    } else {
331
                        $filecontent="";
332
                    }
333
334
                    // return 
335
                    return "\n".$filecontent;'
336
                ),
337
                $in
338
            );
339
        } else {
340
            $out = $in;
341
        }
342
        return $out;
343
    }
344
    
345
    protected function minify_single($pathIn) {
346
		// determine JS or CSS and set var (also mimetype), return false if neither
347
		if ( $this->str_ends_in($pathIn,".js") === true ) {
348
			$codeType="js";
349
			$codeMime="text/javascript";
350
		} else if ( $this->str_ends_in($pathIn,".css") === true ) {
351
			$codeType="css";
352
			$codeMime="text/css";			
353
		} else {
354
			return false;
355
		}
356
		
357
		// if min.js or min.css return false
358
		if (( $this->str_ends_in($pathIn,"-min.".$codeType) === true ) || ( $this->str_ends_in($pathIn,".min.".$codeType) === true ) || ( $this->str_ends_in($pathIn,"js/jquery/jquery.js") === true ) ) {
359
			return false;
360
		}
361
		
362
		// read file, return false if empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
363
		$_toMinify = file_get_contents($pathIn);
364
		if ( empty($_toMinify) ) return false;
365
		
366
		// check cache
367
		$_md5hash = "single_".md5($_toMinify);
368
		$_cache = new autoptimizeCache($_md5hash,$codeType);
369
		if ($_cache->check() ) {
370
			$_CachedMinifiedUrl = AUTOPTIMIZE_CACHE_URL.$_cache->getname();
371
		} else {
372
			// if not in cache first minify
373
			$_Minified = $_toMinify;
374
			if ($codeType === "js") {
375
				if (class_exists('JSMin') && apply_filters( 'autoptimize_js_do_minify' , true)) {
376
					if (@is_callable(array("JSMin","minify"))) {
377
						$tmp_code = trim(JSMin::minify($_toMinify));
378
					}
379
				}
380 View Code Duplication
			} else if ($codeType === "css") {
381
                if (class_exists('Minify_CSS_Compressor')) {
382
					$tmp_code = trim(Minify_CSS_Compressor::process($_toMinify));
383
                } else if(class_exists('CSSmin')) {
384
                    $cssmin = new CSSmin();
385
                    if (method_exists($cssmin,"run")) {
386
                        $tmp_code = trim($cssmin->run($_toMinify));
387
                    } elseif (@is_callable(array($cssmin,"minify"))) {
388
                        $tmp_code = trim(CssMin::minify($_toMinify));
389
                    }
390
                }
391
			}
392
			if (!empty($tmp_code)) {
393
				$_Minified = $tmp_code;
394
				unset($tmp_code);
395
			}
396
			// and then cache
397
			$_cache->cache($_Minified,$codeMime);
398
			$_CachedMinifiedUrl = AUTOPTIMIZE_CACHE_URL.$_cache->getname();
399
		}
400
		unset($_cache);
401
	
402
		// if CDN, then CDN
403
		$_CachedMinifiedUrl = $this->url_replace_cdn($_CachedMinifiedUrl);									
404
405
		return $_CachedMinifiedUrl;
406
	}
407
	
408
	protected function str_ends_in($haystack,$needle) {
409
		$needleLength = strlen($needle);
410
		$haystackLength = strlen($haystack);
411
		$lastPos=strrpos($haystack,$needle);
412
		if ($lastPos === $haystackLength - $needleLength) {
413
			return true;
414
		} else {
415
			return false;
416
		}
417
	}
418
}
419