Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since 17 Sept 2013 */ namespace IPS\Data\Cache; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Memcache Cache Class */ class _Memcache extends \IPS\Data\Cache { /** * Server supports this method? * * @return bool */ public static function supported() { return ( class_exists( 'Memcached' ) OR class_exists( 'Memcache' ) ); } /** * Configuration * * @param array $configuration Existing settings * @return array \IPS\Helpers\Form\FormAbstract elements */ public static function configuration( $configuration ) { return array( 'servers' => new \IPS\Helpers\Form\Stack( 'datastore_memcache_servers', isset( $configuration['servers'] ) ? $configuration['servers'] : array(), FALSE, array( 'placeholder' => '127.0.0.1:11211' ), function( $val ) { if ( \IPS\Request::i()->cache_method === 'Memcache' ) { if ( empty( $val ) ) { throw new \DomainException( 'datastore_memcache_servers_err' ); } else { $test = new \IPS\Data\Cache\Memcache( array( 'servers' => $val ) ); if ( $test->link->getStats() === FALSE ) { throw new \DomainException( 'datastore_memcache_servers_err2' ); } } } } ) ); } /** * @brief Connection resource */ protected $link = null; /** * @brief Keys that do not exist */ protected $doesNotExist = array(); /** * Get the server link * * @return \Memcache|\Memcached */ protected function _getLink() { if( class_exists( 'Memcached' ) ) { return new \Memcached; } else { return new \Memcache; } } /** * Constructor * * @param array $configuration Configuration * @return void */ public function __construct( $configuration ) { /* Connect and add the servers that are defined to the pool */ $this->link = $this->_getLink(); $configuration['servers'] = ( !is_array( $configuration['servers'] ) ) ? array( $configuration['servers'] ) : $configuration['servers']; foreach( $configuration['servers'] as $server ) { $exploded = explode( ':', $server ); /* set the port to 0 if unix sockets are used - see http://php.net/manual/de/memcached.addserver.php */ if ( \mb_substr( $server, -5) == '.sock' AND !isset( $exploded[1] ) ) { $exploded[1] = 0; } $this->link->addServer( $exploded[0], isset( $exploded[1] ) ? $exploded[1] : 11211 ); } } /** * Abstract Method: Get * * @param string $key Key * @return string Value from the _datastore */ protected function get( $key ) { if( !isset( $this->cache[ $key ] ) ) { $value = $this->link->get( \IPS\SUITE_UNIQUE_KEY . '_' . $key ); if( method_exists( $this->link, 'getResultCode' ) and $this->link->getResultCode() == \Memcached::RES_NOTFOUND ) { $this->doesNotExist[] = $key; throw new \OutOfRangeException; } $this->cache[ $key ] = $value; } return $this->cache[ $key ]; } /** * Abstract Method: Set * * @param string $key Key * @param string $value Value * @para, \IPS\DateTime $expire Expreation time, or NULL for no expiration * @return bool */ protected function set( $key, $value, \IPS\DateTime $expire = NULL ) { if ( in_array( $key, $this->doesNotExist ) ) { unset( $this->doesNotExist[ array_search( $key, $this->doesNotExist ) ] ); } $this->cache[ $key ] = $value; if ( $this->link instanceof \Memcached ) { $result = (bool) $this->link->set( \IPS\SUITE_UNIQUE_KEY . '_' . $key, $value, $expire ? $expire->getTimestamp() : 0 ); if ( !$result ) { $error = $this->link->getResultCode(); /** will log the error code returned by memcached; lookup: http://php.net/manual/en/memcached.getresultcode.php */ \IPS\Log::debug( 'error code ' . $error, 'memcached_set_error' ); } return $result; } else { return (bool) $this->link->set( \IPS\SUITE_UNIQUE_KEY . '_' . $key, $value, MEMCACHE_COMPRESSED, $expire ? $expire->getTimestamp() : 0 ); } } /** * Abstract Method: Exists? * * @param string $key Key * @return bool */ protected function exists( $key ) { if ( in_array( $key, $this->doesNotExist ) ) { return FALSE; } elseif ( isset( $this->cache[ $key ] ) ) { return TRUE; } else { $value = $this->link->get( \IPS\SUITE_UNIQUE_KEY . '_' . $key ); if( method_exists( $this->link, 'getResultCode' ) ) { if ( $this->link->getResultCode() == \Memcached::RES_NOTFOUND or $value === FALSE ) { $this->doesNotExist[] = $key; return FALSE; } } else { if ( $value === FALSE ) { $this->doesNotExist[] = $key; return FALSE; } } $this->cache[ $key ] = $value; return TRUE; } } /** * Abstract Method: Delete * * @param string $key Key * @return bool */ protected function delete( $key ) { return (bool) $this->link->delete( \IPS\SUITE_UNIQUE_KEY . '_' . $key ); } /** * Destructor * * @return void */ public function __destruct() { if( $this->link ) { if( method_exists( $this->link, 'quit' ) ) { $this->link->quit(); } elseif( method_exists( $this->link, 'close' ) ) { $this->link->close(); } } } /** * Abstract Method: Clear All Caches * * @return void */ public function clearAll() { parent::clearAll(); $this->link->flush(); } }