<?php
/**
* @version	$Id: application.php 13782 2010-06-15 20:41:16Z 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.
*/

/**
* 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 incapsulates 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 guranteed by NOT calling the class constuctor directly, but rather calling kApplication::Instance() method,
* which returns an instance of the application. The method gurantees that it will return exactly the same instance for any call.<br>
* See singleton pattern by GOF.
* @package kernel4
*/

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

class kApplication {

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

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

	/**
	* Holds parser output buffer
	* @access private
	* @var string
	*/
	var $HTML;

	/**
	 * Prevents request from beeing proceeded twice in case if application init is called mere then one time
	 *
	 * @var bool
	 * @todo This is not good anyway (by Alex)
	 */
	var $RequestProcessed = false;

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

	/**
	 * All ConfigurationValues table content (hash) here
	 *
	 * @var Array
	 * @access private
	 */
	var $ConfigHash = Array();

	/**
	 * Ids of config variables used in current run (for caching)
	 *
	 * @var Array
	 * @access private
	 */
	var $ConfigCacheIds = array();

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

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

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

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

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

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

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


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

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

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

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

	/**
	 * Ref to itself, needed because everybody used to write $this->Application, even inside kApplication
	 *
	 * @var kApplication
	 */
	var $Application = null;

	/**
	 * Ref for TemplatesChache
	 *
	 * @var TemplatesCache
	 */
	var $TemplatesCache = null;

	/**
	 * Physical template name mapping to their template names based on structure
	 *
	 * @var Array
	 */
	var $structureTemplateMapping = Array ();

	var $CompilationCache = array(); //used when compiling templates
	var $CachedProcessors = array(); //used when running compiled templates

	var $LambdaElements = 1; // for autonumbering unnamed RenderElements [any better place for this prop? KT]

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

	/**
	 * Object of memory caching class
	 *
	 * @var kCache
	 */
	var $memoryCache = null;

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

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

 	/**
	 * Instance of site domain object
	 *
	 * @var kDBItem
	 */
	var $siteDomain = null;

	/**
	* 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 guranteed 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 descendand 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.
 	* @static
 	* @access public
	* @return kApplication
	*/
	function &Instance()
	{
		static $instance = false;

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

		return $instance;
	}

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

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

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

		if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) {
			$this->Debugger->appendMemoryUsage('Application before Init:');
		}
		if (!$this->isDebugMode() && !constOn('DBG_ZEND_PRESENT')) {
			error_reporting(0);
			ini_set('display_errors', 0);
		}
		if (!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;
			}
		}

		$this->Conn = new kDBConnection(SQL_TYPE, Array(&$this, 'handleSQLError') );
		$this->Conn->debugMode = $this->isDebugMode();
		$this->Conn->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB);

		$this->Factory = new kFactory();
		$this->registerDefaultClasses();
		$this->Phrases = new PhrasesCache();
		$this->memoryCache =& $this->Factory->makeClass('Cache');
		$this->EventManager =& $this->Factory->makeClass('EventManager');
		$this->Factory->Storage['EventManager'] =& $this->EventManager;
		$this->RegisterDefaultBuildEvents();
		$this->SetDefaultConstants();

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

		$this->UnitConfigReader =& $this->recallObject('kUnitConfigReader');
		$this->UnitConfigReader->scanModules(MODULES_PATH);
		$this->registerModuleConstants();

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

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

		$this->HttpQuery =& $this->recallObject('HTTPQuery');

		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');
		}

		if (!$this->RecallVar('UserGroups')) {
			$user_groups = trim($this->Session->GetField('GroupList'), ',');
			if (!$user_groups) {
				$user_groups = $this->ConfigValue('User_GuestGroup');
			}

			$this->Session->SetField('GroupList', $user_groups);
			$this->StoreVar('UserGroups', $user_groups, true); // true for optional
		}

		$this->LoadStructureTemplateMapping();
		$this->HttpQuery->AfterInit();

		$this->Session->ValidateExpired();

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

		$this->LoadCache();
		$this->InitConfig();

		$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();

		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) );
		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;
	}

	/**
	 * Returns module information. Searches module by requested field
	 *
	 * @param string $field
	 * @param mixed $value
	 * @param string field value to returns, if not specified, then return all fields
	 * @param string field to return
	 * @return Array
	 */
	function findModule($field, $value, $return_field = null)
	{
		$found = false;
		foreach ($this->ModuleInfo as $module_name => $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;
	}

	function refreshModuleInfo()
	{
		if (defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules')) {
			$this->registerModuleConstants();
			return false;
		}

		$modules_helper =& $this->recallObject('ModulesHelper');
		/* @var $modules_helper kModulesHelper */

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

		$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
	 *
	 */
	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
	 *
	 */
	function VerifyThemeId()
	{
		if ($this->isAdmin) {
			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');
		}
		safeDefine('THEMES_PATH', $path);

		/*$theme_id = $this->GetVar('m_theme');
		if (!$theme_id) {
			$theme_id =  $this->GetDefaultThemeId();
			if (!$theme_id) {
				if (!defined('IS_INSTALL')) $this->ApplicationDie('No Primary Theme Selected');
			}
		}
		$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');
		if (!$theme->IsLoaded() || !$theme->GetDBField('Enabled')) {
		if (!defined('IS_INSTALL')) $this->ApplicationDie('Unknown or disabled theme');
		}
		safeDefine('THEMES_PATH', '/themes/'.$theme->GetDBField('Name'));*/
	}

	function GetFrontThemePath($force=0)
	{
		static $path=null;
		if (!$force && isset($path)) return $path;

		$theme_id = $this->GetVar('m_theme');
		if (!$theme_id) {
//			$theme_id =  $this->GetDefaultThemeId(1); //1 to force front-end mode!
			$theme_id = 'default';
		}
		$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');
		if (!$theme->IsLoaded() || !$theme->GetDBField('Enabled')) {
			return false;
		}
		$path = '/themes/'.$theme->GetDBField('Name');
		return $path;
	}

	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;
	}

	function GetDefaultThemeId($force_front=0)
	{
		static $theme_id = 0;

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

		if (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
	 */
	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
	 */
	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 ItemController, GridController and LoginController
	*
	* Called automatically while initializing Application
	* @access private
	* @return void
	*/
	function RegisterDefaultClasses()
	{
		$this->registerClass('kTempTablesHandler', KERNEL_PATH . '/utility/temp_handler.php');
		$this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php', 'EventManager');
		$this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php');

		$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', 'Cache', 'Params');
		$this->registerClass('kHTTPQuery', KERNEL_PATH . '/utility/http_query.php', 'HTTPQuery', 'Params');

		$this->registerClass('kHelper', KERNEL_PATH . '/kbase.php');
		$this->registerClass('kMultipleFilter', KERNEL_PATH . '/utility/filters.php');

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

		$this->registerClass('kTagProcessor', KERNEL_PATH . '/processors/tag_processor.php');
		$this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php','m_TagProcessor', 'kTagProcessor');

		$this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php');
		$this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php');
		$this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php');
		$this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php', null, 'kTagProcessor');
		$this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php');
		$this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php');
		$this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.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', null, Array ('kHelper', 'kDBTagProcessor'));

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

		if (file_exists(MODULES_PATH . '/in-commerce/units/currencies/currency_rates.php')) {
			$this->registerClass('kCurrencyRates', MODULES_PATH . '/in-commerce/units/currencies/currency_rates.php');
		}

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

	function RegisterDefaultBuildEvents()
	{
		$event_manager =& $this->recallObject('EventManager');
		$event_manager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild');
	}

	/**
	 * Returns cached category informaton 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
	 */
	function getCategoryCache($category_id, $name)
	{
		$serial_name = '[%CIDSerial:' . $category_id . '%]';
		$cache_key = $name . $serial_name;
		$ret = $this->getCache($cache_key);

		if ($ret === false) {
			if (!$category_id) {
				// don't query database for "Home" category (ID = 0), because it doesn't exist in database
				return false;
			}

			// this allows to save 2 sql queries for each category
			$this->Conn->nextQueryCachable = true;
			$sql = 'SELECT NamedParentPath, CachedTemplate, TreeLeft, TreeRight
					FROM ' . TABLE_PREFIX . 'Category
					WHERE CategoryId = ' . (int)$category_id;
			$category_data = $this->Conn->GetRow($sql);

			if ($category_data !== false) {
				// only direct links to category pages work (symlinks, container pages and so on won't work)
				$this->setCache('filenames' . $serial_name,				$category_data['NamedParentPath']);
				$this->setCache('category_designs' . $serial_name,		ltrim($category_data['CachedTemplate'], '/'));
				$this->setCache('category_tree' . $serial_name,			$category_data['TreeLeft'] . ';' . $category_data['TreeRight']);
			}
		}

		return $this->getCache($cache_key);
	}

	/**
	 * Returns item's filename that corresponds id passed. If possible, then get it from cache
	 *
	 * @param string $prefix
	 * @param int $id
	 * @param int $category_id
	 * @return string
	 */
	function getFilename($prefix, $id, $category_id = null)
	{
		if ($prefix == 'c') {
			trigger_error('Method "<strong>' . __FUNCTION__ . '</strong>" no longer work with "<strong>c</strong>" prefix. Please use "<strong>getCategoryCache</strong>" method instead.', E_USER_ERROR);
			return false;
		}

		$category_id = isset($category_id) ? $category_id : $this->GetVar('m_cat_id');

		$cache_key = 'filenames[%' . $this->incrementCacheSerial($prefix, $id, false) . '%]:' . (int)$category_id;
		$filename = $this->getCache($cache_key);

		if ($filename === false) {
			$this->Conn->nextQueryCachable = true;
			$sql = 'SELECT ResourceId
					FROM ' . $this->getUnitOption($prefix, 'TableName') . '
					WHERE ' . $this->getUnitOption($prefix, 'IDField') . ' = ' . $this->Conn->qstr($id);
			$resource_id = $this->Conn->GetOne($sql);

			$this->Conn->nextQueryCachable = true;
			$sql = 'SELECT Filename
					FROM ' . TABLE_PREFIX . 'CategoryItems
					WHERE (ItemResourceId = ' . $resource_id . ') AND (CategoryId = ' . (int)$category_id . ')';
			$filename = $this->Conn->GetOne($sql);

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

		return $filename;
	}

	/**
	 * Returns caching type (none, memory, temporary)
	 *
	 * @return int
	 */
	function isCachingType($caching_type)
	{
		return $this->memoryCache->getCachingType() == $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
	 */
	function incrementCacheSerial($prefix, $id = null, $increment = true)
	{
		$pascal_case_prefix = implode('', array_map('ucfirst', explode('-', $prefix)));
		$serial_name = $pascal_case_prefix . (isset($id) ? 'IDSerial:' . $id : 'Serial');

		if ($increment) {
			if ($this->isCachingType(CACHING_TYPE_MEMORY)) {
				if (defined('DEBUG_MODE') && DEBUG_MODE && $this->isDebugMode()) {
					$this->Application->Debugger->appendHTML('Incrementing serial: <strong>' . $serial_name . '</strong>.');
				}

				$this->setCache($serial_name, (int)$this->getCache($serial_name) + 1);
			}

			if (!defined('IS_INSTALL') || !IS_INSTALL) {
				// delete cached mod-rewrite urls related to given prefix and id
				$delete_clause = isset($id) ? $prefix . ':' . $id : $prefix;

				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls
						WHERE Prefixes LIKE ' . $this->Conn->qstr('%|' . $delete_clause . '|%');
				$this->Conn->Query($sql);
			}
		}

		return $serial_name;
	}

	/**
	 * Adds new value to cache $cache_name and identified by key $key
	 *
	 * @param int $key key name to add to cache
	 * @param mixed $value value of chached record
	 * @param int $expiration when value expires (0 - doesn't expire)
	 */
	function setCache($key, $value, $expiration = 0)
	{
		return $this->memoryCache->setCache($key, $value, $expiration);
	}

	/**
	 * Sets value to database cache
	 *
	 * @param string $name
	 * @param mixed $value
	 * @param int $expiration
	 */
	function setDBCache($name, &$value, $expiration = false)
	{
		if ((int)$expiration <= 0) {
			$expiration = -1;
		}

		$fields_hash = Array (
			'VarName' => $name,
			'Data' => &$value,
			'Cached' => adodb_mktime(),
			'LifeTime' => (int)$expiration,
		);

		$this->Conn->nextQueryCachable = true;
		$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache', 'REPLACE');
	}

	/**
	 * 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
	 * @return mixed
	 */
	function getCache($key, $store_locally = true)
	{
		return $this->memoryCache->getCache($key, $store_locally);
	}

	/**
	 * Returns value from database cache
	 *
	 * @param string $name key name
	 * @return mixed
	 */
	function getDBCache($name)
	{
		$this->Conn->nextQueryCachable = true;

		$sql = 'SELECT Data, Cached, LifeTime
				FROM ' . TABLE_PREFIX . 'Cache
				WHERE VarName = ' . $this->Conn->qstr($name);
		$data = $this->Conn->GetRow($sql);

		if ($data) {
			$lifetime = (int)$data['LifeTime']; // in seconds
			if (($lifetime > 0) && ($data['Cached'] + $lifetime < adodb_mktime())) {
				// delete expired
				$this->Conn->nextQueryCachable = true;

				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache
						WHERE VarName = ' . $this->Conn->qstr($name);
				$this->Conn->Query($sql);

				return false;
			}

			return $data['Data'];
		}

		return false;
	}

	/**
	 * Deletes key from cache
	 *
	 * @param string $key
	 */
	function deleteCache($key)
	{
		$this->memoryCache->delete($key);
	}

	/**
	 * Deletes key from database cache
	 *
	 * @param string $name
	 */
	function deleteDBCache($name)
	{
		$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache
				WHERE VarName = ' . $this->Conn->qstr($name);
		$this->Conn->Query($sql);
	}

	/**
	* Defines default constants if it's not defined before - in config.php
	*
	* @access private
	*/
	function SetDefaultConstants() // it's defined in startup.php - can be removed??
	{
		safeDefine('SERVER_NAME', $_SERVER['HTTP_HOST']);
	}

	/**
	 * Registers each module specific constants if any found
	 *
	 */
	function registerModuleConstants()
	{
		if (file_exists(KERNEL_PATH.'/constants.php')) {
			k4_include_once(KERNEL_PATH.'/constants.php');
		}

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

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

			if (file_exists($contants_file)) {
				k4_include_once($contants_file);
			}
		}

		return true;
	}

	function ProcessRequest()
	{
		$event_manager =& $this->recallObject('EventManager');
		/* @var $event_manager kEventManager */

		if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_SHOW_HTTPQUERY')) {
			$this->Debugger->appendHTML('HTTPQuery:');
			$this->Debugger->dumpVars($this->HttpQuery->_Params);
		}

		$event_manager->ProcessRequest();
		$event_manager->RunRegularEvents(reBEFORE);
		$this->RequestProcessed =  true;
	}

	/**
	* 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.
	* @access public
	* @return void
	*/
	function Run()
	{
		if (defined('DEBUG_MODE') && $this->isDebugMode() && 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
				define('DBG_SKIP_REPORTING', 1);
			}
		}
		elseif ($this->GetVar('admin')) {
			// viewing front-end through admin's frame
			$admin_session =& $this->Application->recallObject('Session.admin');
			$user = (int)$admin_session->RecallVar('user_id'); // in case, when no valid admin session found
			$perm_helper =& $this->recallObject('PermissionsHelper');
			/* @var $perm_helper kPermissionsHelper */

			if ($perm_helper->CheckUserPermission($user, 'CATEGORY.MODIFY', 0, $this->ModuleInfo['Core']['RootCat'])) {
				// user can edit cms blocks
				$editing_mode = $this->GetVar('editing_mode');
				define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE);
			}
		}

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

		if (!$this->RequestProcessed) $this->ProcessRequest();

		$this->InitParser();
		$t = $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() && constOn('DBG_PROFILE_MEMORY')) {
			$this->Debugger->appendMemoryUsage('Application before Parsing:');
		}

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

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

	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.
	* @access public
	* @return void
	*/
	function Done()
	{
		$this->HandleEvent( new kEvent('adm:OnBeforeShutdown') );

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

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

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

			if (constOn('DBG_CACHE')) {
				$this->memoryCache->printStatistics();
			}

			$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;
		}

		if ($this->UseOutputCompression()) {
			$compression_level = $this->ConfigValue('OutputCompressionLevel');
			if ($compression_level < 0 || $compression_level > 9) {
				$compression_level = 7;
			}

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

		$this->UpdateCache();
		flush();

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

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

	/**
	 * Stores script execution statistics to database
	 *
	 */
	function _storeStatistics()
	{
		global $start;

		$script_time = getmicrotime() - $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
	 */
	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;
		}
	}

	function logSlowQuery($slow_sql, $time)
	{
		$query_crc = 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 string
	 */
	function UseOutputCompression()
	{
		if (constOn('IS_INSTALL') || constOn('DBG_ZEND_PRESENT') || 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)
	* @access public
	* @return longint
	*/
	function GetSID()
	{
		$session =& $this->recallObject('Session');
		return $session->GetID();
	}

	function DestroySession()
	{
		$session =& $this->recallObject('Session');
		$session->Destroy();
	}

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

	/**
	* Returns ALL variables passed to the script as GET/POST/COOKIE
	*
	* @access public
	* @return array
	*/
	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>
	*
	* This method is formerly known as $this->Session->SetProperty.
	* @param string $var Variable name to set
	* @param mixed $val Variable value
	* @access public
	* @return void
	*/
	function SetVar($var,$val)
	{
		return $this->HttpQuery->Set($var, $val);
	}

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

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

	function RemovePersistentVar($var)
	{
		return $this->Session->RemovePersistentVar($var);
	}

	/**
	 * Restores Session variable to it's db version
	 *
	 * @param string $var
	 */
	function RestoreVar($var)
	{
		return $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.
	*
	* @see SimpleSession
	* @access public
	* @param string $var Variable name
	* @param mixed $default Default value to return if no $var variable found in session
	* @return mixed
	*/
	function RecallVar($var,$default=false)
	{
		return $this->Session->RecallVar($var,$default);
	}

	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.
	* @see RecallVar
	* @access public
	* @param string $var Variable name
	* @param mixed $val Variable value
	*/
	function StoreVar($var, $val, $optional = false)
	{
		$session =& $this->recallObject('Session');
		$this->Session->StoreVar($var, $val, $optional);
	}

	function StorePersistentVar($var, $val, $optional = false)
	{
		$this->Session->StorePersistentVar($var, $val, $optional);
	}

	function StoreVarDefault($var, $val, $optional=false)
	{
		$session =& $this->recallObject('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
	* @access public
	* @param string $var HTTP Query (GPC) variable name
	* @param mixed $ses_var Session variable name
	* @param mixed $default Default variable value
	*/
	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
	*
	* @see LinkVar
	* @access public
	* @param string $var HTTP Query (GPC) variable name
	* @param mixed $ses_var Session variable name
	* @param mixed $default Default variable value
	* @return mixed
	*/
	function GetLinkedVar($var, $ses_var = null, $default = '')
	{
		$this->LinkVar($var, $ses_var, $default);
		return $this->GetVar($var);
	}

	function AddBlock($name, $tpl)
	{
		$this->cache[$name] = $tpl;
	}

	function ProcessParsedTag($prefix, $tag, $params)
	{
		$processor = $this->Parser->GetProcessor($prefix);

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

	/**
	* Return ADODB Connection object
	*
	* Returns ADODB Connection object already connected to the project database, configurable in config.php
	* @access public
	* @return kDBConnection
	*/
	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 Array $pass_params Forces to pass current parser params to this block/template. Use with cauntion, because you can accidently pass "block_no_data" parameter.
	 * @param bool $as_template
	 * @return string
	 */
	function ParseBlock($params, $pass_params = 0, $as_template = false)
	{
		if (substr($params['name'], 0, 5) == 'html:') {
			return substr($params['name'], 6);
		}

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

	/**
	 * Checks, that we have given block defined
	 *
	 * @param string $name
	 * @return bool
	 */
	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
	 */
	function IncludeTemplate($params)
	{
		return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0);
	}

	/**
	 * Returns index file, that could be passed as parameter to method, as parameter to tag and as constant or not passed at all
	 *
	 * @param string $prefix
	 * @param string $index_file
	 * @param Array $params
	 * @return string
	 */
	function getIndexFile($prefix, $index_file, &$params)
	{
		if (isset($params['index_file'])) {
			$index_file = $params['index_file'];
			unset($params['index_file']);
			return $index_file;
		}

		if (isset($index_file)) {
			return $index_file;
		}

		if (defined('INDEX_FILE')) {
			return INDEX_FILE;
		}

		$cut_prefix = trim(BASE_PATH, '/').'/'.trim($prefix, '/');
		return trim(preg_replace('/'.preg_quote($cut_prefix, '/').'(.*)/', '\\1', $_SERVER['PHP_SELF']), '/');
	}

	/**
	* Return href for template
	*
	* @access public
	* @param string $t Template path
	* @var string $prefix index.php prefix - could be blank, 'admin'
	*/
	function HREF($t, $prefix = '', $params = null, $index_file = null)
	{
		static $theme_id = null;

		if (!isset($theme_id)) {
			$theme_id = $this->GetVar('m_theme');
		}

		if (!$t) {
			// when template not specified, use current
			$t = $this->GetVar('t');
		}

		$t = preg_replace('/^Content\//i', '', $t);

		if (substr($t, -4) == '.tpl') {
			// cut template extension (deprecated link format)
			$t = substr($t, 0, strlen($t) - 4);
		}

		if (substr($t, 0, 3) == 'id:') {
			// link to structure page using it's id
			$params['m_cat_id'] = substr($t, 3);
			$t = $this->structureTemplateMapping[$t];
		}

		if (array_key_exists('use_section', $params)) {
			$use_section = $params['use_section'];
			unset($params['use_section']);
		}

		if (isset($use_section) && $use_section && array_key_exists($t . ':' . $theme_id, $this->structureTemplateMapping)) {
			// structure template corresponding to given physical template
			$t = $this->structureTemplateMapping[$t . ':' . $theme_id];
			unset($params['use_section']);
		}

		if (preg_match('/external:(.*)/', $t, $rets)) {
			// external url
			return $rets[1];
		}

		if ($this->isAdmin && $prefix == '') $prefix = ADMIN_DIRECTORY;
		if ($this->isAdmin && $prefix == '_FRONT_END_') $prefix = '';

		$index_file = $this->getIndexFile($prefix, $index_file, $params);

		if (isset($params['_auto_prefix_'])) {
			unset($params['_auto_prefix_']); // this is parser-related param, do not need to pass it here
		}

		$ssl = isset($params['__SSL__']) ? $params['__SSL__'] : null;
		if ($ssl !== null) {
			$session =& $this->recallObject('Session');
			/* @var $session Session */

			$target_url = rtrim($this->BaseURL('', $ssl, false), '/');
			$cookie_url = trim($session->CookieDomain . $session->CookiePath, '/.');

			// set session to GET_ONLY, to pass sid only if sid is REAL AND session is set
			if (!preg_match('#' . preg_quote($cookie_url) . '#', $target_url) && $session->SessionSet) {
				// when SSL<->NON-SSL redirect to different domain pass SID in url
 				$session->SetMode(smGET_ONLY);
 			}
		}

		if (isset($params['opener']) && $params['opener'] == 'u') {
			$wid = $this->Application->GetVar('m_wid');
			$stack_name = rtrim('opener_stack_'.$wid, '_');
			$opener_stack = $this->RecallVar($stack_name);

			if ($opener_stack && $opener_stack != serialize(Array())) {
				$opener_stack = unserialize($opener_stack);
				list($index_file, $env) = explode('|', $opener_stack[count($opener_stack) - 1]);
				$ret = $this->BaseURL($prefix, $ssl).$index_file.'?'.ENV_VAR_NAME.'='.$env;
				if ( getArrayValue($params,'escape') ) $ret = addslashes($ret);

				if (isset($params['m_opener']) && $params['m_opener'] == 'u') {
					array_pop($opener_stack);
					if (!$opener_stack) {
						$this->RemoveVar($stack_name);
						// remove popups last templates, because popup is closing now
						$this->RemoveVar('last_template_'.$wid);
						$this->RemoveVar('last_template_popup_'.$wid);

						// don't save popups last templates again :)
						$this->SetVar('skip_last_template', 1);
					}
					else {
						$this->StoreVar($stack_name, serialize($opener_stack));
					}

					/*// store window relations
					$window_relations = $this->Application->RecallVar('window_relations');
					$window_relations = $window_relations ? unserialize($window_relations) : Array ();
					if (array_key_exists($wid, $window_relations)) {
						unset($window_relations[$wid]);
						$this->Application->StoreVar('window_relations', serialize($window_relations));
					}*/
				}
				return $ret;
			}
			else {
				//define('DBG_REDIRECT', 1);
				$t = $this->GetVar('t');
			}
		}

		$pass = isset($params['pass']) ? $params['pass'] : '';

		// pass events with url
		$pass_events = false;
		if( isset($params['pass_events']) )
		{
			$pass_events = $params['pass_events'];
			unset($params['pass_events']);
		}

		$map_link = '';
		if( isset($params['anchor']) )
		{
			$map_link = '#'.$params['anchor'];
			unset($params['anchor']);
		}

		if ( isset($params['no_amp']) )
		{
			$params['__URLENCODE__'] = $params['no_amp'];
			unset($params['no_amp']);
		}

		$no_rewrite = false;
		if( isset($params['__NO_REWRITE__']) )
		{
			$no_rewrite = true;
			unset($params['__NO_REWRITE__']);
		}

		$force_rewrite = false;
		if( isset($params['__MOD_REWRITE__']) )
		{
			$force_rewrite = true;
			unset($params['__MOD_REWRITE__']);
		}

		$force_no_sid = false;
		if( isset($params['__NO_SID__']) )
		{
			$force_no_sid = true;
			unset($params['__NO_SID__']);
		}

		// append pass through variables to each link to be build
		// $params = array_merge_recursive2($this->getPassThroughVariables($params), $params);
		$params = array_merge($this->getPassThroughVariables($params), $params);

		if ($force_rewrite || ($this->RewriteURLs($ssl) && !$no_rewrite)) {
			static $rewrite_listeners_done = false;

			if (!$rewrite_listeners_done) {
				$mod_rewrite_helper =& $this->recallObject('ModRewriteHelper');
				/* @var $mod_rewrite_helper kModRewriteHelper */

				$mod_rewrite_helper->initRewriteListeners();

				$rewrite_listeners_done = true;
			}

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

			if ($session->NeedQueryString() && !$force_no_sid) {
				$params['sid'] = $this->GetSID();
			}

			$url = $this->BuildEnv_NEW($t, $params, $pass, $pass_events);
			$ret = $this->BaseURL($prefix, $ssl).$url.$map_link;
		}
		else {
			unset($params['pass_category']); // we don't need to pass it when mod_rewrite is off
			$env = $this->BuildEnv($t, $params, $pass, $pass_events);
			$ret = $this->BaseURL($prefix, $ssl).$index_file.'?'.$env.$map_link;
		}

		return $ret;
	}

	/**
	 * Returns variables with values that should be passed throught with this link + variable list
	 *
	 * @param Array $params
	 * @return Array
	 */
	function getPassThroughVariables(&$params)
	{
		static $cached_pass_through = null;

		if (isset($params['no_pass_through']) && $params['no_pass_through']) {
			unset($params['no_pass_through']);
			return Array();
		}

		// because pass through is not changed during script run, then we can cache it
		if (is_null($cached_pass_through)) {

			$cached_pass_through = Array();
			$pass_through = $this->Application->GetVar('pass_through');

			if ($pass_through) {
				// names of variables to pass to each link
				$cached_pass_through['pass_through'] = $pass_through;
				$pass_through = explode(',', $pass_through);
				foreach ($pass_through as $pass_through_var) {
					$cached_pass_through[$pass_through_var] = $this->Application->GetVar($pass_through_var);
				}
			}

		}

		return $cached_pass_through;
	}


	/**
	 * Returns sorted array of passed prefixes (to build url from)
	 *
	 * @param string $pass
	 * @return Array
	 */
	function getPassInfo($pass = 'all')
	{
		if (!$pass) $pass = 'all';
		$pass = trim(
				preg_replace(
					'/(?<=,|\\A)all(?=,|\\z)/',
					trim($this->GetVar('passed'), ','),
					trim($pass, ',')
				),
		 ',');

		if (!$pass) {
			return Array();
		}

		$pass_info = array_unique( explode(',', $pass) ); // array( prefix[.special], prefix[.special] ...

		// we need to keep that sorting despite the sorting below, because this sorts prefixes with same priority by name
		sort($pass_info, SORT_STRING); // to be prefix1,prefix1.special1,prefix1.special2,prefix3.specialX

		foreach ($pass_info as $prefix) {
			list ($prefix_only, ) = explode('.', $prefix, 2);
			$sorted[$prefix] = $this->getUnitOption($prefix_only, 'RewritePriority', 0);
		}

		asort($sorted, SORT_NUMERIC);
		$pass_info = array_keys($sorted);

		// ensure that "m" prefix is at the beginning
		$main_index = array_search('m', $pass_info);
		if ($main_index !== false) {
			unset($pass_info[$main_index]);
			array_unshift($pass_info, 'm');
		}
		return $pass_info;
	}

	function BuildEnv_NEW($t, $params, $pass='all', $pass_events = false)
	{
		if ($this->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin'])) {
			$params['admin'] = 1;

			if (!array_key_exists('editing_mode', $params)) {
				$params['editing_mode'] = EDITING_MODE;
			}
		}

		$ret = '';
		$env = '';

		$encode = false;

		if (isset($params['__URLENCODE__'])) {
			$encode = $params['__URLENCODE__'];
			unset($params['__URLENCODE__']);
		}

		if (isset($params['__SSL__'])) {
			unset($params['__SSL__']);
		}

		$catalog_item_found = false;
		$pass_info = $this->getPassInfo($pass);

		if ($pass_info) {
			if ($pass_info[0] == 'm') {
				array_shift($pass_info);
			}

			$inject_parts = Array (); // url parts for beginning of url
			$params['t'] = $t; // make template available for rewrite listeners
			$params['pass_template'] = true; // by default we keep given template in resulting url

			if (!array_key_exists('pass_category', $params)) {
				$params['pass_category'] = false; // by default we don't keep categories in url
			}

			foreach ($pass_info as $pass_index => $pass_element) {
				list ($prefix) = explode('.', $pass_element);
				$catalog_item = $this->findModule('Var', $prefix) && $this->getUnitOption($prefix, 'CatalogItem');

				if (array_key_exists($prefix, $this->RewriteListeners)) {
					// if next prefix is same as current, but with special => exclude current prefix from url
					$next_prefix = array_key_exists($pass_index + 1, $pass_info) ? $pass_info[$pass_index + 1] : false;
					if ($next_prefix) {
						$next_prefix = substr($next_prefix, 0, strlen($prefix) + 1);
						if ($prefix . '.' == $next_prefix) {
							continue;
						}
					}

					// rewrited url part
					$url_part = $this->BuildModuleEnv_NEW($pass_element, $params, $pass_events);

					if (is_string($url_part) && $url_part) {
						$ret .= $url_part . '/';

						if ($catalog_item) {
							// pass category later only for catalog items
							$catalog_item_found = true;
						}
					}
					elseif (is_array($url_part)) {
						// rewrite listener want to insert something at the beginning of url too
						if ($url_part[0]) {
							$inject_parts[] = $url_part[0];
						}

						if ($url_part[1]) {
							$ret .= $url_part[1] . '/';
						}

						if ($catalog_item) {
							// pass category later only for catalog items
							$catalog_item_found = true;
						}
					} elseif ($url_part === false) {
						// rewrite listener decided not to rewrite given $pass_element
						$env .= ':' . $this->BuildModuleEnv($pass_element, $params, $pass_events);
					}
				}
				else {
					$env .= ':' . $this->BuildModuleEnv($pass_element, $params, $pass_events);
				}
			}

			if ($catalog_item_found || preg_match('/c\.[-\d]*/', implode(',', $pass_info))) {
				// "c" prefix is present -> keep category
				$params['pass_category'] = true;
			}

			$params['inject_parts'] = $inject_parts;

			$ret = $this->BuildModuleEnv_NEW('m', $params, $pass_events) . '/' . $ret;
			$cat_processed = array_key_exists('category_processed', $params) && $params['category_processed'];

			// remove tempporary parameters used by listeners
			unset($params['t'], $params['inject_parts'], $params['pass_template'], $params['pass_category'], $params['category_processed']);

			if (array_key_exists('url_ending', $params)) {
				$ret = trim($ret, '/') . $params['url_ending'];
				unset($params['url_ending']);
			}
			else {
				$ret = trim($ret, '/') . MOD_REWRITE_URL_ENDING;
			}

			if ($env) {
				$params[ENV_VAR_NAME] = ltrim($env, ':');
			}
		}

		unset($params['pass'], $params['opener'], $params['m_event']);

		if (array_key_exists('escape', $params) && $params['escape']) {
			$ret = addslashes($ret);
			unset($params['escape']);
		}

		$ret = str_replace('%2F', '/', urlencode($ret));

		$params_str = '';
		$join_string = $encode ? '&' : '&amp;';

		foreach ($params as $param => $value) {
			$params_str .= $join_string . $param . '=' . $value;
		}

		if ($params_str) {
			$ret .= '?' . substr($params_str, strlen($join_string));
		}

		if ($encode) {
			$ret = str_replace('\\', '%5C', $ret);
		}

		return $ret;
	}

	function BuildModuleEnv_NEW($prefix_special, &$params, $keep_events = false)
	{
		list ($prefix) = explode('.', $prefix_special);

		$url_parts = Array ();
		$listener = $this->RewriteListeners[$prefix][0];

		$ret = $listener[0]->$listener[1](REWRITE_MODE_BUILD, $prefix_special, $params, $url_parts, $keep_events);

		return $ret;
	}

	/**
	 * Builds env part that corresponds prefix passed
	 *
	 * @param string $prefix_special item's prefix & [special]
	 * @param Array $params url params
	 * @param bool $pass_events
	 */
	function BuildModuleEnv($prefix_special, &$params, $pass_events = false)
	{
		list($prefix) = explode('.', $prefix_special);
		$query_vars = $this->getUnitOption($prefix, 'QueryString');

		//if pass events is off and event is not implicity passed
		if( !$pass_events && !isset($params[$prefix_special.'_event']) ) {
			$params[$prefix_special.'_event'] = ''; // remove event from url if requested
			//otherwise it will use value from get_var
		}

		if(!$query_vars) return '';

		$tmp_string = Array(0 => $prefix_special);
		foreach($query_vars as $index => $var_name)
		{
			//if value passed in params use it, otherwise use current from application
			$var_name = $prefix_special.'_'.$var_name;
			$tmp_string[$index] =  isset( $params[$var_name] ) ? $params[$var_name] : $this->GetVar($var_name);
			if ( isset($params[$var_name]) ) unset( $params[$var_name] );
		}

		$escaped = array();
		foreach ($tmp_string as $tmp_val) {
			$escaped[] = str_replace(Array('-',':'), Array('\-','\:'), $tmp_val);
		}

		$ret = implode('-', $escaped);
		if ($this->getUnitOption($prefix, 'PortalStyleEnv') == true)
		{
			$ret = preg_replace('/^([a-zA-Z]+)-([0-9]+)-(.*)/','\\1\\2-\\3', $ret);
		}
		return $ret;
	}

	function BuildEnv($t, $params, $pass='all', $pass_events = false, $env_var = true)
	{
		if ($this->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin'])) {
			$params['admin'] = 1;

			if (!array_key_exists('editing_mode', $params)) {
				$params['editing_mode'] = EDITING_MODE;
			}
		}

		$session =& $this->recallObject('Session');
		$ssl = isset($params['__SSL__']) ? $params['__SSL__'] : 0;
		$sid = $session->NeedQueryString() && !$this->RewriteURLs($ssl) ? $this->GetSID() : '';
//		if (getArrayValue($params,'admin') == 1) $sid = $this->GetSID();

		$ret = '';
		if ($env_var) {
			$ret = ENV_VAR_NAME.'=';
		}

		$ret .=	$sid . '-'; // SID-TEMPLATE

		$encode = false;
		if (isset($params['__URLENCODE__'])) {
			$encode = $params['__URLENCODE__'];
			unset($params['__URLENCODE__']);
		}

		if (isset($params['__SSL__'])) {
			unset($params['__SSL__']);
		}

		$env_string = '';
		$category_id = isset($params['m_cat_id']) ? $params['m_cat_id'] : $this->GetVar('m_cat_id');

		$item_id = false;
		$pass_info = $this->getPassInfo($pass);
		if ($pass_info) {
			if ($pass_info[0] == 'm') array_shift($pass_info);
			foreach ($pass_info as $pass_element) {
				list($prefix) = explode('.', $pass_element);
				$require_rewrite = $this->findModule('Var', $prefix);
				if ($require_rewrite) {
					$item_id = isset($params[$pass_element.'_id']) ? $params[$pass_element.'_id'] : $this->GetVar($pass_element.'_id');
				}
				$env_string .= ':'.$this->BuildModuleEnv($pass_element, $params, $pass_events);
			}
		}

		if (strtolower($t) == '__default__') {
			if (is_numeric($item_id)) {
				$mod_rw_helper =& $this->Application->recallObject('ModRewriteHelper');
				/* @var $mod_rw_helper kModRewriteHelper */

				$t = $mod_rw_helper->GetItemTemplate($category_id, $pass_element); // $pass_element should be the last processed element
				// $t = $this->getCategoryCache($category_id, 'item_templates');
			}
			elseif ($category_id) {
				$t = strtolower(preg_replace('/^Content\//i', '', $this->getCategoryCache($category_id, 'filenames') ));
			}
			else {
				$t = 'index';
			}
		}

		$ret .= $t.':'.$this->BuildModuleEnv('m', $params, $pass_events).$env_string;

		unset($params['pass'], $params['opener'], $params['m_event']);

		if (array_key_exists('escape', $params) && $params['escape']) {
			$ret = addslashes($ret);
			unset($params['escape']);
		}

		$join_string = $encode ? '&' : '&amp;';
		$params_str = '';
		foreach ($params as $param => $value)
		{
			$params_str .= $join_string.$param.'='.$value;
		}
		$ret .= $params_str;

		if ($encode) {
			$ret = str_replace('\\', '%5C', $ret);
		}
		return $ret;
	}

	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 : '') . rtrim(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 : '') . rtrim($this->ConfigValue('Site_Path'), '/') . $prefix . '/';
	}

	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() && (constOn('DBG_REDIRECT') || (constOn('DBG_RAISE_ON_WARNINGS') && $this->Application->Debugger->WarningCount))) {
			$this->Debugger->appendTrace();
			echo "<b>Debug output above!!!</b> Proceed to redirect: <a href=\"$location\">$location</a><br>";
		}
		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 true;
			}
			else {
				if ($this->GetVar('ajax') == 'yes' && $t != $this->GetVar('t')) {
					// redirection to other then current template during ajax request
					echo '#redirect#' . $location;
				}
				elseif (headers_sent() != '') {
					// some output occured -> 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);
				}
			}
		}

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

		$this->HandleEvent( new kEvent('adm:OnBeforeShutdown') );
		$session->SaveData();
		exit;
	}

	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
	 */
	function ReplaceLanguageTags($text, $force_escape=null)
	{
		// !!!!!!!!
//		if( !is_object($this->Phrases) ) $this->Debugger->appendTrace();
		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.
	 *
	 * @access private
	 */
	function ValidateLogin()
	{
		$session =& $this->recallObject('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.current');
			$user->SetError('ValidateLogin', 'session_expired', 'la_text_sess_expired');
		}

		if (($user_id != USER_GUEST) && constOn('DBG_REQUREST_LOG') ) {
			$http_query =& $this->recallObject('HTTPQuery');
			$http_query->writeRequestLog(DBG_REQUREST_LOG);
		}

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

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

	function LoadCache()
	{
		// TODO: maybe language part isn't required, since same phrase from different languages have one ID now
		$cache_key = $this->GetVar('t') . $this->GetVar('m_theme') . $this->GetVar('m_lang') . $this->isAdmin;

		$sql = 'SELECT PhraseList, ConfigVariables
				FROM ' . TABLE_PREFIX . 'PhraseCache
				WHERE Template = ' . $this->Conn->qstr( md5($cache_key) );
		$res = $this->Conn->GetRow($sql);

		if ($res) {
			$this->Caches['PhraseList'] = $res['PhraseList'] ? explode(',', $res['PhraseList']) : Array ();
			$config_ids = $res['ConfigVariables'] ? explode(',', $res['ConfigVariables']) : Array ();

			if (isset($this->Caches['ConfigVariables'])) {
				$config_ids = array_diff($config_ids, $this->Caches['ConfigVariables']);
			}
		}
		else {
			$config_ids = Array ();
		}

		$this->Phrases->Init('phrases');
		$this->Caches['ConfigVariables'] = $config_ids;
		$this->ConfigCacheIds = $config_ids;
	}

	/**
	 * Loads template mapping for Front-End
	 *
	 */
	function LoadStructureTemplateMapping()
	{
		if (!$this->isAdmin) {
			$category_helper =& $this->Application->recallObject('CategoryHelper');
			/* @var $category_helper CategoryHelper */

			$this->structureTemplateMapping = $category_helper->getTemplateMapping();
		}
	}

	function UpdateCache()
	{
		$update = false;
		//something changed
		$update = $update || $this->Phrases->NeedsCacheUpdate();
		$update = $update || (count($this->ConfigCacheIds) && $this->ConfigCacheIds != $this->Caches['ConfigVariables']);

		if ($update) {
			$cache_key = $this->GetVar('t').$this->GetVar('m_theme').$this->GetVar('m_lang').$this->isAdmin;
			$query = sprintf("REPLACE %s (PhraseList, CacheDate, Template, ConfigVariables)
												VALUES (%s, %s, %s, %s)",
												TABLE_PREFIX.'PhraseCache',
												$this->Conn->qstr(join(',', $this->Phrases->Ids)),
												adodb_mktime(),
												$this->Conn->qstr(md5($cache_key)),
												$this->Conn->qstr(implode(',', array_unique($this->ConfigCacheIds))));
			$this->Conn->Query($query);
		}
	}

	function InitConfig()
	{
		if (isset($this->Caches['ConfigVariables']) && count($this->Caches['ConfigVariables']) > 0) {
			$sql = 'SELECT VariableValue, VariableName
					FROM ' . TABLE_PREFIX . 'ConfigurationValues
				 	WHERE VariableId IN (' . implode(',', $this->Caches['ConfigVariables']) . ')';
			$this->ConfigHash = array_merge($this->ConfigHash, $this->Conn->GetCol($sql, 'VariableName'));
		}
	}

	/**
	 * Returns configuration option value by name
	 *
	 * @param string $name
	 * @return string
	 */
	function ConfigValue($name)
	{
		if ($name == 'Smtp_AdminMailFrom') {
			$res = $this->siteDomainField('AdminEmail');

			if ($res) {
				return $res;
			}
		}

		$res = array_key_exists($name, $this->ConfigHash) ? $this->ConfigHash[$name] : false;
		if ($res !== false) {
			return $res;
		}

		if (defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('ConfigurationValues')) {
			return false;
		}

		$sql = 'SELECT VariableId, VariableValue
				FROM '.TABLE_PREFIX.'ConfigurationValues
				WHERE VariableName = '.$this->Conn->qstr($name);
		$res = $this->Conn->GetRow($sql);

		if ($res !== false) {
			$this->ConfigHash[$name] = $res['VariableValue'];
			$this->ConfigCacheIds[] = $res['VariableId'];
			return $res['VariableValue'];
		}

		return false;
	}

	function UpdateConfigCache()
	{
		if ($this->ConfigCacheIds) {

		}
	}

	/**
	 * Allows to process any type of event
	 *
	 * @param kEvent $event
	 * @access public
	 * @author Alex
	 */
	function HandleEvent(&$event, $params=null, $specificParams=null)
	{
		if ( isset($params) ) {
			$event = new kEvent( $params, $specificParams );
		}

		if (!isset($this->EventManager)) {
			$this->EventManager =& $this->recallObject('EventManager');
		}

		$this->EventManager->HandleEvent($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
	 * @param Array $dependecies List of classes required for this class functioning
	 * @access public
	 * @author Alex
	 */
	function registerClass($real_class, $file, $pseudo_class = null, $dependecies = Array() )
	{
		$this->Factory->registerClass($real_class, $file, $pseudo_class, $dependecies);
	}

	/**
	 * Add $class_name to required classes list for $depended_class class.
	 * All required class files are included before $depended_class file is included
	 *
	 * @param string $depended_class
	 * @param string $class_name
	 * @author Alex
	 */
	function registerDependency($depended_class, $class_name)
	{
		$this->Factory->registerDependency($depended_class, $class_name);
	}

	/**
	 * Registers Hook from subprefix event to master prefix event
	 *
	 * @param string $hookto_prefix
	 * @param string $hookto_special
	 * @param string $hookto_event
	 * @param string $mode
	 * @param string $do_prefix
	 * @param string $do_special
	 * @param string $do_event
	 * @param string $conditional
	 * @access public
	 * @todo take care of a lot parameters passed
	 * @author Kostja
	 */
	function registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional)
	{
		$event_manager =& $this->recallObject('EventManager');
		$event_manager->registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional);
	}

	/**
	 * Allows one TagProcessor tag act as other TagProcessor tag
	 *
	 * @param Array $tag_info
	 * @author Kostja
	 */
	function registerAggregateTag($tag_info)
	{
		$aggregator =& $this->recallObject('TagsAggregator', 'kArray');
		$aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], Array($tag_info['LocalPrefix'], $tag_info['LocalTagName'], getArrayValue($tag_info, 'LocalSpecial')));
	}

	/**
	 * Returns object using params specified,
	 * creates it if is required
	 *
	 * @param string $name
	 * @param string $pseudo_class
	 * @param Array $event_params
	 * @return Object
	 * @author Alex
	 */
	function &recallObject($name,$pseudo_class=null,$event_params=Array())
	{
		$result =& $this->Factory->getObject($name, $pseudo_class, $event_params);
		return $result;
	}

	/**
	 * Returns object using Variable number of params,
	 * all params starting with 4th are passed to object consturctor
	 *
	 * @param string $name
	 * @param string $pseudo_class
	 * @param Array $event_params
	 * @return Object
	 * @author Alex
	 */
	function &recallObjectP($name,$pseudo_class=null,$event_params=Array())
	{
		$func_args = func_get_args();
		$result =& ref_call_user_func_array( Array(&$this->Factory, 'getObjectP'), $func_args );
		return $result;
	}

	/**
	 * Returns tag processor for prefix specified
	 *
	 * @param string $prefix
	 * @return kDBTagProcessor
	 */
	function &recallTagProcessor($prefix)
	{
		$this->InitParser(); // because kDBTagProcesor is in NParser dependencies
		$result =& $this->recallObject($prefix . '_TagProcessor');

		return $result;
	}

	/**
	 * Checks if object with prefix passes was already created in factory
	 *
	 * @param string $name object presudo_class, prefix
	 * @return bool
	 * @author Kostja
	 */
	function hasObject($name)
	{
		return isset($this->Factory->Storage[$name]);
	}

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

	/**
	 * Get's real class name for pseudo class,
	 * includes class file and creates class
	 * instance
	 *
	 * @param string $pseudo_class
	 * @return Object
	 * @access public
	 * @author Alex
	 */
	function &makeClass($pseudo_class)
	{
		$func_args = func_get_args();
		$result =& ref_call_user_func_array( Array(&$this->Factory, 'makeClass'), $func_args);

		return $result;
	}

	/**
	 * 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
	 */
	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 $ssl Force ssl link to be build
	 * @return bool
	 */
	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 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
	 * @author Alex
	 */
	function getUnitOption($prefix, $option, $default = false)
	{
		/*if (!isset($this->UnitConfigReader)) {
			$this->UnitConfigReader =& $this->recallObject('kUnitConfigReader');
		}*/
		return $this->UnitConfigReader->getUnitOption($prefix, $option, $default);
	}

	/**
	 * Set's new unit option value
	 *
	 * @param string $prefix
	 * @param string $name
	 * @param string $value
	 * @author Alex
	 * @access public
	 */
	function setUnitOption($prefix, $option, $value)
	{
//		$unit_config_reader =& $this->recallObject('kUnitConfigReader');
		return $this->UnitConfigReader->setUnitOption($prefix,$option,$value);
	}

	/**
	 * Read all unit with $prefix options
	 *
	 * @param string $prefix
	 * @return Array
	 * @access public
	 * @author Alex
	 */
	function getUnitOptions($prefix)
	{
//		$unit_config_reader =& $this->recallObject('kUnitConfigReader');
		return $this->UnitConfigReader->getUnitOptions($prefix);
	}

	/**
	 * Returns true if config exists and is allowed for reading
	 *
	 * @param string $prefix
	 * @return bool
	 */
	function prefixRegistred($prefix)
	{
		/*if (!isset($this->UnitConfigReader)) {
			$this->UnitConfigReader =& $this->recallObject('kUnitConfigReader');
		}*/
		return $this->UnitConfigReader->prefixRegistred($prefix);
	}

	/**
	 * Splits any mixing of prefix and
	 * special into correct ones
	 *
	 * @param string $prefix_special
	 * @return Array
	 * @access public
	 * @author Alex
	 */
	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
	 * @access public
	 */
	function setEvent($prefix_special,$event_name)
	{
		$event_manager =& $this->recallObject('EventManager');
		$event_manager->setEvent($prefix_special,$event_name);
	}


	/**
	 * SQL Error Handler
	 *
	 * @param int $code
	 * @param string $msg
	 * @param string $sql
	 * @return bool
	 * @access private
	 * @author Alex
	 */
	function handleSQLError($code, $msg, $sql)
	{
		if ( isset($this->Debugger) )
		{
			$errorLevel = constOn('DBG_SQL_FAILURE') && !defined('IS_INSTALL') ? E_USER_ERROR : E_USER_WARNING;
			$this->Debugger->appendTrace();

			$error_msg = '<span class="debug_error">'.$msg.' ('.$code.')</span><br><a href="javascript:$Debugger.SetClipboard(\''.htmlspecialchars($sql).'\');"><b>SQL</b></a>: '.$this->Debugger->formatSQL($sql);
			$long_id = $this->Debugger->mapLongError($error_msg);
			trigger_error( mb_substr($msg.' ('.$code.') ['.$sql.']',0,1000).' #'.$long_id, $errorLevel);
			return true;
		}
		else
		{
			//$errorLevel = constOn('IS_INSTALL') ? E_USER_WARNING : E_USER_ERROR;
			$errorLevel = E_USER_WARNING;
			trigger_error('<b>SQL Error</b> in sql: '.$sql.', code <b>'.$code.'</b> ('.$msg.')', $errorLevel);
			/*echo '<b>xProcessing SQL</b>: '.$sql.'<br>';
			echo '<b>Error ('.$code.'):</b> '.$msg.'<br>';*/
			return $errorLevel == E_USER_ERROR ? false : true;
		}
	}

	/**
	 * Default error handler
	 *
	 * @param int $errno
	 * @param string $errstr
	 * @param string $errfile
	 * @param int $errline
	 * @param Array $errcontext
	 */
	function handleError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '')
	{
		if (defined('SILENT_LOG') && SILENT_LOG) {
			if ( !(defined('DBG_IGNORE_STRICT_ERRORS') && DBG_IGNORE_STRICT_ERRORS && defined('E_STRICT') && ($errno == E_STRICT)) ) {
				$fp = fopen(FULL_PATH.'/silent_log.txt','a');
				$time = adodb_date('d/m/Y H:i:s');
				fwrite($fp, '['.$time.'] #'.$errno.': '.strip_tags($errstr).' in ['.$errfile.'] on line '.$errline."\n");
				fclose($fp);
			}
		}

		$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)
			$ignore_fatal_errors = defined('DBG_IGNORE_FATAL_ERRORS') && DBG_IGNORE_FATAL_ERRORS;

			if (($errno == E_USER_ERROR) && !$ignore_fatal_errors) {
				echo ('	<div style="background-color: #FEFFBF; margin: auto; padding: 10px; border: 2px solid red; text-align: center">
							<strong>Fatal Error: </strong>'."$errstr in $errfile on line $errline".'
						</div>');
				exit;
			}

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

		$res = false;
		$i = 0; // while (not foreach) because it is array of references in some cases
		$eh_count = count($this->errorHandlers);
		while ($i < $eh_count) {
			if ( is_array($this->errorHandlers[$i]) ) {
				$object =& $this->errorHandlers[$i][0];
				$method = $this->errorHandlers[$i][1];
				$res = $object->$method($errno, $errstr, $errfile, $errline, $errcontext);
			}
			else {
				$function = $this->errorHandlers[$i];
				$res = $function($errno, $errstr, $errfile, $errline, $errcontext);
			}
			$i++;
		}
		return $res;
	}

	/**
	 * Returns & blocks next ResourceId available in system
	 *
	 * @return int
	 * @access public
	 * @author Alex
	 */
	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 subtable prefix passes
	 * OR prefix, that has been found in REQUEST and some how is parent of passed subtable prefix
	 *
	 * @param string $current_prefix
	 * @param string $real_top if set to true will return real topmost prefix, regardless of its id is passed or not
	 * @return string
	 * @access public
	 * @author Kostja / Alex
	 */
	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
	 */
	function &EmailEventAdmin($email_event_name, $to_user_id = null, $send_params = Array ())
	{
		$event =& $this->EmailEvent($email_event_name, EVENT_TYPE_ADMIN, $to_user_id, $send_params);

		return $event;
	}

	/**
	 * 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
	 */
	function &EmailEventUser($email_event_name, $to_user_id = null, $send_params = Array ())
	{
		$event =& $this->EmailEvent($email_event_name, EVENT_TYPE_FRONTEND, $to_user_id, $send_params);

		return $event;
	}

	/**
	 * 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
	 */
	function &EmailEvent($email_event_name, $email_event_type, $to_user_id = null, $send_params = Array ())
	{
		$params = Array (
			'EmailEventName' => $email_event_name,
			'EmailEventToUserId' => $to_user_id,
			'EmailEventType' => $email_event_type,
			'DirectSendParams' => $send_params,
		);

		if (array_key_exists('use_special', $send_params)) {
			$event_str = 'emailevents.' . $send_params['use_special'] . ':OnEmailEvent';
		}
		else {
			$event_str = 'emailevents:OnEmailEvent';
		}

		$this->HandleEvent($event, $event_str, $params);

		return $event;
	}

	/**
	 * Allows to check if user in this session is logged in or not
	 *
	 * @return bool
	 */
	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
	 */
	function CheckPermission($name, $type = 1, $cat_id = null)
	{
		$perm_helper =& $this->recallObject('PermissionsHelper');
		return $perm_helper->CheckPermission($name, $type, $cat_id);
	}

	/**
	 * Set's any field of current visit
	 *
	 * @param string $field
	 * @param mixed $value
	 */
	function setVisitField($field, $value)
	{
		if ($this->isAdmin || !$this->ConfigValue('UseVisitorTracking')) {
			// admin logins are not registred 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
	 */
	function isInstalled()
	{
		return $this->InitDone && (count($this->ModuleInfo) > 0);
	}

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

	}

	function reportError($class, $method)
	{
		$this->Debugger->appendTrace();
		trigger_error('depricated method <b>'.$class.'->'.$method.'(...)</b>', E_USER_ERROR);
	}

	/**
	 * Returns Window ID of passed prefix main prefix (in edit mode)
	 *
	 * @param string $prefix
	 * @return mixed
	 */
	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
	 */
	function GetTempName($table, $wid = '')
	{
		if (preg_match('/prefix:(.*)/', $wid, $regs)) {
			$wid = $this->GetTopmostWid($regs[1]);
		}

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

	function GetTempTablePrefix($wid = '')
	{
		if (preg_match('/prefix:(.*)/', $wid, $regs)) {
			$wid = $this->GetTopmostWid($regs[1]);
		}

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

	function IsTempTable($table)
	{
		return preg_match('/'.TABLE_PREFIX.'ses_'.$this->GetSID().'(_[\d]+){0,1}_edit_(.*)/',$table);
	}

	/**
	 * Checks, that given prefix is in temp mode
	 *
	 * @param string $prefix
	 * @return bool
	 */
	function IsTempMode($prefix, $special = '')
	{
		$top_prefix = $this->Application->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->Application->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
	 */
	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;
		}
	}

	function CheckProcessors($processors)
	{
		foreach ($processors as $a_processor)
		{
			if (!isset($this->CachedProcessors[$a_processor])) {
				$this->CachedProcessors[$a_processor] =& $this->recallObject($a_processor.'_TagProcessor');
			}
		}
	}

	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;
	}


	/* moved from MyApplication */

	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 . 'UserGroup
						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 agents supported)
	 *
	 * @return bool
	 */
	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 presense in database
	 *
	 * @param string $table_name
	 * @return bool
	 */
	function TableFound($table_name)
	{
		return $this->Conn->TableFound($table_name);
	}

	/**
	 * 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 parmeters)
	 * @param bool $multiple_results
	 * @return mixed
	 */
	function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false)
	{
		$count_helper =& $this->Application->recallObject('CountHelper');
		/* @var $count_helper kCountHelper */

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

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

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

		return $count_helper->resetCounters($tables);
	}

	/**
	 * Sends XML header + optionally displays xml heading
	 *
	 * @param string $xml_version
	 * @return string
	 * @author Alex
	 */
	function XMLHeader($xml_version = false)
	{
		$lang =& $this->recallObject('lang.current');
		header('Content-type: text/xml; charset='.$lang->GetDBField('Charset'));

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

	/**
	 * Returns category tree
	 *
	 * @param int $category_id
	 * @return Array
	 */
	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;
	}

}