<?php
/**
 *
 * @package Kleeja
 * @copyright (c) 2007 Kleeja.net
 * @license ./docs/license.txt
 *
 */


//no direct access
if (! defined('IN_COMMON'))
{
    exit;
}

class kleeja_style
{
    protected $vars; //Reference to $GLOBALS
    protected $loop = [];
    protected $reg  = ['var' => '/([{]{1,2})+([A-Z0-9_\.]+)[}]{1,2}/i'];
    public $caching = true; //save templates as caches to not compiled a lot of times

    /**
     * Function to load a template file.
     * @param $template_name
     * @param null|mixed $style_path
     */
    protected function _load_template($template_name, $style_path = null)
    {
        global $config, $THIS_STYLE_PATH_ABS, $STYLE_PATH_ADMIN_ABS, $DEFAULT_PATH_ADMIN_ABS;


        if (! ($template_path = $this->template_exists($template_name, $style_path)))
        {
            big_error('No Template !', 'Requested <b>"' . $template_name . '"</b> template doesnt exist!');
        }

        $html = file_get_contents($template_path);
        $html = $this->_parse($html, $template_name);

        //use 'b' to force binary mode
        if ($filename = @fopen(PATH . 'cache/tpl_' . $this->re_name_tpl($template_name, $style_path) . '.php', 'wb'))
        {
            is_array($plugin_run_result = Plugins::getInstance()->run('style_load_template_func', get_defined_vars())) ? extract($plugin_run_result) : null; //run hook


            @flock($filename, LOCK_EX);
            @fwrite($filename, $html);
            @flock($filename, LOCK_UN);
            @fclose($filename);
            // Read and write for owner, read for everybody else
            @chmod(PATH . 'cache/tpl_' . $this->re_name_tpl($template_name, $style_path) . '.php', 0644);
        }
    }

    /**
     * check if a template exists or not
     * @param $template_name
     * @param null $style_path
     */
    public function template_exists($template_name, $style_path  = null)
    {
        global $config, $STYLE_PATH_ADMIN_ABS, $THIS_STYLE_PATH_ABS, $DEFAULT_PATH_ADMIN_ABS;


        $is_admin_template = false;

        $style_path = str_replace(DIRECTORY_SEPARATOR, '/', $style_path);

        //admin template always begin with admin_
        if (substr($template_name, 0, 6) == 'admin_')
        {
            $current_style_path = ! empty($style_path) ? $style_path : $STYLE_PATH_ADMIN_ABS;
            $is_admin_template  = true;
        }
        else
        {
            $current_style_path = ! empty($style_path) ? $style_path : $THIS_STYLE_PATH_ABS;
        }


        $template_path = rtrim($current_style_path, '/') . '/' . $template_name . '.html';


        //if template not found and default style is there and not admin tpl
        $is_tpl_exist = file_exists($template_path);


        if (! $is_tpl_exist)
        {
            if (trim($config['style_depend_on']) != '')
            {
                $template_path_alternative = str_replace('/' . $config['style'] . '/', '/' . $config['style_depend_on'] . '/', $template_path);

                if (file_exists($template_path_alternative))
                {
                    $template_path = $template_path_alternative;
                    $is_tpl_exist  = true;
                }
            }
            elseif ($is_admin_template)
            {
                $template_path = $DEFAULT_PATH_ADMIN_ABS . $template_name . '.html';
                $is_tpl_exist  = true;
            }
            elseif ($config['style'] != 'default' && ! $is_admin_template)
            {
                $template_path_alternative = str_replace('/' . $config['style'] . '/', '/default/', $template_path);

                if (file_exists($template_path_alternative))
                {
                    $template_path = $template_path_alternative;
                    $is_tpl_exist  = true;
                }
            }
        }

        return $is_tpl_exist ? $template_path : false;
    }

    /**
     * Function to parse the Template Tags
     * @param mixed $html
     * @param mixed $template_name
     */
    protected function _parse($html, $template_name = '')
    {
        is_array($plugin_run_result = Plugins::getInstance()->run('style_parse_func', get_defined_vars())) ? extract($plugin_run_result) : null; //run hook


        $html = preg_replace_callback('/<IGNORE>(.*?)<\/IGNORE>/is', function($m) {
            return '<STRREV>' . strrev($m[1]) . '</STRREV>';
        }, $html);
        $html = preg_replace(['#<([\?%])=?.*?\1>#s', '#<script\s+language\s*=\s*(["\']?)php\1\s*>.*?</script\s*>#s', '#<\?php(?:\r\n?|[ \n\t]).*?\?>#s'], '', $html);
        $html = preg_replace_callback('/\(([{A-Z0-9_\.}\s!=<>]+)\?(.*):(.*)\)/iU', function($m) {
            return '<IF NAME="' . $m[1] . '">' . $m[2] . '<ELSE>' . $m[3] . '</IF>';
        }, $html);
        $html = preg_replace_callback('/<(IF|ELSEIF|UNLESS) (.+)>/iU', ['kleeja_style', '_if_callback'], $html);
        $html = preg_replace_callback('/<LOOP\s+NAME\s*=\s*(\"|)+([a-z0-9_\.]{1,})+(\"|)\s*>/i', function($m) {
            return '<?php foreach($this->vars["' . (strpos($m[2], '.') !== false ? str_replace('.', '"]["', $m[2]) : $m[2]) . '"] as $key=>$value){ ?>';
        }, $html);
        $html = preg_replace_callback(kleeja_style::reg('var'), ['kleeja_style', '_vars_callback'], $html);
        $html = preg_replace_callback('/<STRREV>(.*?)<\/STRREV>/is', function($m) {
            return strrev($m[1]);
        }, $html);

        $rep =
        [
            '/<\/(LOOP|IF|END|IS_BROWSER|UNLESS)>/i'                                           => '<?php } ?>',
            '/<INCLUDE(\s+NAME|)\s*=*\s*"(.+)"\s*(PATH=\s*=*\s*"(.+)")?>/iU'                   => '<?php echo $this->display("\\2",  empty("\\4") ? null : "\\4"); ?>',
            '/<IS_BROWSER\s*=\s*"([a-z0-9,]+)"\s*>/iU'                                         => '<?php if(is_browser("\\1")){ ?>',
            '/<IS_BROWSER\s*\!=\s*"([a-z0-9,]+)"\s*>/iU'                                       => '<?php if(!is_browser("\\1")){ ?>',
            '/(<ELSE>|<ELSE\s?\/>)/i'                                                          => '<?php }else{ ?>',
            '/<ODD\s*=\s*"([a-zA-Z0-9_\-\+\.\/]+)"\s*>(.*?)<\/ODD\>/is'                        => "<?php if(intval(\$value['\\1'])%2){?> \\2 <?php } ?>",
            '/<EVEN\s*=\s*"([a-zA-Z0-9_\-\+\.\/]+)"\s*>(.*?)<\/EVEN>/is'                       => "<?php if(intval(\$value['\\1'])% 2 == 0){?> \\2 <?php } ?>",
            '/<RAND\s*=\s*"(.*?)\"\s*,\s*"(.*?)"\s*>/is'                                       => "<?php \$KLEEJA_tpl_rand_is=(!isset(\$KLEEJA_tpl_rand_is) || \$KLEEJA_tpl_rand_is==0)?1:0; print((\$KLEEJA_tpl_rand_is==1) ?'\\1':'\\2'); ?>",
            '/\{%(key|value)%\}/i'                                                             => '<?php echo $\\1; ?>',
        ];

        return preg_replace(array_keys($rep), array_values($rep), $html);
    }

    /**
     * if tag
     * @param $matches
     * @return string
     */
    protected function _if_callback($matches)
    {
        $atts      = call_user_func(['kleeja_style', '_get_attributes'], $matches[0]);
        $condition = '';

        foreach ([
            'NAME' => '', 'LOOP' => '', 'AND' => ' && ', 'OR' => ' || ', 'ISSET' => ' isset', 'EMPTY' => ' empty'
        ] as $attribute=>$separator)
        {
            if (isset($atts[$attribute]))
            {
                $haveParentheses = in_array($attribute, ['ISSET', 'EMPTY']);

                $condition .= $separator . ($haveParentheses ? '(' : '') .
                                $this->parse_condition($atts[$attribute], ! empty($atts['LOOP'])) .
                                ($haveParentheses ? ')' : '')
                    ;
            }
        }

        return strtoupper($matches[1]) == 'IF'
              ? '<?php if(' . $condition . '){ ?>'
              : (strtoupper($matches[1]) == 'UNLESS' ? '<?php if(!(' . $condition . ')){ ?>' : '<?php }elseif(' . $condition . '){ ?>');
    }

    protected function parse_condition($condition, $is_loop)
    {
        $char = [' eq ', ' lt ', ' gt ', ' lte ', ' gte ', ' neq ', '==', '!=', '>=', '<=', '<', '>'];
        $reps = ['==', '<', '>', '<=', '>=', '!=', '==', '!=', '>=', '<=', '<', '>'];

        if (trim($condition) == '')
        {
            return '';
        }

        $con = str_replace('$this->vars', '[----this-vars----]', $condition);

        if (preg_match('/(.*)(' . implode('|', $char) . ')(.*)/i', $con, $arr))
        {
            $arr[1] = trim($arr[1]);
            $var1   = $arr[1][0] != '$' ? call_user_func(['kleeja_style', '_var_callback'], (! $is_loop ? '{' . $arr[1] . '}' : '{{' . $arr[1] . '}}')) : $arr[1];
            $opr    = str_replace($char, $reps, $arr[2]);
            $var2   = trim($arr[3]);

            //check for type
            if ($var2[0] != '$' && ! preg_match('/[0-9]/', $var2))
            {
                $var2 = '"' . str_replace('"', '\"', $var2) . '"';
            }

            $con = "$var1 $opr $var2";
        }
        elseif ($con[0] !== '$' && strpos($con, '(') === false)
        {
            $con = call_user_func(['kleeja_style', '_var_callback'], (! $is_loop ? '{' . $con . '}' : '{{' . $con . '}}'));
        }

        return str_replace('[----this-vars----]', '$this->vars', $con);
    }


    /**
     * make variable printable
     * @param $matches
     * @return string
     */
    protected function _vars_callback($matches)
    {
        $variable = call_user_func(['kleeja_style', '_var_callback'], $matches);

        if (strpos($matches[0], '{lang')  !== false || strpos($matches[0], '{olang') !== false)
        {
            return '<?=' . $variable . ' ?? \'' . $matches[0] . '\'?>';
        }

        return '<?=' . $variable . '?>';
    }


    /**
     * variable replace
     * @param $matches
     * @return string
     */
    protected function _var_callback($matches)
    {
        if (! is_array($matches))
        {
            preg_match(kleeja_style::reg('var'), $matches, $matches);
        }

        $var = trim(! empty($matches[2]) ? str_replace('.', '\'][\'', $matches[2]) : '');

        if (empty($var))
        {
            return '';
        }

        return ! empty($matches[1]) && trim($matches[1]) == '{{' ? '$value[\'' . $var . '\']' : '$this->vars[\'' . $var . '\']';
    }

    /**
     * att variable replace
     * @param $matches
     * @return string
     */
    protected function _var_callback_att($matches)
    {
        return trim($matches[1]) == '{' ? $this->_var_callback($matches) : '{' . $this->_var_callback($matches) . '}';
    }


    /**
     * get reg var
     * @param $var
     * @return mixed
     */
    protected function reg($var)
    {
        $vars = get_class_vars(__CLASS__);
        return $vars['reg'][$var];
    }


    /**
     * get tag  attributes
     * @param $tag
     * @return array
     */
    protected function _get_attributes($tag)
    {
        preg_match_all('/([a-z]+)="(.+)?"/iU', $tag, $attribute);

        $attributes = [];

        for ($i = 0; $i < count($attribute[1]); $i++)
        {
            $att = strtoupper($attribute[1][$i]);

            $attributes[$att] = preg_replace_callback(kleeja_style::reg('var'), ['kleeja_style', '_var_callback'], $attribute[2][$i]);
        }
        return $attributes;
    }

    /**
     * Assign Variables
     * @param $var
     * @param $to
     */
    public function assign($var, $to)
    {
        $GLOBALS[$var] = $to;
    }


    /**
     * load parser and return page content
     * @param $template_name
     * @param  null         $style_path optional, good for plugins
     * @return mixed|string
     */
    public function display($template_name, $style_path = null)
    {
        global $config;

        $this->vars = &$GLOBALS;

        //is there ?
        if (! file_exists(PATH . 'cache/tpl_' . $this->re_name_tpl($template_name, $style_path) . '.php') || ! $this->caching)
        {
            $this->_load_template($template_name, $style_path);
        }

        ob_start();
        include PATH . 'cache/tpl_' . $this->re_name_tpl($template_name, $style_path) . '.php';
        $page = ob_get_contents();
        ob_end_clean();

        return $page;
    }

    /**
     * generate admin option block
     * @param $html
     * @return string
     */
    public function admindisplayoption($html)
    {
        $this->vars = &$GLOBALS;

        $eval_on = false;
        eval('$eval_on = true;');

        $parsed_html = trim($this->_parse(stripcslashes($html)));

        ob_start();

        if ($eval_on)
        {
            eval(' ?' . '>' . $parsed_html . '<' . '?php ');
        }
        else
        {
            $path  = PATH . 'cache/tpl_' . md5($parsed_html) . '.php';
            file_put_contents($path, $parsed_html);
            include_once $path;
        }

        $page = ob_get_contents();
        ob_end_clean();

        return $page;
    }

    /**
     * change name of template to be valid
     * @param $name
     * @param  null|mixed $style_path
     * @return mixed
     */
    protected function re_name_tpl($name, $style_path = null)
    {
        return preg_replace('/[^a-z0-9-_]/', '-', strtolower($name)) .
            (! empty($style_path) ? md5($style_path) : '');
    }
}
