options = $config; /* no config, nothing is going to work */ if ( empty ( $this->options ) ) { return false; } /* split hosts entry to servers */ $this->set_servers(); /* call backend initiator based on cache type */ $init = $this->proxy( 'init' ); $this->log ( __translate__(' init starting', self::plugin_constant )); $this->$init(); } /*********************** PUBLIC FUNCTIONS ***********************/ /** * build key to make requests with * * @param string $suffix suffix to add to prefix * */ public function key ( $suffix = 'meta' ) { /* use the full accessed URL string as key, same will be generated by nginx as well we need a data and a meta key: data is string only with content, meta is not used in nginx */ if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) $_SERVER['HTTPS'] = 'on'; $protocol = ( isset($_SERVER['HTTPS']) && ( ( strtolower($_SERVER['HTTPS']) == 'on' ) || ( $_SERVER['HTTPS'] == '1' ) ) ) ? 'https://' : 'http://'; return $suffix . '-' . $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; } /** * public get function, transparent proxy to internal function based on backend * * @param string $key Cache key to get value for * * @return mixed False when entry not found or entry value on success */ public function get ( &$key ) { /* look for backend aliveness, exit on inactive backend */ if ( ! $this->is_alive() ) return false; /* log the current action */ $this->log ( __translate__('get ', self::plugin_constant ). $key ); /* proxy to internal function */ $internal = $this->proxy( 'get' ); $result = $this->$internal( $key ); if ( $result === false ) $this->log ( __translate__( "failed to get entry: ", self::plugin_constant ) . $key ); return $result; } /** * public set function, transparent proxy to internal function based on backend * * @param string $key Cache key to set with ( reference only, for speed ) * @param mixed $data Data to set ( reference only, for speed ) * * @return mixed $result status of set function */ public function set ( &$key, &$data ) { /* look for backend aliveness, exit on inactive backend */ if ( ! $this->is_alive() ) return false; /* log the current action */ $this->log( __translate__('set ', self::plugin_constant ) . $key . __translate__(' expiration time: ', self::plugin_constant ) . $this->options['expire']); /* proxy to internal function */ $internal = $this->options['cache_type'] . '_set'; $result = $this->$internal( $key, $data ); /* check result validity */ if ( $result === false ) $this->log ( __translate__('failed to set entry: ', self::plugin_constant ) . $key, LOG_WARNING ); return $result; } /** * public get function, transparent proxy to internal function based on backend * * @param string $key Cache key to invalidate, false mean full flush */ public function clear ( $post_id = false ) { /* look for backend aliveness, exit on inactive backend */ if ( ! $this->is_alive() ) return false; /* exit if no post_id is specified */ if ( empty ( $post_id ) && $this->options['invalidation_method'] != 0) { $this->log ( __translate__('not clearing unidentified post ', self::plugin_constant ), LOG_WARNING ); return false; } /* if invalidation method is set to full, flush cache */ if ( $this->options['invalidation_method'] === 0 || empty ( $post_id ) ) { /* log action */ $this->log ( __translate__('flushing cache', self::plugin_constant ) ); /* proxy to internal function */ $internal = $this->proxy ( 'flush' ); $result = $this->$internal(); if ( $result === false ) $this->log ( __translate__('failed to flush cache', self::plugin_constant ), LOG_WARNING ); return $result; } /* need permalink functions */ if ( !function_exists('get_permalink') ) include_once ( ABSPATH . 'wp-includes/link-template.php' ); /* get path from permalink */ $path = substr ( get_permalink( $post_id ) , 7 ); /* no path, don't do anything */ if ( empty( $path ) ) { $this->log ( __translate__('unable to determine path from Post Permalink, post ID: ', self::plugin_constant ) . $post_id , LOG_WARNING ); return false; } if ( isset($_SERVER['HTTPS']) && ( ( strtolower($_SERVER['HTTPS']) == 'on' ) || ( $_SERVER['HTTPS'] == '1' ) ) ) $protocol = 'https://'; else $protocol = 'http://'; /* elements to clear */ $to_clear = array ( $this->options['prefix-meta'] . $protocol . $path, $this->options['prefix-data'] . $protocol . $path, ); /* delete entries */ $internal = $this->proxy ( 'clear' ); $this->$internal ( $to_clear ); } /** * get backend aliveness * * @return array Array of configured servers with aliveness value * */ public function status () { /* look for backend aliveness, exit on inactive backend */ if ( ! $this->is_alive() ) return false; $internal = $this->proxy ( 'status' ); return $this->status; } /** * backend proxy function name generator * * @return string Name of internal function based on cache_type * */ private function proxy ( $method ) { return $this->options['cache_type'] . '_' . $method; } /** * function to check backend aliveness * * @return boolean true if backend is alive, false if not * */ private function is_alive() { if ( ! $this->alive ) { $this->log ( __translate__("backend is not active, exiting function ", self::plugin_constant ) . __FUNCTION__, LOG_WARNING ); return false; } return true; } /** * split hosts string to backend servers * * */ private function set_servers () { /* replace servers array in config according to hosts field */ $servers = explode( self::host_separator , $this->options['hosts']); $options['servers'] = array(); foreach ( $servers as $snum => $sstring ) { $separator = strpos( $sstring , self::port_separator ); $host = substr( $sstring, 0, $separator ); $port = substr( $sstring, $separator + 1 ); /* IP server */ if ( !empty ( $host ) && !empty($port) && is_numeric($port) ) { $this->options['servers'][$sstring] = array ( 'host' => $host, 'port' => $port ); } } } /** * get current array of servers * * @return array Server list in current config * */ public function get_servers () { return $this->options['servers']; } /** * sends message to sysog * * @param mixed $message message to add besides basic info * */ public function log ( $message, $log_level = LOG_WARNING ) { if ( @is_array( $message ) || @is_object ( $message ) ) $message = serialize($message); if (! $this->options['log'] ) return false; switch ( $log_level ) { case LOG_ERR : if ( function_exists( 'syslog' ) ) syslog( $log_level , self::plugin_constant . " with " . $this->options['cache_type'] . ' ' . $message ); /* error level is real problem, needs to be displayed on the admin panel */ //throw new Exception ( $message ); break; default: if ( function_exists( 'syslog' ) && $this->options['debug'] ) syslog( $log_level , self::plugin_constant . " with " . $this->options['cache_type'] . ' ' . $message ); break; } } /*********************** END PUBLIC FUNCTIONS ***********************/ /*********************** APC FUNCTIONS ***********************/ /** * init apc backend: test APC availability and set alive status */ private function apc_init () { /* verify apc functions exist, apc extension is loaded */ if ( ! function_exists( 'apc_sma_info' ) ) { $this->log ( __translate__('APC extension missing', self::plugin_constant ) ); return false; } /* verify apc is working */ if ( apc_sma_info() ) { $this->log ( __translate__('backend OK', self::plugin_constant ) ); $this->alive = true; } } /** * health checker for APC * * @return boolean Aliveness status * */ private function apc_status () { return $this->alive; } /** * get function for APC backend * * @param string $key Key to get values for * * @return mixed Fetched data based on key * */ private function apc_get ( &$key ) { return apc_fetch( $key ); } /** * Set function for APC backend * * @param string $key Key to set with * @param mixed $data Data to set * * @return boolean APC store outcome */ private function apc_set ( &$key, &$data ) { return apc_store( $key , $data , $this->options['expire'] ); } /** * Flushes APC user entry storage * * @return boolean APC flush outcome status * */ private function apc_flush ( ) { return apc_clear_cache('user'); } /** * Removes entry from APC or flushes APC user entry storage * * @param mixed $keys Keys to clear, string or array */ private function apc_clear ( $keys ) { /* make an array if only one string is present, easier processing */ if ( !is_array ( $keys ) ) $keys = array ( $keys ); foreach ( $keys as $key ) { if ( ! apc_delete ( $key ) ) { $this->log ( __translate__('Failed to delete APC entry: ', self::plugin_constant ) . $key, LOG_ERR ); //throw new Exception ( __translate__('Deleting APC entry failed with key ', self::plugin_constant ) . $key ); } else { $this->log ( __translate__( 'APC entry delete: ', self::plugin_constant ) . $key ); } } } /*********************** END APC FUNCTIONS ***********************/ /*********************** MEMCACHED FUNCTIONS ***********************/ /** * init memcached backend */ private function memcached_init () { /* Memcached class does not exist, Memcached extension is not available */ if (!class_exists('Memcached')) { $this->log ( __translate__(' Memcached extension missing', self::plugin_constant ), LOG_ERR ); return false; } /* check for existing server list, otherwise we cannot add backends */ if ( empty ( $this->options['servers'] ) && ! $this->alive ) { $this->log ( __translate__("Memcached servers list is empty, init failed", self::plugin_constant ), LOG_WARNING ); return false; } /* check is there's no backend connection yet */ if ( $this->connection === NULL ) { /* persistent backend needs an identifier */ if ( $this->options['persistent'] == '1' ) $this->connection = new Memcached( self::plugin_constant ); else $this->connection = new Memcached(); /* use binary and not compressed format, good for nginx and still fast */ $this->connection->setOption( Memcached::OPT_COMPRESSION , false ); $this->connection->setOption( Memcached::OPT_BINARY_PROTOCOL , true ); } /* check if initialization was success or not */ if ( $this->connection === NULL ) { $this->log ( __translate__( 'error initializing Memcached PHP extension, exiting', self::prefix ) ); return false; } /* check if we already have list of servers, only add server(s) if it's not already connected */ $servers_alive = array(); if ( !empty ( $this->status ) ) { $servers_alive = $this->connection->getServerList(); /* create check array if backend servers are already connected */ if ( !empty ( $servers ) ) { foreach ( $servers_alive as $skey => $server ) { $skey = $server['host'] . ":" . $server['port']; $servers_alive[ $skey ] = true; } } } /* adding servers */ foreach ( $this->options['servers'] as $server_id => $server ) { /* reset server status to unknown */ $this->status[$server_id] = -1; /* only add servers that does not exists already in connection pool */ if ( !@array_key_exists($server_id , $servers_alive ) ) { $this->connection->addServer( $server['host'], $server['port'] ); $this->log ( $server_id . __translate__(" added, persistent mode: ", self::plugin_constant ) . $this->options['persistent'] ); } } /* backend is now alive */ $this->alive = true; $this->memcached_status(); } /** * sets current backend alive status for Memcached servers * */ private function memcached_status () { /* server status will be calculated by getting server stats */ $this->log ( __translate__("checking server statuses", self::plugin_constant )); /* get servers statistic from connection */ $report = $this->connection->getStats(); foreach ( $report as $server_id => $details ) { /* reset server status to offline */ $this->status[$server_id] = 0; /* if server uptime is not empty, it's most probably up & running */ if ( !empty($details['uptime']) ) { $this->log ( $server_id . __translate__(" server is up & running", self::plugin_constant )); $this->status[$server_id] = 1; } } } /** * get function for Memcached backend * * @param string $key Key to get values for * */ private function memcached_get ( &$key ) { return $this->connection->get($key); } /** * Set function for Memcached backend * * @param string $key Key to set with * @param mixed $data Data to set * */ private function memcached_set ( &$key, &$data ) { $result = $this->connection->set ( $key, $data , $this->options['expire'] ); /* if storing failed, log the error code */ if ( $result === false ) { $code = $this->connection->getResultCode(); $this->log ( __translate__('unable to set entry ', self::plugin_constant ) . $key . __translate__( ', Memcached error code: ', self::plugin_constant ) . $code ); //throw new Exception ( __translate__('Unable to store Memcached entry ', self::plugin_constant ) . $key . __translate__( ', error code: ', self::plugin_constant ) . $code ); } return $result; } /** * * Flush memcached entries */ private function memcached_flush ( ) { return $this->connection->flush(); } /** * Removes entry from Memcached or flushes Memcached storage * * @param mixed $keys String / array of string of keys to delete entries with */ private function memcached_clear ( $keys ) { /* make an array if only one string is present, easier processing */ if ( !is_array ( $keys ) ) $keys = array ( $keys ); foreach ( $keys as $key ) { $kresult = $this->connection->delete( $key ); if ( $kresult === false ) { $code = $this->connection->getResultCode(); $this->log ( __translate__('unable to delete entry ', self::plugin_constant ) . $key . __translate__( ', Memcached error code: ', self::plugin_constant ) . $code ); } else { $this->log ( __translate__( 'entry deleted: ', self::plugin_constant ) . $key ); } } } /*********************** END MEMCACHED FUNCTIONS ***********************/ /*********************** MEMCACHE FUNCTIONS ***********************/ /** * init memcache backend */ private function memcache_init () { /* Memcached class does not exist, Memcache extension is not available */ if (!class_exists('Memcache')) { $this->log ( __translate__('PHP Memcache extension missing', self::plugin_constant ), LOG_ERR ); return false; } /* check for existing server list, otherwise we cannot add backends */ if ( empty ( $this->options['servers'] ) && ! $this->alive ) { $this->log ( __translate__("servers list is empty, init failed", self::plugin_constant ), LOG_WARNING ); return false; } /* check is there's no backend connection yet */ if ( $this->connection === NULL ) $this->connection = new Memcache(); /* check if initialization was success or not */ if ( $this->connection === NULL ) { $this->log ( __translate__( 'error initializing Memcache PHP extension, exiting', self::prefix ) ); return false; } /* adding servers */ foreach ( $this->options['servers'] as $server_id => $server ) { /* reset server status to unknown */ if ( $this->options['persistent'] == '1' ) $this->status[$server_id] = $this->connection->pconnect ( $server['host'] , $server['port'] ); else $this->status[$server_id] = $this->connection->connect ( $server['host'] , $server['port'] ); $this->log ( $server_id . __translate__(" added, persistent mode: ", self::plugin_constant ) . $this->options['persistent'] ); } /* backend is now alive */ $this->alive = true; $this->memcache_status(); } /** * check current backend alive status for Memcached * */ private function memcache_status () { /* server status will be calculated by getting server stats */ $this->log ( __translate__("checking server statuses", self::plugin_constant )); /* get servers statistic from connection */ foreach ( $this->options['servers'] as $server_id => $server ) { $this->status[$server_id] = $this->connection->getServerStatus( $server['host'], $server['port'] ); if ( $this->status[$server_id] == 0 ) $this->log ( $server_id . __translate__(" server is down", self::plugin_constant )); else $this->log ( $server_id . __translate__(" server is up & running", self::plugin_constant )); } } /** * get function for Memcached backend * * @param string $key Key to get values for * */ private function memcache_get ( &$key ) { return $this->connection->get($key); } /** * Set function for Memcached backend * * @param string $key Key to set with * @param mixed $data Data to set * */ private function memcache_set ( &$key, &$data ) { $result = $this->connection->set ( $key, $data , 0 , $this->options['expire'] ); return $result; } /** * * Flush memcached entries */ private function memcache_flush ( ) { return $this->connection->flush(); } /** * Removes entry from Memcached or flushes Memcached storage * * @param mixed $keys String / array of string of keys to delete entries with */ private function memcache_clear ( $keys ) { /* make an array if only one string is present, easier processing */ if ( !is_array ( $keys ) ) $keys = array ( $keys ); foreach ( $keys as $key ) { $kresult = $this->connection->delete( $key ); if ( $kresult === false ) { $this->log ( __translate__('unable to delete entry ', self::plugin_constant ) . $key ); } else { $this->log ( __translate__( 'entry deleted: ', self::plugin_constant ) . $key ); } } } /*********************** END MEMCACHE FUNCTIONS ***********************/ } } ?>