<?php
/**
 * This class contains all Pro and Extra features
 */

if (!class_exists('Swift_Performance_Pro')){
      class Swift_Performance_Pro {
            /**
             * Construct Pro object
             */
            public function __construct(){
                  // Background requests
                  self::background_requests();

                  // Limit WP Cron requests
                  self::limit_wp_cron();

                  // WooCommerce features
                  self::init_woocommerce();

                  // Plugin Updater
                  if (defined('SWIFT_PERFORMANCE_FILE')){
                        add_action('init', array('Swift_Performance_Pro', 'plugin_updater'));
                  }
            }

            /**
             * Plugin Updater (Pro Only)
             */
            public static function plugin_updater(){
                  if (!defined('SWIFT_PERFORMANCE_UNINSTALL') && !defined('SWIFT_PERFORMANCE_DISABLE_UPDATE')){
                        require 'puc/plugin-update-checker.php';
                        $update_checker = Puc_v4_Factory::buildUpdateChecker(
                              add_query_arg(array('purchase-key' => Swift_Performance::get_option('purchase-key'), 'site' => Swift_Performance::home_url(), 'beta' => Swift_Performance::get_option('enable-beta')), SWIFT_PERFORMANCE_API_URL . 'update/info/'),
                              SWIFT_PERFORMANCE_FILE,
                              SWIFT_PERFORMANCE_SLUG
                        );

                        // Add purchase key to download url
                        add_filter('puc_request_info_result-' . SWIFT_PERFORMANCE_SLUG, function($info){
                              @$info->download_url = str_replace('[[PARAMETERS]]', '?purchase-key=' . Swift_Performance::get_option('purchase-key') . '&site=' . Swift_Performance::home_url() . '&beta=' . Swift_Performance::get_option('enable-beta'), $info->download_url);
                              return $info;
                        });
                  }
            }

            /**=======================================**/
                        /*** FONTS ***/
            /**=======================================**/

            /**
      	 * Collect and preload fonts (Pro only)
      	 * @param string critical_css
      	 * @param string html
      	 * @return array
      	 */
      	 public static function preload_fonts($critical_css, $html){
      		$font_preload = $font_collections = array();

      		// Merge critical CSS for viewport based mode
      		if (is_array($critical_css)){
      			$critical_css = implode("\n", $critical_css);
      		}

      		// Get exceptions
      		$exclude_strings	= array_filter((array)Swift_Performance::get_option('exclude-preload-fonts'));

      		// Collect mentioned weights
      		preg_match_all('~font-weight:\s?(bold|\d{3})~', preg_replace('~@font-face\{([^\}]*)\}~', '', $critical_css), $mentioned_weights);
      		$mentioned_weights = array_map(function($value){
      			return str_replace(array('bold', 'normal'), array('700', '400'), $value);
      		}, $mentioned_weights[1]);

      		preg_match_all('~@font-face\{([^\}]*)\}~', $critical_css, $matches);
       		foreach ((array)$matches[1] as $font_face){
       			$rules = array();

      			// Parse rules
       			foreach (explode(';', $font_face) as $rule){
      				$exploded = explode(':', $rule);
      				$key = array_shift($exploded);
      				$value = implode(':', $exploded);
       				$rules[trim($key)] = trim($value);
       			}

      			// Skip if src is missing
      			if (!isset($rules['src'])){
      				continue;
      			}

      			// Skip excluded fonts
      			$is_excluded = false;
      			foreach ((array)$exclude_strings as $exclude_string){
      				if (strpos($rules['src'], $exclude_string) !== false){
      					$is_excluded = true;
      				}
      			}

      			// Skip
      			if ($is_excluded){
      				continue;
      			}

       			/*
      			 * Check unicode range
      			 */
       			if (isset($rules['unicode-range'])){
       				$in_range = false;
       				$ranges = explode(',', $rules['unicode-range']);
       				$ranges = array_map(function($value) {
       					$value = str_replace('u+', '', strtolower($value));
       					$value = preg_replace('~([abcdef0-9]+)~', '\x{$1}', $value);
       					return $value;
       				}, $ranges);
       				foreach ($ranges as $range) {
       					if (preg_match('~['.$range.']+~u', $html)){
       						$in_range = true;
       					}
       				}

       				if (!$in_range){
       					continue;
       				}
       			}

      			//Check font weights
      			// Font file weight
      			$weight = (isset($rules['font-weight']) ? str_replace(array('normal', 'bold'), array('400', '700'), $rules['font-weight']) : '400');

      			if ($weight != 400 && !in_array($weight, $mentioned_weights)){
      				continue;
      			}

       			/*
      			 * Add fonts to collection
      			 */

      			foreach (explode(',', $rules['src']) as $src){
      				preg_match('~url\(([^\)]*)\.(eot|woff2?|ttf)\)~', $src, $url);
      				if (isset($url[1]) && isset($url[2])){
      					$format	= $url[2];
      					$font		= $url[1];
      					$font_url	= (preg_match('~^//~', $font) ? 'https:' : (preg_match('~^/~', $font) ? trim(Swift_Performance::home_url(),'/') : '')) . $font . '.' . $format;

      					$font_collections[$font][$format] = $font_url;
      				}
      			}

       		}

      		// Preload only the best available format
      		foreach ((array)$font_collections as $font_collection){
      			$font_src = $format = '';

      			if (isset($font_collection['woff2'])){
      				$font_src	= $font_collection['woff2'];
      				$format	= 'woff2';
      			}
      			else if (isset($font_collection['woff'])){
      				$font_src	= $font_collection['woff'];
      				$format	= 'woff';
      			}
      			else if (isset($font_collection['eot'])){
      				$font_src	= $font_collection['eot'];
      				$format	= 'eot';
      			}

                        if (!empty($format)){
                              $font_preload[] = '<link rel="preload" href="'.$font_src.'" as="font" type="font/' . $format . '" crossorigin>';
                        }

      			// Load TTF for older browsers if available
      			if (isset($font_collection['ttf'])){
      				$font_src	= $font_collection['ttf'];
      				$format	= 'ttf';
                              $font_preload[] = '<link rel="preload" href="'.$font_src.'" as="font" type="font/' . $format . '" crossorigin>';
      			}


      		}

      		return $font_preload;
      	 }

            /**
             * Manual preload fonts (Pro only)
             * @return array
             */
            public static function manual_preload_fonts(){
                  $font_preload = array();
                  $manual_preload_fonts = array_filter((array)Swift_Performance::get_option('manual-preload-fonts'));
                  if (!empty($manual_preload_fonts)){
                        foreach ((array)$manual_preload_fonts as $manual_preload_font){
                              $manual_preload_font_format = pathinfo(parse_url($manual_preload_font, PHP_URL_PATH), PATHINFO_EXTENSION);
                              $font_preload[] = '<link rel="preload" href="'.$manual_preload_font.'" as="font" type="font/' . $manual_preload_font_format . '" crossorigin>';
                        }
                  }
                  return $font_preload;
            }

            /**
             * Force font display swap
             * @param string
             * @return string
             */
            public static function font_display_swap($_css){
                  $_css = preg_replace_callback('~@font-face\s?\{([^\{\}]+)\}~', function($matches){
                        $rule = trim($matches[1]);
                        preg_match('~font-family\s?:([^;]+)~', $rule, $family);
                        $family = strtolower(preg_replace('~("|\')~','',$family[1]));
                        $excluded = (array)Swift_Performance::get_option('exclude-font-display');
                        if (!in_array($family, $excluded)){
                              if (strpos($rule, 'font-display') !== false){
                                    $rule = preg_replace('~font-display:\s?(auto|block);?~', 'font-display:swap;', $rule);
                              }
                              else {
                                    $rule = $rule . (substr($rule, -1, 1) == ';' ? '' : ';') . 'font-display:swap;';
                              }
                        }
                        return '@font-face{'.$rule.'}';
                  }, $_css);

                  return $_css;
            }

            /**
      	 * Host fonts locally
      	 * @param string $css
      	 * @param string $css_dir
      	 * @return string
      	 */

      	public static function local_fonts($matches){
      		if (!isset($matches[2]) || !isset($matches[3]) || empty($matches[2]) || empty($matches[3])){
      			return $matches[0];
      		}

      		$current_path = ltrim(trailingslashit(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)),'/');

      		$font_dir = trailingslashit(apply_filters('swift_performance_css_dir', Swift_Performance::check_option('separate-css', 1) ? $current_path . 'css/fonts' : 'css/fonts'));
      		$font_src = (preg_match('~^//~', $matches[2]) ? 'https:' : '') . $matches[2] . '.' . $matches[3];

      		if (strpos(Swift_Performance::home_url(), $font_src) === 0 || strpos('/', $font_src)){
      			return $matches[0];
      		}

      		$filename = basename($font_src);
      		if (!file_exists(SWIFT_PERFORMANCE_CACHE_DIR . $font_dir . $filename)){
      			$response = wp_remote_get($font_src, array('timeout' => 10, 'sslverify' => false));
      			if (!is_wp_error($response) && !empty($response['body'])){
      				return 'url(' . Swift_Performance_Cache::write_file($font_dir . $filename, $response['body']) . ')';
      			}
      			else {
      				return $matches[0];
      			}
      		}
      		return 'url(' . SWIFT_PERFORMANCE_CACHE_URL . $font_dir . $filename . ')';
      	}

            /**=======================================**/
                        /*** AJAXIFY ***/
            /**=======================================**/

            /**
            * Generate ajaxify styles and scripts and add them to buffer (Pro only)
            * @param string buffer
            * @return string
            */
            public static function get_ajaxify_scirpts($buffer){
                  $ajaxify = apply_filters('swift_performance_ajaxify', Swift_Performance::get_option('ajaxify'));
                  if (!empty($ajaxify)){
                        ob_start();
                        include SWIFT_PERFORMANCE_DIR . 'modules/cache/ajaxify.php';
                        $ajaxify_html = ob_get_clean();
                        $buffer = str_replace('</body>',"{$ajaxify_html}\n</body>", $buffer);
                  }

                  $lazyload_shortcodes    = array_filter((array)Swift_Performance::get_option('lazyload-shortcodes'));
                  $lazyload_widgets       = array_filter((array)Swift_Performance::get_option('lazyload-widgets'));
                  if (!empty($lazyload_shortcodes) || !empty($lazyload_widgets) || Swift_Performance::check_option('lazyload-blocks', 1) || Swift_Performance::check_option('woocommerce-price-ajaxify', 1)){

                        $preload_point = (int)Swift_Performance::get_option('ajaxify-preload-point');

                        $ajaxify_script = "<script data-dont-merge>(function() { function iv(a) { if (typeof a.getBoundingClientRect !== 'function') { return false } var b = a.getBoundingClientRect(); return (b.bottom + {$preload_point} >= 0 && b.right + {$preload_point} >= 0 && b.top - {$preload_point} <= (window.innerHeight || document.documentElement.clientHeight) && b.left - {$preload_point} <= (window.innerWidth || document.documentElement.clientWidth))}function ll() { var elements = document.querySelectorAll('.swift-lazy-wrapper:not(.swift-is-loading)'); elements.forEach(function(element){ if (iv(element)) { element.classList.add('swift-is-loading'); var data = element.dataset['request']; var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function(){if(this.readyState==4&&this.status==200){element.innerHTML=this.responseText;element.classList.remove('swift-lazy-wrapper');element.classList.remove('swift-is-loading');}};xhttp.open('POST', '".admin_url('admin-ajax.php')."', true); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhttp.send('action=swift_performance_ajaxify&data=' + encodeURIComponent(data)); } }); if (elements.length > 0){ requestAnimationFrame(ll) } } requestAnimationFrame(ll)})();</script>";

                        $ajaxify_style = '';
                        if (Swift_Performance::check_option('ajaxify-placeholder', 'blur')){
                              $ajaxify_style = '<style>.swift-lazy-wrapper{display:block;filter:blur(5px);pointer-events:none;}</style>';
                        }
                        else if (Swift_Performance::check_option('ajaxify-placeholder', 'hidden')){
                              $ajaxify_style = '<style>.swift-lazy-wrapper{opacity:0;pointer-events:none;}</style>';
                        }

                        $buffer = str_replace('<head>',"<head>\n{$ajaxify_style}", $buffer);

                        $buffer = str_replace('</body>',"{$ajaxify_script}\n</body>", $buffer);
                  }

                  return $buffer;
            }


            /**
            * Filter for do_shortcode_tag to modify html output for lazyloading (Pro only)
            * @param string content
            * @param string shortcode
            * @param array atts
            * @return string
            */
            public static function lazyload_shortcode($content, $shortcode, $atts){
                  $lazyload_shortcodes = (array)Swift_Performance::get_option('lazyload-shortcode');

                  if (!is_admin() && !Swift_Performance::is_rest() && in_array($shortcode, $lazyload_shortcodes)){
                        $request = base64_encode(json_encode(array('shortcode', get_the_ID(), Swift_Performance_Cache::set_lazyload_buffer(array($shortcode, $atts)))));
                        return '<span class="swift-lazy-wrapper" data-request="'.$request.'">'.$content.'</span>';
                  }
                  return $content;
            }

            /**
            * Filter for Gutenberg block to modify html output for lazyloading (Pro only)
            * @param string content
            * @param array block
            * @return string
            */
            public static function lazyload_blocks($content, $block){
                  if (isset($block['attrs']['isSwiftPerfrormanceLazyloaded']) && $block['attrs']['isSwiftPerfrormanceLazyloaded'] && !is_admin() && !Swift_Performance::is_rest()){
                        return '<span class="swift-lazy-wrapper" data-request="'.base64_encode(json_encode(array('block', get_the_ID(), Swift_Performance_Cache::set_lazyload_buffer($block)))).'">'.$content.'</span>';
                  }
                  return $content;
            }

            /**
            * Filter for widget_display_callback to modify html output for lazyloading (Pro only)
            * @param WP_Wigdet instance
            * @param object WP_Widget
            * @param array args
            * @return string
            */
            public static function lazyload_widgets($instance, $that, $args){
                  $lazyload_widgets = (array)Swift_Performance::get_option('lazyload-widgets');
                  if (!in_array(get_class($that), $lazyload_widgets) || defined('DOING_AJAX') && DOING_AJAX){
                        return $instance;
                  }
                  $request = base64_encode(json_encode(array('widget', get_the_ID(), Swift_Performance_Cache::set_lazyload_buffer(array(get_class($that), $instance, $args)))));
                  $args['before_widget'] = '<span class="swift-lazy-wrapper" data-request="' . $request . '">' . $args['before_widget'];
                  $args['after_widget'] .= '</span>';
                  $that->widget( $args, $instance );
                  return false;
            }

            /**=======================================**/
                        /*** WOOCOMMERCE ***/
            /**=======================================**/

            /**
             * Init WooCommerce features
             */
            public static function init_woocommerce(){
                  // WooCommerce GEOIP
                  if (Swift_Performance::check_option('woocommerce-geoip-support',1) && Swift_Performance::check_option('caching-mode', array('memcached_php', 'disk_cache_php'), 'IN')){
                        add_filter('swift_performance_cache_folder_prefix', array(__CLASS__, 'woocommerce_geoip_prefix'));
                        add_action('swift_performance_prebuild_cache_hit', array(__CLASS__, 'woocommerce_geiop_prebuild'));
                  }

                  // WooCommerce Session Cache
                  if (Swift_Performance::check_option('woocommerce-session-cache', 1)){
                        add_action( 'swift_performance_woocommerce_session_cache_prebuild', array(__CLASS__, 'woocommerce_session_cache_prebuild'), 10, 2);

                        global $wpdb;
                        $shop_pages = (array)$wpdb->get_col("SELECT post_name FROM {$wpdb->posts} LEFT JOIN {$wpdb->options} ON option_value = ID WHERE option_name IN ('woocommerce_cart_page_id', 'woocommerce_checkout_page_id')");

                        foreach (array('wp_login','woocommerce_ajax_added_to_cart', 'woocommerce_removed_coupon','woocommerce_cart_emptied','woocommerce_add_to_cart','woocommerce_cart_item_removed','woocommerce_cart_item_restored','woocommerce_applied_coupon') as $action){
                              add_action($action, array(__CLASS__, 'woocommere_clear_session_cache'), PHP_INT_MAX);
                        }
                        add_filter('woocommerce_update_cart_action_cart_updated', array(__CLASS__, 'woocommere_clear_session_cache'), PHP_INT_MAX);

                        $cookie_name = apply_filters( 'woocommerce_cookie', 'wp_woocommerce_session_' . COOKIEHASH );
                        if (in_array(trim($_SERVER['REQUEST_URI'],'/'), $shop_pages) && isset($_COOKIE[$cookie_name]) && !empty($_COOKIE[$cookie_name]) && !isset($_POST['update_cart'])) {
                              add_filter('swift_performance_is_cacheable_dynamic', '__return_true');
                              $_POST['woocommerce-session-cache'] = md5($_COOKIE[$cookie_name]);

                              if (Swift_Performance::check_option('optimize-woocommerce-session-cache', 1, '!=')){
                                    Swift_Performance::set_option('merge-scripts', 0);
                                    Swift_Performance::set_option('merge-styles', 0);
                                    add_filter('swift_performance_dynamic_cache_expiry', function(){
                                          return 3600;
                                    });
                              }
                        }
                  }

                  // Disable WooCommerce Cart Fragments AJAX
                  add_action( 'wp_enqueue_scripts', array(__CLASS__, 'dequeue_woocommerce_cart_fragments'), 11);


                  // WooCommerce Geolocation + Caching prebuild fix
                  add_filter('option_woocommerce_default_customer_address', function($value){
                        if ($value == 'geolocation_ajax' && (isset($_GET['swift-preview']) || isset($_SERVER['HTTP_X_PREBUILD']) || isset($_SERVER['HTTP_X_MERGE_ASSETS']))){
                              return 'base';
                        }
                        else {
                              return $value;
                        }
                  });

                  // AJAXIFY WooCommerce prices
                  if (Swift_Performance::check_option('woocommerce-price-ajaxify', 1)){
                        add_filter('woocommerce_get_price_html', function($content, $product){
                              if (!is_admin() && !Swift_Performance::is_rest()){
                                    $request = base64_encode(json_encode(array('woo-price', get_the_ID(), $product->get_id())));
                                    return '<span class="swift-lazy-wrapper" data-request="'.$request.'">'.$content.'</span>';
                              }
                              return $content;
                        },10,2);
                  }

                  // Prebuild WooCommerce Variations
                  if (Swift_Performance::check_option('prebuild-woocommerce-variations',1)){
                        Swift_Performance::set_option('dynamic-caching',1);
                        add_filter('swift_performance_option_cacheable-dynamic-requests', function($params){
                              foreach ($_GET as $key => $value){
                                    if (preg_match('~^attribute_pa_~', $key)){
                                          $params[] = $key;
                                    }
                              }
                              return $params;
                        });

                        add_filter('swift_performance_is_object_cacheable', function($result, $url){
                              parse_str(parse_url($url, PHP_URL_QUERY), $get);
                              foreach ($get as $key => $value){
                                    if (preg_match('~^attribute_pa_~', $key)){
                                          return true;
                                    }
                              }
                              return $result;
                        },10 ,2);

                        add_action('swift_performance_prebuild_cache_hit', array(__CLASS__, 'woocommerce_variation_prebuild'));
                  }
            }

            /**
            * Disable Cart Fragments
            */
            public static function dequeue_woocommerce_cart_fragments() {
                  $disable = false;
                  if (Swift_Performance::check_option('disable-cart-fragments', 'everywhere')){
                        $disable = true;
                  }
                  else if (Swift_Performance::check_option('disable-cart-fragments', 'non-shop')){
                        global $wpdb;
                        $results = $wpdb->get_col("SELECT option_value FROM {$wpdb->options} WHERE option_name LIKE 'woocommerce_%_page_id'", ARRAY_A);

                        if ((!function_exists('is_shop') || !is_shop()) || in_array(get_the_ID(), $results)){
                              $disable = true;
                        }
                  }
                  else if (Swift_Performance::check_option('disable-cart-fragments', 'specified-pages')){
                        $pages = (array)Swift_Performance::get_option('disable-cart-fragments-pages');
                        if (in_array(get_the_ID(), $pages)){
                              $disable = true;
                        }
                  }
                  else if (Swift_Performance::check_option('disable-cart-fragments', 'specified-urls')){
                        $urls = (array)Swift_Performance::get_option('disable-cart-fragments-urls');
                        foreach ($urls as $url){
                              if (strpos(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), parse_url($url, PHP_URL_PATH)) !== false){
                                    $disable = true;
                              }
                        }
                  }
                  if ($disable){
                        wp_dequeue_script('wc-cart-fragments');
                  }
            }

            /**
            * Run prebuild cache for all enabled countries
            */
            public static function woocommerce_geiop_prebuild($permalink){
                  // Get allowed countries
                  $allowed_countries = array_filter((array)Swift_Performance::get_option('woocommerce-geoip-allowed-countries'));

                  if(empty($allowed_countries) && file_exists(WP_PLUGIN_DIR . '/woocommerce/i18n/countries.php')){
                        $allowed_countries = array_keys((array)apply_filters( 'woocommerce_countries', include WP_PLUGIN_DIR . '/woocommerce/i18n/countries.php'));
                  }

                  foreach ((array)$allowed_countries as $allowed_country) {
                        // Add country code to prebuild header
                        add_filter('swift_performance_prebuild_headers', function($headers) use ($allowed_country){
                              $headers['X-swift-country-code'] = strtoupper($allowed_country);
                              return $headers;
                        });

                        // Add country code to mobile prebuild header
                        add_filter('swift_performance_mobile_prebuild_headers', function($headers) use ($allowed_country){
                              $headers['X-swift-country-code'] = strtoupper($allowed_country);
                              return $headers;
                        });

                        Swift_Performance::prebuild_cache_hit($permalink);
                  }
            }

            /**
            * Add country prefix
            */
            public static function woocommerce_geoip_prefix($prefix){
                  if (isset($_SERVER['HTTP_X_SWIFT_COUNTRY_CODE'])){
                        add_filter('woocommerce_geolocate_ip', function(){
                              return $_SERVER['HTTP_X_SWIFT_COUNTRY_CODE'];
                        });
                  }

                  if(@file_exists(WP_PLUGIN_DIR . '/woocommerce/includes/class-wc-geolocation.php')){
                        include_once WP_PLUGIN_DIR . '/woocommerce/includes/class-wc-geolocation.php';
                        include_once WP_PLUGIN_DIR . '/woocommerce/includes/class-wc-geo-ip.php';

                        $geoloacte            = WC_Geolocation::geolocate_ip();
                        $allowed_countries    = (array)Swift_Performance::get_option('woocommerce-geoip-allowed-countries');
                        if (isset($geoloacte['country']) && !empty($geoloacte['country']) && (empty($allowed_countries) || in_array($geoloacte['country'], $allowed_countries)) ){
                              $prefix    = $geoloacte['country'];
                        }
                  }

                  return $prefix;
            }

            /**
            * Prebuild variations for WooCommerce product
            */
            public static function woocommerce_variation_prebuild($permalink){
                  @$maybe_product_id      = url_to_postid($permalink);
                  $maybe_product          = wc_get_product($maybe_product_id);
                  if (!empty($maybe_product) && get_class($maybe_product) == 'WC_Product_Variable'){
                        foreach ((array)$maybe_product->get_available_variations() as $variation){
                              $variation_permalink = get_permalink($variation['variation_id']);
                              Swift_Performance::log('Prebuild variation for ' . $maybe_product->get_name() . ', variation ID: ' . $variation['variation_id']);
                              Swift_Performance::prebuild_cache_hit($variation_permalink);
                        }

                  }
            }

            /**
            * Clear WooCommerce session cache
            */
            public static function woocommere_clear_session_cache($param){
                  global $wpdb;
                  $cookie_name = apply_filters( 'woocommerce_cookie', 'wp_woocommerce_session_' . COOKIEHASH );
                  if (isset($_COOKIE[$cookie_name])){
                        $hash = hash('crc32', serialize(array('woocommerce-session-cache' => md5($_COOKIE[$cookie_name]))));
                        $transients = $wpdb->get_col("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_swift_performance_dynamic_%_{$hash}'");
                        foreach ($transients as $transient) {
                              delete_transient(str_replace('_transient_','',$transient));
                        }

                        // Prebuild cache
                        $useragent = apply_filters('swift_performance_session_cache_useragent', (isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:52.0) Gecko/20100101 Firefox/52.0'));
                        wp_schedule_single_event(time(), 'swift_performance_woocommerce_session_cache_prebuild', array($_COOKIE, $useragent));
                  }

                  return $param;
            }

            /**
            * Preload session cache
            * @param array $user_cookies
            */
            public static function woocommerce_session_cache_prebuild($user_cookies, $useragent){
                  global $wpdb;
                  $shop_pages = (array)$wpdb->get_col("SELECT post_name FROM {$wpdb->posts} LEFT JOIN {$wpdb->options} ON option_value = ID WHERE option_name IN ('woocommerce_cart_page_id', 'woocommerce_checkout_page_id')");
                  $cookies = array();

                  foreach ($user_cookies as $name => $value) {
                      $cookies[] = new WP_Http_Cookie( array( 'name' => $name, 'value' => $value ) );
                  }

                  foreach ($shop_pages as $shop_page){
                        $response = wp_remote_get( trailingslashit(home_url($shop_page)), array('sslverify' => false,'useragent' => $useragent, 'headers' => array('X-merge-assets' => 'true', 'X-Prebuild' => 'true'),  'cookies' => $cookies ) );
                  }

            }




            /**=======================================**/
                        /*** IMAGES ***/
            /**=======================================**/

            /**
            * Collect and preload images (Pro only)
            * @param string buffer
            * @param array media_preload
            * @return array
            */
            public static function preload_media($buffer, $media_preload){
                 $preload_images_by_url = array_filter((array)Swift_Performance::get_option('preload-images-by-url'));

                 if (!empty($preload_images_by_url)){
                       preg_match_all('~url\s?\((\'|")?((((?!\)).)*)\.(png|jpg|webp|gif|svg))(\'|")?\)~', $buffer, $matches);
                       if (isset($matches[2])){
                             foreach ((array)$matches[2] as $src){
                                   if (preg_match('~('.implode('|', $preload_images_by_url).')~', $src)){
                                         $media_preload[] = '<link rel="preload" href="'.$src.'" as="image">';
                                   }
                             }
                       }
                 }

                 return $media_preload;
            }

            /**
             * Flag images for preload in DOM (Pro only)
             * @param SimpleDOMParser
             * @return SimpleDOMParser
             */
            public static function preload_images_by_class($html){
                  $preload_images_by_class = array_filter((array)Swift_Performance::get_option('preload-images-by-class'));
      		foreach ($preload_images_by_class as $preload_image){
      			foreach ($html->find('.' . $preload_image . ' img') as $node){
      				$node->{'data-swift-preloaded'} = 'true';
      			}
      		}
                  return $html;
            }

            /**
             * Prepare preload image link tag (Pro only)
             * @param SimpleDOMParser node
             * @return SimpleDOMParser node
             */
            public static function preload_image_tag($node){
                  $preload = '';

                  $preload_images_by_url		= array_filter((array)Swift_Performance::get_option('preload-images-by-url'));
                  $preload_images_by_class	= array_filter((array)Swift_Performance::get_option('preload-images-by-class'));
                  if ((!empty($preload_images_by_url) && preg_match('~('.implode('|', $preload_images_by_url).')~', $node->src)) || (!empty($preload_images_by_class) && isset($node->class) && preg_match('~('.implode('|', Swift_Performance::padding_str($preload_images_by_class, '\b')).')~', $node->class)) || isset($node->{'data-swift-preloaded'})){
                        $preload = '<link rel="preload" href="'.$node->src.'" as="image">';
                  }

                  return $preload;
            }

            /**
             * Fix missing image dimensions
             * @param SimpleDOMParser node
             * @return SimpleDOMParser node
             */
            public static function fix_missing_image_dimensions($node){
                  $file = str_replace(apply_filters('swift_performance_media_host', Swift_Performance::home_url()), Swift_Performance::get_home_path(), Swift_Performance::canonicalize($node->src));

                  // local files only
                  if (file_exists($file)){
                        list($width, $height, $image_type) = getimagesize($file);
                        if (!isset($node->width)){
                              $node->width = $width;
                        }
                        if (!isset($node->height)){
                              $node->height = $height;
                        }
                  }
                  else {
                        $src = (preg_match('~^//~', $node->src) ? 'https:' . $node->src : (preg_match('~^http~', $node->src) ? $node->src : parse_url(Swift_Performance::home_url(), PHP_URL_SCHEME) . '://' . $node->src));
                        $response = wp_remote_get($src, array('sslverify' => false, 'timeout' => 15, 'headers' => array('Referer' => home_url())));

                        if (is_wp_error($response)){
                              Swift_Performance::log($src . ' can not be loaded. ' . $response->get_error_message(), 6);
                              return $node;
                        }
                        else if (empty($response['body'])){
                              Swift_Performance::log($src . ' empty response. ' . $response->get_error_message(), 6);
                              return $node;
                        }
                        else {
                              // Disable logging to prevent warnings in log if the image is not in a recognized format
                              @$tmp_image	= imagecreatefromstring( $response['body'] );
                              @$width	= imagesx($tmp_image);
                              @$height	= imagesy($tmp_image);
                              @imagedestroy($tmp_image);

                              if (!isset($node->width) && !empty($width)){
                                    $node->width = $width;
                              }
                              if (!isset($node->height) && !empty($height)){
                                    $node->height = $height;
                              }
                        }
                  }

                  return $node;
            }


            /**=======================================**/
                        /*** CACHE ***/
            /**=======================================**/


            /**
             * Bypass or extend WP nonce lifetime (Pro only)
             */
            public static function bypass_nonce(){
                  if (Swift_Performance::check_option('cache-expiry-mode','actionbased') && Swift_Performance::check_option('bypass-nonce',1)){
                        add_filter('nonce_user_logged_out', function($uid){
                              add_filter('nonce_life', $that = function($nonce_life) use (&$that, $uid){
                                    remove_filter('nonce_life', $that, PHP_INT_MAX);
                                    // If $uid was overwritten return the original expiry
                                    if (!empty($uid)){
                                          return $nonce_life;
                                    }
                                    return PHP_INT_MAX;
                              }, PHP_INT_MAX);
                              return $uid;
                        }, PHP_INT_MAX);
                  }
                  else if (Swift_Performance::check_option('cache-expiry-mode','timebased') && Swift_Performance::check_option('extend-nonce-life',1)){
                        add_filter('nonce_life', function($nonce_life){
                              $expiry = Swift_Performance::get_option('cache-expiry-time');
                              return max($nonce_life, $expiry*2);
                        });
                  }
            }

            /**
            * Clear cache for short lifespan pages (Pro only)
            */
            public static function clear_short_lifespan(){
                  global $wpdb;
                  do_action('swift_performance_before_clear_short_lifespan_cache');

                  $short_lifespan_page_ids = implode(', ',
                        array_map(function($page_id){
                              return '"' . Swift_Performance::get_warmup_id(get_permalink($page_id)) . '"';
                        }, array_filter((array)Swift_Performance::get_option('short-lifespan-pages')))
                  );

                  if (!empty($short_lifespan_page_ids)){
                        $timestamp  = time() - 39600;
                        $expired    = $wpdb->get_col("SELECT url FROM " . SWIFT_PERFORMANCE_TABLE_PREFIX . "warmup WHERE id IN ({$short_lifespan_page_ids}) AND timestamp <= '{$timestamp}'");
                        foreach ($expired as $permalink) {
                              Swift_Performance_Cache::clear_permalink_cache($permalink);
                        }
                  }

                  do_action('swift_performance_after_clear_short_lifespan_cache');
            }

            /**
             * Send Proxy cache headers (Pro only)
             */
            public static function proxy_cache(){
                  Swift_Performance::header('Cache-Control: "s-maxage=' . Swift_Performance::get_option('proxy-cache-maxage') . ', max-age=0, public, must-revalidate"');
            }

            /**=======================================**/
                        /*** MISC ***/
            /**=======================================**/


            /**
            * Check request and call flush connection if the parameters are met with one rule of the ruleset
            */
            public static function background_requests(){
                  foreach ((array)Swift_Performance::get_option('background-requests') as $rule) {
                        if (!empty($rule)){
                              @list($key, $value) = explode('=', $rule);
                              $key        = trim($key);
                              $value      = (!isset($value) ? '' : trim($value));
                              if (isset($key) && !empty($key) && isset($_REQUEST[$key]) && $_REQUEST[$key] == $value){
                                    Swift_Performance::flush_connection();
                              }
                        }
                  }
            }

            /**
            * Limit WP Cron requests
            */
            public static function limit_wp_cron(){
                  $limit = Swift_Performance::get_option('limit-wp-cron');
                  if ($limit < 100 && mt_rand(0,100) > $limit){
                        remove_action('init', 'wp_cron');
                  }
            }

            /**
            * Exclude specific roles from GA
            */
            public static function ga_exclude_roles(){
                  $excluded_roles = Swift_Performance::get_option('ga-exclude-roles');
                  if (!empty($excluded_roles) && isset($_COOKIE[LOGGED_IN_COOKIE]) && !empty($_COOKIE[LOGGED_IN_COOKIE])){
                        @list($login, ) = explode('|', $_COOKIE[LOGGED_IN_COOKIE]);
                        if (!empty($login)){
                              global $wpdb;
                              $roles = maybe_unserialize($wpdb->get_var($wpdb->prepare("SELECT meta_value FROM {$wpdb->usermeta} LEFT JOIN {$wpdb->users} ON user_id = ID WHERE user_login = %s AND meta_key LIKE '{$wpdb->prefix}capabilities'", $login)));
                              foreach ($excluded_roles as $excluded_role){
                                    if ( isset($roles[$excluded_role]) ) {
                                        // Role is excluded, stop here
                                        die;
                                    }
                              }
                        }
                  }
            }

            /**
            * Youtube Smart Embed
            * @param SimpleDOMParser node
            * @return SimpleDOMParser node
            */
            public static function youtube_smart_embed($node, $current_path){
                  preg_match('#https://www.youtube(-nocookie)?.com/embed/([^\?/\s"]+)#',$node->src, $matches);
                  $height = isset($node->height) ? $node->height . 'px' : '100%';
                  if (isset($matches[2]) && !empty($matches[2])){
                        $src = add_query_arg('autoplay', 1, $node->src);
                        $node->src='';
                        $node->class = (isset($node->class) ? $node->class . ' ' : '')  . 'data-swift-youtube-player';

                        // Fallback image
                        $preview_remote = 'https://img.youtube.com/vi/' . $matches[2]. '/sddefault.jpg';

                        // Try maxres
                        $response	= wp_remote_get('https://img.youtube.com/vi/' . $matches[2]. '/maxresdefault.jpg');

                        //Use standard if maxres is not available
                        if (wp_remote_retrieve_response_code($response) == 404){
                              $response = wp_remote_get($preview_remote);
                        }

                        if (is_wp_error($response) || empty($response['body']) || wp_remote_retrieve_response_code($response) == 404){
                              $preview = $preview_remote;
                        }
                        else {
                              $preview_local	= $current_path . 'images/' . hash('crc32', $matches[2]) . '.jpg';
                              $preview		= Swift_Performance_Cache::write_file($preview_local, $response['body']);
                              apply_filters('swift_performance_handle_upload', SWIFT_PERFORMANCE_CACHE_DIR . $preview_local);
                        }

                        $preload_point = Swift_Performance::get_option('smart-youtube-preload-point');

                        $node->srcdoc = '<html><head><style>.swift-ytp{position: absolute;left: 50%;top: 50%;width: 68px;height: 48px;margin-left: -34px;margin-top: -24px;-moz-transition: opacity .25s cubic-bezier(0.0,0.0,0.2,1);-webkit-transition: opacity .25s cubic-bezier(0.0,0.0,0.2,1);transition: opacity .25s cubic-bezier(0.0,0.0,0.2,1);z-index: 63;}.swift-ytp svg {height: 100%;left: 0;position: absolute;top: 0;width: 100%;}body:hover .swift-ytp-bg {fill: #ff0000;}body{cursor:pointer;overflow:hidden;margin:0;padding:0;}</style><script>function loadFrame(){window.frameElement.removeAttribute(\'srcdoc\');window.frameElement.src=\'' . $src . '\'}</script></head><body><div id=\'swift-ytc\' onclick=\'loadFrame();\' ontouchstart=\'loadFrame();\' style=\'position:relative;width:100%;height:100vh;background-image: url(' . $preview . ');background-size:cover;background-position:center center;\'><div class=\'swift-ytp\' aria-label=\'Play\'><svg height=\'100%\' version=\'1.1\' viewBox=\'0 0 68 48\' width=\'100%\'><path class=\'swift-ytp-bg\' d=\'M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z\' fill=\'#212121\' fill-opacity=\'0.8\'></path><path d=\'M 45,24 27,14 27,34\' fill=\'#fff\'></path></svg></div></div><script>function r(){var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);if(h > 0){document.getElementById(\'swift-ytc\').style.height = h + \'px\'};}r();window.addEventListener(\'resize\',r);if(\'ontouchstart\' in document.documentElement && !window.frameElement.src.match(/&x$/)){function iv(a){if (typeof a.getBoundingClientRect !== \'function\'){return false}var b = a.getBoundingClientRect(); return ((a.innerHeight || a.clientHeight) > 0 && b.bottom + ' . $preload_point . ' >= 0 && b.right + ' . $preload_point . ' >= 0 && b.top - ' . $preload_point . ' <= (window.innerHeight || document.documentElement.clientHeight) && b.left - ' . $preload_point . ' <= (window.innerWidth || document.documentElement.clientWidth))}function ll(){if (iv(window.frameElement)){loadFrame();}requestAnimationFrame(ll);}requestAnimationFrame(ll);}</script></body ></html>';
                  }
                  return $node;
            }

            /**
            * Remove http to avoid mixed content on cached pages
            * @param string html
            * @return string
            */
            public static function avoid_mixed_content($html){
                  $html = preg_replace('~http://([^"\'\s]*)\.(jpe?g|png|gif|swf|flv|mpeg|mpg|mpe|3gp|mov|avi|wav|flac|mp2|mp3|m4a|mp4|m4p|aac)~i', "//$1.$2", $html);
                  $html = preg_replace('~src=(\'|")https?:~', 'src=$1', $html);
                  $html = preg_replace('~<link rel=\'stylesheet\'((?!href=).)*href=(\'|")https?:~', '<link rel=\'stylesheet\'$1href=$2', $html);

                  // Add http back to meta tags (avoid twitter card issues)
                  $html = preg_replace('~<meta([^>]+)content=("|\')?//([^"\'\s]*)\.(jpe?g|png|gif)~', '<meta$1content=$2http://$3', $html);

                  return $html;
            }

            /**
            * Catch sent headers and parse them into an array
            * @return array
            */
            public static function keep_original_headers(){
                  $kept_headers = array();
                  foreach ((array)headers_list() as $header){
                        preg_match('~([^:]+):\s(.*)~', $header, $matches);
                        $kept_headers[$matches[1]] = $matches[2];
                  }

                  return array_filter((array)apply_filters('swift_performance_kept_headers', $kept_headers));
            }

            /**
            * Insert discovered links into warmup table
            */
            public static function discover(){
                  // Save URL for warmup if it doesn't exists yet
                  if (isset($_SERVER['REQUEST_URI']) && (Swift_Performance_Cache::is_cacheable() || Swift_Performance_Cache::is_cacheable_dynamic()) && !Swift_Performance::is_404()){
                        global $wpdb;
                        $table_name = SWIFT_PERFORMANCE_TABLE_PREFIX . 'warmup';
                        $priority = Swift_Performance::get_default_warmup_priority();
                        $url = trim(Swift_Performance::home_url(), '/') . $_SERVER['REQUEST_URI'];

                        if (Swift_Performance::check_option('cache-case-insensitive',1)){
                              $url = strtolower($url);
                        }

                        if (Swift_Performance_Cache::is_cached($url)){
                              return;
                        }

                        $result = Swift_Performance_Cache::update_warmup_link($url, $priority);

                        // Prebuild
                        if ($result == 1 && Swift_Performance::check_option('automated_prebuild_cache', 1)){
                              Swift_Performance::log('New page discovered: ' . esc_url($url), 9);
                              wp_schedule_single_event(time(), 'swift_performance_prebuild_page_cache', array(array($url)));
                        }
                  }
            }

            /**
             * Update plugin file header for whitelabel (Pro only)
             */
            public static function update_plugin_header(){
                  if (!is_writable(SWIFT_PERFORMANCE_FILE)){
                        return;
                  }

                  // Get source
                  $source = file_get_contents(SWIFT_PERFORMANCE_FILE);

                  // Get version
                  preg_match('~\* Version: ([\d\.]+)~', $source, $matches);
                  $version = (isset($matches[1]) ? $matches[1] : SWIFT_PERFORMANCE_VER);

                  if (Swift_Performance::check_option('whitelabel-plugin-name', '', '!=') && ((defined('SWIFT_PERFORMANCE_WHITELABEL') && SWIFT_PERFORMANCE_WHITELABEL) || doing_action('swift_performance_options_saved'))){
                        $plugin_header[] = " * Plugin Name: " . Swift_Performance::get_option('whitelabel-plugin-name');
                        $plugin_header[] = " * Plugin URI: " . Swift_Performance::get_option('whitelabel-plugin-uri');
                        $plugin_header[] = " * Description: " . Swift_Performance::get_option('whitelabel-plugin-desc');
                        $plugin_header[] = " * Version: " . $version;
                        $plugin_header[] = " * Author: " . Swift_Performance::get_option('whitelabel-plugin-author');
                        $plugin_header[] = " * Author URI: " . Swift_Performance::get_option('whitelabel-plugin-author-uri');
                        $plugin_header[] = " * Text Domain: swift-performance";
                  }
                  else {
                        $plugin_header[] = " * Plugin Name: Swift Performance";
                        $plugin_header[] = " * Plugin URI: https://swiftperformance.io";
                        $plugin_header[] = " * Description: Boost your WordPress site";
                        $plugin_header[] = " * Version: " . $version;
                        $plugin_header[] = " * Author: SWTE";
                        $plugin_header[] = " * Author URI: https://swteplugins.com";
                        $plugin_header[] = " * Text Domain: swift-performance";
                  }

                  file_put_contents(SWIFT_PERFORMANCE_FILE, preg_replace('~/\*\*((?!\*).*?)\*/~s', "/**\n".implode("\n", $plugin_header)."\n */", $source, 1));
            }

            /**
             * Disable Admin Notices (except API messages)
             * @param array messages
             * @return array
             */
            public static function disable_admin_notices($messages){
                  foreach ($messages as $message_id => $message){
                        if (!preg_match('~^API_MESSAGE_~', $message_id)){
                              unset($messages[$message_id]);
                        }
                  }
                  return $messages;
            }
      }
}

return new Swift_Performance_Pro();
?>