PluginProbe
Docket Cache – Object Cache Accelerator / 22.07.04
Docket Cache – Object Cache Accelerator v22.07.04
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 22.07.04, at includes/cache.php

2,373 lines 67.3 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 // prefix:md5hash:microtime
944 // wp_query|get_terms|get_comments|comment_feed|get_sites|get_network_ids|get_page_by_path|other?
945 elseif (false !== strpos($key, ':') && @preg_match('@^([a-zA-Z0-9\._-]+):([0-9a-f]{32}):([0-9\. ]+)$@', $key)) {
946 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
947 }
948
949 // wp stale cache
950 // cache timestamp
951 elseif ('last_changed' === $key) {
952 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
953 }
954
955 // advcpost
956 // docketcache-post-(found|media|timestamp)
957 elseif (false !== strpos($group, 'docketcache-post-')) {
958 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
959 }
960
961 // advcpost
962 // docketcache-post-media
963 elseif ('docketcache-post-media' === $group) {
964 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
965 }
966
967 // advcpost
968 // cache timestamp
969 elseif ('docketcache-post' === $group && 'cache_incr' === $key) {
970 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
971 }
972
973 // woocommerce stale cache
974 // cache prefix
975 elseif (false !== strpos($key, '_cache_prefix') && @preg_match('@^wc_(.*?)_cache_prefix$@', $key)) {
976 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
977 }
978
979 // woocommerce stale cache
980 // wc_cache_0.72953700 1651592702
981 elseif (false !== strpos($key, 'wc_cache_') && @preg_match('@^wc_cache_([0-9\. ]+)_@', $key)) {
982 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
983 } elseif (false !== strpos($group, 'wc_cache_') && @preg_match('@^wc_cache_([0-9\. ]+)_@', $group)) {
984 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
985 }
986
987 // common cache
988 elseif (preg_match('@[0-9a-f]{32}@', $key)) {
989 $expire = $maxttl < 86400 ? $maxttl : 86400; // 1d
990 }
991
992 // else
993 else {
994 $expire = $maxttl;
995 }
996 }
997
998 return $expire;
999 }
1000
1001 /**
1002 * get_item_hash.
1003 */
1004 private function get_item_hash($file)
1005 {
1006 return basename($file, '.php');
1007 }
1008
1009 /**
1010 * item_hash.
1011 */
1012 private function item_hash($str, $length = 12)
1013 {
1014 if (!$this->is_valid_key($str)) {
1015 $str = serialize($str);
1016 }
1017
1018 if (empty($length)) {
1019 return md5($str);
1020 }
1021
1022 return substr(md5($str), 0, $length);
1023 }
1024
1025 /**
1026 * get_file_path.
1027 */
1028 private function get_file_path($key, $group)
1029 {
1030 $hash_group = $this->item_hash($group);
1031 $hash_key = $this->item_hash($key);
1032
1033 $index = $hash_group.'-'.$hash_key;
1034
1035 if ($this->cf()->is_dcfalse('CHUNKCACHEDIR', true)) {
1036 return $this->cache_path.$index.'.php';
1037 }
1038
1039 $chunk_path = $this->fs()->get_chunk_path($hash_group, $hash_key);
1040
1041 return $this->cache_path.$chunk_path.$index.'.php';
1042 }
1043
1044 /**
1045 * skip_stats.
1046 */
1047 private function skip_stats($group, $key = '')
1048 {
1049 if ($this->is_non_persistent_groups($group) || (!empty($keys) && $this->is_non_persistent_keys($key))) {
1050 return true;
1051 }
1052
1053 return $this->cf()->is_dcfalse('LOG_ALL') && $this->fs()->is_docketcachegroup($group);
1054 }
1055
1056 /**
1057 * has_stalecache.
1058 */
1059 private function has_stalecache($key, $group = '')
1060 {
1061 if ('wc_' === substr($key, 0, 3) && '_cache_prefix' === substr($key, -13)) {
1062 return true;
1063 }
1064
1065 if ('wc_cache_' === substr($key, 0, 9) || 'wc_cache_' === substr($group, 0, 9)) {
1066 return true;
1067 }
1068
1069 if (false !== strpos($key, ':') && @preg_match('@^([a-zA-Z0-9\._-]+):([0-9a-f]{32}):([0-9\. ]+)$@', $key)) {
1070 return true;
1071 }
1072
1073 if (false !== strpos($group, 'docketcache-post-') && preg_match('@^docketcache-post-\d+$@', $group)) {
1074 return true;
1075 }
1076
1077 return false;
1078 }
1079
1080 /**
1081 * is_stalecache_ignored.
1082 */
1083 private function is_stalecache_ignored($key, $group = '')
1084 {
1085 if ($this->ignore_stalecache) {
1086 return $this->has_stalecache($key, $group = '');
1087 }
1088
1089 return false;
1090 }
1091
1092 /**
1093 * fs.
1094 */
1095 private function fs()
1096 {
1097 static $inst;
1098 if (!\is_object($inst)) {
1099 $inst = new Nawawi\DocketCache\Filesystem();
1100 }
1101
1102 return $inst;
1103 }
1104
1105 /**
1106 * cf.
1107 */
1108 private function cf()
1109 {
1110 static $inst;
1111 if (!\is_object($inst)) {
1112 $inst = new Nawawi\DocketCache\Constans();
1113 }
1114
1115 return $inst;
1116 }
1117
1118 /**
1119 * transientdb.
1120 */
1121 private function transient_db()
1122 {
1123 static $inst;
1124 if (!\is_object($inst)) {
1125 $inst = new Nawawi\DocketCache\TransientDb();
1126 }
1127
1128 return $inst;
1129 }
1130
1131 /**
1132 * dc_key.
1133 */
1134 private function dc_key($key, $group)
1135 {
1136 if ($this->multisite && !\array_key_exists($group, $this->global_groups)) {
1137 $key = $this->blog_prefix.$key;
1138 }
1139
1140 return $key;
1141 }
1142
1143 /**
1144 * dc_log.
1145 */
1146 private function dc_log($tag, $id, $data)
1147 {
1148 if ($this->cf()->is_dcfalse('LOG')) {
1149 return false;
1150 }
1151
1152 if ($this->skip_stats($data)) {
1153 return false;
1154 }
1155
1156 if ($this->cf()->is_dcfalse('LOG_ALL')) {
1157 if (!\in_array($tag, ['hit', 'miss'])) {
1158 return false;
1159 }
1160
1161 if (false !== strpos($data, 'user') && @preg_match('@^user(s|email|logins|_meta)\:.*@', $data)) {
1162 return false;
1163 }
1164 }
1165
1166 $caller = '';
1167 if (!empty($_SERVER['REQUEST_URI'])) {
1168 $caller = $_SERVER['REQUEST_URI'];
1169 } elseif ($this->cf()->is_dctrue('WPCLI')) {
1170 $caller = 'wp-cli';
1171 }
1172
1173 if (false !== strpos($caller, '?page=docket-cache')) {
1174 return false;
1175 }
1176
1177 static $duplicate = [];
1178
1179 $buff = $this->item_hash($tag.$id.$data.$caller);
1180 if (isset($duplicate[$buff])) {
1181 return false;
1182 }
1183
1184 $duplicate[$buff] = 1;
1185
1186 return $this->fs()->log($tag, $id, $data, $caller);
1187 }
1188
1189 /**
1190 * dc_flush.
1191 */
1192 private function dc_flush()
1193 {
1194 $dir = $this->cache_path;
1195 $is_timeout = false;
1196 $cnt = $this->fs()->cachedir_flush($dir, false, $is_timeout);
1197 $logkey = '000000000000-'.$this->item_hash(__FUNCTION__);
1198
1199 if ($is_timeout) {
1200 $this->dc_log('err', $logkey, 'Process aborted. Reached maximum execution time. Total cache flushed: '.$cnt);
1201
1202 return false;
1203 }
1204
1205 if (false === $cnt) {
1206 $this->dc_log('err', $logkey, 'Cache could not be flushed');
1207
1208 return false;
1209 }
1210
1211 if ($cnt > 0) {
1212 $this->dc_log('flush', $logkey, 'Total cache flushed: '.$cnt);
1213 }
1214
1215 return true;
1216 }
1217
1218 /**
1219 * dc_remove.
1220 */
1221 private function dc_remove($key, $group)
1222 {
1223 $result = true;
1224 if ($this->use_transientdb && $this->fs()->is_transient($group)) {
1225 $result = $this->transient_db()->delete($key, $group);
1226 }
1227
1228 $file = $this->get_file_path($key, $group);
1229 $this->fs()->unlink($file, false);
1230 $this->dc_log('del', $this->get_item_hash($file), $group.':'.$key);
1231
1232 return $result;
1233 }
1234
1235 /**
1236 * dc_remove_group.
1237 */
1238 public function dc_remove_group($group)
1239 {
1240 $total = 0;
1241 if (!$this->fs()->is_docketcachedir($this->cache_path)) {
1242 return $total;
1243 }
1244
1245 $pattern = '@^'.$this->item_hash($group).'\-([a-z0-9]{12})\.php$@';
1246
1247 if (\is_array($group) && !empty($group)) {
1248 $groups = array_map(function ($name) {
1249 return $this->item_hash($name);
1250 }, $group);
1251
1252 $pattern = '@^('.implode('|', $groups).")\-([a-z0-9]{12})\.php$@";
1253 $group = implode(',', $group);
1254 }
1255
1256 $this->fs()->suspend_cache_write(true);
1257 $max_execution_time = $this->fs()->get_max_execution_time(180);
1258
1259 $slowdown = 0;
1260 foreach ($this->fs()->scanfiles($this->cache_path, null, $pattern) as $object) {
1261 if ($object->isFile()) {
1262 $fx = $object->getPathName();
1263 $fn = $object->getFileName();
1264 $this->fs()->unlink($fx, true);
1265 ++$total;
1266
1267 array_map(function ($grp) use ($fx) {
1268 unset($this->cache[$grp]);
1269 $this->dc_log('flush', $this->get_item_hash($fx), $grp.':*');
1270 }, explode(',', $group));
1271 }
1272
1273 if ($slowdown > 10) {
1274 $slowdown = 0;
1275 usleep(5000);
1276 }
1277
1278 ++$slowdown;
1279
1280 if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) {
1281 break;
1282 }
1283 }
1284
1285 $this->fs()->suspend_cache_write(false);
1286
1287 if ($this->use_transientdb && $this->fs()->is_transient(explode(',', $group)) && \function_exists('nwdcx_cleanuptransient')) {
1288 $total += nwdcx_cleanuptransient();
1289 }
1290
1291 return $total;
1292 }
1293
1294 /**
1295 * dc_remove_group_match.
1296 */
1297 public function dc_remove_group_match($group)
1298 {
1299 $total = 0;
1300 if (!$this->fs()->is_docketcachedir($this->cache_path)) {
1301 return $total;
1302 }
1303
1304 $this->fs()->suspend_cache_write(true);
1305 $max_execution_time = $this->fs()->get_max_execution_time(180);
1306
1307 $slowdown = 0;
1308 $pattern = '@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@';
1309 foreach ($this->fs()->scanfiles($this->cache_path, null, $pattern) as $object) {
1310 if ($object->isFile()) {
1311 $fx = $object->getPathName();
1312 $data = $this->fs()->cache_get($fx);
1313 if (!empty($data) && !empty($data['group'])) {
1314 $match = $data['group'];
1315
1316 if (\is_array($group) && !empty($group)) {
1317 foreach ($group as $grp) {
1318 if ($grp === substr($match, 0, \strlen($grp))) {
1319 $this->fs()->unlink($fx, true);
1320 $this->dc_log('flush', $this->get_item_hash($fx), $match.':*');
1321 unset($this->cache[$match]);
1322
1323 ++$total;
1324 }
1325 }
1326 } else {
1327 if ($group === substr($match, 0, \strlen($group))) {
1328 $this->fs()->unlink($fx, true);
1329 $this->dc_log('flush', $this->get_item_hash($fx), $match.':*');
1330 unset($this->cache[$match]);
1331
1332 ++$total;
1333 }
1334 }
1335 }
1336 unset($data);
1337 }
1338
1339 if ($slowdown > 10) {
1340 $slowdown = 0;
1341 usleep(5000);
1342 }
1343
1344 ++$slowdown;
1345
1346 if ($max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $max_execution_time) {
1347 break;
1348 }
1349 }
1350
1351 $this->fs()->suspend_cache_write(false);
1352
1353 return $total;
1354 }
1355
1356 /**
1357 * dc_get.
1358 */
1359 private function dc_get($key, $group, $is_raw = false, &$codestub_false = false)
1360 {
1361 $file = $this->get_file_path($key, $group);
1362 $logkey = $this->get_item_hash($file);
1363
1364 if ($this->use_transientdb && !\in_array($key, $this->bypass_transientdb)) {
1365 if ($this->fs()->is_transient($group)) {
1366 return $this->transient_db()->get($key, $group);
1367 }
1368
1369 if ($this->fs()->is_wp_options($group) && $this->transient_db()->match_key($key)) {
1370 return false;
1371 }
1372 }
1373
1374 $data = $this->fs()->cache_get($file);
1375 if (false === $data) {
1376 if (!$this->skip_stats($group) && !$this->fs()->is_transient($group)) {
1377 $this->dc_log('miss', $logkey, $group.':'.$key);
1378 }
1379
1380 /*if (!$this->skip_stats($group) && !$this->fs()->is_transient($group)) {
1381 ++$this->cache_misses;
1382
1383 $this->dc_log('miss', $logkey, $group.':'.$key);
1384 }*/
1385
1386 return false;
1387 }
1388
1389 $is_timeout = false;
1390 if (!empty($data['timeout']) && $this->fs()->valid_timestamp($data['timeout']) && time() >= $data['timeout']) {
1391 $this->dc_log('exp', $logkey, $group.':'.$key);
1392 $this->fs()->unlink($file, false);
1393 $is_timeout = true;
1394 }
1395
1396 // incase gc not run
1397 if (!$is_timeout && !empty($data['timestamp']) && $this->fs()->valid_timestamp($data['timestamp'])) {
1398 $maxttl = time() - $this->cache_maxttl;
1399 if ($maxttl > $data['timestamp']) {
1400 $this->dc_log('exp', $logkey, $group.':'.$key);
1401 $this->fs()->unlink($file, true); // true = delete it instead of truncate
1402 }
1403 }
1404
1405 if (!$this->skip_stats($group)) {
1406 ++$this->persistent_cache_hits;
1407 $this->dc_log('hit', $logkey, $group.':'.$key);
1408 }
1409
1410 // If the transient does not exist, does not have a value, or has expired, then the return value will be false.
1411 if (!empty($data['group']) && $this->fs()->is_transient($data['group']) && ('' === $data['data'] || $is_timeout)) {
1412 $data['data'] = false;
1413 }
1414
1415 // nwdcx_unserialize failed to convert serialize object.
1416 // we unserialize it here to get the object.
1417 if (!empty($data['data'])) {
1418 // *_serialize set at dc_save, to load it faster
1419 if (false !== strpos($data['type'], '_serialize')) {
1420 $data['data'] = unserialize($data['data']);
1421 } elseif ('string' === $data['type'] && \function_exists('maybe_unserialize')) {
1422 // old cache data
1423 $data['data'] = maybe_unserialize($data['data']);
1424 }
1425 }
1426 clearstatcache();
1427
1428 // skip precache.
1429 if (isset($GLOBALS['DOCKET_CACHE_CODESTUB_FALSE']) && isset($GLOBALS['DOCKET_CACHE_CODESTUB_FALSE'][$file])) {
1430 $codestub_false = true;
1431 }
1432
1433 return $is_raw ? $data : $data['data'];
1434 }
1435
1436 /**
1437 * dc_code.
1438 */
1439 private function dc_code($file, $arr)
1440 {
1441 $logkey = $this->get_item_hash($file);
1442 $logpref = __FUNCTION__.'():';
1443
1444 $data = $this->fs()->export_var($arr, $error);
1445
1446 if (false === $data) {
1447 $this->dc_log('err', $logkey, $logpref.' Failed to export var -> '.$error);
1448
1449 return false;
1450 }
1451
1452 $code = $this->fs()->code_stub($data);
1453 $stat = $this->fs()->dump($file, $code, false); // 3rd param = validate
1454
1455 if (false === $stat) {
1456 return false;
1457 }
1458
1459 if (-1 === $stat) {
1460 $this->dc_log('err', $logkey, $logpref.' Failed to write');
1461
1462 return false;
1463 }
1464
1465 // remove lock
1466 $this->fs()->validate_fatal_error_file($file);
1467
1468 return $stat;
1469 }
1470
1471 /**
1472 * dc_save.
1473 */
1474 private function dc_save($cache_key, $data, $group = 'default', $expire = 0, $key = '')
1475 {
1476 if (wp_suspend_cache_addition()) {
1477 return false;
1478 }
1479
1480 $logkey = $this->item_hash($group).'-'.$this->item_hash($cache_key);
1481 $logpref = __FUNCTION__.'():';
1482
1483 if ($this->use_transientdb && !\in_array($cache_key, $this->bypass_transientdb)) {
1484 if ($this->fs()->is_transient($group)) {
1485 if (!$expire) {
1486 $expire = 86400;
1487 }
1488
1489 return $this->transient_db()->set($cache_key, $data, $group, time() + $expire);
1490 }
1491
1492 if ($this->fs()->is_wp_options($group) && $this->transient_db()->match_key($cache_key)) {
1493 return false;
1494 }
1495
1496 if (\in_array($cache_key, ['notoptions', 'alloptions']) && \is_array($data) && !empty($data)) {
1497 foreach ($data as $m => $n) {
1498 if ($this->transient_db()->match_key($m)) {
1499 unset($data[$m]);
1500 }
1501 }
1502 }
1503 }
1504
1505 if (!$this->fs()->mkdir_p($this->cache_path)) {
1506 return false;
1507 }
1508
1509 @$this->fs()->placeholder($this->cache_path);
1510
1511 $file = $this->get_file_path($cache_key, $group);
1512
1513 // Skip save to disk, return true.
1514 if (('' === $data || (\is_array($data) && empty($data))) && ($this->fs()->is_transient($group) || $this->ignore_emptycache)) {
1515 nwdcx_debuglog(__FUNCTION__.': '.$logkey.': Process aborted. No data availale.');
1516 $this->fs()->unlink($file, false);
1517
1518 return true;
1519 }
1520
1521 // Chunk dir.
1522 if ($this->cf()->is_dctrue('CHUNKCACHEDIR', true) && !$this->fs()->mkdir_p(\dirname($file))) {
1523 return false;
1524 }
1525
1526 // If $expire is larger than 0, convert it to timestamp.
1527 $timeout = ($expire > 0 ? time() + $expire : 0);
1528
1529 $type = \gettype($data);
1530 if ('NULL' === $type && null === $data) {
1531 $data = '';
1532 }
1533
1534 if (!empty($data)) {
1535 // Abort if object too large.
1536 $len = 0;
1537 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
1538 if (\function_exists('maybe_serialize')) {
1539 $len = \strlen(@maybe_serialize($data));
1540 } else {
1541 $len = \strlen(@serialize($data));
1542 }
1543 nwdcx_suppresserrors($nwdcx_suppresserrors);
1544
1545 if ($len >= $this->cache_maxsize) {
1546 $this->dc_log('err', $logkey, $group.':'.$cache_key.' '.$logpref.' Object too large -> '.$len.'/'.$this->cache_maxsize);
1547
1548 nwdcx_debuglog(__FUNCTION__.': '.$logkey.': Process aborted. Object too large ('.$len.'/'.$this->cache_maxsize.')');
1549
1550 return false;
1551 }
1552
1553 // Unserialize content first.
1554 if ('string' === $type) {
1555 $data = nwdcx_unserialize($data);
1556 } elseif ('array' === $type) {
1557 $data_r = nwdcx_arraymap('nwdcx_unserialize', $data);
1558
1559 if (!empty($data_r)) {
1560 $data = $data_r;
1561 }
1562 unset($data_r);
1563 }
1564 }
1565
1566 $meta = [];
1567 $meta['timestamp'] = time();
1568
1569 if ($this->multisite) {
1570 $meta['network_id'] = $this->network_id;
1571 }
1572
1573 $final_type = \gettype($data);
1574 if ('string' === $final_type && nwdcx_serialized($data)) {
1575 $final_type = 'string_serialize';
1576 } elseif ('array' === $final_type) {
1577 // The Data needs to be serialized.
1578 // The cache always returns false if the object has a class instance
1579 // other than stdClass since the class has not been loaded yet.
1580 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
1581 $export_data = @var_export($data, 1);
1582 if (!empty($export_data)) {
1583 // 1st priority. If has the "Request" instance.
1584 if (false !== strpos($export_data, 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) {
1585 $data = @serialize($data);
1586 if (nwdcx_serialized($data)) {
1587 $final_type = 'array_serialize';
1588 }
1589 }
1590
1591 // 2nd priority. If Transients and has class instance.
1592 if ('array' === $final_type && $this->fs()->is_transient($group) && false !== strpos($export_data, '::__set_state')) {
1593 $data = @serialize($data);
1594 if (nwdcx_serialized($data)) {
1595 $final_type = 'array_serialize';
1596 }
1597 }
1598 }
1599 unset($export_data);
1600 nwdcx_suppresserrors($nwdcx_suppresserrors);
1601 // Pass to code_stub.
1602 }
1603
1604 $meta['site_id'] = get_current_blog_id();
1605 $meta['group'] = $group;
1606 $meta['key'] = $cache_key;
1607 $meta['type'] = $final_type;
1608
1609 // If 0 let gc handle it by comparing file mtime
1610 // and maxttl constants.
1611 $meta['timeout'] = $timeout;
1612
1613 // Before code_stub.
1614 $meta['data'] = $data;
1615
1616 // Only count new file.
1617 clearstatcache(true, $file);
1618 $has_cache_file = is_file($file);
1619 if (true === $this->dc_code($file, $meta)) {
1620 nwdcx_debuglog(__FUNCTION__.': '.$this->get_item_hash($file).': Storing to disk.');
1621
1622 if (!$has_cache_file && $this->maxfile_livecheck) {
1623 $count_file = (int) $this->get('count_file', 'docketcache-gc');
1624 if ($this->maxfile > $count_file) {
1625 ++$count_file;
1626 $this->set('count_file', $count_file, 'docketcache-gc', 86400); // 1d
1627 }
1628 }
1629
1630 return true;
1631 }
1632
1633 return false;
1634 }
1635
1636 /**
1637 * dc_update.
1638 */
1639 private function dc_update($cache_key, $data, $group)
1640 {
1641 $meta = $this->dc_get($cache_key, $group, true);
1642 if (false === $meta || !\is_array($meta) || !isset($meta['data'])) {
1643 return false;
1644 }
1645
1646 $file = $this->get_file_path($cache_key, $group);
1647 $meta['data'] = $data;
1648
1649 if (true === $this->dc_code($file, $meta)) {
1650 return true;
1651 }
1652
1653 return false;
1654 }
1655
1656 /**
1657 * dc_precache_load.
1658 */
1659 private function dc_precache_load($hash)
1660 {
1661 static $is_done = false;
1662
1663 if ($is_done) {
1664 return;
1665 }
1666
1667 $cached = [];
1668 $group = 'docketcache-precache';
1669 $keys = $this->get($hash, $group);
1670
1671 if (empty($keys) || !\is_array($keys)) {
1672 return;
1673 }
1674
1675 nwdcx_debuglog(__FUNCTION__.': Process started.');
1676
1677 $this->precache_loaded[$hash] = $keys;
1678
1679 $slowdown = 0;
1680 $cnt_max = 0;
1681
1682 foreach ($keys as $cache_group => $arr) {
1683 foreach ($arr as $cache_key) {
1684 if ($cnt_max >= $this->precache_maxkey) {
1685 break 2;
1686 }
1687
1688 $force = false;
1689 $found = false;
1690 $doing_precache = true;
1691 if (!isset($cached[$cache_key.$cache_group]) && false !== $this->get($cache_key, $cache_group, $force, $found, $doing_precache)) {
1692 $cached[$cache_key.$cache_group] = 1;
1693 }
1694
1695 ++$cnt_max;
1696
1697 if ($slowdown > 10) {
1698 $slowdown = 0;
1699 usleep(1000);
1700 }
1701
1702 ++$slowdown;
1703
1704 if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) {
1705 break 2;
1706 }
1707 }
1708 }
1709
1710 nwdcx_debuglog(__FUNCTION__.': Process ended. Loaded '.\count($cached));
1711
1712 unset($keys, $cached);
1713 $is_done = true;
1714 }
1715
1716 /**
1717 * dc_precache_set.
1718 */
1719 private function dc_precache_set($hash)
1720 {
1721 $group = 'docketcache-precache';
1722 $file = $this->get_file_path($hash, $group);
1723
1724 if (empty($this->precache) || !\is_array($this->precache)) {
1725 $this->fs()->unlink($file, true);
1726
1727 return;
1728 }
1729
1730 $file_hash = $this->get_item_hash($file);
1731 $data = [];
1732 $slowdown = 0;
1733 $cnt_max = 0;
1734
1735 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process started.');
1736
1737 // docketcache-precache-gc
1738 $count_file = (int) $this->get('count_file', $group.'-gc');
1739 if ($count_file >= $this->precache_maxfile) {
1740 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum file limit ('.$count_file.'/'.$this->precache_maxfile.')');
1741
1742 return;
1743 }
1744
1745 foreach ($this->precache as $cache_group => $cache_keys) {
1746 if ($cnt_max >= $this->precache_maxkey) {
1747 break;
1748 }
1749
1750 if ($cache_group !== $group) {
1751 $data[$cache_group] = array_keys($cache_keys);
1752 }
1753
1754 ++$cnt_max;
1755
1756 if ($slowdown > 10) {
1757 $slowdown = 0;
1758 usleep(100);
1759 }
1760
1761 ++$slowdown;
1762
1763 if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) {
1764 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum execution time.');
1765 break;
1766 }
1767 }
1768
1769 if (!empty($data)) {
1770 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Total items = '.\count($data, 1));
1771
1772 if (!empty($this->precache_loaded) && \function_exists('nwdcx_arraysimilar') && nwdcx_arraysimilar($this->precache_loaded[$hash], $data)) {
1773 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. No data changes.');
1774
1775 return;
1776 }
1777
1778 // docketcache-precache-gc
1779 if ($this->precache_maxfile > $count_file) {
1780 clearstatcache(true, $file);
1781
1782 // only count new file.
1783 $has_precache_file = is_file($file);
1784
1785 if ($this->set($hash, $data, $group, 86400)) { // 1d
1786 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. Storing cache to disk.');
1787
1788 if (!$has_precache_file) {
1789 ++$count_file;
1790 $this->set('count_file', $count_file, $group.'-gc', 86400); // 1d
1791 }
1792
1793 return;
1794 }
1795 }
1796 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process aborted. Reached maximum file limit ('.$count_file.'/'.$this->precache_maxfile.')');
1797
1798 return;
1799 }
1800
1801 nwdcx_debuglog(__FUNCTION__.': '.$file_hash.': Process ended. No data available.');
1802 unset($data, $hash);
1803 }
1804
1805 /**
1806 * dc_precache.
1807 */
1808 private function dc_precache_init()
1809 {
1810 if (!empty($_POST) || empty($_SERVER['REQUEST_URI']) || $this->cf()->is_dctrue('WPCLI')) {
1811 return;
1812 }
1813
1814 $req_uri = $_SERVER['REQUEST_URI'];
1815 $dostrip = !empty($_SERVER['QUERY_STRING']);
1816
1817 $intersect_key = [
1818 'docketcache_ping' => 1,
1819 'doing_wp_cron' => 1,
1820 'wc-ajax' => 1,
1821 '_fs_blog_admin' => 1,
1822 'action' => 1,
1823 'message' => 1,
1824 ];
1825
1826 if ($dostrip && !empty($_GET) && array_intersect_key($intersect_key, $_GET)) {
1827 return;
1828 }
1829
1830 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')) {
1831 return;
1832 }
1833
1834 $req_host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
1835 if ('localhost' !== $req_host) {
1836 $req_host = nwdcx_fixhost($req_host);
1837 }
1838
1839 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)) {
1840 $dostrip = false;
1841 }
1842
1843 // without pretty permalink
1844 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')) {
1845 $dostrip = false;
1846 }
1847
1848 if ($dostrip) {
1849 $req_uri = strtok($req_uri, '?#');
1850 }
1851
1852 if (empty($req_host) || empty($req_uri)) {
1853 return;
1854 }
1855
1856 // $this->precache_hashkey = $this->item_hash($req_host.$req_uri);
1857 $this->precache_hashkey = md5($req_host.$req_uri);
1858
1859 $this->dc_precache_load($this->precache_hashkey);
1860 }
1861
1862 /**
1863 * dc_close.
1864 * reference:
1865 * wp_cache_close()
1866 * wp-includes/load.php -> shutdown_action_hook().
1867 */
1868 public function dc_close()
1869 {
1870 static $is_done = false;
1871
1872 if (!$is_done) {
1873 if ($this->is_precache && !empty($this->precache_hashkey) && !$this->fs()->suspend_cache_write()) {
1874 $this->dc_precache_set($this->precache_hashkey);
1875 }
1876
1877 $is_done = true;
1878 }
1879 }
1880
1881 /**
1882 * dc_init.
1883 */
1884 private function dc_init()
1885 {
1886 $this->wp_start_timestamp = \defined('WP_START_TIMESTAMP') ? WP_START_TIMESTAMP : microtime(true);
1887 $this->max_execution_time = $this->fs()->get_max_execution_time();
1888 $this->is_dev = $this->cf()->is_dctrue('DEV');
1889
1890 if ($this->cf()->is_dcint('MAXSIZE', $dcvalue)) {
1891 if (!empty($dcvalue)) {
1892 $this->cache_maxsize = $this->fs()->sanitize_maxsize($dcvalue);
1893 }
1894 }
1895
1896 if ($this->cf()->is_dcint('MAXTTL', $dcvalue)) {
1897 if (!empty($dcvalue)) {
1898 $this->cache_maxttl = $this->fs()->sanitize_maxttl($dcvalue);
1899 }
1900 }
1901
1902 if ($this->cf()->is_dcarray('GLOBAL_GROUPS', $dcvalue)) {
1903 $this->add_global_groups($dcvalue);
1904 }
1905
1906 if ($this->cf()->is_dcarray('IGNORED_GROUPS', $dcvalue)) {
1907 $this->add_non_persistent_groups($dcvalue);
1908 }
1909
1910 if ($this->cf()->is_dcarray('IGNORED_KEYS', $dcvalue)) {
1911 $this->add_non_persistent_keys($dcvalue);
1912 }
1913
1914 if ($this->cf()->is_dcarray('FILTERED_GROUPS', $dcvalue)) {
1915 $this->filtered_groups = $dcvalue;
1916 }
1917
1918 if ($this->cf()->is_dcarray('IGNORED_GROUPKEY', $dcvalue)) {
1919 $this->non_persistent_groupkey = $dcvalue;
1920 }
1921
1922 if ($this->cf()->is_dcarray('IGNORED_PRECACHE', $dcvalue)) {
1923 $this->bypass_precache = $dcvalue;
1924 }
1925
1926 if (class_exists('Nawawi\\DocketCache\\TransientDb')) {
1927 $this->use_transientdb = $this->cf()->is_dctrue('TRANSIENTDB');
1928 if ($this->cf()->is_dcarray('IGNORED_TRANSIENTDB', $dcvalue)) {
1929 $this->bypass_transientdb = $dcvalue;
1930 }
1931 }
1932
1933 $this->cache_path = $this->fs()->define_cache_path($this->cf()->dcvalue('PATH'));
1934 if ($this->multisite) {
1935 $this->cache_path = nwdcx_network_dirpath($this->cache_path);
1936 }
1937
1938 if ($this->cf()->is_dctrue('WPOPTALOAD')) {
1939 $this->fs()->optimize_alloptions();
1940 }
1941
1942 add_filter(
1943 'pre_cache_alloptions',
1944 function ($alloptions) {
1945 if (isset($alloptions['cron'])) {
1946 unset($alloptions['cron']);
1947 }
1948
1949 if (isset($alloptions['litespeed_messages'])) {
1950 unset($alloptions['litespeed_messages']);
1951 }
1952
1953 if (isset($alloptions['litespeed.admin_display.messages'])) {
1954 unset($alloptions['litespeed.admin_display.messages']);
1955 }
1956
1957 return $alloptions;
1958 },
1959 \PHP_INT_MAX
1960 );
1961
1962 // litespeed admin notice
1963 add_action(
1964 'litespeed_purged_all',
1965 function () {
1966 $this->delete('alloptions', 'options');
1967 $this->delete('litespeed_messages', 'options');
1968 $this->delete('litespeed.admin_display.messages', 'options');
1969 },
1970 \PHP_INT_MAX
1971 );
1972
1973 add_action(
1974 'all_admin_notices',
1975 function () {
1976 if (\function_exists('run_litespeed_cache')) {
1977 $this->delete('litespeed_messages', 'options');
1978 $this->delete('litespeed.admin_display.messages', 'options');
1979 }
1980 },
1981 \PHP_INT_MAX
1982 );
1983
1984 foreach (['added', 'updated', 'deleted'] as $prefix) {
1985 add_action(
1986 $prefix.'_option',
1987 function ($option) {
1988 if (!wp_installing()) {
1989 $alloptions = wp_load_alloptions();
1990 if (isset($alloptions[$option])) {
1991 add_action(
1992 'shutdown',
1993 function () {
1994 $this->delete('alloptions', 'options');
1995 },
1996 \PHP_INT_MAX - 1
1997 );
1998 }
1999 unset($alloptions);
2000 }
2001 },
2002 \PHP_INT_MAX
2003 );
2004 }
2005
2006 foreach (['activate', 'deactivate'] as $prefix) {
2007 add_action(
2008 $prefix.'_plugin',
2009 function ($plugin, $network) {
2010 add_action(
2011 'shutdown',
2012 function () {
2013 if ($this->multisite) {
2014 $this->delete($this->network_id.':active_sitewide_plugins', 'site-options');
2015 $this->delete($this->network_id.':auto_update_plugins', 'site-options');
2016 }
2017
2018 $this->delete('uninstall_plugins', 'options');
2019 $this->delete('auto_update_plugins', 'options');
2020 },
2021 \PHP_INT_MAX - 1
2022 );
2023 },
2024 \PHP_INT_MAX,
2025 2
2026 );
2027 }
2028
2029 // filtered groups hooks
2030 if (\is_array($this->filtered_groups)) {
2031 add_action(
2032 'save_post',
2033 function ($post_id, $post, $update) {
2034 $this->flush_filtered_groups('save_post', [$post_id, $post, $update]);
2035 },
2036 \PHP_INT_MIN,
2037 3
2038 );
2039
2040 add_action(
2041 'edit_post',
2042 function ($post_id, $post) {
2043 $this->flush_filtered_groups('edit_post', [$post_id, $post]);
2044 },
2045 \PHP_INT_MIN,
2046 2
2047 );
2048
2049 add_action(
2050 'delete_post',
2051 function ($post_id) {
2052 $this->flush_filtered_groups('delete_post', [$post_id]);
2053 },
2054 \PHP_INT_MIN
2055 );
2056 }
2057
2058 // html comment
2059 $this->add_signature = false;
2060 if ($this->cf()->is_dctrue('SIGNATURE')) {
2061 add_action(
2062 'wp_head',
2063 function () {
2064 if (!$this->is_user_logged_in()) {
2065 $this->add_signature = true;
2066 }
2067 },
2068 \PHP_INT_MIN
2069 );
2070
2071 add_action(
2072 'shutdown',
2073 function () {
2074 if ($this->add_signature && !$this->is_user_logged_in()) {
2075 echo apply_filters('docketcache/filter/signature/htmlfooter', "\n<!-- Performance optimized by Docket Cache: https://wordpress.org/plugins/docket-cache -->\n");
2076 }
2077 },
2078 \PHP_INT_MAX
2079 );
2080 }
2081
2082 // bypass stalecache
2083 $this->ignore_stalecache = $this->cf()->is_dctrue('STALECACHE_IGNORE', true);
2084
2085 // bypass emptyache
2086 $this->ignore_emptycache = $this->cf()->is_dctrue('EMPTYCACHE_IGNORE', true);
2087
2088 // maxfile check
2089 // true = count file at dc_save, false = will handle by GC.
2090 $this->maxfile_livecheck = $this->cf()->is_dctrue('MAXFILE_LIVECHECK', true);
2091
2092 // maxfile
2093 $this->maxfile = (int) $this->fs()->sanitize_maxfile($this->cf()->dcvalue('MAXFILE', true));
2094 $count_file = (int) $this->get('count_file', 'docketcache-gc');
2095 if ($count_file >= $this->maxfile) {
2096 $this->fs()->suspend_cache_write(true);
2097 }
2098
2099 // load precache
2100 $this->is_precache = $this->cf()->is_dctrue('PRECACHE', true);
2101 if ($this->is_precache) {
2102 if ($this->cf()->is_dcint('PRECACHE_MAXGROUP', $dcvalue)) {
2103 if (!empty($dcvalue)) {
2104 $this->precache_maxgroup = $dcvalue;
2105 }
2106 }
2107
2108 if ($this->cf()->is_dcint('PRECACHE_MAXKEY', $dcvalue)) {
2109 if (!empty($dcvalue)) {
2110 $this->precache_maxkey = $dcvalue;
2111 }
2112 }
2113
2114 if ($this->cf()->is_dcint('PRECACHE_MAXFILE', $dcvalue)) {
2115 if (!empty($dcvalue)) {
2116 $this->precache_maxfile = $this->fs()->sanitize_precache_maxfile($dcvalue);
2117 }
2118 }
2119
2120 $this->dc_precache_init();
2121 }
2122 }
2123 }
2124
2125 /**
2126 * Sets up Object Cache Global and assigns it.
2127 *
2128 * @global WP_Object_Cache $wp_object_cache
2129 */
2130 function wp_cache_init()
2131 {
2132 global $wp_object_cache;
2133 if (!($wp_object_cache instanceof WP_Object_Cache)) {
2134 $wp_object_cache = new WP_Object_Cache();
2135 }
2136 }
2137
2138 /**
2139 * @see WP_Object_Cache::add()
2140 */
2141 function wp_cache_add($key, $data, $group = '', $expire = 0)
2142 {
2143 global $wp_object_cache;
2144
2145 return $wp_object_cache->add($key, $data, $group, (int) $expire);
2146 }
2147
2148 /**
2149 * @see WP_Object_Cache::add_multiple()
2150 */
2151 function wp_cache_add_multiple(array $data, $group = '', $expire = 0)
2152 {
2153 global $wp_object_cache;
2154
2155 return $wp_object_cache->add_multiple($data, $group, $expire);
2156 }
2157
2158 /**
2159 * @see WP_Object_Cache::replace()
2160 */
2161 function wp_cache_replace($key, $data, $group = '', $expire = 0)
2162 {
2163 global $wp_object_cache;
2164
2165 return $wp_object_cache->replace($key, $data, $group, (int) $expire);
2166 }
2167
2168 /**
2169 * @see WP_Object_Cache::set()
2170 */
2171 function wp_cache_set($key, $data, $group = '', $expire = 0)
2172 {
2173 global $wp_object_cache;
2174
2175 return $wp_object_cache->set($key, $data, $group, (int) $expire);
2176 }
2177
2178 /**
2179 * @see WP_Object_Cache::set_multiple()
2180 */
2181 function wp_cache_set_multiple(array $data, $group = '', $expire = 0)
2182 {
2183 global $wp_object_cache;
2184
2185 return $wp_object_cache->set_multiple($data, $group, $expire);
2186 }
2187
2188 /**
2189 * @see WP_Object_Cache::get()
2190 */
2191 function wp_cache_get($key, $group = '', $force = false, &$found = null)
2192 {
2193 global $wp_object_cache;
2194
2195 return $wp_object_cache->get($key, $group, $force, $found);
2196 }
2197
2198 /**
2199 * @see WP_Object_Cache::get_multiple()
2200 */
2201 function wp_cache_get_multiple(array $keys, $group = '', $force = false)
2202 {
2203 global $wp_object_cache;
2204
2205 return $wp_object_cache->get_multiple($keys, $group, $force);
2206 }
2207
2208 /**
2209 * @see WP_Object_Cache::delete()
2210 */
2211 function wp_cache_delete($key, $group = '')
2212 {
2213 global $wp_object_cache;
2214
2215 return $wp_object_cache->delete($key, $group);
2216 }
2217
2218 /**
2219 * @see WP_Object_Cache::delete_multiple()
2220 */
2221 function wp_cache_delete_multiple(array $keys, $group = '')
2222 {
2223 global $wp_object_cache;
2224
2225 return $wp_object_cache->delete_multiple($keys, $group);
2226 }
2227
2228 /**
2229 * @see WP_Object_Cache::incr()
2230 */
2231 function wp_cache_incr($key, $offset = 1, $group = '')
2232 {
2233 global $wp_object_cache;
2234
2235 return $wp_object_cache->incr($key, $offset, $group);
2236 }
2237
2238 /**
2239 * @see WP_Object_Cache::decr()
2240 */
2241 function wp_cache_decr($key, $offset = 1, $group = '')
2242 {
2243 global $wp_object_cache;
2244
2245 return $wp_object_cache->decr($key, $offset, $group);
2246 }
2247
2248 /**
2249 * @see WP_Object_Cache::flush()
2250 */
2251 function wp_cache_flush()
2252 {
2253 global $wp_object_cache;
2254
2255 return $wp_object_cache->flush();
2256 }
2257
2258 /**
2259 * @see WP_Object_Cache::flush()
2260 */
2261 function wp_cache_flush_runtime()
2262 {
2263 global $wp_object_cache;
2264
2265 return $wp_object_cache->flush(true);
2266 }
2267
2268 /**
2269 * Determines whether the object cache implementation supports a particular feature.
2270 *
2271 * @since 6.1.0
2272 *
2273 * @param string $feature Name of the feature to check for. Possible values include:
2274 * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple',
2275 * 'flush_runtime', 'flush_group'.
2276 *
2277 * @return bool true if the feature is supported, false otherwise
2278 */
2279 function wp_cache_supports($feature)
2280 {
2281 switch ($feature) {
2282 case 'add_multiple':
2283 case 'set_multiple':
2284 case 'get_multiple':
2285 case 'delete_multiple':
2286 case 'flush_runtime':
2287 case 'flush_group':
2288 return true;
2289
2290 default:
2291 return false;
2292 }
2293 }
2294
2295 /**
2296 * @see WP_Object_Cache::dc_close()
2297 */
2298 function wp_cache_close()
2299 {
2300 global $wp_object_cache;
2301
2302 $wp_object_cache->dc_close();
2303
2304 return true;
2305 }
2306
2307 /**
2308 * @see WP_Object_Cache::add_non_persistent_groups()
2309 */
2310 function wp_cache_add_non_persistent_groups($groups)
2311 {
2312 global $wp_object_cache;
2313 $wp_object_cache->add_non_persistent_groups($groups);
2314 }
2315
2316 /**
2317 * @see WP_Object_Cache::add_non_persistent_keys()
2318 */
2319 function wp_cache_add_non_persistent_keys($keys)
2320 {
2321 global $wp_object_cache;
2322 $wp_object_cache->add_non_persistent_keys($keys);
2323 }
2324
2325 /**
2326 * @see WP_Object_Cache::switch_to_blog()
2327 */
2328 function wp_cache_switch_to_blog($blog_id)
2329 {
2330 global $wp_object_cache;
2331
2332 $wp_object_cache->switch_to_blog($blog_id);
2333 }
2334
2335 /**
2336 * @see WP_Object_Cache::add_global_groups()
2337 */
2338 function wp_cache_add_global_groups($groups)
2339 {
2340 global $wp_object_cache;
2341
2342 $wp_object_cache->add_global_groups($groups);
2343 }
2344
2345 /**
2346 * @see WP_Object_Cache::stats()
2347 */
2348 function wp_cache_stats()
2349 {
2350 global $wp_object_cache;
2351 $wp_object_cache->stats();
2352 }
2353
2354 /**
2355 * @see WP_Object_Cache::dc_remove_group()
2356 */
2357 function wp_cache_flush_group($group = 'default')
2358 {
2359 global $wp_object_cache;
2360
2361 return $wp_object_cache->dc_remove_group($group);
2362 }
2363
2364 /**
2365 * @see WP_Object_Cache::dc_remove_group_match()
2366 */
2367 function wp_cache_flush_group_match($group = 'default')
2368 {
2369 global $wp_object_cache;
2370
2371 return $wp_object_cache->dc_remove_group_match($group);
2372 }
2373