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

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

2,156 lines 60.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 (empty($group)) {
269 $group = 'default';
270 }
271
272 if (!$this->is_valid_key($key)) {
273 return false;
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 (empty($group)) {
321 $group = 'default';
322 }
323
324 if (!$this->is_valid_key($key)) {
325 return false;
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 (empty($group)) {
355 $group = 'default';
356 }
357
358 if (!$this->is_valid_key($key)) {
359 return false;
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 (empty($group)) {
425 $group = 'default';
426 }
427
428 if (!$this->is_valid_key($key)) {
429 return false;
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 (empty($group)) {
485 $group = 'default';
486 }
487
488 if (!$this->is_valid_key($key)) {
489 return false;
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 (empty($group)) {
535 $group = 'default';
536 }
537
538 if (!$this->is_valid_key($key)) {
539 return false;
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 (empty($group)) {
577 $group = 'default';
578 }
579
580 if (!$this->is_valid_key($key)) {
581 return false;
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 return \is_string($key) || \is_int($key);
742 }
743
744 /**
745 * is_user_logged_in.
746 */
747 private function is_user_logged_in()
748 {
749 return \function_exists('is_user_logged_in') && is_user_logged_in();
750 }
751
752 /**
753 * is_filtered_groups.
754 */
755 protected function is_filtered_groups($group, $key)
756 {
757 if (!\is_array($this->filtered_groups) || !isset($this->filtered_groups[$group])) {
758 return false;
759 }
760
761 if (false === $this->filtered_groups[$group]) {
762 $this->filtered_groups[$group][] = $key;
763 $this->filtered_groups[$group] = array_unique($this->filtered_groups[$group]);
764
765 return true;
766 }
767
768 if (\in_array($key, $this->filtered_groups[$group])) {
769 return true;
770 }
771
772 return false;
773 }
774
775 /**
776 * flush_filtered_groups.
777 */
778 private function flush_filtered_groups($hook, $args)
779 {
780 if (!\is_array($this->filtered_groups)) {
781 return false;
782 }
783
784 foreach ($this->filtered_groups as $group => $keys) {
785 if (empty($keys) || !\is_array($keys)) {
786 continue;
787 }
788
789 $keys = array_unique($keys);
790 foreach ($keys as $key) {
791 $this->delete($key, $group);
792 $this->dc_log('flush', '000000000000-'.$this->item_hash(__FUNCTION__), $group.':'.$key);
793 }
794 }
795
796 return true;
797 }
798
799 /**
800 * maybe_expire.
801 */
802 private function maybe_expire($group, $expire = 0, $key = '')
803 {
804 if (empty($expire)) {
805 $expire = 0;
806 }
807
808 $expire = $this->fs()->sanitize_timestamp($expire);
809 $maxttl = $this->cache_maxttl;
810
811 if (0 === $expire && $maxttl < 2419200) {
812 if (\in_array($group, ['site-transient', 'transient'])) {
813 if ('site-transient' === $group && \in_array($key, ['update_plugins', 'update_themes', 'update_core', '_woocommerce_helper_updates'])) {
814 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
815 } elseif ('transient' === $group && 'health-check-site-status-result' === $key) {
816 $expire = 0; // to check with is_data_uptodate
817 } else {
818 $expire = $maxttl < 604800 ? 604800 : $maxttl; // 7d
819 }
820 } elseif (\in_array($group, ['options', 'site-options'])) {
821 $expire = $maxttl < 1209600 ? 1209600 : $maxttl; // 14d
822 } elseif (\in_array($group, ['terms', 'posts', 'post_meta', 'comments'])) {
823 $expire = $maxttl < 1209600 ? 1209600 : $maxttl; // 14d
824
825 // wp stale cache
826 // prefix:md5hash:microtime
827 if (false !== strpos($key, ':') && @preg_match('@(.*):([a-z0-9]{32}):([0-9\. ]+)$@', $key)) {
828 $expire = $maxttl < 345600 ? $maxttl : 345600; // 4d
829 }
830
831 // advcpost
832 // docketcache-post-timestamp
833 } elseif (false !== strpos($group, 'docketcache-post-')) {
834 $expire = $maxttl < 345600 ? $maxttl : 345600; // 4d
835
836 // woocommerce stale cache
837 // wc_cache_0.72953700 1651592702
838 } elseif (false !== strpos($key, 'wc_cache_') && @preg_match('@^wc_cache_([0-9\. ]+)_@', $key)) {
839 $expire = $maxttl < 345600 ? $maxttl : 345600; // 4d
840 }
841 }
842
843 // if 0 let's gc handle it by comparing file mtime.
844 return $expire;
845 }
846
847 /**
848 * get_item_hash.
849 */
850 private function get_item_hash($file)
851 {
852 return basename($file, '.php');
853 }
854
855 /**
856 * item_hash.
857 */
858 private function item_hash($str, $length = 12)
859 {
860 if (!$this->is_valid_key($str)) {
861 $str = serialize($str);
862 }
863
864 if (empty($length)) {
865 return md5($str);
866 }
867
868 return substr(md5($str), 0, $length);
869 }
870
871 /**
872 * get_file_path.
873 */
874 private function get_file_path($key, $group)
875 {
876 $hash_group = $this->item_hash($group);
877 $hash_key = $this->item_hash($key);
878
879 $index = $hash_group.'-'.$hash_key;
880
881 if ($this->cf()->is_dcfalse('CHUNKCACHEDIR')) {
882 return $this->cache_path.$index.'.php';
883 }
884
885 $chunk_path = $this->fs()->get_chunk_path($hash_group, $hash_key);
886
887 return $this->cache_path.$chunk_path.$index.'.php';
888 }
889
890 /**
891 * skip_stats.
892 */
893 private function skip_stats($group, $key = '')
894 {
895 if ($this->is_non_persistent_groups($group)) {
896 return true;
897 }
898
899 return $this->cf()->is_dcfalse('LOG_ALL') && $this->fs()->is_docketcachegroup($group);
900 }
901
902 /**
903 * is_data_uptodate.
904 */
905 private function is_data_uptodate($key, $group, $data, $data_serialized = null)
906 {
907 $file = $this->get_file_path($key, $group);
908 $data_p = $this->fs()->cache_get($file);
909 if (false === $data_p || !isset($data_p['data'])) {
910 return false;
911 }
912
913 $data_p = $data_p['data'];
914 $data_p_type = \gettype($data_p);
915 $data_type = \gettype($data);
916 $doserialize = 'array' === $data_type || 'object' === $data_type;
917
918 if ($data_p_type !== $data_type) {
919 return false;
920 }
921
922 if (!$doserialize && ((false !== strpos($data_type, 'string') && 0 === strcmp($data_p, $data)) || $data_p === $data)) {
923 return true;
924 }
925
926 // @note 2122: use md5, serialize can be large.
927 if ($doserialize) {
928 $data_ps = !empty($data_serialized) ? $data_serialized : @serialize($data_p);
929 if (@md5($data_serialized) === @md5(@serialize($data))) {
930 return true;
931 }
932 }
933
934 return false;
935 }
936
937 /**
938 * fs.
939 */
940 private function fs()
941 {
942 static $inst;
943 if (!\is_object($inst)) {
944 $inst = new Nawawi\DocketCache\Filesystem();
945 }
946
947 return $inst;
948 }
949
950 /**
951 * cf.
952 */
953 private function cf()
954 {
955 static $inst;
956 if (!\is_object($inst)) {
957 $inst = new Nawawi\DocketCache\Constans();
958 }
959
960 return $inst;
961 }
962
963 /**
964 * dc_key.
965 */
966 private function dc_key($key, $group)
967 {
968 if ($this->multisite && !\array_key_exists($group, $this->global_groups)) {
969 $key = $this->blog_prefix.$key;
970 }
971
972 return $key;
973 }
974
975 /**
976 * dc_log.
977 */
978 private function dc_log($tag, $id, $data)
979 {
980 if ($this->cf()->is_dcfalse('LOG')) {
981 return false;
982 }
983
984 if ($this->skip_stats($data)) {
985 return false;
986 }
987
988 if ($this->cf()->is_dcfalse('LOG_ALL')) {
989 if (!\in_array($tag, ['hit', 'miss'])) {
990 return false;
991 }
992
993 if (false !== strpos($data, 'user') && @preg_match('@^user(s|email|logins|_meta)\:.*@', $data)) {
994 return false;
995 }
996 }
997
998 $caller = '';
999 if (!empty($_SERVER['REQUEST_URI'])) {
1000 $caller = $_SERVER['REQUEST_URI'];
1001 } elseif ($this->cf()->is_dctrue('WPCLI')) {
1002 $caller = 'wp-cli';
1003 }
1004
1005 if (false !== strpos($caller, '?page=docket-cache')) {
1006 return false;
1007 }
1008
1009 static $duplicate = [];
1010
1011 $buff = $this->item_hash($tag.$id.$data.$caller);
1012 if (isset($duplicate[$buff])) {
1013 return false;
1014 }
1015
1016 $duplicate[$buff] = 1;
1017
1018 return $this->fs()->log($tag, $id, $data, $caller);
1019 }
1020
1021 /**
1022 * dc_flush.
1023 */
1024 private function dc_flush()
1025 {
1026 $dir = $this->cache_path;
1027 $is_timeout = false;
1028 $cnt = $this->fs()->cachedir_flush($dir, false, $is_timeout);
1029 $logkey = '000000000000-'.$this->item_hash(__FUNCTION__);
1030
1031 if ($is_timeout) {
1032 $this->dc_log('err', $logkey, 'Process aborted. Reached maximum execution time. Total cache flushed: '.$cnt);
1033
1034 return false;
1035 }
1036
1037 if (false === $cnt) {
1038 $this->dc_log('err', $logkey, 'Cache could not be flushed');
1039
1040 return false;
1041 }
1042
1043 if ($cnt > 0) {
1044 $this->dc_log('flush', $logkey, 'Total cache flushed: '.$cnt);
1045 }
1046
1047 return true;
1048 }
1049
1050 /**
1051 * dc_remove.
1052 */
1053 private function dc_remove($key, $group)
1054 {
1055 $file = $this->get_file_path($key, $group);
1056 $this->fs()->unlink($file, false);
1057 $this->dc_log('del', $this->get_item_hash($file), $group.':'.$key);
1058 }
1059
1060 /**
1061 * dc_remove_group.
1062 */
1063 public function dc_remove_group($group)
1064 {
1065 $total = 0;
1066 if (!$this->fs()->is_docketcachedir($this->cache_path)) {
1067 return $total;
1068 }
1069
1070 $pattern = '@^'.$this->item_hash($group).'\-([a-z0-9]{12})\.php$@';
1071
1072 if (\is_array($group) && !empty($group)) {
1073 $groups = array_map(function ($name) {
1074 return $this->item_hash($name);
1075 }, $group);
1076
1077 $pattern = '@^('.implode('|', $groups).")\-([a-z0-9]{12})\.php$@";
1078 $group = implode(',', $group);
1079 }
1080
1081 $slowdown = 0;
1082 foreach ($this->fs()->scanfiles($this->cache_path, null, $pattern) as $object) {
1083 if ($object->isFile()) {
1084 $fx = $object->getPathName();
1085 $fn = $object->getFileName();
1086 $this->fs()->unlink($fx, true);
1087 $this->dc_log('flush', $this->get_item_hash($fx), $group.':*');
1088 ++$total;
1089 unset($this->cache[$group]);
1090 }
1091
1092 if ($slowdown > 10) {
1093 $slowdown = 0;
1094 usleep(5000);
1095 }
1096
1097 ++$slowdown;
1098
1099 if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) {
1100 break;
1101 }
1102 }
1103
1104 return $total;
1105 }
1106
1107 /**
1108 * dc_remove_group_match.
1109 */
1110 public function dc_remove_group_match($group)
1111 {
1112 $total = 0;
1113 if (!$this->fs()->is_docketcachedir($this->cache_path)) {
1114 return $total;
1115 }
1116
1117 $slowdown = 0;
1118 $pattern = '@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@';
1119 foreach ($this->fs()->scanfiles($this->cache_path, null, $pattern) as $object) {
1120 if ($object->isFile()) {
1121 $fx = $object->getPathName();
1122 $data = $this->fs()->cache_get($fx);
1123 if (!empty($data) && !empty($data['group'])) {
1124 $match = $data['group'];
1125
1126 if (\is_array($group) && !empty($group)) {
1127 foreach ($group as $grp) {
1128 if ($grp === substr($match, 0, \strlen($grp))) {
1129 $this->fs()->unlink($fx, true);
1130 $this->dc_log('flush', $this->get_item_hash($fx), $match.':*');
1131 unset($this->cache[$match]);
1132
1133 ++$total;
1134 }
1135 }
1136 } else {
1137 if ($group === substr($match, 0, \strlen($group))) {
1138 $this->fs()->unlink($fx, true);
1139 $this->dc_log('flush', $this->get_item_hash($fx), $match.':*');
1140 unset($this->cache[$match]);
1141
1142 ++$total;
1143 }
1144 }
1145 }
1146 unset($data);
1147 }
1148
1149 if ($slowdown > 10) {
1150 $slowdown = 0;
1151 usleep(5000);
1152 }
1153
1154 ++$slowdown;
1155
1156 if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) {
1157 break;
1158 }
1159 }
1160
1161 return $total;
1162 }
1163
1164 /**
1165 * dc_stalecache_filter.
1166 */
1167 private function dc_stalecache_filter($key, $group)
1168 {
1169 if ('wc_' === substr($key, 0, 3) && '_cache_prefix' === substr($key, -13)) {
1170 // get previous usec
1171 $usec = $this->get('wc_'.$group.'_cache_prefix', $group);
1172 if ($usec) {
1173 $val = 'wc_cache:'.$group.':'.$usec;
1174 $this->stalecache_list[md5($val)] = $val;
1175 }
1176 } elseif ('last_changed' === $key) {
1177 // get previous usec
1178 $usec = $this->get('last_changed', $group);
1179 if ($usec) {
1180 $val = 'last_changed:'.$group.':'.$usec;
1181 $this->stalecache_list[md5($val)] = $val;
1182 }
1183
1184 // can't capture by last_changed.
1185 // we compare key prefix and timestamp.
1186 } elseif (false !== strpos($key, ':') && @preg_match('@(.*):([a-z0-9]{32}):([0-9\. ]+)$@', $key, $mm)) {
1187 $val = 'after:'.$group.':'.$mm[3].':'.$mm[1];
1188 $this->stalecache_list[md5($val)] = $val;
1189 }
1190 }
1191
1192 /**
1193 * advcpost_stalecache_se.
1194 */
1195 public function add_stalecache($lists)
1196 {
1197 if ($this->is_stalecache && !empty($lists) && \is_array($lists)) {
1198 $this->stalecache_list = array_merge($this->stalecache_list, $lists);
1199 }
1200 }
1201
1202 /**
1203 * dc_get.
1204 */
1205 private function dc_get($key, $group, $is_raw = false)
1206 {
1207 $file = $this->get_file_path($key, $group);
1208 $logkey = $this->get_item_hash($file);
1209
1210 $data = $this->fs()->cache_get($file);
1211 if (false === $data) {
1212 if (!$this->skip_stats($group)) {
1213 ++$this->cache_misses;
1214
1215 $this->dc_log('miss', $logkey, $group.':'.$key);
1216 }
1217
1218 return false;
1219 }
1220
1221 $is_timeout = false;
1222 if (!empty($data['timeout']) && $this->fs()->valid_timestamp($data['timeout']) && time() >= $data['timeout']) {
1223 $this->dc_log('exp', $logkey, $group.':'.$key);
1224 $this->fs()->unlink($file, false);
1225 $is_timeout = true;
1226 }
1227
1228 // incase gc not run
1229 if (!$is_timeout && !empty($this->cache_maxttl) && !empty($data['timestamp']) && $this->fs()->valid_timestamp($data['timestamp'])) {
1230 $maxttl = time() - $this->cache_maxttl;
1231 if ($data['timestamp'] < $maxttl) {
1232 $this->dc_log('exp', $logkey, $group.':'.$key);
1233 $this->fs()->unlink($file, true); // true = delete it instead of truncate
1234 }
1235 }
1236
1237 if (!$this->skip_stats($group)) {
1238 ++$this->cache_hits;
1239 $this->dc_log('hit', $logkey, $group.':'.$key);
1240 }
1241
1242 // If the transient does not exist, does not have a value, or has expired, then the return value will be false.
1243 if (!empty($data['group']) && $this->fs()->is_transient($data['group']) && ('' === $data['data'] || $is_timeout)) {
1244 $data['data'] = false;
1245 }
1246
1247 // nwdcx_unserialize failed to convert serialize object.
1248 // we unserialize it here to get the object.
1249 if (!empty($data['data'])) {
1250 // *_serialize set at dc_save, to load it faster
1251 if (false !== strpos($data['type'], '_serialize')) {
1252 $data['data'] = unserialize($data['data']);
1253 } elseif ('string' === $data['type'] && \function_exists('maybe_unserialize')) {
1254 // old cache data
1255 $data['data'] = maybe_unserialize($data['data']);
1256 }
1257 }
1258 clearstatcache();
1259
1260 return $is_raw ? $data : $data['data'];
1261 }
1262
1263 /**
1264 * dc_code.
1265 */
1266 private function dc_code($file, $arr)
1267 {
1268 $logkey = $this->get_item_hash($file);
1269 $logpref = __FUNCTION__.'():';
1270
1271 $data = $this->fs()->export_var($arr, $error);
1272 if (false === $data) {
1273 $this->dc_log('err', $logkey, $logpref.' Failed to export var -> '.$error);
1274
1275 return false;
1276 }
1277
1278 $code = $this->fs()->code_stub($data);
1279 $stat = $this->fs()->dump($file, $code, false); // 3rd param = validate
1280
1281 if (false === $stat) {
1282 return false;
1283 }
1284
1285 if (-1 === $stat) {
1286 $this->dc_log('err', $logkey, $logpref.' Failed to write');
1287
1288 return false;
1289 }
1290
1291 // remove lock
1292 $this->fs()->validate_fatal_error_file($file);
1293
1294 return $stat;
1295 }
1296
1297 /**
1298 * dc_save.
1299 */
1300 private function dc_save($cache_key, $data, $group = 'default', $expire = 0, $key = '')
1301 {
1302 if (wp_suspend_cache_addition()) {
1303 return false;
1304 }
1305
1306 $logkey = $this->item_hash($group).'-'.$this->item_hash($cache_key);
1307 $logpref = __FUNCTION__.'():';
1308
1309 // skip save to disk, return true;
1310 if ('' === $data && $this->fs()->is_transient($group)) {
1311 if ($this->is_dev) {
1312 $this->dc_log('debug', $logkey, $group.':'.$cache_key.' '.$logpref.' Data empty');
1313 }
1314
1315 return true;
1316 }
1317
1318 if (!$this->fs()->mkdir_p($this->cache_path)) {
1319 return false;
1320 }
1321
1322 @$this->fs()->placeholder($this->cache_path);
1323
1324 $file = $this->get_file_path($cache_key, $group);
1325
1326 // chunk dir
1327 if ($this->cf()->is_dctrue('CHUNKCACHEDIR') && !$this->fs()->mkdir_p(\dirname($file))) {
1328 return false;
1329 }
1330
1331 // if $expire is larger than 0, convert it to timestamp
1332 $timeout = ($expire > 0 ? time() + $expire : 0);
1333
1334 $type = \gettype($data);
1335 if ('NULL' === $type && null === $data) {
1336 $data = '';
1337 }
1338
1339 if (!empty($data)) {
1340 if ('string' === $type) {
1341 $data = nwdcx_unserialize($data);
1342 } elseif ('array' === $type) {
1343 $data_r = nwdcx_arraymap('nwdcx_unserialize', $data);
1344
1345 if (!empty($data_r)) {
1346 $data = $data_r;
1347 }
1348 unset($data_r);
1349 }
1350 }
1351
1352 // abort if object too large
1353 $data_serialized = serialize($data);
1354 $len = \strlen(serialize($data_serialized));
1355 if ($len >= $this->cache_maxsize) {
1356 $this->dc_log('err', $logkey, $group.':'.$cache_key.' '.$logpref.' Object too large -> '.$len.'/'.$this->cache_maxsize);
1357
1358 return false;
1359 }
1360
1361 // since timeout set to timestamp.
1362 if (0 === $expire && !empty($key) && @is_file($file) && $this->is_data_uptodate($key, $group, $data, $data_serialized)) {
1363 if ($this->is_dev) {
1364 $this->dc_log('debug', $logkey, $group.':'.$cache_key.' '.$logpref.' No changes');
1365 }
1366
1367 return false;
1368 }
1369
1370 $meta = [];
1371 $meta['timestamp'] = time();
1372
1373 if ($this->multisite) {
1374 // try to avoid error-prone
1375 // in rare condition, get_current_network_id dependencies not load properly.
1376 try {
1377 $meta['network_id'] = get_current_network_id();
1378 } catch (\Throwable $e) {
1379 $meta['network_id'] = 0;
1380 }
1381 }
1382
1383 $final_type = \gettype($data);
1384 if ('string' === $final_type && nwdcx_serialized($data)) {
1385 $final_type = 'string_serialize';
1386 } elseif ('array' === $final_type) {
1387 // may lead to __PHP_Incomplete_Class
1388 // headers => Requests_Utility_CaseInsensitiveDictionary Object
1389 if (!empty($data['headers']) && \is_object($data['headers']) && false !== strpos(var_export($data['headers'], 1), 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) {
1390 $data = @serialize($data);
1391 if (nwdcx_serialized($data)) {
1392 $final_type = 'array_serialize';
1393 }
1394 }
1395 }
1396
1397 $meta['site_id'] = get_current_blog_id();
1398 $meta['group'] = $group;
1399 $meta['key'] = $cache_key;
1400 $meta['type'] = $final_type;
1401
1402 // if 0 let gc handle it by comparing file mtime
1403 // and maxttl constants.
1404 $meta['timeout'] = $timeout;
1405
1406 $meta['data'] = $data;
1407
1408 if (true === $this->dc_code($file, $meta)) {
1409 if ($this->is_dev) {
1410 $this->dc_log('debug', $logkey, $group.':'.$cache_key.' '.$logpref.' Storing to disk');
1411 }
1412
1413 return true;
1414 }
1415
1416 return false;
1417 }
1418
1419 /**
1420 * dc_update.
1421 */
1422 private function dc_update($cache_key, $data, $group)
1423 {
1424 $meta = $this->dc_get($cache_key, $group, true);
1425 if (false === $meta || !\is_array($meta) || !isset($meta['data'])) {
1426 return false;
1427 }
1428
1429 $file = $this->get_file_path($cache_key, $group);
1430 $meta['data'] = $data;
1431
1432 if (true === $this->dc_code($file, $meta)) {
1433 return true;
1434 }
1435
1436 return false;
1437 }
1438
1439 /**
1440 * dc_precache_load.
1441 */
1442 private function dc_precache_load($hash)
1443 {
1444 static $is_done = false;
1445 $logkey = $this->item_hash('docketcache-precache').'-'.$this->item_hash(__FUNCTION__);
1446 $logpref = __FUNCTION__.'():';
1447
1448 if ($is_done) {
1449 if ($this->is_dev) {
1450 $this->dc_log('debug', $logkey, $logpref.' Precache Ignored: Already loaded');
1451 }
1452
1453 return;
1454 }
1455
1456 $cached = [];
1457 $group = 'docketcache-precache';
1458 $keys = $this->get($hash, $group);
1459
1460 if (empty($keys) || !\is_array($keys)) {
1461 return;
1462 }
1463
1464 if ($this->is_dev) {
1465 $this->dc_log('debug', $logkey, $logpref.' Precache Load: Start');
1466 }
1467
1468 $this->precache_loaded[$hash] = $keys;
1469
1470 $slowdown = 0;
1471 $cnt_max = 0;
1472
1473 foreach ($keys as $cache_group => $arr) {
1474 foreach ($arr as $cache_key) {
1475 if ($cnt_max >= $this->precache_maxlist) {
1476 break 2;
1477 }
1478
1479 if (!isset($cached[$cache_key.$cache_group]) && false !== $this->get($cache_key, $cache_group)) {
1480 $cached[$cache_key.$cache_group] = 1;
1481 }
1482
1483 ++$cnt_max;
1484
1485 if ($slowdown > 10) {
1486 $slowdown = 0;
1487 usleep(1000);
1488 }
1489
1490 ++$slowdown;
1491
1492 if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) {
1493 break 2;
1494 }
1495 }
1496 }
1497
1498 if ($this->is_dev) {
1499 $this->dc_log('debug', $logkey, $logpref.' Precache Load: End -> '.\count($cached));
1500 }
1501
1502 unset($keys, $cached);
1503 $is_done = true;
1504 }
1505
1506 /**
1507 * dc_precache_set.
1508 */
1509 private function dc_precache_set($hash)
1510 {
1511 if (empty($this->precache) || !\is_array($this->precache)) {
1512 return;
1513 }
1514
1515 $group = 'docketcache-precache';
1516 $data = [];
1517 $slowdown = 0;
1518 $cnt_max = 0;
1519
1520 $logkey = $this->item_hash('docketcache-precache').'-'.$this->item_hash(__FUNCTION__);
1521 $logpref = __FUNCTION__.'():';
1522
1523 if ($this->is_dev) {
1524 $this->dc_log('debug', $logkey, $logpref.' Precache Set: Start');
1525 }
1526
1527 foreach ($this->precache as $cache_group => $cache_keys) {
1528 if ($cnt_max >= $this->precache_maxlist) {
1529 break;
1530 }
1531
1532 if ($cache_group !== $group) {
1533 $cache_keys = array_keys($cache_keys);
1534 $data[$cache_group] = $cache_keys;
1535 }
1536
1537 ++$cnt_max;
1538
1539 if ($slowdown > 10) {
1540 $slowdown = 0;
1541 usleep(100);
1542 }
1543
1544 ++$slowdown;
1545
1546 if ($this->max_execution_time > 0 && (microtime(true) - $this->wp_start_timestamp) > $this->max_execution_time) {
1547 // bypass, maybe data too big
1548 $data = [];
1549 $this->delete($hash);
1550 break;
1551 }
1552 }
1553
1554 if ($this->is_dev) {
1555 $this->dc_log('debug', $logkey, $logpref.' Precache Set: End -> '.\count($data));
1556 }
1557
1558 if (!empty($data)) {
1559 if (!empty($this->precache_loaded) && md5(serialize($this->precache_loaded[$hash])) === md5(serialize($data))) {
1560 if ($this->is_dev) {
1561 $this->dc_log('debug', $logkey, $logpref.' '.$hash.' No changes');
1562 }
1563
1564 return;
1565 }
1566
1567 $this->set($hash, $data, $group, 86400); // 1d
1568 }
1569
1570 unset($data, $hash);
1571 }
1572
1573 /**
1574 * dc_precache.
1575 */
1576 private function dc_precache()
1577 {
1578 if (!empty($_POST) || empty($_SERVER['REQUEST_URI']) || $this->cf()->is_dctrue('WPCLI')) {
1579 return;
1580 }
1581
1582 $logkey = $this->item_hash('docketcache-precache').'-'.$this->item_hash(__FUNCTION__);
1583 $logpref = __FUNCTION__.'():';
1584
1585 $req_uri = $_SERVER['REQUEST_URI'];
1586 $dostrip = !empty($_SERVER['QUERY_STRING']);
1587
1588 $intersect_key = [
1589 'docketcache_ping' => 1,
1590 'doing_wp_cron' => 1,
1591 'wc-ajax' => 1,
1592 '_fs_blog_admin' => 1,
1593 'action' => 1,
1594 'message' => 1,
1595 ];
1596
1597 if ($dostrip && !empty($_GET) && array_intersect_key($intersect_key, $_GET)) {
1598 $this->dc_log('info', $logkey, $logpref.' Bypass GET key');
1599
1600 return;
1601 }
1602
1603 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')) {
1604 $this->dc_log('info', $logkey, $logpref.' Bypass Request');
1605
1606 return;
1607 }
1608
1609 $req_host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
1610 if ('localhost' !== $req_host) {
1611 $req_host = nwdcx_fixhost($req_host);
1612 }
1613
1614 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)) {
1615 $dostrip = false;
1616 }
1617
1618 // without pretty permalink
1619 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')) {
1620 $dostrip = false;
1621 }
1622
1623 if ($dostrip) {
1624 $req_uri = strtok($req_uri, '?#');
1625 }
1626
1627 if (empty($req_host) || empty($req_uri)) {
1628 return;
1629 }
1630
1631 $this->precache_hashkey = $this->item_hash($req_host.$req_uri);
1632
1633 $this->dc_precache_load($this->precache_hashkey);
1634 }
1635
1636 /**
1637 * dc_close.
1638 * reference:
1639 * wp_cache_close()
1640 * wp-includes/load.php -> shutdown_action_hook().
1641 */
1642 public function dc_close()
1643 {
1644 $this->fs()->close_buffer();
1645 static $is_done = false;
1646
1647 if (!$is_done) {
1648 if ($this->is_precache && !empty($this->precache_hashkey) && $this->fs()->close_buffer()) {
1649 $this->dc_precache_set($this->precache_hashkey);
1650 }
1651
1652 if ($this->is_stalecache && !empty($this->stalecache_list)) {
1653 $this->add('items', $this->stalecache_list, 'docketcache-stalecache', 3600);
1654 }
1655
1656 $is_done = true;
1657 }
1658 }
1659
1660 /**
1661 * dc_init.
1662 */
1663 private function dc_init()
1664 {
1665 $this->wp_start_timestamp = \defined('WP_START_TIMESTAMP') ? WP_START_TIMESTAMP : microtime(true);
1666 $this->max_execution_time = $this->fs()->get_max_execution_time();
1667 $this->is_dev = $this->cf()->is_dctrue('DEV');
1668
1669 if ($this->cf()->is_dcint('MAXSIZE', $dcvalue)) {
1670 if (!empty($dcvalue)) {
1671 $this->cache_maxsize = $this->fs()->sanitize_maxsize($dcvalue);
1672 }
1673 }
1674
1675 if ($this->cf()->is_dcint('MAXTTL', $dcvalue)) {
1676 if (!empty($dcvalue)) {
1677 $this->cache_maxttl = $this->fs()->sanitize_maxttl($dcvalue);
1678 }
1679 }
1680
1681 if ($this->cf()->is_dcarray('GLOBAL_GROUPS', $dcvalue)) {
1682 $this->add_global_groups($dcvalue);
1683 }
1684
1685 if ($this->cf()->is_dcarray('IGNORED_GROUPS', $dcvalue)) {
1686 $this->non_persistent_groups = $dcvalue;
1687 }
1688
1689 if ($this->cf()->is_dcarray('IGNORED_KEYS', $dcvalue)) {
1690 $this->non_persistent_keys = $dcvalue;
1691 }
1692
1693 if ($this->cf()->is_dcarray('FILTERED_GROUPS', $dcvalue)) {
1694 $this->filtered_groups = $dcvalue;
1695 }
1696
1697 if ($this->cf()->is_dcarray('IGNORED_GROUPKEY', $dcvalue)) {
1698 $this->non_persistent_groupkey = $dcvalue;
1699 }
1700
1701 if ($this->cf()->is_dcarray('IGNORED_PRECACHE', $dcvalue)) {
1702 $this->bypass_precache = $dcvalue;
1703 }
1704
1705 $this->cache_path = $this->fs()->define_cache_path($this->cf()->dcvalue('PATH'));
1706 if ($this->multisite) {
1707 $this->cache_path = nwdcx_network_dirpath($this->cache_path);
1708 }
1709
1710 if ($this->cf()->is_dctrue('WPOPTALOAD')) {
1711 $this->fs()->optimize_alloptions();
1712 }
1713
1714 add_filter(
1715 'pre_cache_alloptions',
1716 function ($alloptions) {
1717 if (isset($alloptions['cron'])) {
1718 unset($alloptions['cron']);
1719 }
1720
1721 if (isset($alloptions['litespeed_messages'])) {
1722 unset($alloptions['litespeed_messages']);
1723 }
1724
1725 if (isset($alloptions['litespeed.admin_display.messages'])) {
1726 unset($alloptions['litespeed.admin_display.messages']);
1727 }
1728
1729 return $alloptions;
1730 },
1731 \PHP_INT_MAX
1732 );
1733
1734 // litespeed admin notice
1735 add_action(
1736 'litespeed_purged_all',
1737 function () {
1738 $this->delete('alloptions', 'options');
1739 $this->delete('litespeed_messages', 'options');
1740 $this->delete('litespeed.admin_display.messages', 'options');
1741 },
1742 \PHP_INT_MAX
1743 );
1744
1745 add_action(
1746 'all_admin_notices',
1747 function () {
1748 if (\function_exists('run_litespeed_cache')) {
1749 $this->delete('litespeed_messages', 'options');
1750 $this->delete('litespeed.admin_display.messages', 'options');
1751 }
1752 },
1753 \PHP_INT_MAX
1754 );
1755
1756 foreach (['added', 'updated', 'deleted'] as $prefix) {
1757 add_action(
1758 $prefix.'_option',
1759 function ($option) {
1760 if (!wp_installing()) {
1761 $alloptions = wp_load_alloptions();
1762 if (isset($alloptions[$option])) {
1763 add_action(
1764 'shutdown',
1765 function () {
1766 $this->fs()->close_buffer();
1767 $this->delete('alloptions', 'options');
1768 },
1769 \PHP_INT_MAX - 1
1770 );
1771 }
1772 unset($alloptions);
1773 }
1774 },
1775 \PHP_INT_MAX
1776 );
1777 }
1778
1779 foreach (['activate', 'deactivate'] as $prefix) {
1780 add_action(
1781 $prefix.'_plugin',
1782 function ($plugin, $network) {
1783 if ($this->multisite) {
1784 add_action(
1785 'shutdown',
1786 function () {
1787 $this->fs()->close_buffer();
1788 $this->delete(get_current_network_id().':active_sitewide_plugins', 'site-options');
1789 },
1790 \PHP_INT_MAX - 1
1791 );
1792 }
1793 add_action(
1794 'shutdown',
1795 function () {
1796 $this->fs()->close_buffer();
1797 $this->delete('uninstall_plugins', 'options');
1798 },
1799 \PHP_INT_MAX - 1
1800 );
1801 },
1802 \PHP_INT_MAX,
1803 2
1804 );
1805 }
1806
1807 // filtered groups hooks
1808 if (\is_array($this->filtered_groups)) {
1809 add_action(
1810 'save_post',
1811 function ($post_id, $post, $update) {
1812 $this->flush_filtered_groups('save_post', [$post_id, $post, $update]);
1813 },
1814 \PHP_INT_MIN,
1815 3
1816 );
1817
1818 add_action(
1819 'edit_post',
1820 function ($post_id, $post) {
1821 $this->flush_filtered_groups('edit_post', [$post_id, $post]);
1822 },
1823 \PHP_INT_MIN,
1824 2
1825 );
1826
1827 add_action(
1828 'delete_post',
1829 function ($post_id) {
1830 $this->flush_filtered_groups('delete_post', [$post_id]);
1831 },
1832 \PHP_INT_MIN
1833 );
1834 }
1835
1836 if ($this->cf()->is_dctrue('OPTWPQUERY')) {
1837 add_action(
1838 'pre_get_posts',
1839 function (&$args) {
1840 if (\is_object($args)) {
1841 $args->no_found_rows = true;
1842 $args->order = 'ASC';
1843 } elseif (\is_array($args)) {
1844 $args['no_found_rows'] = true;
1845 $args['order'] = 'ASC';
1846 }
1847 },
1848 \PHP_INT_MIN
1849 );
1850
1851 add_action(
1852 'parse_query',
1853 function (&$args) {
1854 if (\is_object($args)) {
1855 $args->no_found_rows = true;
1856 $args->order = 'ASC';
1857 } elseif (\is_array($args)) {
1858 $args['no_found_rows'] = true;
1859 $args['order'] = 'ASC';
1860 }
1861 },
1862 \PHP_INT_MIN
1863 );
1864
1865 add_action(
1866 'pre_get_users',
1867 function ($wpq) {
1868 if (nwdcx_wpdb($wpdb) && !empty($wpq->query_vars['count_total'])) {
1869 $wpq->query_vars['count_total'] = false;
1870 $wpq->query_vars['nwdcx_count_total'] = true;
1871 }
1872 },
1873 \PHP_INT_MIN
1874 );
1875
1876 add_action(
1877 'pre_user_query',
1878 function ($wpq) {
1879 if (nwdcx_wpdb($wpdb) && !empty($wpq->query_vars['nwdcx_count_total'])) {
1880 unset($wpq->query_vars['nwdcx_count_total']);
1881 $sql = "SELECT COUNT(*) {$wpq->query_from} {$wpq->query_where}";
1882 $wpq->total_users = $wpdb->get_var($sql);
1883 }
1884 },
1885 \PHP_INT_MIN
1886 );
1887 }
1888
1889 // html comment
1890 $this->add_signature = false;
1891 if ($this->cf()->is_dctrue('SIGNATURE')) {
1892 add_action(
1893 'wp_head',
1894 function () {
1895 if (!$this->is_user_logged_in()) {
1896 $this->add_signature = true;
1897 }
1898 },
1899 \PHP_INT_MIN
1900 );
1901
1902 add_action(
1903 'shutdown',
1904 function () {
1905 if ($this->add_signature && !$this->is_user_logged_in()) {
1906 echo apply_filters('docketcache/filter/signature/htmlfooter', "\n<!-- Performance optimized by Docket Cache: https://wordpress.org/plugins/docket-cache -->\n");
1907 $this->fs()->close_buffer();
1908 }
1909 },
1910 \PHP_INT_MAX
1911 );
1912 }
1913
1914 // stalecache
1915 $this->is_stalecache = $this->cf()->is_dctrue('FLUSH_STALECACHE');
1916
1917 // load precache
1918 $this->is_precache = $this->cf()->is_dctrue('PRECACHE');
1919 if ($this->is_precache) {
1920 $this->precache_maxlist = (int) $this->cf()->dcvalue('PRECACHE_MAXLIST');
1921 $this->dc_precache();
1922 }
1923
1924 // maxfile
1925 $maxfile = (int) $this->fs()->sanitize_maxfile($this->cf()->dcvalue('MAXFILE'));
1926 $numfile = (int) $this->get('numfile', 'docketcache-gc');
1927 $numfile = $numfile > 0 ? $numfile : 0;
1928 if ($numfile > $maxfile) {
1929 wp_suspend_cache_addition(true);
1930 }
1931 }
1932 }
1933
1934 /**
1935 * Sets up Object Cache Global and assigns it.
1936 *
1937 * @global WP_Object_Cache $wp_object_cache
1938 */
1939 function wp_cache_init()
1940 {
1941 global $wp_object_cache;
1942 if (!($wp_object_cache instanceof WP_Object_Cache)) {
1943 $wp_object_cache = new WP_Object_Cache();
1944 }
1945 }
1946
1947 /**
1948 * @see WP_Object_Cache::add()
1949 */
1950 function wp_cache_add($key, $data, $group = '', $expire = 0)
1951 {
1952 global $wp_object_cache;
1953
1954 return $wp_object_cache->add($key, $data, $group, (int) $expire);
1955 }
1956
1957 /**
1958 * @see WP_Object_Cache::add_multiple()
1959 */
1960 function wp_cache_add_multiple(array $data, $group = '', $expire = 0)
1961 {
1962 global $wp_object_cache;
1963
1964 return $wp_object_cache->add_multiple($data, $group, $expire);
1965 }
1966
1967 /**
1968 * @see WP_Object_Cache::replace()
1969 */
1970 function wp_cache_replace($key, $data, $group = '', $expire = 0)
1971 {
1972 global $wp_object_cache;
1973
1974 return $wp_object_cache->replace($key, $data, $group, (int) $expire);
1975 }
1976
1977 /**
1978 * @see WP_Object_Cache::set()
1979 */
1980 function wp_cache_set($key, $data, $group = '', $expire = 0)
1981 {
1982 global $wp_object_cache;
1983
1984 return $wp_object_cache->set($key, $data, $group, (int) $expire);
1985 }
1986
1987 /**
1988 * @see WP_Object_Cache::set_multiple()
1989 */
1990 function wp_cache_set_multiple(array $data, $group = '', $expire = 0)
1991 {
1992 global $wp_object_cache;
1993
1994 return $wp_object_cache->set_multiple($data, $group, $expire);
1995 }
1996
1997 /**
1998 * @see WP_Object_Cache::get()
1999 */
2000 function wp_cache_get($key, $group = '', $force = false, &$found = null)
2001 {
2002 global $wp_object_cache;
2003
2004 return $wp_object_cache->get($key, $group, $force, $found);
2005 }
2006
2007 /**
2008 * @see WP_Object_Cache::get_multiple()
2009 */
2010 function wp_cache_get_multiple(array $keys, $group = '', $force = false)
2011 {
2012 global $wp_object_cache;
2013
2014 return $wp_object_cache->get_multiple($keys, $group, $force);
2015 }
2016
2017 /**
2018 * @see WP_Object_Cache::delete()
2019 */
2020 function wp_cache_delete($key, $group = '')
2021 {
2022 global $wp_object_cache;
2023
2024 return $wp_object_cache->delete($key, $group);
2025 }
2026
2027 /**
2028 * @see WP_Object_Cache::delete_multiple()
2029 */
2030 function wp_cache_delete_multiple(array $keys, $group = '')
2031 {
2032 global $wp_object_cache;
2033
2034 return $wp_object_cache->delete_multiple($keys, $group);
2035 }
2036
2037 /**
2038 * @see WP_Object_Cache::incr()
2039 */
2040 function wp_cache_incr($key, $offset = 1, $group = '')
2041 {
2042 global $wp_object_cache;
2043
2044 return $wp_object_cache->incr($key, $offset, $group);
2045 }
2046
2047 /**
2048 * @see WP_Object_Cache::decr()
2049 */
2050 function wp_cache_decr($key, $offset = 1, $group = '')
2051 {
2052 global $wp_object_cache;
2053
2054 return $wp_object_cache->decr($key, $offset, $group);
2055 }
2056
2057 /**
2058 * @see WP_Object_Cache::flush()
2059 */
2060 function wp_cache_flush()
2061 {
2062 global $wp_object_cache;
2063
2064 return $wp_object_cache->flush();
2065 }
2066
2067 /**
2068 * @see WP_Object_Cache::flush()
2069 */
2070 function wp_cache_flush_runtime()
2071 {
2072 global $wp_object_cache;
2073
2074 return $wp_object_cache->flush(true);
2075 }
2076
2077 /**
2078 * @see WP_Object_Cache::dc_close()
2079 */
2080 function wp_cache_close()
2081 {
2082 global $wp_object_cache;
2083
2084 $wp_object_cache->dc_close();
2085
2086 return true;
2087 }
2088
2089 /**
2090 * @see WP_Object_Cache::add_non_persistent_groups()
2091 */
2092 function wp_cache_add_non_persistent_groups($groups)
2093 {
2094 global $wp_object_cache;
2095 $wp_object_cache->add_non_persistent_groups($groups);
2096 }
2097
2098 /**
2099 * @see WP_Object_Cache::switch_to_blog()
2100 */
2101 function wp_cache_switch_to_blog($blog_id)
2102 {
2103 global $wp_object_cache;
2104
2105 $wp_object_cache->switch_to_blog($blog_id);
2106 }
2107
2108 /**
2109 * @see WP_Object_Cache::add_global_groups()
2110 */
2111 function wp_cache_add_global_groups($groups)
2112 {
2113 global $wp_object_cache;
2114
2115 $wp_object_cache->add_global_groups($groups);
2116 }
2117
2118 /**
2119 * @see WP_Object_Cache::stats()
2120 */
2121 function wp_cache_stats()
2122 {
2123 global $wp_object_cache;
2124 $wp_object_cache->stats();
2125 }
2126
2127 /**
2128 * @see WP_Object_Cache::dc_remove_group()
2129 */
2130 function wp_cache_flush_group($group = 'default')
2131 {
2132 global $wp_object_cache;
2133
2134 return $wp_object_cache->dc_remove_group($group);
2135 }
2136
2137 /**
2138 * @see WP_Object_Cache::dc_remove_group_match()
2139 */
2140 function wp_cache_flush_group_match($group = 'default')
2141 {
2142 global $wp_object_cache;
2143
2144 return $wp_object_cache->dc_remove_group_match($group);
2145 }
2146
2147 /**
2148 * @see WP_Object_Cache::add_stalecache()
2149 */
2150 function wp_cache_add_stalecache($lists)
2151 {
2152 global $wp_object_cache;
2153
2154 return $wp_object_cache->add_stalecache($lists);
2155 }
2156