Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+34 -27
View File
@@ -11,24 +11,31 @@
namespace IPS\Data;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Log;
use OutOfRangeException;
use function defined;
use function get_class;
use const IPS\CACHING_LOG;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Abstract Data Class
*/
abstract class _AbstractData
abstract class AbstractData
{
/**
* Configuration
*
* @param array $configuration Existing settings
* @param array $configuration Existing settings
* @return array \IPS\Helpers\Form\FormAbstract elements
*/
public static function configuration( $configuration )
public static function configuration( array $configuration ): array
{
return array();
}
@@ -36,28 +43,28 @@ abstract class _AbstractData
/**
* @brief Data Store
*/
protected $_data = array();
protected array $_data = array();
/**
* @brief Keys that exist
*/
protected $_exists = array();
protected array $_exists = array();
/**
* Magic Method: Get
*
* @param string $key Key
* @return string Value from the _datastore
* @throws \OutOfRangeException
* @param string $key Key
* @return mixed Value from the _datastore
* @throws OutOfRangeException
*/
public function __get( $key )
public function __get( string $key ): mixed
{
if( !isset( $this->_data[ $key ] ) )
{
if( $this->exists( $key ) )
{
$value = $this->decode( $this->get( $key ) );
if ( \IPS\CACHING_LOG )
if ( CACHING_LOG )
{
$this->log[ sprintf( '%.4f', microtime(true) ) ] = array( 'get', $key, json_encode( $value, JSON_PRETTY_PRINT ), var_export( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ), TRUE ) );
}
@@ -65,7 +72,7 @@ abstract class _AbstractData
}
else
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
}
@@ -75,13 +82,13 @@ abstract class _AbstractData
/**
* Magic Method: Set
*
* @param string $key Key
* @param string $value Value
* @param string $key Key
* @param string $value Value
* @return void
*/
public function __set( $key, $value )
public function __set( string $key, mixed $value )
{
if ( \IPS\CACHING_LOG )
if ( CACHING_LOG )
{
$this->log[ sprintf( '%.4f', microtime(true) ) ] = array( 'set', $key, json_encode( $value, JSON_PRETTY_PRINT ), var_export( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ), TRUE ) );
}
@@ -95,7 +102,7 @@ abstract class _AbstractData
{
/* We can only really log if datastore fails, because if cache fails we create a catch-22 where settings can't/hasn't loaded but
we rely on that data for logging, so don't log if this is for cache */
$classname = explode( '\\', \get_class( $this ) );
$classname = explode( '\\', get_class( $this ) );
if( $classname[2] == 'Cache' )
{
@@ -105,17 +112,17 @@ abstract class _AbstractData
$classarea = array_pop( $classname );
$namespace = array_pop( $classname );
\IPS\Log::log( "Could not write to {$namespace}-{$classarea} ({$key})", 'datastore' );
Log::log( "Could not write to {$namespace}-{$classarea} ({$key})", 'datastore' );
}
}
/**
* Magic Method: Isset
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
public function __isset( $key )
public function __isset( string $key ): bool
{
if( array_key_exists( $key, $this->_data ) or array_key_exists( $key, $this->_exists ) )
{
@@ -128,7 +135,7 @@ abstract class _AbstractData
{
$this->_exists[ $key ] = $key;
}
if ( \IPS\CACHING_LOG )
if ( CACHING_LOG )
{
$this->log[ sprintf( '%.4f', microtime(true) ) ] = array( 'check', $key, var_export( $return, TRUE ), var_export( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ), TRUE ) );
}
@@ -139,12 +146,12 @@ abstract class _AbstractData
/**
* Magic Method: Unset
*
* @param string $key Key
* @param string $key Key
* @return void
*/
public function __unset( $key )
public function __unset( string $key )
{
if ( \IPS\CACHING_LOG )
if ( CACHING_LOG )
{
$this->log[ sprintf( '%.4f', microtime(true) ) ] = array( 'delete', $key, NULL, var_export( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ), TRUE ) );
}
@@ -159,7 +166,7 @@ abstract class _AbstractData
* @param mixed $value Value
* @return string
*/
protected function encode( $value )
protected function encode( mixed $value ): string
{
return json_encode( $value );
}
@@ -170,7 +177,7 @@ abstract class _AbstractData
* @param mixed $value Value
* @return mixed
*/
protected function decode( $value )
protected function decode( mixed $value ): mixed
{
return json_decode( $value, TRUE );
}
+58 -41
View File
@@ -11,31 +11,48 @@
namespace IPS\Data;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Data\Cache\Exception;
use IPS\Data\Cache\None;
use IPS\DateTime;
use IPS\Settings;
use IPS\Text\Encrypt;
use OutOfRangeException;
use function count;
use function defined;
use function function_exists;
use function in_array;
use function is_array;
use const IPS\CACHE_CONFIG;
use const IPS\CACHE_METHOD;
use const IPS\CACHING_LOG;
use const IPS\TEST_CACHING;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Abstract Storage Class
*/
abstract class _Cache extends AbstractData
abstract class Cache extends AbstractData
{
/**
* @brief Instance
*/
protected static $instance;
protected static ?Cache $instance = NULL;
/**
* @brief Caches already retrieved this instance
*/
protected $cache = array();
protected array $cache = array();
/**
* @brief Log
*/
public $log = array();
public array $log = array();
/**
* Available Cache Store Methods
@@ -50,7 +67,7 @@ abstract class _Cache extends AbstractData
'Redis' => 'IPS\Data\Cache\Redis',
];
if( \IPS\TEST_CACHING )
if( TEST_CACHING )
{
$return['Test'] = 'IPS\Data\Cache\Test';
}
@@ -61,32 +78,32 @@ abstract class _Cache extends AbstractData
/**
* Get instance
*
* @return \IPS\Data\Cache
* @return Cache|null
*/
public static function i()
public static function i(): ?Cache
{
if( static::$instance === NULL )
{
$classname = '\IPS\Data\Cache\None';
if( isset( static::availableMethods()[ \IPS\CACHE_METHOD ] ) AND class_exists( static::availableMethods()[ \IPS\CACHE_METHOD ] ) )
if( isset( static::availableMethods()[ CACHE_METHOD ] ) AND class_exists( static::availableMethods()[ CACHE_METHOD ] ) )
{
$classname = static::availableMethods()[ \IPS\CACHE_METHOD ];
$classname = static::availableMethods()[ CACHE_METHOD ];
}
if ( $classname::supported() )
{
try
{
static::$instance = new $classname( json_decode( \IPS\CACHE_CONFIG, TRUE ) );
static::$instance = new $classname( json_decode( CACHE_CONFIG, TRUE ) );
}
catch( \IPS\Data\Cache\Exception $e )
catch( Exception $e )
{
static::$instance = new \IPS\Data\Cache\None( array() );
static::$instance = new None( array() );
}
}
else
{
static::$instance = new \IPS\Data\Cache\None( array() );
static::$instance = new None( array() );
}
}
@@ -98,7 +115,7 @@ abstract class _Cache extends AbstractData
*
* @return boolean
*/
public function checkKeys()
public function checkKeys(): bool
{
return true;
}
@@ -106,17 +123,17 @@ abstract class _Cache extends AbstractData
/**
* Store value using cache method if available or falling back to the database
*
* @param string $key Key
* @param string $key Key
* @param mixed $value Value
* @param \IPS\DateTime $expire Expiration if using database
* @param bool $fallback Use database if no caching method is available?
* @param DateTime $expire Expiration if using database
* @param bool $fallback Use database if no caching method is available?
* @return bool
*/
public function storeWithExpire( $key, $value, \IPS\DateTime $expire, $fallback=FALSE )
public function storeWithExpire( string $key, mixed $value, DateTime $expire, bool $fallback=FALSE ): bool
{
$value = array( 'value' => $value, 'expires' => $expire->getTimestamp() );
if ( \IPS\CACHING_LOG )
if ( CACHING_LOG )
{
$this->log[ microtime(true) ] = array( 'set', $key, json_encode( $value, JSON_PRETTY_PRINT ), var_export( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ), TRUE ) );
}
@@ -135,20 +152,20 @@ abstract class _Cache extends AbstractData
/**
* Get value using cache method if available or falling back to the database
*
* @param string $key Key
* @param bool $fallback Use database if no caching method is available?
* @param string $key Key
* @param bool $fallback Use database if no caching method is available?
* @return mixed
* @throws \OutOfRangeException
* @throws OutOfRangeException
*/
public function getWithExpire( $key, $fallback=FALSE )
public function getWithExpire( string $key, bool $fallback=FALSE ): mixed
{
if ( !isset( $this->$key ) )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
$data = $this->$key;
if( \count( $data ) and isset( $data['value'] ) and isset( $data['expires'] ) )
if( count( $data ) and isset( $data['value'] ) and isset( $data['expires'] ) )
{
/* Is it expired? */
if( $data['expires'] AND time() < $data['expires'] )
@@ -158,13 +175,13 @@ abstract class _Cache extends AbstractData
else
{
unset( $this->$key );
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
}
else
{
unset( $this->$key );
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
}
@@ -173,13 +190,13 @@ abstract class _Cache extends AbstractData
*
* @return void
*/
public function clearAll()
public function clearAll() : void
{
/* cacheKeys stored md5 hashes of the correct value, and the cache
is only used if the value it returns matches the hash, so clearing
this out invalidates all caches, even if the caching engine
does not allow us to actually clear them */
\IPS\Data\Store::i()->cacheKeys = array();
Store::i()->cacheKeys = array();
}
/**
@@ -187,10 +204,10 @@ abstract class _Cache extends AbstractData
*
* @return string
*/
protected function _encryptionKey()
protected function _encryptionKey(): string
{
$password = \IPS\Settings::i()->sql_pass;
if ( \function_exists( 'openssl_digest' ) and \in_array( 'sha256', openssl_get_md_methods() ) )
$password = Settings::i()->sql_pass;
if ( function_exists( 'openssl_digest' ) and in_array( 'sha256', openssl_get_md_methods() ) )
{
$password = openssl_digest( $password, 'sha256', TRUE );
}
@@ -203,9 +220,9 @@ abstract class _Cache extends AbstractData
* @param mixed $value Value
* @return string
*/
protected function encode( $value )
protected function encode( mixed $value ): string
{
return \IPS\Text\Encrypt::fromPlaintext( json_encode( $value ) )->tag();
return Encrypt::fromPlaintext( json_encode( $value ) )->tag();
}
/**
@@ -214,22 +231,22 @@ abstract class _Cache extends AbstractData
* @param mixed $value Value
* @return mixed
*/
protected function decode( $value )
protected function decode( mixed $value ): mixed
{
return json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE );
return json_decode( Encrypt::fromTag( $value )->decrypt(), TRUE );
}
/**
* Magic Method: Isset
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
public function __isset( $key )
public function __isset( string $key ): bool
{
if ( parent::__isset( $key ) )
{
return ( \is_array( $this->$key ) or $this->$key === 0 ) ? true : (bool) $this->$key;
return ( is_array( $this->$key ) or $this->$key === 0 ) ? true : (bool) $this->$key;
}
return FALSE;
}
+7 -3
View File
@@ -11,16 +11,20 @@
namespace IPS\Data\Cache;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use RuntimeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Cache Exception Class
*/
class _Exception extends \RuntimeException
class Exception extends RuntimeException
{
}
+52 -42
View File
@@ -11,16 +11,26 @@
namespace IPS\Data\Cache;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Data\Cache;
use IPS\DateTime;
use IPS\Db;
use IPS\Db\Exception;
use OutOfRangeException;
use RuntimeException;
use UnderflowException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Dummy Storage Class
*/
class _None extends \IPS\Data\Cache
class None extends Cache
{
/**
* Constructor
@@ -29,43 +39,43 @@ class _None extends \IPS\Data\Cache
* @return void
* @note Overridden for performance reasons
*/
public function __construct( $configuration )
public function __construct( array $configuration )
{
}
/**
* Magic Method: Get
*
* @param string $key Key
* @param string $key Key
* @return string Value from the _datastore
* @throws \OutOfRangeException
* @throws OutOfRangeException
* @note Overridden for performance reasons
*/
public function __get( $key )
public function __get( string $key ): string
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
/**
* Magic Method: Set
*
* @param string $key Key
* @param string $value Value
* @param string $key Key
* @param string $value Value
* @return void
* @note Overridden for performance reasons
*/
public function __set( $key, $value )
public function __set( string $key, mixed $value )
{
}
/**
* Magic Method: Isset
*
* @param string $key Key
* @param string $key Key
* @return bool
* @note Overridden for performance reasons
*/
public function __isset( $key )
public function __isset( string $key ): bool
{
return FALSE;
}
@@ -73,11 +83,11 @@ class _None extends \IPS\Data\Cache
/**
* Magic Method: Unset
*
* @param string $key Key
* @param string $key Key
* @return void
* @note Overridden for performance reasons
*/
public function __unset( $key )
public function __unset( string $key )
{
}
@@ -86,7 +96,7 @@ class _None extends \IPS\Data\Cache
*
* @return bool
*/
public static function supported()
public static function supported(): bool
{
return TRUE;
}
@@ -94,49 +104,49 @@ class _None extends \IPS\Data\Cache
/**
* Abstract Method: Get
*
* @param string $key Key
* @param string $key Key
* @return string Value from the _datastore
*/
protected function get( $key )
protected function get( string $key ): string
{
throw new \RuntimeException;
throw new RuntimeException;
}
/**
* Get value using cache method if available or falling back to the database
*
* @param string $key Key
* @param bool $fallback Use database if no caching method is available?
* @param string $key Key
* @param bool $fallback Use database if no caching method is available?
* @return mixed
* @throws \OutOfRangeException
* @throws OutOfRangeException
*/
public function getWithExpire( $key, $fallback=FALSE )
public function getWithExpire( string $key, bool $fallback=FALSE ): mixed
{
if ( $fallback )
{
try
{
return json_decode( \IPS\Db::i()->select( 'cache_value', 'core_cache', array( 'cache_key=? AND cache_expire>?', $key, time() ) )->first(), TRUE );
return json_decode( Db::i()->select( 'cache_value', 'core_cache', array( 'cache_key=? AND cache_expire>?', $key, time() ) )->first(), TRUE );
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
}
else
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
}
/**
* Abstract Method: Set
*
* @param string $key Key
* @param string $value Value
* @param string $key Key
* @param string $value Value
* @return bool
*/
protected function set( $key, $value )
protected function set( string $key, string $value ): bool
{
return FALSE;
}
@@ -144,17 +154,17 @@ class _None extends \IPS\Data\Cache
/**
* Store value using cache method if available or falling back to the database
*
* @param string $key Key
* @param string $key Key
* @param mixed $value Value
* @param \IPS\DateTime $expire Expiration if using database
* @param bool $fallback Use database if no caching method is available?
* @param DateTime $expire Expiration if using database
* @param bool $fallback Use database if no caching method is available?
* @return bool
*/
public function storeWithExpire( $key, $value, \IPS\DateTime $expire, $fallback=FALSE )
public function storeWithExpire(string $key, mixed $value, DateTime $expire, bool $fallback=FALSE): bool
{
if ( $fallback )
{
\IPS\Db::i()->replace( 'core_cache', array(
Db::i()->replace( 'core_cache', array(
'cache_key' => $key,
'cache_value' => json_encode( $value ),
'cache_expire' => $expire->getTimestamp()
@@ -172,10 +182,10 @@ class _None extends \IPS\Data\Cache
/**
* Abstract Method: Exists?
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
protected function exists( $key )
protected function exists( string $key ): bool
{
return FALSE;
}
@@ -183,10 +193,10 @@ class _None extends \IPS\Data\Cache
/**
* Abstract Method: Delete
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
protected function delete( $key )
protected function delete( string $key ): bool
{
return TRUE;
}
@@ -196,13 +206,13 @@ class _None extends \IPS\Data\Cache
*
* @return void
*/
public function clearAll()
public function clearAll() : void
{
parent::clearAll();
try
{
\IPS\Db::i()->delete( 'core_cache' );
Db::i()->delete( 'core_cache' );
}
catch ( \IPS\Db\Exception $e ){}
catch ( Exception $e ){}
}
}
+65 -51
View File
@@ -11,38 +11,57 @@
namespace IPS\Data\Cache;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DomainException;
use IPS\Data\Cache;
use IPS\DateTime;
use IPS\Helpers\Form\Number;
use IPS\Helpers\Form\Password;
use IPS\Helpers\Form\Text;
use IPS\Redis as RedisClass;
use IPS\Request;
use RedisException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Redis Cache Class
*/
class _Redis extends \IPS\Data\Cache
class Redis extends Cache
{
/**
* Redis key
*/
protected string|false $_redisKey;
/**
* Constructor
*
* @param array $configuration Configuration to use
* @return void
*/
public function __construct( $configuration )
public function __construct( array $configuration )
{
try
{
$writeConnection = \IPS\Redis::i()->connection('write');
$readConnection = \IPS\Redis::i()->connection('read');
$writeConnection = RedisClass::i()->connection('write');
$readConnection = RedisClass::i()->connection('read');
if( !$writeConnection OR !$readConnection )
{
throw new \RedisException;
throw new RedisException;
}
$this->_redisKey = false;
}
catch( \RedisException $e )
catch( RedisException $e )
{
throw new \IPS\Data\Cache\Exception;
throw new Exception;
}
}
@@ -51,7 +70,7 @@ class _Redis extends \IPS\Data\Cache
*
* @return bool
*/
public static function supported()
public static function supported(): bool
{
return class_exists('Redis');
}
@@ -61,7 +80,7 @@ class _Redis extends \IPS\Data\Cache
*
* @return boolean
*/
public function checkKeys()
public function checkKeys(): bool
{
return false;
}
@@ -72,45 +91,40 @@ class _Redis extends \IPS\Data\Cache
* @param array $configuration Existing settings
* @return array \IPS\Helpers\Form\FormAbstract elements
*/
public static function configuration( $configuration )
public static function configuration( array $configuration ): array
{
return array(
'server' => new \IPS\Helpers\Form\Text( 'server_host', isset( $configuration['server'] ) ? $configuration['server'] : '', FALSE, array( 'placeholder' => '127.0.0.1' ), function( $val )
'server' => new Text( 'server_host', $configuration['server'] ?? '', FALSE, array( 'placeholder' => '127.0.0.1' ), function( $val )
{
if ( \IPS\Request::i()->cache_method === 'Redis' and empty( $val ) )
if ( Request::i()->cache_method === 'Redis' and empty( $val ) )
{
throw new \DomainException( 'datastore_redis_servers_err' );
throw new DomainException( 'datastore_redis_servers_err' );
}
}, NULL, NULL, 'redis_host' ),
'port' => new \IPS\Helpers\Form\Number( 'server_port', isset( $configuration['port'] ) ? $configuration['port'] : NULL, FALSE, array( 'placeholder' => '6379' ), function( $val )
'port' => new Number( 'server_port', $configuration['port'] ?? NULL, FALSE, array( 'placeholder' => '6379' ), function( $val )
{
if ( \IPS\Request::i()->cache_method === 'Redis' AND $val AND ( $val < 0 OR $val > 65535 ) )
if ( Request::i()->cache_method === 'Redis' AND $val AND ( $val < 0 OR $val > 65535 ) )
{
throw new \DomainException( 'datastore_redis_servers_err' );
throw new DomainException( 'datastore_redis_servers_err' );
}
}, NULL, NULL, 'redis_port' ),
'password' => new \IPS\Helpers\Form\Password( 'server_password', isset( $configuration['password'] ) ? $configuration['password'] : '', FALSE, array(), NULL, NULL, NULL, 'redis_password' ),
'password' => new Password( 'server_password', $configuration['password'] ?? '', FALSE, array(), NULL, NULL, NULL, 'redis_password' ),
);
}
/**
* Redis key
*/
protected $_redisKey;
/**
* Get random string used in the keys to identify this site compared to other sites
*
* @return string|FALSE Value from the _datastore; FALSE if key doesn't exist
*/
protected function _getRedisKey()
protected function _getRedisKey(): string|FALSE
{
if ( !$this->_redisKey )
{
if ( !( $this->_redisKey = \IPS\Redis::i()->get( 'redisKey' ) ) )
if ( !( $this->_redisKey = RedisClass::i()->get( 'redisKey' ) ) )
{
$this->_redisKey = md5( mt_rand() );
\IPS\Redis::i()->setex( 'redisKey', 604800, $this->_redisKey );
RedisClass::i()->setex( 'redisKey', 604800, $this->_redisKey );
}
}
@@ -120,10 +134,10 @@ class _Redis extends \IPS\Data\Cache
/**
* Abstract Method: Get
*
* @param string $key
* @param string $key
* @return string|FALSE Value from the _datastore; FALSE if key doesn't exist
*/
protected function get( $key )
protected function get( string $key ): string|FALSE
{
if( array_key_exists( $key, $this->cache ) )
{
@@ -132,13 +146,13 @@ class _Redis extends \IPS\Data\Cache
try
{
$this->cache[ $key ] = \IPS\Redis::i()->get( $this->_getRedisKey() . '_' . $key );
$this->cache[ $key ] = RedisClass::i()->get( $this->_getRedisKey() . '_' . $key );
return $this->cache[ $key ];
}
catch( \RedisException $e )
catch( RedisException $e )
{
\IPS\Redis::i()->resetConnection( $e );
RedisClass::i()->resetConnection( $e );
return FALSE;
}
@@ -147,28 +161,28 @@ class _Redis extends \IPS\Data\Cache
/**
* Abstract Method: Set
*
* @param string $key Key
* @param string $value Value
* @param \IPS\DateTime|NULL $expire Expreation time, or NULL for no expiration
* @param string $key Key
* @param string $value Value
* @param DateTime|NULL $expire Expreation time, or NULL for no expiration
* @return bool
*/
protected function set( $key, $value, \IPS\DateTime $expire = NULL )
protected function set( string $key, string $value, DateTime $expire = NULL ): bool
{
try
{
if ( $expire )
{
return (bool) \IPS\Redis::i()->setex( $this->_getRedisKey() . '_' . $key, $expire->getTimestamp() - time(), $value );
return RedisClass::i()->setex( $this->_getRedisKey() . '_' . $key, $expire->getTimestamp() - time(), $value );
}
else
{
/* Set for 24 hours */
return (bool) \IPS\Redis::i()->setex( $this->_getRedisKey() . '_' . $key, 86400, $value );
return RedisClass::i()->setex( $this->_getRedisKey() . '_' . $key, 86400, $value );
}
}
catch( \RedisException $e )
catch( RedisException $e )
{
\IPS\Redis::i()->resetConnection( $e );
RedisClass::i()->resetConnection( $e );
return FALSE;
}
@@ -177,35 +191,35 @@ class _Redis extends \IPS\Data\Cache
/**
* Abstract Method: Exists?
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
protected function exists( $key )
protected function exists( string $key ): bool
{
if( array_key_exists( $key, $this->cache ) )
{
return ( $this->cache[ $key ] === FALSE ) ? FALSE : TRUE;
return !( ( $this->cache[$key] === FALSE ) );
}
/* We do a get instead of an exists() check because it will cause the cache value to be fetched and cached inline, saving another call to the server */
return ( $this->get( $key ) === FALSE ) ? FALSE : TRUE;
return !( ( $this->get( $key ) === FALSE ) );
}
/**
* Abstract Method: Delete
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
protected function delete( $key )
protected function delete( string $key ): bool
{
try
{
return (bool) \IPS\Redis::i()->del( $this->_getRedisKey() . '_' . $key );
return (bool) RedisClass::i()->del( $this->_getRedisKey() . '_' . $key );
}
catch( \RedisException $e )
catch( RedisException $e )
{
\IPS\Redis::i()->resetConnection( $e );
RedisClass::i()->resetConnection( $e );
return FALSE;
}
}
@@ -215,12 +229,12 @@ class _Redis extends \IPS\Data\Cache
*
* @return void
*/
public function clearAll()
public function clearAll() : void
{
parent::clearAll();
$this->_redisKey = md5( mt_rand() );
\IPS\Redis::i()->setex( 'redisKey', 604800, $this->_redisKey );
RedisClass::i()->setex( 'redisKey', 604800, $this->_redisKey );
}
}
+31 -22
View File
@@ -11,23 +11,32 @@
namespace IPS\Data\Cache;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DirectoryIterator;
use IPS\Data\Cache;
use IPS\DateTime;
use IPS\Helpers\Form\Text;
use function defined;
use function file_put_contents;
use const IPS\IPS_FILE_PERMISSION;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Test Cache Class
*/
class _Test extends \IPS\Data\Cache
class Test extends Cache
{
/**
* Server supports this method?
*
* @return bool
*/
public static function supported()
public static function supported(): bool
{
return TRUE;
}
@@ -38,15 +47,15 @@ class _Test extends \IPS\Data\Cache
* @param array $configuration Existing settings
* @return array \IPS\Helpers\Form\FormAbstract elements
*/
public static function configuration( $configuration )
public static function configuration( array $configuration ): array
{
return array( 'path' => new \IPS\Helpers\Form\Text( 'datastore_test_path', ( isset( $configuration['path'] ) ) ? $configuration['path'] : '', FALSE ) );
return array( 'path' => new Text( 'datastore_test_path', ( isset( $configuration['path'] ) ) ? $configuration['path'] : '', FALSE ) );
}
/**
* @brief Storage Path
*/
public $_path;
public string $_path;
/**
* Constructor
@@ -54,7 +63,7 @@ class _Test extends \IPS\Data\Cache
* @param array $configuration Configuration
* @return void
*/
public function __construct( $configuration )
public function __construct( array $configuration )
{
$this->_path = rtrim( $configuration['path'], '/' );
}
@@ -62,10 +71,10 @@ class _Test extends \IPS\Data\Cache
/**
* Abstract Method: Get
*
* @param string $key Key
* @param string $key Key
* @return string Value from the _datastore
*/
protected function get( $key )
protected function get( string $key ): string
{
return file_get_contents( $this->_path . '/' . $key . '.txt' );
}
@@ -73,25 +82,25 @@ class _Test extends \IPS\Data\Cache
/**
* Abstract Method: Set
*
* @param string $key Key
* @param string $value Value
* @param \IPS\DateTime|NULL $expire Expreation time, or NULL for no expiration
* @param string $key Key
* @param string $value Value
* @param DateTime|NULL $expire Expreation time, or NULL for no expiration
* @return bool
*/
protected function set( $key, $value, \IPS\DateTime $expire = NULL )
protected function set( string $key, string $value, DateTime $expire = NULL ): bool
{
$return = \file_put_contents( $this->_path . '/' . $key . '.txt', $value );
chmod( $this->_path . '/' . $key . '.txt', \IPS\IPS_FILE_PERMISSION );
$return = file_put_contents( $this->_path . '/' . $key . '.txt', $value );
chmod( $this->_path . '/' . $key . '.txt', IPS_FILE_PERMISSION );
return $return;
}
/**
* Abstract Method: Exists?
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
protected function exists( $key )
protected function exists( string $key ): bool
{
return file_exists( $this->_path . '/' . $key . '.txt' );
}
@@ -99,10 +108,10 @@ class _Test extends \IPS\Data\Cache
/**
* Abstract Method: Delete
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
protected function delete( $key )
protected function delete( string $key ): bool
{
return @unlink( $this->_path . '/' . $key . '.txt' );
}
@@ -112,10 +121,10 @@ class _Test extends \IPS\Data\Cache
*
* @return void
*/
public function clearAll()
public function clearAll() : void
{
parent::clearAll();
foreach ( new \DirectoryIterator( $this->_path ) as $file )
foreach ( new DirectoryIterator( $this->_path ) as $file )
{
if ( !$file->isDot() and $file != 'index.html' )
{
+71 -60
View File
@@ -11,21 +11,32 @@
namespace IPS\Data;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Data\Cache\None;
use IPS\Data\Store\Exception as StoreException;
use OutOfRangeException;
use function defined;
use function is_array;
use const IPS\CACHE_METHOD;
use const IPS\STORE_CONFIG;
use const IPS\STORE_METHOD;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Abstract Storage Class
*/
abstract class _Store extends AbstractData
abstract class Store extends AbstractData
{
/**
* @brief Instance
*/
protected static $instance;
protected static ?Store $instance = NULL;
/**
* Available Data Store Methods
@@ -45,32 +56,32 @@ abstract class _Store extends AbstractData
/**
* Get instance
*
* @return \IPS\Data\Store
* @return Store
*/
public static function i()
public static function i(): Store
{
if( static::$instance === NULL )
{
try
{
if( !isset( static::availableMethods()[ \IPS\STORE_METHOD ] ) )
if( !isset( static::availableMethods()[ STORE_METHOD ] ) )
{
throw new \IPS\Data\Store\Exception;
throw new StoreException;
}
$classname = static::availableMethods()[ \IPS\STORE_METHOD ];
$classname = static::availableMethods()[ STORE_METHOD ];
if( !$classname::supported() )
{
throw new \IPS\Data\Store\Exception;
throw new StoreException;
}
static::$instance = new $classname( json_decode( \IPS\STORE_CONFIG, TRUE ) );
static::$instance = new $classname( json_decode( STORE_CONFIG, TRUE ) );
}
catch( \IPS\Data\Store\Exception $e )
catch( StoreException $e )
{
/* Fall back to MySQL (if not using it currently, and then FileSystem if we are) */
if ( \IPS\STORE_METHOD !== 'Database' )
if ( STORE_METHOD !== 'Database' )
{
$classname = static::availableMethods()['Database'];
}
@@ -79,7 +90,7 @@ abstract class _Store extends AbstractData
$classname = static::availableMethods()['FileSystem'];
}
static::$instance = new $classname( json_decode( \IPS\STORE_CONFIG, TRUE ) );
static::$instance = new $classname( json_decode( STORE_CONFIG, TRUE ) );
}
}
@@ -89,33 +100,33 @@ abstract class _Store extends AbstractData
/**
* @brief Always needed Store keys
*/
public $initLoad = array();
public array $initLoad = array();
/**
* @brief Template store keys
*/
public $templateLoad = array();
public array $templateLoad = array();
/**
* @brief Log
*/
public $log = array();
public array $log = array();
/**
* Test to see if this store works
*
* @return boolean
*/
public static function testStore()
public static function testStore(): bool
{
try
{
$classname = 'IPS\Data\Store\\' . \IPS\STORE_METHOD;
$store = new $classname( json_decode( \IPS\STORE_CONFIG, TRUE ) );
$classname = 'IPS\Data\Store\\' . STORE_METHOD;
$store = new $classname( json_decode( STORE_CONFIG, TRUE ) );
return $store->test();
}
catch( \Exception $e )
catch( Exception $e )
{
return FALSE;
}
@@ -138,7 +149,7 @@ abstract class _Store extends AbstractData
*
* @return bool
*/
public function test()
public function test(): bool
{
/* Set a test key */
$key = 'test_' . md5( mt_rand() );
@@ -154,7 +165,7 @@ abstract class _Store extends AbstractData
unset( $this->$key );
return ( $value == 1 ) ? TRUE : FALSE;
return $value == 1;
}
return FALSE;
@@ -163,10 +174,10 @@ abstract class _Store extends AbstractData
/**
* Magic Method: Get
*
* @param string $key Key
* @return string Value from the _datastore
* @param string $key Key
* @return mixed Value from the _datastore
*/
public function __get( $key )
public function __get( string $key ): mixed
{
/* Try to get it from the cache store... */
try
@@ -174,25 +185,25 @@ abstract class _Store extends AbstractData
/* If caching is enabled, and this isn't the special map of hashes... */
if ( $this->useCache( $key ) )
{
if ( \IPS\Data\Cache::i()->checkKeys() === false )
if ( Cache::i()->checkKeys() === false )
{
if ( isset( \IPS\Data\Cache::i()->$key ) and \IPS\Data\Cache::i()->$key !== NULL )
if ( isset( Cache::i()->$key ) and Cache::i()->$key !== NULL )
{
/* Cache engine always returns current content, so return it */
return \IPS\Data\Cache::i()->$key;
return Cache::i()->$key;
}
/* Still here? throw an exception so we get it from the data storage engine */
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
else
{
/* It exists in the caching engine, and we know the hash of the correct value... */
$cacheKeys = ( isset( $this->cacheKeys ) and \is_array( $this->cacheKeys ) ) ? $this->cacheKeys : array();
if ( array_key_exists( $key, $cacheKeys ) and isset( \IPS\Data\Cache::i()->$key ) )
$cacheKeys = ( isset( $this->cacheKeys ) and is_array( $this->cacheKeys ) ) ? $this->cacheKeys : array();
if ( array_key_exists( $key, $cacheKeys ) and isset( Cache::i()->$key ) )
{
/* Get it... */
$value = \IPS\Data\Cache::i()->$key;
$value = Cache::i()->$key;
/* But only use it if the hash matches */
if( $cacheKeys[ $key ] == md5( json_encode( $value ) ) )
@@ -204,11 +215,11 @@ abstract class _Store extends AbstractData
}
/* Still here? throw an exception so we get it from the data storage engine */
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
/* If we couldn't get it from the cache engine, get it from the data storage engine */
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
/* Actually get it... */
$value = parent::__get( $key );
@@ -217,12 +228,12 @@ abstract class _Store extends AbstractData
if ( $this->useCache( $key ) )
{
/* Set it in the caching engine... */
\IPS\Data\Cache::i()->$key = $value;
Cache::i()->$key = $value;
if ( \IPS\Data\Cache::i()->checkKeys() === true )
if ( Cache::i()->checkKeys() === true )
{
/* And set the hash in the cacheKeys hash map */
$cacheKeys = ( isset( $this->cacheKeys ) and \is_array( $this->cacheKeys ) ) ? $this->cacheKeys : array();
$cacheKeys = ( isset( $this->cacheKeys ) and is_array( $this->cacheKeys ) ) ? $this->cacheKeys : array();
$cacheKeys[ $key ] = md5( json_encode( $value ) );
$this->cacheKeys = $cacheKeys;
}
@@ -235,11 +246,11 @@ abstract class _Store extends AbstractData
/**
* Magic Method: Set
*
* @param string $key Key
* @param string $value Value
* @param string $key Key
* @param mixed $value Value
* @return void
*/
public function __set( $key, $value )
public function __set( string $key, mixed $value )
{
/* Actually set it in the data storage engine */
parent::__set( $key, $value );
@@ -248,14 +259,14 @@ abstract class _Store extends AbstractData
if ( $this->useCache( $key ) )
{
/* Then also set it in the cache... */
\IPS\Data\Cache::i()->$key = $value;
Cache::i()->$key = $value;
/* And set the hash in the cacheKeys hash map */
if ( \IPS\Data\Cache::i()->checkKeys() === true )
if ( Cache::i()->checkKeys() === true )
{
if ( ( isset( $this->cacheKeys ) and \is_array( $this->cacheKeys ) ) )
if ( ( isset( $this->cacheKeys ) and is_array( $this->cacheKeys ) ) )
{
$cacheKeys = \is_array( $this->cacheKeys ) ? $this->cacheKeys : array();
$cacheKeys = $this->cacheKeys;
$cacheKeys[ $key ] = md5( json_encode( $value ) );
$this->cacheKeys = $cacheKeys;
}
@@ -270,28 +281,28 @@ abstract class _Store extends AbstractData
/**
* Magic Method: Isset
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
public function __isset( $key )
public function __isset( string $key ): bool
{
/* If caching is enabled, and this isn't the special map of hashes, try to get it from the cache store... */
if ( $this->useCache( $key ) )
{
/* Does it exist in the caching engine? */
if ( isset( \IPS\Data\Cache::i()->$key ) )
if ( isset( Cache::i()->$key ) )
{
/* Get it... */
$value = \IPS\Data\Cache::i()->$key;
$value = Cache::i()->$key;
if ( \IPS\Data\Cache::i()->checkKeys() === false )
if ( Cache::i()->checkKeys() === false )
{
/* Cache engine always returns current content, so return it */
return TRUE;
}
/* Only use it if the hash matches */
$cacheKeys = ( isset( $this->cacheKeys ) and \is_array( $this->cacheKeys ) ) ? $this->cacheKeys : array();
$cacheKeys = ( isset( $this->cacheKeys ) and is_array( $this->cacheKeys ) ) ? $this->cacheKeys : array();
if ( isset( $cacheKeys[ $key ] ) and $cacheKeys[ $key ] == md5( json_encode( $value ) ) )
{
return TRUE;
@@ -306,10 +317,10 @@ abstract class _Store extends AbstractData
/**
* Magic Method: Unset
*
* @param string $key Key
* @param string $key Key
* @return void
*/
public function __unset( $key )
public function __unset( string $key )
{
/* Unset it in the data storage engine */
parent::__unset( $key );
@@ -318,17 +329,17 @@ abstract class _Store extends AbstractData
if ( $this->useCache( $key ) )
{
/* Remove it from the cache store... */
unset( \IPS\Data\Cache::i()->$key );
unset( Cache::i()->$key );
if ( \IPS\Data\Cache::i()->checkKeys() === true )
if ( Cache::i()->checkKeys() === true )
{
/* And from the special map of hashes */
try
{
$cacheKeys = \is_array( $this->cacheKeys ) ? $this->cacheKeys : array();
$cacheKeys = is_array( $this->cacheKeys ) ? $this->cacheKeys : array();
}
/* If the cache doesn't exist we shouldn't error out with an uncaught OutOfRangeException */
catch( \OutOfRangeException $e )
catch( OutOfRangeException $e )
{
$cacheKeys = array();
}
@@ -346,11 +357,11 @@ abstract class _Store extends AbstractData
/**
* Check the cache for this key?
*
* @param string $key The store key
* @param string $key The store key
* @return boolean
*/
protected function useCache( $key )
protected function useCache( string $key ): bool
{
return ( \IPS\CACHE_METHOD != \IPS\STORE_METHOD and ( ! \IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None ) and $key !== 'cacheKeys' );
return ( CACHE_METHOD != STORE_METHOD and ( ! Cache::i() instanceof None ) and $key !== 'cacheKeys' );
}
}
+49 -35
View File
@@ -11,23 +11,34 @@
namespace IPS\Data\Store;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Data\Cache;
use IPS\Data\Cache\None;
use IPS\Data\Store;
use IPS\Db;
use IPS\Db\Exception;
use IPS\Session\Front;
use UnderflowException;
use function defined;
use function in_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Database Storage Class
*/
class _Database extends \IPS\Data\Store
class Database extends Store
{
/**
* Server supports this method?
*
* @return bool
*/
public static function supported()
public static function supported(): bool
{
return TRUE;
}
@@ -35,17 +46,17 @@ class _Database extends \IPS\Data\Store
/**
* @brief Always needed Store keys
*/
public $initLoad = array( 'cacheKeys', 'settings', 'storageConfigurations', 'themes', 'languages', 'groups', 'applications', 'modules', 'widgets', 'furl', 'javascript_map', 'metaTags', 'bannedIpAddresses', 'license_data', 'furl_configuration', 'rssFeeds', 'frontNavigation', 'globalStreamIds', 'profileSteps' );
public array $initLoad = array( 'cacheKeys', 'settings', 'storageConfigurations', 'themes', 'languages', 'groups', 'applications', 'modules', 'widgets', 'furl', 'javascript_map', 'metaTags', 'bannedIpAddresses', 'license_data', 'furl_configuration', 'rssFeeds', 'frontNavigation', 'globalStreamIds', 'profileSteps' );
/**
* @brief Have we done the intitial load?
*/
protected $doneInitLoad = FALSE;
protected bool $doneInitLoad = FALSE;
/**
* @brief Cache
*/
protected static $cache = array();
protected static array $cache = array();
/**
* Constructor
@@ -56,7 +67,7 @@ class _Database extends \IPS\Data\Store
public function __construct()
{
/* When caching is enabled, we only need cacheKeys, the rest will come from the cache */
if ( !\IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None )
if ( !Cache::i() instanceof None )
{
$this->initLoad = array( 'cacheKeys' );
}
@@ -70,13 +81,16 @@ class _Database extends \IPS\Data\Store
$this->initLoad[] = 'acpNotifications';
$this->initLoad[] = 'emoticons';
if ( \IPS\Session\Front::loggedIn() )
{
$this->initLoad[] = 'administrators';
$this->initLoad[] = 'moderators';
$this->initLoad[] = 'group_promotions';
$this->initLoad[] = 'promoters';
}
$this->initLoad[] = 'administrators';
$this->initLoad[] = 'moderators';
$this->initLoad[] = 'group_promotions';
$this->initLoad[] = 'promoters';
/* things from other apps */
$this->initLoad[] = 'nexusPackagesWithReviews';
$this->initLoad[] = 'cms_menu';
$this->initLoad[] = 'cms_databases';
$this->initLoad[] = 'pages_page_urls';
}
}
@@ -87,9 +101,9 @@ class _Database extends \IPS\Data\Store
* @param array $keys Keys
* @return void
*/
public function loadIntoMemory( array $keys )
public function loadIntoMemory( array $keys ) : void
{
foreach ( \IPS\Db::i()->select( '*', 'core_store', \IPS\Db::i()->in( 'store_key', $keys ) ) as $row )
foreach ( Db::i()->select( '*', 'core_store', Db::i()->in( 'store_key', $keys ) ) as $row )
{
static::$cache[ $row['store_key'] ] = $row['store_value'];
}
@@ -98,12 +112,12 @@ class _Database extends \IPS\Data\Store
/**
* Abstract Method: Get
*
* @param string $key Key
* @param string $key Key
* @return string Value from the datastore
*/
public function get( $key )
public function get( string $key ) : string
{
if ( !$this->doneInitLoad and \in_array( $key, $this->initLoad ) )
if ( !$this->doneInitLoad and in_array( $key, $this->initLoad ) )
{
$this->loadIntoMemory( $this->initLoad );
$this->doneInitLoad = TRUE;
@@ -113,11 +127,11 @@ class _Database extends \IPS\Data\Store
{
try
{
static::$cache[ $key ] = \IPS\Db::i()->select( 'store_value', 'core_store', array( 'store_key=?', $key ) )->first();
static::$cache[ $key ] = Db::i()->select( 'store_value', 'core_store', array( 'store_key=?', $key ) )->first();
}
catch ( \IPS\Db\Exception $e )
catch ( Exception $e )
{
throw new \UnderflowException;
throw new UnderflowException;
}
}
@@ -127,13 +141,13 @@ class _Database extends \IPS\Data\Store
/**
* Abstract Method: Set
*
* @param string $key Key
* @param string $value Value
* @param string $key Key
* @param string $value Value
* @return bool
*/
public function set( $key, $value )
public function set( string $key, string $value ): bool
{
\IPS\Db::i()->replace( 'core_store', array(
Db::i()->replace( 'core_store', array(
'store_key' => $key,
'store_value' => $value
) );
@@ -146,10 +160,10 @@ class _Database extends \IPS\Data\Store
/**
* Abstract Method: Exists?
*
* @param string $key Key
* @param string $key Key
* @return bool
*/
public function exists( $key )
public function exists( string $key ): bool
{
if ( isset( static::$cache[ $key ] ) )
{
@@ -162,7 +176,7 @@ class _Database extends \IPS\Data\Store
$this->get( $key );
return TRUE;
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
return FALSE;
}
@@ -175,31 +189,31 @@ class _Database extends \IPS\Data\Store
* @param string $key Key
* @return bool
*/
public function delete( $key )
public function delete( string $key ): bool
{
if( isset( static::$cache[ $key ] ) )
{
unset( static::$cache[ $key ] );
}
\IPS\Db::i()->delete( 'core_store', array( 'store_key=?', $key ) );
Db::i()->delete( 'core_store', array( 'store_key=?', $key ) );
return TRUE;
}
/**
* Abstract Method: Clear All Caches
*
* @param NULL|string $exclude Key to exclude (keep)
* @param string|null $exclude Key to exclude (keep)
* @return void
*/
public function clearAll( $exclude=NULL )
public function clearAll( string $exclude=NULL ) : void
{
$where = array();
if( $exclude !== NULL )
{
$where[] = array( 'store_key != ?', $exclude );
}
\IPS\Db::i()->delete( 'core_store', $where );
Db::i()->delete( 'core_store', $where );
foreach( static::$cache as $key => $value )
{
+7 -3
View File
@@ -11,16 +11,20 @@
namespace IPS\Data\Store;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use RuntimeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Cache Exception Class
*/
class _Exception extends \RuntimeException
class Exception extends RuntimeException
{
}
+53 -38
View File
@@ -11,23 +11,38 @@
namespace IPS\Data\Store;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DirectoryIterator;
use DomainException;
use IPS\Data\Store;
use IPS\Helpers\Form\Text;
use IPS\Request;
use ParseError;
use UnderflowException;
use function defined;
use function file_put_contents;
use function function_exists;
use const IPS\IPS_FILE_PERMISSION;
use const IPS\IPS_FOLDER_PERMISSION;
use const IPS\SUITE_UNIQUE_KEY;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* File System Storage Class
*/
class _FileSystem extends \IPS\Data\Store
class FileSystem extends Store
{
/**
* Server supports this method?
*
* @return bool
*/
public static function supported()
public static function supported(): bool
{
return TRUE;
}
@@ -38,23 +53,23 @@ class _FileSystem extends \IPS\Data\Store
* @param array $configuration Existing settings
* @return array \IPS\Helpers\Form\FormAbstract elements
*/
public static function configuration( $configuration )
public static function configuration( array $configuration ): array
{
return array(
'path' => new \IPS\Helpers\Form\Text( 'datastore_filesystem_path', ( isset( $configuration['path'] ) ) ? rtrim( str_replace( '{root}', \IPS\ROOT_PATH, $configuration['path'] ), '/' ) : \IPS\ROOT_PATH . '/datastore', FALSE, array(), function( $val )
'path' => new Text( 'datastore_filesystem_path', ( isset( $configuration['path'] ) ) ? rtrim( str_replace( '{root}', \IPS\ROOT_PATH, $configuration['path'] ), '/' ) : \IPS\ROOT_PATH . '/datastore', FALSE, array(), function( $val )
{
if ( \IPS\Request::i()->datastore_method === 'FileSystem' )
if ( Request::i()->datastore_method === 'FileSystem' )
{
if ( !is_dir( $val ) and is_writable( $val ) )
{
mkdir( $val );
chmod( $val, \IPS\IPS_FOLDER_PERMISSION );
\file_put_contents( $val . '/index.html', '' );
chmod( $val, IPS_FOLDER_PERMISSION );
file_put_contents( $val . '/index.html', '' );
}
if ( !is_dir( $val ) or !is_writable( $val ) )
{
throw new \DomainException( 'datastore_filesystem_path_err' );
throw new DomainException( 'datastore_filesystem_path_err' );
}
}
} )
@@ -64,7 +79,7 @@ class _FileSystem extends \IPS\Data\Store
/**
* @brief Storage Path
*/
public $_path;
public string $_path;
/**
* Constructor
@@ -72,7 +87,7 @@ class _FileSystem extends \IPS\Data\Store
* @param array $configuration Configuration
* @return void
*/
public function __construct( $configuration )
public function __construct( array $configuration )
{
$this->_path = rtrim( str_replace( '{root}', \IPS\ROOT_PATH, $configuration['path'] ), '/' );
@@ -86,7 +101,7 @@ class _FileSystem extends \IPS\Data\Store
/**
* @brief Cache
*/
protected static $cache = array();
protected static array $cache = array();
/**
* Abstract Method: Get
@@ -94,24 +109,24 @@ class _FileSystem extends \IPS\Data\Store
* @param string $key Key
* @return string Value from the _datastore
*/
public function get( $key )
public function get( string $key ): string
{
if ( !isset( static::$cache[ $key ] ) )
{
if ( @filesize( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php' ) )
if ( @filesize( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php' ) )
{
try
{
static::$cache[ $key ] = require( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php' );
static::$cache[ $key ] = require( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php' );
}
catch ( \ParseError $e )
catch ( ParseError $e )
{
throw new \UnderflowException;
throw new UnderflowException;
}
}
else
{
throw new \UnderflowException;
throw new UnderflowException;
}
}
@@ -121,11 +136,11 @@ class _FileSystem extends \IPS\Data\Store
/**
* Abstract Method: Set
*
* @param string $key Key
* @param string $value Value
* @param string $key Key
* @param string $value Value
* @return bool
*/
public function set( $key, $value )
public function set( string $key, string $value ): bool
{
$contents = <<<CONTENTS
<?php
@@ -136,24 +151,24 @@ VALUE;
CONTENTS;
$result = (bool) @\file_put_contents( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php', $contents, LOCK_EX );
$result = (bool) @file_put_contents( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php', $contents, LOCK_EX );
/* Sometimes LOCK_EX is unavailable and throws file_put_contents(): Exclusive locks are not supported for this stream.
While we would prefer an exclusive lock, it would be better to write the file if possible. */
if( !$result )
{
@unlink( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php' );
$result = (bool) @\file_put_contents( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php', $contents );
@unlink( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php' );
$result = (bool) @file_put_contents( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php', $contents );
}
@chmod( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php', \IPS\IPS_FILE_PERMISSION );
@chmod( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php', IPS_FILE_PERMISSION );
static::$cache[ $key ] = $value;
/* Clear zend opcache if enabled */
if ( \function_exists( 'opcache_invalidate' ) )
if ( function_exists( 'opcache_invalidate' ) )
{
@opcache_invalidate( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php' );
@opcache_invalidate( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php' );
}
return $result;
@@ -165,7 +180,7 @@ CONTENTS;
* @param string $key Key
* @return bool
*/
public function exists( $key )
public function exists( string $key ): bool
{
if( isset( static::$cache[ $key ] ) )
{
@@ -178,7 +193,7 @@ CONTENTS;
$this->get( $key );
return TRUE;
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
return FALSE;
}
@@ -191,12 +206,12 @@ CONTENTS;
* @param string $key Key
* @return bool
*/
public function delete( $key )
public function delete( string $key ): bool
{
$return = false;
if ( file_exists( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php' ) )
if ( file_exists( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php' ) )
{
$return = @unlink( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php' );
$return = @unlink( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php' );
}
if( array_key_exists( $key, static::$cache ) )
@@ -205,9 +220,9 @@ CONTENTS;
}
/* Clear zend opcache if enabled */
if ( \function_exists( 'opcache_invalidate' ) )
if ( function_exists( 'opcache_invalidate' ) )
{
@opcache_invalidate( $this->_path . '/' . $key . '.' . \IPS\SUITE_UNIQUE_KEY . '.php' );
@opcache_invalidate( $this->_path . '/' . $key . '.' . SUITE_UNIQUE_KEY . '.php' );
}
return $return;
@@ -219,9 +234,9 @@ CONTENTS;
* @param NULL|string $exclude Key to exclude (keep)
* @return void
*/
public function clearAll( $exclude=NULL )
public function clearAll( string $exclude=NULL ) : void
{
foreach ( new \DirectoryIterator( $this->_path ) as $file )
foreach ( new DirectoryIterator( $this->_path ) as $file )
{
if ( !$file->isDot() and ( mb_substr( $file, -9 ) === '.ipsstore' or ( mb_substr( $file, -4 ) === '.php' and $file != $exclude . '.php' ) ) )
{
@@ -240,7 +255,7 @@ CONTENTS;
/* Clear zend opcache if enabled - we call reset here since we're wiping multiple files
and since this gets called as part of a general 'rebuild everything', a reset is
probably a good idea anyway */
if ( \function_exists( 'opcache_reset' ) )
if ( function_exists( 'opcache_reset' ) )
{
@opcache_reset();
}
+53 -45
View File
@@ -11,31 +11,38 @@
namespace IPS\Data\Store;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Data\Store;
use IPS\Redis as RedisClass;
use RedisException;
use UnderflowException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Redis Storage Class
*/
class _Redis extends \IPS\Data\Store
class Redis extends Store
{
/**
* Server supports this method?
*
* @return bool
*/
public static function supported()
public static function supported(): bool
{
return class_exists('Redis');
return class_exists('\\Redis');
}
/**
* Redis key
*/
protected $_redisKey;
protected string|false $_redisKey;
/**
* Constructor
@@ -43,21 +50,23 @@ class _Redis extends \IPS\Data\Store
* @param array $configuration Configuration to use
* @return void
*/
public function __construct( $configuration )
public function __construct( array $configuration )
{
try
{
$writeConnection = \IPS\Redis::i()->connection('write');
$readConnection = \IPS\Redis::i()->connection('read');
$writeConnection = RedisClass::i()->connection('write');
$readConnection = RedisClass::i()->connection('read');
if( !$writeConnection OR !$readConnection )
{
throw new \RedisException;
throw new RedisException;
}
$this->_redisKey = false;
}
catch( \RedisException $e )
catch( RedisException $e )
{
throw new \IPS\Data\Store\Exception;
throw new Exception;
}
}
@@ -66,16 +75,16 @@ class _Redis extends \IPS\Data\Store
*
* @return string|FALSE Value from the _datastore; FALSE if key doesn't exist
*/
protected function _getRedisKey()
protected function _getRedisKey(): string|FALSE
{
if ( !$this->_redisKey )
{
/* Last access ensures that the data is not stale if we fail back to MySQL and then go back to Redis later */
if ( !( $this->_redisKey = \IPS\Redis::i()->get( 'redisKey_store' ) ) OR ! \IPS\Redis::i()->get( 'redisStore_lastAccess' ) )
if ( !( $this->_redisKey = RedisClass::i()->get( 'redisKey_store' ) ) OR ! RedisClass::i()->get( 'redisStore_lastAccess' ) )
{
$this->_redisKey = md5( mt_rand() );
\IPS\Redis::i()->setex( 'redisKey_store', 604800, $this->_redisKey );
\IPS\Redis::i()->setex( 'redisStore_lastAccess', ( 3 * 3600 ), time() );
RedisClass::i()->setex( 'redisKey_store', 604800, $this->_redisKey );
RedisClass::i()->setex( 'redisStore_lastAccess', ( 3 * 3600 ), time() );
}
}
@@ -85,12 +94,12 @@ class _Redis extends \IPS\Data\Store
/**
* @brief Cache
*/
protected static $cache = array();
protected static array $cache = array();
/**
* @brief Already updated lastAccess?
*/
protected static $updatedLastAccess = FALSE;
protected static bool $updatedLastAccess = FALSE;
/**
* Abstract Method: Get
@@ -98,7 +107,7 @@ class _Redis extends \IPS\Data\Store
* @param string $key Key
* @return string|FALSE Value from the _datastore; FALSE if key doesn't exist
*/
public function get( $key )
public function get( string $key ): string|FALSE
{
if( array_key_exists( $key, static::$cache ) )
{
@@ -110,31 +119,31 @@ class _Redis extends \IPS\Data\Store
/* Set the last access time */
if ( static::$updatedLastAccess === FALSE )
{
\IPS\Redis::i()->setex( 'redisStore_lastAccess', ( 3 * 3600 ), time() );
RedisClass::i()->setex( 'redisStore_lastAccess', ( 3 * 3600 ), time() );
static::$updatedLastAccess = TRUE;
}
$return = \IPS\Redis::i()->get( $this->_getRedisKey() . '_' . $key );
$return = RedisClass::i()->get( $this->_getRedisKey() . '_' . $key );
if ( $return !== FALSE AND $decoded = \IPS\Redis::i()->decode( $return ) )
if ( $return !== FALSE AND $decoded = RedisClass::i()->decode( $return ) )
{
static::$cache[ $key ] = $decoded;
return static::$cache[ $key ];
}
else
{
throw new \UnderflowException;
throw new UnderflowException;
}
}
catch( \RedisException $e )
catch( RedisException $e )
{
/* Do not reset the connection if the decode failed */
if ( $e->getMessage() !== 'DECODE_ERROR' )
{
\IPS\Redis::i()->resetConnection( $e );
RedisClass::i()->resetConnection( $e );
}
throw new \UnderflowException;
throw new UnderflowException;
}
}
@@ -145,15 +154,15 @@ class _Redis extends \IPS\Data\Store
* @param string $value Value
* @return bool
*/
public function set( $key, $value )
public function set( string $key, string $value ): bool
{
try
{
return (bool) \IPS\Redis::i()->setex( $this->_getRedisKey() . '_' . $key, 604800, \IPS\Redis::i()->encode( $value ) );
return RedisClass::i()->setex( $this->_getRedisKey() . '_' . $key, 604800, RedisClass::i()->encode( $value ) );
}
catch( \RedisException $e )
catch( RedisException $e )
{
\IPS\Redis::i()->resetConnection( $e );
RedisClass::i()->resetConnection( $e );
return FALSE;
}
@@ -165,19 +174,19 @@ class _Redis extends \IPS\Data\Store
* @param string $key Key
* @return bool
*/
public function exists( $key )
public function exists( string $key ): bool
{
if( array_key_exists( $key, static::$cache ) )
{
return ( static::$cache[ $key ] === FALSE ) ? FALSE : TRUE;
return !( ( static::$cache[$key] === FALSE ) );
}
/* We do a get instead of an exists() check because it will cause the cache value to be fetched and cached inline, saving another call to the server */
try
{
return ( $this->get( $key ) === FALSE ) ? FALSE : TRUE;
return !( ( $this->get( $key ) === FALSE ) );
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
return FALSE;
}
@@ -189,16 +198,16 @@ class _Redis extends \IPS\Data\Store
* @param string $key Key
* @return bool
*/
public function delete( $key )
public function delete( string $key ): bool
{
try
{
unset( static::$cache[ $key ] );
return (bool) \IPS\Redis::i()->del( $this->_getRedisKey() . '_' . $key );
return (bool) RedisClass::i()->del( $this->_getRedisKey() . '_' . $key );
}
catch( \RedisException $e )
catch( RedisException $e )
{
\IPS\Redis::i()->resetConnection( $e );
RedisClass::i()->resetConnection( $e );
return FALSE;
}
}
@@ -208,17 +217,16 @@ class _Redis extends \IPS\Data\Store
*
* @return void
*/
public function clearAll()
public function clearAll() : void
{
try
{
$this->_redisKey = md5( mt_rand() );
\IPS\Redis::i()->setex( 'redisKey_store', 604800, $this->_redisKey );
RedisClass::i()->setex( 'redisKey_store', 604800, $this->_redisKey );
}
catch( \RedisException $e )
catch( RedisException $e )
{
\IPS\Redis::i()->resetConnection( $e );
return FALSE;
RedisClass::i()->resetConnection( $e );
}
}
@@ -228,8 +236,8 @@ class _Redis extends \IPS\Data\Store
*
* @return bool
*/
public function test()
public function test(): bool
{
return \IPS\Redis::i()->test();
return RedisClass::i()->test();
}
}