<?php
error_reporting(0);
defined('MINUTE') or define('MINUTE', 60);
defined('HOUR') or define('HOUR', 3600);
defined('DAY') or define('DAY', 86400);
defined('WEEK') or define('WEEK', 604800);
defined('MONTH') or define('MONTH', 2592000);
defined('YEAR') or define('YEAR', 31536000);
defined('LEAPYEAR') or define('LEAPYEAR', 31622400);

require_once DIR_SYSTEM . '/php-sql-parser.php';
class QCache
{
    private $setting, $query_count = 0, $parsed_query = array('result' => array(), 'active_tables' => array(), 'math_functions' => array(), 'sys_functions' => array());
    
    public function __construct()
    {
        $this->setting = array(
            'disable_functions' => array(
                'sys_exec',
                'sys_get',
                'sys_set',
                'load_file'
            ),
            'disable_tables' => array(
            ),
            'tables_cache' => array(
                'language' => DAY,
                'country' => DAY
            ),
            'methods_cache' => array(
                'Customer::getBalance' => MINUTE
            ),
            'scripts_cache' => array(
                '/index.php' => MINUTE
            ),
            'max_query' => 3,
            'default_ttl' => HOUR,
            'dir_cache' => DIR_CACHE . 'selects/'
        );
    }
    
    public function query($sql, $driver, $trace, $cache_ttl = NULL)
    {
        $hash = md5($sql, false);
        list($cache_file) = glob($this->setting['dir_cache'] . '*_' . $hash, GLOB_NOCHECK);
        
        if (is_file($cache_file)) {
            list($file_ttl) = explode('_', basename($cache_file));
            if (time() - filemtime($cache_file) < $file_ttl) {
                return unserialize(file_get_contents($cache_file));
            }
        }
        if (($this->query_count < $this->setting['max_query']) && $sql{0} === 'S' || $sql{0} === 's') {
            $this->parsed_query['result']         = new PHPSQLParser($sql);
            $this->parsed_query['result']         = $this->parsed_query['result']->parsed;
            $this->parsed_query['active_tables']  = $this->search($this->parsed_query['result'], 'expr_type', 'table');
            $this->parsed_query['math_functions'] = $this->search($this->parsed_query['result'], 'expr_type', 'aggregate_function');
            $this->parsed_query['sys_functions']  = $this->search($this->parsed_query['result'], 'expr_type', 'function');
            
            if ($this->is_disabled($this->setting['disable_functions'], $this->parsed_query['sys_functions'], 'base_expr')) {
                /* Защита от зацикливания переадресации в случае неверно указанных данных */
                if (!isset($_GET['forbidden'])) {
                    Header("HTTP/1.1 301 Moved Permanently");
                    Header("Location: /index.php?route=error/not_found&forbidden");
                } else {
                    trigger_error('Error: Could not load!');
                }
                exit;
            }
            if ($this->is_disabled($this->setting['disable_tables'], $this->parsed_query['active_tables'], 'table')) {
                if (!isset($_GET['forbidden'])) {
                    Header("HTTP/1.1 301 Moved Permanently");
                    Header("Location: /index.php?route=error/not_found&forbidden");
                } else {
                    trigger_error('Error: Could not load!');
                }
                exit;
            }
            
            $result    = $driver->query($sql);
            $cache_ttl = ($cache_ttl === NULL) ? $this->getMaxTTL(array(
                $this->getTTLCacheTable(),
                $this->getTTLCachePath($trace),
                $this->getTTLCacheClass($trace)
            )) : $cache_ttl;
            
            if ($cache_ttl) {
                file_put_contents($this->setting['dir_cache'] . $cache_ttl . '_' . $hash, serialize($result));
                ++$this->query_count;
            }
            return $result;
        } else {
            return $driver->query($sql);
        }
    }
    
    public function getTTLCacheTable()
    {
        foreach ($this->setting['tables_cache'] as $key => $val) {
            if ($this->search($this->parsed_query['active_tables'], 'table', $key))
                return $val;
        }
        return $this->setting['default_ttl'];
    }
    
    public function getTTLCachePath($trace)
    {
        $path = isset($trace[1]) ? implode('', explode(DIR_ROOT, $trace[1]['file'])) : NULL;
        return isset($this->setting['scripts_cache'][$path]) ? $this->setting['scripts_cache'][$path] : $this->setting['default_ttl'];
    }
    
    public function getTTLCacheClass($trace)
    {
        if (isset($trace[1])) {
            $whole_class  = $trace[1]['class'] . '::NULL';
            $class_method = $trace[1]['class'] . '::' . $trace[1]['function'];
            $each_method  = 'NULL::' . $trace[1]['function'];
            
            if (isset($this->setting['methods_cache'][$whole_class]))
                return $this->setting['methods_cache'][$whole_class];
            if (isset($this->setting['methods_cache'][$class_method]))
                return $this->setting['methods_cache'][$class_method];
            if (isset($this->setting['methods_cache'][$each_method]))
                return $this->setting['methods_cache'][$each_method];
        }
        return $this->setting['default_ttl'];
    }
    
    public function is_disabled($exc, $arr, $key)
    {
        foreach ($exc as $val) {
            if ($this->search($arr, $key, $val, true))
                return true;
        }
        return false;
    }
    
    public function search($array, $key, $value, $once = false)
    {
        $results = array();
        
        if (is_array($array)) {
            if (isset($array[$key]) && $array[$key] == $value) {
                /* если достаточно одного найденного элемента */
                if ($once) {
                    return $array;
                } else {
                    $results[] = $array;
                }
            }
            
            foreach ($array as $subarray)
                $results = array_merge($results, $this->search($subarray, $key, $value));
        }
        return $results;
    }
    
    public function getMaxTTL($array)
    {
        return (min($array) == 0) ? false : max($array);
    }
}
?>