$strlen) { $start = $strlen; } if (null === $length || '' === $length) { $start2 = $strlen; } else if ($length < 0) { $start2 = $strlen + $length; if ($start2 < $start) { $start2 = $start; } } else { $start2 = $start + $length; } if (null === $encoding) { $leader = $start ? mb_substr($string, 0, $start) : ''; $trailer = ($start2 < $strlen) ? mb_substr($string, $start2, null) : ''; } else { $leader = $start ? mb_substr($string, 0, $start, $encoding) : ''; $trailer = ($start2 < $strlen) ? mb_substr($string, $start2, null, $encoding) : ''; } return "{$leader}{$replacement}{$trailer}"; } return (null === $length) ? substr_replace($string, $replacement, $start) : substr_replace($string, $replacement, $start, $length); } /** * Decides whether this is a "subdirectory site" or not. * * @param bool $override Allows overriding the decision when needed. * * @return bool */ public static function siteurl_not_root($override = null) { static $subdir = null; if (null === $subdir) { $parts = self::get_ao_wp_site_url_parts(); $subdir = (isset($parts['path']) && ('/' !== $parts['path'])); } if (null !== $override) { $subdir = $override; } return $subdir; } /** * Parse TWO_WP_SITE_URL into components using \parse_url(), but do * so only once per request/lifecycle. * * @return array */ public static function get_ao_wp_site_url_parts() { static $parts = array(); if (empty($parts)) { $parts = parse_url(TWO_WP_SITE_URL); } return $parts; } /** * Modify given $cdn_url to include the site path when needed. * * @param string $cdn_url CDN URL to tweak. * @param bool $force_cache_miss Force a cache miss in order to be able * to re-run the filter. * * @return string */ public static function tweak_cdn_url_if_needed($cdn_url, $force_cache_miss = false) { static $results = array(); if (!isset($results[$cdn_url]) || $force_cache_miss) { // In order to return unmodified input when there's no need to tweak. $results[$cdn_url] = $cdn_url; // Behind a default true filter for backcompat, and only for sites // in a subfolder/subdirectory, but still easily turned off if // not wanted/needed... if (twoptimizeUtils::siteurl_not_root()) { $site_url_parts = twoptimizeUtils::get_ao_wp_site_url_parts(); $cdn_url_parts = parse_url($cdn_url); $schemeless = self::is_protocol_relative($cdn_url); $cdn_url_parts = self::maybe_replace_cdn_path($site_url_parts, $cdn_url_parts); if (false !== $cdn_url_parts) { $results[$cdn_url] = self::assemble_parsed_url($cdn_url_parts, $schemeless); } } } return $results[$cdn_url]; } /** * When siteurl contans a path other than '/' and the CDN URL does not have * a path or it's path is '/', this will modify the CDN URL's path component * to match that of the siteurl. * This is to support "magic" CDN urls that worked that way before v2.4... * * @param array $site_url_parts Site URL components array. * @param array $cdn_url_parts CDN URL components array. * * @return array|false */ public static function maybe_replace_cdn_path(array $site_url_parts, array $cdn_url_parts) { if (isset($site_url_parts['path']) && '/' !== $site_url_parts['path']) { if (!isset($cdn_url_parts['path']) || '/' === $cdn_url_parts['path']) { $cdn_url_parts['path'] = $site_url_parts['path']; return $cdn_url_parts; } } return false; } /** * Given an array or components returned from \parse_url(), assembles back * the complete URL. * If optional * * @param array $parsed_url URL components array. * @param bool $schemeless Whether the assembled URL should be * protocol-relative (schemeless) or not. * * @return string */ public static function assemble_parsed_url(array $parsed_url, $schemeless = false) { $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; if ($schemeless) { $scheme = '//'; } $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; $pass = ($user || $pass) ? "$pass@" : ''; $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; return "$scheme$user$pass$host$port$path$query$fragment"; } /** * Returns true if given $url is protocol-relative. * * @param string $url URL to check. * * @return bool */ public static function is_protocol_relative($url) { $result = false; if (!empty($url)) { $result = (0 === strpos($url, '//')); } return $result; } /** * Canonicalizes the given path regardless of it existing or not. * * @param string $path Path to normalize. * * @return string */ public static function path_canonicalize($path) { $patterns = array( '~/{2,}~', '~/(\./)+~', '~([^/\.]+/(?R)*\.{2,}/)~', '~\.\./~', ); $replacements = array( '/', '/', '', '', ); return preg_replace($patterns, $replacements, $path); } /** * Returns true if the string is a valid regex. * * @param string $string String, duh. * * @return bool */ public static function str_is_valid_regex($string) { set_error_handler(function () { }, E_WARNING); $is_regex = (false !== preg_match($string, '')); restore_error_handler(); return $is_regex; } /** * Returns true if a certain WP plugin is active/loaded. * * @param string $plugin_file Main plugin file. * * @return bool */ public static function is_plugin_active($plugin_file) { static $ipa_exists = null; if (null === $ipa_exists) { if (!function_exists('\is_plugin_active')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $ipa_exists = function_exists('\is_plugin_active'); } return $ipa_exists && is_plugin_active($plugin_file); } public static function replace_font($css) { $two_async_font = get_option("two_async_font"); if (isset($two_async_font) && $two_async_font == "on") { /*$re = '~(?>@font-face\s*{\s*|\G(?!\A))(\S+)\s*:\s*([^;]+);\s*~';*/ $re = '/@font-face.*{\K[^}]*(?=})/'; preg_match_all($re, $css, $matches, PREG_SET_ORDER, 0); foreach ($matches as $el) { if (isset($el[0])) { if (strpos($el[0], "font-display")) { $re1 = '/font-display.*:\K[^;]*(?=;)/'; preg_match_all($re1, $el[0], $matches, PREG_SET_ORDER, 0); if (isset($matches) && isset($matches[0]) && isset($matches[0][0])) { $style = str_replace($matches[0][0], "swap", $el[0]); $css = str_replace($el[0], $style, $css); } } else { $style = $el[0] . ";font-display: swap;"; $css = str_replace($el[0], $style, $css); } } } } return $css; } public static function addSwapToGoogleFonts($content) { $two_async_font = get_option("two_async_font"); if (isset($two_async_font) && $two_async_font == "on") { // Remove existing display swaps $content = str_replace("&display=swap", "", $content); // Add font-display=swap as a querty parameter to Google fonts $content = str_replace("googleapis.com/css?family", "googleapis.com/css?display=swap&family", $content); // Fix for Web Font Loader $content = preg_replace("/(WebFontConfig\['google'\])(.+[\w])(.+};)/", '$1$2&display=swap$3', $content); } return $content; } public static function replace_bg($css) { $replaced_images = array(); $two_bg_lazyload = get_option("two_bg_lazyload"); if (isset($two_bg_lazyload) && $two_bg_lazyload == "on") { //$re = '~\bbackground[-image]*?\s*:.*?url.*?\(\s*[\'|"]?(.*?)?[\'|"]?\s*\)~i'; $re = '~url\s*\(\s*[\'|"]?(.*?)?[\'|"]?\s*\)~i'; $svg_data = "data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20"; preg_match_all($re, $css, $matches); $ext_list = array("svg", "jpeg", "png", "gif", "jpg"); if (isset($matches) && is_array($matches) && isset($matches[1])) { $images = $matches[1]; foreach ($images as $src) { $ext = pathinfo($src, PATHINFO_EXTENSION); if (!in_array($ext, $ext_list)) { continue; } if (in_array($src, $replaced_images)) { continue; } $flag_continue = false; $two_exclude_lazyload = get_option("two_exclude_lazyload"); if (isset($two_exclude_lazyload) && !empty($two_exclude_lazyload)) { $exclude_lazyload = explode(",", $two_exclude_lazyload); foreach ($exclude_lazyload as $path) { if (strpos($src, $path)) { $flag_continue = true; } } } if ($flag_continue) { continue; } array_push($replaced_images, $src); $css = str_replace($src, $svg_data . "#}" . $src, $css); } } } return $css; } /** * Cache compare. * * @param array $args */ public static function cache_compare($args = []) { $type = $args['type']; $post_id = $args['post_id']; $new_cache_files = $args['new_cache_files']; $old_cache_files = get_post_meta($post_id, 'two_cache_' . $type, true); if (!empty($new_cache_files) && empty($old_cache_files)) { add_post_meta($post_id, 'two_cache_' . $type, []); } if (!empty($new_cache_files)) { update_post_meta($post_id, 'two_cache_' . $type, $new_cache_files); if (!empty($old_cache_files)) { $dir_gzip = WP_CONTENT_DIR . TWO_CACHE_CHILD_DIR; foreach ($old_cache_files as $old_file) { if (!in_array($old_file, $new_cache_files)) { $old_file_name = ($type == 'gzip') ? $old_file : $type . '/' . $old_file; self::delete_cache_file($old_file_name); if ($type == 'gzip') { foreach (['deflate', 'none', 'gzip'] as $val) { $file_gzip = $old_file . '.' . $val; if (is_file($dir_gzip . $file_gzip)) { self::delete_cache_file($file_gzip); } } } } } } } } /** * Delete all cache on DB. * * @param array $args * * @return bool */ public static function delete_all_cache_db($args = []) { global $wpdb; $tbl = $wpdb->prefix . 'postmeta'; $css = $wpdb->delete($tbl, array('meta_key' => 'two_cache_css')); $js = $wpdb->delete($tbl, array('meta_key' => 'two_cache_js')); $gzip = $wpdb->delete($tbl, array('meta_key' => 'two_cache_gzip')); return true; } /** * Deleted recursively directory and its entire contents. * * @param string $dir * @param array $not_allow_delete * * @return mixed */ public static function delete_all_cache_file($dir = '', $not_allow_delete = []) { if (is_dir($dir)) { $objects = scandir($dir); foreach ($objects as $object) { if ($object != "." && $object != ".." && $object != "index.html") { if (is_dir($dir . '/' . $object) && !is_link($dir . '/' . $object)) self::delete_all_cache_file($dir . '/' . $object, $not_allow_delete); else unlink($dir . '/' . $object); } } if (!empty($not_allow_delete) && !in_array($dir, $not_allow_delete)) { rmdir($dir); } return true; } } /** * Delete cache file. * * @param string $file * * @return bool */ public static function delete_cache_file($file = '') { $file = WP_CONTENT_DIR . TWO_CACHE_CHILD_DIR . $file; if (is_file($file)) { $delete = @unlink($file); if ($delete) { return true; } } return false; } public static function get_page_url() { if (isset($_SERVER['HTTP_HOST']) && isset($_SERVER['REQUEST_URI'])) { if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') { $link = 'https'; } else { $link = 'http'; } // Here append the common URL characters. $link .= '://'; // Append the host(domain name, ip) to the URL. $link .= $_SERVER['HTTP_HOST']; // Append the requested resource location to the URL $link .= $_SERVER['REQUEST_URI']; return $link; } return ''; } } }