<?php

if (!defined('ABSPATH')) die('No direct access allowed');

// handle better utf-8 and Unicode encoding
if (function_exists('mb_internal_encoding')) {
	mb_internal_encoding('UTF-8');
}

// must have
// phpcs:disable
// Squiz.PHP.DiscouragedFunctions.Discouraged -- Not applicable here
ini_set('pcre.backtrack_limit', 5000000);
ini_set('pcre.recursion_limit', 5000000);
// phpcs:enable
	
use MatthiasMullie\Minify;

class WP_Optimize_Minify_Functions {

	/**
	 * Applies `strip_tags` function for given array of messages
	 *
	 * @param array  $messages     Array of messages
	 * @param string $allowed_tags Tags to retain in message (optional)
	 *
	 * @return array
	 */
	public static function apply_strip_tags_for_messages_array($messages, $allowed_tags = '<strong>') {
		return array_map(function($message) use ($allowed_tags) {
			return strip_tags($message, $allowed_tags);
		}, $messages);
	}

	/**
	 * Detect external or internal scripts
	 *
	 * @param string $src
	 * @return boolean
	 */
	public static function is_local_domain($src) {
		$wpo_minify_options = wp_optimize_minify_config()->get();

		$locations = array(home_url(), site_url(), network_home_url(), network_site_url());
		$async_using_js = 'all' === $wpo_minify_options['enable_defer_js'] && 'async_using_js' === $wpo_minify_options['defer_js_type'];
		
		// excluded from cdn because of https://www.chromestatus.com/feature/5718547946799104 (we use document.write to preserve render blocking)
		if (!empty($wpo_minify_options['cdn_url'])
			&& (!$async_using_js || $wpo_minify_options['cdn_force'])
		) {
			array_push($locations, $wpo_minify_options['cdn_url']);
		}

		// cleanup locations
		$locations = array_filter(array_unique($locations));

		// external or not?
		$ret = false;
		foreach ($locations as $l) {
			$l = preg_replace('/^https?:\/\//i', '', trim($l));
			$l = trim(trim(preg_replace('/^www./', '', $l), '/'));
			if (false !== stripos($src, $l) && false === $ret) {
				$ret = true;
			}
		}

		// response
		return $ret;
	}

	/**
	 * Functions, get hurl info
	 *
	 * @param mixed $src
	 *
	 * @return string
	 */
	public static function get_hurl($src) {
		$wp_home = site_url();
		$wp_domain = trim(str_ireplace(array('http://', 'https://'), '', trim($wp_home, '/')));
		// preserve empty source handles
		$hurl = null === $src ? '' : trim($src);
		if (empty($hurl)) {
			return $hurl;
		}

		// some fixes
		$hurl = str_ireplace(array('&#038;', '&amp;'), '&', $hurl);

		if (is_ssl()) {
			$protocol = 'https://';
		} else {
			$protocol = 'http://';
		}

		// make sure wp_home doesn't have a forward slash
		$wp_home = rtrim($wp_home, '/');

		// apply some filters
		if ("//" === substr($hurl, 0, 2)) {
			$hurl = $protocol.ltrim($hurl, "/");
		}//end if
		if ("http" === substr($hurl, 0, 4) && false === stripos($hurl, $wp_domain)) {
			return $hurl;
		}//end if
		if ("http" !== substr($hurl, 0, 4) && false !== stripos($hurl, $wp_domain)) {
			$hurl = $wp_home.'/'.ltrim($hurl, "/");
		}//end if

		// prevent double forward slashes in the middle
		$hurl = str_ireplace('###', '://', str_ireplace('//', '/', str_ireplace('://', '###', $hurl)));

		// consider different wp-content directory
		$proceed = 0;
		if (!empty($wp_home)) {
			$alt_wp_content = basename($wp_home);
			if (substr($hurl, 0, strlen($alt_wp_content)) === $alt_wp_content) {
				$proceed = 1;
			}
		}

		// Get the name of the WP-CONTENT folder. Default is wp-content, but can be changed by the user.
		$wp_content_folder = str_replace(ABSPATH, '', WP_CONTENT_DIR);

		// protocol + home for relative paths
		if ("/".WPINC === substr($hurl, 0, 12)
			|| "/wp-admin" === substr($hurl, 0, 9)
			|| "/$wp_content_folder" === substr($hurl, 0, 11)
			|| 1 === $proceed
		) {
			$hurl = $wp_home.'/'.ltrim($hurl, "/");
		}

		// make sure there is a protocol prefix as required
		$hurl = $protocol.preg_replace('/^https?:\/\//i', '', $hurl); // enforce protocol

		// no query strings
		if (false !== stripos($hurl, '.js?v')) {
			$hurl = stristr($hurl, '.js?v', true).'.js';
		}//end if
		if (false !== stripos($hurl, '.css?v')) {
			$hurl = stristr($hurl, '.css?v', true).'.css';
		}//end if

		return $hurl;
	}

	/**
	 * Check if it's an internal url or not
	 *
	 * @param string $hurl
	 * @param string $wp_home
	 * @param mixed  $noxtra
	 * @return boolean
	 */
	public static function internal_url($hurl, $wp_home, $noxtra = null) {
		$wpo_minify_options = wp_optimize_minify_config()->get();

		if (substr($hurl, 0, strlen($wp_home)) === $wp_home) {
			return true;
		}
		if (false !== stripos($hurl, $wp_home)) {
			return true;
		}
		if (isset($_SERVER['HTTP_HOST']) && false !== stripos($hurl, preg_replace('/:\d+$/', '', sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST']))))) {
			return true;
		}
		if (isset($_SERVER['SERVER_NAME']) && false !== stripos($hurl, preg_replace('/:\d+$/', '', sanitize_text_field(wp_unslash($_SERVER['SERVER_NAME']))))) {
			return true;
		}
		if (isset($_SERVER['SERVER_ADDR']) && '::1' !== sanitize_text_field(wp_unslash($_SERVER['SERVER_ADDR'])) && false !== stripos($hurl, preg_replace('/:\d+$/', '', sanitize_text_field(wp_unslash($_SERVER['SERVER_ADDR']))))) {
			return true;
		}

		// allow specific external urls to be merged
		if (null === $noxtra) {
			$merge_allowed_urls = array_map('trim', explode("\n", $wpo_minify_options['merge_allowed_urls']));
			if (is_array($merge_allowed_urls) && strlen(implode($merge_allowed_urls)) > 0) {
				foreach ($merge_allowed_urls as $e) {
					if (false !== stripos($hurl, $e) && !empty($e)) {
						return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * Case-insensitive in_array() wrapper
	 *
	 * @param string $hurl
	 * @param array  $ignore
	 * @return boolean
	 */
	public static function in_arrayi($hurl, $ignore) {
		$hurl = preg_replace('/^https?:\/\//i', '//', $hurl); // better compatibility
		$hurl = strtok(urldecode(rawurldecode($hurl)), '?'); // no query string, decode entities
		
		if (!empty($hurl) && is_array($ignore)) {
			foreach ($ignore as $i) {
				$i = preg_replace('/^https?:\/\//i', '//', $i); // better compatibility
				$i = strtok(urldecode(rawurldecode($i)), '?'); // no query string, decode entities
				$i = trim(trim(trim(rtrim($i, '/')), '*')); // wildcard char removal
				if (!empty($i) && false !== stripos($hurl, $i)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Check if selected url is point to already minified css/js file
	 *
	 * @param string $url
	 * @return bool
	 */
	public static function is_minified_css_js_filename($url) {
		$parts = wp_parse_url($url);
		if (empty($parts['path']) || !is_string(basename($parts['path']))) return false;
		return 1 === preg_match('/\.min\.(js|css)$/i', basename($parts['path']));
	}

	/**
	 * Better compatibility urls + fix w3.org NamespaceAndDTDIdentifiers
	 *
	 * @param string $code
	 * @return string
	 * */
	private static function compat_urls($code) {
		$wpo_minify_options = wp_optimize_minify_config()->get();
		$default_protocol = $wpo_minify_options['default_protocol'];
		if ('dynamic' === $default_protocol) {
			if ((isset($_SERVER['HTTPS']) && ('on' === $_SERVER['HTTPS'] || 1 === (int) $_SERVER['HTTPS']))
				|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'])
			) {
				$default_protocol = 'https://';
			} else {
				$default_protocol = 'http://';
			}
		} else {
			$default_protocol = $default_protocol.'://';
		}
		$code = preg_replace('/^https?:\/\//i', $default_protocol, $code);
		$code = str_ireplace($default_protocol.'www.w3.org', 'http://www.w3.org', $code);
		return $code;
	}

	/**
	 * Minify css string with PHP Minify
	 *
	 * @param string $css
	 * @return string
	 */
	public static function minify_css_string($css) {
		$css = apply_filters('wpo_minify_css_string', $css);
		$minifier = new Minify\CSS($css);
		$minifier->setMaxImportSize(15); // [css only] embed assets up to 15 Kb (default 5Kb) - processes gif, png, jpg, jpeg, svg & woff
		$min = $minifier->minify();
		if (false !== $min) {
			return self::compat_urls($min);
		}
		return self::compat_urls($css);
	}

	/**
	 * Minify js on demand (one file at one time, for compatibility)
	 *
	 * @param string  $url
	 * @param string  $js
	 * @param boolean $enable_js_minification
	 * @return string
	 */
	public static function get_js($url, $js, $enable_js_minification) {
		$wpo_minify_options = wp_optimize_minify_config()->get();

		// exclude minification on already minified files + jquery (because minification might break those)
		$excl = array('jquery.js', '.min.js', '-min.js', '/uploads/fusion-scripts/', '/min/', '.packed.js', '/includes/builder/scripts/');
		foreach ($excl as $e) {
			if (false !== stripos(basename($url), $e)) {
				$enable_js_minification = false;
				break;
			}
		}

		$encoding = mb_detect_encoding($js);

		// remove BOM
		$js = self::remove_utf8_bom($js);

		self::maybe_log_error_message($url, $encoding, $js);

		// minify JS
		if ($enable_js_minification) {
			$js = self::minify_js_string($js);
		} else {
			$js = self::compat_urls($js);
		}

		// Remove source mapping files
		$js = preg_replace('/(\/\/\s*[#]\s*sourceMappingURL\s*[=]\s*)(.+)\s*/ui', '', $js);

		// needed when merging js files
		$js = trim($js);
		if (';' !== substr($js, -1)) {
			$js = $js.';';
		}
		if ($wpo_minify_options['debug']) {
			$js = '/* info: ' . $url . ' */' . "\n" . $js;
		}

		/**
		 * Filters the imported JavaScript
		 *
		 * @param string  $js                     - The imported JS
		 * @param string  $url                    - The imported url
		 * @param boolean $enable_js_minification - Whether to minify or not
		 */
		$filtered_js = apply_filters('wpo_minify_get_js', $js . "\n", $url, $enable_js_minification);
		return is_string($filtered_js) ? $filtered_js : $js;
	}

	/**
	 * Minify JS string with PHP Minify or YUI Compressors
	 *
	 * @param string $js
	 * @return string
	 */
	public static function minify_js_string($js) {
		$js = apply_filters('wpo_minify_js_string', $js);
		// PHP Minify from https://github.com/matthiasmullie/minify
		$minifier = new Minify\JS($js);
		$min = $minifier->minify();
		if (false !== $min && (strlen(trim($js)) === strlen(trim($min)) || strlen(trim($min)) > 0)) {
			return self::compat_urls($min);
		}
		
		// if we are here, something went  wrong and minification didn't work
		$js = "\n/*! wpo_min: Minification of the following section failed, so it has been merged instead. */\n".$js;
		return self::compat_urls($js);
	}

	/**
	 * Wrapper to minify the inlined JS string - Adds an extra check for JSON
	 *
	 * @param string $js
	 * @return string
	 */
	public static function minify_inline_js($js) {
		$js = apply_filters('wpo_minify_inline_js_string', $js);
		// Do not minify JSON (JS minification will minify `true` to `!0`, which will break the JSON)
		if (json_decode($js)) return $js;
		return self::minify_js_string($js, true);
	}

	/**
	 * Functions, minify html
	 *
	 * @param string $html
	 * @return string
	 */
	public static function minify_html($html) {
		$minify_css = wp_optimize_minify_config()->get('enable_css_minification');
		$minify_js = wp_optimize_minify_config()->get('enable_js_minification');
		$options = array();
		if ($minify_css && apply_filters('wpo_minify_inline_css', true)) {
			$options['cssMinifier'] = array('WP_Optimize_Minify_Functions', 'minify_css_string');
		}
		if ($minify_js && apply_filters('wpo_minify_inline_js', true)) {
			$options['jsMinifier'] = array('WP_Optimize_Minify_Functions', 'minify_inline_js');
		}
		return Minify_HTML::minify($html, $options);
	}

	/**
	 * Functions to minify HTML
	 *
	 * @param string $html
	 * @return string
	 */
	public static function html_compression_finish($html) {
		return self::minify_html($html);
	}

	/**
	 * Start the compression
	 *
	 * @return void
	 */
	public static function html_compression_start() {
		if (self::exclude_contents()) {
			return;
		}
		ob_start(array(__CLASS__, 'html_compression_finish'));
	}

	/**
	 * Remove default HTTP headers
	 *
	 * @return void
	 */
	public static function remove_redundant_shortlink() {
		remove_action('wp_head', 'wp_shortlink_wp_head', 10);
		remove_action('template_redirect', 'wp_shortlink_header', 11);
	}

	/**
	 * Minify css on demand (one file at one time, for compatibility)
	 *
	 * @param string  $url
	 * @param string  $css
	 * @param boolean $enable_css_minification
	 * @return string
	 */
	public static function get_css($url, $css, $enable_css_minification) {
		$wpo_minify_options = wp_optimize_minify_config()->get();

		$encoding = mb_detect_encoding($css);

		// remove BOM
		$css = self::remove_utf8_bom($css);

		self::maybe_log_error_message($url, $encoding, $css);

		// fix url paths
		if (!empty($url)) {
			$css = self::make_css_urls_absolute($css, $url);
		}

		$css = str_ireplace('@charset "UTF-8";', '', $css);

		// remove query strings from fonts (for better seo, but add a small cache buster based on most recent updates)
		// last update or zero
		$cache_time = $wpo_minify_options['last-cache-update'];
		// fonts cache buster
		$css = preg_replace('/(.eot|.woff2|.woff|.ttf)+[?+](.+?)(\)|\'|\")/ui', "$1"."#".$cache_time."$3", $css);
		// Remove Sourcemappingurls
		$css = preg_replace('/(\/\*\s*[#]\s*sourceMappingURL\s*[=]\s*)(.[^*]+)\s*\*\//ui', '', $css);
		// If @import is found, process it/them
		if (false !== strpos($css, '@import')) $css = self::replace_css_import($css, $url);

		// minify CSS
		if ($enable_css_minification) {
			$css = self::minify_css_string($css);
		} else {
			$css = self::compat_urls($css);
		}

		// cdn urls
		$cdn_url = $wpo_minify_options['cdn_url'];
		if (!empty($cdn_url)) {
			$wp_domain = trim(preg_replace('/^https?:\/\//i', '', trim(site_url(), '/')));
			$cdn_url = trim(trim(preg_replace('/^https?:\/\//i', '', trim($cdn_url, '/'))), '/');
			$css = str_ireplace($wp_domain, $cdn_url, $css);
		}

		// add css comment
		$css = trim($css);
		if ($wpo_minify_options['debug']) {
			$css = '/* info: ' . $url . ' */' . "\n" . trim($css);
		}

		/**
		 * Filters the imported CSS
		 *
		 * @param string  $css                     - The imported CSS
		 * @param string  $url                     - The imported url
		 * @param boolean $enable_css_minification - Whether to minify or not
		 */
		$filtered_css = apply_filters('wpo_minify_get_css', $css, $url, $enable_css_minification);
		return is_string($filtered_css) ? $filtered_css : $css;
	}

	/**
	 * Adds full path to relative url() rules
	 *
	 * @param string $css - The CSS to process
	 * @param string $url - The URL or the CSS being processed
	 * @return string
	 */
	public static function make_css_urls_absolute($css, $url) {
		$matches = array();
		preg_match_all("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/ui", $css, $matches);
		foreach ($matches[1] as $a) {
			$b = trim($a);
			if ($b !== $a) {
				$css = str_replace($a, $b, $css);
			}
		}
		return (string) preg_replace("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/ui", "url(".dirname($url)."/$1)", $css);
	}

	/**
	 * Include @import[ed] files - The @import statement can only be used at the top of a file, which breaks when merging everything.
	 *
	 * @param string $css      - The original CSS containing the @import statement
	 * @param string $file_url - The original CSS' URL
	 * @return string
	 */
	public static function replace_css_import($css, $file_url) {
		$remove_print_mediatypes = wp_optimize_minify_config()->get('remove_print_mediatypes');
		$debug = wp_optimize_minify_config()->get('debug');
		return preg_replace_callback('/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/im', function($matches) use ($file_url, $remove_print_mediatypes, $debug) {
			// @import contains url()
			if (preg_match('/url\s*\((.[^\)]*)[\)*?](.*);/', $matches[0], $url_matches)) {
				$url = trim(str_replace(array('"', "'"), '', $url_matches[1]));
				$media_query = trim($url_matches[2]);
			// @import uses quotes only
			} elseif (preg_match('/["\'](.*)["\'](.*);?/', $matches[0], $no_url_matches)) {
				$url = trim($no_url_matches[1]);
				$media_query = trim($no_url_matches[2], ") ;");
			}
			
			// If $media_query contains print, and $remove_print_mediatypes is true, return empty string
			if ($remove_print_mediatypes && false !== strpos($media_query, 'print') && apply_filters('wpo_minfy_remove_print_mediatypes_import', true, $url, $media_query, $matches[0], $file_url)) return ($debug ? '/*! Info: the import of "'.$url.'" was removed because the setting remove_print_mediatypes is enabled. */' : '');

			$purl = wp_parse_url($url);
			// If there's no host, the url is relative to $file_url, so prepend with the base url.
			if (!isset($purl['host'])) {
				$url = dirname($file_url).'/'.$url;
			}

			// Download @import
			$asset_content = WP_Optimize_Minify_Functions::get_asset_content($url);
			$content = $asset_content['content'];
			
			if (!$content) return '';

			// Fix the URLs
			$content = WP_Optimize_Minify_Functions::make_css_urls_absolute($content, $url);

			if ($media_query) {
				// Wrap the code with the media query
				$content = "@media $media_query {\n$content\n}";
			}

			if ($debug) {
				$content = "/*! CSS import Information: code imported from $url */\n$content\n/*! END CSS import Information */";
			}
			
			// If the code contains its own @import, recursively include it.
			if (false !== strpos($content, '@import')) {
				return WP_Optimize_Minify_Functions::replace_css_import($content, $url);
			}

			return $content;
		}, $css);
	}

	/**
	 * Download and cache css and js files
	 *
	 * @param ?string $hurl
	 * @param ?string $inline
	 * @param boolean $enable_minification
	 * @param string  $type
	 * @param ?string $handle
	 * @param mixed   $version
	 *
	 * @return boolean|string
	 */
	public static function download_and_minify($hurl, $inline, $enable_minification, $type, $handle, $version = '') {
		// must have
		if (empty($hurl)) {
			return false;
		}
		if (!in_array($type, array('js', 'css'))) {
			return false;
		}

		$wpo_minify_options = wp_optimize_minify_config()->get();

		// filters and defaults
		$print_url = str_ireplace(array(site_url(), home_url(), 'http:', 'https:'), '', $hurl);

		$log = array(
			'url' => $print_url,
		);

		// defaults
		if (false !== $enable_minification) {
			$enable_minification = true;
		}
		if (empty($inline)) {
			$inline = '';
		}
		$print_handle = '';
		if (empty($handle)) {
			$handle = '';
		} else {
			$print_handle = "[$handle]";
		}

		// debug request
		$dreq = array(
			'hurl' => $hurl,
			'inline' => $inline,
			'enable_minification' => $enable_minification,
			'type' => $type,
			'handle' => $handle,
			'version' => $version
		);

		$asset_content = self::get_asset_content($hurl);
		$code = $asset_content['content'];

		// If $code is empty:
		if (!$code) {
			$log['success'] = false;
			if ($wpo_minify_options['debug']) {
				$log['debug'] = "$print_handle failed. Tried wp_remote_get and local file_get_contents.";
			}
			$return = array('request' => $dreq, 'log' => $log, 'code' => '', 'status' => false);
			return wp_json_encode($return);
		}

		if ('js' === $type) {
			$code = self::get_js($hurl, $code, $enable_minification);
		} else {
			$code = self::get_css($hurl, $code.$inline, $enable_minification);
		}

		// log, save and return
		if ($wpo_minify_options['debug']) {
			$version_msg = ('' !== $version) ? "[Version: $version]" : "";
			$log['debug'] = $print_handle . $version_msg . ' was '.('local' === $asset_content['method'] ? 'opened' : 'fetched').' from '.$hurl;
		}
		$log['success'] = true;
		$return = array('request' => $dreq, 'log' => $log, 'code' => $code, 'status' => true);
		return wp_json_encode($return);
	}

	/**
	 * Get the content of an asset, whether local or remote
	 *
	 * @param string $url
	 * @return array
	 */
	public static function get_asset_content($url) {

		$wp_home = site_url();
		$wp_domain = wp_parse_url($wp_home, PHP_URL_HOST);
		// If the file is local.
		if (false !== stripos($url, $wp_domain)) {
			// default
			$f = str_ireplace(rtrim($wp_home, '/'), rtrim(ABSPATH, '/'), $url);
			// fail over when home_url != site_url
			if (!file_exists($f)) {
				$nhurl = str_ireplace(site_url(), home_url(), $url);
				$f = str_ireplace(rtrim($wp_home, '/'), rtrim(ABSPATH, '/'), $nhurl);
			}
			clearstatcache();
			if (file_exists($f)) {
				$content = file_get_contents($f);
				// check for php code, skip if found
				if ("<?php" !== strtolower(substr($content, 0, 5)) && false === stripos($content, "<?php")) {
					return array('content' => $content, 'method' => 'local');
				}
			}
		}


		// else, fallback to remote urls (or windows)
		$content = self::download_remote($url);
		if (false !== $content
			&& !empty($content)
			&& "<!doctype" !== strtolower(substr($content, 0, 9))
		) {
			// check if we got HTML instead of js or css code
			return array('content' => $content, 'method' => 'remote');
		}


		// fallback when home_url != site_url
		if (false !== stripos($url, $wp_domain) && home_url() !== site_url()) {
			$nhurl = str_ireplace(site_url(), home_url(), $url);
			$content = self::download_remote($nhurl);
			if (false !== $content && !empty($content) && '<!doctype' !== strtolower(substr($content, 0, 9))) {
				return array('content' => $content, 'method' => 'remote');
			}
		}
		
		return array('content' => '', 'method' => 'none');
	}

	/**
	 * Remove emoji support
	 *
	 * @return void
	 */
	public static function disable_wp_emojicons() {
		remove_action('wp_head', 'print_emoji_detection_script', 7);
		remove_action('admin_print_scripts', 'print_emoji_detection_script');
		remove_action('wp_print_styles', 'print_emoji_styles');
		remove_action('wp_enqueue_scripts', 'wp_enqueue_emoji_styles');
		remove_action('admin_print_styles', 'print_emoji_styles');
		remove_action('admin_enqueue_scripts', 'wp_enqueue_emoji_styles');
		remove_filter('the_content_feed', 'wp_staticize_emoji');
		remove_filter('comment_text_rss', 'wp_staticize_emoji');
		remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
	}

	/**
	 * Remove from tinymce
	 *
	 * @return array
	 */
	public static function disable_emojis_tinymce($plugins) {
		if (is_array($plugins)) {
			return array_diff($plugins, array('wpemoji'));
		} else {
			return array();
		}
	}

	/**
	 * Remove UTF8 BOM.
	 * Returns BOM removed string or null when `$string` does not have a recognised encoding
	 *
	 * @param string $string
	 * @return string|null
	 */
	public static function remove_utf8_bom($string) {
		$bom = pack('H*', 'EFBBBF');
		return preg_replace("/^$bom/ui", '', $string);
	}

	/**
	 * Remove query string from static css files
	 *
	 * @param string $src
	 * @return string
	 */
	public static function remove_cssjs_ver($src) {
		if (stripos($src, '?ver=')) {
			$src = remove_query_arg('ver', $src);
		}
		return $src;
	}

	/**
	 * Rewrite cache files to http, https or dynamic
	 *
	 * @param string $url
	 * @return string
	 */
	public static function get_protocol($url) {
		$wp_domain = trim(preg_replace('/^https?:\/\//i', '', trim(site_url(), '/')));
		$wpo_minify_options = wp_optimize_minify_config()->get();
		$default_protocol = $wpo_minify_options['default_protocol'];

		$url = ltrim(preg_replace('/^https?:\/\//i', '', $url), '/'); // better compatibility

		// cdn support
		$cdn_url = $wpo_minify_options['cdn_url'];
		$cdn_url = trim(trim(preg_replace('/^https?:\/\//i', '', trim($cdn_url, '/'))), '/');
		
		// process cdn rewrite
		if (!empty($cdn_url) && false !== self::is_local_domain($url)) {
			
			// for js files, we need to consider thew defer for insights option
			if ('.js' === substr($url, -3)) {
				$async_using_js = 'all' === $wpo_minify_options['enable_defer_js'] && 'async_using_js' === $wpo_minify_options['defer_js_type'];
				if (!$async_using_js
					|| $wpo_minify_options['cdn_force']
				) {
					$url = str_ireplace($wp_domain, $cdn_url, $url);
				}
			} else {
				$url = str_ireplace($wp_domain, $cdn_url, $url);
			}
		}

		// enforce protocol if needed
		if ('dynamic' === $default_protocol) {
			if ((isset($_SERVER['HTTPS']) && ('on' === $_SERVER['HTTPS'] || 1 === (int) $_SERVER['HTTPS']))
				|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'])
			) {
				$default_protocol = 'https://';
			} else {
				$default_protocol = 'http://';
			}
		} else {
			$default_protocol = $default_protocol.'://';
		}
		
		// return
		return $default_protocol . $url;
	}

	/**
	 * Exclude processing from some pages / posts / contents
	 *
	 * @return boolean
	 */
	public static function exclude_contents() {
		// prevent execution for specific urls
		if (isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI'])) {
			$disable_on_url = array_filter(array_map('trim', explode("\n", get_option('wpo_min_disable_on_url', ''))));
			foreach ($disable_on_url as $url) {
				if (wp_parse_url(esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])), PHP_URL_PATH) === $url) {
					return true;
				}
			}
		}
	
		// for compatibility, let's always skip the checkout page
		if (function_exists('is_checkout') && true === is_checkout()) {
			return true;
		}
		
		if (isset($_SERVER['REQUEST_URI'])) {
			$is_txt_extension = '.txt' === strtolower(substr(esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])), -4));
			$is_xml_extension = '.xml' === strtolower(substr(esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])), -4));
		}

		// exclude processing here
		if (is_feed()
			|| is_admin()
			|| is_preview()
			|| (function_exists('is_customize_preview') && is_customize_preview())
			|| (defined('DOING_AJAX') && DOING_AJAX)
			|| (function_exists('wp_doing_ajax') && wp_doing_ajax())
			|| (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
			|| (defined('WP_BLOG_ADMIN') && WP_BLOG_ADMIN)
			|| (defined('WP_NETWORK_ADMIN') && WP_NETWORK_ADMIN)
			|| (defined('WP_INSTALLING') && WP_INSTALLING)
			|| (defined('WP_IMPORTING') && WP_IMPORTING)
			|| (defined('WP_REPAIRING') && WP_REPAIRING)
			|| (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST)
			|| (defined('SHORTINIT') && SHORTINIT)
			|| (defined('REST_REQUEST') && REST_REQUEST)
			|| (isset($_SERVER['REQUEST_METHOD']) && 'POST' === $_SERVER['REQUEST_METHOD'])
			|| (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 'xmlhttprequest' === strtolower(sanitize_text_field(wp_unslash($_SERVER['HTTP_X_REQUESTED_WITH']))))
			|| (isset($_SERVER['REQUEST_URI']) && ($is_txt_extension || $is_xml_extension))
		) {
			return true;
		}

		// phpcs:disable
		// WordPress.Security.NonceVerification.Recommended -- Using $_GET element only to compare, returns boolean
		// Thrive plugins and other post_types
		$arr = array('tve_form_type', 'tve_lead_shortcode', 'tqb_splash');
		foreach ($arr as $a) {
			if (isset($_GET['post_type']) && $a === $_GET['post_type']) {
				return true;
			}
		}

		// Thrive architect
		if (isset($_GET['tve']) && 'true' === $_GET['tve']) return true;
		

		if (is_array($_GET)) {
			foreach ($_GET as $k => $v) {
				if (is_string($v) && is_string($k)) {
					if (false !== stripos($k, 'elementor') || false !== stripos($v, 'elementor')) {
						return true;
					}
				}
			}
		}

		// Other _GET parameters
		if (is_array($_GET)) {
			$get_params = array_keys($_GET);
			$excluded_params = array(
				// customizer preview, visual composer
				'customize_theme',
				'preview_id',
				'preview',
				// Elementor
				'elementor-preview',
				// Divi builder
				'et_fb',
				'PageSpeed',
				// Brizy Editor
				'brizy-edit',
				'brizy-edit-iframe',
				// Beaver builder
				'fl_builder',
				'trp-edit-translation',
			);
			return (bool) count(array_intersect($excluded_params, $get_params));
		}
		// phpcs:enable

		/**
		 * Whether to exclude the content or not from the minifying process.
		 */
		return (bool) apply_filters('wpo_minify_exclude_contents', false);
	}

	/**
	 * Get the default files which are ignored / excluded from processing
	 *
	 * @return array
	 */
	public static function get_default_ignore() {
		$default_exclusions = array(
			'/genericons.css',
			'/Avada/assets/js/main.min.js',
			'/woocommerce-product-search/js/product-search.js',
			'/includes/builder/scripts/frontend-builder-scripts.js',
			'/assets/js/jquery.themepunch.tools.min.js',
			'/js/TweenMax.min.js',
			'/jupiter/assets/js/min/full-scripts',
			'/wp-content/themes/Divi/core/admin/js/react-dom.production.min.js',
			'/LayerSlider/static/layerslider/js/greensock.js',
			'/themes/kalium/assets/js/main.min.js',
			'/wp-includes/js/mediaelement/wp-mediaelement.min.js',
			'elementor-admin-bar',
			'pdfjs-dist',
			'wordpress-popular-posts',
			'uploads/bb-plugin/cache', // Beaver builder page specific pages
		);
		/**
		 * Filters the default exclusions
		 *
		 * @param array The exclusions
		 * @return array
		 */
		$filtered_exclusions = apply_filters('wp-optimize-minify-default-exclusions', $default_exclusions);
		return is_array($filtered_exclusions) ? $filtered_exclusions : $default_exclusions;
	}

	/**
	 * Know files that should always be ignored
	 *
	 * @param array $ignore
	 * @return array
	 */
	public static function compile_ignore_list($ignore) {
		$ignore_list = self::get_default_ignore();
		$user_excluded_ignore_items = wp_optimize_minify_config()->get('ignore_list');
		if (is_array($user_excluded_ignore_items)) $ignore_list = array_diff($ignore_list, $user_excluded_ignore_items);
		if (is_array($ignore)) {
			$master_ignore = array_merge(array_map('strtolower', $ignore), array_map('strtolower', $ignore_list));
			return array_unique($master_ignore);
		} else {
			return $ignore_list;
		}
	}

	/**
	 * Get the files excluded for IE compatibility
	 *
	 * @return array
	 */
	public static function get_default_ie_blacklist() {
		$default_ie_blacklist = array(
			'/html5shiv.js',
			'/html5shiv-printshiv.min.js',
			'/excanvas.js',
			'/avada-ie9.js',
			'/respond.js',
			'/respond.min.js',
			'/selectivizr.js',
			'/Avada/assets/css/ie.css',
			'/html5.js',
			'/IE9.js',
			'/fusion-ie9.js',
			'/vc_lte_ie9.min.css',
			'/old-ie.css',
			'/ie.css',
			'/vc-ie8.min.css',
			'/mailchimp-for-wp/assets/js/third-party/placeholders.min.js',
			'/assets/js/plugins/wp-enqueue/min/webfontloader.js',
			'/a.optnmstr.com/app/js/api.min.js',
			'/pixelyoursite/js/public.js',
			'/assets/js/wcdrip-drip.js',
			'/instantpage.js',
		);
		/**
		 * Filters the default IE specific / blacklisted items
		 *
		 * @param array The blacklist
		 * @return array
		 */
		$filtered_ie_blacklist = apply_filters('wp-optimize-minify-blacklist', $default_ie_blacklist);
		return is_array($filtered_ie_blacklist) ? $filtered_ie_blacklist : $default_ie_blacklist;
	}

	/**
	 * Get the files excluded for IE compatibility
	 *
	 * @return array
	 */
	public static function get_ie_blacklist() {
		$user_excluded_blacklist_items = wp_optimize_minify_config()->get('blacklist');
		$default_blacklist = self::get_default_ie_blacklist();
		if (!is_array($user_excluded_blacklist_items)) return $default_blacklist;
		return array_diff($default_blacklist, $user_excluded_blacklist_items);
	}

	/**
	 * IE only files that should always be ignored, without incrementing our groups
	 *
	 * @param string $url
	 * @return boolean
	 */
	public static function is_url_in_ie_blacklist($url) {
		// from the database
		$blacklist = self::get_ie_blacklist();
		// must have
		$blacklist[] = '/wpo_min/cache/';
		
		// is the url on our list and return
		$res = self::in_arrayi($url, $blacklist);
		if ($res) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Download function with fallback
	 *
	 * @param string $url
	 * @return boolean|string
	 */
	public static function download_remote($url) {
		
		$args = array(
			// info (needed for Google fonts woff files + hinted fonts) as well as to bypass some security filters
			'user-agent' => WP_Optimize_Utils::get_user_agent(),
			'timeout' => 7
		);

		// fetch via WordPress functions
		$response = wp_remote_get(
			$url,
			/**
			 * Filters the arguments passed to wp_remote_get when downloading the scripts.
			 *
			 * @param array  $args - The arguments filtered
			 * @param string $url  - The URL of the downloaded asset
			 * @return array
			 */
			apply_filters('wpo_minify_download_request_args', $args, $url)
		);

		$res_code = wp_remote_retrieve_response_code($response);
		if (200 === $res_code) {
			$data = wp_remote_retrieve_body($response);
			if (strlen($data) > 1) {
				return $data;
			}
		}
		
		return false;
	}

	/**
	 * Prepares the merged JavaScript
	 *
	 * @param string $script
	 * @param string $merged_url
	 * @return string
	 */
	public static function prepare_merged_js($script, $merged_url) {
		$enable_js_trycatch = wp_optimize_minify_config()->get('enable_js_trycatch');
		if ($enable_js_trycatch) {
			return 'try{'."\n".$script."\n".'}'."\n".'catch(e){console.error("WP-Optimize Minify: An error has occurred in the minified code. \n\n- Original script: '.esc_attr($merged_url).'\n- Error message: "+ e.message);}'."\n";
		}
		return $script;
	}

	/**
	 * Checks if URL is a font-awesome resource (checks if it contains font-awesome or fontawesome)
	 *
	 * @param string $href
	 * @return boolean
	 */
	public static function is_font_awesome($href) {
		return (boolean) preg_match('/font[-_]?awesome/i', $href);
	}

	/**
	 * Checks if URL is a Google font resource
	 *
	 * @param string $href
	 * @return boolean
	 */
	public static function is_google_font($href) {
		return 'fonts.googleapis.com' === strtolower(wp_parse_url($href, PHP_URL_HOST));
	}

	/**
	 * Get the content of an asset, whether local or remote
	 *
	 * @param string $url
	 * @return int|false
	 */
	public static function get_file_size($url) {
		$original_url = $url;
		if (is_multisite()) {
			if (function_exists('get_main_site_id')) {
				$site_id = get_main_site_id();
			} else {
				$network = get_network();
				$site_id = $network->site_id;
			}
			switch_to_blog($site_id);
		}
		$upload_dir = wp_upload_dir();
		$uploads_url = trailingslashit($upload_dir['baseurl']);
		$uploads_dir = trailingslashit($upload_dir['basedir']);
		if (is_multisite()) {
			restore_current_blog();
		}
		$possible_urls = array(
			WP_CONTENT_URL => WP_CONTENT_DIR,
			WP_PLUGIN_URL => WP_PLUGIN_DIR,
			$uploads_url => $uploads_dir,
			get_template_directory_uri() => get_template_directory(),
			untrailingslashit(includes_url()) => ABSPATH . WPINC,
		);

		$file = false;
		foreach ($possible_urls as $possible_url => $path) {
			$pos = strpos($url, $possible_url);
			if (false !== $pos) {
				$file = substr_replace($url, $path, $pos, strlen($possible_url));
				break;
			}
		}
		if (is_string($file) && file_exists($file)) {
			return filesize($file);
		}

		return self::get_remote_file_size($original_url);
	}

	/**
	 * Get the file size of a file hosting in other servers
	 *
	 * @param string $url
	 * @return int|false
	 */
	public static function get_remote_file_size($url) {
		$args = array(
			// info (needed for Google fonts woff files + hinted fonts) as well as to bypass some security filters
			'user-agent' => WP_Optimize_Utils::get_user_agent(),
			'timeout' => 7
		);

		// fetch via WordPress functions
		$response = wp_remote_get($url, $args);

		if (is_wp_error($response)) return false;

		if (!empty($response) && is_array($response) && isset($response['headers']['Content-Length'])) {
			return $response['headers']['Content-Length'];
		}

		return false;
	}

	/**
	 * Check if it is 'flatsome' handle
	 *
	 * @param string $handle Handle of enqueued css file
	 *
	 * @return bool
	 */
	public static function is_flatsome_handle($handle) {
		return 0 === strcmp('flatsome-googlefonts', $handle);
	}

	/**
	 * Fix google fonts url for 'flatsome' theme
	 *
	 * @param string $href Enqueued google fonts url
	 *
	 * @return string Fixed google fonts url
	 */
	public static function fix_flatsome_google_fonts_url($href) {
		// Get query from $href
		$query = wp_parse_url($href, PHP_URL_QUERY);
		$query_arr = explode('&', $query);

		// Separate 'family and display' arguments in query
		$family = str_replace('family=', '', $query_arr[0]);
		$display_type = $query_arr[1];
		
		// Remove empty fonts
		$font_families = explode('|', $family);
		$font_families = array_diff($font_families, array(':regular', 'initial', 'inherit', ''));
		
		$system_fonts = array('+apple+system', '-apple-system', 'BlinkMacSystemFont', 'Segoe+UI', 'Helvetica+Neue', 'sans+serif', 'sans-serif', 'Georgia', 'Times', 'Times+New+Roman', 'serif');
		$google_fonts = array();

		// URL may look like following, fix it
		// https://fonts.googleapis.com/css?family=-apple-system,+BlinkMacSystemFont,+%22Segoe+UI%22,+Roboto,+Oxygen-Sans,+Ubuntu,+Cantarell,+%22Helvetica+Neue%22,+sans-serif:regular,700,regular,700|Bebas+Neue:regular,regular&display=block
		// http://fonts.googleapis.com/css?family=Bellota:regular|:regular|Lato:regular|Creepster:regular&display=block
		// http://fonts.googleapis.com/css?family=Lora:regular,regular|initial|Lato:regular,regular|&display=swap

		foreach ($font_families as $font) {
			$font_variant = explode(':', $font);

			// Remove beginning '+'
			$font = str_replace(',+', ',', $font_variant[0]);

			// Replace '-' with '+'
			$font = str_replace(array('-', ' '), '+', $font);

			// Remove `"` or '%22'
			$font = str_replace(array('%22', '"'), '', $font);
			$font_arr = explode(',', $font);
			$font_arr = array_diff($font_arr, $system_fonts);
			$variant = '';
			if (!empty($font_variant[1])) {
				$variant = implode(',', array_unique(explode(',', $font_variant[1])));
			}
			if (empty($variant)) {
				$variant = 'regular';
			}
			foreach ($font_arr as $font) {
				$google_fonts[] = $font . ':' . $variant;
			}
		}
		$protocol = is_ssl() ? 'https:' : 'http:';
		return $protocol . '//fonts.googleapis.com/css?family=' . implode('|', $google_fonts) . '&' . $display_type;
	}

	/**
	 * Get the file modification time
	 *
	 * @param string $asset_src
	 * @return string
	 */
	public static function get_modification_time($asset_src) {
		$hurl = self::get_hurl($asset_src);
		$abs_file_path = WP_Optimize_Utils::get_file_path($hurl);
		if (empty($abs_file_path)) return '';

		$modification_time = strval(@filemtime($abs_file_path)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Suppress E-Warning on failure
		$filtered_modification_time = apply_filters('wpo_minify_file_modification_time', $modification_time, $abs_file_path);

		return is_string($filtered_modification_time) ? $filtered_modification_time : $modification_time;
	}

	/**
	 * When BOM removed code is null (due to unrecognised character encoding), logs error message
	 *
	 * @param string        $url      URL of the script/stylesheet
	 * @param string|false  $encoding Character encoding
	 * @param string|null   $code     Script/Stylesheet code
	 *
	 * @return void
	 */
	private static function maybe_log_error_message($url, $encoding, $code) {
		if (null === $code) {
			$message = "Minify: Could not process {$url}, it contains invalid characters. ";
			if (false === $encoding) {
				$message .= "Could not determine its character encoding.";
			}
			error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Using for debugging purpose
		}
	}
}
