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

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