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 / src / Becache.php

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

395 lines 12.5 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
11 /*
12 * This class is a minimal version of cache.php, only use for object-cache.php.
13 *
14 * Reference:
15 * wp-content/plugins/docket-cache/includes/cache.php
16 * wp-content/plugins/docket-cache/includes/object-cache.php
17 */
18
19 namespace Nawawi\DocketCache;
20
21 \defined('ABSPATH') || exit;
22
23 class Becache
24 {
25 private $cache_path;
26 private $cache_maxsize = 3145728;
27 private $cache_maxttl = 345600;
28 private $multisite = false;
29 private $blog_prefix;
30 private $qlimit = 5000;
31 private static $inst;
32 private $max_execution_time = 0;
33
34 public function __construct()
35 {
36 $this->multisite = \function_exists('is_multisite') && is_multisite();
37 $this->blog_prefix = $this->switch_to_blog(get_current_blog_id());
38
39 $this->cache_path = $this->fs()->define_cache_path($this->cf()->dcvalue('PATH'));
40 if (\function_exists('is_multisite') && is_multisite()) {
41 $this->cache_path = nwdcx_network_dirpath($this->cache_path);
42 }
43
44 if ($this->cf()->is_dcint('MAXSIZE', $dcvalue)) {
45 if (!empty($dcvalue)) {
46 $this->cache_maxsize = $this->fs()->sanitize_maxsize($dcvalue);
47 }
48 }
49
50 if ($this->cf()->is_dcint('MAXTTL', $dcvalue)) {
51 if (!empty($dcvalue)) {
52 $this->cache_maxttl = $this->fs()->sanitize_maxttl($dcvalue);
53 }
54 }
55
56 $this->max_execution_time = $this->fs()->get_max_execution_time();
57 }
58
59 public static function export()
60 {
61 if (!isset(self::$inst)) {
62 self::$inst = new self();
63 }
64
65 self::$inst->export_transient();
66 self::$inst->export_alloptions();
67 }
68
69 private function fs()
70 {
71 static $inst;
72 if (!\is_object($inst)) {
73 $inst = new Filesystem();
74 }
75
76 return $inst;
77 }
78
79 private function cf()
80 {
81 static $inst;
82 if (!\is_object($inst)) {
83 $inst = new Constans();
84 }
85
86 return $inst;
87 }
88
89 private function cache_key($key, $group)
90 {
91 if ($this->multisite && 'site-transient' === $group) {
92 $key = $this->blog_prefix.$key;
93 }
94
95 return $key;
96 }
97
98 private function item_hash($str, $length = 12)
99 {
100 if (empty($length)) {
101 return md5($str);
102 }
103
104 return substr(md5($str), 0, $length);
105 }
106
107 private function get_file_path($key, $group)
108 {
109 $hash_group = $this->item_hash($group);
110 $hash_key = $this->item_hash($key);
111
112 $index = $hash_group.'-'.$hash_key;
113
114 if ($this->cf()->is_dcfalse('CHUNKCACHEDIR')) {
115 return $this->cache_path.$index.'.php';
116 }
117
118 $chunk_path = $this->fs()->get_chunk_path($hash_group, $hash_key);
119
120 return $this->cache_path.$chunk_path.$index.'.php';
121 }
122
123 private function dump_code($file, $arr)
124 {
125 $data = $this->fs()->export_var($arr, $error);
126 if (false === $data) {
127 return false;
128 }
129
130 $code = $this->fs()->code_stub($data);
131 $stat = $this->fs()->dump($file, $code, false);
132
133 if (false === $stat) {
134 return false;
135 }
136
137 if (-1 === $stat) {
138 return false;
139 }
140
141 $this->fs()->validate_fatal_error_file($file);
142
143 return $stat;
144 }
145
146 private function maybe_expire($group, $expire = 0, $key = '')
147 {
148 if (empty($expire)) {
149 $expire = 0;
150 }
151
152 $expire = $this->fs()->sanitize_timestamp($expire);
153 $maxttl = $this->cache_maxttl;
154
155 if (0 === $expire && $maxttl < 2419200) {
156 if ($this->fs()->is_transient($group)) {
157 if ('site-transient' === $group && \in_array($key, ['update_plugins', 'update_themes', 'update_core', '_woocommerce_helper_updates'])) {
158 $expire = $maxttl < 2419200 ? 2419200 : $maxttl; // 28d
159 } elseif ('transient' === $group && 'health-check-site-status-result' === $key) {
160 $expire = 0; // to check with is_data_uptodate
161 } else {
162 $expire = $maxttl < 604800 ? 604800 : $maxttl; // 7d
163 }
164 } elseif (\in_array($group, ['terms', 'posts', 'post_meta', 'options', 'site-options', 'comments'])) {
165 $expire = $maxttl < 1209600 ? 1209600 : $maxttl; // 14d
166
167 // woocommerce stale cache
168 // wc_cache_0.72953700 1651592702
169 } elseif ('wc_cache_' === substr($key, 0, 9) && preg_match('@^wc_cache_([0-9\. ]+)_@', $key)) {
170 $expire = 86400; // 1d
171 }
172 }
173
174 return $expire;
175 }
176
177 private function store_cache($key, $data, $group, $expire = 0)
178 {
179 if (!$this->fs()->mkdir_p($this->cache_path)) {
180 return false;
181 }
182
183 $expire = $this->maybe_expire($group, $expire, $key);
184
185 $cache_key = $this->cache_key($key, $group);
186 $file = $this->get_file_path($cache_key, $group);
187
188 // chunk dir
189 if ($this->cf()->is_dctrue('CHUNKCACHEDIR') && !$this->fs()->mkdir_p(\dirname($file))) {
190 return false;
191 }
192
193 $timeout = ($expire > 0 ? time() + $expire : 0);
194
195 $type = \gettype($data);
196 if ('NULL' === $type && null === $data) {
197 $data = '';
198 }
199
200 if (!empty($data)) {
201 if ('string' === $type) {
202 $data = nwdcx_unserialize($data);
203 } elseif ('array' === $type) {
204 $data_r = nwdcx_arraymap('nwdcx_unserialize', $data);
205
206 if (!empty($data_r)) {
207 $data = $data_r;
208 }
209 unset($data_r);
210 }
211 }
212
213 $len = \strlen(serialize($data));
214 if ($len >= $this->cache_maxsize) {
215 return false;
216 }
217
218 $meta = [];
219 $meta['timestamp'] = time();
220
221 if ($this->multisite) {
222 try {
223 $meta['network_id'] = get_current_network_id();
224 } catch (\Throwable $e) {
225 $meta['network_id'] = 0;
226 }
227 }
228
229 $final_type = \gettype($data);
230 if ('array' === $final_type) {
231 // http remote request
232 // headers => Requests_Utility_CaseInsensitiveDictionary Object
233 if (!empty($data['headers']) && \is_object($data['headers']) && false !== strpos(var_export($data['headers'], 1), 'Requests_Utility_CaseInsensitiveDictionary::__set_state')) {
234 $data = @serialize($data);
235 if (nwdcx_serialized($data)) {
236 $final_type = 'string';
237 }
238 }
239 }
240
241 $meta['site_id'] = get_current_blog_id();
242 $meta['group'] = $group;
243 $meta['key'] = $cache_key;
244 $meta['type'] = $final_type;
245 $meta['timeout'] = $timeout;
246 $meta['data'] = $data;
247
248 if (true === $this->dump_code($file, $meta)) {
249 return true;
250 }
251
252 return false;
253 }
254
255 public function export_transient()
256 {
257 if (!nwdcx_wpdb($wpdb)) {
258 return false;
259 }
260
261 $suppress = $wpdb->suppress_errors(true);
262 $collect = [];
263
264 $results = $wpdb->get_results('SELECT `option_id`,`option_name`,`option_value` FROM `'.$wpdb->options.'` WHERE `option_name` LIKE "_transient_%" OR `option_name` LIKE "_site_transient_%" ORDER BY `option_id` ASC LIMIT '.$this->qlimit, ARRAY_A);
265 if (!empty($results) && \is_array($results)) {
266 while ($row = @array_shift($results)) {
267 if ($this->max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $this->max_execution_time) {
268 $collect = [];
269 break;
270 }
271
272 // ignore empty value
273 if ('' === $row['option_value']) {
274 continue;
275 }
276
277 $key = @preg_replace('@^(_site)?(_transient)(_timeout)?_@', '', $row['option_name']);
278 if (!isset($collect[$key])) {
279 $collect[$key] = ['value' => '', 'group' => 'transient', 'timeout' => 0];
280 }
281
282 $collect[$key]['value'] = $row['option_value'];
283 if ('_site_' === substr($row['option_name'], 0, 6)) {
284 $collect[$key]['group'] = 'site-transient';
285 }
286
287 if (false !== strpos($row['option_name'], '_transient_timeout_')) {
288 $collect[$key]['timeout'] = (int) $row['option_value'];
289 }
290 }
291
292 if (!empty($collect)) {
293 foreach ($collect as $key => $arr) {
294 if ($this->max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $this->max_execution_time) {
295 break;
296 }
297
298 $timeout = isset($arr['timeout']) ? $arr['timeout'] : 0;
299 $this->store_cache($key, $arr['value'], $arr['group'], $timeout);
300 }
301 }
302 }
303
304 $collect = [];
305
306 if ($this->multisite && isset($wpdb->sitemeta)) {
307 $results = $wpdb->get_results('SELECT `meta_id`,`meta_key`,`meta_value` FROM `'.$wpdb->sitemeta.'` WHERE `meta_key` LIKE "_site_transient_%" ORDER BY `meta_id` ASC LIMIT '.$this->qlimit, ARRAY_A);
308 if (!empty($results) && \is_array($results)) {
309 while ($row = @array_shift($results)) {
310 if ($this->max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $this->max_execution_time) {
311 $collect = [];
312 break;
313 }
314
315 // ignore empty value
316 if ('' === $row['meta_value']) {
317 continue;
318 }
319
320 $key = @preg_replace('@^(_site)?(_transient)(_timeout)?_@', '', $row['meta_key']);
321 if (!isset($collect[$key])) {
322 $collect[$key] = ['value' => '', 'group' => 'site-transient', 'expire' => 0];
323 }
324
325 $collect[$key]['value'] = $row['meta_value'];
326
327 if (false !== strpos($row['meta_key'], '_site_transient_timeout_')) {
328 $collect[$key]['timeout'] = (int) $row['meta_value'];
329 }
330 }
331
332 if (!empty($collect)) {
333 foreach ($collect as $key => $arr) {
334 $timeout = isset($arr['timeout']) ? $arr['timeout'] : 0;
335 $this->store_cache($key, $arr['value'], $arr['group'], $timeout);
336 }
337 }
338 }
339 }
340
341 unset($collect, $results);
342 $wpdb->suppress_errors($suppress);
343
344 return true;
345 }
346
347 public function export_alloptions()
348 {
349 if (!nwdcx_wpdb($wpdb) || $this->multisite) {
350 return false;
351 }
352
353 $suppress = $wpdb->suppress_errors(true);
354 $alloptions_db = $wpdb->get_results('SELECT `option_name`,`option_value` FROM `'.$wpdb->options.'` WHERE autoload = \'yes\'', ARRAY_A);
355 if (empty($alloptions_db)) {
356 $alloptions_db = $wpdb->get_results('SELECT `option_name`,`option_value` FROM `'.$wpdb->options.'`', ARRAY_A);
357 }
358 $wpdb->suppress_errors($suppress);
359
360 $alloptions = [];
361
362 if (!empty($alloptions_db) && \is_array($alloptions_db)) {
363 $wp_options = $this->fs()->keys_alloptions();
364 $is_filter = $this->cf()->is_dctrue('WPOPTALOAD');
365 foreach ($alloptions_db as $num => $options) {
366 if ($this->max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $this->max_execution_time) {
367 $alloptions = [];
368 break;
369 }
370
371 $key = $options['option_name'];
372 if ($is_filter && \array_key_exists($key, $wp_options)) {
373 continue;
374 }
375 $alloptions[$key] = $options['option_value'];
376 }
377
378 if (!empty($alloptions)) {
379 $this->store_cache('alloptions', $alloptions, 'options');
380 }
381 }
382
383 unset($alloptions, $alloptions_db);
384
385 // always true
386 return true;
387 }
388
389 private function switch_to_blog($blog_id)
390 {
391 $blog_id = (int) $blog_id;
392 $this->blog_prefix = $this->multisite ? $blog_id.':' : '';
393 }
394 }
395