*/ class Abovethefold_Proxy { /** * Above the fold controller * * @var object $CTRL */ public $CTRL; /** * Preload list for javascript */ public $js_preload = array(); /** * Preload list for styles (CSS) */ public $css_preload = array(); /** * Include list for javascript */ public $js_include = array(); /** * Include list for styles (CSS) */ public $css_include = array(); /** * Exclude list for javascript */ public $js_exclude = array(); /** * Exclude list for styles (CSS) */ public $css_exclude = array( 'fonts.googleapis.com/css' ); /** * Resources with custom expire time */ public $custom_expire = array(); /** * Regex based target url translation */ public $regex_url_translation = array(); /** * CDN for cached resources */ public $cdn = false; /** * Custom CDN url for individual resources */ public $custom_resource_cdn = array(); /** * CDN hosts (for localhost checking) */ public $cdn_hosts = array(); /** * Valid javascript mimetypes */ public $js_mimetypes = array( 'application/javascript', 'application/x-javascript', 'application/ecmascript', 'text/javascript', 'text/ecmascript', 'text/plain' ); /** * Valid CSS mimetypes */ public $css_mimetypes = array( 'text/css', 'text/plain' ); /** * Absolute path with trailingslash */ private $abspath; /** * Default cache expire time in seconds */ public $default_cache_expire = 2592000; // 30 days /** * Initialize the class and set its properties */ public function __construct(&$CTRL) { $this->CTRL = & $CTRL; if ($this->CTRL->disabled) { return; // above the fold optimization disabled for area / page } if (!isset($this->CTRL->options['js_proxy'])) { $this->CTRL->options['js_proxy'] = false; } if (!isset($this->CTRL->options['css_proxy'])) { $this->CTRL->options['css_proxy'] = false; } if (isset($this->CTRL->options['proxy_cdn']) && $this->CTRL->options['proxy_cdn']) { $this->cdn = $this->CTRL->options['proxy_cdn']; $parsed_url = parse_url($this->cdn); $this->cdn_hosts[] = $parsed_url['host']; } // set include/exclude list $keys = array('js_include','css_include','js_exclude','css_exclude'); foreach ($keys as $key) { $params = explode('_', $key); if (empty($this->$key)) { if (isset($this->CTRL->options[$params[0] . '_proxy_' . $params[1]]) && is_array($this->CTRL->options[$params[0] . '_proxy_' . $params[1]])) { $this->$key = $this->CTRL->options[$params[0] . '_proxy_' . $params[1]]; continue 1; } } // merge default include / exclude list with settings $this->$key = array_unique( array_filter( array_merge($this->$key, ((isset($this->CTRL->options[$params[0] . '_proxy_' . $params[1]]) && is_array($this->CTRL->options[$params[0] . '_proxy_' . $params[1]])) ? $this->CTRL->options[$params[0] . '_proxy_' . $params[1]] : array())), create_function('$value', 'return trim($value) !== "";') ) ); } // preload list $preloadlist = array(); if ($this->CTRL->options['css_proxy']) { // add filter for CSS file processing $this->CTRL->loader->add_filter('abtf_cssfile_pre', $this, 'process_cssfile'); /** * Preload urls */ if (isset($this->CTRL->options['css_proxy_preload']) && is_array($this->CTRL->options['css_proxy_preload']) && !empty($this->CTRL->options['css_proxy_preload'])) { if (!empty($this->CTRL->options['css_proxy_preload'])) { foreach ($this->CTRL->options['css_proxy_preload'] as $url) { $preloadlist[] = array($url,'css'); } } } } if ($this->CTRL->options['js_proxy']) { // add filter for javascript file processing $this->CTRL->loader->add_filter('abtf_jsfile_pre', $this, 'process_jsfile'); /** * Preload urls */ if (isset($this->CTRL->options['js_proxy_preload']) && is_array($this->CTRL->options['js_proxy_preload']) && !empty($this->CTRL->options['js_proxy_preload'])) { foreach ($this->CTRL->options['js_proxy_preload'] as $url) { $preloadlist[] = array($url,'js'); } } } if (!empty($preloadlist)) { $preload_hashes = array(); foreach ($preloadlist as $preloadurl) { list($url, $type) = $preloadurl; // JSON config if ($url && is_array($url)) { // regex if (!isset($url['url']) || trim($url['url']) === '') { // no target url continue 1; } // apply custom expire time if (isset($url['expire']) && $url['expire']) { $this->custom_expire[$url['url']] = $url['expire']; } // regex if (isset($url['regex']) && $url['regex']) { $this->regex_url_translation[$url['url']] = array($url['regex'],$url['regex-flags']); } // custom resource CDN if (isset($url['cdn']) && $url['cdn']) { $this->custom_resource_cdn[$url['url']] = $url['cdn']; $parsed_url = parse_url($url['cdn']); if (!in_array($parsed_url['host'], $this->cdn_hosts)) { $this->cdn_hosts[] = $parsed_url['host']; } } $url_config = $url; $url = $url_config['url']; unset($url_config['url']); } else { $url_config = false; } // verify url $url = trim($url); if ($url === '') { continue; } $cache_hash = $this->cache_hash($url, $type, $url); if ($url_config) { $preload_url = array(); // JSON config object if (isset($url_config['regex'])) { $preload_url[0] = 'regex'; $preload_url[1] = ($cache_hash) ? $cache_hash : false; $preload_url[2] = $url_config['regex']; $preload_url[3] = (isset($url_config['regex-flags']) ? $url_config['regex-flags'] : ''); } else { if ($cache_hash || (isset($url_config['cdn']) && $url_config['cdn'])) { $preload_url[0] = $url; $preload_url[1] = ($cache_hash) ? $cache_hash : false; $preload_url[2] = false; $preload_url[3] = false; } } if (isset($url_config['cdn']) && $url_config['cdn']) { $preload_url[4] = preg_replace('|^(http(s)?:)?//[^/]+/|Ui', trailingslashit($url_config['cdn']), $this->CTRL->cache_dir('proxy')); } $this->{$type . '_preload'}[] = $preload_url; } elseif ($cache_hash) { // general cached resource $this->{$type . '_preload'}[] = array($url,$cache_hash); } } } // WordPress root with trailingslash $this->abspath = trailingslashit(ABSPATH); } /** * Get proxy url for an URL * * Returns cache url */ public function url($url = '{PROXY:URL}', $type = '{PROXY:TYPE}', $tryCache = false, $htmlUrl = false) { if ($url !== '{PROXY:URL}') { // strip hash from url if (strpos($url, '#') !== false) { $url = strstr($url, '#', true); } // parse url $parsed = $this->parse_url($url); if ($parsed) { list($url, $filehash, $local_file) = $parsed; // try direct url to file if ($tryCache) { // CDN $cdn = false; if (!empty($this->custom_resource_cdn) && isset($this->custom_resource_cdn[$url])) { $cdn = $this->custom_resource_cdn[$url]; } elseif ($this->cdn) { $cdn = $this->cdn; } $cache_url = $this->cache_url($filehash, $type, $url, $cdn); if ($cache_url) { return $cache_url; } } $url = urlencode($url); } } // html valid ampersand $amp = ($htmlUrl) ? '&' : '&'; // custom proxy url if (isset($this->CTRL->options['proxy_url']) && $this->CTRL->options['proxy_url'] !== '') { $proxy_url = $this->CTRL->options['proxy_url']; if ($url !== '{PROXY:URL}') { $proxy_url = str_replace(array( '{PROXY:URL}', '{PROXY:TYPE}' ), array( $url, $type ), $proxy_url); } } else { // default WordPress PHP proxy url $site_url = site_url(); $proxy_url = $site_url . ((strpos($site_url, '?') !== false) ? $amp : '?') . 'url=' . $url . $amp . 'type=' . $type . $amp . 'abtf-proxy=' . md5(SECURE_AUTH_KEY . AUTH_KEY); } return $proxy_url; } /** * Parse CSS file in CSS file loop */ public function process_cssfile($cssfile) { // ignore if (!$cssfile || is_array($cssfile) || in_array($cssfile, array('delete','ignore'))) { return $cssfile; } $parsed_url = parse_url($cssfile); if ($localfile = $this->is_local($parsed_url, $cssfile)) { // not external return $localfile; } if (!empty($this->cdn_hosts) && in_array($parsed_url['host'], $this->cdn_hosts)) { // on CDN, not external return $cssfile; } /** * File does not match include list, ignore */ if (!$this->url_include($cssfile, 'css')) { return $cssfile; } /** * File matches exclude list, ignore */ if ($this->url_exclude($cssfile, 'css')) { return $cssfile; } // External, proxify url return $this->url($cssfile, 'css', true, true); } /** * Parse javascript file in javascript file loop */ public function process_jsfile($jsfile) { // ignore if (!$jsfile || in_array($jsfile, array('delete','ignore'))) { return $jsfile; } $parsed_url = parse_url($jsfile); if ($localfile = $this->is_local($parsed_url, $jsfile)) { // not external return $localfile; } if (!empty($this->cdn_hosts) && in_array($parsed_url['host'], $this->cdn_hosts)) { // on CDN, not external return $jsfile; } /** * File does not match include list, ignore */ if (!$this->url_include($jsfile, 'js')) { return $jsfile; } /** * File matches exclude list, ignore */ if ($this->url_exclude($jsfile, 'js')) { return $jsfile; } // External, proxify url return $this->url($jsfile, 'js', true, true); } /** * Handle forbidden requests */ public function forbidden($text = 'Forbidden') { while (ob_get_level()) { ob_end_clean(); }; wp_die($text, 'Proxy Forbidden - Above The Fold Optimization', array( 'response' => '403' )); } /** * Handle errors */ public function error($text = 'Forbidden') { while (ob_get_level()) { ob_end_clean(); }; wp_die($text, 'Proxy Error - Above The Fold Optimization', array( 'response' => '500' )); } /** * Cache file path */ public function cache_file_path($hash, $type, $create = true) { // verify hash if (strlen($hash) !== 32) { $this->forbidden('Invalid cache file hash'); } // Initialize cache path $cache_path = $this->CTRL->cache_path('proxy'); if (!is_dir($cache_path)) { $this->error('Proxy cache directory not available ' . $cache_path); } $dir_blocks = array_slice(str_split($hash, 2), 0, 3); foreach ($dir_blocks as $block) { $cache_path .= $block . '/'; if (!is_dir($cache_path)) { if (!$create) { return false; } else { if (!$this->CTRL->mkdir($cache_path)) { $this->error('Failed to create directory ' . $cache_path); } } } } $cache_path .= $hash; if ($type === 'js') { $cache_path .= '.js'; } elseif ($type === 'css') { $cache_path .= '.css'; } if (!$create && !file_exists($cache_path)) { return false; } return $cache_path; } /** * Cache file expired check */ public function cache_file_expired($cache_file, $url) { // file does not exist if (!$cache_file || !file_exists($cache_file)) { return true; } $last_modified = filemtime($cache_file); if (!empty($this->custom_expire) && isset($this->custom_expire[$url])) { $expire_time = $this->custom_expire[$url]; } else { $expire_time = $this->default_cache_expire; } // expired if ($last_modified < (time() - $expire_time)) { return true; } return false; } /** * Strip www. from hostname */ public function nowww_host($hostname) { if (stripos($hostname, 'www.') === 0) { return substr($hostname, 4); } return $hostname; } /** * Is local url? */ public function is_local($url, $originalUrl = false) { if (!$originalUrl) { $originalUrl = $url; $url = parse_url($url); } if (is_array($url) && $url['host']) { $hostname = $url['host']; } else { $parsed_url = parse_url($url); $hostname = $parsed_url['host']; } /** * Verify hostname */ if ($this->nowww_host($hostname) === $this->nowww_host($_SERVER['HTTP_HOST'])) { if (stripos($originalUrl, $_SERVER['HTTP_HOST']) === false) { $originalUrl = str_ireplace($hostname, $_SERVER['HTTP_HOST'], $originalUrl); } /** * Translate protocol relative url */ if (substr($originalUrl, 0, 2) === '//') { // prefix url with protocol $originalUrl = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') ? 'https:' : 'http:') . $originalUrl; } return $originalUrl; } return false; } /** * Cache url */ public function cache_url($hash, $type, $urlExpiredCheck = false, $cdn = false) { // verify hash if (strlen($hash) !== 32) { $this->forbidden('Invalid cache file hash'); } // check if cache file exists $cache_path = $this->cache_file_path($hash, $type, false); if (!$cache_path) { return false; } // check if cache file is expired if ($urlExpiredCheck && $this->cache_file_expired($cache_path, $urlExpiredCheck)) { return false; } $url = $this->CTRL->cache_dir('proxy'); $dir_blocks = array_slice(str_split($hash, 2), 0, 3); foreach ($dir_blocks as $block) { $url .= $block . '/'; } $url .= $hash; if ($type === 'js') { $url .= '.js'; } elseif ($type === 'css') { $url .= '.css'; } // apply CDN if ($cdn) { $url = preg_replace('|^http(s)?://[^/]+/|Ui', trailingslashit($cdn), $url); } return $url; } /** * Handle request */ public function handle_request() { if ((!isset($this->CTRL->options['js_proxy']) || !$this->CTRL->options['js_proxy']) && (!isset($this->CTRL->options['css_proxy']) || !$this->CTRL->options['css_proxy'])) { $this->forbidden('Proxy is disabled'); } $url = (isset($_REQUEST['url'])) ? trim($_REQUEST['url']) : ''; $type = (isset($_REQUEST['type'])) ? trim($_REQUEST['type']) : ''; if (!in_array($type, array('js','css'))) { $this->forbidden(); } // proxy resource $proxy_resource = $this->proxy_resource($url, $type, true); if (!$proxy_resource) { $this->forbidden(); } list($filehash, $cache_file, $url) = $proxy_resource; // Proxy failed for url (potentially insecure, not a valid javascript or CSS resource, url not recognized etc) if (!$cache_file) { // forward request to original location header("Location: " . $url); exit; } // get last modified time $last_modified = filemtime($cache_file); /** * Verify last modified */ if ( (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified) || (isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) == $filehash) ) { header("Etag: $filehash"); header("Last-Modified: " . gmdate("D, d M Y H:i:s", $last_modified) . " GMT"); header("HTTP/1.1 304 Not Modified"); exit; } /** * Turn of output buffering */ while (ob_get_level()) { ob_end_clean(); }; /** * File headers */ if ($type === 'css') { header("Content-Type: text/css", true); } else { header("Content-Type: application/javascript", true); } header("Last-Modified: " . gmdate("D, d M Y H:i:s", $last_modified) . " GMT"); /** * Set gzip compression */ //if (extension_loaded("zlib") && (ini_get("output_handler") != "ob_gzhandler")) { //ini_set("zlib.output_compression", 1); //} // prevent sniffing of content type header("X-Content-Type-Options: nosniff", true); /** * Custom expire time for url */ if (!empty($this->custom_expire) && isset($this->custom_expire[$url])) { $expire_time = $this->custom_expire[$url]; } else { $expire_time = $this->default_cache_expire; } /** * Cache headers */ header("Pragma: cache"); header("Cache-Control: max-age=".$expire_time.", public"); header("Expires: " . gmdate("D, d M Y H:i:s", ($last_modified + $expire_time)) . " GMT"); readfile($cache_file); exit; } /** * Proxy resource */ public function proxy_resource($url, $type, $debugExit = false) { if (!in_array($type, array('js','css'))) { if ($debugExit) { $this->error('Invalid proxy resource type'); } return false; } // parse url $parsed = $this->parse_url($url, true); if (!$parsed) { return false; } list($url, $filehash, $local_file) = $parsed; /** * File does not match include list, ignore */ if (!$this->url_include($url, $type)) { if ($debugExit) { $this->forbidden('The resource is not specifically included via the include list.'); } return false; } /** * File matches exclude list, ignore */ if ($this->url_exclude($url, $type)) { if ($debugExit) { $this->forbidden('The resource is excluded via the exclude list.'); } return false; } // cache file $cache_file = $this->cache_file_path($filehash, $type); /** * Return cache file */ if (file_exists($cache_file)) { // check if cache file is expired if (!$this->cache_file_expired($cache_file, $url)) { return array($filehash,$cache_file, $url); } } // verify local file if ($local_file) { /** * Detect mime type of file */ $mime = mime_content_type($local_file); if (!$mime) { // failed // @todo test support / stability in all environments if ($debugExit) { $this->forbidden('The local javascript resource does not have a valid mimetype.

File: '.str_replace(ABSPATH, '[HIDDEN]/', $local_file).'
Mime Type: '.$mime.''); } return false; } /** * Make sure file has valid mime type */ if ($type === 'js') { // valid javascript mime type? if (!in_array($mime, $this->js_mimetypes) && !(substr($local_file, -3) === '.js' && substr($mime, 0, 5) === 'text/')) { if ($debugExit) { $this->forbidden('The local javascript resource does not have a valid mimetype.

File: '.str_replace(ABSPATH, '[HIDDEN]/', $local_file).'
Mime Type: '.$mime.''); } return false; } } elseif ($type === 'css') { // valid CSS mime type? if (!in_array($mime, $this->css_mimetypes) && !(substr($local_file, -4) === '.css' && substr($mime, 0, 5) === 'text/')) { if ($debugExit) { $this->forbidden('The local CSS resource does not have a valid mimetype.

File: '.str_replace(ABSPATH, '[HIDDEN]/', $local_file) . '
Mime Type: '.$mime.''); } return false; } } } /** * External file? Require proxy to be enabled */ if (!$local_file && (!isset($this->CTRL->options[$type . '_proxy']) || !$this->CTRL->options[$type . '_proxy'])) { if ($debugExit) { $this->forbidden('The proxy is not enabled for file type '.$type); } return false; } /** * Download file */ if ($local_file) { $file_data = file_get_contents($local_file); } else { $file_data = $this->CTRL->remote_get($url); } /** * Add proxy identification header (and increase security) */ if ($file_data) { if (!empty($this->custom_expire) && isset($this->custom_expire[$url])) { $expire_time = $this->custom_expire[$url]; } else { $expire_time = $this->default_cache_expire; } $file_data = "/** " . (($type === 'js') ? 'Javascript' : 'CSS') . " Proxy / Above The Fold Optimization v".WPABTF_VERSION."\n * @url ".$url."\n * @expire ".date("Y/m/d H:i:s", (time() + $expire_time))." */\n" . $file_data; } /** * Apply optimization filters to resource content */ $file_data = apply_filters('abtf_css', $file_data); if ($file_data) { $this->CTRL->file_put_contents($cache_file, $file_data); } else { if ($debugExit) { $this->error('Failed to proxy file ' . htmlentities($url, ENT_COMPAT, 'utf-8')); } } return array($filehash,$cache_file,$url); } /** * Parse url */ public function parse_url($url, $x = false) { $url = trim($url); /** * Translate protocol relative url */ if (substr($url, 0, 2) === '//') { // prefix url with protocol $url = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') ? 'https:' : 'http:') . $url; } /** * Handle local file */ $local_file = false; // http(s):// based file, match host with server host if (strpos($url, '://') !== false) { $http_prefix = false; $prefix_match = substr($url, 0, 6); if ($prefix_match === 'https:') { // HTTPS $http_prefix = 'https://'; } elseif ($prefix_match === 'http:/') { // HTTPS $http_prefix = 'http://'; } else { /** * Invalid protocol * @security */ return false; } $parsed_url = parse_url($url); if ($this->is_local($parsed_url, $url) || (!empty($this->cdn_hosts) && in_array($parsed_url['host'], $this->cdn_hosts))) { // local file $url = str_ireplace($http_prefix . $parsed_url['host'], '', $url); } } // local file if (strpos($url, '://') === false) { // remove query string if (strpos($url, '?') !== false) { $url = substr($url, 0, strpos($url, "?")); } // remove hash if (strpos($url, '#') !== false) { $url = substr($url, 0, strpos($url, "#")); } // get real path for url if (substr($url, 0, 1) === '/') { $url = substr($url, 1); } $resource_path = realpath($this->abspath . $url); /** * Make sure resource is in WordPress root * @security */ if (strpos($resource_path, $this->abspath) === false || !file_exists($resource_path)) { return false; } // create file hash based on file contents (force browser cache update on file changes) $filehash = md5_file($resource_path); return array($url, $filehash, $resource_path); } else { // translate url $url = $this->regex_translate_url($url); // file hash based on url $filehash = md5($url); return array($url, $filehash, false); } } /** * Translate url based on regex config */ public function regex_translate_url($url) { if (empty($this->regex_url_translation)) { return $url; } foreach ($this->regex_url_translation as $matchurl => $regex) { if (preg_match('|'.str_replace('|', '\\|', $regex[0]).'|' . $regex[1], $url)) { return $matchurl; } } return $url; } /** * Return cache hash for url */ public function cache_hash($url, $type, $urlExpiredCheck = false) { $parsed = $this->parse_url($url); if ($parsed) { $cache_path = $this->cache_file_path($parsed[1], $type, false); if (!$cache_path) { return false; } // check if cache file is expired if ($urlExpiredCheck && $this->cache_file_expired($cache_path, $urlExpiredCheck)) { return false; } return $parsed[1]; } // not in cache return false; } /** * Match url against include list */ public function url_include($url, $type) { /** * Require proxy to be enabled */ if (!isset($this->CTRL->options[$type . '_proxy']) || !$this->CTRL->options[$type . '_proxy']) { return false; } $include_key = $type . '_include'; /** * Include list empty, include all */ if (empty($this->$include_key)) { return true; } /** * Match url against include list */ foreach ($this->$include_key as $str) { if (strpos($url, $str) !== false) { return true; } } return false; } /** * Match url against exclude list */ public function url_exclude($url, $type) { /** * Require proxy to be enabled */ if (!isset($this->CTRL->options[$type . '_proxy']) || !$this->CTRL->options[$type . '_proxy']) { return false; } $exclude_key = $type . '_exclude'; /** * Exclude list empty, exclude none */ if (empty($this->$exclude_key)) { return false; } /** * Match url against exclude list */ foreach ($this->$exclude_key as $str) { if (strpos($url, $str) !== false) { return true; } } return false; } /** * Javascript client settings */ public function client_jssettings(&$jssettings, &$jsfiles, $jsdebug) { $proxyindex = $this->CTRL->optimization->client_config_ref['proxy']; $proxyindexsub = $this->CTRL->optimization->client_config_ref['proxy-sub']; /** * Proxy settings */ $proxy_url = $this->url(); $jssettings[$proxyindex] = array( $proxyindexsub['url'] => $proxy_url, $proxyindexsub['js'] => (isset($this->CTRL->options['js_proxy']) && $this->CTRL->options['js_proxy']) ? true : false, $proxyindexsub['css'] => (isset($this->CTRL->options['css_proxy']) && $this->CTRL->options['css_proxy']) ? true : false ); if ($this->cdn) { $jssettings[$proxyindex][$proxyindexsub['cdn']] = trailingslashit($this->cdn); } /** * Preload urls */ $preload_hashes = array(); if (isset($this->CTRL->options['css_proxy']) && $this->CTRL->options['css_proxy'] && !empty($this->CTRL->proxy->css_preload)) { foreach ($this->CTRL->proxy->css_preload as $url) { $preload[] = $url; } } if (isset($this->CTRL->options['js_proxy']) && $this->CTRL->options['js_proxy'] && !empty($this->CTRL->proxy->js_preload)) { foreach ($this->CTRL->proxy->js_preload as $url) { $preload[] = $url; } } if (!empty($preload)) { $jssettings[$proxyindex][$proxyindexsub['preload']] = $preload; } // base path $jssettings[$proxyindex][$proxyindexsub['base']] = $this->CTRL->cache_dir('proxy'); $keys = array('js_include','css_include','js_exclude','css_exclude'); foreach ($keys as $key) { $params = explode('_', $key); if ($this->CTRL->options[$params[0] . '_proxy'] && is_array($this->CTRL->proxy->$key) && !empty($this->CTRL->proxy->$key)) { $jssettings[$proxyindex][$proxyindexsub[$key]] = $this->CTRL->proxy->$key; } } // fill empty array values to preserve JSON array format $max = 0; foreach ($jssettings[$proxyindex] as $index => $value) { if ($index > $max) { $max = $index; } } if ($max > 0) { for ($i = 0; $i <= $max; $i++) { if (!isset($jssettings[$proxyindex][$i])) { $jssettings[$proxyindex][$i] = -1; } } ksort($jssettings[$proxyindex]); } $jsfiles[] = WPABTF_PATH . 'public/js/abovethefold-proxy'.$jsdebug.'.min.js'; } /** * Prune expired cache entries */ public function prune($stats_only = false) { // age to delete cache file $prune_age = 30 * 86400; // 1 month $prune_time = (time() - $prune_age); $file_count = 0; $file_size = 0; $deleted_count = 0; $cache_path = $this->CTRL->cache_path('proxy'); $root_dir = array_diff(scandir($cache_path), array('..', '.')); foreach ($root_dir as $dirA) { if (strlen($dirA) === 2 && is_dir($cache_path . $dirA . '/')) { $A_dir = array_diff(scandir($cache_path . $dirA . '/'), array('..', '.')); foreach ($A_dir as $dirB) { if (strlen($dirB) === 2 && is_dir($cache_path . $dirA . '/' . $dirB . '/')) { $C_dir = array_diff(scandir($cache_path . $dirA . '/' . $dirB . '/'), array('..', '.')); foreach ($C_dir as $dirC) { if (strlen($dirC) === 2 && is_dir($cache_path . $dirA . '/' . $dirB . '/' . $dirC . '/')) { $C_cache_path = $cache_path . $dirA . '/' . $dirB . '/' . $dirC . '/'; $cache_files = array_diff(scandir($C_cache_path), array('..', '.')); foreach ($cache_files as $file) { // date created $date_created = filemtime($C_cache_path . $file); // older than min age, delete cache file if ($date_created < $prune_time) { if (!$stats_only) { @unlink($C_cache_path . $file); $deleted_count++; } } else { $file_count++; $file_size += filesize($C_cache_path . $file); } } } } } } } } // add warning for admin if ($file_count > 500) { $this->CTRL->admin->set_notice('

Above The Fold Optimization

The Proxy Cache directory contains '.number_format($file_count, 0, '.', ',').' cache entries. This may indicate that auto-capture captures a script with a changing url that causes a new cache entry to be created on each request.

The Proxy configuration page shows a solution to capture scripts with a changing url using a JSON config object.

', 'ERROR', array( 'date' => time(), 'expire' => (60 * 2) )); } // empty cache directory when reaching > 5,000 files if ($file_count > 5000) { $this->empty_cache(); $this->CTRL->admin->set_notice('

Above The Fold Optimization

The Proxy Cache directory reached '.number_format($file_count, 0, '.', ',').' cache entries. The cache directory has been emptied.

', 'ERROR', array( 'date' => time(), 'expire' => (60 * 2) )); $deleted_count = $file_count + $deleted_count; $file_size = $file_count = 0; } // update proxy stats $stats = get_option('abovethefold-proxy-stats'); if (!is_array($stats)) { $stats = array(); } $stats['files'] = $file_count; $stats['size'] = $file_size; $stats['deleted'] = $deleted_count; $stats['date'] = time(); update_option('abovethefold-proxy-stats', $stats, false); return $stats; } /** * Empty cache directory */ public function empty_cache() { $cache_path = $this->CTRL->cache_path('proxy'); $root_dir = array_diff(scandir($cache_path), array('..', '.')); foreach ($root_dir as $dirA) { $this->CTRL->rmdir($cache_path . $dirA . '/'); } $this->prune(true); } /** * Get proxy cache stats */ public function cache_stats() { $stats = get_option('abovethefold-proxy-stats'); if (is_array($stats) && isset($stats['date']) && intval($stats['date']) > (time() - (60 * 60))) { return $stats; } return $this->prune(true); } /** * Cron prune method */ public function cron_prune() { // cron logfile $cache_path = $this->CTRL->cache_path('proxy'); $cronlog = $cache_path . 'cleanup_cron.log'; $this->CTRL->file_put_contents($cronlog, 'start: ' . date('r')); // prune cache $stats = $this->prune(false); // log result $this->CTRL->file_put_contents($cronlog, 'completed: ' . date('r') . "\nDeleted: " . $stats['deleted'] . "\nFiles: " . $stats['files'] . "\nSize: " . $stats['size']); } }