Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

+94 -105
View File
@@ -11,7 +11,7 @@
namespace IPS;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -51,24 +51,27 @@ class _Redis
* @brief Unpack the config once
*/
protected static $config = NULL;
/**
* @brief Can we encrypt?
* Writes made
*/
protected $canEncrypt = FALSE;
protected static $writes = array();
/**
* Get instance
*
* @param NULL|array $configuration Configuration to use (NULL to use \IPS\REDIS_CONFIG or \IPS\CACHE_CONFIG)
* @param NULL|string $identifier Identifier (to support multiple instances)
* @return \Redis
*/
public static function i( $configuration=NULL, $identifier=NULL )
{
if ( static::$config === NULL )
{
static::$config = $configuration ?: json_decode( \IPS\CACHE_CONFIG, true );
$config = ( \defined( '\IPS\REDIS_CONFIG' ) and \IPS\REDIS_CONFIG !== NULL ) ? \IPS\REDIS_CONFIG : \IPS\CACHE_CONFIG;
static::$config = $configuration ?: json_decode( $config, true );
}
$identifier = $identifier ? $identifier : '_MAIN';
if ( ! isset( static::$multitons[ $identifier ] ) )
@@ -81,23 +84,34 @@ class _Redis
/* Return */
return static::$multitons[ $identifier ];
}
}
/**
* Constructor
* Destructor
*
* @return void
*/
public function __construct()
public function __destruct()
{
$this->canEncrypt = \IPS\REDIS_ENCRYPT === TRUE and function_exists( 'openssl_encrypt' ) and in_array( 'aes-256-ctr', openssl_get_cipher_methods() );
if ( \IPS\DEBUG_LOG and \count( static::$writes ) > 50 )
{
$slice = implode( ', ', \array_slice( static::$writes, 0, 50 ) );
\IPS\Log::debug( "Large number of Redis writes: " . $slice, 'redis_writes' );
}
}
/**
* @brief Chosen reader for this session
*/
protected static $reader = NULL;
/**
* Connect to Redis
*
* @param string $identifier
* @param string|NULL $identifier Identifier
* @return \Redis
* @throws \RedisException If connection cannot be established
* @throws \BadMethodCallException If Redis PHP extension is not installed
*/
public function connection( $identifier=NULL )
{
@@ -116,9 +130,13 @@ class _Redis
}
else if ( $identifier === 'read' )
{
$randomReaderIndex = rand( 0, count( static::$config['read'] ) - 1 );
$useConfig = static::$config['read'][ $randomReaderIndex ];
$identifier = 'read' . $randomReaderIndex;
if ( static::$reader === NULL )
{
static::$reader = rand( 0, \count( static::$config['read'] ) - 1 );
}
$useConfig = static::$config['read'][ static::$reader ];
$identifier = 'read' . static::$reader;
}
else
{
@@ -173,7 +191,7 @@ class _Redis
static::$log[ sprintf( '%.4f', microtime(true) ) ] = array( 'redis', "Redis connected (" . $identifier . ' ' . $useConfig['server'] . ")" );
}
if ( count( static::$connections ) === 1 )
if ( \count( static::$connections ) === 1 )
{
register_shutdown_function( function( $object ){
try
@@ -199,6 +217,12 @@ class _Redis
}
catch( \RedisException $e )
{
/* Unset this connection */
if ( isset( static::$connections[ $identifier ] ) )
{
unset( static::$connections[ $identifier ] );
}
$this->resetConnection( $e );
}
}
@@ -214,19 +238,29 @@ class _Redis
/**
* Call methods
*
* @param string $method
* @param mixed $args
* @return mixed
* @param string $method Method
* @param mixed $args Arguments
* @return mixed
*/
public function __call( $method, $args )
{
if ( method_exists( 'Redis', $method ) )
{
$type = ( \stristr( $method, 'get' ) or \stristr( $method, 'RevRange' ) ) ? 'read' : 'write';
$return = call_user_func_array( array( $this->connection( $type ), $method ), $args );
$return = $this->connection( $type )->$method( ...$args );
if ( \IPS\REDIS_LOG )
if ( $type === 'write' and \count( $args ) )
{
static::$writes[] = $args[0];
}
if ( \IPS\REDIS_LOG and \count( $args ) )
{
if ( preg_match( '#^[a-f0-9]{32}_str__#', $args[0] ) )
{
$args[0] = preg_replace( '#^[a-f0-9]{32}_str__#', '[DATASTORE] ', $args[0] );
}
static::$log[ sprintf( '%.4f', microtime(true) ) ] = array( 'redis', "({$type}) {$method} " . $args[0], json_encode( $args ) );
}
@@ -238,9 +272,9 @@ class _Redis
* Add one or more members to a sorted set or update its score if it already exists
* Overloaded here so it can add a TTL to prevent permanent keys
*
* @param string $key
* @param float $score
* @param string $value
* @param string $key Key
* @param float $score Score
* @param string $value Value
* @param int|NULL $ttl TTL in seconds
* @return 1 if the element is added. 0 otherwise.
*/
@@ -262,8 +296,8 @@ class _Redis
* Fills in a whole hash. Non-string values are converted to string, using the standard (string) cast. NULL values are stored as empty strings.
* Overloaded here so it can add a TTL to prevent permanent keys
*
* @param string $key
* @param array $value
* @param string $key Key
* @param array $value Value
* @param int|NULL $ttl TTL in seconds
* @return boolean
*/
@@ -285,9 +319,9 @@ class _Redis
* Set the string value in argument as value of the key, with a time to live
* Overloaded here so it can be logged
*
* @param string $key
* @param string $key Key
* @param int|NULL $ttl TTL in seconds
* @param string $value
* @param string $value Value
* @return boolean
*/
public function setEx( $key, $ttl, $value )
@@ -306,7 +340,7 @@ class _Redis
* Sort the elements in a list, set or sorted set.
* Overloaded here so we can adjust the key
*
* @param string $key
* @param string $key Key
* @param array $options Options: array(key => value, ...) - optional
* @return array
*/
@@ -331,7 +365,7 @@ class _Redis
* Returns the whole hash, as an array of strings indexed by strings.
* Overloaded here so it can be logged
*
* @param string $key
* @param string $key Key
* @return array
*/
public function hGetAll( $key )
@@ -351,9 +385,9 @@ class _Redis
* Increments the score of a member from a sorted set by a given amount.
* Overloaded here so it can be logged and a ttl set
*
* @param string $key
* @param string $key Key
* @param int $inc Value to increment
* @param string $value
* @param string $value Value
* @param int|NULL $ttl TTL in seconds
* @return boolean
*/
@@ -373,7 +407,7 @@ class _Redis
/**
* Strip prefixes from keys as PHP redis will handle this
*
* @param string $key
* @param string $key Key
* @return string
*/
protected function key( $key )
@@ -388,32 +422,6 @@ class _Redis
return $key;
}
/**
* @brief Cached encryption key
*/
protected $encryptionKey = NULL;
/**
* Encryption key
*
* @return string
*/
protected function _encryptionKey()
{
if( $this->encryptionKey !== NULL )
{
return $this->encryptionKey;
}
$this->encryptionKey = \IPS\Settings::i()->sql_pass;
if ( function_exists( 'openssl_digest' ) and in_array( 'sha256', openssl_get_md_methods() ) )
{
$this->encryptionKey = openssl_digest( $this->encryptionKey, 'sha256', TRUE );
}
return $this->encryptionKey;
}
/**
* Encode
@@ -423,15 +431,7 @@ class _Redis
*/
public function encode( $value )
{
$value = json_encode( $value );
if ( $this->canEncrypt )
{
$iv = \IPS\Login::generateRandomString( 16 );
$value = $iv . openssl_encrypt( $value, 'aes-256-ctr', $this->_encryptionKey(), OPENSSL_RAW_DATA, $iv );
}
return $value;
return \IPS\Text\Encrypt::fromPlaintext( json_encode( $value ) )->tag();
}
/**
@@ -442,43 +442,13 @@ class _Redis
*/
public function decode( $value )
{
if ( $this->canEncrypt )
{
$value = @openssl_decrypt( \substr( $value, 16 ), 'aes-256-ctr', $this->_encryptionKey(), OPENSSL_RAW_DATA, \substr( $value, 0, 16 ) );
}
return json_decode( $value, TRUE );
}
/**
* Log a page hit
*
* @param \IPS\Http\Url|NULL $url URL to log (or null)
* @return void
*/
public function logPageHit( $url=NULL )
{
$url = $url ? $url : \IPS\Request::i()->url();
$key = 'counter-member';
if ( ! \IPS\Member::loggedIn()->member_id )
{
$key = \IPS\Session::i()->userAgent->spider ? 'counter-spider' : 'counter-guest';
}
$value = $this->connection('write')->hIncrBy( $this->key( $key ), ( \IPS\Session::i()->userAgent->spider ? '/' : ( $url->getFurlQuery() ?: '/' ) ), 1 );
$this->connection('write')->expire( $this->key( $key ), static::$ttl );
if ( \IPS\REDIS_LOG )
{
static::$log[ sprintf( '%.4f', microtime(true) ) ] = array( 'redis', "Page hit logged " . $key . " = " . ($url->getFurlQuery() ?: '/'), json_encode( $value ) );
}
return json_decode( \IPS\Text\Encrypt::fromTag( $value )->decrypt(), TRUE );
}
/**
* Reset connection
*
* @param \RedisException|NULL If this was called as a result of an exception, log that to the debug log
* @param \RedisException|NULL $e If this was called as a result of an exception, log that to the debug log
* @return void
*/
public function resetConnection( \RedisException $e = NULL )
@@ -497,11 +467,23 @@ class _Redis
}
/**
* Debug method to fetch all keys. Never use in production!
* Is Redis working?
*
* @param string $pattern
* @param boolean $keyNamesOnly
* @return array
* @return bool
*/
public function test()
{
return ( \count( static::$connections ) ) ? TRUE : FALSE;
}
/**
* Debug method to fetch all keys.
*
* @warning Never use this in production, as it can expose sensitive data!
* @param string $pattern Pattern (* to fetch all)
* @param boolean $keyNamesOnly Return names only
* @return array
* @throws \RedisException
*/
public function debugGetKeys( $pattern='*', $keyNamesOnly=FALSE )
{
@@ -526,7 +508,14 @@ class _Redis
switch( $type )
{
case \Redis::REDIS_STRING:
$return[ $key . ' (TTL: ' . $ttl . ')' ] = \IPS\Redis::i()->decode( $this->connection('write')->get( $key ) );
if ( mb_stristr( $key, '_pg__page_' ) )
{
$return[ $key . ' (TTL: ' . $ttl . ')' ] = @gzdecode( \IPS\Text\Encrypt::fromCipher( \IPS\Redis::i()->get( $key ) )->decrypt() );
}
else
{
$return[ $key . ' (TTL: ' . $ttl . ')' ] = \IPS\Redis::i()->decode( $this->connection('write')->get( $key ) );
}
break;
case \Redis::REDIS_ZSET:
$return[ $key . ' (TTL: ' . $ttl . ')' ] = $this->connection('write')->zRange( $key, 0, -1, TRUE );