plugins/cache/classes/yf_output_cache.class.php
<?php
/**
* Class to handle output caching.
*
* @author YFix Team <yfix.dev@gmail.com>
* @version 1.0
*/
load('cache', '', 'classes/');
class yf_output_cache extends yf_cache
{
/** @var bool Output caching on/off */
public $OUTPUT_CACHING = false;
/** @var int Output cache TTL, in seconds (0 - for unlimited) */
public $OUTPUT_CACHE_TTL = 604800; // 1 week (7 * 24 * 60 * 60)
/** @var string Output cache dir (relative to the SITE_PATH constant) */
public $OUTPUT_CACHE_DIR = 'pages_cache/';
/** @var array Stop-list for output caching (REGEXPs allowed here) @conf_skip */
public $_OC_STOP_LIST = [
'object=(account|advert|aff|email|forum|manage_escorts|que|reviews|reviews_search|stats|task_loader|user_info|user_profile).*',
'object=search&+',
'task=(login|logout).*',
'debug_mode',
];
/** @var string Use instead of '_OC_STOP_LIST', include _ONLY_ that is matched this pattern, will be checked if non-empty */
public $WHITE_LIST_PATTERN = '';
/** @var array Use this if you need to have some page cached different from the global setting 'OUTPUT_CACHE_TTL'
* NOTE: NOT WORKING ON WINDOWS (php < 5.3.0) ! (because we are using touch function that is broken under windows)
*/
public $CUSTOM_CACHE_TTLS = [
// 'object=(user_profile)' => 20,
];
/** @var bool @conf_skip Tells that current page will not be cached (default) */
public $NO_NEED_TO_CACHE = false;
/** @var bool Append string to the end of the cached file */
public $CACHE_APPEND_STRING = true;
/** @var string PHP code that will be eval'ed and added to the cache file @conf_skip */
public $APPEND_STRING_CODE = 'return PHP_EOL."<!-- cache generated at ".date("Y-m-d H:i:s")." in ".common()->_format_time_value(microtime(true) - main()->_time_start)." secs -->".PHP_EOL;';
/** @var string Namespace for drivers other than 'file' */
public $CACHE_NS = 'oc_';
/** @var bool Allow to refresh cache from url */
public $CACHE_CONTROL_FROM_URL = true;
/**
* Catch missing method call.
* @param mixed $name
* @param mixed $args
*/
public function __call($name, $args)
{
return main()->extend_call($this, $name, $args);
}
/**
* Module constructor.
* @param mixed $params
*/
public function _init($params = [])
{
// Assign group_id = 1 for guests
if (empty($_SESSION['user_group'])) {
$_SESSION['user_group'] = 1;
}
$this->OUTPUT_CACHING = main()->OUTPUT_CACHING;
if ($_SESSION['user_group'] > 1 || $_COOKIE['member_id']) {
main()->OUTPUT_CACHING = false;
$this->OUTPUT_CACHING = false;
}
// Ability to handle output cache through http query
if ($this->OUTPUT_CACHING && $this->CACHE_CONTROL_FROM_URL) {
// Display current page without as it is without cache
if (isset($_GET['no_cache']) || false !== strpos($_SERVER['REQUEST_URI'], '?no_cache')) {
conf('no_output_cache', true);
}
if (isset($_GET['refresh_cache']) || false !== strpos($_SERVER['REQUEST_URI'], '?refresh_cache')) {
conf('refresh_output_cache', true);
}
}
// $params = array(
// 'driver' => 'files',
// ) + (array)$params;
parent::_init($params);
}
/**
* Output cache file and stop.
*/
public function _process_output_cache()
{
$this->_check_if_need_to_cache();
if ( ! $this->OUTPUT_CACHING || $_SERVER['REQUEST_METHOD'] != 'GET' || MAIN_TYPE_ADMIN || $this->NO_NEED_TO_CACHE) {
return false;
}
if (DEBUG_MODE) {
$time_start = microtime(true);
}
$cache_key = $this->CACHE_NS . $this->_get_page_cache_name();
/*
if ($this->USE_MEMCACHED) {
// Remove old page from cache (force)
if (conf('refresh_output_cache')) {
$this->_memcache->del($cache_key);
return false;
}
} else {
// Prepare path to the current page cache
$this->CACHE_FILE_PATH = $this->_prepare_cache_path();
// Try to process output cache file
if (!file_exists($this->CACHE_FILE_PATH)) {
// Do create empty file to lock current page creation from being used
file_put_contents($this->CACHE_FILE_PATH, '');
return false;
}
// Get cache last modification time
$cache_last_modified_time = filemtime($this->CACHE_FILE_PATH);
// Check if file is locked for generation (prevent parallel creation)
if (filesize($this->CACHE_FILE_PATH) < 5) {
// Remove old lock
$lock_ttl = 600;
if ($cache_last_modified_time < (time() - $lock_ttl)) {
unlink($this->CACHE_FILE_PATH);
}
return false;
}
// Remove old page from cache
if (($this->OUTPUT_CACHE_TTL != 0 && $cache_last_modified_time < (time() - $this->OUTPUT_CACHE_TTL)) || conf('refresh_output_cache')) {
unlink($this->CACHE_FILE_PATH);
return false;
}
}
*/
/*
main()->_IN_OUTPUT_CACHE = true;
$this->_post_filter();
*/
/*
if ($this->USE_MEMCACHED) {
$mc_result = $this->_memcache->get($this->CACHE_NS. $cache_key);
if (DEBUG_MODE) {
debug('output_cache::size', strlen($mc_result));
}
if (empty($mc_result)) {
return false;
}
if ($this->OUTPUT_CACHE_INCLUDE) {
eval('?>'.$mc_result.'<?php ');
} else {
echo $mc_result;
}
} else {
if (DEBUG_MODE) {
debug('output_cache::size', filesize($this->CACHE_FILE_PATH));
}
if ($this->OUTPUT_CACHE_INCLUDE) {
include ($this->CACHE_FILE_PATH);
} else {
echo file_get_contents($this->CACHE_FILE_PATH);
}
}
*/
/*
$output = ob_get_clean();
if (DEBUG_MODE) {
debug('output_cache::exec_time', microtime(true) - $time_start);
}
if (DEBUG_MODE || conf('exec_time')) {
echo common()->_show_execution_time();
}
if (DEBUG_MODE) {
echo common()->show_debug_info();
}
$this->_send_http_headers($cache_last_modified_time);
ob_end_flush();
main()->NO_GRAPHICS = true;
exit();
*/
}
/**
* Send HTTP headers.
* @param mixed $cache_last_modified_time
*/
public function _send_http_headers($cache_last_modified_time = 0)
{
// Send correct headers
header('Content-Type: text/html; charset=' . conf('charset'), 1);
header('Content-language: ' . conf('language'), 1);
header('Expires: ' . gmdate('D, d M Y H:i:s', $cache_last_modified_time + 600/*$this->OUTPUT_CACHE_TTL*/) . ' GMT', 1);
header('Cache-Control: ', 1);
header('Pragma: ', 1);
// Set default values:
$date = gmdate('D, d M Y H:i:s', $cache_last_modified_time) . ' GMT';
$etag = '' . md5($date) . '';
// Check http headers:
$modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] == $date : null;
if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && ($timestamp = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) > 0) {
$modified_since = $cache_last_modified_time <= $timestamp;
} else {
$modified_since = null;
}
$none_match = ! empty($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] == $etag : null;
// The type checking here is very important, be careful when changing entries.
if (($modified_since !== null || $none_match !== null) && $modified_since !== false && $none_match !== false) {
header('HTTP/1.0 304 Not Modified');
exit();
}
// Send appropriate response:
header('Last-Modified: ' . $date, 1);
header('ETag: ' . $etag, 1);
}
/**
* Try to put current page to the output cache.
* @param mixed $body
*/
public function _put_page_to_output_cache($body = [])
{
$this->_check_if_need_to_cache();
/*
if (!$this->OUTPUT_CACHING || $_SERVER['REQUEST_METHOD'] != 'GET' || $this->NO_NEED_TO_CACHE) {
return false;
}
if (common()->_error_exists()) {
return false;
}
if (is_string($body)) {
$body = array('content' => $body);
}
if ($this->USE_MEMCACHED) {
$cache_key = $this->_get_page_cache_name();
if ($this->CACHE_APPEND_STRING) {
$body['content'] .= eval($this->APPEND_STRING_CODE);
}
// Special actions when needed
$body['content'] = $this->_pre_filter($body['content']);
$this->_memcache->set($this->CACHE_NS. $cache_key, $body['content'], MEMCACHE_COMPRESSED, $this->OUTPUT_CACHE_TTL);
} else {
$this->CACHE_FILE_PATH = $this->_prepare_cache_path();
if (file_exists($this->CACHE_FILE_PATH)) {
$last_modified = filemtime($this->CACHE_FILE_PATH);
if ($this->OUTPUT_CACHE_TTL != 0 && $last_modified < (time() - $this->OUTPUT_CACHE_TTL)) {
unlink($this->CACHE_FILE_PATH);
}
}
if (!file_exists($this->CACHE_FILE_PATH) || filesize($this->CACHE_FILE_PATH) < 5) {
if ($this->CACHE_APPEND_STRING) {
$body['content'] .= eval($this->APPEND_STRING_CODE);
}
$body['content'] = $this->_pre_filter($body['content']);
file_put_contents($this->CACHE_FILE_PATH, $body['content']);
// Set different last_modified time (not working on Windows)
if (!OS_WINDOWS && !empty($this->CUSTOM_CACHE_TTLS)) {
$CUSTOM_TTL = 0;
foreach ((array)$this->CUSTOM_CACHE_TTLS as $_cur_pattern => $_cur_ttl) {
if (preg_match('/'.$_cur_pattern.'/i', $_SERVER['QUERY_STRING'])) {
$CUSTOM_TTL = $_cur_ttl;
break;
}
}
if (!empty($CUSTOM_TTL)) {
touch($this->CACHE_FILE_PATH, time() + ($CUSTOM_TTL - $this->OUTPUT_CACHE_TTL));
}
}
}
}
*/
}
/**
* Generate cahe name.
*/
public function _get_page_cache_name()
{
if ( ! isset($this->_cur_cache_name)) {
$this->_cur_cache_name = md5(
$_SERVER['HTTP_HOST']
. '/' . $_SERVER['SCRIPT_NAME']
. '?' . $_SERVER['QUERY_STRING']
. '---' . conf('language')
. '---' . (int) conf('SITE_ID')
. '---' . ($_SESSION['user_group'] <= 1 ? 'guest' : 'member')
);
}
return $this->_cur_cache_name;
}
/**
* Check if current page need to be cached.
*/
public function _check_if_need_to_cache()
{
// Fast implementation of disabling output caching for the current page
if (conf('no_output_cache')) {
$this->NO_NEED_TO_CACHE = true;
return false;
}
if (main()->NO_GRAPHICS && ! conf('output_cache_force')) {
$this->NO_NEED_TO_CACHE = true;
return false;
}
if ($_SESSION['user_group'] > 1 || $_COOKIE['member_id']) {
$this->NO_NEED_TO_CACHE = true;
return false;
}
// Special for the 'share on facebook' feature
if (false !== strpos($_SERVER['HTTP_USER_AGENT'], 'facebookexternalhit')) {
$this->NO_NEED_TO_CACHE = true;
return false;
}
// Check 'white' list first
$w = $this->WHITE_LIST_PATTERN;
if ( ! empty($w)) {
// Array like: 'search' => array(), 'static_pages' => array('show')
if (is_array($w)) {
if ( ! isset($w[$_GET['object']])) {
$this->NO_NEED_TO_CACHE = true;
return $this->NO_NEED_TO_CACHE;
} elseif ( ! empty($w[$_GET['object']]) && ! in_array($_GET['action'], (array) $w[$_GET['object']])) {
$this->NO_NEED_TO_CACHE = true;
return $this->NO_NEED_TO_CACHE;
}
} else {
if ( ! preg_match('/' . $w . '/i', $_SERVER['QUERY_STRING'])) {
$this->NO_NEED_TO_CACHE = true;
return $this->NO_NEED_TO_CACHE;
}
}
return false;
}
// Try to search current query string in the stop list
foreach ((array) $this->_OC_STOP_LIST as $pattern) {
if (preg_match('/' . $pattern . '/i', $_SERVER['QUERY_STRING'])) {
$this->NO_NEED_TO_CACHE = true;
return $this->NO_NEED_TO_CACHE;
}
}
return false; // Default return value
}
/**
* Refreshing cache files.
* @param mixed $event
* @param mixed $params
*/
public function refresh($event = '', $params = [])
{
}
/**
* Do some actions before throw output.
*/
public function _post_filter()
{
}
/**
* Do special actions before put to output cache.
* @param mixed $body
*/
public function _pre_filter($body = '')
{
return $body;
}
/**
* Cleanup all cache files.
*/
public function _cache_refresh_all()
{
}
/**
* Refreshing cache files (by given array of hashes).
* @param mixed $hashes
*/
public function _clean_by_hashes($hashes = [])
{
}
/**
* Refreshing cache files (by given direct sql).
* @param mixed $sql
*/
public function _clean_by_sql($sql = '')
{
}
/**
* Refreshing cache files (by given array of params).
* @param mixed $params
*/
public function _clean_by_params($params = [])
{
}
/**
* Output cache file and stop.
* @param mixed $params
*/
public function _exec_trigger($params = [])
{
}
}