PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.0.2
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.0.2
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 2.0.2, at includes/dropins/redis-object-cache.php

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