<?php
/**
* @version	$Id: application.php 15374 2012-05-30 15:27:07Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

defined('FULL_PATH') or die('restricted access!');

/**
* Basic class for Kernel4-based Application
*
* This class is a Facade for any other class which needs to deal with Kernel4 framework.<br>
* The class encapsulates the main run-cycle of the script, provide access to all other objects in the framework.<br>
* <br>
* The class is a singleton, which means that there could be only one instance of kApplication in the script.<br>
* This could be guaranteed by NOT calling the class constructor directly, but rather calling kApplication::Instance() method,
* which returns an instance of the application. The method guarantees that it will return exactly the same instance for any call.<br>
* See singleton pattern by GOF.
*/
class kApplication implements kiCacheable {

	/**
	 * Location of module helper class (used in installator too)
	 */
	const MODULE_HELPER_PATH = '/../units/helpers/modules_helper.php';

	/**
	 * Is true, when Init method was called already, prevents double initialization
	 *
	 * @var bool
	 */
	public $InitDone = false;

	/**
	 * Holds internal NParser object
	 *
	 * @var NParser
	 * @access public
	 */
	public $Parser;

	/**
	 * Holds parser output buffer
	 *
	 * @var string
	 * @access protected
	 */
	protected $HTML = '';

	/**
	 * The main Factory used to create
	 * almost any class of kernel and
	 * modules
	 *
	 * @var kFactory
	 * @access protected
	 */
	protected $Factory;

	/**
	 * Template names, that will be used instead of regular templates
	 *
	 * @var Array
	 * @access public
	 */
	public $ReplacementTemplates = Array ();

	/**
	 * Mod-Rewrite listeners used during url building and parsing
	 *
	 * @var Array
	 * @access public
	 */
	public $RewriteListeners = Array ();

	/**
	 * Reference to debugger
	 *
	 * @var Debugger
	 * @access public
	 */
	public $Debugger = null;

	/**
	 * Holds all phrases used
	 * in code and template
	 *
	 * @var PhrasesCache
	 * @access public
	 */
	public $Phrases;

	/**
	 * Modules table content, key - module name
	 *
	 * @var Array
	 * @access public
	 */
	public $ModuleInfo = Array ();

	/**
	 * Holds DBConnection
	 *
	 * @var kDBConnection
	 * @access public
	 */
	public $Conn = null;

	/**
	 * Maintains list of user-defined error handlers
	 *
	 * @var Array
	 * @access public
	 */
	public $errorHandlers = Array ();

	/**
	 * Maintains list of user-defined exception handlers
	 *
	 * @var Array
	 * @access public
	 */
	public $exceptionHandlers = Array ();

	// performance needs:
	/**
	 * Holds a reference to httpquery
	 *
	 * @var kHttpQuery
	 * @access public
	 */
	public $HttpQuery = null;

	/**
	 * Holds a reference to UnitConfigReader
	 *
	 * @var kUnitConfigReader
	 * @access public
	 */
	public $UnitConfigReader = null;

	/**
	 * Holds a reference to Session
	 *
	 * @var Session
	 * @access public
	 */
	public $Session = null;

	/**
	 * Holds a ref to kEventManager
	 *
	 * @var kEventManager
	 * @access public
	 */
	public $EventManager = null;

	/**
	 * Holds a ref to kUrlManager
	 *
	 * @var kUrlManager
	 * @access public
	 */
	public $UrlManager = null;

	/**
	 * Ref for TemplatesCache
	 *
	 * @var TemplatesCache
	 * @access public
	 */
	public $TemplatesCache = null;

	/**
	 * Holds current NParser tag while parsing, can be used in error messages to display template file and line
	 *
	 * @var _BlockTag
	 * @access public
	 */
	public $CurrentNTag = null;

	/**
	 * Object of unit caching class
	 *
	 * @var kCacheManager
	 * @access public
	 */
	public $cacheManager = null;

	/**
	 * Tells, that administrator has authenticated in administrative console
	 * Should be used to manipulate data change OR data restrictions!
	 *
	 * @var bool
	 * @access public
	 */
	public $isAdminUser = false;

	/**
	 * Tells, that admin version of "index.php" was used, nothing more!
	 * Should be used to manipulate data display!
	 *
	 * @var bool
	 * @access public
	 */
	public $isAdmin = false;

	/**
	 * Instance of site domain object
	 *
	 * @var kDBItem
	 * @access public
	 * @todo move away into separate module
	 */
	public $siteDomain = null;

	/**
	 * Prevent kApplication class to be created directly, only via Instance method
	 *
	 * @access private
	 */
	private function __construct()
	{

	}

	final private function __clone() {}

	/**
	 * Returns kApplication instance anywhere in the script.
	 *
	 * This method should be used to get single kApplication object instance anywhere in the
	 * Kernel-based application. The method is guaranteed to return the SAME instance of kApplication.
	 * Anywhere in the script you could write:
	 * <code>
	 *		$application =& kApplication::Instance();
	 * </code>
	 * or in an object:
	 * <code>
	 *		$this->Application =& kApplication::Instance();
	 * </code>
	 * to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class.
	 * To use descendant of standard kApplication class in your project you would need to define APPLICATION_CLASS constant
	 * BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would
	 * create and return default KernelApplication instance.
	 *
	 * Pattern: Singleton
	 *
	 * @static
	 * @return kApplication
	 * @access public
	 */
	public static function &Instance()
	{
		static $instance = false;

		if ( !$instance ) {
			$class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication';
			$instance = new $class();
		}

		return $instance;
	}

	/**
	 * Initializes the Application
	 *
	 * @param string $factory_class
	 * @return bool Was Init actually made now or before
	 * @access public
	 * @see kHTTPQuery
	 * @see Session
	 * @see TemplatesCache
	 */
	public function Init($factory_class = 'kFactory')
	{
		if ( $this->InitDone ) {
			return false;
		}

		$this->isAdmin = kUtil::constOn('ADMIN');

		if ( !kUtil::constOn('SKIP_OUT_COMPRESSION') ) {
			ob_start(); // collect any output from method (other then tags) into buffer
		}

		if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
			$this->Debugger->appendMemoryUsage('Application before Init:');
		}

		if ( !$this->isDebugMode() && !kUtil::constOn('DBG_ZEND_PRESENT') ) {
			error_reporting(0);
			ini_set('display_errors', 0);
		}

		if ( !kUtil::constOn('DBG_ZEND_PRESENT') ) {
			$error_handler = set_error_handler(Array (&$this, 'handleError'));
			if ( $error_handler ) {
				// wrap around previous error handler, if any was set
				$this->errorHandlers[] = $error_handler;
			}

			$exception_handler = set_exception_handler(Array (&$this, 'handleException'));
			if ( $exception_handler ) {
				// wrap around previous exception handler, if any was set
				$this->exceptionHandlers[] = $exception_handler;
			}
		}

		$this->Factory = new $factory_class();
		$this->registerDefaultClasses();

		$vars = kUtil::parseConfig(true);
		$db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : ($this->isDebugMode() ? 'kDBConnectionDebug' : 'kDBConnection');
		$this->Conn = $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array (&$this, 'handleSQLError')));
		$this->Conn->setup($vars);

		$this->cacheManager = $this->makeClass('kCacheManager');
		$this->cacheManager->InitCache();

		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
			$this->Debugger->appendTimestamp('Before UnitConfigReader');
		}

		// init config reader and all managers
		$this->UnitConfigReader = $this->makeClass('kUnitConfigReader');
		$this->UnitConfigReader->scanModules(MODULES_PATH); // will also set RewriteListeners when existing cache is read

		$this->registerModuleConstants();

		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
			$this->Debugger->appendTimestamp('After UnitConfigReader');
		}

		define('MOD_REWRITE', $this->ConfigValue('UseModRewrite') && !$this->isAdmin ? 1 : 0);

		// start processing request
		$this->HttpQuery = $this->recallObject('HTTPQuery');
		$this->HttpQuery->process();

		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
			$this->Debugger->appendTimestamp('Processed HTTPQuery initial');
		}

		$this->Session = $this->recallObject('Session');

		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
			$this->Debugger->appendTimestamp('Processed Session');
		}

		$this->Session->ValidateExpired(); // needs mod_rewrite url already parsed to keep user at proper template after session expiration

		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
			$this->Debugger->appendTimestamp('Processed HTTPQuery AfterInit');
		}

		$this->cacheManager->LoadApplicationCache();

		$site_timezone = $this->ConfigValue('Config_Site_Time');

		if ( $site_timezone ) {
			putenv('TZ=' . $site_timezone);
		}

		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
			$this->Debugger->appendTimestamp('Loaded cache and phrases');
		}

		$this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there

		$this->UnitConfigReader->AfterConfigRead(); // will set RewriteListeners when missing cache is built first time

		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
			$this->Debugger->appendTimestamp('Processed AfterConfigRead');
		}

		if ( $this->GetVar('m_cat_id') === false ) {
			$this->SetVar('m_cat_id', 0);
		}

		if ( !$this->RecallVar('curr_iso') ) {
			$this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional
		}

		$visit_id = $this->RecallVar('visit_id');

		if ( $visit_id !== false ) {
			$this->SetVar('visits_id', $visit_id);
		}

		$language = $this->recallObject('lang.current', null, Array ('live_table' => true));
		/* @var $language LanguagesItem */

		if ( preg_match('/utf-8/', $language->GetDBField('Charset')) ) {
			setlocale(LC_ALL, 'en_US.UTF-8');
			mb_internal_encoding('UTF-8');
		}

		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
			$this->Debugger->profileFinish('kernel4_startup');
		}

		$this->InitDone = true;

		$this->HandleEvent(new kEvent('adm:OnStartup'));

		return true;
	}

	/**
	 * Performs initialization of manager classes, that can be overridden from unit configs
	 *
	 * @return void
	 * @access public
	 * @throws Exception
	 */
	public function InitManagers()
	{
		if ( $this->InitDone ) {
			throw new Exception('Duplicate call of ' . __METHOD__, E_USER_ERROR);
			return;
		}

		$this->UrlManager = $this->makeClass('kUrlManager');
		$this->EventManager = $this->makeClass('EventManager');
		$this->Phrases = $this->makeClass('kPhraseCache');

		$this->RegisterDefaultBuildEvents();
	}

	/**
	 * Returns module information. Searches module by requested field
	 *
	 * @param string $field
	 * @param mixed $value
	 * @param string $return_field field value to returns, if not specified, then return all fields
	 * @return Array
	 */
	public function findModule($field, $value, $return_field = null)
	{
		$found = $module_info = false;

		foreach ($this->ModuleInfo as $module_info) {
			if ( strtolower($module_info[$field]) == strtolower($value) ) {
				$found = true;
				break;
			}
		}

		if ( $found ) {
			return isset($return_field) ? $module_info[$return_field] : $module_info;
		}

		return false;
	}

	/**
	 * Refreshes information about loaded modules
	 *
	 * @return void
	 * @access public
	 */
	public function refreshModuleInfo()
	{
		if ( defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules', true) ) {
			$this->registerModuleConstants();
			return;
		}

		// use makeClass over recallObject, since used before kApplication initialization during installation
		$modules_helper = $this->makeClass('ModulesHelper');
		/* @var $modules_helper kModulesHelper */

		$this->Conn->nextQueryCachable = true;
		$sql = 'SELECT *
				FROM ' . TABLE_PREFIX . 'Modules
				WHERE ' . $modules_helper->getWhereClause() . '
				ORDER BY LoadOrder';
		$this->ModuleInfo = $this->Conn->Query($sql, 'Name');

		$this->registerModuleConstants();
	}

	/**
	 * Checks if passed language id if valid and sets it to primary otherwise
	 *
	 * @return void
	 * @access public
	 */
	public function VerifyLanguageId()
	{
		$language_id = $this->GetVar('m_lang');

		if ( !$language_id ) {
			$language_id = 'default';
		}

		$this->SetVar('lang.current_id', $language_id);
		$this->SetVar('m_lang', $language_id);

		$lang_mode = $this->GetVar('lang_mode');
		$this->SetVar('lang_mode', '');

		$lang = $this->recallObject('lang.current');
		/* @var $lang kDBItem */

		if ( !$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled')) ) {
			if ( !defined('IS_INSTALL') ) {
				$this->ApplicationDie('Unknown or disabled language');
			}
		}

		$this->SetVar('lang_mode', $lang_mode);
	}

	/**
	 * Checks if passed theme id if valid and sets it to primary otherwise
	 *
	 * @return void
	 * @access public
	 */
	public function VerifyThemeId()
	{
		if ( $this->isAdmin ) {
			kUtil::safeDefine('THEMES_PATH', '/core/admin_templates');

			return;
		}

		$path = $this->GetFrontThemePath();

		if ( $path === false ) {
			$this->ApplicationDie('No Primary Theme Selected or Current Theme is Unknown or Disabled');
		}

		kUtil::safeDefine('THEMES_PATH', $path);
	}

	/**
	 * Returns relative path to current front-end theme
	 *
	 * @param bool $force
	 * @return string
	 * @access public
	 */
	public function GetFrontThemePath($force = false)
	{
		static $path = null;

		if ( !$force && isset($path) ) {
			return $path;
		}

		$theme_id = $this->GetVar('m_theme');
		if ( !$theme_id ) {
			$theme_id = 'default'; // $this->GetDefaultThemeId(1); // 1 to force front-end mode!
		}

		$this->SetVar('m_theme', $theme_id);
		$this->SetVar('theme.current_id', $theme_id); // KOSTJA: this is to fool theme' getPassedID

		$theme = $this->recallObject('theme.current');
		/* @var $theme ThemeItem */

		if ( !$theme->isLoaded() || !$theme->GetDBField('Enabled') ) {
			return false;
		}

		// assign & then return, since it's static variable
		$path = '/themes/' . $theme->GetDBField('Name');

		return $path;
	}

	/**
	 * Returns primary front/admin language id
	 *
	 * @param bool $init
	 * @return int
	 * @access public
	 */
	public function GetDefaultLanguageId($init = false)
	{
		$cache_key = 'primary_language_info[%LangSerial%]';
		$language_info = $this->getCache($cache_key);

		if ( $language_info === false ) {
			// cache primary language info first
			$table = $this->getUnitOption('lang', 'TableName');
			$id_field = $this->getUnitOption('lang', 'IDField');

			$this->Conn->nextQueryCachable = true;
			$sql = 'SELECT ' . $id_field . ', IF(AdminInterfaceLang, "Admin", "Front") AS LanguageKey
					FROM ' . $table . '
					WHERE (AdminInterfaceLang = 1 OR PrimaryLang = 1) AND (Enabled = 1)';
			$language_info = $this->Conn->GetCol($sql, 'LanguageKey');

			if ( $language_info !== false ) {
				$this->setCache($cache_key, $language_info);
			}
		}

		$language_key = ($this->isAdmin && $init) || count($language_info) == 1 ? 'Admin' : 'Front';

		if ( array_key_exists($language_key, $language_info) && $language_info[$language_key] > 0 ) {
			// get from cache
			return $language_info[$language_key];
		}

		$language_id = $language_info && array_key_exists($language_key, $language_info) ? $language_info[$language_key] : false;

		if ( !$language_id && defined('IS_INSTALL') && IS_INSTALL ) {
			$language_id = 1;
		}

		return $language_id;
	}

	/**
	 * Returns front-end primary theme id (even, when called from admin console)
	 *
	 * @param bool $force_front
	 * @return int
	 * @access public
	 */
	public function GetDefaultThemeId($force_front = false)
	{
		static $theme_id = 0;

		if ( $theme_id > 0 ) {
			return $theme_id;
		}

		if ( kUtil::constOn('DBG_FORCE_THEME') ) {
			$theme_id = DBG_FORCE_THEME;
		}
		elseif ( !$force_front && $this->isAdmin ) {
			$theme_id = 999;
		}
		else {
			$cache_key = 'primary_theme[%ThemeSerial%]';
			$theme_id = $this->getCache($cache_key);

			if ( $theme_id === false ) {
				$this->Conn->nextQueryCachable = true;
				$sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . '
						FROM ' . $this->getUnitOption('theme', 'TableName') . '
						WHERE (PrimaryTheme = 1) AND (Enabled = 1)';
				$theme_id = $this->Conn->GetOne($sql);

				if ( $theme_id !== false ) {
					$this->setCache($cache_key, $theme_id);
				}
			}
		}

		return $theme_id;
	}

	/**
	 * Returns site primary currency ISO code
	 *
	 * @return string
	 * @access public
	 * @todo Move into In-Commerce
	 */
	public function GetPrimaryCurrency()
	{
		$cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId');
		$currency_iso = $this->getCache($cache_key);

		if ( $currency_iso === false ) {
			if ( $this->isModuleEnabled('In-Commerce') ) {
				$this->Conn->nextQueryCachable = true;
				$currency_id = $this->siteDomainField('PrimaryCurrencyId');

				$sql = 'SELECT ISO
						FROM ' . $this->getUnitOption('curr', 'TableName') . '
						WHERE ' . ($currency_id > 0 ? 'CurrencyId = ' . $currency_id : 'IsPrimary = 1');
				$currency_iso = $this->Conn->GetOne($sql);
			}
			else {
				$currency_iso = 'USD';
			}

			$this->setCache($cache_key, $currency_iso);
		}

		return $currency_iso;
	}

	/**
	 * Returns site domain field. When none of site domains are found false is returned.
	 *
	 * @param string $field
	 * @param bool $formatted
	 * @param string $format
	 * @return mixed
	 * @todo Move into separate module
	 */
	public function siteDomainField($field, $formatted = false, $format = null)
	{
		if ( $this->isAdmin ) {
			// don't apply any filtering in administrative console
			return false;
		}

		if ( !$this->siteDomain ) {
			$this->siteDomain = $this->recallObject('site-domain.current');
			/* @var $site_domain kDBItem */
		}

		if ( $this->siteDomain->isLoaded() ) {
			return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field);
		}

		return false;
	}

	/**
	 * Registers default classes such as kDBEventHandler, kUrlManager
	 *
	 * Called automatically while initializing kApplication
	 *
	 * @return void
	 * @access public
	 */
	public function RegisterDefaultClasses()
	{
		$this->registerClass('kHelper', KERNEL_PATH . '/kbase.php');
		$this->registerClass('kMultipleFilter', KERNEL_PATH . '/utility/filters.php');
		$this->registerClass('kiCacheable', KERNEL_PATH . '/interfaces/cacheable.php');

		$this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php', 'EventManager');
		$this->registerClass('kHookManager', KERNEL_PATH . '/managers/hook_manager.php');
		$this->registerClass('kScheduledTaskManager', KERNEL_PATH . '/managers/scheduled_task_manager.php');
		$this->registerClass('kRequestManager', KERNEL_PATH . '/managers/request_manager.php');
		$this->registerClass('kSubscriptionManager', KERNEL_PATH . '/managers/subscription_manager.php');

		$this->registerClass('kUrlManager', KERNEL_PATH . '/managers/url_manager.php');
		$this->registerClass('kUrlProcessor', KERNEL_PATH . '/managers/url_processor.php');
		$this->registerClass('kPlainUrlProcessor', KERNEL_PATH . '/managers/plain_url_processor.php');
		$this->registerClass('kRewriteUrlProcessor', KERNEL_PATH . '/managers/rewrite_url_processor.php');

		$this->registerClass('kCacheManager', KERNEL_PATH . '/managers/cache_manager.php');
		$this->registerClass('PhrasesCache', KERNEL_PATH . '/languages/phrases_cache.php', 'kPhraseCache');
		$this->registerClass('kTempTablesHandler', KERNEL_PATH . '/utility/temp_handler.php');
		$this->registerClass('kValidator', KERNEL_PATH . '/utility/validator.php');
		$this->registerClass('kOpenerStack', KERNEL_PATH . '/utility/opener_stack.php');

		$this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php');

		// Params class descendants
		$this->registerClass('kArray', KERNEL_PATH . '/utility/params.php');
		$this->registerClass('Params', KERNEL_PATH . '/utility/params.php');
		$this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions');
		$this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php', 'kCache', 'Params');
		$this->registerClass('kHTTPQuery', KERNEL_PATH . '/utility/http_query.php', 'HTTPQuery');

		// session
		$this->registerClass('Session', KERNEL_PATH . '/session/session.php');
		$this->registerClass('SessionStorage', KERNEL_PATH . '/session/session_storage.php');
		$this->registerClass('InpSession', KERNEL_PATH . '/session/inp_session.php', 'Session');
		$this->registerClass('InpSessionStorage', KERNEL_PATH . '/session/inp_session_storage.php', 'SessionStorage');

		// template parser
		$this->registerClass('kTagProcessor', KERNEL_PATH . '/processors/tag_processor.php');
		$this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php', 'm_TagProcessor');
		$this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php');
		$this->registerClass('kCatDBTagProcessor', KERNEL_PATH . '/db/cat_tag_processor.php');
		$this->registerClass('NParser', KERNEL_PATH . '/nparser/nparser.php');
		$this->registerClass('TemplatesCache', KERNEL_PATH . '/nparser/template_cache.php');

		// database
		$this->registerClass('kDBConnection', KERNEL_PATH . '/db/db_connection.php');
		$this->registerClass('kDBConnectionDebug', KERNEL_PATH . '/db/db_connection.php');
		$this->registerClass('kDBLoadBalancer', KERNEL_PATH . '/db/db_load_balancer.php');
		$this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php');
		$this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php');
		$this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php');
		$this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php');
		$this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php');
		$this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php');

		// email sending
		$this->registerClass('kEmail', KERNEL_PATH . '/utility/email.php');
		$this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender');
		$this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket');

		// do not move to config - this helper is used before configs are read
		$this->registerClass('kModulesHelper', KERNEL_PATH . self::MODULE_HELPER_PATH, 'ModulesHelper');
	}

	/**
	 * Registers default build events
	 *
	 * @return void
	 * @access protected
	 */
	protected function RegisterDefaultBuildEvents()
	{
		$this->EventManager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild');
	}

	/**
	 * Returns cached category information by given cache name. All given category
	 * information is recached, when at least one of 4 caches is missing.
	 *
	 * @param int $category_id
	 * @param string $name cache name = {filenames, category_designs, category_tree}
	 * @return string
	 * @access public
	 */
	public function getCategoryCache($category_id, $name)
	{
		return $this->cacheManager->getCategoryCache($category_id, $name);
	}

	/**
	 * Returns caching type (none, memory, temporary)
	 *
	 * @param int $caching_type
	 * @return bool
	 * @access public
	 */
	public function isCachingType($caching_type)
	{
		return $this->cacheManager->isCachingType($caching_type);
	}

	/**
	 * Increments serial based on prefix and it's ID (optional)
	 *
	 * @param string $prefix
	 * @param int $id ID (value of IDField) or ForeignKeyField:ID
	 * @param bool $increment
	 * @return string
	 * @access public
	 */
	public function incrementCacheSerial($prefix, $id = null, $increment = true)
	{
		return $this->cacheManager->incrementCacheSerial($prefix, $id, $increment);
	}

	/**
	 * Returns cached $key value from cache named $cache_name
	 *
	 * @param int $key key name from cache
	 * @param bool $store_locally store data locally after retrieved
	 * @param int $max_rebuild_seconds
	 * @return mixed
	 * @access public
	 */
	public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0)
	{
		return $this->cacheManager->getCache($key, $store_locally, $max_rebuild_seconds);
	}

	/**
	 * Stores new $value in cache with $key name
	 *
	 * @param int $key key name to add to cache
	 * @param mixed $value value of cached record
	 * @param int $expiration when value expires (0 - doesn't expire)
	 * @return bool
	 * @access public
	 */
	public function setCache($key, $value, $expiration = 0)
	{
		return $this->cacheManager->setCache($key, $value, $expiration);
	}

	/**
	 * Stores new $value in cache with $key name (only if it's not there)
	 *
	 * @param int $key key name to add to cache
	 * @param mixed $value value of cached record
	 * @param int $expiration when value expires (0 - doesn't expire)
	 * @return bool
	 * @access public
	 */
	public function addCache($key, $value, $expiration = 0)
	{
		return $this->cacheManager->addCache($key, $value, $expiration);
	}

	/**
	 * Sets rebuilding mode for given cache
	 *
	 * @param string $name
	 * @param int $mode
	 * @param int $max_rebuilding_time
	 * @return bool
	 * @access public
	 */
	public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0)
	{
		return $this->cacheManager->rebuildCache($name, $mode, $max_rebuilding_time);
	}

	/**
	 * Deletes key from cache
	 *
	 * @param string $key
	 * @return void
	 * @access public
	 */
	public function deleteCache($key)
	{
		$this->cacheManager->deleteCache($key);
	}

	/**
	 * Reset's all memory cache at once
	 *
	 * @return void
	 * @access public
	 */
	public function resetCache()
	{
		$this->cacheManager->resetCache();
	}

	/**
	 * Returns value from database cache
	 *
	 * @param string $name key name
	 * @param int $max_rebuild_seconds
	 * @return mixed
	 * @access public
	 */
	public function getDBCache($name, $max_rebuild_seconds = 0)
	{
		return $this->cacheManager->getDBCache($name, $max_rebuild_seconds);
	}

	/**
	 * Sets value to database cache
	 *
	 * @param string $name
	 * @param mixed $value
	 * @param int|bool $expiration
	 * @return void
	 * @access public
	 */
	public function setDBCache($name, $value, $expiration = false)
	{
		$this->cacheManager->setDBCache($name, $value, $expiration);
	}

	/**
	 * Sets rebuilding mode for given cache
	 *
	 * @param string $name
	 * @param int $mode
	 * @param int $max_rebuilding_time
	 * @return bool
	 * @access public
	 */
	public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0)
	{
		return $this->cacheManager->rebuildDBCache($name, $mode, $max_rebuilding_time);
	}

	/**
	 * Deletes key from database cache
	 *
	 * @param string $name
	 * @return void
	 * @access public
	 */
	public function deleteDBCache($name)
	{
		$this->cacheManager->deleteDBCache($name);
	}

	/**
	 * Registers each module specific constants if any found
	 *
	 * @return bool
	 * @access protected
	 */
	protected function registerModuleConstants()
	{
		if ( file_exists(KERNEL_PATH . '/constants.php') ) {
			kUtil::includeOnce(KERNEL_PATH . '/constants.php');
		}

		if ( !$this->ModuleInfo ) {
			return false;
		}

		foreach ($this->ModuleInfo as $module_info) {
			$constants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php';

			if ( file_exists($constants_file) ) {
				kUtil::includeOnce($constants_file);
			}
		}

		return true;
	}

	/**
	 * Performs redirect to hard maintenance template
	 *
	 * @return void
	 * @access public
	 */
	public function redirectToMaintenance()
	{
		$maintenance_page = WRITEBALE_BASE . '/maintenance.html';
		$query_string = ''; // $this->isAdmin ? '' : '?next_template=' . urlencode($_SERVER['REQUEST_URI']);

		if ( file_exists(FULL_PATH . $maintenance_page) ) {
			header('Location: ' . BASE_PATH . $maintenance_page . $query_string);
			exit;
		}
	}

	/**
	 * Actually runs the parser against current template and stores parsing result
	 *
	 * This method gets 't' variable passed to the script, loads the template given in 't' variable and
	 * parses it. The result is store in {@link $this->HTML} property.
	 *
	 * @return void
	 * @access public
	 */
	public function Run()
	{
		// process maintenance mode redirect: begin
		$maintenance_mode = $this->getMaintenanceMode();

		if ( $maintenance_mode == MaintenanceMode::HARD ) {
			$this->redirectToMaintenance();
		}
		elseif ( $maintenance_mode == MaintenanceMode::SOFT ) {
			$maintenance_template = $this->isAdmin ? 'login' : $this->ConfigValue('SoftMaintenanceTemplate');

			if ( $this->GetVar('t') != $maintenance_template ) {
				$redirect_params = Array ();

				if ( !$this->isAdmin ) {
					$redirect_params['next_template'] = urlencode($_SERVER['REQUEST_URI']);
				}

				$this->Redirect($maintenance_template, $redirect_params);
			}
		}
		// process maintenance mode redirect: end

		if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
			$this->Debugger->appendMemoryUsage('Application before Run:');
		}

		if ( $this->isAdminUser ) {
			// for permission checking in events & templates
			$this->LinkVar('module'); // for common configuration templates
			$this->LinkVar('module_key'); // for common search templates
			$this->LinkVar('section'); // for common configuration templates

			if ( $this->GetVar('m_opener') == 'p' ) {
				$this->LinkVar('main_prefix'); // window prefix, that opened selector
				$this->LinkVar('dst_field'); // field to set value choosed in selector
			}

			if ( $this->GetVar('ajax') == 'yes' && !$this->GetVar('debug_ajax') ) {
				// hide debug output from ajax requests automatically
				kUtil::safeDefine('DBG_SKIP_REPORTING', 1); // safeDefine, because debugger also defines it
			}
		}
		elseif ( $this->GetVar('admin') ) {
			$admin_session = $this->recallObject('Session.admin');
			/* @var $admin_session Session */

			// store Admin Console User's ID to Front-End's session for cross-session permission checks
			$this->StoreVar('admin_user_id', (int)$admin_session->RecallVar('user_id'));

			if ( $this->CheckAdminPermission('CATEGORY.MODIFY', 0, $this->getBaseCategory()) ) {
				// user can edit cms blocks (when viewing front-end through admin's frame)
				$editing_mode = $this->GetVar('editing_mode');
				define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE);
			}
		}

		kUtil::safeDefine('EDITING_MODE', ''); // user can't edit anything
		$this->Phrases->setPhraseEditing();

		$this->EventManager->ProcessRequest();

		$this->InitParser();
		$t = $this->GetVar('render_template', $this->GetVar('t'));

		if ( !$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin ) {
			$cms_handler = $this->recallObject('st_EventHandler');
			/* @var $cms_handler CategoriesEventHandler */

			$t = ltrim($cms_handler->GetDesignTemplate(), '/');

			if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
				$this->Debugger->appendHTML('<strong>Design Template</strong>: ' . $t . '; <strong>CategoryID</strong>: ' . $this->GetVar('m_cat_id'));
			}
		}
		/*else {
			$cms_handler->SetCatByTemplate();
		}*/

		if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
			$this->Debugger->appendMemoryUsage('Application before Parsing:');
		}

		$this->HTML = $this->Parser->Run($t);

		if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
			$this->Debugger->appendMemoryUsage('Application after Parsing:');
		}
	}

	/**
	 * Only renders template
	 *
	 * @see kDBEventHandler::_errorNotFound()
	 */
	public function QuickRun()
	{
		$this->InitParser();
		$this->HTML = $this->ParseBlock(Array ('name' => $this->GetVar('t')));
	}

	/**
	 * Performs template parser/cache initialization
	 *
	 * @param bool|string $theme_name
	 * @return void
	 * @access public
	 */
	public function InitParser($theme_name = false)
	{
		if ( !is_object($this->Parser) ) {
			$this->Parser = $this->recallObject('NParser');
			$this->TemplatesCache = $this->recallObject('TemplatesCache');
		}

		$this->TemplatesCache->forceThemeName = $theme_name;
	}

	/**
	 * Send the parser results to browser
	 *
	 * Actually send everything stored in {@link $this->HTML}, to the browser by echoing it.
	 *
	 * @return void
	 * @access public
	 */
	public function Done()
	{
		$this->HandleEvent(new kEvent('adm:OnBeforeShutdown'));

		$debug_mode = defined('DEBUG_MODE') && $this->isDebugMode();

		if ( $debug_mode && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
			$this->Debugger->appendMemoryUsage('Application before Done:');
		}

		if ( $debug_mode ) {
			$this->EventManager->runScheduledTasks();
			$this->Session->SaveData();

			$this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true);
		}
		else {
			// send "Set-Cookie" header before any output is made
			$this->Session->SetSession();

			$this->HTML = ob_get_clean() . $this->HTML;
		}

		$this->setContentType();

		if ( $this->UseOutputCompression() ) {
			$compression_level = $this->ConfigValue('OutputCompressionLevel');

			if ( !$compression_level || $compression_level < 0 || $compression_level > 9 ) {
				$compression_level = 7;
			}

			header('Content-Encoding: gzip');
			echo gzencode($this->HTML, $compression_level);
		}
		else {
			echo $this->HTML;
		}

		$this->cacheManager->UpdateApplicationCache();
		flush();

		if ( !$debug_mode ) {
			$this->EventManager->runScheduledTasks();
			$this->Session->SaveData();
		}

		if ( defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin ) {
			$this->_storeStatistics();
		}
	}

	/**
	 * Stores script execution statistics to database
	 *
	 * @return void
	 * @access protected
	 */
	protected function _storeStatistics()
	{
		global $start;

		$script_time = microtime(true) - $start;
		$query_statistics = $this->Conn->getQueryStatistics(); // time & count

		$sql = 'SELECT *
				FROM ' . TABLE_PREFIX . 'StatisticsCapture
				WHERE TemplateName = ' . $this->Conn->qstr($this->GetVar('t'));
		$data = $this->Conn->GetRow($sql);

		if ( $data ) {
			$this->_updateAverageStatistics($data, 'ScriptTime', $script_time);
			$this->_updateAverageStatistics($data, 'SqlTime', $query_statistics['time']);
			$this->_updateAverageStatistics($data, 'SqlCount', $query_statistics['count']);

			$data['Hits']++;
			$data['LastHit'] = adodb_mktime();

			$this->Conn->doUpdate($data, TABLE_PREFIX . 'StatisticsCapture', 'StatisticsId = ' . $data['StatisticsId']);
		}
		else {
			$data['ScriptTimeMin'] = $data['ScriptTimeAvg'] = $data['ScriptTimeMax'] = $script_time;
			$data['SqlTimeMin'] = $data['SqlTimeAvg'] = $data['SqlTimeMax'] = $query_statistics['time'];
			$data['SqlCountMin'] = $data['SqlCountAvg'] = $data['SqlCountMax'] = $query_statistics['count'];
			$data['TemplateName'] = $this->GetVar('t');
			$data['Hits'] = 1;
			$data['LastHit'] = adodb_mktime();
			$this->Conn->doInsert($data, TABLE_PREFIX . 'StatisticsCapture');
		}
	}

	/**
	 * Calculates average time for statistics
	 *
	 * @param Array $data
	 * @param string $field_prefix
	 * @param float $current_value
	 * @return void
	 * @access protected
	 */
	protected function _updateAverageStatistics(&$data, $field_prefix, $current_value)
	{
		$data[$field_prefix . 'Avg'] = (($data['Hits'] * $data[$field_prefix . 'Avg']) + $current_value) / ($data['Hits'] + 1);

		if ( $current_value < $data[$field_prefix . 'Min'] ) {
			$data[$field_prefix . 'Min'] = $current_value;
		}

		if ( $current_value > $data[$field_prefix . 'Max'] ) {
			$data[$field_prefix . 'Max'] = $current_value;
		}
	}

	/**
	 * Remembers slow query SQL and execution time into log
	 *
	 * @param string $slow_sql
	 * @param int $time
	 * @return void
	 * @access public
	 */
	public function logSlowQuery($slow_sql, $time)
	{
		$query_crc = kUtil::crc32($slow_sql);

		$sql = 'SELECT *
				FROM ' . TABLE_PREFIX . 'SlowSqlCapture
				WHERE QueryCrc = ' . $query_crc;
		$data = $this->Conn->Query($sql, null, true);

		if ( $data ) {
			$this->_updateAverageStatistics($data, 'Time', $time);

			$template_names = explode(',', $data['TemplateNames']);
			array_push($template_names, $this->GetVar('t'));
			$data['TemplateNames'] = implode(',', array_unique($template_names));

			$data['Hits']++;
			$data['LastHit'] = adodb_mktime();

			$this->Conn->doUpdate($data, TABLE_PREFIX . 'SlowSqlCapture', 'CaptureId = ' . $data['CaptureId']);
		}
		else {
			$data['TimeMin'] = $data['TimeAvg'] = $data['TimeMax'] = $time;
			$data['SqlQuery'] = $slow_sql;
			$data['QueryCrc'] = $query_crc;
			$data['TemplateNames'] = $this->GetVar('t');
			$data['Hits'] = 1;
			$data['LastHit'] = adodb_mktime();

			$this->Conn->doInsert($data, TABLE_PREFIX . 'SlowSqlCapture');
		}
	}

	/**
	 * Checks if output compression options is available
	 *
	 * @return bool
	 * @access protected
	 */
	protected function UseOutputCompression()
	{
		if ( kUtil::constOn('IS_INSTALL') || kUtil::constOn('DBG_ZEND_PRESENT') || kUtil::constOn('SKIP_OUT_COMPRESSION') ) {
			return false;
		}

		return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');
	}

	//	Facade

	/**
	 * Returns current session id (SID)
	 *
	 * @return int
	 * @access public
	 */
	public function GetSID()
	{
		$session = $this->recallObject('Session');
		/* @var $session Session */

		return $session->GetID();
	}

	/**
	 * Destroys current session
	 *
	 * @return void
	 * @access public
	 * @see UserHelper::logoutUser()
	 */
	public function DestroySession()
	{
		$session = $this->recallObject('Session');
		/* @var $session Session */

		$session->Destroy();
	}

	/**
	 * Returns variable passed to the script as GET/POST/COOKIE
	 *
	 * @param string $name Name of variable to retrieve
	 * @param mixed $default default value returned in case if variable not present
	 * @return mixed
	 * @access public
	 */
	public function GetVar($name, $default = false)
	{
		return isset($this->HttpQuery->_Params[$name]) ? $this->HttpQuery->_Params[$name] : $default;
	}

	/**
	 * Returns variable passed to the script as $type
	 *
	 * @param string $name Name of variable to retrieve
	 * @param string $type Get/Post/Cookie
	 * @param mixed $default default value returned in case if variable not present
	 * @return mixed
	 * @access public
	 */
	public function GetVarDirect($name, $type, $default = false)
	{
//		$type = ucfirst($type);
		$array = $this->HttpQuery->$type;

		return isset($array[$name]) ? $array[$name] : $default;
	}

	/**
	 * Returns ALL variables passed to the script as GET/POST/COOKIE
	 *
	 * @return Array
	 * @access public
	 * @deprecated
	 */
	public function GetVars()
	{
		return $this->HttpQuery->GetParams();
	}

	/**
	 * Set the variable 'as it was passed to the script through GET/POST/COOKIE'
	 *
	 * This could be useful to set the variable when you know that
	 * other objects would relay on variable passed from GET/POST/COOKIE
	 * or you could use SetVar() / GetVar() pairs to pass the values between different objects.<br>
	 *
	 * @param string $var Variable name to set
	 * @param mixed $val Variable value
	 * @return void
	 * @access public
	 */
	public function SetVar($var,$val)
	{
		$this->HttpQuery->Set($var, $val);
	}

	/**
	 * Deletes kHTTPQuery variable
	 *
	 * @param string $var
	 * @return void
	 * @todo Think about method name
	 */
	public function DeleteVar($var)
	{
		$this->HttpQuery->Remove($var);
	}

	/**
	 * Deletes Session variable
	 *
	 * @param string $var
	 * @return void
	 * @access public
	 */
	public function RemoveVar($var)
	{
		$this->Session->RemoveVar($var);
	}

	/**
	 * Removes variable from persistent session
	 *
	 * @param string $var
	 * @return void
	 * @access public
	 */
	public function RemovePersistentVar($var)
	{
		$this->Session->RemovePersistentVar($var);
	}

	/**
	 * Restores Session variable to it's db version
	 *
	 * @param string $var
	 * @return void
	 * @access public
	 */
	public function RestoreVar($var)
	{
		$this->Session->RestoreVar($var);
	}

	/**
	 * Returns session variable value
	 *
	 * Return value of $var variable stored in Session. An optional default value could be passed as second parameter.
	 *
	 * @param string $var Variable name
	 * @param mixed $default Default value to return if no $var variable found in session
	 * @return mixed
	 * @access public
	 * @see Session::RecallVar()
	 */
	public function RecallVar($var,$default=false)
	{
		return $this->Session->RecallVar($var,$default);
	}

	/**
	 * Returns variable value from persistent session
	 *
	 * @param string $var
	 * @param mixed $default
	 * @return mixed
	 * @access public
	 * @see Session::RecallPersistentVar()
	 */
	public function RecallPersistentVar($var, $default = false)
	{
		return $this->Session->RecallPersistentVar($var, $default);
	}

	/**
	 * Stores variable $val in session under name $var
	 *
	 * Use this method to store variable in session. Later this variable could be recalled.
	 *
	 * @param string $var Variable name
	 * @param mixed $val Variable value
	 * @param bool $optional
	 * @return void
	 * @access public
	 * @see kApplication::RecallVar()
	 */
	public function StoreVar($var, $val, $optional = false)
	{
		$session = $this->recallObject('Session');
		/* @var $session Session */

		$this->Session->StoreVar($var, $val, $optional);
	}

	/**
	 * Stores variable to persistent session
	 *
	 * @param string $var
	 * @param mixed $val
	 * @param bool $optional
	 * @return void
	 * @access public
	 */
	public function StorePersistentVar($var, $val, $optional = false)
	{
		$this->Session->StorePersistentVar($var, $val, $optional);
	}

	/**
	 * Stores default value for session variable
	 *
	 * @param string $var
	 * @param string $val
	 * @param bool $optional
	 * @return void
	 * @access public
	 * @see Session::RecallVar()
	 * @see Session::StoreVar()
	 */
	public function StoreVarDefault($var, $val, $optional = false)
	{
		$session = $this->recallObject('Session');
		/* @var $session Session */

		$this->Session->StoreVarDefault($var, $val, $optional);
	}

	/**
	 * Links HTTP Query variable with session variable
	 *
	 * If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session.
	 * This method could be used for making sure that GetVar will return query or session value for given
	 * variable, when query variable should overwrite session (and be stored there for later use).<br>
	 * This could be used for passing item's ID into popup with multiple tab -
	 * in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id').
	 * After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session
	 *
	 * @param string $var HTTP Query (GPC) variable name
	 * @param mixed $ses_var Session variable name
	 * @param mixed $default Default variable value
	 * @param bool $optional
	 * @return void
	 * @access public
	 */
	public function LinkVar($var, $ses_var = null, $default = '', $optional = false)
	{
		if ( !isset($ses_var) ) {
			$ses_var = $var;
		}

		if ( $this->GetVar($var) !== false ) {
			$this->StoreVar($ses_var, $this->GetVar($var), $optional);
		}
		else {
			$this->SetVar($var, $this->RecallVar($ses_var, $default));
		}
	}

	/**
	 * Returns variable from HTTP Query, or from session if not passed in HTTP Query
	 *
	 * The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed.
	 * Returns the default value if variable does not exist in session and was not passed in HTTP Query
	 *
	 * @param string $var HTTP Query (GPC) variable name
	 * @param mixed $ses_var Session variable name
	 * @param mixed $default Default variable value
	 * @return mixed
	 * @access public
	 * @see LinkVar
	 */
	public function GetLinkedVar($var, $ses_var = null, $default = '')
	{
		$this->LinkVar($var, $ses_var, $default);

		return $this->GetVar($var);
	}

	/**
	 * Renders given tag and returns it's output
	 *
	 * @param string $prefix
	 * @param string $tag
	 * @param Array $params
	 * @return mixed
	 * @access public
	 * @see kApplication::InitParser()
	 */
	public function ProcessParsedTag($prefix, $tag, $params)
	{
		$processor = $this->Parser->GetProcessor($prefix);
		/* @var $processor kDBTagProcessor */

		return $processor->ProcessParsedTag($tag, $params, $prefix);
	}

	/**
	 * Return ADODB Connection object
	 *
	 * Returns ADODB Connection object already connected to the project database, configurable in config.php
	 *
	 * @return kDBConnection
	 * @access public
	 */
	public function &GetADODBConnection()
	{
		return $this->Conn;
	}

	/**
	 * Allows to parse given block name or include template
	 *
	 * @param Array $params Parameters to pass to block. Reserved parameter "name" used to specify block name.
	 * @param bool $pass_params Forces to pass current parser params to this block/template. Use with caution, because you can accidentally pass "block_no_data" parameter.
	 * @param bool $as_template
	 * @return string
	 * @access public
	 */
	public function ParseBlock($params, $pass_params = false, $as_template = false)
	{
		if ( substr($params['name'], 0, 5) == 'html:' ) {
			return substr($params['name'], 5);
		}

		return $this->Parser->ParseBlock($params, $pass_params, $as_template);
	}

	/**
	 * Checks, that we have given block defined
	 *
	 * @param string $name
	 * @return bool
	 * @access public
	 */
	public function ParserBlockFound($name)
	{
		return $this->Parser->blockFound($name);
	}

	/**
	 * Allows to include template with a given name and given parameters
	 *
	 * @param Array $params Parameters to pass to template. Reserved parameter "name" used to specify template name.
	 * @return string
	 * @access public
	 */
	public function IncludeTemplate($params)
	{
		return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0);
	}

	/**
	 * Return href for template
	 *
	 * @param string $t Template path
	 * @param string $prefix index.php prefix - could be blank, 'admin'
	 * @param Array $params
	 * @param string $index_file
	 * @return string
	 */
	public function HREF($t, $prefix = '', $params = Array (), $index_file = null)
	{
		return $this->UrlManager->HREF($t, $prefix, $params, $index_file);
	}

	/**
	 * Returns theme template filename and it's corresponding page_id based on given seo template
	 *
	 * @param string $seo_template
	 * @return string
	 * @access public
	 */
	public function getPhysicalTemplate($seo_template)
	{
		return $this->UrlManager->getPhysicalTemplate($seo_template);
	}

	/**
	 * Returns template name, that corresponds with given virtual (not physical) page id
	 *
	 * @param int $page_id
	 * @return string|bool
	 * @access public
	 */
	public function getVirtualPageTemplate($page_id)
	{
		return $this->UrlManager->getVirtualPageTemplate($page_id);
	}

	/**
	 * Returns variables with values that should be passed through with this link + variable list
	 *
	 * @param Array $params
	 * @return Array
	 * @access public
	 */
	public function getPassThroughVariables(&$params)
	{
		return $this->UrlManager->getPassThroughVariables($params);
	}

	/**
	 * Builds url
	 *
	 * @param string $t
	 * @param Array $params
	 * @param string $pass
	 * @param bool $pass_events
	 * @param bool $env_var
	 * @return string
	 * @access public
	 */
	public function BuildEnv($t, $params, $pass = 'all', $pass_events = false, $env_var = true)
	{
		return $this->UrlManager->plain->build($t, $params, $pass, $pass_events, $env_var);
	}

	/**
	 * Process QueryString only, create
	 * events, ids, based on config
	 * set template name and sid in
	 * desired application variables.
	 *
	 * @param string $env_var environment string value
	 * @param string $pass_name
	 * @return Array
	 * @access public
	 */
	public function processQueryString($env_var, $pass_name = 'passed')
	{
		return $this->UrlManager->plain->parse($env_var, $pass_name);
	}

	/**
	 * Parses rewrite url and returns parsed variables
	 *
	 * @param string $url
	 * @param string $pass_name
	 * @return Array
	 * @access public
	 */
	public function parseRewriteUrl($url, $pass_name = 'passed')
	{
		return $this->UrlManager->rewrite->parse($url, $pass_name);
	}

	/**
	 * Returns base part of all urls, build on website
	 *
	 * @param string $prefix
	 * @param bool $ssl
	 * @param bool $add_port
	 * @return string
	 * @access public
	 */
	public function BaseURL($prefix = '', $ssl = null, $add_port = true)
	{
		if ( $ssl === null ) {
			// stay on same encryption level
			return PROTOCOL . SERVER_NAME . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/';
		}

		if ( $ssl ) {
			// going from http:// to https://
			$base_url = $this->isAdmin ? $this->ConfigValue('AdminSSL_URL') : false;

			if ( !$base_url ) {
				$ssl_url = $this->siteDomainField('SSLUrl');
				$base_url = $ssl_url !== false ? $ssl_url : $this->ConfigValue('SSL_URL');
			}

			return rtrim($base_url, '/') . $prefix . '/';
		}

		// going from https:// to http://
		$domain = $this->siteDomainField('DomainName');

		if ( $domain === false ) {
			$domain = DOMAIN;
		}

		return 'http://' . $domain . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/';
	}

	/**
	 * Redirects user to url, that's build based on given parameters
	 *
	 * @param string $t
	 * @param Array $params
	 * @param string $prefix
	 * @param string $index_file
	 * @return void
	 * @access public
	 */
	public function Redirect($t = '', $params = Array(), $prefix = '', $index_file = null)
	{
		$js_redirect = getArrayValue($params, 'js_redirect');

		if ( $t == '' || $t === true ) {
			$t = $this->GetVar('t');
		}

		// pass prefixes and special from previous url
		if ( array_key_exists('js_redirect', $params) ) {
			unset($params['js_redirect']);
		}

		// allows to send custom responce code along with redirect header
		if ( array_key_exists('response_code', $params) ) {
			$response_code = (int)$params['response_code'];
			unset($params['response_code']);
		}
		else {
			$response_code = 302; // Found
		}

		if ( !array_key_exists('pass', $params) ) {
			$params['pass'] = 'all';
		}

		if ( $this->GetVar('ajax') == 'yes' && $t == $this->GetVar('t') ) {
			// redirects to the same template as current
			$params['ajax'] = 'yes';
		}

		$params['__URLENCODE__'] = 1;
		$location = $this->HREF($t, $prefix, $params, $index_file);

		if ( $this->isDebugMode() && (kUtil::constOn('DBG_REDIRECT') || (kUtil::constOn('DBG_RAISE_ON_WARNINGS') && $this->Debugger->WarningCount)) ) {
			$this->Debugger->appendTrace();
			echo '<strong>Debug output above !!!</strong><br/>' . "\n";

			if ( array_key_exists('HTTP_REFERER', $_SERVER) ) {
				echo 'Referer: <strong>' . $_SERVER['HTTP_REFERER'] . '</strong><br/>' . "\n";
			}

			echo "Proceed to redirect: <a href=\"{$location}\">{$location}</a><br/>\n";
		}
		else {
			if ( $js_redirect ) {
				// show "redirect" template instead of redirecting,
				// because "Set-Cookie" header won't work, when "Location"
				// header is used later
				$this->SetVar('t', 'redirect');
				$this->SetVar('redirect_to', $location);

				// make all additional parameters available on "redirect" template too
				foreach ($params as $name => $value) {
					$this->SetVar($name, $value);
				}

				return;
			}
			else {
				if ( $this->GetVar('ajax') == 'yes' && $t != $this->GetVar('t') ) {
					// redirection to other then current template during ajax request
					kUtil::safeDefine('DBG_SKIP_REPORTING', 1);
					echo '#redirect#' . $location;
				}
				elseif ( headers_sent() != '' ) {
					// some output occurred -> redirect using javascript
					echo '<script type="text/javascript">window.location.href = \'' . $location . '\';</script>';
				}
				else {
					// no output before -> redirect using HTTP header

//					header('HTTP/1.1 302 Found');
					header('Location: ' . $location, true, $response_code);
				}
			}
		}

		// session expiration is called from session initialization,
		// that's why $this->Session may be not defined here
		$session = $this->recallObject('Session');
		/* @var $session Session */

		if ( $this->InitDone ) {
			// if redirect happened in the middle of application initialization don't call event,
			// that presumes that application was successfully initialized
			$this->HandleEvent(new kEvent('adm:OnBeforeShutdown'));
		}

		$session->SaveData();

		ob_end_flush();
		exit;
	}

	/**
	 * Returns translation of given label
	 *
	 * @param string $label
	 * @param bool $allow_editing return translation link, when translation is missing on current language
	 * @param bool $use_admin use current Admin Console language to translate phrase
	 * @return string
	 * @access public
	 */
	public function Phrase($label, $allow_editing = true, $use_admin = false)
	{
		return $this->Phrases->GetPhrase($label, $allow_editing, $use_admin);
	}

	/**
	 * Replace language tags in exclamation marks found in text
	 *
	 * @param string $text
	 * @param bool $force_escape force escaping, not escaping of resulting string
	 * @return string
	 * @access public
	 */
	public function ReplaceLanguageTags($text, $force_escape = null)
	{
		return $this->Phrases->ReplaceLanguageTags($text, $force_escape);
	}

	/**
	 * Checks if user is logged in, and creates
	 * user object if so. User object can be recalled
	 * later using "u.current" prefix_special. Also you may
	 * get user id by getting "u.current_id" variable.
	 *
	 * @return void
	 * @access protected
	 */
	protected function ValidateLogin()
	{
		$session = $this->recallObject('Session');
		/* @var $session Session */

		$user_id = $session->GetField('PortalUserId');

		if ( !$user_id && $user_id != USER_ROOT ) {
			$user_id = USER_GUEST;
		}

		$this->SetVar('u.current_id', $user_id);

		if ( !$this->isAdmin ) {
			// needed for "profile edit", "registration" forms ON FRONT ONLY
			$this->SetVar('u_id', $user_id);
		}

		$this->StoreVar('user_id', $user_id, $user_id == USER_GUEST); // storing Guest user_id (-2) is optional

		$this->isAdminUser = $this->isAdmin && $this->LoggedIn();

		if ( $this->GetVar('expired') == 1 ) {
			// this parameter is set only from admin
			$user = $this->recallObject('u.login-admin', null, Array ('form_name' => 'login'));
			/* @var $user UsersItem */

			$user->SetError('UserLogin', 'session_expired', 'la_text_sess_expired');
		}

		if ( ($user_id != USER_GUEST) && defined('DBG_REQUREST_LOG') && DBG_REQUREST_LOG ) {
			$this->HttpQuery->writeRequestLog(DBG_REQUREST_LOG);
		}

		if ( $user_id != USER_GUEST ) {
			// normal users + root
			$this->LoadPersistentVars();
		}

		$user_timezone = $this->Session->GetField('TimeZone');

		if ( $user_timezone ) {
			putenv('TZ=' . $user_timezone);
		}
	}

	/**
	 * Loads current user persistent session data
	 *
	 * @return void
	 * @access public
	 */
	public function LoadPersistentVars()
	{
		$this->Session->LoadPersistentVars();
	}

	/**
	 * Returns configuration option value by name
	 *
	 * @param string $name
	 * @return string
	 * @access public
	 */
	public function ConfigValue($name)
	{
		return $this->cacheManager->ConfigValue($name);
	}

	/**
	 * Changes value of individual configuration variable (+resets cache, when needed)
	 *
	 * @param string $name
	 * @param string $value
	 * @param bool $local_cache_only
	 * @return string
	 * @access public
	 */
	public function SetConfigValue($name, $value, $local_cache_only = false)
	{
		return $this->cacheManager->SetConfigValue($name, $value, $local_cache_only);
	}

	/**
	 * Allows to process any type of event
	 *
	 * @param kEvent $event
	 * @param Array $params
	 * @param Array $specific_params
	 * @return void
	 * @access public
	 */
	public function HandleEvent($event, $params = null, $specific_params = null)
	{
		if ( isset($params) ) {
			$event = new kEvent($params, $specific_params);
		}

		$this->EventManager->HandleEvent($event);
	}

	/**
	 * Notifies event subscribers, that event has occured
	 *
	 * @param kEvent $event
	 * @return void
	 */
	public function notifyEventSubscribers(kEvent $event)
	{
		$this->EventManager->notifySubscribers($event);
	}

	/**
	 * Allows to process any type of event
	 *
	 * @param kEvent $event
	 * @return bool
	 * @access public
	 */
	public function eventImplemented(kEvent $event)
	{
		return $this->EventManager->eventImplemented($event);
	}

	/**
	 * Registers new class in the factory
	 *
	 * @param string $real_class Real name of class as in class declaration
	 * @param string $file Filename in what $real_class is declared
	 * @param string $pseudo_class Name under this class object will be accessed using getObject method
	 * @return void
	 * @access public
	 */
	public function registerClass($real_class, $file, $pseudo_class = null)
	{
		$this->Factory->registerClass($real_class, $file, $pseudo_class);
	}

	/**
	 * Unregisters existing class from factory
	 *
	 * @param string $real_class Real name of class as in class declaration
	 * @param string $pseudo_class Name under this class object is accessed using getObject method
	 * @return void
	 * @access public
	 */
	public function unregisterClass($real_class, $pseudo_class = null)
	{
		$this->Factory->unregisterClass($real_class, $pseudo_class);
	}

	/**
	 * Add new scheduled task
	 *
	 * @param string $short_name name to be used to store last maintenance run info
	 * @param string $event_string
	 * @param int $run_interval run interval in seconds
	 * @param int $status
	 * @access public
	 */
	public function registerScheduledTask($short_name, $event_string, $run_interval, $status = STATUS_ACTIVE)
	{
		$this->EventManager->registerScheduledTask($short_name, $event_string, $run_interval, $status);
	}

	/**
	 * Registers Hook from subprefix event to master prefix event
	 *
	 * Pattern: Observer
	 *
	 * @param string $hook_event
	 * @param string $do_event
	 * @param int $mode
	 * @param bool $conditional
	 * @access public
	 */
	public function registerHook($hook_event, $do_event, $mode = hAFTER, $conditional = false)
	{
		$this->EventManager->registerHook($hook_event, $do_event, $mode, $conditional);
	}

	/**
	 * Registers build event for given pseudo class
	 *
	 * @param string $pseudo_class
	 * @param string $event_name
	 * @access public
	 */
	public function registerBuildEvent($pseudo_class, $event_name)
	{
		$this->EventManager->registerBuildEvent($pseudo_class, $event_name);
	}

	/**
	 * Allows one TagProcessor tag act as other TagProcessor tag
	 *
	 * @param Array $tag_info
	 * @return void
	 * @access public
	 */
	public function registerAggregateTag($tag_info)
	{
		$aggregator = $this->recallObject('TagsAggregator', 'kArray');
		/* @var $aggregator kArray */

		$tag_data = Array (
			$tag_info['LocalPrefix'],
			$tag_info['LocalTagName'],
			getArrayValue($tag_info, 'LocalSpecial')
		);

		$aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], $tag_data);
	}

	/**
	 * Returns object using params specified, creates it if is required
	 *
	 * @param string $name
	 * @param string $pseudo_class
	 * @param Array $event_params
	 * @param Array $arguments
	 * @return kBase
	 */
	public function recallObject($name, $pseudo_class = null, $event_params = Array(), $arguments = Array ())
	{
		/*if ( !$this->hasObject($name) && $this->isDebugMode() && ($name == '_prefix_here_') ) {
			// first time, when object with "_prefix_here_" prefix is accessed
			$this->Debugger->appendTrace();
		}*/

		return $this->Factory->getObject($name, $pseudo_class, $event_params, $arguments);
	}

	/**
	 * Returns tag processor for prefix specified
	 *
	 * @param string $prefix
	 * @return kDBTagProcessor
	 * @access public
	 */
	public function recallTagProcessor($prefix)
	{
		$this->InitParser(); // because kDBTagProcesor is in NParser dependencies

		return $this->recallObject($prefix . '_TagProcessor');
	}

	/**
	 * Checks if object with prefix passes was already created in factory
	 *
	 * @param string $name object pseudo_class, prefix
	 * @return bool
	 * @access public
	 */
	public function hasObject($name)
	{
		return $this->Factory->hasObject($name);
	}

	/**
	 * Removes object from storage by given name
	 *
	 * @param string $name Object's name in the Storage
	 * @return void
	 * @access public
	 */
	public function removeObject($name)
	{
		$this->Factory->DestroyObject($name);
	}

	/**
	 * Get's real class name for pseudo class, includes class file and creates class instance
	 *
	 * Pattern: Factory Method
	 *
	 * @param string $pseudo_class
	 * @param Array $arguments
	 * @return kBase
	 * @access public
	 */
	public function makeClass($pseudo_class, $arguments = Array ())
	{
		return $this->Factory->makeClass($pseudo_class, $arguments);
	}

	/**
	 * Checks if application is in debug mode
	 *
	 * @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant
	 * @return bool
	 * @author Alex
	 * @access public
	 */
	public function isDebugMode($check_debugger = true)
	{
		$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
		if ($check_debugger) {
			$debug_mode = $debug_mode && is_object($this->Debugger);
		}
		return $debug_mode;
	}

	/**
	 * Apply url rewriting used by mod_rewrite or not
	 *
	 * @param bool|null $ssl Force ssl link to be build
	 * @return bool
	 * @access public
	 */
	public function RewriteURLs($ssl = false)
	{
		// case #1,#4:
		//			we want to create https link from http mode
		//			we want to create https link from https mode
		//			conditions: ($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')

		// case #2,#3:
		//			we want to create http link from https mode
		//			we want to create http link from http mode
		//			conditions: !$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')

		$allow_rewriting =
			(!$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')) // always allow mod_rewrite for http
			|| // or allow rewriting for redirect TO httpS or when already in httpS
			(($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')); // but only if it's allowed in config!

		return kUtil::constOn('MOD_REWRITE') && $allow_rewriting;
	}

	/**
	 * Reads unit (specified by $prefix)
	 * option specified by $option
	 *
	 * @param string $prefix
	 * @param string $option
	 * @param mixed $default
	 * @return string
	 * @access public
	 */
	public function getUnitOption($prefix, $option, $default = false)
	{
		return $this->UnitConfigReader->getUnitOption($prefix, $option, $default);
	}

	/**
	 * Set's new unit option value
	 *
	 * @param string $prefix
	 * @param string $option
	 * @param string $value
	 * @access public
	 */
	public function setUnitOption($prefix, $option, $value)
	{
		$this->UnitConfigReader->setUnitOption($prefix,$option,$value);
	}

	/**
	 * Read all unit with $prefix options
	 *
	 * @param string $prefix
	 * @return Array
	 * @access public
	 */
	public function getUnitOptions($prefix)
	{
		return $this->UnitConfigReader->getUnitOptions($prefix);
	}

	/**
	 * Returns true if config exists and is allowed for reading
	 *
	 * @param string $prefix
	 * @return bool
	 */
	public function prefixRegistred($prefix)
	{
		return $this->UnitConfigReader->prefixRegistred($prefix);
	}

	/**
	 * Splits any mixing of prefix and
	 * special into correct ones
	 *
	 * @param string $prefix_special
	 * @return Array
	 * @access public
	 */
	public function processPrefix($prefix_special)
	{
		return $this->Factory->processPrefix($prefix_special);
	}

	/**
	 * Set's new event for $prefix_special
	 * passed
	 *
	 * @param string $prefix_special
	 * @param string $event_name
	 * @return void
	 * @access public
	 */
	public function setEvent($prefix_special, $event_name)
	{
		$this->EventManager->setEvent($prefix_special, $event_name);
	}

	/**
	 * SQL Error Handler
	 *
	 * @param int $code
	 * @param string $msg
	 * @param string $sql
	 * @return bool
	 * @access public
	 */
	public function handleSQLError($code, $msg, $sql)
	{
		if ( isset($this->Debugger) ) {
			$long_error_msg = '<span class="debug_error">' . $msg . ' (' . $code . ')</span><br/><a href="javascript:$Debugger.SetClipboard(\'' . htmlspecialchars($sql) . '\');"><strong>SQL</strong></a>: ' . $this->Debugger->formatSQL($sql);
			$long_id = $this->Debugger->mapLongError($long_error_msg);
			$error_msg = mb_substr($msg . ' (' . $code . ') [' . $sql . ']', 0, 1000) . ' #' . $long_id;

			if ( kUtil::constOn('DBG_SQL_FAILURE') && !defined('IS_INSTALL') ) {
				throw new Exception($error_msg);
			}
			else {
				$this->Debugger->appendTrace();
			}
		}
		else {
			// when not debug mode, then fatal database query won't break anything
			$error_msg = '<strong>SQL Error</strong> in sql: ' . $sql . ', code <strong>' . $code . '</strong> (' . $msg . ')';
		}

		trigger_error($error_msg, E_USER_WARNING);

		return true;
	}

	/**
	 * Default error handler
	 *
	 * @param int $errno
	 * @param string $errstr
	 * @param string $errfile
	 * @param int $errline
	 * @param Array $errcontext
	 * @return bool
	 * @access public
	 */
	public function handleError($errno, $errstr, $errfile = null, $errline = null, $errcontext = Array ())
	{
		$this->errorLogSilent($errno, $errstr, $errfile, $errline);

		$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
		$skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING;

		if ( !$this->errorHandlers || ($debug_mode && $skip_reporting) ) {
			// when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request)
			if ( $errno == E_USER_ERROR ) {
				$this->errorDisplayFatal('<strong>Fatal Error: </strong>' . "{$errstr} in {$errfile} on line {$errline}");
			}

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

		$res = false;
		/* @var $handler Closure */

		foreach ($this->errorHandlers as $handler) {
			if ( is_array($handler) ) {
				$object =& $handler[0];
				$method = $handler[1];
				$res = $object->$method($errno, $errstr, $errfile, $errline, $errcontext);
			}
			else {
				$res = $handler($errno, $errstr, $errfile, $errline, $errcontext);
			}
		}

		return $res;
	}

	/**
	 * Handles exception
	 *
	 * @param Exception $exception
	 * @return bool
	 * @access public
	 */
	public function handleException($exception)
	{
		// transform exception to regular error (no need to rewrite existing error handlers)
		$errno = $exception->getCode();
		$errstr = $exception->getMessage();
		$errfile = $exception->getFile();
		$errline = $exception->getLine();

		$this->errorLogSilent($errno, $errstr, $errfile, $errline);

		$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
		$skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING;

		if ( $exception instanceof kRedirectException ) {
			/* @var $exception kRedirectException */

			$exception->run();
		}

		if ( !$this->exceptionHandlers || ($debug_mode && $skip_reporting) ) {
			// when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request)
			$this->errorDisplayFatal('<strong>' . get_class($exception) . ': </strong>' . "{$errstr} in {$errfile} on line {$errline}");

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

		$res = false;
		/* @var $handler Closure */

		foreach ($this->exceptionHandlers as $handler) {
			if ( is_array($handler) ) {
				$object =& $handler[0];
				$method = $handler[1];
				$res = $object->$method($exception);
			}
			else {
				$res = $handler($exception);
			}
		}

		return $res;
	}

	/**
	 * Silently saves each given error message to "silent_log.txt" file, when silent log mode is enabled
	 * @param int $errno
	 * @param string $errstr
	 * @param string $errfile
	 * @param int $errline
	 * @return void
	 * @access protected
	 */
	protected function errorLogSilent($errno, $errstr = '', $errfile = '', $errline = null)
	{
		if ( !defined('SILENT_LOG') || !SILENT_LOG ) {
			return;
		}

		if ( !(defined('DBG_IGNORE_STRICT_ERRORS') && DBG_IGNORE_STRICT_ERRORS && defined('E_STRICT') && ($errno == E_STRICT)) ) {
			$time = adodb_date('d/m/Y H:i:s');

			$fp = fopen((defined('RESTRICTED') ? RESTRICTED : FULL_PATH) . '/silent_log.txt', 'a');
			fwrite($fp, '[' . $time . '] #' . $errno . ': ' . strip_tags($errstr) . ' in [' . $errfile . '] on line ' . $errline . "\n");
			fclose($fp);
		}
	}

	/**
	 * Displays div with given error message
	 *
	 * @param string $msg
	 * @return void
	 * @access protected
	 */
	protected function errorDisplayFatal($msg)
	{
		$margin = $this->isAdmin ? '8px' : 'auto';
		echo '<div style="background-color: #FEFFBF; margin: ' . $margin . '; padding: 10px; border: 2px solid red; text-align: center">' . $msg . '</div>';
		exit;
	}

	/**
	 * Prints trace, when debug mode is not available
	 *
	 * @param bool $return_result
	 * @param int $skip_levels
	 * @return string
	 * @access public
	 */
	public function printTrace($return_result = false, $skip_levels = 1)
	{
		$ret = Array ();
		$trace = debug_backtrace(false);

		for ($i = 0; $i < $skip_levels; $i++) {
			array_shift($trace);
		}

		foreach ($trace as $level => $trace_info) {
			if ( isset($trace_info['class']) ) {
				$object = $trace_info['class'];
			}
			elseif ( isset($trace_info['object']) ) {
				$object = get_class($trace_info['object']);
			}
			else {
				$object = '';
			}

			$args = '';
			$type = isset($trace_info['type']) ? $trace_info['type'] : '';

			if ( isset($trace_info['args']) ) {
				foreach ($trace_info['args'] as $argument) {
					if ( is_object($argument) ) {
						$args .= get_class($argument) . ' instance, ';
					}
					else {
						$args .= is_array($argument) ? 'Array' : substr($argument, 0, 10) . ' ..., ';
					}
				}

				$args = substr($args, 0, -2);
			}

			$ret[] = '#' . $level . '  ' . $object . $type . $trace_info['function'] . '(' . $args . ') called at [' . $trace_info['file'] . ':' . $trace_info['line'] . ']';
		}

		if ( $return_result ) {
			return implode("\n", $ret);
		}

		echo implode("\n", $ret);

		return '';
	}

	/**
	 * Returns & blocks next ResourceId available in system
	 *
	 * @return int
	 * @access public
	 */
	public function NextResourceId()
	{
		$table_name = TABLE_PREFIX . 'IdGenerator';

		$this->Conn->Query('LOCK TABLES ' . $table_name . ' WRITE');
		$this->Conn->Query('UPDATE ' . $table_name . ' SET lastid = lastid + 1');
		$id = $this->Conn->GetOne('SELECT lastid FROM ' . $table_name);

		if ( $id === false ) {
			$this->Conn->Query('INSERT INTO ' . $table_name . ' (lastid) VALUES (2)');
			$id = 2;
		}

		$this->Conn->Query('UNLOCK TABLES');

		return $id - 1;
	}

	/**
	 * Returns genealogical main prefix for sub-table prefix passes
	 * OR prefix, that has been found in REQUEST and some how is parent of passed sub-table prefix
	 *
	 * @param string $current_prefix
	 * @param bool $real_top if set to true will return real topmost prefix, regardless of its id is passed or not
	 * @return string
	 * @access public
	 */
	public function GetTopmostPrefix($current_prefix, $real_top = false)
	{
		// 1. get genealogical tree of $current_prefix
		$prefixes = Array ($current_prefix);
		while ($parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix')) {
			if ( !$this->prefixRegistred($parent_prefix) ) {
				// stop searching, when parent prefix is not registered
				break;
			}

			$current_prefix = $parent_prefix;
			array_unshift($prefixes, $current_prefix);
		}

		if ( $real_top ) {
			return $current_prefix;
		}

		// 2. find what if parent is passed
		$passed = explode(',', $this->GetVar('all_passed'));
		foreach ($prefixes as $a_prefix) {
			if ( in_array($a_prefix, $passed) ) {
				return $a_prefix;
			}
		}

		return $current_prefix;
	}

	/**
	 * Triggers email event of type Admin
	 *
	 * @param string $email_event_name
	 * @param int $to_user_id
	 * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text
	 * @return kEvent
	 * @access public
	 */
	public function EmailEventAdmin($email_event_name, $to_user_id = null, $send_params = Array ())
	{
		return $this->_emailEvent($email_event_name, EmailEvent::EVENT_TYPE_ADMIN, $to_user_id, $send_params);
	}

	/**
	 * Triggers email event of type User
	 *
	 * @param string $email_event_name
	 * @param int $to_user_id
	 * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text
	 * @return kEvent
	 * @access public
	 */
	public function EmailEventUser($email_event_name, $to_user_id = null, $send_params = Array ())
	{
		return $this->_emailEvent($email_event_name, EmailEvent::EVENT_TYPE_FRONTEND, $to_user_id, $send_params);
	}

	/**
	 * Triggers general email event
	 *
	 * @param string $email_event_name
	 * @param int $email_event_type (0 for User, 1 for Admin)
	 * @param int $to_user_id
	 * @param array $send_params associative array of direct send params,
	 *  possible keys: to_email, to_name, from_email, from_name, message, message_text
	 * @return kEvent
	 * @access protected
	 */
	protected function _emailEvent($email_event_name, $email_event_type, $to_user_id = null, $send_params = Array ())
	{
		$email = $this->makeClass('kEmail');
		/* @var $email kEmail */

		if ( !$email->findEvent($email_event_name, $email_event_type) ) {
			return false;
		}

		$email->setParams($send_params);

		return $email->send($to_user_id);
	}

	/**
	 * Allows to check if user in this session is logged in or not
	 *
	 * @return bool
	 * @access public
	 */
	public function LoggedIn()
	{
		// no session during expiration process
		return is_null($this->Session) ? false : $this->Session->LoggedIn();
	}

	/**
	 * Check current user permissions based on it's group permissions in specified category
	 *
	 * @param string $name permission name
	 * @param int $cat_id category id, current used if not specified
	 * @param int $type permission type {1 - system, 0 - per category}
	 * @return int
	 * @access public
	 */
	public function CheckPermission($name, $type = 1, $cat_id = null)
	{
		$perm_helper = $this->recallObject('PermissionsHelper');
		/* @var $perm_helper kPermissionsHelper */

		return $perm_helper->CheckPermission($name, $type, $cat_id);
	}

	/**
	 * Check current admin permissions based on it's group permissions in specified category
	 *
	 * @param string $name permission name
	 * @param int $cat_id category id, current used if not specified
	 * @param int $type permission type {1 - system, 0 - per category}
	 * @return int
	 * @access public
	 */
	public function CheckAdminPermission($name, $type = 1, $cat_id = null)
	{
		$perm_helper = $this->recallObject('PermissionsHelper');
		/* @var $perm_helper kPermissionsHelper */

		return $perm_helper->CheckAdminPermission($name, $type, $cat_id);
	}

	/**
	 * Set's any field of current visit
	 *
	 * @param string $field
	 * @param mixed $value
	 * @return void
	 * @access public
	 * @todo move to separate module
	 */
	public function setVisitField($field, $value)
	{
		if ( $this->isAdmin || !$this->ConfigValue('UseVisitorTracking') ) {
			// admin logins are not registered in visits list
			return;
		}

		$visit = $this->recallObject('visits', null, Array ('raise_warnings' => 0));
		/* @var $visit kDBItem */

		if ( $visit->isLoaded() ) {
			$visit->SetDBField($field, $value);
			$visit->Update();
		}
	}

	/**
	 * Allows to check if in-portal is installed
	 *
	 * @return bool
	 * @access public
	 */
	public function isInstalled()
	{
		return $this->InitDone && (count($this->ModuleInfo) > 0);
	}

	/**
	 * Allows to determine if module is installed & enabled
	 *
	 * @param string $module_name
	 * @return bool
	 * @access public
	 */
	public function isModuleEnabled($module_name)
	{
		return $this->findModule('Name', $module_name) !== false;
	}

	/**
	 * Returns Window ID of passed prefix main prefix (in edit mode)
	 *
	 * @param string $prefix
	 * @return int
	 * @access public
	 */
	public function GetTopmostWid($prefix)
	{
		$top_prefix = $this->GetTopmostPrefix($prefix);
		$mode = $this->GetVar($top_prefix . '_mode');

		return $mode != '' ? substr($mode, 1) : '';
	}

	/**
	 * Get temp table name
	 *
	 * @param string $table
	 * @param mixed $wid
	 * @return string
	 * @access public
	 */
	public function GetTempName($table, $wid = '')
	{
		return $this->GetTempTablePrefix($wid) . $table;
	}

	/**
	 * Builds temporary table prefix based on given window id
	 *
	 * @param string $wid
	 * @return string
	 * @access public
	 */
	public function GetTempTablePrefix($wid = '')
	{
		if ( preg_match('/prefix:(.*)/', $wid, $regs) ) {
			$wid = $this->GetTopmostWid($regs[1]);
		}

		return TABLE_PREFIX . 'ses_' . $this->GetSID() . ($wid ? '_' . $wid : '') . '_edit_';
	}

	/**
	 * Checks if given table is a temporary table
	 *
	 * @param string $table
	 * @return bool
	 * @access public
	 */
	public function IsTempTable($table)
	{
		static $cache = Array ();

		if ( !array_key_exists($table, $cache) ) {
			$cache[$table] = preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $table);
		}

		return (bool)$cache[$table];
	}

	/**
	 * Checks, that given prefix is in temp mode
	 *
	 * @param string $prefix
	 * @param string $special
	 * @return bool
	 * @access public
	 */
	public function IsTempMode($prefix, $special = '')
	{
		$top_prefix = $this->GetTopmostPrefix($prefix);

		$var_names = Array (
			$top_prefix,
			rtrim($top_prefix . '_' . $special, '_'), // from post
			rtrim($top_prefix . '.' . $special, '.'), // assembled locally
		);

		$var_names = array_unique($var_names);

		$temp_mode = false;
		foreach ($var_names as $var_name) {
			$value = $this->GetVar($var_name . '_mode');

			if ( $value && (substr($value, 0, 1) == 't') ) {
				$temp_mode = true;
				break;
			}
		}

		return $temp_mode;
	}

	/**
	 * Return live table name based on temp table name
	 *
	 * @param string $temp_table
	 * @return string
	 */
	public function GetLiveName($temp_table)
	{
		if ( preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $temp_table, $rets) ) {
			// cut wid from table end if any
			return $rets[2];
		}
		else {
			return $temp_table;
		}
	}

	/**
	 * Stops processing of user request and displays given message
	 *
	 * @param string $message
	 * @access public
	 */
	public function ApplicationDie($message = '')
	{
		$message = ob_get_clean() . $message;

		if ( $this->isDebugMode() ) {
			$message .= $this->Debugger->printReport(true);
		}

		echo $this->UseOutputCompression() ? gzencode($message, DBG_COMPRESSION_LEVEL) : $message;
		exit;
	}

	/**
	 * Returns comma-separated list of groups from given user
	 *
	 * @param int $user_id
	 * @return string
	 */
	public function getUserGroups($user_id)
	{
		switch ($user_id) {
			case USER_ROOT:
				$user_groups = $this->ConfigValue('User_LoggedInGroup');
				break;

			case USER_GUEST:
				$user_groups = $this->ConfigValue('User_LoggedInGroup') . ',' . $this->ConfigValue('User_GuestGroup');
				break;

			default:
				$sql = 'SELECT GroupId
						FROM ' . TABLE_PREFIX . 'UserGroupRelations
						WHERE PortalUserId = ' . (int)$user_id;
				$res = $this->Conn->GetCol($sql);

				$user_groups = Array ($this->ConfigValue('User_LoggedInGroup'));
				if ( $res ) {
					$user_groups = array_merge($user_groups, $res);
				}

				$user_groups = implode(',', $user_groups);
		}

		return $user_groups;
	}


	/**
	 * Allows to detect if page is browsed by spider (293 scheduled_tasks supported)
	 *
	 * @return bool
	 * @access public
	 */
	/*public function IsSpider()
	{
		static $is_spider = null;

		if ( !isset($is_spider) ) {
			$user_agent = trim($_SERVER['HTTP_USER_AGENT']);
			$robots = file(FULL_PATH . '/core/robots_list.txt');
			foreach ($robots as $robot_info) {
				$robot_info = explode("\t", $robot_info, 3);
				if ( $user_agent == trim($robot_info[2]) ) {
					$is_spider = true;
					break;
				}
			}
		}

		return $is_spider;
	}*/

	/**
	 * Allows to detect table's presence in database
	 *
	 * @param string $table_name
	 * @param bool $force
	 * @return bool
	 * @access public
	 */
	public function TableFound($table_name, $force = false)
	{
		return $this->Conn->TableFound($table_name, $force);
	}

	/**
	 * Returns counter value
	 *
	 * @param string $name counter name
	 * @param Array $params counter parameters
	 * @param string $query_name specify query name directly (don't generate from parameters)
	 * @param bool $multiple_results
	 * @return mixed
	 * @access public
	 */
	public function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false)
	{
		$count_helper = $this->recallObject('CountHelper');
		/* @var $count_helper kCountHelper */

		return $count_helper->getCounter($name, $params, $query_name, $multiple_results);
	}

	/**
	 * Resets counter, which are affected by one of specified tables
	 *
	 * @param string $tables comma separated tables list used in counting sqls
	 * @return void
	 * @access public
	 */
	public function resetCounters($tables)
	{
		if ( kUtil::constOn('IS_INSTALL') ) {
			return;
		}

		$count_helper = $this->recallObject('CountHelper');
		/* @var $count_helper kCountHelper */

		$count_helper->resetCounters($tables);
	}

	/**
	 * Sends XML header + optionally displays xml heading
	 *
	 * @param string|bool $xml_version
	 * @return string
	 * @access public
	 * @author Alex
	 */
	public function XMLHeader($xml_version = false)
	{
		$lang = $this->recallObject('lang.current');
		/* @var $lang LanguagesItem */

		$this->setContentType('text/xml');

		return $xml_version ? '<?xml version="' . $xml_version . '" encoding="' . $lang->GetDBField('Charset') . '"?>' : '';
	}

	/**
	 * Returns category tree
	 *
	 * @param int $category_id
	 * @return Array
	 * @access public
	 */
	public function getTreeIndex($category_id)
	{
		$tree_index = $this->getCategoryCache($category_id, 'category_tree');

		if ( $tree_index ) {
			$ret = Array ();
			list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index);

			return $ret;
		}

		return false;
	}

	/**
	 * Base category of all categories
	 * Usually replaced category, with ID = 0 in category-related operations.
	 *
	 * @return int
	 * @access public
	 */
	public function getBaseCategory()
	{
		// same, what $this->findModule('Name', 'Core', 'RootCat') does
		// don't cache while IS_INSTALL, because of kInstallToolkit::createModuleCategory and upgrade

		return $this->ModuleInfo['Core']['RootCat'];
	}

	/**
	 * Deletes all data, that was cached during unit config parsing (excluding unit config locations)
	 *
	 * @param Array $config_variables
	 * @access public
	 */
	public function DeleteUnitCache($config_variables = null)
	{
		$this->cacheManager->DeleteUnitCache($config_variables);
	}

	/**
	 * Deletes cached section tree, used during permission checking and admin console tree display
	 *
	 * @return void
	 * @access public
	 */
	public function DeleteSectionCache()
	{
		$this->cacheManager->DeleteSectionCache();
	}

	/**
	 * Sets data from cache to object
	 *
	 * @param Array $data
	 * @access public
	 */
	public function setFromCache(&$data)
	{
		$this->Factory->setFromCache($data);
		$this->UnitConfigReader->setFromCache($data);
		$this->EventManager->setFromCache($data);

		$this->ReplacementTemplates = $data['Application.ReplacementTemplates'];
		$this->RewriteListeners = $data['Application.RewriteListeners'];
		$this->ModuleInfo = $data['Application.ModuleInfo'];
	}

	/**
	 * Gets object data for caching
	 * The following caches should be reset based on admin interaction (adjusting config, enabling modules etc)
	 *
	 * @access public
	 * @return Array
	 */
	public function getToCache()
	{
		return array_merge(
			$this->Factory->getToCache(),
			$this->UnitConfigReader->getToCache(),
			$this->EventManager->getToCache(),
			Array (
				'Application.ReplacementTemplates' => $this->ReplacementTemplates,
				'Application.RewriteListeners' => $this->RewriteListeners,
				'Application.ModuleInfo' => $this->ModuleInfo,
			)
		);
	}

	public function delayUnitProcessing($method, $params)
	{
		$this->cacheManager->delayUnitProcessing($method, $params);
	}

	/**
	 * Returns current maintenance mode state
	 *
	 * @param bool $check_ips
	 * @return int
	 * @access public
	 */
	public function getMaintenanceMode($check_ips = true)
	{
		$exception_ips = defined('MAINTENANCE_MODE_IPS') ? MAINTENANCE_MODE_IPS : '';
		$setting_name = $this->isAdmin ? 'MAINTENANCE_MODE_ADMIN' : 'MAINTENANCE_MODE_FRONT';

		if ( defined($setting_name) && constant($setting_name) > MaintenanceMode::NONE ) {
			$exception_ip = $check_ips ? kUtil::ipMatch($exception_ips) : false;

			if ( !$exception_ip ) {
				return constant($setting_name);
			}
		}

		return MaintenanceMode::NONE;
	}

	/**
	 * Sets content type of the page
	 *
	 * @param string $content_type
	 * @param bool $include_charset
	 * @return void
	 * @access public
	 */
	public function setContentType($content_type = 'text/html', $include_charset = null)
	{
		static $aleady_set = false;

		if ( $aleady_set ) {
			return;
		}

		$header = 'Content-type: ' . $content_type;

		if ( !isset($include_charset) ) {
			$include_charset = $content_type = 'text/html' || $content_type = 'text/xml';
		}

		if ( $include_charset ) {
			$language = $this->recallObject('lang.current');
			/* @var $language LanguagesItem */

			$header .= '; charset=' . $language->GetDBField('Charset');
		}

		$aleady_set = true;
		header($header);
	}
}