/home/arranoyd/energyclinic.com.hr/wp-content/plugins/docket-cache/includes/src/Filesystem.php
<?php
/**
 * Docket Cache.
 *
 * @author  Nawawi Jamili
 * @license MIT
 *
 * @see    https://github.com/nawawi/docket-cache
 */

namespace Nawawi\DocketCache;

\defined('ABSPATH') || exit;

use Nawawi\DocketCache\Exporter\VarExporter;

class Filesystem
{
    /**
     * is_docketcachedir.
     */
    public function is_docketcachedir($path)
    {
        static $cached = [];

        if (!empty($cached[$path])) {
            return true;
        }

        $name = 'docket-cache';
        $ok = false;

        $dir = nwdcx_normalizepath($path);

        if (false === strpos($dir.'/', '/'.$name.'/')) {
            return $ok;
        }

        $dir = array_reverse(explode('/', trim($dir, '/')));

        // 28042022: new cache directory.
        // depth = 3: cache/docket-cache/93/b2/8a/
        // depth = 4: cache/docket-cache/network-1/93/b2/8a/
        // depth = 5: cache/docket-cache/network/network-1/93/b2/8a/
        $maxdepth = 5;
        foreach ($dir as $n => $c) {
            if ($n <= $maxdepth && 0 === strcmp($name, $c)) {
                $ok = true;
                $cached[$path] = 1;
                break;
            }
        }

        return $ok;
    }

    /**
     * is_docketcachefile.
     */
    public function is_docketcachefile($file)
    {
        static $cached = [];

        if (!empty($cached[$file])) {
            return true;
        }

        if (false !== strpos($file, '/docket-cache/') && @preg_match('@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@', basename($file))) {
            $cached[$file] = 1;

            return true;
        }

        return false;
    }

    /**
     * is_docketcachegroup.
     */
    public function is_docketcachegroup($group, $key = '')
    {
        return 'docketcache' === substr($group, 0, 11) || !empty($key) && 'docketcache' === substr($key, 0, 11);
    }

    /**
     * is_transient.
     */
    public function is_transient($group)
    {
        if (\is_array($group)) {
            return \in_array($group, ['transient', 'site-transient']);
        }

        return 'transient' === $group || 'site-transient' === $group;
    }

    /**
     * is_wp_options.
     */
    public function is_wp_options($group)
    {
        if (\is_array($group)) {
            return \in_array($group, ['options', 'site-options']);
        }

        return 'options' === $group || 'site-options' === $group;
    }

    /**
     * is_wp_cache_group_queries.
     */
    public function is_wp_cache_group_queries($group)
    {
        return \in_array($group, ['term-queries', 'post-queries', 'comment-queries', 'site-queries', 'network-queries', 'user-queries']);
    }

    /**
     * is_dirempty.
     */
    public function is_dirempty($dir)
    {
        foreach (new \DirectoryIterator($dir) as $object) {
            if ($object->isDot()) {
                continue;
            }

            return false;
        }

        return true;
    }

    /**
     * get_max_execution_time.
     */
    public function get_max_execution_time($second = 0)
    {
        $max_execution_time = (int) \ini_get('max_execution_time');

        $second = (int) $second;
        if ($second > 0 && $max_execution_time > 0 && $second > $max_execution_time) {
            set_time_limit($second);
            $max_execution_time = (int) \ini_get('max_execution_time');
        }

        if ($max_execution_time > 10) {
            --$max_execution_time;
        }

        return $max_execution_time;
    }

    /**
     * filesize.
     */
    public function filesize($file)
    {
        if (!@is_file($file)) {
            return 0;
        }

        return sprintf('%u', @filesize($file));
    }

    /**
     * touch.
     */
    public function touch($file, $time = 0, $atime = 0)
    {
        if (0 == $time) {
            $time = time();
        }

        if (0 == $atime) {
            $atime = time();
        }

        $nwdcx_suppresserrors = nwdcx_suppresserrors(true);

        $ok = @touch($file, $time, $atime);

        // user:group not same -> Utime failed: Operation not permitted
        if (!$ok) {
            $e = error_get_last();
            if (!\is_array($e)) {
                nwdcx_throwable(__METHOD__, $e);
            }
        }

        // restore error level
        nwdcx_suppresserrors($nwdcx_suppresserrors);

        return $ok;
    }

    /**
     * getchmod.
     */
    public function getchmod($file)
    {
        return substr(decoct(@fileperms($file)), -3);
    }

    /**
     * chmod.
     */
    public function chmod($file, $mode = false)
    {
        if (!$mode) {
            if (@is_file($file) && \defined('FS_CHMOD_FILE')) {
                $mode = FS_CHMOD_FILE;
            } elseif (@is_dir($file) && \defined('FS_CHMOD_DIR')) {
                $mode = FS_CHMOD_DIR;
            } else {
                clearstatcache();
                $stat = @stat(\dirname($file));
                $mode = $stat['mode'] & 0007777;

                if (@is_file($file)) {
                    $mode = $mode & 0000666;
                }
            }
        }

        clearstatcache();

        $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
        $ok = false;
        try {
            if (@is_file($file)) {
                $ok = @chmod($file, $mode);
            }
        } catch (\Throwable $e) {
            nwdcx_throwable(__METHOD__, $e);
        }

        nwdcx_suppresserrors($nwdcx_suppresserrors);

        return $ok;
    }

    /**
     * mkdir.
     */
    public function mkdir_p($path)
    {
        $parent = \dirname($path);
        $okperms = [
            '777',
            '775',
            '755',
        ];

        if (@is_dir($path) && \in_array($this->getchmod($path), $okperms) && \in_array($this->getchmod($parent), $okperms)) {
            return true;
        }

        $nwdcx_suppresserrors = nwdcx_suppresserrors(true);

        if (\function_exists('wp_mkdir_p')) {
            $ok = @wp_mkdir_p($path);
        } else {
            $stat = @stat($parent);
            if ($stat) {
                $dir_perms = $stat['mode'] & 0007777;
            } else {
                $dir_perms = 0777;
            }

            $ok = @mkdir($path, $dir_perms, true);
        }

        nwdcx_suppresserrors($nwdcx_suppresserrors);

        if (!$ok) {
            return false;
        }

        if (!\in_array($this->getchmod($parent), $okperms)) {
            $this->chmod($parent, 0755);
        }

        if (!\in_array($this->getchmod($path), $okperms)) {
            $this->chmod($path, 0755);
        }

        return true;
    }

    /**
     * copy.
     */
    public function copy($src, $dst)
    {
        $this->opcache_flush($src);
        $this->opcache_flush($dst);

        if (@copy($src, $dst)) {
            $this->chmod($dst);

            return true;
        }

        return false;
    }

    /**
     * scanfiles.
     */
    public function scanfiles($dir, $maxdepth = null, $pattern = false)
    {
        $dir = nwdcx_normalizepath(realpath($dir));
        if (false === $dir || !is_dir($dir) || !is_readable($dir)) {
            return [];
        }

        if (null === $maxdepth) {
            $maxdepth = 13;
        }

        if (empty($pattern)) {
            $pattern = '@^(dump_)?([a-z0-9_]+)\-([a-z0-9]+).*\.php$@';
        }

        $rec_dir_iterator_flags = \FilesystemIterator::SKIP_DOTS | \RecursiveDirectoryIterator::KEY_AS_FILENAME | \RecursiveDirectoryIterator::CURRENT_AS_FILEINFO;
        $rec_dir_iterator = new \RecursiveDirectoryIterator($dir, $rec_dir_iterator_flags);
        $reg_ite_iterator = new \RecursiveIteratorIterator($rec_dir_iterator);
        $reg_ite_pattern = $pattern;
        $reg_ite_mode = \RegexIterator::MATCH;
        $reg_ite_flags = \RegexIterator::USE_KEY;
        $reg_iterator = new \RegexIterator($reg_ite_iterator, $reg_ite_pattern, $reg_ite_mode, $reg_ite_flags);
        $reg_iterator->setMaxDepth($maxdepth);

        return $reg_iterator;
    }

    /**
     * validate_file.
     */
    public function validate_file($filename)
    {
        try {
            $fileo = new \SplFileObject($filename, 'rb');
        } catch (\Throwable $e) {
            nwdcx_throwable(__METHOD__, $e);

            return false;
        }

        if ($fileo->flock(\LOCK_EX)) {
            $fileo->seek(\PHP_INT_MAX);
            $lines = $fileo->key();
            $object = new \LimitIterator($fileo, $lines - 2);
            foreach ($object as $line) {
                if (false !== strpos($line, '/*@DOCKET_CACHE_EOF*/')) {
                    $fileo->flock(\LOCK_UN);

                    return true;
                }
            }
            $fileo->flock(\LOCK_UN);
        }

        $fileo = null;

        return false;
    }

    /**
     * export_var.
     */
    public function export_var($data, &$error = '')
    {
        // 28022023, self-note to future me.
        // We use native var_export to improve cache writing, since VarExporter::export will use preg_replace_callback on string types and
        // ReflectionClass to resolve class instances. Cached data may look not pretty.
        try {
            if (version_compare(\PHP_VERSION, '7.3.0', '>=') && !empty($data['type']) && \in_array($data['type'], ['object', 'array', 'string', 'string_serialize', 'array_serialize', 'integer', 'boolean'])) {
                $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
                $arr_data = @var_export($data, true);
                nwdcx_suppresserrors($nwdcx_suppresserrors);
                // Return exported data, if doesn't have a class instance.
                if (!empty($arr_data) && false === strpos($arr_data, '::__set_state')) {
                    return $arr_data;
                }
            }
        } catch (\Throwable $e) {
            nwdcx_throwable(__METHOD__, $e);
        }

        // If native var_export failed or not from cache.
        try {
            $data = VarExporter::export($data);
        } catch (\Throwable $e) {
            nwdcx_throwable(__METHOD__, $e);
            $error = $e->getMessage();

            // php < 7.3
            if (false !== strpos($error, 'Cannot export value of type "stdClass"')) {
                $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
                $data = @var_export($data, true);
                $data = str_replace('stdClass::__set_state', '(object)', $data);
                nwdcx_suppresserrors($nwdcx_suppresserrors);
            } else {
                $this->log('err', '000000000000-000000000000', 'export_var: '.$error);

                return false;
            }
        }

        // alias: shorter name
        // map it in includes/compat.php
        $data = str_replace(
            '\Nawawi\Symfony\Component\VarExporter\Internal\\',
            '\Nawawi\DocketCache\Exporter\\',
            $data
        );

        return $data;
    }

    /**
     * shutdown_cleanup.
     */
    public function shutdown_cleanup($file, $seq = 10)
    {
        // for dump().
        if (empty($this->filesize($file))) {
            return false;
        }

        // dont use register_shutdown_function to avoid issue with page cache plugin
        add_action(
            'shutdown',
            function () use ($file) {
                $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
                clearstatcache(true, $file);
                if (@is_file($file)) {
                    @unlink($file);
                }
                nwdcx_suppresserrors($nwdcx_suppresserrors);
            },
            $seq
        );

        return true;
    }

    /**
     * unlink.
     */
    public function unlink($file, $is_delete = false, $is_block = false)
    {
        clearstatcache(true, $file);

        // skip if not exist
        if (!@is_file($file)) {
            return true;
        }

        $ok = false;

        $handle = @fopen($file, 'cb');
        if ($handle) {
            $lock = $is_block ? \LOCK_EX : \LOCK_EX | \LOCK_NB;
            if (@flock($handle, $lock)) {
                $ok = @ftruncate($handle, 0); // true, false
                @flock($handle, \LOCK_UN);
            }
            @fclose($handle);
        }

        // bcoz we empty the file
        $this->opcache_flush($file);

        $do_delete = (nwdcx_construe('FLUSH_DELETE') && $this->is_php($file)) || $is_delete;

        if ($do_delete && @unlink($file)) {
            $ok = true;
        }

        // cleanup if ftruncate() failed
        if (false === $ok) {
            clearstatcache(true, $file);
            if (@is_file($file) && !@unlink($file)) {
                // try cleanup at shutdown
                $this->shutdown_cleanup($file);
            }
        }

        // reset all
        clearstatcache(true);

        // always true
        return true;
    }

    /**
     * put.
     */
    public function put($file, $data, $flag = 'cb', $is_block = false)
    {
        if (!$handle = @fopen($file, $flag)) {
            return false;
        }

        $lock = $is_block ? \LOCK_EX : \LOCK_EX | \LOCK_NB;
        $ok = false;
        if (@flock($handle, $lock)) {
            $len = \strlen($data);
            $cnt = @fwrite($handle, $data);
            @fflush($handle);
            @flock($handle, \LOCK_UN);
            if ($len === $cnt) {
                $ok = true;
            }
        }
        @fclose($handle);

        if (false === $ok) {
            $this->unlink($file, true);

            return -1;
        }

        clearstatcache(true);

        $this->opcache_flush($file);
        $this->chmod($file);

        return $ok;
    }

    /**
     * dump.
     */
    public function dump($file, $data, $is_validate = false)
    {
        $dir = \dirname($file);
        $tmpfile = $dir.'/'.'dump_'.uniqid().'_'.basename($file);

        // cleanup at shutdown
        $this->shutdown_cleanup($tmpfile, \PHP_INT_MAX);

        // truncate reason
        $this->opcache_flush($file);

        // make query-monitor happy.
        $nwdcx_suppresserrors = nwdcx_suppresserrors(true);

        $ok = $this->put($tmpfile, $data, 'cb', true);
        if (true === $ok) {
            try {
                clearstatcache(true, $tmpfile);
                if (@is_file($tmpfile) && @rename($tmpfile, $file)) {
                    if (isset($nwdcx_suppresserrors)) {
                        nwdcx_suppresserrors($nwdcx_suppresserrors);
                    }

                    if ($is_validate && !$this->validate_file($file)) {
                        return false;
                    }

                    $this->chmod($file);

                    // compile
                    $this->opcache_compile($file);

                    return true;
                }
            } catch (\Throwable $e) {
                nwdcx_throwable(__METHOD__, $e);
            }

            // failed to replace
            $ok = false;
        }

        // cleanup if not bool true
        clearstatcache(true, $tmpfile);
        if (@is_file($tmpfile)) {
            @unlink($tmpfile);
        }

        nwdcx_suppresserrors($nwdcx_suppresserrors);

        clearstatcache(true);

        // maybe -1, >= 1, false: return from put()
        return $ok;
    }

    /**
     * placeholder.
     */
    public function placeholder($path)
    {
        if (!@is_dir($path)) {
            return false;
        }

        $file = rtrim($path, '/\\').'/index.html';
        if (@is_file($file)) {
            return false;
        }

        $code = '<html><head><meta name="robots" content="noindex, nofollow"><title>Docket Cache</title></head>';
        $code .= '<body>Generated by <a href="https://wordpress.org/plugins/docket-cache/" rel="nofollow">Docket Cache</a></body></html>';

        return $this->put($file, $code);
    }

    /**
     * is_php.
     */
    public function is_php($file)
    {
        $file = basename($file);

        return '.php' === substr($file, -4);
    }

    public function is_function_disabled($name)
    {
        $disable_functions = @\ini_get('disable_functions');
        if (!empty($disable_functions)) {
            $funcs = explode(',', $disable_functions);
            if (\in_array($name, $funcs)) {
                return true;
            }
        }

        return false;
    }

    public function get_chunk_path($group, $key)
    {
        $chunk = substr($group, 0, 2).substr($key, 0, 2).substr($key, -2);

        return chunk_split($chunk, 2, '/');
    }

    /**
     * remove cache file with previous structure.
     */
    public function remove_non_chunk_cache($cache_path, $file)
    {
        if (nwdcx_construe('CHUNKCACHEDIR')) {
            $filename = basename($file);
            $filepath = rtrim($cache_path, '\\/').'/'.$filename;
            if (is_file($filepath) && is_writable($filepath) && $this->is_docketcachefile($filepath)) {
                return @unlink($filepath);
            }
        }

        return false;
    }

    /**
     * opcache_function_exists.
     */
    public function opcache_function_exists($name = null)
    {
        $list = [
            'opcache_get_status',
            'opcache_reset',
            'opcache_compile_file',
            'opcache_invalidate',
            'opcache_is_script_cached',
            'opcache_get_configuration',
        ];

        if (!empty($name)) {
            return \in_array($name, $list) && !$this->is_function_disabled($name) && \function_exists($name);
        }

        foreach ($list as $func) {
            if (!$this->is_function_disabled($name) && \function_exists($func)) {
                return true;
            }
        }

        return false;
    }

    /**
     * is_opcache_enable.
     */
    public function is_opcache_enable()
    {
        if (@\ini_get('opcache.enable')) {
            if (\function_exists('extension_loaded') && \extension_loaded('Zend OPcache')) {
                return true;
            }

            if (\function_exists('get_loaded_extensions')) {
                $arr = get_loaded_extensions(true);
                if (\is_array($arr) && \in_array('Zend OPcache', $arr)) {
                    return true;
                }
            }

            if ($this->opcache_function_exists()) {
                return true;
            }
        }

        return false;
    }

    /**
     * is_opcache_blacklisted.
     */
    public function is_opcache_blacklisted()
    {
        $conf = \ini_get('opcache.blacklist_filename');
        if (empty($conf)) {
            return false;
        }

        $files = glob($conf);
        if (empty($files) || !\is_array($files)) {
            return false;
        }

        $abspath = rtrim(ABSPATH, '\\/');
        foreach ($files as $file) {
            if (!is_file($file) || !is_readable($file)) {
                continue;
            }
            $buff = file($file, \FILE_SKIP_EMPTY_LINES | \FILE_IGNORE_NEW_LINES);
            if (!empty($buff) && \is_array($buff)) {
                foreach ($buff as $line) {
                    if (empty($line) || ';' === substr($line, 0, 1)) {
                        continue;
                    }

                    if ($abspath === substr(rtrim($line, '\\/'), 0, \strlen($abspath))) {
                        return true;
                    }
                }
            }
            unset($buff);
        }

        return false;
    }

    /**
     * is_opcache_filecache_only.
     */
    public function is_opcache_filecache_only()
    {
        return @\ini_get('opcache.file_cache_only');
    }

    /**
     * opcache_filecache_only.
     */
    public function opcache_filecache_only()
    {
        if ($this->is_opcache_filecache_only() && \function_exists('opcache_get_status')) {
            $nwdcx_suppresserrors = nwdcx_suppresserrors(true);
            $data = opcache_get_status();
            nwdcx_suppresserrors($nwdcx_suppresserrors);
            if (!empty($data) && \is_array($data) && !empty($data['file_cache_only']) && !empty($data['file_cache']) && is_dir($data['file_cache'])) {
                return $data;
            }
        }

        return false;
    }

    /**
     * opcache_filecache_scanfiles.
     */
    public function opcache_filecache_scanfiles($dir)
    {
        return $this->scanfiles($dir, -1, '@.*\.php\.bin$@');
    }

    /**
     * opcache_filecache_flush.
     */
    public function opcache_filecache_flush($file)
    {
        if (!($fcdata = $this->opcache_filecache_only()) || false === strpos(nwdcx_normalizepath($file), nwdcx_normalizepath(ABSPATH))) {
            return false;
        }

        $filem = nwdcx_normalizepath(rtrim($fcdata['file_cache'], '/\\').'/*/'.ltrim($file, '/\\').'.bin');
        $match = glob($filem);
        if (!empty($match) && \is_array($match)) {
            foreach ($match as $fm) {
                if (is_file($fm) && is_writable($fm)) {
                    @unlink($fm);
                }
            }
        }

        return true;
    }

    /**
     * opcache_filecache_reset.
     */
    public function opcache_filecache_reset()
    {
        static $is_done = false;

        $fcdata = $this->opcache_filecache_only();
        if (!$is_done && !empty($fcdata)) {
            $dir = $fcdata['file_cache'];
            $cnt = 0;

            $this->suspend_cache_write(true);

            $max_execution_time = $this->get_max_execution_time(180);
            $slowdown = 0;
            foreach ($this->opcache_filecache_scanfiles($dir) as $object) {
                try {
                    if ($object->isFile()) {
                        $file = nwdcx_normalizepath($object->getPathName());
                        if (false !== strpos($file, nwdcx_normalizepath(ABSPATH)) && is_writable($file)) {
                            @unlink($file);
                            ++$cnt;
                        }
                    }
                } catch (\Throwable $e) {
                    nwdcx_throwable(__METHOD__, $e);
                }

                if ($slowdown > 10) {
                    $slowdown = 0;
                    usleep(5000);
                }

                ++$slowdown;

                if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) {
                    break;
                }
            }

            clearstatcache(true);

            $is_done = true;

            $this->suspend_cache_write(false);

            return $cnt;
        }

        return false;
    }

    /**
     * opcache_is_cached.
     */
    public function opcache_is_cached($file)
    {
        if (!$this->is_opcache_enable()) {
            return -1;
        }

        $fcdata = $this->opcache_filecache_only();
        if (!empty($fcdata) && false !== strpos(nwdcx_normalizepath($file), nwdcx_normalizepath(ABSPATH))) {
            $filem = nwdcx_normalizepath(rtrim($fcdata['file_cache'], '/\\').'/*/'.ltrim($file, '/\\').'.bin');
            $match = glob($filem);
            if (!empty($match) && \is_array($match)) {
                return true;
            }
        }

        if (\function_exists('opcache_is_script_cached')) {
            return @opcache_is_script_cached($file);
        }

        return -1;
    }

    /**
     * opcache_flush.
     */
    public function opcache_flush($file)
    {
        if (!$this->is_php($file) || !@is_file($file) || !$this->is_opcache_enable()) {
            return false;
        }

        if ($this->opcache_filecache_flush($file)) {
            return true;
        }

        // wp 5.5
        if (\function_exists('wp_opcache_invalidate')) {
            return @wp_opcache_invalidate($file, true);
        }

        if (\function_exists('opcache_invalidate')) {
            return @opcache_invalidate($file, true);
        }

        return false;
    }

    /**
     * opcache_compile.
     */
    public function opcache_compile($file)
    {
        if (!$this->is_opcache_enable()) {
            return false;
        }

        if (!empty($_GET['_wpnonce']) && !empty($_GET['action']) && !empty($_GET['page']) && 'docket-cache' === $_GET['page'] && 'docket-flush-opcache' === $_GET['action']) {
            return -1;
        }

        if (\function_exists('opcache_compile_file') && $this->is_php($file) && @is_file($file) && false === $this->opcache_is_cached($file)) {
            $this->touch($file, time() - 60);

            try {
                return @opcache_compile_file($file);
            } catch (\Throwable $e) {
                nwdcx_throwable(__METHOD__, $e);
            }
        }

        return false;
    }

    /**
     * opcache_reset.
     */
    public function opcache_reset()
    {
        if (!$this->is_opcache_enable()) {
            return false;
        }

        try {
            if (!@opcache_reset()) {
                return $this->opcache_filecache_reset();
            }
        } catch (\Throwable $e) {
            nwdcx_throwable(__METHOD__, $e);

            return false;
        }

        // always true
        return true;
    }

    /**
     * opcache_cleanup.
     */
    public function opcache_cleanup()
    {
        add_action(
            'shutdown',
            function () {
                $this->opcache_reset();
            },
            \PHP_INT_MAX
        );
    }

    /**
     * define_cache_path.
     */
    public function define_cache_path($cache_path)
    {
        $content_path = \defined('DOCKET_CACHE_CONTENT_PATH') ? DOCKET_CACHE_CONTENT_PATH : WP_CONTENT_DIR;
        $content_path = nwdcx_normalizepath($content_path);

        $cache_path = !empty($cache_path) && '/' !== $cache_path ? rtrim($cache_path, '/\\').'/' : $content_path.'/cache/docket-cache/';
        $cache_path = nwdcx_normalizepath($cache_path);

        if (!$this->is_docketcachedir($cache_path)) {
            $cache_path = rtrim($cache_path, '/\\').'docket-cache/';
        }

        // create if not normal installation
        if (false === strpos($content_path, '/wp-content/')) {
            if (!@is_dir($cache_path)) {
                $this->mkdir_p($cache_path);
            }

            if (!@is_dir($content_path)) {
                $this->mkdir_p($content_path);
            }
        }

        return $cache_path;
    }

    /**
     * cachedir_flush.
     */
    public function cachedir_flush($dir, $cleanup = false, &$is_timeout = false)
    {
        static $is_done = false;

        if ($is_done) {
            return 0;
        }

        clearstatcache();

        $dir = nwdcx_normalizepath(realpath($dir));

        if (false === $dir || !@is_dir($dir) || !@is_writable($dir) || !$this->is_docketcachedir($dir)) {
            return false;
        }

        if ($this->is_dirempty($dir)) {
            return true;
        }

        $this->suspend_cache_write(true);

        $gcisrun_lock = $dir.'/.gc-is-run.txt';

        $max_execution_time = $this->get_max_execution_time(180);
        $flush_lock = DOCKET_CACHE_CONTENT_PATH.'/.object-cache-flush.txt';
        $flush_lock_expiry = time() + $max_execution_time + 10;
        if ($this->put($flush_lock, $flush_lock_expiry)) {
            $this->touch($flush_lock, $flush_lock_expiry);
        }

        $slowdown = 0;
        $cnt = 0;

        foreach ($this->scanfiles($dir) as $object) {
            try {
                if ($object->isFile()) {
                    $this->unlink($object->getPathName(), $cleanup ? true : false);
                    ++$cnt;
                }
            } catch (\Throwable $e) {
                nwdcx_throwable(__METHOD__, $e);
            }

            if ($slowdown > 10) {
                $slowdown = 0;
                usleep(5000);
            }

            ++$slowdown;

            if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) {
                $is_timeout = true;
                break;
            }
        }

        if ($cleanup) {
            // 24122020: deprecate
            $this->unlink($dir.'/index.php', true);

            // placeholder
            $this->unlink($dir.'/index.html', true);
        }

        $this->unlink($gcisrun_lock, true);

        if (@is_file($flush_lock)) {
            @unlink($flush_lock);
        }

        $this->suspend_cache_write(false);
        $is_done = true;

        return $cnt;
    }

    /**
     * cache_size.
     */
    public function cache_size($dir)
    {
        static $is_done = false;

        $bytestotal = 0;
        $fsizetotal = 0;
        $filestotal = 0;

        clearstatcache();
        $gcisrun_lock = $dir.'/.gc-is-run.txt';

        if (!$is_done && $this->is_docketcachedir($dir) && !@is_file(DOCKET_CACHE_CONTENT_PATH.'/.object-cache-flush.txt') && !@is_file($gcisrun_lock)) {
            // hardmax
            $maxfile = 999000; // 1000000 - 1000;
            $cnt = 0;
            $slowdown = 0;

            ignore_user_abort(true);
            $max_execution_time = $this->get_max_execution_time(180);

            $pattern = '@^([a-z0-9]{12})\-([a-z0-9]{12})\.php$@';
            foreach ($this->scanfiles($dir, null, $pattern) as $object) {
                try {
                    if ($object->isFile()) {
                        $fx = $object->getPathName();

                        if (!$this->remove_non_chunk_cache($dir, $fx)) {
                            $fs = $object->getSize();

                            if ($cnt >= $maxfile) {
                                $this->unlink($fx, true);
                            } elseif (0 === $fs) {
                                $this->unlink($fx, true);
                            } else {
                                $data = $this->cache_get($fx);
                                if (false === $data) {
                                    $this->unlink($fx, true);
                                } else {
                                    $bytestotal += \strlen(serialize($data));
                                    unset($data);

                                    $fsizetotal += $fs;

                                    ++$filestotal;
                                    ++$cnt;
                                }
                            }
                        }
                    }
                } catch (\Throwable $e) {
                    nwdcx_throwable(__METHOD__, $e);
                }

                if ($slowdown > 10) {
                    $slowdown = 0;
                    usleep(5000);
                }

                ++$slowdown;

                if ($max_execution_time > 0 && \defined('WP_START_TIMESTAMP') && (microtime(true) - WP_START_TIMESTAMP) > $max_execution_time) {
                    break;
                }

                if (@is_file($gcisrun_lock)) {
                    break;
                }
            }
        }

        $is_done = true;

        wp_cache_set('count_file', $filestotal, 'docketcache-gc', 86400);

        return [
            'timestamp' => time(),
            'size' => $bytestotal,
            'filesize' => $fsizetotal,
            'files' => $filestotal,
        ];
    }

    public function get_fatal_error_filename($file)
    {
        // sesimple yang mungkin.
        return $file.'-error.txt';
    }

    public function has_fatal_error_before($file)
    {
        $file_fatal = $this->get_fatal_error_filename($file);

        return @is_file($file_fatal);
    }

    public function validate_fatal_error_file($file)
    {
        $file_fatal = $this->get_fatal_error_filename($file);
        if (!@is_file($file_fatal)) {
            return;
        }

        if (!@is_file($file)) {
            @unlink($file_fatal);

            return;
        }

        $fm = time() - 10; // 10s
        if ($fm > @filemtime($file_fatal)) {
            if ($this->validate_file($file)) {
                @unlink($file_fatal);

                return;
            }

            // update timestamp
            $this->touch($file_fatal);
        }
    }

    private function suspend_cache_file($file, $error, $seconds = 0)
    {
        $seconds = (int) $seconds;
        $file_fatal = $this->get_fatal_error_filename($file);

        $errmsg = date('Y-m-d H:i:s T').\PHP_EOL.$error;
        if ($this->dump($file_fatal, $errmsg)) {
            if ($seconds > 0) {
                $this->touch($file, time() + $seconds);
            }

            $this->dump(\dirname($file_fatal).'/last-error.txt', $errmsg);

            return true;
        }

        return false;
    }

    private function capture_fatal_error()
    {
        register_shutdown_function(
            function () {
                $error = error_get_last();
                if ($error && \in_array($error['type'], [\E_ERROR, \E_PARSE, \E_COMPILE_ERROR, \E_USER_ERROR, \E_RECOVERABLE_ERROR, \E_CORE_ERROR], true)) {
                    $file_error = $error['file'];

                    if ($this->is_docketcachefile($file_error)) {
                        $this->dump($file_error, '<?php return false;');

                        $error['file'] = basename($error['file']);
                        // 300s = 5m delay
                        if ($this->suspend_cache_file($file_error, $this->export_var($error), 300)) {
                            // refresh page if possible
                            if ('cli' !== \PHP_SAPI && !wp_doing_ajax()) {
                                echo '<script>document.body.innerHTML="";window.setTimeout(function() { window.location.assign(window.location.href); }, 750);</script>';
                            }
                        }
                    }
                }
            }
        );
    }

    /**
     * suspend_cache_write.
     */
    public function suspend_cache_write($suspend = null)
    {
        static $_suspend = false;

        if (\is_bool($suspend)) {
            $_suspend = $suspend;
        }

        return $_suspend;
    }

    /**
     * cache_get.
     */
    public function cache_get($file)
    {
        if (!@is_file($file) || empty($this->filesize($file))) {
            return false;
        }

        if (!$handle = @fopen($file, 'rb')) {
            return false;
        }

        if ($this->has_fatal_error_before($file)) {
            return false;
        }

        // capture non-throwable
        if (nwdcx_construe('CAPTURE_FATALERROR')) {
            $this->capture_fatal_error();
        }

        // cache data
        $data = [];

        // include when we can read, try to avoid fatal error.
        // LOCK_SH = shared lock
        if (flock($handle, \LOCK_SH)) {
            try {
                $data = @include $file;
            } catch (\Throwable $e) {
                $error = $e->getMessage();

                $file_error = $e->getFile();
                if ($this->is_docketcachefile($file_error)) {
                    $errmsg = 'E: '.$error.\PHP_EOL;
                    $errmsg .= 'L: '.$e->getLine().\PHP_EOL;
                    $errmsg .= 'F: '.basename($file_error).\PHP_EOL;
                    $this->suspend_cache_file($file, $errmsg);
                }

                $this->log('err', '000000000000-000000000000', 'cache_get: '.$error);
                $data = false;
            }

            @flock($handle, \LOCK_UN);
        }
        @fclose($handle);

        if (empty($data) || !isset($data['data'])) {
            return false;
        }

        // 15052022
        /*if (false === $this->opcache_is_cached($file)) {
            $this->opcache_compile($file);
        }*/

        return $data;
    }

    /**
     * code_stub.
     */
    public function code_stub($data = '')
    {
        $is_debug = \defined('WP_DEBUG') && WP_DEBUG;
        $is_data = !empty($data);
        $is_precache = nwdcx_construe('PRECACHE');
        // capture class not exist.
        if (($is_debug || $is_precache) && empty($GLOBALS['DOCKET_CACHE_CODESTUB_FALSE'])) {
            $GLOBALS['DOCKET_CACHE_CODESTUB_FALSE'] = [];
        }

        $ucode = '';
        if ($is_data && false !== strpos($data, 'Registry::p(')) {
            if (@preg_match_all('@Registry::p\(\'([a-zA-Z0-9_]+)\'\)@', $data, $mm)) {
                if (!empty($mm) && isset($mm[1]) && \is_array($mm[1])) {
                    $cls = $mm[1];
                    foreach ($cls as $clsname) {
                        if ('stdClass' !== $clsname) {
                            $clsfname = false;
                            if ($is_debug) {
                                $reflector = new \ReflectionClass($clsname);
                                $clsfname = $reflector->getFileName();
                                if (false !== $clsfname) {
                                    $clsfname = str_replace(ABSPATH, '', $clsfname);
                                    $ucode .= '/* f: '.$clsfname.' */'.\PHP_EOL;
                                }
                            }

                            // 13022023, self-note to future me.
                            // The logic here, we can't just "include_once" a class if it doesn't exist
                            // because if the main code uses "include" or "require" it will throw an error
                            // since the class has already been loaded by us.
                            $ucode .= "if(!@class_exists('".$clsname."',false)){";
                            if ($is_debug || $is_precache) {
                                $ucode .= "\$GLOBALS['DOCKET_CACHE_CODESTUB_FALSE'][__FILE__]=['".$clsname."','".$clsfname."'];";
                            }
                            $ucode .= 'return false;}'.\PHP_EOL;
                        }
                    }
                    unset($cls, $clsname);
                }
                unset($mm);
            }
        }

        $code = '<?php ';
        if ($is_data) {
            if (!empty($ucode) || false !== strpos($data, '\Nawawi\DocketCache\\')) {
                $code .= "if(!\defined('ABSPATH')){return false;}";
            }
            $code .= \PHP_EOL;
            if (!empty($ucode)) {
                $code .= $ucode;
            }
            $code .= 'return '.$data.';'.\PHP_EOL;
            $code .= '/*@DOCKET_CACHE_EOF*/';
        }

        return $code;
    }

    /**
     * log.
     */
    public function log($tag, $id, $data, $caller = '')
    {
        $do_flush = false;
        $file = nwdcx_constval('LOG_FILE');
        if (empty($file)) {
            return false;
        }

        $logsize = nwdcx_constval('LOG_SIZE');
        if (empty($logsize) || !\is_int($logsize)) {
            $logsize = 0;
        }

        if (is_multisite()) {
            $file = nwdcx_network_filepath($file);
        }

        if (@is_file($file)) {
            if (nwdcx_construe('LOG_FLUSH') && 'flush' === $tag || ($logsize > 0 && $this->filesize($file) >= $logsize)) {
                $do_flush = true;
            }
        }

        $timestamp = date('Y-m-d H:i:s T');

        $rtag = trim($tag);
        if (\in_array($rtag, ['hit', 'miss', 'err', 'exp', 'del', 'info', 'dev'])) {
            $tag = str_pad($rtag, 5);
        }
        $log = '['.$timestamp.'] '.$tag.': "'.$id.'" "'.trim($data).'" "'.$caller.'"';

        $flags = !$do_flush ? \LOCK_EX | \FILE_APPEND : \LOCK_EX;
        $do_chmod = !@is_file($file);
        if (@file_put_contents($file, $log.\PHP_EOL, $flags)) {
            if ($do_chmod) {
                $this->chmod($file);
            }

            return true;
        }

        return false;
    }

    /**
     * sanitize_timestamp.
     */
    public function sanitize_timestamp($time)
    {
        $time = (int) $time;
        if ($time < 0) {
            $time = 0;
        } else {
            $max = ceil(log10($time));
            if ($max > 10 || 'NaN' === $max) {
                $time = 0;
            }
        }

        return $time;
    }

    /**
     * sanitize_maxttl.
     */
    public function sanitize_maxttl($seconds)
    {
        $seconds = $this->sanitize_timestamp($seconds);

        // 86400 = 1d
        // 345600 = 4d
        // 2419200 = 28d
        if ($seconds < 86400) {
            $seconds = 345600;
        } elseif ($seconds > 2419200) {
            $seconds = 2419200;
        }

        return $seconds;
    }

    /**
     * sanitize_maxfile.
     */
    public function sanitize_maxfile($maxfile, $default = 50000)
    {
        $maxfile = (int) $maxfile;
        $min = 200;
        $max = 1000000;
        if (empty($maxfile)) {
            $maxfile = $default;
        }

        if ($maxfile < $min) {
            $maxfile = $default;
        } elseif ($maxfile > $max) {
            $maxfile = $max;
        }

        return $maxfile;
    }

    /**
     * sanitize_precache_maxfile.
     */
    public function sanitize_precache_maxfile($maxfile)
    {
        $default = 100;
        if (empty($maxfile) || (int) $maxfile < 1) {
            return $default;
        }

        return $this->sanitize_maxfile($maxfile, $default);
    }

    /**
     * sanitize_maxsize.
     */
    public function sanitize_maxsize($bytes)
    {
        $min = 1048576; // 1M
        $max = 10485760; // 10M
        $bytes = (int) $bytes;

        if ($bytes < $min) {
            return 3145728; // 3M
        }

        if ($bytes > $max) {
            $bytes = $max;
        }

        return $bytes;
    }

    /**
     * sanitize_maxsizedisk.
     */
    public function sanitize_maxsizedisk($bytes)
    {
        if (empty($bytes) || !\is_int($bytes)) {
            return 524288000; // 500MB
        }

        if ($bytes < 104857600) {
            $bytes = 104857600;
        }

        return $bytes;
    }

    /**
     * valid_timestamp.
     */
    public function valid_timestamp($timestamp)
    {
        $timestamp = $this->sanitize_timestamp($timestamp);

        return $timestamp > 0;
    }

    // put here because use in Becache.
    public function keys_alloptions()
    {
        // reference: wp-admin/includes/schema.php
        // exclude: rewrite_rules, active_plugins, uninstall_plugins, cron
        // exclude: widget_categories, widget_text, widget_rss
        return [
            'siteurl' => 1,
            'home' => 1,
            'blogname' => 1,
            'blogdescription' => 1,
            'users_can_register' => 1,
            'admin_email' => 1,
            'start_of_week' => 1,
            'use_balanceTags' => 1,
            'use_smilies' => 1,
            'require_name_email' => 1,
            'comments_notify' => 1,
            'posts_per_rss' => 1,
            'rss_use_excerpt' => 1,
            'mailserver_url' => 1,
            'mailserver_login' => 1,
            'mailserver_pass' => 1,
            'mailserver_port' => 1,
            'default_category' => 1,
            'default_comment_status' => 1,
            'default_ping_status' => 1,
            'default_pingback_flag' => 1,
            'posts_per_page' => 1,
            'date_format' => 1,
            'time_format' => 1,
            'links_updated_date_format' => 1,
            'comment_moderation' => 1,
            'moderation_notify' => 1,
            'permalink_structure' => 1,
            'hack_file' => 1,
            'blog_charset' => 1,
            'category_base' => 1,
            'ping_sites' => 1,
            'comment_max_links' => 1,
            'gmt_offset' => 1,
            'default_email_category' => 1,
            'template' => 1,
            'stylesheet' => 1,
            'comment_registration' => 1,
            'html_type' => 1,
            'use_trackback' => 1,
            'default_role' => 1,
            'db_version' => 1,
            'uploads_use_yearmonth_folders' => 1,
            'upload_path' => 1,
            'blog_public' => 1,
            'default_link_category' => 1,
            'show_on_front' => 1,
            'tag_base' => 1,
            'show_avatars' => 1,
            'avatar_rating' => 1,
            'upload_url_path' => 1,
            'thumbnail_size_w' => 1,
            'thumbnail_size_h' => 1,
            'thumbnail_crop' => 1,
            'medium_size_w' => 1,
            'medium_size_h' => 1,
            'avatar_default' => 1,
            'large_size_w' => 1,
            'large_size_h' => 1,
            'image_default_link_type' => 1,
            'image_default_size' => 1,
            'image_default_align' => 1,
            'close_comments_for_old_posts' => 1,
            'close_comments_days_old' => 1,
            'thread_comments' => 1,
            'thread_comments_depth' => 1,
            'page_comments' => 1,
            'comments_per_page' => 1,
            'default_comments_page' => 1,
            'comment_order' => 1,
            'timezone_string' => 1,
            'page_for_posts' => 1,
            'page_on_front' => 1,
            'default_post_format' => 1,
            'link_manager_enabled' => 1,
            'finished_splitting_shared_terms' => 1,
            'site_icon' => 1,
            'medium_large_size_w' => 1,
            'medium_large_size_h' => 1,
            'wp_page_for_privacy_policy' => 1,
            'show_comments_cookies_opt_in' => 1,
            'admin_email_lifespan' => 1,
            'initial_db_version' => 1,
            'fresh_site' => 1,
            'current_theme' => 1,
            'theme_switched' => 1,
            'generate_update_core_typography' => 1,
            'WPLANG' => 1,
            'new_admin_email' => 1,
            'recovery_mode_email_last_sent' => 1,
            'comment_previously_approved' => 1,
            'finished_updating_comment_type' => 1,
            'db_upgraded' => 1,
            /* wp >= 5.6.0 */
            'auto_update_core_dev' => 1,
            'auto_update_core_minor' => 1,
            'auto_update_core_major' => 1,
            /* wp >= 5.7 */
            'https_detection_errors' => 1,
            /* wp >= 5.8 */
            'wp_force_deactivated_plugins' => 1,
        ];
    }

    /**
     * optimize_alloptions.
     */
    public function optimize_alloptions()
    {
        add_filter(
            'pre_cache_alloptions',
            function ($alloptions) {
                $wp_options = $this->keys_alloptions();

                foreach ($alloptions as $key => $value) {
                    // skip
                    if (empty($value)) {
                        continue;
                    }

                    if (!\array_key_exists($key, $wp_options)) {
                        unset($alloptions[$key]);
                    }
                }

                return $alloptions;
            },
            \PHP_INT_MIN
        );
    }
}