PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 3.6
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v3.6
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
powered-cache / includes / dropins / redis-object-cache.php

redis-object-cache.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 3.6, at includes/dropins/redis-object-cache.php

1,563 lines 47.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // WP Redis
4 // This file needs to be symlinked or copied to wp-content/object-cache.php
5
6 // Users with setups where multiple installs share a common wp-config.php or $table_prefix
7 // can use this to guarantee uniqueness for the keys generated by this object cache.
8 if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {
9 define( 'WP_CACHE_KEY_SALT', '' );
10 }
11
12 if ( ! defined( 'WP_REDIS_OBJECT_CACHE' ) ) {
13 define( 'WP_REDIS_OBJECT_CACHE', true );
14 }
15
16 if ( ! defined( 'WP_REDIS_USE_CACHE_GROUPS' ) ) {
17 define( 'WP_REDIS_USE_CACHE_GROUPS', false );
18 }
19
20 if ( ! defined( 'WP_REDIS_DEFAULT_EXPIRE_SECONDS' ) ) {
21 define( 'WP_REDIS_DEFAULT_EXPIRE_SECONDS', 0 );
22 }
23
24 if ( ! defined( 'WP_REDIS_IGNORE_GLOBAL_GROUPS' ) ) {
25 define( 'WP_REDIS_IGNORE_GLOBAL_GROUPS', false );
26 }
27
28 /**
29 * Adds data to the cache, if the cache key doesn't already exist.
30 *
31 * @uses $wp_object_cache Object Cache Class
32 * @see WP_Object_Cache::add()
33 *
34 * @param int|string $key The cache key to use for retrieval later
35 * @param mixed $data The data to add to the cache store
36 * @param string $group The group to add the cache to
37 * @param int $expire When the cache data should be expired
38 * @return bool False if cache key and group already exist, true on success
39 */
40 function wp_cache_add( $key, $data, $group = '', $expire = WP_REDIS_DEFAULT_EXPIRE_SECONDS ) {
41 global $wp_object_cache;
42
43 return $wp_object_cache->add( $key, $data, $group, (int) $expire );
44 }
45
46 /**
47 * Closes the cache.
48 *
49 * This function has ceased to do anything since WordPress 2.5. The
50 * functionality was removed along with the rest of the persistent cache. This
51 * does not mean that plugins can't implement this function when they need to
52 * make sure that the cache is cleaned up after WordPress no longer needs it.
53 *
54 * @return bool Always returns True
55 */
56 function wp_cache_close() {
57 return true;
58 }
59
60 /**
61 * Decrement numeric cache item's value
62 *
63 * @uses $wp_object_cache Object Cache Class
64 * @see WP_Object_Cache::decr()
65 *
66 * @param int|string $key The cache key to increment
67 * @param int $offset The amount by which to decrement the item's value. Default is 1.
68 * @param string $group The group the key is in.
69 * @return false|int False on failure, the item's new value on success.
70 */
71 function wp_cache_decr( $key, $offset = 1, $group = '' ) {
72 global $wp_object_cache;
73
74 return $wp_object_cache->decr( $key, $offset, $group );
75 }
76
77 /**
78 * Removes the cache contents matching key and group.
79 *
80 * @uses $wp_object_cache Object Cache Class
81 * @see WP_Object_Cache::delete()
82 *
83 * @param int|string $key What the contents in the cache are called
84 * @param string $group Where the cache contents are grouped
85 * @return bool True on successful removal, false on failure
86 */
87 function wp_cache_delete( $key, $group = '' ) {
88 global $wp_object_cache;
89
90 return $wp_object_cache->delete( $key, $group );
91 }
92
93 /**
94 * Removes cache contents for a given group.
95 *
96 * @uses $wp_object_cache Object Cache Class
97 * @see WP_Object_Cache::delete_group()
98 *
99 * @param string $group Where the cache contents are grouped
100 * @return bool True on successful removal, false on failure
101 */
102 function wp_cache_delete_group( $group ) {
103 global $wp_object_cache;
104 return $wp_object_cache->delete_group( $group );
105 }
106
107
108 /**
109 * Removes all cache items.
110 *
111 * @uses $wp_object_cache Object Cache Class
112 * @see WP_Object_Cache::flush()
113 *
114 * @return bool False on failure, true on success
115 */
116 function wp_cache_flush() {
117 global $wp_object_cache;
118
119 return $wp_object_cache->flush();
120 }
121
122 /**
123 * Retrieves the cache contents from the cache by key and group.
124 *
125 * @uses $wp_object_cache Object Cache Class
126 * @see WP_Object_Cache::get()
127 *
128 * @param int|string $key What the contents in the cache are called
129 * @param string $group Where the cache contents are grouped
130 * @param bool $force Whether to force an update of the local cache from the persistent cache (default is false)
131 * @param &bool $found Whether key was found in the cache. Disambiguates a return of false, a storable value.
132 * @return bool|mixed False on failure to retrieve contents or the cache contents on success
133 */
134 function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
135 global $wp_object_cache;
136
137 return $wp_object_cache->get( $key, $group, $force, $found );
138 }
139
140 /**
141 * Retrieves multiple values from the cache in one call.
142 *
143 * @see WP_Object_Cache::get_multiple()
144 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
145 *
146 * @param array $keys Array of keys under which the cache contents are stored.
147 * @param string $group Optional. Where the cache contents are grouped. Default empty.
148 * @param bool $force Optional. Whether to force an update of the local cache
149 * from the persistent cache. Default false.
150 * @return array Array of values organized into groups.
151 */
152 function wp_cache_get_multiple( $keys, $group = '', $force = false ) {
153 global $wp_object_cache;
154
155 return $wp_object_cache->get_multiple( $keys, $group, $force );
156 }
157
158 /**
159 * Removes all cache items from the in-memory runtime cache.
160 *
161 * @see WP_Object_Cache::flush()
162 *
163 * @return bool True on success, false on failure.
164 */
165 function wp_cache_flush_runtime() {
166 global $wp_object_cache;
167
168 return $wp_object_cache->flush( false );
169 }
170
171 /**
172 * Removes all cache items in a group, if the object cache implementation supports it.
173 *
174 * Before calling this function, always check for group flushing support using the
175 * `wp_cache_supports( 'flush_group' )` function.
176 *
177 * @see WP_Object_Cache::flush_group()
178 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
179 *
180 * @param string $group Name of group to remove from cache.
181 * @return bool True if group was flushed, false otherwise.
182 */
183 function wp_cache_flush_group( $group ) {
184 global $wp_object_cache;
185
186 return $wp_object_cache->flush_group( $group );
187 }
188
189
190 /**
191 * Increment numeric cache item's value
192 *
193 * @uses $wp_object_cache Object Cache Class
194 * @see WP_Object_Cache::incr()
195 *
196 * @param int|string $key The cache key to increment
197 * @param int $offset The amount by which to increment the item's value. Default is 1.
198 * @param string $group The group the key is in.
199 * @return false|int False on failure, the item's new value on success.
200 */
201 function wp_cache_incr( $key, $offset = 1, $group = '' ) {
202 global $wp_object_cache;
203
204 return $wp_object_cache->incr( $key, $offset, $group );
205 }
206
207 /**
208 * Sets up Object Cache Global and assigns it.
209 *
210 * @global WP_Object_Cache $wp_object_cache WordPress Object Cache
211 */
212 function wp_cache_init() {
213 global $wp_object_cache;
214
215 if ( ! ( $wp_object_cache instanceof WP_Object_Cache ) ) {
216 $wp_object_cache = new WP_Object_Cache;
217 }
218 }
219
220 /**
221 * Replaces the contents of the cache with new data.
222 *
223 * @uses $wp_object_cache Object Cache Class
224 * @see WP_Object_Cache::replace()
225 *
226 * @param int|string $key What to call the contents in the cache
227 * @param mixed $data The contents to store in the cache
228 * @param string $group Where to group the cache contents
229 * @param int $expire When to expire the cache contents
230 * @return bool False if not exists, true if contents were replaced
231 */
232 function wp_cache_replace( $key, $data, $group = '', $expire = WP_REDIS_DEFAULT_EXPIRE_SECONDS ) {
233 global $wp_object_cache;
234
235 return $wp_object_cache->replace( $key, $data, $group, (int) $expire );
236 }
237
238 /**
239 * Saves the data to the cache.
240 *
241 * @uses $wp_object_cache Object Cache Class
242 * @see WP_Object_Cache::set()
243 *
244 * @param int|string $key What to call the contents in the cache
245 * @param mixed $data The contents to store in the cache
246 * @param string $group Where to group the cache contents
247 * @param int $expire When to expire the cache contents
248 * @return bool False on failure, true on success
249 */
250 function wp_cache_set( $key, $data, $group = '', $expire = WP_REDIS_DEFAULT_EXPIRE_SECONDS ) {
251 global $wp_object_cache;
252
253 return $wp_object_cache->set( $key, $data, $group, (int) $expire );
254 }
255
256 /**
257 * Switch the internal blog id.
258 *
259 * This changes the blog id used to create keys in blog specific groups.
260 *
261 * @param int $blog_id Blog ID
262 */
263 function wp_cache_switch_to_blog( $blog_id ) {
264 global $wp_object_cache;
265
266 return $wp_object_cache->switch_to_blog( $blog_id );
267 }
268
269 /**
270 * Adds a group or set of groups to the list of global groups.
271 *
272 * @param string|array $groups A group or an array of groups to add
273 */
274 function wp_cache_add_global_groups( $groups ) {
275 global $wp_object_cache;
276
277 return $wp_object_cache->add_global_groups( $groups );
278 }
279
280 /**
281 * Adds a group or set of groups to the list of non-persistent groups.
282 *
283 * @param string|array $groups A group or an array of groups to add
284 */
285 function wp_cache_add_non_persistent_groups( $groups ) {
286 global $wp_object_cache;
287
288 $wp_object_cache->add_non_persistent_groups( $groups );
289 }
290
291 /**
292 * Adds a group or set of groups to the list of groups that use Redis hashes.
293 *
294 * @param string|array $groups A group or an array of groups to add.
295 */
296 function wp_cache_add_redis_hash_groups( $groups ) {
297 global $wp_object_cache;
298
299 $wp_object_cache->add_redis_hash_groups( $groups );
300 }
301
302 /**
303 * Reset internal cache keys and structures. If the cache backend uses global
304 * blog or site IDs as part of its cache keys, this function instructs the
305 * backend to reset those keys and perform any cleanup since blog or site IDs
306 * have changed since cache init.
307 *
308 * This function is deprecated. Use wp_cache_switch_to_blog() instead of this
309 * function when preparing the cache for a blog switch. For clearing the cache
310 * during unit tests, consider using wp_cache_init(). wp_cache_init() is not
311 * recommended outside of unit tests as the performance penalty for using it is
312 * high.
313 *
314 * @deprecated 3.5.0
315 */
316 function wp_cache_reset() {
317 _deprecated_function( __FUNCTION__, '3.5' );
318
319 global $wp_object_cache;
320
321 return $wp_object_cache->reset();
322 }
323
324 /**
325 * Determines whether the object cache implementation supports a particular feature.
326 *
327 * @since 6.1.0
328 *
329 * @param string $feature Name of the feature to check for. Possible values include:
330 * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
331 * 'flush_runtime', 'flush_group'.
332 * @return bool True if the feature is supported, false otherwise.
333 */
334 function wp_cache_supports( $feature ) {
335 switch ( $feature ) {
336 case 'get_multiple':
337 case 'flush_runtime':
338 case 'flush_group':
339 return true;
340
341 case 'add_multiple':
342 case 'set_multiple':
343 case 'delete_multiple':
344 default:
345 return false;
346 }
347 }
348
349 /**
350 * WordPress Object Cache
351 *
352 * The WordPress Object Cache is used to save on trips to the database. The
353 * Object Cache stores all of the cache data to memory and makes the cache
354 * contents available by using a key, which is used to name and later retrieve
355 * the cache contents.
356 *
357 * The Object Cache can be replaced by other caching mechanisms by placing files
358 * in the wp-content folder which is looked at in wp-settings. If that file
359 * exists, then this file will not be included.
360 */
361 #[AllowDynamicProperties]
362 class WP_Object_Cache {
363
364 /**
365 * Holds the cached objects
366 *
367 * @var array
368 */
369 public $cache = [];
370
371 /**
372 * The amount of times the cache data was already stored in the cache.
373 *
374 * @var int
375 */
376 public $cache_hits = 0;
377
378 /**
379 * Amount of times the cache did not have the request in cache
380 *
381 * @var int
382 */
383 public $cache_misses = 0;
384
385 /**
386 * The amount of times a request was made to Redis
387 *
388 * @var int
389 */
390 public $redis_calls = [];
391
392 /**
393 * List of global groups
394 *
395 * @var array
396 */
397 public $global_groups = [];
398
399 /**
400 * List of non-persistent groups
401 *
402 * @var array
403 */
404 public $non_persistent_groups = [];
405
406 /**
407 * List of groups which use Redis hashes.
408 *
409 * @var array
410 */
411 public $redis_hash_groups = [];
412
413 /**
414 * The blog prefix to prepend to keys in non-global groups.
415 *
416 * @var int
417 */
418 public $blog_prefix;
419
420 /**
421 * Whether or not Redis is connected
422 *
423 * @var bool
424 */
425 public $is_redis_connected = false;
426
427 /**
428 * Whether or not the object cache thinks Redis needs a flush
429 *
430 * @var bool
431 */
432 public $do_redis_failback_flush = false;
433
434 /**
435 * The last triggered error
436 *
437 * @var string
438 */
439 public $last_triggered_error = '';
440
441 /**
442 * The missing redis message.
443 *
444 * @var string
445 */
446 public $missing_redis_message = '';
447
448 /**
449 * Whether or not to use true cache groups, instead of flattening.
450 *
451 * @var bool
452 */
453 const USE_GROUPS = WP_REDIS_USE_CACHE_GROUPS;
454
455 /**
456 * Adds data to the cache if it doesn't already exist.
457 *
458 * @uses WP_Object_Cache::_exists Checks to see if the cache already has data.
459 * @uses WP_Object_Cache::set Sets the data after the checking the cache
460 * contents existence.
461 *
462 * @param int|string $key What to call the contents in the cache
463 * @param mixed $data The contents to store in the cache
464 * @param string $group Where to group the cache contents
465 * @param int $expire When to expire the cache contents
466 * @return bool False if cache key and group already exist, true on success
467 */
468 public function add( $key, $data, $group = 'default', $expire = WP_REDIS_DEFAULT_EXPIRE_SECONDS ) {
469
470 if ( empty( $group ) ) {
471 $group = 'default';
472 }
473
474 if ( function_exists( 'wp_suspend_cache_addition' ) && wp_suspend_cache_addition() ) {
475 return false;
476 }
477
478 if ( $this->_exists( $key, $group ) ) {
479 return false;
480 }
481
482 return $this->set( $key, $data, $group, (int) $expire );
483 }
484
485 /**
486 * Sets the list of global groups.
487 *
488 * @param array $groups List of groups that are global.
489 */
490 public function add_global_groups( $groups ) {
491 $groups = (array) $groups;
492
493 // Allow force ignoring of global groups.
494 if ( is_array( WP_REDIS_IGNORE_GLOBAL_GROUPS ) ) {
495 $groups = array_diff( $groups, WP_REDIS_IGNORE_GLOBAL_GROUPS );
496 }
497
498 $groups = array_fill_keys( $groups, true );
499 $this->global_groups = array_merge( $this->global_groups, $groups );
500 }
501
502 /**
503 * Sets the list of non-persistent groups.
504 *
505 * @param array $groups List of groups that are non-persistent.
506 */
507 public function add_non_persistent_groups( $groups ) {
508 $groups = (array) $groups;
509
510 $groups = array_fill_keys( $groups, true );
511 $this->non_persistent_groups = array_merge( $this->non_persistent_groups, $groups );
512 }
513
514 /**
515 * Sets the list of groups that use Redis hashes.
516 *
517 * @param array $groups List of groups that use Redis hashes.
518 */
519 public function add_redis_hash_groups( $groups ) {
520 $groups = (array) $groups;
521
522 $groups = array_fill_keys( $groups, true );
523 $this->redis_hash_groups = array_merge( $this->redis_hash_groups, $groups );
524 }
525
526 /**
527 * Decrement numeric cache item's value
528 *
529 * @param int|string $key The cache key to increment
530 * @param int $offset The amount by which to decrement the item's value. Default is 1.
531 * @param string $group The group the key is in.
532 * @return false|int False on failure, the item's new value on success.
533 */
534 public function decr( $key, $offset = 1, $group = 'default' ) {
535
536 if ( empty( $group ) ) {
537 $group = 'default';
538 }
539
540 // The key needs to exist in order to be decremented
541 if ( ! $this->_exists( $key, $group ) ) {
542 return false;
543 }
544
545 $offset = (int) $offset;
546
547 // If this isn't a persistent group, we have to sort this out ourselves, grumble grumble.
548 if ( ! $this->_should_persist( $group ) ) {
549 $existing = $this->_get_internal( $key, $group );
550 if ( empty( $existing ) || ! is_numeric( $existing ) ) {
551 $existing = 0;
552 } else {
553 $existing -= $offset;
554 }
555 if ( $existing < 0 ) {
556 $existing = 0;
557 }
558 $this->_set_internal( $key, $group, $existing );
559 return $existing;
560 }
561
562 if ( $this->_should_use_redis_hashes( $group ) ) {
563 $redis_safe_group = $this->_key( '', $group );
564 $result = $this->_call_redis( 'hIncrBy', $redis_safe_group, $key, -$offset, $group );
565 if ( $result < 0 ) {
566 $result = 0;
567 $this->_call_redis( 'hSet', $redis_safe_group, $key, $result );
568 }
569 } else {
570 $id = $this->_key( $key, $group );
571 $result = $this->_call_redis( 'decrBy', $id, $offset );
572 if ( $result < 0 ) {
573 $result = 0;
574 $this->_call_redis( 'set', $id, $result );
575 }
576 }
577
578 if ( is_int( $result ) ) {
579 $this->_set_internal( $key, $group, $result );
580 }
581 return $result;
582 }
583
584 /**
585 * Remove the contents of the cache key in the group
586 *
587 * If the cache key does not exist in the group and $force parameter is set
588 * to false, then nothing will happen. The $force parameter is set to false
589 * by default.
590 *
591 * @param int|string $key What the contents in the cache are called
592 * @param string $group Where the cache contents are grouped
593 * @param bool $force Optional. Whether to force the unsetting of the cache
594 * key in the group
595 * @return bool False if the contents weren't deleted and true on success
596 */
597 public function delete( $key, $group = 'default', $force = false ) {
598
599 if ( empty( $group ) ) {
600 $group = 'default';
601 }
602
603 if ( ! $force && ! $this->_exists( $key, $group ) ) {
604 return false;
605 }
606
607 if ( $this->_should_persist( $group ) ) {
608 if ( $this->_should_use_redis_hashes( $group ) ) {
609 $redis_safe_group = $this->_key( '', $group );
610 $result = $this->_call_redis( 'hDel', $redis_safe_group, $key );
611 } else {
612 $id = $this->_key( $key, $group );
613 $result = $this->_call_redis( 'del', $id );
614 }
615 if ( 1 !== $result ) {
616 return false;
617 }
618 }
619
620 $this->_unset_internal( $key, $group );
621 return true;
622 }
623
624 /**
625 * Remove the contents of all cache keys in the group.
626 *
627 * @param string $group Where the cache contents are grouped.
628 * @return boolean True on success, false on failure.
629 */
630 public function delete_group( $group ) {
631 if ( ! $this->_should_use_redis_hashes( $group ) ) {
632 return false;
633 }
634
635 $multisite_safe_group = $this->multisite && ! isset( $this->global_groups[ $group ] ) ? $this->blog_prefix . $group : $group;
636 $redis_safe_group = $this->_key( '', $group );
637 if ( $this->_should_persist( $group ) ) {
638 $result = $this->_call_redis( 'del', $redis_safe_group );
639 if ( 1 !== $result ) {
640 return false;
641 }
642 } elseif ( ! $this->_should_persist( $group ) && ! isset( $this->cache[ $multisite_safe_group ] ) ) {
643 return false;
644 }
645 unset( $this->cache[ $multisite_safe_group ] );
646 return true;
647 }
648
649 /**
650 * Clears the object cache of all data.
651 *
652 * By default, this will flush the session cache as well as Redis, but we
653 * can leave the redis cache intact if we want. This is helpful when, for
654 * instance, you're running a batch process and want to clear the session
655 * store to reduce the memory footprint, but you don't want to have to
656 * re-fetch all the values from the database.
657 *
658 * @param bool $redis Should we flush redis as well as the session cache?
659 * @return bool Always returns true
660 */
661 public function flush( $redis = true ) {
662 $this->cache = array();
663 if ( $redis ) {
664 $this->_call_redis( 'flushdb' );
665 }
666
667 return true;
668 }
669
670 /**
671 * Retrieves the cache contents, if it exists
672 *
673 * The contents will be first attempted to be retrieved by searching by the
674 * key in the cache group. If the cache is hit (success) then the contents
675 * are returned.
676 *
677 * On failure, the number of cache misses will be incremented.
678 *
679 * @param int|string $key What the contents in the cache are called
680 * @param string $group Where the cache contents are grouped
681 * @param string $force Whether to force a refetch rather than relying on the local cache (default is false)
682 * @param bool $found Optional. Whether the key was found in the cache. Disambiguates a return of false, a storable value. Passed by reference. Default null.
683 * @return bool|mixed False on failure to retrieve contents or the cache contents on success
684 */
685 public function get( $key, $group = 'default', $force = false, &$found = null ) {
686
687 if ( empty( $group ) ) {
688 $group = 'default';
689 }
690
691 // Key is set internally, so we can use this value
692 if ( $this->_isset_internal( $key, $group ) && ! $force ) {
693 $this->cache_hits += 1;
694 $found = true;
695 return $this->_get_internal( $key, $group );
696 }
697
698 // Not a persistent group, so don't try Redis if the value doesn't exist
699 // internally
700 if ( ! $this->_should_persist( $group ) ) {
701 $this->cache_misses += 1;
702 $found = false;
703 return false;
704 }
705
706 if ( $this->_should_use_redis_hashes( $group ) ) {
707 $redis_safe_group = $this->_key( '', $group );
708 $value = $this->_call_redis( 'hGet', $redis_safe_group, $key );
709 } else {
710 $id = $this->_key( $key, $group );
711 $value = $this->_call_redis( 'get', $id );
712 }
713
714 // PhpRedis returns `false` when the key doesn't exist
715 if ( false === $value ) {
716 $this->cache_misses += 1;
717 $found = false;
718 return false;
719 }
720
721 // All non-numeric values are serialized
722 $value = is_numeric( $value ) ? intval( $value ) : unserialize( $value );
723
724 $this->_set_internal( $key, $group, $value );
725 $this->cache_hits += 1;
726 $found = true;
727 return $value;
728 }
729
730 /**
731 * Retrieves multiple values from the cache in one call.
732 *
733 * @param array $keys Array of keys under which the cache contents are stored.
734 * @param string $group Optional. Where the cache contents are grouped. Default empty.
735 * @param bool $force Optional. Whether to force an update of the local cache
736 * from the persistent cache. Default false.
737 * @return array Array of values organized into groups.
738 */
739 public function get_multiple( $keys, $group = 'default', $force = false ) {
740 if ( empty( $group ) ) {
741 $group = 'default';
742 }
743
744 $cache = array();
745 if ( ! $this->_should_persist( $group ) ) {
746 foreach ( $keys as $key ) {
747 $cache[ $key ] = $this->_isset_internal( $key, $group ) ? $this->_get_internal( $key, $group ) : false;
748 false !== $cache[ $key ] ? $this->cache_hits++ : $this->cache_misses++;
749 }
750 return $cache;
751 }
752
753 // Attempt to fetch values from the internal cache.
754 if ( ! $force ) {
755 foreach ( $keys as $key ) {
756 if ( $this->_isset_internal( $key, $group ) ) {
757 $cache[ $key ] = $this->_get_internal( $key, $group );
758 $this->cache_hits++;
759 }
760 }
761 }
762 $remaining_keys = array_values( array_diff( $keys, array_keys( $cache ) ) );
763 // If all keys were satisfied by the internal cache, we're sorted.
764 if ( empty( $remaining_keys ) ) {
765 return $cache;
766 }
767 if ( $this->_should_use_redis_hashes( $group ) ) {
768 $redis_safe_group = $this->_key( '', $group );
769 $results = $this->_call_redis( 'hmGet', $redis_safe_group, $remaining_keys );
770 $results = is_array( $results ) ? array_values( $results ) : $results;
771 } else {
772 $ids = array();
773 foreach ( $remaining_keys as $key ) {
774 $ids[] = $this->_key( $key, $group );
775 }
776 $results = $this->_call_redis( 'mget', $ids );
777 }
778 // Process the results from the Redis call.
779 foreach ( $remaining_keys as $i => $key ) {
780 $value = isset( $results[ $i ] ) ? $results[ $i ] : false;
781 if ( false !== $value ) {
782 // All non-numeric values are serialized
783 $value = is_numeric( $value ) ? intval( $value ) : unserialize( $value );
784 $this->_set_internal( $key, $group, $value );
785 $this->cache_hits++;
786 } else {
787 $this->cache_misses++;
788 }
789 $cache[ $key ] = $value;
790 }
791 // Make sure return values are returned in the order of the passed keys.
792 $return_cache = array();
793 foreach ( $keys as $key ) {
794 $return_cache[ $key ] = isset( $cache[ $key ] ) ? $cache[ $key ] : false;
795 }
796 return $return_cache;
797 }
798
799 /**
800 * Increment numeric cache item's value
801 *
802 * @param int|string $key The cache key to increment
803 * @param int $offset The amount by which to increment the item's value. Default is 1.
804 * @param string $group The group the key is in.
805 * @return false|int False on failure, the item's new value on success.
806 */
807 public function incr( $key, $offset = 1, $group = 'default' ) {
808
809 if ( empty( $group ) ) {
810 $group = 'default';
811 }
812
813 // The key needs to exist in order to be incremented
814 if ( ! $this->_exists( $key, $group ) ) {
815 return false;
816 }
817
818 $offset = (int) $offset;
819
820 // If this isn't a persistent group, we have to sort this out ourselves, grumble grumble.
821 if ( ! $this->_should_persist( $group ) ) {
822 $existing = $this->_get_internal( $key, $group );
823 if ( empty( $existing ) || ! is_numeric( $existing ) ) {
824 $existing = 1;
825 } else {
826 $existing += $offset;
827 }
828 if ( $existing < 0 ) {
829 $existing = 0;
830 }
831 $this->_set_internal( $key, $group, $existing );
832 return $existing;
833 }
834
835 if ( $this->_should_use_redis_hashes( $group ) ) {
836 $redis_safe_group = $this->_key( '', $group );
837 $result = $this->_call_redis( 'hIncrBy', $redis_safe_group, $key, $offset, $group );
838 if ( $result < 0 ) {
839 $result = 0;
840 $this->_call_redis( 'hSet', $redis_safe_group, $key, $result );
841 }
842 } else {
843 $id = $this->_key( $key, $group );
844 $result = $this->_call_redis( 'incrBy', $id, $offset );
845 if ( $result < 0 ) {
846 $result = 0;
847 $this->_call_redis( 'set', $id, $result );
848 }
849 }
850
851 if ( is_int( $result ) ) {
852 $this->_set_internal( $key, $group, $result );
853 }
854 return $result;
855 }
856
857 /**
858 * Replace the contents in the cache, if contents already exist
859 * @see WP_Object_Cache::set()
860 *
861 * @param int|string $key What to call the contents in the cache
862 * @param mixed $data The contents to store in the cache
863 * @param string $group Where to group the cache contents
864 * @param int $expire When to expire the cache contents
865 * @return bool False if not exists, true if contents were replaced
866 */
867 public function replace( $key, $data, $group = 'default', $expire = WP_REDIS_DEFAULT_EXPIRE_SECONDS ) {
868
869 if ( empty( $group ) ) {
870 $group = 'default';
871 }
872
873 if ( ! $this->_exists( $key, $group ) ) {
874 return false;
875 }
876
877 return $this->set( $key, $data, $group, (int) $expire );
878 }
879
880 /**
881 * Reset keys
882 *
883 * @deprecated 3.5.0
884 */
885 public function reset() {
886 _deprecated_function( __FUNCTION__, '3.5', 'switch_to_blog()' );
887 }
888
889 /**
890 * Sets the data contents into the cache
891 *
892 * The cache contents is grouped by the $group parameter followed by the
893 * $key. This allows for duplicate ids in unique groups. Therefore, naming of
894 * the group should be used with care and should follow normal function
895 * naming guidelines outside of core WordPress usage.
896 *
897 * The $expire parameter is not used, because the cache will automatically
898 * expire for each time a page is accessed and PHP finishes. The method is
899 * more for cache plugins which use files.
900 *
901 * @param int|string $key What to call the contents in the cache
902 * @param mixed $data The contents to store in the cache
903 * @param string $group Where to group the cache contents
904 * @param int $expire TTL for the data, in seconds
905 * @return bool Always returns true
906 */
907 public function set( $key, $data, $group = 'default', $expire = WP_REDIS_DEFAULT_EXPIRE_SECONDS ) {
908
909 if ( empty( $group ) ) {
910 $group = 'default';
911 }
912
913 if ( is_object( $data ) ) {
914 $data = clone $data;
915 }
916
917 $this->_set_internal( $key, $group, $data );
918
919 if ( ! $this->_should_persist( $group ) ) {
920 return true;
921 }
922
923 // If this is an integer, store it as such. Otherwise, serialize it.
924 if ( ! is_numeric( $data ) || intval( $data ) !== $data ) {
925 $data = serialize( $data );
926 }
927
928 // Redis doesn't support expire on hash group keys
929 if ( $this->_should_use_redis_hashes( $group ) ) {
930 $redis_safe_group = $this->_key( '', $group );
931 $this->_call_redis( 'hSet', $redis_safe_group, $key, $data );
932 return true;
933 }
934
935 $id = $this->_key( $key, $group );
936 if ( empty( $expire ) ) {
937 $this->_call_redis( 'set', $id, $data );
938 } else {
939 $this->_call_redis( 'setex', $id, $expire, $data );
940 }
941 return true;
942 }
943
944 /**
945 * Echoes the stats of the caching.
946 *
947 * Gives the cache hits, and cache misses. Also prints every cached group,
948 * key and the data.
949 */
950 public function stats() {
951 $total_redis_calls = 0;
952 foreach ( $this->redis_calls as $method => $calls ) {
953 $total_redis_calls += $calls;
954 }
955 $out = array();
956 $out[] = '<p>';
957 $out[] = '<strong>Cache Hits:</strong>' . (int) $this->cache_hits . '<br />';
958 $out[] = '<strong>Cache Misses:</strong>' . (int) $this->cache_misses . '<br />';
959 $out[] = '<strong>Redis Client:</strong>' . get_class( $this->redis ) . '<br />';
960 $out[] = '<strong>Redis Calls:</strong>' . (int) $total_redis_calls . ':<br />';
961 foreach ( $this->redis_calls as $method => $calls ) {
962 $out[] = ' - ' . esc_html( $method ) . ': ' . (int) $calls . '<br />';
963 }
964 $out[] = '</p>';
965 $out[] = '<ul>';
966 foreach ( $this->cache as $group => $cache ) {
967 $out[] = '<li><strong>Group:</strong> ' . esc_html( $group ) . ' - ( ' . number_format( strlen( serialize( $cache ) ) / 1024, 2 ) . 'k )</li>';
968 }
969 $out[] = '</ul>';
970 echo implode( PHP_EOL, $out ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped,WordPressDotOrg.sniffs.OutputEscaping.UnescapedOutputParameter
971 }
972
973 /**
974 * Switch the internal blog id.
975 *
976 * This changes the blog id used to create keys in blog specific groups.
977 *
978 * @param int $blog_id Blog ID
979 */
980 public function switch_to_blog( $blog_id ) {
981 $blog_id = (int) $blog_id;
982 $this->blog_prefix = $this->multisite ? $blog_id . ':' : '';
983 }
984
985 /**
986 * Utility function to determine whether a key exists in the cache.
987 *
988 * @access protected
989 */
990 protected function _exists( $key, $group ) {
991 if ( $this->_isset_internal( $key, $group ) ) {
992 return true;
993 }
994
995 if ( ! $this->_should_persist( $group ) ) {
996 return false;
997 }
998
999 if ( $this->_should_use_redis_hashes( $group ) ) {
1000 $redis_safe_group = $this->_key( '', $group );
1001 return $this->_call_redis( 'hExists', $redis_safe_group, $key );
1002 }
1003 $id = $this->_key( $key, $group );
1004 return $this->_call_redis( 'exists', $id );
1005 }
1006
1007 /**
1008 * Check whether there's a value in the internal object cache.
1009 *
1010 * @param string $key
1011 * @param string $group
1012 * @return boolean
1013 */
1014 protected function _isset_internal( $key, $group ) {
1015 if ( $this->_should_use_redis_hashes( $group ) ) {
1016 $multisite_safe_group = $this->multisite && ! isset( $this->global_groups[ $group ] ) ? $this->blog_prefix . $group : $group;
1017 return isset( $this->cache[ $multisite_safe_group ] ) && array_key_exists( $key, $this->cache[ $multisite_safe_group ] );
1018 } else {
1019 $key = $this->_key( $key, $group );
1020 return array_key_exists( $key, $this->cache );
1021 }
1022 }
1023
1024 /**
1025 * Get a value from the internal object cache
1026 *
1027 * @param string $key
1028 * @param string $group
1029 * @return mixed
1030 */
1031 protected function _get_internal( $key, $group ) {
1032 $value = null;
1033 if ( $this->_should_use_redis_hashes( $group ) ) {
1034 $multisite_safe_group = $this->multisite && ! isset( $this->global_groups[ $group ] ) ? $this->blog_prefix . $group : $group;
1035 if ( isset( $this->cache[ $multisite_safe_group ] ) && array_key_exists( $key, $this->cache[ $multisite_safe_group ] ) ) {
1036 $value = $this->cache[ $multisite_safe_group ][ $key ];
1037 }
1038 } else {
1039 $key = $this->_key( $key, $group );
1040 if ( array_key_exists( $key, $this->cache ) ) {
1041 $value = $this->cache[ $key ];
1042 }
1043 }
1044 if ( is_object( $value ) ) {
1045 return clone $value;
1046 }
1047 return $value;
1048 }
1049
1050 /**
1051 * Set a value to the internal object cache
1052 *
1053 * @param string $key
1054 * @param string $group
1055 * @param mixed $value
1056 */
1057 protected function _set_internal( $key, $group, $value ) {
1058 if ( $this->_should_use_redis_hashes( $group ) ) {
1059 $multisite_safe_group = $this->multisite && ! isset( $this->global_groups[ $group ] ) ? $this->blog_prefix . $group : $group;
1060 if ( ! isset( $this->cache[ $multisite_safe_group ] ) ) {
1061 $this->cache[ $multisite_safe_group ] = array();
1062 }
1063 $this->cache[ $multisite_safe_group ][ $key ] = $value;
1064 } else {
1065 $key = $this->_key( $key, $group );
1066 $this->cache[ $key ] = $value;
1067 }
1068 }
1069
1070 /**
1071 * Unset a value from the internal object cache
1072 *
1073 * @param string $key
1074 * @param string $group
1075 */
1076 protected function _unset_internal( $key, $group ) {
1077 if ( $this->_should_use_redis_hashes( $group ) ) {
1078 $multisite_safe_group = $this->multisite && ! isset( $this->global_groups[ $group ] ) ? $this->blog_prefix . $group : $group;
1079 if ( isset( $this->cache[ $multisite_safe_group ] ) && array_key_exists( $key, $this->cache[ $multisite_safe_group ] ) ) {
1080 unset( $this->cache[ $multisite_safe_group ][ $key ] );
1081 }
1082 } else {
1083 $key = $this->_key( $key, $group );
1084 if ( array_key_exists( $key, $this->cache ) ) {
1085 unset( $this->cache[ $key ] );
1086 }
1087 }
1088 }
1089
1090 /**
1091 * Utility function to generate the redis key for a given key and group.
1092 *
1093 * @param string $key The cache key.
1094 * @param string $group The cache group.
1095 * @return string A properly prefixed redis cache key.
1096 */
1097 protected function _key( $key = '', $group = 'default' ) {
1098 if ( empty( $group ) ) {
1099 $group = 'default';
1100 }
1101
1102 if ( ! empty( $this->global_groups[ $group ] ) ) {
1103 $prefix = $this->global_prefix;
1104 } else {
1105 $prefix = $this->blog_prefix;
1106 }
1107
1108 return preg_replace( '/\s+/', '', WP_CACHE_KEY_SALT . "$prefix$group:$key" );
1109 }
1110
1111 /**
1112 * Does this group use persistent storage?
1113 *
1114 * @param string $group Cache group.
1115 * @return bool true if the group is persistent, false if not.
1116 */
1117 protected function _should_persist( $group ) {
1118 return empty( $this->non_persistent_groups[ $group ] );
1119 }
1120
1121 /**
1122 * Should this group use Redis hashes?
1123 *
1124 * @param string $group Cache group.
1125 * @return bool True if the group should use Redis hashes, false if not.
1126 */
1127 protected function _should_use_redis_hashes( $group ) {
1128 if ( self::USE_GROUPS || ! empty( $this->redis_hash_groups[ $group ] ) ) {
1129 return true;
1130 }
1131 return false;
1132 }
1133
1134 /**
1135 * Wrapper method for connecting to Redis, which lets us retry the connection
1136 */
1137 protected function _connect_redis() {
1138 global $redis_server;
1139
1140 $check_dependencies = array( $this, 'check_client_dependencies' );
1141 /**
1142 * Permits alternate dependency check mechanism to be used.
1143 *
1144 * @param callable $check_dependencies Callback to execute.
1145 */
1146 $check_dependencies = apply_filters( 'wp_redis_check_client_dependencies_callback', $check_dependencies );
1147 $dependencies_ok = call_user_func( $check_dependencies );
1148 if ( true !== $dependencies_ok ) {
1149 $this->is_redis_connected = false;
1150 $this->missing_redis_message = $dependencies_ok;
1151 return $this->is_redis_connected;
1152 }
1153 $client_parameters = $this->build_client_parameters( $redis_server );
1154
1155 try {
1156 $client_connection = array( $this, 'prepare_client_connection' );
1157 /**
1158 * Permits alternate initial client connection mechanism to be used.
1159 *
1160 * @param callable $client_connection Callback to execute.
1161 */
1162 $client_connection = apply_filters( 'wp_redis_prepare_client_connection_callback', $client_connection );
1163 $this->redis = call_user_func_array( $client_connection, array( $client_parameters ) );
1164 } catch ( Exception $e ) {
1165 $this->_exception_handler( $e );
1166 $this->is_redis_connected = false;
1167 return $this->is_redis_connected;
1168 }
1169
1170 $keys_methods = array(
1171 'auth' => 'auth',
1172 'database' => 'select',
1173 );
1174
1175 try {
1176 $setup_connection = array( $this, 'perform_client_connection' );
1177 /**
1178 * Permits alternate setup client connection mechanism to be used.
1179 *
1180 * @param callable $setup_connection Callback to execute.
1181 */
1182 $setup_connection = apply_filters( 'wp_redis_perform_client_connection_callback', $setup_connection );
1183 call_user_func_array( $setup_connection, array( $this->redis, $client_parameters, $keys_methods ) );
1184 } catch ( Exception $e ) {
1185 $this->_exception_handler( $e );
1186 $this->is_redis_connected = false;
1187 return $this->is_redis_connected;
1188 }
1189
1190 $this->is_redis_connected = $this->redis->isConnected();
1191 if ( ! $this->is_redis_connected ) {
1192 $this->missing_redis_message = 'Warning! WP Redis object cache cannot connect to Redis server.';
1193 }
1194 return $this->is_redis_connected;
1195 }
1196
1197 /**
1198 * Are the required dependencies for connecting to Redis available?
1199 *
1200 * @return mixed True if the required dependencies are present, string if
1201 * not with a message describing the issue.
1202 */
1203 public function check_client_dependencies() {
1204 $class_to_check = 'Redis';
1205 if ( defined( 'WP_REDIS_USE_RELAY' ) && WP_REDIS_USE_RELAY ) {
1206 $class_to_check = 'Relay\Relay';
1207 }
1208
1209 if ( ! class_exists( $class_to_check ) ) {
1210 return 'Warning! PHPRedis extension is unavailable, which is required by WP Redis object cache.';
1211 }
1212
1213 return true;
1214 }
1215
1216 /**
1217 * Builds an array to be passed to a function that will set up the Redis
1218 * client.
1219 *
1220 * @param array $redis_server Parameters used to construct a Redis client.
1221 * @return array Final parameters to use to construct a Redis client with
1222 * with defaults applied.
1223 */
1224 public function build_client_parameters( $redis_server ) {
1225 // Default Redis port.
1226 $port = 6379;
1227 // Default Redis database number.
1228 $database = 0;
1229
1230 if ( empty( $redis_server ) ) {
1231 // Attempt to automatically load Pantheon's Redis config from the env.
1232 if ( isset( $_SERVER['CACHE_HOST'] ) ) {
1233 $redis_server = [
1234 // Don't use WP methods to sanitize the host due to plugin loading issues with other caching methods.
1235 // @phpcs:ignore WordPressVIPMinimum.Functions.StripTags.StripTagsOneParameter,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1236 'host' => strip_tags( $_SERVER['CACHE_HOST'] ),
1237 'port' => ! empty( $_SERVER['CACHE_PORT'] ) ? intval( $_SERVER['CACHE_PORT'] ) : $port,
1238 // Don't attempt to sanitize passwords as this can break authentication.
1239 // @phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1240 'auth' => ! empty( $_SERVER['CACHE_PASSWORD'] ) ? $_SERVER['CACHE_PASSWORD'] : null,
1241 'database' => ! empty( $_SERVER['CACHE_DB'] ) ? intval( $_SERVER['CACHE_DB'] ) : $database,
1242 ];
1243 } else {
1244 $redis_server = [
1245 'host' => '127.0.0.1',
1246 'port' => $port,
1247 'database' => $database,
1248 ];
1249 }
1250 }
1251
1252 if ( file_exists( $redis_server['host'] ) && 'socket' === filetype( $redis_server['host'] ) ) { // unix socket connection.
1253 // port must be null or socket won't connect.
1254 unset( $redis_server['port'] );
1255 $port = null;
1256 }
1257
1258 $defaults = [
1259 'host' => $redis_server['host'],
1260 'port' => $port,
1261 'timeout' => 1000, // I multiplied this by 1000 so we'd have a common measure of ms instead of s and ms, need to make sure this gets divided by 1000.
1262 'retry_interval' => 100,
1263 ];
1264 // 1s timeout, 100ms delay between reconnections.
1265
1266 // merging the defaults with the original $redis_server enables any custom parameters to get sent downstream to the redis client.
1267 return array_replace_recursive( $defaults, $redis_server );
1268 }
1269
1270 /**
1271 * Constructs a PHPRedis Redis client.
1272 *
1273 * @param array $client_parameters Parameters used to construct a Redis client.
1274 * @return Redis Redis client.
1275 */
1276 public function prepare_client_connection( $client_parameters ) {
1277 if ( defined( 'WP_REDIS_USE_RELAY' ) && WP_REDIS_USE_RELAY ) {
1278 $redis = new Relay\Relay;
1279 } else {
1280 $redis = new Redis;
1281 }
1282
1283 $redis->connect(
1284 $client_parameters['host'],
1285 $client_parameters['port'],
1286 // $client_parameters['timeout'] is sent in milliseconds,
1287 // connect() takes seconds, so divide by 1000
1288 $client_parameters['timeout'] / 1000,
1289 null,
1290 $client_parameters['retry_interval']
1291 );
1292
1293 return $redis;
1294 }
1295
1296 /**
1297 * Sets up the Redis connection (ie authentication and specific database).
1298 *
1299 * @param Redis $redis Redis client.
1300 * @param array $client_parameters Parameters used to configure Redis.
1301 * @param array $keys_methods Associative array of keys from
1302 * $client_parameters to use as method arguments for $redis.
1303 * @return bool True if successful.
1304 */
1305 public function perform_client_connection( $redis, $client_parameters, $keys_methods ) {
1306 foreach ( $keys_methods as $key => $method ) {
1307 if ( ! isset( $client_parameters[ $key ] ) ) {
1308 continue;
1309 }
1310 try {
1311 $redis->$method( $client_parameters[ $key ] );
1312 } catch ( RedisException $e ) {
1313
1314 // PhpRedis throws an Exception when it fails a server call.
1315 // To prevent WordPress from fataling, we catch the Exception.
1316 throw new Exception( $e->getMessage(), $e->getCode(), $e );
1317 }
1318 }
1319 return true;
1320 }
1321
1322 /**
1323 * Wrapper method for calls to Redis, which fails gracefully when Redis is unavailable
1324 *
1325 * @param string $method
1326 * @param mixed $args
1327 * @return mixed
1328 */
1329 protected function _call_redis( $method ) {
1330 global $wpdb;
1331
1332 $arguments = func_get_args();
1333 array_shift( $arguments ); // ignore $method
1334
1335 // $group is intended for the failback, and isn't passed to the Redis callback
1336 if ( 'hIncrBy' === $method ) {
1337 $group = array_pop( $arguments );
1338 }
1339
1340 if ( $this->is_redis_connected ) {
1341 try {
1342 if ( ! isset( $this->redis_calls[ $method ] ) ) {
1343 $this->redis_calls[ $method ] = 0;
1344 }
1345 $this->redis_calls[ $method ]++;
1346 $retval = call_user_func_array( array( $this->redis, $method ), $arguments );
1347 return $retval;
1348 } catch ( Exception $e ) {
1349 $retry_exception_messages = $this->retry_exception_messages();
1350 // PhpRedis throws an Exception when it fails a server call.
1351 // To prevent WordPress from fataling, we catch the Exception.
1352 if ( $this->exception_message_matches( $e->getMessage(), $retry_exception_messages ) ) {
1353
1354 $this->_exception_handler( $e );
1355
1356 // Attempt to refresh the connection if it was successfully established once
1357 // $this->is_redis_connected will be set inside _connect_redis()
1358 if ( $this->_connect_redis() ) {
1359 return call_user_func_array( array( $this, '_call_redis' ), array_merge( array( $method ), $arguments ) );
1360 }
1361 // Fall through to fallback below
1362 } else {
1363 throw $e;
1364 }
1365 }
1366 } // End if().
1367
1368 if ( $this->is_redis_failback_flush_enabled() && ! $this->do_redis_failback_flush && ! empty( $wpdb ) ) {
1369 if ( $this->multisite ) {
1370 $table = $wpdb->sitemeta;
1371 $col1 = 'meta_key';
1372 $col2 = 'meta_value';
1373 } else {
1374 $table = $wpdb->options;
1375 $col1 = 'option_name';
1376 $col2 = 'option_value';
1377 }
1378 // @codingStandardsIgnoreStart
1379 $wpdb->query( "INSERT IGNORE INTO {$table} ({$col1},{$col2}) VALUES ('wp_redis_do_redis_failback_flush',1)" );
1380 // @codingStandardsIgnoreEnd
1381 $this->do_redis_failback_flush = true;
1382 }
1383
1384 // Mock expected behavior from Redis for these methods
1385 switch ( $method ) {
1386 case 'incr':
1387 case 'incrBy':
1388 $val = $this->cache[ $arguments[0] ];
1389 $offset = isset( $arguments[1] ) && 'incrBy' === $method ? $arguments[1] : 1;
1390 $val = $val + $offset;
1391 return $val;
1392 case 'hIncrBy':
1393 $val = $this->_get_internal( $arguments[1], $group );
1394 return $val + $arguments[2];
1395 case 'decrBy':
1396 case 'decr':
1397 $val = $this->cache[ $arguments[0] ];
1398 $offset = isset( $arguments[1] ) && 'decrBy' === $method ? $arguments[1] : 1;
1399 $val = $val - $offset;
1400 return $val;
1401 case 'del':
1402 case 'hDel':
1403 return 1;
1404 case 'flushAll':
1405 case 'flushdb':
1406 case 'IsConnected':
1407 case 'exists':
1408 case 'get':
1409 case 'mget':
1410 case 'hGet':
1411 case 'hmGet':
1412 return false;
1413 }
1414
1415 }
1416
1417 /**
1418 * Returns a filterable array of expected Exception messages that may be thrown
1419 *
1420 * @return array Array of expected exception messages
1421 */
1422 public function retry_exception_messages() {
1423 $retry_exception_messages = array( 'socket error on read socket', 'Connection closed', 'Redis server went away' );
1424 return apply_filters( 'wp_redis_retry_exception_messages', $retry_exception_messages );
1425 }
1426
1427 /**
1428 * Compares individual message to list of messages.
1429 *
1430 * @param string $error Message to compare
1431 * @param array $errors Array of messages to compare to
1432 * @return bool whether $error matches any items in $errors
1433 */
1434 public function exception_message_matches( $error, $errors ) {
1435 foreach ( $errors as $message ) {
1436 $pattern = $this->_format_message_for_pattern( $message );
1437 $matches = (bool) preg_match( $pattern, $error );
1438 if ( $matches ) {
1439 return true;
1440 }
1441 }
1442 return false;
1443 }
1444
1445 /**
1446 * Prepends and appends '/' if not present in a string
1447 *
1448 * @param string $message Potential regex string that may need '/'
1449 * @return string Regex pattern
1450 */
1451 protected function _format_message_for_pattern( $message ) {
1452 $var = $message;
1453 $var = '/' === $var[0] ? $var : '/' . $var;
1454 $var = '/' === $var[ strlen( $var ) - 1 ] ? $var : $var . '/';
1455 return $var;
1456 }
1457
1458 /**
1459 * Handles exceptions by triggering a php error.
1460 *
1461 * @param Exception $exception
1462 * @return null
1463 */
1464 protected function _exception_handler( $exception ) {
1465 try {
1466 $this->last_triggered_error = 'WP Redis: ' . $exception->getMessage();
1467 // pc:fix
1468 $this->missing_redis_message = 'Redis Connection error, please check the credentials!';
1469 // Be friendly to developers debugging production servers by triggering an error
1470 // @codingStandardsIgnoreStart
1471 trigger_error( $this->last_triggered_error, E_USER_WARNING );
1472 // @codingStandardsIgnoreEnd
1473 } catch ( PHPUnit_Framework_Error_Warning $e ) {
1474 // PHPUnit throws an Exception when `trigger_error()` is called.
1475 // To ensure our tests (which expect Exceptions to be caught) continue to run,
1476 // we catch the PHPUnit exception and inspect the RedisException message
1477 }
1478 }
1479
1480 /**
1481 * Admin UI to let the end user know something about the Redis connection isn't working.
1482 */
1483 public function wp_action_admin_notices_warn_missing_redis() {
1484 if ( ! current_user_can( 'manage_options' ) || empty( $this->missing_redis_message ) ) {
1485 return;
1486 }
1487 echo '<div class="message error"><p>' . esc_html( $this->missing_redis_message ) . '</p></div>';
1488 }
1489
1490 /**
1491 * Whether or not wakeup flush is enabled
1492 *
1493 * @return bool
1494 */
1495 private function is_redis_failback_flush_enabled() {
1496 if ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) {
1497 return false;
1498 } elseif ( defined( 'WP_REDIS_DISABLE_FAILBACK_FLUSH' ) && WP_REDIS_DISABLE_FAILBACK_FLUSH ) {
1499 return false;
1500 }
1501 return true;
1502 }
1503
1504 /**
1505 * Sets up object properties; PHP 5 style constructor
1506 *
1507 * @return null|WP_Object_Cache If cache is disabled, returns null.
1508 */
1509 public function __construct() {
1510 global $blog_id, $table_prefix, $wpdb;
1511
1512 $this->multisite = is_multisite();
1513 $this->blog_prefix = $this->multisite ? $blog_id . ':' : '';
1514
1515 if ( ! $this->_connect_redis() && function_exists( 'add_action' ) ) {
1516 add_action( 'admin_notices', array( $this, 'wp_action_admin_notices_warn_missing_redis' ) );
1517 }
1518
1519 if ( $this->is_redis_failback_flush_enabled() && ! empty( $wpdb ) ) {
1520 if ( $this->multisite ) {
1521 $table = $wpdb->sitemeta;
1522 $col1 = 'meta_key';
1523 $col2 = 'meta_value';
1524 } else {
1525 $table = $wpdb->options;
1526 $col1 = 'option_name';
1527 $col2 = 'option_value';
1528 }
1529 // @codingStandardsIgnoreStart
1530 $this->do_redis_failback_flush = (bool) $wpdb->get_results( "SELECT {$col2} FROM {$table} WHERE {$col1}='wp_redis_do_redis_failback_flush'" );
1531 // @codingStandardsIgnoreEnd
1532 if ( $this->is_redis_connected && $this->do_redis_failback_flush ) {
1533 $ret = $this->_call_redis( 'flushdb' );
1534 if ( $ret ) {
1535 // @codingStandardsIgnoreStart
1536 $wpdb->query( "DELETE FROM {$table} WHERE {$col1}='wp_redis_do_redis_failback_flush'" );
1537 // @codingStandardsIgnoreEnd
1538 $this->do_redis_failback_flush = false;
1539 }
1540 }
1541 }
1542
1543 $this->global_prefix = ( $this->multisite || defined( 'CUSTOM_USER_TABLE' ) && defined( 'CUSTOM_USER_META_TABLE' ) ) ? '' : $table_prefix;
1544
1545 /**
1546 * @todo This should be moved to the PHP4 style constructor, PHP5
1547 * already calls __destruct()
1548 */
1549 register_shutdown_function( array( $this, '__destruct' ) );
1550 }
1551
1552 /**
1553 * Will save the object cache before object is completely destroyed.
1554 *
1555 * Called upon object destruction, which should be when PHP ends.
1556 *
1557 * @return bool True value. Won't be used by PHP
1558 */
1559 public function __destruct() {
1560 return true;
1561 }
1562 }
1563