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

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