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

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