From 14afadc178a651ba84484a0f14fb5086687bc542 Mon Sep 17 00:00:00 2001 From: tino Date: Fri, 12 May 2023 20:18:53 +0100 Subject: [PATCH 01/15] Added try/catch so that an incorrect connection doesn't kill ElkArte # Conflicts: # sources/ElkArte/Cache/CacheMethod/Redis.php --- sources/ElkArte/Cache/CacheMethod/Redis.php | 196 ++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 sources/ElkArte/Cache/CacheMethod/Redis.php diff --git a/sources/ElkArte/Cache/CacheMethod/Redis.php b/sources/ElkArte/Cache/CacheMethod/Redis.php new file mode 100644 index 0000000000..26dfa9b816 --- /dev/null +++ b/sources/ElkArte/Cache/CacheMethod/Redis.php @@ -0,0 +1,196 @@ +isAvailable()) + { + parent::__construct($options); + $this->addServers(); + } + } + + /** + * {@inheritdoc} + */ + public function isAvailable() + { + return class_exists('\Predis\Client'); + } + + /** + * Add Redis servers. + * + * Don't add servers if they already exist. Ideal for persistent connections. + * + * @return bool True if there are servers in the daemon, false if not. + */ + protected function addServers() + { + if(!empty($this->_options['servers'])) + { + foreach ($this->_options['servers'] as $server) + { + $server = explode(':', trim($server)); + $server[0] = !empty($server[0]) ? $server[0] : 'localhost'; + $server[1] = !empty($server[1]) ? $server[1] : 6379; + + $params = [ + 'scheme' => 'tcp', + 'host' => $server[0], + 'port' => $server[1], + ]; + + try { + $this->obj = new \Predis\Client($params); + $this->obj->connect(); + $this->server[] = "tcp://{$server[0]}:{$server[1]}"; + } + catch(\Predis\Connection\ConnectionException $e) { + // Clear the object, should we log an error here? + $this->obj = null; + } + } + } + } + + /** + * Get redis servers. + * + * @return array A list of servers in the daemon. + */ + protected function getServers() + { + return $this->server; + } + + /** + * {@inheritdoc} + */ + public function exists($key) + { + $this->get($key); + + return !$this->is_miss; + } + + /** + * {@inheritdoc} + */ + public function get($key, $ttl = 120) + { + if(!is_object($this->obj)) + { + return ''; + } + + $result = $this->obj->get($key); + $this->is_miss = $result == null; + + return $result; + } + + /** + * {@inheritdoc} + */ + public function put($key, $value, $ttl = 120) + { + if(!is_object($this->obj)) + { + return ''; + } + + if ($value === null) + { + $this->obj->del($key); + } + + $this->obj->set($key, $value); + } + + /** + * {@inheritdoc} + */ + public function clean($type = '') + { + if(!is_object($this->obj)) + { + return ''; + } + + // Clear it out, really invalidate whats there + $this->obj->flush(); + } + + /** + * {@inheritdoc} + */ + public function details() + { + if(!is_object($this->obj)) + { + return ''; + } + + $version = $this->obj->info()['Server']; + + return array( + 'title' => $this->title(), + 'version' => !empty($version['redis_version']) ? $version['redis_version'] : '0.0.0' + ); + } + + /** + * Adds the settings to the settings page. + * + * Used by integrate_modify_cache_settings added in the title method + * + * @param array $config_vars + */ + public function settings(&$config_vars) + { + global $txt; + + $var = array( + 'cache_redis', $txt['cache_redis'], 'file', 'text', 30, 'cache_redis', + 'force_div_id' => 'redis_cache_redis', + ); + + $serversmList = $this->getServers(); + $serversmList = empty($serversmList) ? array($txt['admin_search_results_none']) : $serversmList; + $var['postinput'] = $txt['cache_redis_servers'] . implode('
  • ', $serversmList) . '
  • '; + + $config_vars[] = $var; + } +} From 6815031f1a9fd477c7e7f8b357f2813e18f7de27 Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 09:47:19 -0500 Subject: [PATCH 02/15] ! cleanup sample files --- Settings.sample.php | 69 +++++++++++++++++++++++------------------ Settings_bak.sample.php | 69 +++++++++++++++++++++++------------------ 2 files changed, 78 insertions(+), 60 deletions(-) diff --git a/Settings.sample.php b/Settings.sample.php index 3bbb551ef8..3f10e46e93 100644 --- a/Settings.sample.php +++ b/Settings.sample.php @@ -5,21 +5,18 @@ * The maintenance "mode" * Set to 1 to enable Maintenance Mode, 2 to make the forum untouchable. (you'll have to make it 0 again manually!) * 0 is default and disables maintenance mode. - * @var int 0, 1, 2 * @global int $maintenance */ $maintenance = 0; /** * Title for the Maintenance Mode message. - * @var string * @global string $mtitle */ $mtitle = 'Maintenance Mode'; /** * Description of why the forum is in maintenance mode. - * @var string * @global string $mmessage */ $mmessage = 'Okay faithful users...we\'re attempting to restore an older backup of the database...news will be posted once we\'re back!'; @@ -27,31 +24,31 @@ ########## Forum Info ########## /** * The name of your forum. - * @var string + * @global string $mbname */ $mbname = 'My Community'; /** * The default language file set for the forum. - * @var string + * @global string $language */ $language = 'English'; /** * URL to your forum's folder. (without the trailing /!) - * @var string + * @global string $boardurl */ $boardurl = 'http://127.0.0.1/elkarte'; /** * Email address to send emails from. (like noreply@yourdomain.com.) - * @var string + * @global string $webmaster_email */ $webmaster_email = 'noreply@myserver.com'; /** * Name of the cookie to set for authentication. - * @var string + * @global string $cookiename */ $cookiename = 'ElkArteCookie11'; @@ -59,68 +56,68 @@ /** * The database type * Default options: mysql, sqlite, postgresql - * @var string + * @global string $db_type */ $db_type = 'mysql'; /** * The server to connect to (or a Unix socket) - * @var string + * @global string $db_server */ $db_server = 'localhost'; /** * The port for the database server - * @var string + * @global string $db_port */ $db_port = ''; /** * The database name - * @var string + * @global string $db_name */ $db_name = 'elkarte'; /** * Database username - * @var string + * @global string $db_user */ $db_user = 'root'; /** * Database password - * @var string + * @global string $db_passwd */ $db_passwd = ''; /** * Database user for when connecting with SSI - * @var string + * @global string $ssi_db_user */ $ssi_db_user = ''; /** * Database password for when connecting with SSI - * @var string + * @global string $ssi_db_passwd */ $ssi_db_passwd = ''; /** * A prefix to put in front of your table names. * This helps to prevent conflicts - * @var string + * @global string $db_prefix */ $db_prefix = 'elkarte_'; /** * Use a persistent database connection - * @var int|bool + * @global int|bool $db_persist */ $db_persist = 0; /** * - * @var int|bool + * @global int|bool $db_error_send */ $db_error_send = 0; @@ -129,51 +126,63 @@ * Select a cache system. You want to leave this up to the cache area of the admin panel for * proper detection of apc, eaccelerator, memcache, mmcache, output_cache or filesystem-based * (you can add more with a mod). - * @var string + * @global string $cache_accelerator */ $cache_accelerator = ''; /** * The level at which you would like to cache. Between 0 (off) through 3 (cache a lot). - * @var int + * @global int $cache_enable */ $cache_enable = 0; /** - * This is only used for memcache / memcached. Should be a string of 'server:port,server:port' - * @var array + * This is only used for memcache / memcached / redis. Should be a string of 'server:port,server:port' + * @global string $cache_servers */ -$cache_memcached = ''; +$cache_servers = ''; /** * This is only for the 'filebased' cache system. It is the path to the cache directory. * It is also recommended that you place this in /tmp/ if you are going to use this. - * @var string + * @global string $cachedir */ $cachedir = __DIR__ . '/cache'; +/** + * Cache accelerator userid / dbname, required by some engines + * @global string $cache_uid + */ +$cache_uid = ''; + +/** + * Cache accelerator password for connecting, required by somme engines + * @global string $cache_password + */ +$cache_password = ''; + ########## Directories/Files ########## # Note: These directories do not have to be changed unless you move things. /** * The absolute path to the forum's folder. (not just '.'!) - * @var string + * @global string $boarddir */ $boarddir = __DIR__; /** * Path to the sources directory. - * @var string + * @global string $sourcedir */ $sourcedir = __DIR__ . '/sources'; /** - * Path to the external resources directory. - * @var string + * Path to the external resources' directory. + * @global string $extdir */ $extdir = __DIR__ . '/sources/ext'; /** * Path to the languages directory. - * @var string + * @global string */ $languagedir = __DIR__ . '/sources/ElkArte/Languages'; diff --git a/Settings_bak.sample.php b/Settings_bak.sample.php index e88b9df3f5..b60d0f1e8b 100644 --- a/Settings_bak.sample.php +++ b/Settings_bak.sample.php @@ -5,21 +5,18 @@ * The maintenance "mode" * Set to 1 to enable Maintenance Mode, 2 to make the forum untouchable. (you'll have to make it 0 again manually!) * 0 is default and disables maintenance mode. - * @var int 0, 1, 2 * @global int $maintenance */ $maintenance = 0; /** * Title for the Maintenance Mode message. - * @var string - * @global int $mtitle + * @global string $mtitle */ $mtitle = 'Maintenance Mode'; /** * Description of why the forum is in maintenance mode. - * @var string * @global string $mmessage */ $mmessage = 'Okay faithful users...we\'re attempting to restore an older backup of the database...news will be posted once we\'re back!'; @@ -27,31 +24,31 @@ ########## Forum Info ########## /** * The name of your forum. - * @var string + * @global string $mbname */ $mbname = 'My Community'; /** * The default language file set for the forum. - * @var string + * @global string $language */ $language = 'English'; /** * URL to your forum's folder. (without the trailing /!) - * @var string + * @global string $boardurl */ $boardurl = 'http://127.0.0.1/elkarte'; /** * Email address to send emails from. (like noreply@yourdomain.com.) - * @var string + * @global string $webmaster_email */ $webmaster_email = 'noreply@myserver.com'; /** * Name of the cookie to set for authentication. - * @var string + * @global string $cookiename */ $cookiename = 'ElkArteCookie11'; @@ -59,68 +56,68 @@ /** * The database type * Default options: mysql, sqlite, postgresql - * @var string + * @global string $db_type */ $db_type = 'mysql'; /** * The server to connect to (or a Unix socket) - * @var string + * @global string $db_server */ $db_server = 'localhost'; /** * The port for the database server - * @var string + * @global string $db_port */ $db_port = ''; /** * The database name - * @var string + * @global string $db_name */ $db_name = 'elkarte'; /** * Database username - * @var string + * @global string $db_user */ $db_user = 'root'; /** * Database password - * @var string + * @global string $db_passwd */ $db_passwd = ''; /** * Database user for when connecting with SSI - * @var string + * @global string $ssi_db_user */ $ssi_db_user = ''; /** * Database password for when connecting with SSI - * @var string + * @global string $ssi_db_passwd */ $ssi_db_passwd = ''; /** * A prefix to put in front of your table names. * This helps to prevent conflicts - * @var string + * @global string $db_prefix */ $db_prefix = 'elkarte_'; /** * Use a persistent database connection - * @var int|bool + * @global int|bool $db_persist */ $db_persist = 0; /** * - * @var int|bool + * @global int|bool $db_error_send */ $db_error_send = 0; @@ -129,51 +126,63 @@ * Select a cache system. You want to leave this up to the cache area of the admin panel for * proper detection of apc, eaccelerator, memcache, mmcache, output_cache or filesystem-based * (you can add more with a mod). - * @var string + * @global string $cache_accelerator */ $cache_accelerator = ''; /** * The level at which you would like to cache. Between 0 (off) through 3 (cache a lot). - * @var int + * @global int $cache_enable */ $cache_enable = 0; /** - * This is only used for memcache / memcached. Should be a string of 'server:port,server:port' - * @var array + * This is only used for memcache / memcached / redis. Should be a string of 'server:port,server:port' + * @global string $cache_servers */ -$cache_memcached = ''; +$cache_servers = ''; /** * This is only for the 'filebased' cache system. It is the path to the cache directory. * It is also recommended that you place this in /tmp/ if you are going to use this. - * @var string + * @global string $cachedir */ $cachedir = __DIR__ . '/cache'; +/** + * Cache accelerator userid / dbname, required by some engines + * @global string $cache_uid + */ +$cache_uid = ''; + +/** + * Cache accelerator password for connecting, required by somme engines + * @global string $cache_password + */ +$cache_password = ''; + ########## Directories/Files ########## # Note: These directories do not have to be changed unless you move things. /** * The absolute path to the forum's folder. (not just '.'!) - * @var string + * @global string $boarddir */ $boarddir = __DIR__; /** * Path to the sources directory. - * @var string + * @global string $sourcedir */ $sourcedir = __DIR__ . '/sources'; /** * Path to the external resources directory. - * @var string + * @global string $extdir */ $extdir = __DIR__ . '/sources/ext'; /** * Path to the languages directory. - * @var string + * @global string $languagedir */ $languagedir = __DIR__ . '/sources/ElkArte/Languages'; From 9cc923e57e1f0c32798c21ae7bede531540602cd Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 09:52:27 -0500 Subject: [PATCH 03/15] ! add redis cache methods --- .../CacheMethod/{Redis.php => Predis.php} | 57 ++++++++++++++++--- 1 file changed, 48 insertions(+), 9 deletions(-) rename sources/ElkArte/Cache/CacheMethod/{Redis.php => Predis.php} (62%) diff --git a/sources/ElkArte/Cache/CacheMethod/Redis.php b/sources/ElkArte/Cache/CacheMethod/Predis.php similarity index 62% rename from sources/ElkArte/Cache/CacheMethod/Redis.php rename to sources/ElkArte/Cache/CacheMethod/Predis.php index 26dfa9b816..3a97cfb146 100644 --- a/sources/ElkArte/Cache/CacheMethod/Redis.php +++ b/sources/ElkArte/Cache/CacheMethod/Predis.php @@ -13,14 +13,14 @@ namespace ElkArte\Cache\CacheMethod; /** - * Redis + * Predis */ -class Redis extends AbstractCacheMethod +class Predis extends AbstractCacheMethod { /** {@inheritdoc} */ - protected $title = 'Redis'; + protected $title = 'Predis'; - /** @var \Redis Redis instance representing the connection to the Redis servers. */ + /** @var \Predis Predis instance representing the connection to the Redis servers. */ protected $obj; /** @var server */ @@ -95,6 +95,46 @@ protected function getServers() return $this->server; } + /** + * Retrieves statistics about the cache. + * + * @return array An associative array containing the cache statistics. + * The array has the following keys: + * - curr_items: The number of items currently stored in the cache. + * - get_hits: The number of successful cache hits. + * - get_misses: The number of cache misses. + * - curr_connections: The number of current open connections to the cache server. + * - version: The version of the cache server. + * - hit_rate: The cache hit rate as a decimal value with two decimal places. + * - miss_rate: The cache miss rate as a decimal value with two decimal places. + * + * If the statistics cannot be obtained, an empty array is returned. + */ + public function getStats() + { + $results = []; + + $cache = $this->obj->info(); + + if ($cache === false) + { + return $results; + } + + $elapsed = max($cache['Server']['uptime_in_seconds'], 1) / 60; + $cache['Stats']['tracking_total_keys'] = count($this->obj->keys('*')); + + $results['curr_items'] = comma_format($cache['Stats']['tracking_total_keys'] ?? 0, 0); + $results['get_hits'] = comma_format($cache['Stats']['keyspace_hits'] ?? 0, 0); + $results['get_misses'] = comma_format($cache['Stats']['keyspace_misses'] ?? 0, 0); + $results['curr_connections'] = $cache['Server']['connected_clients'] ?? 0; + $results['version'] = $cache['Server']['redis_version'] ?? '0.0.0'; + $results['hit_rate'] = sprintf("%.2f", $cache['keyspace_hits'] / $elapsed); + $results['miss_rate'] = sprintf("%.2f", $cache['keyspace_misses'] / $elapsed); + + return $results; + } + /** * {@inheritdoc} */ @@ -182,13 +222,12 @@ public function settings(&$config_vars) { global $txt; - $var = array( - 'cache_redis', $txt['cache_redis'], 'file', 'text', 30, 'cache_redis', - 'force_div_id' => 'redis_cache_redis', - ); + $var = [ + 'cache_servers_redis', $txt['cache_redis'], 'file', 'text', 30, 'cache_redis', 'force_div_id' => 'redis_cache_redis', + ]; $serversmList = $this->getServers(); - $serversmList = empty($serversmList) ? array($txt['admin_search_results_none']) : $serversmList; + $serversmList = empty($serversmList) ? [$txt['admin_search_results_none']] : $serversmList; $var['postinput'] = $txt['cache_redis_servers'] . implode('
  • ', $serversmList) . '
  • '; $config_vars[] = $var; From 766ec968b997b7dc98550e423433de68b6655106 Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 09:56:24 -0500 Subject: [PATCH 04/15] ! phpredis --- sources/ElkArte/Cache/CacheMethod/Redis.php | 347 ++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 sources/ElkArte/Cache/CacheMethod/Redis.php diff --git a/sources/ElkArte/Cache/CacheMethod/Redis.php b/sources/ElkArte/Cache/CacheMethod/Redis.php new file mode 100644 index 0000000000..e01cf6ec4b --- /dev/null +++ b/sources/ElkArte/Cache/CacheMethod/Redis.php @@ -0,0 +1,347 @@ +isAvailable()) + { + $this->obj = new \Redis(); + $this->addServers(); + $this->setOptions(); + $this->setSerializerValue(); + $this->isConnected(); + } + } + + /** + * {@inheritDoc} + */ + public function isAvailable() + { + return class_exists(\Redis::class); + } + + /** + * Check if the connection to Redis server is active. + * + * @return bool Returns true if the connection is active, false otherwise. + */ + public function isConnected() + { + try + { + $this->isConnected = $this->obj->ping(); + } + catch (\RedisException $e) + { + $this->isConnected = false; + } + + return $this->isConnected; + } + + /** + * If this should be done as a persistent connection + * + * @return string|null + */ + private function _is_persist() + { + global $db_persist; + + return empty($db_persist) ? null : $this->prefix . '_redis'; + } + + /** + * {@inheritDoc} + */ + protected function setOptions() + { + try + { + if (!empty($this->_options['cache_password'])) + { + $this->obj->auth($this->_options['cache_password']); + } + + if (!empty($this->_options['cache_uid'])) + { + $this->obj->select($this->_options['cache_uid']); + } + } + catch (\RedisException $e) + { + $this->isConnected = false; + } + } + + /** + * Returns the redis serializer value based on certain conditions. + */ + private function setSerializerValue() + { + $serializer = $this->obj::SERIALIZER_PHP; + if (defined('Redis::SERIALIZER_IGBINARY') && extension_loaded('igbinary')) + { + $serializer = $this->obj::SERIALIZER_IGBINARY; + } + + try + { + $this->obj->setOption($this->obj::OPT_SERIALIZER, $serializer); + } + catch (\RedisException $e) + { + $this->isConnected = false; + } + } + + /** + * Add redis server. Currently, does not support RedisArray / RedisCluster + * + * @return bool True if there are servers in the daemon, false if not. + */ + protected function addServers() + { + $retVal = false; + + $server = reset($this->_options['servers']); + if ($server !== false) + { + $server = explode(':', trim($server)); + $host = empty($server[0]) ? 'localhost' : $server[0]; + $port = empty($server[1]) ? 6379 : (int) $server[1]; + + set_error_handler(static function () { /* ignore php_network_getaddresses errors */ }); + try + { + if ($this->_is_persist()) + { + $retVal = $this->obj->pconnect($host, $port, 0.0, $this->_is_persist()); + } + else + { + $retVal = $this->obj->connect($host, $port, 0.0); + } + } + catch (\RedisException $e) + { + $retVal = false; + } + finally { + restore_error_handler(); + } + } + + return $retVal; + } + + /** + * Get redis servers. + * + * @return string A server name if we are attached. + */ + protected function getServers() + { + $server = ''; + + if ($this->isConnected()) + { + $server = reset($this->_options['servers']); + } + + return $server; + } + + /** + * Retrieves statistics about the cache. + * + * @return array An associative array containing the cache statistics. + * The array has the following keys: + * - curr_items: The number of items currently stored in the cache. + * - get_hits: The number of successful cache hits. + * - get_misses: The number of cache misses. + * - curr_connections: The number of current open connections to the cache server. + * - version: The version of the cache server. + * - hit_rate: The cache hit rate as a decimal value with two decimal places. + * - miss_rate: The cache miss rate as a decimal value with two decimal places. + * + * If the statistics cannot be obtained, an empty array is returned. + */ + public function getStats() + { + $results = []; + + try + { + $cache = $this->obj->info(); + } + catch (\RedisException $e) + { + $cache = false; + } + + if ($cache === false) + { + return $results; + } + + $elapsed = max($cache['uptime_in_seconds'], 1) / 60; + $cache['tracking_total_keys'] = count($this->obj->keys('*')); + + $results['curr_items'] = comma_format($cache['tracking_total_keys'] ?? 0, 0); + $results['get_hits'] = comma_format($cache['keyspace_hits'] ?? 0, 0); + $results['get_misses'] = comma_format($cache['keyspace_misses'] ?? 0, 0); + $results['curr_connections'] = $cache['connected_clients'] ?? 0; + $results['version'] = $cache['redis_version'] ?? '0.0.0'; + $results['hit_rate'] = sprintf("%.2f", $cache['keyspace_hits'] / $elapsed); + $results['miss_rate'] = sprintf("%.2f", $cache['keyspace_misses'] / $elapsed); + + return $results; + } + + /** + * {@inheritDoc} + */ + public function exists($key) + { + return $this->obj->exists($key); + } + + /** + * {@inheritDoc} + */ + public function get($key, $ttl = 120) + { + if (!$this->isConnected) + { + return false; + } + + try + { + $result = $this->obj->get($key); + } + catch (\RedisException $e) + { + $result = null; + } + + $this->is_miss = $result === null || $result === false; + + return $result; + } + + /** + * {@inheritDoc} + */ + public function put($key, $value, $ttl = 120) + { + if (!$this->isConnected) + { + return false; + } + + try + { + if ($value === null) + { + $this->obj->del($key); + } + + if ($ttl > 0) + { + return $this->obj->setex($key, $ttl, $value); + } + + return $this->obj->set($key, $value); + } + catch (\RedisException $e) + { + return false; + } + } + + /** + * {@inheritDoc} + */ + public function clean($type = '') + { + // Clear it out + $this->obj->flushDB(); + } + + /** + * {@inheritDoc} + */ + public function details() + { + return [ + 'title' => $this->title(), + 'version' => phpversion('redis') + ]; + } + + /** + * Adds the settings to the settings page. + * + * Used by integrate_modify_cache_settings added in the title method + * + * @param array $config_vars + */ + public function settings(&$config_vars) + { + global $txt, $cache_servers, $cache_servers_redis; + + $var = [ + 'cache_servers_redis', $txt['cache_redis'], 'file', 'text', 30, 'cache_redis', 'force_div_id' => 'redis_cache_redis', + ]; + + // Use generic global cache_servers value to load the initial form value + if (HttpReq::instance()->getQuery('save') === null) + { + $cache_servers_redis = $cache_servers; + } + + $serversList = $this->getServers(); + $serversList = empty($serversList) ? $txt['admin_search_results_none'] : $serversList; + $var['postinput'] = $txt['cache_redis_servers'] . $serversList . ''; + + $config_vars[] = $var; + $config_vars[] = ['cache_uid', $txt['cache_uid'], 'file', 'text', $txt['cache_uid'], 'cache_uid', 'force_div_id' => 'redis_cache_uid']; + $config_vars[] = ['cache_password', $txt['cache_password'], 'file', 'password', $txt['cache_password'], 'cache_password', 'force_div_id' => 'redis_cache_password', 'skip_verify_pass' => true]; + } +} From 76c05b2ef8e5d6d5cce0b3f22a8472634545be74 Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 10:39:07 -0500 Subject: [PATCH 05/15] ! add password and uid (back) for cache methods that may use them (redis) ! provide a way to fetch the cache object method in use --- sources/ElkArte/Cache/Cache.php | 57 ++++++++++++------- .../Cache/CacheMethod/AbstractCacheMethod.php | 2 +- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/sources/ElkArte/Cache/Cache.php b/sources/ElkArte/Cache/Cache.php index 942198674b..fe2078f48e 100644 --- a/sources/ElkArte/Cache/Cache.php +++ b/sources/ElkArte/Cache/Cache.php @@ -16,6 +16,7 @@ namespace ElkArte\Cache; +use ElkArte\Cache\CacheMethod\AbstractCacheMethod; use ElkArte\Debug; use ElkArte\Helper\FileFunctions; use ElkArte\Helper\Util; @@ -46,7 +47,7 @@ class Cache /** @var string[] Cached keys */ protected $_cached_keys = []; - /** @var object|bool The caching object */ + /** @var AbstractCacheMethod|null The caching engine object */ protected $_cache_obj; /** @@ -65,7 +66,7 @@ public function __construct($level, $accelerator, $options) { $accelerator = 'filebased'; } - $this->_accelerator = $accelerator; + $this->_accelerator = ucfirst($accelerator); $this->setLevel($level); if ($level > 0) @@ -100,7 +101,7 @@ public function enable($enable) */ protected function _init() { - $cache_class = '\\ElkArte\\Cache\\CacheMethod\\' . ucfirst($this->_accelerator); + $cache_class = '\\ElkArte\\Cache\\CacheMethod\\' . $this->_accelerator; if (class_exists($cache_class)) { @@ -109,8 +110,7 @@ protected function _init() } else { - $this->_cache_obj = false; - + $this->_cache_obj = null; $this->enabled = false; } @@ -142,23 +142,41 @@ public function isEnabled() return $this->enabled; } + /** + * Return the current cache_obj + * + * @return AbstractCacheMethod|null + */ + public function getCacheEngine() + { + return $this->_cache_obj; + } + + /** + * Return the cache accelerator in use + * + * @return string + */ + public function getAccelerator() + { + return $this->_accelerator; + } + /** * Find and return the instance of the Cache class if it exists, - * or create it if it doesn't exist + * otherwise start a new instance */ public static function instance() { if (self::$_instance === null) { - global $cache_accelerator, $cache_enable, $cache_memcached; + global $cache_accelerator, $cache_enable, $cache_uid, $cache_password, $cache_servers; - $options = []; - if (strpos($cache_accelerator ?? '', 'memcache') === 0) - { - $options = [ - 'servers' => explode(',', $cache_memcached), - ]; - } + $options = [ + 'servers' => empty($cache_servers) ? [] : explode(',', $cache_servers), + 'cache_uid' => empty($cache_uid) ? '' : $cache_uid, + 'cache_password' => empty($cache_password) ? '' : $cache_password, + ]; self::$_instance = new Cache($cache_enable, $cache_accelerator, $options); } @@ -243,12 +261,11 @@ protected function _key($key) * - It may "miss" so shouldn't be depended on * - Uses the cache engine chosen in the ACP and saved in settings.php * - It supports: - * - memcache: http://www.php.net/memcache - * - memcached: http://www.php.net/memcached - * - APC: http://www.php.net/apc - * - APCu: http://us3.php.net/manual/en/book.apcu.php - * - Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm - * - Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm + * - Memcache: https://www.php.net/memcache + * - MemcacheD: https://www.php.net/memcached + * - APCu: https://us3.php.net/manual/en/book.apcu.php + * - Zend: https://help.zend.com/zend/current/content/data_cache_component.htm + * - Redis: https://redis.io/learn/develop/php * * @param string $key * @param string|int|array|null $value diff --git a/sources/ElkArte/Cache/CacheMethod/AbstractCacheMethod.php b/sources/ElkArte/Cache/CacheMethod/AbstractCacheMethod.php index 17693c3dd8..ae46a546c5 100644 --- a/sources/ElkArte/Cache/CacheMethod/AbstractCacheMethod.php +++ b/sources/ElkArte/Cache/CacheMethod/AbstractCacheMethod.php @@ -33,7 +33,7 @@ abstract class AbstractCacheMethod implements CacheMethodInterface /** @var string This is prefixed to all cache entries so that different applications won't interfere with each other. */ protected $prefix = 'elkarte'; - /** @var \ElkArte\Helper\FileFunctions instance of file functions for use in cache methods */ + /** @var FileFunctions instance of file functions for use in cache methods */ protected $fileFunc; /** From 4cf0be8e585612f0d7da13b61e6730934e5318ef Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 10:40:29 -0500 Subject: [PATCH 06/15] ! change cache stats to per/min vs per/sec --- sources/ElkArte/Cache/CacheMethod/Apc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/ElkArte/Cache/CacheMethod/Apc.php b/sources/ElkArte/Cache/CacheMethod/Apc.php index 5a835654c0..2f9f1bfe31 100644 --- a/sources/ElkArte/Cache/CacheMethod/Apc.php +++ b/sources/ElkArte/Cache/CacheMethod/Apc.php @@ -59,7 +59,7 @@ public function getStats() $results['version'] = phpversion('apcu'); // Seems start_time is really up_time, at least going by its value ? - $elapsed = max($cache['start_time'], 1); + $elapsed = max($cache['start_time'], 1)/ 60; $results['hit_rate'] = sprintf("%.2f", $cache['num_hits'] / $elapsed); $results['miss_rate'] = sprintf("%.2f", $cache['num_misses'] / $elapsed); From 5f1b82f97c1543f189ac5236d5436e2c33cee22d Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 10:41:32 -0500 Subject: [PATCH 07/15] ! account for opcache in the filebased cache --- .../ElkArte/Cache/CacheMethod/Filebased.php | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sources/ElkArte/Cache/CacheMethod/Filebased.php b/sources/ElkArte/Cache/CacheMethod/Filebased.php index bc45a39fe7..e881e4612c 100644 --- a/sources/ElkArte/Cache/CacheMethod/Filebased.php +++ b/sources/ElkArte/Cache/CacheMethod/Filebased.php @@ -75,11 +75,13 @@ public function put($key, $value, $ttl = 120) // Write out the cache file, check that the cache write was successful; all the data must be written // If it fails due to low diskspace, or other, remove the cache file - if (@file_put_contents(CACHEDIR . '/' . $fName, $cache_data, LOCK_EX) !== strlen($cache_data)) + if (file_put_contents(CACHEDIR . '/' . $fName, $cache_data, LOCK_EX) !== strlen($cache_data)) { $this->fileFunc->delete(CACHEDIR . '/' . $fName); } } + + $this->opcacheReset($fName); } /** @@ -115,6 +117,30 @@ public function get($key, $ttl = 120) return $return; } + /** + * Resets the opcache for a specific file. + * + * If opcache is switched on, and we can use it, immediately invalidates that opcode cache + * after a file is written so that future includes are not using a stale opcode cached file. + * + * @param string $fName The name of the cached file. + */ + private function opcacheReset($fName) + { + if (extension_loaded('Zend OPcache') && ini_get('opcache.enable')) + { + $opcache = ini_get('opcache.restrict_api'); + if ($opcache === false || $opcache === '') + { + opcache_invalidate(CACHEDIR . '/' . $fName, true); + } + elseif (stripos(BOARDDIR, $opcache) !== 0) + { + opcache_invalidate(CACHEDIR . '/' . $fName, true); + } + } + } + /** * {@inheritDoc} */ From da531c54789a7581b84ff0bf6d4da7b5a0079522 Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 10:45:49 -0500 Subject: [PATCH 08/15] ! we only use a single cache servers variable, so swap form value to this on save --- .../ElkArte/AdminController/ManageServer.php | 24 ++++++++++++------- .../ElkArte/Cache/CacheMethod/Memcache.php | 20 +++++++--------- .../ElkArte/Cache/CacheMethod/Memcached.php | 17 +++++++++---- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/sources/ElkArte/AdminController/ManageServer.php b/sources/ElkArte/AdminController/ManageServer.php index d455b32a70..50d5f2b6e9 100644 --- a/sources/ElkArte/AdminController/ManageServer.php +++ b/sources/ElkArte/AdminController/ManageServer.php @@ -20,6 +20,7 @@ use ElkArte\AbstractController; use ElkArte\Action; +use ElkArte\Cache\CacheMethod\AbstractCacheMethod; use ElkArte\Exceptions\Exception; use ElkArte\SettingsForm\SettingsForm; use ElkArte\Languages\Txt; @@ -404,11 +405,18 @@ public function action_cacheSettings_display() { call_integration_hook('integrate_save_cache_settings'); + // Move accelerator servers to the cache_servers value + $var = 'cache_servers_' . $this->_req->post->cache_accelerator; + if (isset($this->_req->post->$var)) + { + $this->_req->post->cache_servers = $this->_req->post->$var; + } + $settingsForm->setConfigValues((array) $this->_req->post); $settingsForm->save(); // we need to save the $cache_enable to $modSettings as well - updateSettings(array('cache_enable' => (int) $this->_req->post->cache_enable)); + updateSettings(['cache_enable' => (int) $this->_req->post->cache_enable]); // exit so we reload our new settings on the page redirectexit('action=admin;area=serversettings;sa=cache;' . $context['session_var'] . '=' . $context['session_id']); @@ -423,8 +431,6 @@ public function action_cacheSettings_display() let cache_type = document.getElementById(\'cache_accelerator\'); cache_type.addEventListener("change", showCache); - - createEventListener(cache_type); cache_type.addEventListener("change", toggleCache); let event = new Event("change"); @@ -451,12 +457,13 @@ private function _cacheSettings() { global $txt, $cache_accelerator, $context; - // Detect all available optimizers + // Detect all available cache engines require_once(SUBSDIR . '/Cache.subs.php'); $detected = loadCacheEngines(false); $detected_names = []; $detected_supported = []; + /** @var $value AbstractCacheMethod */ foreach ($detected as $key => $value) { $detected_names[] = $value->title(); @@ -484,12 +491,13 @@ private function _cacheSettings() ['cache_accelerator', $txt['cache_accelerator'], 'file', 'select', $detected_supported], ]; - // If the cache engine has specific settings, add them in - foreach ($detected as $value) + // If the cache engine has any specific settings, add them in + foreach ($detected as $engine) { - if ($value->isAvailable()) + /** @var $engine AbstractCacheMethod */ + if ($engine->isAvailable()) { - $value->settings($config_vars); + $engine->settings($config_vars); } } diff --git a/sources/ElkArte/Cache/CacheMethod/Memcache.php b/sources/ElkArte/Cache/CacheMethod/Memcache.php index fa5861f21e..8571f024cd 100644 --- a/sources/ElkArte/Cache/CacheMethod/Memcache.php +++ b/sources/ElkArte/Cache/CacheMethod/Memcache.php @@ -24,7 +24,7 @@ class Memcache extends AbstractCacheMethod /** @var \Memcache Creates a Memcache instance representing the connection to the memcache servers. */ protected $obj; - /** @var bool If the daemon has valid servers in it pool */ + /** @var bool If the daemon has valid servers in its pool */ protected $_is_running; /** @@ -128,8 +128,8 @@ protected function setOptions($server, $port) * - 'get_misses': The number of cache lookups that did not find a matching item. * - 'curr_connections': The number of currently open connections to the cache server. * - 'version': The version of the cache server. - * - 'hit_rate': The rate of successful cache lookups per second. - * - 'miss_rate': The rate of cache lookups that did not find a matching item per second. + * - 'hit_rate': The rate of successful cache lookups per minute. + * - 'miss_rate': The rate of cache lookups that did not find a matching item per minute. * * If the statistics cannot be obtained, an empty array is returned. */ @@ -146,7 +146,7 @@ public function getStats() // Only user the first server reset($cache); $server = current($cache); - $elapsed = max($server['uptime'], 1); + $elapsed = max($server['uptime'], 1) / 60; $results['curr_items'] = comma_format($server['curr_items'] ?? 0, 0); $results['get_hits'] = comma_format($server['get_hits'] ?? 0, 0); @@ -242,16 +242,12 @@ public function settings(&$config_vars) global $txt; $var = [ - 'cache_memcached', $txt['cache_memcache'], 'file', 'text', 30, 'cache_memcached', - 'force_div_id' => 'memcache_cache_memcache', + 'cache_servers', $txt['cache_memcache'], 'file', 'text', 30, 'cache_memcached', 'force_div_id' => 'memcache_cache_memcache', ]; - $serversmList = $this->getServers(); - - if (!empty($serversmList)) - { - $var['postinput'] = $txt['cache_memcached_servers'] . implode('
  • ', $serversmList) . '
  • '; - } + $serversList = $this->getServers(); + $serversList = empty($serversList) ? [$txt['admin_search_results_none']] : $serversList; + $var['postinput'] = $txt['cache_memcached_servers'] . implode('
  • ', $serversList) . '
  • '; $config_vars[] = $var; } diff --git a/sources/ElkArte/Cache/CacheMethod/Memcached.php b/sources/ElkArte/Cache/CacheMethod/Memcached.php index d373b4413e..b75e6eab2f 100644 --- a/sources/ElkArte/Cache/CacheMethod/Memcached.php +++ b/sources/ElkArte/Cache/CacheMethod/Memcached.php @@ -13,6 +13,8 @@ namespace ElkArte\Cache\CacheMethod; +use ElkArte\Helper\HttpReq; + /** * Memcached */ @@ -165,7 +167,7 @@ public function getStats() // Only user the first server reset($cache); $server = current($cache); - $elapsed = max($server['uptime'], 1); + $elapsed = max($server['uptime'], 1) / 60; $results['curr_items'] = comma_format($server['curr_items'] ?? 0, 0); $results['get_hits'] = comma_format($server['get_hits'] ?? 0, 0); @@ -217,7 +219,7 @@ public function put($key, $value, $ttl = 120) */ public function clean($type = '') { - // Clear it out, really invalidate whats there + // Clear it out, really invalidate what is there $this->obj->flush(); } @@ -243,13 +245,18 @@ public function details() */ public function settings(&$config_vars) { - global $txt; + global $txt, $cache_servers, $cache_servers_memcached; $var = [ - 'cache_memcached', $txt['cache_memcached'], 'file', 'text', 30, 'cache_memcached', - 'force_div_id' => 'memcached_cache_memcached', + 'cache_servers_memcached', $txt['cache_memcached'], 'file', 'text', 30, 'cache_memcached', 'force_div_id' => 'memcached_cache_memcached', ]; + // Use generic global cache_servers value to load the initial form value + if (HttpReq::instance()->getQuery('save') === null) + { + $cache_servers_memcached = $cache_servers; + } + $serversList = $this->getServers(); $serversList = empty($serversList) ? [$txt['admin_search_results_none']] : $serversList; $var['postinput'] = $txt['cache_memcached_servers'] . implode('
  • ', $serversList) . '
  • '; From e9d072a1e2268aa730044fd3016f231fa21a8674 Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 10:46:47 -0500 Subject: [PATCH 09/15] ! allow for a simple password field (w/o a confirm box) --- .../SettingsForm/SettingsFormAdapter/File.php | 170 +++++++++++++----- themes/default/Admin.template.php | 28 ++- 2 files changed, 148 insertions(+), 50 deletions(-) diff --git a/sources/ElkArte/SettingsForm/SettingsFormAdapter/File.php b/sources/ElkArte/SettingsForm/SettingsFormAdapter/File.php index 353d851901..07ef2541ab 100644 --- a/sources/ElkArte/SettingsForm/SettingsFormAdapter/File.php +++ b/sources/ElkArte/SettingsForm/SettingsFormAdapter/File.php @@ -50,8 +50,7 @@ class File extends Db * 1 label - the text to show on the settings page * 2 saveto - file or db, where to save the variable name - value pair * 3 type - type of data to display int, float, text, check, select, password - * 4 size - false or field size, if type is select, this needs to be an array of - * select options + * 4 size - false or field size, if type is select, this needs to be an array of select options * 5 help - '' or helptxt variable name * ) * - The following named keys are also permitted @@ -59,6 +58,8 @@ class File extends Db * 'postinput' => * 'preinput' => * 'subtext' => + * 'force_div_id' => + * 'skip_verify_pass' => */ public function prepare() { @@ -184,6 +185,7 @@ private function _cleanSettings() $config_passwords = [ 'db_passwd', 'ssi_db_passwd', + 'cache_password', ]; // All the strings to write. @@ -201,8 +203,9 @@ private function _cleanSettings() 'db_prefix', 'ssi_db_user', 'cache_accelerator', - 'cache_memcached', + 'cache_servers', 'url_format', + 'cache_uid', ]; // These need HTML encoded. Be sure they all exist in $config_strs! @@ -224,14 +227,77 @@ private function _cleanSettings() 'maintenance', ]; - // Now sort everything into a big array, and figure out arrays and etc. + // Now sort everything into a big array, and figure out arrays etc. + $this->cleanPasswords($config_passwords); + + // Escape and update Setting strings + $this->cleanStrings($config_strs, $safe_strings); + + // Ints are saved as integers + $this->cleanInts($config_ints); + + // Convert checkbox selections to 0 / 1 + $this->cleanBools($config_bools); + } + + /** + * Fix the cookie name by removing invalid characters + */ + private function _fixCookieName() + { + // Fix the darn stupid cookiename! (more may not be allowed, but these for sure!) + if (isset($this->configValues['cookiename'])) + { + $this->configValues['cookiename'] = preg_replace('~[,;\s\.$]+~u', '', $this->configValues['cookiename']); + } + } + + /** + * Fix the forum's URL if necessary so that it is a valid root url + */ + private function _fixBoardUrl() + { + if (isset($this->configValues['boardurl'])) + { + if (substr($this->configValues['boardurl'], -10) === '/index.php') + { + $this->configValues['boardurl'] = substr($this->configValues['boardurl'], 0, -10); + } + elseif (substr($this->configValues['boardurl'], -1) === '/') + { + $this->configValues['boardurl'] = substr($this->configValues['boardurl'], 0, -1); + } + + $this->configValues['boardurl'] = addProtocol($this->configValues['boardurl'], ['http://', 'https://', 'file://']); + } + } + + /** + * Clean passwords and add them to the new settings array + * + * @param array $config_passwords The array of config passwords to clean + */ + public function cleanPasswords(array $config_passwords) + { foreach ($config_passwords as $configVar) { + // Handle skip_verify_pass. Only password[0] will exist from the form + $key = $this->_array_key_exists__recursive($this->configVars, $configVar, 0); + if ($key !== false + && !empty($this->configVars[$key]['skip_verify_pass']) + && $this->configValues[$configVar][0] !== '*#fakepass#*') + { + $this->new_settings[$configVar] = "'" . addcslashes($this->configValues[$configVar][0], '\'\\') . "'"; + continue; + } + + // Validate the _confirm password box exists if (!isset($this->configValues[$configVar][1])) { continue; } + // And that it has the same password if ($this->configValues[$configVar][0] !== $this->configValues[$configVar][1]) { continue; @@ -239,13 +305,22 @@ private function _cleanSettings() $this->new_settings[$configVar] = "'" . addcslashes($this->configValues[$configVar][0], '\'\\') . "'"; } + } - // Escape and update Setting strings + /** + * Clean strings in the configuration values by escaping characters and applying safe transformations + * and add them to the new settings array + * + * @param array $config_strs The configuration strings to clean + * @param array $safe_strings The safe strings that should receive additional transformations + */ + public function cleanStrings(array $config_strs, array $safe_strings) + { foreach ($config_strs as $configVar) { if (isset($this->configValues[$configVar])) { - if (in_array($configVar, $safe_strings)) + if (in_array($configVar, $safe_strings, true)) { $this->new_settings[$configVar] = "'" . addcslashes(Util::htmlspecialchars(strtr($this->configValues[$configVar], ["\n" => '
    ', "\r" => '']), ENT_QUOTES), '\'\\') . "'"; } @@ -255,8 +330,17 @@ private function _cleanSettings() } } } + } - // Ints are saved as integers + /** + * Clean/cast integer values in the configuration array and add them to the new settings array + * + * @param array $config_ints The array of configuration variables to clean + * + * @return void + */ + public function cleanInts(array $config_ints): void + { foreach ($config_ints as $configVar) { if (isset($this->configValues[$configVar])) @@ -264,47 +348,23 @@ private function _cleanSettings() $this->new_settings[$configVar] = (int) $this->configValues[$configVar]; } } - - // Convert checkbox selections to 0 / 1 - foreach ($config_bools as $key) - { - // Check boxes need to be part of this settings form - if ($this->_array_value_exists__recursive($key, $this->getConfigVars())) - { - $this->new_settings[$key] = (int) !empty($this->configValues[$key]); - } - } } /** - * Fix the cookie name by removing invalid characters - */ - private function _fixCookieName() - { - // Fix the darn stupid cookiename! (more may not be allowed, but these for sure!) - if (isset($this->configValues['cookiename'])) - { - $this->configValues['cookiename'] = preg_replace('~[,;\s\.$]+~u', '', $this->configValues['cookiename']); - } - } - - /** - * Fix the forum's URL if necessary so that it is a valid root url + * Clean boolean values in the provided config array to be 0 or 1 and add them to the new settings array + * + * @param array $config_bools The array of boolean keys to clean. + * @return void */ - private function _fixBoardUrl() + public function cleanBools(array $config_bools): void { - if (isset($this->configValues['boardurl'])) + foreach ($config_bools as $key) { - if (substr($this->configValues['boardurl'], -10) === '/index.php') - { - $this->configValues['boardurl'] = substr($this->configValues['boardurl'], 0, -10); - } - elseif (substr($this->configValues['boardurl'], -1) === '/') + // Check boxes need to be part of this settings form + if ($this->_array_value_exists__recursive($key, $this->getConfigVars())) { - $this->configValues['boardurl'] = substr($this->configValues['boardurl'], 0, -1); + $this->new_settings[$key] = (int) !empty($this->configValues[$key]); } - - $this->configValues['boardurl'] = addProtocol($this->configValues['boardurl'], ['http://', 'https://', 'file://']); } } @@ -329,6 +389,34 @@ private function _array_value_exists__recursive($needle, $haystack) return false; } + /** + * Recursively search for a value in a multidimensional array and return the key + * + * @param array $haystack The array to search in + * @param mixed $needle The value to search for + * @param mixed $index The index to compare against (optional) + * @return string|int|false The key of the found value, false if array search completed without finding value + */ + private function _array_key_exists__recursive($haystack, $needle, $index = null) + { + $aIt = new \RecursiveArrayIterator($haystack); + $it = new \RecursiveIteratorIterator($aIt); + + while ($it->valid()) + { + if (((isset($index) && $it->key() === $index) || (!isset($index))) + && $it->current() === $needle) + { + return $aIt->key(); + } + + $it->next(); + } + + // If the loop completed without finding the value, return false + return false; + } + /** * Updates / Validates the Settings array for later output. * @@ -350,7 +438,7 @@ private function _prepareSettings() $this->settingsArray[$k] = strtr($dummy, ["\r" => '']) . "\n"; } - // go line by line and see whats changing + // go line by line and see what's changing for ($i = 0, $n = count($this->settingsArray); $i < $n; $i++) { // Don't trim or bother with it if it's not a variable. diff --git a/themes/default/Admin.template.php b/themes/default/Admin.template.php index c46554431d..b642b0e213 100644 --- a/themes/default/Admin.template.php +++ b/themes/default/Admin.template.php @@ -569,7 +569,7 @@ function template_show_settings() else { echo ' - '; + '; // Some quick helpers... $preinput = empty($config_var['preinput']) ? '' : $config_var['preinput']; @@ -592,11 +592,11 @@ function template_show_settings() } echo ' - - ', $subtext, ' + + ', $subtext, ' ', - $preinput; + $preinput; // Show a checkbox. if ($config_var['type'] === 'check') @@ -604,17 +604,25 @@ function template_show_settings() echo ' '; } - // Escape (via htmlspecialchars.) the text box. + // Password and confirm password fields elseif ($config_var['type'] === 'password') { echo ' - + '; + + if (empty($config_var['skip_verify_pass'])) + { + echo ' -
    - +
    + + + +
    '; + } } // Show a selection box. elseif ($config_var['type'] === 'select') @@ -730,7 +738,9 @@ function template_show_settings() ' : ''; echo isset($config_var['postinput']) && $config_var['postinput'] !== '' ? ' - ' . $config_var['postinput'] : '', ' + ' . $config_var['postinput'] : ''; + + echo '
    '; } } From 3680f5ca79936c02e12b098bbd21135a860758d2 Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 10:48:15 -0500 Subject: [PATCH 10/15] ! grab the new global for cache pw/uid --- bootstrap.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap.php b/bootstrap.php index c035b0d920..1c2f37bde2 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -143,9 +143,9 @@ private function loadSettingsFile() // All those wonderful things found in settings global $maintenance, $mtitle, $msubject, $mmessage, $mbname, $language, $boardurl, $webmaster_email; global $cookiename, $db_type, $db_server, $db_port, $db_name, $db_user, $db_passwd; - global $ssi_db_user, $ssi_db_passwd, $db_prefix, $db_persist, $db_error_send, $cache_accelerator; - global $cache_uid, $cache_password, $cache_enable, $cache_memcached, $db_show_debug, $url_format; - global $cachedir, $boarddir, $sourcedir, $extdir, $languagedir; + global $ssi_db_user, $ssi_db_passwd, $db_prefix, $db_persist, $db_error_send; + global $cache_uid, $cache_password, $cache_enable, $cache_servers, $cache_accelerator; + global $db_show_debug, $url_format, $cachedir, $boarddir, $sourcedir, $extdir, $languagedir; // Where the Settings.php file is located $settings_loc = __DIR__ . '/Settings.php'; From d303bff44b0a7165a552bf526d64256f7a4da00a Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 10:48:53 -0500 Subject: [PATCH 11/15] ! new / updated language strings --- sources/ElkArte/Languages/Help/English.php | 6 +++++- sources/ElkArte/Languages/Maintenance/English.php | 4 ++-- sources/ElkArte/Languages/ManageSettings/English.php | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/sources/ElkArte/Languages/Help/English.php b/sources/ElkArte/Languages/Help/English.php index 0714ec1e7e..7b95f94636 100644 --- a/sources/ElkArte/Languages/Help/English.php +++ b/sources/ElkArte/Languages/Help/English.php @@ -268,8 +268,12 @@ $helptxt['databaseSession_loose'] = 'Turning this on will decrease the bandwidth your forum uses, and make it so clicking back will not reload the page - the downside is that the (new) icons won\'t update, among other things. (unless you click to that page instead of going back to it.)'; $helptxt['databaseSession_lifetime'] = 'This is the number of seconds for sessions to last after they haven\'t been accessed. If a session is not accessed for too long, it is said to have "timed out". Anything higher than 2400 is recommended.'; $helptxt['cache_enable'] = 'ElkArte performs caching at a variety of levels. The higher the level of caching enabled the more CPU time will be spent retrieving cached information. If caching is available on your machine it is recommended that you try caching at level 1 first.'; -$helptxt['cache_memcached'] = 'If you are using memcached you need to provide the server details. This should be entered as a comma separated list as shown in the example below:

    "server1,server2,server3:port,server4"

    Note that if no port is specified the software will use port 11211, set this to 0 when using UNIX domain sockets.'; +$helptxt['cache_memcached'] = 'If you are using memcached you need to provide the server details. This should be entered as a comma separated list as shown in this example:

    "server1,server2,server3:port,server4"

    Note that if no port is specified the software will use port 11211, set this to 0 when using UNIX domain sockets.'; +$helptxt['cache_redis'] = 'If you are using redis you need to provide the server details. This should be entered as a comma separated list as shown in this example:

    "server1,server2,server3:port,server4"

    Note that if no port is specified the software will use port 6379, set this to 0 when using UNIX domain sockets.'; $helptxt['cache_cachedir'] = 'This setting is only for the filesystem based cache system. It specifies the path to the cache directory. It is recommended that you place this in /tmp/ if you are going to use this, although it will work in any directory'; +$helptxt['cache_uid'] = 'Some cache systems, for example Redis, may require a user ID to allow ElkArte access the cache.'; +$helptxt['cache_password'] = 'Some cache systems, for example Redis, may require a password to allow ElkArte access the cache.'; + $helptxt['enableErrorLogging'] = 'This will log any errors, like a failed login, so you can see what went wrong.'; $helptxt['enableErrorQueryLogging'] = 'This will include the full query sent to the database in the error log. Requires error logging to be turned on.

    Note: This will affect the ability to filter the error log by the error message.'; $helptxt['allow_disableAnnounce'] = 'This will allow users to opt out of notification of topics you announce by checking the "announce topic" checkbox when posting.'; diff --git a/sources/ElkArte/Languages/Maintenance/English.php b/sources/ElkArte/Languages/Maintenance/English.php index 636d9de4a5..d90309eb62 100644 --- a/sources/ElkArte/Languages/Maintenance/English.php +++ b/sources/ElkArte/Languages/Maintenance/English.php @@ -145,8 +145,8 @@ $txt['maintain_cache_get_misses'] = 'The number of cache item retrievals that did not find a matching item.'; $txt['maintain_cache_curr_connections'] = 'The number of currently open connections to the caching daemon.'; $txt['maintain_cache_version'] = 'The version number of the caching server.'; -$txt['maintain_cache_hit_rate'] = 'Hit Rate in requests/second'; -$txt['maintain_cache_miss_rate'] = 'Miss Rate in requests/second'; +$txt['maintain_cache_hit_rate'] = 'Hit Rate in requests/minute'; +$txt['maintain_cache_miss_rate'] = 'Miss Rate in requests/minute'; $txt['maintain_backup'] = 'Backup Database'; $txt['maintain_backup_info'] = 'Download a backup copy of your forums database in case of emergency.'; diff --git a/sources/ElkArte/Languages/ManageSettings/English.php b/sources/ElkArte/Languages/ManageSettings/English.php index 3b4ae8ef4f..23686c200a 100644 --- a/sources/ElkArte/Languages/ManageSettings/English.php +++ b/sources/ElkArte/Languages/ManageSettings/English.php @@ -123,10 +123,14 @@ $txt['cache_level1'] = 'Level 1 Caching (Recommended)'; $txt['cache_level2'] = 'Level 2 Caching'; $txt['cache_level3'] = 'Level 3 Caching (Not Recommended)'; -$txt['cache_memcached'] = 'Memcached settings'; -$txt['cache_memcache'] = 'Memcache settings'; +$txt['cache_memcached'] = 'Memcached Server(s)'; +$txt['cache_memcache'] = 'Memcache Server(s)'; $txt['cache_memcached_servers'] = '
    Added servers:
    • '; +$txt['cache_redis'] = 'Redis Server'; +$txt['cache_redis_servers'] = '
      Connected servers:
      • '; $txt['cache_accelerator'] = 'Caching Accelerator'; +$txt['cache_uid'] = 'Cache Accelerator ID'; +$txt['cache_password'] = 'Cache Accelerator Password'; $txt['loadavg_warning'] = 'Please note: the settings below are to be edited with care. Setting any of them too low may render your forum unusable! The current load average is %01.2f'; $txt['loadavg_enable'] = 'Enable load management by load averages'; From 264974ef5984a1bf831793127c21e55abf113fc8 Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 10:51:02 -0500 Subject: [PATCH 12/15] ! don't initialize a second cache method when in the settings area. fix#3712 --- sources/subs/Cache.subs.php | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/sources/subs/Cache.subs.php b/sources/subs/Cache.subs.php index 625c301de5..bcf0186dc7 100644 --- a/sources/subs/Cache.subs.php +++ b/sources/subs/Cache.subs.php @@ -44,10 +44,7 @@ function cache_quick_get($key, $file, $function, $params, $level = 1) * * - It may "miss" so shouldn't be depended on * - Uses the cache engine chosen in the ACP and saved in settings.php - * - It supports: - * memcache: http://www.php.net/memcache - * APC: http://www.php.net/apc - * Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm + * - It supports Memcache, Memcached, APCu, Zend, Redis & Filebased engines * * @param string $key * @param string|int|array|null $value @@ -117,12 +114,19 @@ function clean_cache($type = '') */ function loadCacheEngines($supported_only = true) { - global $cache_memcached; + global $cache_servers, $cache_uid, $cache_password; $engines = []; $classes = new GlobIterator(SOURCEDIR . '/ElkArte/Cache/CacheMethod/*.php', FilesystemIterator::SKIP_DOTS); + $current = ''; + $cache = Cache::instance(); + if ($cache->isEnabled()) + { + $current = $cache->getAccelerator(); + } + foreach ($classes as $file_path) { // Get the engine name from the file name @@ -137,15 +141,22 @@ function loadCacheEngines($supported_only = true) // Validate the class name exists if (class_exists($class)) { - $options = []; - if (strpos($engine_name, 'Memcache') === 0) + $options = [ + 'servers' => empty($cache_servers) ? [] : explode(',', $cache_servers), + 'cache_uid' => empty($cache_uid) ? '' : $cache_uid, + 'cache_password' => empty($cache_password) ? '' : $cache_password, + ]; + + // Use the current Cache object if its been enabled + if ($engine_name === $current) + { + $obj = $cache->getCacheEngine(); + } + else { - $options = [ - 'servers' => explode(',', $cache_memcached), - ]; + $obj = new $class($options); } - $obj = new $class($options); if ($obj instanceof AbstractCacheMethod) { if ($supported_only && $obj->isAvailable()) From 581a33184cd728279892f6d706c8a5ae55358ecf Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 11:00:49 -0500 Subject: [PATCH 13/15] ! update js for cache settings form area --- themes/default/scripts/admin.js | 67 +++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/themes/default/scripts/admin.js b/themes/default/scripts/admin.js index f2e08810e9..aef7e14c6c 100644 --- a/themes/default/scripts/admin.js +++ b/themes/default/scripts/admin.js @@ -965,31 +965,66 @@ function showCache () */ function toggleCache () { - let memcache = document.getElementById('cache_memcached').parentNode, - cachedir = document.getElementById('cachedir').parentNode; + let cacheServers = document.getElementById('cache_servers'), + cacheDir = document.getElementById('cachedir'), + cacheUid = document.getElementById('cache_uid'), + cachePassword = document.getElementById('cache_password'), + cacheConfirm = document.getElementById('cache_password_confirm'); - // Show the memcache server box only if memcache has been selected - if (cache_type.value.substring(0, 8) === 'memcache') + cacheServers = cacheServers ? cacheServers.parentNode : null; + cacheDir = cacheDir ? cacheDir.parentNode : null; + cacheUid = cacheUid ? cacheUid.parentNode : null; + cachePassword = cachePassword ? cachePassword.parentNode : null; + cacheConfirm = cacheConfirm ? cacheConfirm.parentNode : null; + + // Show the server box only if memcache/memcached/redis has been selected + if (cache_type.value.substring(0, 8) === 'memcache' || cache_type.value === 'redis') { - memcache.slideDown(); - memcache.previousElementSibling.slideDown(100); + showHideCacheOption(cacheServers, (cache_type.value.substring(0, 8) === 'memcache' || cache_type.value === 'redis' || cache_type.value === 'predis')); } - else + + // Don't show the directory if its not filebased + if (cache_type.value === 'filebased') { - memcache.slideUp(); - memcache.previousElementSibling.slideUp(100); + showHideCacheOption(cacheDir, cache_type.value === 'filebased'); } - // don't show the directory if its not filebased - if (cache_type.value === 'filebased') + // Currently only redis (optionally) uses the uid/password + if (cacheUid) { - cachedir.slideDown(); - cachedir.previousElementSibling.slideDown(100); + showHideCacheOption(cacheUid, (cache_type.value === "redis" || cache_type.value === "predis")); } - else + if (cachePassword) + { + showHideCacheOption(cachePassword, (cache_type.value === "redis" || cache_type.value === "predis")); + } + if (cacheConfirm) + { + showHideCacheOption(cacheConfirm, (cache_type.value === "redis" || cache_type.value === "predis")); + } +} + +/** + * Toggles the visibility of a cache option element and its previous sibling element. + * + * @param {Element} elem - The cache option element to toggle. + * @param {boolean} show - Determines whether to show or hide the cache option element. + * + * @return {undefined} + */ +function showHideCacheOption(elem, show) +{ + if (elem) { - cachedir.slideUp(100); - cachedir.previousElementSibling.slideUp(100); + if (show) + { + elem.slideDown(100); + elem.previousElementSibling.slideDown(150); + return; + } + + elem.slideUp(100); + elem.previousElementSibling.slideUp(150); } } From 1d368039fe55d44f63accc541edc0adfbb9d10bf Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 11:02:01 -0500 Subject: [PATCH 14/15] ! for now, only support a single server (use the first listed if multiple) --- sources/ElkArte/Cache/CacheMethod/Predis.php | 52 ++++++++++---------- sources/ElkArte/Cache/CacheMethod/Redis.php | 3 +- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/sources/ElkArte/Cache/CacheMethod/Predis.php b/sources/ElkArte/Cache/CacheMethod/Predis.php index 3a97cfb146..e291dcc981 100644 --- a/sources/ElkArte/Cache/CacheMethod/Predis.php +++ b/sources/ElkArte/Cache/CacheMethod/Predis.php @@ -10,6 +10,7 @@ * @version 2.0 dev * */ + namespace ElkArte\Cache\CacheMethod; /** @@ -33,7 +34,6 @@ public function __construct($options) { require_once(EXTDIR . '/predis/autoload.php'); - if ($this->isAvailable()) { parent::__construct($options); @@ -58,29 +58,29 @@ public function isAvailable() */ protected function addServers() { - if(!empty($this->_options['servers'])) + if (!empty($this->_options['servers'])) { - foreach ($this->_options['servers'] as $server) + $server = reset($this->_options['servers']); + $server = explode(':', trim($server)); + $server[0] = !empty($server[0]) ? $server[0] : 'localhost'; + $server[1] = !empty($server[1]) ? $server[1] : 6379; + + $params = [ + 'scheme' => 'tcp', + 'host' => $server[0], + 'port' => $server[1], + ]; + + try + { + $this->obj = new \Predis\Client($params); + $this->obj->connect(); + $this->server[] = "tcp://{$server[0]}:{$server[1]}"; + } + catch (\Predis\Connection\ConnectionException $e) { - $server = explode(':', trim($server)); - $server[0] = !empty($server[0]) ? $server[0] : 'localhost'; - $server[1] = !empty($server[1]) ? $server[1] : 6379; - - $params = [ - 'scheme' => 'tcp', - 'host' => $server[0], - 'port' => $server[1], - ]; - - try { - $this->obj = new \Predis\Client($params); - $this->obj->connect(); - $this->server[] = "tcp://{$server[0]}:{$server[1]}"; - } - catch(\Predis\Connection\ConnectionException $e) { - // Clear the object, should we log an error here? - $this->obj = null; - } + // Clear the object, should we log an error here? + $this->obj = null; } } } @@ -150,7 +150,7 @@ public function exists($key) */ public function get($key, $ttl = 120) { - if(!is_object($this->obj)) + if (!is_object($this->obj)) { return ''; } @@ -166,7 +166,7 @@ public function get($key, $ttl = 120) */ public function put($key, $value, $ttl = 120) { - if(!is_object($this->obj)) + if (!is_object($this->obj)) { return ''; } @@ -184,7 +184,7 @@ public function put($key, $value, $ttl = 120) */ public function clean($type = '') { - if(!is_object($this->obj)) + if (!is_object($this->obj)) { return ''; } @@ -198,7 +198,7 @@ public function clean($type = '') */ public function details() { - if(!is_object($this->obj)) + if (!is_object($this->obj)) { return ''; } diff --git a/sources/ElkArte/Cache/CacheMethod/Redis.php b/sources/ElkArte/Cache/CacheMethod/Redis.php index e01cf6ec4b..b3f517390c 100644 --- a/sources/ElkArte/Cache/CacheMethod/Redis.php +++ b/sources/ElkArte/Cache/CacheMethod/Redis.php @@ -161,7 +161,8 @@ protected function addServers() { $retVal = false; } - finally { + finally + { restore_error_handler(); } } From adfb03c2733c8dcc65bb1c75e8b5b9ee4c5c7536 Mon Sep 17 00:00:00 2001 From: Spuds Date: Sat, 1 Jun 2024 11:06:55 -0500 Subject: [PATCH 15/15] ! until its decided how to add in that library --- sources/ElkArte/Cache/CacheMethod/Predis.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/ElkArte/Cache/CacheMethod/Predis.php b/sources/ElkArte/Cache/CacheMethod/Predis.php index e291dcc981..ee80b7104a 100644 --- a/sources/ElkArte/Cache/CacheMethod/Predis.php +++ b/sources/ElkArte/Cache/CacheMethod/Predis.php @@ -32,7 +32,7 @@ class Predis extends AbstractCacheMethod */ public function __construct($options) { - require_once(EXTDIR . '/predis/autoload.php'); + // require_once(EXTDIR . '/predis/autoload.php'); if ($this->isAvailable()) {