*/ define('DSX', DIRECTORY_SEPARATOR); define('FISHPIG_BOLT', true); define('FISHPIG_BOLT_DEBUG', true); define('FISHPIG_BOLT_PARAM_REFRESH', '___refresh'); if (FISHPIG_BOLT_DEBUG) { error_reporting(E_ALL); ini_set('display_errors', 1); } /** * Main Bolt class file * */ class Fishpig_Bolt { /** * Cache for config file * * @static array */ static protected $_config = null; /** * Cache for session file * * @static array */ static protected $_session = null; /** * Cache ID for current request * * @static array */ static protected $_cacheId = null; /** * Cache for the processed request uri * * @static string */ static protected $_requestUri = null; /** * System parameters that can be ignored * This is merged with the custom list from the config * * @static array */ static protected $_excludeParams = array( '___store', '___from_store', 'isAjax', FISHPIG_BOLT_PARAM_REFRESH, ); /** * A list of URI patterns that should not be cached * This is merged with config * * @static array */ static protected $_excludeUris = array(); /** * Flag that determines whether the store is being changed * * @static bool */ static protected $_isChangingStore = false; /** * Main method * * @return void */ static public function strike() { try { self::getSession(); if (self::getConfig() !== false && self::validateConfig()) { if (self::getRequestUri() !== false) { if (self::canLoadCachedRequest()) { if (($html = Fishpig_Bolt_Cache::getCachedPage(self::getCacheId())) !== false) { // Hole punch $html if enabled Fishpig_Bolt_Hole_Punch::punch($html); // Send response to the browser self::sendResponse($html); } } } } } catch (Exception $e) { if (FISHPIG_BOLT_DEBUG) { echo sprintf('

%s

%s
', $e->getMessage(), $e->getTraceAsString()); exit; } } include(dirname(__FILE__) . DSX . 'index.php'); } /** * Retrieve a config value * * @param string|null $key = null * @return mixed */ static public function getConfig($key = null) { if (is_null(self::$_config)) { // Initialise the config file self::$_config = array(); if (($config = self::getFile(self::getDir('app' . DSX . 'etc' . DSX . 'bolt.config'))) !== false) { self::$_config = (array)unserialize($config); } else { return false; } // Check for Admin URL if (preg_match('/^(' . basename(__FILE__) . '\/' . self::$_config['admin_route'] . '(\/|\z)|' . self::$_config['admin_route'] . '(\/|\z))/', trim($_SERVER['REQUEST_URI'], '/'))) { return false; } if (isset($_GET['___store'])) { self::$_isChangingStore = true; } if (isset($_GET['___store']) && ($store = self::getStoreByCode($_GET['___store'])) !== false) { self::$_isChangingStore = $_GET['___store'] !== self::getSession('core/store_code'); // Changing store via the query string } else if (is_array(($store = self::getStoreByCode(self::getSession('core/store_code'))))) { // Loaded store via the session } else { // Load through environment variables or just load default $code = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : ''; $type = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store'; if (!$code && isset(self::$_config['websites'][self::$_config['default_website_id']])) { $code = self::$_config['default_website_code']; $type = 'website'; } $store = false; if ($type === 'store') { $store = self::getStoreByCode($code); } else if ($type === 'website') { $store = self::getStoreByWebsiteCode($code); } } if (!is_array($store)) { throw new Exception('Unable to load store'); } unset(self::$_config['websites']); self::$_config = array_merge(self::$_config, (array)$store); } return is_null($key) ? self::$_config : self::getArrayValue(self::$_config, $key); } /** * Ensure the config details are valid * If not we cannot run * * @return bool */ static public function validateConfig() { if ((int)self::getConfig('use_cache') !== 1) { return false; } else if (!Fishpig_Bolt_Cache::setCachePath(self::getDir(self::getConfig('cache_file/path')))) { return false; } return true; } /** * Retrieve the session data * * @return array * @todo Add ability to load session from the database */ static public function getSession($key = null) { if (is_null(self::$_session)) { self::$_session = array(); $sessionFile = rtrim(self::getDir('var' . DSX . 'session'), DSX) . DSX . 'sess_' . (isset($_COOKIE['frontend']) ? $_COOKIE['frontend'] : session_id()); if (($data = self::getFile($sessionFile)) === false) { return null; } $session = array(); $split = preg_split('/([a-z\_]{1,}\|*)\|/', $data,-1,PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); $len = count($split); for ($i = 0; $i < $len; $i++, $i++) { $session[$split[$i]] = @unserialize($split[$i+1]); } self::$_session = $session; } return is_null($key) ? self::$_session : self::getArrayValue(self::$_session, $key); } /** * Determine whether to cache the current request * * @return bool */ static public function canCacheRequest() { return self::getRequestUri() !== false && !self::isHttpPostRequest() && !self::hasCartItems() && !self::isCustomerLoggedIn() && !self::hasMessages(); } /** * Determine whether to cache the current request * * @return bool */ static public function canLoadCachedRequest() { return !(isset($_GET[FISHPIG_BOLT_PARAM_REFRESH]) && $_GET[FISHPIG_BOLT_PARAM_REFRESH] === 'bolt') && !self::isHttpPostRequest() && (Fishpig_Bolt_Hole_Punch::isEnabled() || (!self::hasCartItems() && !self::isCustomerLoggedIn())) && !self::hasMessages() && !self::$_isChangingStore; } /** * Send a response to the browser * * @param string $html * @return void */ static public function sendResponse($html) { header('Content-Type: text/html; charset=utf8'); header("Pragma: no-cache"); header("Cache-Control: no-cache, must-revalidate, no-store, post-check=0, pre-check=0"); echo str_replace('', '', $html); exit; } /** * Cache $html against the current request * * @param string $html * @return void */ static public function cachePage($html, $lifetime = 99999) { return Fishpig_Bolt_Cache::cachePage(self::getCacheId(), $html, $lifetime); } /** * Initialise the request URI * * @return string|false */ static public function getRequestUri() { if (is_null(self::$_requestUri)) { self::$_requestUri = false; $uri = trim($_SERVER['REQUEST_URI'], '/'); // Trim the filename from the URI if (strpos($uri, basename(__FILE__)) === 0) { $uri = ltrim(substr($uri, strlen(basename(__FILE__))), '/'); } // Ensure not Admin if (strpos($uri, self::getConfig('admin_route') . '/') === 0) { return false; } // Process query string if (strpos($uri, '?') !== false) { parse_str(substr($uri, strpos($uri, '?')+1), $query); $uri = substr($uri, 0, strpos($uri, '?')); if (isset($query['___store']) && $query['___store'] !== self::getConfig('store_code')) { return false; } // Sort keys so that the order doesn't create a new cache entry ksort($query); $ignoreParams = array_merge(self::$_excludeParams, explode(',', self::getConfig('exclude/parameters'))); foreach($ignoreParams as $param) { if (($param = trim($param)) !== '') { if (isset($query[$param])) { unset($query[$param]); } } } if (count($query) > 0) { $uri .= '?' . http_build_query($query); } } if ($uris = array_merge(self::$_excludeUris, (array)@unserialize(self::getConfig('exclude/uris')))) { foreach($uris as $xuri) { if ($xuri && preg_match('/' . $xuri . '/i', $uri, $m)) { return false; } } } self::$_requestUri = $uri; } return self::$_requestUri; } /** * Retrieve the cache URL for the current request * * @return string */ static public function getCacheId() { if (is_null(self::$_cacheId)) { $url = implode('', array( self::isHttpsRequest() ? 'https://' : 'http://', $_SERVER['SERVER_NAME'] . '/', self::getRequestUri(), )); $cookies = array(); foreach((array)array_merge(array('currency'), explode(',', self::getConfig('conditions/cookies'))) as $cookie) { if (!$cookie) { continue; } if (isset($_COOKIE[$cookie])) { $cookies['_fpck_' . $cookie] = $_COOKIE[$cookie]; } } self::$_cacheId = self::createCacheId($url, self::getConfig('store_id'), $cookies); } return self::$_cacheId; } /** * Create a cache ID * * @param string $url * @param int $storeId * @param string $currency = null * @return string */ static public function createCacheId($url, $storeId, $cookies = array()) { $url = rtrim($url, '/?&'); $url .= strpos($url, '?') === false ? '?' : '&'; $url .= http_build_query(array_merge($cookies, array( '_fps' => $storeId, '_fpgz' => 1, ))); return Fishpig_Bolt_Cache::getAppEtcSubstring() . '_BOLT_' . md5(trim($url, '&?')); } /** * Determine whether the current request is a HTTP Post request * * @return bool */ static public function isHttpPostRequest() { return strtolower($_SERVER['REQUEST_METHOD']) === 'post'; } /** * Determine whether the current request is a HTTPs secure request * * @return bool */ static public function isHttpsRequest() { return !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on'; } /** * Determine whether the customer has items in their cart * * @return bool * @todo save cart item count in session via Magento */ static public function hasCartItems() { return (int)self::getSession('core/cart_item_count') > 0; } /** * Determine whether the customer is logged in * * @return bool */ static public function isCustomerLoggedIn() { return self::getSession('customer_base/id'); } /** * Determine whether there are any messages * * @return bool */ static public function hasMessages() { return self::getSession('core/session_messages_exists'); } /** * Retrieve a store by a website code * * @param string $websiteCode * @return array|false */ static public function getStoreByWebsiteCode($websiteCode) { foreach(self::$_config['websites'] as $websiteId => $website) { if ($website['code'] === $websiteCode) { if (isset($website['groups'][$website['default_group_id']])) { $group = $website['groups'][$website['default_group_id']]; if (isset($group['stores'][$group['default_store_id']])) { return $group['stores'][$group['default_store_id']]; } } break; } } return false; } /** * Retrieve a store for $code * * @param string $code * @return array|false */ static public function getStoreByCode($code) { foreach(self::$_config['websites'] as $websiteId => $website) { foreach($website['groups'] as $group) { foreach($group['stores'] as $store) { if ($store['store_code'] === $code) { return $store; } } } } return false; } /** * Retrieve a file local to the Magento install * * @param string $file * @return string|false */ static public function getFile($file) { return is_readable($file) ? @file_get_contents($file) : false; } /** * Retrieve a value for a multi-dimensional array * * @param array $arr * @param string $key * @return mixed */ static public function getArrayValue(&$arr, $key) { if (is_null($key)) { return $arr; } $key = trim($key, '/'); if (strpos($key, '/') !== false) { $keys = explode('/', $key); $buffer = $arr; foreach($keys as $key) { if (isset($buffer[$key])) { $buffer = $buffer[$key]; } else { $buffer = null; break; } } return $buffer; } return isset($arr[$key]) ? $arr[$key] : null; } /** * Get the correct directory * * @param string $dir * @return string */ static public function getDir($dir) { return substr($dir, 0, 1) !== DSX ? dirname(__FILE__) . DSX . $dir : $dir; } } /** * Hole punch class * */ class Fishpig_Bolt_Hole_Punch { /** * Block apply options * * @const int */ const APPLY_ALWAYS = 1; const APPLY_WITH_CART = 2; const APPLY_WITH_CUSTOMER = 3; const APPLY_WITH_CART_OR_CUSTOMER = 4; /** * Cache variable to store hole data * Is false if cannot hole punch * * @static null|false|array */ static protected $_validHoles = null; /** * Punch holes in $html * * @param string $html * @return void */ static public function punch(&$html) { if (self::getValidHoles() === false) { return false; } define('FISHPIG_BOLT_PUNCHING_HOLES', true); define('MAGENTO_ROOT', getcwd()); include(dirname(__FILE__) . DSX . 'app' . DSX . 'Mage.php'); try { // Throw exception contain JSON blocks Mage::app(Fishpig_Bolt::getConfig('store_code'))->getFrontController()->dispatch(); } catch (Exception $e) { $holes = @json_decode($e->getMessage(), true); foreach((array)$holes as $alias => $data) { $html = preg_replace('/<\!--BOLT-' . $alias . '-->.*<\!--\/BOLT-' . $alias . '-->/Uis', $data, $html); } } } /** * Retrieve all holes valid for current request * * @return false|array */ static public function getValidHoles() { if (is_array(self::$_validHoles)) { return self::$_validHoles; } else if (self::$_validHoles === false) { return false; } self::$_validHoles = false; if (($holes = self::getHoles()) === false) { return false; } $isLoggedIn = Fishpig_Bolt::isCustomerLoggedIn(); $hasCart = Fishpig_Bolt::hasCartItems(); $holesToPunch = array(); foreach($holes as $hole) { $apply = (int)$hole['apply']; if ($apply === self::APPLY_ALWAYS) { $holesToPunch[] = $hole['name']; } else if ($apply === self::APPLY_WITH_CART) { if ($hasCart) { $holesToPunch[] = $hole['name']; } } else if ($apply === self::APPLY_WITH_CUSTOMER) { if ($isLoggedIn) { $holesToPunch[] = $hole['name']; } } else if ($apply === self::APPLY_WITH_CART_OR_CUSTOMER) { if ($hasCart || $isLoggedIn) { $holesToPunch[] = $hole['name']; } } } if (count($holesToPunch) > 0) { self::$_validHoles = $holesToPunch; } return self::$_validHoles; } /** * Retrieve all hole data * * @return array|false */ static public function getHoles() { if (Fishpig_Bolt::getConfig('holepunch/enabled') !== '1') { return false; } if (($holes = trim(Fishpig_Bolt::getConfig('holepunch/blocks'))) === '') { return false; } if (($holes = @unserialize($holes)) === false) { return false; } $data = array(); foreach($holes as $item) { $data[$item['name']] = $item; } if (count($data) > 0) { return $data; } return false; } /** * Determine whether the hole punch is enabled * * @return bool */ static public function isEnabled() { return self::getHoles() !== false; } } /** * Class handle all caching of html * */ class Fishpig_Bolt_Cache { /** * Cache prefix defined by Magento * * @static string */ static protected $_prefix = 'mage--'; /** * The path used to cache files * * @static string */ static protected $_cachePath = null; /** * App/etc md5 hash * * @static string */ static protected $_appEtcSubstring = null; /** * Determine whether $id has been cached * * @param string $id * @return bool */ static public function isIdCached($id) { return is_file(self::getFilename($id)); } /** * Determine whether $id has expired * * @param string * @return bool */ static public function hasIdExpired($id) { $metaFile = self::getMetaFilename($id); if (is_file($metaFile)) { if ($data = @unserialize(@file_get_contents($metaFile))) { if (isset($data['expire'])) { return $data['expire'] < time(); } } } return true; } /** * Delete a cache page by it's ID * * @param string $id * @return void */ static public function deleteById($id) { if (self::isIdCached($id)) { @unlink(self::getFilename($id)); @unlink(self::getMetaFilename($id)); } } /** * Retrieve a cached page by it's ID * * @param string $id * @return string|false */ static public function getCachedPage($id) { if (self::isIdCached($id) && !self::hasIdExpired($id)) { return gzuncompress(@file_get_contents(self::getFilename($id))); } return false; } /** * Cache a page * * @param string $id * @param string $html * @return bool */ static public function cachePage($id, $html, $lifetime = 99999) { $path = self::getPath($id); foreach(array(dirname($path), $path) as $dir) { if (!is_dir($dir)) { @mkdir($dir); } } @file_put_contents(self::getFilename($id), gzcompress($html)); @file_put_contents(self::getMetaFilename($id), serialize(array( 'hash' => crc32($html), 'mtime' => time(), 'expire' => time() + $lifetime, 'tags' => array( self::getAppEtcSubstring() . '_BOLT', self::getAppEtcSubstring() . '_MAGE' ), ))); return is_file(self::getFilename($html)); } /** * Retrieve the meta filename of the cache file * * @param string $id * @return string */ static public function getMetaFilename($id) { return self::getPath($id) . DSX . self::$_prefix . '-' . 'internal-metadatas---' . $id; } /** * Retrieve the filename of the cache file * * @param string $id * @return string */ static public function getFilename($id) { return self::getPath($id) . DSX . self::$_prefix . '-' . $id; } /** * Retrieve the relative path to the cache directory * * @return string */ static public function getPath($id) { return self::$_cachePath . DSX . self::$_prefix . substr(hash('adler32', $id), 0, 1); } /** * Set the cache path * * @param string $path * @return bool */ static public function setCachePath($path = '') { if ($path === '') { $path = 'var/cache'; } self::$_cachePath = $path; return is_writable($path); } /** * Get the first 3 characters of an md5 hash to the etc dir * * @return string */ static public function getAppEtcSubstring() { if (is_null(self::$_appEtcSubstring)) { self::$_appEtcSubstring = substr(md5(dirname(__FILE__) . DSX . 'app' . DSX . 'etc'), 0, 3); } return self::$_appEtcSubstring; } } // Every body was kung-fu fighting.... those cats were as fast as lightning! Fishpig_Bolt::strike();