timer = microtime(true);
$this->active = true;
$this->optionFilename = dirname(__FILE__) . '/settings.php';
$this->load_settings();
if (function_exists('add_action')) {
//Actions
add_action('init', array($this, 'init_hook'), 0);
//add_action('plugins_loaded', array($this, 'init_hook'), 0);
if (is_admin()) {
add_action( 'admin_menu', array($this, '_add_menu'));
add_action( 'admin_init', array($this, 'upgrade'), 0);
add_action( 'admin_notices', array($this, 'admin_notice'), 10);
add_action( 'admin_footer', array($this, 'htaccess'));
add_filter( 'plugin_action_links', array($this, 'add_action_links'), 10, 2 );
}
//Activity hooks
if (!empty($this->ac_set['chTRACK'])) {
//changing post
add_action('delete_post', array($this, 'post_hook'));
add_action('post_updated', array($this, 'post_hook'));
//changing comment
add_action('wp_set_comment_status', array($this, 'comment_status_hook'));
add_action('wp_insert_comment', array($this, 'comment_status_hook'));
add_action('trash_comment', array($this, 'comment_status_hook'));
add_action('spam_comment', array($this, 'comment_status_hook'));
add_action('edit_comment', array($this, 'comment_status_hook'));
}
}
//start maintain routine
$this->maintain();
}
public function add_action_links($links, $file) {
if(strpos($file, 'alpha_cache.php' ) === false ) return $links;
$mylinks = array(
'' . __('Settings') . '',
);
return array_merge( $links, $mylinks );
}
/* comment status hook */
public function comment_status_hook($comment_id) {
global $wpdb;
$comment_id += 0;
$post_id = $wpdb->get_var("SELECT comment_post_ID FROM {$wpdb->prefix}comments WHERE comment_ID = {$comment_id}");
$uri = $this->posturi($post_id);
$this->delete_cache($uri);
}
/* site relative uri - get by post_id */
static function posturi($post_id) {
$uri = get_permalink($post_id);
$a = parse_url($uri);
unset($a['scheme'], $a['host'], $a['fragment']);
if (!empty($a['query'])) $a['query'] = '?' . $a['query'];
return implode('', $a);
}
/* post hook */
public function post_hook($post_id) {
$uri = $this->posturi($post_id);
$this->delete_cache('/'); //front page
$this->delete_cache($uri);
}
/* admin_menu hook */
public function _add_menu() {
add_options_page('Alpha Cache', 'Alpha Cache', 8, __FILE__, array($this, '_options_page'));
}
/* output buffer hook */
public function call_back_ob($data) {
$this->log($_SERVER['REQUEST_URI']);
$this->set_cache($_SERVER['REQUEST_URI'], $data);
return $data;
}
/* init hook */
public function init_hook() {
global $wpdb;
$uri = $_SERVER['REQUEST_URI'];
if ($this->canDo($uri)) {
//look to cache
if (($data = $this->get_cache($uri)) !== false) {
$this->stat_hit();
$this->log('HIT!');
echo $data . "\n';
exit;
}
$this->stat_miss();
$this->log('MISS!');
//start buffering
ob_start(array($this, 'call_back_ob'));
} else {
$this->log('Uncachable request: ' . $uri);
}
}
/* can do cache? */
public function canDo($uri) {
global $user_ID, $user_login;
if (is_admin() || empty($this->ac_set['on']) || !$this->active) return false;
//check URL list
$u = explode("\n", $this->ac_set['avoid_urls']);
foreach($u as $v) {
$v = trim($v);
if ($v && preg_match("#{$v}#is", $uri, $m)) {
$this->active = false;
return false;
}
}
//cache for anonymous users only
if (!empty($this->ac_set['chAnon']) && $user_ID > 0) {
$this->active = false;
return false;
}
if (!empty($user_login)) {
//check users list
$u = split("[\s]*,[\s]*", $this->ac_set['users_nocache']);
if (in_array($user_login, $u)) {
$this->active = false;
return false;
}
}
/* check post vars */
if (!empty($_POST)) {
$this->active = false;
return false;
//allow any kind of form to do what they do
}
return $this->active;
}
/* successful hit to cache */
function stat_hit() {
if (!empty($this->ac_set['doStat'])) {
if ($this->lockOPT('c+', LOCK_EX)) {
$data = unserialize(substr(@fread($this->mutex_options, filesize($this->optionFilename)), 8));
$data['hits'] ++;
$this->ac_set = $data;
$data = 'mutex_options, 0);
rewind($this->mutex_options);
fwrite($this->mutex_options, $data);
$this->unlockOPT();
}
}
}
/* miss to cache */
private function stat_miss() {
if (!empty($this->ac_set['doStat'])) {
if ($this->lockOPT('c+', LOCK_EX)) {
$data = unserialize(substr(@fread($this->mutex_options, filesize($this->optionFilename)), 8));
$data['miss'] ++;
$this->ac_set = $data;
$data = 'mutex_options, 0);
rewind($this->mutex_options);
fwrite($this->mutex_options, $data);
$this->unlockOPT();
}
}
}
private function getkey($uri) {
static $theme_key = '';
$p = parse_url($uri);
//query insensitive case
if (!empty($this->ac_set['getIns']))
$p['query'] = '';
//normalize
if (substr($p['path'], 0, 1) == '/')
$p['path'] = substr($p['path'], 1);
if (substr($p['path'], -1) == '/')
$p['path'] = substr($p['path'], 0, -1);
$uri = $p['path'] . (empty($p['query']) ? '' : '?' . $p['query']);
$uri = str_replace('..', '', preg_replace('/[ <>\'\"\r\n\t\(\)]/', '', $uri) );
if (function_exists('wp_get_theme') && !empty($this->ac_set['multythemes']) && $theme_key == '') {
$obj = wp_get_theme();
$theme_key = '|' . $obj->__get('theme_root') . '/' . $obj->__get('stylesheet') . AUTH_KEY;
}
return md5($uri . $theme_key);
}
static function server_chema() {
if (isset($_SERVER['REQUEST_SCHEME']))
return $_SERVER['REQUEST_SCHEME'];
if (empty($_SERVER["HTTPS"])) return 'http';
return 'https';
}
//get cache_file_path by key suffix.
public function cache_file_path($key_end, $ext = 'html') {
return "{$this->ac_set['cache-dir']}/" . $this->server_chema() . "-{$_SERVER['SERVER_NAME']}/{$key_end}.$ext";
}
private function delete_cache($uri) {
$key = $this->getkey($uri);
$cache_files = $this->cache_file_path($key, '*');
array_map("unlink", glob($cache_files));
}
private function delete_all_cache() {
$cache_files = $this->cache_file_path('*');
array_map("unlink", glob($cache_files));
}
public function get_cache($uri) {
global $user_ID;
$key = self::getkey($uri);
$user_ID += 0;
$cache_file = $this->cache_file_path("$key.{$user_ID}");
$this->log($cache_file);
if (file_exists($cache_file)) {
$time = filemtime($cache_file);
if (!$time || $time < time() - $this->ac_set['cache_lifetime']) {
//expired
$this->log('EXPIRED');
$this->log(date('d H:i:s', $time) . ' ' . date('d H:i:s', time() - $this->ac_set['cache_lifetime']));
$this->lock('r', LOCK_EX);
unlink($cache_file);
$this->unlock();
return false;
};
//read
if ($this->lock('r', LOCK_SH)) {
$data = file_get_contents($cache_file);
} else {
$data = '';
}
$this->unlock();
return $data;
}
return false;
}
private function set_cache($uri, $data) {
if (empty($data)) return false;
global $user_ID;
$user_ID += 0;
if (is_404()) {
//prevent cache spamming
$uri = '404-not-found-page.html';
}
$key = $this->getkey($uri);
//try restore cache storage
if (!is_dir($this->ac_set['cache-dir'])) {
$def = self::default_settings();
$this->ac_set['cache-dir'] = $def['cache-dir'];
$this->touch_cache_dir();
$this->save_setttings();
} else {
$HOSTDIR = "{$this->ac_set['cache-dir']}/" . $this->server_chema() . "-{$_SERVER['SERVER_NAME']}";
if (!file_exists($HOSTDIR)) mkdir($HOSTDIR, 0750);
$cache_file = "{$HOSTDIR}/$key.{$user_ID}.html";
$this->lock('r', LOCK_EX);
file_put_contents($cache_file, $data);
$this->unlock();
return true;
}
return false;
}
//MUTEXES
private function lock($openflag, $locker) {
$this->mutex_handler = fopen($this->ac_set['cache-dir'] . '/cache_mutex.lock', $openflag);
$got_lock = true;
$timeout_secs = 500;
while (!flock($this->mutex_handler, $locker | LOCK_NB, $wouldblock)) {
if ($wouldblock && --$timeout_secs > 0) {
sleep(1);
} else {
$got_lock = false;
break;
}
}
return $got_lock;
}
private function unlock() {
flock($this->mutex_handler, LOCK_UN);
fclose($this->mutex_handler);
}
private function lockOPT($openflag, $locker) {
$this->mutex_options = fopen($this->optionFilename, $openflag);
$timeout_secs = 500;
$got_lock = true;
while (!flock($this->mutex_options, $locker | LOCK_NB, $wouldblock)) {
if ($wouldblock && --$timeout_secs > 0) {
sleep(1);
} else {
$got_lock = false;
break;
}
}
return $got_lock;
}
private function unlockOPT() {
flock($this->mutex_options, LOCK_UN);
fclose($this->mutex_options);
}
public function load_settings() {
if (!file_exists($this->optionFilename)) {
$this->ac_set = self::default_settings();
$this->save_setttings();
} else {
if ($this->lockOPT('r', LOCK_SH)) {
$data = unserialize(substr(@fread($this->mutex_options, filesize($this->optionFilename)), 8));
} else {
$data = self::default_settings();
}
$this->unlockOPT();
$this->ac_set = array_merge(self::default_settings(), $data);
}
}
public function save_setttings() {
$data = 'ac_set);
$this->lockOPT('c', LOCK_EX);
ftruncate($this->mutex_options, 0);
fwrite($this->mutex_options, $data);
$this->unlockOPT();
}
//clean up cache
public function maintain() {
//too early for maintain
if ($this->ac_set['last-maintain'] + $this->ac_set['dbmaintain_period'] > time()) return;
$expire_limit = time() - $this->ac_set['dbmaintain_period'];
if (is_dir($this->ac_set['cache-dir']) && $hdir = opendir($this->ac_set['cache-dir'])) {
$this->lock('r', LOCK_EX);
while (false !== ($entry = readdir($hdir))) {
$dname = $this->ac_set['cache-dir'] . '/' . $entry;
if ($entry != "." && $entry != ".." && is_dir($dname) && $hcache = opendir($dname)) {
while (false !== ($entry_file = readdir($hcache))) {
$fname = $dname . '/' . $entry_file;
if ($entry_file != "." && $entry_file != ".." && filectime($fname) < $expire_limit ) {
//expired
unlink($fname);
}
}
closedir($hcache);
}
}
$this->unlock();
closedir($hdir);
}
$this->ac_set['last-maintain'] = time();
$this->save_setttings();
}
/* check htaccess */
public function htaccess() {
$this->lockOPT('r', LOCK_EX);
//update and check .htaccess
$ht = file_get_contents(ABSPATH . '.htaccess');
//find host name
$host = get_option('siteurl');
if (preg_match('@^https?://(.+)$@is', $host, $m)) {
$host = $m[1];
} else {
$host = $_SERVER['HTTP_HOST'];
}
$code = '';
if (!empty($this->ac_set['on'])) {
$relative_url = str_replace("\\", '/', substr(dirname(__FILE__), strlen($_SERVER['DOCUMENT_ROOT']) + 1));
$abs_url = str_replace("\\", '/', ABSPATH) . $relative_url;
$code .= '
' . __("Settings were updated.") . '