PluginProbe ʕ •ᴥ•ʔ
10Web Booster – Website speed optimization, Cache & Page Speed optimizer / 2.33.6
10Web Booster – Website speed optimization, Cache & Page Speed optimizer v2.33.6
2.33.6 2.33.5 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 / OptimizerUtils.php
tenweb-speed-optimizer / includes Last commit date
WebPageCache 2 weeks ago external 1 week ago ExcludeJsFromDelay.php 2 years ago OptimizerBanner.php 1 year ago OptimizerBase.php 2 years ago OptimizerCSSMin.php 2 years ago OptimizerCache.php 2 weeks ago OptimizerCacheStructure.php 2 years ago OptimizerCriticalCss.php 2 weeks ago OptimizerElementor.php 2 years ago OptimizerFonts.php 2 years ago OptimizerImages.php 1 year ago OptimizerLogger.php 1 year ago OptimizerMain.php 2 weeks ago OptimizerOnInit.php 2 weeks ago OptimizerScripts.php 2 years ago OptimizerSettings.php 2 weeks ago OptimizerStyles.php 2 weeks ago OptimizerUrl.php 1 year ago OptimizerUtils.php 2 weeks ago OptimizerWhiteLabel.php 2 years ago
OptimizerUtils.php
3175 lines
1 <?php
2
3 namespace TenWebOptimizer;
4
5 use autoptimizeCache;
6 use Cache_Enabler;
7 use Endurance_Page_Cache;
8 use Exception;
9 use JSMin\JSMin;
10 use TenWebIO\CompressService;
11 use WP_Optimize_Cache_Commands;
12 use WP_Rewrite;
13 use WP_Term;
14 use WP_User;
15 use WpeCommon;
16
17 /*
18 * General helpers.
19 */
20 if (!defined('ABSPATH')) {
21 exit;
22 }
23
24 class OptimizerUtils
25 {
26 const TWO_INCOMPATIBLE_PLUGIN_LIST = [
27 'w3-total-cache/w3-total-cache.php',
28 'wp-super-cache/wp-cache.php',
29 'wp-rocket/wp-rocket.php',
30 'rocket-footer-js/rocket-footer-js.php',
31 'autoptimize/autoptimize.php',
32 'perfmatters/perfmatters.php',
33 'wp-fastest-cache/wpFastestCache.php',
34 'wp-optimize/wp-optimize.php',
35 'wp-asset-clean-up/wpacu.php',
36 'rocket-lazy-load/rocket-lazy-load.php',
37 'hummingbird-performance/wp-hummingbird.php',
38 'wp-hummingbird/wp-hummingbird.php',
39 'flying-scripts/flying-scripts.php',
40 'async-javascript/async-javascript.php',
41 'nitropack/main.php',
42 'psn-pagespeed-ninja/pagespeedninja.php',
43 'swift-performance-lite/performance.php',
44 'swift-performance/performance.php',
45 'fast-velocity-minify/fvm.php',
46 'wp-performance-score-booster/wp-performance-score-booster.php',
47 'ezoic-integration/ezoic-integration.php',
48 'a3-lazy-load/a3-lazy-load.php',
49 'page-optimize/page-optimize.php',
50 'wp-smushit/wp-smush.php',
51 'performance-lab/load.php',
52 'airlift/airlift.php',
53 ];
54
55 const OPTIMIZED_BG_MARKER = '++TWO_OPTIMIZED_BG_IMAGE++';
56
57 const SVG_DATA = 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20';
58
59 const BG_SVG_PLACEHOLDER = OptimizerUtils::SVG_DATA . '%20' . '%22%3E%3C/svg%3E' . '#}';
60
61 const BACKGROUND_IMAGE_REGEXP_NEW__PERFECT__SLOW = "~(?:\})*(.*)\{(?:[^}]|\w\s\S)*(?:background[-image]*\s*:.*url.*\(\s*((?:[\'\"]{1}(?:.*)?[\'\"]{1})|(?:[^\'\"]{1}(?:.*)?[^\'\"]{1}))\s*\))+[^}]*\}~U";
62
63 const BACKGROUND_IMAGE_REGEXP_OLD__NOT_PERFECT__FAST = "~(.*)\{(?:[^}]|\w\s\S)*(?:background[-image]*?\s*:.*?url.*?\(\s*((?:[\'|\"]{1}(?:.*?)?[\'|\"]{1})|(?:[^\'|\"]{1}(?:.*?)?[^\'|\"]{1}))\s*\))+[^}]*\}~";
64
65 const FROM_PLUGIN = '10webspeedoptimizer';
66
67 const MODES = [
68 'extreme' => [
69 'title' => 'Extreme',
70 'mode' => 'extreme',
71 'two_delay_all_js_execution' => true,
72 'critical_enabled' => true,
73 'lazy_load_type' => 'vanilla',
74 'next' => 'strong',
75 'level' => 1,
76 ],
77 'strong' => [
78 'title' => 'Strong',
79 'mode' => 'strong',
80 'two_delay_all_js_execution' => true,
81 'critical_enabled' => false,
82 'lazy_load_type' => 'vanilla',
83 'next' => 'balanced',
84 'level' => 2,
85 ],
86 'balanced' => [
87 'title' => 'Balanced',
88 'mode' => 'balanced',
89 'two_delay_all_js_execution' => false,
90 'critical_enabled' => true,
91 'lazy_load_type' => 'vanilla',
92 'next' => 'standard',
93 'level' => 3,
94 ],
95 'standard' => [
96 'title' => 'Standard',
97 'mode' => 'standard',
98 'two_delay_all_js_execution' => false,
99 'critical_enabled' => false,
100 'lazy_load_type' => 'browser',
101 'next' => 'no_optimize',
102 'level' => 4,
103 ],
104 'no_optimize' => [
105 'title' => 'No optimize',
106 'mode' => 'no_optimize',
107 'next' => '0',
108 'level' => 5,
109 ],
110 ];
111
112 private static $has_changed_bg_image = false;
113
114 /**
115 * Returns true when mbstring is available.
116 *
117 * @param bool|null $override allows overriding the decision
118 *
119 * @return bool
120 */
121 public static function mbstring_available($override = null)
122 {
123 static $available = null;
124
125 if (null === $available) {
126 $available = \extension_loaded('mbstring');
127 }
128
129 if (null !== $override) {
130 $available = $override;
131 }
132
133 return $available;
134 }
135
136 /**
137 * Multibyte-capable strpos() if support is available on the server.
138 * If not, it falls back to using \strpos().
139 *
140 * @param string $haystack haystack
141 * @param string $needle needle
142 * @param int $offset offset
143 * @param string|null $encoding Encoding. Default null.
144 *
145 * @return int|false
146 */
147 public static function strpos($haystack, $needle, $offset = 0, $encoding = null)
148 {
149 if (self::mbstring_available()) {
150 return (null === $encoding) ? \mb_strpos($haystack, $needle, $offset) : \mb_strpos($haystack, $needle, $offset, $encoding);
151 } else {
152 return \strpos($haystack, $needle, $offset);
153 }
154 }
155
156 /**
157 * Finds the position of the <body> tag in an HTML string.
158 *
159 * This method searches for the first occurrence of the <body> tag
160 * in the given HTML content. If a </head> tag is found after the <body>
161 * tag, it will search for the next <body> tag.
162 *
163 * @param string $haystack the HTML content as a string
164 *
165 * @return int|false the position of the <body> tag, or false if not found
166 */
167 public static function get_body_position($haystack)
168 {
169 // Find the first occurrence of the <body> tag
170 $bodyPosition = self::strpos($haystack, '<body');
171
172 // If <body> tag is not found, return false
173 if ($bodyPosition === false) {
174 return false;
175 }
176
177 // Extract substring starting from the <body> tag
178 $afterBody = substr($haystack, $bodyPosition);
179
180 // Check if there's a </head> tag after the found <body> tag
181 $headClosePosition = self::strpos($afterBody, '</head>');
182
183 // If </head> is found after <body>, search for the next <body> tag
184 if ($headClosePosition !== false) {
185 // Search for the next <body> tag after </head>
186 $bodyPosition = self::strpos($haystack, '<body', $bodyPosition + 1);
187 }
188
189 return $bodyPosition;
190 }
191
192 /**
193 * Multibyte-capable strrpos() if support is available on the server.
194 * If not, it falls back to using \strrpos().
195 *
196 * @param string $haystack haystack
197 * @param string $needle needle
198 * @param int $offset offset
199 * @param string|null $encoding Encoding. Default null.
200 *
201 * @return int|false
202 */
203 public static function strrpos($haystack, $needle, $offset = 0, $encoding = null)
204 {
205 if (self::mbstring_available()) {
206 return (null === $encoding) ? \mb_strrpos($haystack, $needle, $offset) : \mb_strrpos($haystack, $needle, $offset, $encoding);
207 } else {
208 return \strrpos($haystack, $needle, $offset);
209 }
210 }
211
212 /**
213 * Attempts to return the number of characters in the given $string if
214 * mbstring is available. Returns the number of bytes
215 * (instead of characters) as fallback.
216 *
217 * @param string $string string
218 * @param string|null $encoding encoding
219 *
220 * @return int number of charcters or bytes in given $string
221 * (characters if/when supported, bytes otherwise)
222 */
223 public static function strlen($string, $encoding = null)
224 {
225 if (self::mbstring_available()) {
226 return (null === $encoding) ? \mb_strlen($string) : \mb_strlen($string, $encoding);
227 } else {
228 return \strlen($string);
229 }
230 }
231
232 /**
233 * Our wrapper around implementations of \substr_replace()
234 * that attempts to not break things horribly if at all possible.
235 * Uses mbstring if available, before falling back to regular
236 * substr_replace() (which works just fine in the majority of cases).
237 *
238 * @param string $string string
239 * @param string $replacement replacement
240 * @param int $start start offset
241 * @param int|null $length length
242 * @param string|null $encoding encoding
243 *
244 * @return string
245 */
246 public static function substr_replace($string, $replacement, $start, $length = null, $encoding = null)
247 {
248 if (self::mbstring_available()) {
249 $strlen = self::strlen($string, $encoding);
250
251 if ($start < 0) {
252 if (-$start < $strlen) {
253 $start = $strlen + $start;
254 } else {
255 $start = 0;
256 }
257 } elseif ($start > $strlen) {
258 $start = $strlen;
259 }
260
261 if (null === $length || '' === $length) {
262 $start2 = $strlen;
263 } elseif ($length < 0) {
264 $start2 = $strlen + $length;
265
266 if ($start2 < $start) {
267 $start2 = $start;
268 }
269 } else {
270 $start2 = $start + $length;
271 }
272
273 if (null === $encoding) {
274 $leader = $start ? \mb_substr($string, 0, $start) : '';
275 $trailer = ($start2 < $strlen) ? \mb_substr($string, $start2, null) : '';
276 } else {
277 $leader = $start ? \mb_substr($string, 0, $start, $encoding) : '';
278 $trailer = ($start2 < $strlen) ? \mb_substr($string, $start2, null, $encoding) : '';
279 }
280
281 return "{$leader}{$replacement}{$trailer}";
282 }
283
284 return (null === $length) ? \substr_replace($string, $replacement, $start) : \substr_replace($string, $replacement, $start, $length);
285 }
286
287 /**
288 * Decides whether this is a "subdirectory site" or not.
289 *
290 * @param bool $override allows overriding the decision when needed
291 *
292 * @return bool
293 */
294 public static function siteurl_not_root($override = null)
295 {
296 static $subdir = null;
297
298 if (null === $subdir) {
299 $parts = self::get_ao_wp_site_url_parts();
300 $subdir = (isset($parts['path']) && ('/' !== $parts['path']));
301 }
302
303 if (null !== $override) {
304 $subdir = $override;
305 }
306
307 return $subdir;
308 }
309
310 /**
311 * Parse TWO_WP_SITE_URL into components using \parse_url(), but do
312 * so only once per request/lifecycle.
313 *
314 * @return array
315 */
316 public static function get_ao_wp_site_url_parts()
317 {
318 static $parts = [];
319
320 if (empty($parts)) {
321 $parts = \wp_parse_url(TWO_WP_SITE_URL);
322 }
323
324 return $parts;
325 }
326
327 /**
328 * Modify given $cdn_url to include the site path when needed.
329 *
330 * @param string $cdn_url CDN URL to tweak
331 * @param bool $force_cache_miss force a cache miss in order to be able
332 * to re-run the filter
333 *
334 * @return string
335 */
336 public static function tweak_cdn_url_if_needed($cdn_url, $force_cache_miss = false)
337 {
338 static $results = [];
339
340 if (!isset($results[$cdn_url]) || $force_cache_miss) {
341
342 // In order to return unmodified input when there's no need to tweak.
343 $results[$cdn_url] = $cdn_url;
344
345 // Behind a default true filter for backcompat, and only for sites
346 // in a subfolder/subdirectory, but still easily turned off if
347 // not wanted/needed...
348 if (OptimizerUtils::siteurl_not_root()) {
349 $site_url_parts = OptimizerUtils::get_ao_wp_site_url_parts();
350 $cdn_url_parts = \wp_parse_url($cdn_url);
351 $schemeless = self::is_protocol_relative($cdn_url);
352 $cdn_url_parts = self::maybe_replace_cdn_path($site_url_parts, $cdn_url_parts);
353
354 if (false !== $cdn_url_parts) {
355 $results[$cdn_url] = self::assemble_parsed_url($cdn_url_parts, $schemeless);
356 }
357 }
358 }
359
360 return $results[$cdn_url];
361 }
362
363 /**
364 * When siteurl contans a path other than '/' and the CDN URL does not have
365 * a path or it's path is '/', this will modify the CDN URL's path component
366 * to match that of the siteurl.
367 * This is to support "magic" CDN urls that worked that way before v2.4...
368 *
369 * @param array $site_url_parts site URL components array
370 * @param array $cdn_url_parts CDN URL components array
371 *
372 * @return array|false
373 */
374 public static function maybe_replace_cdn_path(array $site_url_parts, array $cdn_url_parts)
375 {
376 if (isset($site_url_parts['path']) && '/' !== $site_url_parts['path']) {
377 if (!isset($cdn_url_parts['path']) || '/' === $cdn_url_parts['path']) {
378 $cdn_url_parts['path'] = $site_url_parts['path'];
379
380 return $cdn_url_parts;
381 }
382 }
383
384 return false;
385 }
386
387 /**
388 * Given an array or components returned from \parse_url(), assembles back
389 * the complete URL.
390 * If optional
391 *
392 * @param array $parsed_url URL components array
393 * @param bool $schemeless whether the assembled URL should be
394 * protocol-relative (schemeless) or not
395 *
396 * @return string
397 */
398 public static function assemble_parsed_url(array $parsed_url, $schemeless = false)
399 {
400 $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
401
402 if ($schemeless) {
403 $scheme = '//';
404 }
405 $host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
406 $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
407 $user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
408 $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
409 $pass = ($user || $pass) ? "$pass@" : '';
410 $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
411 $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
412 $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
413
414 return "$scheme$user$pass$host$port$path$query$fragment";
415 }
416
417 /**
418 * Returns true if given $url is protocol-relative.
419 *
420 * @param string $url URL to check
421 *
422 * @return bool
423 */
424 public static function is_protocol_relative($url)
425 {
426 $result = false;
427
428 if (!empty($url)) {
429 $result = (0 === strpos($url, '//'));
430 }
431
432 return $result;
433 }
434
435 /**
436 * Canonicalizes the given path regardless of it existing or not.
437 *
438 * @param string $path path to normalize
439 *
440 * @return string
441 */
442 public static function path_canonicalize($path)
443 {
444 $patterns = [
445 '~/{2,}~',
446 '~/(\./)+~',
447 '~([^/\.]+/(?R)*\.{2,}/)~',
448 '~\.\./~',
449 ];
450 $replacements = [
451 '/',
452 '/',
453 '',
454 '',
455 ];
456
457 return preg_replace($patterns, $replacements, $path);
458 }
459
460 /**
461 * Returns true if the string is a valid regex.
462 *
463 * @param string $string string, duh
464 *
465 * @return bool
466 */
467 public static function str_is_valid_regex($string)
468 {
469 set_error_handler(function () {}, E_WARNING); // phpcs:ignore
470 $is_regex = (false !== preg_match($string, ''));
471 restore_error_handler();
472
473 return $is_regex;
474 }
475
476 /**
477 * Returns true if a certain WP plugin is active/loaded.
478 *
479 * @param string $plugin_file main plugin file
480 *
481 * @return bool
482 */
483 public static function is_plugin_active($plugin_file)
484 {
485 static $ipa_exists = null;
486
487 if (null === $ipa_exists) {
488 if (!function_exists('\is_plugin_active')) {
489 require_once ABSPATH . 'wp-admin/includes/plugin.php';
490 }
491 $ipa_exists = function_exists('\is_plugin_active');
492 }
493
494 return $ipa_exists && \is_plugin_active($plugin_file);
495 }
496
497 public static function replace_font($css)
498 {
499 global $TwoSettings;
500 $two_async_font = $TwoSettings->get_settings('two_async_font');
501
502 if (isset($two_async_font) && $two_async_font == 'on') {
503 /*$re = '~(?>@font-face\s*{\s*|\G(?!\A))(\S+)\s*:\s*valet([^;]+);\s*~';*/ // phpcs:ignore
504 $re = '/@font-face.*{\K[^}]*(?=})/';
505 preg_match_all($re, $css, $matches, PREG_SET_ORDER, 0);
506
507 foreach ($matches as $el) {
508 if (isset($el[0])) {
509 if (strpos($el[0], 'font-display') !== false) {
510 $re1 = '/font-display\s*:\s*\K[^;]*(?=;)/';
511 preg_match_all($re1, $el[0], $elMatches, PREG_SET_ORDER, 0);
512
513 if (isset($elMatches, $elMatches[0], $elMatches[0][0])) {
514 $style = str_replace($elMatches[0][0], 'swap;', $el[0]);
515 $css = str_replace($el[0], $style, $css);
516 }
517 } else {
518 $style = $el[0] . ';font-display: swap;';
519 $css = str_replace($el[0], $style, $css);
520 }
521 }
522 }
523 }
524
525 return $css;
526 }
527
528 public static function replace_google_font_url($url)
529 {
530 if (strpos($url, 'display')) {
531 $url = str_replace('&amp;', '&', $url);
532 $parsed_url = wp_parse_url(urldecode($url));
533 parse_str($parsed_url['query'], $url_params);
534
535 if (isset($url_params['display'])) {
536 $url = str_replace($url_params['display'], 'swap', $url);
537 } else {
538 $url = add_query_arg('display', 'swap', $url);
539 }
540 } else {
541 $url = add_query_arg('display', 'swap', $url);
542 }
543
544 return $url;
545 }
546
547 public static function serve_different_sizes_for_critical_bg_image($images_data)
548 {
549 //tenweb_optimizer_mobile
550 $imagesArray = [];
551 $css = '';
552
553 if (is_array($images_data)) {
554 $allSizes = get_intermediate_image_sizes(); // phpcs:ignore
555
556 foreach ($images_data as $image_data) {
557 if (isset($image_data[ 'bg_url' ]) && isset($image_data[ 'selector' ]) && is_array($image_data[ 'selector' ])) {
558 $imageId = self::getImageIdByUrl(strtok($image_data[ 'bg_url' ], '?'));
559
560 if ($imageId) {
561 $css_rule = implode(':not(.two_bg), ', $image_data['selector']);
562 $css_rule .= ':not(.two_bg)';
563
564 //create an array with all image sizes
565 foreach ($allSizes as $i => $size) {
566 $imagesArray[ $size ] = wp_get_attachment_image_src($imageId, $size);
567 }
568 $important = '';
569
570 if (isset($image_data[ 'value' ]) && strpos($image_data[ 'value' ], '!important') < 1) {
571 $important = ' !important; ';
572 }
573 $mobileRule = '';
574
575 foreach ($imagesArray as $size => $imageArray) {
576 if (!empty($imageArray)) {
577 if ($size === 'medium_large' && empty($mobileRule)) {
578 if (isset($imageArray[ 0 ]) && !empty($imageArray[ 0 ])) {
579 if (isset($image_data[ 'value' ]) && strpos($image_data[ 'value' ], $image_data[ 'bg_url' ]) !== false) {
580 $css_param_value = str_replace($image_data[ 'bg_url' ], $imageArray[ 0 ], $image_data[ 'value' ]);
581 $mobileRule = ' background-image: ' . $css_param_value . $important;
582 } else {
583 $mobileRule = ' background-image: url(' . $imageArray[ 0 ] . ') !important; ';
584 }
585 }
586 break;
587 }
588 }
589 }
590
591 //fallback to elementor images
592 if (empty($mobileRule)) {
593 if (isset($imagesArray[ 'tenweb_optimizer_mobile' ])) {
594 if (isset($imagesArray[ 'tenweb_optimizer_mobile' ][ 0 ]) && !empty($imagesArray[ 'tenweb_optimizer_mobile' ][ 0 ])) {
595 if (isset($image_data[ 'value' ]) && strpos($image_data[ 'value' ], $image_data[ 'bg_url' ]) !== false) {
596 $css_param_value = str_replace($image_data[ 'bg_url' ], $imagesArray[ 'tenweb_optimizer_mobile' ][ 0 ], $image_data[ 'value' ]);
597 $mobileRule = ' background-image: ' . $css_param_value . $important;
598 } else {
599 $mobileRule = ' background-image: url(' . $imagesArray[ 'tenweb_optimizer_mobile' ][ 0 ] . ') !important; ';
600 }
601 }
602 }
603 }
604
605 //generate media css blocks and add to the end of css file
606 if (!empty($mobileRule)) {
607 $mobileCss = "\r\n" . rtrim($css_rule, ',')
608 . ' { ' . $mobileRule . ' } ';
609 $css .= ' ' . $mobileCss;
610 }
611 }
612 }
613 }
614 }
615
616 if ($css) {
617 $css = self::replace_bg($css);
618 $css = "/* Autogenerated by 10Web Booster plugin*/\r\n
619 @media (min-width: 320px) and (max-width: 480px) { \r\n" . $css . '}';
620 }
621
622 return $css;
623 }
624
625 public static function getImageIdByUrl($url)
626 {
627 global $wpdb;
628 // If the URL is auto-generated thumbnail, remove the sizes and get the URL of the original image
629 $url = preg_replace('/-\d+x\d+(?=\.(jpg|jpeg|png|gif)$)/i', '', $url);
630 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
631 // $imgid = attachment_url_to_postid($url); // phpcs:ignore
632 $wp_uploads = wp_upload_dir();
633 $attachment_id = $wpdb->get_var($wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attached_file' AND meta_value = %s LIMIT 1", str_replace($wp_uploads['baseurl'] . '/', '', $url))); // phpcs:ignore
634
635 return $attachment_id;
636 }
637
638 /**
639 * This is the copy of WP function to regenerate metadata if it is set but is missing sizes for some reason.
640 * The original is in wp-includes/media.php
641 *
642 * Maybe attempts to generate attachment metadata, if missing.
643 *
644 * @param WP_Post $attachment attachment object
645 */
646 public static function wp_maybe_generate_attachment_metadata($attachment)
647 {
648 if (empty($attachment) || empty($attachment->ID)) {
649 return;
650 }
651
652 $attachment_id = (int) $attachment->ID;
653 $file = get_attached_file($attachment_id);
654 $meta = wp_get_attachment_metadata($attachment_id);
655
656 if (empty($meta) && file_exists($file)) {
657 $_meta = get_post_meta($attachment_id);
658 $_lock = 'wp_generating_att_' . $attachment_id;
659
660 if ((! array_key_exists('_wp_attachment_metadata', $_meta) || empty($_meta[ '_wp_attachment_metadata' ][ 'sizes' ])) && ! \TenWebWpTransients\OptimizerTransients::get($_lock)) {
661 \TenWebWpTransients\OptimizerTransients::set($_lock, $file);
662 wp_update_attachment_metadata($attachment_id, wp_generate_attachment_metadata($attachment_id, $file));
663 \TenWebWpTransients\OptimizerTransients::delete($_lock);
664 }
665 }
666 }
667
668 public static function replace_bg($css)
669 {
670 /* Exclude ::after and ::before elements from CSS because if we change their bg urls,
671 we cannot restore it on the fronted using JS */
672 $css_without_after_before = $css;
673
674 if (preg_match_all('#(::before.*})|(::after.*})#Usmi', $css_without_after_before, $css_matches)) {
675 foreach ($css_matches[0] as $css_block) {
676 $css_without_after_before = str_replace($css_block, '}', $css_without_after_before);
677 }
678 }
679
680 $replaced_images = [];
681 global $TwoSettings;
682 $two_lazyload = $TwoSettings->get_settings('two_lazyload');
683 $two_bg_lazyload = $TwoSettings->get_settings('two_bg_lazyload');
684 $two_img_in_viewport_lazyload = $TwoSettings->get_settings('two_img_in_viewport_lazyload');
685 $critical = new OptimizerCriticalCss();
686
687 if (TWO_LAZYLOAD && isset($two_bg_lazyload) && ($two_bg_lazyload == 'on' || ($two_img_in_viewport_lazyload == 'on' && $critical->images_in_viewport)) && !$critical->use_uncritical) {
688 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
689 //$re = '~\bbackground[-image]*?\s*:.*?url.*?\(\s*[\'|"]?(.*?)?[\'|"]?\s*\)~i'; // phpcs:ignore
690 //$re = '~url\s*\(\s*[\'|"]?(.*?)?[\'|"]?\s*\)~i'; // phpcs:ignore
691 //$re = '~\bbackground[-image]*?\s*:.*?url.*?\(\s*[\'|"]?(.*?)?[\'|"]\s*\)~i'; // phpcs:ignore
692 $re = '~\bbackground[-image]*?\s*:.*?url.*?\(\s*?(.*?)?\s*\)~i';
693 preg_match_all($re, $css_without_after_before, $matches);
694 $ext_list = ['svg', 'jpeg', 'png', 'gif', 'jpg', 'webp', 'bmp'];
695
696 if (isset($matches[1]) && is_array($matches)) {
697 $images_urls = $matches[1];
698 $bg_styles = $matches[0];
699
700 foreach ($images_urls as $key => $src) {
701 $src = htmlspecialchars_decode($src, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401);
702 $src = str_replace(["'", '"'], [''], $src);
703 $url = strtok($src, '?');
704 $ext = pathinfo($url, PATHINFO_EXTENSION);
705 $current_bg_style = '';
706
707 if (is_array($bg_styles) && isset($bg_styles[$key])) {
708 $current_bg_style = $bg_styles[$key];
709 }
710
711 if (!in_array(strtolower($ext), $ext_list)) {
712 continue;
713 }
714
715 if (in_array($src, $replaced_images)) {
716 continue;
717 }
718
719 //pass serve_different_sizes_for_bg_image added backgrounds
720 if (self::strpos($src, self::OPTIMIZED_BG_MARKER) !== false) {
721 continue;
722 }
723 $flag_continue = false;
724 $two_exclude_lazyload = $TwoSettings->get_settings('two_exclude_lazyload');
725
726 if (isset($two_exclude_lazyload) && !empty($two_exclude_lazyload)) {
727 $exclude_lazyload = explode(',', $two_exclude_lazyload);
728
729 foreach ($exclude_lazyload as $path) {
730 if (strpos($src, $path) !== false) {
731 $flag_continue = true;
732 }
733 }
734 }
735
736 if ($flag_continue === false && $two_img_in_viewport_lazyload == 'on' && $critical->images_in_viewport) {
737 $abs_src = self::get_absolute_url($src);
738 $flag_continue = in_array($abs_src, $critical->images_in_viewport);
739 }
740
741 if ($flag_continue) {
742 continue;
743 }
744 $replaced_images[] = $src;
745
746 $pos = strpos($src, '#}');
747
748 if ($pos === false && !empty($current_bg_style)) {
749 $new_bg_style = str_replace($src, self::BG_SVG_PLACEHOLDER . $src, $current_bg_style);
750 $css = str_replace($current_bg_style, $new_bg_style, $css);
751 } elseif ($pos === false) {
752 $css = str_replace($src, self::BG_SVG_PLACEHOLDER . $src, $css);
753 }
754 self::$has_changed_bg_image = true;
755 }
756 }
757 }
758
759 return $css;
760 }
761
762 /**
763 * Cache compare.
764 *
765 * @param array $args
766 */
767 public static function cache_compare($args = [])
768 {
769 $type = $args['type'];
770 $post_id = $args['post_id'];
771 $new_cache_files = $args['new_cache_files'];
772 $old_cache_files = get_post_meta($post_id, 'two_cache_' . $type, true);
773
774 if (!empty($new_cache_files) && empty($old_cache_files)) {
775 add_post_meta($post_id, 'two_cache_' . $type, []);
776 }
777
778 if (!empty($new_cache_files)) {
779 update_post_meta($post_id, 'two_cache_' . $type, $new_cache_files);
780
781 if (!empty($old_cache_files)) {
782 $dir_gzip = OptimizerCache::get_path();
783
784 foreach ($old_cache_files as $old_file) {
785 if (!in_array($old_file, $new_cache_files)) {
786 $old_file_name = ($type == 'gzip') ? $old_file : $type . '/' . $old_file;
787 self::delete_cache_file($old_file_name);
788
789 if ($type == 'gzip') {
790 foreach (['deflate', 'none', 'gzip'] as $val) {
791 $file_gzip = $old_file . '.' . $val;
792
793 if (is_file($dir_gzip . $file_gzip)) {
794 self::delete_cache_file($file_gzip);
795 }
796 }
797 }
798 }
799 }
800 }
801 }
802 }
803
804 /**
805 * Cache files parsing array.
806 *
807 * @return array
808 */
809 public static function cache_files_parsing_array()
810 {
811 $js = [];
812 $css = [];
813 $gzip = [];
814 $file_name = '_all_cache_files.txt';
815
816 if (is_file(TWO_CACHE_DIR . $file_name)) {
817 $files = file_get_contents(TWO_CACHE_DIR . $file_name); // phpcs:ignore
818
819 if (!empty($files)) {
820 OptimizerUtils::delete_cache_file($file_name);
821 $files = json_decode($files);
822
823 foreach ($files as $file) {
824 preg_match('/^css\/(.*).css$/', $file, $matches_css);
825 preg_match('/^js\/(.*).js$/', $file, $matches_js);
826 preg_match('/^(.*).php$/', $file, $matches_php);
827
828 if (!empty($matches_css)) {
829 $css[] = str_replace('css/', '', $matches_css[0]);
830 }
831
832 if (!empty($matches_js)) {
833 if (strpos($matches_js[0], 'two_snippet_') < -1) {
834 $js[] = str_replace('js/', '', $matches_js[0]);
835 }
836 }
837
838 if (!empty($matches_php)) {
839 if (strpos($matches_php[0], 'two_snippet_') < -1) {
840 $gzip[] = $matches_php[0];
841 }
842 }
843 }
844 }
845 }
846
847 return ['js' => array_unique($js), 'css' => array_unique($css), 'gzip' => array_unique($gzip)];
848 }
849
850 /**
851 * Delete all cache on DB.
852 *
853 * @param array $args
854 *
855 * @return bool
856 */
857 public static function delete_all_cache_db($args = [])
858 {
859 global $wpdb;
860 $tbl = $wpdb->prefix . 'postmeta';
861 $css = $wpdb->delete($tbl, ['meta_key' => 'two_cache_css']); // phpcs:ignore
862 $js = $wpdb->delete($tbl, ['meta_key' => 'two_cache_js']); // phpcs:ignore
863 $gzip = $wpdb->delete($tbl, ['meta_key' => 'two_cache_gzip']); // phpcs:ignore
864
865 return true;
866 }
867
868 /**
869 * Deleted recursively directory and its entire contents.
870 *
871 * @param string $dir
872 * @param array $not_allow_delete
873 *
874 * @return mixed
875 */
876 public static function delete_all_cache_file($dir = '', $not_allow_delete = [], $not_allow_folder = null)
877 {
878 if (is_dir($dir)) {
879 $objects = scandir($dir);
880
881 if (!is_array($objects)) {
882 return false;
883 }
884
885 foreach ($objects as $key => $object) {
886 if ($object === $not_allow_folder) {
887 continue;
888 }
889
890 if ($object != '.' && $object != '..') {
891 if (is_dir($dir . '/' . $object) && !is_link($dir . '/' . $object)) {
892 self::delete_all_cache_file(rtrim($dir, '/') . '/' . $object, $not_allow_delete);
893 } else {
894 unlink($dir . '/' . $object); // phpcs:ignore
895 }
896 unset($objects[$key]);
897 }
898 }
899
900 if (count($objects) === 2 && (empty($not_allow_delete) || !in_array($dir, $not_allow_delete) || empty($not_allow_folder))) {
901 rmdir($dir); // phpcs:ignore
902 }
903
904 return true;
905 }
906
907 return true;
908 }
909
910 public static function delete_files_by_prefix($prefix)
911 {
912 $dir = OptimizerCache::get_path();
913 $mask = $dir . $prefix;
914 array_map('unlink', glob($mask));
915 }
916
917 /**
918 * Delete cache file.
919 *
920 * @param string $file
921 *
922 * @return bool
923 */
924 public static function delete_cache_file($file = '')
925 {
926 $file = OptimizerCache::get_path() . $file;
927
928 if (is_file($file)) {
929 $delete = @unlink($file); // phpcs:ignore
930
931 if ($delete) {
932 return true;
933 }
934 }
935
936 return false;
937 }
938
939 public static function get_page_url()
940 {
941 if (isset($_SERVER['HTTP_HOST']) && isset($_SERVER['REQUEST_URI'])) {
942 if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
943 $link = 'https';
944 } else {
945 $link = 'http';
946 }
947 // Here append the common URL characters.
948 $link .= '://';
949 // Append the host(domain name, ip) to the URL.
950 $link .= sanitize_text_field($_SERVER['HTTP_HOST']);
951 // Append the requested resource location to the URL
952 $link .= sanitize_text_field($_SERVER['REQUEST_URI']);
953
954 return $link;
955 }
956
957 return '';
958 }
959
960 public static function is_pagespeed_enabled()
961 {
962 return defined('TW_NGX_PAGESPEED') && TW_NGX_PAGESPEED === 'enabled';
963 }
964
965 public static function is_pagespeed_lazyload_enabled()
966 {
967 return self::is_pagespeed_enabled() && defined('TW_NGX_PAGESPEED_FILTERS') && in_array('lazyload_images', TW_NGX_PAGESPEED_FILTERS, true);
968 }
969
970 public static function is_pagespeed_image_optimization_enables()
971 {
972 return self::is_pagespeed_enabled() && defined('TW_NGX_PAGESPEED_FILTERS') &&
973 (
974 in_array('convert_gif_to_png', TW_NGX_PAGESPEED_FILTERS, true) ||
975 in_array('recompress_png', TW_NGX_PAGESPEED_FILTERS, true) ||
976 in_array('convert_png_to_jpeg', TW_NGX_PAGESPEED_FILTERS, true) ||
977 in_array('convert_jpeg_to_progressive', TW_NGX_PAGESPEED_FILTERS, true) ||
978 in_array('recompress_jpeg', TW_NGX_PAGESPEED_FILTERS, true) ||
979 in_array('convert_jpeg_to_webp', TW_NGX_PAGESPEED_FILTERS, true) ||
980 in_array('convert_to_webp_lossless', TW_NGX_PAGESPEED_FILTERS, true) ||
981 in_array('convert_to_webp_animated', TW_NGX_PAGESPEED_FILTERS, true) ||
982 in_array('recompress_webp', TW_NGX_PAGESPEED_FILTERS, true) ||
983 in_array('inline_images', TW_NGX_PAGESPEED_FILTERS, true) ||
984 in_array('resize_images', TW_NGX_PAGESPEED_FILTERS, true)
985 );
986 }
987
988 public static function is_pagespeed_js_defer_enabled()
989 {
990 return self::is_pagespeed_enabled() && defined('TW_NGX_PAGESPEED_FILTERS') && in_array('defer_javascript', TW_NGX_PAGESPEED_FILTERS, true);
991 }
992
993 public static function purge_pagespeed_cache()
994 {
995 if (OptimizerUtils::is_pagespeed_enabled()) {
996 $url = rtrim(get_home_url(), '/') . '/*';
997 $response = wp_remote_request($url, ['method' => 'PURGE']);
998 }
999
1000 return true;
1001 }
1002
1003 /**
1004 * remove markers that serve_different_sizes_for_bg_image added for backgrounds
1005 *
1006 * @return string|string[]
1007 */
1008 public static function removeBgImageMarkers($css)
1009 {
1010 return str_replace(
1011 self::OPTIMIZED_BG_MARKER,
1012 '',
1013 str_replace(
1014 self::OPTIMIZED_BG_MARKER . self::SVG_DATA . '#}',
1015 '',
1016 $css
1017 )
1018 );
1019 }
1020
1021 /**
1022 * Run a match on the array's keys
1023 *
1024 * @param int $flags
1025 *
1026 * @return array
1027 */
1028 public static function preg_grep_keys($pattern, $input, $flags = 0)
1029 {
1030 return array_intersect_key($input, array_flip(preg_grep($pattern, array_keys($input), $flags)));
1031 }
1032
1033 /**
1034 * Checks if the current request is a WP REST API request.
1035 *
1036 * Case #1: After WP_REST_Request initialisation
1037 * Case #2: Support "plain" permalink settings
1038 * Case #3: It can happen that WP_Rewrite is not yet initialized,
1039 * so do this (wp-settings.php)
1040 * Case #4: URL Path begins with wp-json/ (your REST prefix)
1041 * Also supports WP installations in subfolders
1042 *
1043 * @returns boolean
1044 */
1045 public static function is_rest()
1046 {
1047 if ((defined('REST_REQUEST') && REST_REQUEST)
1048 || (isset($_GET['rest_route']))) { // phpcs:ignore
1049 return true;
1050 }
1051 global $wp_rewrite;
1052
1053 if ($wp_rewrite === null) {
1054 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1055 $wp_rewrite = new WP_Rewrite();
1056 }
1057
1058 $rest_url = wp_parse_url(trailingslashit(rest_url()));
1059 $current_url = wp_parse_url(add_query_arg([]));
1060
1061 return isset($current_url['path']) && strpos($current_url['path'], $rest_url['path'], 0) === 0;
1062 }
1063
1064 /**
1065 * Get parameters from a URL string
1066 *
1067 * @return bool|mixed
1068 */
1069 public static function get_url_query($url, $name)
1070 {
1071 $url_params = wp_parse_url($url);
1072
1073 if (is_array($url_params) && isset($url_params['query'])) {
1074 parse_str($url_params['query'], $query_array);
1075
1076 if (is_array($query_array) && isset($query_array[$name])) {
1077 $url_param = $query_array[$name];
1078
1079 return $url_param;
1080 }
1081 }
1082
1083 return false;
1084 }
1085
1086 /**
1087 * Remove domain part of a url
1088 *
1089 * @return string
1090 */
1091 public static function remove_domain_part($url)
1092 {
1093 $urlparts = wp_parse_url($url);
1094 $extracted = '';
1095
1096 if (isset($urlparts['path'])) {
1097 $extracted = $urlparts['path'];
1098 }
1099
1100 if (isset($urlparts['query'])) {
1101 $extracted .= '?' . $urlparts['query'];
1102 }
1103
1104 return $extracted;
1105 }
1106
1107 public static function get_javascipt_type($tag)
1108 {
1109 preg_match('/type="(.+?)"/', $tag, $matches);
1110
1111 return isset($matches[1]) ? $matches[1] : 'text/javascript';
1112 }
1113
1114 /**
1115 * Injects/replaces the given payload markup into `$this->content`
1116 * at the specified location.
1117 * If the specified tag cannot be found, the payload is appended into
1118 * $this->content along with a warning wrapped inside <!--noptimize--> tags.
1119 *
1120 * @param string $payload markup to inject
1121 * @param array $where Array specifying the tag name and method of injection.
1122 * Index 0 is the tag name (i.e., `</body>`).
1123 * Index 1 specifies ˛'before', 'after' or 'replace'. Defaults to 'before'.
1124 *
1125 * @return string
1126 */
1127 public static function inject_in_html($content, $payload, $where)
1128 {
1129
1130 if ($where[0] === '</body>') {
1131 $position_function = 'strrpos'; //choose the latest matching element
1132 } else {
1133 $position_function = 'strpos'; //choose the first matching element
1134 }
1135
1136 if ($where[0] === '<body') {
1137 $position = self::get_body_position($content);
1138 } else {
1139 $position = self::$position_function($content, $where[0]);
1140 }
1141
1142 if (false !== $position) {
1143 // Found the tag, setup content/injection as specified.
1144 if ('after' === $where[1]) {
1145 $replacement = $where[0] . $payload;
1146 } elseif ('replace' === $where[1]) {
1147 $replacement = $payload;
1148 } elseif ('after_tag' === $where[1]) {
1149 $afterTag = mb_substr($content, $position);
1150 $tagEndPosition = self::$position_function($afterTag, '>');
1151 $position = $position + $tagEndPosition;
1152 $replacement = '>' . $payload;
1153 } else {
1154 $replacement = $payload . $where[0];
1155 }
1156 $replacementLength = strlen($where[0]);
1157
1158 if ('after_tag' === $where[1]) {
1159 //count length of '>'
1160 $replacementLength = 1;
1161 }
1162 // Place where specified.
1163 $content = self::substr_replace($content, $replacement, $position, // Using plain strlen() should be safe here for now, since
1164 // we're not searching for multibyte chars here still...
1165 $replacementLength);
1166 } else {
1167 // Couldn't find what was specified, just append and add a warning.
1168 $content .= $payload;
1169 }
1170
1171 return $content;
1172 }
1173
1174 public static function isJson($string)
1175 {
1176 return is_string($string) && (is_object(json_decode($string)) || is_array(json_decode($string)));
1177 }
1178
1179 public static function findArr($arr, $field, $value)
1180 {
1181 foreach ($arr as $key => $inner_arr) {
1182 if ($inner_arr[$field] === $value) {
1183 return $arr[$key];
1184 }
1185 }
1186
1187 return false;
1188 }
1189
1190 public static function get_worker_script()
1191 {
1192 global $TwoSettings;
1193 $critical = new OptimizerCriticalCss();
1194 $merge_gf = '';
1195
1196 if ($TwoSettings->get_settings('two_merge_google_font_faces') === 'on') {
1197 // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown
1198 $merge_google_font_faces_js = file_get_contents(TENWEB_SO_PLUGIN_DIR . 'includes/external/js/two_merge_google_font_faces.js');
1199 $merge_gf = '<script ' . esc_attr(OptimizerScripts::TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE) . ' ' . OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE . ' type="text/javascript">
1200 ' . trim(JSMin::minify($merge_google_font_faces_js)) . '
1201 </script>';
1202 }
1203 $two_font_actions = $TwoSettings->get_settings('two_font_actions');
1204 // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown
1205 $two_worker_js = file_get_contents(TENWEB_SO_PLUGIN_DIR . 'includes/external/js/two_worker.js');
1206 // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown
1207 $two_delay_js = file_get_contents(TENWEB_SO_PLUGIN_DIR . 'includes/external/js/two_delay.js');
1208
1209 return '
1210 <script ' . esc_attr(OptimizerScripts::TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE) . ' ' . OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE . ' type="text/javascript">
1211
1212 </script>' . $merge_gf . '
1213 <script ' . esc_attr(OptimizerScripts::TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE) . ' ' . OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE . ' id="two_worker" type="javascript/worker">
1214 let two_font_actions = "' . $two_font_actions . '";
1215 ' . trim(JSMin::minify($two_worker_js)) . '
1216 </script>
1217 <script ' . esc_attr(OptimizerScripts::TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE) . ' ' . OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE . ' type="text/javascript">
1218 let two_font_actions = "' . $two_font_actions . '";
1219 ' . trim(JSMin::minify($two_delay_js)) . '
1220 </script>';
1221 }
1222
1223 public static function clear_iframe_src($content)
1224 {
1225 if (preg_match_all('#<iframe[^>]*src[^>]*>#Usmi', $content, $matches)) {
1226 // only used is image optimization is NOT active but lazyload is.
1227 foreach ($matches[0] as $tag) {
1228 $new_tag = str_replace(' src=', ' src="" data-two_src=', $tag);
1229 $content = str_replace($tag, $new_tag, $content);
1230 }
1231 }
1232 $tags_to_remove = [
1233 [
1234 'tag' => 'link',
1235 'attribute' => 'media',
1236 'value' => 'print',
1237 ],
1238 [
1239 'tag' => 'style',
1240 'attribute' => 'media',
1241 'value' => 'print',
1242 ],
1243 [
1244 'tag' => 'script',
1245 'attribute' => 'src',
1246 'value' => 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js',
1247 ],
1248 ];
1249 $regex_to_remove_tags = [];
1250
1251 foreach ($tags_to_remove as $tag) {
1252 $regex_to_remove_tags[] = '(<' . $tag[ 'tag' ] . '[^>]*' . $tag[ 'attribute' ] . '=[\'"]' . $tag[ 'value' ] . '[\'"].*\/(' . $tag[ 'tag' ] . ')?>)';
1253 }
1254
1255 if (preg_match_all('#' . implode('|', $regex_to_remove_tags) . '#Usmi', $content, $matches)) {
1256 foreach ($matches[0] as $tag) {
1257 $content = str_replace($tag, '', $content);
1258 }
1259 }
1260 $content = OptimizerUtils::inject_in_html($content, '<script ' . esc_attr(OptimizerScripts::TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE) . ' ' . esc_attr(OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE) . ' src="' . plugins_url('external/js/two_elementor_video_to_iframe.js', __FILE__) . '"></script>', ['</body>', 'before']);
1261 /*this code to remove all iframes */
1262 $content = OptimizerUtils::inject_in_html($content, '<script ' . esc_attr(OptimizerScripts::TWO_DISABLE_PAGESPEED_DEFER_ATTRIBUTE) . ' ' . esc_attr(OptimizerScripts::TWO_NO_DELAYED_JS_ATTRIBUTE) . '>
1263 const two_frames = window.frames;
1264 for (let i = 0; i < two_frames.length; i++) {
1265 two_frames[i].stop();
1266 }
1267 let clear_iframe_interval = setInterval(function(){
1268 const two_dom_frames = window.frames;
1269 for (let i = 0; i < two_dom_frames.length; i++) {
1270 two_dom_frames[i].stop();
1271 }
1272 },20);
1273 setTimeout(function(){
1274 clearInterval(clear_iframe_interval);
1275 },2000);
1276
1277 </script>', ['</body>', 'before']);
1278
1279 return $content;
1280 }
1281
1282 public static function split_css_to_arr($code)
1283 {
1284 $return_data = [];
1285 $return_data['font'] = $code;
1286 preg_match_all('#\bhttps?://[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $code, $match);
1287
1288 if (isset($match[0]) && !empty($match[0])) {
1289 $return_data['urls'] = $match[0];
1290 }
1291
1292 return $return_data;
1293 }
1294
1295 public static function get_default_critical_pages($status = false)
1296 {
1297 // This theme works better without critical. There is a CLS with uncritical loaded.
1298 $theme = wp_get_theme();
1299
1300 if ('Twenty Twenty-Two' == $theme->name) {
1301 $criticalPages = [];
1302 } else {
1303 global $TwoSettings;
1304
1305 $waitUntil = 'load';
1306 $loadType = 'load_type';
1307 $use_uncritical = 'off';
1308 $two_critical_default_settings = get_option('two_critical_default_settings');
1309
1310 if (isset($two_critical_default_settings['wait_until'])) {
1311 $waitUntil = $two_critical_default_settings['wait_until'];
1312 }
1313
1314 if (isset($two_critical_default_settings['load_type'])) {
1315 $loadType = $two_critical_default_settings['load_type'];
1316 }
1317
1318 if (isset($two_critical_default_settings['use_uncritical'])) {
1319 $use_uncritical = $two_critical_default_settings['use_uncritical'];
1320 }
1321 $page_sizes = OptimizerUtils::get_critical_default_sizes($two_critical_default_settings);
1322
1323 $homeUrl = get_home_url();
1324 $pageId = 'front_page';
1325 $criticalPages = [
1326 'front_page' => [
1327 'title' => 'Home',
1328 'url' => $homeUrl,
1329 'id' => $pageId,
1330 'sizes' => $page_sizes,
1331 'load_type' => $loadType,
1332 'wait_until' => $waitUntil,
1333 'use_uncritical' => $use_uncritical
1334 ]
1335 ];
1336
1337 if ($status) {
1338 $criticalPages['front_page']['status'] = 'in_progress';
1339 }
1340 }
1341
1342 return $criticalPages;
1343 }
1344
1345 /**
1346 * @param $regeneration_mode string 'front_page' will generate for front page only
1347 * @param bool $rightAfterConnect
1348 *
1349 * @return void
1350 */
1351 public static function regenerate_critical($regeneration_mode = 'front_page', $rightAfterConnect = false)
1352 {
1353 global $TwoSettings;
1354 //todo new_flow_process get and send flow_id
1355
1356 $two_critical_pages = OptimizerUtils::getCriticalPages();
1357 $url_query = $TwoSettings->get_settings('two_critical_url_args');
1358
1359 if (empty($two_critical_pages)) {
1360 $homeUrl = get_home_url();
1361 $pageId = 'front_page';
1362
1363 $waitUntil = 'load';
1364 $two_critical_default_settings = get_option('two_critical_default_settings');
1365
1366 if (isset($two_critical_default_settings['wait_until'])) {
1367 $waitUntil = $two_critical_default_settings['wait_until'];
1368 }
1369
1370 $page_sizes = OptimizerUtils::get_critical_default_sizes($two_critical_default_settings);
1371
1372 $data = [
1373 'action' => 'two_critical',
1374 'data' => [
1375 'page_url' => $homeUrl,
1376 'page_id' => $pageId,
1377 'page_sizes' => $page_sizes,
1378 'wait_until' => $waitUntil,
1379 'url_query' => $TwoSettings->get_settings('two_critical_url_args'),
1380 'task' => 'generate',
1381 'newly_connected_website' => $rightAfterConnect,
1382 ],
1383 'two_critical_sizes' => $TwoSettings->get_settings('two_critical_sizes'),
1384 'two_critical_pages' => self::get_default_critical_pages(true),
1385 ];
1386
1387 if ($rightAfterConnect) {
1388 $data['data']['flow_id'] = get_site_option(TENWEB_PREFIX . '_flow_id');
1389 }
1390 OptimizerCriticalCss::generateCriticalCSS($data);
1391
1392 if (OptimizerUtils::is_wpml_active()) {
1393 OptimizerUtils::generate_wpml_home_pages_critical_css($data);
1394 }
1395 } else {
1396 $regenerate_data = \TenWebWpTransients\OptimizerTransients::get('two_regenerate_critical_data');
1397
1398 if (is_array($regenerate_data) && !empty($regenerate_data)) {
1399 $two_critical_pages = $regenerate_data;
1400 } else {
1401 if ('front_page' == $regeneration_mode) {
1402 // Invalidate all critical css.
1403 self::update_critical_statuses($two_critical_pages, 'not_started');
1404
1405 // Regenerate front page only.
1406 foreach ($two_critical_pages as $key => $two_page) {
1407 if ('front_page' != $key) {
1408 unset($two_critical_pages[$key]);
1409 }
1410 }
1411 }
1412 self::update_critical_statuses($two_critical_pages, 'in_progress');
1413 }
1414 $two_critical_sizes = $TwoSettings->get_settings('two_critical_sizes');
1415
1416 if (!empty($two_critical_pages)) {
1417 $two_page = reset($two_critical_pages);
1418 $key = key($two_critical_pages);
1419 unset($two_critical_pages[$key]);
1420 $critical_sizes = [];
1421
1422 if (isset($two_page['sizes']) && is_array($two_page['sizes'])) {
1423 foreach ($two_page['sizes'] as $size_id) {
1424 if (isset($size_id['uid'], $two_critical_sizes[$size_id['uid']]) && is_array($size_id)) {
1425 $critical_sizes[] = $two_critical_sizes[$size_id['uid']];
1426 }
1427 }
1428 }
1429 $data = [
1430 'data' => [
1431 'page_url' => $two_page['url'],
1432 'page_id' => $two_page['id'],
1433 'page_sizes' => $critical_sizes,
1434 'wait_until' => $two_page['wait_until'],
1435 'url_query' => $url_query,
1436 'task' => 'generate',
1437 'newly_connected_website' => $rightAfterConnect,
1438 ],
1439 ];
1440
1441 if (isset($two_page['use_uncritical'])) {
1442 $data['data']['use_uncritical'] = $two_page['use_uncritical'];
1443 }
1444 OptimizerCriticalCss::generateCriticalCSS($data);
1445 $two_critical_pages_count = count($two_critical_pages);
1446 \TenWebWpTransients\OptimizerTransients::set('two_regenerate_critical_data', $two_critical_pages, 60 * $two_critical_pages_count);
1447 }
1448 }
1449 }
1450
1451 public static function get_critical_default_sizes($critical_defaults)
1452 {
1453 global $TwoSettings;
1454 $new_sizes = [];
1455 $two_critical_sizes = $TwoSettings->get_settings('two_critical_sizes');
1456
1457 if (is_array($critical_defaults) && isset($critical_defaults['default_sizes'])) {
1458 foreach ($critical_defaults['default_sizes'] as $key) {
1459 if (isset($two_critical_sizes[$key])) {
1460 $new_sizes[$key] = $two_critical_sizes[$key];
1461 }
1462 }
1463 }
1464
1465 if (!empty($new_sizes)) {
1466 return array_keys($new_sizes);
1467 }
1468
1469 return array_keys($two_critical_sizes);
1470 }
1471
1472 public static function update_critical_statuses($two_critical_pages, $status)
1473 {
1474 global $TwoSettings;
1475
1476 foreach ($two_critical_pages as $key => $two_page) {
1477 $two_critical_pages[$key]['status'] = $status;
1478 }
1479 $TwoSettings->update_setting('two_critical_pages', $two_critical_pages);
1480 }
1481
1482 public static function init_defines()
1483 {
1484 global $TwoSettings;
1485
1486 if (!defined('TWO_LAZYLOAD')) {
1487 define('TWO_LAZYLOAD', true);
1488 }
1489
1490 if (!defined('TWO_WP_SITE_URL')) {
1491 if (function_exists('domain_mapping_siteurl')) {
1492 define('TWO_WP_SITE_URL', domain_mapping_siteurl(get_current_blog_id()));
1493 } else {
1494 define('TWO_WP_SITE_URL', site_url());
1495 }
1496 }
1497
1498 if (!defined('TWO_WP_CONTENT_URL')) {
1499 if (function_exists('get_original_url')) {
1500 define('TWO_WP_CONTENT_URL', str_replace(get_original_url(TWO_WP_SITE_URL), TWO_WP_SITE_URL, content_url()));
1501 } else {
1502 define('TWO_WP_CONTENT_URL', content_url());
1503 }
1504 }
1505
1506 if (!defined('TWO_WP_CONTENT_NAME')) {
1507 define('TWO_WP_CONTENT_NAME', '/' . wp_basename(WP_CONTENT_DIR));
1508 }
1509
1510 if (!defined('TWO_WP_ROOT_URL')) {
1511 define('TWO_WP_ROOT_URL', str_replace(TWO_WP_CONTENT_NAME, '', TWO_WP_CONTENT_URL));
1512 }
1513
1514 if (!defined('TWO_CACHE_URL')) {
1515 if (is_multisite()) {
1516 $blog_id = get_current_blog_id();
1517 define('TWO_CACHE_URL', TWO_WP_CONTENT_URL . TENWEB_SO_CACHE_CHILD_DIR . $blog_id . '/');
1518 } else {
1519 define('TWO_CACHE_URL', TWO_WP_CONTENT_URL . TENWEB_SO_CACHE_CHILD_DIR);
1520 }
1521 }
1522
1523 if (!defined('WP_ROOT_DIR')) {
1524 define('WP_ROOT_DIR', substr(WP_CONTENT_DIR, 0, strlen(WP_CONTENT_DIR) - strlen(TWO_WP_CONTENT_NAME)));
1525 }
1526
1527 if (!defined('TWO_HASH')) {
1528 define('TWO_HASH', wp_hash(TWO_CACHE_URL));
1529 }
1530
1531 if (!defined('TWO_CACHE_NOGZIP')) {
1532 $two_gzip = $TwoSettings->get_settings('two_gzip');
1533
1534 if (!TENWEB_SO_HOSTED_ON_10WEB && $two_gzip === 'on') {
1535 define('TWO_CACHE_NOGZIP', false);
1536 } else {
1537 define('TWO_CACHE_NOGZIP', true);
1538 }
1539 }
1540
1541 if (!defined('TWO_CACHEFILE_PREFIX')) {
1542 define('TWO_CACHEFILE_PREFIX', 'two_');
1543 }
1544 }
1545
1546 public static function flushCloudflareCache($postId = null)
1547 {
1548 if (class_exists('\CF\WordPress\Hooks')) {
1549 $cloudflareHooks = new \CF\WordPress\Hooks();
1550
1551 if (is_int($postId)) {
1552 $cloudflareHooks->purgeCacheByRelevantURLs($postId);
1553 } else {
1554 $cloudflareHooks->purgeCacheEverything();
1555 }
1556 }
1557 }
1558
1559 public static function get_tenweb_connection_link($endpoint = 'sign-up', $args = [])
1560 {
1561 // copied from manager.py
1562 $return_url = get_admin_url() . 'admin.php';
1563
1564 if (is_multisite()) {
1565 $return_url = network_admin_url() . 'admin.php';
1566 }
1567
1568 $token = wp_create_nonce('two_10web_connection');
1569 update_site_option(TW_OPTIMIZE_PREFIX . '_saved_nonce', $token);
1570 $return_url_args = ['page' => 'two_settings_page'];
1571 $register_url_args = [
1572 'site_url' => urlencode(get_site_url()),
1573 'utm_source' => '10webspeedoptimizer',
1574 'from_plugin' => self::FROM_PLUGIN,
1575 'utm_medium' => 'freeplugin',
1576 'nonce' => $token,
1577 'subscr_id' => TENWEB_SO_FREE_SUBSCRIPTION_ID,
1578 'version' => TENWEB_SO_VERSION,
1579 'new_connection_flow' => 1,
1580 ];
1581
1582 if (!empty($args['old_connection_flow'])) {
1583 unset($register_url_args['new_connection_flow']);
1584 }
1585
1586 if (!empty($args)) {
1587 $register_url_args = $register_url_args + $args;
1588 $return_url_args = $return_url_args + $args;
1589 }
1590
1591 $register_url_args['return_url'] = urlencode(add_query_arg($return_url_args, $return_url));
1592
1593 $plugin_from = get_site_option('tenweb_manager_installed');
1594
1595 if ($plugin_from !== false) {
1596 $plugin_from = json_decode($plugin_from, true);
1597
1598 if (is_array($plugin_from) && reset($plugin_from) !== false) {
1599 $register_url_args['plugin_id'] = reset($plugin_from);
1600
1601 if (isset($plugin_from['type'])) {
1602 $register_url_args['utm_source'] = $plugin_from['type'];
1603 }
1604 }
1605 }
1606
1607 $url = add_query_arg($register_url_args, TENWEB_DASHBOARD . '/' . $endpoint . '/');
1608
1609 return $url;
1610 }
1611
1612 public static function getCriticalPages()
1613 {
1614 global $TwoSettings;
1615
1616 if (empty($TwoSettings)) {
1617 return [];
1618 }
1619 $two_critical_pages_from_options = $TwoSettings->get_settings('two_critical_pages');
1620 $two_critical_pages = self::get_meta_values('two_critical_pages');
1621
1622 if ($two_critical_pages_from_options) {
1623 $two_critical_pages = array_replace($two_critical_pages_from_options, $two_critical_pages);
1624 }
1625
1626 return $two_critical_pages;
1627 }
1628
1629 public static function stripslashes_deep($value)
1630 {
1631 // copied from wp-includes/formatting.php
1632 return self::map_deep($value, function ($value) {
1633 return is_string($value) ? stripslashes($value) : $value;
1634 });
1635 }
1636
1637 public static function map_deep($value, $callback)
1638 {
1639 // copied from wp-includes/formatting.php
1640
1641 if (is_array($value)) {
1642 foreach ($value as $index => $item) {
1643 $value[$index] = self::map_deep($item, $callback);
1644 }
1645 } elseif (is_object($value)) {
1646 $object_vars = get_object_vars($value);
1647
1648 foreach ($object_vars as $property_name => $property_value) {
1649 $value->$property_name = self::map_deep($property_value, $callback);
1650 }
1651 } else {
1652 $value = call_user_func($callback, $value);
1653 }
1654
1655 return $value;
1656 }
1657
1658 public static function get_meta_values($key = '')
1659 {
1660 if (empty($key)) {
1661 return null;
1662 }
1663 global $wpdb;
1664
1665 $query = $wpdb->get_results($wpdb->prepare("SELECT post_id, meta_value FROM $wpdb->postmeta WHERE meta_key = %s", $key)); // phpcs:ignore
1666 $result = [];
1667
1668 foreach ($query as $row) {
1669 $result[ $row->post_id ] = unserialize($row->meta_value); // phpcs:ignore
1670 }
1671
1672 return $result;
1673 }
1674
1675 public static function check_if_hosted_website()
1676 {
1677 if (is_file(WPMU_PLUGIN_DIR . '/10web-manager/10web-manager.php')) {
1678 return true;
1679 }
1680
1681 return false;
1682 }
1683
1684 /* WPML functions*/
1685 public static function get_wpml_home_urls()
1686 {
1687 if (!OptimizerUtils::is_wpml_active()) {
1688 return [];
1689 }
1690
1691 $front_page_id = get_option('page_on_front');
1692
1693 // if $front_page_id is empty or 0, it means home page is archive page and there is no translation for that page
1694 if (!$front_page_id) {
1695 return [];
1696 }
1697
1698 $element_id = $front_page_id;
1699 $element_type = get_post_type($front_page_id);
1700
1701 $home_pages = [];
1702 $languages = apply_filters('wpml_active_languages', '');
1703
1704 foreach ($languages as $lang_code => $language_data) {
1705 $post_id = apply_filters('wpml_object_id', $element_id, $element_type, false, $lang_code);
1706
1707 if (!$post_id) {
1708 continue;
1709 }
1710 $home_pages[$lang_code] = [
1711 'post_id' => $post_id,
1712 'permalink' => get_permalink($post_id),
1713 'title' => get_the_title($post_id)
1714 ];
1715 }
1716
1717 return $home_pages;
1718 }
1719
1720 public static function add_wpml_home_pages_into_critical_pages($critical_pages = null, $home_url = null)
1721 {
1722 /*
1723 * The function added home pages generated by WPML into critical_pages list, if home page is in that list. The
1724 * function doesn't generate critical css.
1725 * */
1726
1727 global $TwoSettings;
1728
1729 if ($critical_pages === null) {
1730 $critical_pages = OptimizerUtils::getCriticalPages();
1731 }
1732
1733 if ($home_url === null && $critical_pages['front_page']) {
1734 $home_url = $critical_pages['front_page']['url'];
1735 }
1736
1737 if (!$home_url) {
1738 return $critical_pages;
1739 }
1740
1741 foreach (OptimizerUtils::get_wpml_home_urls() as $lang_code => $post_data) {
1742 if (rtrim($post_data['permalink'], '/') === rtrim($home_url, '/')) {
1743 continue;
1744 }
1745
1746 if (isset($critical_pages[$post_data['id']])) {
1747 continue;
1748 }
1749
1750 $page_data = $critical_pages['front_page'];
1751 $page_data['title'] = $post_data['title'];
1752 $page_data['url'] = $post_data['permalink'];
1753 $page_data['id'] = $post_data['post_id'];
1754 $critical_pages[$post_data['post_id']] = $page_data;
1755 }
1756
1757 $TwoSettings->update_setting('two_critical_pages', $critical_pages);
1758
1759 return $critical_pages;
1760 }
1761
1762 public static function generate_wpml_home_pages_critical_css($data)
1763 {
1764 self::add_wpml_home_pages_into_critical_pages();
1765
1766 $wpml_data = ['data' => $data['data']];
1767 $home_page_url = $data['data']['page_url'];
1768
1769 foreach (self::getCriticalPages() as $page) {
1770 if ($page['url'] == $home_page_url) {
1771 continue;
1772 }
1773
1774 $is_wpml_page = false;
1775
1776 foreach (self::get_wpml_home_urls() as $wpml_page) {
1777 if ($wpml_page['post_id'] == $page['id']) {
1778 $is_wpml_page = true;
1779 break;
1780 }
1781 }
1782
1783 if ($is_wpml_page === false) {
1784 continue;
1785 }
1786
1787 $wpml_data['data']['page_url'] = $page['url'];
1788 $wpml_data['data']['page_id'] = $page['id'];
1789 OptimizerCriticalCss::generateCriticalCSS($wpml_data);
1790 }
1791 }
1792
1793 public static function get_wpml_post_flag_url($post_id)
1794 {
1795 if ($post_id == 'front_page') {
1796 $post_id = get_option('page_on_front');
1797 }
1798
1799 if (!$post_id) {
1800 $lang_code = apply_filters('wpml_default_language', null);
1801 } else {
1802 $post_language_details = apply_filters('wpml_post_language_details', null, $post_id);
1803 $lang_code = $post_language_details['language_code'];
1804 }
1805
1806 return plugins_url('sitepress-multilingual-cms/res/flags/' . $lang_code . '.png');
1807 }
1808
1809 public static function is_wpml_active()
1810 {
1811 return defined('ICL_SITEPRESS_VERSION');
1812 }
1813
1814 public static function get_modes($name = null, $preview = false, $level = null)
1815 {
1816 $modes = self::MODES;
1817
1818 if ($preview) {
1819 foreach ($modes as $key => $mode) {
1820 $modes[$key]['preview_url'] = add_query_arg(['two_preview' => '1', 'two_level' => $mode['level']], get_home_url() . '/');
1821 }
1822
1823 return $modes;
1824 }
1825
1826 if (isset($name)) {
1827 return self::MODES[$name];
1828 } elseif (isset($level)) {
1829 foreach ($modes as $key => $mode) {
1830 if ($mode['level'] === $level) {
1831 return $mode;
1832 }
1833 }
1834 }
1835 $global_mode = get_option('two_default_mode', OptimizerUtils::MODES['extreme']);
1836
1837 if (is_array($global_mode)) {
1838 $modes['global'] = $global_mode;
1839 }
1840
1841 return $modes;
1842 }
1843
1844 public static function testWebPDelivery()
1845 {
1846 $requestUrl = TENWEB_SO_URL . '/test/webp_test.jpg';
1847 $requestArgs = [
1848 'headers' => [
1849 'ACCEPT' => 'image/webp'
1850 ]
1851 ];
1852 global $TwoSettings;
1853 $wpResult = wp_remote_get($requestUrl, $requestArgs); // phpcs:ignore
1854
1855 if (!is_wp_error($wpResult)) {
1856 if (isset($wpResult['headers']['content-type']) && 'image/webp' === $wpResult['headers']['content-type']) {
1857 $TwoSettings->update_setting('two_webp_delivery_working', '1');
1858
1859 return true;
1860 }
1861 }
1862 $TwoSettings->update_setting('two_webp_delivery_working', '0');
1863
1864 return false;
1865 }
1866
1867 public static function clear_third_party_cache()
1868 {
1869 global $wp_fastest_cache, $kinsta_cache, $nginx_purger;
1870
1871 // if W3 Total Cache is being used, clear the cache
1872 if (function_exists('w3tc_pgcache_flush')) {
1873 w3tc_pgcache_flush();
1874 }
1875
1876 // if WP Super Cache is being used, clear the cache
1877 if (function_exists('wp_cache_clean_cache')) {
1878 global $file_prefix, $supercachedir;
1879
1880 if (empty($supercachedir) && function_exists('get_supercache_dir')) {
1881 $supercachedir = get_supercache_dir();
1882 }
1883 wp_cache_clean_cache($file_prefix);
1884 }
1885
1886 if (class_exists('WpeCommon')) {
1887 //be extra careful, just in case 3rd party changes things on us
1888 if (method_exists('WpeCommon', 'purge_memcached')) {
1889 WpeCommon::purge_memcached();
1890 }
1891
1892 if (method_exists('WpeCommon', 'clear_maxcdn_cache')) {
1893 WpeCommon::clear_maxcdn_cache();
1894 }
1895
1896 if (method_exists('WpeCommon', 'purge_varnish_cache')) {
1897 WpeCommon::purge_varnish_cache();
1898 }
1899 }
1900
1901 if (method_exists('WpFastestCache', 'deleteCache') && !empty($wp_fastest_cache)) {
1902 $wp_fastest_cache->deleteCache(true);
1903 }
1904
1905 if (class_exists('\Kinsta\Cache') && !empty($kinsta_cache) && !empty($kinsta_cache->kinsta_cache_purge)) {
1906 if (method_exists($kinsta_cache->kinsta_cache_purge, 'purge_complete_caches')) {
1907 $kinsta_cache->kinsta_cache_purge->purge_complete_caches();
1908 }
1909 }
1910
1911 if (class_exists('\WPaaS\Cache')) {
1912 if (!\WPaaS\Cache::has_ban()) {
1913 remove_action('shutdown', [ '\WPaaS\Cache', 'purge' ], PHP_INT_MAX);
1914 add_action('shutdown', [ '\WPaaS\Cache', 'ban' ], PHP_INT_MAX);
1915 }
1916 }
1917
1918 if (class_exists('WP_Optimize') && defined('WPO_PLUGIN_MAIN_PATH')) {
1919 if (!class_exists('WP_Optimize_Cache_Commands')) {
1920 include_once WPO_PLUGIN_MAIN_PATH . 'cache/class-cache-commands.php';
1921 }
1922
1923 if (class_exists('WP_Optimize_Cache_Commands')) {
1924 $wpoptimize_cache_commands = new WP_Optimize_Cache_Commands();
1925 $wpoptimize_cache_commands->purge_page_cache();
1926 }
1927 }
1928
1929 if (class_exists('Breeze_Admin')) {
1930 do_action('breeze_clear_all_cache');
1931 }
1932
1933 if (defined('LSCWP_V')) {
1934 do_action('litespeed_purge_all');
1935 }
1936
1937 // This function clears the Site Ground cache only if it is on in plugin.
1938 // As their hosting does not respect plugin's settings sometimes, maybe we'll need to call them directly.
1939 if (function_exists('sg_cachepress_purge_everything')) {
1940 sg_cachepress_purge_everything();
1941 }
1942
1943 if (class_exists('autoptimizeCache')) {
1944 autoptimizeCache::clearall();
1945 }
1946
1947 if (class_exists('Cache_Enabler')) {
1948 Cache_Enabler::clear_total_cache();
1949 }
1950
1951 if (defined('NGINX_HELPER_BASEPATH') && !empty($nginx_purger)) {
1952 $nginx_purger->purge_all();
1953 }
1954
1955 if (function_exists('rocket_clean_domain')) {
1956 rocket_clean_domain();
1957 }
1958
1959 if (defined('EZOIC_CACHE') && EZOIC_CACHE) {
1960 $cache = new \Ezoic_Namespace\Ezoic_Integration_Cache();
1961 $cache->Clear();
1962 }
1963
1964 if (class_exists('Endurance_Page_Cache')) {
1965 $epc = new Endurance_Page_Cache();
1966 $epc->force_purge = true;
1967 $epc->purge_all();
1968 }
1969 }
1970
1971 public static function two_flatten(array $array)
1972 {
1973 $return = [];
1974 array_walk_recursive($array, function ($a) use (&$return) {
1975 $return[] = $a;
1976 });
1977
1978 return $return;
1979 }
1980
1981 public static function triggerPostOptimizationTasks($disabled_incompatible_plugins = [])
1982 {
1983 //todo new_flow_process this should be triggered manually when user clicks the apply mode button
1984 //todo new_flow_process disable test mode, apply mode, call post-optimization
1985 global $TwoSettings;
1986 $flow_id = get_site_option(TENWEB_PREFIX . '_flow_id');
1987 $domain_id = get_site_option('tenweb_domain_id');
1988 $access_token = get_site_option(TENWEB_PREFIX . '_access_token');
1989 $two_conflicting_plugins = get_site_option('two_conflicting_plugins');
1990
1991 if (!is_array($two_conflicting_plugins)) {
1992 $two_conflicting_plugins = [];
1993 }
1994
1995 if ($access_token && $domain_id) {
1996 $response = wp_remote_post(TENWEB_SO_CRITICAL_URL . '/v1/workspaces/performance/' . $domain_id . '/post-optimization-tasks', [
1997 'redirection' => 15,
1998 'blocking' => false,
1999 'headers' => [
2000 'accept' => 'application/x.10webperformance.v1+json',
2001 'authorization' => 'Bearer ' . $access_token,
2002 ],
2003 'body' => [
2004 'notification_id' => sanitize_text_field($_POST['notification_id']), // phpcs:ignore
2005 'flow_id' => $flow_id,
2006 'disabled_incompatible_plugins' => $disabled_incompatible_plugins,
2007 'incompatible_plugins' => $two_conflicting_plugins,
2008 'has_excluded_slider' => false
2009 ],
2010 'cookies' => []
2011 ]);
2012
2013 update_option('two_critical_data_import_response_' . time(), [
2014 !is_wp_error($response) ? $response['body'] : $response->get_error_message(),
2015 wp_remote_retrieve_response_code($response)
2016 ], false);
2017 }
2018 }
2019
2020 public static function check_score()
2021 {
2022 $domain_id = get_site_option('tenweb_domain_id');
2023 $access_token = get_site_option(TENWEB_PREFIX . '_access_token');
2024 $workspace_id = get_site_option(TENWEB_PREFIX . '_workspace_id');
2025
2026 if ($access_token && $domain_id && $workspace_id) {
2027 wp_remote_post(TENWEB_SO_CRITICAL_URL . '/v1/workspaces/' . $workspace_id . '/performance/' . $domain_id . '/googlepagespeed', [
2028 'redirection' => 15,
2029 'blocking' => false,
2030 'headers' => [
2031 'accept' => 'application/x.10webperformance.v1+json',
2032 'authorization' => 'Bearer ' . $access_token,
2033 ],
2034 'body' => [
2035 ],
2036 'cookies' => []
2037 ]);
2038 }
2039 }
2040
2041 public static function set_global_mode()
2042 {
2043 global $TwoSettings;
2044 $modes = self::get_modes();
2045 $two_critical_status = $TwoSettings->get_settings('two_critical_status');
2046 $two_delay_all_js_execution = $TwoSettings->get_settings('two_delay_all_js_execution');
2047
2048 if ($two_critical_status == 'true' && $two_delay_all_js_execution == 'on') {
2049 $mode = 'extreme';
2050 } elseif ($two_critical_status == 'true' && $two_delay_all_js_execution != 'on') {
2051 $mode = 'balanced';
2052 } elseif ($two_critical_status != 'true' && $two_delay_all_js_execution === 'on') {
2053 $mode = 'strong';
2054 } else {
2055 $mode = 'standard';
2056 }
2057
2058 if (isset($modes[$mode])) {
2059 foreach ($modes[$mode] as $key => $val) {
2060 if (isset($settings_names[$key])) {
2061 $TwoSettings->update_setting($key, $val);
2062 } elseif ($key === 'critical_enabled') {
2063 if ($val) {
2064 $TwoSettings->update_setting('two_critical_status', 'true');
2065 } else {
2066 $TwoSettings->update_setting('two_critical_status', '');
2067 }
2068 }
2069 }
2070 update_option('two_default_mode', $modes[$mode]);
2071 }
2072 }
2073
2074 /**
2075 * @return int|string page_id|front_page|term_$term_id
2076 */
2077 public static function get_current_post_info()
2078 {
2079 $queried_object = get_queried_object();
2080 $id = 0;
2081
2082 if (is_front_page()) {
2083 $id = 'front_page';
2084 } elseif (class_exists('WooCommerce') && is_shop()) {
2085 $id = wc_get_page_id('shop');
2086 } elseif ($queried_object instanceof WP_Term) {
2087 $id = 'term_' . $queried_object->term_id;
2088 } elseif ($queried_object instanceof WP_User) {
2089 $id = 'user_' . $queried_object->ID;
2090 } elseif ($queried_object instanceof \WP_Post) {
2091 $id = $queried_object->ID;
2092 }
2093
2094 if ($id === 0) {
2095 global $wp;
2096 $home_url = trailingslashit(home_url($wp->request));
2097 $db_home_url = trailingslashit(get_home_url());
2098
2099 if ($home_url === $db_home_url) {
2100 $id = 'front_page';
2101 }
2102 }
2103
2104 if ($id === 0 && isset($queried_object->has_archive)) {
2105 global $TwoSettings;
2106
2107 if ($TwoSettings->get_settings('two_optimize_archive_pages') == 'on') {
2108 $id = 'no_critical_' . md5($home_url);
2109 }
2110 }
2111
2112 return $id;
2113 }
2114
2115 public static function get_permalink_name_by_id($id = 'front_page')
2116 {
2117 if ('front_page' === $id) {
2118 return [ 'url' => home_url(), 'title' => 'Home' ];
2119 } else {
2120 if (0 === strpos($id, 'term_')) {
2121 $term_id = (int) ltrim ($id, 'term_');
2122 $term = get_term($term_id);
2123
2124 return [ 'url' => get_term_link($term_id), 'title' => $term->name ];
2125 } elseif (0 === strpos($id, 'user_')) {
2126 $user_id = (int) ltrim ($id, 'user_');
2127
2128 return [ 'url' => get_author_posts_url($user_id), 'title' => get_the_author_meta('nicename', $user_id) ];
2129 } else {
2130 return [ 'url' => get_permalink($id), 'title' => get_the_title($id) ];
2131 }
2132 }
2133 }
2134
2135 public static function remove_url_protocol($url)
2136 {
2137 $url = rtrim($url, '/');
2138 $disallowed = ['http://www.', 'https://www.', 'http://', 'https://'];
2139
2140 foreach ($disallowed as $d) {
2141 if (strpos($url, $d) === 0) {
2142 return str_replace($d, '', $url);
2143 }
2144 }
2145
2146 return $url;
2147 }
2148
2149 public static function get_post_id($page_url = null)
2150 {
2151 $pageUrlHash = md5((string) $page_url); // this function is called in a loop to replace backgrounds, in order not to make a thousand requests to website to find page_id we will cache it in globals
2152 //phpcs:ignore PHPCompatibility.Variables.ForbiddenGlobalVariableVariable.NonBareVariableFound
2153 global ${'two_current_page_id' . $pageUrlHash};
2154
2155 if (!empty(${'two_current_page_id' . $pageUrlHash})) {
2156 return ${'two_current_page_id' . $pageUrlHash};
2157 }
2158 $home_url = rtrim(get_home_url(), '/');
2159 $page_url = rtrim((string) $page_url, '/');
2160 $id = 0;
2161
2162 if (!empty($page_url)) {
2163 if (self::remove_url_protocol($home_url) === self::remove_url_protocol($page_url)) {
2164 return 'front_page';
2165 }
2166 $id = url_to_postid($page_url); // phpcs:ignore
2167
2168 if ($id === 0 && class_exists('WooCommerce')) {
2169 $shop_page_id = wc_get_page_id('shop');
2170 $shop_page_url = rtrim(get_permalink(wc_get_page_id('shop')), '/');
2171
2172 if ($shop_page_url === $page_url) {
2173 $id = $shop_page_id;
2174 }
2175 }
2176
2177 if ($id === 0) {
2178 $page_for_posts = get_option('page_for_posts');
2179 $post_page_id = (int) $page_for_posts;
2180 $post_page_url = rtrim(get_permalink($post_page_id), '/');
2181
2182 if ($post_page_url === $page_url) {
2183 $id = $post_page_id;
2184 }
2185 }
2186
2187 if (0 === $id) {
2188 $page_url_host = wp_parse_url($page_url, PHP_URL_HOST);
2189 $site_url_host = wp_parse_url(get_site_url(), PHP_URL_HOST);
2190
2191 if ($page_url_host === $site_url_host) {
2192 $page_headers = wp_get_http_headers(add_query_arg([ 'two_detect_post_id' => '1' ], $page_url));
2193 $id = isset($page_headers[ 'x-two-post-id' ]) ? $page_headers[ 'x-two-post-id' ] : 0;
2194 }
2195 }
2196 ${'two_current_page_id' . $pageUrlHash} = $id;
2197 } else {
2198 ${'two_current_page_id' . $pageUrlHash} = self::get_current_post_info();
2199 }
2200
2201 return ${'two_current_page_id' . $pageUrlHash};
2202 }
2203
2204 public static function two_update_subscription()
2205 {
2206 $tenweb_subscription_id = false;
2207 $tenweb_plan_title = false;
2208 $domain_id = get_site_option('tenweb_domain_id');
2209 $access_token = get_site_option(TENWEB_PREFIX . '_access_token');
2210 $workspace_id = get_site_option(TENWEB_PREFIX . '_workspace_id');
2211
2212 if (!empty($access_token) && !empty($domain_id) && !empty($workspace_id)) {
2213 $response = wp_remote_post(TENWEB_SO_CRITICAL_URL . '/v1/workspaces/' . $workspace_id . '/domains/' . $domain_id . '/get_subscription', [
2214 'timeout' => 5, // phpcs:ignore
2215 'redirection' => 5,
2216 'httpversion' => '1.0',
2217 'blocking' => true,
2218 'headers' => [
2219 'accept' => 'application/x.10webperformance.v1+json',
2220 'authorization' => 'Bearer ' . $access_token,
2221 ],
2222 'body' => [],
2223 'cookies' => []
2224 ]);
2225
2226 if (!is_wp_error($response) && isset($response['body'], $response['response']['code']) && $response['response']['code'] === 200) {
2227 $response_body = json_decode($response['body'], true);
2228
2229 if ($response_body['data']['success']) {
2230 if (isset($response_body['data']['subscription_id'])) {
2231 $tenweb_subscription_id = $response_body['data']['subscription_id'];
2232 \TenWebWpTransients\OptimizerTransients::set(TENWEB_PREFIX . '_subscription_id', $tenweb_subscription_id, 12 * HOUR_IN_SECONDS);
2233 }
2234
2235 if (isset($response_body['data']['plan_title'])) {
2236 $tenweb_plan_title = $response_body['data']['plan_title'];
2237 $tenweb_plan_title = strtolower($tenweb_plan_title) == 'speed' ? 'Free' : $tenweb_plan_title; //sometimes we get 'speed' from service, it means free
2238
2239 if (in_array((int) $tenweb_subscription_id, TENWEB_SO_FREE_SUBSCRIPTION_IDS)) {
2240 $tenweb_plan_title = 'Free';
2241 }
2242 \TenWebWpTransients\OptimizerTransients::set(TENWEB_PREFIX . '_plan_title', $tenweb_plan_title, 12 * HOUR_IN_SECONDS);
2243 }
2244
2245 if (isset($response_body['data']['referral_hash'])) {
2246 $referral_hash = $response_body['data']['referral_hash'];
2247
2248 if (!empty($referral_hash)) {
2249 update_site_option(TENWEB_PREFIX . '_client_referral_hash', $referral_hash);
2250 }
2251 }
2252 }
2253 } elseif (is_wp_error($response) || !isset($response['body'], $response['response']['code']) || (string) $response['response']['code'] !== '429') {
2254 \TenWebWpTransients\OptimizerTransients::set(TENWEB_PREFIX . '_subscription_id', '0', HOUR_IN_SECONDS);
2255 \TenWebWpTransients\OptimizerTransients::set(TENWEB_PREFIX . '_plan_title', '', HOUR_IN_SECONDS);
2256 }
2257
2258 if (!is_wp_error($response) && isset($response['response']['code'])) {
2259 \TenWebWpTransients\OptimizerTransients::set(TENWEB_PREFIX . '_subscription_response_code', (string) $response['response']['code'], HOUR_IN_SECONDS);
2260 }
2261 }
2262
2263 return compact('tenweb_subscription_id', 'tenweb_plan_title');
2264 }
2265
2266 public static function two_critical_status($page_id = false)
2267 {
2268 global $TwoSettings;
2269 $two_critical_pages = self::getCriticalPages();
2270
2271 if (is_array($two_critical_pages)) {
2272 if ($page_id === false) {
2273 foreach ($two_critical_pages as $critical_page) {
2274 self::two_critical_status($critical_page['id']);
2275 }
2276 } elseif (isset($two_critical_pages[$page_id])) {
2277 $critical_in_progress_key = 'two_critical_in_progress_' . $page_id;
2278 $critical_in_progress = \TenWebWpTransients\OptimizerTransients::get($critical_in_progress_key);
2279
2280 if ($critical_in_progress !== '1') {
2281 if (isset($two_critical_pages[$page_id]['status']) && $two_critical_pages[$page_id]['status'] == 'in_progress') {
2282 $two_critical_pages[$page_id]['status'] = 'not_started';
2283 $TwoSettings->update_setting('two_critical_pages', $two_critical_pages);
2284 }
2285 }
2286 }
2287 }
2288 }
2289
2290 public static function two_redirect($url, $exit = true)
2291 {
2292 while (ob_get_level() !== 0) {
2293 ob_end_clean();
2294 }
2295 wp_redirect($url); //phpcs:ignore WordPressVIPMinimum.Security.ExitAfterRedirect.NoExit
2296
2297 if ($exit) {
2298 exit();
2299 }
2300 }
2301
2302 public static function check_page_has_no_redirects($url, $arg = true)
2303 {
2304 if ($arg) {
2305 $url = add_query_arg([
2306 'two_check_redirect' => '1',
2307 ], $url);
2308 }
2309 $response = wp_remote_head($url, [
2310 'timeout' => 20, // phpcs:ignore
2311 'redirection' => 0,
2312 'httpversion' => '1.0',
2313 'blocking' => true,
2314 'sslverify' => false
2315 ]);
2316
2317 if (!is_wp_error($response)) {
2318 $headers = $response[ 'headers' ];
2319
2320 if (isset($headers[ 'Location' ])) {
2321 $location = $headers[ 'Location' ];
2322
2323 if (!empty($location)) {
2324 if (is_array($location)) {
2325 $location = end($location);
2326 }
2327 $redirect_url_parse = wp_parse_url($location);
2328 $main_url_parse = wp_parse_url($url);
2329
2330 if (isset($redirect_url_parse[ 'host' ]) && isset($main_url_parse[ 'host' ])) {
2331 if ($redirect_url_parse[ 'host' ] === $main_url_parse[ 'host' ]) {
2332 return true;
2333 }
2334 }
2335
2336 return false;
2337 }
2338
2339 return false;
2340 }
2341 }
2342
2343 return true;
2344 }
2345
2346 public static function is_paid_user()
2347 {
2348 global $tenweb_subscription_id;
2349
2350 return (defined('TENWEB_SO_HOSTED_ON_10WEB') && TENWEB_SO_HOSTED_ON_10WEB) || (defined('TENWEB_SO_FREE_SUBSCRIPTION_IDS') && (!in_array((int) $tenweb_subscription_id, TENWEB_SO_FREE_SUBSCRIPTION_IDS)));
2351 }
2352
2353 public static function is_tenweb_booster_connected()
2354 {
2355 return (defined('TENWEB_SO_HOSTED_ON_10WEB') && TENWEB_SO_HOSTED_ON_10WEB) ||
2356 (defined('TENWEB_CONNECTED_SPEED')
2357 && \Tenweb_Authorization\Login::get_instance()->check_logged_in()
2358 && \Tenweb_Authorization\Login::get_instance()->get_connection_type() == TENWEB_CONNECTED_SPEED
2359 && !empty(get_option('two_first_connect')));
2360 }
2361
2362 /**
2363 * For hosting cache , run only connected sites
2364 **/
2365 public static function update_post($id = 0)
2366 {
2367 if (TENWEB_SO_HOSTED_ON_10WEB) {
2368 return;
2369 }
2370
2371 if ($id === 0) {
2372 $id = get_option('page_on_front');
2373
2374 if ($id === '0' || $id === 0) {
2375 $recent_post = wp_get_recent_posts([
2376 'numberposts' => 1,
2377 'post_type' => 'post',
2378 'post_status' => 'publish',
2379 ], OBJECT);
2380
2381 if (is_array($recent_post) && isset($recent_post[0]->ID)) {
2382 $id = $recent_post[0]->ID;
2383 }
2384 }
2385 }
2386
2387 if ($id !== 0) {
2388 $post_data = ['ID' => $id];
2389 // removing kses_filter to avoid striping script or style tags from gutenberg editor
2390 remove_filter('content_save_pre', 'wp_filter_post_kses');
2391 wp_update_post(wp_slash($post_data));
2392 add_filter('content_save_pre', 'wp_filter_post_kses');
2393 }
2394 }
2395
2396 /**
2397 * Option key for critical CSS callback tokens. Uses a separator so page_id
2398 * values cannot collide with unrelated options (e.g. two_critical_blocked).
2399 *
2400 * @param string $page_id validated critical page id
2401 *
2402 * @return string
2403 */
2404 public static function get_critical_token_option_key($page_id)
2405 {
2406 return 'two_critical_token_' . $page_id;
2407 }
2408
2409 /**
2410 * Whether page_id is a known critical-CSS target (not an arbitrary option suffix).
2411 *
2412 * @param mixed $page_id
2413 *
2414 * @return bool
2415 */
2416 public static function is_valid_critical_page_id($page_id)
2417 {
2418 if (!is_string($page_id) && !is_int($page_id)) {
2419 return false;
2420 }
2421
2422 $page_id = (string) $page_id;
2423
2424 if ($page_id === 'front_page') {
2425 return true;
2426 }
2427
2428 if ((string) absint($page_id) === $page_id && absint($page_id) > 0) {
2429 return true;
2430 }
2431
2432 if (0 === strpos($page_id, 'term_') && absint(substr($page_id, 5)) > 0) {
2433 return true;
2434 }
2435
2436 if (0 === strpos($page_id, 'user_') && absint(substr($page_id, 5)) > 0) {
2437 return true;
2438 }
2439
2440 return false;
2441 }
2442
2443 /**
2444 * Store a critical CSS callback token for a page.
2445 *
2446 * @param string $page_id
2447 * @param string $token
2448 *
2449 * @return void
2450 */
2451 public static function set_critical_token($page_id, $token)
2452 {
2453 if (!self::is_valid_critical_page_id($page_id) || !is_string($token) || $token === '') {
2454 return;
2455 }
2456
2457 update_option(self::get_critical_token_option_key($page_id), $token, false);
2458 // Remove legacy concatenated key if present (two_critical{page_id}).
2459 delete_option('two_critical' . $page_id);
2460 }
2461
2462 /**
2463 * Get stored critical CSS callback token for a page.
2464 *
2465 * @param string $page_id
2466 *
2467 * @return string|false
2468 */
2469 public static function get_critical_token($page_id)
2470 {
2471 if (!self::is_valid_critical_page_id($page_id)) {
2472 return false;
2473 }
2474
2475 $token = get_option(self::get_critical_token_option_key($page_id));
2476
2477 if (is_string($token) && $token !== '') {
2478 return $token;
2479 }
2480
2481 // Migrate legacy key once.
2482 $legacy = get_option('two_critical' . $page_id);
2483
2484 if (is_string($legacy) && $legacy !== '' && !in_array($legacy, ['1', '0'], true)) {
2485 self::set_critical_token($page_id, $legacy);
2486
2487 return $legacy;
2488 }
2489
2490 return false;
2491 }
2492
2493 /**
2494 * Verify critical CSS callback token without consuming it.
2495 *
2496 * @param string $page_id
2497 * @param string $token
2498 *
2499 * @return bool
2500 */
2501 public static function verify_critical_token($page_id, $token)
2502 {
2503 if (!self::is_valid_critical_page_id($page_id) || !is_string($token) || $token === '') {
2504 return false;
2505 }
2506
2507 $stored = self::get_critical_token($page_id);
2508
2509 return is_string($stored) && $stored !== '' && hash_equals($stored, $token);
2510 }
2511
2512 /**
2513 * Consume (delete) critical CSS callback token after successful delivery.
2514 *
2515 * @param string $page_id
2516 *
2517 * @return void
2518 */
2519 public static function consume_critical_token($page_id)
2520 {
2521 if (!self::is_valid_critical_page_id($page_id)) {
2522 return;
2523 }
2524
2525 delete_option(self::get_critical_token_option_key($page_id));
2526 delete_option('two_critical' . $page_id);
2527 \TenWebWpTransients\OptimizerTransients::delete(self::get_critical_token_option_key($page_id));
2528 \TenWebWpTransients\OptimizerTransients::delete('two_critical' . $page_id);
2529 }
2530
2531 /**
2532 * Reject critical/uncritical CSS that contains HTML markup (Stored XSS breakout).
2533 * Allows literal "<" in CSS values (e.g. content: "<") but blocks </style> and HTML tags.
2534 *
2535 * @param string $css
2536 *
2537 * @return string empty string if unsafe
2538 */
2539 public static function sanitize_critical_css($css)
2540 {
2541 if (!is_string($css) || $css === '') {
2542 return '';
2543 }
2544
2545 // Block </style> breakout and HTML-like tags (e.g. <img, </div), not bare "<" in strings.
2546 if (preg_match('/<\/style\b|<\s*\/?\s*[a-zA-Z]/i', $css)) {
2547 return '';
2548 }
2549
2550 return $css;
2551 }
2552
2553 /**
2554 * For not hosted sites
2555 **/
2556 public static function set_critical()
2557 {
2558 $page_id = isset($_POST['page_id']) ? sanitize_text_field(wp_unslash($_POST['page_id'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
2559 $token = isset($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
2560
2561 if (!self::verify_critical_token($page_id, $token)) {
2562 die('Invalid token');
2563 }
2564
2565 if (!isset($_FILES['covered_css']) || !isset($_FILES['covered_css']['tmp_name'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
2566 die('no covered_css data');
2567 }
2568
2569 $uploadfile = $_FILES['covered_css']['tmp_name']; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput
2570 \TenWebWpTransients\OptimizerTransients::delete('two_critical_in_process');
2571 $triggerPostOptimizationTasks = !empty($_POST['newly_connected_website']) && !empty($_POST['notification_id']); // phpcs:ignore WordPress.Security.NonceVerification.Missing
2572 update_option('two_critical_data_import_data_' . time(), $triggerPostOptimizationTasks, false);
2573
2574 $success = \TenWebOptimizer\OptimizerCriticalCss::createCriticalCSS($uploadfile, $triggerPostOptimizationTasks, '', false, $page_id);
2575
2576 if ($success) {
2577 // Consume only after CSS was actually saved so the cloud can retry on failure.
2578 self::consume_critical_token($page_id);
2579 echo '{"status":"ok"}';
2580 die(0);
2581 }
2582
2583 echo '{"status":"error"}';
2584 die(0);
2585 }
2586
2587 public static function download_critical()
2588 {
2589 if (isset($_GET['two_update_critical'], $_GET['page_id']) && $_GET['two_update_critical'] === '1') { // phpcs:ignore
2590 $return_data = [
2591 'success' => false,
2592 'message' => 'error'
2593 ];
2594 $page_id = sanitize_text_field($_GET['page_id']); // phpcs:ignore
2595 $triggerPostOptimizationTasks = !empty($_GET['newly_connected_website']) && !empty($_GET['notification_id']); // phpcs:ignore
2596 $domain_id = get_site_option('tenweb_domain_id');
2597 $access_token = get_site_option(TENWEB_PREFIX . '_access_token');
2598 $file_content_response = wp_remote_get(TENWEB_SO_CRITICAL_URL . '/v1/critical/' . $domain_id . '/pages/' . $page_id . '/get', [ // phpcs:ignore
2599 'timeout' => 5, // phpcs:ignore
2600 'redirection' => 5,
2601 'httpversion' => '1.0',
2602 'blocking' => true,
2603 'headers' => [
2604 'accept' => 'application/x.10webperformance.v1+json',
2605 'authorization' => 'Bearer ' . $access_token,
2606 ],
2607 'cookies' => []
2608 ]);
2609
2610 if (!is_wp_error($file_content_response) && isset($file_content_response['body'])) {
2611 $file_content = $file_content_response['body'];
2612 $file_content_arr = json_decode($file_content, true);
2613
2614 if (isset($file_content_arr['data']['data']['covered_css']['value'])) {
2615 \TenWebOptimizer\OptimizerCriticalCss::createCriticalCSS(false, $triggerPostOptimizationTasks, $file_content_arr['data']['data']['covered_css']['value'], true);
2616 $return_data['success'] = true;
2617 $return_data['message'] = 'success';
2618 }
2619 }
2620 echo json_encode($return_data); // phpcs:ignore
2621 die;
2622 }
2623 }
2624
2625 public static function update_connection_flow_progress($status, $step, $metaData = [])
2626 {
2627 $flow_id = get_site_option(TENWEB_PREFIX . '_flow_id');
2628 $notification_id = get_site_option(TENWEB_PREFIX . '_notification_id');
2629 $domain_id = get_site_option('tenweb_domain_id');
2630 $access_token = get_site_option(TENWEB_PREFIX . '_access_token');
2631
2632 if ($access_token && $domain_id) {
2633 $response = wp_remote_post(TENWEB_SO_CRITICAL_URL . '/v1/domains/' . $domain_id . '/update_connection_flow_progress', [
2634 'timeout' => 89, // phpcs:ignore
2635 'redirection' => 15,
2636 'blocking' => true,
2637 'headers' => [
2638 'accept' => 'application/x.10webperformance.v1+json',
2639 'authorization' => 'Bearer ' . $access_token,
2640 ],
2641 'body' => [
2642 'flow_id' => $flow_id,
2643 'notification_id' => $notification_id,
2644 'status' => $status,
2645 'step' => $step,
2646 'debug_data' => $metaData
2647 ],
2648 'cookies' => []
2649 ]);
2650 }
2651 }
2652
2653 public static function filter_incompatible_plugins($plugins)
2654 {
2655 $two_incompatible_plugin_list = self::TWO_INCOMPATIBLE_PLUGIN_LIST;
2656 $return_list = [
2657 'incompatible' => [],
2658 'compatible' => [],
2659 ];
2660
2661 foreach ($plugins as $plugin) {
2662 $key = array_search($plugin, $two_incompatible_plugin_list, true);
2663
2664 if ($key) {
2665 $return_list['incompatible'][] = $two_incompatible_plugin_list[$key];
2666 } else {
2667 $return_list['compatible'][] = $plugin;
2668 }
2669 }
2670
2671 return $return_list;
2672 }
2673
2674 public static function get_conflicting_plugins()
2675 {
2676 $two_incompatible_plugin_list = self::TWO_INCOMPATIBLE_PLUGIN_LIST;
2677 $active_plugins = get_option('active_plugins');
2678 $all_plugins = get_plugins();
2679
2680 $incompatible_active_plugin_slugs = array_intersect($two_incompatible_plugin_list, $active_plugins);
2681 $incompatible_active_plugin_list = [];
2682
2683 foreach ($incompatible_active_plugin_slugs as $plugin) {
2684 $incompatible_active_plugin_list[$plugin] = $all_plugins[$plugin]['Name'];
2685 }
2686
2687 return $incompatible_active_plugin_list;
2688 }
2689
2690 public static function injectCriticalBg($content, $critical, $cacheStructure)
2691 {
2692 if (isset($_GET['no_critical_css']) && $_GET['no_critical_css'] == 1) { // phpcs:ignore
2693 return $content;
2694 }
2695 global $TwoSettings;
2696 $two_serve_optimized_bg_image = $TwoSettings->get_settings('two_serve_optimized_bg_image');
2697
2698 if ($two_serve_optimized_bg_image === 'on') {
2699 if (isset($critical) && isset($critical->critical_bg) && !empty($critical->critical_bg)) {
2700 $critical_bg_file_dir = TWO_CACHE_DIR . 'critical/' . $critical->critical_bg;
2701
2702 if (file_exists($critical_bg_file_dir)) {
2703 $critical_bg_data = file_get_contents($critical_bg_file_dir); // phpcs:ignore
2704 $critical_bg_data_arr = json_decode($critical_bg_data, true);
2705 $critical_bg_css = self::serve_different_sizes_for_critical_bg_image($critical_bg_data_arr);
2706
2707 if (!empty($critical_bg_css)) {
2708 $critical_bg_css = "<style id='two_critical_bg' class='two_critical_bg'>" . $critical_bg_css . '</style>';
2709 }
2710 }
2711 }
2712 }
2713
2714 if (isset($critical_bg_css) && !empty($critical_bg_css)) {
2715 $content = OptimizerUtils::inject_in_html($content, $critical_bg_css, ['</head>', 'before']);
2716 $cacheStructure->addToTagsToAdd($critical_bg_css, ['</head>', 'before']);
2717 }
2718
2719 return $content;
2720 }
2721
2722 public static function delete_define($key, $content)
2723 {
2724 $re = '/define\s*\(\s*[\'\"](' . $key . ')[\'\"].?,(.*?)\);/';
2725 preg_match_all($re, $content, $matches, PREG_SET_ORDER, 0);
2726
2727 if (is_array($matches)) {
2728 foreach ($matches as $define_arr) {
2729 if (isset($define_arr[0], $define_arr[1]) && $define_arr[1] == 'WP_CACHE') {
2730 $content = str_replace($define_arr[0], '', $content);
2731 }
2732 }
2733 }
2734
2735 return $content;
2736 }
2737
2738 public static function check_plugin_update()
2739 {
2740 $plugins = get_site_transient('update_plugins');
2741
2742 if (isset($plugins->response) && is_array($plugins->response)) {
2743 foreach ($plugins->response as $plugin_data) {
2744 if (isset($plugin_data->slug) && $plugin_data->slug === 'tenweb-speed-optimizer') {
2745 return true;
2746 }
2747 }
2748 }
2749
2750 return false;
2751 }
2752
2753 public static function clear_cloudflare_cache($prefixes = [], $clear_cache_from = '')
2754 {
2755 global $TwoSettings;
2756 $cloudflare_cache_status = $TwoSettings->get_settings('cloudflare_cache_status');
2757
2758 if (($cloudflare_cache_status !== 'on' || TENWEB_SO_HOSTED_ON_10WEB) && $clear_cache_from != 'settings_page') {
2759 return true;
2760 }
2761 $domain_id = get_site_option('tenweb_domain_id');
2762 $access_token = get_site_option(TENWEB_PREFIX . '_access_token');
2763 $workspace_id = get_site_option(TENWEB_PREFIX . '_workspace_id');
2764
2765 $req_body = [];
2766 $req_body['prefixes'] = $prefixes;
2767
2768 if (isset($access_token, $domain_id, $workspace_id) && !empty($access_token) && !empty($domain_id) && !empty($workspace_id)) {
2769 $response = wp_remote_post(TENWEB_SO_CRITICAL_URL . '/v1/workspaces/' . $workspace_id . '/domains/' . $domain_id . '/clear_cloudflare_cache', [
2770 'timeout' => 1,
2771 'redirection' => 5,
2772 'httpversion' => '1.0',
2773 'blocking' => false,
2774 'headers' => [
2775 'accept' => 'application/x.10webperformance.v1+json',
2776 'authorization' => 'Bearer ' . $access_token,
2777 ],
2778 'body' => $req_body,
2779 'cookies' => []
2780 ]);
2781 }
2782
2783 return true;
2784 }
2785
2786 public static function has_changed_bg_image()
2787 {
2788 return self::$has_changed_bg_image;
2789 }
2790
2791 public static function delete_critical_page($page_id)
2792 {
2793 global $TwoSettings;
2794 $critical_key = 'two_critical_' . $page_id;
2795 $critical_in_progress_key = 'two_critical_in_progress_' . $page_id;
2796 \TenWebWpTransients\OptimizerTransients::delete($critical_key);
2797 \TenWebWpTransients\OptimizerTransients::delete($critical_in_progress_key);
2798
2799 $two_critical_all_pages = OptimizerUtils::getCriticalPages();
2800
2801 if (OptimizerUrl::isCriticalSavedInSettings($page_id)) {
2802 $two_critical_pages = $TwoSettings->get_settings('two_critical_pages');
2803 unset($two_critical_pages[$page_id]);
2804 unset($two_critical_pages['']);
2805 $TwoSettings->update_setting('two_critical_pages', $two_critical_pages);
2806 } else {
2807 delete_post_meta($page_id, 'two_critical_pages');
2808 }
2809 $prefix = 'critical/two_' . $page_id . '_*.*';
2810 self::delete_files_by_prefix($prefix);
2811
2812 $tenweb_subscription_id = \TenWebWpTransients\OptimizerTransients::get(TENWEB_PREFIX . '_subscription_id');
2813 $is_free = (in_array((int) $tenweb_subscription_id, TENWEB_SO_FREE_SUBSCRIPTION_IDS) && !TENWEB_SO_HOSTED_ON_10WEB);
2814
2815 if (is_array($two_critical_all_pages) && isset($two_critical_all_pages[$page_id]) && $is_free) {
2816 self::delete_so_page($page_id);
2817 }
2818 }
2819
2820 public static function delete_so_page($page_id)
2821 {
2822 $domain_id = get_site_option('tenweb_domain_id');
2823 $access_token = get_site_option(TENWEB_PREFIX . '_access_token');
2824 $workspace_id = get_site_option(TENWEB_PREFIX . '_workspace_id');
2825
2826 if ($access_token && $domain_id && $workspace_id) {
2827 wp_remote_post(TENWEB_SO_CRITICAL_URL . '/v1/workspaces/' . $workspace_id . '/domains/' . $domain_id . '/delete-so-page', [
2828 'timeout' => 5, // phpcs:ignore
2829 'redirection' => 5,
2830 'httpversion' => '1.0',
2831 'blocking' => true,
2832 'headers' => [
2833 'accept' => 'application/x.10webperformance.v1+json',
2834 'authorization' => 'Bearer ' . $access_token,
2835 ],
2836 'body' => [
2837 'pageId' => $page_id
2838 ],
2839 'cookies' => []
2840 ]);
2841 }
2842 }
2843
2844 public static function update_site_state()
2845 {
2846 $domain_id = get_option('tenweb_domain_id');
2847
2848 if ($domain_id) {
2849 global $wp_version, $wpdb;
2850 $sql_version = $wpdb->get_var('SELECT VERSION() AS version'); // phpcs:ignore
2851 $home_url = get_home_url();
2852 $admin_url = get_admin_url();
2853 $site_title = get_bloginfo('name');
2854 $url = TENWEB_API_URL . '/site-state/' . $domain_id;
2855 $site_info = [
2856 'site_info' => [
2857 'platform' => 'wordpress',
2858 'site_url' => $home_url,
2859 'admin_url' => $admin_url,
2860 'name' => $home_url,
2861 'site_title' => $site_title,
2862 'site_screenshot_url' => $home_url,
2863 'platform_version' => $wp_version,
2864 'php_version' => PHP_VERSION,
2865 'mysql_version' => $sql_version,
2866 'manager_version' => get_site_option(TENWEB_PREFIX . '_from_image_optimizer') ? 'iowd_' . TENWEBIO_PREFIX : TENWEB_VERSION,
2867 'other_data' => [
2868 'manager_version' => TENWEB_VERSION,
2869 ]
2870 ]
2871 ];
2872 $args = [
2873 'method' => 'POST',
2874 'body' => ['data' => $site_info]
2875 ];
2876 $Helper = \Tenweb_Authorization\Helper::get_instance();
2877
2878 return $Helper->request($url, $args, 'send_site_state');
2879 }
2880 }
2881
2882 public static function init_flow_score_check($only_do_request = false)
2883 {
2884 if ($only_do_request) {
2885 $nonce = uniqid('two_', false);
2886 update_option('wp_two_nonce_two_init_flow_score', $nonce);
2887 $res = wp_remote_post(admin_url('admin-ajax.php'), [
2888 'timeout' => 5, // phpcs:ignore
2889 'redirection' => 5,
2890 'httpversion' => '1.0',
2891 'blocking' => false,
2892 'body' => [
2893 'action' => 'two_init_flow_score',
2894 'nonce' => $nonce
2895 ],
2896 'cookies' => []
2897 ]);
2898
2899 return;
2900 }
2901 $flow_score_check_init = get_option('flow_score_check_init', false);
2902 $two_flow_speed = get_option('two_flow_speed', false);
2903
2904 if ($flow_score_check_init !== '1' && !is_array($two_flow_speed) && !\Tenweb_Authorization\Login::get_instance()->check_logged_in()) {
2905 update_option('flow_score_check_init', '1');
2906 self::add_log_for_score_check_flow('init_flow_score_check', 'start two_google_check_score');
2907 \TenWebSC\TWScoreChecker::twsc_check_score('front_page', $old = true, $no_optimized = true);
2908 $score_data = get_option('two-front-page-speed');
2909 $speed_data['nooptimize_score'] = [];
2910
2911 if (isset($score_data['previous_score']['desktop_score'], $score_data['previous_score']['desktop_tti'])) {
2912 $speed_data['nooptimize_score']['desktopScore'] = $score_data['previous_score']['desktop_score'];
2913 $speed_data['nooptimize_score']['desktopTti'] = $score_data['previous_score']['desktop_tti'];
2914 }
2915
2916 if (isset($score_data['previous_score']['mobile_score'], $score_data['previous_score']['mobile_tti'])) {
2917 $speed_data['nooptimize_score']['mobileScore'] = $score_data['previous_score']['mobile_score'];
2918 $speed_data['nooptimize_score']['mobileTti'] = $score_data['previous_score']['mobile_tti'];
2919 }
2920 $speed_data['nooptimize_score']['tool'] = 'pageSpeedInsight';
2921 $speed_data['nooptimize_score']['two_version'] = TENWEB_SO_VERSION;
2922 $speed_data['nooptimize_score']['desktopData'] = [];
2923 $speed_data['nooptimize_score']['mobileData'] = [];
2924
2925 update_site_option('two_flow_speed', $speed_data);
2926 }
2927 global $wpdb;
2928 $row = $wpdb->get_row($wpdb->prepare("SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", TENWEB_PREFIX . '_access_token')); // phpcs:ignore
2929 $access_token = false;
2930
2931 if (is_object($row) && isset($row->option_value)) {
2932 $access_token = $row->option_value;
2933 }
2934 $two_flow_speed = get_option('two_flow_speed', false);
2935 self::add_log_for_score_check_flow('two_flow_speed', $two_flow_speed);
2936
2937 if ($access_token && is_array($two_flow_speed) && isset($two_flow_speed['nooptimize_score']['mobileScore'], $two_flow_speed['nooptimize_score']['desktopScore'])) {
2938 $domain_id = get_site_option('tenweb_domain_id');
2939 $flow_id = get_site_option(TENWEB_PREFIX . '_flow_id', false);
2940
2941 $workspace_id = get_site_option(TENWEB_PREFIX . '_workspace_id');
2942 $route = TENWEB_SO_CRITICAL_URL . '/v1/workspaces/' . $workspace_id . '/domains/' . $domain_id . '/set_nooptimize_score';
2943 $body = $two_flow_speed;
2944 $body['check_with_no_optimization'] = true;
2945 $body['flow_id'] = $flow_id;
2946 $body['is_first_optimization_flow'] = false;
2947 self::add_log_for_score_check_flow('send_data_to_performance', 'start send_data_to_performance');
2948 self::send_data_to_performance($route, $body, $access_token);
2949 }
2950 }
2951
2952 public static function send_data_to_performance($route, $body, $access_token)
2953 {
2954 $res = wp_remote_post($route, [
2955 'timeout' => 10, // phpcs:ignore
2956 'redirection' => 10,
2957 'httpversion' => '1.0',
2958 'blocking' => true,
2959 'headers' => [
2960 'accept' => 'application/x.10webperformance.v1+json',
2961 'authorization' => 'Bearer ' . $access_token,
2962 ],
2963 'body' => $body,
2964 'cookies' => []
2965 ]);
2966 }
2967
2968 public static function add_log_for_score_check_flow($key, $val)
2969 {
2970 $two_flow_score_log = get_option('two_flow_score_log', []);
2971 $flag = 1;
2972
2973 if (isset($two_flow_score_log['flag'])) {
2974 $flag = (int) $two_flow_score_log['flag'];
2975 $flag ++;
2976 }
2977 $arr_key = $flag . '_' . $key;
2978 $two_flow_score_log[$arr_key] = $val;
2979 $two_flow_score_log['flag'] = $flag;
2980 update_option('two_flow_score_log', $two_flow_score_log);
2981 }
2982
2983 public static function warmup_cache()
2984 {
2985 if (!self::check_if_hosted_website()) {
2986 $site_url = site_url() . '?tenweb_warmup_cache=1&tenweb_version=' . TENWEB_SO_VERSION . '&warmup_time=' . time();
2987 wp_remote_get($site_url, ['sslverify' => false, 'blocking' => false, 'timeout' => 0.1]); // phpcs:ignore
2988 }
2989 }
2990
2991 public static function get_absolute_url($url)
2992 {
2993 $parsed = parse_url($url); // phpcs:ignore
2994 $path = $parsed['path'];
2995
2996 if (!str_contains($path, '..')) {
2997 return $url;
2998 }
2999
3000 // fix dots in relative path
3001 $path_parts = explode('/', $path);
3002 $absolutes = [];
3003
3004 foreach ($path_parts as $part) {
3005 if ('..' == $part) {
3006 array_pop($absolutes);
3007 } else {
3008 $absolutes[] = $part;
3009 }
3010 }
3011
3012 return str_replace($parsed['path'], implode('/', $absolutes), $url);
3013 }
3014
3015 public static function IOConnected()
3016 {
3017 return get_site_option(TENWEB_PREFIX . '_from_image_optimizer');
3018 }
3019
3020 public static function TWOConnected()
3021 {
3022 return get_option('two_first_connect');
3023 }
3024
3025 public static function request_webp_action($task, $url_list = '')
3026 {
3027 try {
3028 if ('regenerate' === $task) {
3029 $image_list = [];
3030 $page_list = [];
3031
3032 foreach (explode(' ', $url_list) as $url) {
3033 if (0 === strpos($url, site_url())) {
3034 if (preg_match('/\.(jpg|png|jpeg)$/', $url)) {
3035 $image_list[] = $url;
3036 } else {
3037 $page_list[] = $url . (strpos($url, '?') > -1 ? '&' : '?') . 'two_nooptimize=1';
3038 }
3039 }
3040 }
3041
3042 if (empty($image_list) || TENWEB_SO_HOSTED_ON_10WEB) {
3043 $request_data = [
3044 'force_convert' => 0,
3045 'quality' => 50,
3046 'image_list' => implode(',', $image_list),
3047 'url_list' => implode(',', $page_list),
3048 'site_url' => site_url(),
3049 ];
3050 $method = 'POST';
3051 $endpoint = \TenWebIO\Api::API_WEBP_CONVERT;
3052 $api_instance = new \TenWebIO\Api($endpoint);
3053 $response = $api_instance->apiRequest($method, $request_data);
3054
3055 if (false !== $response) {
3056 $response_data = [
3057 'status' => 'success',
3058 ];
3059 } else {
3060 $response_data = [
3061 'status' => 'error',
3062 'error' => false
3063 ];
3064 }
3065 } else {
3066 //if we have array of urls, and website is not hosted on 10Web call internal IO classes to optimize them
3067 $compressService = new CompressService();
3068 $compressService->compressCustom($image_list, 'front_page', 1);
3069 $response_data = [
3070 'status' => 'success',
3071 ];
3072 }
3073 } elseif ('delete' === $task) {
3074 $count = \TenWebIO\Utils::deleteWebPImages();
3075 $response_data = [
3076 'status' => 'success',
3077 'count' => $count
3078 ];
3079 } else {
3080 $response_data = [
3081 'status' => 'error',
3082 'error' => 'Invalid Task'
3083 ];
3084 }
3085 } catch (Exception $e) {
3086 $response_data = [
3087 'status' => 'error',
3088 'error' => $e->getMessage()
3089 ];
3090 }
3091
3092 return $response_data; // phpcs:ignore
3093 }
3094
3095 public static function check_admin_capabilities()
3096 {
3097 return current_user_can('manage_options');
3098 }
3099
3100 public static function dirsize($dir)
3101 {
3102 @$dh = opendir($dir);
3103 $size = 0;
3104
3105 if ($dh) {
3106 while ($file = @readdir($dh)) { // phpcs:ignore
3107 if ($file != '.' and $file != '..') {
3108 $path = $dir . '/' . $file;
3109
3110 if (is_dir($path)) {
3111 $size += self::dirsize($path); // recursive in sub-folders
3112 } elseif (is_file($path)) {
3113 $size += filesize($path); // add file
3114 }
3115 }
3116 }
3117 @closedir($dh);
3118 }
3119
3120 return $size;
3121 }
3122
3123 public static function get_booster_icon()
3124 {
3125 if (defined('TENWEB_WHITELABEL_DIR') && is_dir(TENWEB_WHITELABEL_DIR . '/images')) {
3126 $icon_dir = TENWEB_WHITELABEL_DIR . '/images';
3127 $icons = scandir($icon_dir);
3128
3129 //return first element
3130 if (isset($icons[2]) && $icons[2] != '.' && $icons != '..') {
3131 return TENWEB_URL_WHITELABEL . '/images/' . $icons[2];
3132 }
3133 }
3134
3135 return strtolower(TWO_SO_ORGANIZATION_NAME) == '10web' ? TENWEB_SO_URL . '/assets/images/logo_green.svg' : '';
3136 }
3137
3138 /**
3139 * Detect builder type based on active theme
3140 *
3141 * @return string|null Returns 'wvc' for wvc-theme, 'section_based' for tenweb-website-builder-theme, or null
3142 */
3143 public static function detect_builder_type()
3144 {
3145 if (!function_exists('wp_get_theme')) {
3146 return;
3147 }
3148 $active_theme = wp_get_theme();
3149 $theme_slug = $active_theme->get_stylesheet(); // Gets theme directory name
3150 $text_domain = $active_theme->get('TextDomain');
3151 $theme_name = $active_theme->get('Name');
3152
3153 // Check by theme directory/slug or text domain
3154 if ($theme_slug === 'wvc-theme' || $text_domain === 'wvc-theme') {
3155 return 'wvc';
3156 }
3157
3158 if ($theme_slug === 'tenweb-website-builder-theme' || $text_domain === 'tenweb-website-builder-theme') {
3159 return 'section_based';
3160 }
3161
3162 // Fallback: check by theme name
3163 if (stripos($theme_name, 'WordPress AI Builder') !== false ||
3164 stripos($theme_name, 'wvc') !== false) {
3165 return 'wvc';
3166 }
3167
3168 if (stripos($theme_name, 'Builder Theme') !== false) {
3169 return 'section_based';
3170 }
3171
3172 return null;
3173 }
3174 }
3175