PluginProbe ʕ •ᴥ•ʔ
10Web Booster – Website speed optimization, Cache & Page Speed optimizer / trunk
10Web Booster – Website speed optimization, Cache & Page Speed optimizer vtrunk
2.33.0 2.30.5 2.30.7 2.30.9 2.31.10 2.31.8 2.32.11 2.32.21 2.32.3 2.32.4 2.32.7 2.6.31 2.6.40 2.6.42 2.6.7 2.7.37 2.7.44 2.7.47 2.8.18 2.8.19 2.8.32 2.8.34 2.8.35 2.9.23 2.9.24 2.9.25 2.9.27 v2.27.4 trunk 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.17 2.0.18 2.0.21 2.0.22 2.0.25 2.0.26 2.0.27 2.0.3 2.0.7 2.0.9 2.10.46 2.10.65 2.10.66 2.10.68 2.11.41 2.11.42 2.11.43 2.12.15 2.12.21 2.12.22 2.12.23 2.12.26 2.13.37 2.13.40 2.13.41 2.13.42 2.13.44 2.13.45 2.13.47 2.14.49 2.14.50 2.15.18 2.17.21 2.17.23 2.18.17 2.19.44 2.19.45 2.19.46 2.19.49 2.2.12 2.2.15 2.2.16 2.2.18 2.2.8 2.20.31 2.20.32 2.20.33 2.21.11 2.21.12 2.21.16 2.21.25 2.22.32 2.23.13 2.23.15 2.23.16 2.23.18 2.24.12 2.24.14 2.24.18 2.25.14 2.26.6 2.28.10 2.28.13 2.28.14 2.28.7 2.29.1 2.29.2 2.29.3 2.3.0 2.3.1 2.3.2 2.3.3 2.30.18
tenweb-speed-optimizer / includes / OptimizerBase.php
tenweb-speed-optimizer / includes Last commit date
WebPageCache 7 months ago external 2 months ago ExcludeJsFromDelay.php 2 years ago OptimizerBanner.php 11 months ago OptimizerBase.php 2 years ago OptimizerCSSMin.php 2 years ago OptimizerCache.php 1 year ago OptimizerCacheStructure.php 2 years ago OptimizerCriticalCss.php 1 year ago OptimizerElementor.php 2 years ago OptimizerFonts.php 2 years ago OptimizerImages.php 1 year ago OptimizerLogger.php 1 year ago OptimizerMain.php 2 months ago OptimizerOnInit.php 11 months ago OptimizerScripts.php 2 years ago OptimizerSettings.php 2 months ago OptimizerStyles.php 2 years ago OptimizerUrl.php 1 year ago OptimizerUtils.php 2 months ago OptimizerWhiteLabel.php 2 years ago
OptimizerBase.php
599 lines
1 <?php
2
3 namespace TenWebOptimizer;
4
5 /*
6 * Base class other (more-specific) classes inherit from.
7 */
8 if (!defined('ABSPATH')) {
9 exit;
10 }
11
12 abstract class OptimizerBase
13 {
14 /**
15 * Holds content being processed (html, scripts, styles)
16 *
17 * @var string
18 */
19 protected $content = '';
20
21 /**
22 * Controls debug logging.
23 *
24 * @var bool
25 */
26 public $debug_log = false;
27
28 /** @var string */
29 public $cdn_url = '';
30
31 public function __construct($content)
32 {
33 $this->content = $content;
34 }
35
36 /**
37 * Reads the page and collects tags.
38 *
39 * @param array $options options
40 *
41 * @return bool
42 */
43 abstract public function read($options);
44
45 /**
46 * Joins and optimizes collected things.
47 *
48 * @return bool
49 */
50 abstract public function optimize();
51
52 /**
53 * Caches the things.
54 *
55 * @return void
56 */
57 abstract public function cache();
58
59 /**
60 * Returns the content
61 *
62 * @return string
63 */
64 abstract public function getcontent();
65
66 /**
67 * Tranfsorms a given URL to a full local filepath if possible.
68 * Returns local filepath or false.
69 *
70 * @param string $url URL to transform
71 *
72 * @return bool|string
73 */
74 public function getpath($url)
75 {
76 if (false !== strpos($url, '%')) {
77 $url = urldecode($url);
78 }
79 $site_host = wp_parse_url(TWO_WP_SITE_URL, PHP_URL_HOST);
80 $content_host = wp_parse_url(TWO_WP_ROOT_URL, PHP_URL_HOST);
81 // Normalizing attempts...
82 $double_slash_position = strpos($url, '//');
83
84 if (0 === $double_slash_position) {
85 if (is_ssl()) {
86 $url = 'https:' . $url;
87 } else {
88 $url = 'http:' . $url;
89 }
90 } elseif ((false === $double_slash_position) && (false === strpos($url, $site_host))) {
91 if (TWO_WP_SITE_URL === $site_host) {
92 $url = TWO_WP_SITE_URL . $url;
93 } else {
94 $url = TWO_WP_SITE_URL . OptimizerUtils::path_canonicalize($url);
95 }
96 }
97
98 if ($site_host !== $content_host) {
99 $url = str_replace(TWO_WP_CONTENT_URL, TWO_WP_SITE_URL . TWO_WP_CONTENT_NAME, $url);
100 }
101 // First check; hostname wp site should be hostname of url!
102 $url_host = @parse_url($url, PHP_URL_HOST); // @codingStandardsIgnoreLine
103
104 if ($url_host !== $site_host) {
105 $multidomains = [];
106 $multidomains_wpml = apply_filters('wpml_setting', [], 'language_domains');
107
108 if (!empty($multidomains_wpml)) {
109 $multidomains = array_map([$this, 'get_url_hostname'], $multidomains_wpml);
110 }
111
112 if (!empty($this->cdn_url)) {
113 $multidomains[] = wp_parse_url($this->cdn_url, PHP_URL_HOST);
114 }
115
116 if (!empty($multidomains)) {
117 if (in_array($url_host, $multidomains)) {
118 $url = str_replace($url_host, $site_host, $url);
119 } else {
120 return false;
121 }
122 } else {
123 return false;
124 }
125 }
126 // Try to remove "wp root url" from url while not minding http<>https.
127 $tmp_ao_root = preg_replace('/https?:/', '', TWO_WP_ROOT_URL);
128
129 if ($site_host !== $content_host) {
130 // As we replaced the content-domain with the site-domain, we should match against that.
131 $tmp_ao_root = preg_replace('/https?:/', '', TWO_WP_SITE_URL);
132 }
133 $tmp_url = preg_replace('/https?:/', '', $url);
134 $path = str_replace($tmp_ao_root, '', $tmp_url);
135
136 // If path starts with :// or //, this is not a URL in the WP context and
137 // we have to assume we can't aggregate.
138 if (preg_match('#^:?//#', $path)) {
139 // External script/css (adsense, etc).
140 return false;
141 }
142 // Prepend with WP_ROOT_DIR to have full path to file.
143 $path = str_replace('//', '/', WP_ROOT_DIR . $path);
144
145 // Final check: does file exist and is it readable?
146 if (file_exists($path) && is_file($path) && is_readable($path)) {
147 return $path;
148 } else {
149 return false;
150 }
151 }
152
153 /**
154 * Returns the hostname part of a given $url if we're able to parse it.
155 * If not, it returns the original url (prefixed with http:// scheme in case
156 * it was missing).
157 * Used as callback for WPML multidomains filter.
158 *
159 * @param string $url URL
160 *
161 * @return string
162 */
163 protected function get_url_hostname($url)
164 {
165 // Checking that the url starts with something vaguely resembling a protocol.
166 if ((0 !== strpos($url, 'http')) && (0 !== strpos($url, '//'))) {
167 $url = 'http://' . $url;
168 }
169 // Grab the hostname.
170 $hostname = wp_parse_url($url, PHP_URL_HOST);
171
172 // Fallback when parse_url() fails.
173 if (empty($hostname)) {
174 $hostname = $url;
175 }
176
177 return $hostname;
178 }
179
180 /**
181 * Hides everything between noptimize-comment tags.
182 *
183 * @param string $markup markup to process
184 *
185 * @return string
186 */
187 protected function hide_noptimize($markup)
188 {
189 return $this->replace_contents_with_marker_if_exists('NOPTIMIZE', '/<!--\s?noptimize\s?-->/', '#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is', $markup);
190 }
191
192 /**
193 * Unhide noptimize-tags.
194 *
195 * @param string $markup markup to process
196 *
197 * @return string
198 */
199 protected function restore_noptimize($markup)
200 {
201 return $this->restore_marked_content('NOPTIMIZE', $markup);
202 }
203
204 /**
205 * Hides "iehacks" content.
206 *
207 * @param string $markup markup to process
208 *
209 * @return string
210 */
211 protected function hide_iehacks($markup)
212 {
213 return $this->replace_contents_with_marker_if_exists('IEHACK', // Marker name...
214 '<!--[if', // Invalid regex, will fallback to search using strpos()...
215 '#<!--\[if.*?\[endif\]-->#is', // Replacement regex...
216 $markup);
217 }
218
219 /**
220 * Restores "hidden" iehacks content.
221 *
222 * @param string $markup markup to process
223 *
224 * @return string
225 */
226 protected function restore_iehacks($markup)
227 {
228 return $this->restore_marked_content('IEHACK', $markup);
229 }
230
231 /**
232 * "Hides" content within HTML comments using a regex-based replacement
233 * if HTML comment markers are found.
234 * `<!--example-->` becomes `%%COMMENTS%%ZXhhbXBsZQ==%%COMMENTS%%`
235 *
236 * @param string $markup markup to process
237 *
238 * @return string
239 */
240 protected function hide_comments($markup)
241 {
242 return $this->replace_contents_with_marker_if_exists('COMMENTS', '<!--', '#<!--.*?-->#is', $markup);
243 }
244
245 /**
246 * Restores original HTML comment markers inside a string whose HTML
247 * comments have been "hidden" by using `hide_comments()`.
248 *
249 * @param string $markup markup to process
250 *
251 * @return string
252 */
253 protected function restore_comments($markup)
254 {
255 return $this->restore_marked_content('COMMENTS', $markup);
256 }
257
258 /**
259 * Replaces the given URL with the CDN-version of it when CDN replacement
260 * is supposed to be done.
261 *
262 * @param string $url URL to process
263 *
264 * @return string
265 */
266 public function url_replace_cdn($url)
267 {
268 // For 2.3 back-compat in which cdn-ing appeared to be automatically
269 // including WP subfolder/subdirectory into account as part of cdn-ing,
270 // even though it might've caused serious troubles in certain edge-cases.
271 $cdn_url = OptimizerUtils::tweak_cdn_url_if_needed($this->cdn_url);
272
273 // Allows API/filter to further tweak the cdn url...
274 if (!empty($cdn_url)) {
275 $this->debug_log('before=' . $url);
276 // Simple str_replace-based approach fails when $url is protocol-or-host-relative.
277 $is_protocol_relative = OptimizerUtils::is_protocol_relative($url);
278 $is_host_relative = (!$is_protocol_relative && ('/' === $url[0]));
279 $cdn_url = rtrim($cdn_url, '/');
280
281 if ($is_host_relative) {
282 // Prepending host-relative urls with the cdn url.
283 $url = $cdn_url . $url;
284 } else {
285 // Either a protocol-relative or "regular" url, replacing it either way.
286 if ($is_protocol_relative) {
287 // Massage $site_url so that simple str_replace() still "works" by
288 // searching for the protocol-relative version of TWO_WP_SITE_URL.
289 $site_url = str_replace(['http:', 'https:'], '', TWO_WP_SITE_URL);
290 } else {
291 $site_url = TWO_WP_SITE_URL;
292 }
293 $this->debug_log('`' . $site_url . '` -> `' . $cdn_url . '` in `' . $url . '`');
294 $url = str_replace($site_url, $cdn_url, $url);
295 }
296 $this->debug_log('after=' . $url);
297 }
298
299 return $url;
300 }
301
302 /**
303 * Returns true if given `$tag` is found in the list of `$list_for_delay`.
304 *
305 * @param string $tag tag to search for
306 * @param array $list_for_delay list of scripts considered to be delayed
307 *
308 * @return bool
309 */
310 protected function isfordelay($tag, $list_for_delay)
311 {
312 if (false !== strpos($tag, OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE)) {
313 return false;
314 }
315
316 foreach ($list_for_delay as $match) {
317 if (false !== strpos($tag, $match)) {
318 return true;
319 }
320 }
321
322 return false;
323 }
324
325 /**
326 * Returns true if given `$tag` is found in the list of `$removables`.
327 *
328 * @param string $tag tag to search for
329 * @param array $removables list of things considered completely removable
330 *
331 * @return bool
332 */
333 protected function isremovable($tag, $removables)
334 {
335 foreach ($removables as $match) {
336 if (false !== strpos($tag, $match)) {
337 return true;
338 }
339 }
340
341 return false;
342 }
343
344 /**
345 * Callback used in `self::inject_minified()`.
346 *
347 * @param array $matches regex matches
348 *
349 * @return string
350 */
351 public function inject_minified_callback($matches)
352 {
353 static $conf = null;
354 $filepath = null;
355 $filehash = null;
356 $parts = explode('|', $matches[1]);
357
358 if (!empty($parts)) {
359 $filepath = isset($parts[0]) ? base64_decode($parts[0]) : null;
360 $filehash = isset($parts[1]) ? $parts[1] : null;
361 }
362
363 // Bail early if something's not right...
364 if (!$filepath || !$filehash) {
365 return "\n";
366 }
367 $filecontent = file_get_contents($filepath); // phpcs:ignore
368 // Some things are differently handled for css/js...
369 $is_js_file = ('.js' === substr($filepath, -3, 3));
370 $is_css_file = false;
371
372 if (!$is_js_file) {
373 $is_css_file = ('.css' === substr($filepath, -4, 4));
374 }
375 // BOMs being nuked here unconditionally (regardless of where they are)!
376 $filecontent = preg_replace("#\x{EF}\x{BB}\x{BF}#", '', $filecontent);
377
378 // Remove comments and blank lines.
379 if ($is_js_file) {
380 $filecontent = preg_replace('#^\s*\/\/.*$#Um', '', $filecontent);
381 }
382 // Nuke un-important comments.
383 $filecontent = preg_replace('#^\s*\/\*[^!].*\*\/\s?#Um', '', $filecontent);
384 // Normalize newlines.
385 $filecontent = preg_replace('#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#', "\n", $filecontent);
386
387 // JS specifics.
388 if ($is_js_file) {
389 // Append a semicolon at the end of js files if it's missing.
390 $last_char = substr($filecontent, -1, 1);
391
392 if (';' !== $last_char && '}' !== $last_char) {
393 $filecontent .= ';';
394 }
395 // Check if try/catch should be used.
396 $opt_js_try_catch = 'on';
397
398 if ('on' === $opt_js_try_catch) {
399 // It should, wrap in try/catch.
400 $filecontent = 'try{' . $filecontent . '}catch(e){}';
401 }
402 } elseif ($is_css_file) {
403 $filecontent = OptimizerStyles::fixurls($filepath, $filecontent);
404 } else {
405 $filecontent = '';
406 }
407
408 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
409 // Return modified (or empty) code/content.
410 return "\n" . $filecontent;
411 }
412
413 /**
414 * Inject already minified code in optimized JS/CSS.
415 *
416 * @param string $in markup
417 *
418 * @return string
419 */
420 protected function inject_minified($in)
421 {
422 $out = $in;
423
424 if (false !== strpos($in, '%%INJECTLATER%%')) {
425 $out = preg_replace_callback('#\/\*\!%%INJECTLATER' . TWO_HASH . '%%(.*?)%%INJECTLATER%%\*\/#is', [
426 $this,
427 'inject_minified_callback',
428 ], $in);
429 }
430
431 return $out;
432 }
433
434 /**
435 * Specialized method to create the INJECTLATER marker.
436 * These are somewhat "special", in the sense that they're additionally wrapped
437 * within an "exclamation mark style" comment, so that they're not stripped
438 * out by minifiers.
439 * They also currently contain the hash of the file's contents too (unlike other markers).
440 *
441 * @param string $filepath filepath
442 * @param string $hash hash
443 *
444 * @return string
445 */
446 public static function build_injectlater_marker($filepath, $hash)
447 {
448 $contents = '/*!' . self::build_marker('INJECTLATER', $filepath, $hash) . '*/';
449
450 return $contents;
451 }
452
453 /**
454 * Creates and returns a `%%`-style named marker which holds
455 * the base64 encoded `$data`.
456 * If `$hash` is provided, it's appended to the base64 encoded string
457 * using `|` as the separator (in order to support building the
458 * somewhat special/different INJECTLATER marker).
459 *
460 * @param string $name marker name
461 * @param string $data marker data which will be base64-encoded
462 * @param string|null $hash optional
463 *
464 * @return string
465 */
466 public static function build_marker($name, $data, $hash = null)
467 {
468 // Start the marker, add the data.
469 $marker = '%%' . $name . TWO_HASH . '%%' . base64_encode($data);
470
471 // Add the hash if provided.
472 if (null !== $hash) {
473 $marker .= '|' . $hash;
474 }
475 // Close the marker.
476 $marker .= '%%' . $name . '%%';
477
478 return $marker;
479 }
480
481 public static function replace_contents_with_marker_if_exists($marker, $search, $re_replace_pattern, $content)
482 {
483 $found = false;
484 $is_regex = OptimizerUtils::str_is_valid_regex($search);
485
486 if ($is_regex) {
487 $found = preg_match($search, $content);
488 } else {
489 $found = (false !== strpos($content, $search));
490 }
491
492 if ($found) {
493 $content = preg_replace_callback($re_replace_pattern, function ($matches) use ($marker) {
494 return OptimizerBase::build_marker($marker, $matches[0]);
495 }, $content);
496 }
497
498 return $content;
499 }
500
501 public static function restore_marked_content($marker, $content)
502 {
503 if (false !== strpos($content, $marker)) {
504 $content = preg_replace_callback('#%%' . $marker . TWO_HASH . '%%(.*?)%%' . $marker . '%%#is', function ($matches) {
505 return base64_decode($matches[1]);
506 }, $content);
507 }
508
509 return $content;
510 }
511
512 /**
513 * Logs given `$data` for debugging purposes (when debug logging is on).
514 *
515 * @param mixed $data data to log
516 *
517 * @return void
518 */
519 protected function debug_log($data)
520 {
521 if (!isset($this->debug_log) || !$this->debug_log) {
522 return;
523 }
524
525 if (!is_string($data) && !is_resource($data)) {
526 $data = var_export($data, true); // phpcs:ignore
527 }
528 error_log($data); // phpcs:ignore
529 }
530
531 /**
532 * Checks if a single local css/js file can be minified and returns source if so.
533 *
534 * @param string $filepath filepath
535 *
536 * @return bool|string to be minified code or false
537 */
538 protected function prepare_minify_single($filepath)
539 {
540 // Decide what we're dealing with, return false if we don't know.
541 if ($this->str_ends_in($filepath, '.js')) {
542 $type = 'js';
543 } elseif ($this->str_ends_in($filepath, '.css')) {
544 $type = 'css';
545 } else {
546 return false;
547 }
548 // Bail if it looks like its already minifed (by having -min or .min
549 // in filename) or if it looks like WP jquery.js (which is minified).
550 $minified_variants = [
551 '-min.' . $type,
552 '.min.' . $type,
553 'js/jquery/jquery.js',
554 ];
555
556 foreach ($minified_variants as $ending) {
557 if ($this->str_ends_in($filepath, $ending)) {
558 return false;
559 }
560 }
561 // Get file contents, bail if empty.
562 $contents = file_get_contents($filepath); // phpcs:ignore
563
564 return $contents;
565 }
566
567 protected function build_minify_single_url(OptimizerCache $cache)
568 {
569 $url = TWO_CACHE_URL . $cache->getname(true);
570 $url = $this->url_replace_cdn($url);
571
572 return $url;
573 }
574
575 /**
576 * Returns true if given $str ends with given $test.
577 *
578 * @param string $str string to check
579 * @param string $test ending to match
580 *
581 * @return bool
582 */
583 protected function str_ends_in($str, $test)
584 {
585 // @codingStandardsIgnoreStart
586 // substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH
587 // return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) );
588 // @codingStandardsIgnoreEnd
589 $length = strlen($test);
590
591 return substr($str, -$length, $length) === $test;
592 }
593
594 protected function remove_from_html($tag)
595 {
596 $this->content = str_replace($tag, '', $this->content);
597 }
598 }
599