plugins/tpl/classes/yf_tpl.class.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

/**
 * Stadard Framework template engine.
 *
 * @author      YFix Team <yfix.dev@gmail.com>
 * @version      1.0
 */
class yf_tpl
{
    /** @var string @conf_skip Path to the templates (including current theme path) */
    public $TPL_PATH = '';
    /** @var string default template name */
    public $TPL_NAME = 'main';
    /** @var bool Compressing output by cutting '\t','\r','\n','  ','   ' */
    public $COMPRESS_OUTPUT = false;
    /** @var bool Using SEO - friendly URLs (All links need to be absolute) */
    public $REWRITE_MODE = false;
    /** @var bool Custom meta information (customizable for every page) : page titles, meta keywords, description */
    public $CUSTOM_META_INFO = false;
    /** @var bool Exit after sending main content */
    public $EXIT_AFTER_ECHO = false;
    /** @var bool Use database to store templates */
    public $GET_STPLS_FROM_DB = false;
    /** @var bool SECURITY: allow or not eval php code (with _PATTERN_INCLUDE) */
    public $ALLOW_EVAL_PHP_CODE = true;
    /** @var bool Get all templates from db or not (1 query or multiple)  (NOTE: If true - Slow PHP processing but just 1 db query) */
    public $FROM_DB_GET_ALL = false;
    /** @var bool Catch any output before gzipped content (works only with GZIP) */
    public $OB_CATCH_CONTENT = true;
    /** @var bool Use or not Tidy to cleanup output */
    public $TIDY_OUTPUT = false;
    /** @var bool Use backtrace to get STPLs source (where called from) FOR DEBUG_MODE ONLY ! */
    public $USE_SOURCE_BACKTRACE = true;
    /** @var bool If available - use packed STPLs without checking if some exists in project */
    public $AUTO_LOAD_PACKED_STPLS = false;
    /** @var bool Allow custom filter for all parsed stpls */
    public $ALLOW_CUSTOM_FILTER = false;
    /** @var bool Allow language-based special stpls */
    public $ALLOW_LANG_BASED_STPLS = false;
    /** @var bool Allow skin inheritance (only one level used) */
    public $ALLOW_SKIN_INHERITANCE = true;
    /** @var bool Allow to compile templates */
    public $COMPILE_TEMPLATES = false;
    /** @var bool TTL for compiled stpls */
    public $COMPILE_TTL = 3600;
    /** @var bool TTL for compiled stpls */
    public $COMPILE_CHECK_STPL_CHANGED = false;
    /** @var bool Allow pure php templates */
    public $ALLOW_PHP_TEMPLATES = false;
    /** @var bool */
    public $DEBUG_STPL_VARS = false;
    /** @var bool Will add cur date, generation time, memory and db queries into any common page before body */
    public $ADD_QUICK_PAGE_INFO = true;
    /** @var bool Compile templates folder */
    public $COMPILED_DIR = 'stpls_compiled/';
    /** @var string @conf_skip */
    public $_STPL_EXT = '.stpl';
    /** @var string @conf_skip Ability to use files with these extensions as templates */
    public $ALLOWED_EXTS = ['tpl', 'stpl', 'html'];
    /** @var string @conf_skip */
    public $_THEMES_PATH = 'templates/';
    /** @var string @conf_skip */
    public $_IMAGES_PATH = 'images/';
    /** @var string @conf_skip */
    public $_UPLOADS_PATH = 'uploads/';
    /** @var string Current tempalte engine dirver to use */
    public $DRIVER_NAME = 'yf';
    /** @var array Global scope tags (included in any parsed template) */
    public $_global_tags = [];
    /** @var array @conf_skip  For '_process_conditions', Will be availiable in conditions with such form: {if('get.object' eq 'login_form')} Hello from login form {/if} */
    public $_avail_arrays = [
        'get' => '_GET',
        'post' => '_POST',
        'server' => '_SERVER',
        'env' => '_ENV',
    ];

    /**
     * Catch missing method call.
     * @param mixed $name
     * @param mixed $args
     */
    public function __call($name, $args)
    {
        return main()->extend_call($this, $name, $args);
    }

    /**
     * Framework constructor.
     */
    public function _init()
    {
        // Needed to ensure backtracking still works on big templates (extended from 1 000 000 on 26kb stpl js() parsing)
        ini_set('pcre.backtrack_limit', '10000000');

        if (defined('IS_FRONT')) {
            conf('IS_FRONT', (bool) IS_FRONT);
        }
        $this->IS_FRONT = (bool) conf('IS_FRONT');
        // Set custom skin
        if ( ! empty($_SESSION['user_skin']) && MAIN_TYPE_USER) {
            conf('theme', $_SESSION['user_skin']);
        } elseif (defined('DEFAULT_SKIN')) {
            conf('theme', DEFAULT_SKIN);
        }
        if ( ! conf('theme')) {
            conf('theme', MAIN_TYPE);
        }
        // Directory where themes are stored
        conf('THEMES_PATH', $this->_THEMES_PATH);
        // Template files extensions
        conf('_STPL_EXT', $this->_STPL_EXT);
        // Set path to the templates including selected skin
        $this->TPL_PATH = $this->_THEMES_PATH . conf('theme') . '/';

        if ($this->COMPRESS_OUTPUT) {
            $this->register_output_filter([$this, '_simple_cleanup_callback'], 'simple_cleanup');
        }
        if ($this->ALLOW_LANG_BASED_STPLS) {
            $this->_lang_theme_path = PROJECT_PATH . $this->_THEMES_PATH . conf('theme') . '.' . conf('language') . '/';
            if ( ! file_exists($this->_lang_theme_path)) {
                $this->ALLOW_LANG_BASED_STPLS = false;
                $this->_lang_theme_path = '';
            }
        }
        if ($this->ALLOW_SKIN_INHERITANCE) {
            if (defined('INHERIT_SKIN')) {
                conf('INHERIT_SKIN', INHERIT_SKIN);
            }
            if (conf('INHERIT_SKIN') != conf('theme')) {
                $this->_INHERITED_SKIN = conf('INHERIT_SKIN');
            }
            if (defined('INHERIT_SKIN2')) {
                conf('INHERIT_SKIN2', INHERIT_SKIN2);
            }
            if (conf('INHERIT_SKIN2') != conf('theme')) {
                $this->_INHERITED_SKIN2 = conf('INHERIT_SKIN2');
            }
        }
        // Turn off CPU expensive features on overloading
        if (conf('HIGH_CPU_LOAD') == 1) {
            $this->COMPRESS_OUTPUT = false;
            $this->TIDY_OUTPUT = false;
            $this->FROM_DB_GET_ALL = false;
        }
        $this->_init_global_tags();

        if (DEBUG_MODE) {
            $this->register_output_filter([$this, '_debug_mode_callback'], 'debug_mode');
        }
        if (main()->is_console()) {
            $this->OB_CATCH_CONTENT = false;
        }
        $this->_set_default_driver($this->DRIVER_NAME);
    }

    /**
     * @param mixed $name
     */
    public function _set_default_driver($name = '')
    {
        if ( ! $name) {
            $name = $this->DRIVER_NAME;
        }
        if ( ! $name) {
            $name = 'yf';
        }
        $this->DRIVER_NAME = $name;
        $this->driver = _class('tpl_driver_' . $name, 'classes/tpl/');
    }

    /**
     * Global scope tags.
     */
    public function _init_global_tags()
    {
        $data = [
            'main_user_id' => (int) main()->USER_ID,
            'is_logged_in' => (int) ((bool) main()->USER_ID),
            'site_id' => (int) conf('SITE_ID'),
            'lang' => conf('language'),
            'tpl_path' => MEDIA_PATH . $this->TPL_PATH,
        ];
        foreach ($data as $k => $v) {
            $this->_global_tags[$k] = $v;
        }
    }

    /**
     * Initialization of the main content
     * Throws one 'echo' at the end.
     */
    public function init_graphics()
    {
        $init_type = MAIN_TYPE;
        // Do not remove this!
        $this->_init_global_tags();
        // Default user group
        if ($init_type == 'user' && empty($_SESSION['user_group'])) {
            $_SESSION['user_group'] = 1;
        }
        if (main()->OUTPUT_CACHING && $init_type == 'user' && $_SERVER['REQUEST_METHOD'] == 'GET') {
            _class('output_cache')->_process_output_cache();
        }
        if ( ! main()->no_graphics()) {
            if ($this->OB_CATCH_CONTENT) {
                ob_start();
            }
            // Trying to get default task
            if ($init_type == 'user' && ! empty($_SESSION['user_id']) && ! empty($_SESSION['user_group'])) {
                $go = conf('default_page_user');
            } elseif ($init_type == 'admin') {
                $go = conf('default_page_admin');
            }
            // If setting exists - assign it to the location
            if ( ! empty($go) && empty($_GET['object'])) {
                $go = str_replace(['./?', './'], '', $go);
                $tmp_array = [];
                parse_str($go, $tmp_array);
                foreach ((array) $tmp_array as $k => $v) {
                    $_GET[$k] = $v;
                }
            }
            $skip_prefetch = false;
            // Determine what template need to be loaded in the center area
            $tpl_name = $this->TPL_NAME;
            if ($init_type == 'admin' && (empty($_SESSION['admin_id']) || empty($_SESSION['admin_group']))) {
                $tpl_name = 'login';
                if (main()->is_ajax()) {
                    no_graphics(true);
                    main()->IS_403 = true;
                    header(($_SERVER['SERVER_PROTOCOL'] ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1') . ' 403 Forbidden');
                    $skip_prefetch = true;
                }
                if ( ! main()->is_console()) {
                    $skip_prefetch = true;
                }
            }
            if ($this->GET_STPLS_FROM_DB && $this->FROM_DB_GET_ALL) {
                $tmp = from('templates')->where('theme_name', conf('theme'))->where('active', '1')->get_2d('name,text');
                foreach ((array) $data as $k => $v) {
                    $tmp[$k] = stripslashes($v);
                }
                $this->_TMP_FROM_DB = $tmp;
                unset($tmp);
            }
            if ( ! $skip_prefetch) {
                if (main()->is_console()) {
                    // Skip security checks for console mode
                    _class('core_blocks')->tasks(false);
                } else {
                    _class('core_blocks')->prefetch_center();
                }
            }
        }
        if ( ! main()->no_graphics()) {
            $body['content'] = $this->_init_main_stpl($tpl_name);
            $this->_CENTER_RESULT = '';
            if ($this->CUSTOM_META_INFO && $init_type == 'user') {
                $this->register_output_filter([$this, '_custom_replace_callback'], 'custom_replace');
            }
            if ($init_type == 'user' && _class('graphics')->IFRAME_CENTER && (false === strpos($_SERVER['QUERY_STRING'], 'center_area=1'))) {
                $this->register_output_filter([$this, '_replace_for_iframe_callback'], 'replace_for_iframe');
            }
        }
        if ( ! main()->no_graphics()) {
            // Replace images paths with their absolute ones
            if ($this->REWRITE_MODE && $init_type != 'admin') {
                $this->register_output_filter([$this, '_rewrite_links_callback'], 'rewrite_links');
            }
            if ($this->TIDY_OUTPUT && $init_type != 'admin') {
                $this->register_output_filter([$this, '_tidy_cleanup_callback'], 'tidy_cleanup');
            }
            $body['content'] = $this->_apply_output_filters($body['content']);
            if (main()->OUTPUT_CACHING && $init_type == 'user' && $_SERVER['REQUEST_METHOD'] == 'GET') {
                _class('output_cache')->_put_page_to_output_cache($body);
            }
            if ( ! main()->is_console() && ! main()->is_ajax()) {
                if (DEBUG_MODE) {
                    $body['debug_info'] = common()->show_debug_info();
                }
                $_last_pos = strpos($body['content'], '</body>');
                if ($_last_pos) {
                    $body['content'] = substr($body['content'], 0, $_last_pos) . $body['debug_info'] . '</body></html>';
                    $body['debug_info'] = '';
                }
                if ($this->ADD_QUICK_PAGE_INFO) {
                    $body['exec_time'] = $this->_get_quick_page_info();
                }
            }
            $output = implode('', $body);
            $this->_output_body_length = strlen($output);
            main()->_send_main_headers($this->_output_body_length);
            // Throw generated output to user
            echo $output;
        }
        if (DEBUG_MODE && main()->no_graphics() && ! main()->is_console() && ! main()->is_ajax()) {
            echo common()->show_debug_info();
        }
        // Output cache for 'no graphics' content
        if (main()->no_graphics() && main()->OUTPUT_CACHING && $init_type == 'user' && $_SERVER['REQUEST_METHOD'] == 'GET') {
            _class('output_cache')->_put_page_to_output_cache(ob_get_clean());
        }
        if (main()->LOG_EXEC || $this->LOG_EXEC_INFO) {
            _class('logs')->log_exec();
        }
        // End sending main output
        ob_end_flush();
        if ($this->EXIT_AFTER_ECHO) {
            exit();
        }
    }


    public function _get_quick_page_info()
    {
        if ( ! $this->ADD_QUICK_PAGE_INFO) {
            return false;
        }
        return PHP_EOL . '<!-- date: ' . gmdate('Y-m-d H:i:s') . ' UTC, time: ' . round(microtime(true) - main()->_time_start, 3) . ', memory: ' . memory_get_peak_usage() . ', db: ' . (int) db()->NUM_QUERIES . ' -->' . PHP_EOL;
    }

    /**
     * Process output filters for the given text.
     * @param mixed $text
     */
    public function _apply_output_filters($text = '')
    {
        foreach ((array) $this->_OUTPUT_FILTERS as $cur_filter) {
            if (is_callable($cur_filter)) {
                $text = call_user_func($cur_filter, $text);
            }
        }
        return $text;
    }

    /**
     * Initialization of the main template in the theme (could be overwritten to match design)
     * Return contents of the main template.
     * @param mixed $tpl_name
     */
    public function _init_main_stpl($tpl_name = '')
    {
        return $this->parse($tpl_name);
    }

    /**
     * Simple template parser (*.stpl).
     * @param mixed $name
     * @param mixed $replace
     * @param mixed $params
     */
    public function parse($name, $replace = [], $params = [])
    {
        $name = strtolower(trim($name));
        // Support for the driver name in prefix, example: "twig:user/account", "smarty:user/account"
        if (strpos($name, ':') !== false) {
            list($driver, $name) = explode(':', $name);
            if ($driver) {
                $params['driver'] = $driver;
            }
        }
        // Support for the framework calls
        $yf_prefix = 'yf_';
        $yfp_len = strlen($yf_prefix);
        if (substr($name, 0, $yfp_len) == $yf_prefix) {
            $name = substr($name, $yfp_len);
        }
        if (false !== strpos($name, '@')) {
            $r = [
                '@object' => $_GET['object'],
                '@action' => $_GET['action'],
                '@id' => $_GET['id'],
            ];
            $name = str_replace(array_keys($r), array_values($r), $name);
        }
        if ( ! is_array($params)) {
            $params = [];
        }
        $string = $params['string'] ?: false;
        $params['replace_images'] = $params['replace_images'] ?: true;
        $params['no_cache'] = $params['no_cache'] ?: false;
        $params['force_storage'] = $params['force_storage'] ?: '';
        $params['no_include'] = $params['no_include'] ?: false;
        if (DEBUG_MODE) {
            $stpl_time_start = microtime(true);
        }
        $replace = (array) $replace + (array) $this->_global_tags;
        $replace['error'] = $this->_parse_get_user_errors($name, $replace['error']);
        if (isset($replace[''])) {
            unset($replace['']);
        }
        if ($this->ALLOW_CUSTOM_FILTER) {
            $this->_custom_filter($name, $replace);
        }
        // Allowing to override driver
        if ($params['driver'] && $params['driver'] != $this->DRIVER_NAME) {
            $string = _class('tpl_driver_' . $params['driver'], 'classes/tpl/')->parse($name, $replace, $params);
        } else {
            $string = $this->driver->parse($name, $replace, $params);
        }
        if ($params['replace_images']) {
            $string = $this->_replace_images_paths($string);
        }
        if (DEBUG_MODE) {
            $this->_parse_set_debug_info($name, $replace, $params, $string, $stpl_time_start);
        }
        return $string;
    }

    /**
     * Wrapper to parse given template string.
     * @param mixed $string
     * @param mixed $replace
     * @param mixed $name
     * @param mixed $params
     */
    public function parse_string($string = '', $replace = [], $name = '', $params = [])
    {
        if ( ! strlen($string)) {
            $string = ' ';
        }
        if ( ! $name) {
            $name = 'auto__' . abs(crc32($string));
        }
        $params['string'] = $string;
        return $this->parse($name, $replace, $params);
    }

    /**
     * Wrapper on parse(), silently failing if template not exists.
     * @param mixed $name
     * @param mixed $replace
     * @param mixed $params
     */
    public function parse_if_exists($name, $replace = [], $params = [])
    {
        return $this->exists($name) ? $this->parse($name, $replace, $params) : '';
    }

    /**
     * @param mixed $name
     * @param mixed $err
     */
    public function _parse_get_user_errors($name, $err)
    {
        if (isset($err)) {
            return $err;
        }
        $err = '';
        if ($name != 'main' && common()->_error_exists()) {
            if ( ! isset($this->_user_error_msg)) {
                $this->_user_error_msg = common()->_show_error_message('', false);
            }
            $err = $this->_user_error_msg;
        }
        return $err;
    }

    /**
     * @param mixed $name
     * @param mixed $replace
     * @param mixed $params
     * @param mixed $string
     * @param mixed $stpl_time_start
     */
    public function _parse_set_debug_info($name = '', $replace = [], $params = [], $string = '', $stpl_time_start)
    {
        if ( ! DEBUG_MODE) {
            return false;
        }
        if ( ! isset($this->driver->CACHE[$name]['exec_time'])) {
            $this->driver->CACHE[$name]['exec_time'] = 0;
        }
        $this->driver->CACHE[$name]['exec_time'] += (microtime(true) - $stpl_time_start);
        // For debug store information about variables used while processing template
        if ($this->DEBUG_STPL_VARS) {
            debug('STPL_REPLACE_VARS::' . $name . '[]', $replace);
        }
        if ($this->USE_SOURCE_BACKTRACE) {
            debug('STPL_TRACES::' . $name, main()->trace_string());
        }
        return true;
    }

    /**
     * Alias.
     * @param mixed $stpl_name
     * @param mixed $force_storage
     */
    public function exists($stpl_name = '', $force_storage = '')
    {
        return (bool) $this->_stpl_exists($stpl_name, $force_storage);
    }

    /**
     * Check if template exists (simple wrapper for the '_get_template_file').
     * @param mixed $stpl_name
     * @param mixed $force_storage
     */
    public function _stpl_exists($stpl_name = '', $force_storage = '')
    {
        // Exists in cache
        if ( ! $force_storage && isset($this->driver->CACHE[$stpl_name])) {
            return true;
        }
        return (bool) $this->_get_template_file($stpl_name, $force_storage, 1);
    }

    /**
     * Alias.
     * @param mixed $file_name
     * @param mixed $force_storage
     * @param mixed $JUST_CHECK_IF_EXISTS
     * @param mixed $RETURN_TEMPLATE_PATH
     */
    public function get($file_name = '', $force_storage = '', $JUST_CHECK_IF_EXISTS = false, $RETURN_TEMPLATE_PATH = false)
    {
        return $this->_get_template_file($file_name, $force_storage, $JUST_CHECK_IF_EXISTS, $RETURN_TEMPLATE_PATH);
    }


    public function _get_cached_paths()
    {
        $cache_name = __FUNCTION__ . '_' . MAIN_TYPE;
        if (isset($this->$cache_name)) {
            return $this->$cache_name;
        }
        $this->$cache_name = getset('tpl_get_cached_paths', function () {
            $allowed_exts = $this->ALLOWED_EXTS;
            $templates_dir = trim($this->_THEMES_PATH, '/');
            $pattern = '{,plugins/*/}' . $templates_dir . '/*/{*,*/*,*/*/*}.*';
            $globs = [
                'framework' => YF_PATH . $pattern,
                'project' => PROJECT_PATH . $pattern,
                'app' => APP_PATH . $pattern,
            ];
            $plens = [
                'framework' => strlen(YF_PATH),
                'project' => strlen(PROJECT_PATH),
                'app' => strlen(APP_PATH),
            ];
            $site_path = (MAIN_TYPE_USER ? SITE_PATH : ADMIN_SITE_PATH);
            if (is_site_path()) {
                $globs['site'] = $site_path . $pattern;
                $plens['site'] = strlen($site_path);
            }
            $names = [];
            foreach ($globs as $gname => $glob) {
                foreach (glob($glob, GLOB_BRACE | GLOB_NOSORT) as $path) {
                    $name = substr($path, $plens[$gname]);
                    $ext = pathinfo($name, PATHINFO_EXTENSION);
                    if ( ! $ext || ! in_array($ext, $allowed_exts)) {
                        continue;
                    }
                    $p = explode('/', $name);
                    $p[0] == 'plugins' && $p = array_slice($p, 2);
                    $theme = '';
                    if ($p[0] == $templates_dir) {
                        $theme = $p[1];
                        $p = array_slice($p, 2);
                    }
                    $name = implode('/', $p);
                    $name = substr($name, 0, -strlen('.' . $ext));
                    $names[$name][$gname][$theme] = $path;
                }
            }
            return $names;
        });
        return $this->$cache_name;
    }

    /**
     * Read template file contents (or get it from DB).
     * @param mixed $file_name
     * @param mixed $force_storage
     * @param mixed $JUST_CHECK_IF_EXISTS
     * @param mixed $RETURN_TEMPLATE_PATH
     */
    public function _get_template_file($file_name = '', $force_storage = '', $JUST_CHECK_IF_EXISTS = false, $RETURN_TEMPLATE_PATH = false)
    {
        $string = false;
        $NOT_FOUND = false;
        $storage = 'inline';
        $file_name = trim(trim(trim($file_name), '/'));
        $l = strlen(YF_PREFIX);
        if (substr($file_name, 0, $l) == YF_PREFIX) {
            $file_name = substr($file_name, $l);
        }
        $stpl_ext = $this->_STPL_EXT;
        $path_ext = pathinfo($file_name, PATHINFO_EXTENSION);
        $path_ext && $path_ext = '.' . $path_ext;
        // Allowed extension overrides
        if ( ! $path_ext || ! in_array($path_ext, $this->ALLOWED_EXTS)) {
            $file_name .= $stpl_ext;
        }
        // Fix double extesion
        $file_name = str_replace($stpl_ext . $stpl_ext, $stpl_ext, $file_name);
        $stpl_name = str_replace([$stpl_ext, $path_ext], '', $file_name);

        if ($this->GET_STPLS_FROM_DB || $force_storage == 'db') {
            if ($this->FROM_DB_GET_ALL) {
                if ( ! empty($this->_TMP_FROM_DB[$stpl_name])) {
                    $string = $this->_TMP_FROM_DB[$stpl_name];
                    unset($this->_TMP_FROM_DB[$stpl_name]);
                } else {
                    $NOT_FOUND = true;
                }
            } else {
                $text = from('templates')->where('theme_name', conf('theme'))->where('name', $stpl_name)->where('active', '1')->one('text');
                if (isset($text)) {
                    $string = stripslashes($text);
                } else {
                    $NOT_FOUND = true;
                }
            }
            $storage = 'db';
        } else {
            $def_theme = $this->_get_def_user_theme();
            $all_tpls_paths = $this->_get_cached_paths();
            $paths = $all_tpls_paths[$stpl_name];
            // Storages are defined in specially crafted `order`, so do not change it unless you have strong reason
            $storages = [];
            $site_path = (MAIN_TYPE_USER ? SITE_PATH : ADMIN_SITE_PATH);
            $theme = conf('theme');

            $storages = [
                'dev',
                'site_lang',
                'site',
                'site_inherit',
                'site_inherit2',
                'app_lang',
                'app',
                'app_inherit',
                'app_inherit2',
                'app_user',
                'project',
                'project_user',
                'framework',
                'framework_user',
            ];
            $storages = array_filter($storages);
            foreach ((array) $storages as $_storage) {
                if ($force_storage && $force_storage != $_storage) {
                    continue;
                }
                $file_path = '';
                if (in_array($_storage, ['app', 'project', 'framework'])) {
                    $_theme = $_storage == 'framework' ? MAIN_TYPE : $theme;
                    if (isset($paths[$_storage][$_theme])) {
                        $file_path = $paths[$_storage][$_theme];
                    }
                } elseif (in_array($_storage, ['app_user', 'project_user', 'framework_user']) && MAIN_TYPE_ADMIN && ! in_array($stpl_name, ['main'])) {
                    $s = substr($_storage, 0, -strlen('_user'));
                    if (isset($paths[$s]['user'])) {
                        $file_path = $paths[$s]['user'];
                    }
                } elseif ($_storage == 'site') {
                    if (isset($paths[$_storage][$_theme])) {
                        $file_path = $paths[$_storage][$_theme];
                    }
                } elseif (in_array($_storage, ['app_lang', 'site_lang']) && $this->ALLOW_LANG_BASED_STPLS) {
                    $lang = conf('language');
                    $_theme = $theme . '.' . $lang;
                    $s = substr($_storage, 0, -strlen('_lang'));
                    if (isset($paths[$s][$_theme])) {
                        $file_path = $paths[$s][$_theme];
                    }
                } elseif (in_array($_storage, ['app_inherit', 'site_inherit']) && $this->_INHERITED_SKIN) {
                    $_theme = $this->_INHERITED_SKIN;
                    $s = substr($_storage, 0, -strlen('_inherit'));
                    if (isset($paths[$s][$_theme])) {
                        $file_path = $paths[$s][$_theme];
                    }
                } elseif (in_array($_storage, ['app_inherit2', 'site_inherit2']) && $this->_INHERITED_SKIN2) {
                    $_theme = $this->_INHERITED_SKIN2;
                    $s = substr($_storage, 0, -strlen('_inherit2'));
                    if (isset($paths[$s][$_theme])) {
                        $file_path = $paths[$s][$_theme];
                    }
                } elseif (in_array($_storage, ['dev'])) {
                    //                    // Developer overrides
//                    $dev_path = '.dev/'.main()->HOSTNAME.'/';
//                    if (conf('DEV_MODE')) {
//                        if ($site_path && $site_path != PROJECT_PATH) {
//                            $storages['dev_site'] = $site_path. $dev_path. $this->TPL_PATH. $file_name;
//                        }
//                        $storages['dev_app'] = APP_PATH. $dev_path. $this->TPL_PATH. $file_name;
//                        $storages['dev_project'] = PROJECT_PATH. $dev_path. $this->TPL_PATH. $file_name;
//                    }
                }
                if ( ! $file_path || ! $this->_stpl_path_exists($file_path)) {
                    continue;
                }
                $string = $this->_stpl_path_get($file_path);
                if ($string !== false) {
                    $storage = $_storage;
                    break;
                }
            }
            // Last try from cache (preloaded templates)
            if ($string === false) {
                $compiled_stpl = conf('_compiled_stpls::' . $stpl_name);
                if ($compiled_stpl) {
                    $string = $compiled_stpl;
                    $storage = 'compiled_cache';
                }
            }
            if ($string === false) {
                $NOT_FOUND = true;
            }
        }
        if (DEBUG_MODE) {
            $this->driver->debug[$stpl_name]['storage'] = $storage;
            $this->driver->debug[$stpl_name]['storages'] = $paths;
        }
        if ($RETURN_TEMPLATE_PATH) {
            return $file_path;
        }
        // If we just checking template existance - then stop here
        if ($JUST_CHECK_IF_EXISTS) {
            return ! $NOT_FOUND;
        }
        // Log error message if template file was not found
        if ($NOT_FOUND) {
            trigger_error('STPL: template "' . $file_name . '" in theme "' . conf('theme') . '" not found.', E_USER_WARNING);
        }
        return $string;
    }

    /**
     * Get default user theme (for admin section).
     */
    public function _get_def_user_theme()
    {
        if ( ! empty($this->_def_user_theme)) {
            return $this->_def_user_theme;
        }
        //        $sites = conf('sites_info');
        //        $first = array_shift($sites);
        //        if (file_exists(PROJECT_PATH. $this->_THEMES_PATH. $first['DEFAULT_SKIN']. '/')) {
        //            $this->_def_user_theme = $first['DEFAULT_SKIN'];
        //        }
        if (empty($this->_def_user_theme)) {
            $this->_def_user_theme = 'user';
        }
        return $this->_def_user_theme;
    }

    /**
     * @param mixed $file_name
     */
    public function _stpl_path_get($file_name)
    {
        return file_get_contents($file_name);
    }

    /**
     * Check if given template exists.
     * @param mixed $file_name
     */
    public function _stpl_path_exists($file_name)
    {
        return file_exists($file_name);
    }

    /**
     * If content need to be cleaned from unused tags - do that.
     * @param mixed $string
     * @param mixed $replace
     * @param mixed $name
     */
    public function _process_clear_unused($string, $replace = [], $name = '')
    {
        return preg_replace('/\{[\w_]+\}/i', '', $string);
    }

    /**
     * @param mixed $string
     * @param mixed $replace
     * @param mixed $name
     */
    public function _process_eval_string($string, $replace = [], $name = '')
    {
        return eval('return "' . str_replace('"', '\"', $string) . '";');
    }

    /**
     * Registers custom function to be used in templates.
     * @param mixed $callback_impl
     * @param mixed $filter_name
     */
    public function register_output_filter($callback_impl, $filter_name = '')
    {
        if (empty($filter_name)) {
            $filter_name = substr(abs(crc32(microtime(true))), 0, 8);
        }
        $this->_OUTPUT_FILTERS[$filter_name] = $callback_impl;
    }

    /**
     * Simple cleanup (compress) output.
     * @param mixed $text
     */
    public function _simple_cleanup_callback($text = '')
    {
        if (DEBUG_MODE) {
            debug('compress_output::size_original', strlen($text));
        }
        $text = str_replace(["\r", "\n", "\t"], '', $text);
        $text = preg_replace('#[\s]{2,}#ms', ' ', $text);
        // Remove html comments
        $text = preg_replace('#<\!--[\w\s\-\/]*?-->#ms', '', $text);
        if (DEBUG_MODE) {
            debug('compress_output::size_compressed', strlen($text));
        }
        return $text;
    }

    /**
     * Custom text replacing method.
     * @param mixed $text
     */
    public function _custom_replace_callback($text = '')
    {
        return _class('custom_meta_info')->_process($text);
    }

    /**
     * Replace method for 'IFRAME in center' mode.
     * @param mixed $text
     */
    public function _replace_for_iframe_callback($text = '')
    {
        return _class('rewrite')->_replace_links_for_iframe($text);
    }

    /**
     * Rewrite links callback method.
     * @param mixed $text
     */
    public function _rewrite_links_callback($text = '')
    {
        return _class('rewrite')->_rewrite_replace_links($text);
    }

    /**
     * Clenup HTML output with Tidy.
     * @param mixed $text
     */
    public function _tidy_cleanup_callback($text = '')
    {
        if ( ! class_exists('tidy') || ! extension_loaded('tidy')) {
            return $text;
        }
        $tidy_default_config = [
            'alt-text' => '',
            'output-xhtml' => true,
        ];
        $tidy = new tidy();
        $tidy->parseString($text, $this->_TIDY_CONFIG ?: $tidy_default_config, conf('charset'));
        $tidy->cleanRepair();
        return $tidy;
    }

    /**
     * @param mixed $text
     */
    public function _debug_mode_callback($text = '')
    {
        if ( ! DEBUG_MODE) {
            return $text;
        }
        return preg_replace_callback('~(<title>)(.*?)(</title>)~ims', function ($m) {
            return $m[1] . strip_tags($m[2]) . $m[3];
        }, $text);
    }

    /**
     * Custom filter (Inherit this method and customize anything you want).
     * @param mixed $stpl_name
     */
    public function _custom_filter($stpl_name = '', &$replace)
    {
        if ($stpl_name == 'home_page/main') {
            // example only:
            // print_r($replace);
            // $replace['recent_ads'] = '';
        }
    }

    /**
     * Wrapper function for t/translate/i18n calls inside templates.
     * @param mixed $input
     * @param mixed $replace
     */
    public function _i18n_wrapper($input = '', $replace = [])
    {
        if ( ! strlen($input)) {
            return '';
        }
        $input = stripslashes(trim($input, '"\''));
        $args = [];
        // Complex case with substitutions
        if (preg_match('/(?P<text>.+?)["\']{1},[\s\t]*%(?P<args>[a-z]+.+)$/ims', $input, $m)) {
            foreach (explode(';%', $m['args']) as $arg) {
                $attr_name = $attr_val = '';
                if (false !== strpos($arg, '=')) {
                    list($attr_name, $attr_val) = explode('=', trim($arg));
                }
                $attr_name = trim(str_replace(["'", '"'], '', $attr_name));
                $attr_val = trim(str_replace(["'", '"'], '', $attr_val));
                $args['%' . $attr_name] = $attr_val;
            }
            $text_to_translate = $m['text'];
        } else {
            $text_to_translate = $input;
        }
        $output = t($text_to_translate, $args);
        // Do replacement of the template vars on the last stage
        // example: @replace1 will be got from $replace['replace1'] array item
        if (false !== strpos($output, '@') && ! empty($replace)) {
            $r = [];
            foreach ((array) $replace as $k => $v) {
                $r['@' . $k] = $v;
            }
            $output = str_replace(array_keys($r), array_values($r), $output);
        }
        return $output;
    }

    /**
     * Wrapper for translation method (for call from templates or other).
     * @param mixed $string
     * @param mixed $args_from_tpl
     * @param mixed $lang
     */
    public function _translate_for_stpl($string = '', $args_from_tpl = '', $lang = '')
    {
        $args = [];
        if (is_string($args_from_tpl) && strlen($args_from_tpl)) {
            $args = _attrs_string2array($args_from_tpl);
        }
        return t($string, $args, $lang);
    }

    /**
     * Wrapper around 'url()' function, called like this inside templates:
     * {url(object=home_page;action=test)}.
     * @param mixed $params
     */
    public function _url_wrapper($params = [])
    {
        // Try to process method params (string like attrib1=value1;attrib2=value2)
        if (is_string($params) && strlen($params)) {
            // Url like this: /object/action/id
            if ($params[0] == '/') {
                // Do nothing, just directly pass this to url() as string
            } elseif (false !== strpos($params, '=')) {
                $params = _attrs_string2array($params);
            } else {
                list($object, $action, $id, $page) = explode(';', str_replace(',', ';', $params));
                $params = [
                    'object' => $object,
                    'action' => $action,
                    'id' => $id,
                    'page' => $page,
                ];
            }
        }
        return url($params);
    }

    /**
     * Replace paths to images.
     * @param mixed $string
     */
    public function _replace_images_paths($string = '')
    {
        $images_path = (MAIN_TYPE_USER ? $this->MEDIA_PATH : ADMIN_WEB_PATH) . $this->TPL_PATH . $this->_IMAGES_PATH;
        $uploads_path = $this->MEDIA_PATH . $this->_UPLOADS_PATH;
        $r = [
            '"images/' => '"' . $images_path,
            '\'images/' => '\'' . $images_path,
            'src="uploads/' => 'src="' . $uploads_path,
            '"uploads/' => '"' . $uploads_path,
            '\'uploads/' => '\'' . $uploads_path,
        ];
        return str_replace(array_keys($r), array_values($r), $string);
    }

    /**
     * @param mixed $text
     * @param mixed $filters
     */
    public function _process_var_filters($text = '', $filters = '')
    {
        if (is_string($filters) && strpos($filters, '|') !== false) {
            $filters = explode('|', $filters);
        }
        if ( ! is_array($filters)) {
            $filters = [$filters];
        }
        foreach ($filters as $fname) {
            if (is_callable($fname)) {
                $text = $fname($text);
            } elseif (is_string($fname) && function_exists($fname)) {
                $text = $fname($text);
            }
        }
        return $text;
    }

    /**
     * @param mixed $name
     * @param mixed $func
     * @param mixed $params
     */
    public function add_function_callback($name, $func, $params = [])
    {
        $pattern = '/\{(?P<name>' . preg_quote($name) . ')\(\s*["\']{0,1}(?P<args>[a-z0-9_:\/\.]+?)["\']{0,1}\s*\)\}/ims';
        if ($params['only_pattern']) {
            return $pattern;
        }
        $this->add_pattern_callback($pattern, $func, $name);
    }

    /**
     * @param mixed $name
     * @param mixed $func
     * @param mixed $params
     */
    public function add_section_callback($name, $func, $params = [])
    {
        $pattern = '/\{(?P<name>' . preg_quote($name) . ')\(\s*["\']{0,1}(?P<args>[^"\'\)\}]*?)["\']{0,1}\s*\)\}\s*(?P<body>.+?)\s*{\/(\1)\}/ims';
        if ($params['only_pattern']) {
            return $pattern;
        }
        $this->add_pattern_callback($pattern, $func, $name);
    }

    /**
     * @param mixed $pattern
     * @param mixed $func
     * @param mixed $pattern_name
     */
    public function add_pattern_callback($pattern, $func, $pattern_name = '')
    {
        $this->_custom_patterns_funcs[$pattern] = $func;
        $this->_custom_patterns_index[crc32($pattern)] = $pattern;
        $pattern_name && $this->_custom_patterns_index[$pattern_name] = $pattern;
    }
}