<?php


// mailbeez caching class

/*

    // set cache reference
    $this->set_cache_id($cache_id, 'cacheblock');


    // return cached value if exists
    if ($cache = $this->read_cache()) {
        return $cache['value'];
    }

    // calculate value
    $cnt = time();


    // cache and return
    return $this->set_cache($cnt, 'session'); // session | request
 *
 */

mh_define('MAILBEEZ_APP_CACHE_FOLDER', MH_DIR_FS_CACHE);

class mailbeez_cache
{

    const gc_time = 600; // how many seconds between garbage collection
    var $code = 'mailbeez_cache';
    public $session_cache_time;
    public $app_cache_time;
    public $cache_id;
    public $cache_reference;
    public $cache = array();



    function __construct()
    {
        $this->session_cache_time = 60; // cache time in sec
        $this->app_cache_time = 3600 * 60; // cache time in sec

    }

    function mailbeez_cache()
    {
        self::__construct();
    }

    function set_cache_id($id, $reference = false)
    {
        $this->cache_id = ($id) ? urlencode($id) : false;
        $this->cache_reference = ($reference) ? 'cacheblock_' . urlencode($reference) : 'main';
    }

    // request object caching
    function set_cache($value, $scope = "app")
    {
        if (!$this->cache_id || !$this->cache_reference) {
            return $value;
        }

        $id = $this->cache_id;
        $ref = $this->cache_reference;

        $session_cache_key = 'cache_' . $this->code . $id;

        $data = array('value' => $value, 'timestamp' => time());

        if ($scope == 'session') {
            if (!isset($_SESSION[$ref])) {
                $_SESSION[$ref] = array();
                mh_log("created session cache: $ref - %s", $_SESSION[$ref]);
            }

            $_SESSION[$ref][$session_cache_key] = $data;
            mh_log("set session cache: $id - %s", $_SESSION[$ref][$session_cache_key]);
            mh_log("session cache for: $ref - %s", $_SESSION[$ref]);

        } elseif ($scope == 'app') {
            $this->_file_write($ref, $session_cache_key, $data);
            mh_log("set app cache: $id - %s", "$ref, $session_cache_key");
        } elseif ($scope == 'request') {
            $this->cache[$id] = $data;
            mh_log("set request cache: $id - %s", $this->cache[$id]);
        }
        return $value;
    }

    function read_cache($scope = "app", $flag_expired = false)
    {
        if (!$this->cache_id || !$this->cache_reference) {
            return false;
        }
        $id = $this->cache_id;
        $ref = $this->cache_reference;

        $session_cache_key = 'cache_' . $this->code . $id;
        if ($scope == 'session') {
            if (!isset($_SESSION[$ref][$session_cache_key])) {
                mh_log("read session cache failed: %s", $id);
                mh_log("cache reference: %s", $ref);
                return false;
            } elseif (time() - $_SESSION[$ref][$session_cache_key]['timestamp'] > $this->session_cache_time) {
                mh_log("session cache timeout: $id - %s", $_SESSION[$ref][$session_cache_key]);
                return false;
            } else {
                mh_log("read session cache: $id - %s", $_SESSION[$ref][$session_cache_key]);
                return $_SESSION[$ref][$session_cache_key];
            }
        } elseif ($scope == 'app') {
            $data = $this->_file_read($ref, $session_cache_key);
            if (!$data) {
                mh_log("read app cache failed: %s", $id);
                mh_log("cache reference: %s", $ref);
                return false;
            } elseif (time() - $data['timestamp'] > $this->app_cache_time) {
                mh_log("app cache timeout: $id - %s", "$ref, $session_cache_key");

                if ($flag_expired) {
                    $data['expired'] = true;
                    return $data;
                }
                return false;
            } else {
                mh_log("read session cache: $id - %s", "$ref, $session_cache_key");
                return $data;
            }
        } elseif ($scope == 'request') {
            if (isset($this->cache[$id])) {
                mh_log("read request cache: $id - %s", $this->cache[$id]);
                return $this->cache[$id];
            } else {
                mh_log("read request cache failed: %s", $id);
                return false;
            }
        }
    }

    function get_timestamp($scope = "app")
    {
        if (!$this->cache_id || !$this->cache_reference) {
            return false;
        }
        $id = $this->cache_id;
        $ref = $this->cache_reference;

        $session_cache_key = 'cache_' . $this->code . $id;

        if ($scope == 'session') {
            if (!isset($_SESSION[$ref][$session_cache_key])) {
                return -1;
            } else {
                return $_SESSION[$ref][$session_cache_key]['timestamp'];
            }
        } elseif ($scope == 'app') {
            $data = $this->_file_read($ref, $session_cache_key);
            if (!$data) {
                return -1;
            } else {
                return $data['timestamp'];
            }
        } elseif ($scope == 'request') {
            return 0;
        }
    }

    function purge_cache($scope = "app")
    {
        $id = $this->cache_id;
        $ref = $this->cache_reference;

        $session_cache_key = 'cache_' . $this->code . $id;

        if ($scope == 'session') {
            mh_log("purge session cache: %s", $id);
            unset($_SESSION[$ref][$session_cache_key]);
        } elseif ($scope == 'app') {
            $this->_file_purge($ref, $session_cache_key);
        } elseif ($scope == 'request') {
            mh_log("purge request cache: %s", $id);
            unset($this->cache[$id]);
        }
        return true;
    }

    function purge_cache_group($scope = "app")
    {
        $ref = $this->cache_reference;

        if ($scope == 'session') {
            mh_log("purge session cache group: %s", $ref);
            unset($_SESSION[$ref]);
        } elseif ($scope == 'app') {
            mh_log("purge app cache group: %s", $ref);
            $this->_group_file_purge($ref);
        } elseif ($scope == 'request') {
            mh_log("NOT IMPLEMENTED: purge request cache group: %s", $ref);
        }
        return true;
    }


    function _file_read($ref, $session_cache_key)
    {
        $file_path = $this->_get_file_path($ref, $session_cache_key);
        $fdata = false;
        if (file_exists($file_path)) {
            $fdata = file_get_contents($file_path);
        }

        if ($fdata) {
            return unserialize($fdata);
        } else {
            return false;
        }
    }

    function _file_write($ref, $session_cache_key, $data)
    {
        $file_path = $this->_get_file_path($ref, $session_cache_key);

        if (($fp = fopen($file_path, 'wb')) !== FALSE) {
            if (flock($fp, LOCK_EX) === TRUE) {
                fwrite($fp, serialize($data));
                flock($fp, LOCK_UN);
            }
            fclose($fp);
        }
        return true;
    }

    function _file_purge($ref, $session_cache_key)
    {
        $file_path = $this->_get_file_path($ref, $session_cache_key);
        if (file_exists($file_path) && is_writable($file_path) && !stristr($file_path, '..')) {
            return @unlink($file_path);
        }
        return false;
    }

    function _group_file_purge($ref)
    {
        $file_path_pattern = $this->_get_group_file_path($ref);
        array_map('unlink', glob($file_path_pattern));
    }

    function _get_file_path($ref, $session_cache_key)
    {
        $ref .= md5($_SERVER['SERVER_NAME']);
        return MAILBEEZ_APP_CACHE_FOLDER . 'appcache_' . $ref . '$$' . md5($session_cache_key);
    }


    function _get_group_file_path($ref)
    {
        $ref .= md5($_SERVER['SERVER_NAME']);
        return MAILBEEZ_APP_CACHE_FOLDER . 'appcache_' . $ref . '$$*';
    }

    static function gc_file($pattern = '$$', $cache_life = 86400)
    {

        $gc_key = 'mailbeez_cache_gc_' . $pattern;

        if (!isset($_SESSION[$gc_key]) || (time() - $_SESSION[$gc_key] > mailbeez_cache::gc_time)) {

            if ($dir = opendir(MAILBEEZ_CONFIG_TEMPLATE_ENGINE_COMPILE_DIR)) {
                while (false !== ($c_file = readdir($dir))) {
                    $file_path = MAILBEEZ_CONFIG_TEMPLATE_ENGINE_COMPILE_DIR . $c_file;

                    if (file_exists($file_path) && !is_dir($file_path) && !stristr($file_path, '.') && !stristr($file_path, '..') && preg_match('/' . $pattern . '/', $c_file) && (time() - filemtime($file_path) >= $cache_life)) {
                        unlink($file_path);
                    }
                }
                closedir($dir);
            }
            $_SESSION[$gc_key] = time();
        }
    }


    static function gc($cache_time = 3000)
    {
        // strange bug on some servers, session does not fit into DB
        $session_size_limit = 60000; //TEXT = 64Kb


        $gc = false;

        foreach ($_SESSION as $session_key => $session_item) {
            if (is_array($session_item)) {
                foreach ($session_item as $session_cache_key => $session_cache_item) {
                    if (is_array($session_cache_item) && isset($session_cache_item['timestamp'])) {
                        if (time() - $session_cache_item['timestamp'] > $cache_time) {
                            unset($_SESSION[$session_key][$session_cache_key]);
                            $gc = true;
                            //echo "<br>unset ($cache_time) [$session_key][$session_cache_key]";
                        }
                    }
                }

            }
        }

        if ($gc && strlen(serialize($_SESSION)) > $session_size_limit) {
            //echo "session size: " . strlen(serialize($_SESSION)) . " bytes<br>" ;
            if ($cache_time > 360) {
                mailbeez_cache::gc($cache_time / 2);
            }
        }
    }
}
