Plugin Directory

Changeset 2694612


Ignore:
Timestamp:
03/16/2022 06:15:35 AM (4 years ago)
Author:
baseapp
Message:

Minor Updates and Bug Fixes , Removal of Error Notices for Wordpress 5.9 .

Location:
wpbase-cache/trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • wpbase-cache/trunk/includes/object-cache.php

    r2321389 r2694612  
    11<?php
    2 /*
    3 Plugin Name: Redis Object Cache Drop-In
    4 Plugin URI: http://wordpress.org/plugins/redis-cache/
    5 Description: A persistent object cache backend powered by Redis. Supports Predis, PhpRedis, HHVM, replication, clustering and WP-CLI.
    6 Version: 1.3.5
    7 Author: Till Krüss
    8 Author URI: https://till.im/
    9 License: GPLv3
    10 License URI: http://www.gnu.org/licenses/gpl-3.0.html
    11 
    12 Based on Eric Mann's and Erick Hitter's Redis Object Cache:
    13 https://github.com/ericmann/Redis-Object-Cache
    14 */
    15 
     2/**
     3 * Plugin Name: Redis Object Cache Drop-In
     4 * Plugin URI: http://wordpress.org/plugins/redis-cache/
     5 * Description: A persistent object cache backend powered by Redis. Supports Predis, PhpRedis, Credis, HHVM, replication, clustering and WP-CLI.
     6 * Version: 2.0.24-dev
     7 * Author: Till Krüss
     8 * Author URI: https://objectcache.pro
     9 * License: GPLv3
     10 * License URI: http://www.gnu.org/licenses/gpl-3.0.html
     11 *
     12 * @package Rhubarb\RedisCache
     13 */
     14
     15defined( '\\ABSPATH' ) || exit;
     16
     17// phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact, Generic.WhiteSpace.ScopeIndent.Incorrect
    1618if ( ! defined( 'WP_REDIS_DISABLED' ) || ! WP_REDIS_DISABLED ) :
    17 define('WP_CACHE_KEY_SALT', DB_NAME);
     19define('WP_CACHE_KEY_SALT', DB_NAME);   
     20
     21
    1822/**
    1923 * Adds a value to cache.
     
    2731 * @param int    $expiration The expiration time, defaults to 0.
    2832 *
    29  * @global WP_Object_Cache $wp_object_cache
    30  *
    3133 * @return bool              Returns TRUE on success or FALSE on failure.
    3234 */
    3335function wp_cache_add( $key, $value, $group = '', $expiration = 0 ) {
     36    global $wp_object_cache;
     37
     38    return $wp_object_cache->add( $key, $value, $group, $expiration );
     39}
     40
     41/**
     42 * Adds multiple values to the cache in one call.
     43 *
     44 * @param array  $data   Array of keys and values to be set.
     45 * @param string $group  Optional. Where the cache contents are grouped. Default empty.
     46 * @param int    $expire Optional. When to expire the cache contents, in seconds.
     47 *                       Default 0 (no expiration).
     48 * @return bool[] Array of return values, grouped by key. Each value is either
     49 *                true on success, or false if cache key and group already exist.
     50 */
     51function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) {
    3452    global $wp_object_cache;
    3553
    36     return $wp_object_cache->add( $key, $value, $group, $expiration );
     54    return $wp_object_cache->add_multiple( $data, $group, $expire );
    3755}
    3856
     
    4866 */
    4967function wp_cache_close() {
    50     return true;
     68    return true;
    5169}
    5270
     
    5876 * @param string $group  The group value appended to the $key.
    5977 *
    60  * @global WP_Object_Cache $wp_object_cache
    61  *
    6278 * @return int|bool      Returns item's new value on success or FALSE on failure.
    6379 */
    6480function wp_cache_decr( $key, $offset = 1, $group = '' ) {
    65     global $wp_object_cache;
    66 
    67     return $wp_object_cache->decrement( $key, $offset, $group );
     81    global $wp_object_cache;
     82
     83    return $wp_object_cache->decrement( $key, $offset, $group );
    6884}
    6985
     
    7591 * @param int    $time   The amount of time the server will wait to delete the item in seconds.
    7692 *
    77  * @global WP_Object_Cache $wp_object_cache
    78  *
    79  * @return bool           Returns TRUE on success or FALSE on failure.
     93 * @return bool          Returns TRUE on success or FALSE on failure.
    8094 */
    8195function wp_cache_delete( $key, $group = '', $time = 0 ) {
     96    global $wp_object_cache;
     97
     98    return $wp_object_cache->delete( $key, $group, $time );
     99}
     100
     101/**
     102 * Deletes multiple values from the cache in one call.
     103 *
     104 * @param array  $keys  Array of keys under which the cache to deleted.
     105 * @param string $group Optional. Where the cache contents are grouped. Default empty.
     106 * @return bool[] Array of return values, grouped by key. Each value is either
     107 *                true on success, or false if the contents were not deleted.
     108 */
     109function wp_cache_delete_multiple( array $keys, $group = '' ) {
    82110    global $wp_object_cache;
    83111
    84     return $wp_object_cache->delete( $key, $group, $time );
     112    return $wp_object_cache->delete_multiple( $keys, $group );
    85113}
    86114
    87115/**
    88  * Invalidate all items in the cache.
     116 * Invalidate all items in the cache. If `WP_REDIS_SELECTIVE_FLUSH` is `true`,
     117 * only keys prefixed with the `WP_REDIS_PREFIX` are flushed.
    89118 *
    90119 * @param int $delay  Number of seconds to wait before invalidating the items.
    91120 *
    92  * @global WP_Object_Cache $wp_object_cache
    93  *
    94  * @return bool             Returns TRUE on success or FALSE on failure.
     121 * @return bool       Returns TRUE on success or FALSE on failure.
    95122 */
    96123function wp_cache_flush( $delay = 0 ) {
    97     global $wp_object_cache;
    98 
    99     return $wp_object_cache->flush( $delay );
     124    global $wp_object_cache;
     125
     126    return $wp_object_cache->flush( $delay );
     127}
     128
     129/**
     130 * Removes all cache items from the in-memory runtime cache.
     131 *
     132 * @return bool True on success, false on failure.
     133 */
     134function wp_cache_flush_runtime() {
     135    global $wp_object_cache;
     136
     137    return $wp_object_cache->flush_runtime();
    100138}
    101139
     
    105143 * Gets an object from cache based on $key and $group.
    106144 *
    107  * @param string      $key        The key under which to store the value.
    108  * @param string      $group      The group value appended to the $key.
    109  * @param bool        $force      Optional. Whether to force an update of the local cache from the persistent
    110  *                                cache. Default false.
    111  * @param bool        &$found     Optional. Whether the key was found in the cache. Disambiguates a return of false,
    112  *                                a storable value. Passed by reference. Default null.
    113  *
    114  * @global WP_Object_Cache $wp_object_cache
    115  *
    116  * @return bool|mixed             Cached object value.
     145 * @param string $key        The key under which to store the value.
     146 * @param string $group      The group value appended to the $key.
     147 * @param bool   $force      Optional. Whether to force an update of the local cache from the persistent
     148 *                           cache. Default false.
     149 * @param bool   $found      Optional. Whether the key was found in the cache. Disambiguates a return of false,
     150 *                           a storable value. Passed by reference. Default null.
     151 *
     152 * @return bool|mixed        Cached object value.
    117153 */
    118154function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
    119     global $wp_object_cache;
    120 
    121     return $wp_object_cache->get( $key, $group, $force, $found );
     155    global $wp_object_cache;
     156
     157    return $wp_object_cache->get( $key, $group, $force, $found );
    122158}
    123159
    124160/**
    125  * Retrieve multiple values from cache.
    126  *
    127  * Gets multiple values from cache, including across multiple groups
    128  *
    129  * Usage: array( 'group0' => array( 'key0', 'key1', 'key2', ), 'group1' => array( 'key0' ) )
    130  *
    131  * Mirrors the Memcached Object Cache plugin's argument and return-value formats
    132  *
    133  * @param   array       $groups  Array of groups and keys to retrieve
    134  *
    135  * @global WP_Object_Cache $wp_object_cache
    136  *
    137  * @return  bool|mixed           Array of cached values, keys in the format $group:$key. Non-existent keys false
     161 * Retrieves multiple values from the cache in one call.
     162 *
     163 * @param array  $keys  Array of keys under which the cache contents are stored.
     164 * @param string $group Optional. Where the cache contents are grouped. Default empty.
     165 * @param bool   $force Optional. Whether to force an update of the local cache
     166 *                      from the persistent cache. Default false.
     167 * @return array Array of values organized into groups.
    138168 */
    139 function wp_cache_get_multi( $groups ) {
    140     global $wp_object_cache;
    141 
    142     return $wp_object_cache->get_multi( $groups );
     169function wp_cache_get_multiple( $keys, $group = '', $force = false ) {
     170    global $wp_object_cache;
     171
     172    return $wp_object_cache->get_multiple( $keys, $group, $force );
    143173}
    144174
     
    150180 * @param string $group  The group value appended to the $key.
    151181 *
    152  * @global WP_Object_Cache $wp_object_cache
    153  *
    154182 * @return int|bool      Returns item's new value on success or FALSE on failure.
    155183 */
    156184function wp_cache_incr( $key, $offset = 1, $group = '' ) {
    157     global $wp_object_cache;
    158 
    159     return $wp_object_cache->increment( $key, $offset, $group );
     185    global $wp_object_cache;
     186
     187    return $wp_object_cache->increment( $key, $offset, $group );
    160188}
    161189
    162190/**
    163191 * Sets up Object Cache Global and assigns it.
    164  *
    165  * @global  WP_Object_Cache $wp_object_cache    WordPress Object Cache
    166192 *
    167193 * @return  void
    168194 */
    169195function wp_cache_init() {
    170     global $wp_object_cache;
    171 
    172     if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) {
    173         $wp_object_cache = new WP_Object_Cache;
    174     }
     196    global $wp_object_cache;
     197
     198    if ( ! defined( 'WP_REDIS_PREFIX' ) && getenv( 'WP_REDIS_PREFIX' ) ) {
     199        define( 'WP_REDIS_PREFIX', getenv( 'WP_REDIS_PREFIX' ) );
     200    }
     201
     202    if ( ! defined( 'WP_REDIS_SELECTIVE_FLUSH' ) && getenv( 'WP_REDIS_SELECTIVE_FLUSH' ) ) {
     203        define( 'WP_REDIS_SELECTIVE_FLUSH', (bool) getenv( 'WP_REDIS_SELECTIVE_FLUSH' ) );
     204    }
     205
     206    // Backwards compatibility: map `WP_CACHE_KEY_SALT` constant to `WP_REDIS_PREFIX`.
     207    if ( defined( 'WP_CACHE_KEY_SALT' ) && ! defined( 'WP_REDIS_PREFIX' ) ) {
     208        define( 'WP_REDIS_PREFIX', WP_CACHE_KEY_SALT );
     209    }
     210
     211    // Set unique prefix for sites hosted on Cloudways
     212    if ( ! defined( 'WP_REDIS_PREFIX' ) && isset( $_SERVER['cw_allowed_ip'] ) )  {
     213        define( 'WP_REDIS_PREFIX', getenv( 'HTTP_X_APP_USER' ) );
     214    }
     215
     216    if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) {
     217        $fail_gracefully = ! defined( 'WP_REDIS_GRACEFUL' ) || WP_REDIS_GRACEFUL;
     218
     219        // We need to override this WordPress global in order to inject our cache.
     220        // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
     221        $wp_object_cache = new WP_Object_Cache( $fail_gracefully );
     222    }
    175223}
    176224
     
    186234 * @param int    $expiration The expiration time, defaults to 0.
    187235 *
    188  * @global WP_Object_Cache $wp_object_cache
    189  *
    190236 * @return bool              Returns TRUE on success or FALSE on failure.
    191237 */
    192238function wp_cache_replace( $key, $value, $group = '', $expiration = 0 ) {
    193     global $wp_object_cache;
    194 
    195     return $wp_object_cache->replace( $key, $value, $group, $expiration );
     239    global $wp_object_cache;
     240
     241    return $wp_object_cache->replace( $key, $value, $group, $expiration );
    196242}
    197243
     
    206252 * @param int    $expiration The expiration time, defaults to 0.
    207253 *
    208  * @global WP_Object_Cache $wp_object_cache
    209  *
    210254 * @return bool              Returns TRUE on success or FALSE on failure.
    211255 */
    212256function wp_cache_set( $key, $value, $group = '', $expiration = 0 ) {
     257    global $wp_object_cache;
     258
     259    return $wp_object_cache->set( $key, $value, $group, $expiration );
     260}
     261
     262/**
     263 * Sets multiple values to the cache in one call.
     264 *
     265 * @param array  $data   Array of keys and values to be set.
     266 * @param string $group  Optional. Where the cache contents are grouped. Default empty.
     267 * @param int    $expire Optional. When to expire the cache contents, in seconds.
     268 *                       Default 0 (no expiration).
     269 * @return bool[] Array of return values, grouped by key. Each value is either
     270 *                true on success, or false on failure.
     271 */
     272function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) {
    213273    global $wp_object_cache;
    214274
    215     return $wp_object_cache->set( $key, $value, $group, $expiration );
     275    return $wp_object_cache->set_multiple( $data, $group, $expire );
    216276}
    217277
    218278/**
    219  * Switch the interal blog id.
     279 * Switch the internal blog id.
    220280 *
    221281 * This changes the blog id used to create keys in blog specific groups.
    222282 *
    223  * @param  int $_blog_id Blog ID
    224  *
    225  * @global WP_Object_Cache $wp_object_cache
     283 * @param  int $_blog_id The blog ID.
    226284 *
    227285 * @return bool
    228286 */
    229287function wp_cache_switch_to_blog( $_blog_id ) {
    230     global $wp_object_cache;
    231 
    232     return $wp_object_cache->switch_to_blog( $_blog_id );
     288    global $wp_object_cache;
     289
     290    return $wp_object_cache->switch_to_blog( $_blog_id );
    233291}
    234292
     
    237295 *
    238296 * @param   string|array $groups     A group or an array of groups to add.
    239  *
    240  * @global WP_Object_Cache $wp_object_cache
    241297 *
    242298 * @return  void
    243299 */
    244300function wp_cache_add_global_groups( $groups ) {
    245     global $wp_object_cache;
    246 
    247     $wp_object_cache->add_global_groups( $groups );
     301    global $wp_object_cache;
     302
     303    $wp_object_cache->add_global_groups( $groups );
    248304}
    249305
     
    252308 *
    253309 * @param   string|array $groups     A group or an array of groups to add.
    254  *
    255  * @global WP_Object_Cache $wp_object_cache
    256310 *
    257311 * @return  void
    258312 */
    259313function wp_cache_add_non_persistent_groups( $groups ) {
    260     global $wp_object_cache;
    261 
    262     $wp_object_cache->add_non_persistent_groups( $groups );
     314    global $wp_object_cache;
     315
     316    $wp_object_cache->add_non_persistent_groups( $groups );
    263317}
    264318
     319/**
     320 * Object cache class definition
     321 */
    265322class WP_Object_Cache {
    266 
    267     /**
    268      * The Redis client.
     323    /**
     324     * Operation pertains to internal cache, not Redis.
     325     *
     326     * @since 2.0.18
     327     * @var int
     328     */
     329    const TRACE_FLAG_INTERNAL = 1 << 0;
     330    /**
     331     * Operation resulted in a cache hit.
     332     *
     333     * @since 2.0.18
     334     * @var int
     335     */
     336    const TRACE_FLAG_HIT = 1 << 1;
     337    /**
     338     * Read operation.
     339     *
     340     * @since 2.0.18
     341     * @var int
     342     */
     343    const TRACE_FLAG_READ = 1 << 2;
     344    /**
     345     * Write operation.
     346     *
     347     * @since 2.0.18
     348     * @var int
     349     */
     350    const TRACE_FLAG_WRITE = 1 << 3;
     351    /**
     352     * Delete operation.
     353     *
     354     * @since 2.0.18
     355     * @var int
     356     */
     357    const TRACE_FLAG_DEL = 1 << 4;
     358    /**
     359     * Operation bypassed internal cache.
     360     *
     361     * @since 2.0.18
     362     * @var int
     363     */
     364    const TRACE_FLAG_REFRESH = 1 << 5;
     365
     366    /**
     367     * The Redis client.
     368     *
     369     * @var mixed
     370     */
     371    private $redis;
     372
     373    /**
     374     * The Redis server version.
     375     *
     376     * @var null|string
     377     */
     378    private $redis_version = null;
     379
     380    /**
     381     * Track if Redis is available.
     382     *
     383     * @var bool
     384     */
     385    private $redis_connected = false;
     386
     387    /**
     388     * Check to fail gracefully or throw an exception.
     389     *
     390     * @var bool
     391     */
     392    private $fail_gracefully = true;
     393
     394    /**
     395     * Holds the non-Redis objects.
     396     *
     397     * @var array
     398     */
     399    public $cache = [];
     400
     401    /**
     402     * Holds the diagnostics values.
     403     *
     404     * @var array
     405     */
     406    public $diagnostics = null;
     407
     408    /**
     409     * Holds the error messages.
     410     *
     411     * @var array
     412     */
     413    public $trace_enabled = false;
     414
     415    /**
     416     * Holds the error messages.
     417     *
     418     * @var array
     419     */
     420    public $errors = [];
     421
     422    /**
     423     * List of global groups.
     424     *
     425     * @var array
     426     */
     427    public $global_groups = [
     428        'blog-details',
     429        'blog-id-cache',
     430        'blog-lookup',
     431        'global-posts',
     432        'networks',
     433        'rss',
     434        'sites',
     435        'site-details',
     436        'site-lookup',
     437        'site-options',
     438        'site-transient',
     439        'users',
     440        'useremail',
     441        'userlogins',
     442        'usermeta',
     443        'user_meta',
     444        'userslugs',
     445    ];
     446
     447    /**
     448     * List of groups that will not be flushed.
     449     *
     450     * @var array
     451     */
     452    public $unflushable_groups = [];
     453
     454    /**
     455     * List of groups not saved to Redis.
     456     *
     457     * @var array
     458     */
     459    public $ignored_groups = [
     460        'counts',
     461        'plugins',
     462        'themes',
     463    ];
     464
     465    /**
     466     * Prefix used for global groups.
     467     *
     468     * @var string
     469     */
     470    public $global_prefix = '';
     471
     472    /**
     473     * Prefix used for non-global groups.
     474     *
     475     * @var string
     476     */
     477    public $blog_prefix = '';
     478
     479    /**
     480     * Track how many requests were found in cache.
     481     *
     482     * @var int
     483     */
     484    public $cache_hits = 0;
     485
     486    /**
     487     * Track how may requests were not cached.
     488     *
     489     * @var int
     490     */
     491    public $cache_misses = 0;
     492
     493    /**
     494     * Track how long request took.
     495     *
     496     * @var float
     497     */
     498    public $cache_time = 0;
     499
     500    /**
     501     * Track how may calls were made.
     502     *
     503     * @var int
     504     */
     505    public $cache_calls = 0;
     506
     507    /**
     508     * Instantiate the Redis class.
     509     *
     510     * @param bool $fail_gracefully Handles and logs errors if true throws exceptions otherwise.
     511     */
     512    public function __construct( $fail_gracefully = true ) {
     513        global $blog_id, $table_prefix;
     514
     515        $this->fail_gracefully = $fail_gracefully;
     516
     517        if ( defined( 'WP_REDIS_GLOBAL_GROUPS' ) && is_array( WP_REDIS_GLOBAL_GROUPS ) ) {
     518            $this->global_groups = array_map( [ $this, 'sanitize_key_part' ], WP_REDIS_GLOBAL_GROUPS );
     519        }
     520
     521        $this->global_groups[] = 'redis-cache';
     522
     523        if ( defined( 'WP_REDIS_IGNORED_GROUPS' ) && is_array( WP_REDIS_IGNORED_GROUPS ) ) {
     524            $this->ignored_groups = array_map( [ $this, 'sanitize_key_part' ], WP_REDIS_IGNORED_GROUPS );
     525        }
     526
     527        if ( defined( 'WP_REDIS_UNFLUSHABLE_GROUPS' ) && is_array( WP_REDIS_UNFLUSHABLE_GROUPS ) ) {
     528            $this->unflushable_groups = array_map( [ $this, 'sanitize_key_part' ], WP_REDIS_UNFLUSHABLE_GROUPS );
     529        }
     530
     531        if ( defined( 'WP_REDIS_TRACE' ) && WP_REDIS_TRACE ) {
     532            $this->trace_enabled = true;
     533        }
     534
     535        $client = $this->determine_client();
     536        $parameters = $this->build_parameters();
     537
     538        try {
     539            switch ( $client ) {
     540                case 'hhvm':
     541                    $this->connect_using_hhvm( $parameters );
     542                    break;
     543                case 'phpredis':
     544                    $this->connect_using_phpredis( $parameters );
     545                    break;
     546                case 'relay':
     547                    $this->connect_using_relay( $parameters );
     548                    break;
     549                case 'credis':
     550                    $this->connect_using_credis( $parameters );
     551                    break;
     552                case 'predis':
     553                default:
     554                    $this->connect_using_predis( $parameters );
     555                    break;
     556            }
     557
     558            if ( defined( 'WP_REDIS_CLUSTER' ) ) {
     559                $connectionID = current( array_values( WP_REDIS_CLUSTER ) );
     560
     561                $this->diagnostics[ 'ping' ] = ($client === 'predis')
     562                    ? $this->redis->getClientFor( $connectionID )->ping()
     563                    : $this->redis->ping( $connectionID );
     564            } else {
     565                $this->diagnostics[ 'ping' ] = $this->redis->ping();
     566            }
     567
     568            $this->fetch_info();
     569
     570            $this->redis_connected = true;
     571        } catch ( Exception $exception ) {
     572            $this->handle_exception( $exception );
     573        }
     574
     575        // Assign global and blog prefixes for use with keys.
     576        if ( function_exists( 'is_multisite' ) ) {
     577            $this->global_prefix = is_multisite() ? '' : $table_prefix;
     578            $this->blog_prefix = is_multisite() ? $blog_id : $table_prefix;
     579        }
     580    }
     581
     582    /**
     583     * Determine the Redis client.
     584     *
     585     * @return string
     586     */
     587    protected function determine_client() {
     588        $client = 'predis';
     589
     590        if ( class_exists( 'Redis' ) ) {
     591            $client = defined( 'HHVM_VERSION' ) ? 'hhvm' : 'phpredis';
     592        }
     593
     594        if ( defined( 'WP_REDIS_CLIENT' ) ) {
     595            $client = (string) WP_REDIS_CLIENT;
     596            $client = str_replace( 'pecl', 'phpredis', $client );
     597        }
     598
     599        return trim( strtolower( $client ) );
     600    }
     601
     602    /**
     603     * Build the connection parameters from config constants.
     604     *
     605     * @return array
     606     */
     607    protected function build_parameters() {
     608        $parameters = [
     609            'scheme' => 'tcp',
     610            'host' => '127.0.0.1',
     611            'port' => 6379,
     612            'database' => 0,
     613            'timeout' => 1,
     614            'read_timeout' => 1,
     615            'retry_interval' => null,
     616        ];
     617
     618        $settings = [
     619            'scheme',
     620            'host',
     621            'port',
     622            'path',
     623            'password',
     624            'database',
     625            'timeout',
     626            'read_timeout',
     627            'retry_interval',
     628        ];
     629
     630        foreach ( $settings as $setting ) {
     631            $constant = sprintf( 'WP_REDIS_%s', strtoupper( $setting ) );
     632
     633            if ( defined( $constant ) ) {
     634                $parameters[ $setting ] = constant( $constant );
     635            }
     636        }
     637
     638        if ( isset( $parameters[ 'password' ] ) && $parameters[ 'password' ] === '' ) {
     639            unset( $parameters[ 'password' ] );
     640        }
     641
     642        return $parameters;
     643    }
     644
     645    /**
     646     * Connect to Redis using the PhpRedis (PECL) extension.
     647     *
     648     * @param  array $parameters Connection parameters built by the `build_parameters` method.
     649     * @return void
     650     */
     651    protected function connect_using_phpredis( $parameters ) {
     652        $version = phpversion( 'redis' );
     653
     654        $this->diagnostics[ 'client' ] = sprintf( 'PhpRedis (v%s)', $version );
     655
     656        if ( defined( 'WP_REDIS_SHARDS' ) ) {
     657            $this->redis = new RedisArray( array_values( WP_REDIS_SHARDS ) );
     658
     659            $this->diagnostics[ 'shards' ] = WP_REDIS_SHARDS;
     660        } elseif ( defined( 'WP_REDIS_CLUSTER' ) ) {
     661            $args = [
     662                'cluster' => array_values( WP_REDIS_CLUSTER ),
     663                'timeout' => $parameters['timeout'],
     664                'read_timeout' => $parameters['read_timeout'],
     665            ];
     666
     667            if ( isset( $parameters['password'] ) && version_compare( $version, '4.3.0', '>=' ) ) {
     668                $args['password'] = $parameters['password'];
     669            }
     670
     671            $this->redis = new RedisCluster( null, ...array_values( $args ) );
     672
     673            $this->diagnostics += $args;
     674        } else {
     675            $this->redis = new Redis();
     676
     677            $args = [
     678                'host' => $parameters['host'],
     679                'port' => $parameters['port'],
     680                'timeout' => $parameters['timeout'],
     681                '',
     682                'retry_interval' => (int) $parameters['retry_interval'],
     683            ];
     684
     685            if ( strcasecmp( 'tls', $parameters['scheme'] ) === 0 ) {
     686                $args['host'] = sprintf(
     687                    '%s://%s',
     688                    $parameters['scheme'],
     689                    str_replace( 'tls://', '', $parameters['host'] )
     690                );
     691            }
     692
     693            if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) {
     694                $args['host'] = $parameters['path'];
     695                $args['port'] = null;
     696            }
     697
     698            if ( version_compare( $version, '3.1.3', '>=' ) ) {
     699                $args['read_timeout'] = $parameters['read_timeout'];
     700            }
     701
     702            call_user_func_array( [ $this->redis, 'connect' ], array_values( $args ) );
     703
     704            if ( isset( $parameters['password'] ) ) {
     705                $args['password'] = $parameters['password'];
     706                $this->redis->auth( $parameters['password'] );
     707            }
     708
     709            if ( isset( $parameters['database'] ) ) {
     710                if ( ctype_digit( (string) $parameters['database'] ) ) {
     711                    $parameters['database'] = (int) $parameters['database'];
     712                }
     713
     714                $args['database'] = $parameters['database'];
     715
     716                if ( $parameters['database'] ) {
     717                    $this->redis->select( $parameters['database'] );
     718                }
     719            }
     720
     721            $this->diagnostics += $args;
     722        }
     723
     724        if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) {
     725            $this->redis->setOption( Redis::OPT_SERIALIZER, WP_REDIS_SERIALIZER );
     726        }
     727    }
     728
     729    /**
     730     * Connect to Redis using the Relay extension.
     731     *
     732     * @param  array $parameters Connection parameters built by the `build_parameters` method.
     733     * @return void
     734     */
     735    protected function connect_using_relay( $parameters ) {
     736        $version = phpversion( 'relay' );
     737
     738        $this->diagnostics[ 'client' ] = sprintf( 'Relay (v%s)', $version );
     739
     740        if ( defined( 'WP_REDIS_SHARDS' ) ) {
     741            throw new Exception('Relay does not support sharding.');
     742        } elseif ( defined( 'WP_REDIS_CLUSTER' ) ) {
     743            throw new Exception('Relay does not cluster connections.');
     744        } else {
     745            $this->redis = new Relay\Relay;
     746
     747            $args = [
     748                'host' => $parameters['host'],
     749                'port' => $parameters['port'],
     750                'timeout' => $parameters['timeout'],
     751                '',
     752                'retry_interval' => (int) $parameters['retry_interval'],
     753            ];
     754
     755            if ( strcasecmp( 'tls', $parameters['scheme'] ) === 0 ) {
     756                $args['host'] = sprintf(
     757                    '%s://%s',
     758                    $parameters['scheme'],
     759                    str_replace( 'tls://', '', $parameters['host'] )
     760                );
     761            }
     762
     763            if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) {
     764                $args['host'] = $parameters['path'];
     765                $args['port'] = null;
     766            }
     767
     768            $args['read_timeout'] = $parameters['read_timeout'];
     769
     770            call_user_func_array( [ $this->redis, 'connect' ], array_values( $args ) );
     771
     772            if ( isset( $parameters['password'] ) ) {
     773                $args['password'] = $parameters['password'];
     774                $this->redis->auth( $parameters['password'] );
     775            }
     776
     777            if ( isset( $parameters['database'] ) ) {
     778                if ( ctype_digit( (string) $parameters['database'] ) ) {
     779                    $parameters['database'] = (int) $parameters['database'];
     780                }
     781
     782                $args['database'] = $parameters['database'];
     783
     784                if ( $parameters['database'] ) {
     785                    $this->redis->select( $parameters['database'] );
     786                }
     787            }
     788
     789            $this->diagnostics += $args;
     790        }
     791
     792        if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) {
     793            $this->redis->setOption( Relay\Relay::OPT_SERIALIZER, WP_REDIS_SERIALIZER );
     794        }
     795    }
     796
     797    /**
     798     * Connect to Redis using the Predis library.
     799     *
     800     * @param  array $parameters Connection parameters built by the `build_parameters` method.
     801     * @throws \Exception If the Predis library was not found or is unreadable.
     802     * @return void
     803     */
     804    protected function connect_using_predis( $parameters ) {
     805        $client = 'Predis';
     806
     807        // Load bundled Predis library.
     808        if ( ! class_exists( 'Predis\Client' ) ) {
     809            $predis = sprintf(
     810                '%s/wpbase-cache/includes/predis.php',
     811                defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins'
     812            );
     813
     814            if ( is_readable( $predis ) ) {
     815                require_once $predis;
     816            } else {
     817                throw new Exception(
     818                    'Predis library not found. Re-install Redis Cache plugin or delete the object-cache.php.'
     819                );
     820            }
     821        }
     822
     823        $servers = false;
     824        $options = [];
     825
     826        if ( defined( 'WP_REDIS_SHARDS' ) ) {
     827            $servers = WP_REDIS_SHARDS;
     828            $parameters['shards'] = $servers;
     829        } elseif ( defined( 'WP_REDIS_SENTINEL' ) ) {
     830            $servers = WP_REDIS_SERVERS;
     831            $parameters['servers'] = $servers;
     832            $options['replication'] = 'sentinel';
     833            $options['service'] = WP_REDIS_SENTINEL;
     834        } elseif ( defined( 'WP_REDIS_SERVERS' ) ) {
     835            $servers = WP_REDIS_SERVERS;
     836            $parameters['servers'] = $servers;
     837            $options['replication'] = true;
     838        } elseif ( defined( 'WP_REDIS_CLUSTER' ) ) {
     839            $servers = WP_REDIS_CLUSTER;
     840            $parameters['cluster'] = $servers;
     841            $options['cluster'] = 'redis';
     842        }
     843
     844        if ( isset( $parameters['read_timeout'] ) && $parameters['read_timeout'] ) {
     845            $parameters['read_write_timeout'] = $parameters['read_timeout'];
     846        }
     847
     848        foreach ( [ 'WP_REDIS_SERVERS', 'WP_REDIS_SHARDS', 'WP_REDIS_CLUSTER' ] as $constant ) {
     849            if ( defined( $constant ) ) {
     850                if ( $parameters['database'] ) {
     851                    $options['parameters']['database'] = $parameters['database'];
     852                }
     853
     854                if ( isset( $parameters['password'] ) ) {
     855                    $options['parameters']['password'] = WP_REDIS_PASSWORD;
     856                }
     857            }
     858        }
     859
     860        $this->redis = new Predis\Client( $servers ?: $parameters, $options );
     861        $this->redis->connect();
     862
     863        $this->diagnostics = array_merge(
     864            [ 'client' => sprintf( '%s (v%s)', $client, Predis\Client::VERSION ) ],
     865            $parameters,
     866            $options
     867        );
     868    }
     869
     870    /**
     871     * Connect to Redis using the Credis library.
     872     *
     873     * @param  array $parameters Connection parameters built by the `build_parameters` method.
     874     * @throws \Exception If the Credis library was not found or is unreadable.
     875     * @throws \Exception If redis sharding should be configured as Credis does not support sharding.
     876     * @throws \Exception If more than one seninel is configured as Credis does not support multiple sentinel servers.
     877     * @return void
     878     */
     879    protected function connect_using_credis( $parameters ) {
     880        $client = 'Credis';
     881
     882        $creds_path = sprintf(
     883            '%s/redis-cache/dependencies/colinmollenhour/credis/',
     884            defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins'
     885        );
     886
     887        $to_load = [];
     888
     889        if ( ! class_exists( 'Credis_Client' ) ) {
     890            $to_load[] = 'Client.php';
     891        }
     892
     893        $has_shards = defined( 'WP_REDIS_SHARDS' );
     894        $has_sentinel = defined( 'WP_REDIS_SENTINEL' );
     895        $has_servers = defined( 'WP_REDIS_SERVERS' );
     896        $has_cluster = defined( 'WP_REDIS_CLUSTER' );
     897
     898        if ( ( $has_shards || $has_sentinel || $has_servers || $has_cluster ) && ! class_exists( 'Credis_Cluster' ) ) {
     899            $to_load[] = 'Cluster.php';
     900
     901            if ( defined( 'WP_REDIS_SENTINEL' ) && ! class_exists( 'Credis_Sentinel' ) ) {
     902                $to_load[] = 'Sentinel.php';
     903            }
     904        }
     905
     906        foreach ( $to_load as $sub_path ) {
     907            $path = $creds_path . $sub_path;
     908
     909            if ( file_exists( $path ) ) {
     910                require_once $path;
     911            } else {
     912                throw new Exception(
     913                    'Credis library not found. Re-install Redis Cache plugin or delete object-cache.php.'
     914                );
     915            }
     916        }
     917
     918        if ( defined( 'WP_REDIS_SHARDS' ) ) {
     919            throw new Exception(
     920                'Sharding not supported by bundled Credis library. Please review your Redis Cache configuration.'
     921            );
     922        }
     923
     924        if ( defined( 'WP_REDIS_SENTINEL' ) ) {
     925            if ( is_array( WP_REDIS_SERVERS ) && count( WP_REDIS_SERVERS ) > 1 ) {
     926                throw new Exception(
     927                    'Multipe sentinel servers are not supported by the bundled Credis library. Please review your Redis Cache configuration.'
     928                );
     929            }
     930
     931            $connection_string = array_values( WP_REDIS_SERVERS )[0];
     932            $sentinel = new Credis_Sentinel( new Credis_Client( $connection_string ) );
     933            $this->redis = $sentinel->getCluster( WP_REDIS_SENTINEL );
     934            $args['servers'] = WP_REDIS_SERVERS;
     935        } elseif ( defined( 'WP_REDIS_CLUSTER' ) || defined( 'WP_REDIS_SERVERS' ) ) {
     936            $parameters['db'] = $parameters['database'];
     937
     938            $is_cluster = defined( 'WP_REDIS_CLUSTER' );
     939            $clients = $is_cluster ? WP_REDIS_CLUSTER : WP_REDIS_SERVERS;
     940
     941            foreach ( $clients as $index => $connection_string ) {
     942                // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
     943                $url_components = parse_url( $connection_string );
     944
     945                parse_str( $url_components['query'], $add_params );
     946
     947                if ( ! $is_cluster && isset( $add_params['alias'] ) ) {
     948                    $add_params['master'] = 'master' === $add_params['alias'];
     949                }
     950
     951                $add_params['host'] = $url_components['host'];
     952
     953                if ( ! isset( $add_params['alias'] ) ) {
     954                    $add_params['alias'] = "redis-$index";
     955                }
     956
     957                $clients[ $index ] = array_merge( $parameters, $add_params );
     958            }
     959
     960            $this->redis = new Credis_Cluster( $clients );
     961
     962            foreach ( $clients as $index => $_client ) {
     963                $connection_string = "{$_client['scheme']}://{$_client['host']}:{$_client['port']}";
     964                unset( $_client['scheme'], $_client['host'], $_client['port'] );
     965
     966                $params = array_filter( $_client );
     967
     968                if ( $params ) {
     969                    $connection_string .= '?' . http_build_query( $params, null, '&' );
     970                }
     971
     972                $clients[ $index ] = $connection_string;
     973            }
     974
     975            $args['servers'] = $clients;
     976        } else {
     977            $args = [
     978                'host' => $parameters['scheme'] === 'unix' ? $parameters['path'] : $parameters['host'],
     979                'port' => $parameters['port'],
     980                'timeout' => $parameters['timeout'],
     981                'persistent' => null,
     982                'database' => $parameters['database'],
     983                'password' => isset( $parameters['password'] ) ? $parameters['password'] : null,
     984            ];
     985
     986            $this->redis = new Credis_Client( ...array_values( $args ) );
     987        }
     988
     989        // Don't use PhpRedis if it is available.
     990        $this->redis->forceStandalone();
     991
     992        $this->redis->connect();
     993
     994        if ( $parameters['read_timeout'] ) {
     995            $args['read_timeout'] = $parameters['read_timeout'];
     996            $this->redis->setReadTimeout( $parameters['read_timeout'] );
     997        }
     998
     999        $this->diagnostics = array_merge(
     1000            [ 'client' => sprintf( '%s (v%s)', $client, Credis_Client::VERSION ) ],
     1001            $args
     1002        );
     1003    }
     1004
     1005    /**
     1006     * Connect to Redis using HHVM's Redis extension.
     1007     *
     1008     * @param  array $parameters Connection parameters built by the `build_parameters` method.
     1009     * @return void
     1010     */
     1011    protected function connect_using_hhvm( $parameters ) {
     1012        $this->redis = new Redis();
     1013
     1014        // Adjust host and port if the scheme is `unix`.
     1015        if ( strcasecmp( 'unix', $parameters['scheme'] ) === 0 ) {
     1016            $parameters['host'] = 'unix://' . $parameters['path'];
     1017            $parameters['port'] = 0;
     1018        }
     1019
     1020        $this->redis->connect(
     1021            $parameters['host'],
     1022            $parameters['port'],
     1023            $parameters['timeout'],
     1024            null,
     1025            $parameters['retry_interval']
     1026        );
     1027
     1028        if ( $parameters['read_timeout'] ) {
     1029            $this->redis->setOption( Redis::OPT_READ_TIMEOUT, $parameters['read_timeout'] );
     1030        }
     1031
     1032        if ( isset( $parameters['password'] ) ) {
     1033            $this->redis->auth( $parameters['password'] );
     1034        }
     1035
     1036        if ( isset( $parameters['database'] ) ) {
     1037            if ( ctype_digit( (string) $parameters['database'] ) ) {
     1038                $parameters['database'] = (int) $parameters['database'];
     1039            }
     1040
     1041            if ( $parameters['database'] ) {
     1042                $this->redis->select( $parameters['database'] );
     1043            }
     1044        }
     1045
     1046        $this->diagnostics = array_merge(
     1047            [ 'client' => sprintf( 'HHVM Extension (v%s)', HHVM_VERSION ) ],
     1048            $parameters
     1049        );
     1050    }
     1051
     1052    /**
     1053     * Fetches Redis `INFO` mostly for server version.
     1054     *
     1055     * @return void
     1056     */
     1057    public function fetch_info() {
     1058        $options = method_exists( $this->redis, 'getOptions' )
     1059            ? $this->redis->getOptions()
     1060            : new stdClass();
     1061
     1062        if ( isset( $options->replication ) && $options->replication ) {
     1063            return;
     1064        }
     1065
     1066        if ( defined( 'WP_REDIS_CLUSTER' ) ) {
     1067            $info = $this->redis->info( current( array_values( WP_REDIS_CLUSTER ) ) );
     1068        } else {
     1069            $info = $this->redis->info();
     1070        }
     1071
     1072        if ( isset( $info['redis_version'] ) ) {
     1073            $this->redis_version = $info['redis_version'];
     1074        } elseif ( isset( $info['Server']['redis_version'] ) ) {
     1075            $this->redis_version = $info['Server']['redis_version'];
     1076        }
     1077    }
     1078
     1079    /**
     1080     * Is Redis available?
     1081     *
     1082     * @return bool
     1083     */
     1084    public function redis_status() {
     1085        return (bool) $this->redis_connected;
     1086    }
     1087
     1088    /**
     1089     * Returns the Redis instance.
     1090     *
     1091     * @return mixed
     1092     */
     1093    public function redis_instance() {
     1094        return $this->redis;
     1095    }
     1096
     1097    /**
     1098     * Returns the Redis server version.
     1099     *
     1100     * @return null|string
     1101     */
     1102    public function redis_version() {
     1103        return $this->redis_version;
     1104    }
     1105
     1106    /**
     1107     * Adds a value to cache.
     1108     *
     1109     * If the specified key already exists, the value is not stored and the function
     1110     * returns false.
     1111     *
     1112     * @param   string $key            The key under which to store the value.
     1113     * @param   mixed  $value          The value to store.
     1114     * @param   string $group          The group value appended to the $key.
     1115     * @param   int    $expiration     The expiration time, defaults to 0.
     1116     * @return  bool                   Returns TRUE on success or FALSE on failure.
     1117     */
     1118    public function add( $key, $value, $group = 'default', $expiration = 0 ) {
     1119        return $this->add_or_replace( true, $key, $value, $group, $expiration );
     1120    }
     1121
     1122    /**
     1123     * Adds multiple values to the cache in one call.
    2691124     *
    270      * @var mixed
     1125     * @param array  $data   Array of keys and values to be added.
     1126     * @param string $group  Optional. Where the cache contents are grouped.
     1127     * @param int    $expire Optional. When to expire the cache contents, in seconds.
     1128     *                       Default 0 (no expiration).
     1129     * @return bool[] Array of return values, grouped by key. Each value is either
     1130     *                true on success, or false if cache key and group already exist.
    2711131     */
    272     private $redis;
    273 
    274     /**
    275      * Track if Redis is available
     1132    public function add_multiple( array $data, $group = 'default', $expire = 0 ) {
     1133        $values = [];
     1134
     1135        foreach ( $data as $key => $value ) {
     1136            $values[ $key ] = $this->add( $key, $value, $group, $expire );
     1137        }
     1138
     1139        return $values;
     1140    }
     1141
     1142    /**
     1143     * Replace a value in the cache.
     1144     *
     1145     * If the specified key doesn't exist, the value is not stored and the function
     1146     * returns false.
     1147     *
     1148     * @param   string $key            The key under which to store the value.
     1149     * @param   mixed  $value          The value to store.
     1150     * @param   string $group          The group value appended to the $key.
     1151     * @param   int    $expiration     The expiration time, defaults to 0.
     1152     * @return  bool                   Returns TRUE on success or FALSE on failure.
     1153     */
     1154    public function replace( $key, $value, $group = 'default', $expiration = 0 ) {
     1155        return $this->add_or_replace( false, $key, $value, $group, $expiration );
     1156    }
     1157
     1158    /**
     1159     * Add or replace a value in the cache.
     1160     *
     1161     * Add does not set the value if the key exists; replace does not replace if the value doesn't exist.
     1162     *
     1163     * @param   bool   $add            True if should only add if value doesn't exist, false to only add when value already exists.
     1164     * @param   string $key            The key under which to store the value.
     1165     * @param   mixed  $value          The value to store.
     1166     * @param   string $group          The group value appended to the $key.
     1167     * @param   int    $expiration     The expiration time, defaults to 0.
     1168     * @return  bool                   Returns TRUE on success or FALSE on failure.
     1169     */
     1170    protected function add_or_replace( $add, $key, $value, $group = 'default', $expiration = 0 ) {
     1171        $cache_addition_suspended = function_exists( 'wp_suspend_cache_addition' )
     1172            ? wp_suspend_cache_addition()
     1173            : false;
     1174
     1175        if ( $add && $cache_addition_suspended ) {
     1176            return false;
     1177        }
     1178
     1179        $result = true;
     1180        $derived_key = $this->build_key( $key, $group );
     1181
     1182        // Save if group not excluded and redis is up.
     1183        if ( ! $this->is_ignored_group( $group ) && $this->redis_status() ) {
     1184            try {
     1185                $orig_exp = $expiration;
     1186                $expiration = $this->validate_expiration( $expiration );
     1187
     1188                /**
     1189                 * Filters the cache expiration time
     1190                 *
     1191                 * @since 1.4.2
     1192                 * @param int    $expiration The time in seconds the entry expires. 0 for no expiry.
     1193                 * @param string $key        The cache key.
     1194                 * @param string $group      The cache group.
     1195                 * @param mixed  $orig_exp   The original expiration value before validation.
     1196                 */
     1197                $expiration = apply_filters( 'redis_cache_expiration', $expiration, $key, $group, $orig_exp );
     1198                $start_time = microtime( true );
     1199
     1200                if ( $add ) {
     1201                    $args = [ $derived_key, $this->maybe_serialize( $value ) ];
     1202
     1203                    if ( $this->redis instanceof Predis\Client ) {
     1204                        $args[] = 'nx';
     1205
     1206                        if ( $expiration ) {
     1207                            $args[] = 'ex';
     1208                            $args[] = $expiration;
     1209                        }
     1210                    } else {
     1211                        if ( $expiration ) {
     1212                            $args[] = [
     1213                                'nx',
     1214                                'ex' => $expiration,
     1215                            ];
     1216                        } else {
     1217                            $args[] = [ 'nx' ];
     1218                        }
     1219                    }
     1220
     1221                    $result = $this->parse_redis_response(
     1222                        $this->redis->set( ...$args )
     1223                    );
     1224
     1225                    if ( ! $result ) {
     1226                        return false;
     1227                    }
     1228                } elseif ( $expiration ) {
     1229                    $result = $this->parse_redis_response( $this->redis->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ) );
     1230                } else {
     1231                    $result = $this->parse_redis_response( $this->redis->set( $derived_key, $this->maybe_serialize( $value ) ) );
     1232                }
     1233
     1234                $execute_time = microtime( true ) - $start_time;
     1235
     1236                if ( $this->trace_enabled ) {
     1237                    $this->trace_command( 'set', $group, [
     1238                        $key => [
     1239                            'value' => $value,
     1240                            'status' => self::TRACE_FLAG_WRITE,
     1241                        ],
     1242                    ], microtime( true ) - $start_time );
     1243                }
     1244
     1245                $this->cache_calls++;
     1246                $this->cache_time += $execute_time;
     1247            } catch ( Exception $exception ) {
     1248                $this->handle_exception( $exception );
     1249
     1250                return false;
     1251            }
     1252        }
     1253
     1254        $exists = isset( $this->cache[ $derived_key ] );
     1255
     1256        if ( (bool) $add === $exists ) {
     1257            return false;
     1258        }
     1259
     1260        if ( $result ) {
     1261            $this->add_to_internal_cache( $derived_key, $value );
     1262        }
     1263
     1264        return $result;
     1265    }
     1266
     1267    /**
     1268     * Remove the item from the cache.
     1269     *
     1270     * @param   string $key        The key under which to store the value.
     1271     * @param   string $group      The group value appended to the $key.
     1272     * @return  bool               Returns TRUE on success or FALSE on failure.
     1273     */
     1274    public function delete( $key, $group = 'default' ) {
     1275        $result = false;
     1276        $derived_key = $this->build_key( $key, $group );
     1277
     1278        if ( isset( $this->cache[ $derived_key ] ) ) {
     1279            unset( $this->cache[ $derived_key ] );
     1280            $result = true;
     1281        }
     1282
     1283        $start_time = microtime( true );
     1284
     1285        if ( $this->redis_status() && ! $this->is_ignored_group( $group ) ) {
     1286            try {
     1287                $result = $this->parse_redis_response( $this->redis->del( $derived_key ) );
     1288            } catch ( Exception $exception ) {
     1289                $this->handle_exception( $exception );
     1290
     1291                return false;
     1292            }
     1293        }
     1294
     1295        $execute_time = microtime( true ) - $start_time;
     1296
     1297        if ( $this->trace_enabled ) {
     1298            $this->trace_command( 'del', $group, [
     1299                $key => [
     1300                    'value' => null,
     1301                    'status' => self::TRACE_FLAG_DEL,
     1302                ],
     1303            ], $execute_time );
     1304        }
     1305
     1306        $this->cache_calls++;
     1307        $this->cache_time += $execute_time;
     1308
     1309        if ( function_exists( 'do_action' ) ) {
     1310            /**
     1311             * Fires on every cache key deletion
     1312             *
     1313             * @since 1.3.3
     1314             * @param string $key          The cache key.
     1315             * @param string $group        The group value appended to the $key.
     1316             * @param float  $execute_time Execution time for the request in seconds.
     1317             */
     1318            do_action( 'redis_object_cache_delete', $key, $group, $execute_time );
     1319        }
     1320
     1321        return (bool) $result;
     1322    }
     1323
     1324    /**
     1325     * Deletes multiple values from the cache in one call.
    2761326     *
    277      * @var bool
     1327     * @param array  $keys  Array of keys to be deleted.
     1328     * @param string $group Optional. Where the cache contents are grouped.
     1329     * @return bool[] Array of return values, grouped by key. Each value is either
     1330     *                true on success, or false if the contents were not deleted.
    2781331     */
    279     private $redis_connected = false;
    280 
    281     /**
    282      * Holds the non-Redis objects.
     1332    public function delete_multiple( array $keys, $group = 'default' ) {
     1333        if ( $this->redis_status() && method_exists( $this->redis, 'pipeline' ) ) {
     1334            return $this->delete_multiple_at_once( $keys, $group );
     1335        }
     1336
     1337        $values = [];
     1338
     1339        foreach ( $keys as $key ) {
     1340            $values[ $key ] = $this->delete( $key, $group );
     1341        }
     1342
     1343        return $values;
     1344    }
     1345
     1346    /**
     1347     * Deletes multiple values from the cache in one call.
    2831348     *
    284      * @var array
     1349     * @param array  $keys  Array of keys to be deleted.
     1350     * @param string $group Optional. Where the cache contents are grouped.
     1351     * @return bool[] Array of return values, grouped by key. Each value is either
     1352     *                true on success, or false if the contents were not deleted.
    2851353     */
    286     public $cache = array();
    287 
    288     /**
    289      * Name of the used Redis client
     1354    public function delete_multiple_at_once( array $keys, $group = 'default' ) {
     1355        if ( $this->is_ignored_group( $group ) ) {
     1356            $results = [];
     1357
     1358            foreach ( $keys as $key ) {
     1359                $derived_key = $this->build_key( $key, $group );
     1360
     1361                $results[ $key ] = isset( $this->cache[ $derived_key ] );
     1362
     1363                unset( $this->cache[ $derived_key ] );
     1364            }
     1365
     1366            return $results;
     1367        }
     1368
     1369        try {
     1370            $tx = $this->redis->pipeline();
     1371
     1372            foreach ($keys as $key) {
     1373                $derived_key = $this->build_key( (string) $key, $group );
     1374
     1375                $tx->del( $derived_key );
     1376
     1377                unset( $this->cache[ $derived_key ] );
     1378            }
     1379
     1380            $results = array_map( function ( $response ) {
     1381                return (bool) $this->parse_redis_response( $response );
     1382            }, $tx->exec() );
     1383
     1384            return array_combine( $keys, $results );
     1385        } catch ( Exception $exception ) {
     1386            $this->handle_exception( $exception );
     1387
     1388            return array_combine( $keys, array_fill( 0, count( $keys ), false ) );
     1389        }
     1390    }
     1391
     1392    /**
     1393     * Removes all cache items from the in-memory runtime cache.
     1394     *
     1395     * @return bool True on success, false on failure.
     1396     */
     1397    public function flush_runtime() {
     1398        $this->cache = [];
     1399
     1400        return true;
     1401    }
     1402
     1403    /**
     1404     * Invalidate all items in the cache. If `WP_REDIS_SELECTIVE_FLUSH` is `true`,
     1405     * only keys prefixed with the `WP_REDIS_PREFIX` are flushed.
     1406     *
     1407     * @param   int $delay      Number of seconds to wait before invalidating the items.
     1408     * @return  bool            Returns TRUE on success or FALSE on failure.
     1409     */
     1410    public function flush( $delay = 0 ) {
     1411        $delay = abs( (int) $delay );
     1412
     1413        if ( $delay ) {
     1414            sleep( $delay );
     1415        }
     1416
     1417        $results = [];
     1418        $this->cache = [];
     1419
     1420        if ( $this->redis_status() ) {
     1421            $salt = defined( 'WP_REDIS_PREFIX' ) ? trim( WP_REDIS_PREFIX ) : null;
     1422            $selective = defined( 'WP_REDIS_SELECTIVE_FLUSH' ) ? WP_REDIS_SELECTIVE_FLUSH : null;
     1423
     1424            $start_time = microtime( true );
     1425
     1426            if ( $salt && $selective ) {
     1427                $script = $this->get_flush_closure( $salt );
     1428
     1429                if ( defined( 'WP_REDIS_CLUSTER' ) ) {
     1430                    try {
     1431                        foreach ( $this->redis->_masters() as $master ) {
     1432                            $redis = new Redis();
     1433                            $redis->connect( $master[0], $master[1] );
     1434                            $results[] = $this->parse_redis_response( $script() );
     1435                            unset( $redis );
     1436                        }
     1437                    } catch ( Exception $exception ) {
     1438                        $this->handle_exception( $exception );
     1439
     1440                        return false;
     1441                    }
     1442                } else {
     1443                    try {
     1444                        $results[] = $this->parse_redis_response( $script() );
     1445                    } catch ( Exception $exception ) {
     1446                        $this->handle_exception( $exception );
     1447
     1448                        return false;
     1449                    }
     1450                }
     1451            } else {
     1452                if ( defined( 'WP_REDIS_CLUSTER' ) ) {
     1453                    try {
     1454                        foreach ( $this->redis->_masters() as $master ) {
     1455                            $results[] = $this->parse_redis_response( $this->redis->flushdb( $master ) );
     1456                        }
     1457                    } catch ( Exception $exception ) {
     1458                        $this->handle_exception( $exception );
     1459
     1460                        return false;
     1461                    }
     1462                } else {
     1463                    try {
     1464                        $results[] = $this->parse_redis_response( $this->redis->flushdb() );
     1465                    } catch ( Exception $exception ) {
     1466                        $this->handle_exception( $exception );
     1467
     1468                        return false;
     1469                    }
     1470                }
     1471            }
     1472
     1473            if ( function_exists( 'do_action' ) ) {
     1474                $execute_time = microtime( true ) - $start_time;
     1475
     1476                /**
     1477                 * Fires on every cache flush
     1478                 *
     1479                 * @since 1.3.5
     1480                 * @param null|array $results      Array of flush results.
     1481                 * @param int        $delay        Given number of seconds to waited before invalidating the items.
     1482                 * @param bool       $seletive     Whether a selective flush took place.
     1483                 * @param string     $salt         The defined key prefix.
     1484                 * @param float      $execute_time Execution time for the request in seconds.
     1485                 */
     1486                do_action( 'redis_object_cache_flush', $results, $delay, $selective, $salt, $execute_time );
     1487            }
     1488        }
     1489
     1490        if ( empty( $results ) ) {
     1491            return false;
     1492        }
     1493
     1494        foreach ( $results as $result ) {
     1495            if ( ! $result ) {
     1496                return false;
     1497            }
     1498        }
     1499
     1500        return true;
     1501    }
     1502
     1503    /**
     1504     * Returns a closure to flush selectively.
     1505     *
     1506     * @param   string $salt  The salt to be used to differentiate.
     1507     * @return  callable      Generated callable executing the lua script.
     1508     */
     1509    protected function get_flush_closure( $salt ) {
     1510        if ( $this->unflushable_groups ) {
     1511            return $this->lua_flush_extended_closure( $salt );
     1512        } else {
     1513            return $this->lua_flush_closure( $salt );
     1514        }
     1515    }
     1516
     1517    /**
     1518     * Quotes a string for usage in the `glob` function
     1519     *
     1520     * @param string $string The string to quote.
     1521     * @return string
     1522     */
     1523    protected function glob_quote( $string ) {
     1524        $characters = [ '*', '+', '?', '!', '{', '}', '[', ']', '(', ')', '|', '@' ];
     1525
     1526        return str_replace(
     1527            $characters,
     1528            array_map(
     1529                function ( $character ) {
     1530                    return "[{$character}]";
     1531                },
     1532                $characters
     1533            ),
     1534            $string
     1535        );
     1536    }
     1537
     1538    /**
     1539     * Returns a closure ready to be called to flush selectively ignoring unflushable groups.
     1540     *
     1541     * @param   string $salt  The salt to be used to differentiate.
     1542     * @return  callable      Generated callable executing the lua script.
     1543     */
     1544    protected function lua_flush_closure( $salt ) {
     1545        $salt = $this->glob_quote( $salt );
     1546
     1547        return function () use ( $salt ) {
     1548            $script = <<<LUA
     1549                local cur = 0
     1550                local i = 0
     1551                local tmp
     1552                repeat
     1553                    tmp = redis.call('SCAN', cur, 'MATCH', '{$salt}*')
     1554                    cur = tonumber(tmp[1])
     1555                    if tmp[2] then
     1556                        for _, v in pairs(tmp[2]) do
     1557                            redis.call('del', v)
     1558                            i = i + 1
     1559                        end
     1560                    end
     1561                until 0 == cur
     1562                return i
     1563LUA;
     1564
     1565            if ( version_compare( $this->redis_version(), '5', '<' ) && version_compare( $this->redis_version(), '3.2', '>=' ) ) {
     1566                $script = 'redis.replicate_commands()' . "\n" . $script;
     1567            }
     1568
     1569            $args = ( $this->redis instanceof Predis\Client )
     1570                ? [ $script, 0 ]
     1571                : [ $script ];
     1572
     1573            return call_user_func_array( [ $this->redis, 'eval' ], $args );
     1574        };
     1575    }
     1576
     1577    /**
     1578     * Returns a closure ready to be called to flush selectively.
     1579     *
     1580     * @param   string $salt  The salt to be used to differentiate.
     1581     * @return  callable      Generated callable executing the lua script.
     1582     */
     1583    protected function lua_flush_extended_closure( $salt ) {
     1584        $salt = $this->glob_quote( $salt );
     1585
     1586        return function () use ( $salt ) {
     1587            $salt_length = strlen( $salt );
     1588
     1589            $unflushable = array_map(
     1590                function ( $group ) {
     1591                    return ":{$group}:";
     1592                },
     1593                $this->unflushable_groups
     1594            );
     1595
     1596            $script = <<<LUA
     1597                local cur = 0
     1598                local i = 0
     1599                local d, tmp
     1600                repeat
     1601                    tmp = redis.call('SCAN', cur, 'MATCH', '{$salt}*')
     1602                    cur = tonumber(tmp[1])
     1603                    if tmp[2] then
     1604                        for _, v in pairs(tmp[2]) do
     1605                            d = true
     1606                            for _, s in pairs(KEYS) do
     1607                                d = d and not v:find(s, {$salt_length})
     1608                                if not d then break end
     1609                            end
     1610                            if d then
     1611                                redis.call('del', v)
     1612                                i = i + 1
     1613                            end
     1614                        end
     1615                    end
     1616                until 0 == cur
     1617                return i
     1618LUA;
     1619            if ( version_compare( $this->redis_version(), '5', '<' ) && version_compare( $this->redis_version(), '3.2', '>=' ) ) {
     1620                $script = 'redis.replicate_commands()' . "\n" . $script;
     1621            }
     1622
     1623            $args = ( $this->redis instanceof Predis\Client )
     1624                ? array_merge( [ $script, count( $unflushable ) ], $unflushable )
     1625                : [ $script, $unflushable, count( $unflushable ) ];
     1626
     1627            return call_user_func_array( [ $this->redis, 'eval' ], $args );
     1628        };
     1629    }
     1630
     1631    /**
     1632     * Retrieve object from cache.
     1633     *
     1634     * Gets an object from cache based on $key and $group.
     1635     *
     1636     * @param   string $key        The key under which to store the value.
     1637     * @param   string $group      The group value appended to the $key.
     1638     * @param   bool   $force      Optional. Whether to force a refetch rather than relying on the local
     1639     *                             cache. Default false.
     1640     * @param   bool   $found      Optional. Whether the key was found in the cache. Disambiguates a return of
     1641     *                             false, a storable value. Passed by reference. Default null.
     1642     * @return  bool|mixed         Cached object value.
     1643     */
     1644    public function get( $key, $group = 'default', $force = false, &$found = null ) {
     1645        $trace_flags = self::TRACE_FLAG_READ;
     1646
     1647        if ( $force ) {
     1648            $trace_flags |= self::TRACE_FLAG_REFRESH;
     1649        }
     1650
     1651        $start_time = microtime( true );
     1652        $derived_key = $this->build_key( $key, $group );
     1653
     1654        if ( isset( $this->cache[ $derived_key ] ) && ! $force ) {
     1655            $found = true;
     1656            $this->cache_hits++;
     1657            $value = $this->get_from_internal_cache( $derived_key );
     1658
     1659            if ( $this->trace_enabled ) {
     1660                $this->trace_command( 'get', $group, [
     1661                    $key => [
     1662                        'value' => $value,
     1663                        'status' => $trace_flags | self::TRACE_FLAG_HIT | self::TRACE_FLAG_INTERNAL,
     1664                    ],
     1665                ], microtime( true ) - $start_time);
     1666            }
     1667
     1668            return $value;
     1669        } elseif ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) {
     1670            $found = false;
     1671            $this->cache_misses++;
     1672
     1673            if ( $this->trace_enabled ) {
     1674                $this->trace_command( 'get', $group, [
     1675                    $key => [
     1676                        'value' => null,
     1677                        'status' => $trace_flags | self::TRACE_FLAG_INTERNAL,
     1678                    ],
     1679                ], microtime( true ) - $start_time );
     1680            }
     1681
     1682            return false;
     1683        }
     1684
     1685
     1686        try {
     1687            $result = $this->redis->get( $derived_key );
     1688        } catch ( Exception $exception ) {
     1689            $this->handle_exception( $exception );
     1690
     1691            return false;
     1692        }
     1693
     1694        $execute_time = microtime( true ) - $start_time;
     1695
     1696        $this->cache_calls++;
     1697        $this->cache_time += $execute_time;
     1698
     1699        if ( $result === null || $result === false ) {
     1700            $found = false;
     1701            $this->cache_misses++;
     1702
     1703            if ( $this->trace_enabled ) {
     1704                $this->trace_command( 'get', $group, [
     1705                    $key => [
     1706                        'value' => null,
     1707                        'status' => $trace_flags,
     1708                    ],
     1709                ], microtime( true ) - $start_time );
     1710            }
     1711
     1712            return false;
     1713        } else {
     1714            $found = true;
     1715            $this->cache_hits++;
     1716            $value = $this->maybe_unserialize( $result );
     1717        }
     1718
     1719        $this->add_to_internal_cache( $derived_key, $value );
     1720
     1721        if ( $this->trace_enabled ) {
     1722            $this->trace_command( 'get', $group, [
     1723                $key => [
     1724                    'value' => $value,
     1725                    'status' => $trace_flags | self::TRACE_FLAG_HIT,
     1726                ],
     1727            ], microtime( true ) - $start_time );
     1728        }
     1729
     1730        if ( function_exists( 'do_action' ) ) {
     1731            /**
     1732             * Fires on every cache get request
     1733             *
     1734             * @since 1.2.2
     1735             * @param mixed  $value        Value of the cache entry.
     1736             * @param string $key          The cache key.
     1737             * @param string $group        The group value appended to the $key.
     1738             * @param bool   $force        Whether a forced refetch has taken place rather than relying on the local cache.
     1739             * @param bool   $found        Whether the key was found in the cache.
     1740             * @param float  $execute_time Execution time for the request in seconds.
     1741             */
     1742            do_action( 'redis_object_cache_get', $key, $value, $group, $force, $found, $execute_time );
     1743        }
     1744
     1745        if ( function_exists( 'apply_filters' ) && function_exists( 'has_filter' ) ) {
     1746            if ( has_filter( 'redis_object_cache_get_value' ) ) {
     1747                /**
     1748                 * Filters the return value
     1749                 *
     1750                 * @since 1.4.2
     1751                 * @param mixed  $value Value of the cache entry.
     1752                 * @param string $key   The cache key.
     1753                 * @param string $group The group value appended to the $key.
     1754                 * @param bool   $force Whether a forced refetch has taken place rather than relying on the local cache.
     1755                 * @param bool   $found Whether the key was found in the cache.
     1756                 */
     1757                return apply_filters( 'redis_object_cache_get_value', $value, $key, $group, $force, $found );
     1758            }
     1759        }
     1760
     1761        return $value;
     1762    }
     1763
     1764    /**
     1765     * Retrieves multiple values from the cache in one call.
     1766     *
     1767     * @param array  $keys  Array of keys under which the cache contents are stored.
     1768     * @param string $group Optional. Where the cache contents are grouped. Default empty.
     1769     * @param bool   $force Optional. Whether to force an update of the local cache
     1770     *                      from the persistent cache. Default false.
     1771     * @return array Array of values organized into groups.
     1772     */
     1773    public function get_multiple( $keys, $group = 'default', $force = false ) {
     1774        if ( ! is_array( $keys ) ) {
     1775            return false;
     1776        }
     1777
     1778        $trace_flags = self::TRACE_FLAG_READ;
     1779
     1780        if ( $force ) {
     1781            $trace_flags |= self::TRACE_FLAG_REFRESH;
     1782        }
     1783
     1784        $cache = [];
     1785        $derived_keys = [];
     1786        $start_time = microtime( true );
     1787
     1788        foreach ( $keys as $key ) {
     1789            $derived_keys[ $key ] = $this->build_key( $key, $group );
     1790        }
     1791
     1792        if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) {
     1793            $traceKV = [];
     1794
     1795            foreach ( $keys as $key ) {
     1796                $value = $this->get_from_internal_cache( $derived_keys[ $key ] );
     1797                $cache[ $key ] = $value;
     1798
     1799                if ($value === false) {
     1800                    $this->cache_misses++;
     1801
     1802                    if ( $this->trace_enabled ) {
     1803                        $traceKV[ $key ] = [
     1804                            'value' => null,
     1805                            'status' => $trace_flags | self::TRACE_FLAG_INTERNAL,
     1806                        ];
     1807                    }
     1808                } else {
     1809                    $this->cache_hits++;
     1810
     1811                    if ( $this->trace_enabled ) {
     1812                        $traceKV[ $key ] = [
     1813                            'value' => $value,
     1814                            'status' => $trace_flags | self::TRACE_FLAG_HIT | self::TRACE_FLAG_INTERNAL,
     1815                        ];
     1816                    }
     1817                }
     1818            }
     1819
     1820            $this->trace_command( 'mget', $group, $traceKV, microtime( true ) - $start_time );
     1821
     1822            return $cache;
     1823        }
     1824
     1825        $traceKV = [];
     1826
     1827        if ( ! $force ) {
     1828            foreach ( $keys as $key ) {
     1829                $value = $this->get_from_internal_cache( $derived_keys[ $key ] );
     1830
     1831                if ( $value === false ) {
     1832                    $this->cache_misses++;
     1833
     1834                    if ( $this->trace_enabled ) {
     1835                        $traceKV[ $key ] = [
     1836                            'value' => null,
     1837                            'status' => $trace_flags | self::TRACE_FLAG_INTERNAL,
     1838                        ];
     1839                    }
     1840                } else {
     1841                    $cache[ $key ] = $value;
     1842                    $this->cache_hits++;
     1843
     1844                    if ( $this->trace_enabled ) {
     1845                        $traceKV[ $key ] = [
     1846                            'value' => $value,
     1847                            'status' => $trace_flags | self::TRACE_FLAG_HIT | self::TRACE_FLAG_INTERNAL,
     1848                        ];
     1849                    }
     1850                }
     1851            }
     1852        }
     1853
     1854        $remaining_keys = array_filter(
     1855            $keys,
     1856            function ( $key ) use ( $cache ) {
     1857                return ! isset( $cache[ $key ] );
     1858            }
     1859        );
     1860
     1861        if ( empty( $remaining_keys ) ) {
     1862            $this->trace_enabled
     1863                && $this->trace_command( 'mget', $group, $traceKV, microtime( true ) - $start_time );
     1864
     1865            return $cache;
     1866        }
     1867
     1868        $start_time = microtime( true );
     1869
     1870        try {
     1871            $remaining_ids = array_map(
     1872                function ( $key ) use ( $derived_keys ) {
     1873                    return $derived_keys[ $key ];
     1874                },
     1875                $remaining_keys
     1876            );
     1877
     1878            $results = array_combine(
     1879                $remaining_keys,
     1880                $this->redis->mget( $remaining_ids )
     1881                    ?: array_fill( 0, count( $remaining_ids ), false )
     1882            );
     1883        } catch ( Exception $exception ) {
     1884            $this->handle_exception( $exception );
     1885
     1886            $cache = array_fill( 0, count( $derived_keys ) - 1, false );
     1887        }
     1888
     1889        $execute_time = microtime( true ) - $start_time;
     1890
     1891        $this->cache_calls++;
     1892        $this->cache_time += $execute_time;
     1893
     1894        foreach ( $results as $key => $value ) {
     1895            if ( $value === null || $value === false ) {
     1896                $cache[ $key ] = false;
     1897                $this->cache_misses++;
     1898
     1899                if ( $this->trace_enabled ) {
     1900                    $traceKV[ $key ] = [
     1901                        'value' => null,
     1902                        'status' => $trace_flags,
     1903                    ];
     1904                }
     1905            } else {
     1906                $cache[ $key ] = $this->maybe_unserialize( $value );
     1907                $this->add_to_internal_cache( $derived_keys[ $key ], $cache[ $key ] );
     1908                $this->cache_hits++;
     1909
     1910                if ( $this->trace_enabled ) {
     1911                    $traceKV[ $key ] = [
     1912                        'value' => $value,
     1913                        'status' => $trace_flags | self::TRACE_FLAG_HIT,
     1914                    ];
     1915                }
     1916            }
     1917        }
     1918
     1919        $this->trace_enabled
     1920            && $this->trace_command( 'mget', $group, $traceKV, $execute_time );
     1921
     1922        if ( function_exists( 'do_action' ) ) {
     1923            /**
     1924             * Fires on every cache get multiple request
     1925             *
     1926             * @since 2.0.6
     1927             * @param mixed  $value        Value of the cache entry.
     1928             * @param string $key          The cache key.
     1929             * @param string $group        The group value appended to the $key.
     1930             * @param bool   $force        Whether a forced refetch has taken place rather than relying on the local cache.
     1931             * @param float  $execute_time Execution time for the request in seconds.
     1932             */
     1933            do_action( 'redis_object_cache_get_multiple', $keys, $cache, $group, $force, $execute_time );
     1934        }
     1935
     1936        if ( function_exists( 'apply_filters' ) && function_exists( 'has_filter' ) ) {
     1937            if ( has_filter( 'redis_object_cache_get_value' ) ) {
     1938                foreach ( $cache as $key => $value ) {
     1939                    /**
     1940                     * Filters the return value
     1941                     *
     1942                     * @since 1.4.2
     1943                     * @param mixed  $value Value of the cache entry.
     1944                     * @param string $key   The cache key.
     1945                     * @param string $group The group value appended to the $key.
     1946                     * @param bool   $force Whether a forced refetch has taken place rather than relying on the local cache.
     1947                     */
     1948                    $cache[ $key ] = apply_filters( 'redis_object_cache_get_value', $value, $key, $group, $force );
     1949                }
     1950            }
     1951        }
     1952
     1953        return $cache;
     1954    }
     1955
     1956    /**
     1957     * Sets a value in cache.
     1958     *
     1959     * The value is set whether or not this key already exists in Redis.
     1960     *
     1961     * @param   string $key        The key under which to store the value.
     1962     * @param   mixed  $value      The value to store.
     1963     * @param   string $group      The group value appended to the $key.
     1964     * @param   int    $expiration The expiration time, defaults to 0.
     1965     * @return  bool               Returns TRUE on success or FALSE on failure.
     1966     */
     1967    public function set( $key, $value, $group = 'default', $expiration = 0 ) {
     1968        $result = true;
     1969        $start_time = microtime( true );
     1970        $derived_key = $this->build_key( $key, $group );
     1971
     1972        // Save if group not excluded from redis and redis is up.
     1973        if ( ! $this->is_ignored_group( $group ) && $this->redis_status() ) {
     1974            $orig_exp = $expiration;
     1975            $expiration = $this->validate_expiration( $expiration );
     1976
     1977            /**
     1978             * Filters the cache expiration time
     1979             *
     1980             * @since 1.4.2
     1981             * @param int    $expiration The time in seconds the entry expires. 0 for no expiry.
     1982             * @param string $key        The cache key.
     1983             * @param string $group      The cache group.
     1984             * @param mixed  $orig_exp   The original expiration value before validation.
     1985             */
     1986            $expiration = apply_filters( 'redis_cache_expiration', $expiration, $key, $group, $orig_exp );
     1987
     1988            try {
     1989                if ( $expiration ) {
     1990                    $result = $this->parse_redis_response( $this->redis->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ) );
     1991                } else {
     1992                    $result = $this->parse_redis_response( $this->redis->set( $derived_key, $this->maybe_serialize( $value ) ) );
     1993                }
     1994            } catch ( Exception $exception ) {
     1995                $this->handle_exception( $exception );
     1996
     1997                return false;
     1998            }
     1999
     2000            $execute_time = microtime( true ) - $start_time;
     2001            $this->cache_calls++;
     2002            $this->cache_time += $execute_time;
     2003
     2004            if ( $this->trace_enabled ) {
     2005                $this->trace_command( 'set', $group, [
     2006                    $key => [
     2007                        'value' => null,
     2008                        'status' => self::TRACE_FLAG_WRITE,
     2009                    ],
     2010                ], $execute_time );
     2011            }
     2012        }
     2013
     2014        // If the set was successful, or we didn't go to redis.
     2015        if ( $result ) {
     2016            $this->add_to_internal_cache( $derived_key, $value );
     2017        }
     2018
     2019        if ( function_exists( 'do_action' ) ) {
     2020            $execute_time = microtime( true ) - $start_time;
     2021
     2022            /**
     2023             * Fires on every cache set
     2024             *
     2025             * @since 1.2.2
     2026             * @param string $key          The cache key.
     2027             * @param mixed  $value        Value of the cache entry.
     2028             * @param string $group        The group value appended to the $key.
     2029             * @param int    $expiration   The time in seconds the entry expires. 0 for no expiry.
     2030             * @param float  $execute_time Execution time for the request in seconds.
     2031             */
     2032            do_action( 'redis_object_cache_set', $key, $value, $group, $expiration, $execute_time );
     2033        }
     2034
     2035        return $result;
     2036    }
     2037
     2038    /**
     2039     * Sets multiple values to the cache in one call.
    2902040     *
    291      * @var bool
     2041     * @param array  $data   Array of key and value to be set.
     2042     * @param string $group  Optional. Where the cache contents are grouped.
     2043     * @param int    $expire Optional. When to expire the cache contents, in seconds.
     2044     *                       Default 0 (no expiration).
     2045     * @return bool[] Array of return values, grouped by key. Each value is always true.
    2922046     */
    293     public $redis_client = null;
    294 
    295     /**
    296      * List of global groups.
    297      *
    298      * @var array
    299      */
    300     public $global_groups = array(
    301         'blog-details',
    302         'blog-id-cache',
    303         'blog-lookup',
    304         'global-posts',
    305         'networks',
    306         'rss',
    307         'sites',
    308         'site-details',
    309         'site-lookup',
    310         'site-options',
    311         'site-transient',
    312         'users',
    313         'useremail',
    314         'userlogins',
    315         'usermeta',
    316         'user_meta',
    317         'userslugs',
    318     );
    319 
    320     /**
    321      * List of groups not saved to Redis.
    322      *
    323      * @var array
    324      */
    325     public $ignored_groups = array( 'counts', 'plugins' );
    326 
    327     /**
    328      * Prefix used for global groups.
    329      *
    330      * @var string
    331      */
    332     public $global_prefix = '';
    333 
    334     /**
    335      * Prefix used for non-global groups.
    336      *
    337      * @var string
    338      */
    339     public $blog_prefix = '';
    340 
    341     /**
    342      * Track how many requests were found in cache
    343      *
    344      * @var int
    345      */
    346     public $cache_hits = 0;
    347 
    348     /**
    349      * Track how may requests were not cached
    350      *
    351      * @var int
    352      */
    353     public $cache_misses = 0;
    354 
    355     /**
    356      * Instantiate the Redis class.
    357      *
    358      * Instantiates the Redis class.
    359      *
    360      * @param null $persistent_id To create an instance that persists between requests, use persistent_id to specify a unique ID for the instance.
    361      */
    362     public function __construct() {
    363         global $blog_id, $table_prefix;
    364 
    365         $parameters = array(
    366             'scheme' => 'tcp',
    367             'host' => '127.0.0.1',
    368             'port' => 6379
    369         );
    370 
    371         foreach ( array( 'scheme', 'host', 'port', 'path', 'password', 'database' ) as $setting ) {
    372             $constant = sprintf( 'WP_REDIS_%s', strtoupper( $setting ) );
    373             if ( defined( $constant ) ) {
    374                 $parameters[ $setting ] = constant( $constant );
    375             }
     2047    public function set_multiple( array $data, $group = 'default', $expiration = 0 ) {
     2048        $values = [];
     2049
     2050        foreach ( $data as $key => $value ) {
     2051            $values[ $key ] = $this->set( $key, $value, $group, $expiration );
    3762052        }
    3772053
    378         if ( defined( 'WP_REDIS_GLOBAL_GROUPS' ) && is_array( WP_REDIS_GLOBAL_GROUPS ) ) {
    379             $this->global_groups = WP_REDIS_GLOBAL_GROUPS;
    380         }
    381 
    382         if ( defined( 'WP_REDIS_IGNORED_GROUPS' ) && is_array( WP_REDIS_IGNORED_GROUPS ) ) {
    383             $this->ignored_groups = WP_REDIS_IGNORED_GROUPS;
    384         }
    385 
    386         $client = defined( 'WP_REDIS_CLIENT' ) ? WP_REDIS_CLIENT : null;
    387 
    388         if ( class_exists( 'Redis' ) && strcasecmp( 'predis', $client ) !== 0 ) {
    389             $client = defined( 'HHVM_VERSION' ) ? 'hhvm' : 'pecl';
    390         } else {
    391             $client = 'predis';
    392         }
    393 
    394         try {
    395 
    396             if ( strcasecmp( 'hhvm', $client ) === 0 ) {
    397 
    398                 $this->redis_client = sprintf( 'HHVM Extension (v%s)', HHVM_VERSION );
    399                 $this->redis = new Redis();
    400 
    401                 // Adjust host and port, if the scheme is `unix`
    402                 if ( strcasecmp( 'unix', $parameters[ 'scheme' ] ) === 0 ) {
    403                     $parameters[ 'host' ] = 'unix://' . $parameters[ 'path' ];
    404                     $parameters[ 'port' ] = 0;
    405                 }
    406 
    407                 $this->redis->connect( $parameters[ 'host' ], $parameters[ 'port' ] );
    408             }
    409 
    410             if ( strcasecmp( 'pecl', $client ) === 0 ) {
    411 
    412                 $this->redis_client = sprintf( 'PECL Extension (v%s)', phpversion( 'redis' ) );
    413                 $this->redis = new Redis();
    414 
    415                 if ( strcasecmp( 'unix', $parameters[ 'scheme' ] ) === 0 ) {
    416                     $this->redis->connect( $parameters[ 'path' ] );
    417                 } else {
    418                     $this->redis->connect( $parameters[ 'host' ], $parameters[ 'port' ] );
    419                 }
    420             }
    421 
    422             if ( strcasecmp( 'pecl', $client ) === 0 || strcasecmp( 'hhvm', $client ) === 0 ) {
    423                 if ( isset( $parameters[ 'password' ] ) ) {
    424                     $this->redis->auth( $parameters[ 'password' ] );
    425                 }
    426 
    427                 if ( isset( $parameters[ 'database' ] ) ) {
    428                     $this->redis->select( $parameters[ 'database' ] );
    429                 }
    430             }
    431 
    432             if ( strcasecmp( 'predis', $client ) === 0 ) {
    433 
    434                 $this->redis_client = 'Predis';
    435 
    436                 // Require PHP 5.4 or greater
    437                 if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) {
    438                     throw new Exception;
    439                 }
    440 
    441                 // Load bundled Predis library
    442                 if ( ! class_exists( 'Predis\Client' ) ) {
    443                     $plugin_dir = defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins';
    444                     require_once $plugin_dir . '/wpbase-cache/includes/predis.php';
    445                     Predis\Autoloader::register();
    446                 }
    447 
    448                 $options = array();
    449 
    450                 if ( defined( 'WP_REDIS_CLUSTER' ) ) {
    451                     $parameters = WP_REDIS_CLUSTER;
    452                     $options[ 'cluster' ] = 'redis';
    453                 }
    454 
    455                 if ( defined( 'WP_REDIS_SERVERS' ) ) {
    456                     $parameters = WP_REDIS_SERVERS;
    457                     $options[ 'replication' ] = true;
    458                 }
    459 
    460                 if ( ( defined( 'WP_REDIS_SERVERS' ) || defined( 'WP_REDIS_CLUSTER' ) ) && defined( 'WP_REDIS_PASSWORD' ) ) {
    461                     $options[ 'parameters' ][ 'password' ] = WP_REDIS_PASSWORD;
    462                 }
    463 
    464                 $this->redis = new Predis\Client( $parameters, $options );
    465                 $this->redis->connect();
    466 
    467                 $this->redis_client .= sprintf( ' (v%s)', Predis\Client::VERSION );
    468 
    469             }
    470 
    471             // Throws exception if Redis is unavailable
    472             $this->redis->ping();
    473 
    474             $this->redis_connected = true;
    475 
    476         } catch ( Exception $exception ) {
    477 
    478             // When Redis is unavailable, fall back to the internal back by forcing all groups to be "no redis" groups
    479             $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $this->global_groups ) );
    480 
    481             $this->redis_connected = false;
    482 
    483         }
    484 
    485         /**
    486          * This approach is borrowed from Sivel and Boren. Use the salt for easy cache invalidation and for
    487          * multi single WP installs on the same server.
    488          */
    489         if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {
    490             define( 'WP_CACHE_KEY_SALT', '' );
    491         }
    492 
    493         // Assign global and blog prefixes for use with keys
    494         if ( function_exists( 'is_multisite' ) ) {
    495             $this->global_prefix = ( is_multisite() || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix;
    496             $this->blog_prefix = ( is_multisite() ? $blog_id : $table_prefix );
    497         }
     2054        return $values;
    4982055    }
    4992056
    500     /**
    501      * Is Redis available?
    502      *
    503      * @return bool
    504      */
    505     public function redis_status() {
    506         return $this->redis_connected;
    507     }
    508 
    509     /**
    510      * Returns the Redis instance.
    511      *
    512      * @return mixed
    513      */
    514     public function redis_instance() {
    515         return $this->redis;
    516     }
    517 
    518     /**
    519      * Adds a value to cache.
    520      *
    521      * If the specified key already exists, the value is not stored and the function
    522      * returns false.
    523      *
    524      * @param   string $key            The key under which to store the value.
    525      * @param   mixed  $value          The value to store.
    526      * @param   string $group          The group value appended to the $key.
    527      * @param   int    $expiration     The expiration time, defaults to 0.
    528      * @return  bool                   Returns TRUE on success or FALSE on failure.
    529      */
    530     public function add( $key, $value, $group = 'default', $expiration = 0 ) {
    531         return $this->add_or_replace( true, $key, $value, $group, $expiration );
    532     }
    533 
    534     /**
    535      * Replace a value in the cache.
    536      *
    537      * If the specified key doesn't exist, the value is not stored and the function
    538      * returns false.
    539      *
    540      * @param   string $key            The key under which to store the value.
    541      * @param   mixed  $value          The value to store.
    542      * @param   string $group          The group value appended to the $key.
    543      * @param   int    $expiration     The expiration time, defaults to 0.
    544      * @return  bool                   Returns TRUE on success or FALSE on failure.
    545      */
    546     public function replace( $key, $value, $group = 'default', $expiration = 0 ) {
    547         return $this->add_or_replace( false, $key, $value, $group, $expiration );
    548     }
    549 
    550     /**
    551      * Add or replace a value in the cache.
    552      *
    553      * Add does not set the value if the key exists; replace does not replace if the value doesn't exist.
    554      *
    555      * @param   bool   $add            True if should only add if value doesn't exist, false to only add when value already exists
    556      * @param   string $key            The key under which to store the value.
    557      * @param   mixed  $value          The value to store.
    558      * @param   string $group          The group value appended to the $key.
    559      * @param   int    $expiration     The expiration time, defaults to 0.
    560      * @return  bool                   Returns TRUE on success or FALSE on failure.
    561      */
    562     protected function add_or_replace( $add, $key, $value, $group = 'default', $expiration = 0 ) {
    563         $result = true;
    564         $derived_key = $this->build_key( $key, $group );
    565 
    566         // save if group not excluded and redis is up
    567         if ( ! in_array( $group, $this->ignored_groups ) && $this->redis_status() ) {
    568             $exists = $this->redis->exists( $derived_key );
    569 
    570             if ( $add == $exists ) {
    571                 return false;
    572             }
    573 
    574             $expiration = $this->validate_expiration( $expiration );
    575 
    576             if ( $expiration ) {
    577                 $result = $this->parse_redis_response( $this->redis->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ) );
    578             } else {
    579                 $result = $this->parse_redis_response( $this->redis->set( $derived_key, $this->maybe_serialize( $value ) ) );
    580             }
    581         }
    582 
    583         $exists = isset( $this->cache[ $derived_key ] );
    584 
    585         if ( $add == $exists ) {
    586             return false;
    587         }
    588 
    589         if ( $result ) {
    590             $this->add_to_internal_cache( $derived_key, $value );
    591         }
    592 
    593         return $result;
    594     }
    595 
    596     /**
    597      * Remove the item from the cache.
    598      *
    599      * @param   string $key        The key under which to store the value.
    600      * @param   string $group      The group value appended to the $key.
    601      * @return  bool               Returns TRUE on success or FALSE on failure.
    602      */
    603     public function delete( $key, $group = 'default' ) {
    604         $result = false;
    605         $derived_key = $this->build_key( $key, $group );
    606 
    607         if ( isset( $this->cache[ $derived_key ] ) ) {
    608             unset( $this->cache[ $derived_key ] );
    609             $result = true;
    610         }
    611 
    612         if ( $this->redis_status() && ! in_array( $group, $this->ignored_groups ) ) {
    613             $result = $this->parse_redis_response( $this->redis->del( $derived_key ) );
    614         }
    615 
    616         if ( function_exists( 'do_action' ) ) {
    617             do_action( 'redis_object_cache_delete', $key, $group );
    618         }
    619 
    620         return $result;
    621     }
    622 
    623     /**
    624      * Invalidate all items in the cache.
    625      *
    626      * @param   int $delay      Number of seconds to wait before invalidating the items.
    627      * @return  bool            Returns TRUE on success or FALSE on failure.
    628      */
    629     public function flush( $delay = 0 ) {
    630         $delay = abs( intval( $delay ) );
    631 
    632         if ( $delay ) {
    633             sleep( $delay );
    634         }
    635 
    636         $result = false;
    637         $this->cache = array();
    638 
    639         if ( $this->redis_status() ) {
    640             $result = $this->parse_redis_response( $this->redis->flushdb() );
    641 
    642             if ( function_exists( 'do_action' ) ) {
    643                 do_action( 'redis_object_cache_flush', $result, $delay );
    644             }
    645         }
    646 
    647         return $result;
    648     }
    649 
    650     /**
    651      * Retrieve object from cache.
    652      *
    653      * Gets an object from cache based on $key and $group.
    654      *
    655      * @param   string        $key        The key under which to store the value.
    656      * @param   string        $group      The group value appended to the $key.
    657      * @param   string        $force      Optional. Whether to force a refetch rather than relying on the local
    658      *                                    cache. Default false.
    659      * @param   bool          &$found     Optional. Whether the key was found in the cache. Disambiguates a return of
    660      *                                    false, a storable value. Passed by reference. Default null.
    661      * @return  bool|mixed                Cached object value.
    662      */
    663     public function get( $key, $group = 'default', $force = false, &$found = null ) {
    664         $derived_key = $this->build_key( $key, $group );
    665 
    666         if ( isset( $this->cache[ $derived_key ] ) && ! $force ) {
    667             $found = true;
    668             $this->cache_hits++;
    669 
    670             return is_object( $this->cache[ $derived_key ] ) ? clone $this->cache[ $derived_key ] : $this->cache[ $derived_key ];
    671         } elseif ( in_array( $group, $this->ignored_groups ) || ! $this->redis_status() ) {
    672             $found = false;
    673             $this->cache_misses++;
    674 
    675             return false;
    676         }
    677 
    678         $result = $this->redis->get( $derived_key );
    679 
    680         if ( $result === null || $result === false ) {
    681             $found = false;
    682             $this->cache_misses++;
    683 
    684             return false;
    685         } else {
    686             $found = true;
    687             $this->cache_hits++;
    688             $value = $this->maybe_unserialize( $result );
    689         }
    690 
    691         $this->add_to_internal_cache( $derived_key, $value );
    692 
    693         $value = is_object( $value ) ? clone $value : $value;
    694 
    695         if ( function_exists( 'do_action' ) ) {
    696             do_action( 'redis_object_cache_get', $key, $value, $group, $force, $found );
    697         }
    698 
    699         if ( function_exists( 'apply_filters' ) && function_exists( 'has_filter' ) ) {
    700             if ( has_filter( 'redis_object_cache_get' ) ) {
    701                 return apply_filters( 'redis_object_cache_get', $value, $key, $group, $force, $found );
    702             }
    703         }
    704 
    705         return $value;
    706     }
    707 
    708     /**
    709      * Retrieve multiple values from cache.
    710      *
    711      * Gets multiple values from cache, including across multiple groups
    712      *
    713      * Usage: array( 'group0' => array( 'key0', 'key1', 'key2', ), 'group1' => array( 'key0' ) )
    714      *
    715      * Mirrors the Memcached Object Cache plugin's argument and return-value formats
    716      *
    717      * @param   array                           $groups  Array of groups and keys to retrieve
    718      * @return  bool|mixed                               Array of cached values, keys in the format $group:$key. Non-existent keys null.
    719      */
    720     public function get_multi( $groups ) {
    721         if ( empty( $groups ) || ! is_array( $groups ) ) {
    722             return false;
    723         }
    724 
    725         // Retrieve requested caches and reformat results to mimic Memcached Object Cache's output
    726         $cache = array();
    727 
    728         foreach ( $groups as $group => $keys ) {
    729             if ( in_array( $group, $this->ignored_groups ) || ! $this->redis_status() ) {
    730                 foreach ( $keys as $key ) {
    731                     $cache[ $this->build_key( $key, $group ) ] = $this->get( $key, $group );
    732                 }
    733             } else {
    734                 // Reformat arguments as expected by Redis
    735                 $derived_keys = array();
    736 
    737                 foreach ( $keys as $key ) {
    738                     $derived_keys[] = $this->build_key( $key, $group );
    739                 }
    740 
    741                 // Retrieve from cache in a single request
    742                 $group_cache = $this->redis->mget( $derived_keys );
    743 
    744                 // Build an array of values looked up, keyed by the derived cache key
    745                 $group_cache = array_combine( $derived_keys, $group_cache );
    746 
    747                 // Restores cached data to its original data type
    748                 $group_cache = array_map( array( $this, 'maybe_unserialize' ), $group_cache );
    749 
    750                 // Redis returns null for values not found in cache, but expected return value is false in this instance
    751                 $group_cache = array_map( array( $this, 'filter_redis_get_multi' ), $group_cache );
    752 
    753                 $cache = array_merge( $cache, $group_cache );
    754             }
    755         }
    756 
    757         // Add to the internal cache the found values from Redis
    758         foreach ( $cache as $key => $value ) {
    759             if ( $value ) {
    760                 $this->cache_hits++;
    761                 $this->add_to_internal_cache( $key, $value );
    762             } else {
    763                 $this->cache_misses++;
    764             }
    765         }
    766 
    767         return $cache;
    768     }
    769 
    770     /**
    771      * Sets a value in cache.
    772      *
    773      * The value is set whether or not this key already exists in Redis.
    774      *
    775      * @param   string $key        The key under which to store the value.
    776      * @param   mixed  $value      The value to store.
    777      * @param   string $group      The group value appended to the $key.
    778      * @param   int    $expiration The expiration time, defaults to 0.
    779      * @return  bool               Returns TRUE on success or FALSE on failure.
    780      */
    781     public function set( $key, $value, $group = 'default', $expiration = 0 ) {
    782         $result = true;
    783         $derived_key = $this->build_key( $key, $group );
    784 
    785         // save if group not excluded from redis and redis is up
    786         if ( ! in_array( $group, $this->ignored_groups ) && $this->redis_status() ) {
    787             $expiration = $this->validate_expiration( $expiration );
    788 
    789             if ( $expiration ) {
    790                 $result = $this->parse_redis_response( $this->redis->setex( $derived_key, $expiration, $this->maybe_serialize( $value ) ) );
    791             } else {
    792                 $result = $this->parse_redis_response( $this->redis->set( $derived_key, $this->maybe_serialize( $value ) ) );
    793             }
    794         }
    795 
    796         // if the set was successful, or we didn't go to redis
    797         if ( $result ) {
    798             $this->add_to_internal_cache( $derived_key, $value );
    799         }
    800 
    801         if ( function_exists( 'do_action' ) ) {
    802             do_action( 'redis_object_cache_set', $key, $value, $group, $expiration );
    803         }
    804 
    805         return $result;
    806     }
    807 
    808     /**
    809      * Increment a Redis counter by the amount specified
    810      *
    811      * @param  string $key
    812      * @param  int    $offset
    813      * @param  string $group
    814      * @return int|bool
    815      */
    816     public function increment( $key, $offset = 1, $group = 'default' ) {
    817         $derived_key = $this->build_key( $key, $group );
    818         $offset = (int) $offset;
    819 
    820         // If group is a non-Redis group, save to internal cache, not Redis
    821         if ( in_array( $group, $this->ignored_groups ) || ! $this->redis_status() ) {
    822             $value = $this->get_from_internal_cache( $derived_key, $group );
    823             $value += $offset;
    824             $this->add_to_internal_cache( $derived_key, $value );
    825 
    826             return $value;
    827         }
    828 
    829         // Save to Redis
    830         $result = $this->parse_redis_response( $this->redis->incrBy( $derived_key, $offset ) );
    831 
    832         $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) );
    833 
    834         return $result;
    835     }
    836 
    837     /**
    838      * Alias of `increment()`.
    839      *
    840      * @param  string $key
    841      * @param  int    $offset
    842      * @param  string $group
    843      * @return bool
    844      */
    845     public function incr( $key, $offset = 1, $group = 'default' ) {
    846         return $this->increment( $key, $offset, $group );
    847     }
    848 
    849     /**
    850      * Decrement a Redis counter by the amount specified
    851      *
    852      * @param  string $key
    853      * @param  int    $offset
    854      * @param  string $group
    855      * @return int|bool
    856      */
    857     public function decrement( $key, $offset = 1, $group = 'default' ) {
    858         $derived_key = $this->build_key( $key, $group );
    859         $offset = (int) $offset;
    860 
    861         // If group is a non-Redis group, save to internal cache, not Redis
    862         if ( in_array( $group, $this->ignored_groups ) || ! $this->redis_status() ) {
    863             $value = $this->get_from_internal_cache( $derived_key, $group );
    864             $value -= $offset;
    865             $this->add_to_internal_cache( $derived_key, $value );
    866 
    867             return $value;
    868         }
    869 
    870         // Save to Redis
    871         $result = $this->parse_redis_response( $this->redis->decrBy( $derived_key, $offset ) );
    872 
    873         $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) );
    874 
    875         return $result;
    876     }
    877 
    878     /**
    879      * Render data about current cache requests
    880      *
    881      * @return string
    882      */
    883     public function stats() { ?>
    884 
    885         <p>
    886             <strong>Redis Status:</strong> <?php echo $this->redis_status() ? 'Connected' : 'Not Connected'; ?><br />
    887             <strong>Redis Client:</strong> <?php echo $this->redis_client; ?><br />
    888             <strong>Cache Hits:</strong> <?php echo $this->cache_hits; ?><br />
    889             <strong>Cache Misses:</strong> <?php echo $this->cache_misses; ?>
    890         </p>
    891 
    892         <ul>
    893             <?php foreach ( $this->cache as $group => $cache ) : ?>
    894                 <li><?php printf( '%s - %sk', strip_tags( $group ), number_format( strlen( serialize( $cache ) ) / 1024, 2 ) ); ?></li>
    895             <?php endforeach; ?>
    896         </ul><?php
    897 
    898     }
    899 
    900     /**
    901      * Builds a key for the cached object using the prefix, group and key.
    902      *
    903      * @param   string $key        The key under which to store the value.
    904      * @param   string $group      The group value appended to the $key.
    905      *
    906      * @return  string
    907      */
    908     public function build_key( $key, $group = 'default' ) {
    909         if ( empty( $group ) ) {
    910             $group = 'default';
    911         }
    912 
    913         if ( in_array( $group, $this->global_groups ) ) {
    914             $prefix = $this->global_prefix;
    915         } else {
    916             $prefix = $this->blog_prefix;
    917         }
    918 
    919         return WP_CACHE_KEY_SALT . "{$prefix}:{$group}:{$key}";
    920     }
    921 
    922     /**
    923      * Convert data types when using Redis MGET
    924      *
    925      * When requesting multiple keys, those not found in cache are assigned the value null upon return.
    926      * Expected value in this case is false, so we convert
    927      *
    928      * @param   string  $value  Value to possibly convert
    929      * @return  string          Converted value
    930      */
    931     protected function filter_redis_get_multi( $value ) {
    932         if ( is_null( $value ) ) {
    933             $value = false;
    934         }
    935 
    936         return $value;
    937     }
    938 
    939     /**
    940      * Convert Redis responses into something meaningful
    941      *
    942      * @param mixed $response
    943      * @return mixed
    944      */
    945     protected function parse_redis_response( $response ) {
    946         if ( is_bool( $response ) ) {
    947             return $response;
    948         }
    949 
    950         if ( is_numeric( $response ) ) {
    951             return $response;
    952         }
    953 
    954         if ( is_object( $response ) && method_exists( $response, 'getPayload' ) ) {
    955             return $response->getPayload() === 'OK';
    956         }
    957 
    958         return false;
    959     }
    960 
    961     /**
    962      * Simple wrapper for saving object to the internal cache.
    963      *
    964      * @param   string $derived_key    Key to save value under.
    965      * @param   mixed  $value          Object value.
    966      */
    967     public function add_to_internal_cache( $derived_key, $value ) {
    968         $this->cache[ $derived_key ] = $value;
    969     }
    970 
    971     /**
    972      * Get a value specifically from the internal, run-time cache, not Redis.
    973      *
    974      * @param   int|string $key        Key value.
    975      * @param   int|string $group      Group that the value belongs to.
    976      *
    977      * @return  bool|mixed              Value on success; false on failure.
    978      */
    979     public function get_from_internal_cache( $key, $group ) {
    980         $derived_key = $this->build_key( $key, $group );
    981 
    982         if ( isset( $this->cache[ $derived_key ] ) ) {
    983             return $this->cache[ $derived_key ];
    984         }
    985 
    986         return false;
    987     }
    988 
    989     /**
    990      * In multisite, switch blog prefix when switching blogs
    991      *
    992      * @param int $_blog_id
    993      * @return bool
    994      */
    995     public function switch_to_blog( $_blog_id ) {
    996         if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) {
    997             return false;
    998         }
    999 
    1000         $this->blog_prefix = $_blog_id;
    1001 
    1002         return true;
    1003     }
    1004 
    1005     /**
    1006      * Sets the list of global groups.
    1007      *
    1008      * @param array $groups List of groups that are global.
    1009      */
    1010     public function add_global_groups( $groups ) {
    1011         $groups = (array) $groups;
    1012 
    1013         if ( $this->redis_status() ) {
    1014             $this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) );
    1015         } else {
    1016             $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
    1017         }
    1018     }
    1019 
    1020     /**
    1021      * Sets the list of groups not to be cached by Redis.
    1022      *
    1023      * @param array $groups List of groups that are to be ignored.
    1024      */
    1025     public function add_non_persistent_groups( $groups ) {
    1026         $groups = (array) $groups;
    1027 
    1028         $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
    1029     }
    1030 
    1031     /**
    1032      * Wrapper to validate the cache keys expiration value
    1033      *
    1034      * @param mixed $expiration Incomming expiration value (whatever it is)
    1035      */
    1036     protected function validate_expiration( $expiration ) {
    1037         $expiration = ( is_array( $expiration ) || is_object( $expiration ) ? 0 : abs( intval( $expiration ) ) );
    1038 
    1039         if ( $expiration === 0 && defined( 'WP_REDIS_MAXTTL' ) ) {
    1040             $expiration = intval( WP_REDIS_MAXTTL );
    1041         }
    1042 
    1043         return $expiration;
    1044     }
    1045 
    1046     /**
    1047      * Unserialize value only if it was serialized.
    1048      *
    1049      * @param string $original Maybe unserialized original, if is needed.
    1050      * @return mixed Unserialized data can be any type.
    1051      */
    1052     protected function maybe_unserialize( $original ) {
    1053         // don't attempt to unserialize data that wasn't serialized going in
    1054         if ( $this->is_serialized( $original ) ) {
    1055             return @unserialize( $original );
    1056         }
    1057 
    1058         return $original;
    1059     }
    1060 
    1061     /**
    1062      * Serialize data, if needed.
    1063      * @param string|array|object $data Data that might be serialized.
    1064      * @return mixed A scalar data
    1065      */
    1066     protected function maybe_serialize( $data ) {
    1067         if ( is_array( $data ) || is_object( $data ) ) {
    1068             return serialize( $data );
    1069         }
    1070 
    1071         if ( $this->is_serialized( $data, false ) ) {
    1072             return serialize( $data );
    1073         }
    1074 
    1075         return $data;
    1076     }
    1077 
    1078     /**
    1079      * Check value to find if it was serialized.
    1080      *
    1081      * If $data is not an string, then returned value will always be false.
    1082      * Serialized data is always a string.
    1083      *
    1084      * @param string $data   Value to check to see if was serialized.
    1085      * @param bool   $strict Optional. Whether to be strict about the end of the string. Default true.
    1086      * @return bool False if not serialized and true if it was.
    1087      */
    1088     protected function is_serialized( $data, $strict = true ) {
    1089         // if it isn't a string, it isn't serialized.
    1090         if ( ! is_string( $data ) ) {
    1091             return false;
    1092         }
    1093 
    1094         $data = trim( $data );
    1095 
    1096          if ( 'N;' == $data ) {
    1097             return true;
    1098         }
    1099 
    1100         if ( strlen( $data ) < 4 ) {
    1101             return false;
    1102         }
    1103 
    1104         if ( ':' !== $data[1] ) {
    1105             return false;
    1106         }
    1107 
    1108         if ( $strict ) {
    1109             $lastc = substr( $data, -1 );
    1110 
    1111             if ( ';' !== $lastc && '}' !== $lastc ) {
    1112                 return false;
    1113             }
    1114         } else {
    1115             $semicolon = strpos( $data, ';' );
    1116             $brace = strpos( $data, '}' );
    1117 
    1118             // Either ; or } must exist.
    1119             if ( false === $semicolon && false === $brace ) {
    1120                 return false;
    1121             }
    1122 
    1123             // But neither must be in the first X characters.
    1124             if ( false !== $semicolon && $semicolon < 3 ) {
    1125                 return false;
    1126             }
    1127 
    1128             if ( false !== $brace && $brace < 4 ) {
    1129                 return false;
    1130             }
    1131         }
    1132         $token = $data[0];
    1133 
    1134         switch ( $token ) {
    1135             case 's':
    1136                 if ( $strict ) {
    1137                     if ( '"' !== substr( $data, -2, 1 ) ) {
    1138                         return false;
    1139                     }
    1140                 } elseif ( false === strpos( $data, '"' ) ) {
    1141                     return false;
    1142                 }
    1143                 // or else fall through
    1144             case 'a':
    1145             case 'O':
    1146                 return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
    1147             case 'b':
    1148             case 'i':
    1149             case 'd':
    1150                 $end = $strict ? '$' : '';
    1151 
    1152                 return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data );
    1153         }
    1154 
    1155         return false;
    1156     }
    1157 
     2057    /**
     2058     * Increment a Redis counter by the amount specified
     2059     *
     2060     * @param  string $key    The key name.
     2061     * @param  int    $offset Optional. The increment. Defaults to 1.
     2062     * @param  string $group  Optional. The key group. Default is 'default'.
     2063     * @return int|bool
     2064     */
     2065    public function increment( $key, $offset = 1, $group = 'default' ) {
     2066        $offset = (int) $offset;
     2067        $start_time = microtime( true );
     2068        $derived_key = $this->build_key( $key, $group );
     2069        $trace_flags = self::TRACE_FLAG_READ | self::TRACE_FLAG_WRITE;
     2070
     2071        // If group is a non-Redis group, save to internal cache, not Redis.
     2072        if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) {
     2073            $value = $this->get_from_internal_cache( $derived_key );
     2074            $value += $offset;
     2075            $this->add_to_internal_cache( $derived_key, $value );
     2076
     2077            if ( $this->trace_enabled ) {
     2078                $this->trace_command( 'incr', $group, [
     2079                    $key => [
     2080                        'value' => $value,
     2081                        'status' => $trace_flags | self::TRACE_FLAG_INTERNAL,
     2082                    ],
     2083                ], microtime( true ) - $start_time );
     2084            }
     2085
     2086            return $value;
     2087        }
     2088
     2089        try {
     2090            $result = $this->parse_redis_response( $this->redis->incrBy( $derived_key, $offset ) );
     2091
     2092            $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) );
     2093        } catch ( Exception $exception ) {
     2094            $this->handle_exception( $exception );
     2095
     2096            return false;
     2097        }
     2098
     2099        $execute_time = microtime( true ) - $start_time;
     2100
     2101        if ( $this->trace_enabled ) {
     2102            $this->trace_command( 'incr', $group, [
     2103                $key => [
     2104                    'value' => $result,
     2105                    'status' => $trace_flags,
     2106                ],
     2107            ], $execute_time );
     2108        }
     2109
     2110        $this->cache_calls += 2;
     2111        $this->cache_time += $execute_time;
     2112
     2113        return $result;
     2114    }
     2115
     2116    /**
     2117     * Alias of `increment()`.
     2118     *
     2119     * @see self::increment()
     2120     * @param  string $key    The key name.
     2121     * @param  int    $offset Optional. The increment. Defaults to 1.
     2122     * @param  string $group  Optional. The key group. Default is 'default'.
     2123     * @return int|bool
     2124     */
     2125    public function incr( $key, $offset = 1, $group = 'default' ) {
     2126        return $this->increment( $key, $offset, $group );
     2127    }
     2128
     2129    /**
     2130     * Decrement a Redis counter by the amount specified
     2131     *
     2132     * @param  string $key    The key name.
     2133     * @param  int    $offset Optional. The decrement. Defaults to 1.
     2134     * @param  string $group  Optional. The key group. Default is 'default'.
     2135     * @return int|bool
     2136     */
     2137    public function decrement( $key, $offset = 1, $group = 'default' ) {
     2138        $offset = (int) $offset;
     2139        $start_time = microtime( true );
     2140        $derived_key = $this->build_key( $key, $group );
     2141        $trace_flags = self::TRACE_FLAG_READ | self::TRACE_FLAG_WRITE;
     2142
     2143        // If group is a non-Redis group, save to internal cache, not Redis.
     2144        if ( $this->is_ignored_group( $group ) || ! $this->redis_status() ) {
     2145            $value = $this->get_from_internal_cache( $derived_key );
     2146            $value -= $offset;
     2147            $this->add_to_internal_cache( $derived_key, $value );
     2148
     2149            if ( $this->trace_enabled ) {
     2150                $this->trace_command( 'decr', $group, [
     2151                    $key => [
     2152                        'value' => $value,
     2153                        'status' => $trace_flags | self::TRACE_FLAG_INTERNAL,
     2154                    ],
     2155                ], microtime( true ) - $start_time );
     2156            }
     2157
     2158            return $value;
     2159        }
     2160
     2161
     2162        try {
     2163            // Save to Redis.
     2164            $result = $this->parse_redis_response( $this->redis->decrBy( $derived_key, $offset ) );
     2165
     2166            $this->add_to_internal_cache( $derived_key, (int) $this->redis->get( $derived_key ) );
     2167        } catch ( Exception $exception ) {
     2168            $this->handle_exception( $exception );
     2169
     2170            return false;
     2171        }
     2172
     2173        $execute_time = microtime( true ) - $start_time;
     2174
     2175        if ( $this->trace_enabled ) {
     2176            $this->trace_command( 'decr', $group, [
     2177                $key => [
     2178                    'value' => $result,
     2179                    'status' => $trace_flags,
     2180                ],
     2181            ], $execute_time );
     2182        }
     2183
     2184        $this->cache_calls += 2;
     2185        $this->cache_time += $execute_time;
     2186
     2187        return $result;
     2188    }
     2189
     2190    /**
     2191     * Alias of `decrement()`.
     2192     *
     2193     * @see self::decrement()
     2194     * @param  string $key    The key name.
     2195     * @param  int    $offset Optional. The decrement. Defaults to 1.
     2196     * @param  string $group  Optional. The key group. Default is 'default'.
     2197     * @return int|bool
     2198     */
     2199    public function decr( $key, $offset = 1, $group = 'default' ) {
     2200        return $this->decrement( $key, $offset, $group );
     2201    }
     2202
     2203    /**
     2204     * Render data about current cache requests
     2205     * Used by the Debug bar plugin
     2206     *
     2207     * @return void
     2208     */
     2209    public function stats() {
     2210        ?>
     2211    <p>
     2212        <strong>Redis Status:</strong>
     2213        <?php echo $this->redis_status() ? 'Connected' : 'Not connected'; ?>
     2214        <br />
     2215        <strong>Redis Client:</strong>
     2216        <?php echo $this->diagnostics['client'] ?: 'Unknown'; ?>
     2217        <br />
     2218        <strong>Cache Hits:</strong>
     2219        <?php echo (int) $this->cache_hits; ?>
     2220        <br />
     2221        <strong>Cache Misses:</strong>
     2222        <?php echo (int) $this->cache_misses; ?>
     2223        <br />
     2224        <strong>Cache Size:</strong>
     2225        <?php echo number_format( strlen( serialize( $this->cache ) ) / 1024, 2 ); ?> KB
     2226    </p>
     2227        <?php
     2228    }
     2229
     2230    /**
     2231     * Returns various information about the object cache.
     2232     *
     2233     * @return object
     2234     */
     2235    public function info() {
     2236        $total = $this->cache_hits + $this->cache_misses;
     2237
     2238        $bytes = array_map(
     2239            function ( $keys ) {
     2240                // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
     2241                return strlen( serialize( $keys ) );
     2242            },
     2243            $this->cache
     2244        );
     2245
     2246        return (object) [
     2247            'hits' => $this->cache_hits,
     2248            'misses' => $this->cache_misses,
     2249            'ratio' => $total > 0 ? round( $this->cache_hits / ( $total / 100 ), 1 ) : 100,
     2250            'bytes' => array_sum( $bytes ),
     2251            'time' => $this->cache_time,
     2252            'calls' => $this->cache_calls,
     2253            'groups' => (object) [
     2254                'global' => $this->global_groups,
     2255                'non_persistent' => $this->ignored_groups,
     2256                'unflushable' => $this->unflushable_groups,
     2257            ],
     2258            'errors' => empty( $this->errors ) ? null : $this->errors,
     2259            'meta' => [
     2260                'Client' => $this->diagnostics['client'] ?: 'Unknown',
     2261                'Redis Version' => $this->redis_version,
     2262            ],
     2263        ];
     2264    }
     2265
     2266    /**
     2267     * Builds a key for the cached object using the prefix, group and key.
     2268     *
     2269     * @param   string $key        The key under which to store the value.
     2270     * @param   string $group      The group value appended to the $key.
     2271     *
     2272     * @return  string
     2273     */
     2274    public function build_key( $key, $group = 'default' ) {
     2275        if ( empty( $group ) ) {
     2276            $group = 'default';
     2277        }
     2278
     2279        $salt = defined( 'WP_REDIS_PREFIX' ) ? trim( WP_REDIS_PREFIX ) : '';
     2280        $prefix = $this->is_global_group( $group ) ? $this->global_prefix : $this->blog_prefix;
     2281
     2282        $key = $this->sanitize_key_part( $key );
     2283        $group = $this->sanitize_key_part( $group );
     2284
     2285        $prefix = trim( $prefix, '_-:$' );
     2286
     2287        return "{$salt}{$prefix}:{$group}:{$key}";
     2288    }
     2289
     2290    /**
     2291     * Replaces the set group separator by another one
     2292     *
     2293     * @param   string $part  The string to sanitize.
     2294     * @return  string        Sanitized string.
     2295     */
     2296    protected function sanitize_key_part( $part ) {
     2297        return str_replace( ':', '-', $part );
     2298    }
     2299
     2300    /**
     2301     * Checks if the given group is part the ignored group array
     2302     *
     2303     * @param string $group  Name of the group to check.
     2304     * @return bool
     2305     */
     2306    protected function is_ignored_group( $group ) {
     2307        return in_array( $this->sanitize_key_part( $group ), $this->ignored_groups, true );
     2308    }
     2309
     2310    /**
     2311     * Checks if the given group is part the global group array
     2312     *
     2313     * @param string $group  Name of the group to check.
     2314     * @return bool
     2315     */
     2316    protected function is_global_group( $group ) {
     2317        return in_array( $this->sanitize_key_part( $group ), $this->global_groups, true );
     2318    }
     2319
     2320    /**
     2321     * Checks if the given group is part the unflushable group array
     2322     *
     2323     * @param string $group  Name of the group to check.
     2324     * @return bool
     2325     */
     2326    protected function is_unflushable_group( $group ) {
     2327        return in_array( $this->sanitize_key_part( $group ), $this->unflushable_groups, true );
     2328    }
     2329
     2330    /**
     2331     * Convert Redis responses into something meaningful
     2332     *
     2333     * @param mixed $response Response sent from the redis instance.
     2334     * @return mixed
     2335     */
     2336    protected function parse_redis_response( $response ) {
     2337        if ( is_bool( $response ) ) {
     2338            return $response;
     2339        }
     2340
     2341        if ( is_numeric( $response ) ) {
     2342            return $response;
     2343        }
     2344
     2345        if ( is_object( $response ) && method_exists( $response, 'getPayload' ) ) {
     2346            return $response->getPayload() === 'OK';
     2347        }
     2348
     2349        return false;
     2350    }
     2351
     2352    /**
     2353     * Simple wrapper for saving object to the internal cache.
     2354     *
     2355     * @param   string $derived_key    Key to save value under.
     2356     * @param   mixed  $value          Object value.
     2357     */
     2358    public function add_to_internal_cache( $derived_key, $value ) {
     2359        if ( is_object( $value ) ) {
     2360            $value = clone $value;
     2361        }
     2362
     2363        $this->cache[ $derived_key ] = $value;
     2364    }
     2365
     2366    /**
     2367     * Get a value specifically from the internal, run-time cache, not Redis.
     2368     *
     2369     * @param   int|string $derived_key Key value.
     2370     *
     2371     * @return  bool|mixed              Value on success; false on failure.
     2372     */
     2373    public function get_from_internal_cache( $derived_key ) {
     2374        if ( ! isset( $this->cache[ $derived_key ] ) ) {
     2375            return false;
     2376        }
     2377
     2378        if ( is_object( $this->cache[ $derived_key ] ) ) {
     2379            return clone $this->cache[ $derived_key ];
     2380        }
     2381
     2382        return $this->cache[ $derived_key ];
     2383    }
     2384
     2385    /**
     2386     * In multisite, switch blog prefix when switching blogs
     2387     *
     2388     * @param int $_blog_id Blog ID.
     2389     * @return bool
     2390     */
     2391    public function switch_to_blog( $_blog_id ) {
     2392        if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) {
     2393            return false;
     2394        }
     2395
     2396        $this->blog_prefix = $_blog_id;
     2397
     2398        return true;
     2399    }
     2400
     2401    /**
     2402     * Sets the list of global groups.
     2403     *
     2404     * @param array $groups List of groups that are global.
     2405     */
     2406    public function add_global_groups( $groups ) {
     2407        $groups = (array) $groups;
     2408
     2409        if ( $this->redis_status() ) {
     2410            $this->global_groups = array_unique( array_merge( $this->global_groups, $groups ) );
     2411        } else {
     2412            $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
     2413        }
     2414    }
     2415
     2416    /**
     2417     * Sets the list of groups not to be cached by Redis.
     2418     *
     2419     * @param array $groups  List of groups that are to be ignored.
     2420     */
     2421    public function add_non_persistent_groups( $groups ) {
     2422        $groups = (array) $groups;
     2423
     2424        $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
     2425    }
     2426
     2427    /**
     2428     * Sets the list of groups not to flushed cached.
     2429     *
     2430     * @param array $groups List of groups that are unflushable.
     2431     */
     2432    public function add_unflushable_groups( $groups ) {
     2433        $groups = (array) $groups;
     2434
     2435        $this->unflushable_groups = array_unique( array_merge( $this->unflushable_groups, $groups ) );
     2436    }
     2437
     2438    /**
     2439     * Wrapper to validate the cache keys expiration value
     2440     *
     2441     * @param mixed $expiration  Incoming expiration value (whatever it is).
     2442     */
     2443    protected function validate_expiration( $expiration ) {
     2444        $expiration = is_int( $expiration ) || ctype_digit( (string) $expiration ) ? (int) $expiration : 0;
     2445
     2446        if ( defined( 'WP_REDIS_MAXTTL' ) ) {
     2447            $max = (int) WP_REDIS_MAXTTL;
     2448
     2449            if ( $expiration === 0 || $expiration > $max ) {
     2450                $expiration = $max;
     2451            }
     2452        }
     2453
     2454        return $expiration;
     2455    }
     2456
     2457    /**
     2458     * Unserialize value only if it was serialized.
     2459     *
     2460     * @param string $original  Maybe unserialized original, if is needed.
     2461     * @return mixed            Unserialized data can be any type.
     2462     */
     2463    protected function maybe_unserialize( $original ) {
     2464        if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) {
     2465            return $original;
     2466        }
     2467
     2468        if ( defined( 'WP_REDIS_IGBINARY' ) && WP_REDIS_IGBINARY && function_exists( 'igbinary_unserialize' ) ) {
     2469            return igbinary_unserialize( $original );
     2470        }
     2471
     2472        // Don't attempt to unserialize data that wasn't serialized going in.
     2473        if ( $this->is_serialized( $original ) ) {
     2474            // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
     2475            $value = @unserialize( $original );
     2476
     2477            return is_object( $value ) ? clone $value : $value;
     2478        }
     2479
     2480        return $original;
     2481    }
     2482
     2483    /**
     2484     * Serialize data, if needed.
     2485     *
     2486     * @param mixed $data  Data that might be serialized.
     2487     * @return mixed       A scalar data
     2488     */
     2489    protected function maybe_serialize( $data ) {
     2490        if ( is_object( $data ) ) {
     2491            $data = clone $data;
     2492        }
     2493
     2494        if ( defined( 'WP_REDIS_SERIALIZER' ) && ! empty( WP_REDIS_SERIALIZER ) ) {
     2495            return $data;
     2496        }
     2497
     2498        if ( defined( 'WP_REDIS_IGBINARY' ) && WP_REDIS_IGBINARY && function_exists( 'igbinary_serialize' ) ) {
     2499            return igbinary_serialize( $data );
     2500        }
     2501
     2502        if ( is_array( $data ) || is_object( $data ) ) {
     2503            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
     2504            return serialize( $data );
     2505        }
     2506
     2507        if ( $this->is_serialized( $data, false ) ) {
     2508            // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
     2509            return serialize( $data );
     2510        }
     2511
     2512        return $data;
     2513    }
     2514
     2515    /**
     2516     * Check value to find if it was serialized.
     2517     *
     2518     * If $data is not an string, then returned value will always be false.
     2519     * Serialized data is always a string.
     2520     *
     2521     * @param string $data    Value to check to see if was serialized.
     2522     * @param bool   $strict  Optional. Whether to be strict about the end of the string. Default true.
     2523     * @return bool           False if not serialized and true if it was.
     2524     */
     2525    protected function is_serialized( $data, $strict = true ) {
     2526        // if it isn't a string, it isn't serialized.
     2527        if ( ! is_string( $data ) ) {
     2528            return false;
     2529        }
     2530
     2531        $data = trim( $data );
     2532
     2533        if ( 'N;' === $data ) {
     2534            return true;
     2535        }
     2536
     2537        if ( strlen( $data ) < 4 ) {
     2538            return false;
     2539        }
     2540
     2541        if ( ':' !== $data[1] ) {
     2542            return false;
     2543        }
     2544
     2545        if ( $strict ) {
     2546            $lastc = substr( $data, -1 );
     2547
     2548            if ( ';' !== $lastc && '}' !== $lastc ) {
     2549                return false;
     2550            }
     2551        } else {
     2552            $semicolon = strpos( $data, ';' );
     2553            $brace = strpos( $data, '}' );
     2554
     2555            // Either ; or } must exist.
     2556            if ( false === $semicolon && false === $brace ) {
     2557                return false;
     2558            }
     2559
     2560            // But neither must be in the first X characters.
     2561            if ( false !== $semicolon && $semicolon < 3 ) {
     2562                return false;
     2563            }
     2564
     2565            if ( false !== $brace && $brace < 4 ) {
     2566                return false;
     2567            }
     2568        }
     2569        $token = $data[0];
     2570
     2571        switch ( $token ) {
     2572            case 's':
     2573                if ( $strict ) {
     2574                    if ( '"' !== substr( $data, -2, 1 ) ) {
     2575                        return false;
     2576                    }
     2577                } elseif ( false === strpos( $data, '"' ) ) {
     2578                    return false;
     2579                }
     2580                // Or else fall through.
     2581                // No break!
     2582            case 'a':
     2583            case 'O':
     2584                return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
     2585            case 'b':
     2586            case 'i':
     2587            case 'd':
     2588                $end = $strict ? '$' : '';
     2589
     2590                return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data );
     2591        }
     2592
     2593        return false;
     2594    }
     2595
     2596    /**
     2597     * Invoke the `redis_object_cache_trace` hook.
     2598     *
     2599     * @param  string             $command
     2600     * @param  string             $group
     2601     * @param  array[string]array $keyValues
     2602     * @param  float              $duration
     2603     * @return void
     2604     */
     2605    private function trace_command ( $command, $group, $keyValues, $duration ) {
     2606        if ( ! $this->trace_enabled || ! function_exists( 'do_action' ) ) {
     2607            return;
     2608        }
     2609
     2610        /**
     2611         * Fires on every cache call.
     2612         *
     2613         * This hook is called on every cache request.
     2614         * It reports statistics per key involved. @see WP_Object_Cache::TRACE_FLAG_READ and friends.
     2615         *
     2616         * @param  string             $command   The command that was executed.
     2617         * @param  string             $group     Key group.
     2618         * @param  array[string]array $keyValues Maps keys to the returned values (if any) and the resulting status.
     2619         *  $keyValues = [
     2620         *      "foo" => ["value" => "bar", "status" => TRACE_FLAG_READ | TRACE_FLAG_HIT],                       // hit on redis (implies internal miss)
     2621         *      "baz" => ["value" => "quo", "status" => TRACE_FLAG_READ | TRACE_FLAG_HIT | TRACE_FLAG_INTERNAL], // hit on internal cache
     2622         *      "eta" => ["value" => null,  "status" => TRACE_FLAG_READ],                                        // miss
     2623         * ];
     2624         * @param  float              $duration  Duration of the request in microseconds.
     2625         * @return void
     2626         */
     2627        do_action( 'redis_object_cache_trace', $command, $group, $keyValues, $duration );
     2628    }
     2629
     2630    /**
     2631     * Handle the redis failure gracefully or throw an exception.
     2632     *
     2633     * @param \Exception $exception  Exception thrown.
     2634     * @throws \Exception If `fail_gracefully` flag is set to a falsy value.
     2635     * @return void
     2636     */
     2637    protected function handle_exception( $exception ) {
     2638        $this->redis_connected = false;
     2639
     2640        // When Redis is unavailable, fall back to the internal cache by forcing all groups to be "no redis" groups.
     2641        $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $this->global_groups ) );
     2642
     2643        if ( ! $this->fail_gracefully ) {
     2644            throw $exception;
     2645        }
     2646
     2647        $this->errors[] = $exception->getMessage();
     2648
     2649        error_log( $exception ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     2650
     2651        if ( function_exists( 'do_action' ) ) {
     2652            /**
     2653             * Fires on every cache error
     2654             *
     2655             * @since 1.5.0
     2656             * @param \Exception $exception The exception triggered.
     2657             */
     2658            do_action( 'redis_object_cache_error', $exception );
     2659        }
     2660    }
     2661
     2662    /**
     2663     * Allows access to private properties for backwards compatibility.
     2664     *
     2665     * @param string $name Name of the property.
     2666     * @return mixed
     2667     */
     2668    public function __get( $name ) {
     2669        return isset( $this->{$name} ) ? $this->{$name} : null;
     2670    }
    11582671}
    11592672
    11602673endif;
     2674// phpcs:enable Generic.WhiteSpace.ScopeIndent.IncorrectExact, Generic.WhiteSpace.ScopeIndent.Incorrect
  • wpbase-cache/trunk/readme.txt

    r2321389 r2694612  
    33Tags: cache,chaching,speed,performance,db cache,optimization,nginx,varnish
    44Requires at least: 3.5
    5 Tested up to: 5.4.1
     5Tested up to: 5.9
    66Stable tag: trunk
    77Donate link:
     
    2020- Vladimir Kolesnikov
    2121
    22 Visit our blog for more information on deployment of wordpress on varnish, nginx and php-fpm stack at [WPOven Blog](http://blog.wpoven.com/2013/08/03/wpbase-cache-plugin-features-and-advantages/).
     22Visit our blog for more information on deployment of wordpress on varnish, nginx and php-fpm stack at [WPOven Blog](https://www.wpoven.com/blog/wpbase-cache-plugin-features-and-advantages/).
    2323
    2424Please note that generally shared hosting don't have varnish + nginx as server software so this plugin will not work on shared hostings.
  • wpbase-cache/trunk/wpbase-cache-admin.php

    r2006650 r2694612  
    11<?php
    2 class WPBase_Cache_Admin {
    3 
    4     public function __construct() {
    5         $plugin = $GLOBALS[ 'redisObjectCache' ];
    6        
     2class WPBase_Cache_Admin
     3{
     4
     5    public function __construct()
     6    {
     7        $plugin = $GLOBALS['redisObjectCache'];
     8
    79        add_action('admin_menu', array($this, 'add_wpbase_cache_page'));
    810        add_action('admin_init', array($this, 'wpbase_cache_page_init'));
     
    1315    }
    1416
    15     public function add_wpbase_cache_page() {
     17    public function add_wpbase_cache_page()
     18    {
    1619        add_options_page('WPBase Cache', 'WPBase', 'manage_options', 'wpbasecache', array($this, 'create_wpbase_cache_page'));
    1720    }
    1821
    19     public function create_wpbase_cache_page() {
     22    public function create_wpbase_cache_page()
     23    {
    2024        if (!current_user_can('manage_options')) {
    2125            wp_die(__('You do not have sufficient permissions to access this page.'));
    2226        }
    23         ?>
     27?>
    2428        <div class="wrap">
    25         <?php screen_icon(); ?>
     29            <?php //screen_icon(); ?>
    2630            <h2>WPBase Cache</h2>
    2731            <form method="post" action="options.php">
     
    3034                do_settings_sections('wpbasecache');
    3135                ?>
    32         <?php submit_button(); ?>
     36                <?php submit_button(); ?>
    3337                <a class="button" id="wpbase_cache_flush_all">Empty All Caches</a>
    3438            </form>
    3539        </div>
    36         <?php
    37     }
    38 
    39     public function wpbase_cache_page_init() {
     40    <?php
     41    }
     42
     43    public function wpbase_cache_page_init()
     44    {
    4045        register_setting('wpbase_cache_options', 'wpbase_cache_options');
    4146
    4247        add_settings_section(
    43                 'wpbase_cache_section', 'WPBase Cache Settings', array($this, 'wpbase_cache_section_desc'), 'wpbasecache'
    44         );
    45 
    46     add_settings_field(
    47                 'wpbase_cache_options_admin_bar_button', 'Show dashboard button in admin bar', array($this, 'admin_bar_button_input'), 'wpbasecache', 'wpbase_cache_section'
    48         );
    49 
    50        
    51         add_settings_field(
    52                 'wpbase_cache_options_varnish_cache', 'Enable Varnish Cache', array($this, 'varnish_cache_input'), 'wpbasecache', 'wpbase_cache_section'
    53         );
    54 
    55         add_settings_field(
    56                 'wpbase_cache_options_send_as', 'Send Mail As', array($this, 'send_as_input'), 'wpbasecache', 'wpbase_cache_section'
    57         );
    58        
    59         add_settings_field(
    60                 'wpbase_cache_options_redis_cache', 'Enable Redis Cache', array($this, 'redis_cache_input'), 'wpbasecache', 'wpbase_cache_section'
     48            'wpbase_cache_section',
     49            'WPBase Cache Settings',
     50            array($this, 'wpbase_cache_section_desc'),
     51            'wpbasecache'
     52        );
     53
     54        add_settings_field(
     55            'wpbase_cache_options_admin_bar_button',
     56            'Show dashboard button in admin bar',
     57            array($this, 'admin_bar_button_input'),
     58            'wpbasecache',
     59            'wpbase_cache_section'
     60        );
     61
     62
     63        add_settings_field(
     64            'wpbase_cache_options_varnish_cache',
     65            'Enable Varnish Cache',
     66            array($this, 'varnish_cache_input'),
     67            'wpbasecache',
     68            'wpbase_cache_section'
     69        );
     70
     71        add_settings_field(
     72            'wpbase_cache_options_send_as',
     73            'Send Mail As',
     74            array($this, 'send_as_input'),
     75            'wpbasecache',
     76            'wpbase_cache_section'
     77        );
     78
     79        add_settings_field(
     80            'wpbase_cache_options_redis_cache',
     81            'Enable Redis Cache',
     82            array($this, 'redis_cache_input'),
     83            'wpbasecache',
     84            'wpbase_cache_section'
    6185        );
    6286        //add_settings_field(
    6387        //        'wpbase_cache_options_cookie_text', 'Cookie warning text', array($this, 'cookie_warning_text'), 'wpbasecache', 'wpbase_cache_section'
    6488        //);       
    65        
    66     }
    67 
    68 
    69     public function cookie_warning_text() {
    70         $options = get_option('wpbase_cache_options');
    71 
    72         if($options['send_as']!=''){
    73             $send_as= $options['send_as'];
     89
     90    }
     91
     92
     93    public function cookie_warning_text()
     94    {
     95        $options = get_option('wpbase_cache_options');
     96
     97        if ($options['send_as'] != '') {
     98            $send_as = $options['send_as'];
    7499        }
    75100        if (!(defined('WPBASE_CACHE_SANDBOX') && WPBASE_CACHE_SANDBOX)) {
    76101            echo "<input id='wpbase_cache_send_as' name='wpbase_cache_options[send_as]' type='textbox' value='$cookie_text' style='width: 50%;' /> <br /><font color='gray'>GDPR warning text.</font>";
    77         }
    78     }
    79 
    80 
    81     public function wpbase_cache_section_desc() {
    82     }
    83 
    84     public function admin_bar_button_input(){
     102        }
     103    }
     104
     105
     106    public function wpbase_cache_section_desc()
     107    {
     108    }
     109
     110    public function admin_bar_button_input()
     111    {
    85112        $options = get_option('wpbase_cache_options');
    86113        $checked = checked(1, $options['admin_bar_button'], FALSE);
     
    91118        }
    92119    }
    93    
    94         public function redis_cache_input() {
    95             $plugin = $GLOBALS[ 'redisObjectCache' ];
    96         $options = get_option('wpbase_cache_options');
     120
     121    public function redis_cache_input()
     122    {
     123        $plugin = $GLOBALS['redisObjectCache'];
     124        $options = get_option('wpbase_cache_options');
     125
     126        if(!isset($options['redis_cache'])) {
     127            $options['redis_cache'] = 0;
     128        }
    97129
    98130        $checked = checked(1, $options['redis_cache'], FALSE);
    99131        $status = $plugin->get_status();
    100         if($status == 'Unknown'){
    101               echo "<input id='wpbase_cache_redis_cache' disabled='disabled' name='wpbase_cache_options[redis_cache]' type='checkbox' value='' /> Redis not installed";
    102         }
    103         else if (!(defined('WPBASE_CACHE_SANDBOX') && WPBASE_CACHE_SANDBOX)) {
     132        if ($status == 'Unknown') {
     133            echo "<input id='wpbase_cache_redis_cache' disabled='disabled' name='wpbase_cache_options[redis_cache]' type='checkbox' value='' /> Redis not installed";
     134        } else if (!(defined('WPBASE_CACHE_SANDBOX') && WPBASE_CACHE_SANDBOX)) {
    104135            echo "<input id='wpbase_cache_redis_cache' name='wpbase_cache_options[redis_cache]' type='checkbox' value='1' $checked />";
    105             echo 'Status: '.$plugin->get_status();;
    106            
     136            echo 'Status: ' . $plugin->get_status();;
    107137        } else {
    108138            echo "<input id='wpbase_cache_redis_cache' disabled='disabled' name='wpbase_cache_options[redis_cache]' type='checkbox' value='1' $checked />";
    109139        }
    110140    }
    111    
    112    
    113     public function varnish_cache_input() {
     141
     142
     143    public function varnish_cache_input()
     144    {
    114145        $options = get_option('wpbase_cache_options');
    115146
     
    122153    }
    123154
    124     public function send_as_input() {
    125         $options = get_option('wpbase_cache_options');
    126 
    127         if($options['send_as']!=''){
    128             $send_as= $options['send_as'];
     155    public function send_as_input()
     156    {
     157        $options = get_option('wpbase_cache_options');
     158
     159        if ($options['send_as'] != '') {
     160            $send_as = $options['send_as'];
    129161        }
    130162        if (!(defined('WPBASE_CACHE_SANDBOX') && WPBASE_CACHE_SANDBOX)) {
     
    135167    }
    136168
    137     public function add_javascript() {
     169    public function add_javascript()
     170    {
    138171        $nonce = wp_create_nonce('wpbase_cache_flush_all');
    139         ?>
    140         <script type="text/javascript" >
     172    ?>
     173        <script type="text/javascript">
    141174            jQuery(document).ready(function($) {
    142175
     
    161194            });
    162195        </script>
    163         <?php
    164     }
    165 
    166     public function ajax_flush_cache() {
     196<?php
     197    }
     198
     199    public function ajax_flush_cache()
     200    {
    167201        check_ajax_referer('wpbase_cache_flush_all');
    168202
     
    176210
    177211        delete_option('wpbase_req_cache_new');
    178        
     212
    179213        echo 1;
    180214        die;
    181215    }
    182216
    183     public function update_options($oldvalue, $newvalue) {
    184        
    185     }
    186 
     217    public function update_options($oldvalue, $newvalue)
     218    {
     219    }
    187220}
    188221$options = get_option('wpbase_cache_options');
    189222
    190 if ($options['admin_bar_button'] == '1'){
    191    add_action('init', 'check_wpoven_site');   
    192 }
    193 else{
     223if ($options['admin_bar_button'] == '1') {
     224    add_action('init', 'check_wpoven_site');
     225} else {
    194226    delete_option('wpbase_req_cache_new');
    195227    delete_option('wpbase_check_site');
     
    197229
    198230$wpbase_cache_admin = new WPBase_Cache_Admin();
    199 
  • wpbase-cache/trunk/wpbase-cache.php

    r2362636 r2694612  
    44  Plugin URI: https://www.wpoven.com/
    55  Description: Custom WordPress Caching plugin for WPOven Hosted Sites which uses all caches on varnish, nginx, php-fpm stack and Redis.
    6   Version: 5.5.4
     6  Version: 5.5.5
    77  Author: Vikrant Datta
    88  Author URI: https://www.wpoven.com/
     
    2727define('WPBASE_CACHE_DIR', WP_PLUGIN_DIR . '/wpbase-cache');
    2828define('WPBASE_CACHE_INC_DIR', WP_PLUGIN_DIR . '/wpbase-cache/inc');
    29 
    30 preg_match('/define.*DB_NAME.*\'(.*)\'/', $configs, $m);
    31 $dbname = $m[1];
    32 define('DB_NAME', $dbname);
    3329
    3430add_action('admin_enqueue_scripts', function() {
  • wpbase-cache/trunk/wpbase-redis-cache.php

    r2362636 r2694612  
    2929        add_action('load-options.php', array($this, 'do_admin_actions'), 1);
    3030        //add_action( 'load-' . $this->screen, array( $this, 'do_admin_actions' ) );
    31         add_action( 'load-' . $this->screen, array( $this, 'add_admin_page_notices' ) );
     31        //add_action( 'load-' . $this->screen, array( $this, 'add_admin_page_notices' ) );
    3232
    3333        add_filter( sprintf(
     
    9191    public function enqueue_admin_styles( $hook_suffix ) {
    9292
    93         if ( $hook_suffix === $this->screen ) {
     93        /*if ( $hook_suffix === $this->screen ) {
    9494            $plugin = get_plugin_data( __FILE__ );
    9595            wp_enqueue_style( 'redis-cache', plugin_dir_url( __FILE__ ) . 'includes/admin-page.css', null, $plugin[ 'Version' ] );
    9696        }
     97        */
    9798
    9899    }
Note: See TracChangeset for help on using the changeset viewer.