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

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