From 841c2d0afe2a05bb142df730f412fc7a7c9c455c Mon Sep 17 00:00:00 2001
From: Sebastian Thulin
Date: Tue, 3 Oct 2023 12:42:12 +0200
Subject: [PATCH] 231028 - Stable Release 3.4.0
-Update to modularity 331
-Removes acf-extended, since modularity ahs this requirement
-Updates all extenal vendor plugins to the latest version. Also better defines their required versions.
---
composer.json | 35 +-
wp-content/object-cache.php | 1123 ++++++++++++++++++++++++++++++-----
2 files changed, 1000 insertions(+), 158 deletions(-)
diff --git a/composer.json b/composer.json
index 3e6ffe0..28615c6 100644
--- a/composer.json
+++ b/composer.json
@@ -61,7 +61,7 @@
"helsingborg-stad/lix-calculator": "^3.0.0",
"helsingborg-stad/media-usage": "^3.0.0",
"helsingborg-stad/mod-my-pages": "^1.0.0",
- "helsingborg-stad/modularity": "^3.0.1",
+ "helsingborg-stad/modularity": "^3.3.1",
"helsingborg-stad/modularity-contact-banner": "^2.0.0",
"helsingborg-stad/modularity-entryscape": "^2.0.0",
"helsingborg-stad/modularity-form-builder": "^2.0.0",
@@ -90,25 +90,24 @@
"helsingborg-stad/wpmu-network-admin-url": "^1.0.0",
"helsingborg-stad/wpmu-mu-plugins-url-everywhere": "~1.0.0",
"humanmade/s3-uploads": "^3.0.4",
- "johnpbloch/wordpress": "6.3.1",
+ "johnpbloch/wordpress": "~6.3.1",
"municipio-se/modularity-noticeboard": "^1.0.0",
"wikimedia/composer-merge-plugin": "dev-master",
- "wpackagist-plugin/acf-extended": "0.8.8.7",
- "wpackagist-plugin/autodescription": ">=2.9.0",
- "wpackagist-plugin/cookies-and-content-security-policy": "^1.44",
- "wpackagist-plugin/fakerpress": "0.5.2",
- "wpackagist-plugin/kirki": "^5.0",
- "wpackagist-plugin/litespeed-cache": "^5.3.0",
- "wpackagist-plugin/network-plugin-auditor": "^1.10.0",
- "wpackagist-plugin/nginx-helper": "^2.2",
- "wpackagist-plugin/redirection": "^4.3.1",
- "wpackagist-plugin/redis-cache": "2.0.15",
- "wpackagist-plugin/simple-smtp": "^1.3.2",
- "wpackagist-plugin/user-switching": ">=1.5.1",
- "wpackagist-plugin/username-changer": "3.1.3",
- "wpackagist-plugin/varnish-http-purge": "^4.1.1",
- "wpackagist-plugin/wp-multi-network": "^2.5.2",
- "wpackagist-plugin/wp-nested-pages": "3.1.4",
+ "wpackagist-plugin/autodescription": "~4.2.8",
+ "wpackagist-plugin/cookies-and-content-security-policy": "~2.17",
+ "wpackagist-plugin/fakerpress": "~0.6.1",
+ "wpackagist-plugin/kirki": "~5.0",
+ "wpackagist-plugin/litespeed-cache": "~5.3.0",
+ "wpackagist-plugin/network-plugin-auditor": "~1.10.0",
+ "wpackagist-plugin/nginx-helper": "~2.2",
+ "wpackagist-plugin/redirection": "~5.3.10",
+ "wpackagist-plugin/redis-cache": "2.4.4",
+ "wpackagist-plugin/simple-smtp": "~1.3.2",
+ "wpackagist-plugin/user-switching": "~1.5.1",
+ "wpackagist-plugin/username-changer": "~3.2.2",
+ "wpackagist-plugin/varnish-http-purge": "~5.1.3",
+ "wpackagist-plugin/wp-multi-network": "~2.5.2",
+ "wpackagist-plugin/wp-nested-pages": "~3.2.4",
"wpackagist-plugin/wp-sentry-integration":"~6.24",
"wpackagist-plugin/performant-translations":"~1.0.3"
},
diff --git a/wp-content/object-cache.php b/wp-content/object-cache.php
index 304aab2..001776e 100644
--- a/wp-content/object-cache.php
+++ b/wp-content/object-cache.php
@@ -1,13 +1,14 @@
add( $key, $value, $group, $expiration );
}
+/**
+ * Adds multiple values to the cache in one call.
+ *
+ * @param array $data Array of keys and values to be set.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if cache key and group already exist.
+ */
+function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->add_multiple( $data, $group, $expire );
+}
+
/**
* Closes the cache.
*
@@ -80,18 +123,54 @@ function wp_cache_delete( $key, $group = '', $time = 0 ) {
return $wp_object_cache->delete( $key, $group, $time );
}
+/**
+ * Deletes multiple values from the cache in one call.
+ *
+ * @param array $keys Array of keys under which the cache to deleted.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if the contents were not deleted.
+ */
+function wp_cache_delete_multiple( array $keys, $group = '' ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->delete_multiple( $keys, $group );
+}
+
/**
* Invalidate all items in the cache. If `WP_REDIS_SELECTIVE_FLUSH` is `true`,
* only keys prefixed with the `WP_REDIS_PREFIX` are flushed.
*
- * @param int $delay Number of seconds to wait before invalidating the items.
- *
* @return bool Returns TRUE on success or FALSE on failure.
*/
-function wp_cache_flush( $delay = 0 ) {
+function wp_cache_flush() {
+ global $wp_object_cache;
+
+ return $wp_object_cache->flush();
+}
+
+/**
+ * Removes all cache items in a group.
+ *
+ * @param string $group Name of group to remove from cache.
+ * @return true Returns TRUE on success or FALSE on failure.
+ */
+function wp_cache_flush_group( $group )
+{
+ global $wp_object_cache;
+
+ return $wp_object_cache->flush_group( $group );
+}
+
+/**
+ * Removes all cache items from the in-memory runtime cache.
+ *
+ * @return bool True on success, false on failure.
+ */
+function wp_cache_flush_runtime() {
global $wp_object_cache;
- return $wp_object_cache->flush( $delay );
+ return $wp_object_cache->flush_runtime();
}
/**
@@ -165,10 +244,14 @@ function wp_cache_init() {
define( 'WP_REDIS_PREFIX', WP_CACHE_KEY_SALT );
}
+ // Set unique prefix for sites hosted on Cloudways
+ if ( ! defined( 'WP_REDIS_PREFIX' ) && isset( $_SERVER['cw_allowed_ip'] ) ) {
+ define( 'WP_REDIS_PREFIX', getenv( 'HTTP_X_APP_USER' ) );
+ }
+
if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) {
- $fail_gracefully = ! defined( 'WP_REDIS_GRACEFUL' ) || WP_REDIS_GRACEFUL;
+ $fail_gracefully = defined( 'WP_REDIS_GRACEFUL' ) && WP_REDIS_GRACEFUL;
- // We need to override this WordPress global in order to inject our cache.
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_object_cache = new WP_Object_Cache( $fail_gracefully );
}
@@ -211,6 +294,22 @@ function wp_cache_set( $key, $value, $group = '', $expiration = 0 ) {
return $wp_object_cache->set( $key, $value, $group, $expiration );
}
+/**
+ * Sets multiple values to the cache in one call.
+ *
+ * @param array $data Array of keys and values to be set.
+ * @param string $group Optional. Where the cache contents are grouped. Default empty.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false on failure.
+ */
+function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) {
+ global $wp_object_cache;
+
+ return $wp_object_cache->set_multiple( $data, $group, $expire );
+}
+
/**
* Switch the internal blog id.
*
@@ -256,7 +355,6 @@ function wp_cache_add_non_persistent_groups( $groups ) {
* Object cache class definition
*/
class WP_Object_Cache {
-
/**
* The Redis client.
*
@@ -309,7 +407,7 @@ class WP_Object_Cache {
/**
* List of global groups.
*
- * @var array
+ * @var array
*/
public $global_groups = [
'blog-details',
@@ -349,6 +447,13 @@ class WP_Object_Cache {
'themes',
];
+ /**
+ * List of groups and their types.
+ *
+ * @var array
+ */
+ public $group_type = [];
+
/**
* Prefix used for global groups.
*
@@ -359,9 +464,9 @@ class WP_Object_Cache {
/**
* Prefix used for non-global groups.
*
- * @var string
+ * @var int
*/
- public $blog_prefix = '';
+ public $blog_prefix = 0;
/**
* Track how many requests were found in cache.
@@ -378,25 +483,25 @@ class WP_Object_Cache {
public $cache_misses = 0;
/**
- * Track how long request took.
+ * The amount of Redis commands made.
*
* @var int
*/
- public $cache_time = 0;
+ public $cache_calls = 0;
/**
- * Track how may calls were made.
+ * The amount of microseconds (μs) waited for Redis commands.
*
- * @var int
+ * @var float
*/
- public $cache_calls = 0;
+ public $cache_time = 0;
/**
* Instantiate the Redis class.
*
* @param bool $fail_gracefully Handles and logs errors if true throws exceptions otherwise.
*/
- public function __construct( $fail_gracefully = true ) {
+ public function __construct( $fail_gracefully = false ) {
global $blog_id, $table_prefix;
$this->fail_gracefully = $fail_gracefully;
@@ -415,6 +520,14 @@ public function __construct( $fail_gracefully = true ) {
$this->unflushable_groups = array_map( [ $this, 'sanitize_key_part' ], WP_REDIS_UNFLUSHABLE_GROUPS );
}
+ $this->cache_group_types();
+
+ if ( function_exists( '_doing_it_wrong' ) ) {
+ if ( defined( 'WP_REDIS_TRACE' ) && WP_REDIS_TRACE ) {
+ _doing_it_wrong( __FUNCTION__ , 'Tracing feature was removed.' , '2.1.2' );
+ }
+ }
+
$client = $this->determine_client();
$parameters = $this->build_parameters();
@@ -426,6 +539,9 @@ public function __construct( $fail_gracefully = true ) {
case 'phpredis':
$this->connect_using_phpredis( $parameters );
break;
+ case 'relay':
+ $this->connect_using_relay( $parameters );
+ break;
case 'credis':
$this->connect_using_credis( $parameters );
break;
@@ -436,7 +552,13 @@ public function __construct( $fail_gracefully = true ) {
}
if ( defined( 'WP_REDIS_CLUSTER' ) ) {
- $this->diagnostics[ 'ping' ] = $this->redis->ping( current( array_values( WP_REDIS_CLUSTER ) ) );
+ $connectionId = is_string( WP_REDIS_CLUSTER )
+ ? WP_REDIS_CLUSTER
+ : current( $this->build_cluster_connection_array() );
+
+ $this->diagnostics[ 'ping' ] = $client === 'predis'
+ ? $this->redis->getClientBy( 'id', $connectionId )->ping()
+ : $this->redis->ping( $connectionId );
} else {
$this->diagnostics[ 'ping' ] = $this->redis->ping();
}
@@ -455,6 +577,25 @@ public function __construct( $fail_gracefully = true ) {
}
}
+ /**
+ * Set group type array
+ *
+ * @return void
+ */
+ protected function cache_group_types() {
+ foreach ( $this->global_groups as $group ) {
+ $this->group_type[ $group ] = 'global';
+ }
+
+ foreach ( $this->unflushable_groups as $group ) {
+ $this->group_type[ $group ] = 'unflushable';
+ }
+
+ foreach ( $this->ignored_groups as $group ) {
+ $this->group_type[ $group ] = 'ignored';
+ }
+ }
+
/**
* Determine the Redis client.
*
@@ -489,6 +630,7 @@ protected function build_parameters() {
'timeout' => 1,
'read_timeout' => 1,
'retry_interval' => null,
+ 'persistent' => false,
];
$settings = [
@@ -511,19 +653,15 @@ protected function build_parameters() {
}
}
- if ( isset( $parameters['password'] ) ) {
- $password = $parameters['password'];
-
- if ( is_null( $password ) || $password === '' ) {
- unset( $parameters['password'] );
- }
+ if ( isset( $parameters[ 'password' ] ) && $parameters[ 'password' ] === '' ) {
+ unset( $parameters[ 'password' ] );
}
return $parameters;
}
/**
- * Connect to Redis using the PhpRedis (PECL) extention.
+ * Connect to Redis using the PhpRedis (PECL) extension.
*
* @param array $parameters Connection parameters built by the `build_parameters` method.
* @return void
@@ -538,45 +676,129 @@ protected function connect_using_phpredis( $parameters ) {
$this->diagnostics[ 'shards' ] = WP_REDIS_SHARDS;
} elseif ( defined( 'WP_REDIS_CLUSTER' ) ) {
+ if ( is_string( WP_REDIS_CLUSTER ) ) {
+ $this->redis = new RedisCluster( WP_REDIS_CLUSTER );
+ } else {
+ $args = [
+ 'cluster' => $this->build_cluster_connection_array(),
+ 'timeout' => $parameters['timeout'],
+ 'read_timeout' => $parameters['read_timeout'],
+ 'persistent' => $parameters['persistent'],
+ ];
+
+ if ( isset( $parameters['password'] ) && version_compare( $version, '4.3.0', '>=' ) ) {
+ $args['password'] = $parameters['password'];
+ }
+
+ $this->redis = new RedisCluster( null, ...array_values( $args ) );
+ $this->diagnostics += $args;
+ }
+ } else {
+ $this->redis = new Redis();
+
$args = [
- 'cluster' => array_values( WP_REDIS_CLUSTER ),
+ 'host' => $parameters['host'],
+ 'port' => $parameters['port'],
'timeout' => $parameters['timeout'],
- 'read_timeout' => $parameters['read_timeout'],
+ '',
+ 'retry_interval' => (int) $parameters['retry_interval'],
];
- if ( isset( $parameters['password'] ) && version_compare( $version, '4.3.0', '>=' ) ) {
+ if ( version_compare( $version, '3.1.3', '>=' ) ) {
+ $args['read_timeout'] = $parameters['read_timeout'];
+ }
+
+ if ( strcasecmp( 'tls', $parameters['scheme'] ) === 0 ) {
+ $args['host'] = sprintf(
+ '%s://%s',
+ $parameters['scheme'],
+ str_replace( 'tls://', '', $parameters['host'] )
+ );
+
+ if ( version_compare( $version, '5.3.0', '>=' ) && defined( 'WP_REDIS_SSL_CONTEXT' ) && ! empty( WP_REDIS_SSL_CONTEXT ) ) {
+ $args['others']['stream'] = WP_REDIS_SSL_CONTEXT;
+ }
+ }
+
+ if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) {
+ $args['host'] = $parameters['path'];
+ $args['port'] = -1;
+ }
+
+ call_user_func_array( [ $this->redis, 'connect' ], array_values( $args ) );
+
+ if ( isset( $parameters['password'] ) ) {
$args['password'] = $parameters['password'];
+ $this->redis->auth( $parameters['password'] );
}
- $this->redis = new RedisCluster( null, ...array_values( $args ) );
+ if ( isset( $parameters['database'] ) ) {
+ if ( ctype_digit( (string) $parameters['database'] ) ) {
+ $parameters['database'] = (int) $parameters['database'];
+ }
+
+ $args['database'] = $parameters['database'];
+
+ if ( $parameters['database'] ) {
+ $this->redis->select( $parameters['database'] );
+ }
+ }
$this->diagnostics += $args;
+ }
+
+ if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) {
+ $this->redis->setOption( Redis::OPT_SERIALIZER, WP_REDIS_SERIALIZER );
+
+ if ( function_exists( '_doing_it_wrong' ) ) {
+ _doing_it_wrong( __FUNCTION__ , 'The `WP_REDIS_SERIALIZER` configuration constant has been deprecated, use `WP_REDIS_IGBINARY` instead.', '2.3.1' );
+ }
+ }
+ }
+
+ /**
+ * Connect to Redis using the Relay extension.
+ *
+ * @param array $parameters Connection parameters built by the `build_parameters` method.
+ * @return void
+ */
+ protected function connect_using_relay( $parameters ) {
+ $version = phpversion( 'relay' );
+
+ $this->diagnostics[ 'client' ] = sprintf( 'Relay (v%s)', $version );
+
+ if ( defined( 'WP_REDIS_SHARDS' ) ) {
+ throw new Exception('Relay does not support sharding.');
+ } elseif ( defined( 'WP_REDIS_CLUSTER' ) ) {
+ throw new Exception('Relay does not cluster connections.');
} else {
- $this->redis = new Redis();
+ $this->redis = new Relay\Relay;
$args = [
'host' => $parameters['host'],
'port' => $parameters['port'],
'timeout' => $parameters['timeout'],
- null,
- 'retry_interval' => $parameters['retry_interval'],
+ '',
+ 'retry_interval' => (int) $parameters['retry_interval'],
];
+ $args['read_timeout'] = $parameters['read_timeout'];
+
if ( strcasecmp( 'tls', $parameters['scheme'] ) === 0 ) {
$args['host'] = sprintf(
'%s://%s',
$parameters['scheme'],
str_replace( 'tls://', '', $parameters['host'] )
);
+
+ if ( defined( 'WP_REDIS_SSL_CONTEXT' ) && ! empty( WP_REDIS_SSL_CONTEXT ) ) {
+ $args['others']['stream'] = WP_REDIS_SSL_CONTEXT;
+ }
}
if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) {
$args['host'] = $parameters['path'];
- $args['port'] = null;
- }
-
- if ( version_compare( $version, '3.1.3', '>=' ) ) {
- $args['read_timeout'] = $parameters['read_timeout'];
+ $args['port'] = -1;
}
call_user_func_array( [ $this->redis, 'connect' ], array_values( $args ) );
@@ -587,8 +809,8 @@ protected function connect_using_phpredis( $parameters ) {
}
if ( isset( $parameters['database'] ) ) {
- if ( ctype_digit( $parameters['database'] ) ) {
- $parameters['database'] = intval( $parameters['database'] );
+ if ( ctype_digit( (string) $parameters['database'] ) ) {
+ $parameters['database'] = (int) $parameters['database'];
}
$args['database'] = $parameters['database'];
@@ -602,7 +824,11 @@ protected function connect_using_phpredis( $parameters ) {
}
if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) {
- $this->redis->setOption( Redis::OPT_SERIALIZER, WP_REDIS_SERIALIZER );
+ $this->redis->setOption( Relay\Relay::OPT_SERIALIZER, WP_REDIS_SERIALIZER );
+
+ if ( function_exists( '_doing_it_wrong' ) ) {
+ _doing_it_wrong( __FUNCTION__ , 'The `WP_REDIS_SERIALIZER` configuration constant has been deprecated, use `WP_REDIS_IGBINARY` instead.', '2.3.1' );
+ }
}
}
@@ -618,13 +844,18 @@ protected function connect_using_predis( $parameters ) {
// Load bundled Predis library.
if ( ! class_exists( 'Predis\Client' ) ) {
- $predis = sprintf(
- '%s/redis-cache/dependencies/predis/predis/autoload.php',
- defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins'
- );
-
- if ( is_readable( $predis ) ) {
- require_once $predis;
+ $predis = '/dependencies/predis/predis/autoload.php';
+
+ $pluginDir = defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR . '/redis-cache' : null;
+ $contentDir = defined( 'WP_CONTENT_DIR' ) ? WP_CONTENT_DIR . '/plugins/redis-cache' : null;
+ $pluginPath = defined( 'WP_REDIS_PLUGIN_PATH' ) ? WP_REDIS_PLUGIN_PATH : null;
+
+ if ( $pluginDir && is_readable( $pluginDir . $predis ) ) {
+ require_once $pluginDir . $predis;
+ } elseif ( $contentDir && is_readable( $contentDir . $predis ) ) {
+ require_once $contentDir . $predis;
+ } elseif ( $pluginPath && is_readable( $pluginPath . $predis ) ) {
+ require_once $pluginPath . $predis;
} else {
throw new Exception(
'Predis library not found. Re-install Redis Cache plugin or delete the object-cache.php.'
@@ -646,13 +877,17 @@ protected function connect_using_predis( $parameters ) {
} elseif ( defined( 'WP_REDIS_SERVERS' ) ) {
$servers = WP_REDIS_SERVERS;
$parameters['servers'] = $servers;
- $options['replication'] = true;
+ $options['replication'] = 'predis';
} elseif ( defined( 'WP_REDIS_CLUSTER' ) ) {
- $servers = WP_REDIS_CLUSTER;
+ $servers = $this->build_cluster_connection_array();
$parameters['cluster'] = $servers;
$options['cluster'] = 'redis';
}
+ if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) {
+ unset($parameters['host'], $parameters['port']);
+ }
+
if ( isset( $parameters['read_timeout'] ) && $parameters['read_timeout'] ) {
$parameters['read_write_timeout'] = $parameters['read_timeout'];
}
@@ -664,11 +899,31 @@ protected function connect_using_predis( $parameters ) {
}
if ( isset( $parameters['password'] ) ) {
- $options['parameters']['password'] = WP_REDIS_PASSWORD;
+ if ( is_array( $parameters['password'] ) ) {
+ $options['parameters']['username'] = WP_REDIS_PASSWORD[0];
+ $options['parameters']['password'] = WP_REDIS_PASSWORD[1];
+ } else {
+ $options['parameters']['password'] = WP_REDIS_PASSWORD;
+ }
}
}
}
+ if ( isset( $parameters['password'] ) ) {
+ if ( is_array( $parameters['password'] ) ) {
+ $parameters['username'] = array_shift( $parameters['password'] );
+ $parameters['password'] = implode( '', $parameters['password'] );
+ }
+
+ if ( defined( 'WP_REDIS_USERNAME' ) ) {
+ $parameters['username'] = WP_REDIS_USERNAME;
+ }
+ }
+
+ if ( defined( 'WP_REDIS_SSL_CONTEXT' ) && ! empty( WP_REDIS_SSL_CONTEXT ) ) {
+ $parameters['ssl'] = WP_REDIS_SSL_CONTEXT;
+ }
+
$this->redis = new Predis\Client( $servers ?: $parameters, $options );
$this->redis->connect();
@@ -689,6 +944,8 @@ protected function connect_using_predis( $parameters ) {
* @return void
*/
protected function connect_using_credis( $parameters ) {
+ _doing_it_wrong( __FUNCTION__ , 'Credis support will be removed in future versions.' , '2.0.26' );
+
$client = 'Credis';
$creds_path = sprintf(
@@ -754,19 +1011,24 @@ protected function connect_using_credis( $parameters ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
$url_components = parse_url( $connection_string );
- parse_str( $url_components['query'], $add_params );
+ if ( isset( $url_components['query'] ) ) {
+ parse_str( $url_components['query'], $add_params );
+ }
if ( ! $is_cluster && isset( $add_params['alias'] ) ) {
$add_params['master'] = 'master' === $add_params['alias'];
}
$add_params['host'] = $url_components['host'];
+ $add_params['port'] = $url_components['port'];
if ( ! isset( $add_params['alias'] ) ) {
$add_params['alias'] = "redis-$index";
}
$clients[ $index ] = array_merge( $parameters, $add_params );
+
+ unset($add_params);
}
$this->redis = new Credis_Cluster( $clients );
@@ -778,7 +1040,7 @@ protected function connect_using_credis( $parameters ) {
$params = array_filter( $_client );
if ( $params ) {
- $connection_string .= '?' . http_build_query( $params, null, '&' );
+ $connection_string .= '?' . http_build_query( $params, '', '&' );
}
$clients[ $index ] = $connection_string;
@@ -790,7 +1052,7 @@ protected function connect_using_credis( $parameters ) {
'host' => $parameters['scheme'] === 'unix' ? $parameters['path'] : $parameters['host'],
'port' => $parameters['port'],
'timeout' => $parameters['timeout'],
- 'persistent' => null,
+ 'persistent' => '',
'database' => $parameters['database'],
'password' => isset( $parameters['password'] ) ? $parameters['password'] : null,
];
@@ -815,12 +1077,14 @@ protected function connect_using_credis( $parameters ) {
}
/**
- * Connect to Redis using HHVM's Redis extention.
+ * Connect to Redis using HHVM's Redis extension.
*
* @param array $parameters Connection parameters built by the `build_parameters` method.
* @return void
*/
protected function connect_using_hhvm( $parameters ) {
+ _doing_it_wrong( __FUNCTION__ , 'HHVM support will be removed in future versions.' , '2.0.26' );
+
$this->redis = new Redis();
// Adjust host and port if the scheme is `unix`.
@@ -846,8 +1110,8 @@ protected function connect_using_hhvm( $parameters ) {
}
if ( isset( $parameters['database'] ) ) {
- if ( ctype_digit( $parameters['database'] ) ) {
- $parameters['database'] = intval( $parameters['database'] );
+ if ( ctype_digit( (string) $parameters['database'] ) ) {
+ $parameters['database'] = (int) $parameters['database'];
}
if ( $parameters['database'] ) {
@@ -876,7 +1140,13 @@ public function fetch_info() {
}
if ( defined( 'WP_REDIS_CLUSTER' ) ) {
- $info = $this->redis->info( current( array_values( WP_REDIS_CLUSTER ) ) );
+ $connectionId = is_string( WP_REDIS_CLUSTER )
+ ? 'SERVER'
+ : current( $this->build_cluster_connection_array() );
+
+ $info = $this->determine_client() === 'predis'
+ ? $this->redis->getClientBy( 'id', $connectionId )->info()
+ : $this->redis->info( $connectionId );
} else {
$info = $this->redis->info();
}
@@ -931,6 +1201,130 @@ public function add( $key, $value, $group = 'default', $expiration = 0 ) {
return $this->add_or_replace( true, $key, $value, $group, $expiration );
}
+ /**
+ * Adds multiple values to the cache in one call.
+ *
+ * @param array $data Array of keys and values to be added.
+ * @param string $group Optional. Where the cache contents are grouped.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if cache key and group already exist.
+ */
+ public function add_multiple( array $data, $group = 'default', $expire = 0 ) {
+ if ( function_exists( 'wp_suspend_cache_addition' ) && wp_suspend_cache_addition() ) {
+ return array_combine( array_keys( $data ), array_fill( 0, count( $data ), false ) );
+ }
+
+ if (
+ $this->redis_status() &&
+ method_exists( $this->redis, 'pipeline' ) &&
+ ! $this->is_ignored_group( $group )
+ ) {
+ return $this->add_multiple_at_once( $data, $group, $expire );
+ }
+
+ $values = [];
+
+ foreach ( $data as $key => $value ) {
+ $values[ $key ] = $this->add( $key, $value, $group, $expire );
+ }
+
+ return $values;
+ }
+
+ /**
+ * Adds multiple values to the cache in one call.
+ *
+ * @param array $data Array of keys and values to be added.
+ * @param string $group Optional. Where the cache contents are grouped.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if cache key and group already exist.
+ */
+ protected function add_multiple_at_once( array $data, $group = 'default', $expire = 0 )
+ {
+ $keys = array_keys( $data );
+
+ $san_group = $this->sanitize_key_part( $group );
+
+ $tx = $this->redis->pipeline();
+
+ $orig_exp = $expire;
+ $expire = $this->validate_expiration( $expire );
+ $derived_keys = [];
+
+ foreach ( $data as $key => $value ) {
+ /**
+ * Filters the cache expiration time
+ *
+ * @param int $expiration The time in seconds the entry expires. 0 for no expiry.
+ * @param string $key The cache key.
+ * @param string $group The cache group.
+ * @param mixed $orig_exp The original expiration value before validation.
+ */
+ $expire = apply_filters( 'redis_cache_expiration', $expire, $key, $group, $orig_exp );
+
+ $san_key = $this->sanitize_key_part( $key );
+ $derived_key = $derived_keys[ $key ] = $this->fast_build_key( $san_key, $san_group );
+
+ $args = [ $derived_key, $this->maybe_serialize( $value ) ];
+
+ if ( $this->is_predis() ) {
+ $args[] = 'nx';
+
+ if ( $expire ) {
+ $args[] = 'ex';
+ $args[] = $expire;
+ }
+ } else {
+ if ( $expire ) {
+ $args[] = [ 'nx', 'ex' => $expire ];
+ } else {
+ $args[] = [ 'nx' ];
+ }
+ }
+
+ $tx->set( ...$args );
+ }
+
+ try {
+ $start_time = microtime( true );
+
+ $method = $this->is_predis() ? 'execute' : 'exec';
+
+ $results = array_map( function ( $response ) {
+ return (bool) $this->parse_redis_response( $response );
+ }, $tx->{$method}() ?: [] );
+
+ if ( count( $results ) !== count( $keys ) ) {
+ $tx->discard();
+
+ return array_fill_keys( $keys, false );
+ }
+
+ $results = array_combine( $keys, $results );
+
+ foreach ( $results as $key => $result ) {
+ if ( $result ) {
+ $this->add_to_internal_cache( $derived_keys[ $key ], $data[ $key ] );
+ }
+ }
+
+ $execute_time = microtime( true ) - $start_time;
+
+ $this->cache_calls++;
+ $this->cache_time += $execute_time;
+ } catch ( Exception $exception ) {
+ $this->handle_exception( $exception );
+
+ return array_combine( $keys, array_fill( 0, count( $keys ), false ) );
+ }
+
+ return $results;
+ }
+
/**
* Replace a value in the cache.
*
@@ -960,19 +1354,21 @@ public function replace( $key, $value, $group = 'default', $expiration = 0 ) {
* @return bool Returns TRUE on success or FALSE on failure.
*/
protected function add_or_replace( $add, $key, $value, $group = 'default', $expiration = 0 ) {
- $cache_addition_suspended = function_exists( 'wp_suspend_cache_addition' )
- ? wp_suspend_cache_addition()
- : false;
+ $cache_addition_suspended = function_exists( 'wp_suspend_cache_addition' ) && wp_suspend_cache_addition();
if ( $add && $cache_addition_suspended ) {
return false;
}
$result = true;
- $derived_key = $this->build_key( $key, $group );
+
+ $san_key = $this->sanitize_key_part( $key );
+ $san_group = $this->sanitize_key_part( $group );
+
+ $derived_key = $this->fast_build_key( $san_key, $san_group );
// Save if group not excluded and redis is up.
- if ( ! $this->is_ignored_group( $group ) && $this->redis_status() ) {
+ if ( ! $this->is_ignored_group( $san_group ) && $this->redis_status() ) {
try {
$orig_exp = $expiration;
$expiration = $this->validate_expiration( $expiration );
@@ -992,7 +1388,7 @@ protected function add_or_replace( $add, $key, $value, $group = 'default', $expi
if ( $add ) {
$args = [ $derived_key, $this->maybe_serialize( $value ) ];
- if ( $this->redis instanceof Predis\Client ) {
+ if ( $this->is_predis() ) {
$args[] = 'nx';
if ( $expiration ) {
@@ -1034,7 +1430,7 @@ protected function add_or_replace( $add, $key, $value, $group = 'default', $expi
}
}
- $exists = isset( $this->cache[ $derived_key ] );
+ $exists = array_key_exists( $derived_key, $this->cache );
if ( (bool) $add === $exists ) {
return false;
@@ -1054,18 +1450,22 @@ protected function add_or_replace( $add, $key, $value, $group = 'default', $expi
* @param string $group The group value appended to the $key.
* @return bool Returns TRUE on success or FALSE on failure.
*/
- public function delete( $key, $group = 'default' ) {
+ public function delete( $key, $group = 'default', $deprecated = false ) {
$result = false;
- $derived_key = $this->build_key( $key, $group );
- if ( isset( $this->cache[ $derived_key ] ) ) {
+ $san_key = $this->sanitize_key_part( $key );
+ $san_group = $this->sanitize_key_part( $group );
+
+ $derived_key = $this->fast_build_key( $san_key, $san_group );
+
+ if ( array_key_exists( $derived_key, $this->cache ) ) {
unset( $this->cache[ $derived_key ] );
$result = true;
}
$start_time = microtime( true );
- if ( $this->redis_status() && ! $this->is_ignored_group( $group ) ) {
+ if ( $this->redis_status() && ! $this->is_ignored_group( $san_group ) ) {
try {
$result = $this->parse_redis_response( $this->redis->del( $derived_key ) );
} catch ( Exception $exception ) {
@@ -1096,19 +1496,107 @@ public function delete( $key, $group = 'default' ) {
}
/**
- * Invalidate all items in the cache. If `WP_REDIS_SELECTIVE_FLUSH` is `true`,
- * only keys prefixed with the `WP_REDIS_PREFIX` are flushed.
+ * Deletes multiple values from the cache in one call.
*
- * @param int $delay Number of seconds to wait before invalidating the items.
- * @return bool Returns TRUE on success or FALSE on failure.
+ * @param array $keys Array of keys to be deleted.
+ * @param string $group Optional. Where the cache contents are grouped.
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if the contents were not deleted.
*/
- public function flush( $delay = 0 ) {
- $delay = abs( intval( $delay ) );
+ public function delete_multiple( array $keys, $group = 'default' ) {
+ if (
+ $this->redis_status() &&
+ method_exists( $this->redis, 'pipeline' ) &&
+ ! $this->is_ignored_group( $group )
+ ) {
+ return $this->delete_multiple_at_once( $keys, $group );
+ }
+
+ $values = [];
+
+ foreach ( $keys as $key ) {
+ $values[ $key ] = $this->delete( $key, $group );
+ }
+
+ return $values;
+ }
+
+ /**
+ * Deletes multiple values from the cache in one call.
+ *
+ * @param array $keys Array of keys to be deleted.
+ * @param string $group Optional. Where the cache contents are grouped.
+ * @return bool[] Array of return values, grouped by key. Each value is either
+ * true on success, or false if the contents were not deleted.
+ */
+ protected function delete_multiple_at_once( array $keys, $group = 'default' ) {
+ $start_time = microtime( true );
+
+ try {
+ $tx = $this->redis->pipeline();
- if ( $delay ) {
- sleep( $delay );
+ foreach ( $keys as $key ) {
+ $derived_key = $this->build_key( (string) $key, $group );
+
+ $tx->del( $derived_key );
+
+ unset( $this->cache[ $derived_key ] );
+ }
+
+ $method = $this->is_predis() ? 'execute' : 'exec';
+
+ $results = array_map( function ( $response ) {
+ return (bool) $this->parse_redis_response( $response );
+ }, $tx->{$method}() ?: [] );
+
+ if ( count( $results ) !== count( $keys ) ) {
+ $tx->discard();
+
+ return array_fill_keys( $keys, false );
+ }
+
+ $execute_time = microtime( true ) - $start_time;
+ } catch ( Exception $exception ) {
+ $this->handle_exception( $exception );
+
+ return array_combine( $keys, array_fill( 0, count( $keys ), false ) );
+ }
+
+ if ( function_exists( 'do_action' ) ) {
+ foreach ( $keys as $key ) {
+ /**
+ * Fires on every cache key deletion
+ *
+ * @since 1.3.3
+ * @param string $key The cache key.
+ * @param string $group The group value appended to the $key.
+ * @param float $execute_time Execution time for the request in seconds.
+ */
+ do_action( 'redis_object_cache_delete', $key, $group, $execute_time );
+ }
}
+ return array_combine( $keys, $results );
+ }
+
+ /**
+ * Removes all cache items from the in-memory runtime cache.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function flush_runtime() {
+ $this->cache = [];
+
+ return true;
+ }
+
+ /**
+ * Invalidate all items in the cache. If `WP_REDIS_SELECTIVE_FLUSH` is `true`,
+ * only keys prefixed with the `WP_REDIS_PREFIX` are flushed.
+ *
+ * @return bool True on success, false on failure.
+ */
+ public function flush() {
$results = [];
$this->cache = [];
@@ -1173,12 +1661,12 @@ public function flush( $delay = 0 ) {
*
* @since 1.3.5
* @param null|array $results Array of flush results.
- * @param int $delay Given number of seconds to waited before invalidating the items.
+ * @param int $deprecated Unused. Default 0.
* @param bool $seletive Whether a selective flush took place.
* @param string $salt The defined key prefix.
* @param float $execute_time Execution time for the request in seconds.
*/
- do_action( 'redis_object_cache_flush', $results, $delay, $selective, $salt, $execute_time );
+ do_action( 'redis_object_cache_flush', $results, 0, $selective, $salt, $execute_time );
}
}
@@ -1195,6 +1683,81 @@ public function flush( $delay = 0 ) {
return true;
}
+ /**
+ * Removes all cache items in a group.
+ *
+ * @param string $group Name of group to remove from cache.
+ * @return bool Returns TRUE on success or FALSE on failure.
+ */
+ public function flush_group( $group )
+ {
+ $san_group = $this->sanitize_key_part( $group );
+
+ if ( is_multisite() && ! $this->is_global_group( $san_group ) ) {
+ $salt = str_replace( "{$this->blog_prefix}:{$san_group}", "*:{$san_group}", $this->fast_build_key( '*', $san_group ) );
+ } else {
+ $salt = $this->fast_build_key( '*', $san_group );
+ }
+
+ foreach ( $this->cache as $key => $value ) {
+ if ( strpos( $key, "{$san_group}:" ) === 0 || strpos( $key, ":{$san_group}:" ) !== false ) {
+ unset( $this->cache[ $key ] );
+ }
+ }
+
+ if ( in_array( $san_group, $this->unflushable_groups ) ) {
+ return false;
+ }
+
+ if ( ! $this->redis_status() ) {
+ return false;
+ }
+
+ $results = [];
+
+ $start_time = microtime( true );
+ $script = $this->lua_flush_closure( $salt, false );
+
+ try {
+ if ( defined( 'WP_REDIS_CLUSTER' ) ) {
+ foreach ( $this->redis->_masters() as $master ) {
+ $redis = new Redis;
+ $redis->connect( $master[0], $master[1] );
+ $results[] = $this->parse_redis_response( $script() );
+ unset( $redis );
+ }
+ } else {
+ $results[] = $this->parse_redis_response( $script() );
+ }
+ } catch ( Exception $exception ) {
+ $this->handle_exception( $exception );
+
+ return false;
+ }
+
+ if ( function_exists( 'do_action' ) ) {
+ $execute_time = microtime( true ) - $start_time;
+
+ /**
+ * Fires on every group cache flush
+ *
+ * @param null|array $results Array of flush results.
+ * @param string $salt The defined key prefix.
+ * @param float $execute_time Execution time for the request in seconds.
+ * @since 2.2.3
+ */
+ do_action( 'redis_object_cache_flush_group', $results, $salt, $execute_time );
+ }
+
+ foreach ( $results as $result ) {
+ if ( ! $result ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/**
* Returns a closure to flush selectively.
*
@@ -1234,10 +1797,11 @@ function ( $character ) {
* Returns a closure ready to be called to flush selectively ignoring unflushable groups.
*
* @param string $salt The salt to be used to differentiate.
+ * @param bool $escape ...
* @return callable Generated callable executing the lua script.
*/
- protected function lua_flush_closure( $salt ) {
- $salt = $this->glob_quote( $salt );
+ protected function lua_flush_closure( $salt, $escape = true ) {
+ $salt = $escape ? $this->glob_quote( $salt ) : $salt;
return function () use ( $salt ) {
$script = <<redis instanceof Predis\Client )
- ? [ $script, 0 ]
- : [ $script ];
+ $args = $this->is_predis() ? [ $script, 0 ] : [ $script ];
return call_user_func_array( [ $this->redis, 'eval' ], $args );
};
@@ -1315,7 +1877,7 @@ function ( $group ) {
$script = 'redis.replicate_commands()' . "\n" . $script;
}
- $args = ( $this->redis instanceof Predis\Client )
+ $args = $this->is_predis()
? array_merge( [ $script, count( $unflushable ) ], $unflushable )
: [ $script, $unflushable, count( $unflushable ) ];
@@ -1337,13 +1899,16 @@ function ( $group ) {
* @return bool|mixed Cached object value.
*/
public function get( $key, $group = 'default', $force = false, &$found = null ) {
- $derived_key = $this->build_key( $key, $group );
+ $san_key = $this->sanitize_key_part( $key );
+ $san_group = $this->sanitize_key_part( $group );
+ $derived_key = $this->fast_build_key( $san_key, $san_group );
- if ( isset( $this->cache[ $derived_key ] ) && ! $force ) {
+ if ( array_key_exists( $derived_key, $this->cache ) && ! $force ) {
$found = true;
$this->cache_hits++;
+ $value = $this->get_from_internal_cache( $derived_key );
- return $this->get_from_internal_cache( $derived_key );
+ return $value;
} elseif ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) {
$found = false;
$this->cache_misses++;
@@ -1420,7 +1985,7 @@ public function get( $key, $group = 'default', $force = false, &$found = null )
* @param string $group Optional. Where the cache contents are grouped. Default empty.
* @param bool $force Optional. Whether to force an update of the local cache
* from the persistent cache. Default false.
- * @return array Array of values organized into groups.
+ * @return array|false Array of values organized into groups.
*/
public function get_multiple( $keys, $group = 'default', $force = false ) {
if ( ! is_array( $keys ) ) {
@@ -1429,15 +1994,25 @@ public function get_multiple( $keys, $group = 'default', $force = false ) {
$cache = [];
$derived_keys = [];
+ $start_time = microtime( true );
+
+ $san_group = $this->sanitize_key_part( $group );
foreach ( $keys as $key ) {
- $derived_keys[ $key ] = $this->build_key( $key, $group );
+ $san_key = $this->sanitize_key_part( $key );
+ $derived_keys[ $key ] = $this->fast_build_key( $san_key, $san_group );
}
if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) {
foreach ( $keys as $key ) {
- $cache[ $key ] = $this->get_from_internal_cache( $derived_keys[ $key ] );
- $cache[ $key ] === false ? $this->cache_misses++ : $this->cache_hits++;
+ $value = $this->get_from_internal_cache( $derived_keys[ $key ] );
+ $cache[ $key ] = $value;
+
+ if ($value === false) {
+ $this->cache_misses++;
+ } else {
+ $this->cache_hits++;
+ }
}
return $cache;
@@ -1449,6 +2024,7 @@ public function get_multiple( $keys, $group = 'default', $force = false ) {
if ( $value === false ) {
$this->cache_misses++;
+
} else {
$cache[ $key ] = $value;
$this->cache_hits++;
@@ -1459,7 +2035,7 @@ public function get_multiple( $keys, $group = 'default', $force = false ) {
$remaining_keys = array_filter(
$keys,
function ( $key ) use ( $cache ) {
- return ! isset( $cache[ $key ] );
+ return ! array_key_exists( $key, $cache );
}
);
@@ -1468,23 +2044,28 @@ function ( $key ) use ( $cache ) {
}
$start_time = microtime( true );
+ $results = [];
- try {
- $remaining_ids = array_map(
- function ( $key ) use ( $derived_keys ) {
- return $derived_keys[ $key ];
- },
- $remaining_keys
- );
+ $remaining_ids = array_map(
+ function ( $key ) use ( $derived_keys ) {
+ return $derived_keys[ $key ];
+ },
+ $remaining_keys
+ );
+ try {
$results = array_combine(
$remaining_keys,
$this->redis->mget( $remaining_ids )
+ ?: array_fill( 0, count( $remaining_ids ), false )
);
} catch ( Exception $exception ) {
$this->handle_exception( $exception );
- $cache = array_fill( 0, count( $derived_keys ) - 1, false );
+ $results = array_combine(
+ $remaining_keys,
+ array_fill( 0, count( $remaining_ids ), false )
+ );
}
$execute_time = microtime( true ) - $start_time;
@@ -1499,7 +2080,6 @@ function ( $key ) use ( $derived_keys ) {
} else {
$cache[ $key ] = $this->maybe_unserialize( $value );
$this->add_to_internal_cache( $derived_keys[ $key ], $cache[ $key ] );
-
$this->cache_hits++;
}
}
@@ -1509,8 +2089,8 @@ function ( $key ) use ( $derived_keys ) {
* Fires on every cache get multiple request
*
* @since 2.0.6
- * @param mixed $value Value of the cache entry.
- * @param string $key The cache key.
+ * @param array $keys Array of keys under which the cache contents are stored.
+ * @param array $cache Cache items.
* @param string $group The group value appended to the $key.
* @param bool $force Whether a forced refetch has taken place rather than relying on the local cache.
* @param float $execute_time Execution time for the request in seconds.
@@ -1551,10 +2131,13 @@ function ( $key ) use ( $derived_keys ) {
*/
public function set( $key, $value, $group = 'default', $expiration = 0 ) {
$result = true;
- $derived_key = $this->build_key( $key, $group );
-
$start_time = microtime( true );
+ $san_key = $this->sanitize_key_part( $key );
+ $san_group = $this->sanitize_key_part( $group );
+
+ $derived_key = $this->fast_build_key( $san_key, $san_group );
+
// Save if group not excluded from redis and redis is up.
if ( ! $this->is_ignored_group( $group ) && $this->redis_status() ) {
$orig_exp = $expiration;
@@ -1583,8 +2166,9 @@ public function set( $key, $value, $group = 'default', $expiration = 0 ) {
return false;
}
+ $execute_time = microtime( true ) - $start_time;
$this->cache_calls++;
- $this->cache_time += ( microtime( true ) - $start_time );
+ $this->cache_time += $execute_time;
}
// If the set was successful, or we didn't go to redis.
@@ -1611,6 +2195,126 @@ public function set( $key, $value, $group = 'default', $expiration = 0 ) {
return $result;
}
+ /**
+ * Sets multiple values to the cache in one call.
+ *
+ * @param array $data Array of key and value to be set.
+ * @param string $group Optional. Where the cache contents are grouped.
+ * @param int $expire Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is always true.
+ */
+ public function set_multiple( array $data, $group = 'default', $expire = 0 ) {
+ if (
+ $this->redis_status() &&
+ method_exists( $this->redis, 'pipeline' ) &&
+ ! $this->is_ignored_group( $group )
+ ) {
+ return $this->set_multiple_at_once( $data, $group, $expire );
+ }
+
+ $values = [];
+
+ foreach ( $data as $key => $value ) {
+ $values[ $key ] = $this->set( $key, $value, $group, $expire );
+ }
+
+ return $values;
+ }
+
+ /**
+ * Sets multiple values to the cache in one call.
+ *
+ * @param array $data Array of key and value to be set.
+ * @param string $group Optional. Where the cache contents are grouped.
+ * @param int $expiration Optional. When to expire the cache contents, in seconds.
+ * Default 0 (no expiration).
+ * @return bool[] Array of return values, grouped by key. Each value is always true.
+ */
+ protected function set_multiple_at_once( array $data, $group = 'default', $expiration = 0 )
+ {
+ $start_time = microtime( true );
+
+ $san_group = $this->sanitize_key_part( $group );
+ $derived_keys = [];
+
+ $orig_exp = $expiration;
+ $expiration = $this->validate_expiration( $expiration );
+ $expirations = [];
+
+ $tx = $this->redis->pipeline();
+ $keys = array_keys( $data );
+
+ foreach ( $data as $key => $value ) {
+ $san_key = $this->sanitize_key_part( $key );
+ $derived_key = $derived_keys[ $key ] = $this->fast_build_key( $san_key, $san_group );
+
+ /**
+ * Filters the cache expiration time
+ *
+ * @param int $expiration The time in seconds the entry expires. 0 for no expiry.
+ * @param string $key The cache key.
+ * @param string $group The cache group.
+ * @param mixed $orig_exp The original expiration value before validation.
+ */
+ $expiration = $expirations[ $key ] = apply_filters( 'redis_cache_expiration', $expiration, $key, $group, $orig_exp );
+
+ if ( $expiration ) {
+ $tx->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) );
+ } else {
+ $tx->set( $derived_key, $this->maybe_serialize( $value ) );
+ }
+ }
+
+ try {
+ $method = $this->is_predis() ? 'execute' : 'exec';
+
+ $results = array_map( function ( $response ) {
+ return (bool) $this->parse_redis_response( $response );
+ }, $tx->{$method}() ?: [] );
+
+ if ( count( $results ) !== count( $keys ) ) {
+ $tx->discard();
+
+ return array_fill_keys( $keys, false );
+ }
+
+ $results = array_combine( $keys, $results );
+
+ foreach ( $results as $key => $result ) {
+ if ( $result ) {
+ $this->add_to_internal_cache( $derived_keys[ $key ], $data[ $key ] );
+ }
+ }
+ } catch ( Exception $exception ) {
+ $this->handle_exception( $exception );
+
+ return array_combine( $keys, array_fill( 0, count( $keys ), false ) );
+ }
+
+ $execute_time = microtime( true ) - $start_time;
+
+ $this->cache_calls++;
+ $this->cache_time += $execute_time;
+
+ if ( function_exists( 'do_action' ) ) {
+ foreach ( $data as $key => $value ) {
+ /**
+ * Fires on every cache set
+ *
+ * @param string $key The cache key.
+ * @param mixed $value Value of the cache entry.
+ * @param string $group The group value appended to the $key.
+ * @param int $expiration The time in seconds the entry expires. 0 for no expiry.
+ * @param float $execute_time Execution time for the request in seconds.
+ */
+ do_action( 'redis_object_cache_set', $key, $value, $group, $expirations[ $key ], $execute_time );
+ }
+ }
+
+ return $results;
+ }
+
/**
* Increment a Redis counter by the amount specified
*
@@ -1621,7 +2325,12 @@ public function set( $key, $value, $group = 'default', $expiration = 0 ) {
*/
public function increment( $key, $offset = 1, $group = 'default' ) {
$offset = (int) $offset;
- $derived_key = $this->build_key( $key, $group );
+ $start_time = microtime( true );
+
+ $san_key = $this->sanitize_key_part( $key );
+ $san_group = $this->sanitize_key_part( $group );
+
+ $derived_key = $this->fast_build_key( $san_key, $san_group );
// If group is a non-Redis group, save to internal cache, not Redis.
if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) {
@@ -1632,8 +2341,6 @@ public function increment( $key, $offset = 1, $group = 'default' ) {
return $value;
}
- $start_time = microtime( true );
-
try {
$result = $this->parse_redis_response( $this->redis->incrBy( $derived_key, $offset ) );
@@ -1674,8 +2381,13 @@ public function incr( $key, $offset = 1, $group = 'default' ) {
* @return int|bool
*/
public function decrement( $key, $offset = 1, $group = 'default' ) {
- $derived_key = $this->build_key( $key, $group );
$offset = (int) $offset;
+ $start_time = microtime( true );
+
+ $san_key = $this->sanitize_key_part( $key );
+ $san_group = $this->sanitize_key_part( $group );
+
+ $derived_key = $this->fast_build_key( $san_key, $san_group );
// If group is a non-Redis group, save to internal cache, not Redis.
if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) {
@@ -1686,10 +2398,7 @@ public function decrement( $key, $offset = 1, $group = 'default' ) {
return $value;
}
- $start_time = microtime( true );
-
try {
- // Save to Redis.
$result = $this->parse_redis_response( $this->redis->decrBy( $derived_key, $offset ) );
$this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) );
@@ -1707,6 +2416,19 @@ public function decrement( $key, $offset = 1, $group = 'default' ) {
return $result;
}
+ /**
+ * Alias of `decrement()`.
+ *
+ * @see self::decrement()
+ * @param string $key The key name.
+ * @param int $offset Optional. The decrement. Defaults to 1.
+ * @param string $group Optional. The key group. Default is 'default'.
+ * @return int|bool
+ */
+ public function decr( $key, $offset = 1, $group = 'default' ) {
+ return $this->decrement( $key, $offset, $group );
+ }
+
/**
* Render data about current cache requests
* Used by the Debug bar plugin
@@ -1723,13 +2445,13 @@ public function stats() {
diagnostics['client'] ?: 'Unknown'; ?>
Cache Hits:
- cache_hits ); ?>
+ cache_hits; ?>
Cache Misses:
- cache_misses ); ?>
+ cache_misses; ?>
Cache Size:
- cache ) ) / 1024, 2 ); ?> kB
+ cache ) ) / 1024, 2 ); ?> KB
'...',
'hits' => $this->cache_hits,
'misses' => $this->cache_misses,
'ratio' => $total > 0 ? round( $this->cache_hits / ( $total / 100 ), 1 ) : 100,
@@ -1766,7 +2486,7 @@ function ( $keys ) {
],
'errors' => empty( $this->errors ) ? null : $this->errors,
'meta' => [
- 'Client' => $this->diagnostics['client'] ?: 'Unknown',
+ 'Client' => $this->diagnostics['client'] ?? 'Unknown',
'Redis Version' => $this->redis_version,
],
];
@@ -1775,8 +2495,8 @@ function ( $keys ) {
/**
* Builds a key for the cached object using the prefix, group and key.
*
- * @param string $key The key under which to store the value.
- * @param string $group The group value appended to the $key.
+ * @param string $key The key under which to store the value, pre-sanitized.
+ * @param string $group The group value appended to the $key, pre-sanitized.
*
* @return string
*/
@@ -1785,12 +2505,28 @@ public function build_key( $key, $group = 'default' ) {
$group = 'default';
}
- $salt = defined( 'WP_REDIS_PREFIX' ) ? trim( WP_REDIS_PREFIX ) : '';
- $prefix = $this->is_global_group( $group ) ? $this->global_prefix : $this->blog_prefix;
+ $san_key = $this->sanitize_key_part( $key );
+ $san_group = $this->sanitize_key_part( $group );
+
+ return $this->fast_build_key($san_key, $san_group);
+ }
+
+ /**
+ * Builds a key for the cached object using the prefix, group and key.
+ *
+ * @param string $key The key under which to store the value, pre-sanitized.
+ * @param string $group The group value appended to the $key, pre-sanitized.
+ *
+ * @return string
+ */
+ public function fast_build_key( $key, $group = 'default' ) {
+ if ( empty( $group ) ) {
+ $group = 'default';
+ }
- $key = $this->sanitize_key_part( $key );
- $group = $this->sanitize_key_part( $group );
+ $salt = defined( 'WP_REDIS_PREFIX' ) ? trim( WP_REDIS_PREFIX ) : '';
+ $prefix = $this->is_global_group( $group ) ? $this->global_prefix : $this->blog_prefix;
$prefix = trim( $prefix, '_-:$' );
return "{$salt}{$prefix}:{$group}:{$key}";
@@ -1809,31 +2545,43 @@ protected function sanitize_key_part( $part ) {
/**
* Checks if the given group is part the ignored group array
*
- * @param string $group Name of the group to check.
+ * @param string $group Name of the group to check, pre-sanitized.
* @return bool
*/
protected function is_ignored_group( $group ) {
- return in_array( $this->sanitize_key_part( $group ), $this->ignored_groups, true );
+ return $this->is_group_of_type( $group, 'ignored' );
}
/**
* Checks if the given group is part the global group array
*
- * @param string $group Name of the group to check.
+ * @param string $group Name of the group to check, pre-sanitized.
* @return bool
*/
protected function is_global_group( $group ) {
- return in_array( $this->sanitize_key_part( $group ), $this->global_groups, true );
+ return $this->is_group_of_type( $group, 'global' );
}
/**
* Checks if the given group is part the unflushable group array
*
- * @param string $group Name of the group to check.
+ * @param string $group Name of the group to check, pre-sanitized.
* @return bool
*/
protected function is_unflushable_group( $group ) {
- return in_array( $this->sanitize_key_part( $group ), $this->unflushable_groups, true );
+ return $this->is_group_of_type( $group, 'unflushable' );
+ }
+
+ /**
+ * Checks the type of the given group
+ *
+ * @param string $group Name of the group to check, pre-sanitized.
+ * @param string $type Type of the group to check.
+ * @return bool
+ */
+ private function is_group_of_type( $group, $type ) {
+ return isset( $this->group_type[ $group ] )
+ && $this->group_type[ $group ] == $type;
}
/**
@@ -1880,7 +2628,7 @@ public function add_to_internal_cache( $derived_key, $value ) {
* @return bool|mixed Value on success; false on failure.
*/
public function get_from_internal_cache( $derived_key ) {
- if ( ! isset( $this->cache[ $derived_key ] ) ) {
+ if ( ! array_key_exists( $derived_key, $this->cache ) ) {
return false;
}
@@ -1902,7 +2650,7 @@ public function switch_to_blog( $_blog_id ) {
return false;
}
- $this->blog_prefix = $_blog_id;
+ $this->blog_prefix = (int) $_blog_id;
return true;
}
@@ -1920,6 +2668,8 @@ public function add_global_groups( $groups ) {
} else {
$this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
}
+
+ $this->cache_group_types();
}
/**
@@ -1928,9 +2678,16 @@ public function add_global_groups( $groups ) {
* @param array $groups List of groups that are to be ignored.
*/
public function add_non_persistent_groups( $groups ) {
- $groups = (array) $groups;
+ /**
+ * Filters list of groups to be added to {@see self::$ignored_groups}
+ *
+ * @since 2.1.7
+ * @param string[] $groups List of groups to be ignored.
+ */
+ $groups = apply_filters( 'redis_cache_add_non_persistent_groups', (array) $groups );
$this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
+ $this->cache_group_types();
}
/**
@@ -1942,6 +2699,7 @@ public function add_unflushable_groups( $groups ) {
$groups = (array) $groups;
$this->unflushable_groups = array_unique( array_merge( $this->unflushable_groups, $groups ) );
+ $this->cache_group_types();
}
/**
@@ -1950,7 +2708,7 @@ public function add_unflushable_groups( $groups ) {
* @param mixed $expiration Incoming expiration value (whatever it is).
*/
protected function validate_expiration( $expiration ) {
- $expiration = is_int( $expiration ) || ctype_digit( $expiration ) ? (int) $expiration : 0;
+ $expiration = is_int( $expiration ) || ctype_digit( (string) $expiration ) ? (int) $expiration : 0;
if ( defined( 'WP_REDIS_MAXTTL' ) ) {
$max = (int) WP_REDIS_MAXTTL;
@@ -2115,14 +2873,14 @@ protected function handle_exception( $exception ) {
// When Redis is unavailable, fall back to the internal cache by forcing all groups to be "no redis" groups.
$this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $this->global_groups ) );
+ error_log( $exception ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+
if ( ! $this->fail_gracefully ) {
- throw $exception;
+ $this->show_error_and_die( $exception );
}
$this->errors[] = $exception->getMessage();
- error_log( $exception ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
-
if ( function_exists( 'do_action' ) ) {
/**
* Fires on every cache error
@@ -2134,6 +2892,91 @@ protected function handle_exception( $exception ) {
}
}
+ /**
+ * Show Redis connection error screen, or load custom `/redis-error.php`.
+ *
+ * @return void
+ */
+ protected function show_error_and_die( Exception $exception ) {
+ wp_load_translations_early();
+
+ add_filter( 'pre_determine_locale', function () {
+ return defined( 'WPLANG' ) ? WPLANG : 'en_US';
+ } );
+
+ // Load custom DB error template, if present.
+ if ( file_exists( WP_CONTENT_DIR . '/redis-error.php' ) ) {
+ require_once WP_CONTENT_DIR . '/redis-error.php';
+ die();
+ }
+
+ $verbose = wp_installing()
+ || defined( 'WP_ADMIN' )
+ || ( defined( 'WP_DEBUG' ) && WP_DEBUG );
+
+ $message = '' . __( 'Error establishing a Redis connection', 'redis-cache' ) . "
\n";
+
+ if ( $verbose ) {
+ $message .= "" . $exception->getMessage() . "
\n";
+
+ $message .= '' . sprintf(
+ // translators: %s = Formatted wp-config.php file name.
+ __( 'WordPress is unable to establish a connection to Redis. This means that the connection information in your %s file are incorrect, or that the Redis server is not reachable.', 'redis-cache' ),
+ 'wp-config.php
'
+ ) . "
\n";
+
+ $message .= "\n";
+ $message .= '- ' . __( 'Is the correct Redis host and port set?', 'redis-cache' ) . "
\n";
+ $message .= '- ' . __( 'Is the Redis server running?', 'redis-cache' ) . "
\n";
+ $message .= "
\n";
+
+ $message .= '' . sprintf(
+ // translators: %s = Link to installation instructions.
+ __( 'If you need help, please read the installation instructions.', 'redis-cache' ),
+ 'https://github.com/rhubarbgroup/redis-cache/blob/develop/INSTALL.md'
+ ) . "
\n";
+ }
+
+ $message .= '' . sprintf(
+ // translators: %1$s = Formatted object-cache.php file name, %2$s = Formatted wp-content directory name.
+ __( 'To disable Redis, delete the %1$s file in the %2$s directory.', 'redis-cache' ),
+ 'object-cache.php
',
+ '/wp-content/
'
+ ) . "
\n";
+
+ wp_die( $message );
+ }
+
+ /**
+ * Builds a clean connection array out of redis clusters array.
+ *
+ * @return array
+ */
+ protected function build_cluster_connection_array() {
+ $cluster = array_values( WP_REDIS_CLUSTER );
+
+ foreach ( $cluster as $key => $server ) {
+ $connection_string = parse_url( $server );
+
+ $cluster[ $key ] = sprintf(
+ "%s:%s",
+ $connection_string['host'],
+ $connection_string['port']
+ );
+ }
+
+ return $cluster;
+ }
+
+ /**
+ * Check whether Predis client is in use.
+ *
+ * @return bool
+ */
+ protected function is_predis() {
+ return $this->redis instanceof Predis\Client;
+ }
+
/**
* Allows access to private properties for backwards compatibility.
*