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

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

1,686 lines 45.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 namespace Nawawi\DocketCache;
12
13 \defined('ABSPATH') || exit;
14
15 use Nawawi\DocketCache\Exporter\VarExporter;
16
17 class Filesystem
18 {
19 /**
20 * is_docketcachedir.
21 */
22 public function is_docketcachedir($path)
23 {
24 static $cached = [];
25
26 if (!empty($cached[$path])) {
27 return true;
28 }
29
30 $name = 'docket-cache';
31 $ok = false;
32
33 $dir = nwdcx_normalizepath($path);
34
35 if (false === strpos($dir.'/', '/'.$name.'/')) {
36 return $ok;
37 }
38
39 $dir = array_reverse(explode('/', trim($dir, '/')));
40
41 // 28042022: new cache directory.
42 // depth = 3: cache/docket-cache/93/b2/8a/
43 // depth = 4: cache/docket-cache/network-1/93/b2/8a/
44 $maxdepth = 4;
45 foreach ($dir as $n => $c) {
46 if ($n <= $maxdepth && 0 === strcmp($name, $c)) {
47 $ok = true;
48 $cached[$path] = 1;
49 break;
50 }
51 }
52
53 return $ok;
54 }
55
56 /**
57 * is_docketcachefile.
58 */
59 public function is_docketcachefile($file)
60 {
61 static $cached = [];
62
63 if (!empty($cached[$file])) {
64 return true;
65 }
66
67 if (false !== strpos($file, '/docket-cache/') && @preg_match('@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@', basename($file))) {
68 $cached[$file] = 1;
69
70 return true;
71 }
72
73 return false;
74 }
75
76 /**
77 * is_docketcachegroup.
78 */
79 public function is_docketcachegroup($group, $key = '')
80 {
81 return 'docketcache' === substr($group, 0, 11) || !empty($key) && 'docketcache' === substr($key, 0, 11);
82 }
83
84 /**
85 * is_transient.
86 */
87 public function is_transient($group)
88 {
89 if (\is_array($group)) {
90 return \in_array($group, ['transient', 'site-transient']);
91 }
92
93 return 'transient' === $group || 'site-transient' === $group;
94 }
95
96 /**
97 * is_wp_options.
98 */
99 public function is_wp_options($group)
100 {
101 if (\is_array($group)) {
102 return \in_array($group, ['options', 'site-options']);
103 }
104
105 return 'options' === $group || 'site-options' === $group;
106 }
107
108 /**
109 * is_wp_cache_group_queries.
110 */
111 public function is_wp_cache_group_queries($group)
112 {
113 return \in_array($group, ['term-queries', 'post-queries', 'comment-queries', 'site-queries', 'network-queries', 'user-queries']);
114 }
115
116 /**
117 * is_dirempty.
118 */
119 public function is_dirempty($dir)
120 {
121 foreach (new \DirectoryIterator($dir) as $object) {
122 if ($object->isDot()) {
123 continue;
124 }
125
126 return false;
127 }
128
129 return true;
130 }
131
132 /**
133 * get_max_execution_time.
134 */
135 public function get_max_execution_time($second = 0)
136 {
137 $max_execution_time = (int) \ini_get('max_execution_time');
138
139 $second = (int) $second;
140 if ($second > 0 && $max_execution_time > 0 && $second > $max_execution_time) {
141 set_time_limit($second);
142 $max_execution_time = (int) \ini_get('max_execution_time');
143 }
144
145 if ($max_execution_time > 10) {
146 --$max_execution_time;
147 }
148
149 return $max_execution_time;
150 }
151
152 /**
153 * filesize.
154 */
155 public function filesize($file)
156 {
157 if (!@is_file($file)) {
158 return 0;
159 }
160
161 return sprintf('%u', @filesize($file));
162 }
163
164 /**
165 * touch.
166 */
167 public function touch($file, $time = 0, $atime = 0)
168 {
169 if (0 == $time) {
170 $time = time();
171 }
172
173 if (0 == $atime) {
174 $atime = time();
175 }
176
177 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
178
179 $ok = @touch($file, $time, $atime);
180
181 // user:group not same -> Utime failed: Operation not permitted
182 if (!$ok) {
183 $e = error_get_last();
184 if (!\is_array($e)) {
185 nwdcx_throwable(__METHOD__, $e);
186 }
187 }
188
189 // restore error level
190 nwdcx_suppresserrors($nwdcx_suppresserrors);
191
192 return $ok;
193 }
194
195 /**
196 * getchmod.
197 */
198 public function getchmod($file)
199 {
200 return substr(decoct(@fileperms($file)), -3);
201 }
202
203 /**
204 * chmod.
205 */
206 public function chmod($file, $mode = false)
207 {
208 if (!$mode) {
209 if (@is_file($file) && \defined('FS_CHMOD_FILE')) {
210 $mode = FS_CHMOD_FILE;
211 } elseif (@is_dir($file) && \defined('FS_CHMOD_DIR')) {
212 $mode = FS_CHMOD_DIR;
213 } else {
214 clearstatcache();
215 $stat = @stat(\dirname($file));
216 $mode = $stat['mode'] & 0007777;
217
218 if (@is_file($file)) {
219 $mode = $mode & 0000666;
220 }
221 }
222 }
223
224 clearstatcache();
225
226 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
227 $ok = false;
228 try {
229 if (@is_file($file)) {
230 $ok = @chmod($file, $mode);
231 }
232 } catch (\Throwable $e) {
233 nwdcx_throwable(__METHOD__, $e);
234 }
235
236 nwdcx_suppresserrors($nwdcx_suppresserrors);
237
238 return $ok;
239 }
240
241 /**
242 * mkdir.
243 */
244 public function mkdir_p($path)
245 {
246 $parent = \dirname($path);
247 $okperms = [
248 '777',
249 '775',
250 '755',
251 ];
252
253 if (@is_dir($path) && \in_array($this->getchmod($path), $okperms) && \in_array($this->getchmod($parent), $okperms)) {
254 return true;
255 }
256
257 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
258
259 if (\function_exists('wp_mkdir_p')) {
260 $ok = @wp_mkdir_p($path);
261 } else {
262 $stat = @stat($parent);
263 if ($stat) {
264 $dir_perms = $stat['mode'] & 0007777;
265 } else {
266 $dir_perms = 0777;
267 }
268
269 $ok = @mkdir($path, $dir_perms, true);
270 }
271
272 nwdcx_suppresserrors($nwdcx_suppresserrors);
273
274 if (!$ok) {
275 return false;
276 }
277
278 if (!\in_array($this->getchmod($parent), $okperms)) {
279 $this->chmod($parent, 0755);
280 }
281
282 if (!\in_array($this->getchmod($path), $okperms)) {
283 $this->chmod($path, 0755);
284 }
285
286 return true;
287 }
288
289 /**
290 * copy.
291 */
292 public function copy($src, $dst)
293 {
294 $this->opcache_flush($src);
295 $this->opcache_flush($dst);
296
297 if (@copy($src, $dst)) {
298 $this->chmod($dst);
299
300 return true;
301 }
302
303 return false;
304 }
305
306 /**
307 * scanfiles.
308 */
309 public function scanfiles($dir, $maxdepth = null, $pattern = false)
310 {
311 $dir = nwdcx_normalizepath(realpath($dir));
312 if (false === $dir || !is_dir($dir) || !is_readable($dir)) {
313 return [];
314 }
315
316 if (null === $maxdepth) {
317 $maxdepth = 13;
318 }
319
320 if (empty($pattern)) {
321 $pattern = '@^(dump_)?([a-z0-9_]+)\-([a-z0-9]+).*\.php$@';
322 }
323
324 $rec_dir_iterator_flags = \FilesystemIterator::SKIP_DOTS | \RecursiveDirectoryIterator::KEY_AS_FILENAME | \RecursiveDirectoryIterator::CURRENT_AS_FILEINFO;
325 $rec_dir_iterator = new \RecursiveDirectoryIterator($dir, $rec_dir_iterator_flags);
326 $reg_ite_iterator = new \RecursiveIteratorIterator($rec_dir_iterator);
327 $reg_ite_pattern = $pattern;
328 $reg_ite_mode = \RegexIterator::MATCH;
329 $reg_ite_flags = \RegexIterator::USE_KEY;
330 $reg_iterator = new \RegexIterator($reg_ite_iterator, $reg_ite_pattern, $reg_ite_mode, $reg_ite_flags);
331 $reg_iterator->setMaxDepth($maxdepth);
332
333 return $reg_iterator;
334 }
335
336 /**
337 * validate_file.
338 */
339 public function validate_file($filename)
340 {
341 try {
342 $fileo = new \SplFileObject($filename, 'rb');
343 } catch (\Throwable $e) {
344 nwdcx_throwable(__METHOD__, $e);
345
346 return false;
347 }
348
349 if ($fileo->flock(\LOCK_EX)) {
350 $fileo->seek(\PHP_INT_MAX);
351 $lines = $fileo->key();
352 $object = new \LimitIterator($fileo, $lines - 2);
353 foreach ($object as $line) {
354 if (false !== strpos($line, '/*@DOCKET_CACHE_EOF*/')) {
355 $fileo->flock(\LOCK_UN);
356
357 return true;
358 }
359 }
360 $fileo->flock(\LOCK_UN);
361 }
362
363 $fileo = null;
364
365 return false;
366 }
367
368 /**
369 * export_var.
370 */
371 public function export_var($data, &$error = '')
372 {
373 // 28022023, self-note to future me.
374 // We use native var_export to improve cache writing, since VarExporter::export will use preg_replace_callback on string types and
375 // ReflectionClass to resolve class instances. Cached data may look not pretty.
376 try {
377 if (version_compare(\PHP_VERSION, '7.3.0', '>=') && !empty($data['type']) && \in_array($data['type'], ['object', 'array', 'string', 'string_serialize', 'array_serialize', 'integer', 'boolean'])) {
378 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
379 $arr_data = @var_export($data, true);
380 nwdcx_suppresserrors($nwdcx_suppresserrors);
381 // Return exported data, if doesn't have a class instance.
382 if (!empty($arr_data) && false === strpos($arr_data, '::__set_state')) {
383 return $arr_data;
384 }
385 }
386 } catch (\Throwable $e) {
387 nwdcx_throwable(__METHOD__, $e);
388 }
389
390 // If native var_export failed or not from cache.
391 try {
392 $data = VarExporter::export($data);
393 } catch (\Throwable $e) {
394 nwdcx_throwable(__METHOD__, $e);
395 $error = $e->getMessage();
396
397 // php < 7.3
398 if (false !== strpos($error, 'Cannot export value of type "stdClass"')) {
399 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
400 $data = @var_export($data, true);
401 $data = str_replace('stdClass::__set_state', '(object)', $data);
402 nwdcx_suppresserrors($nwdcx_suppresserrors);
403 } else {
404 $this->log('err', '000000000000-000000000000', 'export_var: '.$error);
405
406 return false;
407 }
408 }
409
410 // alias: shorter name
411 // map it in includes/compat.php
412 $data = str_replace(
413 '\Nawawi\Symfony\Component\VarExporter\Internal\\',
414 '\Nawawi\DocketCache\Exporter\\',
415 $data
416 );
417
418 return $data;
419 }
420
421 /**
422 * shutdown_cleanup.
423 */
424 public function shutdown_cleanup($file, $seq = 10)
425 {
426 // for dump().
427 if (empty($this->filesize($file))) {
428 return false;
429 }
430
431 // dont use register_shutdown_function to avoid issue with page cache plugin
432 add_action(
433 'shutdown',
434 function () use ($file) {
435 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
436 clearstatcache(true, $file);
437 if (@is_file($file)) {
438 @unlink($file);
439 }
440 nwdcx_suppresserrors($nwdcx_suppresserrors);
441 },
442 $seq
443 );
444
445 return true;
446 }
447
448 /**
449 * unlink.
450 */
451 public function unlink($file, $is_delete = false, $is_block = false)
452 {
453 clearstatcache(true, $file);
454
455 // skip if not exist
456 if (!@is_file($file)) {
457 return true;
458 }
459
460 $ok = false;
461
462 $handle = @fopen($file, 'cb');
463 if ($handle) {
464 $lock = $is_block ? \LOCK_EX : \LOCK_EX | \LOCK_NB;
465 if (@flock($handle, $lock)) {
466 $ok = @ftruncate($handle, 0); // true, false
467 @flock($handle, \LOCK_UN);
468 }
469 @fclose($handle);
470 }
471
472 // bcoz we empty the file
473 $this->opcache_flush($file);
474
475 $do_delete = (nwdcx_construe('FLUSH_DELETE') && $this->is_php($file)) || $is_delete;
476
477 if ($do_delete && @unlink($file)) {
478 $ok = true;
479 }
480
481 // cleanup if ftruncate() failed
482 if (false === $ok) {
483 clearstatcache(true, $file);
484 if (@is_file($file) && !@unlink($file)) {
485 // try cleanup at shutdown
486 $this->shutdown_cleanup($file);
487 }
488 }
489
490 // reset all
491 clearstatcache(true);
492
493 // always true
494 return true;
495 }
496
497 /**
498 * put.
499 */
500 public function put($file, $data, $flag = 'cb', $is_block = false)
501 {
502 if (!$handle = @fopen($file, $flag)) {
503 return false;
504 }
505
506 $lock = $is_block ? \LOCK_EX : \LOCK_EX | \LOCK_NB;
507 $ok = false;
508 if (@flock($handle, $lock)) {
509 $len = \strlen($data);
510 $cnt = @fwrite($handle, $data);
511 @fflush($handle);
512 @flock($handle, \LOCK_UN);
513 if ($len === $cnt) {
514 $ok = true;
515 }
516 }
517 @fclose($handle);
518
519 if (false === $ok) {
520 $this->unlink($file, true);
521
522 return -1;
523 }
524
525 clearstatcache(true);
526
527 $this->opcache_flush($file);
528 $this->chmod($file);
529
530 return $ok;
531 }
532
533 /**
534 * dump.
535 */
536 public function dump($file, $data, $is_validate = false)
537 {
538 $dir = \dirname($file);
539 $tmpfile = $dir.'/'.'dump_'.uniqid().'_'.basename($file);
540
541 // cleanup at shutdown
542 $this->shutdown_cleanup($tmpfile, \PHP_INT_MAX);
543
544 // truncate reason
545 $this->opcache_flush($file);
546
547 // make query-monitor happy.
548 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
549
550 $ok = $this->put($tmpfile, $data, 'cb', true);
551 if (true === $ok) {
552 try {
553 clearstatcache(true, $tmpfile);
554 if (@is_file($tmpfile) && @rename($tmpfile, $file)) {
555 if (isset($nwdcx_suppresserrors)) {
556 nwdcx_suppresserrors($nwdcx_suppresserrors);
557 }
558
559 if ($is_validate && !$this->validate_file($file)) {
560 return false;
561 }
562
563 $this->chmod($file);
564
565 // compile
566 $this->opcache_compile($file);
567
568 return true;
569 }
570 } catch (\Throwable $e) {
571 nwdcx_throwable(__METHOD__, $e);
572 }
573
574 // failed to replace
575 $ok = false;
576 }
577
578 // cleanup if not bool true
579 clearstatcache(true, $tmpfile);
580 if (@is_file($tmpfile)) {
581 @unlink($tmpfile);
582 }
583
584 nwdcx_suppresserrors($nwdcx_suppresserrors);
585
586 clearstatcache(true);
587
588 // maybe -1, >= 1, false: return from put()
589 return $ok;
590 }
591
592 /**
593 * placeholder.
594 */
595 public function placeholder($path)
596 {
597 if (!@is_dir($path)) {
598 return false;
599 }
600
601 $file = rtrim($path, '/\\').'/index.html';
602 if (@is_file($file)) {
603 return false;
604 }
605
606 $code = '<html><head><meta name="robots" content="noindex, nofollow"><title>Docket Cache</title></head>';
607 $code .= '<body>Generated by <a href="https://wordpress.org/plugins/docket-cache/" rel="nofollow">Docket Cache</a></body></html>';
608
609 return $this->put($file, $code);
610 }
611
612 /**
613 * is_php.
614 */
615 public function is_php($file)
616 {
617 $file = basename($file);
618
619 return '.php' === substr($file, -4);
620 }
621
622 public function is_function_disabled($name)
623 {
624 $disable_functions = @\ini_get('disable_functions');
625 if (!empty($disable_functions)) {
626 $funcs = explode(',', $disable_functions);
627 if (\in_array($name, $funcs)) {
628 return true;
629 }
630 }
631
632 return false;
633 }
634
635 public function get_chunk_path($group, $key)
636 {
637 $chunk = substr($group, 0, 2).substr($key, 0, 2).substr($key, -2);
638
639 return chunk_split($chunk, 2, '/');
640 }
641
642 /**
643 * remove cache file with previous structure.
644 */
645 public function remove_non_chunk_cache($cache_path, $file)
646 {
647 if (nwdcx_construe('CHUNKCACHEDIR')) {
648 $filename = basename($file);
649 $filepath = rtrim($cache_path, '\\/').'/'.$filename;
650 if (is_file($filepath) && is_writable($filepath) && $this->is_docketcachefile($filepath)) {
651 return @unlink($filepath);
652 }
653 }
654
655 return false;
656 }
657
658 /**
659 * opcache_function_exists.
660 */
661 public function opcache_function_exists($name = null)
662 {
663 $list = [
664 'opcache_get_status',
665 'opcache_reset',
666 'opcache_compile_file',
667 'opcache_invalidate',
668 'opcache_is_script_cached',
669 'opcache_get_configuration',
670 ];
671
672 if (!empty($name)) {
673 return \in_array($name, $list) && !$this->is_function_disabled($name) && \function_exists($name);
674 }
675
676 foreach ($list as $func) {
677 if (!$this->is_function_disabled($name) && \function_exists($func)) {
678 return true;
679 }
680 }
681
682 return false;
683 }
684
685 /**
686 * is_opcache_enable.
687 */
688 public function is_opcache_enable()
689 {
690 if (@\ini_get('opcache.enable')) {
691 if (\function_exists('extension_loaded') && \extension_loaded('Zend OPcache')) {
692 return true;
693 }
694
695 if (\function_exists('get_loaded_extensions')) {
696 $arr = get_loaded_extensions(true);
697 if (\is_array($arr) && \in_array('Zend OPcache', $arr)) {
698 return true;
699 }
700 }
701
702 if ($this->opcache_function_exists()) {
703 return true;
704 }
705 }
706
707 return false;
708 }
709
710 /**
711 * is_opcache_blacklisted.
712 */
713 public function is_opcache_blacklisted()
714 {
715 $conf = \ini_get('opcache.blacklist_filename');
716 if (empty($conf)) {
717 return false;
718 }
719
720 $files = glob($conf);
721 if (empty($files) || !\is_array($files)) {
722 return false;
723 }
724
725 $abspath = rtrim(ABSPATH, '\\/');
726 foreach ($files as $file) {
727 if (!is_file($file) || !is_readable($file)) {
728 continue;
729 }
730 $buff = file($file, \FILE_SKIP_EMPTY_LINES | \FILE_IGNORE_NEW_LINES);
731 if (!empty($buff) && \is_array($buff)) {
732 foreach ($buff as $line) {
733 if (empty($line) || ';' === substr($line, 0, 1)) {
734 continue;
735 }
736
737 if ($abspath === substr(rtrim($line, '\\/'), 0, \strlen($abspath))) {
738 return true;
739 }
740 }
741 }
742 unset($buff);
743 }
744
745 return false;
746 }
747
748 /**
749 * is_opcache_filecache_only.
750 */
751 public function is_opcache_filecache_only()
752 {
753 return @\ini_get('opcache.file_cache_only');
754 }
755
756 /**
757 * opcache_filecache_only.
758 */
759 public function opcache_filecache_only()
760 {
761 if ($this->is_opcache_filecache_only() && \function_exists('opcache_get_status')) {
762 $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
763 $data = opcache_get_status();
764 nwdcx_suppresserrors($nwdcx_suppresserrors);
765 if (!empty($data) && \is_array($data) && !empty($data['file_cache_only']) && !empty($data['file_cache']) && is_dir($data['file_cache'])) {
766 return $data;
767 }
768 }
769
770 return false;
771 }
772
773 /**
774 * opcache_filecache_scanfiles.
775 */
776 public function opcache_filecache_scanfiles($dir)
777 {
778 return $this->scanfiles($dir, -1, '@.*\.php\.bin$@');
779 }
780
781 /**
782 * opcache_filecache_flush.
783 */
784 public function opcache_filecache_flush($file)
785 {
786 if (!($fcdata = $this->opcache_filecache_only()) || false === strpos(nwdcx_normalizepath($file), nwdcx_normalizepath(ABSPATH))) {
787 return false;
788 }
789
790 $filem = nwdcx_normalizepath(rtrim($fcdata['file_cache'], '/\\').'/*/'.ltrim($file, '/\\').'.bin');
791 $match = glob($filem);
792 if (!empty($match) && \is_array($match)) {
793 foreach ($match as $fm) {
794 if (is_file($fm) && is_writable($fm)) {
795 @unlink($fm);
796 }
797 }
798 }
799
800 return true;
801 }
802
803 /**
804 * opcache_filecache_reset.
805 */
806 public function opcache_filecache_reset()
807 {
808 static $is_done = false;
809
810 $fcdata = $this->opcache_filecache_only();
811 if (!$is_done && !empty($fcdata)) {
812 $dir = $fcdata['file_cache'];
813 $cnt = 0;
814
815 $this->suspend_cache_write(true);
816
817 $max_execution_time = $this->get_max_execution_time(180);
818 $slowdown = 0;
819 foreach ($this->opcache_filecache_scanfiles($dir) as $object) {
820 try {
821 if ($object->isFile()) {
822 $file = nwdcx_normalizepath($object->getPathName());
823 if (false !== strpos($file, nwdcx_normalizepath(ABSPATH)) && is_writable($file)) {
824 @unlink($file);
825 ++$cnt;
826 }
827 }
828 } catch (\Throwable $e) {
829 nwdcx_throwable(__METHOD__, $e);
830 }
831
832 if ($slowdown > 10) {
833 $slowdown = 0;
834 usleep(5000);
835 }
836
837 ++$slowdown;
838
839 if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) {
840 break;
841 }
842 }
843
844 clearstatcache(true);
845
846 $is_done = true;
847
848 $this->suspend_cache_write(false);
849
850 return $cnt;
851 }
852
853 return false;
854 }
855
856 /**
857 * opcache_is_cached.
858 */
859 public function opcache_is_cached($file)
860 {
861 if (!$this->is_opcache_enable()) {
862 return -1;
863 }
864
865 $fcdata = $this->opcache_filecache_only();
866 if (!empty($fcdata) && false !== strpos(nwdcx_normalizepath($file), nwdcx_normalizepath(ABSPATH))) {
867 $filem = nwdcx_normalizepath(rtrim($fcdata['file_cache'], '/\\').'/*/'.ltrim($file, '/\\').'.bin');
868 $match = glob($filem);
869 if (!empty($match) && \is_array($match)) {
870 return true;
871 }
872 }
873
874 if (\function_exists('opcache_is_script_cached')) {
875 return @opcache_is_script_cached($file);
876 }
877
878 return -1;
879 }
880
881 /**
882 * opcache_flush.
883 */
884 public function opcache_flush($file)
885 {
886 if (!$this->is_php($file) || !@is_file($file) || !$this->is_opcache_enable()) {
887 return false;
888 }
889
890 if ($this->opcache_filecache_flush($file)) {
891 return true;
892 }
893
894 // wp 5.5
895 if (\function_exists('wp_opcache_invalidate')) {
896 return @wp_opcache_invalidate($file, true);
897 }
898
899 if (\function_exists('opcache_invalidate')) {
900 return @opcache_invalidate($file, true);
901 }
902
903 return false;
904 }
905
906 /**
907 * opcache_compile.
908 */
909 public function opcache_compile($file)
910 {
911 if (!$this->is_opcache_enable()) {
912 return false;
913 }
914
915 if (!empty($_GET['_wpnonce']) && !empty($_GET['action']) && !empty($_GET['page']) && 'docket-cache' === $_GET['page'] && 'docket-flush-opcache' === $_GET['action']) {
916 return -1;
917 }
918
919 if (\function_exists('opcache_compile_file') && $this->is_php($file) && @is_file($file) && false === $this->opcache_is_cached($file)) {
920 $this->touch($file, time() - 60);
921
922 try {
923 return @opcache_compile_file($file);
924 } catch (\Throwable $e) {
925 nwdcx_throwable(__METHOD__, $e);
926 }
927 }
928
929 return false;
930 }
931
932 /**
933 * opcache_reset.
934 */
935 public function opcache_reset()
936 {
937 if (!$this->is_opcache_enable()) {
938 return false;
939 }
940
941 try {
942 if (!@opcache_reset()) {
943 return $this->opcache_filecache_reset();
944 }
945 } catch (\Throwable $e) {
946 nwdcx_throwable(__METHOD__, $e);
947
948 return false;
949 }
950
951 // always true
952 return true;
953 }
954
955 /**
956 * opcache_cleanup.
957 */
958 public function opcache_cleanup()
959 {
960 add_action(
961 'shutdown',
962 function () {
963 $this->opcache_reset();
964 },
965 \PHP_INT_MAX
966 );
967 }
968
969 /**
970 * define_cache_path.
971 */
972 public function define_cache_path($cache_path)
973 {
974 $content_path = \defined('DOCKET_CACHE_CONTENT_PATH') ? DOCKET_CACHE_CONTENT_PATH : WP_CONTENT_DIR;
975 $content_path = nwdcx_normalizepath($content_path);
976
977 $cache_path = !empty($cache_path) && '/' !== $cache_path ? rtrim($cache_path, '/\\').'/' : $content_path.'/cache/docket-cache/';
978 $cache_path = nwdcx_normalizepath($cache_path);
979
980 if (!$this->is_docketcachedir($cache_path)) {
981 $cache_path = rtrim($cache_path, '/\\').'docket-cache/';
982 }
983
984 // create if not normal installation
985 if (false === strpos($content_path, '/wp-content/')) {
986 if (!@is_dir($cache_path)) {
987 $this->mkdir_p($cache_path);
988 }
989
990 if (!@is_dir($content_path)) {
991 $this->mkdir_p($content_path);
992 }
993 }
994
995 return $cache_path;
996 }
997
998 /**
999 * cachedir_flush.
1000 */
1001 public function cachedir_flush($dir, $cleanup = false, &$is_timeout = false)
1002 {
1003 static $is_done = false;
1004
1005 if ($is_done) {
1006 return 0;
1007 }
1008
1009 clearstatcache();
1010
1011 $dir = nwdcx_normalizepath(realpath($dir));
1012
1013 if (false === $dir || !@is_dir($dir) || !@is_writable($dir) || !$this->is_docketcachedir($dir)) {
1014 return false;
1015 }
1016
1017 if ($this->is_dirempty($dir)) {
1018 return true;
1019 }
1020
1021 $this->suspend_cache_write(true);
1022
1023 $gcisrun_lock = $dir.'/.gc-is-run.txt';
1024
1025 $max_execution_time = $this->get_max_execution_time(180);
1026 $flush_lock = DOCKET_CACHE_CONTENT_PATH.'/.object-cache-flush.txt';
1027 $flush_lock_expiry = time() + $max_execution_time + 10;
1028 if ($this->put($flush_lock, $flush_lock_expiry)) {
1029 $this->touch($flush_lock, $flush_lock_expiry);
1030 }
1031
1032 $slowdown = 0;
1033 $cnt = 0;
1034
1035 foreach ($this->scanfiles($dir) as $object) {
1036 try {
1037 if ($object->isFile()) {
1038 $this->unlink($object->getPathName(), $cleanup ? true : false);
1039 ++$cnt;
1040 }
1041 } catch (\Throwable $e) {
1042 nwdcx_throwable(__METHOD__, $e);
1043 }
1044
1045 if ($slowdown > 10) {
1046 $slowdown = 0;
1047 usleep(5000);
1048 }
1049
1050 ++$slowdown;
1051
1052 if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) {
1053 $is_timeout = true;
1054 break;
1055 }
1056 }
1057
1058 if ($cleanup) {
1059 // 24122020: deprecate
1060 $this->unlink($dir.'/index.php', true);
1061
1062 // placeholder
1063 $this->unlink($dir.'/index.html', true);
1064 }
1065
1066 $this->unlink($gcisrun_lock, true);
1067
1068 if (@is_file($flush_lock)) {
1069 @unlink($flush_lock);
1070 }
1071
1072 $this->suspend_cache_write(false);
1073 $is_done = true;
1074
1075 return $cnt;
1076 }
1077
1078 /**
1079 * cache_size.
1080 */
1081 public function cache_size($dir)
1082 {
1083 static $is_done = false;
1084
1085 $bytestotal = 0;
1086 $fsizetotal = 0;
1087 $filestotal = 0;
1088
1089 clearstatcache();
1090 $gcisrun_lock = $dir.'/.gc-is-run.txt';
1091
1092 if (!$is_done && $this->is_docketcachedir($dir) && !@is_file(DOCKET_CACHE_CONTENT_PATH.'/.object-cache-flush.txt') && !@is_file($gcisrun_lock)) {
1093 // hardmax
1094 $maxfile = 999000; // 1000000 - 1000;
1095 $cnt = 0;
1096 $slowdown = 0;
1097
1098 ignore_user_abort(true);
1099 $max_execution_time = $this->get_max_execution_time(180);
1100
1101 $pattern = '@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@';
1102 foreach ($this->scanfiles($dir, null, $pattern) as $object) {
1103 try {
1104 if ($object->isFile()) {
1105 $fx = $object->getPathName();
1106
1107 if (!$this->remove_non_chunk_cache($dir, $fx)) {
1108 $fs = $object->getSize();
1109
1110 if ($cnt >= $maxfile) {
1111 $this->unlink($fx, true);
1112 } elseif (0 === $fs) {
1113 $this->unlink($fx, true);
1114 } else {
1115 $data = $this->cache_get($fx);
1116 if (false === $data) {
1117 $this->unlink($fx, true);
1118 } else {
1119 $bytestotal += \strlen(serialize($data));
1120 unset($data);
1121
1122 $fsizetotal += $fs;
1123
1124 ++$filestotal;
1125 ++$cnt;
1126 }
1127 }
1128 }
1129 }
1130 } catch (\Throwable $e) {
1131 nwdcx_throwable(__METHOD__, $e);
1132 }
1133
1134 if ($slowdown > 10) {
1135 $slowdown = 0;
1136 usleep(5000);
1137 }
1138
1139 ++$slowdown;
1140
1141 if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) {
1142 break;
1143 }
1144
1145 if (@is_file($gcisrun_lock)) {
1146 break;
1147 }
1148 }
1149 }
1150
1151 $is_done = true;
1152
1153 wp_cache_set('count_file', $filestotal, 'docketcache-gc', 86400);
1154
1155 return [
1156 'timestamp' => time(),
1157 'size' => $bytestotal,
1158 'filesize' => $fsizetotal,
1159 'files' => $filestotal,
1160 ];
1161 }
1162
1163 public function get_fatal_error_filename($file)
1164 {
1165 // sesimple yang mungkin.
1166 return $file.'-error.txt';
1167 }
1168
1169 public function has_fatal_error_before($file)
1170 {
1171 $file_fatal = $this->get_fatal_error_filename($file);
1172
1173 return @is_file($file_fatal);
1174 }
1175
1176 public function validate_fatal_error_file($file)
1177 {
1178 $file_fatal = $this->get_fatal_error_filename($file);
1179 if (!@is_file($file_fatal)) {
1180 return;
1181 }
1182
1183 if (!@is_file($file)) {
1184 @unlink($file_fatal);
1185
1186 return;
1187 }
1188
1189 $fm = time() - 10; // 10s
1190 if ($fm > @filemtime($file_fatal)) {
1191 if ($this->validate_file($file)) {
1192 @unlink($file_fatal);
1193
1194 return;
1195 }
1196
1197 // update timestamp
1198 $this->touch($file_fatal);
1199 }
1200 }
1201
1202 private function suspend_cache_file($file, $error, $seconds = 0)
1203 {
1204 $seconds = (int) $seconds;
1205 $file_fatal = $this->get_fatal_error_filename($file);
1206
1207 $errmsg = date('Y-m-d H:i:s T').\PHP_EOL.$error;
1208 if ($this->dump($file_fatal, $errmsg)) {
1209 if ($seconds > 0) {
1210 $this->touch($file, time() + $seconds);
1211 }
1212
1213 $this->dump(\dirname($file_fatal).'/last-error.txt', $errmsg);
1214
1215 return true;
1216 }
1217
1218 return false;
1219 }
1220
1221 private function capture_fatal_error()
1222 {
1223 register_shutdown_function(
1224 function () {
1225 $error = error_get_last();
1226 if ($error && \in_array($error['type'], [\E_ERROR, \E_PARSE, \E_COMPILE_ERROR, \E_USER_ERROR, \E_RECOVERABLE_ERROR, \E_CORE_ERROR], true)) {
1227 $file_error = $error['file'];
1228
1229 if ($this->is_docketcachefile($file_error)) {
1230 $this->dump($file_error, '<?php return false;');
1231
1232 $error['file'] = basename($error['file']);
1233 // 300s = 5m delay
1234 if ($this->suspend_cache_file($file_error, $this->export_var($error), 300)) {
1235 // refresh page if possible
1236 if ('cli' !== \PHP_SAPI && !wp_doing_ajax()) {
1237 echo '<script>document.body.innerHTML="";window.setTimeout(function() { window.location.assign(window.location.href); }, 750);</script>';
1238 }
1239 }
1240 }
1241 }
1242 }
1243 );
1244 }
1245
1246 /**
1247 * suspend_cache_write.
1248 */
1249 public function suspend_cache_write($suspend = null)
1250 {
1251 static $_suspend = false;
1252
1253 if (\is_bool($suspend)) {
1254 $_suspend = $suspend;
1255 }
1256
1257 return $_suspend;
1258 }
1259
1260 /**
1261 * cache_get.
1262 */
1263 public function cache_get($file)
1264 {
1265 if (!@is_file($file) || empty($this->filesize($file))) {
1266 return false;
1267 }
1268
1269 if (!$handle = @fopen($file, 'rb')) {
1270 return false;
1271 }
1272
1273 if ($this->has_fatal_error_before($file)) {
1274 return false;
1275 }
1276
1277 // capture non-throwable
1278 if (nwdcx_construe('CAPTURE_FATALERROR')) {
1279 $this->capture_fatal_error();
1280 }
1281
1282 // cache data
1283 $data = [];
1284
1285 // include when we can read, try to avoid fatal error.
1286 // LOCK_SH = shared lock
1287 if (flock($handle, \LOCK_SH)) {
1288 try {
1289 $data = @include $file;
1290 } catch (\Throwable $e) {
1291 $error = $e->getMessage();
1292
1293 $file_error = $e->getFile();
1294 if ($this->is_docketcachefile($file_error)) {
1295 $errmsg = 'E: '.$error.\PHP_EOL;
1296 $errmsg .= 'L: '.$e->getLine().\PHP_EOL;
1297 $errmsg .= 'F: '.basename($file_error).\PHP_EOL;
1298 $this->suspend_cache_file($file, $errmsg);
1299 }
1300
1301 $this->log('err', '000000000000-000000000000', 'cache_get: '.$error);
1302 $data = false;
1303 }
1304
1305 @flock($handle, \LOCK_UN);
1306 }
1307 @fclose($handle);
1308
1309 if (empty($data) || !isset($data['data'])) {
1310 return false;
1311 }
1312
1313 // 15052022
1314 /*if (false === $this->opcache_is_cached($file)) {
1315 $this->opcache_compile($file);
1316 }*/
1317
1318 return $data;
1319 }
1320
1321 /**
1322 * code_stub.
1323 */
1324 public function code_stub($data = '')
1325 {
1326 $is_debug = \defined('WP_DEBUG') && WP_DEBUG;
1327 $is_data = !empty($data);
1328 $is_precache = nwdcx_construe('PRECACHE');
1329 // capture class not exist.
1330 if (($is_debug || $is_precache) && empty($GLOBALS['DOCKET_CACHE_CODESTUB_FALSE'])) {
1331 $GLOBALS['DOCKET_CACHE_CODESTUB_FALSE'] = [];
1332 }
1333
1334 $ucode = '';
1335 if ($is_data && false !== strpos($data, 'Registry::p(')) {
1336 if (@preg_match_all('@Registry::p\(\'([a-zA-Z0-9_]+)\'\)@', $data, $mm)) {
1337 if (!empty($mm) && isset($mm[1]) && \is_array($mm[1])) {
1338 $cls = $mm[1];
1339 foreach ($cls as $clsname) {
1340 if ('stdClass' !== $clsname) {
1341 $clsfname = false;
1342 if ($is_debug) {
1343 $reflector = new \ReflectionClass($clsname);
1344 $clsfname = $reflector->getFileName();
1345 if (false !== $clsfname) {
1346 $clsfname = str_replace(ABSPATH, '', $clsfname);
1347 $ucode .= '/* f: '.$clsfname.' */'.\PHP_EOL;
1348 }
1349 }
1350
1351 // 13022023, self-note to future me.
1352 // The logic here, we can't just "include_once" a class if it doesn't exist
1353 // because if the main code uses "include" or "require" it will throw an error
1354 // since the class has already been loaded by us.
1355 $ucode .= "if(!@class_exists('".$clsname."',false)){";
1356 if ($is_debug || $is_precache) {
1357 $ucode .= "\$GLOBALS['DOCKET_CACHE_CODESTUB_FALSE'][__FILE__]=['".$clsname."','".$clsfname."'];";
1358 }
1359 $ucode .= 'return false;}'.\PHP_EOL;
1360 }
1361 }
1362 unset($cls, $clsname);
1363 }
1364 unset($mm);
1365 }
1366 }
1367
1368 $code = '<?php ';
1369 if ($is_data) {
1370 if (!empty($ucode) || false !== strpos($data, '\Nawawi\DocketCache\\')) {
1371 $code .= "if(!\defined('ABSPATH')){return false;}";
1372 }
1373 $code .= \PHP_EOL;
1374 if (!empty($ucode)) {
1375 $code .= $ucode;
1376 }
1377 $code .= 'return '.$data.';'.\PHP_EOL;
1378 $code .= '/*@DOCKET_CACHE_EOF*/';
1379 }
1380
1381 return $code;
1382 }
1383
1384 /**
1385 * log.
1386 */
1387 public function log($tag, $id, $data, $caller = '')
1388 {
1389 $do_flush = false;
1390 $file = nwdcx_constval('LOG_FILE');
1391 if (empty($file)) {
1392 return false;
1393 }
1394
1395 $logsize = nwdcx_constval('LOG_SIZE');
1396 if (empty($logsize) || !\is_int($logsize)) {
1397 $logsize = 0;
1398 }
1399
1400 if (is_multisite()) {
1401 $file = nwdcx_network_filepath($file);
1402 }
1403
1404 if (@is_file($file)) {
1405 if (nwdcx_construe('LOG_FLUSH') && 'flush' === $tag || ($logsize > 0 && $this->filesize($file) >= $logsize)) {
1406 $do_flush = true;
1407 }
1408 }
1409
1410 $timestamp = date('Y-m-d H:i:s T');
1411
1412 $rtag = trim($tag);
1413 if (\in_array($rtag, ['hit', 'miss', 'err', 'exp', 'del', 'info', 'dev'])) {
1414 $tag = str_pad($rtag, 5);
1415 }
1416 $log = '['.$timestamp.'] '.$tag.': "'.$id.'" "'.trim($data).'" "'.$caller.'"';
1417
1418 $flags = !$do_flush ? \LOCK_EX | \FILE_APPEND : \LOCK_EX;
1419 $do_chmod = !@is_file($file);
1420 if (@file_put_contents($file, $log.\PHP_EOL, $flags)) {
1421 if ($do_chmod) {
1422 $this->chmod($file);
1423 }
1424
1425 return true;
1426 }
1427
1428 return false;
1429 }
1430
1431 /**
1432 * sanitize_timestamp.
1433 */
1434 public function sanitize_timestamp($time)
1435 {
1436 $time = (int) $time;
1437 if ($time < 0) {
1438 $time = 0;
1439 } else {
1440 $max = ceil(log10($time));
1441 if ($max > 10 || 'NaN' === $max) {
1442 $time = 0;
1443 }
1444 }
1445
1446 return $time;
1447 }
1448
1449 /**
1450 * sanitize_maxttl.
1451 */
1452 public function sanitize_maxttl($seconds)
1453 {
1454 $seconds = $this->sanitize_timestamp($seconds);
1455
1456 // 86400 = 1d
1457 // 345600 = 4d
1458 // 2419200 = 28d
1459 if ($seconds < 86400) {
1460 $seconds = 345600;
1461 } elseif ($seconds > 2419200) {
1462 $seconds = 2419200;
1463 }
1464
1465 return $seconds;
1466 }
1467
1468 /**
1469 * sanitize_maxfile.
1470 */
1471 public function sanitize_maxfile($maxfile, $default = 50000)
1472 {
1473 $maxfile = (int) $maxfile;
1474 $min = 200;
1475 $max = 1000000;
1476 if (empty($maxfile)) {
1477 $maxfile = $default;
1478 }
1479
1480 if ($maxfile < $min) {
1481 $maxfile = $default;
1482 } elseif ($maxfile > $max) {
1483 $maxfile = $max;
1484 }
1485
1486 return $maxfile;
1487 }
1488
1489 /**
1490 * sanitize_precache_maxfile.
1491 */
1492 public function sanitize_precache_maxfile($maxfile)
1493 {
1494 $default = 100;
1495 if (empty($maxfile) || (int) $maxfile < 1) {
1496 return $default;
1497 }
1498
1499 return $this->sanitize_maxfile($maxfile, $default);
1500 }
1501
1502 /**
1503 * sanitize_maxsize.
1504 */
1505 public function sanitize_maxsize($bytes)
1506 {
1507 $min = 1048576; // 1M
1508 $max = 10485760; // 10M
1509 $bytes = (int) $bytes;
1510
1511 if ($bytes < $min) {
1512 return 3145728; // 3M
1513 }
1514
1515 if ($bytes > $max) {
1516 $bytes = $max;
1517 }
1518
1519 return $bytes;
1520 }
1521
1522 /**
1523 * sanitize_maxsizedisk.
1524 */
1525 public function sanitize_maxsizedisk($bytes)
1526 {
1527 if (empty($bytes) || !\is_int($bytes)) {
1528 $maxsizedisk = 524288000; // 500MB
1529 }
1530
1531 if ($bytes < 104857600) {
1532 $bytes = 104857600;
1533 }
1534
1535 return $bytes;
1536 }
1537
1538 /**
1539 * valid_timestamp.
1540 */
1541 public function valid_timestamp($timestamp)
1542 {
1543 $timestamp = $this->sanitize_timestamp($timestamp);
1544
1545 return $timestamp > 0;
1546 }
1547
1548 // put here because use in Becache.
1549 public function keys_alloptions()
1550 {
1551 // reference: wp-admin/includes/schema.php
1552 // exclude: rewrite_rules, active_plugins, uninstall_plugins, cron
1553 // exclude: widget_categories, widget_text, widget_rss
1554 return [
1555 'siteurl' => 1,
1556 'home' => 1,
1557 'blogname' => 1,
1558 'blogdescription' => 1,
1559 'users_can_register' => 1,
1560 'admin_email' => 1,
1561 'start_of_week' => 1,
1562 'use_balanceTags' => 1,
1563 'use_smilies' => 1,
1564 'require_name_email' => 1,
1565 'comments_notify' => 1,
1566 'posts_per_rss' => 1,
1567 'rss_use_excerpt' => 1,
1568 'mailserver_url' => 1,
1569 'mailserver_login' => 1,
1570 'mailserver_pass' => 1,
1571 'mailserver_port' => 1,
1572 'default_category' => 1,
1573 'default_comment_status' => 1,
1574 'default_ping_status' => 1,
1575 'default_pingback_flag' => 1,
1576 'posts_per_page' => 1,
1577 'date_format' => 1,
1578 'time_format' => 1,
1579 'links_updated_date_format' => 1,
1580 'comment_moderation' => 1,
1581 'moderation_notify' => 1,
1582 'permalink_structure' => 1,
1583 'hack_file' => 1,
1584 'blog_charset' => 1,
1585 'category_base' => 1,
1586 'ping_sites' => 1,
1587 'comment_max_links' => 1,
1588 'gmt_offset' => 1,
1589 'default_email_category' => 1,
1590 'template' => 1,
1591 'stylesheet' => 1,
1592 'comment_registration' => 1,
1593 'html_type' => 1,
1594 'use_trackback' => 1,
1595 'default_role' => 1,
1596 'db_version' => 1,
1597 'uploads_use_yearmonth_folders' => 1,
1598 'upload_path' => 1,
1599 'blog_public' => 1,
1600 'default_link_category' => 1,
1601 'show_on_front' => 1,
1602 'tag_base' => 1,
1603 'show_avatars' => 1,
1604 'avatar_rating' => 1,
1605 'upload_url_path' => 1,
1606 'thumbnail_size_w' => 1,
1607 'thumbnail_size_h' => 1,
1608 'thumbnail_crop' => 1,
1609 'medium_size_w' => 1,
1610 'medium_size_h' => 1,
1611 'avatar_default' => 1,
1612 'large_size_w' => 1,
1613 'large_size_h' => 1,
1614 'image_default_link_type' => 1,
1615 'image_default_size' => 1,
1616 'image_default_align' => 1,
1617 'close_comments_for_old_posts' => 1,
1618 'close_comments_days_old' => 1,
1619 'thread_comments' => 1,
1620 'thread_comments_depth' => 1,
1621 'page_comments' => 1,
1622 'comments_per_page' => 1,
1623 'default_comments_page' => 1,
1624 'comment_order' => 1,
1625 'timezone_string' => 1,
1626 'page_for_posts' => 1,
1627 'page_on_front' => 1,
1628 'default_post_format' => 1,
1629 'link_manager_enabled' => 1,
1630 'finished_splitting_shared_terms' => 1,
1631 'site_icon' => 1,
1632 'medium_large_size_w' => 1,
1633 'medium_large_size_h' => 1,
1634 'wp_page_for_privacy_policy' => 1,
1635 'show_comments_cookies_opt_in' => 1,
1636 'admin_email_lifespan' => 1,
1637 'initial_db_version' => 1,
1638 'fresh_site' => 1,
1639 'current_theme' => 1,
1640 'theme_switched' => 1,
1641 'generate_update_core_typography' => 1,
1642 'WPLANG' => 1,
1643 'new_admin_email' => 1,
1644 'recovery_mode_email_last_sent' => 1,
1645 'comment_previously_approved' => 1,
1646 'finished_updating_comment_type' => 1,
1647 'db_upgraded' => 1,
1648 /* wp >= 5.6.0 */
1649 'auto_update_core_dev' => 1,
1650 'auto_update_core_minor' => 1,
1651 'auto_update_core_major' => 1,
1652 /* wp >= 5.7 */
1653 'https_detection_errors' => 1,
1654 /* wp >= 5.8 */
1655 'wp_force_deactivated_plugins' => 1,
1656 ];
1657 }
1658
1659 /**
1660 * optimize_alloptions.
1661 */
1662 public function optimize_alloptions()
1663 {
1664 add_filter(
1665 'pre_cache_alloptions',
1666 function ($alloptions) {
1667 $wp_options = $this->keys_alloptions();
1668
1669 foreach ($alloptions as $key => $value) {
1670 // skip
1671 if (empty($value)) {
1672 continue;
1673 }
1674
1675 if (!\array_key_exists($key, $wp_options)) {
1676 unset($alloptions[$key]);
1677 }
1678 }
1679
1680 return $alloptions;
1681 },
1682 \PHP_INT_MIN
1683 );
1684 }
1685 }
1686