PluginProbe
Docket Cache – Object Cache Accelerator / 23.08.01
Docket Cache – Object Cache Accelerator v23.08.01
26.04.05 trunk 22.07.01 22.07.02 22.07.03 22.07.04 22.07.05 23.08.01 23.08.02 24.07.01 24.07.02 24.07.03 24.07.04 24.07.05 24.07.06 24.07.07 26.04.03 26.04.04
docket-cache / includes / cache.php

cache.php in Docket Cache – Object Cache Accelerator 23.08.01, at includes/cache.php

2,392 lines 68.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Docket Cache.
4 *
5 * @author Nawawi Jamili
6 * @license MIT
7 *
8 * @see https://github.com/nawawi/docket-cache
9 */
10 \defined('ABSPATH') || exit;
11
12 /*
13 * Reference:
14 * wp-includes/class-wp-object-cache.php
15 * wp-includes/cache.php
16 */
17
18 /**
19 * Core class that implements an object cache.
20 */
21 #[AllowDynamicProperties]
22 class WP_Object_Cache
23 {
24 /**
25 * Holds the cached objects.
26 *
27 * @var array
28 */
29 private $cache = [];
30
31 /**
32 * The amount of times the cache data was already stored in the cache.
33 *
34 * @var int
35 */
36 public $cache_hits = 0;
37
38 /**
39 * Amount of times the cache did not have the request in cache.
40 *
41 * @var int
42 */
43 public $cache_misses = 0;
44
45 /**
46 * The amount of times the cached data was fetched from the cache file.
47 *
48 * @var int
49 */
50 public $persistent_cache_hits = 0;
51
52 /**
53 * List of global cache groups.
54 *
55 * @var array
56 */
57 protected $global_groups = [];
58
59 /**
60 * List of non persistent groups.
61 *
62 * @var array
63 */
64 protected $non_persistent_groups = [];
65
66 /**
67 * List of non persistent keys.
68 *
69 * @var array
70 */
71 protected $non_persistent_keys = [];
72
73 /**
74 * List of non persistent group:key.
75 *
76 * @var array
77 */
78 protected $non_persistent_groupkey = [];
79
80 /**
81 * List of group:key exclude from pecaching.
82 *
83 * @var array
84 */
85 private $bypass_precache = [];
86
87 /**
88 * The blog prefix to prepend to keys in non-global groups.
89 *
90 * @var string
91 */
92 private $blog_prefix;
93
94 /**
95 * Holds the value of is_multisite().
96 *
97 * @var bool
98 */
99 private $multisite;
100
101 /**
102 * Holds the value of Network Id.
103 *
104 * @var bool
105 */
106 private $network_id = 1;
107
108 /**
109 * The cache path.
110 *
111 * @var string
112 */
113 private $cache_path;
114
115 /**
116 * The cache maximum size of cache file.
117 *
118 * @var int
119 */
120 private $cache_maxsize = 3145728;
121
122 /**
123 * The cache file lifespan.
124 *
125 * @var int
126 */
127 private $cache_maxttl = 345600;
128
129 /**
130 * List of filtered groups.
131 *
132 * @var array
133 */
134 private $filtered_groups = false;
135
136 /**
137 * Show signature.
138 *
139 * @var bool
140 */
141 private $add_signature;
142
143 /**
144 * List of caches to preload.
145 *
146 * @var array
147 */
148 public $precache = [];
149
150 /**
151 * List of loaded keys.
152 *
153 * @var array
154 */
155 public $precache_loaded = [];
156
157 /**
158 * Precache status.
159 *
160 * @var bool
161 */
162 private $is_precache = false;
163
164 /**
165 * Precache key.
166 *
167 * @var string
168 */
169 private $precache_hashkey = '';
170
171 /**
172 * Precache max keys.
173 *
174 * @var int
175 */
176 private $precache_maxkey = 20;
177
178 /**
179 * Precache max groups.
180 *
181 * @var int
182 */
183 private $precache_maxgroup = 20;
184
185 /**
186 * Precache max cache file (url).
187 *
188 * @var int
189 */
190 private $precache_maxfile = 100;
191
192 /**
193 * Maximum cache file.
194 *
195 * @var int
196 */
197 private $maxfile = 50000;
198
199 /**
200 * Check cache file limit in real-time.
201 *
202 * @var bool
203 */
204 private $maxfile_livecheck = true;
205
206 /**
207 * The maximum time in seconds a script is allowed to run.
208 *
209 * @var int
210 */
211 private $max_execution_time = 0;
212
213 /**
214 * Start of run timestamp.
215 *
216 * @var int
217 */
218 private $wp_start_timestamp = 0;
219
220 /**
221 * Dev mode.
222 *
223 * @var bool
224 */
225 private $is_dev = false;
226
227 /**
228 * Ignore stale cache.
229 *
230 * @var bool
231 */
232 private $ignore_stalecache = false;
233
234 /**
235 * Ignore empty cache.
236 *
237 * @var bool
238 */
239 private $ignore_emptycache = false;
240
241 /**
242 * Store Transients in DB.
243 *
244 * @var bool
245 */
246 private $use_transientdb = false;
247
248 /**
249 * List of transient exclude from store in DB.
250 *
251 * @var array
252 */
253 private $bypass_transientdb = [];
254
255 /**
256 * Sets up object properties.
257 */
258 public function __construct()
259 {
260 $this->multisite = \function_exists('is_multisite') && is_multisite();
261 $this->blog_prefix = $this->switch_to_blog(get_current_blog_id());
262 $this->network_id = (int) nwdcx_network_id();
263 $this->dc_init();
264 }
265
266 /**
267 * Serves as a utility function to determine whether a key exists in the cache.
268 *
269 * @param int|string $key cache key to check for existence
270 * @param string $group cache group for the key existence check
271 *
272 * @return bool whether the key exists in the cache for the given group
273 */
274 protected function _exists($key, $group, $force = false)
275 {
276 // check key
277 if (!$this->is_valid_key($key)) {
278 return false;
279 }
280
281 // check group
282 if (!\is_string($group)) {
283 // unset junk
284 unset($this->cache[$group]);
285 unset($this->precache[$group]);
286
287 return false;
288 }
289
290 $is_exists = !$force && !empty($this->cache) && isset($this->cache[$group]) && (isset($this->cache[$group][$key]) || \array_key_exists($key, $this->cache[$group]));
291
292 if (!$is_exists && !$this->is_non_persistent_groups($group) && !$this->is_non_persistent_keys($key) && !$this->is_non_persistent_groupkey($group, $key) && !$this->is_stalecache_ignored($key, $group)) {
293 $data = $this->dc_get($key, $group, false, $codestub_false);
294 if (false !== $data) {
295 $is_exists = true;
296 $this->cache[$group][$key] = $data;
297
298 if ($this->is_precache && !$codestub_false && !$this->fs()->suspend_cache_write() && !$this->is_bypass_precache($group, $key)) {
299 if (empty($this->precache[$group])) {
300 $this->precache[$group][$key] = 1;
301 } elseif (\count($this->precache[$group]) < $this->precache_maxkey && \count($this->precache) < $this->precache_maxgroup) {
302 $this->precache[$group][$key] = 1;
303 }
304 }
305 }
306 }
307
308 return $is_exists;
309 }
310
311 /**
312 * Adds data to the cache if it doesn't already exist.
313 *
314 * @uses WP_Object_Cache::_exists() Checks to see if the cache already has data.
315 * @uses WP_Object_Cache::set() Sets the data after the checking the cache
316 * contents existence.
317 *
318 * @param int|string $key what to call the contents in the cache
319 * @param mixed $data the contents to store in the cache
320 * @param string $group Optional. Where to group the cache contents. Default 'default'.
321 * @param int $expire Optional. When to expire the cache contents. Default 0 (no expiration).
322 *
323 * @return bool true on success, false if cache key and group already exist
324 */
325 public function add($key, $data, $group = 'default', $expire = 0)
326 {
327 if (wp_suspend_cache_addition()) {
328 return false;
329 }
330
331 if (!$this->is_valid_key($key)) {
332 return false;
333 }
334
335 if (empty($group)) {
336 $group = 'default';
337 }
338
339 $cache_key = $this->dc_key($key, $group);
340 if ($this->_exists($cache_key, $group)) {
341 return false;
342 }
343
344 return $this->set($key, $data, $group, (int) $expire);
345 }
346
347 /**
348 * Adds multiple values to the cache in one call.
349 *
350 * @param array $data array of keys and values to be added
351 * @param string $group Optional. Where the cache contents are grouped. Default empty.
352 * @param int $expire Optional. When to expire the cache contents, in seconds.
353 * Default 0 (no expiration).
354 *
355 * @return bool[] Array of return values, grouped by key. Each value is either
356 * true on success, or false if cache key and group already exist.
357 */
358 public function add_multiple(array $data, $group = '', $expire = 0)
359 {
360 $values = [];
361
362 foreach ($data as $key => $value) {
363 $values[$key] = $this->add($key, $value, $group, $expire);
364 }
365
366 return $values;
367 }
368
369 /**
370 * Replaces the contents in the cache, if contents already exist.
371 *
372 * @see WP_Object_Cache::set()
373 *
374 * @param int|string $key what to call the contents in the cache
375 * @param mixed $data the contents to store in the cache
376 * @param string $group Optional. Where to group the cache contents. Default 'default'.
377 * @param int $expire Optional. When to expire the cache contents. Default 0 (no expiration).
378 *
379 * @return bool false if not exists, true if contents were replaced
380 */
381 public function replace($key, $data, $group = 'default', $expire = 0)
382 {
383 if (!$this->is_valid_key($key)) {
384 return false;
385 }
386
387 if (empty($group)) {
388 $group = 'default';
389 }
390
391 $cache_key = $this->dc_key($key, $group);
392
393 if (!$this->_exists($cache_key, $group)) {
394 return false;
395 }
396
397 return $this->set($key, $data, $group, (int) $expire);
398 }
399
400 /**
401 * Sets the data contents into the cache.
402 *
403 * The cache contents are grouped by the $group parameter followed by the
404 * $key. This allows for duplicate ids in unique groups. Therefore, naming of
405 * the group should be used with care and should follow normal function
406 * naming guidelines outside of core WordPress usage.
407 *
408 * @param int|string $key what to call the contents in the cache
409 * @param mixed $data the contents to store in the cache
410 * @param string $group Optional. Where to group the cache contents. Default 'default'.
411 * @param int $expire the expiration time, defaults to 0
412 *
413 * @return true always returns true
414 */
415 public function set($key, $data, $group = 'default', $expire = 0)
416 {
417 if (!$this->is_valid_key($key)) {
418 return false;
419 }
420
421 if (empty($group)) {
422 $group = 'default';
423 }
424
425 $cache_key = $this->dc_key($key, $group);
426
427 if (\is_object($data)) {
428 $data = clone $data;
429 }
430
431 $this->cache[$group][$cache_key] = $data;
432
433 // suspend new cache
434 if ($this->fs()->suspend_cache_write() && !is_file($this->get_file_path($cache_key, $group))) {
435 return true;
436 }
437
438 if ((!$this->is_non_persistent_groups($group) && !$this->is_non_persistent_keys($key) && !$this->is_non_persistent_groupkey($group, $key) && !$this->is_stalecache_ignored($key, $group)) || $this->is_filtered_groups($group, $key)) {
439 $expire = $this->maybe_expire($group, $expire, $key);
440 $this->dc_save($cache_key, $this->cache[$group][$cache_key], $group, $expire, $key);
441 }
442
443 return true;
444 }
445
446 /**
447 * Sets multiple values to the cache in one call.
448 *
449 * @param array $data array of key and value to be set
450 * @param string $group Optional. Where the cache contents are grouped. Default empty.
451 * @param int $expire Optional. When to expire the cache contents, in seconds.
452 * Default 0 (no expiration).
453 *
454 * @return bool[] Array of return values, grouped by key. Each value is always true.
455 */
456 public function set_multiple(array $data, $group = '', $expire = 0)
457 {
458 $values = [];
459
460 foreach ($data as $key => $value) {
461 $values[$key] = $this->set($key, $value, $group, $expire);
462 }
463
464 return $values;
465 }
466
467 /**
468 * Retrieves the cache contents, if it exists.
469 *
470 * The contents will be first attempted to be retrieved by searching by the
471 * key in the cache group. If the cache is hit (success) then the contents
472 * are returned.
473 *
474 * On failure, the number of cache misses will be incremented.
475 *
476 * @param int|string $key what the contents in the cache are called
477 * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
478 * @param bool $force Optional. Unused. Whether to force a refetch rather than relying on the local
479 * cache. Default false.
480 * @param bool $found Optional. Whether the key was found in the cache (passed by reference).
481 * Disambiguates a return of false, a storable value. Default null.
482 *
483 * @return mixed|false the cache contents on success, false on failure to retrieve contents
484 */
485 public function get($key, $group = 'default', $force = false, &$found = null, $doing_precache = false)
486 {
487 if (!$this->is_valid_key($key)) {
488 return false;
489 }
490
491 if (empty($group)) {
492 $group = 'default';
493 }
494
495 $cache_key = $this->dc_key($key, $group);
496
497 if ($this->_exists($cache_key, $group, $force)) {
498 if (!$doing_precache) {
499 ++$this->cache_hits;
500 }
501 $found = true;
502 if (\is_object($this->cache[$group][$cache_key])) {
503 return clone $this->cache[$group][$cache_key];
504 }
505
506 return $this->cache[$group][$cache_key];
507 }
508
509 $found = false;
510 if (!$doing_precache) {
511 ++$this->cache_misses;
512 }
513
514 return false;
515 }
516
517 /**
518 * Retrieves multiple values from the cache in one call.
519 *
520 * @param array $keys array of keys under which the cache contents are stored
521 * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
522 * @param bool $force Optional. Whether to force an update of the local cache
523 * from the persistent cache. Default false.
524 *
525 * @return array array of values organized into groups
526 */
527 public function get_multiple($keys, $group = 'default', $force = false)
528 {
529 $values = [];
530 if (!empty($keys) && \is_array($keys)) {
531 foreach ($keys as $key) {
532 $values[$key] = $this->get($key, $group, $force);
533 }
534 }
535
536 return $values;
537 }
538
539 /**
540 * Removes the contents of the cache key in the group.
541 *
542 * If the cache key does not exist in the group, then nothing will happen.
543 *
544 * @param int|string $key what the contents in the cache are called
545 * @param string $group Optional. Where the cache contents are grouped. Default 'default'.
546 * @param bool $deprecated Optional. Unused. Default false.
547 *
548 * @return bool false if the contents weren't deleted and true on success
549 */
550 public function delete($key, $group = 'default', $deprecated = false)
551 {
552 if (!$this->is_valid_key($key)) {
553 return false;
554 }
555
556 if (empty($group)) {
557 $group = 'default';
558 }
559
560 $key = $this->dc_key($key, $group);
561
562 unset($this->cache[$group][$key]);
563 unset($this->precache[$group][$key]);
564
565 return $this->dc_remove($key, $group);
566 }
567
568 /**
569 * Deletes multiple values from the cache in one call.
570 *
571 * @param array $keys array of keys to be deleted
572 * @param string $group Optional. Where the cache contents are grouped. Default empty.
573 *
574 * @return bool[] Array of return values, grouped by key. Each value is either
575 * true on success, or false if the contents were not deleted.
576 */
577 public function delete_multiple(array $keys, $group = '')
578 {
579 $values = [];
580
581 foreach ($keys as $key) {
582 $values[$key] = $this->delete($key, $group);
583 }
584
585 return $values;
586 }
587
588 /**
589 * Increments numeric cache item's value.
590 *
591 * @param int|string $key The cache key to increment
592 * @param int $offset Optional. The amount by which to increment the item's value. Default 1.
593 * @param string $group Optional. The group the key is in. Default 'default'.
594 *
595 * @return int|false the item's new value on success, false on failure
596 */
597 public function incr($key, $offset = 1, $group = 'default')
598 {
599 if (!$this->is_valid_key($key)) {
600 return false;
601 }
602
603 if (empty($group)) {
604 $group = 'default';
605 }
606
607 $cache_key = $this->dc_key($key, $group);
608
609 if (!$this->_exists($cache_key, $group)) {
610 return false;
611 }
612
613 if (!is_numeric($this->cache[$group][$cache_key])) {
614 $this->cache[$group][$cache_key] = 0;
615 }
616
617 $offset = (int) $offset;
618
619 $this->cache[$group][$cache_key] += $offset;
620
621 if ($this->cache[$group][$cache_key] < 0) {
622 $this->cache[$group][$cache_key] = 0;
623 }
624
625 $this->dc_update($cache_key, $this->cache[$group][$cache_key], $group);
626
627 return $this->cache[$group][$cache_key];
628 }
629
630 /**
631 * Decrements numeric cache item's value.
632 *
633 * @param int|string $key the cache key to decrement
634 * @param int $offset Optional. The amount by which to decrement the item's value. Default 1.
635 * @param string $group Optional. The group the key is in. Default 'default'.
636 *
637 * @return int|false the item's new value on success, false on failure
638 */
639 public function decr($key, $offset = 1, $group = 'default')
640 {
641 if (!$this->is_valid_key($key)) {
642 return false;
643 }
644
645 if (empty($group)) {
646 $group = 'default';
647 }
648
649 $cache_key = $this->dc_key($key, $group);
650
651 if (!$this->_exists($cache_key, $group)) {
652 return false;
653 }
654
655 if (!is_numeric($this->cache[$group][$cache_key])) {
656 $this->cache[$group][$cache_key] = 0;
657 }
658
659 $offset = (int) $offset;
660
661 $this->cache[$group][$cache_key] -= $offset;
662
663 if ($this->cache[$group][$cache_key] < 0) {
664 $this->cache[$group][$cache_key] = 0;
665 }
666
667 $this->dc_update($cache_key, $this->cache[$group][$cache_key], $group);
668
669 return $this->cache[$group][$cache_key];
670 }
671
672 /**
673 * Clears the object cache of all data.
674 *
675 * @param bool $is_runtime Optional. Only removes cache items from the in-memory runtime cache.
676 *
677 * @return bool true on success, false on failure
678 */
679 public function flush($is_runtime = false)
680 {
681 $this->cache = [];
682 $this->precache = [];
683 $this->precache_loaded = [];
684
685 return $is_runtime ? true : $this->dc_flush();
686 }
687
688 /**
689 * Sets the list of global cache groups.
690 *
691 * @param array $groups list of groups that are global
692 */
693 public function add_global_groups($groups)
694 {
695 $groups = (array) $groups;
696
697 $groups = array_fill_keys($groups, true);
698 $this->global_groups = array_merge($this->global_groups, $groups);
699 }
700
701 /**
702 * Switches the internal blog ID.
703 *
704 * This changes the blog ID used to create keys in blog specific groups.
705 *
706 * @param int $blog_id blog ID
707 */
708 public function switch_to_blog($blog_id)
709 {
710 $blog_id = (int) $blog_id;
711 $this->blog_prefix = $this->multisite ? $blog_id.':' : '';
712 }
713
714 /**
715 * Echoes the stats of the caching.
716 *
717 * Gives the cache hits, and cache misses. Also prints every cached group,
718 * key and the data.
719 */
720 public function stats()
721 {
722 $ret = '';
723 $ret .= '<p>';
724 $ret .= "<strong>Cache Hits:</strong> {$this->cache_hits}<br />";
725 $ret .= "<strong>Persistent Cache Hits:</strong> {$this->persistent_cache_hits}<br />";
726 $ret .= "<strong>Cache Misses:</strong> {$this->cache_misses}<br />";
727 $ret .= '</p>';
728 $ret .= '<ul>';
729 $total = 0;
730
731 foreach ($this->cache as $group => $cache) {
732 $ret .= '<li><strong>Group:</strong> '.esc_html($group).' - ( '.number_format(\strlen(serialize($cache)) / KB_IN_BYTES, 2).'K )</li>';
733 $total += \strlen(serialize($cache));
734 }
735
736 $ret .= '</ul>';
737 $ret .= '<p>total: '.number_format($total / KB_IN_BYTES).'</p>';
738 echo $ret;
739 }
740
741 /**
742 * Sets the list of non persistent groups.
743 *
744 * @param array $groups list of groups that are to be ignored
745 */
746 public function add_non_persistent_groups($groups)
747 {
748 $groups = (array) $groups;
749
750 $groups = array_fill_keys($groups, true);
751 $this->non_persistent_groups = array_merge($this->non_persistent_groups, $groups);
752 }
753
754 /**
755 * Check if group in non persistent groups.
756 *
757 * @param bool $group cache group
758 */
759 protected function is_non_persistent_groups($group)
760 {
761 return !empty($this->non_persistent_groups) && \array_key_exists($group, $this->non_persistent_groups);
762 }
763
764 /**
765 * Sets the list of non persistent keys.
766 *
767 * @param array $keys list of keys that are to be ignored
768 */
769 public function add_non_persistent_keys($keys)
770 {
771 $keys = (array) $keys;
772
773 $keys = array_fill_keys($keys, true);
774 $this->non_persistent_keys = array_merge($this->non_persistent_keys, $keys);
775 }
776
777 /**
778 * Check if key in non persistent keys.
779 *
780 * @param bool $key cache key
781 */
782 protected function is_non_persistent_keys($key)
783 {
784 return !empty($this->non_persistent_keys) && \array_key_exists($key, $this->non_persistent_keys);
785 }
786
787 /**
788 * Check if key in non persistent index.
789 *
790 * @param bool $group cache group
791 * @param bool $key cache key
792 */
793 protected function is_non_persistent_groupkey($group, $key)
794 {
795 if (!empty($this->non_persistent_groupkey) && !empty($this->non_persistent_groupkey[$group])) {
796 $bypass_data = $this->non_persistent_groupkey[$group];
797
798 if (\is_array($bypass_data)) {
799 return false !== array_search($key, $bypass_data);
800 }
801
802 if ($key === $bypass_data) {
803 return true;
804 }
805 }
806
807 return false;
808 }
809
810 /**
811 * Bypass preache.
812 *
813 * @param bool $group cache group
814 * @param bool $key cache key
815 */
816 private function is_bypass_precache($group, $key)
817 {
818 if ($this->fs()->is_docketcachegroup($group, $key) || $this->fs()->is_transient($group) || $this->has_stalecache($key, $group)) {
819 return true;
820 }
821
822 if ('gbmedia-cpttables' === $group || 'doing_cron' === $key || preg_match('@^(\d+|[0-9a-f]{32})$@', $key)) {
823 return true;
824 }
825
826 if (!empty($this->bypass_precache) && !empty($this->bypass_precache[$group])) {
827 $bypass_data = $this->bypass_precache[$group];
828
829 if (\is_array($bypass_data)) {
830 return false !== array_search($key, $bypass_data);
831 }
832
833 if ($key === $bypass_data) {
834 return true;
835 }
836 }
837
838 return false;
839 }
840
841 /**
842 * is_valid_key.
843 */
844 private function is_valid_key($key)
845 {
846 if (\is_int($key)) {
847 return true;
848 }
849
850 if (\is_string($key) && '' !== trim($key)) {
851 return true;
852 }
853
854 if (!\function_exists('__') && \function_exists('wp_load_translations_early')) {
855 wp_load_translations_early();
856 }
857
858 return false;
859 }
860
861 /**
862 * is_user_logged_in.
863 */
864 private function is_user_logged_in()
865 {
866 return \function_exists('is_user_logged_in') && is_user_logged_in();
867 }
868
869 /**
870 * is_filtered_groups.
871 */
872 protected function is_filtered_groups($group, $key)
873 {
874 if (!\is_array($this->filtered_groups) || !isset($this->filtered_groups[$group])) {
875 return false;
876 }
877
878 if (false === $this->filtered_groups[$group]) {
879 $this->filtered_groups[$group][] = $key;
880 $this->filtered_groups[$group] = array_unique($this->filtered_groups[$group]);
881
882 return true;
883 }
884
885 if (\in_array($key, $this->filtered_groups[$group])) {
886 return true;
887 }
888
889 return false;
890 }
891
892 /**
893 * flush_filtered_groups.
894 */
895 private function flush_filtered_groups($hook, $args)
896 {
897 if (!\is_array($this->filtered_groups)) {
898 return false;
899 }
900
901 foreach ($this->filtered_groups as $group => $keys) {
902 if (empty($keys) || !\is_array($keys)) {
903 continue;
904 }
905
906 $keys = array_unique($keys);
907 foreach ($keys as $key) {
908 $this->delete($key, $group);
909 $this->dc_log('flush', '000000000000-'.$this->item_hash(__FUNCTION__), $group.':'.$key);
910 }
911 }
912
913 return true;
914 }
915
916 /**
917 * maybe_expire.
918 */
919 private function maybe_expire($group, $expire = 0, $key = '')
920 {
921 if (empty($expire)) {
922 $expire = 0;
923 }
924
925 $expire = $this->fs()->sanitize_timestamp($expire);
926 $maxttl = $this->cache_maxttl;
927
928 if (0 === $expire && $maxttl < 2419200) {
929 if ($this->fs()->is_transient($group)) {
930 if ('site-transient' === $group && \in_array($key, ['update_plugins', 'update_themes', 'update_core', '_woocommerce_helper_updates'])) {
931 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
932 } else {
933 // $expire = $maxttl < 604800 ? 604800 : $maxttl; // 7d
934 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
935 }
936 } elseif ($this->fs()->is_wp_options($group)) {
937 $expire = $maxttl < 1209600 ? 1209600 : $maxttl; // 14d
938 } elseif (\in_array($group, ['terms', 'posts', 'post_meta', 'comments', 'comment_feed', 'sites', 'networks'])) {
939 $expire = $maxttl < 1209600 ? 1209600 : $maxttl; // 14d
940 }
941
942 // wp stale cache
943 // group-queries: wp >= 6.3
944 elseif ($this->fs()->is_wp_cache_group_queries($group)) {
945 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
946 }
947
948 // wp stale cache
949 // prefix:md5hash:microtime
950 // wp_query|get_terms|get_comments|comment_feed|get_sites|get_network_ids|get_page_by_path|other?
951 elseif (false !== strpos($key, ':') && @preg_match('@^([a-zA-Z0-9\._-]+):([0-9a-f]{32}):([0-9\. ]+)$@', $key)) {
952 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
953 }
954
955 // wp stale cache
956 // cache timestamp
957 elseif ('last_changed' === $key) {
958 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
959 }
960
961 // advcpost
962 // docketcache-post-(found|media|timestamp)
963 elseif (false !== strpos($group, 'docketcache-post-')) {
964 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
965 }
966
967 // advcpost
968 // docketcache-post-media
969 elseif ('docketcache-post-media' === $group) {
970 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
971 }
972
973 // advcpost
974 // cache timestamp
975 elseif ('docketcache-post' === $group && 'cache_incr' === $key) {
976 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
977 }
978
979 // woocommerce stale cache
980 // cache prefix
981 elseif (false !== strpos($key, '_cache_prefix') && @preg_match('@^wc_(.*?)_cache_prefix$@', $key)) {
982 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
983 }
984
985 // woocommerce stale cache
986 // wc_cache_0.72953700 1651592702
987 elseif (false !== strpos($key, 'wc_cache_') && @preg_match('@^wc_cache_([0-9\. ]+)_@', $key)) {
988 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
989 } elseif (false !== strpos($group, 'wc_cache_') && @preg_match('@^wc_cache_([0-9\. ]+)_@', $group)) {
990 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
991 }
992
993 // common cache
994 elseif (preg_match('@[0-9a-f]{32}@', $key)) {
995 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
996 }
997
998 // else
999 else {
1000 $expire = $maxttl;
1001 }
1002 }
1003
1004 return $expire;
1005 }
1006
1007 /**
1008 * get_item_hash.
1009 */
1010 private function get_item_hash($file)
1011 {
1012 return basename($file, '.php');
1013 }
1014
1015 /**
1016 * item_hash.
1017 */
1018 private function item_hash($str, $length = 12)
1019 {
1020 if (!$this->is_valid_key($str)) {
1021 $str = serialize($str);
1022 }
1023
1024 if (empty($length)) {
1025 return md5($str);
1026 }
1027
1028 return substr(md5($str), 0, $length);
1029 }
1030
1031 /**
1032 * get_file_path.
1033 */
1034 private function get_file_path($key, $group)
1035 {
1036 $hash_group = $this->item_hash($group);
1037 $hash_key = $this->item_hash($key);
1038
1039 $index = $hash_group.'-'.$hash_key;
1040
1041 if ($this->cf()->is_dcfalse('CHUNKCACHEDIR', true)) {
1042 return $this->cache_path.$index.'.php';
1043 }
1044
1045 $chunk_path = $this->fs()->get_chunk_path($hash_group, $hash_key);
1046
1047 return $this->cache_path.$chunk_path.$index.'.php';
1048 }
1049
1050 /**
1051 * skip_stats.
1052 */
1053 private function skip_stats($group, $key = '')
1054 {
1055 if ($this->is_non_persistent_groups($group) || (!empty($keys) && $this->is_non_persistent_keys($key))) {
1056 return true;
1057 }
1058
1059 return $this->cf()->is_dcfalse('LOG_ALL') && $this->fs()->is_docketcachegroup($group);
1060 }
1061
1062 /**
1063 * has_stalecache.
1064 */
1065 private function has_stalecache($key, $group = '')
1066 {
1067 if ($this->fs()->is_wp_cache_group_queries($group)) {
1068 return true;
1069 }
1070
1071 if ('wc_' === substr($key, 0, 3) && '_cache_prefix' === substr($key, -13)) {
1072 return true;
1073 }
1074
1075 if ('wc_cache_' === substr($key, 0, 9) || 'wc_cache_' === substr($group, 0, 9)) {
1076 return true;
1077 }
1078
1079 if (false !== strpos($key, ':') && @preg_match('@^([a-zA-Z0-9\._-]+):([0-9a-f]{32}):([0-9\. ]+)$@', $key)) {
1080 return true;
1081 }
1082
1083 if (false !== strpos($group, 'docketcache-post-') && preg_match('@^docketcache-post-\d+$@', $group)) {
1084 return true;
1085 }
1086
1087 return false;
1088 }
1089
1090 /**
1091 * is_stalecache_ignored.
1092 */
1093 private function is_stalecache_ignored($key, $group = '')
1094 {
1095 if ($this->ignore_stalecache) {
1096 return $this->has_stalecache($key, $group);
1097 }
1098
1099 return false;
1100 }
1101
1102 /**
1103 * fs.
1104 */
1105 private function fs()
1106 {
1107 static $inst;
1108 if (!\is_object($inst)) {
1109 $inst = new Nawawi\DocketCache\Filesystem();
1110 }
1111
1112 return $inst;
1113 }
1114
1115 /**
1116 * cf.
1117 */
1118 private function cf()
1119 {
1120 static $inst;
1121 if (!\is_object($inst)) {
1122 $inst = new Nawawi\DocketCache\Constans();
1123 }
1124
1125 return $inst;
1126 }
1127
1128 /**
1129 * transientdb.
1130 */
1131 private function transient_db()
1132 {
1133 static $inst;
1134 if (!\is_object($inst)) {
1135 $inst = new Nawawi\DocketCache\TransientDb();
1136 }
1137
1138 return $inst;
1139 }
1140
1141 /**
1142 * dc_key.
1143 */
1144 private function dc_key($key, $group)
1145 {
1146 if ($this->multisite && !\array_key_exists($group, $this->global_groups)) {
1147 $key = $this->blog_prefix.$key;
1148 }
1149
1150 return $key;
1151 }
1152
1153 /**
1154 * dc_log.
1155 */
1156 private function dc_log($tag, $id, $data)
1157 {
1158 if ($this->cf()->is_dcfalse('LOG')) {
1159 return false;
1160 }
1161
1162 if ($this->skip_stats($data)) {
1163 return false;
1164 }
1165
1166 if ($this->cf()->is_dcfalse('LOG_ALL')) {
1167 if (!\in_array($tag, ['hit', 'miss'])) {
1168 return false;
1169 }
1170
1171 if (false !== strpos($data, 'user') && @preg_match('@^user(s|email|logins|_meta)\:.*@', $data)) {
1172 return false;
1173 }
1174 }
1175
1176 $caller = '';
1177 if (!empty($_SERVER['REQUEST_URI'])) {
1178 $caller = $_SERVER['REQUEST_URI'];
1179 } elseif ($this->cf()->is_dctrue('WPCLI')) {
1180 $caller = 'wp-cli';
1181 }
1182
1183 if (false !== strpos($caller, '?page=docket-cache')) {
1184 return false;
1185 }
1186
1187 static $duplicate = [];
1188
1189 $buff = $this->item_hash($tag.$id.$data.$caller);
1190 if (isset($duplicate[$buff])) {
1191 return false;
1192 }
1193
1194 $duplicate[$buff] = 1;
1195
1196 return $this->fs()->log($tag, $id, $data, $caller);
1197 }
1198
1199 /**
1200 * dc_flush.
1201 */
1202 private function dc_flush()
1203 {
1204 $dir = $this->cache_path;
1205 $is_timeout = false;
1206 $cnt = $this->fs()->cachedir_flush($dir, false, $is_timeout);
1207 $logkey = '000000000000-'.$this->item_hash(__FUNCTION__);
1208
1209 if ($is_timeout) {
1210 $this->dc_log('err', $logkey, 'Process aborted. Reached maximum execution time. Total cache flushed: '.$cnt);
1211
1212 return false;
1213 }
1214
1215 if (false === $cnt) {
1216 $this->dc_log('err', $logkey, 'Cache could not be flushed');
1217
1218 return false;
1219 }
1220
1221 if ($cnt > 0) {
1222 $this->dc_log('flush', $logkey, 'Total cache flushed: '.$cnt);
1223 }
1224
1225 return true;
1226 }
1227
1228 /**
1229 * dc_remove.
1230 */
1231 private function dc_remove($key, $group)
1232 {
1233 $result = true;
1234 if ($this->use_transientdb && $this->fs()->is_transient($group)) {
1235 $result = $this->transient_db()->delete($key, $group);
1236 }
1237
1238 $file = $this->get_file_path($key, $group);
1239 $this->fs()->unlink($file, false);
1240 $this->dc_log('del', $this->get_item_hash($file), $group.':'.$key);
1241
1242 return $result;
1243 }
1244
1245 /**
1246 * dc_remove_group.
1247 */
1248 public function dc_remove_group($group)
1249 {
1250 $total = 0;
1251 if (!$this->fs()->is_docketcachedir($this->cache_path)) {
1252 return $total;
1253 }
1254
1255 $pattern = '@^'.$this->item_hash($group).'\-([a-z0-9]{12})\.php$@';
1256
1257 if (\is_array($group) && !empty($group)) {
1258 $groups = array_map(function ($name) {
1259 return $this->item_hash($name);
1260 }, $group);
1261
1262 $pattern = '@^('.implode('|', $groups).")\-([a-z0-9]{12})\.php$@";
1263 $group = implode(',', $group);
1264 }
1265
1266 $this->fs()->suspend_cache_write(true);
1267 $max_execution_time = $this->fs()->get_max_execution_time(180);
1268
1269 $slowdown = 0;
1270 foreach ($this->fs()->scanfiles($this->cache_path, null, $pattern) as $object) {
1271 if ($object->isFile()) {
1272 $fx = $object->getPathName();
1273 $fn = $object->getFileName();
1274 $this->fs()->unlink($fx, true);
1275 ++$total;
1276
1277 array_map(function ($grp) use ($fx) {
1278 unset($this->cache[$grp]);
1279 $this->dc_log('flush', $this->get_item_hash($fx), $grp.':*');
1280 }, explode(',', $group));
1281 }
1282
1283 if ($slowdown > 10) {
1284 $slowdown = 0;
1285 usleep(5000);
1286 }
1287
1288 ++$slowdown;
1289
1290 if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) {
1291 break;
1292 }
1293 }
1294
1295 $this->fs()->suspend_cache_write(false);
1296
1297 if ($this->use_transientdb && $this->fs()->is_transient(explode(',', $group)) && \function_exists('nwdcx_cleanuptransient')) {
1298 $total += nwdcx_cleanuptransient();
1299 }
1300
1301 return $total;
1302 }
1303
1304 /**
1305 * dc_remove_group_match.
1306 */
1307 public function dc_remove_group_match($group)
1308 {
1309 $total = 0;
1310 if (!$this->fs()->is_docketcachedir($this->cache_path)) {
1311 return $total;
1312 }
1313
1314 $this->fs()->suspend_cache_write(true);
1315 $max_execution_time = $this->fs()->get_max_execution_time(180);
1316
1317 $slowdown = 0;
1318 $pattern = '@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@';
1319 foreach ($this->fs()->scanfiles($this->cache_path, null, $pattern) as $object) {
1320 if ($object->isFile()) {
1321 $fx = $object->getPathName();
1322 $data = $this->fs()->cache_get($fx);
1323 if (!empty($data) && !empty($data['group'])) {
1324 $match = $data['group'];
1325
1326 if (\is_array($group) && !empty($group)) {
1327 foreach ($group as $grp) {
1328 if ($grp === substr($match, 0, \strlen($grp))) {
1329 $this->fs()->unlink($fx, true);
1330 $this->dc_log('flush', $this->get_item_hash($fx), $match.':*');
1331 unset($this->cache[$match]);
1332
1333 ++$total;
1334 }
1335 }
1336 } else {
1337 if ($group === substr($match, 0, \strlen($group))) {
1338 $this->fs()->unlink($fx, true);
1339 $this->dc_log('flush', $this->get_item_hash($fx), $match.':*');
1340 unset($this->cache[$match]);
1341
1342 ++$total;
1343 }
1344 }
1345 }
1346 unset($data);
1347 }
1348
1349 if ($slowdown > 10) {
1350 $slowdown = 0;
1351 usleep(5000);
1352 }
1353
1354 ++$slowdown;
1355
1356 if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) {
1357 break;
1358 }
1359 }
1360
1361 $this->fs()->suspend_cache_write(false);
1362
1363 return $total;
1364 }
1365
1366 /**
1367 * dc_get.
1368 */
1369 private function dc_get($key, $group, $is_raw = false, &$codestub_false = false)
1370 {
1371 $file = $this->get_file_path($key, $group);
1372 $logkey = $this->get_item_hash($file);
1373
1374 if ($this->use_transientdb && !\in_array($key, $this->bypass_transientdb)) {
1375 if ($this->fs()->is_transient($group)) {
1376 return $this->transient_db()->get($key, $group);
1377 }
1378
1379 if ($this->fs()->is_wp_options($group) && $this->transient_db()->match_key($key)) {
1380 return false;
1381 }
1382 }
1383
1384 $data = $this->fs()->cache_get($file);
1385 if (false === $data) {
1386 if (!$this->skip_stats($group) && !$this->fs()->is_transient($group)) {
1387 $this->dc_log('miss', $logkey, $group.':'.$key);
1388 }
1389
1390 /*if (!$this->skip_stats($group) && !$this->fs()->is_transient($group)) {
1391 ++$this->cache_misses;
1392
1393 $this->dc_log('miss', $logkey, $group.':'.$key);
1394 }*/
1395
1396 return false;
1397 }
1398
1399 $is_timeout = false;
1400 if (!empty($data['timeout']) && $this->fs()->valid_timestamp($data['timeout']) && time() >= $data['timeout']) {
1401 $this->dc_log('exp', $logkey, $group.':'.$key);
1402 $this->fs()->unlink($file, false);
1403 $is_timeout = true;
1404 }
1405
1406 // incase gc not run
1407 if (!$is_timeout && !empty($data['timestamp']) && $this->fs()->valid_timestamp($data['timestamp'])) {
1408 $maxttl = time() - $this->cache_maxttl;
1409 if ($maxttl > $data['timestamp']) {
1410 $this->dc_log('exp', $logkey, $group.':'.$key);
1411 $this->fs()->unlink($file, true); // true = delete it instead of truncate
1412 }
1413 }
1414
1415 if (!$this->skip_stats($group)) {
1416 ++$this->persistent_cache_hits;
1417 $this->dc_log('hit', $logkey, $group.':'.$key);
1418 }
1419
1420 // If the transient does not exist, does not have a value, or has expired, then the return value will be false.
1421 if (!empty($data['group']) && $this->fs()->is_transient($data['group']) && ('' === $data['data'] || $is_timeout)) {
1422 $data['data'] = false;
1423 }
1424
1425 // nwdcx_unserialize failed to convert serialize object.
1426 // we unserialize it here to get the object.
1427 if (!empty($data['data'])) {
1428 // *_serialize set at dc_save, to load it faster
1429 if (false !== strpos($data['type'], '_serialize')) {
1430 $data['data'] = unserialize($data['data']);
1431 } elseif ('string' === $data['type'] && \function_exists('maybe_unserialize')) {
1432 // old cache data
1433 $data['data'] = maybe_unserialize($data['data']);
1434 }
1435 }
1436 clearstatcache();
1437
1438 // skip precache.
1439 if (isset($GLOBALS['DOCKET_CACHE_CODESTUB_FALSE']) && isset($GLOBALS['DOCKET_CACHE_CODESTUB_FALSE'][$file])) {
1440 $codestub_false = true;
1441 }
1442
1443 return $is_raw ? $data : $data['data'];
1444 }
1445
1446 /**
1447 * dc_code.
1448 */
1449 private function dc_code($file, $arr)
1450 {
1451 $logkey = $this->get_item_hash($file);
1452 $logpref = __FUNCTION__.'():';
1453
1454 $data = $this->fs()->export_var($arr, $error);
1455
1456 if (false === $data) {
1457 $this->dc_log('err', $logkey, $logpref.' Failed to export var -> '.$error);
1458
1459 return false;
1460 }
1461
1462 $code = $this->fs()->code_stub($data);
1463 $stat = $this->fs()->dump($file, $code, false); // 3rd param = validate
1464
1465 if (false === $stat) {
1466 return false;
1467 }
1468
1469 if (-1 === $stat) {
1470 $this->dc_log('err', $logkey, $logpref.' Failed to write');
1471
1472 return false;
1473 }
1474
1475 // remove lock
1476 $this->fs()->validate_fatal_error_file($file);
1477
1478 return $stat;
1479 }
1480
1481 /**
1482 * dc_save.
1483 */
1484 private function dc_save($cache_key, $data, $group = 'default', $expire = 0, $key = '')
1485 {
1486 if (wp_suspend_cache_addition()) {
1487 return false;
1488 }
1489
1490 $logkey = $this->item_hash($group).'-'.$this->item_hash($cache_key);
1491 $logpref = __FUNCTION__.'():';
1492
1493 if ($this->use_transientdb && !\in_array($cache_key, $this->bypass_transientdb)) {
1494 if ($this->fs()->is_transient($group)) {
1495 if (!$expire) {
1496 $expire = 86400;
1497 }
1498
1499 return $this->transient_db()->set($cache_key, $data, $group, time() + $expire);
1500 }
1501
1502 if ($this->fs()->is_wp_options($group) && $this->transient_db()->match_key($cache_key)) {
1503 return false;
1504 }
1505
1506 if (\in_array($cache_key, ['notoptions', 'alloptions']) && \is_array($data) && !empty($data)) {
1507 foreach ($data as $m => $n) {
1508 if ($this->transient_db()->match_key($m)) {
1509 unset($data[$m]);
1510 }
1511 }
1512 }
1513 }
1514
1515 if (!$this->fs()->mkdir_p($this->cache_path)) {
1516 return false;
1517 }
1518
1519 @$this->fs()->placeholder($this->cache_path);
1520
1521 $file = $this->get_file_path($cache_key, $group);
1522
1523 // Skip save to disk, return true.
1524 if (('' === $data || (\is_array($data) && empty($data))) && ($this->fs()->is_transient($group) || $this->ignore_emptycache)) {
1525 nwdcx_debuglog(__FUNCTION__.': '.$logkey.': Process aborted. No data availale.');
1526 $this->fs()->unlink($file, false);
1527 unset($this->precache[$group][$cache_key]);
1528
1529 return true;
1530 }
1531
1532 // Chunk dir.
1533 if ($this->cf()->is_dctrue('CHUNKCACHEDIR', true) && !$this->fs()->mkdir_p(\dirname($file))) {
1534 return false;
1535 }
1536
1537 // If $expire is larger than 0, convert it to timestamp.
1538 $timeout = ($expire > 0 ? time() + $expire : 0);
1539
1540 $type = \gettype($data);
1541 if ('unknown type' === $type) {
1542 return false;
1543 }
1544
1545 if ('NULL' === $type && null === $data) {
1546 $data = '';
1547 }
1548
1549 if (!empty($data)) {
1550 if (!\in_array($type, ['boolean', 'integer', 'double', 'NULL'])) {
1551 // Abort if object too large.
1552 $len = 0;
1553 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
1554 if (\function_exists('maybe_serialize')) {
1555 $len = \strlen(@maybe_serialize($data));
1556 } else {
1557 $len = \strlen(@serialize($data));
1558 }
1559 nwdcx_suppresserrors($nwdcx_suppresserrors);
1560
1561 if ($len >= $this->cache_maxsize) {
1562 $this->dc_log('err', $logkey, $group.':'.$cache_key.' '.$logpref.' The size of object has breached '.$len.' of '.$this->cache_maxsize.' bytes.');
1563
1564 nwdcx_debuglog(__FUNCTION__.': '.$logkey.': Process aborted. The size of object has breached '.$len.' of '.$this->cache_maxsize.' bytes.');
1565 $this->fs()->unlink($file, false);
1566 unset($this->precache[$group][$cache_key]);
1567
1568 return false;
1569 }
1570 }
1571
1572 // Unserialize content first.
1573 if ('string' === $type) {
1574 $data = nwdcx_unserialize($data);
1575 } elseif ('array' === $type) {
1576 $data_r = nwdcx_arraymap('nwdcx_unserialize', $data);
1577
1578 if (!empty($data_r)) {
1579 $data = $data_r;
1580 }
1581 unset($data_r);
1582 }
1583 }
1584
1585 $meta = [];
1586 $meta['timestamp'] = time();
1587
1588 if ($this->multisite) {
1589 $meta['network_id'] = $this->network_id;
1590 }
1591
1592 $final_type = \gettype($data);
1593 if ('string' === $final_type && nwdcx_serialized($data)) {
1594 $final_type = 'string_serialize';
1595 } elseif ('array' === $final_type) {
1596 // The Data needs to be serialized.
1597 // The cache always returns false if the object has a class instance
1598 // other than stdClass since the class has not been loaded yet.
1599 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
1600 $export_data = @var_export($data, 1);
1601 if (!empty($export_data)) {
1602 // 1st priority. If has the "Request" instance.
1603 if (false !== strpos($export_data, 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) {
1604 $data = @serialize($data);
1605 if (nwdcx_serialized($data)) {
1606 $final_type = 'array_serialize';
1607 }
1608 }
1609
1610 // 2nd priority. If Transients and has class instance.
1611 if ('array' === $final_type && $this->fs()->is_transient($group) && false !== strpos($export_data, '::__set_state')) {
1612 $data = @serialize($data);
1613 if (nwdcx_serialized($data)) {
1614 $final_type = 'array_serialize';
1615 }
1616 }
1617 }
1618 unset($export_data);
1619 nwdcx_suppresserrors($nwdcx_suppresserrors);
1620 // Pass to code_stub.
1621 }
1622
1623 $meta['site_id'] = get_current_blog_id();
1624 $meta['group'] = $group;
1625 $meta['key'] = $cache_key;
1626 $meta['type'] = $final_type;
1627
1628 // If 0 let gc handle it by comparing file mtime
1629 // and maxttl constants.
1630 $meta['timeout'] = $timeout;
1631
1632 // Before code_stub.
1633 $meta['data'] = $data;
1634
1635 // Only count new file.
1636 clearstatcache(true, $file);
1637 $has_cache_file = is_file($file);
1638 if (true === $this->dc_code($file, $meta)) {
1639 nwdcx_debuglog(__FUNCTION__.': '.$this->get_item_hash($file).': Storing to disk.');
1640
1641 if (!$has_cache_file && $this->maxfile_livecheck) {
1642 $count_file = (int) $this->get('count_file', 'docketcache-gc');
1643 if ($this->maxfile > $count_file) {
1644 ++$count_file;
1645 $this->set('count_file', $count_file, 'docketcache-gc', 86400); // 1d
1646 }
1647 }
1648
1649 return true;
1650 }
1651
1652 return false;
1653 }
1654
1655 /**
1656 * dc_update.
1657 */
1658 private function dc_update($cache_key, $data, $group)
1659 {
1660 $meta = $this->dc_get($cache_key, $group, true);
1661 if (false === $meta || !\is_array($meta) || !isset($meta['data'])) {
1662 return false;
1663 }
1664
1665 $file = $this->get_file_path($cache_key, $group);
1666 $meta['data'] = $data;
1667
1668 if (true === $this->dc_code($file, $meta)) {
1669 return true;
1670 }
1671
1672 return false;
1673 }
1674
1675 /**
1676 * dc_precache_load.
1677 */
1678 private function dc_precache_load($hash)
1679 {
1680 static $is_done = false;
1681
1682 if ($is_done) {
1683 return;
1684 }
1685
1686 $cached = [];
1687 $group = 'docketcache-precache';
1688 $keys = $this->get($hash, $group);
1689
1690 if (empty($keys) || !\is_array($keys)) {
1691 return;
1692 }
1693
1694 nwdcx_debuglog(__FUNCTION__.': Process started.');
1695
1696 $this->precache_loaded[$hash] = $keys;
1697
1698 $slowdown = 0;
1699 $cnt_max = 0;
1700
1701 foreach ($keys as $cache_group => $arr) {
1702 foreach ($arr as $cache_key) {
1703 if ($cnt_max >= $this->precache_maxkey) {
1704 break 2;
1705 }
1706
1707 $force = false;
1708 $found = false;
1709 $doing_precache = true;
1710 if (!isset($cached[$cache_key.$cache_group]) && false !== $this->get($cache_key, $cache_group, $force, $found, $doing_precache)) {
1711 $cached[$cache_key.$cache_group] = 1;
1712 }
1713
1714 ++$cnt_max;
1715
1716 if ($slowdown > 10) {
1717 $slowdown = 0;
1718 usleep(1000);
1719 }
1720
1721 ++$slowdown;
1722
1723 if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) {
1724 break 2;
1725 }
1726 }
1727 }
1728
1729 nwdcx_debuglog(__FUNCTION__.': Process ended. Loaded '.\count($cached));
1730
1731 unset($keys, $cached);
1732 $is_done = true;
1733 }
1734
1735 /**
1736 * dc_precache_set.
1737 */
1738 private function dc_precache_set($hash)
1739 {
1740 $group = 'docketcache-precache';
1741 $file = $this->get_file_path($hash, $group);
1742
1743 if (empty($this->precache) || !\is_array($this->precache)) {
1744 $this->fs()->unlink($file, true);
1745
1746 return;
1747 }
1748
1749 $file_hash = $this->get_item_hash($file);
1750 $data = [];
1751 $slowdown = 0;
1752 $cnt_max = 0;
1753
1754 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process started.');
1755
1756 // docketcache-precache-gc
1757 $count_file = (int) $this->get('count_file', $group.'-gc');
1758 if ($count_file >= $this->precache_maxfile) {
1759 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum file limit ('.$count_file.'/'.$this->precache_maxfile.')');
1760
1761 return;
1762 }
1763
1764 foreach ($this->precache as $cache_group => $cache_keys) {
1765 if ($cnt_max >= $this->precache_maxkey) {
1766 break;
1767 }
1768
1769 if ($cache_group !== $group) {
1770 $data[$cache_group] = array_keys($cache_keys);
1771 }
1772
1773 ++$cnt_max;
1774
1775 if ($slowdown > 10) {
1776 $slowdown = 0;
1777 usleep(100);
1778 }
1779
1780 ++$slowdown;
1781
1782 if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) {
1783 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum execution time.');
1784 break;
1785 }
1786 }
1787
1788 if (!empty($data)) {
1789 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Total items = '.\count($data, 1));
1790
1791 if (!empty($this->precache_loaded) && \function_exists('nwdcx_arraysimilar') && nwdcx_arraysimilar($this->precache_loaded[$hash], $data)) {
1792 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. No data changes.');
1793
1794 return;
1795 }
1796
1797 // docketcache-precache-gc
1798 if ($this->precache_maxfile > $count_file) {
1799 clearstatcache(true, $file);
1800
1801 // only count new file.
1802 $has_precache_file = is_file($file);
1803
1804 if ($this->set($hash, $data, $group, 86400)) { // 1d
1805 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. Storing cache to disk.');
1806
1807 if (!$has_precache_file) {
1808 ++$count_file;
1809 $this->set('count_file', $count_file, $group.'-gc', 86400); // 1d
1810 }
1811
1812 return;
1813 }
1814 }
1815 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum file limit ('.$count_file.'/'.$this->precache_maxfile.')');
1816
1817 return;
1818 }
1819
1820 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. No data available.');
1821 unset($data, $hash);
1822 }
1823
1824 /**
1825 * dc_precache.
1826 */
1827 private function dc_precache_init()
1828 {
1829 if (!empty($_POST) || empty($_SERVER['REQUEST_URI']) || $this->cf()->is_dctrue('WPCLI')) {
1830 return;
1831 }
1832
1833 $req_uri = $_SERVER['REQUEST_URI'];
1834 $dostrip = !empty($_SERVER['QUERY_STRING']);
1835
1836 $intersect_key = [
1837 'docketcache_ping' => 1,
1838 'doing_wp_cron' => 1,
1839 'wc-ajax' => 1,
1840 '_fs_blog_admin' => 1,
1841 'action' => 1,
1842 'message' => 1,
1843 ];
1844
1845 if ($dostrip && !empty($_GET) && array_intersect_key($intersect_key, $_GET)) {
1846 return;
1847 }
1848
1849 if (false !== strpos($req_uri, '/wp-json/') || false !== strpos($req_uri, '/wp-admin/admin-ajax.php') || false !== strpos($req_uri, '/xmlrpc.php') || false !== strpos($req_uri, '/wp-cron.php') || false !== strpos($req_uri, '/robots.txt') || false !== strpos($req_uri, '/favicon.ico')) {
1850 return;
1851 }
1852
1853 $req_host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
1854 if ('localhost' !== $req_host) {
1855 $req_host = nwdcx_fixhost($req_host);
1856 }
1857
1858 if ($dostrip && $this->is_user_logged_in() && false !== strpos($req_uri, '.php?') && false !== strpos($req_uri, '/wp-admin/') && @preg_match('@/wp-admin/(network/)?.*?\.php\?.*?@', $req_uri)) {
1859 $dostrip = false;
1860 }
1861
1862 // without pretty permalink
1863 if ($dostrip && !empty($_GET) && empty($_GET['s']) && empty($_GET['q']) && (false !== strpos($req_uri, '/?p=') || false !== strpos($req_uri, '/?cat=') || false !== strpos($req_uri, '/?m=') || false !== strpos($req_uri, '/?page_id=') || false !== strpos($req_uri, '/index.php/')) && !@nwdcx_optget('permalink_structure')) {
1864 $dostrip = false;
1865 }
1866
1867 if ($dostrip) {
1868 $req_uri = strtok($req_uri, '?#');
1869 }
1870
1871 if (empty($req_host) || empty($req_uri)) {
1872 return;
1873 }
1874
1875 // $this->precache_hashkey = $this->item_hash($req_host.$req_uri);
1876 $this->precache_hashkey = md5($req_host.$req_uri);
1877
1878 $this->dc_precache_load($this->precache_hashkey);
1879 }
1880
1881 /**
1882 * dc_close.
1883 * reference:
1884 * wp_cache_close()
1885 * wp-includes/load.php -> shutdown_action_hook().
1886 */
1887 public function dc_close()
1888 {
1889 static $is_done = false;
1890
1891 if (!$is_done) {
1892 if ($this->is_precache && !empty($this->precache_hashkey) && !$this->fs()->suspend_cache_write()) {
1893 $this->dc_precache_set($this->precache_hashkey);
1894 }
1895
1896 $is_done = true;
1897 }
1898 }
1899
1900 /**
1901 * dc_init.
1902 */
1903 private function dc_init()
1904 {
1905 $this->wp_start_timestamp = \defined('WP_START_TIMESTAMP') ? WP_START_TIMESTAMP : microtime(true);
1906 $this->max_execution_time = $this->fs()->get_max_execution_time();
1907 $this->is_dev = $this->cf()->is_dctrue('DEV');
1908
1909 if ($this->cf()->is_dcint('MAXSIZE', $dcvalue)) {
1910 if (!empty($dcvalue)) {
1911 $this->cache_maxsize = $this->fs()->sanitize_maxsize($dcvalue);
1912 }
1913 }
1914
1915 if ($this->cf()->is_dcint('MAXTTL', $dcvalue)) {
1916 if (!empty($dcvalue)) {
1917 $this->cache_maxttl = $this->fs()->sanitize_maxttl($dcvalue);
1918 }
1919 }
1920
1921 if ($this->cf()->is_dcarray('GLOBAL_GROUPS', $dcvalue)) {
1922 $this->add_global_groups($dcvalue);
1923 }
1924
1925 if ($this->cf()->is_dcarray('IGNORED_GROUPS', $dcvalue)) {
1926 $this->add_non_persistent_groups($dcvalue);
1927 }
1928
1929 if ($this->cf()->is_dcarray('IGNORED_KEYS', $dcvalue)) {
1930 $this->add_non_persistent_keys($dcvalue);
1931 }
1932
1933 if ($this->cf()->is_dcarray('FILTERED_GROUPS', $dcvalue)) {
1934 $this->filtered_groups = $dcvalue;
1935 }
1936
1937 if ($this->cf()->is_dcarray('IGNORED_GROUPKEY', $dcvalue)) {
1938 $this->non_persistent_groupkey = $dcvalue;
1939 }
1940
1941 if ($this->cf()->is_dcarray('IGNORED_PRECACHE', $dcvalue)) {
1942 $this->bypass_precache = $dcvalue;
1943 }
1944
1945 if (class_exists('Nawawi\\DocketCache\\TransientDb')) {
1946 $this->use_transientdb = $this->cf()->is_dctrue('TRANSIENTDB');
1947 if ($this->cf()->is_dcarray('IGNORED_TRANSIENTDB', $dcvalue)) {
1948 $this->bypass_transientdb = $dcvalue;
1949 }
1950 }
1951
1952 $this->cache_path = $this->fs()->define_cache_path($this->cf()->dcvalue('PATH'));
1953 if ($this->multisite) {
1954 $this->cache_path = nwdcx_network_dirpath($this->cache_path);
1955 }
1956
1957 if ($this->cf()->is_dctrue('WPOPTALOAD')) {
1958 $this->fs()->optimize_alloptions();
1959 }
1960
1961 add_filter(
1962 'pre_cache_alloptions',
1963 function ($alloptions) {
1964 if (isset($alloptions['cron'])) {
1965 unset($alloptions['cron']);
1966 }
1967
1968 if (isset($alloptions['litespeed_messages'])) {
1969 unset($alloptions['litespeed_messages']);
1970 }
1971
1972 if (isset($alloptions['litespeed.admin_display.messages'])) {
1973 unset($alloptions['litespeed.admin_display.messages']);
1974 }
1975
1976 return $alloptions;
1977 },
1978 \PHP_INT_MAX
1979 );
1980
1981 // litespeed admin notice
1982 add_action(
1983 'litespeed_purged_all',
1984 function () {
1985 $this->delete('alloptions', 'options');
1986 $this->delete('litespeed_messages', 'options');
1987 $this->delete('litespeed.admin_display.messages', 'options');
1988 },
1989 \PHP_INT_MAX
1990 );
1991
1992 add_action(
1993 'all_admin_notices',
1994 function () {
1995 if (\function_exists('run_litespeed_cache')) {
1996 $this->delete('litespeed_messages', 'options');
1997 $this->delete('litespeed.admin_display.messages', 'options');
1998 }
1999 },
2000 \PHP_INT_MAX
2001 );
2002
2003 foreach (['added', 'updated', 'deleted'] as $prefix) {
2004 add_action(
2005 $prefix.'_option',
2006 function ($option) {
2007 if (!wp_installing()) {
2008 $alloptions = wp_load_alloptions();
2009 if (isset($alloptions[$option])) {
2010 add_action(
2011 'shutdown',
2012 function () {
2013 $this->delete('alloptions', 'options');
2014 },
2015 \PHP_INT_MAX - 1
2016 );
2017 }
2018 unset($alloptions);
2019 }
2020 },
2021 \PHP_INT_MAX
2022 );
2023 }
2024
2025 foreach (['activate', 'deactivate'] as $prefix) {
2026 add_action(
2027 $prefix.'_plugin',
2028 function ($plugin, $network) {
2029 add_action(
2030 'shutdown',
2031 function () {
2032 if ($this->multisite) {
2033 $this->delete($this->network_id.':active_sitewide_plugins', 'site-options');
2034 $this->delete($this->network_id.':auto_update_plugins', 'site-options');
2035 }
2036
2037 $this->delete('uninstall_plugins', 'options');
2038 $this->delete('auto_update_plugins', 'options');
2039 },
2040 \PHP_INT_MAX - 1
2041 );
2042 },
2043 \PHP_INT_MAX,
2044 2
2045 );
2046 }
2047
2048 // filtered groups hooks
2049 if (\is_array($this->filtered_groups)) {
2050 add_action(
2051 'save_post',
2052 function ($post_id, $post, $update) {
2053 $this->flush_filtered_groups('save_post', [$post_id, $post, $update]);
2054 },
2055 \PHP_INT_MIN,
2056 3
2057 );
2058
2059 add_action(
2060 'edit_post',
2061 function ($post_id, $post) {
2062 $this->flush_filtered_groups('edit_post', [$post_id, $post]);
2063 },
2064 \PHP_INT_MIN,
2065 2
2066 );
2067
2068 add_action(
2069 'delete_post',
2070 function ($post_id) {
2071 $this->flush_filtered_groups('delete_post', [$post_id]);
2072 },
2073 \PHP_INT_MIN
2074 );
2075 }
2076
2077 // html comment
2078 $this->add_signature = false;
2079 if ($this->cf()->is_dctrue('SIGNATURE')) {
2080 add_action(
2081 'wp_head',
2082 function () {
2083 if (!$this->is_user_logged_in()) {
2084 $this->add_signature = true;
2085 }
2086 },
2087 \PHP_INT_MIN
2088 );
2089
2090 add_action(
2091 'shutdown',
2092 function () {
2093 if ($this->add_signature && !$this->is_user_logged_in()) {
2094 echo apply_filters('docketcache/filter/signature/htmlfooter', "\n<!-- Performance optimized by Docket Cache: https://wordpress.org/plugins/docket-cache -->\n");
2095 }
2096 },
2097 \PHP_INT_MAX
2098 );
2099 }
2100
2101 // bypass stalecache
2102 $this->ignore_stalecache = $this->cf()->is_dctrue('STALECACHE_IGNORE', true);
2103
2104 // bypass emptyache
2105 $this->ignore_emptycache = $this->cf()->is_dctrue('EMPTYCACHE_IGNORE', true);
2106
2107 // maxfile check
2108 // true = count file at dc_save, false = will handle by GC.
2109 $this->maxfile_livecheck = $this->cf()->is_dctrue('MAXFILE_LIVECHECK', true);
2110
2111 // maxfile
2112 $this->maxfile = (int) $this->fs()->sanitize_maxfile($this->cf()->dcvalue('MAXFILE', true));
2113 $count_file = (int) $this->get('count_file', 'docketcache-gc');
2114 if ($count_file >= $this->maxfile) {
2115 $this->fs()->suspend_cache_write(true);
2116 }
2117
2118 // load precache
2119 $this->is_precache = $this->cf()->is_dctrue('PRECACHE', true);
2120 if ($this->is_precache) {
2121 if ($this->cf()->is_dcint('PRECACHE_MAXGROUP', $dcvalue)) {
2122 if (!empty($dcvalue)) {
2123 $this->precache_maxgroup = $dcvalue;
2124 }
2125 }
2126
2127 if ($this->cf()->is_dcint('PRECACHE_MAXKEY', $dcvalue)) {
2128 if (!empty($dcvalue)) {
2129 $this->precache_maxkey = $dcvalue;
2130 }
2131 }
2132
2133 if ($this->cf()->is_dcint('PRECACHE_MAXFILE', $dcvalue)) {
2134 if (!empty($dcvalue)) {
2135 $this->precache_maxfile = $this->fs()->sanitize_precache_maxfile($dcvalue);
2136 }
2137 }
2138
2139 $this->dc_precache_init();
2140 }
2141 }
2142 }
2143
2144 /**
2145 * Sets up Object Cache Global and assigns it.
2146 *
2147 * @global WP_Object_Cache $wp_object_cache
2148 */
2149 function wp_cache_init()
2150 {
2151 global $wp_object_cache;
2152 if (!($wp_object_cache instanceof WP_Object_Cache)) {
2153 $wp_object_cache = new WP_Object_Cache();
2154 }
2155 }
2156
2157 /**
2158 * @see WP_Object_Cache::add()
2159 */
2160 function wp_cache_add($key, $data, $group = '', $expire = 0)
2161 {
2162 global $wp_object_cache;
2163
2164 return $wp_object_cache->add($key, $data, $group, (int) $expire);
2165 }
2166
2167 /**
2168 * @see WP_Object_Cache::add_multiple()
2169 */
2170 function wp_cache_add_multiple(array $data, $group = '', $expire = 0)
2171 {
2172 global $wp_object_cache;
2173
2174 return $wp_object_cache->add_multiple($data, $group, $expire);
2175 }
2176
2177 /**
2178 * @see WP_Object_Cache::replace()
2179 */
2180 function wp_cache_replace($key, $data, $group = '', $expire = 0)
2181 {
2182 global $wp_object_cache;
2183
2184 return $wp_object_cache->replace($key, $data, $group, (int) $expire);
2185 }
2186
2187 /**
2188 * @see WP_Object_Cache::set()
2189 */
2190 function wp_cache_set($key, $data, $group = '', $expire = 0)
2191 {
2192 global $wp_object_cache;
2193
2194 return $wp_object_cache->set($key, $data, $group, (int) $expire);
2195 }
2196
2197 /**
2198 * @see WP_Object_Cache::set_multiple()
2199 */
2200 function wp_cache_set_multiple(array $data, $group = '', $expire = 0)
2201 {
2202 global $wp_object_cache;
2203
2204 return $wp_object_cache->set_multiple($data, $group, $expire);
2205 }
2206
2207 /**
2208 * @see WP_Object_Cache::get()
2209 */
2210 function wp_cache_get($key, $group = '', $force = false, &$found = null)
2211 {
2212 global $wp_object_cache;
2213
2214 return $wp_object_cache->get($key, $group, $force, $found);
2215 }
2216
2217 /**
2218 * @see WP_Object_Cache::get_multiple()
2219 */
2220 function wp_cache_get_multiple(array $keys, $group = '', $force = false)
2221 {
2222 global $wp_object_cache;
2223
2224 return $wp_object_cache->get_multiple($keys, $group, $force);
2225 }
2226
2227 /**
2228 * @see WP_Object_Cache::delete()
2229 */
2230 function wp_cache_delete($key, $group = '')
2231 {
2232 global $wp_object_cache;
2233
2234 return $wp_object_cache->delete($key, $group);
2235 }
2236
2237 /**
2238 * @see WP_Object_Cache::delete_multiple()
2239 */
2240 function wp_cache_delete_multiple(array $keys, $group = '')
2241 {
2242 global $wp_object_cache;
2243
2244 return $wp_object_cache->delete_multiple($keys, $group);
2245 }
2246
2247 /**
2248 * @see WP_Object_Cache::incr()
2249 */
2250 function wp_cache_incr($key, $offset = 1, $group = '')
2251 {
2252 global $wp_object_cache;
2253
2254 return $wp_object_cache->incr($key, $offset, $group);
2255 }
2256
2257 /**
2258 * @see WP_Object_Cache::decr()
2259 */
2260 function wp_cache_decr($key, $offset = 1, $group = '')
2261 {
2262 global $wp_object_cache;
2263
2264 return $wp_object_cache->decr($key, $offset, $group);
2265 }
2266
2267 /**
2268 * @see WP_Object_Cache::flush()
2269 */
2270 function wp_cache_flush()
2271 {
2272 global $wp_object_cache;
2273
2274 return $wp_object_cache->flush();
2275 }
2276
2277 /**
2278 * @see WP_Object_Cache::flush()
2279 */
2280 function wp_cache_flush_runtime()
2281 {
2282 global $wp_object_cache;
2283
2284 return $wp_object_cache->flush(true);
2285 }
2286
2287 /**
2288 * Determines whether the object cache implementation supports a particular feature.
2289 *
2290 * @since 6.1.0
2291 *
2292 * @param string $feature Name of the feature to check for. Possible values include:
2293 * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
2294 * 'flush_runtime', 'flush_group'.
2295 *
2296 * @return bool true if the feature is supported, false otherwise
2297 */
2298 function wp_cache_supports($feature)
2299 {
2300 switch ($feature) {
2301 case 'add_multiple':
2302 case 'set_multiple':
2303 case 'get_multiple':
2304 case 'delete_multiple':
2305 case 'flush_runtime':
2306 case 'flush_group':
2307 return true;
2308
2309 default:
2310 return false;
2311 }
2312 }
2313
2314 /**
2315 * @see WP_Object_Cache::dc_close()
2316 */
2317 function wp_cache_close()
2318 {
2319 global $wp_object_cache;
2320
2321 $wp_object_cache->dc_close();
2322
2323 return true;
2324 }
2325
2326 /**
2327 * @see WP_Object_Cache::add_non_persistent_groups()
2328 */
2329 function wp_cache_add_non_persistent_groups($groups)
2330 {
2331 global $wp_object_cache;
2332 $wp_object_cache->add_non_persistent_groups($groups);
2333 }
2334
2335 /**
2336 * @see WP_Object_Cache::add_non_persistent_keys()
2337 */
2338 function wp_cache_add_non_persistent_keys($keys)
2339 {
2340 global $wp_object_cache;
2341 $wp_object_cache->add_non_persistent_keys($keys);
2342 }
2343
2344 /**
2345 * @see WP_Object_Cache::switch_to_blog()
2346 */
2347 function wp_cache_switch_to_blog($blog_id)
2348 {
2349 global $wp_object_cache;
2350
2351 $wp_object_cache->switch_to_blog($blog_id);
2352 }
2353
2354 /**
2355 * @see WP_Object_Cache::add_global_groups()
2356 */
2357 function wp_cache_add_global_groups($groups)
2358 {
2359 global $wp_object_cache;
2360
2361 $wp_object_cache->add_global_groups($groups);
2362 }
2363
2364 /**
2365 * @see WP_Object_Cache::stats()
2366 */
2367 function wp_cache_stats()
2368 {
2369 global $wp_object_cache;
2370 $wp_object_cache->stats();
2371 }
2372
2373 /**
2374 * @see WP_Object_Cache::dc_remove_group()
2375 */
2376 function wp_cache_flush_group($group = 'default')
2377 {
2378 global $wp_object_cache;
2379
2380 return $wp_object_cache->dc_remove_group($group);
2381 }
2382
2383 /**
2384 * @see WP_Object_Cache::dc_remove_group_match()
2385 */
2386 function wp_cache_flush_group_match($group = 'default')
2387 {
2388 global $wp_object_cache;
2389
2390 return $wp_object_cache->dc_remove_group_match($group);
2391 }
2392