PluginProbe
SQLite Object Cache / 1.2.2
SQLite Object Cache v1.2.2
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.2.2, at assets/drop-in/object-cache.php

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