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
← All changes | classes/autoptimizeBase.php +245 -620 2.5.02.1.2 View file →
@@ -1,128 +1,78 @@
1 1 <?php
2 -/**
3 - * Base class other (more-specific) classes inherit from.
4 - */
2 +if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
5 3
6 -if ( ! defined( 'ABSPATH' ) ) {
7 - exit;
8 -}
9 -
10 -abstract class autoptimizeBase
11 -{
12 - /**
13 - * Holds content being processed (html, scripts, styles)
14 - *
15 - * @var string
16 - */
4 +abstract class autoptimizeBase {
17 5 protected $content = '';
18 -
19 - /**
20 - * Controls debug logging.
21 - *
22 - * @var bool
23 - */
24 - public $debug_log = false;
25 -
26 - public function __construct( $content )
27 - {
6 + protected $tagWarning = false;
7 +
8 + public function __construct($content) {
28 9 $this->content = $content;
29 10 }
30 -
31 - /**
32 - * Reads the page and collects tags.
33 - *
34 - * @param array $options Options.
35 - *
36 - * @return bool
37 - */
38 - abstract public function read( $options );
39 -
40 - /**
41 - * Joins and optimizes collected things.
42 - *
43 - * @return bool
44 - */
11 +
12 + //Reads the page and collects tags
13 + abstract public function read($justhead);
14 +
15 + //Joins and optimizes collected things
45 16 abstract public function minify();
46 -
47 - /**
48 - * Caches the things.
49 - *
50 - * @return void
51 - */
17 +
18 + //Caches the things
52 19 abstract public function cache();
53 -
54 - /**
55 - * Returns the content
56 - *
57 - * @return string
58 - */
20 +
21 + //Returns the content
59 22 abstract public function getcontent();
60 -
61 - /**
62 - * Tranfsorms a given URL to a full local filepath if possible.
63 - * Returns local filepath or false.
64 - *
65 - * @param string $url URL to transform.
66 - *
67 - * @return bool|string
68 - */
69 - public function getpath( $url )
70 - {
71 - $url = apply_filters( 'autoptimize_filter_cssjs_alter_url', $url );
72 -
73 - if ( false !== strpos( $url, '%' ) ) {
74 - $url = urldecode( $url );
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);
75 30 }
76 31
77 - $site_host = parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST );
78 - $content_host = parse_url( AUTOPTIMIZE_WP_ROOT_URL, PHP_URL_HOST );
79 -
80 - // Normalizing attempts...
81 - $double_slash_position = strpos( $url, '//' );
82 - if ( 0 === $double_slash_position ) {
83 - if ( is_ssl() ) {
84 - $url = 'https:' . $url;
32 + $siteHost=parse_url(AUTOPTIMIZE_WP_SITE_URL,PHP_URL_HOST);
33 +
34 + // normalize
35 + if (strpos($url,'//')===0) {
36 + if (is_ssl()) {
37 + $url = "https:".$url;
85 38 } else {
86 - $url = 'http:' . $url;
39 + $url = "http:".$url;
87 40 }
88 - } elseif ( ( false === $double_slash_position ) && ( false === strpos( $url, $site_host ) ) ) {
89 - if ( AUTOPTIMIZE_WP_SITE_URL === $site_host ) {
90 - $url = AUTOPTIMIZE_WP_SITE_URL . $url;
41 + } else if ((strpos($url,'//')===false) && (strpos($url,$siteHost)===false)) {
42 + if (AUTOPTIMIZE_WP_SITE_URL === $siteHost) {
43 + $url = AUTOPTIMIZE_WP_SITE_URL.$url;
91 44 } else {
92 - $url = AUTOPTIMIZE_WP_SITE_URL . autoptimizeUtils::path_canonicalize( $url );
45 + $subdir_levels=substr_count(preg_replace("/https?:\/\//","",AUTOPTIMIZE_WP_SITE_URL),"/");
46 + $url = AUTOPTIMIZE_WP_SITE_URL.str_repeat("/..",$subdir_levels).$url;
93 47 }
94 48 }
95 49
96 - if ( $site_host !== $content_host ) {
97 - $url = str_replace( AUTOPTIMIZE_WP_CONTENT_URL, AUTOPTIMIZE_WP_SITE_URL . AUTOPTIMIZE_WP_CONTENT_NAME, $url );
98 - }
99 -
100 - // First check; hostname wp site should be hostname of url!
101 - $url_host = @parse_url( $url, PHP_URL_HOST ); // @codingStandardsIgnoreLine
102 - if ( $url_host !== $site_host ) {
103 - /**
104 - * First try to get all domains from WPML (if available)
105 - * then explicitely declare $this->cdn_url as OK as well
106 - * then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames
107 - * each item in that array will be considered part of the same WP multisite installation
108 - */
50 + // first check; hostname wp site should be hostname of url
51 + $thisHost=@parse_url($url,PHP_URL_HOST);
52 + if ($thisHost !== $siteHost) {
53 + /*
54 + * first try to get all domains from WPML (if available)
55 + * then explicitely declare $this->cdn_url as OK as well
56 + * then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames
57 + * each item in that array will be considered part of the same WP multisite installation
58 + */
109 59 $multidomains = array();
110 -
111 - $multidomains_wpml = apply_filters( 'wpml_setting', array(), 'language_domains' );
112 - if ( ! empty( $multidomains_wpml ) ) {
113 - $multidomains = array_map( array( $this, 'get_url_hostname' ), $multidomains_wpml );
60 +
61 + $multidomainsWPML = apply_filters('wpml_setting', array(), 'language_domains');
62 + if (!empty($multidomainsWPML)) {
63 + $multidomains = array_map(array($this,"ao_getDomain"),$multidomainsWPML);
114 64 }
115 -
116 - if ( ! empty( $this->cdn_url ) ) {
117 - $multidomains[] = parse_url( $this->cdn_url, PHP_URL_HOST );
65 +
66 + if (!empty($this->cdn_url)) {
67 + $multidomains[]=parse_url($this->cdn_url,PHP_URL_HOST);
118 68 }
119 -
120 - $multidomains = apply_filters( 'autoptimize_filter_cssjs_multidomain', $multidomains );
121 -
122 - if ( ! empty( $multidomains ) ) {
123 - if ( in_array( $url_host, $multidomains ) ) {
124 - $url = str_replace( $url_host, $site_host, $url );
69 +
70 + $multidomains = apply_filters('autoptimize_filter_cssjs_multidomain', $multidomains);
71 +
72 + if (!empty($multidomains)) {
73 + if (in_array($thisHost,$multidomains)) {
74 + $url=str_replace($thisHost, parse_url(AUTOPTIMIZE_WP_SITE_URL,PHP_URL_HOST), $url);
125 75 } else {
126 76 return false;
127 77 }
128 78 } else {
@@ -128,576 +78,251 @@
128 78 } else {
129 79 return false;
130 80 }
131 81 }
132 -
133 - // Try to remove "wp root url" from url while not minding http<>https.
134 - $tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_ROOT_URL );
135 -
136 - if ( $site_host !== $content_host ) {
137 - // As we replaced the content-domain with the site-domain, we should match against that.
138 - $tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_SITE_URL );
82 +
83 + // try to remove "wp root url" from url while not minding http<>https
84 + $tmp_ao_root = preg_replace('/https?:/','',AUTOPTIMIZE_WP_ROOT_URL);
85 + $tmp_url = preg_replace('/https?:/','',$url);
86 + $path = str_replace($tmp_ao_root,'',$tmp_url);
87 +
88 + // final check; if path starts with :// or //, this is not a URL in the WP context and we have to assume we can't aggregate
89 + if (preg_match('#^:?//#',$path)) {
90 + /** External script/css (adsense, etc) */
91 + return false;
139 92 }
140 93
141 - $tmp_url = preg_replace( '/https?:/', '', $url );
142 - $path = str_replace( $tmp_ao_root, '', $tmp_url );
94 + $path = str_replace('//','/',WP_ROOT_DIR.$path);
95 + return $path;
96 + }
143 97
144 - // If path starts with :// or //, this is not a URL in the WP context and
145 - // we have to assume we can't aggregate.
146 - if ( preg_match( '#^:?//#', $path ) ) {
147 - // External script/css (adsense, etc).
148 - return false;
98 + // needed for WPML-filter
99 + protected function ao_getDomain($in) {
100 + // make sure the url starts with something vaguely resembling a protocol
101 + if ((strpos($in,"http")!==0) && (strpos($in,"//")!==0)) {
102 + $in="http://".$in;
149 103 }
104 +
105 + // do the actual parse_url
106 + $out = parse_url($in,PHP_URL_HOST);
107 +
108 + // fallback if parse_url does not understand the url is in fact a url
109 + if (empty($out)) $out=$in;
110 +
111 + return $out;
112 + }
150 113
151 - // Prepend with WP_ROOT_DIR to have full path to file.
152 - $path = str_replace( '//', '/', WP_ROOT_DIR . $path );
153 114
154 - // Final check: does file exist and is it readable?
155 - if ( file_exists( $path ) && is_file( $path ) && is_readable( $path ) ) {
156 - return $path;
115 + // logger
116 + protected function ao_logger($logmsg,$appendHTML=true) {
117 + if ($appendHTML) {
118 + $logmsg="<!--noptimize--><!-- ".$logmsg." --><!--/noptimize-->";
119 + $this->content.=$logmsg;
157 120 } else {
158 - return false;
121 + error_log("Autoptimize: ".$logmsg);
159 122 }
160 123 }
161 124
162 - /**
163 - * Returns the hostname part of a given $url if we're able to parse it.
164 - * If not, it returns the original url (prefixed with http:// scheme in case
165 - * it was missing).
166 - * Used as callback for WPML multidomains filter.
167 - *
168 - * @param string $url URL.
169 - *
170 - * @return string
171 - */
172 - protected function get_url_hostname( $url )
173 - {
174 - // Checking that the url starts with something vaguely resembling a protocol.
175 - if ( ( 0 !== strpos( $url, 'http' ) ) && ( 0 !== strpos( $url, '//' ) ) ) {
176 - $url = 'http://' . $url;
125 + // hide everything between noptimize-comment tags
126 + protected function hide_noptimize($noptimize_in) {
127 + if ( preg_match( '/<!--\s?noptimize\s?-->/', $noptimize_in ) ) {
128 + $noptimize_out = preg_replace_callback(
129 + '#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is',
130 + create_function(
131 + '$matches',
132 + 'return "%%NOPTIMIZE".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%NOPTIMIZE%%";'
133 + ),
134 + $noptimize_in
135 + );
136 + } else {
137 + $noptimize_out = $noptimize_in;
177 138 }
178 -
179 - // Grab the hostname.
180 - $hostname = parse_url( $url, PHP_URL_HOST );
181 -
182 - // Fallback when parse_url() fails.
183 - if ( empty( $hostname ) ) {
184 - $hostname = $url;
139 + return $noptimize_out;
140 + }
141 +
142 + // unhide noptimize-tags
143 + protected function restore_noptimize($noptimize_in) {
144 + if ( strpos( $noptimize_in, '%%NOPTIMIZE%%' ) !== false ) {
145 + $noptimize_out = preg_replace_callback(
146 + '#%%NOPTIMIZE'.AUTOPTIMIZE_HASH.'%%(.*?)%%NOPTIMIZE%%#is',
147 + create_function(
148 + '$matches',
149 + 'return base64_decode($matches[1]);'
150 + ),
151 + $noptimize_in
152 + );
153 + } else {
154 + $noptimize_out = $noptimize_in;
185 155 }
186 -
187 - return $hostname;
156 + return $noptimize_out;
188 157 }
189 158
190 - /**
191 - * Hides everything between noptimize-comment tags.
192 - *
193 - * @param string $markup Markup to process.
194 - *
195 - * @return string
196 - */
197 - protected function hide_noptimize( $markup )
198 - {
199 - return $this->replace_contents_with_marker_if_exists(
200 - 'NOPTIMIZE',
201 - '/<!--\s?noptimize\s?-->/',
202 - '#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is',
203 - $markup
204 - );
159 + protected function hide_iehacks($iehacks_in) {
160 + if ( strpos( $iehacks_in, '<!--[if' ) !== false ) {
161 + $iehacks_out = preg_replace_callback(
162 + '#<!--\[if.*?\[endif\]-->#is',
163 + create_function(
164 + '$matches',
165 + 'return "%%IEHACK".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%IEHACK%%";'
166 + ),
167 + $iehacks_in
168 + );
169 + } else {
170 + $iehacks_out = $iehacks_in;
171 + }
172 + return $iehacks_out;
205 173 }
206 174
207 - /**
208 - * Unhide noptimize-tags.
209 - *
210 - * @param string $markup Markup to process.
211 - *
212 - * @return string
213 - */
214 - protected function restore_noptimize( $markup )
215 - {
216 - return $this->restore_marked_content( 'NOPTIMIZE', $markup );
175 + protected function restore_iehacks($iehacks_in) {
176 + if ( strpos( $iehacks_in, '%%IEHACK%%' ) !== false ) {
177 + $iehacks_out = preg_replace_callback(
178 + '#%%IEHACK'.AUTOPTIMIZE_HASH.'%%(.*?)%%IEHACK%%#is',
179 + create_function(
180 + '$matches',
181 + 'return base64_decode($matches[1]);'
182 + ),
183 + $iehacks_in
184 + );
185 + } else {
186 + $iehacks_out=$iehacks_in;
187 + }
188 + return $iehacks_out;
217 189 }
218 190
219 - /**
220 - * Hides "iehacks" content.
221 - *
222 - * @param string $markup Markup to process.
223 - *
224 - * @return string
225 - */
226 - protected function hide_iehacks( $markup )
227 - {
228 - return $this->replace_contents_with_marker_if_exists(
229 - 'IEHACK', // Marker name...
230 - '<!--[if', // Invalid regex, will fallback to search using strpos()...
231 - '#<!--\[if.*?\[endif\]-->#is', // Replacement regex...
232 - $markup
233 - );
191 + protected function hide_comments($comments_in) {
192 + if ( strpos( $comments_in, '<!--' ) !== false ) {
193 + $comments_out = preg_replace_callback(
194 + '#<!--.*?-->#is',
195 + create_function(
196 + '$matches',
197 + 'return "%%COMMENTS".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%COMMENTS%%";'
198 + ),
199 + $comments_in
200 + );
201 + } else {
202 + $comments_out = $comments_in;
203 + }
204 + return $comments_out;
234 205 }
235 206
236 - /**
237 - * Restores "hidden" iehacks content.
238 - *
239 - * @param string $markup Markup to process.
240 - *
241 - * @return string
242 - */
243 - protected function restore_iehacks( $markup )
244 - {
245 - return $this->restore_marked_content( 'IEHACK', $markup );
207 + protected function restore_comments($comments_in) {
208 + if ( strpos( $comments_in, '%%COMMENTS%%' ) !== false ) {
209 + $comments_out = preg_replace_callback(
210 + '#%%COMMENTS'.AUTOPTIMIZE_HASH.'%%(.*?)%%COMMENTS%%#is',
211 + create_function(
212 + '$matches',
213 + 'return base64_decode($matches[1]);'
214 + ),
215 + $comments_in
216 + );
217 + } else {
218 + $comments_out=$comments_in;
219 + }
220 + return $comments_out;
246 221 }
247 222
248 - /**
249 - * "Hides" content within HTML comments using a regex-based replacement
250 - * if HTML comment markers are found.
251 - * `<!--example-->` becomes `%%COMMENTS%%ZXhhbXBsZQ==%%COMMENTS%%`
252 - *
253 - * @param string $markup Markup to process.
254 - *
255 - * @return string
256 - */
257 - protected function hide_comments( $markup )
258 - {
259 - return $this->replace_contents_with_marker_if_exists(
260 - 'COMMENTS',
261 - '<!--',
262 - '#<!--.*?-->#is',
263 - $markup
264 - );
265 - }
223 + protected function url_replace_cdn( $url ) {
224 + // API filter to change base CDN URL
225 + $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $this->cdn_url );
266 226
267 - /**
268 - * Restores original HTML comment markers inside a string whose HTML
269 - * comments have been "hidden" by using `hide_comments()`.
270 - *
271 - * @param string $markup Markup to process.
272 - *
273 - * @return string
274 - */
275 - protected function restore_comments( $markup )
276 - {
277 - return $this->restore_marked_content( 'COMMENTS', $markup );
278 - }
279 -
280 - /**
281 - * Replaces the given URL with the CDN-version of it when CDN replacement
282 - * is supposed to be done.
283 - *
284 - * @param string $url URL to process.
285 - *
286 - * @return string
287 - */
288 - public function url_replace_cdn( $url )
289 - {
290 - // For 2.3 back-compat in which cdn-ing appeared to be automatically
291 - // including WP subfolder/subdirectory into account as part of cdn-ing,
292 - // even though it might've caused serious troubles in certain edge-cases.
293 - $cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $this->cdn_url );
294 -
295 - // Allows API/filter to further tweak the cdn url...
296 - $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
297 - if ( ! empty( $cdn_url ) ) {
298 - $this->debug_log( 'before=' . $url );
299 -
300 - // Simple str_replace-based approach fails when $url is protocol-or-host-relative.
301 - $is_protocol_relative = autoptimizeUtils::is_protocol_relative( $url );
302 - $is_host_relative = ( ! $is_protocol_relative && ( '/' === $url{0} ) );
303 - $cdn_url = rtrim( $cdn_url, '/' );
304 -
305 - if ( $is_host_relative ) {
306 - // Prepending host-relative urls with the cdn url.
307 - $url = $cdn_url . $url;
227 + if ( !empty($cdn_url) ) {
228 + // prepend domain-less absolute URL's
229 + if ( ( substr( $url, 0, 1 ) === '/' ) && ( substr( $url, 1, 1 ) !== '/' ) ) {
230 + $url = rtrim( $cdn_url, '/' ) . $url;
308 231 } else {
309 - // Either a protocol-relative or "regular" url, replacing it either way.
310 - if ( $is_protocol_relative ) {
311 - // Massage $site_url so that simple str_replace() still "works" by
312 - // searching for the protocol-relative version of AUTOPTIMIZE_WP_SITE_URL.
313 - $site_url = str_replace( array( 'http:', 'https:' ), '', AUTOPTIMIZE_WP_SITE_URL );
232 + // get wordpress base URL
233 + $WPSiteBreakdown = parse_url( AUTOPTIMIZE_WP_SITE_URL );
234 + $WPBaseUrl = $WPSiteBreakdown['scheme'] . '://' . $WPSiteBreakdown['host'];
235 + if ( ! empty( $WPSiteBreakdown['port'] ) ) {
236 + $WPBaseUrl .= ":" . $WPSiteBreakdown['port'];
237 + }
238 + // replace full url's with scheme
239 + $tmp_url = str_replace( $WPBaseUrl, rtrim( $cdn_url, '/' ), $url );
240 + if ( $tmp_url === $url ) {
241 + // last attempt; replace scheme-less URL's
242 + $url = str_replace( preg_replace( '/https?:/', '', $WPBaseUrl ), rtrim( $cdn_url, '/' ), $url );
314 243 } else {
315 - $site_url = AUTOPTIMIZE_WP_SITE_URL;
244 + $url = $tmp_url;
316 245 }
317 - $this->debug_log( '`' . $site_url . '` -> `' . $cdn_url . '` in `' . $url . '`' );
318 - $url = str_replace( $site_url, $cdn_url, $url );
319 246 }
320 -
321 - $this->debug_log( 'after=' . $url );
322 247 }
323 248
324 - // Allow API filter to take further care of CDN replacement.
249 + // allow API filter to alter URL after CDN replacement
325 250 $url = apply_filters( 'autoptimize_filter_base_replace_cdn', $url );
326 -
327 251 return $url;
328 252 }
329 253
330 - /**
331 - * Injects/replaces the given payload markup into `$this->content`
332 - * at the specified location.
333 - * If the specified tag cannot be found, the payload is appended into
334 - * $this->content along with a warning wrapped inside <!--noptimize--> tags.
335 - *
336 - * @param string $payload Markup to inject.
337 - * @param array $where Array specifying the tag name and method of injection.
338 - * Index 0 is the tag name (i.e., `</body>`).
339 - * Index 1 specifies Ë›'before', 'after' or 'replace'. Defaults to 'before'.
340 - *
341 - * @return void
342 - */
343 - protected function inject_in_html( $payload, $where )
344 - {
345 - $warned = false;
346 - $position = autoptimizeUtils::strpos( $this->content, $where[0] );
347 - if ( false !== $position ) {
348 - // Found the tag, setup content/injection as specified.
349 - if ( 'after' === $where[1] ) {
350 - $content = $where[0] . $payload;
351 - } elseif ( 'replace' === $where[1] ) {
352 - $content = $payload;
254 + protected function inject_in_html($payload,$replaceTag) {
255 + if (strpos($this->content,$replaceTag[0])!== false) {
256 + if ($replaceTag[1]==="after") {
257 + $replaceBlock=$replaceTag[0].$payload;
258 + } else if ($replaceTag[1]==="replace"){
259 + $replaceBlock=$payload;
353 260 } else {
354 - $content = $payload . $where[0];
261 + $replaceBlock=$payload.$replaceTag[0];
355 262 }
356 - // Place where specified.
357 - $this->content = autoptimizeUtils::substr_replace(
358 - $this->content,
359 - $content,
360 - $position,
361 - // Using plain strlen() should be safe here for now, since
362 - // we're not searching for multibyte chars here still...
363 - strlen( $where[0] )
364 - );
263 + $this->content = substr_replace($this->content,$replaceBlock,strpos($this->content,$replaceTag[0]),strlen($replaceTag[0]));
365 264 } else {
366 - // Couldn't find what was specified, just append and add a warning.
367 265 $this->content .= $payload;
368 - if ( ! $warned ) {
369 - $tag_display = str_replace( array( '<', '>' ), '', $where[0] );
370 - $this->content .= '<!--noptimize--><!-- Autoptimize found a problem with the HTML in your Theme, tag `' . $tag_display . '` missing --><!--/noptimize-->';
371 - $warned = true;
266 + if (!$this->tagWarning) {
267 + $this->content .= "<!--noptimize--><!-- Autoptimize found a problem with the HTML in your Theme, tag \"".str_replace(array("<",">"),"",$replaceTag[0])."\" missing --><!--/noptimize-->";
268 + $this->tagWarning=true;
372 269 }
373 270 }
374 271 }
375 -
376 - /**
377 - * Returns true if given `$tag` is found in the list of `$removables`.
378 - *
379 - * @param string $tag Tag to search for.
380 - * @param array $removables List of things considered completely removable.
381 - *
382 - * @return bool
383 - */
384 - protected function isremovable( $tag, $removables )
385 - {
386 - foreach ( $removables as $match ) {
387 - if ( false !== strpos( $tag, $match ) ) {
272 +
273 + protected function isremovable($tag, $removables) {
274 + foreach ($removables as $match) {
275 + if (strpos($tag,$match)!==false) {
388 276 return true;
389 277 }
390 278 }
391 -
392 279 return false;
393 280 }
281 +
282 + // inject already minified code in optimized JS/CSS
283 + protected function inject_minified($in) {
284 + if ( strpos( $in, '%%INJECTLATER%%' ) !== false ) {
285 + $out = preg_replace_callback(
286 + '#%%INJECTLATER'.AUTOPTIMIZE_HASH.'%%(.*?)%%INJECTLATER%%#is',
287 + create_function(
288 + '$matches',
289 + '$filepath=base64_decode(strtok($matches[1],"|"));
290 + $filecontent=file_get_contents($filepath);
394 291
395 - /**
396 - * Callback used in `self::inject_minified()`.
397 - *
398 - * @param array $matches Regex matches.
399 - *
400 - * @return string
401 - */
402 - public function inject_minified_callback( $matches )
403 - {
404 - static $conf = null;
405 - if ( null === $conf ) {
406 - $conf = autoptimizeConfig::instance();
407 - }
292 + // remove BOM
293 + $filecontent = preg_replace("#\x{EF}\x{BB}\x{BF}#","",$filecontent);
408 294
409 - /**
410 - * $matches[1] holds the whole match caught by regex in self::inject_minified(),
411 - * so we take that and split the string on `|`.
412 - * First element is the filepath, second is the md5 hash of contents
413 - * the filepath had when it was being processed.
414 - * If we don't have those, we'll bail out early.
415 - */
416 - $filepath = null;
417 - $filehash = null;
295 + // remove comments and blank lines
296 + if (substr($filepath,-3,3)===".js") {
297 + $filecontent=preg_replace("#^\s*\/\/.*$#Um","",$filecontent);
298 + }
418 299
419 - // Grab the parts we need.
420 - $parts = explode( '|', $matches[1] );
421 - if ( ! empty( $parts ) ) {
422 - $filepath = isset( $parts[0] ) ? base64_decode( $parts[0] ) : null;
423 - $filehash = isset( $parts[1] ) ? $parts[1] : null;
424 - }
300 + $filecontent=preg_replace("#^\s*\/\*[^!].*\*\/\s?#Um","",$filecontent);
301 + $filecontent=preg_replace("#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $filecontent);
425 302
426 - // Bail early if something's not right...
427 - if ( ! $filepath || ! $filehash ) {
428 - return "\n";
429 - }
303 + // specific stuff for JS-files
304 + if (substr($filepath,-3,3)===".js") {
305 + if ((substr($filecontent,-1,1)!==";")&&(substr($filecontent,-1,1)!=="}")) {
306 + $filecontent.=";";
307 + }
430 308
431 - $filecontent = file_get_contents( $filepath );
309 + if (get_option("autoptimize_js_trycatch")==="on") {
310 + $filecontent="try{".$filecontent."}catch(e){}";
311 + }
312 + } else if ((substr($filepath,-4,4)===".css")) {
313 + $filecontent=autoptimizeStyles::fixurls($filepath,$filecontent);
314 + } else {
315 + $filecontent="";
316 + }
432 317
433 - // Some things are differently handled for css/js...
434 - $is_js_file = ( '.js' === substr( $filepath, -3, 3 ) );
435 -
436 - $is_css_file = false;
437 - if ( ! $is_js_file ) {
438 - $is_css_file = ( '.css' === substr( $filepath, -4, 4 ) );
439 - }
440 -
441 - // BOMs being nuked here unconditionally (regardless of where they are)!
442 - $filecontent = preg_replace( "#\x{EF}\x{BB}\x{BF}#", '', $filecontent );
443 -
444 - // Remove comments and blank lines.
445 - if ( $is_js_file ) {
446 - $filecontent = preg_replace( '#^\s*\/\/.*$#Um', '', $filecontent );
447 - }
448 -
449 - // Nuke un-important comments.
450 - $filecontent = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Um', '', $filecontent );
451 -
452 - // Normalize newlines.
453 - $filecontent = preg_replace( '#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#', "\n", $filecontent );
454 -
455 - // JS specifics.
456 - if ( $is_js_file ) {
457 - // Append a semicolon at the end of js files if it's missing.
458 - $last_char = substr( $filecontent, -1, 1 );
459 - if ( ';' !== $last_char && '}' !== $last_char ) {
460 - $filecontent .= ';';
461 - }
462 - // Check if try/catch should be used.
463 - $opt_js_try_catch = $conf->get( 'autoptimize_js_trycatch' );
464 - if ( 'on' === $opt_js_try_catch ) {
465 - // It should, wrap in try/catch.
466 - $filecontent = 'try{' . $filecontent . '}catch(e){}';
467 - }
468 - } elseif ( $is_css_file ) {
469 - $filecontent = autoptimizeStyles::fixurls( $filepath, $filecontent );
470 - } else {
471 - $filecontent = '';
472 - }
473 -
474 - // Return modified (or empty!) code/content.
475 - return "\n" . $filecontent;
476 - }
477 -
478 - /**
479 - * Inject already minified code in optimized JS/CSS.
480 - *
481 - * @param string $in Markup.
482 - *
483 - * @return string
484 - */
485 - protected function inject_minified( $in )
486 - {
487 - $out = $in;
488 - if ( false !== strpos( $in, '%%INJECTLATER%%' ) ) {
489 - $out = preg_replace_callback(
490 - '#\/\*\!%%INJECTLATER' . AUTOPTIMIZE_HASH . '%%(.*?)%%INJECTLATER%%\*\/#is',
491 - array( $this, 'inject_minified_callback' ),
318 + // return
319 + return "\n".$filecontent;'
320 + ),
492 321 $in
493 322 );
323 + } else {
324 + $out = $in;
494 325 }
495 -
496 326 return $out;
497 - }
498 -
499 - /**
500 - * Specialized method to create the INJECTLATER marker.
501 - * These are somewhat "special", in the sense that they're additionally wrapped
502 - * within an "exclamation mark style" comment, so that they're not stripped
503 - * out by minifiers.
504 - * They also currently contain the hash of the file's contents too (unlike other markers).
505 - *
506 - * @param string $filepath Filepath.
507 - * @param string $hash Hash.
508 - *
509 - * @return string
510 - */
511 - public static function build_injectlater_marker( $filepath, $hash )
512 - {
513 - $contents = '/*!' . self::build_marker( 'INJECTLATER', $filepath, $hash ) . '*/';
514 -
515 - return $contents;
516 - }
517 -
518 - /**
519 - * Creates and returns a `%%`-style named marker which holds
520 - * the base64 encoded `$data`.
521 - * If `$hash` is provided, it's appended to the base64 encoded string
522 - * using `|` as the separator (in order to support building the
523 - * somewhat special/different INJECTLATER marker).
524 - *
525 - * @param string $name Marker name.
526 - * @param string $data Marker data which will be base64-encoded.
527 - * @param string|null $hash Optional.
528 - *
529 - * @return string
530 - */
531 - public static function build_marker( $name, $data, $hash = null )
532 - {
533 - // Start the marker, add the data.
534 - $marker = '%%' . $name . AUTOPTIMIZE_HASH . '%%' . base64_encode( $data );
535 -
536 - // Add the hash if provided.
537 - if ( null !== $hash ) {
538 - $marker .= '|' . $hash;
539 - }
540 -
541 - // Close the marker.
542 - $marker .= '%%' . $name . '%%';
543 -
544 - return $marker;
545 - }
546 -
547 - /**
548 - * Searches for `$search` in `$content` (using either `preg_match()`
549 - * or `strpos()`, depending on whether `$search` is a valid regex pattern or not).
550 - * If something is found, it replaces `$content` using `$re_replace_pattern`,
551 - * effectively creating our named markers (`%%{$marker}%%`.
552 - * These are then at some point replaced back to their actual/original/modified
553 - * contents using `autoptimizeBase::restore_marked_content()`.
554 - *
555 - * @param string $marker Marker name (without percent characters).
556 - * @param string $search A string or full blown regex pattern to search for in $content. Uses `strpos()` or `preg_match()`.
557 - * @param string $re_replace_pattern Regex pattern to use when replacing contents.
558 - * @param string $content Content to work on.
559 - *
560 - * @return string
561 - */
562 - public static function replace_contents_with_marker_if_exists( $marker, $search, $re_replace_pattern, $content )
563 - {
564 - $found = false;
565 -
566 - $is_regex = autoptimizeUtils::str_is_valid_regex( $search );
567 - if ( $is_regex ) {
568 - $found = preg_match( $search, $content );
569 - } else {
570 - $found = ( false !== strpos( $content, $search ) );
571 - }
572 -
573 - if ( $found ) {
574 - $content = preg_replace_callback(
575 - $re_replace_pattern,
576 - function( $matches ) use ( $marker ) {
577 - return autoptimizeBase::build_marker( $marker, $matches[0] );
578 - },
579 - $content
580 - );
581 - }
582 -
583 - return $content;
584 - }
585 -
586 - /**
587 - * Complements `autoptimizeBase::replace_contents_with_marker_if_exists()`.
588 - *
589 - * @param string $marker Marker.
590 - * @param string $content Markup.
591 - *
592 - * @return string
593 - */
594 - public static function restore_marked_content( $marker, $content )
595 - {
596 - if ( false !== strpos( $content, $marker ) ) {
597 - $content = preg_replace_callback(
598 - '#%%' . $marker . AUTOPTIMIZE_HASH . '%%(.*?)%%' . $marker . '%%#is',
599 - function ( $matches ) {
600 - return base64_decode( $matches[1] );
601 - },
602 - $content
603 - );
604 - }
605 -
606 - return $content;
607 - }
608 -
609 - /**
610 - * Logs given `$data` for debugging purposes (when debug logging is on).
611 - *
612 - * @param mixed $data Data to log.
613 - *
614 - * @return void
615 - */
616 - protected function debug_log( $data )
617 - {
618 - if ( ! isset( $this->debug_log ) || ! $this->debug_log ) {
619 - return;
620 - }
621 -
622 - if ( ! is_string( $data ) && ! is_resource( $data ) ) {
623 - $data = var_export( $data, true );
624 - }
625 -
626 - error_log( $data );
627 - }
628 -
629 - /**
630 - * Checks if a single local css/js file can be minified and returns source if so.
631 - *
632 - * @param string $filepath Filepath.
633 - *
634 - * @return bool|string to be minified code or false.
635 - */
636 - protected function prepare_minify_single( $filepath )
637 - {
638 - // Decide what we're dealing with, return false if we don't know.
639 - if ( $this->str_ends_in( $filepath, '.js' ) ) {
640 - $type = 'js';
641 - } elseif ( $this->str_ends_in( $filepath, '.css' ) ) {
642 - $type = 'css';
643 - } else {
644 - return false;
645 - }
646 -
647 - // Bail if it looks like its already minifed (by having -min or .min
648 - // in filename) or if it looks like WP jquery.js (which is minified).
649 - $minified_variants = array(
650 - '-min.' . $type,
651 - '.min.' . $type,
652 - 'js/jquery/jquery.js',
653 - );
654 - foreach ( $minified_variants as $ending ) {
655 - if ( $this->str_ends_in( $filepath, $ending ) ) {
656 - return false;
657 - }
658 - }
659 -
660 - // Get file contents, bail if empty.
661 - $contents = file_get_contents( $filepath );
662 -
663 - return $contents;
664 - }
665 -
666 - /**
667 - * Given an autoptimizeCache instance returns the (maybe cdn-ed) url of
668 - * the cached file.
669 - *
670 - * @param autoptimizeCache $cache autoptimizeCache instance.
671 - *
672 - * @return string
673 - */
674 - protected function build_minify_single_url( autoptimizeCache $cache )
675 - {
676 - $url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
677 -
678 - // CDN-replace the resulting URL if needed...
679 - $url = $this->url_replace_cdn( $url );
680 -
681 - return $url;
682 - }
683 -
684 - /**
685 - * Returns true if given $str ends with given $test.
686 - *
687 - * @param string $str String to check.
688 - * @param string $test Ending to match.
689 - *
690 - * @return bool
691 - */
692 - protected function str_ends_in( $str, $test )
693 - {
694 - // @codingStandardsIgnoreStart
695 - // substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH
696 - // return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) );
697 - // @codingStandardsIgnoreEnd
698 -
699 - $length = strlen( $test );
700 -
701 - return ( substr( $str, -$length, $length ) === $test );
702 327 }
703 328 }