PluginProbe
SQLite Object Cache / 1.1.0
SQLite Object Cache v1.1.0
1.6.5 trunk 0.1.7 1.0.0 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.4.0 1.4.1 1.5.1 1.5.4 1.5.5 1.5.6 1.5.7 All 30 releases
sqlite-object-cache / assets / drop-in / object-cache.php

object-cache.php in SQLite Object Cache 1.1.0, at assets/drop-in/object-cache.php

2,321 lines 68.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: SQLite Object Cache Drop-In
4 * Version: 1.1.0
5 * Note: This Version number must match the one in the ctor for SQLite_Object_Cache.
6 * Plugin URI: https://wordpress.org/plugins/sqlite-object-cache/
7 * Description: A persistent object cache backend powered by SQLite3.
8 * Author: Oliver Jones
9 * Author URI: https://plumislandmedia.net
10 * License: GPLv2+
11 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
12 * Requires PHP: 5.6
13 *
14 * NOTE: This uses the file .../wp-content/.ht.object_cache.sqlite
15 * and the associated files .../wp-content/.ht.object_cache.sqlite-shm
16 * and .../wp-content/.ht.object_cache.sqlite-wal to hold cached data.
17 * These start with .ht. for security: Web servers block requests
18 * for files with that prefix. Use the UNIX ls -a command to
19 * see these files from your command line.
20 *
21 * If you define WP_SQLITE_OBJECT_CACHE_DB_FILE
22 * this plugin will use it as the name of the sqlite file
23 * in place of .../wp-content/.ht.object_cache.sqlite .
24 *
25 *
26 * Credit: Till Krüss's https://wordpress.org/plugins/redis-cache/ plugin. Thanks, Till!
27 *
28 * @package SQLiteCache
29 */
30
31 defined( '\\ABSPATH' ) || exit;
32
33 // phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact, Generic.WhiteSpace.ScopeIndent.Incorrect
34 if ( ! defined( 'WP_SQLITE_OBJECT_CACHE_DISABLED' ) || ! WP_SQLITE_OBJECT_CACHE_DISABLED ) :
35
36 class SQLite_Object_Cache_Exception extends Exception {
37
38 /**
39 * Custom exception for the SQLite Object Cache.
40 *
41 * @param string $note Description of the operation causing the exception.
42 * @param SQLite3 $sqlite SQLite connection object.
43 * @param float $start Start time in microseconds.
44 */
45 public function __construct( $note, $sqlite, $start = null ) {
46 if ( $start ) {
47 $duration = round( 1000.0 * ( $this->time_usec() - $start ) );
48 $note .= ' (' . $duration . ')';
49 }
50 parent::__construct( $note . ': ' . $sqlite->lastErrorMsg(), $sqlite->lastErrorCode(), null );
51 }
52
53 /**
54 * Get the current time in microseconds.
55 *
56 * @return float Current time (relative to some arbitrary epoch, not UNIX's epoch.
57 */
58 private function time_usec() {
59
60 if ( function_exists( 'hrtime' ) ) {
61 /** @noinspection PhpMethodParametersCountMismatchInspection */
62 return hrtime( true ) * 0.001;
63 }
64 if ( function_exists( 'microtime' ) ) {
65 return microtime( true );
66 }
67
68 return time() * 1000000.0;
69 }
70
71 }
72
73 /**
74 * Object Cache API: WP_Object_Cache class, reworked for SQLite3 drop-in.
75 *
76 * @package WordPress
77 * @subpackage Cache
78 * @since 5.4.0
79 */
80
81 /**
82 * Core class that implements an object cache.
83 *
84 * The WordPress Object Cache is used to save on trips to the database. The
85 * Object Cache stores cache data to memory and makes the cache
86 * contents available by using a key, which is used to name and later retrieve
87 * the cache contents.
88 *
89 * This module is a drop-in, placed in the WP_CONTENT folder, implementing
90 * the WordPress Object Cache class, while using SQLite3 for persistent storage.
91 *
92 * @since 0.1.0
93 */
94 class WP_Object_Cache {
95 const OBJECT_STATS_TABLE = 'object_stats';
96 const OBJECT_CACHE_TABLE = 'object_cache';
97 const NOEXPIRE_TIMESTAMP_OFFSET = 500000000000;
98 const MAX_LIFETIME = DAY_IN_SECONDS * 2;
99 const MYSQLITE_TIMEOUT = 500;
100
101 /**
102 * Timeout waiting for transaction completion.
103 *
104 * @var int
105 */
106 private $sqlite_timeout;
107 /**
108 * The amount of times the cache data was already stored in the cache.
109 *
110 * @since 2.5.0
111 * @var int
112 */
113 public $cache_hits = 0;
114 /**
115 * Amount of times the cache did not have the request in cache.
116 *
117 * @since 2.0.0
118 * @var int
119 */
120 public $cache_misses = 0;
121 /**
122 * The amount of times the cache data was already stored in the persistent cache.
123 *
124 * @since 2.5.0
125 * @var int
126 */
127 public $persistent_hits = 0;
128 /**
129 * Amount of times the cache did not have the request in persistent cache.
130 *
131 * @since 2.0.0
132 * @var int
133 */
134 public $persistent_misses = 0;
135 /**
136 * The blog prefix to prepend to keys in non-global groups.
137 *
138 * @since 3.5.0
139 * @var string
140 */
141 public $blog_prefix;
142 /**
143 * List of groups that will not be flushed.
144 *
145 * @var array
146 */
147 public $unflushable_groups = [];
148 /**
149 * List of groups not saved to cache.
150 *
151 * @var array
152 */
153 public $ignored_groups = [
154 'counts',
155 'plugins',
156 'themes',
157 ];
158 /**
159 * List of groups and their types.
160 *
161 * @var array
162 */
163 public $group_type = [];
164 /**
165 * Prefix used for global groups.
166 *
167 * @var string
168 */
169 public $global_prefix = '';
170 /**
171 * List of global groups.
172 *
173 * @var array
174 */
175 protected $global_groups = [
176 'blog-details',
177 'blog-id-cache',
178 'blog-lookup',
179 'global-posts',
180 'networks',
181 'rss',
182 'sites',
183 'site-details',
184 'site-lookup',
185 'site-options',
186 'site-transient',
187 'users',
188 'useremail',
189 'userlogins',
190 'usermeta',
191 'user_meta',
192 'userslugs',
193 ];
194 /**
195 * Holds the cached objects.
196 *
197 * @since 2.0.0
198 * @var array
199 */
200 private $cache = [];
201 /**
202 * Holds the value of is_multisite().
203 *
204 * @since 3.5.0
205 * @var bool
206 */
207 private $multisite;
208
209 /**
210 * Prepared statement to get one cache element.
211 *
212 * @var SQLite3Stmt SELECT statement.
213 */
214 private $getone;
215
216 /**
217 * Prepared statement to delete one cache element.
218 *
219 * @var SQLite3Stmt DELETE statement.
220 */
221 private $deleteone;
222
223 /**
224 * Prepared statement to delete a group of cache elements.
225 *
226 * @var SQLite3Stmt
227 */
228 private $deletegroup;
229
230 /**
231 * Prepared statement to upsert one cache element.
232 *
233 * @var SQLite3Stmt
234 */
235 private $upsertone;
236
237 /**
238 * Prepared statement to insert one cache element.
239 *
240 * @var SQLite3Stmt
241 */
242 private $insertone;
243
244 /**
245 * Prepared statement to update one cache element.
246 *
247 * @var SQLite3Stmt
248 */
249 private $updateone;
250
251 /**
252 * Associative array of items we know ARE NOT in SQLite.
253 *
254 * @var array Keys are names, values don't matter.
255 */
256 private $not_in_persistent_cache = [];
257 /**
258 * Associative array of items we know ARE in SQLite.
259 *
260 * @var array Keys are names, values don't matter.
261 */
262 private $in_persistent_cache = [];
263 /**
264 * Cache table name.
265 *
266 * @var string Usually 'object_cache'.
267 */
268 private $cache_table_name;
269 /**
270 * Flag for availability of igbinary serialization extension.
271 *
272 * @var bool true if it is available.
273 */
274 private $has_igbinary;
275 /**
276 * Flag.
277 *
278 * @var bool true if hrtime is available.
279 */
280 private $has_hrtime;
281 /**
282 * Flag.
283 *
284 * @var bool true if microtime is available.
285 */
286 private $has_microtime;
287 /**
288 * The expiration time of non-expiring cache entries has this added to the timestamp.
289 *
290 * This is a sentinel value, marking a non-expiring cache entry AND
291 * recording when it was inserted or updated.
292 * It allows a least-recently-changed cache-entry purging strategy.
293 *
294 * If we wanted a least-recently-used purge, we would need to
295 * update each cache item's row whenever we accessed it. That
296 * would cost more than it's worth.
297 *
298 * @var int a large number of seconds, much larger than 2**32
299 */
300 private $noexpire_timestamp_offset;
301 /**
302 * The maximum age of an entry before we get rid of it.
303 *
304 * @var int the maximum lifetime of a cache entry, often a week.
305 */
306 private $max_lifetime;
307 /**
308 * An array of elapsed times for each cache-retrieval operation.
309 *
310 * @var array[float]
311 */
312 private $select_times = [];
313 /**
314 * An array of elapsed times for each cache-insertion / update operation.
315 *
316 * @var array[float]
317 */
318 private $insert_times = [];
319 /**
320 * An array of item names for each cache-retrieval operation.
321 *
322 * @var array[string]
323 */
324 private $select_names = [];
325 /**
326 * An array of item names for each cache-insertion / update operation.
327 *
328 * @var array[float]
329 */
330 private $insert_names = [];
331 /**
332 * An array of elapsed times for each single-row cache deletion operation.
333 *
334 * @var array[float]
335 */
336 private $delete_times = [];
337 /**
338 * The time it took to open the db.
339 *
340 * @var float
341 */
342 private $open_time;
343
344 /**
345 * Monitoring options for the SQLite cache.
346 *
347 * Options in array [
348 * 'capture' => (bool)
349 * 'resolution' => how often in seconds (float)
350 * 'lifetime' => how long until entries expire in seconds (int)
351 * 'verbose' => (bool) capture extra stuff.
352 * ]
353 *
354 * @var array $options Option list.
355 */
356 private $monitoring_options;
357
358 /**
359 * Database object.
360 * @var SQLite3 instance.
361 */
362 private $sqlite;
363
364 /**
365 * Constructor for SQLite Object Cache.
366 *
367 * @param string $directory Name of the directory for the db file.
368 * @param string $file Name of the db file.
369 * @param int $timeout Milliseconds before timeout.
370 *
371 * @since 2.0.8
372 */
373 public function __construct( $directory = WP_CONTENT_DIR, $file = '.ht.object-cache.sqlite', $timeout = self::MYSQLITE_TIMEOUT ) {
374
375 $this->cache_group_types();
376
377 $db_file = $directory . '/' . $file;
378 if ( defined( 'WP_SQLITE_OBJECT_CACHE_DB_FILE' ) ) {
379 $db_file = WP_SQLITE_OBJECT_CACHE_DB_FILE;
380 }
381 $this->sqlite_path = $db_file;
382
383 $this->multisite = is_multisite();
384 $this->blog_prefix = $this->multisite ? get_current_blog_id() . ':' : '';
385 $this->sqlite_timeout = $timeout ?: self::MYSQLITE_TIMEOUT;
386 $this->cache_table_name = self::OBJECT_CACHE_TABLE;
387 $this->noexpire_timestamp_offset = self::NOEXPIRE_TIMESTAMP_OFFSET;
388 $this->max_lifetime = self::MAX_LIFETIME;
389
390 $this->has_igbinary =
391 function_exists( 'igbinary_serialize' ) && function_exists( 'igbinary_unserialize' );
392 $this->has_hrtime = function_exists( 'hrtime' );
393 $this->has_microtime = function_exists( 'microtime' );
394 }
395
396 /**
397 * Open SQLite3 connection.
398 * @return void
399 */
400 private function open_connection() {
401 if ( $this->sqlite ) {
402 return;
403 }
404 $max_retries = 3;
405 $retries = 0;
406 while ( ++ $retries <= $max_retries ) {
407 try {
408 $this->actual_open_connection();
409
410 return;
411 } catch ( \Exception $ex ) {
412 /* something went wrong opening */
413 error_log( $ex );
414 $this->delete_offending_files( $retries );
415 }
416 }
417 }
418
419 /**
420 * Open SQLite3 connection.
421 *
422 * @return void
423 * @throws SQLite_Object_Cache_Exception Announce SQLite failure.
424 */
425 private function actual_open_connection() {
426 $start = $this->time_usec();
427 $this->sqlite = new SQLite3( $this->sqlite_path, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, null );
428 $this->sqlite->enableExceptions( true );
429 $this->sqlite->busyTimeout( $this->sqlite_timeout );
430
431 /* set some initial pragma stuff */
432 /* NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3 */
433
434 /* Notice we use a journal mode that risks database corruption.
435 * That's OK, because it's MUCH faster, and because we have an error
436 * recovery procedure that deletes and recreates a corrupt database file.
437 */
438 $this->exec( 'PRAGMA synchronous = OFF' );
439 $this->exec( 'PRAGMA journal_mode = MEMORY' );
440 $this->exec( "PRAGMA encoding = 'UTF-8'" );
441 $this->exec( 'PRAGMA case_sensitive_like = true' );
442
443 $this->create_object_cache_table();
444 $this->prepare_statements( $this->cache_table_name );
445 $this->preload( $this->cache_table_name );
446
447 $this->open_time = $this->time_usec() - $start;
448 }
449
450 /**
451 * Get current time.
452 *
453 * @return float Current time in microseconds, from an arbitrary epoch.
454 */
455 private function time_usec() {
456 if ( $this->has_hrtime ) {
457 /** @noinspection PhpMethodParametersCountMismatchInspection */
458 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */
459 return hrtime( true ) * 0.001;
460 }
461 if ( $this->has_microtime ) {
462 return microtime( true );
463 }
464
465 return time() * 1000000.0;
466 }
467
468 /**
469 * Set group type array
470 *
471 * @return void
472 */
473 protected function cache_group_types() {
474 foreach ( $this->global_groups as $group ) {
475 $this->group_type[ $group ] = 'global';
476 }
477
478 foreach ( $this->unflushable_groups as $group ) {
479 $this->group_type[ $group ] = 'unflushable';
480 }
481
482 foreach ( $this->ignored_groups as $group ) {
483 $this->group_type[ $group ] = 'ignored';
484 }
485 }
486
487 /**
488 * Wrapper for exec, to check for errors.
489 *
490 * @param string $query The SQL Statement.
491 *
492 * @return bool
493 * @throws SQLite_Object_Cache_Exception If something failed.
494 */
495 public function exec( $query ) {
496 $start = $this->time_usec();
497 $result = $this->sqlite->exec( $query );
498 if ( ! $result ) {
499 throw new SQLite_Object_Cache_Exception( 'exec', $this->sqlite, $start );
500 }
501
502 return true;
503 }
504
505 /**
506 * Do the necessary Data Definition Language work.
507 *
508 * @return void
509 * @throws SQLite_Object_Cache_Exception If something fails.
510 * @noinspection SqlResolve
511 */
512 private function create_object_cache_table() {
513 /* NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3 */
514 $this->exec( 'BEGIN;' );
515 /* does our table exist? */
516 $q = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name = '$this->cache_table_name';";
517 $r = $this->querySingle( $q );
518 if ( 0 === $r ) {
519 /* later versions of SQLite3 have clustered primary keys, "WITHOUT ROWID" */
520 if ( version_compare( $this->sqlite_get_version(), '3.8.2' ) < 0 ) {
521 /* @noinspection SqlIdentifier */
522 $t = "
523 CREATE TABLE IF NOT EXISTS $this->cache_table_name (
524 name TEXT NOT NULL COLLATE BINARY,
525 value BLOB,
526 expires INT
527 );
528 CREATE UNIQUE INDEX IF NOT EXISTS name ON $this->cache_table_name (name);
529 CREATE INDEX IF NOT EXISTS expires ON $this->cache_table_name (expires);";
530 } else {
531 /* @noinspection SqlIdentifier */
532 $t = "
533 CREATE TABLE IF NOT EXISTS $this->cache_table_name (
534 name TEXT NOT NULL PRIMARY KEY COLLATE BINARY,
535 value BLOB,
536 expires INT
537 ) WITHOUT ROWID;
538 CREATE INDEX IF NOT EXISTS expires ON $this->cache_table_name (expires);";
539 }
540
541 $this->exec( $t );
542 }
543 $this->exec( 'COMMIT;' );
544 }
545
546 /**
547 * Do the necessary Data Definition Language work.
548 *
549 * @param string $tbl The name of the table.
550 *
551 * @return void
552 * @throws SQLite_Object_Cache_Exception If something fails.
553 * @noinspection SqlResolve
554 */
555 private function maybe_create_stats_table( $tbl ) {
556 /* NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3 */
557 $this->exec( 'BEGIN;' );
558 /* does our table exist? */
559 $q = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name = '$tbl';";
560 $r = $this->querySingle( $q );
561 if ( 0 === $r ) {
562 /* @noinspection SqlIdentifier */
563 $t = "
564 CREATE TABLE IF NOT EXISTS $tbl (
565 value BLOB,
566 timestamp INT
567 );
568 CREATE INDEX IF NOT EXISTS expires ON $tbl (timestamp);";
569 $this->exec( $t );
570 }
571 $this->exec( 'COMMIT;' );
572 }
573
574 /**
575 * Wrapper for querySingle, to detect if something failed.
576 *
577 * @param string $query The query.
578 * @param bool $entire_row If false, give back the first column.
579 *
580 * @return mixed
581 * @throws SQLite_Object_Cache_Exception Announce database failure.
582 */
583 public function querySingle( $query, $entire_row = false ) {
584 $start = $this->time_usec();
585 $result = $this->sqlite->querySingle( $query, $entire_row );
586 if ( false === $result ) {
587 throw new SQLite_Object_Cache_Exception( 'querySingle', $this->sqlite, $start );
588 }
589
590 return $result;
591 }
592
593 /**
594 * Create the prepared statements to use.
595 *
596 * @param string $tbl Table name.
597 *
598 * @return void
599 * @throws SQLite_Object_Cache_Exception Announce failure.
600 * @noinspection SqlResolve
601 */
602 private function prepare_statements( $tbl ) {
603 /* NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3 */
604
605 $now = time();
606 $updateone = "UPDATE $tbl SET value = :value, expires = $now + :expires WHERE name = :name;";
607 $insertone = "INSERT INTO $tbl (name, value, expires) VALUES (:name, :value, $now + :expires);";
608 /* @noinspection SqlResolve */
609 $upsertone = "
610 INSERT INTO $tbl (name, value, expires)
611 VALUES (:name, :value, $now + :expires)
612 ON CONFLICT(name) DO UPDATE
613 SET value=excluded.value, expires=excluded.expires;";
614 $getone = "SELECT value FROM $tbl WHERE name = :name AND expires >= $now;";
615 $deleteone = "DELETE FROM $tbl WHERE name = :name;";
616 $deletegroup = "DELETE FROM $tbl WHERE name LIKE :group || '.%';";
617
618 $this->getone = $this->prepare( $getone );
619 $this->deleteone = $this->prepare( $deleteone );
620 $this->deletegroup = $this->prepare( $deletegroup );
621 /*
622 * Some versions of SQLite3 built into php predate the 3.38 advent of unixepoch() (2022-02-22).
623 * And, others predate the 3.24 advent of UPSERT (that is, ON CONFLICT) syntax.
624 * In that case we have to do attempt-update then insert to get updates to work. Sigh.
625 */
626 $has_upsert = version_compare( $this->sqlite_get_version(), '3.24', 'ge' );
627 if ( $has_upsert ) {
628 $this->upsertone = $this->prepare( $upsertone );
629 } else {
630 $this->insertone = $this->prepare( $insertone );
631 $this->updateone = $this->prepare( $updateone );
632 }
633 }
634
635 /**
636 * Wrapper around prepare()
637 *
638 * @param string $query Statement to prepare.
639 *
640 * @return SQLite3Stmt
641 * @throws SQLite_Object_Cache_Exception Announce failure.
642 */
643 public function prepare( $query ) {
644 $start = $this->time_usec();
645 $result = $this->sqlite->prepare( $query );
646 if ( false === $result ) {
647 throw new SQLite_Object_Cache_Exception( 'prepare', $this->sqlite, $start );
648 }
649
650 return $result;
651 }
652
653 /**
654 * Preload frequently accessed items.
655 *
656 * @param string $tbl Cache table name.
657 *
658 * @return void
659 * @noinspection SqlResolve
660 */
661 public function preload( $tbl ) {
662 $list =
663 [
664 'options|%',
665 'default|%',
666 'posts|last_changed',
667 'terms|last_changed',
668 'site_options|%notoptions',
669 'transient|doing_cron',
670 ];
671
672 $sql = '';
673 $clauses = [];
674 foreach ( $list as $item ) {
675 /* NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3 */
676 $clauses [] = "SELECT name, value FROM $tbl WHERE name LIKE '$item'";
677 }
678 $sql .= implode( ' UNION ALL ', $clauses ) . ';';
679
680 $resultset = $this->sqlite->query( $sql );
681 if ( ! $resultset ) {
682 return;
683 }
684 while ( true ) {
685 $row = $resultset->fetchArray( SQLITE3_NUM );
686 if ( ! $row ) {
687 break;
688 }
689 list( $group, $key ) = explode( '|', $row[0], 2 );
690 $val = $this->maybe_unserialize( $row[1] );
691 /* Put the preloaded value into the cache. */
692 $this->cache[ $group ][ $key ] = $val;
693 }
694 }
695
696 /**
697 * Serialize data for persistence if need be. Use igbinary if available.
698 *
699 * @param mixed $data To be unserialized.
700 *
701 * @return string|mixed Data ready for use.
702 */
703 private function maybe_unserialize( $data ) {
704 if ( $this->has_igbinary ) {
705 return igbinary_unserialize( $data );
706 }
707
708 return maybe_unserialize( $data );
709 }
710
711 /**
712 * Determine whether we can use SQLite3.
713 *
714 * @param string $directory The directory to hold the .sqlite file. Default WP_CONTENT_DIR.
715 *
716 * @return bool|string true, or an error message.
717 */
718 public static function has_sqlite( $directory = WP_CONTENT_DIR ) {
719 if ( ! wp_is_writable( $directory ) ) {
720 return sprintf( /* translators: 1: WP_CONTENT_DIR */ __( 'The SQLite Object Cache cannot be activated because the %s directory is not writable.', 'sqlite-object-cache' ), $directory );
721 }
722
723 if ( ! class_exists( 'SQLite3' ) || ! extension_loaded( 'sqlite3' ) ) {
724 return __( 'The SQLite Object Cache cannot be activated because the SQLite3 extension is not loaded.', 'sqlite-object-cache' );
725 }
726
727 return true;
728 }
729
730 /**
731 * Set the monitoring options for the SQLite cache.
732 *
733 * Options in array [
734 * 'capture' => (bool)
735 * 'resolution' => how often in seconds (float)
736 * 'lifetime' => how long until entries expire in seconds (int)
737 * 'verbose' => (bool) capture extra stuff.
738 * ]
739 *
740 * @param array $options Option list.
741 *
742 * @return void
743 */
744 public function set_sqlite_monitoring_options( $options ) {
745 $this->monitoring_options = $options;
746 }
747
748 /**
749 * Is recording this performance sample appropriate.
750 *
751 * We decide to take a performance sample based upon:
752 * -- the sqlite_object_cache_settings option existing.
753 * -- $option.capture having the 'on' value.
754 * -- $option.samplerate >= 100 or samplerate greater than a random number.
755 *
756 * @return bool True if this sample should be recorded.
757 */
758 private function is_sample() {
759 $options = get_option( 'sqlite_object_cache_settings', 'missing_option' );
760 if ( 'missing_option' === $options ) {
761 /* set an absent option to the empty array, so we don't repeatedly hammer the cache looking for a missing option */
762 update_option( 'sqlite_object_cache_settings', [], true );
763
764 return false;
765 }
766 if ( is_array( $options ) && array_key_exists( 'capture', $options ) && 'on' === $options['capture'] ) {
767 if ( array_key_exists( 'samplerate', $options ) && is_numeric( $options['samplerate'] ) ) {
768 /* samplerate is a percentage likelihood in the option setting */
769 $samplerate = $options['samplerate'] * 0.01;
770 if ( $samplerate > 0.0 ) {
771 /* a random sample at $samplerate */
772 return ( $samplerate >= 1.0 || $samplerate >= lcg_value() );
773 }
774 }
775 }
776
777 return false;
778 }
779
780 /**
781 * Capture statistics if need be, then close the connection.
782 *
783 * @return bool
784 */
785 public function close() {
786 $result = true;
787 if ( $this->sqlite ) {
788 try {
789 if ( $this->is_sample() ) {
790 $this->capture( $this->monitoring_options );
791 }
792 $result = $this->sqlite->close();
793 $this->sqlite = null;
794 } catch ( Exception $ex ) {
795 error_log( $ex );
796 $this->delete_offending_files();
797 }
798 }
799
800 return $result;
801 }
802
803 /**
804 * Generate canonical name for cache item
805 *
806 * @param string $key The key name.
807 * @param string $group The group name.
808 *
809 * @return string The name.
810 */
811 private function name_from_key_group( $key, $group ) {
812 return $group . '|' . $key;
813 }
814
815 /**
816 * Serialize data for persistence if need be. Use igbinary if available.
817 *
818 * @param mixed $data To be serialized.
819 *
820 * @return string|mixed Data ready for dbms insertion.
821 */
822 private function maybe_serialize( $data ) {
823 if ( $this->has_igbinary ) {
824 return igbinary_serialize( $data );
825 }
826
827 return maybe_serialize( $data );
828 }
829
830 /**
831 * Remove statistics entries from the cache
832 *
833 * @param int|null $age Number of seconds' worth to retain. Default: retain none.
834 *
835 * @return void
836 * @throws SQLite_Object_Cache_Exception Announce database error.
837 */
838 public function sqlite_reset_statistics( $age = null ) {
839 if ( ! $this->sqlite ) {
840 $this->open_connection();
841 }
842 $object_stats = self::OBJECT_STATS_TABLE;
843 $this->maybe_create_stats_table( $object_stats );
844 /* NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3 */
845 if ( ! is_numeric( $age ) ) {
846 /* @noinspection SqlWithoutWhere */
847 $sql = "DELETE FROM $object_stats;";
848 } else {
849 $expires = (int) ( time() - $age );
850 /* @noinspection SqlResolve */
851 $sql =
852 "DELETE FROM $object_stats WHERE timestamp < $expires;";
853 }
854 $this->exec( $sql );
855 }
856
857 /**
858 * Remove old entries and VACUUM the database.
859 *
860 * @param mixed $retention How long, in seconds, to keep old entries. Default one week.
861 * @param bool $use_transaction True if the cleanup should be inside BEGIN / COMMIT.
862 * @param bool $vacuum VACUUM the db.
863 *
864 * @return void
865 * @noinspection SqlResolve
866 */
867 public function sqlite_clean_up_cache( $retention = null, $use_transaction = true, $vacuum = false ) {
868 /* NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3 */
869 try {
870 if ( ! $this->sqlite ) {
871 $this->open_connection();
872 }
873 if ( $use_transaction ) {
874 $this->exec( 'BEGIN;' );
875 }
876 /* Remove items with definite expirations, like transients */
877 $sql = "DELETE FROM $this->cache_table_name WHERE expires <= :now;";
878 $stmt = $this->prepare( $sql );
879 $stmt->bindValue( ':now', time(), SQLITE3_INTEGER );
880 $result = $stmt->execute();
881 if ( false !== $result ) {
882 $result->finalize();
883 }
884 /* Remove old items. We use the most recent update time. Tracking use time is too expensive. */
885 $retention = is_numeric( $retention ) ? $retention : $this->max_lifetime;
886 $sql = "DELETE FROM $this->cache_table_name WHERE expires BETWEEN :offset AND :end;";
887 $stmt = $this->prepare( $sql );
888 $offset = $this->noexpire_timestamp_offset;
889 $end = time() + $offset - $retention;
890 $stmt->bindValue( ':offset', $offset, SQLITE3_INTEGER );
891 $stmt->bindValue( ':end', $end, SQLITE3_INTEGER );
892 $result = $stmt->execute();
893 if ( false !== $result ) {
894 $result->finalize();
895 if ( $use_transaction ) {
896 $this->exec( 'COMMIT;' );
897 }
898 }
899 if ( $vacuum ) {
900 $this->exec( 'VACUUM;' );
901 $this->exec( 'PRAGMA analysis_limit=400;' );
902 $this->exec( 'PRAGMA optimize;' );
903 }
904 } catch ( SQLite_Object_Cache_Exception $ex ) {
905 error_log( $ex );
906 $this->delete_offending_files( 0 );
907 }
908 }
909
910 /**
911 * Read rows from the stored statistics.
912 *
913 * @return Generator
914 * @throws SQLite_Object_Cache_Exception Announce SQLite failure.
915 * @noinspection SqlResolve
916 */
917 public function sqlite_load_statistics() {
918 if ( ! $this->sqlite ) {
919 $this->open_connection();
920 }
921
922 $object_stats = self::OBJECT_STATS_TABLE;
923 $this->maybe_create_stats_table( $object_stats );
924 $sql = 'SELECT value FROM object_stats ORDER BY timestamp;';
925 $stmt = $this->prepare( $sql );
926 $resultset = $stmt->execute();
927 while ( true ) {
928 $row = $resultset->fetchArray( SQLITE3_NUM );
929 if ( ! $row ) {
930 break;
931 }
932 $value = $this->maybe_unserialize( $row[0] );
933 yield (object) $value;
934 }
935 $resultset->finalize();
936 }
937
938 /**
939 * Do the performance-capture operation.
940 *
941 * Put a row named sqlite_object_cache.mon.123456 into sqlite containing the raw data.
942 *
943 * @param array $options Contents of $this->monitoring_options.
944 *
945 * @return void
946 * @noinspection SqlResolve
947 */
948 private function capture( $options ) {
949 $now = microtime( true );
950 global $wpdb;
951 $record = [
952 'time' => $now,
953 'RAMhits' => $this->cache_hits,
954 'RAMmisses' => $this->cache_misses,
955 'DISKhits' => $this->persistent_hits,
956 'DISKmisses' => $this->persistent_misses,
957 'open' => $this->open_time,
958 'selects' => $this->select_times,
959 'inserts' => $this->insert_times,
960 'deletes' => $this->delete_times,
961 'DBMSqueries' => $wpdb->num_queries,
962 ];
963 if ( is_array( $options ) && $options['verbose'] ) {
964 $record ['select_names'] = $this->select_names;
965 $record ['delete_names'] = $this->insert_names;
966 }
967
968 $object_stats = self::OBJECT_STATS_TABLE;
969 try {
970 if ( ! $this->sqlite ) {
971 $this->open_connection();
972 }
973 $this->maybe_create_stats_table( $object_stats );
974 $sql =
975 "INSERT INTO $object_stats (value, timestamp) VALUES (:value, :timestamp);";
976 $stmt = $this->prepare( $sql );
977 $stmt->bindValue( ':value', $this->maybe_serialize( $record ), SQLITE3_BLOB );
978 $stmt->bindValue( ':timestamp', time(), SQLITE3_INTEGER );
979 $result = $stmt->execute();
980 if ( $result ) {
981 $result->finalize();
982 }
983 } catch ( \Exception $ex ) {
984 error_log( $ex );
985 $this->delete_offending_files( 0 );
986 }
987 unset( $record, $stmt );
988 }
989
990 /**
991 * Get the version of SQLite in use.
992 *
993 * @return string
994 */
995 public function sqlite_get_version() {
996 $v = SQLite3::version();
997
998 return $v['versionString'];
999 }
1000
1001 /**
1002 * Sets the list of groups not to be cached by Redis.
1003 *
1004 * @param array $groups List of groups that are to be ignored.
1005 */
1006 public function add_non_persistent_groups( $groups ) {
1007 /**
1008 * Filters list of groups to be added to {@see self::$ignored_groups}
1009 *
1010 * @param string[] $groups List of groups to be ignored.
1011 *
1012 * @since 2.1.7
1013 */
1014 $groups = apply_filters( 'sqlite_object_cache_add_non_persistent_groups', (array) $groups );
1015
1016 $this->ignored_groups = array_unique( array_merge( $this->ignored_groups, $groups ) );
1017 $this->cache_group_types();
1018 }
1019
1020 /**
1021 * Makes private properties readable for backward compatibility.
1022 *
1023 * @param string $name Property to get.
1024 *
1025 * @return mixed Property.
1026 * @since 4.0.0
1027 */
1028 public function __get( $name ) {
1029 return $this->$name;
1030 }
1031
1032 /**
1033 * Makes private properties settable for backward compatibility.
1034 *
1035 * @param string $name Property to set.
1036 * @param mixed $value Property value.
1037 *
1038 * @return mixed Newly-set property.
1039 * @since 4.0.0
1040 */
1041 public function __set( $name, $value ) {
1042 return $this->$name = $value;
1043 }
1044
1045 /**
1046 * Makes private properties checkable for backward compatibility.
1047 *
1048 * @param string $name Property to check if set.
1049 *
1050 * @return bool Whether the property is set.
1051 * @since 4.0.0
1052 */
1053 public function __isset( $name ) {
1054 return isset( $this->$name );
1055 }
1056
1057 /**
1058 * Makes private properties un-settable for backward compatibility.
1059 *
1060 * @param string $name Property to unset.
1061 *
1062 * @since 4.0.0
1063 */
1064 public function __unset( $name ) {
1065 unset( $this->$name );
1066 }
1067
1068 /**
1069 * Adds multiple values to the cache in one call.
1070 *
1071 * @param array $data Array of keys and values to be added.
1072 * @param string $group Optional. Where the cache contents are grouped. Default empty.
1073 * @param int $expire Optional. When to expire the cache contents, in seconds.
1074 * Default 0 (no expiration).
1075 *
1076 * @return bool[] Array of return values, grouped by key. Each value is either
1077 * true on success, or false if cache key and group already exist.
1078 * @throws SQLite_Object_Cache_Exception Announce database failure.
1079 * @since 6.0.0
1080 */
1081 public function add_multiple( array $data, $group = '', $expire = 0 ) {
1082 $values = [];
1083
1084 foreach ( $data as $key => $value ) {
1085 $values[ $key ] = $this->add( $key, $value, $group, $expire );
1086 }
1087
1088 return $values;
1089 }
1090
1091 /**
1092 * Adds data to the cache if it doesn't already exist.
1093 *
1094 * @param int|string $key What to call the contents in the cache.
1095 * @param mixed $data The contents to store in the cache.
1096 * @param string $group Optional. Where to group the cache contents. Default 'default'.
1097 * @param int $expire Optional. When to expire the cache contents, in seconds.
1098 * Default 0 (no expiration).
1099 *
1100 * @return bool True on success, false if cache key and group already exist.
1101 * @throws SQLite_Object_Cache_Exception Announce database failure.
1102 * @since 2.0.0
1103 *
1104 * @uses WP_Object_Cache::cache_item_exists() Checks to see if the cache already has data.
1105 * @uses WP_Object_Cache::set() Sets the data after the checking the cache
1106 * contents existence.
1107 */
1108 public function add( $key, $data, $group = 'default', $expire = 0 ) {
1109 if ( wp_suspend_cache_addition() ) {
1110 return false;
1111 }
1112
1113 if ( ! $this->is_valid_key( $key ) ) {
1114 return false;
1115 }
1116
1117 if ( empty( $group ) ) {
1118 $group = 'default';
1119 }
1120
1121 $id = $key;
1122 if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
1123 $id = $this->blog_prefix . $key;
1124 }
1125
1126 if ( $this->cache_item_exists( $id, $group ) ) {
1127 return false;
1128 }
1129
1130 return $this->set( $key, $data, $group, (int) $expire );
1131 }
1132
1133 /**
1134 * Serves as a utility function to determine whether a key is valid.
1135 *
1136 * @param int|string $key Cache key to check for validity.
1137 *
1138 * @return bool Whether the key is valid.
1139 * @since 6.1.0
1140 */
1141 protected function is_valid_key( $key ) {
1142 if ( is_int( $key ) ) {
1143 return true;
1144 }
1145
1146 if ( is_string( $key ) && trim( $key ) !== '' ) {
1147 return true;
1148 }
1149
1150 $type = gettype( $key );
1151
1152 if ( ! function_exists( '__' ) ) {
1153 wp_load_translations_early();
1154 }
1155
1156 $message =
1157 is_string( $key ) ? __( 'Cache key must not be an empty string.' )
1158 /* translators: %s: The type of the given cache key. */
1159 : sprintf( __( 'Cache key must be integer or non-empty string, %s given.' ), $type );
1160 // phpcs:ignore
1161 _doing_it_wrong( sprintf( '%s::%s', __CLASS__, debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2 )[1]['function'] ), $message, '6.1.0' );
1162
1163 return false;
1164 }
1165
1166 /**
1167 * Determine whether a key exists in the cache.
1168 *
1169 * @param int|string $key Cache key to check for existence.
1170 * @param string $group Cache group for the key existence check.
1171 *
1172 * @return bool Whether the key exists in the cache for the given group.
1173 * @throws SQLite_Object_Cache_Exception Announce database failure.
1174 * @since 3.4.0
1175 */
1176 protected function cache_item_exists( $key, $group ) {
1177 $exists =
1178 isset( $this->cache[ $group ] ) && ( isset( $this->cache[ $group ][ $key ] ) || array_key_exists( $key, $this->cache[ $group ] ) );
1179 if ( ! $exists ) {
1180 $val = $this->getone( $key, $group );
1181 if ( null !== $val ) {
1182 if ( ! array_key_exists( $group, $this->cache ) ) {
1183 $this->cache [ $group ] = [];
1184 }
1185 $this->cache[ $group ][ $key ] = $val;
1186 $exists = true;
1187 $this->persistent_hits ++;
1188 } else {
1189 $this->persistent_misses ++;
1190 }
1191 }
1192
1193 return $exists;
1194 }
1195
1196 /**
1197 * Get one item from external cache.
1198 *
1199 * @param string $key Cache key.
1200 * @param string $group Group name.
1201 *
1202 * @return mixed|null Cached item, or null if not found. (Cached item can be false.)
1203 * @throws SQLite_Object_Cache_Exception Announce database failure.
1204 */
1205 private function getone( $key, $group ) {
1206 if ( ! $this->sqlite ) {
1207 $this->open_connection();
1208 }
1209 $start = $this->time_usec();
1210 $name = $this->name_from_key_group( $key, $group );
1211 if ( array_key_exists( $name, $this->not_in_persistent_cache ) ) {
1212 return null;
1213 }
1214 $stmt = $this->getone;
1215 $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1216 try {
1217 $result = $stmt->execute();
1218 if ( false === $result ) {
1219 unset( $this->in_persistent_cache[ $name ] );
1220 $this->not_in_persistent_cache [ $name ] = true;
1221 throw new SQLite_Object_Cache_Exception( 'stmt->execute', $this->sqlite, $start );
1222 }
1223 } catch (Exception $ex) {
1224 unset( $this->in_persistent_cache[ $name ] );
1225 $this->not_in_persistent_cache [ $name ] = true;
1226 throw $ex;
1227 }
1228 $row = $result->fetchArray( SQLITE3_NUM );
1229 $data = false !== $row && is_array( $row ) && 1 === count( $row ) ? $row[0] : null;
1230 if ( null !== $data ) {
1231 $data = $this->maybe_unserialize( $data );
1232 $this->in_persistent_cache [ $name ] = true;
1233 } else {
1234 $this->not_in_persistent_cache [ $name ] = true;
1235 }
1236
1237 $this->select_times[] = $this->time_usec() - $start;
1238 $this->select_names[] = $name;
1239
1240 return $data;
1241 }
1242
1243 /**
1244 * Sets the data contents into the cache.
1245 *
1246 * The cache contents are grouped by the $group parameter followed by the
1247 * $key. This allows for duplicate IDs in unique groups. Therefore, naming of
1248 * the group should be used with care and should follow normal function
1249 * naming guidelines outside of core WordPress usage.
1250 *
1251 * The $expire parameter is not used, because the cache will automatically
1252 * expire for each time a page is accessed and PHP finishes. The method is
1253 * more for cache plugins which use files.
1254 *
1255 * @param int|string $key What to call the contents in the cache.
1256 * @param mixed $data The contents to store in the cache.
1257 * @param string $group Optional. Where to group the cache contents. Default 'default'.
1258 * @param int $expire Optional. Not used.
1259 *
1260 * @return bool True if contents were set, false if key is invalid.
1261 * @since 6.1.0 Returns false if cache key is invalid.
1262 *
1263 * @since 2.0.0
1264 */
1265 public function set( $key, $data, $group = 'default', $expire = 0 ) {
1266 if ( ! $this->is_valid_key( $key ) ) {
1267 return false;
1268 }
1269
1270 if ( empty( $group ) ) {
1271 $group = 'default';
1272 }
1273
1274 if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
1275 $key = $this->blog_prefix . $key;
1276 }
1277
1278 if ( is_object( $data ) ) {
1279 $data = clone $data;
1280 }
1281
1282 $this->cache[ $group ][ $key ] = $data;
1283 $this->handle_put( $key, $data, $group, $expire );
1284
1285 return true;
1286 }
1287
1288 /**
1289 * Write to the persistent cache.
1290 *
1291 * @param int|string $key What to call the contents in the cache.
1292 * @param mixed $data The contents to store in the cache.
1293 * @param string $group Optional. Where to group the cache contents. Default 'default'.
1294 * @param int $expire Optional. Not used.
1295 *
1296 * @return void
1297 * @throws SQLite_Object_Cache_Exception Announce database failure.
1298 */
1299 private function handle_put( $key, $data, $group, $expire ) {
1300 if ( ! $this->sqlite ) {
1301 $this->open_connection();
1302 }
1303
1304 if ( $this->is_ignored_group( $group ) ) {
1305 return;
1306 }
1307 $name = $this->name_from_key_group( $key, $group );
1308 $start = $this->time_usec();
1309 $value = $this->maybe_serialize( $data );
1310 $expires = $expire ?: $this->noexpire_timestamp_offset;
1311 if ( $this->upsertone ) {
1312 $stmt = $this->upsertone;
1313 $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1314 $stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1315 $stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1316 $result = $stmt->execute();
1317 if ( false === $result ) {
1318 $this->delete_offending_files( 0 );
1319 throw new SQLite_Object_Cache_Exception( 'upsert: ' . $name, $this->sqlite, $start );
1320 }
1321 $result->finalize();
1322 } else {
1323 try {
1324 /* Pre-upsert version (pre- 3.24) of SQLite,
1325 * Need to try update, then do insert if need be.
1326 * Race conditions are possible, hence BEGIN / COMMIT
1327 */
1328 $this->exec( 'BEGIN;' );
1329 $stmt = $this->updateone;
1330 $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1331 $stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1332 $stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1333 $result = $stmt->execute();
1334 if ( false === $result ) {
1335 $this->exec( 'ROLLBACK;' );
1336 $this->delete_offending_files( 0 );
1337 throw new SQLite_Object_Cache_Exception( 'update: ' . $name, $this->sqlite, $start );
1338 }
1339 $result->finalize();
1340 if ( 0 === $this->sqlite->changes() ) {
1341 /* Updated no rows: need insert. */
1342 $start2 = $this->time_usec();
1343 $stmt = $this->insertone;
1344 $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1345 $stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1346 $stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1347 $result = $stmt->execute();
1348 if ( false === $result ) {
1349 $this->exec( 'ROLLBACK;' );
1350 $this->delete_offending_files( 0 );
1351 throw new SQLite_Object_Cache_Exception( 'insert: ' . $name, $this->sqlite, $start2 );
1352 }
1353 $result->finalize();
1354 }
1355 $this->exec( 'COMMIT;' );
1356 } catch (Exception $e) {
1357 $this->exec( 'ROLLBACK;' );
1358 $this->delete_offending_files( 0 );
1359 throw new SQLite_Object_Cache_Exception( 'insert: ' . $name, $this->sqlite, $start2 );
1360 }
1361 }
1362 unset( $this->not_in_persistent_cache[ $name ] );
1363 $this->in_persistent_cache[ $name ] = true;
1364 /* track how long it took. */
1365 $this->insert_times[] = $this->time_usec() - $start;
1366 $this->insert_names[] = $name;
1367 }
1368
1369 /**
1370 * Replaces the contents in the cache, if contents already exist.
1371 *
1372 * @param int|string $key What to call the contents in the cache.
1373 * @param mixed $data The contents to store in the cache.
1374 * @param string $group Optional. Where to group the cache contents. Default 'default'.
1375 * @param int $expire Optional. When to expire the cache contents, in seconds.
1376 * Default 0 (no expiration).
1377 *
1378 * @return bool True if contents were replaced, false if original value does not exist.
1379 * @see WP_Object_Cache::set()
1380 *
1381 * @since 2.0.0
1382 *
1383 */
1384 public function replace( $key, $data, $group = 'default', $expire = 0 ) {
1385 if ( ! $this->is_valid_key( $key ) ) {
1386 return false;
1387 }
1388
1389 if ( empty( $group ) ) {
1390 $group = 'default';
1391 }
1392
1393 $id = $key;
1394 if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
1395 $id = $this->blog_prefix . $key;
1396 }
1397
1398 if ( ! $this->cache_item_exists( $id, $group ) ) {
1399 return false;
1400 }
1401
1402 return $this->set( $key, $data, $group, (int) $expire );
1403 }
1404
1405 /**
1406 * Sets multiple values to the cache in one call.
1407 *
1408 * @param array $data Array of key and value to be set.
1409 * @param string $group Optional. Where the cache contents are grouped. Default empty.
1410 * @param int $expire Optional. When to expire the cache contents, in seconds.
1411 * Default 0 (no expiration).
1412 *
1413 * @return bool[] Array of return values, grouped by key. Each value is always true.
1414 * @since 6.0.0
1415 */
1416 public function set_multiple( array $data, $group = '', $expire = 0 ) {
1417 $values = [];
1418
1419 foreach ( $data as $key => $value ) {
1420 $values[ $key ] = $this->set( $key, $value, $group, $expire );
1421 }
1422
1423 return $values;
1424 }
1425
1426 /**
1427 * Retrieves multiple values from the cache in one call.
1428 *
1429 * @param array $keys Array of keys under which the cache contents are stored.
1430 * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
1431 * @param bool $force Optional. Whether to force an update of the local cache
1432 * from the persistent cache. Default false.
1433 *
1434 * @return array Array of return values, grouped by key. Each value is either
1435 * the cache contents on success, or false on failure.
1436 * @throws SQLite_Object_Cache_Exception Announce database failure.
1437 * @since 5.5.5
1438 */
1439 public function get_multiple( $keys, $group = 'default', $force = false ) {
1440 $values = [];
1441
1442 foreach ( $keys as $key ) {
1443 $values[ $key ] = $this->get( $key, $group, $force );
1444 }
1445
1446 return $values;
1447 }
1448
1449 /**
1450 * Retrieves the cache contents, if it exists.
1451 *
1452 * The contents will be first attempted to be retrieved by searching by the
1453 * key in the cache group. If the cache is hit (success) then the contents
1454 * are returned.
1455 *
1456 * On failure, the number of cache misses will be incremented.
1457 *
1458 * @param int|string $key The key under which the cache contents are stored.
1459 * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
1460 * @param bool $force Optional. Whether to force an update of the local cache
1461 * from the persistent cache. Default false.
1462 * @param bool $found Optional. Whether the key was found in the cache (passed by reference).
1463 * Disambiguates a return of false, a storable value. Default null.
1464 *
1465 * @return mixed|false The cache contents on success, false on failure to retrieve contents.
1466 * @throws SQLite_Object_Cache_Exception Announce database failure.
1467 * @since 2.0.0
1468 */
1469 public function get( $key, $group = 'default', $force = false, &$found = null ) {
1470 if ( ! $this->is_valid_key( $key ) ) {
1471 return false;
1472 }
1473
1474 if ( empty( $group ) ) {
1475 $group = 'default';
1476 }
1477
1478 if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
1479 $key = $this->blog_prefix . $key;
1480 }
1481
1482 if ( $force ) {
1483 unset( $this->cache[ $group ][ $key ] );
1484 }
1485
1486 if ( $this->cache_item_exists( $key, $group ) ) {
1487 $found = true;
1488 ++ $this->cache_hits;
1489 if ( is_object( $this->cache[ $group ][ $key ] ) ) {
1490 return clone $this->cache[ $group ][ $key ];
1491 }
1492
1493 return $this->cache[ $group ][ $key ];
1494 }
1495
1496 $found = false;
1497 $this->cache_misses ++;
1498
1499 return false;
1500 }
1501
1502 /**
1503 * Deletes multiple values from the cache in one call.
1504 *
1505 * @param array $keys Array of keys to be deleted.
1506 * @param string $group Optional. Where the cache contents are grouped. Default empty.
1507 *
1508 * @return bool[] Array of return values, grouped by key. Each value is either
1509 * true on success, or false if the contents were not deleted.
1510 * @since 6.0.0
1511 */
1512 public function delete_multiple( array $keys, $group = '' ) {
1513 $values = [];
1514
1515 foreach ( $keys as $key ) {
1516 $values[ $key ] = $this->delete( $key, $group );
1517 }
1518
1519 return $values;
1520 }
1521
1522 /**
1523 * Removes the contents of the cache key in the group.
1524 *
1525 * If the cache key does not exist in the group, then nothing will happen.
1526 *
1527 * @param int|string $key What the contents in the cache are called.
1528 * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
1529 * @param bool $deprecated Optional. Unused. Default false.
1530 *
1531 * @return bool True on success, false if the contents were not deleted.
1532 * @throws SQLite_Object_Cache_Exception Announce database failure
1533 * @since 2.0.0
1534 *
1535 */
1536 public function delete( $key, $group = 'default', $deprecated = false ) {
1537 if ( ! $this->is_valid_key( $key ) ) {
1538 return false;
1539 }
1540
1541 if ( empty( $group ) ) {
1542 $group = 'default';
1543 }
1544
1545 if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
1546 $key = $this->blog_prefix . $key;
1547 }
1548
1549 if ( ! $this->cache_item_exists( $key, $group ) ) {
1550 return false;
1551 }
1552
1553 unset( $this->cache[ $group ][ $key ] );
1554 $this->handle_delete( $key, $group );
1555
1556 return true;
1557 }
1558
1559 /**
1560 * Delete from the persistent cache.
1561 *
1562 * @param int|string $key What to call the contents in the cache.
1563 * @param string $group Optional. Where to group the cache contents. Default 'default'.
1564 *
1565 * @return void
1566 * @throws SQLite_Object_Cache_Exception Announce database failure
1567 */
1568 private function handle_delete( $key, $group ) {
1569 if ( ! $this->sqlite ) {
1570 $this->open_connection();
1571 }
1572 $name = $this->name_from_key_group( $key, $group );
1573 $start = $this->time_usec();
1574 $stmt = $this->deleteone;
1575 $stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1576 $result = $stmt->execute();
1577 if ( false === $result ) {
1578 $this->delete_offending_files( 0 );
1579 throw new SQLite_Object_Cache_Exception( 'delete: ' . $name, $this->sqlite, $start );
1580 }
1581 $result->finalize();
1582 unset( $this->in_persistent_cache[ $name ] );
1583 $this->not_in_persistent_cache[ $name ] = true;
1584 /* track how long it took. */
1585 $this->delete_times[] = $this->time_usec() - $start;
1586 }
1587
1588 /**
1589 * Increments numeric cache item's value.
1590 *
1591 * @param int|string $key The cache key to increment.
1592 * @param int $offset Optional. The amount by which to increment the item's value.
1593 * Default 1.
1594 * @param string $group Optional. The group the key is in. Default 'default'.
1595 *
1596 * @return int|false The item's new value on success, false on failure.
1597 * @since 3.3.0
1598 */
1599 public function incr( $key, $offset = 1, $group = 'default' ) {
1600 if ( ! $this->is_valid_key( $key ) ) {
1601 return false;
1602 }
1603
1604 if ( empty( $group ) ) {
1605 $group = 'default';
1606 }
1607
1608 if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
1609 $key = $this->blog_prefix . $key;
1610 }
1611
1612 if ( ! $this->cache_item_exists( $key, $group ) ) {
1613 return false;
1614 }
1615
1616 if ( ! is_numeric( $this->cache[ $group ][ $key ] ) ) {
1617 $this->cache[ $group ][ $key ] = 0;
1618 }
1619
1620 $offset = (int) $offset;
1621
1622 $this->cache[ $group ][ $key ] += $offset;
1623
1624 if ( $this->cache[ $group ][ $key ] < 0 ) {
1625 $this->cache[ $group ][ $key ] = 0;
1626 }
1627 $this->handle_put( $key, $group, $this->cache[ $group ][ $key ], 0 );
1628
1629 return $this->cache[ $group ][ $key ];
1630 }
1631
1632 /**
1633 * Decrements numeric cache item's value.
1634 *
1635 * @param int|string $key The cache key to decrement.
1636 * @param int $offset Optional. The amount by which to decrement the item's value.
1637 * Default 1.
1638 * @param string $group Optional. The group the key is in. Default 'default'.
1639 *
1640 * @return int|false The item's new value on success, false on failure.
1641 * @since 3.3.0
1642 *
1643 */
1644 public function decr( $key, $offset = 1, $group = 'default' ) {
1645 if ( ! $this->is_valid_key( $key ) ) {
1646 return false;
1647 }
1648
1649 if ( empty( $group ) ) {
1650 $group = 'default';
1651 }
1652
1653 if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) ) {
1654 $key = $this->blog_prefix . $key;
1655 }
1656
1657 if ( ! $this->cache_item_exists( $key, $group ) ) {
1658 return false;
1659 }
1660
1661 if ( ! is_numeric( $this->cache[ $group ][ $key ] ) ) {
1662 $this->cache[ $group ][ $key ] = 0;
1663 }
1664
1665 $offset = (int) $offset;
1666
1667 $this->cache[ $group ][ $key ] -= $offset;
1668
1669 if ( $this->cache[ $group ][ $key ] < 0 ) {
1670 $this->cache[ $group ][ $key ] = 0;
1671 }
1672
1673 $this->handle_put( $key, $group, $this->cache[ $group ][ $key ], 0 );
1674
1675 return $this->cache[ $group ][ $key ];
1676 }
1677
1678 /**
1679 * Clears the object cache of all data.
1680 *
1681 * @param bool $keep_performance_data True to retain performance data.
1682 * @param bool $vacuum True to do a VACUUM operation.
1683 *
1684 * @return bool Always returns true.
1685 * @since 2.0.0
1686 */
1687 public function flush( $vacuum = false ) {
1688 /* NOTE WELL: SQL in this file is not for use with $wpdb, but for SQLite3 */
1689 try {
1690 if ( ! $this->sqlite ) {
1691 $this->open_connection();
1692 }
1693
1694 $this->cache = [];
1695 $this->not_in_persistent_cache = [];
1696
1697 $selective =
1698 defined( 'WP_SQLITE_OBJECT_CACHE_SELECTIVE_FLUSH' ) ? WP_SQLITE_OBJECT_CACHE_SELECTIVE_FLUSH : null;
1699
1700 $clauses = [];
1701 $clauses [] = "(name <> 'sqlite_object_cache|created')";
1702 if ( $selective && is_array( $this->unflushable_groups ) ) {
1703 foreach ( $this->unflushable_groups as $unflushable_group ) {
1704 $unflushable_group = sanitize_key( $unflushable_group );
1705 $clauses [] = "(name NOT LIKE '$unflushable_group|%')";
1706 }
1707 }
1708 /* @noinspection SqlConstantCondition, SqlConstantExpression */
1709 $sql =
1710 'DELETE FROM ' . $this->cache_table_name . ' WHERE 1=1 AND ' . implode( ' AND ', $clauses ) . ';';
1711 $this->exec( $sql );
1712
1713 if ( $vacuum ) {
1714 $this->exec( 'VACUUM;' );
1715 }
1716 } catch ( Exception $ex ) {
1717 error_log( $ex );
1718 $this->delete_offending_files( 0 );
1719 }
1720
1721 return true;
1722 }
1723
1724 /**
1725 * Removes all cache items in a group.
1726 *
1727 * @param string $group Name of group to remove from cache.
1728 *
1729 * @return true Always returns true.
1730 * @since 6.1.0
1731 */
1732 public function flush_group( $group ) {
1733 if ( ! $this->sqlite ) {
1734 $this->open_connection();
1735 }
1736
1737 $start = $this->time_usec();
1738 unset( $this->cache[ $group ] );
1739 $stmt = $this->deletegroup;
1740 $stmt->bindValue( ':group', $group, SQLITE3_TEXT );
1741 $result = $stmt->execute();
1742 if ( false === $result ) {
1743 $this->delete_offending_files( 0 );
1744 error_log( new SQLite_Object_Cache_Exception( 'deletegroup', $this->sqlite, $start ) );
1745 }
1746
1747 /* remove hints about what is in the persistent cache */
1748 $this->not_in_persistent_cache = [];
1749 $this->in_persistent_cache = [];
1750
1751 return true;
1752 }
1753
1754 /**
1755 * Sets the list of groups not to flushed cached.
1756 *
1757 * @param array $groups List of groups that are unflushable.
1758 */
1759 public function add_unflushable_groups( $groups ) {
1760 $groups = (array) $groups;
1761
1762 $this->unflushable_groups = array_unique( array_merge( $this->unflushable_groups, $groups ) );
1763 $this->cache_group_types();
1764 }
1765
1766 /**
1767 * Sets the list of global cache groups.
1768 *
1769 * @param string|string[] $groups List of groups that are global.
1770 *
1771 * @since 3.0.0
1772 */
1773 public function add_global_groups( $groups ) {
1774 $groups = (array) $groups;
1775
1776 $groups = array_fill_keys( $groups, true );
1777 $this->global_groups = array_merge( $this->global_groups, $groups );
1778
1779 $this->cache_group_types();
1780 }
1781
1782 /**
1783 * Switches the internal blog ID.
1784 *
1785 * This changes the blog ID used to create keys in blog specific groups.
1786 *
1787 * @param int $blog_id Blog ID.
1788 *
1789 * @since 3.5.0
1790 *
1791 */
1792 public function switch_to_blog( $blog_id ) {
1793 $blog_id = (int) $blog_id;
1794 $this->blog_prefix = $this->multisite ? $blog_id . ':' : '';
1795 }
1796
1797 /**
1798 * Resets cache keys.
1799 *
1800 * @since 3.0.0
1801 *
1802 * @deprecated 3.5.0 Use WP_Object_Cache::switch_to_blog()
1803 * @see switch_to_blog()
1804 */
1805 public function reset() {
1806 _deprecated_function( __FUNCTION__, '3.5.0', 'WP_Object_Cache::switch_to_blog()' );
1807
1808 // Clear out non-global caches since the blog ID has changed.
1809 foreach ( array_keys( $this->cache ) as $group ) {
1810 if ( ! isset( $this->global_groups[ $group ] ) ) {
1811 unset( $this->cache[ $group ] );
1812 }
1813 }
1814 }
1815
1816 /**
1817 * Echoes the stats of the caching.
1818 *
1819 * Gives the cache hits, and cache misses. Also prints every cached group,
1820 * key and the data.
1821 *
1822 * @since 2.0.0
1823 */
1824 public function stats() {
1825 echo '<p><strong>Cache Hits:</strong> ' . esc_html( $this->cache_hits ) . '<br />';
1826 echo '<strong>Cache Misses:</strong> ' . esc_html( $this->cache_misses ) . '<br /></p>' . PHP_EOL;
1827 echo '<ul>';
1828 foreach ( $this->cache as $group => $cache ) {
1829 $length = number_format( strlen( $this->maybe_serialize( $cache ) ) / KB_IN_BYTES, 1 );
1830 $item = $group . ' - ( ' . $length . 'KiB )';
1831 echo '<li><strong>Group:</strong> ' . esc_html( $item ) . '</li>';
1832 }
1833 echo '</ul>';
1834 }
1835
1836 /**
1837 * Checks if the given group is part the ignored group array
1838 *
1839 * @param string $group Name of the group to check, pre-sanitized.
1840 *
1841 * @return bool
1842 */
1843 protected function is_ignored_group( $group ) {
1844 return $this->is_group_of_type( $group, 'ignored' );
1845 }
1846
1847 /**
1848 * Checks the type of the given group
1849 *
1850 * @param string $group Name of the group to check, pre-sanitized.
1851 * @param string $type Type of the group to check.
1852 *
1853 * @return bool
1854 */
1855 private function is_group_of_type( $group, $type ) {
1856 return isset( $this->group_type[ $group ] ) && $this->group_type[ $group ] === $type;
1857 }
1858
1859 /**
1860 * Checks if the given group is part the global group array
1861 *
1862 * @param string $group Name of the group to check, pre-sanitized.
1863 *
1864 * @return bool
1865 */
1866 protected function is_global_group( $group ) {
1867 return $this->is_group_of_type( $group, 'global' );
1868 }
1869
1870 /**
1871 * Delete sqlite files in hopes of recovering from trouble.
1872 *
1873 * @param int $retries
1874 *
1875 * @return void
1876 */
1877 private function delete_offending_files( $retries = 0 ) {
1878 error_log( "sqlite_object_cache connection failure, deleting sqlite files to retry. $retries" );
1879 require_once ABSPATH . 'wp-admin/includes/file.php';
1880 ob_start();
1881 $credentials = request_filesystem_credentials( '' );
1882 WP_Filesystem( $credentials );
1883 global $wp_filesystem;
1884 foreach ( [ '', '-shm', '-wal' ] as $suffix ) {
1885 $wp_filesystem->delete( $this->sqlite_path . $suffix );
1886 }
1887 ob_end_clean();
1888 }
1889 }
1890
1891 /**
1892 * Object Cache API
1893 *
1894 * @link https://developer.wordpress.org/reference/classes/wp_object_cache/
1895 *
1896 * @package WordPress
1897 * @subpackage Cache
1898 */
1899
1900 /**
1901 * Sets up Object Cache Global and assigns it.
1902 *
1903 * @throws RuntimeException If we cannot write the db file into the specified directory.
1904 * @since 2.0.0
1905 *
1906 * @global WP_Object_Cache $wp_object_cache
1907 */
1908 function wp_cache_init() {
1909 $message = WP_Object_Cache::has_sqlite();
1910 if ( true === $message ) {
1911 // We need to override this WordPress global in order to inject our cache.
1912 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1913 $GLOBALS['wp_object_cache'] = new WP_Object_Cache();
1914 } else {
1915 throw new \RuntimeException( $message );
1916 }
1917 }
1918
1919 /**
1920 * Adds data to the cache, if the cache key doesn't already exist.
1921 *
1922 * @param int|string $key The cache key to use for retrieval later.
1923 * @param mixed $data The data to add to the cache.
1924 * @param string $group Optional. The group to add the cache to. Enables the same key
1925 * to be used across groups. Default empty.
1926 * @param int $expire Optional. When the cache data should expire, in seconds.
1927 * Default 0 (no expiration).
1928 *
1929 * @return bool True on success, false if cache key and group already exist.
1930 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
1931 *
1932 * @since 2.0.0
1933 *
1934 * @see WP_Object_Cache::add()
1935 */
1936 function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
1937 global $wp_object_cache;
1938
1939 return $wp_object_cache->add( $key, $data, $group, (int) $expire );
1940 }
1941
1942 /**
1943 * Adds multiple values to the cache in one call.
1944 *
1945 * @param array $data Array of keys and values to be set.
1946 * @param string $group Optional. Where the cache contents are grouped. Default empty.
1947 * @param int $expire Optional. When to expire the cache contents, in seconds.
1948 * Default 0 (no expiration).
1949 *
1950 * @return bool[] Array of return values, grouped by key. Each value is either
1951 * true on success, or false if cache key and group already exist.
1952 * @see WP_Object_Cache::add_multiple()
1953 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
1954 *
1955 * @since 6.0.0
1956 */
1957 function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) {
1958 global $wp_object_cache;
1959
1960 return $wp_object_cache->add_multiple( $data, $group, $expire );
1961 }
1962
1963 /**
1964 * Replaces the contents of the cache with new data.
1965 *
1966 * @param int|string $key The key for the cache data that should be replaced.
1967 * @param mixed $data The new data to store in the cache.
1968 * @param string $group Optional. The group for the cache data that should be replaced.
1969 * Default empty.
1970 * @param int $expire Optional. When to expire the cache contents, in seconds.
1971 * Default 0 (no expiration).
1972 *
1973 * @return bool True if contents were replaced, false if original value does not exist.
1974 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
1975 *
1976 * @since 2.0.0
1977 *
1978 * @see WP_Object_Cache::replace()
1979 */
1980 function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) {
1981 global $wp_object_cache;
1982
1983 return $wp_object_cache->replace( $key, $data, $group, (int) $expire );
1984 }
1985
1986 /**
1987 * Saves the data to the cache.
1988 *
1989 * Differs from wp_cache_add() and wp_cache_replace() in that it will always write data.
1990 *
1991 * @param int|string $key The cache key to use for retrieval later.
1992 * @param mixed $data The contents to store in the cache.
1993 * @param string $group Optional. Where to group the cache contents. Enables the same key
1994 * to be used across groups. Default empty.
1995 * @param int $expire Optional. When to expire the cache contents, in seconds.
1996 * Default 0 (no expiration).
1997 *
1998 * @return bool True on success, false on failure.
1999 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2000 *
2001 * @since 2.0.0
2002 *
2003 * @see WP_Object_Cache::set()
2004 */
2005 function wp_cache_set( $key, $data, $group = '', $expire = 0 ) {
2006 global $wp_object_cache;
2007
2008 return $wp_object_cache->set( $key, $data, $group, (int) $expire );
2009 }
2010
2011 /**
2012 * Sets multiple values to the cache in one call.
2013 *
2014 * @param array $data Array of keys and values to be set.
2015 * @param string $group Optional. Where the cache contents are grouped. Default empty.
2016 * @param int $expire Optional. When to expire the cache contents, in seconds.
2017 * Default 0 (no expiration).
2018 *
2019 * @return bool[] Array of return values, grouped by key. Each value is either
2020 * true on success, or false on failure.
2021 * @see WP_Object_Cache::set_multiple()
2022 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2023 *
2024 * @since 6.0.0
2025 */
2026 function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) {
2027 global $wp_object_cache;
2028
2029 return $wp_object_cache->set_multiple( $data, $group, $expire );
2030 }
2031
2032 /**
2033 * Retrieves the cache contents from the cache by key and group.
2034 *
2035 * @param int|string $key The key under which the cache contents are stored.
2036 * @param string $group Optional. Where the cache contents are grouped. Default empty.
2037 * @param bool $force Optional. Whether to force an update of the local cache
2038 * from the persistent cache. Default false.
2039 * @param bool $found Optional. Whether the key was found in the cache (passed by reference).
2040 * Disambiguates a return of false, a storable value. Default null.
2041 *
2042 * @return mixed|false The cache contents on success, false on failure to retrieve contents.
2043 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2044 *
2045 * @since 2.0.0
2046 *
2047 * @see WP_Object_Cache::get()
2048 */
2049 function wp_cache_get( $key, $group = '', $force = false, &$found = null ) {
2050 global $wp_object_cache;
2051
2052 return $wp_object_cache->get( $key, $group, $force, $found );
2053 }
2054
2055 /**
2056 * Retrieves multiple values from the cache in one call.
2057 *
2058 * @param array $keys Array of keys under which the cache contents are stored.
2059 * @param string $group Optional. Where the cache contents are grouped. Default empty.
2060 * @param bool $force Optional. Whether to force an update of the local cache
2061 * from the persistent cache. Default false.
2062 *
2063 * @return array Array of return values, grouped by key. Each value is either
2064 * the cache contents on success, or false on failure.
2065 * @see WP_Object_Cache::get_multiple()
2066 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2067 *
2068 * @since 5.5.0
2069 */
2070 function wp_cache_get_multiple( $keys, $group = '', $force = false ) {
2071 global $wp_object_cache;
2072
2073 return $wp_object_cache->get_multiple( $keys, $group, $force );
2074 }
2075
2076 /**
2077 * Removes the cache contents matching key and group.
2078 *
2079 * @param int|string $key What the contents in the cache are called.
2080 * @param string $group Optional. Where the cache contents are grouped. Default empty.
2081 *
2082 * @return bool True on successful removal, false on failure.
2083 * @since 2.0.0
2084 *
2085 * @see WP_Object_Cache::delete()
2086 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2087 */
2088 function wp_cache_delete( $key, $group = '' ) {
2089 global $wp_object_cache;
2090
2091 return $wp_object_cache->delete( $key, $group );
2092 }
2093
2094 /**
2095 * Deletes multiple values from the cache in one call.
2096 *
2097 * @param array $keys Array of keys for deletion.
2098 * @param string $group Optional. Where the cache contents are grouped. Default empty.
2099 *
2100 * @return bool[] Array of return values, grouped by key. Each value is either
2101 * true on success, or false if the contents were not deleted.
2102 * @since 6.0.0
2103 *
2104 * @see WP_Object_Cache::delete_multiple()
2105 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2106 */
2107 function wp_cache_delete_multiple( array $keys, $group = '' ) {
2108 global $wp_object_cache;
2109
2110 return $wp_object_cache->delete_multiple( $keys, $group );
2111 }
2112
2113 /**
2114 * Increments numeric cache item's value.
2115 *
2116 * @param int|string $key The key for the cache contents that should be incremented.
2117 * @param int $offset Optional. The amount by which to increment the item's value.
2118 * Default 1.
2119 * @param string $group Optional. The group the key is in. Default empty.
2120 *
2121 * @return int|false The item's new value on success, false on failure.
2122 * @see WP_Object_Cache::incr()
2123 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2124 *
2125 * @since 3.3.0
2126 */
2127 function wp_cache_incr( $key, $offset = 1, $group = '' ) {
2128 global $wp_object_cache;
2129
2130 return $wp_object_cache->incr( $key, $offset, $group );
2131 }
2132
2133 /**
2134 * Decrements numeric cache item's value.
2135 *
2136 * @param int|string $key The cache key to decrement.
2137 * @param int $offset Optional. The amount by which to decrement the item's value.
2138 * Default 1.
2139 * @param string $group Optional. The group the key is in. Default empty.
2140 *
2141 * @return int|false The item's new value on success, false on failure.
2142 * @see WP_Object_Cache::decr()
2143 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2144 *
2145 * @since 3.3.0
2146 */
2147 function wp_cache_decr( $key, $offset = 1, $group = '' ) {
2148 global $wp_object_cache;
2149
2150 return $wp_object_cache->decr( $key, $offset, $group );
2151 }
2152
2153 /**
2154 * Removes all cache items.
2155 *
2156 * @return bool True on success, false on failure.
2157 * @see WP_Object_Cache::flush()
2158 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2159 *
2160 * @since 2.0.0
2161 *
2162 */
2163 function wp_cache_flush() {
2164 global $wp_object_cache;
2165
2166 return $wp_object_cache->flush();
2167 }
2168
2169 /**
2170 * Removes all cache items from the in-memory runtime cache.
2171 *
2172 * @return bool True on success, false on failure.
2173 * @see WP_Object_Cache::flush()
2174 *
2175 * @since 6.0.0
2176 *
2177 */
2178 function wp_cache_flush_runtime() {
2179 return wp_cache_flush();
2180 }
2181
2182 /**
2183 * Removes all cache items in a group, if the object cache implementation supports it.
2184 *
2185 * Before calling this function, always check for group flushing support using the
2186 * `wp_cache_supports( 'flush_group' )` function.
2187 *
2188 * @param string $group Name of group to remove from cache.
2189 *
2190 * @return bool True if group was flushed, false otherwise.
2191 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2192 *
2193 * @since 6.1.0
2194 *
2195 * @see WP_Object_Cache::flush_group()
2196 */
2197 function wp_cache_flush_group( $group ) {
2198 global $wp_object_cache;
2199
2200 return $wp_object_cache->flush_group( $group );
2201 }
2202
2203 /**
2204 * Determines whether the object cache implementation supports a particular feature.
2205 *
2206 * @param string $feature Name of the feature to check for. Possible values include:
2207 * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
2208 * 'flush_runtime', 'flush_group'.
2209 *
2210 * @return bool True if the feature is supported, false otherwise.
2211 * @since 6.1.0
2212 */
2213 function wp_cache_supports( $feature ) {
2214 switch ( $feature ) {
2215 case 'add_multiple':
2216 case 'set_multiple':
2217 case 'get_multiple':
2218 case 'delete_multiple':
2219 case 'flush_runtime':
2220 case 'flush_group':
2221 return true;
2222
2223 default:
2224 return false;
2225 }
2226 }
2227
2228 /**
2229 * Closes the cache.
2230 *
2231 * This function has ceased to do anything since WordPress 2.5. The
2232 * functionality was removed along with the rest of the persistent cache.
2233 *
2234 * This does not mean that plugins can't implement this function when they need
2235 * to make sure that the cache is cleaned up after WordPress no longer needs it.
2236 *
2237 * @return true Always returns true.
2238 * @since 2.0.0
2239 */
2240 function wp_cache_close() {
2241 global $wp_object_cache;
2242
2243 return $wp_object_cache->close();
2244 }
2245
2246 /**
2247 * Adds a group or set of groups to the list of global groups.
2248 *
2249 * @param string|string[] $groups A group or an array of groups to add.
2250 *
2251 * @see WP_Object_Cache::add_global_groups()
2252 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2253 *
2254 * @since 2.6.0
2255 */
2256 function wp_cache_add_global_groups( $groups ) {
2257 global $wp_object_cache;
2258
2259 $wp_object_cache->add_global_groups( $groups );
2260 }
2261
2262 /**
2263 * Adds a group or set of groups to the list of non-persistent groups.
2264 *
2265 * @param string|string[] $groups A group or an array of groups to add.
2266 *
2267 * @since 2.6.0
2268 */
2269 function wp_cache_add_non_persistent_groups( $groups ) {
2270
2271 global $wp_object_cache;
2272
2273 $wp_object_cache->add_non_persistent_groups( $groups );
2274 }
2275
2276 /**
2277 * Switches the internal blog ID.
2278 *
2279 * This changes the blog id used to create keys in blog specific groups.
2280 *
2281 * @param int $blog_id Site ID.
2282 *
2283 * @see WP_Object_Cache::switch_to_blog()
2284 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2285 *
2286 * @since 3.5.0
2287 */
2288 function wp_cache_switch_to_blog( $blog_id ) {
2289 global $wp_object_cache;
2290
2291 $wp_object_cache->switch_to_blog( $blog_id );
2292 }
2293
2294 /**
2295 * Resets internal cache keys and structures.
2296 *
2297 * If the cache back end uses global blog or site IDs as part of its cache keys,
2298 * this function instructs the back end to reset those keys and perform any cleanup
2299 * since blog or site IDs have changed since cache init.
2300 *
2301 * This function is deprecated. Use wp_cache_switch_to_blog() instead of this
2302 * function when preparing the cache for a blog switch. For clearing the cache
2303 * during unit tests, consider using wp_cache_init(). wp_cache_init() is not
2304 * recommended outside unit tests as the performance penalty for using it is high.
2305 *
2306 * @since 3.0.0
2307 * @deprecated 3.5.0 Use wp_cache_switch_to_blog()
2308 * @see WP_Object_Cache::reset()
2309 *
2310 * @global WP_Object_Cache $wp_object_cache Object cache global instance.
2311 */
2312 function wp_cache_reset() {
2313 _deprecated_function( __FUNCTION__, '3.5.0', 'wp_cache_switch_to_blog()' );
2314
2315 global $wp_object_cache;
2316
2317 $wp_object_cache->reset();
2318 }
2319 endif;
2320 // phpcs:enable Generic.WhiteSpace.ScopeIndent.IncorrectExact, Generic.WhiteSpace.ScopeIndent.Incorrect
2321