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

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