Index: branches/5.3.x/core/kernel/application.php
===================================================================
--- branches/5.3.x/core/kernel/application.php	(revision 16170)
+++ branches/5.3.x/core/kernel/application.php	(revision 16171)
@@ -1,3065 +1,3064 @@
 <?php
 /**
 * @version	$Id$
 * @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
+	 * Registered routers, that are used during url building and parsing.
 	 *
-	 * @var Array
-	 * @access public
+	 * @var array
 	 */
-	public $RewriteListeners = Array ();
+	public $routers = array();
 
 	/**
 	 * Reference to debugger
 	 *
 	 * @var Debugger
 	 * @access public
 	 */
 	public $Debugger = null;
 
 	/**
 	 * Holds all phrases used
 	 * in code and template
 	 *
 	 * @var kPhraseCache
 	 * @access public
 	 */
 	public $Phrases;
 
 	/**
 	 * Modules table content, key - module name
 	 *
 	 * @var Array
 	 * @access public
 	 */
 	public $ModuleInfo = Array ();
 
 	/**
 	 * Holds DBConnection
 	 *
 	 * @var IDBConnection
 	 * @access public
 	 */
 	public $Conn = null;
 
 	/**
 	 * Reference to event log
 	 *
 	 * @var Array|kLogger
 	 * @access public
 	 */
 	protected $_logger = 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;
 		}
 
 		if ( preg_match('/utf-8/i', CHARSET) ) {
 			setlocale(LC_ALL, 'en_US.UTF-8');
 			mb_internal_encoding('UTF-8');
 		}
 
 		$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:');
 		}
 
 		$this->_logger = new kLogger($this->_logger);
 		$this->Factory = new $factory_class();
 		$this->registerDefaultClasses();
 
 		$system_config = new kSystemConfig(true);
 		$vars = $system_config->getData();
 		$db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : ($this->isDebugMode() ? 'kDBConnectionDebug' : 'kDBConnection');
 		$this->Conn = $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array ($this->_logger, '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->UnitConfigReader->scanModules(MODULES_PATH); // Will also set routers.
 
 		$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('kHTTPQuery');
 		$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 ) {
 			date_default_timezone_set($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
+		$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);
 		}
 
 		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);
 		}
 
 		$this->UrlManager = $this->makeClass('kUrlManager');
 		$this->EventManager = $this->makeClass('kEventManager');
 		$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();
 			$this->Factory->configureAutoloader();
 
 			return;
 		}
 
 		// use makeClass over recallObject, since used before kApplication initialization during installation
 		$modules_helper = $this->makeClass('kModulesHelper');
 		/* @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();
 		$this->Factory->configureAutoloader();
 	}
 
 	/**
 	 * Checks if passed language id if valid and sets it to primary otherwise
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function VerifyLanguageId()
 	{
 		/** @var LanguagesItem $lang */
 		$lang = $this->recallObject('lang.current');
 
 		if ( !$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled')) ) {
 			if ( !defined('IS_INSTALL') ) {
 				$this->ApplicationDie('Unknown or disabled language');
 			}
 		}
 	}
 
 	/**
 	 * 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;
 		}
 
 		/** @var ThemeItem $theme */
 		$theme = $this->recallObject('theme.current');
 
 		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
 			$language_config = $this->getUnitConfig('lang');
 
 			$table = $language_config->getTableName();
 			$id_field = $language_config->getIDField();
 
 			$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;
 				$theme_config = $this->getUnitConfig('theme');
 
 				$sql = 'SELECT ' . $theme_config->getIDField() . '
 						FROM ' . $theme_config->getTableName() . '
 						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->prefixRegistred('curr') ) {
 				$this->Conn->nextQueryCachable = true;
 				$currency_id = $this->siteDomainField('PrimaryCurrencyId');
 
 				$sql = 'SELECT ISO
 						FROM ' . $this->getUnitConfig('curr')->getTableName() . '
 						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', null, Array ('live_table' => true));
 			/* @var $site_domain kDBItem */
 		}
 
 		if ( $this->siteDomain->isLoaded() ) {
 			return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field);
 		}
 
 		return false;
 	}
 
 	/**
 	 * Registers classes, that are used before unit configs (where class registration usually is done) are read.
 	 *
 	 * Called automatically while initializing kApplication.
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function RegisterDefaultClasses()
 	{
 		// Database.
 		$this->registerClass('IDBConnection', KERNEL_PATH . '/db/i_db_connection.php');
 		$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');
 
 		// Cache.
 		$this->registerClass('kCacheManager', KERNEL_PATH . '/managers/cache_manager.php');
 		$this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php');
 
 		// Unit configs.
 		$this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php');
 		$this->registerClass('kUnitConfigCloner', KERNEL_PATH . '/utility/unit_config_cloner.php');
 
 		// Urls.
 		$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');
 
 		// Events.
 		$this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php');
 		$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');
 
 		// Misc.
 		$this->registerClass('kPhraseCache', KERNEL_PATH . '/languages/phrases_cache.php');
 		$this->registerClass('kModulesHelper', KERNEL_PATH . self::MODULE_HELPER_PATH);
 
 		// Aliased.
 		$this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions');
 		$this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php', 'm_TagProcessor');
 		$this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender');
 	}
 
 	/**
 	 * 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=' . kUtil::escape($_SERVER['REQUEST_URI'], kUtil::ESCAPE_URL);
 
 		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'] = $_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()
 	{
 		// discard any half-parsed content
 		ob_clean();
 
 		// replace current page content with 404
 		$this->InitParser();
 		$this->HTML = $this->Parser->Run($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 ) {
 			if ( kUtil::constOn('DBG_PROFILE_MEMORY') ) {
 				$this->Debugger->appendMemoryUsage('Application before Done:');
 			}
 
 			$this->Session->SaveData(); // adds session data to debugger report
 			$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->_outputPage();
 		$this->cacheManager->UpdateApplicationCache();
 
 		if ( !$debug_mode ) {
 			$this->Session->SaveData();
 		}
 
 		$this->EventManager->runScheduledTasks();
 
 		if ( defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin ) {
 			$this->_storeStatistics();
 		}
 	}
 
 	/**
 	 * Outputs generated page content to end-user
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function _outputPage()
 	{
 		$this->setContentType();
 		ob_start();
 
 		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 {
 			// when gzip compression not used connection won't be closed early!
 			echo $this->HTML;
 		}
 
 		// send headers to tell the browser to close the connection
 		header('Content-Length: ' . ob_get_length());
 		header('Connection: close');
 
 		// flush all output
 		ob_end_flush();
 
 		if ( ob_get_level() ) {
 			ob_flush();
 		}
 
 		flush();
 
 		// close current session
 		if ( session_id() ) {
 			session_write_close();
 		}
 	}
 
 	/**
 	 * 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'] = time();
 
 			$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'] = time();
 			$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'] = time();
 
 			$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'] = time();
 
 			$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;
 		}
 
 		$accept_encoding = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : '';
 
 		return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($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;
 	}
 
 	/**
 	 * Removes forceful escaping done to the variable upon Front-End submission.
 	 *
 	 * @param string|array $value Value.
 	 *
 	 * @return string|array
 	 * @see    kHttpQuery::StripSlashes
 	 * @todo   Temporary method for marking problematic places to take care of, when forceful escaping will be removed.
 	 */
 	public function unescapeRequestVariable($value)
 	{
 		return $this->HttpQuery->unescapeRequestVariable($value);
 	}
 
 	/**
 	 * 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 object of IDBConnection interface
 	 *
 	 * Return object of IDBConnection interface already connected to the project database, configurable in config.php
 	 *
 	 * @return IDBConnection
 	 * @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 seo template by physical template
 	 *
 	 * @param string $physical_template
 	 * @return string
 	 * @access public
 	 */
 	public function getSeoTemplate($physical_template)
 	{
 		return $this->UrlManager->getSeoTemplate($physical_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 section template for given physical/virtual template
 	 *
 	 * @param string $template
 	 * @param int $theme_id
 	 * @return string
 	 * @access public
 	 */
 	public function getSectionTemplate($template, $theme_id = null)
 	{
 		return $this->UrlManager->getSectionTemplate($template, $theme_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  $domain       Domain override.
 	 * @param boolean $ssl_redirect Redirect to/from SSL.
 	 *
 	 * @return string
 	 */
 	public function BaseURL($domain = '', $ssl_redirect = null)
 	{
 		if ( $ssl_redirect === null ) {
 			// stay on same encryption level
 			return PROTOCOL . ($domain ? $domain : SERVER_NAME) . (defined('PORT') ? ':' . PORT : '') . BASE_PATH . '/';
 		}
 
 		if ( $ssl_redirect ) {
 			// going from http:// to https://
 			$protocol = 'https://';
 			$domain = $this->getSecureDomain();
 		}
 		else {
 			// going from https:// to http://
 			$protocol = 'http://';
 			$domain = $this->siteDomainField('DomainName');
 
 			if ( $domain === false ) {
 				$domain = DOMAIN; // not on site domain
 			}
 		}
 
 		return $protocol . $domain . (defined('PORT') ? ':' . PORT : '') . BASE_PATH . '/';
 	}
 
 	/**
 	 * Returns secure domain.
 	 *
 	 * @return string
 	 */
 	public function getSecureDomain()
 	{
 		$ret = $this->isAdmin ? $this->ConfigValue('AdminSSLDomain') : false;
 
 		if ( !$ret ) {
 			$ssl_domain = $this->siteDomainField('SSLDomainName');
 
 			return strlen($ssl_domain) ? $ssl_domain : $this->ConfigValue('SSLDomain');
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * 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';
 		}
 
 		$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') || !$this->isSOPSafe($location, $t)) ) {
 					// redirection to other then current template during ajax request OR SOP violation
 					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;
 	}
 
 	/**
 	 * Determines if real redirect should be made within AJAX request.
 	 *
 	 * @param string $url      Location.
 	 * @param string $template Template.
 	 *
 	 * @return boolean
 	 * @link   http://en.wikipedia.org/wiki/Same-origin_policy
 	 */
 	protected function isSOPSafe($url, $template)
 	{
 		$parsed_url = parse_url($url);
 
 		if ( $parsed_url['scheme'] . '://' != PROTOCOL ) {
 			return false;
 		}
 
 		if ( $parsed_url['host'] != SERVER_NAME ) {
 			return false;
 		}
 
 		if ( defined('PORT') && isset($parsed_url['port']) && $parsed_url['port'] != PORT ) {
 			return false;
 		}
 
 		return true;
 	}
 
 	/**
 	 * 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');
 		}
 
 		$this->HandleEvent(new kEvent('adm:OnLogHttpRequest'));
 
 		if ( $user_id != USER_GUEST ) {
 			// normal users + root
 			$this->LoadPersistentVars();
 		}
 
 		$user_timezone = $this->Session->GetField('TimeZone');
 
 		if ( $user_timezone ) {
 			date_default_timezone_set($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_schedule run schedule like for Cron
 	 * @param string $module
 	 * @param int $status
 	 * @access public
 	 */
 	public function registerScheduledTask($short_name, $event_string, $run_schedule, $module, $status = STATUS_ACTIVE)
 	{
 		$this->EventManager->registerScheduledTask($short_name, $event_string, $run_schedule, $module, $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);
 	}
 
 	/**
 	 * Returns sub-classes of given ancestor class.
 	 *
 	 * @param string  $ancestor_class Ancestor class.
 	 * @param boolean $concrete_only  Return only non-abstract classes.
 	 *
 	 * @return array
 	 */
 	public function getSubClasses($ancestor_class, $concrete_only = true)
 	{
 		return $this->Factory->getSubClasses($ancestor_class, $concrete_only);
 	}
 
 	/**
 	 * 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;
 	}
 
 	/**
 	 * Returns unit config for given prefix
 	 *
 	 * @param string $prefix
 	 * @return kUnitConfig
 	 * @access public
 	 */
 	public function getUnitConfig($prefix)
 	{
 		return $this->UnitConfigReader->getUnitConfig($prefix);
 	}
 
 
 	/**
 	 * Returns true if config exists and is allowed for reading
 	 *
 	 * @param string $prefix
 	 * @return bool
 	 */
 	public function prefixRegistred($prefix)
 	{
 		return $this->UnitConfigReader->prefixRegistered($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
 	 * @throws Exception
 	 * @deprecated
 	 */
 	public function handleSQLError($code, $msg, $sql)
 	{
 		return $this->_logger->handleSQLError($code, $msg, $sql);
 	}
 
 	/**
 	 * 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->getUnitConfig($current_prefix)->getParentPrefix()) {
 			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_template_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 emailAdmin($email_template_name, $to_user_id = null, $send_params = Array ())
 	{
 		return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_ADMIN, $to_user_id, $send_params);
 	}
 
 	/**
 	 * Triggers email event of type User
 	 *
 	 * @param string $email_template_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 emailUser($email_template_name, $to_user_id = null, $send_params = Array ())
 	{
 		return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_FRONTEND, $to_user_id, $send_params);
 	}
 
 	/**
 	 * Triggers general email event
 	 *
 	 * @param string $email_template_name
 	 * @param int $email_template_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 _email($email_template_name, $email_template_type, $to_user_id = null, $send_params = Array ())
 	{
 		$email = $this->makeClass('kEmail');
 		/* @var $email kEmail */
 
 		if ( !$email->findTemplate($email_template_name, $email_template_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 = '')
 	{
 		while ( ob_get_level() ) {
 			ob_end_clean();
 		}
 
 		if ( $this->isDebugMode() ) {
 			$message .= $this->Debugger->printReport(true);
 		}
 
 		$this->HTML = $message;
 		$this->_outputPage();
 	}
 
 	/**
 	 * 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)
 	{
 		$this->setContentType('text/xml');
 
 		return $xml_version ? '<?xml version="' . $xml_version . '" encoding="' . 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->routers = $data['Application.Routers'];
 		$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.Routers' => $this->routers,
 				'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 $already_set = false;
 
 		if ( $already_set ) {
 			return;
 		}
 
 		$header = 'Content-type: ' . $content_type;
 
 		if ( !isset($include_charset) ) {
 			$include_charset = $content_type = 'text/html' || $content_type == 'text/plain' || $content_type = 'text/xml';
 		}
 
 		if ( $include_charset ) {
 			$header .= '; charset=' . CHARSET;
 		}
 
 		$already_set = true;
 		header($header);
 	}
 
 	/**
 	 * Posts message to event log
 	 *
 	 * @param string $message
 	 * @param int $code
 	 * @param bool $write_now Allows further customization of log record by returning kLog object
 	 * @return bool|int|kLogger
 	 * @access public
 	 */
 	public function log($message, $code = null, $write_now = false)
 	{
 		$log = $this->_logger->prepare($message, $code)->addSource($this->_logger->createTrace(null, 1));
 
 		if ( $write_now ) {
 			return $log->write();
 		}
 
 		return $log;
 	}
 
 	/**
 	 * Deletes log with given id from database or disk, when database isn't available
 	 *
 	 * @param int $unique_id
 	 * @param int $storage_medium
 	 * @return void
 	 * @access public
 	 * @throws InvalidArgumentException
 	 */
 	public function deleteLog($unique_id, $storage_medium = kLogger::LS_AUTOMATIC)
 	{
 		$this->_logger->delete($unique_id, $storage_medium);
 	}
 
 	/**
 	 * Returns the client IP address.
 	 *
 	 * @return string The client IP address
 	 * @access public
 	 */
 	public function getClientIp()
 	{
 		return $this->HttpQuery->getClientIp();
 	}
 }
Index: branches/5.3.x/core/kernel/managers/cache_manager.php
===================================================================
--- branches/5.3.x/core/kernel/managers/cache_manager.php	(revision 16170)
+++ branches/5.3.x/core/kernel/managers/cache_manager.php	(revision 16171)
@@ -1,852 +1,852 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 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!');
 
 class kCacheManager extends kBase implements kiCacheable {
 
 	/**
 	 * Used variables from SystemSettings table
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $configVariables = Array();
 
 	/**
 	 * Used variables from SystemSettings table retrieved from unit cache
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $originalConfigVariables = Array ();
 
 	/**
 	 * IDs of config variables used in current run (for caching)
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $configIDs = Array ();
 
 	/**
 	 * IDs of config variables retrieved from unit cache
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $originalConfigIDs = Array ();
 
 	/**
 	 * Object of memory caching class
 	 *
 	 * @var kCache
 	 * @access protected
 	 */
 	protected $cacheHandler = null;
 
 	protected $temporaryCache = Array (
 		'registerAggregateTag' => Array (),
 		'registerScheduledTask' => Array (),
 		'registerHook' => Array (),
 		'registerBuildEvent' => Array (),
 		'registerAggregateTag' => Array (),
 	);
 
 	/**
 	 * Name of database table, where configuration settings are stored
 	 *
 	 * @var string
 	 * @access protected
 	 */
 	protected $settingTableName = '';
 
 	/**
 	 * Set's references to kApplication and DBConnection interface class instances
 	 *
 	 * @access public
 	 */
 	public function __construct()
 	{
 		parent::__construct();
 
 		$this->settingTableName = TABLE_PREFIX . 'SystemSettings';
 
 		if ( defined('IS_INSTALL') && IS_INSTALL ) {
 			// table substitution required, so "root" can perform login to upgrade to 5.2.0, where setting table was renamed
 			if ( !$this->Application->TableFound(TABLE_PREFIX . 'SystemSettings') ) {
 				$this->settingTableName = TABLE_PREFIX . 'ConfigurationValues';
 			}
 		}
 	}
 	/**
 	 * Creates caching manager instance
 	 *
 	 * @access public
 	 */
 	public function InitCache()
 	{
 		$this->cacheHandler = $this->Application->makeClass('kCache');
 	}
 
 	/**
 	 * Returns cache key, used to cache phrase and configuration variable IDs used on current page
 	 *
 	 * @return string
 	 * @access protected
 	 */
 	protected function getCacheKey()
 	{
 		// TODO: maybe language part isn't required, since same phrase from different languages have one ID now
 		return $this->Application->GetVar('t') . $this->Application->GetVar('m_theme') . $this->Application->GetVar('m_lang') . $this->Application->isAdmin;
 	}
 
 	/**
 	 * Loads phrases and configuration variables, that were used on this template last time
 	 *
 	 * @access public
 	 */
 	public function LoadApplicationCache()
 	{
 		$phrase_ids = $config_ids = Array ();
 
 		$sql = 'SELECT PhraseList, ConfigVariables
 				FROM ' . TABLE_PREFIX . 'PhraseCache
 				WHERE Template = ' . $this->Conn->qstr( md5($this->getCacheKey()) );
 		$res = $this->Conn->GetRow($sql);
 
 		if ($res) {
 			if ( $res['PhraseList'] ) {
 				$phrase_ids = explode(',', $res['PhraseList']);
 			}
 
 			if ( $res['ConfigVariables'] ) {
 				$config_ids = array_diff( explode(',', $res['ConfigVariables']), $this->originalConfigIDs);
 			}
 		}
 
 		$this->Application->Phrases->Init('phrases', '', null, $phrase_ids);
 		$this->configIDs = $this->originalConfigIDs = $config_ids;
 
 		$this->InitConfig();
 	}
 
 	/**
 	 * Updates phrases and configuration variables, that were used on this template
 	 *
 	 * @access public
 	 */
 	public function UpdateApplicationCache()
 	{
 		$update = false;
 
 		//something changed
 		$update = $update || $this->Application->Phrases->NeedsCacheUpdate();
 		$update = $update || (count($this->configIDs) && $this->configIDs != $this->originalConfigIDs);
 
 		if ($update) {
 			$fields_hash = Array (
 				'PhraseList' => implode(',', $this->Application->Phrases->Ids),
 				'CacheDate' => time(),
 				'Template' => md5( $this->getCacheKey() ),
 				'ConfigVariables' => implode(',', array_unique($this->configIDs)),
 			);
 
 			$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'PhraseCache', 'REPLACE');
 		}
 	}
 
 	/**
 	 * Loads configuration variables, that were used on this template last time
 	 *
 	 * @access protected
 	 */
 	protected function InitConfig()
 	{
 		if (!$this->originalConfigIDs) {
 			return ;
 		}
 
 		$sql = 'SELECT VariableValue, VariableName
 				FROM ' . $this->settingTableName . '
 			 	WHERE VariableId IN (' . implode(',', $this->originalConfigIDs) . ')';
 		$config_variables = $this->Conn->GetCol($sql, 'VariableName');
 
 		$this->configVariables = array_merge($this->configVariables, $config_variables);
 	}
 
 	/**
 	 * Returns configuration option value by name
 	 *
 	 * @param string $name
 	 * @return string
 	 * @access public
 	 */
 	public function ConfigValue($name)
 	{
 		$site_domain_override = Array (
 			'DefaultEmailSender' => 'AdminEmail',
 			'DefaultEmailRecipients' => 'DefaultEmailRecipients',
 		);
 
 		if ( isset($site_domain_override[$name]) ) {
 			$res = $this->Application->siteDomainField($site_domain_override[$name]);
 
 			if ( $res ) {
 				return $res;
 			}
 		}
 
 		if ( array_key_exists($name, $this->configVariables) ) {
 			return $this->configVariables[$name];
 		}
 
 		if ( defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound($this->settingTableName, true) ) {
 			return false;
 		}
 
 		$this->Conn->nextQueryCachable = true;
 		$sql = 'SELECT VariableId, VariableValue
 				FROM ' . $this->settingTableName . '
 				WHERE VariableName = ' . $this->Conn->qstr($name);
 		$res = $this->Conn->GetRow($sql);
 
 		if ( $res !== false ) {
 			$this->configIDs[] = $res['VariableId'];
 			$this->configVariables[$name] = $res['VariableValue'];
 
 			return $res['VariableValue'];
 		}
 
 		trigger_error('Usage of undefined configuration variable "<strong>' . $name . '</strong>"', E_USER_NOTICE);
 
 		return false;
 	}
 
 	/**
 	 * 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)
 	{
 		$this->configVariables[$name] = $value;
 
 		if ( $local_cache_only ) {
 			return;
 		}
 
 		$fields_hash = Array ('VariableValue' => $value);
 		$this->Conn->doUpdate($fields_hash, $this->settingTableName, 'VariableName = ' . $this->Conn->qstr($name));
 
 		if ( array_key_exists($name, $this->originalConfigVariables) && $value != $this->originalConfigVariables[$name] ) {
 			$this->DeleteUnitCache();
 		}
 	}
 
 	/**
 	 * Loads data, that was cached during unit config parsing
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function LoadUnitCache()
 	{
 		if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 			$data = $this->Application->getCache('master:configs_parsed', false, CacheSettings::$unitCacheRebuildTime);
 		}
 		else {
 			$data = $this->Application->getDBCache('configs_parsed', CacheSettings::$unitCacheRebuildTime);
 		}
 
 		if ( $data ) {
 			$cache = unserialize($data); // 126 KB all modules
 			unset($data);
 
 			$this->Application->InitManagers();
 
 			$this->Application->setFromCache($cache);
 
 			$aggregator = $this->Application->recallObject('TagsAggregator', 'kArray');
 			/* @var $aggregator kArray */
 
 			$aggregator->setFromCache($cache);
 			$this->setFromCache($cache);
 			unset($cache);
 
 			return true;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Empties factory and event manager cache (without storing changes)
 	 */
 	public function EmptyUnitCache()
 	{
 		// maybe discover keys automatically from corresponding classes
 		$cache_keys = Array (
 			'Factory.Files', 'Factory.ClassInfo', 'Factory.ClassTree', 'Factory.Namespaces', 'Factory.realClasses',
 			'ConfigReader.prefixFiles', 'ConfigCloner.clones',
 			'EventManager.beforeHooks', 'EventManager.afterHooks', 'EventManager.scheduledTasks', 'EventManager.buildEvents',
-			'Application.ReplacementTemplates', 'Application.RewriteListeners', 'Application.ModuleInfo',
+			'Application.ReplacementTemplates', 'Application.Routers', 'Application.ModuleInfo',
 			'Application.ConfigHash', 'Application.ConfigCacheIds',
 		);
 
 		$empty_cache = Array ();
 
 		foreach ($cache_keys as $cache_key) {
 			$empty_cache[$cache_key] = Array ();
 		}
 
 		$this->Application->setFromCache($empty_cache);
 		$this->setFromCache($empty_cache);
 
 		// Otherwise kModulesHelper indirectly used from includeConfigFiles won't work.
 		$this->Application->RegisterDefaultClasses();
 	}
 
 	/**
 	 * Updates data, that was parsed from unit configs this time
 	 *
 	 * @access public
 	 */
 	public function UpdateUnitCache()
 	{
 		$aggregator = $this->Application->recallObject('TagsAggregator', 'kArray');
 		/* @var $aggregator kArray */
 
 		$this->preloadConfigVars(); // preloading will put to cache
 
 		$cache = array_merge(
 			$this->Application->getToCache(),
 			$aggregator->getToCache(),
 			$this->getToCache()
 		);
 
 		$cache_rebuild_by = SERVER_NAME . ' (' . $this->Application->getClientIp() . ') - ' . date('d/m/Y H:i:s');
 
 		if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$this->Application->setCache('master:configs_parsed', serialize($cache));
 			$this->Application->setCache('master:last_cache_rebuild', $cache_rebuild_by);
 		}
 		else {
 			$this->Application->setDBCache('configs_parsed', serialize($cache));
 			$this->Application->setDBCache('last_cache_rebuild', $cache_rebuild_by);
 		}
 	}
 
 	public function delayUnitProcessing($method, $params)
 	{
 		if ($this->Application->InitDone) {
 			// init already done -> call immediately (happens during installation)
 			$function = Array (&$this->Application, $method);
 			call_user_func_array($function, $params);
 
 			return ;
 		}
 
 		$this->temporaryCache[$method][] = $params;
 	}
 
 	public function applyDelayedUnitProcessing()
 	{
 		foreach ($this->temporaryCache as $method => $method_calls) {
 			$function = Array (&$this->Application, $method);
 
 			foreach ($method_calls as $method_call) {
 				call_user_func_array($function, $method_call);
 			}
 
 			$this->temporaryCache[$method] = Array ();
 		}
 	}
 
 	/**
 	 * 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)
 	{
 		if ( isset($config_variables) && !array_intersect(array_keys($this->originalConfigVariables), $config_variables) ) {
 			// prevent cache reset, when given config variables are not in unit cache
 			return;
 		}
 
 		if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 			$this->Application->rebuildCache('master:configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime);
 		}
 		else {
 			$this->rebuildDBCache('configs_parsed', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime);
 		}
 	}
 
 	/**
 	 * Deletes cached section tree, used during permission checking and admin console tree display
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function DeleteSectionCache()
 	{
 		if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 			$this->Application->rebuildCache('master:sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime);
 		}
 		else {
 			$this->rebuildDBCache('sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime);
 		}
 	}
 
 	/**
 	 * Preloads 21 widely used configuration variables, so they will get to cache for sure
 	 *
 	 * @access protected
 	 */
 	protected function preloadConfigVars()
 	{
 		$config_vars = Array (
 			// session related
 			'SessionTimeout', 'SessionCookieName', 'SessionCookieDomains', 'SessionBrowserSignatureCheck',
 			'SessionIPAddressCheck', 'CookieSessions', 'KeepSessionOnBrowserClose', 'User_GuestGroup',
 			'User_LoggedInGroup', 'RegistrationUsernameRequired',
 
 			// output related
 			'UseModRewrite', 'UseContentLanguageNegotiation', 'UseOutputCompression', 'OutputCompressionLevel',
 			'Config_Site_Time', 'SystemTagCache', 'DefaultGridPerPage',
 
 			// tracking related
 			'UseChangeLog', 'UseVisitorTracking', 'ModRewriteUrlEnding', 'ForceModRewriteUrlEnding',
 			'RunScheduledTasksFromCron',
 		);
 
 		$escaped_config_vars = $this->Conn->qstrArray($config_vars);
 
 		$sql = 'SELECT VariableId, VariableName, VariableValue
 				FROM ' . $this->settingTableName . '
 				WHERE VariableName IN (' . implode(',', $escaped_config_vars) . ')';
 		$data = $this->Conn->Query($sql, 'VariableId');
 
 		foreach ($data as $variable_id => $variable_info) {
 			$this->configIDs[] = $variable_id;
 			$this->configVariables[ $variable_info['VariableName'] ] = $variable_info['VariableValue'];
 		}
 	}
 
 	/**
 	 * Sets data from cache to object
 	 *
 	 * Used for cases, when ConfigValue is called before LoadApplicationCache method (e.g. session init, url engine init)
 	 *
 	 * @param Array $data
 	 * @access public
 	 */
 	public function setFromCache(&$data)
 	{
 		$this->configVariables = $this->originalConfigVariables = $data['Application.ConfigHash'];
 		$this->configIDs = $this->originalConfigIDs = $data['Application.ConfigCacheIds'];
 	}
 
 	/**
 	 * 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 (
 			'Application.ConfigHash' => $this->configVariables,
 			'Application.ConfigCacheIds' => $this->configIDs,
 
 			// not in use, since it only represents template specific values, not global ones
 			// 'Application.Caches.ConfigVariables' => $this->originalConfigIDs,
 		);
 	}
 
 	/**
 	 * Returns caching type (none, memory, temporary)
 	 *
 	 * @param int $caching_type
 	 * @return bool
 	 * @access public
 	 */
 	public function isCachingType($caching_type)
 	{
 		return $this->cacheHandler->getCachingType() == $caching_type;
 	}
 
 	/**
 	 * 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->cacheHandler->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->cacheHandler->setCache($key, $value, $expiration);
 	}
 
 	/**
 	 * Stores new $value in cache with $key name (only if not there already)
 	 *
 	 * @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->cacheHandler->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->cacheHandler->rebuildCache($name, $mode, $max_rebuilding_time);
 	}
 
 	/**
 	 * Deletes key from cache
 	 *
 	 * @param string $key
 	 * @return void
 	 * @access public
 	 */
 	public function deleteCache($key)
 	{
 		$this->cacheHandler->delete($key);
 	}
 
 	/**
 	 * Reset's all memory cache at once
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function resetCache()
 	{
 		$this->cacheHandler->reset();
 	}
 
 	/**
 	 * 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)
 	{
 		// no serials in cache key OR cache is outdated
 		$rebuilding = false;
 		$wait_seconds = $max_rebuild_seconds;
 
 		while (true) {
 			$cached_data = $this->_getDBCache(Array ($name, $name . '_rebuilding', $name . '_rebuild'));
 
 			if ( $cached_data[$name . '_rebuild'] ) {
 				// cache rebuild requested -> rebuild now
 				$this->deleteDBCache($name . '_rebuild');
 
 				if ( $this->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M1]') ) {
 					return false;
 				}
 			}
 
 			$cache = $cached_data[$name];
 			$rebuilding = $cached_data[$name . '_rebuilding'];
 
 			if ( ($cache === false) && (!$rebuilding || $wait_seconds == 0) ) {
 				// cache missing and nobody rebuilding it -> rebuild; enough waiting for cache to be ready
 				$this->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M2' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']');
 
 				return false;
 			}
 			elseif ( $cache !== false ) {
 				// cache present -> return it
 				$this->cacheHandler->storeStatistics($name, $rebuilding ? 'h' : 'H');
 
 				return $cache;
 			}
 
 			$wait_seconds -= kCache::WAIT_STEP;
 			sleep(kCache::WAIT_STEP);
 		}
 
 		$this->rebuildDBCache($name, kCache::REBUILD_NOW, $max_rebuild_seconds, '[M3' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']');
 
 		return false;
 	}
 
 	/**
 	 * Returns value from database cache
 	 *
 	 * @param string|Array $names key name
 	 * @return mixed
 	 * @access protected
 	 */
 	protected function _getDBCache($names)
 	{
 		$res = Array ();
 		$names = (array)$names;
 		$this->Conn->nextQueryCachable = true;
 
 		$sql = 'SELECT Data, Cached, LifeTime, VarName
 				FROM ' . TABLE_PREFIX . 'SystemCache
 				WHERE VarName IN (' . implode(',', $this->Conn->qstrArray($names)) . ')';
 		$cached_data = $this->Conn->Query($sql, 'VarName');
 
 		foreach ($names as $name) {
 			if ( !isset($cached_data[$name]) ) {
 				$res[$name] = false;
 				continue;
 			}
 
 			$lifetime = (int)$cached_data[$name]['LifeTime']; // in seconds
 
 			if ( ($lifetime > 0) && ($cached_data[$name]['Cached'] + $lifetime < time()) ) {
 				// delete expired
 				$this->Conn->nextQueryCachable = true;
 
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache
 						WHERE VarName = ' . $this->Conn->qstr($name);
 				$this->Conn->Query($sql);
 
 				$res[$name] = false;
 				continue;
 			}
 
 			$res[$name] = $cached_data[$name]['Data'];
 		}
 
 		return count($res) == 1 ? array_pop($res) : $res;
 	}
 
 	/**
 	 * 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->cacheHandler->storeStatistics($name, 'WU');
 
 		$this->deleteDBCache($name . '_rebuilding');
 		$this->_setDBCache($name, $value, $expiration);
 	}
 
 	/**
 	 * Sets value to database cache
 	 *
 	 * @param string $name
 	 * @param mixed $value
 	 * @param int|bool $expiration
 	 * @param string $insert_type
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _setDBCache($name, $value, $expiration = false, $insert_type = 'REPLACE')
 	{
 		if ( (int)$expiration <= 0 ) {
 			$expiration = -1;
 		}
 
 		$fields_hash = Array (
 			'VarName' => $name,
 			'Data' => &$value,
 			'Cached' => time(),
 			'LifeTime' => (int)$expiration,
 		);
 
 		$this->Conn->nextQueryCachable = true;
 
 		return $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'SystemCache', $insert_type);
 	}
 
 	/**
 	 * Sets value to database cache
 	 *
 	 * @param string $name
 	 * @param mixed $value
 	 * @param int|bool $expiration
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _addDBCache($name, $value, $expiration = false)
 	{
 		return $this->_setDBCache($name, $value, $expiration, 'INSERT');
 	}
 
 	/**
 	 * Sets rebuilding mode for given cache
 	 *
 	 * @param string $name
 	 * @param int $mode
 	 * @param int $max_rebuilding_time
 	 * @param string $miss_type
 	 * @return bool
 	 * @access public
 	 */
 	public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0, $miss_type = 'M')
 	{
 		if ( !isset($mode) || $mode == kCache::REBUILD_NOW ) {
 			$this->cacheHandler->storeStatistics($name, $miss_type);
 
 			if ( !$max_rebuilding_time ) {
 				return true;
 			}
 
 			if ( !$this->_addDBCache($name . '_rebuilding', 1, $max_rebuilding_time) ) {
 				$this->cacheHandler->storeStatistics($name, 'l');
 
 				return false;
 			}
 
 			$this->deleteDBCache($name . '_rebuild');
 			$this->cacheHandler->storeStatistics($name, 'L');
 		}
 		elseif ( $mode == kCache::REBUILD_LATER ) {
 			$this->_setDBCache($name . '_rebuild', 1, 0);
 			$this->deleteDBCache($name . '_rebuilding');
 		}
 
 		return true;
 	}
 
 	/**
 	 * Deletes key from database cache
 	 *
 	 * @param string $name
 	 * @return void
 	 * @access public
 	 */
 	public function deleteDBCache($name)
 	{
 		$sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemCache
 				WHERE VarName = ' . $this->Conn->qstr($name);
 		$this->Conn->Query($sql);
 	}
 
 	/**
 	 * 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)
 	{
 		$pascal_case_prefix = implode('', array_map('ucfirst', explode('-', $prefix)));
 		$serial_name = $pascal_case_prefix . (isset($id) ? 'IDSerial:' . $id : 'Serial');
 
 		if ($increment) {
 			if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->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;
 	}
 
 	/**
 	 * 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
 	 * @access public
 	 */
 	public 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 . 'Categories
 					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);
 	}
 }
Index: branches/5.3.x/core/kernel/managers/url_processor.php
===================================================================
--- branches/5.3.x/core/kernel/managers/url_processor.php	(revision 16170)
+++ branches/5.3.x/core/kernel/managers/url_processor.php	(revision 16171)
@@ -1,127 +1,133 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 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!');
 
 abstract class kUrlProcessor extends kBase {
 
 	/**
 	 * Reference to kUrlProcessor class instance
 	 *
 	 * @var kUrlManager
 	 */
 	protected $manager = null;
 
 	public function __construct(&$manager)
 	{
 		parent::__construct();
 
 		$this->setManager($manager);
 	}
 
 	/**
 	 * Sets reference to url manager
 	 *
 	 * @param kUrlManager $manager
 	 * @return void
 	 * @access public
 	 */
 	public function setManager(&$manager)
 	{
 		$this->manager =& $manager;
 	}
 
 	/**
 	 * Returns sorted array of passed prefixes (to build url from)
 	 *
 	 * @param string $pass
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getPassInfo($pass = 'all')
 	{
 		if ( !$pass ) {
 			$pass = 'all';
 		}
 
 		$pass = trim(
 				preg_replace(
 					'/(?<=,|\\A)all(?=,|\\z)/',
 					trim($this->Application->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->Application->getUnitConfig($prefix_only)->getRewritePriority(0);
+
+			if ( isset($this->Application->routers[$prefix_only]) ) {
+				$sorted[$prefix] = (int)$this->Application->routers[$prefix_only]['priority'];
+			}
+			else {
+				$sorted[$prefix] = 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;
 	}
 
 	/**
 	 * Builds url
 	 *
 	 * @param string $t
 	 * @param Array $params
 	 * @param string $pass
 	 * @param bool $pass_events
 	 * @param bool $env_var
 	 * @return string
 	 * @access public
 	 */
 	abstract public function build($t, $params, $pass='all', $pass_events = false, $env_var = true);
 
 	/**
 	 * Parses given string into a set of variables
 	 *
 	 * @abstract
 	 * @param string $string
 	 * @param string $pass_name
 	 * @return Array
 	 * @access public
 	 */
 	abstract public function parse($string, $pass_name = 'passed');
 
 	/**
 	 * Builds env part that corresponds prefix passed
 	 *
 	 * @param string $prefix_special item's prefix & [special]
 	 * @param Array $params url params
 	 * @param bool $pass_events
 	 * @return string
 	 * @access protected
 	 */
 	abstract protected function BuildModuleEnv($prefix_special, &$params, $pass_events = false);
-}
\ No newline at end of file
+}
Index: branches/5.3.x/core/kernel/managers/rewrite_url_processor.php
===================================================================
--- branches/5.3.x/core/kernel/managers/rewrite_url_processor.php	(revision 16170)
+++ branches/5.3.x/core/kernel/managers/rewrite_url_processor.php	(revision 16171)
@@ -1,1080 +1,1060 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 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!');
 
 class kRewriteUrlProcessor extends kUrlProcessor {
 
 	/**
 	 * Holds a reference to httpquery
 	 *
 	 * @var kHttpQuery
 	 * @access protected
 	 */
 	protected $HTTPQuery = null;
 
 	/**
-	 * Urls parts, that needs to be matched by rewrite listeners
+	 * Urls parts, that needs to be matched by routers.
 	 *
-	 * @var Array
-	 * @access protected
+	 * @var array
 	 */
-	protected $_partsToParse = Array ();
+	protected $_partsToParse = array();
 
 	/**
 	 * Category item prefix, that was found
 	 *
 	 * @var string|boolean
 	 * @access protected
 	 */
 	protected $modulePrefix = false;
 
 	/**
 	 * Template aliases for current theme
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_templateAliases = null;
 
 	/**
 	 * Domain-based primary language id
 	 *
 	 * @var int
 	 * @access public
 	 */
 	public $primaryLanguageId = false;
 
 	/**
 	 * Domain-based primary theme id
 	 *
 	 * @var int
 	 * @access public
 	 */
 	public $primaryThemeId = false;
 
 	/**
 	 * Possible url endings from ModRewriteUrlEnding configuration variable
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_urlEndings = Array ('.html', '/', '');
 
 	/**
-	 * Factory storage sub-set, containing mod-rewrite listeners, used during url building and parsing
+	 * Factory storage sub-set, containing routers, used during url building and parsing.
 	 *
-	 * @var Array
-	 * @access protected
+	 * @var array
 	 */
-	protected $rewriteListeners = Array ();
+	protected $routers = array();
 
 	/**
 	 * Constructor of kRewriteUrlProcessor class
 	 *
 	 * @param $manager
 	 * @return kRewriteUrlProcessor
 	 */
 	public function __construct(&$manager)
 	{
 		parent::__construct($manager);
 
 		$this->HTTPQuery = $this->Application->recallObject('kHTTPQuery');
 
 		// domain based primary language
 		$this->primaryLanguageId = $this->Application->siteDomainField('PrimaryLanguageId');
 
 		if (!$this->primaryLanguageId) {
 			// when domain-based language not found -> use site-wide language
 			$this->primaryLanguageId = $this->Application->GetDefaultLanguageId();
 		}
 
 		// domain based primary theme
 		$this->primaryThemeId = $this->Application->siteDomainField('PrimaryThemeId');
 
 		if (!$this->primaryThemeId) {
 			// when domain-based theme not found -> use site-wide theme
 			$this->primaryThemeId = $this->Application->GetDefaultThemeId(true);
 		}
 
-		$this->_initRewriteListeners();
+		$this->initRouters();
 	}
 
 	/**
 	 * Sets module prefix.
 	 *
 	 * @param string $prefix Unit config prefix.
 	 *
 	 * @return void
 	 */
 	public function setModulePrefix($prefix)
 	{
 		$this->modulePrefix = $prefix;
 	}
 
 	/**
 	 * Parses url
 	 *
 	 * @return void
 	 */
 	public function parseRewriteURL()
 	{
 		$url = $this->Application->GetVar('_mod_rw_url_');
 
 		if ( $url ) {
 			$this->_redirectToDefaultUrlEnding($url);
 			$url = $this->_removeUrlEnding($url);
 		}
 
 		$cached = $this->_getCachedUrl($url);
 
 		if ( $cached !== false ) {
 			$vars = $cached['vars'];
 			$passed = $cached['passed'];
 		}
 		else {
 			$vars = $this->parse($url);
 			$passed = $vars['pass']; // also used in bottom of this method
 			unset($vars['pass']);
 
 			if ( !$this->_partsToParse ) {
 				// don't cache 404 Not Found
 				$this->_setCachedUrl($url, Array ('vars' => $vars, 'passed' => $passed));
 			}
 
 			if ( $this->Application->GetVarDirect('t', 'Post') ) {
 				// template from POST overrides template from URL.
 				$vars['t'] = $this->Application->GetVarDirect('t', 'Post');
 
 				if ( isset($vars['is_virtual']) && $vars['is_virtual'] ) {
 					$vars['m_cat_id'] = 0; // this is virtual template category (for Proj-CMS)
 				}
 			}
 
 			unset($vars['is_virtual']);
 		}
 
 		foreach ($vars as $name => $value) {
 			$this->HTTPQuery->Set($name, $value);
 		}
 
 		$this->_initAll(); // also will use parsed language to load phrases from it
 
 		$this->HTTPQuery->finalizeParsing($passed);
 	}
 
 	/**
 	 * Detects url ending of given url
 	 *
 	 * @param string $url
 	 * @return string
 	 * @access protected
 	 */
 	protected function _findUrlEnding($url)
 	{
 		if ( !$url ) {
 			return '';
 		}
 
 		foreach ($this->_urlEndings as $url_ending) {
 			if ( mb_substr($url, mb_strlen($url) - mb_strlen($url_ending)) == $url_ending ) {
 				return $url_ending;
 			}
 		}
 
 		return '';
 	}
 
 	/**
 	 * Removes url ending from url
 	 *
 	 * @param string $url
 	 * @return string
 	 * @access protected
 	 */
 	protected function _removeUrlEnding($url)
 	{
 		$url_ending = $this->_findUrlEnding($url);
 
 		if ( !$url_ending ) {
 			return $url;
 		}
 
 		return mb_substr($url, 0, mb_strlen($url) - mb_strlen($url_ending));
 	}
 
 	/**
 	 * Redirects user to page with default url ending, where needed
 	 *
 	 * @param string $url
 	 * @return void
 	 * @access protected
 	 */
 	protected function _redirectToDefaultUrlEnding($url)
 	{
 		$default_ending = $this->Application->ConfigValue('ModRewriteUrlEnding');
 
 		if ( $this->_findUrlEnding($url) == $default_ending || !$this->Application->ConfigValue('ForceModRewriteUrlEnding') ) {
 			return;
 		}
 
 		// user manually typed url with different url ending -> redirect to same url with default url ending
 		$target_url = $this->Application->BaseURL() . $this->_removeUrlEnding($url) . $default_ending;
 
 		trigger_error('Mod-rewrite url "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>" without "<strong>' . $default_ending . '</strong>" line ending used', E_USER_NOTICE);
 		$this->Application->Redirect('external:' . $target_url, Array ('response_code' => 301));
 	}
 
 	/**
 	 * Returns url parsing result from cache or false, when not yet parsed
 	 *
 	 * @param $url
 	 * @return Array|bool
 	 * @access protected
 	 */
 	protected function _getCachedUrl($url)
 	{
 		if ( !$url || (defined('DBG_CACHE_URLS') && !DBG_CACHE_URLS) ) {
 			return false;
 		}
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'CachedUrls
 				WHERE Hash = ' . kUtil::crc32($url) . ' AND DomainId = ' . (int)$this->Application->siteDomainField('DomainId');
 		$data = $this->Conn->GetRow($sql);
 
 		if ( $data ) {
 			$lifetime = (int)$data['LifeTime']; // in seconds
 			if ( ($lifetime > 0) && ($data['Cached'] + $lifetime < TIMENOW) ) {
 				// delete expired
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls
 						WHERE UrlId = ' . $data['UrlId'];
 				$this->Conn->Query($sql);
 
 				return false;
 			}
 
 			return unserialize($data['ParsedVars']);
 		}
 
 		return false;
 	}
 
 	/**
 	 * Caches url
 	 *
 	 * @param string $url
 	 * @param Array $data
 	 * @return void
 	 * @access protected
 	 */
 	protected function _setCachedUrl($url, $data)
 	{
 		if ( !$url || (defined('DBG_CACHE_URLS') && !DBG_CACHE_URLS) ) {
 			return;
 		}
 
 		$vars = $data['vars'];
 		$passed = $data['passed'];
 		sort($passed);
 
 		// get expiration
 		if ( $vars['m_cat_id'] > 0 ) {
 			$sql = 'SELECT PageExpiration
 					FROM ' . TABLE_PREFIX . 'Categories
 					WHERE CategoryId = ' . $vars['m_cat_id'];
 			$expiration = $this->Conn->GetOne($sql);
 		}
 
 		// get prefixes
 		$prefixes = Array ();
 		$m_index = array_search('m', $passed);
 
 		if ( $m_index !== false ) {
 			unset($passed[$m_index]);
 
 			if ( $vars['m_cat_id'] > 0 ) {
 				$prefixes[] = 'c:' . $vars['m_cat_id'];
 			}
 
 			$prefixes[] = 'lang:' . $vars['m_lang'];
 			$prefixes[] = 'theme:' . $vars['m_theme'];
 		}
 
 		foreach ($passed as $prefix) {
 			if ( array_key_exists($prefix . '_id', $vars) && is_numeric($vars[$prefix . '_id']) ) {
 				$prefixes[] = $prefix . ':' . $vars[$prefix . '_id'];
 			}
 			else {
 				$prefixes[] = $prefix;
 			}
 		}
 
 		$fields_hash = Array (
 			'Url' => $url,
 			'Hash' => kUtil::crc32($url),
 			'DomainId' => (int)$this->Application->siteDomainField('DomainId'),
 			'Prefixes' => $prefixes ? '|' . implode('|', $prefixes) . '|' : '',
 			'ParsedVars' => serialize($data),
 			'Cached' => time(),
 			'LifeTime' => isset($expiration) && is_numeric($expiration) ? $expiration : -1
 		);
 
 		$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'CachedUrls');
 	}
 
 	/**
-	 * Loads all registered rewrite listeners, so they could be quickly accessed later
+	 * Loads all registered routers, so they could be quickly accessed later.
 	 *
-	 * @access protected
+	 * @return void
 	 */
-	protected function _initRewriteListeners()
+	protected function initRouters()
 	{
 		static $init_done = false;
 
-		if ($init_done || count($this->Application->RewriteListeners) == 0) {
-			// not initialized OR mod-rewrite url with missing config cache
-			return ;
+		// Not initialized OR mod-rewrite url with missing config cache.
+		if ( $init_done || count($this->Application->routers) == 0 ) {
+			return;
 		}
 
-		foreach ($this->Application->RewriteListeners as $prefix => $listener_data) {
-			foreach ($listener_data['listener'] as $index => $rewrite_listener) {
-				list ($listener_prefix, $listener_method) = explode(':', $rewrite_listener);
-
-				// don't use temp variable, since it will swap objects in Factory in PHP5
-				$this->rewriteListeners[$prefix][$index] = Array ();
-				$this->rewriteListeners[$prefix][$index][0] = $this->Application->recallObject($listener_prefix);
-				$this->rewriteListeners[$prefix][$index][1] = $listener_method;
-			}
+		foreach ( $this->Application->routers as $prefix => $router_data ) {
+			$this->routers[$prefix] = $this->Application->makeClass($router_data['class']);
 		}
 
 		define('MOD_REWRITE_URL_ENDING', $this->Application->ConfigValue('ModRewriteUrlEnding'));
-
 		$init_done = true;
 	}
 
 	/**
 	 * Parses given string into a set of variables (url in this case)
 	 *
 	 * @param string $string
 	 * @param string $pass_name
 	 * @return Array
 	 * @access public
 	 */
 	public function parse($string, $pass_name = 'pass')
 	{
 		// external url (could be back this website as well)
 		if ( preg_match('/external:(.*)/', $string, $regs) ) {
 			$string = $regs[1];
 		}
 
 		$vars = Array ();
 		$url_components = parse_url($string);
 
 		if ( isset($url_components['query']) ) {
 			parse_str(html_entity_decode($url_components['query']), $url_params);
 
 			if ( isset($url_params[ENV_VAR_NAME]) ) {
 				$url_params = array_merge($url_params, $this->manager->plain->parse($url_params[ENV_VAR_NAME], $pass_name));
 				unset($url_params[ENV_VAR_NAME]);
 			}
 
 			$vars = array_merge($vars, $url_params);
 		}
 
 		$this->_fixPass($vars, $pass_name);
 
 		if ( isset($url_components['path']) ) {
 			if ( BASE_PATH ) {
 				$string = preg_replace('/^' . preg_quote(BASE_PATH, '/') . '/', '', $url_components['path'], 1);
 			}
 			else {
 				$string = $url_components['path'];
 			}
 
 			$string = $this->_removeUrlEnding(trim($string, '/'));
 		}
 		else {
 			$string = '';
 		}
 
 		$url_parts = $string ? explode('/', mb_strtolower($string)) : Array ();
 
 		$this->setModulePrefix(false);
 		$this->_partsToParse = $url_parts;
 
 		if ( ($this->HTTPQuery->Get('rewrite') == 'on') || !$url_parts ) {
 			$this->_setDefaultValues($vars);
 		}
 
 		if ( !$url_parts ) {
 			$this->_initAll();
 			$vars['t'] = $this->Application->UrlManager->getTemplateName();
 
 			return $vars;
 		}
 
 		$this->_parseLanguage($url_parts, $vars);
 		$this->_parseTheme($url_parts, $vars);
 
 		// http://site-url/<language>/<theme>/<category>[_<category_page>]/<template>/<module_page>
 		// http://site-url/<language>/<theme>/<category>[_<category_page>]/<module_page> (category-based section template)
 		// http://site-url/<language>/<theme>/<category>[_<category_page>]/<template>/<module_item>
 		// http://site-url/<language>/<theme>/<category>[_<category_page>]/<module_item> (category-based detail template)
 		// http://site-url/<language>/<theme>/<rl_injections>/<category>[_<category_page>]/<rl_part> (customized url)
 
-		if ( !$this->_processRewriteListeners($url_parts, $vars) ) {
-			// rewrite listener wasn't able to determine template
+		if ( !$this->processRouters($url_parts, $vars) ) {
+			// Routers weren't able to determine template.
 			$this->_parsePhysicalTemplate($url_parts, $vars);
 
 			if ( ($this->modulePrefix === false) && $vars['m_cat_id'] && !$this->_partsToParse ) {
 				// no category item found, but category found and all url matched -> module index page
 				return $vars;
 			}
 		}
 
 		if ( $this->_partsToParse ) {
 			$vars = array_merge($vars, $this->manager->prepare404($vars['m_theme']));
 		}
 
 		return $vars;
 	}
 
 	/**
 	 * Ensures, that "m" is always in "pass" variable
 	 *
 	 * @param Array $vars
 	 * @param string $pass_name
 	 * @return void
 	 * @access protected
 	 */
 	protected function _fixPass(&$vars, $pass_name)
 	{
 		if ( isset($vars[$pass_name]) ) {
 			$vars[$pass_name] = array_unique(explode(',', 'm,' . $vars[$pass_name]));
 		}
 		else {
 			$vars[$pass_name] = Array ('m');
 		}
 	}
 
 	/**
 	 * Initializes theme & language based on parse results
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function _initAll()
 	{
 		$this->Application->VerifyThemeId();
 		$this->Application->VerifyLanguageId();
 
 		// No need, since we don't have any cached phrase IDs + nobody will use kPhraseCache::LanguageId soon.
 		// $this->Application->Phrases->Init('phrases');
 	}
 
 	/**
 	 * Sets default parsed values before actual url parsing (only, for empty url)
 	 *
 	 * @param Array $vars
 	 * @access protected
 	 */
 	protected function _setDefaultValues(&$vars)
 	{
 		$defaults = Array (
 			'm_cat_id' => 0, // no category
 			'm_cat_page' => 1, // first category page
 			'm_opener' => 's', // stay on same page
 			't' => 'index' // main site page
 		);
 
 		if ($this->primaryLanguageId) {
 			// domain-based primary language
 			$defaults['m_lang'] = $this->primaryLanguageId;
 		}
 
 		if ($this->primaryThemeId) {
 			// domain-based primary theme
 			$defaults['m_theme'] = $this->primaryThemeId;
 		}
 
 		foreach ($defaults as $default_key => $default_value) {
 			if ($this->HTTPQuery->Get($default_key) === false) {
 				$vars[$default_key] = $default_value;
 			}
 		}
 	}
 
 	/**
-	 * Processes url using rewrite listeners
+	 * Processes url using routers.
 	 *
 	 * Pattern: Chain of Command
 	 *
-	 * @param Array $url_parts
-	 * @param Array $vars
-	 * @return bool
-	 * @access protected
+	 * @param array $url_parts Url parts.
+	 * @param array $vars      Vars.
+	 *
+	 * @return boolean
 	 */
-	protected function _processRewriteListeners(&$url_parts, &$vars)
+	protected function processRouters(array &$url_parts, array &$vars)
 	{
-		$this->_initRewriteListeners();
+		$this->initRouters();
 		$page_number = $this->_parsePage($url_parts, $vars);
 
-		foreach ($this->rewriteListeners as $prefix => $listeners) {
-			// set default page
-			// $vars[$prefix . '_Page'] = 1; // will override page in session in case, when none is given in url
-
-			if ($page_number) {
-				// page given in url - use it
+		foreach ( $this->routers as $prefix => $router ) {
+			if ( $page_number ) {
+				// Page given in url - use it.
 				$vars[$prefix . '_id'] = 0;
 				$vars[$prefix . '_Page'] = $page_number;
 			}
 
-			// $listeners[1] - listener, used for parsing
-			$listener_result = $listeners[1][0]->$listeners[1][1](REWRITE_MODE_PARSE, $prefix, $vars, $url_parts);
-			if ($listener_result === false) {
-				// will not proceed to other methods
+			if ( $router->parse($url_parts, $vars) === false ) {
+				// Will not proceed to other methods.
 				return true;
 			}
 		}
 
-		// will proceed to other methods
+		// Will proceed to other methods.
 		return false;
 	}
 
 	/**
 	 * Set's page (when found) to all modules
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return string
 	 * @access protected
 	 *
-	 * @todo Should find a way, how to determine what rewrite listener page is it
+	 * @todo Should find a way, how to determine what router page is it.
 	 */
 	protected function _parsePage(&$url_parts, &$vars)
 	{
 		if (!$url_parts) {
 			return false;
 		}
 
 		$page_number = end($url_parts);
 		if (!is_numeric($page_number)) {
 			return false;
 		}
 
 		array_pop($url_parts);
 		$this->partParsed($page_number, 'rtl');
 
 		return $page_number;
 	}
 
 	/**
 	 * Gets language part from url
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _parseLanguage(&$url_parts, &$vars)
 	{
 		if (!$url_parts) {
 			return false;
 		}
 
 		$url_part = reset($url_parts);
 
 		$sql = 'SELECT LanguageId, IF(LOWER(PackName) = ' . $this->Conn->qstr($url_part) . ', 2, PrimaryLang) AS SortKey
 				FROM ' . TABLE_PREFIX . 'Languages
 				WHERE Enabled = 1
 				ORDER BY SortKey DESC';
 		$language_info = $this->Conn->GetRow($sql);
 
 		if ($language_info && $language_info['LanguageId'] && $language_info['SortKey']) {
 			// primary language will be selected in case, when $url_part doesn't match to other's language pack name
 			// don't use next enabled language, when primary language is disabled
 			$vars['m_lang'] = $language_info['LanguageId'];
 
 			if ($language_info['SortKey'] == 2) {
 				// language was found by pack name
 				array_shift($url_parts);
 				$this->partParsed($url_part);
 			}
 			elseif ($this->primaryLanguageId) {
 				// use domain-based primary language instead of site-wide primary language
 				$vars['m_lang'] = $this->primaryLanguageId;
 			}
 
 			return true;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Gets theme part from url
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return bool
 	 */
 	protected function _parseTheme(&$url_parts, &$vars)
 	{
 		if (!$url_parts) {
 			return false;
 		}
 
 		$url_part = reset($url_parts);
 
 		$sql = 'SELECT ThemeId, IF(LOWER(Name) = ' . $this->Conn->qstr($url_part) . ', 2, PrimaryTheme) AS SortKey, TemplateAliases
 				FROM ' . TABLE_PREFIX . 'Themes
 				WHERE Enabled = 1
 				ORDER BY SortKey DESC';
 		$theme_info = $this->Conn->GetRow($sql);
 
 		if ($theme_info && $theme_info['ThemeId'] && $theme_info['SortKey']) {
 			// primary theme will be selected in case, when $url_part doesn't match to other's theme name
 			// don't use next enabled theme, when primary theme is disabled
 			$vars['m_theme'] = $theme_info['ThemeId'];
 
 			if ($theme_info['TemplateAliases']) {
 				$this->_templateAliases = unserialize($theme_info['TemplateAliases']);
 			}
 			else {
 				$this->_templateAliases = Array ();
 			}
 
 			if ($theme_info['SortKey'] == 2) {
 				// theme was found by name
 				array_shift($url_parts);
 				$this->partParsed($url_part);
 			}
 			elseif ($this->primaryThemeId) {
 				// use domain-based primary theme instead of site-wide primary theme
 				$vars['m_theme'] = $this->primaryThemeId;
 			}
 
 			return true;
 		}
 
 		$vars['m_theme'] = 0; // required, because used later for category/template detection
 
 		return false;
 	}
 
 	/**
 	 * Parses real template name from url
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return bool
 	 */
 	protected function _parsePhysicalTemplate($url_parts, &$vars)
 	{
 		if ( !$url_parts ) {
 			return false;
 		}
 
 		$themes_helper = $this->Application->recallObject('ThemesHelper');
 		/* @var $themes_helper kThemesHelper */
 
 		do {
 			$index_added = false;
 			$template_path = implode('/', $url_parts);
 			$template_found = $themes_helper->getTemplateId($template_path, $vars['m_theme']);
 
 			if ( !$template_found ) {
 				$index_added = true;
 				$template_found = $themes_helper->getTemplateId($template_path . '/index', $vars['m_theme']);
 			}
 
 			if ( !$template_found ) {
 				array_shift($url_parts);
 			}
 		} while ( !$template_found && $url_parts );
 
 		if ( $template_found ) {
 			$template_parts = explode('/', $template_path);
 			$vars['t'] = $template_path . ($index_added ? '/index' : '');
 
 			while ( $template_parts ) {
 				$this->partParsed(array_pop($template_parts), 'rtl');
 			}
 
 			// 1. will damage actual category during category item review add process
 			// 2. will use "use_section" parameter of "m_Link" tag to gain same effect
 //			$vars['m_cat_id'] = $themes_helper->getPageByTemplate($template_path, $vars['m_theme']);
 
 			return true;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Returns environment variable values for given prefix (uses directly given params, when available)
 	 *
 	 * @param string $prefix_special
 	 * @param Array $params
 	 * @param bool $keep_events
 	 * @return Array
 	 * @access public
 	 */
 	public function getProcessedParams($prefix_special, &$params, $keep_events)
 	{
 		list ($prefix) = explode('.', $prefix_special);
 
 		$query_vars = $this->Application->getUnitConfig($prefix)->getQueryString(Array ());
 
 		if ( !$query_vars ) {
 			// given prefix doesn't use "env" variable to pass it's data
 			return false;
 		}
 
 		$event_key = array_search('event', $query_vars);
 		if ( $event_key ) {
 			// pass through event of this prefix
 			unset($query_vars[$event_key]);
 		}
 
 		if ( array_key_exists($prefix_special . '_event', $params) && !$params[$prefix_special . '_event'] ) {
 			// if empty event, then remove it from url
 			unset($params[$prefix_special . '_event']);
 		}
 
 		// if pass events is off and event is not implicity passed
 		if ( !$keep_events && !array_key_exists($prefix_special . '_event', $params) ) {
 			unset($params[$prefix_special . '_event']); // remove event from url if requested
 			//otherwise it will use value from get_var
 		}
 
 		$processed_params = Array ();
 		foreach ($query_vars as $var_name) {
 			// if value passed in params use it, otherwise use current from application
 			$var_name = $prefix_special . '_' . $var_name;
 			$processed_params[$var_name] = array_key_exists($var_name, $params) ? $params[$var_name] : $this->Application->GetVar($var_name);
 
 			if ( array_key_exists($var_name, $params) ) {
 				unset($params[$var_name]);
 			}
 		}
 
 		return $processed_params;
 	}
 
 	/**
 	 * Returns module item details template specified in given category custom field for given module prefix
 	 *
 	 * @param int|Array $category
 	 * @param string $module_prefix
 	 * @param int $theme_id
 	 * @return string
 	 * @access public
 	 * @todo Move to kPlainUrlProcessor
 	 */
 	public function GetItemTemplate($category, $module_prefix, $theme_id = null)
 	{
 		if ( !isset($theme_id) ) {
 			$theme_id = $this->Application->GetVar('m_theme');
 		}
 
 		$category_id = is_array($category) ? $category['CategoryId'] : $category;
 		$cache_key = __CLASS__ . '::' . __FUNCTION__ . '[%CIDSerial:' . $category_id . '%][%ThemeIDSerial:' . $theme_id . '%]' . $module_prefix;
 
 		$cached_value = $this->Application->getCache($cache_key);
 		if ( $cached_value !== false ) {
 			return $cached_value;
 		}
 
 		if ( !is_array($category) ) {
 			if ( $category == 0 ) {
 				$category = $this->Application->findModule('Var', $module_prefix, 'RootCat');
 			}
 			$sql = 'SELECT c.ParentPath, c.CategoryId
 					FROM ' . TABLE_PREFIX . 'Categories AS c
 					WHERE c.CategoryId = ' . $category;
 			$category = $this->Conn->GetRow($sql);
 		}
 		$parent_path = implode(',', explode('|', substr($category['ParentPath'], 1, -1)));
 
 		// item template is stored in module' system custom field - need to get that field Id
 		$primary_lang = $this->Application->GetDefaultLanguageId();
 		$item_template_field_id = $this->getItemTemplateCustomField($module_prefix);
 
 		// looking for item template through cats hierarchy sorted by parent path
 		$query = '	SELECT ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . ',
 								FIND_IN_SET(c.CategoryId, ' . $this->Conn->qstr($parent_path) . ') AS Ord1,
 								c.CategoryId, c.Name, ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . '
 					FROM ' . TABLE_PREFIX . 'Categories AS c
 					LEFT JOIN ' . TABLE_PREFIX . 'CategoryCustomData AS ccd
 					ON ccd.ResourceId = c.ResourceId
 					WHERE c.CategoryId IN (' . $parent_path . ') AND ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . ' != \'\'
 					ORDER BY FIND_IN_SET(c.CategoryId, ' . $this->Conn->qstr($parent_path) . ') DESC';
 		$item_template = $this->Conn->GetOne($query);
 
 		if ( !isset($this->_templateAliases) ) {
 			// when empty url OR mod-rewrite disabled
 
 			$themes_helper = $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$sql = 'SELECT TemplateAliases
 					FROM ' . TABLE_PREFIX . 'Themes
 					WHERE ThemeId = ' . (int)$themes_helper->getCurrentThemeId();
 			$template_aliases = $this->Conn->GetOne($sql);
 
 			$this->_templateAliases = $template_aliases ? unserialize($template_aliases) : Array ();
 		}
 
 		if ( substr($item_template, 0, 1) == '#' ) {
 			// it's template alias + "#" isn't allowed in filenames
 			$item_template = (string)getArrayValue($this->_templateAliases, $item_template);
 		}
 
 		$this->Application->setCache($cache_key, $item_template);
 
 		return $item_template;
 	}
 
 	/**
 	 * Returns category custom field id, where given module prefix item template name is stored
 	 *
 	 * @param string $module_prefix
 	 * @return int
 	 * @access public
 	 * @todo Move to kPlainUrlProcessor; decrease visibility, since used only during upgrade
 	 */
 	public function getItemTemplateCustomField($module_prefix)
 	{
 		$cache_key = __CLASS__ . '::' . __FUNCTION__ . '[%CfSerial%]:' . $module_prefix;
 		$cached_value = $this->Application->getCache($cache_key);
 
 		if ($cached_value !== false) {
 			return $cached_value;
 		}
 
 		$sql = 'SELECT CustomFieldId
 				FROM ' . TABLE_PREFIX . 'CustomFields
 				WHERE FieldName = ' . $this->Conn->qstr($module_prefix . '_ItemTemplate');
 		$item_template_field_id = $this->Conn->GetOne($sql);
 
 		$this->Application->setCache($cache_key, $item_template_field_id);
 
 		return $item_template_field_id;
 	}
 
 	/**
 	 * Marks url part as parsed
 	 *
 	 * @param string $url_part
 	 * @param string $parse_direction
 	 * @access public
 	 */
 	public function partParsed($url_part, $parse_direction = 'ltr')
 	{
 		if ( !$this->_partsToParse ) {
 			return ;
 		}
 
 		if ( $parse_direction == 'ltr' ) {
 			$expected_url_part = reset($this->_partsToParse);
 
 			if ( $url_part == $expected_url_part ) {
 				array_shift($this->_partsToParse);
 			}
 		}
 		else {
 			$expected_url_part = end($this->_partsToParse);
 
 			if ( $url_part == $expected_url_part ) {
 				array_pop($this->_partsToParse);
 			}
 		}
 
 		if ( $url_part != $expected_url_part ) {
 			trigger_error('partParsed: expected URL part "<strong>' . $expected_url_part . '</strong>", received URL part "<strong>' . $url_part . '</strong>"', E_USER_NOTICE);
 		}
 	}
 
 	/**
 	 * Determines if there is more to parse in url
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function moreToParse()
 	{
 		return count($this->_partsToParse) > 0;
 	}
 
 	/**
 	 * 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 build($t, $params, $pass = 'all', $pass_events = false, $env_var = false)
 	{
 		if ( $this->Application->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 = '';
 
 		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
+			$inject_parts = array(); // Url parts for beginning of url.
+			$params['t'] = $t; // Make template available for routers.
+			$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->Application->findModule('Var', $prefix) && $this->Application->getUnitConfig($prefix)->getCatalogItem();
 
-				if ( array_key_exists($prefix, $this->rewriteListeners) ) {
+				if ( array_key_exists($prefix, $this->routers) ) {
 					// 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;
 						}
 					}
 
 					// rewritten url part
 					$url_part = $this->BuildModuleEnv($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
+						// Router 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
+						// Router decided not to rewrite given $pass_element.
 						$env .= ':' . $this->manager->plain->BuildModuleEnv($pass_element, $params, $pass_events);
 					}
 				}
 				else {
 					$env .= ':' . $this->manager->plain->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('m', $params, $pass_events) . '/' . $ret;
 			$cat_processed = array_key_exists('category_processed', $params) && $params['category_processed'];
 
-			// remove temporary parameters used by listeners
+			// Remove temporary parameters used by routers.
 			unset($params['t'], $params['inject_parts'], $params['pass_template'], $params['pass_category'], $params['category_processed']);
 
 			$ret = trim($ret, '/');
 
 			if ( isset($params['url_ending']) ) {
 				if ( $ret ) {
 					$ret .= $params['url_ending'];
 				}
 
 				unset($params['url_ending']);
 			}
 			elseif ( $ret ) {
 				$ret .= MOD_REWRITE_URL_ENDING;
 			}
 
 			if ( $env ) {
 				$params[ENV_VAR_NAME] = ltrim($env, ':');
 			}
 		}
 
 		unset($params['pass'], $params['opener'], $params['m_event']);
 
 		// TODO: why?
 //		$ret = str_replace('%2F', '/', urlencode($ret));
 
 		if ( $params ) {
 			$params_str = http_build_query($params);
 			$ret .= '?' . str_replace('%23', '#', $params_str);
 		}
 
 		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
 	 * @return string
 	 * @access protected
 	 */
 	protected function BuildModuleEnv($prefix_special, &$params, $pass_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, $pass_events);
-
-		return $ret;
+		return $this->routers[$prefix]->buildWrapper($prefix_special, $params, $pass_events);
 	}
 }
Index: branches/5.3.x/core/kernel/utility/Router/AbstractCategoryItemRouter.php
===================================================================
--- branches/5.3.x/core/kernel/utility/Router/AbstractCategoryItemRouter.php	(nonexistent)
+++ branches/5.3.x/core/kernel/utility/Router/AbstractCategoryItemRouter.php	(revision 16171)
@@ -0,0 +1,266 @@
+<?php
+/**
+* @version	$Id$
+* @package	In-Portal
+* @copyright	Copyright (C) 1997 - 2015 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!');
+
+abstract class AbstractCategoryItemRouter extends AbstractRouter
+{
+
+	/**
+	 * Builds url part.
+	 *
+	 * @return boolean Return true to continue to next router; return false not to rewrite given prefix.
+	 */
+	protected function build()
+	{
+		static $default_per_page = array();
+
+		$ret = '';
+		$build_params = $this->extractBuildParams();
+
+		if ( $build_params === false ) {
+			return '';
+		}
+
+		$this->keepEvent();
+		list ($prefix) = explode('.', $this->buildPrefix);
+
+		if ( !array_key_exists($prefix, $default_per_page) ) {
+			$list_helper = $this->Application->recallObject('ListHelper');
+			/* @var $list_helper ListHelper */
+
+			$default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix);
+		}
+
+		if ( $build_params[$this->buildPrefix . '_id'] ) {
+			$category_id = $this->getBuildParam('m_cat_id');
+
+			// If template is also item template of category, then remove template.
+			$template = $this->getBuildParam('t', false);
+			$item_template = $this->getUrlProcessor()->GetItemTemplate($category_id, $prefix);
+
+			if ( $template == $item_template || strtolower($template) == '__default__' ) {
+				// Given template is also default template for this category item or '__default__' given.
+				$this->setBuildParam('pass_template', false);
+			}
+
+			// Get item's filename.
+			if ( $prefix == 'bb' ) {
+				$ret .= 'bb_' . $build_params[$this->buildPrefix . '_id'] . '/';
+			}
+			else {
+				$filename = $this->getFilename($prefix, $build_params[$this->buildPrefix . '_id'], $category_id);
+
+				if ( $filename !== false ) {
+					$ret .= $filename . '/';
+				}
+			}
+		}
+		else {
+			if ( $build_params[$this->buildPrefix . '_Page'] == 1 ) {
+				// When printing category items and we are on the 1st page -> there is no information about
+				// category item prefix and $params['pass_category'] will not be added automatically.
+				$this->setBuildParam('pass_category', true);
+			}
+			elseif ( $build_params[$this->buildPrefix . '_Page'] > 1 ) {
+				$this->setBuildParam('page', $build_params[$this->buildPrefix . '_Page']);
+			}
+
+			$per_page = $build_params[$this->buildPrefix . '_PerPage'];
+
+			if ( $per_page && ($per_page != $default_per_page[$prefix]) ) {
+				$this->setBuildParam('per_page', $build_params[$this->buildPrefix . '_PerPage']);
+			}
+		}
+
+		return mb_strtolower(rtrim($ret, '/'));
+	}
+
+	/**
+	 * Parses url part.
+	 *
+	 * @param array $url_parts Url parts to parse.
+	 * @param array $params    Parameters, that are used for url building or created during url parsing.
+	 *
+	 * @return boolean Return true to continue to next router; return false to stop processing at this router.
+	 */
+	public function parse(array &$url_parts, array &$params)
+	{
+		$module_prefix = $this->parseCategoryItemUrl($url_parts, $params);
+
+		if ( $module_prefix !== false ) {
+			$params['pass'][] = $module_prefix;
+			$this->getUrlProcessor()->setModulePrefix($module_prefix);
+		}
+
+		return true;
+	}
+
+	/**
+	 * Returns item's filename that corresponds id passed. If possible, then get it from cache.
+	 *
+	 * @param string  $prefix      Prefix.
+	 * @param integer $id          Id.
+	 * @param integer $category_id Category Id.
+	 *
+	 * @return string
+	 */
+	protected function getFilename($prefix, $id, $category_id = null)
+	{
+		$category_id = isset($category_id) ? $category_id : $this->Application->GetVar('m_cat_id');
+
+		$serial_name = $this->Application->incrementCacheSerial($prefix, $id, false);
+		$cache_key = 'filenames[%' . $serial_name . '%]:' . (int)$category_id;
+		$filename = $this->Application->getCache($cache_key);
+
+		if ( $filename === false ) {
+			$this->Conn->nextQueryCachable = true;
+			$config = $this->Application->getUnitConfig($prefix);
+
+			$sql = 'SELECT ResourceId
+					FROM ' . $config->getTableName() . '
+					WHERE ' . $config->getIDField() . ' = ' . $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->Application->setCache($cache_key, $filename);
+			}
+		}
+
+		return $filename;
+	}
+
+	/**
+	 * Sets template and id, corresponding to category item given in url
+	 *
+	 * @param array $url_parts Url parts.
+	 * @param array $params    Params.
+	 *
+	 * @return boolean|string
+	 */
+	protected function parseCategoryItemUrl(array &$url_parts, array &$params)
+	{
+		if ( !$url_parts ) {
+			return false;
+		}
+
+		$item_filename = end($url_parts);
+
+		if ( is_numeric($item_filename) ) {
+			// This page, don't process here.
+			return false;
+		}
+
+		if ( $this->buildPrefix == 'bb' && preg_match('/^bb_([\d]+)/', $item_filename, $regs) ) {
+			// Process topics separately, because they don't use item filenames.
+			array_pop($url_parts);
+			$this->partParsed($item_filename, 'rtl');
+
+			return $this->parseTopicUrl($regs[1], $params);
+		}
+
+		$cat_item = $this->findCategoryItem((int)$params['m_cat_id'], $item_filename);
+
+		if ( $cat_item !== false ) {
+			// Item found.
+			$module_prefix = $cat_item['ItemPrefix'];
+			$item_template = $this->getUrlProcessor()->GetItemTemplate($cat_item, $module_prefix, $params['m_theme']);
+
+			// Converting ResourceId to corresponding Item id.
+			$module_config = $this->Application->getUnitConfig($module_prefix);
+
+			$sql = 'SELECT ' . $module_config->getIDField() . '
+					FROM ' . $module_config->getTableName() . '
+					WHERE ResourceId = ' . $cat_item['ItemResourceId'];
+			$item_id = $this->Conn->GetOne($sql);
+
+			if ( $item_id ) {
+				array_pop($url_parts);
+				$this->partParsed($item_filename, 'rtl');
+
+				if ( $item_template ) {
+					// When template is found in category -> set it.
+					$params['t'] = $item_template;
+				}
+
+				// We have category item id.
+				$params[$module_prefix . '_id'] = $item_id;
+
+				return $module_prefix;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set's template and topic id corresponding to topic given in url
+	 *
+	 * @param integer $topic_id Topic ID.
+	 * @param array   $params   Params.
+	 *
+	 * @return string
+	 */
+	protected function parseTopicUrl($topic_id, array &$params)
+	{
+		$sql = 'SELECT c.ParentPath, c.CategoryId
+				FROM ' . TABLE_PREFIX . 'Categories AS c
+				WHERE c.CategoryId = ' . (int)$params['m_cat_id'];
+		$cat_item = $this->Conn->GetRow($sql);
+
+		$item_template = $this->getUrlProcessor()->GetItemTemplate($cat_item, 'bb', $params['m_theme']);
+
+		if ( $item_template ) {
+			$params['t'] = $item_template;
+		}
+
+		$params['bb_id'] = $topic_id;
+
+		return 'bb';
+	}
+
+	/**
+	 * Locating the item in CategoryItems by filename to detect its ItemPrefix and its category ParentPath.
+	 *
+	 * @param integer $category_id Category.
+	 * @param string  $filename    Filename.
+	 *
+	 * @return string
+	 */
+	protected function findCategoryItem($category_id, $filename)
+	{
+		static $cache = array();
+
+		$cache_key = $category_id . ':' . $filename;
+
+		if ( !isset($cache[$cache_key]) ) {
+			$sql = 'SELECT ci.ItemResourceId, ci.ItemPrefix, c.ParentPath, ci.CategoryId
+					FROM ' . TABLE_PREFIX . 'CategoryItems AS ci
+					LEFT JOIN ' . TABLE_PREFIX . 'Categories AS c ON c.CategoryId = ci.CategoryId
+					WHERE (ci.CategoryId = ' . $category_id . ') AND (ci.Filename = ' . $this->Conn->qstr($filename) . ')';
+			$cache[$cache_key] = $this->Conn->GetRow($sql);
+		}
+
+		$category_item = $cache[$cache_key];
+
+		return $category_item && $category_item['ItemPrefix'] == $this->buildPrefix ? $category_item : false;
+	}
+
+}

Property changes on: branches/5.3.x/core/kernel/utility/Router/AbstractCategoryItemRouter.php
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/core/kernel/utility/Router/AbstractRouter.php
===================================================================
--- branches/5.3.x/core/kernel/utility/Router/AbstractRouter.php	(nonexistent)
+++ branches/5.3.x/core/kernel/utility/Router/AbstractRouter.php	(revision 16171)
@@ -0,0 +1,295 @@
+<?php
+/**
+ * @version    $Id$
+ * @package    In-Portal
+ * @copyright    Copyright (C) 1997 - 2015 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!');
+
+abstract class AbstractRouter extends kBase
+{
+
+	/**
+	 * Include debugging url into build urls.
+	 *
+	 * @var boolean
+	 */
+	protected $debug = false;
+
+	/**
+	 * Prefix, that router uses for system integration. Could contain special.
+	 *
+	 * @var string
+	 */
+	protected $buildPrefix = '';
+
+	/**
+	 * Parameters, used during url building.
+	 *
+	 * @var array
+	 */
+	protected $buildParams = array();
+
+	/**
+	 * Router's default template.
+	 *
+	 * @var string
+	 */
+	protected $defaultTemplate = '';
+
+	/**
+	 * Include event from page url into links, generated on that page.
+	 *
+	 * @var boolean
+	 */
+	protected $keepEventFromUrl = false;
+
+	/**
+	 * Creates an instance of router
+	 */
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->buildPrefix = $this->getPrefix();
+		$this->debug = $this->Application->isDebugMode() && (defined('DBG_ROUTING') && DBG_ROUTING);
+	}
+
+	/**
+	 * Add debug information to every returned url part.
+	 *
+	 * @param string|array $url_part Url part.
+	 *
+	 * @return array|string
+	 */
+	protected function debug($url_part)
+	{
+		if ( is_array($url_part) ) {
+			return array_map(array($this, 'debug'), $url_part);
+		}
+
+		return '{' . $this->buildPrefix . ':' . $url_part . '}';
+	}
+
+	/**
+	 * Returns unit config's prefix, that is associated with this router.
+	 *
+	 * @return string
+	 */
+	abstract public function getPrefix();
+
+	/**
+	 * Returns weight of this router among other routers.
+	 *
+	 * @return float|boolean Number - when order is important; false, when order isn't important.
+	 */
+	public function getWeight()
+	{
+		return false;
+	}
+
+	/**
+	 * Wraps building part to hide internal implementation details.
+	 *
+	 * @param string  $prefix_special Prefix.
+	 * @param array   $params         Params.
+	 * @param boolean $keep_events    Keep events in url.
+	 *
+	 * @return mixed
+	 */
+	public final function buildWrapper($prefix_special, array &$params, $keep_events = false)
+	{
+		$this->buildPrefix = $prefix_special;
+		$this->buildParams = $params;
+		$this->keepEventFromUrl = $keep_events;
+
+		$url_part = $this->build();
+		$params = $this->buildParams;
+
+		return $this->debug ? $this->debug($url_part) : $url_part;
+	}
+
+	/**
+	 * Builds url part.
+	 *
+	 * @return boolean Return true to continue to next router; return false not to rewrite given prefix.
+	 */
+	abstract protected function build();
+
+	/**
+	 * Parses url part.
+	 *
+	 * @param array $url_parts Url parts to parse.
+	 * @param array $params    Parameters, that are used for url building or created during url parsing.
+	 *
+	 * @return boolean Return true to continue to next router; return false to stop processing at this router.
+	 */
+	abstract public function parse(array &$url_parts, array &$params);
+
+	/**
+	 * Returns value of build parameter (would use global value, when not given directly).
+	 *
+	 * @param string $name    Name of parameter. Symbol "@" would be replaced to "prefix_special_".
+	 * @param mixed  $default Default value.
+	 *
+	 * @return string|boolean
+	 */
+	protected function getBuildParam($name, $default = null)
+	{
+		$param_name = str_replace('@', $this->buildPrefix . '_', $name);
+
+		if ( isset($this->buildParams[$param_name]) ) {
+			return $this->buildParams[$param_name];
+		}
+
+		return isset($default) ? $default : $this->Application->GetVar($param_name);
+	}
+
+	/**
+	 * Returns build template.
+	 *
+	 * @return string
+	 */
+	protected function getBuildTemplate()
+	{
+		return $this->Application->getPhysicalTemplate($this->getBuildParam('t'));
+	}
+
+	/**
+	 * Sets new value of build parameter.
+	 *
+	 * @param string $name  Name of build parameter.
+	 * @param string $value New build parameter value or NULL to remove it.
+	 *
+	 * @return void
+	 */
+	protected function setBuildParam($name, $value = null)
+	{
+		$param_name = str_replace('@', $this->buildPrefix . '_', $name);
+
+		if ( isset($value) ) {
+			$this->buildParams[$param_name] = $value;
+		}
+		else {
+			unset($this->buildParams[$param_name]);
+		}
+	}
+
+	/**
+	 * Returns environment variable values for given prefix
+	 * Uses directly given params, when available and removes them from build params afterwards.
+	 *
+	 * @return array
+	 */
+	protected function extractBuildParams()
+	{
+		list ($prefix) = explode('.', $this->buildPrefix);
+
+		$query_vars = $this->Application->getUnitConfig($prefix)->getQueryString();
+
+		if ( !$query_vars ) {
+			// Given prefix doesn't use "env" variable to pass it's data.
+			return false;
+		}
+
+		$event_key = array_search('event', $query_vars);
+
+		if ( $event_key ) {
+			// Pass through event of this prefix.
+			unset($query_vars[$event_key]);
+		}
+
+		$event_name = $this->getBuildParam('@event', false);
+
+		if ( ($event_name !== false && !$event_name) || ($event_name === false && !$this->keepEventFromUrl) ) {
+			// If empty event passed, then remove it from url.
+			// If keep event is off and event is not implicitly passed (to prevent global value).
+			$this->setBuildParam('@event');
+		}
+
+		$ret = array();
+
+		foreach ( $query_vars as $name ) {
+			$ret[$this->buildPrefix . '_' . $name] = $this->getBuildParam('@' . $name);
+			$this->setBuildParam('@' . $name);
+		}
+
+		return $ret;
+	}
+
+	/**
+	 * Transforms event into form, that will survive in mod-rewritten url.
+	 *
+	 * @return void
+	 */
+	protected function keepEvent()
+	{
+		$event_name = $this->getBuildParam('@event');
+
+		if ( $event_name ) {
+			$this->setBuildParam('events[' . $this->buildPrefix . ']', $event_name);
+			$this->setBuildParam('@event');
+		}
+	}
+
+	/**
+	 * Returns build parameters from given object (without changing it).
+	 *
+	 * @param kDBBase $object Object.
+	 * @param array   $params Parameter set to use as base.
+	 *
+	 * @return array
+	 */
+	public function getBuildParams(kDBBase $object, array $params = array())
+	{
+		$params[$object->Prefix . '_id'] = $object->GetID();
+
+		if ( !isset($params['pass']) ) {
+			$params['pass'] = 'm,' . $object->Prefix;
+		}
+
+		return $params;
+	}
+
+	/**
+	 * Marks url part as parsed.
+	 *
+	 * @param string $url_part        Url part.
+	 * @param string $parse_direction Parse direction.
+	 *
+	 * @return void
+	 */
+	protected function partParsed($url_part, $parse_direction = 'ltr')
+	{
+		$this->getUrlProcessor()->partParsed($url_part, $parse_direction);
+	}
+
+	/**
+	 * Determines if there is more to parse in url.
+	 *
+	 * @return boolean
+	 */
+	protected function moreToParse()
+	{
+		return $this->getUrlProcessor()->moreToParse();
+	}
+
+	/**
+	 * Returns an instance of url processor in use.
+	 *
+	 * @return kRewriteUrlProcessor
+	 */
+	protected function getUrlProcessor()
+	{
+		return $this->Application->recallObject('kRewriteUrlProcessor');
+	}
+
+}

Property changes on: branches/5.3.x/core/kernel/utility/Router/AbstractRouter.php
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/core/kernel/utility/Router/AbstractReviewRouter.php
===================================================================
--- branches/5.3.x/core/kernel/utility/Router/AbstractReviewRouter.php	(nonexistent)
+++ branches/5.3.x/core/kernel/utility/Router/AbstractReviewRouter.php	(revision 16171)
@@ -0,0 +1,69 @@
+<?php
+
+
+abstract class AbstractReviewRouter extends AbstractRouter
+{
+
+	/**
+	 * Builds url part.
+	 *
+	 * @return boolean Return true to continue to next router; return false not to rewrite given prefix.
+	 */
+	protected function build()
+	{
+		static $default_per_page = array();
+
+		$ret = '';
+		$build_params = $this->extractBuildParams();
+
+		if ( $build_params === false ) {
+			return '';
+		}
+
+		list ($prefix) = explode('.', $this->buildPrefix);
+
+		if ( !array_key_exists($prefix, $default_per_page) ) {
+			$list_helper = $this->Application->recallObject('ListHelper');
+			/* @var $list_helper ListHelper */
+
+			$default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix);
+		}
+
+		if ( $build_params[$this->buildPrefix . '_id'] ) {
+			return false;
+		}
+		else {
+			if ( $build_params[$this->buildPrefix . '_Page'] == 1 ) {
+				// When printing category items and we are on the 1st page -> there is no information about
+				// category item prefix and $params['pass_category'] will not be added automatically.
+				$this->setBuildParam('pass_category', true);
+			}
+			elseif ( $build_params[$this->buildPrefix . '_Page'] > 1 ) {
+				$this->setBuildParam('page', $build_params[$this->buildPrefix . '_Page']);
+			}
+
+			$per_page = $build_params[$this->buildPrefix . '_PerPage'];
+
+			if ( $per_page && ($per_page != $default_per_page[$this->buildPrefix]) ) {
+				$this->setBuildParam('per_page', $build_params[$this->buildPrefix . '_PerPage']);
+			}
+		}
+
+		return mb_strtolower(rtrim($ret, '/'));
+	}
+
+	/**
+	 * Parses url part.
+	 *
+	 * @param array $url_parts Url parts to parse.
+	 * @param array $params    Parameters, that are used for url building or created during url parsing.
+	 *
+	 * @return boolean Return true to continue to next router; return false to stop processing at this router.
+	 */
+	public function parse(array &$url_parts, array &$params)
+	{
+		// Don't parse anything.
+		return true;
+	}
+
+}

Property changes on: branches/5.3.x/core/kernel/utility/Router/AbstractReviewRouter.php
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/core/kernel/utility/unit_config.php
===================================================================
--- branches/5.3.x/core/kernel/utility/unit_config.php	(revision 16170)
+++ branches/5.3.x/core/kernel/utility/unit_config.php	(revision 16171)
@@ -1,1399 +1,1393 @@
 <?php
 /**
  * @version    $Id$
  * @package    In-Portal
  * @copyright    Copyright (C) 1997 - 2012 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!');
 
 /**
  * TODO: Rename following settings
  *
  * ConfigMapping -> ConfigMappings
  * StatusField -> StatusFields
  * PermSection -> PermSections
  */
 
 /**
  * @method Array getFilterMenu(mixed $default = false)
  * @method kUnitConfig setFilterMenu(Array $value)
  *
  * @method Array getConfigMapping(mixed $default = false)
  * @method kUnitConfig setConfigMapping(Array $value)
  *
  *
  * @method string getModuleFolder(mixed $default = false)
  * @method kUnitConfig setModuleFolder(string $value)
  *
  * @method string getBasePath(mixed $default = false)
  * @method kUnitConfig setBasePath(string $value)
  *
  * @method Array getEventHandlerClass(mixed $default = false)
  * @method kUnitConfig setEventHandlerClass(Array $value)
  *
  * @method Array getTagProcessorClass(mixed $default = false)
  * @method kUnitConfig setTagProcessorClass(Array $value)
  *
  * @method Array getItemClass(mixed $default = false)
  * @method kUnitConfig setItemClass(Array $value)
  *
  * @method Array getListClass(mixed $default = false)
  * @method kUnitConfig setListClass(Array $value)
  *
  * @method Array getValidatorClass(mixed $default = false)
  * @method kUnitConfig setValidatorClass(Array $value)
  *
  * @method Array getQueryString(mixed $default = false)
  * @method kUnitConfig setQueryString(Array $value)
  *
  * @method string getPermItemPrefix(mixed $default = false)
  * @method kUnitConfig setPermItemPrefix(string $value)
  *
  * @method string getPermTabText(mixed $default = false)
  * @method kUnitConfig setPermTabText(string $value)
  *
  * @method Array getPermSection(mixed $default = false)
  * @method kUnitConfig setPermSection(Array $value)
  *
  * @method bool getAutoLoad(mixed $default = false)
  * @method kUnitConfig setAutoLoad(bool $value)
  *
  * @method string getIDField(mixed $default = false)
  * @method kUnitConfig setIDField(string $value)
  *
  * @method string getTableName(mixed $default = false)
  * @method kUnitConfig setTableName(string $value)
  *
  * @method string getCustomDataTableName(mixed $default = false)
  * @method kUnitConfig setCustomDataTableName(string $value)
  *
  * @method kUnitConfig setStatusField(Array $value)
  *
  * @method string getTitleField(mixed $default = false)
  * @method kUnitConfig setTitleField(string $value)
  *
  * @method string getOrderField(mixed $default = false)
  * @method kUnitConfig setOrderField(string $value)
  *
  * @method string getOwnerField(mixed $default = false)
  * @method kUnitConfig setOwnerField(string $value)
  *
  * @method int getConfigPriority(mixed $default = false)
  * @method kUnitConfig setConfigPriority(int $value)
  *
  * @method bool getCatalogItem(mixed $default = false)
  * @method kUnitConfig setCatalogItem(bool $value)
  *
  * @method string getCatalogSelectorName(mixed $default = false)
  * @method kUnitConfig setCatalogSelectorName(string $value)
  *
  * @method string getAdminTemplatePath(mixed $default = false)
  * @method kUnitConfig setAdminTemplatePath(string $value)
  *
  * @method string getAdminTemplatePrefix(mixed $default = false)
  * @method kUnitConfig setAdminTemplatePrefix(string $value)
  *
  * @method string getSearchConfigPostfix(mixed $default = false)
  * @method kUnitConfig setSearchConfigPostfix(string $value)
  *
  * @method string getTitlePhrase(mixed $default = false)
  * @method kUnitConfig setTitlePhrase(string $value)
  *
  * @method int getItemType(mixed $default = false)
  * @method kUnitConfig setItemType(int $value)
  *
  * @method Array getStatisticsInfo(mixed $default = false)
  * @method kUnitConfig setStatisticsInfo(Array $value)
  *
  * @method string getViewMenuPhrase(mixed $default = false)
  * @method kUnitConfig setViewMenuPhrase(string $value)
  *
  * @method string getCatalogTabIcon(mixed $default = false)
  * @method kUnitConfig setCatalogTabIcon(string $value)
  *
  * @method bool getCacheModRewrite(mixed $default = false)
  * @method kUnitConfig setCacheModRewrite(bool $value)
  *
  * @method kUnitConfig setParentTableKey(mixed $value)
  *
  * @method kUnitConfig setForeignKey(mixed $value)
  *
  * @method string getConstrain(mixed $default = false)
  * @method kUnitConfig setConstrain(string $value)
  *
  * @method bool getAutoDelete(mixed $default = false)
  * @method kUnitConfig setAutoDelete(bool $value)
  *
  * @method bool getAutoClone(mixed $default = false)
  * @method kUnitConfig setAutoClone(bool $value)
  *
  * @method string getParentPrefix(mixed $default = false)
  * @method kUnitConfig setParentPrefix(string $value)
  *
  * @method bool getCheckSimulatniousEdit(mixed $default = false)
  * @method kUnitConfig setCheckSimulatniousEdit(bool $value)
  *
  * @method bool getPortalStyleEnv(mixed $default = false)
  * @method kUnitConfig setPortalStyleEnv(bool $value)
  *
- * @method int getRewritePriority(mixed $default = false)
- * @method kUnitConfig setRewritePriority(int $value)
- *
- * @method Array getRewriteListener(mixed $default = false)
- * @method kUnitConfig setRewriteListener(Array $value)
- *
  * @method bool getForceDontLogChanges(mixed $default = false)
  * @method kUnitConfig setForceDontLogChanges(bool $value)
  *
  * @method Array getUserProfileMapping(mixed $default = false)
  * @method kUnitConfig setUserProfileMapping(Array $value)
  *
  * @method bool getUsePendingEditing(mixed $default = false)
  * @method kUnitConfig setUsePendingEditing(bool $value)
  *
  * @method string getNavigationSelectClause(mixed $default = false)
  * @method kUnitConfig setNavigationSelectClause(string $value)
  *
  * @method bool getPopulateMlFields(bool $default = false)
  * @method kUnitConfig setPopulateMlFields(bool $value)
  *
  * @method bool getLogChanges(bool $default = false)
  * @method kUnitConfig setLogChanges(bool $value)
  *
  * @method int getFileCount(bool $default = false)
  * @method kUnitConfig setFileCount(int $value)
  *
  * @method int getImageCount(bool $default = false)
  * @method kUnitConfig setImageCount(int $value)
  *
  * @method string getDownloadHelperClass(bool $default = false)
  * @method kUnitConfig setDownloadHelperClass(string $value)
  *
  * @method string getSectionPrefix(bool $default = false)
  * @method kUnitConfig setSectionPrefix(string $value)
  *
  * @method bool getSiteConfigProcessed(bool $default = false)
  * @method kUnitConfig setSiteConfigProcessed(bool $value)
  *
  *
  * @method Array getRegisterClasses(mixed $default = false)
  * @method kUnitConfig setRegisterClasses(Array $value)
  * @method kUnitConfig addRegisterClasses(Array $value, string $name = null)
  * @method kUnitConfig removeRegisterClasses(string $name = null)
  *
  * @method Array getScheduledTasks(mixed $default = false)
  * @method Array getScheduledTaskByName(string $name, mixed $default = false)
  * @method kUnitConfig setScheduledTasks(Array $value)
  * @method kUnitConfig addScheduledTasks(Array $value, string $name = null)
  * @method kUnitConfig removeScheduledTasks(string $name = null)
  *
  * @method Array getTitlePresets(mixed $default = false)
  * @method Array getTitlePresetByName(string $name, mixed $default = false)
  * @method kUnitConfig setTitlePresets(Array $value)
  * @method kUnitConfig addTitlePresets(Array $value, string $name = null)
  * @method kUnitConfig removeTitlePresets(string $name = null)
  *
  * @method Array getSections(mixed $default = false)
  * @method Array getSectionByName(string $name, mixed $default = false)
  * @method kUnitConfig setSections(Array $value)
  * @method kUnitConfig addSections(Array $value, string $name = null)
  * @method kUnitConfig removeSections(string $name = null)
  *
  * @method Array getFields(mixed $default = false)
  * @method Array getFieldByName(string $name, mixed $default = false)
  * @method kUnitConfig setFields(Array $value)
  * @method kUnitConfig addFields(Array $value, string $name = null)
  * @method kUnitConfig removeFields(string $name = null)
  *
  * @method Array getVirtualFields(mixed $default = false)
  * @method Array getVirtualFieldByName(string $name, mixed $default = false)
  * @method kUnitConfig setVirtualFields(Array $value)
  * @method kUnitConfig addVirtualFields(Array $value, string $name = null)
  * @method kUnitConfig removeVirtualFields(string $name = null)
  *
  * @method Array getGrids(mixed $default = false)
  * @method Array getGridByName(string $name, mixed $default = false)
  * @method kUnitConfig setGrids(Array $value)
  * @method kUnitConfig addGrids(Array $value, string $name = null)
  * @method kUnitConfig removeGrids(string $name = null)
  *
  * @method Array getHooks(mixed $default = false)
  * @method kUnitConfig setHooks(Array $value)
  * @method kUnitConfig addHooks(Array $value, string $name = null)
  * @method kUnitConfig removeHooks(string $name = null)
  *
  * @method Array getAggregateTags(mixed $default = false)
  * @method kUnitConfig setAggregateTags(Array $value)
  * @method kUnitConfig addAggregateTags(Array $value, string $name = null)
  * @method kUnitConfig removeAggregateTags(string $name = null)
  *
  * @method Array getEditTabPresets(mixed $default = false)
  * @method Array getEditTabPresetByName(string $name, mixed $default = false)
  * @method kUnitConfig setEditTabPresets(Array $value)
  * @method kUnitConfig addEditTabPresets(Array $value, string $name = null)
  * @method kUnitConfig removeEditTabPresets(string $name = null)
  *
  * @method Array getSubItems(mixed $default = false)
  * @method kUnitConfig setSubItems(Array $value)
  * @method kUnitConfig addSubItems(string $value, string $name = null)
  * @method kUnitConfig removeSubItems(string $name = null)
  *
  * @method Array getCustomFields(mixed $default = false)
  * @method string getCustomFieldByName(string $name, mixed $default = false)
  * @method kUnitConfig setCustomFields(Array $value)
  * @method kUnitConfig addCustomFields(Array $value, string $name = null)
  * @method kUnitConfig removeCustomFields(string $name = null)
  *
  * @method Array getClones(mixed $default = false)
  * @method Array getCloneByName(string $name, mixed $default = false)
  * @method kUnitConfig setClones(Array $value)
  * @method kUnitConfig addClones(Array $value, string $name = null)
  * @method kUnitConfig removeClones(string $name = null)
  *
  * @method Array getProcessPrefixes(mixed $default = false)
  * @method kUnitConfig setProcessPrefixes(Array $value)
  * @method kUnitConfig addProcessPrefixes(string $value, string $name = null)
  * @method kUnitConfig removeProcessPrefixes(string $name = null)
  *
  * @method Array getForms(mixed $default = false)
  * @method Array getFormByName(string $name, mixed $default = false)
  * @method kUnitConfig setForms(Array $value)
  * @method kUnitConfig addForms(Array $value, string $name = null)
  * @method kUnitConfig removeForms(string $name = null)
  *
  * @method Array getReplacementTemplates(mixed $default = false)
  * @method string getReplacementTemplateByName(string $name, mixed $default = false)
  * @method kUnitConfig setReplacementTemplates(Array $value)
  * @method kUnitConfig addReplacementTemplates(string $value, string $name = null)
  * @method kUnitConfig removeReplacementTemplates(string $name = null)
  *
  * @method Array getItemPropertyMappings(mixed $default = false)
  * @method string getItemPropertyMappingByName(string $name, mixed $default = false)
  * @method kUnitConfig setItemPropertyMappings(Array $value)
  * @method kUnitConfig addItemPropertyMappings(string $value, string $name = null)
  * @method kUnitConfig removeItemPropertyMappings(string $name = null)
  *
  * @method Array getSectionAdjustments(mixed $default = false)
  * @method mixed getSectionAdjustmentByName(string $name, mixed $default = false)
  * @method kUnitConfig setSectionAdjustments(Array $value)
  * @method kUnitConfig addSectionAdjustments(mixed $value, string $name = null)
  * @method kUnitConfig removeSectionAdjustments(string $name = null)
  *
  * @method Array getImportKeys(mixed $default = false)
  * @method kUnitConfig setImportKeys(Array $value)
  * @method kUnitConfig addImportKeys(mixed $value, string $name = null)
  * @method kUnitConfig removeImportKeys(string $name = null)
  *
  *
  * @method Array getCalculatedFieldSpecials(mixed $default = Array ())
  * @method Array getCalculatedFieldsBySpecial(mixed $special, mixed $default = Array ())
  * @method kUnitConfig setCalculatedFieldsBySpecial(mixed $special, Array $value)
  * @method kUnitConfig addCalculatedFieldsBySpecial(mixed $special, mixed $value, string $name = null)
  * @method kUnitConfig removeCalculatedFieldsBySpecial(mixed $special, string $name = null)
  *
  * @method Array getAggregatedCalculatedFieldSpecials(mixed $default = Array ())
  * @method Array getAggregatedCalculatedFieldsBySpecial(mixed $special, mixed $default = Array ())
  * @method kUnitConfig setAggregatedCalculatedFieldsBySpecial(mixed $special, Array $value)
  * @method kUnitConfig addAggregatedCalculatedFieldsBySpecial(mixed $special, mixed $value, string $name = null)
  * @method kUnitConfig removeAggregatedCalculatedFieldsBySpecial(mixed $special, string $name = null)
  *
  * @method Array getListSQLSpecials(mixed $default = Array ())
  * @method string getListSQLsBySpecial(mixed $special, mixed $default = Array ())
  * @method kUnitConfig setListSQLsBySpecial(mixed $special, string $value)
  * @method kUnitConfig removeListSQLsBySpecial(mixed $special, string $name = null)
  *
  * @method Array getListSortingSpecials(mixed $default = Array ())
  * @method Array getListSortingsBySpecial(mixed $special, mixed $default = Array ())
  * @method kUnitConfig setListSortingsBySpecial(mixed $special, Array $value)
  * @method kUnitConfig addListSortingsBySpecial(mixed $special, mixed $value, string $name = null)
  * @method kUnitConfig removeListSortingsBySpecial(mixed $special, string $name = null)
  *
  * @method Array getItemSQLSpecials(mixed $default = Array ())
  * @method string getItemSQLsBySpecial(mixed $special, mixed $default = Array ())
  * @method kUnitConfig setItemSQLsBySpecial(mixed $special, string $value)
  * @method kUnitConfig removeItemSQLsBySpecial(mixed $special, string $name = null)
  */
 class kUnitConfig {
 
 	/**
 	 * Reference to global kApplication instance
 	 *
 	 * @var kApplication
 	 * @access protected
 	 */
 	protected $Application = null;
 
 	/**
 	* Connection to database
 	*
 	* @var IDBConnection
 	* @access protected
 	*/
 	protected $Conn = null;
 
 	/**
 	 * Unit config prefix
 	 *
 	 * @var string
 	 * @access protected
 	 */
 	protected $_prefix = '';
 
 	/**
 	 * Filename, where unit config is stored
 	 *
 	 * @var string
 	 * @access protected
 	 */
 	protected $_filename = '';
 
 	/**
 	 * Default unit config data
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected static $_defaults = Array (
 		'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'),
 		'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'),
 		'EventHandlerClass' => Array ('class' => 'kDBEventHandler', 'file' => '', 'build_event' => 'OnBuild'),
 		'TagProcessorClass' => Array ('class' => 'kDBTagProcessor', 'file' => '', 'build_event' => 'OnBuild'),
 
 		'AutoLoad' => true,
 
 		'QueryString' => Array (
 			1 => 'id',
 			2 => 'Page',
 			3 => 'PerPage',
 			4 => 'event',
 			5 => 'mode',
 		),
 
 		'ListSQLs' => Array (
 			'' => '	SELECT %1$s.* %2$s
 					FROM %1$s',
 		),
 
 		'Fields' => Array (),
 	);
 
 	/**
 	 * Word endings, that are suffixed with "es" instead of just "s" during pluralisation
 	 *
 	 * @var string
 	 * @access protected
 	 */
 	protected static $_singularEndingsRegExp = '/(x|s|z|sh|ch)$/';
 
 	/**
 	 * Words, that ends with es/s, but are always singulars
 	 *
 	 * @var string
 	 * @access protected
 	 */
 	protected static $_alwaysSingularRegExp = '/(class|LogChanges|ForceDontLogChanges|PopulateMlFields)$/i';
 
 	/**
 	 * Unit config data
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_data = Array ();
 
 	/**
 	 * Unit config settings that can have different values based on special
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_specialBased = Array (
 		'CalculatedFields', 'AggregatedCalculatedFields', 'ListSQLs', 'ListSortings', 'ItemSQLs',
 	);
 
 	/**
 	 * Unit config settings, that allow data to be added without a key
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_withoutKeys = Array (
 		'RegisterClasses', 'Hooks', 'AggregateTags'
 	);
 
 	/**
 	 * Dynamic method name, that was called
 	 *
 	 * @var string
 	 * @access protected
 	 */
 	protected $_currentMethodName = '';
 
 	/**
 	 * Arguments given to dynamic method name, that was called
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_currentArguments = Array ();
 
 	/**
 	 * Creates new instance of kUnitConfig class
 	 *
 	 * @param string $prefix
 	 * @param Array $defaults
 	 * @param bool $use_global_defaults
 	 * @access public
 	 */
 	public function __construct($prefix, $defaults = null, $use_global_defaults = true)
 	{
 		$this->_prefix = $prefix;
 
 		$merge_with = $use_global_defaults ? self::$_defaults : Array ();
 		$this->_data = array_merge($merge_with, isset($defaults) ? $defaults : Array ());
 
 		$this->Application =& kApplication::Instance();
 		$this->Conn =& $this->Application->GetADODBConnection();
 	}
 
 	/**
 	 * Sets filename, where unit config is stored
 	 *
 	 * @param string $filename
 	 * @return void
 	 * @access public
 	 */
 	public function setFilename($filename)
 	{
 		$this->_filename = $filename;
 	}
 
 	/**
 	 * Returns unit config prefix
 	 *
 	 * @return string
 	 * @access public
 	 */
 	public function getPrefix()
 	{
 		return $this->_prefix;
 	}
 
 	/**
 	 * Ensures, that only unit config data is saved when object is serialized
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function __sleep()
 	{
 		return Array ('_prefix', '_data', 'Application', 'Conn');
 	}
 
 	/**
 	 * Dumps unit config into a file
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function dump()
 	{
 		kUtil::print_r($this->_data, 'Unit Config', true);
 	}
 
 	/**
 	 * Returns unit config data in raw form
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function getRaw()
 	{
 		return $this->_data;
 	}
 
 	/**
 	 * Processed dynamically created methods for changing unit config settings
 	 *
 	 * @param string $method_name
 	 * @param Array $arguments
 	 * @return mixed
 	 * @throws InvalidArgumentException
 	 * @throws RuntimeException
 	 * @access public
 	 */
 	public function __call($method_name, $arguments)
 	{
 //		=== regular ===
 //		get{SettingName}()
 //		set{SettingName}($value)
 //		add{SettingName}s(string $value, string $name = null)
 //		remove{SettingName}(string $name = null)
 
 //		=== by special ===
 //		get{SettingName}Specials()
 //		get{SettingName}sBySpecial($special)
 //		set{SettingName}BySpecial($special, $value)
 //		add{SettingName}sBySpecial(string $special, Array $value, string $name = null)
 //		remove{SettingName}sBySpecial(string $special, $name = null)
 
 		if ( !preg_match('/^(get|set|add|remove)(.*?)(BySpecial|Specials|ByName)*$/', $method_name, $regs) ) {
 			throw new RuntimeException('Unknown method <strong>' . __CLASS__ . '::' . $this->_currentMethodName . '</strong>');
 		}
 
 		$this->_currentMethodName = $method_name;
 		$this->_currentArguments = $arguments;
 		$to_call = '_process' . ucfirst($regs[1]);
 
 		return $this->$to_call($regs[2], isset($regs[3]) ? $regs[3] : '');
 	}
 
 	/**
 	 * Processes calls to "get*" methods
 	 *
 	 * @param string $setting_name
 	 * @param string $suffix
 	 * @return mixed
 	 * @access protected
 	 */
 	protected function _processGet($setting_name, $suffix = '')
 	{
 		$is_plural = $this->_isPluralSetting($setting_name);
 
 		if ( $suffix == 'BySpecial' && $is_plural ) {
 			// get{SettingName}BySpecial($special, $default = false)
 			$this->_verifyArguments(Array ('special'));
 
 			return $this->getSetting($setting_name, $this->_getDefaultValue(1, Array ()), $this->_currentArguments[0]);
 		}
 		elseif ( $suffix == 'Specials' && !$is_plural ) {
 			// get{SettingName}Specials($default = Array ())
 			$result = $this->getSetting($this->_getPlural($setting_name), $this->_getDefaultValue(0, Array ()));
 
 			return array_keys($result);
 		}
 		elseif ( $suffix == 'ByName' && !$is_plural ) {
 			$sub_key = $this->_currentArguments[0];
 			$result = $this->getSetting($this->_getPlural($setting_name), Array ());
 
 			return isset($result[$sub_key]) ? $result[$sub_key] : $this->_getDefaultValue(1, false);
 		}
 
 		// get{SettingName}($default = false)
 		return $this->getSetting($setting_name, $this->_getDefaultValue(0, false));
 	}
 
 	/**
 	 * Returns default value from given argument or false, when not passed
 	 *
 	 * @param int $arg_index
 	 * @param mixed $default
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _getDefaultValue($arg_index, $default = false)
 	{
 		return isset($this->_currentArguments[$arg_index]) ? $this->_currentArguments[$arg_index] : $default;
 	}
 
 	/**
 	 * Processes calls to "set*" methods
 	 *
 	 * @param string $setting_name
 	 * @param string $suffix
 	 * @return kUnitConfig
 	 * @access protected
 	 */
 	protected function _processSet($setting_name, $suffix = '')
 	{
 		$is_plural = $this->_isPluralSetting($setting_name);
 
 		if ( $suffix == 'BySpecial' && $is_plural ) {
 			// set{SettingName}BySpecial($special, $value)
 			$this->_verifyArguments(Array ('special', 'value'));
 
 			return $this->setSetting($setting_name, $this->_currentArguments[1], $this->_currentArguments[0]);
 		}
 
 		// set{SettingName}($value)
 		$this->_verifyArguments(Array ('value'));
 
 		return $this->setSetting($setting_name, $this->_currentArguments[0]);
 	}
 
 	/**
 	 * Processes calls to "add*" method
 	 *
 	 * @param string $setting_name
 	 * @param string $suffix
 	 * @return kUnitConfig
 	 * @access protected
 	 * @throws InvalidArgumentException
 	 */
 	protected function _processAdd($setting_name, $suffix = '')
 	{
 		$arguments = $this->_currentArguments;
 
 		if ( !$this->_isPluralSetting($setting_name) ) {
 			throw new InvalidArgumentException('Setting "' . $setting_name . '" isn\'t plural');
 		}
 
 		if ( $suffix == 'BySpecial' ) {
 			// add{SettingName}BySpecial(string $special, string $value, string $name = null)
 			$this->_verifyArguments(Array ('special', 'value'));
 
 			if ( isset($arguments[2]) ) {
 				// add a single value
 				$this->_addToSetting($this->_getSingular($setting_name), $arguments[1], $arguments[2], $arguments[0]);
 			}
 			else {
 				// add multiple values
 				$this->_addToSetting($setting_name, $arguments[1], null, $arguments[0]);
 			}
 		}
 		else {
 			// add{SettingName}(string $value, string $name = null)
 			$this->_verifyArguments(Array ('value'));
 
 			if ( isset($arguments[1]) ) {
 				// add a single value
 				$this->_addToSetting($this->_getSingular($setting_name), $arguments[0], $arguments[1]);
 			}
 			else {
 				// add multiple value
 				$this->_addToSetting($setting_name, $arguments[0], null);
 			}
 		}
 
 		return $this;
 	}
 
 	/**
 	 * Adds a value to a setting
 	 *
 	 * @param string $name
 	 * @param mixed $value
 	 * @param string $array_key
 	 * @param string $special
 	 * @return kUnitConfig
 	 * @throws InvalidArgumentException
 	 */
 	protected function _addToSetting($name, $value, $array_key, $special = null)
 	{
 		if ( $this->_isPluralSetting($name) ) {
 			// multiple values given - merge with current values
 			if ( !is_array($value) ) {
 				throw new InvalidArgumentException('Argument $value must be an array');
 			}
 
 			$result = $this->getSetting($name, Array (), $special);
 			$this->setSetting($name, array_merge($result, $value), $special);
 		}
 		else {
 			// single value given
 			$result = $this->getSetting($this->_getPlural($name), Array (), $special);
 
 			if ( $this->_isWithoutKeySetting($name) ) {
 				$result[] = $value;
 			}
 			else {
 				$result[$array_key] = $value;
 			}
 
 			$this->setSetting($this->_getPlural($name), $result, $special);
 		}
 
 		return $this;
 	}
 
 	/**
 	 * Processes calls to "remove*" method
 	 *
 	 * @param string $setting_name
 	 * @param string $suffix
 	 * @return kUnitConfig
 	 * @access protected
 	 * @throws InvalidArgumentException
 	 */
 	protected function _processRemove($setting_name, $suffix = '')
 	{
 		$arguments = $this->_currentArguments;
 
 		if ( !$this->_isPluralSetting($setting_name) ) {
 			throw new InvalidArgumentException('Setting "' . $setting_name . '" isn\'t plural');
 		}
 
 		if ( $suffix == 'BySpecial' ) {
 			// remove{SettingName}BySpecial(string $special, string $name = null)
 			$this->_verifyArguments(Array ('special'));
 
 			if ( isset($arguments[1]) ) {
 				// remove single value
 				$this->_removeFromSetting($this->_getSingular($setting_name), $arguments[1], $arguments[0]);
 			}
 			else {
 				// remove multiple value
 				$this->_removeFromSetting($setting_name, null, $arguments[0]);
 			}
 		}
 		else {
 			// remove{SettingName}(string $name = null)
 			if ( isset($arguments[0]) ) {
 				// remove single value
 				$this->_removeFromSetting($this->_getSingular($setting_name), $arguments[0]);
 			}
 			else {
 				// remove multiple values
 				$this->_removeFromSetting($setting_name, null);
 			}
 		}
 
 		return $this;
 	}
 
 	/**
 	 * Removes a value from a setting
 	 *
 	 * @param string $name
 	 * @param string $array_key
 	 * @param string $special
 	 * @return kUnitConfig
 	 * @access protected
 	 * @throws RuntimeException
 	 */
 	protected function _removeFromSetting($name, $array_key = null, $special = null)
 	{
 		if ( $this->_isPluralSetting($name) ) {
 			// remove multiple values
 			if ( $this->getSetting($name, false, $special) !== false ) {
 				$this->setSetting($name, null, $special);
 			}
 		}
 		else {
 			// remove single value
 			if ( $this->_isWithoutKeySetting($name) ) {
 				throw new RuntimeException('Unable to change setting without key');
 			}
 
 			$result = $this->getSetting($this->_getPlural($name), false, $special);
 
 			if ( $result !== false ) {
 				unset($result[$array_key]);
 				$this->setSetting($this->_getPlural($name), $result, $special);
 			}
 		}
 
 		return $this;
 	}
 
 	/**
 	 * Verifies argument count given
 	 *
 	 * @param Array $argument_names
 	 * @return void
 	 * @access protected
 	 * @throws InvalidArgumentException
 	 */
 	protected function _verifyArguments($argument_names)
 	{
 		if ( count($this->_currentArguments) < count($argument_names) ) {
 			throw new InvalidArgumentException('Method <strong>' . __CLASS__ . '::' . $this->_currentMethodName . '</strong> expects following arguments: <strong>$' . implode('</strong>, <strong>$', $argument_names) . '</strong>');
 		}
 	}
 
 	/**
 	 * Returns setting value by name and filter by special (if passed)
 	 *
 	 * @param string $name
 	 * @param mixed $default
 	 * @param string|kBase $special
 	 * @return mixed
 	 * @access public
 	 */
 	public function getSetting($name, $default = false, $special = null)
 	{
 		if ( in_array($name, $this->_specialBased) && isset($special) ) {
 			$use_special = $this->_guessSpecial($name, $special);
 
 			return isset($this->_data[$name][$use_special]) ? $this->_data[$name][$use_special] : $default;
 		}
 
 		return isset($this->_data[$name]) ? $this->_data[$name] : $default;
 	}
 
 	/**
 	 * Changes value of a setting
 	 *
 	 * @param string $name
 	 * @param mixed $value
 	 * @param string|kBase $special
 	 * @return kUnitConfig
 	 * @access public
 	 */
 	public function setSetting($name, $value, $special = null)
 	{
 		if ( in_array($name, $this->_specialBased) && isset($special) ) {
 			if ( !isset($this->_data[$name]) ) {
 				$this->_data[$name] = Array ();
 			}
 
 			$use_special = $this->_guessSpecial($name, $special);
 
 			if ( !isset($value) ) {
 				unset($this->_data[$name][$use_special]);
 			}
 			else {
 				$this->_data[$name][$use_special] = $value;
 			}
 		}
 		else {
 			if ( !isset($value) ) {
 				unset($this->_data[$name]);
 			}
 			else {
 				$this->_data[$name] = $value;
 			}
 		}
 
 		return $this;
 	}
 
 	/**
 	 * Detects settings, that accept arrays
 	 *
 	 * @param string $name
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _isPluralSetting($name)
 	{
 		if ( preg_match(self::$_alwaysSingularRegExp, $name) ) {
 			// simplified exceptions
 			return false;
 		}
 
 		return preg_match('/(es|s)$/', $name);
 	}
 
 	/**
 	 * Detects anonymous settings
 	 *
 	 * @param string $name
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _isWithoutKeySetting($name)
 	{
 		return in_array($this->_getPlural($name), $this->_withoutKeys);
 	}
 
 	/**
 	 * Returns plural form given word
 	 *
 	 * @param string $name
 	 * @return string
 	 * @access protected
 	 */
 	protected function _getPlural($name)
 	{
 		return preg_match(self::$_singularEndingsRegExp, $name) ? $name . 'es' : $name . 's';
 	}
 
 	/**
 	 * Returns singular form of given word
 	 *
 	 * @param $name
 	 * @return mixed
 	 * @throws InvalidArgumentException
 	 */
 	protected function _getSingular($name)
 	{
 		if ( !$this->_isPluralSetting($name) ) {
 			throw new InvalidArgumentException('Setting "' . $name . '" isn\'t plural');
 		}
 
 		return preg_replace('/(es|s)$/', '', $name);
 	}
 
 	/**
 	 * Guesses special to be used by event
 	 *
 	 * @param string $setting_name
 	 * @param string|kBase $special
 	 * @return string
 	 * @access protected
 	 */
 	protected function _guessSpecial($setting_name, $special)
 	{
 		$use_special = $special instanceof kBase ? $special->Special : $special;
 		$value = $this->getSetting($setting_name, Array ());
 
 		return isset($value[$use_special]) ? $use_special : '';
 	}
 
 	/**
 	 * Adds new tab to existing edit tab preset
 	 *
 	 * @param string $preset_name
 	 * @param Array $value
 	 * @param string $name
 	 * @return kUnitConfig
 	 * @throws InvalidArgumentException
 	 * @access public
 	 */
 	public function addEditTabPresetTabs($preset_name, $value, $name = null)
 	{
 		$preset = $this->getEditTabPresetByName($preset_name, false);
 
 		if ( $preset === false ) {
 			throw new InvalidArgumentException('Edit tab preset "' . $preset_name . '" not defined in "' . $this->_prefix . '" unit config');
 		}
 
 		if ( isset($name) ) {
 			$preset[$name] = $value;
 		}
 		else {
 			$preset = array_merge($preset, $value);
 		}
 
 		$this->addEditTabPresets($preset, $preset_name);
 
 		return $this;
 	}
 
 	public function addGridFields($grid_name, $value, $name = null)
 	{
 		$grid = $this->getGridByName($grid_name, false);
 
 		if ( $grid === false ) {
 			throw new InvalidArgumentException('Grid "' . $grid_name . '" not defined in "' . $this->_prefix . '" unit config');
 		}
 
 		if ( isset($name) ) {
 			$grid['Fields'][$name] = $value;
 		}
 		else {
 			$grid['Fields'] = array_merge($grid['Fields'], $value);
 		}
 
 		$this->addGrids($grid, $grid_name);
 
 		return $this;
 	}
 
 	/**
 	 * Returns individual permission section
 	 *
 	 * @param string $name
 	 * @param Array $default
 	 * @return Array
 	 * @access public
 	 * @todo Rename setting to plural form and them remove this method
 	 */
 	public function getPermSectionByName($name, $default = Array ())
 	{
 		$perm_sections = $this->getPermSection(Array ());
 
 		return isset($perm_sections[$name]) ? $perm_sections[$name] : $default;
 	}
 
 	/**
 	 * Returns foreign key by given prefix
 	 *
 	 * @param string $parent_prefix
 	 * @param mixed $default
 	 * @return string|bool
 	 * @access public
 	 */
 	public function getForeignKey($parent_prefix = null, $default = false)
 	{
 		return $this->_getSettingByPrefix('ForeignKey', $parent_prefix, $default);
 	}
 
 	/**
 	 * Returns parent table key by given prefix
 	 *
 	 * @param string $parent_prefix
 	 * @param mixed $default
 	 * @return string|bool
 	 * @access public
 	 */
 	public function getParentTableKey($parent_prefix = null, $default = false)
 	{
 		return $this->_getSettingByPrefix('ParentTableKey', $parent_prefix, $default);
 	}
 
 	/**
 	 * Returns value of a setting by prefix (special workaround for non-special settings)
 	 *
 	 * @param string $name
 	 * @param string $prefix
 	 * @param mixed $default
 	 * @return mixed
 	 * @access protected
 	 */
 	protected function _getSettingByPrefix($name, $prefix = null, $default = false)
 	{
 		$value = $this->getSetting($name, $default);
 
 		if ( !is_array($value) || !isset($prefix) ) {
 			return $value;
 		}
 
 		return isset($value[$prefix]) ? $value[$prefix] : $default;
 	}
 
 	/**
 	 * Returns status field with option to get first field only
 	 *
 	 * @param bool $first_only
 	 * @param mixed $default
 	 * @return mixed
 	 * @access public
 	 */
 	public function getStatusField($first_only = false, $default = false)
 	{
 		$value = $this->getSetting('StatusField', $default);
 
 		return $first_only ? $value[0] : $value;
 	}
 
 	/**
 	 * Register necessary classes
 	 * This method should only process the data which is cached!
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function parse()
 	{
 		$this->_parseClasses();
 		$this->_parseScheduledTasks();
 		$this->_parseHooks();
 		$this->_parseAggregatedTags();
 	}
 
 	protected function _parseClasses()
 	{
 		$register_classes = $this->_getClasses();
 
 		foreach ($register_classes as $class_info) {
 			// Thanks to static class map there is no need to specify file during class registration.
 			$this->Application->registerClass($class_info['class'], '', $class_info['pseudo']);
 
 			if ( isset($class_info['build_event']) && $class_info['build_event'] && $class_info['build_event'] != 'OnBuild' ) {
 				$this->Application->delayUnitProcessing('registerBuildEvent', Array ($class_info['pseudo'], $class_info['build_event']));
 			}
 		}
 	}
 
 	protected function _getClasses()
 	{
 		$register_classes = $this->getRegisterClasses();
 		$class_params = Array ('ItemClass', 'ListClass', 'EventHandlerClass', 'TagProcessorClass');
 
 		foreach ($class_params as $param_name) {
 			$value = $this->getSetting($param_name);
 
 			if ( !$value ) {
 				continue;
 			}
 
 			$value['pseudo'] = $this->_getPseudoByOptionName($param_name);
 			$this->setSetting($param_name, $value);
 
 			$register_classes[] = $value;
 		}
 
 		return $register_classes;
 	}
 
 	protected function _getPseudoByOptionName($option_name)
 	{
 		$pseudo_class_map = Array (
 			'ItemClass' => '%s',
 			'ListClass' => '%s_List',
 			'EventHandlerClass' => '%s_EventHandler',
 			'TagProcessorClass' => '%s_TagProcessor'
 		);
 
 		return sprintf($pseudo_class_map[$option_name], $this->_prefix);
 	}
 
 	protected function _parseScheduledTasks()
 	{
 		if ( !$this->getScheduledTasks() ) {
 			return ;
 		}
 
 		$scheduled_tasks = $this->getScheduledTasks();
 
 		foreach ($scheduled_tasks as $short_name => $scheduled_task_info) {
 			$event_status = array_key_exists('Status', $scheduled_task_info) ? $scheduled_task_info['Status'] : STATUS_ACTIVE;
 			$this->Application->delayUnitProcessing('registerScheduledTask', Array ($short_name, $this->_prefix . ':' . $scheduled_task_info['EventName'], $scheduled_task_info['RunSchedule'], $this->getModule(), $event_status));
 		}
 	}
 
 	/**
 	 * Detects module by unit location.
 	 *
 	 * @return string
 	 */
 	public function getModule()
 	{
 		$module_path = $this->getModuleFolder() . '/';
 
 		foreach ( $this->Application->ModuleInfo as $module_name => $module_data ) {
 			if ( $module_name == 'In-Portal' ) {
 				continue;
 			}
 
 			if ( $module_data['Path'] == $module_path ) {
 				return $module_name;
 			}
 		}
 
 		return '';
 	}
 
 	protected function _parseHooks()
 	{
 		$hooks = $this->getHooks();
 
 		if ( !$hooks ) {
 			return;
 		}
 
 		foreach ($hooks as $hook) {
 			if ( $this->getParentPrefix() && ($hook['HookToPrefix'] == $this->getParentPrefix()) ) {
 				trigger_error('Deprecated Hook Usage [prefix: <strong>' . $this->_prefix . '</strong>; do_prefix: <strong>' . $hook['DoPrefix'] . '</strong>] use <strong>#PARENT#</strong> as <strong>HookToPrefix</strong> value, where HookToPrefix is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE);
 			}
 
 			if ( $hook['HookToPrefix'] == '' ) {
 				// new: set hooktoprefix to current prefix if not set
 				$hook['HookToPrefix'] = $this->_prefix;
 			}
 
 			if ( $this->getParentPrefix() ) {
 				// new: allow to set hook to parent prefix what ever it is
 				if ( $hook['HookToPrefix'] == '#PARENT#' ) {
 					$hook['HookToPrefix'] = $this->getParentPrefix();
 				}
 
 				if ( $hook['DoPrefix'] == '#PARENT#' ) {
 					$hook['DoPrefix'] = $this->getParentPrefix();
 				}
 			}
 			elseif ( $hook['HookToPrefix'] == '#PARENT#' || $hook['DoPrefix'] == '#PARENT#' ) {
 				// we need parent prefix but it's not set !
 				continue;
 			}
 
 			$hook_events = (array)$hook['HookToEvent'];
 			$do_prefix = $hook['DoPrefix'] == '' ? $this->_prefix : $hook['DoPrefix'];
 
 			foreach ($hook_events as $hook_event) {
 				$hook_event = $hook['HookToPrefix'] . '.' . $hook['HookToSpecial'] . ':' . $hook_event;
 				$do_event = $do_prefix . '.' . $hook['DoSpecial'] . ':' . $hook['DoEvent'];
 
 				$this->Application->delayUnitProcessing('registerHook', Array ($hook_event, $do_event, $hook['Mode'], $hook['Conditional']));
 			}
 		}
 	}
 
 	protected function _parseAggregatedTags()
 	{
 		$aggregated_tags = $this->getAggregateTags();
 
 		if ( !$aggregated_tags ) {
 			return;
 		}
 
 		foreach ($aggregated_tags as $aggregate_tag) {
 			if ( $this->getParentPrefix() ) {
 				if ( $aggregate_tag['AggregateTo'] == $this->getParentPrefix() ) {
 					trigger_error('Deprecated Aggregate Tag Usage [prefix: <b>' . $this->_prefix . '</b>; AggregateTo: <b>' . $aggregate_tag['AggregateTo'] . '</b>] use <b>#PARENT#</b> as <b>AggregateTo</b> value, where AggregateTo is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE);
 				}
 
 				if ( $aggregate_tag['AggregateTo'] == '#PARENT#' ) {
 					$aggregate_tag['AggregateTo'] = $this->getParentPrefix();
 				}
 			}
 
 			$aggregate_tag['LocalPrefix'] = $this->_prefix;
 			$this->Application->delayUnitProcessing('registerAggregateTag', Array ($aggregate_tag));
 		}
 	}
 
 	public function validate()
 	{
 		global $debugger;
 
 		$table_name = $this->getTableName();
 		$float_types = Array ('float', 'double', 'numeric');
 
 		$table_found = $this->Conn->Query('SHOW TABLES LIKE "' . $table_name . '"');
 
 		if ( !$table_found ) {
 			// config present, but table missing, strange
 			kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1);
 			$debugger->appendHTML("<b class='debug_error'>Config Warning: </b>Table <strong>$table_name</strong> missing, but prefix <b>" . $this->_prefix . "</b> requires it!");
 			$debugger->WarningCount++;
 
 			return;
 		}
 
 		$res = $this->Conn->Query('DESCRIBE ' . $table_name);
 		$config_link = $debugger->getFileLink(FULL_PATH . $this->_filename, 1, $this->_prefix);
 
 		$error_messages = Array (
 			'field_not_found' => 'Field <strong>%s</strong> exists in the database, but <strong>is not defined</strong> in config',
 			'default_missing' => 'Default value for field <strong>%s</strong> not set in config',
 			'not_null_error1' => 'Field <strong>%s</strong> is NOT NULL in the database, but is not configured as not_null', // or required',
 			'not_null_error2' => 'Field <strong>%s</strong> is described as NOT NULL in config, but <strong>does not have DEFAULT value</strong>',
 			'not_null_error3' => 'Field <strong>%s</strong> is described as <strong>NOT NULL in config</strong>, but is <strong>NULL in db</strong>',
 			'invalid_default' => '<strong>Default value</strong> for field %s<strong>%s</strong> not sync. to db (in config = %s, in db = %s)',
 			'date_column_not_null_error' => 'Field <strong>%s</strong> must be NULL in config and database, since it contains date',
 			'user_column_default_error' => 'Field <strong>%s</strong> must be have NULL as default value, since it holds user id',
 			'type_missing' => '<strong>Type definition</strong> for field <strong>%s</strong> missing in config',
 			'virtual_type_missing' => '<strong>Type definition</strong> for virtual field <strong>%s</strong> missing in config',
 			'virtual_default_missing' => 'Default value for virtual field <strong>%s</strong> not set in config',
 			'virtual_not_null_error' => 'Virtual field <strong>%s</strong> cannot be not null, since it doesn\'t exist in database',
 			'invalid_calculated_field' => 'Calculated field <strong>%s</strong> is missing corresponding virtual field',
 		);
 
 		$config_errors = Array ();
 		$fields = $this->getFields();
 		$table_name = preg_replace('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', '\\1', $table_name); // remove table prefix
 
 		if ( $fields ) {
 			// validate unit config field declaration in relation to database table structure
 			foreach ($res as $field) {
 				$f_name = $field['Field'];
 
 				if ( preg_match('/l[\d]+_[\w]/', $f_name) ) {
 					// skip multilingual fields
 					continue;
 				}
 
 				if ( !array_key_exists($f_name, $fields) ) {
 					$config_errors[] = sprintf($error_messages['field_not_found'], $f_name);
 				}
 				else {
 					$db_default = $field['Default'];
 
 					if ( is_numeric($db_default) ) {
 						$db_default = preg_match('/[\.,]/', $db_default) ? (float)$db_default : (int)$db_default;
 					}
 
 					$default_missing = false;
 					$options = $fields[$f_name];
 					$not_null = isset($options['not_null']) && $options['not_null'];
 					$formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false;
 
 					if ( !array_key_exists('default', $options) ) {
 						$config_errors[] = sprintf($error_messages['default_missing'], $f_name);
 						$default_missing = true;
 					}
 
 					if ( $field['Null'] != 'YES' ) {
 						// field is NOT NULL in database (MySQL5 for null returns "NO", but MySQL4 returns "")
 						if ( $f_name != $this->getIDField() && !isset($options['not_null']) /*&& !isset($options['required'])*/ ) {
 							$config_errors[] = sprintf($error_messages['not_null_error1'], $f_name);
 						}
 						if ( $not_null && !isset($options['default']) ) {
 							$config_errors[] = sprintf($error_messages['not_null_error2'], $f_name);
 						}
 					}
 					elseif ( $not_null ) {
 						$config_errors[] = sprintf($error_messages['not_null_error3'], $f_name);
 					}
 
 					if ( ($formatter == 'kDateFormatter') && $not_null ) {
 						$config_errors[] = sprintf($error_messages['date_column_not_null_error'], $f_name);
 					}
 
 					// columns, holding userid should have NULL as default value
 					if ( array_key_exists('type', $options) && !$default_missing ) {
 						// both type and default value set
 
 						if ( preg_match('/ById$/', $f_name) && $options['default'] !== null ) {
 							$config_errors[] = sprintf($error_messages['user_column_default_error'], $f_name);
 						}
 					}
 
 					if ( !array_key_exists('type', $options) ) {
 						$config_errors[] = sprintf($error_messages['type_missing'], $f_name);
 					}
 
 					if ( !$default_missing && ($field['Type'] != 'text') ) {
 						if ( is_null($db_default) && $not_null ) {
 							$db_default = $options['type'] == 'string' ? '' : 0;
 						}
 
 						if ( $f_name == $this->getIDField() && $options['type'] != 'string' && $options['default'] !== 0 ) {
 							$config_errors[] = sprintf($error_messages['invalid_default'], '<span class="debug_error">IDField</span> ', $f_name, $this->_varDump($options['default']), $this->_varDump($field['Default']));
 						}
 						else if ( ((string)$options['default'] != '#NOW#') && ($db_default !== $options['default']) && !in_array($options['type'], $float_types) ) {
 							$config_errors[] = sprintf($error_messages['invalid_default'], '', $f_name, $this->_varDump($options['default']), $this->_varDump($db_default));
 						}
 					}
 				}
 			}
 		}
 
 		// validate virtual fields
 		$virtual_fields = $this->getVirtualFields();
 
 		if ( $virtual_fields ) {
 			foreach ($virtual_fields as $f_name => $options) {
 				if ( !array_key_exists('type', $options) ) {
 					$config_errors[] = sprintf($error_messages['virtual_type_missing'], $f_name);
 				}
 
 				if ( array_key_exists('not_null', $options) ) {
 					$config_errors[] = sprintf($error_messages['virtual_not_null_error'], $f_name);
 				}
 
 				if ( !array_key_exists('default', $options) ) {
 					$config_errors[] = sprintf($error_messages['virtual_default_missing'], $f_name);
 				}
 			}
 		}
 
 		// validate calculated fields
 		if ( $this->getCalculatedFieldSpecials() ) {
 			foreach ($this->getCalculatedFieldSpecials() as $special) {
 				foreach ($this->getCalculatedFieldsBySpecial($special) as $calculated_field => $calculated_field_expr) {
 					if ( !isset($virtual_fields[$calculated_field]) ) {
 						$config_errors[] = sprintf($error_messages['invalid_calculated_field'], $calculated_field);
 					}
 				}
 			}
 
 			$config_errors = array_unique($config_errors);
 		}
 
 		if ( $config_errors ) {
 			$error_prefix = '<strong class="debug_error">Config Error' . (count($config_errors) > 1 ? 's' : '') . ': </strong> for prefix <strong>' . $config_link . '</strong> (' . $table_name . ') in unit config:<br />';
 			$config_errors = $error_prefix . '&nbsp;&nbsp;&nbsp;' . implode('<br />&nbsp;&nbsp;&nbsp;', $config_errors);
 
 			kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1);
 			$debugger->appendHTML($config_errors);
 			$debugger->WarningCount++;
 		}
 	}
 
 	protected function _varDump($value)
 	{
 		return '<strong>' . var_export($value, true) . '</strong> of ' . gettype($value);
 	}
 }
Index: branches/5.3.x/core/kernel/utility/http_query.php
===================================================================
--- branches/5.3.x/core/kernel/utility/http_query.php	(revision 16170)
+++ branches/5.3.x/core/kernel/utility/http_query.php	(revision 16171)
@@ -1,787 +1,785 @@
 <?php
 /**
 * @version	$Id$
 * @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!');
 
 class kHTTPQuery extends Params {
 
 	/**
 	 * Cache of QueryString parameters
 	 * from config, that are represented
 	 * in environment variable
 	 *
 	 * @var Array
 	 */
 	protected $discoveredUnits = Array ();
 
 	/**
 	 * $_POST vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Post;
 
 	/**
 	 * $_GET vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Get;
 
 	/**
 	 * $_COOKIE vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Cookie;
 
 	/**
 	 * $_SERVER vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Server;
 
 	/**
 	 * $_ENV vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Env;
 
 	/**
 	 * Order in what write
 	 * all vars together in
 	 * the same array
 	 *
 	 * @var string
 	 */
 	var $Order;
 
 	/**
 	 * Uploaded files info
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Files;
 
 	var $specialsToRemove = Array();
 
 	/**
 	 * SessionID is given via "sid" variable in query string
 	 *
 	 * @var bool
 	 */
 	var $_sidInQueryString = false;
 
 	/**
 	 * Trust information, provided by proxy
 	 *
 	 * @var bool
 	 */
 	protected $_trustProxy = false;
 
 	/**
 	 * Loads info from $_POST, $_GET and
 	 * related arrays into common place
 	 *
 	 * @param string $order
 	 * @access public
 	 */
 	public function __construct($order = 'CGPF')
 	{
 		parent::__construct();
 
 		$this->Order = $order;
 
 		if ( isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
 			// when AJAX request is made from jQuery, then create ajax variable,
 			// so any logic based in it (like redirects) will not break down
 			$_GET['ajax'] = 'yes';
 		}
 
 		$this->_trustProxy = kUtil::getSystemConfig()->get('TrustProxy');
 	}
 
 	/**
 	 * Discovers unit form request and returns it's QueryString option on success
 	 *
 	 * @param string $prefix_special
 	 *
 	 * @return Array|bool
 	 * @access public
 	 */
 	public function discoverUnit($prefix_special)
 	{
 		list($prefix) = explode('.', $prefix_special);
 
 		$query_string = $this->getQueryString($prefix);
 
 		if ($query_string) {
 			// only units with QueryString option can be discovered
 			$this->discoveredUnits[$prefix_special] = $query_string;
 
 			return $query_string;
 		}
 
 		unset( $this->discoveredUnits[$prefix] );
 
 		return false;
 	}
 
 	/**
 	 * Returns units, passed in request
 	 *
 	 * @param bool $prefix_special_only
 	 * @return Array
 	 * @access protected
 	 */
 	public function getDiscoveredUnits($prefix_special_only = true)
 	{
 		return $prefix_special_only ? array_keys( $this->discoveredUnits ) : $this->discoveredUnits;
 	}
 
 	/**
 	 * Returns QueryMap for requested unit config.
 	 * In case if unit config is a clone, then get parent item's (from prefix) config to create clone
 	 *
 	 * @param string $prefix
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getQueryString($prefix)
 	{
 		return $this->Application->getUnitConfig($prefix)->getQueryString(Array ());
 	}
 
 	/**
 	 * Removes specials from request
 	 *
 	 * @param Array $array
 	 * @return Array
 	 * @access protected
 	 */
 	protected function _removeSpecials($array)
 	{
 		$ret = Array ();
 		$removed = false;
 
 		foreach ($this->specialsToRemove as $prefix_special => $flag) {
 			if ( $flag ) {
 				$removed = true;
 				list ($prefix, $special) = explode('.', $prefix_special, 2);
 
 				foreach ($array as $key => $val) {
 					$new_key = preg_match("/^" . $prefix . "[._]{1}" . $special . "(.*)/", $key, $regs) ? $prefix . $regs[1] : $key;
 					$ret[$new_key] = is_array($val) ? $this->_removeSpecials($val) : $val;
 				}
 			}
 		}
 
 		return $removed ? $ret : $array;
 	}
 
 	public function process()
 	{
 		$this->AddAllVars();
 		$this->removeSpecials();
 		ini_set('magic_quotes_gpc', 0);
 
 		$this->Application->UrlManager->LoadStructureTemplateMapping();
 
 		$this->AfterInit();
 	}
 
 	/**
 	 * All all requested vars to
 	 * common storage place
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function AddAllVars()
 	{
 		for ($i = 0; $i < strlen($this->Order); $i++) {
 			switch ($this->Order[$i]) {
 				case 'G':
 					$this->Get = $this->AddVars($_GET);
 					if ( array_key_exists('sid', $_GET) ) {
 						$this->_sidInQueryString = true;
 					}
 
 					$vars = $this->Application->processQueryString($this->Get(ENV_VAR_NAME));
 
 					if ( array_key_exists('sid', $vars) ) {
 						// used by Session::GetPassedSIDValue
 						$this->Get['sid'] = $vars['sid'];
 					}
 
 					$this->AddParams($vars);
 					break;
 
 				case 'P':
 					$this->Post = $this->AddVars($_POST);
 					$this->convertPostEvents();
 					$this->_processPostEnvVariables();
 					break;
 
 				case 'C':
 					$cookie_hasher = $this->Application->makeClass('kCookieHasher');
 					/* @var $cookie_hasher kCookieHasher */
 
 					$parsed_cookies = Array ();
 
 					foreach ($_COOKIE as $cookie_name => $encrypted_value) {
 						$parsed_cookies[$cookie_name] = $cookie_hasher->decrypt($cookie_name, $encrypted_value);
 					}
 
 					$this->Cookie = $this->AddVars($parsed_cookies);
 					break;
 
 				/*case 'E';
 					$this->Env = $this->AddVars($_ENV, false); //do not strip slashes!
 					break;
 
 				case 'S';
 					$this->Server = $this->AddVars($_SERVER, false); //do not strip slashes!
 					break;*/
 
 				case 'F';
 					$this->convertFiles();
 					$this->Files = $this->MergeVars($_FILES); // , false); //do not strip slashes!
 					break;
 			}
 		}
 	}
 
 	/**
 	 * Allow POST variables, that names were transformed by PHP ("." replaced with "_") to
 	 * override variables, that were virtually created through environment variable parsing
 	 *
 	 */
 	function _processPostEnvVariables()
 	{
 		$passed = $this->Get('passed');
 		if ( !$passed ) {
 			return;
 		}
 
 		$passed = explode(',', $passed);
 		foreach ($passed as $prefix_special) {
 			if ( strpos($prefix_special, '.') === false ) {
 				continue;
 			}
 
 			list ($prefix, $special) = explode('.', $prefix_special);
 			$query_map = $this->getQueryString($prefix);
 			$post_prefix_special = $prefix . '_' . $special;
 
 			foreach ($query_map as $var_name) {
 				if ( array_key_exists($post_prefix_special . '_' . $var_name, $this->Post) ) {
 					$this->Set($prefix_special . '_' . $var_name, $this->Post[$post_prefix_special . '_' . $var_name]);
 				}
 			}
 		}
 	}
 
 	/**
 	 * Removes requested specials from all request variables
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function removeSpecials()
 	{
 		$this->specialsToRemove = $this->Get('remove_specials');
 
 		if ( $this->specialsToRemove ) {
 			foreach ($this->specialsToRemove as $prefix_special => $flag) {
 				if ( $flag && strpos($prefix_special, '.') === false ) {
 					unset($this->specialsToRemove[$prefix_special]);
 					trigger_error('Incorrect usage of "<strong>remove_specials[' . $prefix_special . ']</strong>" field (no special found)', E_USER_NOTICE);
 				}
 			}
 
 			$this->_Params = $this->_removeSpecials($this->_Params);
 		}
 	}
 
 	/**
-	 * Finishes initialization of kHTTPQuery class
+	 * Finishes initialization of kHTTPQuery class.
 	 *
 	 * @return void
-	 * @access protected
-	 * @todo: only uses build-in rewrite listeners, when cache is build for the first time
 	 */
 	protected function AfterInit()
 	{
 		$rewrite_url = $this->Get('_mod_rw_url_');
 
 		if ( $this->Application->RewriteURLs() || $rewrite_url ) {
 			// maybe call onafterconfigread here
 
 			$this->Application->UrlManager->initRewrite();
 
 			if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->profileStart('url_parsing', 'Parsing <b>MOD_REWRITE</b> url');
 				$this->Application->UrlManager->rewrite->parseRewriteURL();
 				$description = 'Parsing <b>MOD_REWRITE</b> url (template: <b>' . $this->Get('t') . '</b>)';
 				$this->Application->Debugger->profileFinish('url_parsing', $description);
 			}
 			else {
 				$this->Application->UrlManager->rewrite->parseRewriteURL();
 			}
 
 			if ( !$rewrite_url && $this->rewriteRedirectRequired() ) {
 				// rewrite url is missing (e.g. not a script from tools folder)
 				$url_params = $this->getRedirectParams();
 
 				// no idea about how to check, that given template require category to be passed with it, so pass anyway
 				$url_params['pass_category'] = 1;
 				$url_params['response_code'] = 301; // Moved Permanently
 
 				trigger_error('Non mod-rewrite url "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>" used', E_USER_NOTICE);
 				$this->Application->Redirect('', $url_params);
 			}
 		}
 		else {
 			$this->Application->VerifyThemeId();
 			$this->Application->VerifyLanguageId();
 		}
 
 		if ( !$this->Application->isAdmin && $this->Application->ConfigValue('ForceCanonicalUrls') ) {
 			$template = $this->Application->GetVar('t');
 			$seo_template = $this->Application->getSeoTemplate($template);
 
 			if ( $seo_template && $seo_template != $template ) {
 				$url_params = $this->getRedirectParams();
 				$url_params['response_code'] = 301;
 
 				trigger_error('Request url "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>" points directly to physical template', E_USER_NOTICE);
 				$this->Application->Redirect($seo_template, $url_params);
 			}
 		}
 	}
 
 	/**
 	 * Checks, that non-rewrite url was visited and it's automatic rewrite is required
 	 *
 	 * @return bool
 	 */
 	function rewriteRedirectRequired()
 	{
 		$redirect_conditions = Array (
 			!$this->IsHTTPSRedirect(), // not https <-> http redirect
 			!$this->refererIsOurSite(), // referer doesn't match ssl path or non-ssl domain (same for site domains)
 			!defined('GW_NOTIFY'), // not in payment gateway notification script
 			preg_match('/[\/]{0,1}index.php[\/]{0,1}/', $_SERVER['PHP_SELF']), // "index.php" was visited
 			$this->Get('t') != 'index', // not on index page
 		);
 
 		$perform_redirect = true;
 
 		foreach ($redirect_conditions as $redirect_condition) {
 			$perform_redirect = $perform_redirect && $redirect_condition;
 
 			if (!$perform_redirect) {
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * This is redirect from https to http or via versa
 	 *
 	 * @return bool
 	 */
 	function IsHTTPSRedirect()
 	{
 		$http_referer = array_key_exists('HTTP_REFERER', $_SERVER) ? $_SERVER['HTTP_REFERER'] : false;
 
 		return (
 			( PROTOCOL == 'https://' && preg_match('#http:\/\/#', $http_referer) )
 			||
 			( PROTOCOL == 'http://' && preg_match('#https:\/\/#', $http_referer) )
 		);
 	}
 
 	/**
 	 * Checks, that referer is out site
 	 *
 	 * @return bool
 	 */
 	function refererIsOurSite()
 	{
 		if ( !array_key_exists('HTTP_REFERER', $_SERVER) ) {
 			// no referer -> don't care what happens
 			return false;
 		}
 
 		$site_helper = $this->Application->recallObject('SiteHelper');
 		/* @var $site_helper SiteHelper */
 
 		$parsed_url = parse_url($_SERVER['HTTP_REFERER']);
 
 		if ( $parsed_url['scheme'] == 'https' ) {
 			$found = $site_helper->compare($parsed_url['host'], 'SSLDomainName', $this->Application->ConfigValue('SSLDomain'));
 		}
 		else {
 			$found = $site_helper->compare($parsed_url['host'], 'DomainName', DOMAIN);
 		}
 
 		return $found;
 	}
 
 	function convertFiles()
 	{
 		if ( !$_FILES ) {
 			return ;
 		}
 
 		$tmp = Array ();
 		$file_keys = Array ('error', 'name', 'size', 'tmp_name', 'type');
 
 		foreach ($_FILES as $file_name => $file_info) {
 			if ( is_array($file_info['error']) ) {
 				$tmp[$file_name] = $this->getArrayLevel($file_info['error'], $file_name);
 			}
 			else {
 				$normal_files[$file_name] = $file_info;
 			}
 		}
 
 		if ( !$tmp ) {
 			return ;
 		}
 
 		$files = $_FILES;
 		$_FILES = Array ();
 
 		foreach ($tmp as $prefix => $prefix_files) {
 			$anchor =& $_FILES;
 			foreach ($prefix_files['keys'] as $key) {
 				$anchor =& $anchor[$key];
 			}
 
 			foreach ($prefix_files['value'] as $field_name) {
 				unset($inner_anchor, $copy);
 				$work_copy = $prefix_files['keys'];
 
 				foreach ($file_keys as $file_key) {
 					$inner_anchor =& $files[$prefix][$file_key];
 
 					if ( isset($copy) ) {
 						$work_copy = $copy;
 					}
 					else {
 						$copy = $work_copy;
 					}
 
 					array_shift($work_copy);
 					foreach ($work_copy as $prefix_file_key) {
 						$inner_anchor =& $inner_anchor[$prefix_file_key];
 					}
 
 					$anchor[$field_name][$file_key] = $inner_anchor[$field_name];
 				}
 			}
 		}
 		// keys: img_temp, 0, values: LocalPath, ThumbPath
 	}
 
 	function getArrayLevel(&$level, $prefix='')
 	{
 		$ret['keys'] = $prefix ? Array($prefix) : Array();
 		$ret['value'] = Array();
 
 		foreach($level as $level_key => $level_value)
 		{
 			if( is_array($level_value) )
 			{
 				$ret['keys'][] = $level_key;
 				$tmp = $this->getArrayLevel($level_value);
 
 				$ret['keys'] = array_merge($ret['keys'], $tmp['keys']);
 				$ret['value'] = array_merge($ret['value'], $tmp['value']);
 			}
 			else
 			{
 				$ret['value'][] = $level_key;
 			}
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Overwrites GET events with POST events in case if they are set and not empty
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function convertPostEvents()
 	{
 		$events = $this->Get('events', Array ());
 		/* @var $events Array */
 
 		if ( is_array($events) ) {
 			$events = array_filter($events);
 
 			foreach ($events as $prefix_special => $event_name) {
 				$this->Set($prefix_special . '_event', $event_name);
 			}
 		}
 	}
 
 	function finalizeParsing($passed = Array())
 	{
 		if (!$passed) {
 			return;
 		}
 
 		foreach ($passed as $passed_prefix) {
 			$this->discoverUnit($passed_prefix); // from mod-rewrite url parsing
 		}
 
 		$this->Set('passed', implode(',', $this->getDiscoveredUnits()));
 	}
 
 	/**
 	 * Saves variables from array specified
 	 * into common variable storage place
 	 *
 	 * @param Array $array
 	 * @param bool $strip_slashes
 	 * @return Array
 	 * @access private
 	 */
 	function AddVars($array, $strip_slashes = true)
 	{
 		if ( $strip_slashes ) {
 			$array = $this->StripSlashes($array);
 		}
 
 		foreach ($array as $key => $value) {
 			$this->Set($key, $value);
 		}
 
 		return $array;
 	}
 
 	function MergeVars($array, $strip_slashes = true)
 	{
 		if ( $strip_slashes ) {
 			$array = $this->StripSlashes($array);
 		}
 
 		foreach ($array as $key => $value_array) {
 			// $value_array is an array too
 			$this->_Params = kUtil::array_merge_recursive($this->_Params, Array ($key => $value_array));
 		}
 
 		return $array;
 	}
 
 	function StripSlashes($array)
 	{
 		static $magic_quotes = null;
 
 		if (!isset($magic_quotes)) {
 			$magic_quotes = get_magic_quotes_gpc();
 		}
 
 		foreach ($array as $key => $value) {
 			if (is_array($value)) {
 				$array[$key] = $this->StripSlashes($value);
 			}
 			else {
 				if ($magic_quotes) {
 					$value = stripslashes($value);
 				}
 
 				if (!$this->Application->isAdmin) {
 					// TODO: always escape output instead of input
 					$value = kUtil::escape($value, kUtil::ESCAPE_HTML);
 				}
 
 				$array[$key] = $value;
 			}
 		}
 
 		return $array;
 	}
 
 	/**
 	 * Removes forceful escaping done to the variable upon Front-End submission.
 	 *
 	 * @param string|array $value Value.
 	 *
 	 * @return string|array
 	 * @see    StripSlashes
 	 */
 	public function unescapeRequestVariable($value)
 	{
 		if ( $this->Application->isAdmin ) {
 			return $value;
 		}
 
 		// This allows to revert kUtil::escape() call for each field submitted on front-end.
 		if ( is_array($value) ) {
 			foreach ( $value as $param_name => $param_value ) {
 				$value[$param_name] = $this->unescapeRequestVariable($param_value);
 			}
 
 			return $value;
 		}
 
 		return kUtil::unescape($value, kUtil::ESCAPE_HTML);
 	}
 
 	/**
 	 * Returns all $_GET array excluding system parameters, that are not allowed to be passed through generated urls
 	 *
 	 * @param bool $access_error Method is called during no_permission, require login, session expiration link preparation
 	 * @return Array
 	 */
 	function getRedirectParams($access_error = false)
 	{
 		$vars = $this->Get;
 		$unset_vars = Array (ENV_VAR_NAME, 'rewrite', '_mod_rw_url_', 'Action');
 
 		if (!$this->_sidInQueryString) {
 			$unset_vars[] = 'sid';
 		}
 
 		// remove system variables
 		foreach ($unset_vars as $var_name) {
 			if (array_key_exists($var_name, $vars)) {
 				unset($vars[$var_name]);
 			}
 		}
 
 		if ($access_error) {
 			// place 1 of 2 (also in UsersEventHandler::OnSessionExpire)
 			$vars = $this->_removePassThroughVariables($vars);
 		}
 
 		return $vars;
 	}
 
 	/**
 	 * Removes all pass_though variables from redirect params
 	 *
 	 * @param Array $url_params
 	 * @return Array
 	 */
 	function _removePassThroughVariables($url_params)
 	{
 		$pass_through = array_key_exists('pass_through', $url_params) ? $url_params['pass_through'] : '';
 		if (!$pass_through) {
 			return $url_params;
 		}
 
 		$pass_through = explode(',', $pass_through . ',pass_through');
 		foreach ($pass_through as $pass_through_var) {
 			unset($url_params[$pass_through_var]);
 		}
 
 		$url_params['no_pass_through'] = 1; // this way kApplication::HREF won't add them again
 
 		return $url_params;
 	}
 
 	/**
 	 * Checks, that url is empty
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function isEmptyUrl()
 	{
 		if ( $this->Application->RewriteURLs() ) {
 			return !$this->Get('_mod_rw_url_');
 		}
 
 		return !count($this->Get);
 	}
 
 	/**
 	 * Returns the client IP address.
 	 *
 	 * @return string The client IP address
 	 * @access public
 	 */
 	public function getClientIp()
 	{
 		if ( $this->_trustProxy ) {
 			if ( array_key_exists('HTTP_CLIENT_IP', $_SERVER) ) {
 				return $_SERVER['HTTP_CLIENT_IP'];
 			}
 
 			if ( array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) ) {
 				$client_ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
 
 				foreach ($client_ip as $ip_address) {
 					$clean_ip_address = trim($ip_address);
 
 					if ( false !== filter_var($clean_ip_address, FILTER_VALIDATE_IP) ) {
 						return $clean_ip_address;
 					}
 				}
 
 				return '';
 			}
 		}
 
 		return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
 	}
 
 	/**
 	 * Returns headers
 	 *
 	 * @return array
 	 * @access public
 	 */
 	public function getHeaders()
 	{
 		if ( function_exists('apache_request_headers') ) {
 			// If apache_request_headers() exists...
 			$headers = apache_request_headers();
 
 			if ( $headers ) {
 				return $headers; // And works... Use it
 			}
 		}
 
 		$headers = array();
 
 		foreach ( array_keys($_SERVER) as $server_key ) {
 			if ( substr($server_key, 0, 5) == 'HTTP_' ) {
 				$header_name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($server_key, 0, 5)))));
 				$headers[$header_name] = $_SERVER[$server_key];
 			}
 		}
 
 		return $headers;
 	}
 
 }
Index: branches/5.3.x/core/kernel/utility/unit_config_reader.php
===================================================================
--- branches/5.3.x/core/kernel/utility/unit_config_reader.php	(revision 16170)
+++ branches/5.3.x/core/kernel/utility/unit_config_reader.php	(revision 16171)
@@ -1,723 +1,729 @@
 <?php
 /**
 * @version	$Id$
 * @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!');
 
 class kUnitConfigReader extends kBase implements kiCacheable {
 
 	/**
 	 * Unit config data storage.
 	 *
 	 * @var kUnitConfig[]
 	 */
 	protected $configData = array();
 
 	/**
 	 * List of discovered unit config files.
 	 *
 	 * @var array
 	 */
 	protected $configFiles = array();
 
 	/**
 	 * Mapping between unit config prefixes and files, where they data is stored.
 	 *
 	 * @var array
 	 */
 	protected $prefixFiles = array();
 
 	/**
 	 * Tells, that it's final stage of application initialization, where OnAfterConfigRead events can be called.
 	 *
 	 * @var boolean
 	 */
 	protected $finalStage = false;
 
 	/**
 	 * Determines if cache should be stored.
 	 *
 	 * @var boolean
 	 */
 	protected $storeCache = false;
 
 	/**
 	 * List of unit configs, that have called their OnAfterConfigRead event.
 	 *
 	 * @var array
 	 */
 	protected $afterConfigProcessed = array();
 
 	/**
 	 * Escaped directory separator for using in regular expressions
 	 *
 	 * @var string
 	 */
 	protected $directorySeparator = '';
 
 	/**
 	 * Regular expression for detecting module folder
 	 *
 	 * @var string
 	 */
 	protected $moduleFolderRegExp = '';
 
 	/**
 	 * Folders to skip during unit config search
 	 *
 	 * @var array
 	 */
 	protected $skipFolders = array('CVS', '.svn', 'admin_templates', 'libchart');
 
 	/**
 	 * Cloner.
 	 *
 	 * @var kUnitConfigCloner
 	 */
 	protected $cloner;
 
 	/**
 	 * Creates instance of unit config reader.
 	 */
 	public function __construct()
 	{
 		parent::__construct();
 
 		$this->directorySeparator = preg_quote(DIRECTORY_SEPARATOR);
 
 		$editor_path = explode('/', trim(EDITOR_PATH, '/'));
 		$this->skipFolders[] = array_pop($editor_path); // last of cmseditor folders
 
 		$this->moduleFolderRegExp = '#' . $this->directorySeparator . '(core|modules' . $this->directorySeparator . '.*?)' . $this->directorySeparator . '#';
 
 		$this->cloner = $this->Application->makeClass('kUnitConfigCloner', array($this));
 	}
 
 	/**
 	 * Sets data from cache to object
 	 *
 	 * @param Array $data
 	 */
 	public function setFromCache(&$data)
 	{
 		$this->cloner->setFromCache($data);
 
 		$this->prefixFiles = $data['ConfigReader.prefixFiles'];
 	}
 
 	/**
 	 * Gets object data for caching
 	 *
 	 * @return Array
 	 */
 	public function getToCache()
 	{
 		return array_merge(
 			$this->cloner->getToCache(),
 			array(
 				'ConfigReader.prefixFiles' => $this->prefixFiles,
 			)
 		);
 	}
 
 	public function scanModules($folder_path, $cache = true)
 	{
 		if ( defined('IS_INSTALL') && IS_INSTALL && !defined('FORCE_CONFIG_CACHE') ) {
 			// disable config caching during installation
 			$cache = false;
 		}
 
 		if ( $cache ) {
 			$restored = $this->Application->cacheManager->LoadUnitCache();
 
 			if ( $restored ) {
 				if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) {
 					$this->Application->Debugger->appendHTML('UnitConfigReader: Restoring Cache');
 				}
 
 				return;
 			}
 		}
 
 		if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) {
 			$this->Application->Debugger->appendHTML('UnitConfigReader: Generating Cache');
 		}
 
 		// === lines below would only executed on cold start (no unit config cache) ===
 
 		// no cache found -> include all unit configs to create it !
 		$this->includeConfigFiles($folder_path, $cache);
 		$this->parseConfigs();
+		$this->sortRouters();
 
 		// tell AfterConfigRead to store cache if needed
 		// can't store it here because AfterConfigRead needs ability to change config data
 		$this->storeCache = $cache;
 
 		if ( !$this->Application->InitDone ) {
 			// scanModules is called multiple times during installation process
 			$this->Application->InitManagers();
-
-			// get build-in rewrite listeners ONLY to be able to parse mod-rewrite url when unit config cache is missing
-			$this->retrieveCollections();
-			$this->sortRewriteListeners();
 		}
 
 		$this->Application->cacheManager->applyDelayedUnitProcessing();
 	}
 
 	/**
 	 * Locates (recursively) and reads all unit configs at given path.
 	 *
 	 * @param string  $folder_path Folder path.
 	 * @param boolean $cache       Store information to cache.
 	 *
 	 * @throws Exception When unit config file is missing a prefix defined inside it.
 	 */
 	protected function includeConfigFiles($folder_path, $cache = true)
 	{
 		$this->Application->refreshModuleInfo();
 
 		if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 			$data = $this->Application->getCache('master:config_files', false, $cache ? CacheSettings::$unitCacheRebuildTime : 0);
 		}
 		else {
 			$data = $this->Application->getDBCache('config_files', $cache ? CacheSettings::$unitCacheRebuildTime : 0);
 		}
 
 		if ( $data ) {
 			$this->configFiles = unserialize($data);
 
 			if ( !(defined('DBG_VALIDATE_CONFIGS') && DBG_VALIDATE_CONFIGS) ) {
 				shuffle($this->configFiles);
 			}
 		}
 		else {
 			$this->findConfigFiles(FULL_PATH . DIRECTORY_SEPARATOR . 'core'); // search from "core" directory
 			$this->findConfigFiles($folder_path); // search from "modules" directory
 
 			if ( $cache ) {
 				if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 					$this->Application->setCache('master:config_files', serialize($this->configFiles));
 				}
 				else {
 					$this->Application->setDBCache('config_files', serialize($this->configFiles));
 				}
 			}
 		}
 
 		foreach ( $this->configFiles as $filename ) {
 			$prefix = $this->PreloadConfigFile($filename);
 
 			if ( !$prefix ) {
 				throw new Exception('Prefix not defined in config file <strong>' . $filename . '</strong>');
 			}
 		}
 
 		// TODO: needed?
 		if ( $cache ) {
 			unset($this->configFiles);
 		}
 	}
 
 	/**
 	 * Recursively searches for unit configs in given folder.
 	 *
 	 * @param string $folder_path Path to the folder.
 	 * @param int    $level       Deep level of the folder.
 	 *
 	 * @return void
 	 */
 	protected function findConfigFiles($folder_path, $level = 0)
 	{
 		// if FULL_PATH = "/" ensure, that all "/" in $folderPath are not deleted
 		$reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/';
 		$folder_path = preg_replace($reg_exp, '', $folder_path, 1); // this make sense, since $folderPath may NOT contain FULL_PATH
 
 		$base_folder = FULL_PATH . $folder_path . DIRECTORY_SEPARATOR;
 		$sub_folders = glob($base_folder . '*', GLOB_ONLYDIR);
 
 		if ( !$sub_folders ) {
 			return;
 		}
 
 		if ( $level == 0 ) {
 			// don't scan Front-End themes because of extensive directory structure
 			$sub_folders = array_diff($sub_folders, array($base_folder . 'themes', $base_folder . 'tools'));
 		}
 
 		foreach ( $sub_folders as $full_path ) {
 			$sub_folder = substr($full_path, strlen($base_folder));
 
 			if ( in_array($sub_folder, $this->skipFolders) || preg_match('/^\./', $sub_folder) ) {
 				// don't scan skipped or hidden folders
 				continue;
 			}
 
 			$config_name = $this->getConfigName($folder_path . DIRECTORY_SEPARATOR . $sub_folder);
 
 			if ( file_exists(FULL_PATH . $config_name) ) {
 				$this->configFiles[] = $config_name;
 			}
 
 			$this->findConfigFiles($full_path, $level + 1);
 		}
 	}
 
 	/**
 	 * Process all read config files - called ONLY when there is no cache!
 	 *
 	 * @return void
 	 */
 	protected function parseConfigs()
 	{
 		$this->parseUnitConfigs($this->getUnitConfigsWithoutPriority());
 		$this->parseUnitConfigs($this->getUnitConfigsWithPriority());
 	}
 
 	/**
 	 * Parses unit config sub-set.
 	 *
 	 * @param array $prefixes Unit config prefixes.
 	 *
 	 * @return array
 	 */
 	protected function parseUnitConfigs(array $prefixes)
 	{
 		foreach ( $prefixes as $prefix ) {
 			$this->configData[$prefix]->parse();
 		}
 
 		$this->cloner->extrudeAndParse($prefixes);
 	}
 
 	/**
 	 * Returns unit configs prefixes without priority defined.
 	 *
 	 * @return array
 	 */
 	protected function getUnitConfigsWithoutPriority()
 	{
 		$ret = array();
 
 		foreach ( $this->configData as $prefix => $config ) {
 			if ( $config->getConfigPriority() === false ) {
 				$ret[] = $prefix;
 			}
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns unit configs prefixes with priority defined.
 	 *
 	 * @return array
 	 */
 	protected function getUnitConfigsWithPriority()
 	{
 		$ret = array();
 
 		foreach ( $this->configData as $prefix => $config ) {
 			$priority = $config->getConfigPriority();
 
 			if ( $priority !== false ) {
 				$ret[$prefix] = $priority;
 			}
 		}
 
 		asort($ret);
 
 		return array_keys($ret);
 	}
 
 	public function AfterConfigRead($store_cache = null)
 	{
 		$this->finalStage = true;
 
 		foreach ($this->configData as $prefix => $config) {
 			$this->runAfterConfigRead($prefix);
 		}
 
 		if ( !isset($store_cache) ) {
 			// $store_cache not overridden -> use global setting
 			$store_cache = $this->storeCache;
 		}
 
 		if ( $store_cache || (defined('IS_INSTALL') && IS_INSTALL) ) {
 			// cache is not stored during install, but dynamic clones should be processed in any case
 			$this->cloner->processDynamicallyAdded();
 			$this->retrieveCollections();
 		}
 
 		if ( $store_cache ) {
-			$this->sortRewriteListeners();
-
 			$this->Application->HandleEvent(new kEvent('adm:OnAfterCacheRebuild'));
 			$this->Application->cacheManager->UpdateUnitCache();
 
 			if ( defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_VALIDATE_CONFIGS') && DBG_VALIDATE_CONFIGS ) {
 				// validate configs here to have changes from OnAfterConfigRead hooks to prefixes
 				foreach ( $this->configData as $config ) {
 					if ( !$config->getTableName() ) {
 						continue;
 					}
 
 					$config->validate();
 				}
 			}
 		}
 	}
 
 	/**
-	 * Sort rewrite listeners according to RewritePriority (non-prioritized listeners goes first)
+	 * Sort routers according to their weight (non-prioritized routers goes first).
 	 *
 	 * @return void
 	 */
-	protected function sortRewriteListeners()
+	protected function sortRouters()
 	{
-		$listeners = array();
-		$prioritized_listeners = array();
-
-		// process non-prioritized listeners
-		foreach ( $this->Application->RewriteListeners as $prefix => $listener_data ) {
-			if ( $listener_data['priority'] === false ) {
-				$listeners[$prefix] = $listener_data;
+		$sorted_routers = array();
+		$prioritized_routers = array();
+		$routers = $this->collectRouters();
+
+		// Process non-prioritized routers.
+		foreach ( $routers as $prefix => $router_data ) {
+			if ( $router_data['priority'] === false ) {
+				$sorted_routers[$prefix] = $router_data;
 			}
 			else {
-				$prioritized_listeners[$prefix] = $listener_data['priority'];
+				$prioritized_routers[$prefix] = $router_data['priority'];
 			}
 		}
 
-		// process prioritized listeners
-		asort($prioritized_listeners, SORT_NUMERIC);
+		// Process prioritized routers.
+		asort($prioritized_routers, SORT_NUMERIC);
 
-		foreach ( $prioritized_listeners as $prefix => $priority ) {
-			$listeners[$prefix] = $this->Application->RewriteListeners[$prefix];
+		foreach ( $prioritized_routers as $prefix => $priority ) {
+			$sorted_routers[$prefix] = $routers[$prefix];
 		}
 
-		$this->Application->RewriteListeners = $listeners;
+		$this->Application->routers = $sorted_routers;
+	}
+
+	/**
+	 * Collects routers.
+	 *
+	 * @return array
+	 */
+	protected function collectRouters()
+	{
+		$routers = array();
+		$router_classes = $this->Application->getSubClasses('AbstractRouter');
+
+		foreach ( $router_classes as $router_class ) {
+			if ( !class_exists($router_class) ) {
+				// This can happen, when:
+				// - router class (coming from cache) was renamed;
+				// - new cache is built based on outdated class map.
+				continue;
+			}
+
+			/** @var AbstractRouter $router */
+			$router = new $router_class();
+			$routers[$router->getPrefix()] = array(
+				'class' => $router_class,
+				'priority' => $router->getWeight(),
+			);
+		}
+
+		return $routers;
 	}
 
 	/**
 	 * Re-reads all configs.
 	 *
 	 * @return void
 	 */
 	public function ReReadConfigs()
 	{
 		// don't reset prefix file, since file scanning could slow down the process
 		$prefix_files_backup = $this->prefixFiles;
 		$this->Application->cacheManager->EmptyUnitCache();
 		$this->prefixFiles = $prefix_files_backup;
 
 		// parse all configs
 		$this->afterConfigProcessed = array();
 		$this->includeConfigFiles(MODULES_PATH, false);
 		$this->parseConfigs();
+		$this->sortRouters();
 		$this->AfterConfigRead(false);
 		$this->cloner->processDynamicallyAdded();
 		$this->retrieveCollections();
 	}
 
 	/**
 	 * Process all collectible unit config options here to also catch ones, defined from OnAfterConfigRead events
 	 *
 	 */
 	protected function retrieveCollections()
 	{
 		foreach ( $this->configData as $prefix => $config ) {
 			// collect replacement templates
 			if ( $config->getReplacementTemplates() ) {
 				$this->Application->ReplacementTemplates = array_merge($this->Application->ReplacementTemplates, $config->getReplacementTemplates());
 			}
-
-			// collect rewrite listeners
-			if ( $config->getRewriteListener() ) {
-				$rewrite_listeners = $config->getRewriteListener();
-
-				if ( !is_array($rewrite_listeners) ) {
-					// when one method is used to build and parse url
-					$rewrite_listeners = array($rewrite_listeners, $rewrite_listeners);
-				}
-
-				foreach ( $rewrite_listeners as $index => $rewrite_listener ) {
-					if ( strpos($rewrite_listener, ':') === false ) {
-						$rewrite_listeners[$index] = $prefix . '_EventHandler:' . $rewrite_listener;
-					}
-				}
-
-				$rewrite_priority = $config->getRewritePriority();
-
-				$this->Application->RewriteListeners[$prefix] = array('listener' => $rewrite_listeners, 'priority' => $rewrite_priority);
-			}
 		}
 	}
 
 	public function loadConfig($prefix)
 	{
 		$preloaded_prefix = $this->PreloadConfigFile($this->getPrefixFile($prefix));
 
 		if ( $this->finalStage ) {
 			// run prefix OnAfterConfigRead so all hooks to it can define their clones
 			$this->runAfterConfigRead($preloaded_prefix);
 		}
 
 		$clones = $this->cloner->extrude($preloaded_prefix);
 
 		if ( $this->finalStage ) {
 			foreach ( $clones as $a_prefix ) {
 				$this->runAfterConfigRead($a_prefix);
 			}
 		}
 	}
 
 	/**
 	 * Runs OnAfterConfigRead event for given prefix once.
 	 *
 	 * @param string $prefix Unit config prefix.
 	 *
 	 * @return void
 	 */
 	public function runAfterConfigRead($prefix)
 	{
 		if ( in_array($prefix, $this->afterConfigProcessed) ) {
 			return;
 		}
 
 		$this->Application->HandleEvent(new kEvent($prefix . ':OnAfterConfigRead'));
 
 		if ( !(defined('IS_INSTALL') && IS_INSTALL) ) {
 			// allow to call OnAfterConfigRead multiple times during install
 			array_push($this->afterConfigProcessed, $prefix);
 		}
 	}
 
 	/**
 	 * Loads unit config file contents from disk.
 	 *
 	 * @param string $filename Unit config filename.
 	 *
 	 * @return string
 	 */
 	protected function PreloadConfigFile($filename)
 	{
 		$config_found = file_exists(FULL_PATH . $filename) && $this->configAllowed($filename);
 
 		if ( defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_PROFILE_INCLUDES') && DBG_PROFILE_INCLUDES ) {
 			if ( in_array($filename, get_included_files()) ) {
 				return '';
 			}
 
 			global $debugger;
 
 			if ( $config_found ) {
 				$file = FULL_PATH . $filename;
 				$file_crc = crc32($file);
 
 				$debugger->ProfileStart('inc_' . $file_crc, $file);
 				include_once($file);
 				$debugger->ProfileFinish('inc_' . $file_crc);
 				$debugger->profilerAddTotal('includes', 'inc_' . $file_crc);
 			}
 		}
 		elseif ( $config_found ) {
 			include_once(FULL_PATH . $filename);
 		}
 
 		if ( $config_found ) {
 			/* @var $config kUnitConfig|Array */
 
 			if ( isset($config) && $config ) {
 				// config file is included for 1st time -> save it's content for future processing
 				if ( !is_object($config) ) {
 					$prefix = array_key_exists('Prefix', $config) ? $config['Prefix'] : '';
 					$config = new kUnitConfig($prefix, $config);
 				}
 				else {
 					$prefix = $config->getPrefix();
 				}
 
 				preg_match($this->moduleFolderRegExp, $filename, $regs);
 				$config->setModuleFolder(str_replace(DIRECTORY_SEPARATOR, '/', $regs[1]));
 				$config->setBasePath(dirname(FULL_PATH . $filename));
 
 				if ( $config->getAdminTemplatePath() !== false ) {
 					// append template base folder for admin templates path of this prefix
 					$module_templates = $regs[1] == 'core' ? '' : substr($regs[1], 8) . '/';
 					$config->setAdminTemplatePath($module_templates . $config->getAdminTemplatePath());
 				}
 
 				if ( array_key_exists($prefix, $this->prefixFiles) && ($this->prefixFiles[$prefix] != $filename) ) {
 					trigger_error(
 						'Single unit config prefix "<strong>' . $prefix . '</strong>" ' .
 						'is used in multiple unit config files: ' .
 						'"<strong>' . $this->prefixFiles[$prefix] . '</strong>", "<strong>' . $filename . '</strong>"',
 						E_USER_WARNING
 					);
 				}
 
 				$this->add($config, $filename);
 
 				return $prefix;
 			}
 			else {
 				$prefix = array_search($filename, $this->prefixFiles);
 
 				if ( $prefix ) {
 					// attempt is made to include config file twice or more, but include_once prevents that,
 					// but file exists on hdd, then it is already saved to all required arrays, just return it's prefix
 					return $prefix;
 				}
 			}
 		}
 
 		return 'dummy';
 	}
 
 	/**
 	 * Sets a file, for a given prefix.
 	 *
 	 * @param kUnitConfig $config     Unit config.
 	 * @param string      $filename   File.
 	 *
 	 * @return void
 	 */
 	public function add(kUnitConfig $config, $filename)
 	{
 		$config->setFilename($filename);
 
 		$prefix = $config->getPrefix();
 		$this->configData[$prefix] = $config;
 		$this->prefixFiles[$prefix] = $filename;
 	}
 
 	/**
 	 * Removes unit config.
 	 *
 	 * @param string $prefix Unit config prefix.
 	 *
 	 * @return void
 	 */
 	public function remove($prefix)
 	{
 		unset($this->configData[$prefix], $this->prefixFiles[$prefix]);
 	}
 
 	/**
 	 * Returns unit config for given prefix
 	 *
 	 * @param string $prefix
 	 * @return kUnitConfig
 	 */
 	public function getUnitConfig($prefix = null)
 	{
 		if ( !isset($this->configData[$prefix]) ) {
 			$this->loadConfig($prefix);
 		}
 
 		return $this->configData[$prefix];
 	}
 
 	/**
 	 * Returns prefixes of unit configs, that were registered
 	 *
 	 * @return Array
 	 */
 	public function getPrefixes()
 	{
 		return array_keys($this->configData);
 	}
 
 	/**
 	 * Get's config file name based
 	 * on folder name supplied
 	 *
 	 * @param string $folder_path
 	 * @return string
 	 */
 	protected function getConfigName($folder_path)
 	{
 		return $folder_path . DIRECTORY_SEPARATOR . basename($folder_path) . '_config.php';
 	}
 
 	/**
 	 * Checks if config file is allowed for inclusion (if module of config is installed).
 	 *
 	 * @param string $config_path Relative path from In-Portal directory.
 	 *
 	 * @return boolean
 	 */
 	protected function configAllowed($config_path)
 	{
 		static $module_paths = null;
 
 		if ( defined('IS_INSTALL') && IS_INSTALL ) {
 			// at installation start no modules in db and kernel configs could not be read
 			return true;
 		}
 
 		if ( preg_match('#^' . $this->directorySeparator . 'core#', $config_path) ) {
 			// always allow to include configs from "core" module's folder
 			return true;
 		}
 
 		if ( !$this->Application->ModuleInfo ) {
 			return false;
 		}
 
 		if ( !isset($module_paths) ) {
 			$module_paths = array();
 
 			foreach ( $this->Application->ModuleInfo as $module_info ) {
 				$module_paths[] = str_replace('/', DIRECTORY_SEPARATOR, rtrim($module_info['Path'], '/'));
 			}
 
 			$module_paths = array_unique($module_paths);
 		}
 
 		preg_match($this->moduleFolderRegExp, $config_path, $regs);
 
 		// config file path starts with module folder path
 		return in_array($regs[1], $module_paths);
 	}
 
 	/**
 	 * Returns true if config exists and is allowed for reading
 	 *
 	 * @param string $prefix Unit config prefix.
 	 *
 	 * @return boolean
 	 */
 	public function prefixRegistered($prefix)
 	{
 		return isset($this->prefixFiles[$prefix]);
 	}
 
 	/**
 	 * Returns unit config file location by it's prefix.
 	 *
 	 * @param string $prefix Unit config prefix.
 	 *
 	 * @return string
 	 * @throws Exception When unit config is not found.
 	 */
 	public function getPrefixFile($prefix)
 	{
 		if ( !isset($this->prefixFiles[$prefix]) ) {
 			throw new Exception('Configuration file for prefix "<strong>' . $prefix . '</strong>" is unknown');
 		}
 
 		return $this->prefixFiles[$prefix];
 	}
 
-}
\ No newline at end of file
+}
Index: branches/5.3.x/core/units/categories/categories_event_handler.php
===================================================================
--- branches/5.3.x/core/units/categories/categories_event_handler.php	(revision 16170)
+++ branches/5.3.x/core/units/categories/categories_event_handler.php	(revision 16171)
@@ -1,3156 +1,2900 @@
 <?php
 /**
 * @version	$Id$
 * @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!');
 
 	class CategoriesEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnRebuildCache' => Array ('self' => 'add|edit'),
 				'OnCopy' => Array ('self' => true),
 				'OnCut' => Array ('self' => 'edit'),
 				'OnPasteClipboard' => Array ('self' => true),
 				'OnPaste' => Array ('self' => 'add|edit', 'subitem' => 'edit'),
 
 				'OnRecalculatePriorities' => Array ('self' => 'add|edit'), // category ordering
 				'OnItemBuild' => Array ('self' => true), // always allow to view individual categories (regardless of CATEGORY.VIEW right)
 				'OnUpdatePreviewBlock' => Array ('self' => true), // for FCKEditor integration
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Categories are sorted using special sorting event
 		 *
 		 */
 		function mapEvents()
 		{
 			parent::mapEvents();
 
 			$events_map = Array (
 				'OnMassMoveUp' => 'OnChangePriority',
 				'OnMassMoveDown' => 'OnChangePriority',
 			);
 
 			$this->eventMethods = array_merge($this->eventMethods, $events_map);
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( $event->Name == 'OnResetCMSMenuCache' ) {
 				// events from "Tools -> System Tools" section are controlled via that section "edit" permission
 
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				$perm_value = $this->Application->CheckPermission('in-portal:service.edit');
 
 				return $perm_helper->finalizePermissionCheck($event, $perm_value);
 			}
 
 			if ( !$this->Application->isAdmin ) {
 				if ( $event->Name == 'OnSetSortingDirect' ) {
 					// allow sorting on front event without view permission
 					return true;
 				}
 
 				if ( $event->Name == 'OnItemBuild' ) {
 					$category_id = $this->getPassedID($event);
 					if ( $category_id == 0 ) {
 						return true;
 					}
 				}
 			}
 
 			if ( in_array($event->Name, $this->_getMassPermissionEvents()) ) {
 				$items = $this->_getPermissionCheckInfo($event);
 
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				if ( ($event->Name == 'OnSave') && array_key_exists(0, $items) ) {
 					// adding new item (ID = 0)
 					$perm_value = $perm_helper->AddCheckPermission($items[0]['ParentId'], $event->Prefix) > 0;
 				}
 				else {
 					// leave only items, that can be edited
 					$ids = Array ();
 					$check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission';
 					foreach ($items as $item_id => $item_data) {
 						if ( $perm_helper->$check_method($item_data['CreatedById'], $item_data['ParentId'], $event->Prefix) > 0 ) {
 							$ids[] = $item_id;
 						}
 					}
 
 					if ( !$ids ) {
 						// no items left for editing -> no permission
 						return $perm_helper->finalizePermissionCheck($event, false);
 					}
 
 					$perm_value = true;
 					$event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method
 				}
 
 				return $perm_helper->finalizePermissionCheck($event, $perm_value);
 			}
 
 			if ( $event->Name == 'OnRecalculatePriorities' ) {
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				$category_id = $this->Application->GetVar('m_cat_id');
 
 				return $perm_helper->AddCheckPermission($category_id, $event->Prefix) || $perm_helper->ModifyCheckPermission(0, $category_id, $event->Prefix);
 			}
 
 			if ( $event->Name == 'OnPasteClipboard' ) {
 				// forces permission check to work by current category for "Paste In Category" operation
 				$category_id = $this->Application->GetVar('m_cat_id');
 				$this->Application->SetVar('c_id', $category_id);
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Returns events, that require item-based (not just event-name based) permission check
 		 *
 		 * @return Array
 		 */
 		function _getMassPermissionEvents()
 		{
 			return Array (
 				'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove',
 				'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown',
 				'OnCut',
 			);
 		}
 
 		/**
 		 * Returns category item IDs, that require permission checking
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
 		function _getPermissionCheckIDs($event)
 		{
 			if ($event->Name == 'OnSave') {
 				$selected_ids = implode(',', $this->getSelectedIDs($event, true));
 				if (!$selected_ids) {
 					$selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave)
 				}
 			}
 			else {
 				// OnEdit, OnMassDelete events, when items are checked in grid
 				$selected_ids = implode(',', $this->StoreSelectedIDs($event));
 			}
 
 			return $selected_ids;
 		}
 
 		/**
 		 * Returns information used in permission checking
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 */
 		function _getPermissionCheckInfo($event)
 		{
 			// when saving data from temp table to live table check by data from temp table
 			$config = $event->getUnitConfig();
 			$id_field = $config->getIDField();
 			$table_name = $config->getTableName();
 
 			if ($event->Name == 'OnSave') {
 				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $event->Prefix);
 			}
 
 			$sql = 'SELECT ' . $id_field . ', CreatedById, ParentId
 					FROM ' . $table_name . '
 					WHERE ' . $id_field . ' IN (' . $this->_getPermissionCheckIDs($event) . ')';
 			$items = $this->Conn->Query($sql, $id_field);
 
 			if (!$items) {
 				// when creating new category, then no IDs are stored in session
 				$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 				list ($id, $fields_hash) = each($items_info);
 
 				if (array_key_exists('ParentId', $fields_hash)) {
 					$item_category = $fields_hash['ParentId'];
 				}
 				else {
 					$item_category = $this->Application->RecallVar('m_cat_id'); // saved in c:OnPreCreate event permission checking
 				}
 
 				$items[$id] = Array (
 					'CreatedById' => $this->Application->RecallVar('user_id'),
 					'ParentId' => $item_category,
 				);
 			}
 
 			return $items;
 		}
 
 		/**
 		 * Set's mark, that root category is edited
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnEdit(kEvent $event)
 		{
 			$category_id = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
 			$home_category = $this->Application->getBaseCategory();
 
 			$this->Application->StoreVar('IsRootCategory_' . $this->Application->GetVar('m_wid'), ($category_id === '0') || ($category_id == $home_category));
 
 			parent::OnEdit($event);
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				// keep "Section Properties" link (in browse modes) clean
 				$this->Application->DeleteVar('admin');
 			}
 		}
 
 		/**
 		 * Adds selected link to listing
 		 *
 		 * @param kEvent $event
 		 */
 		function OnProcessSelected($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$selected_ids = $this->Application->GetVar('selected_ids');
 
 			$this->RemoveRequiredFields($object);
 			$object->SetDBField($this->Application->RecallVar('dst_field'), $selected_ids['c']);
 			$object->Update();
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Apply system filter to categories list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			// don't show "Content" category in advanced view
 			$object->addFilter('system_categories', '%1$s.Status <> 4');
 
 			// show system templates from current theme only + all virtual templates
 			$object->addFilter('theme_filter', '%1$s.ThemeId = ' . $this->_getCurrentThemeId() . ' OR %1$s.ThemeId = 0');
 
 			if ($event->Special == 'showall') {
 				// if using recycle bin don't show categories from there
 				$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 				if ($recycle_bin) {
 					$sql = 'SELECT TreeLeft, TreeRight
 							FROM '.TABLE_PREFIX.'Categories
 							WHERE CategoryId = '.$recycle_bin;
 					$tree_indexes = $this->Conn->GetRow($sql);
 
 					$object->addFilter('recyclebin_filter', '%1$s.TreeLeft < '.$tree_indexes['TreeLeft'].' OR %1$s.TreeLeft > '.$tree_indexes['TreeRight']);
 				}
 			}
 
 			if ( (string)$event->getEventParam('parent_cat_id') !== '' ) {
 				$parent_cat_id = $event->getEventParam('parent_cat_id');
 
 				if ("$parent_cat_id" == 'Root') {
 					$module_name = $event->getEventParam('module') ? $event->getEventParam('module') : 'In-Commerce';
 					$parent_cat_id = $this->Application->findModule('Name', $module_name, 'RootCat');
 				}
 			}
 			else {
 				$parent_cat_id = $this->Application->GetVar('c_id');
 				if (!$parent_cat_id) {
 					$parent_cat_id = $this->Application->GetVar('m_cat_id');
 				}
 				if (!$parent_cat_id) {
 					$parent_cat_id = 0;
 				}
 			}
 
 			if ("$parent_cat_id" == '0') {
 				// replace "0" category with "Content" category id (this way template
 				$parent_cat_id = $this->Application->getBaseCategory();
 			}
 
 			if ("$parent_cat_id" != 'any') {
 				if ($event->getEventParam('recursive')) {
 					if ($parent_cat_id > 0) {
 						// not "Home" category
 						$tree_indexes = $this->Application->getTreeIndex($parent_cat_id);
 
 						$object->addFilter('parent_filter', '%1$s.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']);
 					}
 				}
 				else {
 					$object->addFilter('parent_filter', '%1$s.ParentId = '.$parent_cat_id);
 				}
 			}
 
 			$this->applyViewPermissionFilter($object);
 
 			if (!$this->Application->isAdminUser)	{
 				// apply status filter only on front
 				$object->addFilter('status_filter', $object->TableName.'.Status = 1');
 			}
 
 			// process "types" and "except" parameters
 			$type_clauses = Array();
 
 			$types = $event->getEventParam('types');
 			$types = $types ? explode(',', $types) : Array ();
 
 			$except_types = $event->getEventParam('except');
 			$except_types = $except_types ? explode(',', $except_types) : Array ();
 
 			$config = $event->getUnitConfig();
 
 			if (in_array('related', $types) || in_array('related', $except_types)) {
 				$related_to = $event->getEventParam('related_to');
 				if (!$related_to) {
 					$related_prefix = $event->Prefix;
 				}
 				else {
 					$sql = 'SELECT Prefix
 							FROM '.TABLE_PREFIX.'ItemTypes
 							WHERE ItemName = '.$this->Conn->qstr($related_to);
 					$related_prefix = $this->Conn->GetOne($sql);
 				}
 
 				$rel_table = $this->Application->getUnitConfig('rel')->getTableName();
 				$item_type = (int)$config->getItemType();
 
 				if ($item_type == 0) {
 					trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING);
 				}
 
 				// process case, then this list is called inside another list
 				$prefix_special = $event->getEventParam('PrefixSpecial');
 				if (!$prefix_special) {
 					$prefix_special = $this->Application->Parser->GetParam('PrefixSpecial');
 				}
 
 				$id = false;
 				if ($prefix_special !== false) {
 					$processed_prefix = $this->Application->processPrefix($prefix_special);
 					if ($processed_prefix['prefix'] == $related_prefix) {
 						// printing related categories within list of items (not on details page)
 						$list = $this->Application->recallObject($prefix_special);
 						/* @var $list kDBList */
 
 						$id = $list->GetID();
 					}
 				}
 
 				if ($id === false) {
 					// printing related categories for single item (possibly on details page)
 					if ($related_prefix == 'c') {
 						$id = $this->Application->GetVar('m_cat_id');
 					}
 					else {
 						$id = $this->Application->GetVar($related_prefix . '_id');
 					}
 				}
 
 				$p_item = $this->Application->recallObject($related_prefix . '.current', null, Array('skip_autoload' => true));
 				/* @var $p_item kCatDBItem */
 
 				$p_item->Load( (int)$id );
 
 				$p_resource_id = $p_item->GetDBField('ResourceId');
 
 				$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
 						WHERE
 							(Enabled = 1)
 							AND (
 									(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 									OR
 									(Type = 1
 										AND (
 												(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 												OR
 												(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
 											)
 									)
 							)';
 
 				$related_ids_array = $this->Conn->Query($sql);
 				$related_ids = Array();
 
 				foreach ($related_ids_array as $key => $record) {
 					$related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ];
 				}
 
 				if (count($related_ids) > 0) {
 					$type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).')';
 					$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).')';
 				}
 				else {
 					$type_clauses['related']['include'] = '0';
 					$type_clauses['related']['except'] = '1';
 				}
 
 				$type_clauses['related']['having_filter'] = false;
 			}
 
 			if (in_array('category_related', $type_clauses)) {
 				$object->removeFilter('parent_filter');
 				$resource_id = $this->Conn->GetOne('
 								SELECT ResourceId FROM '.$config->getTableName().'
 								WHERE CategoryId = '.$parent_cat_id
 							);
 
 				$sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'CatalogRelationships
 						WHERE SourceId = '.$resource_id.' AND SourceType = 1';
 				$related_cats = $this->Conn->GetCol($sql);
 				$related_cats = is_array($related_cats) ? $related_cats : Array();
 
 				$sql = 'SELECT DISTINCT(SourceId) FROM '.TABLE_PREFIX.'CatalogRelationships
 						WHERE TargetId = '.$resource_id.' AND TargetType = 1 AND Type = 1';
 				$related_cats2 = $this->Conn->GetCol($sql);
 				$related_cats2 = is_array($related_cats2) ? $related_cats2 : Array();
 				$related_cats = array_unique( array_merge( $related_cats2, $related_cats ) );
 
 				if ($related_cats) {
 					$type_clauses['category_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')';
 					$type_clauses['category_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')';
 				}
 				else
 				{
 					$type_clauses['category_related']['include'] = '0';
 					$type_clauses['category_related']['except'] = '1';
 				}
 				$type_clauses['category_related']['having_filter'] = false;
 			}
 
 			if (in_array('product_related', $types)) {
 				$object->removeFilter('parent_filter');
 
 				$product_id = $event->getEventParam('product_id') ? $event->getEventParam('product_id') : $this->Application->GetVar('p_id');
 
 				$sql = 'SELECT ResourceId
 						FROM ' . $this->Application->getUnitConfig('p')->getTableName() . '
 						WHERE ProductId = ' . $product_id;
 				$resource_id = $this->Conn->GetOne($sql);
 
 				$sql = 'SELECT DISTINCT(TargetId)
 						FROM ' . TABLE_PREFIX . 'CatalogRelationships
 						WHERE SourceId = '.$resource_id.' AND TargetType = 1';
 				$related_cats = $this->Conn->GetCol($sql);
 
 				$related_cats = is_array($related_cats) ? $related_cats : Array();
 
 				$sql = 'SELECT DISTINCT(SourceId)
 						FROM ' . TABLE_PREFIX . 'CatalogRelationships
 						WHERE TargetId = '.$resource_id.' AND SourceType = 1 AND Type = 1';
 				$related_cats2 = $this->Conn->GetCol($sql);
 
 				$related_cats2 = is_array($related_cats2) ? $related_cats2 : Array();
 				$related_cats = array_unique( array_merge( $related_cats2, $related_cats ) );
 
 				if ($related_cats) {
 					$type_clauses['product_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')';
 					$type_clauses['product_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')';
 				}
 				else {
 					$type_clauses['product_related']['include'] = '0';
 					$type_clauses['product_related']['except'] = '1';
 				}
 
 				$type_clauses['product_related']['having_filter'] = false;
 			}
 
 			$type_clauses['menu']['include'] = '%1$s.IsMenu = 1';
 			$type_clauses['menu']['except'] = '%1$s.IsMenu = 0';
 			$type_clauses['menu']['having_filter'] = false;
 
 			if (in_array('search', $types) || in_array('search', $except_types)) {
 				$event_mapping = Array (
 					'simple'		=>	'OnSimpleSearch',
 					'subsearch'		=>	'OnSubSearch',
 					'advanced'		=>	'OnAdvancedSearch'
 				);
 
 				$keywords = $event->getEventParam('keyword_string');
 				$type = $this->Application->GetVar('search_type', 'simple');
 
 				if ( $keywords ) {
 					// processing keyword_string param of ListProducts tag
 					$this->Application->SetVar('keywords', $keywords);
 					$type = 'simple';
 				}
 
 				$search_event = $event_mapping[$type];
 				$this->$search_event($event);
 
 				$object = $event->getObject();
 				/* @var $object kDBList */
 
 				$search_sql = '	FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search
 								search_result JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
 				$sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL());
 
 				$object->SetSelectSQL($sql);
 
 				$object->addCalculatedField('Relevance', 'search_result.Relevance');
 
 				$type_clauses['search']['include'] = '1';
 				$type_clauses['search']['except'] = '0';
 				$type_clauses['search']['having_filter'] = false;
 			}
 
 			$search_helper = $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$search_helper->SetComplexFilter($event, $type_clauses, implode(',', $types), implode(',', $except_types));
 		}
 
 		/**
 		 * Adds filter, that uses *.VIEW permissions to determine if an item should be shown to a user.
 		 *
 		 * @param kDBList $object Object.
 		 *
 		 * @return void
 		 * @access protected
 		 */
 		protected function applyViewPermissionFilter(kDBList $object)
 		{
 			if ( !$this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) {
 				return;
 			}
 
 			if ( $this->Application->RecallVar('user_id') == USER_ROOT ) {
 				// for "root" CATEGORY.VIEW permission is checked for items lists too
 				$view_perm = 1;
 			}
 			else {
 				$count_helper = $this->Application->recallObject('CountHelper');
 				/* @var $count_helper kCountHelper */
 
 				list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($object->Prefix, 'perm');
 				$object->addFilter('perm_filter2', $view_filter);
 			}
 
 			$object->addFilter('perm_filter', 'perm.PermId = ' . $view_perm); // check for CATEGORY.VIEW permission
 		}
 
 		/**
 		 * Returns current theme id
 		 *
 		 * @return int
 		 */
 		function _getCurrentThemeId()
 		{
 			$themes_helper = $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			return (int)$themes_helper->getCurrentThemeId();
 		}
 
 		/**
 		 * Returns ID of current item to be edited
 		 * by checking ID passed in get/post as prefix_id
 		 * or by looking at first from selected ids, stored.
 		 * Returned id is also stored in Session in case
 		 * it was explicitly passed as get/post
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access public
 		 */
 		public function getPassedID(kEvent $event)
 		{
 			if ( ($event->Special == 'page') || $this->_isVirtual($event) || ($event->Prefix == 'st') ) {
 				return $this->_getPassedStructureID($event);
 			}
 
 			if ( $this->Application->isAdmin ) {
 				return parent::getPassedID($event);
 			}
 
 			return $this->Application->GetVar('m_cat_id');
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function _getPassedStructureID($event)
 		{
 			static $page_by_template = Array ();
 
 			if ( $event->Special == 'current' ) {
 				return $this->Application->GetVar('m_cat_id');
 			}
 
 			$event->setEventParam('raise_warnings', 0);
 
 			$page_id = parent::getPassedID($event);
 
 			if ( $page_id === false ) {
 				$template = $event->getEventParam('page');
 				if ( !$template ) {
 					$template = $this->Application->GetVar('t');
 				}
 
 				// bug: when template contains "-" symbols (or others, that stripDisallowed will replace) it's not found
 				if ( !array_key_exists($template, $page_by_template) ) {
 					$config = $event->getUnitConfig();
 					$template_crc = kUtil::crc32(mb_strtolower($template));
 
 					$sql = 'SELECT ' . $config->getIDField() . '
 							FROM ' . $config->getTableName() . '
 							WHERE
 								(
 									(NamedParentPathHash = ' . $template_crc . ') OR
 									(`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplateHash = ' . $template_crc . ')
 								) AND (ThemeId = ' . $this->_getCurrentThemeId() . ' OR ThemeId = 0)';
 
 					$page_id = $this->Conn->GetOne($sql);
 				}
 				else {
 					$page_id = $page_by_template[$template];
 				}
 
 				if ( $page_id ) {
 					$page_by_template[$template] = $page_id;
 				}
 			}
 
 			if ( !$page_id && !$this->Application->isAdmin ) {
 				$page_id = $this->Application->GetVar('m_cat_id');
 			}
 
 			return $page_id;
 		}
 
 		function ParentGetPassedID($event)
 		{
 			return parent::getPassedID($event);
 		}
 
 		/**
 		 * Adds calculates fields for item statuses
 		 *
 		 * @param kCatDBItem $object
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function prepareObject(&$object, kEvent $event)
 		{
 			if ( $this->_isVirtual($event) ) {
 				return;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$object->addCalculatedField(
 				'IsNew',
 				'	IF(%1$s.NewItem = 2,
 						IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '.
 							$this->Application->ConfigValue('Category_DaysNew').
 							'*3600*24), 1, 0),
 						%1$s.NewItem
 				)');
 		}
 
 		/**
 		 * Checks, that this is virtual page
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access protected
 		 */
 		protected function _isVirtual(kEvent $event)
 		{
 			return strpos($event->Special, '-virtual') !== false;
 		}
 
 		/**
 		 * Gets right special for configuring virtual page
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function _getCategorySpecial(kEvent $event)
 		{
 			return $this->_isVirtual($event) ? '-virtual' : $event->Special;
 		}
 
 		/**
 		 * Set correct parent path for newly created categories
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterCopyToLive(kEvent $event)
 		{
 			parent::OnAfterCopyToLive($event);
 
 			$object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true, 'live_table' => true));
 			/* @var $object CategoriesItem */
 
 			$parent_path = false;
 			$object->Load($event->getEventParam('id'));
 
 			if ( $event->getEventParam('temp_id') == 0 ) {
 				if ( $object->isLoaded() ) {
 					// update path only for real categories (not including "Home" root category)
 					$fields_hash = $object->buildParentBasedFields();
 					$this->Conn->doUpdate($fields_hash, $object->TableName, 'CategoryId = ' . $object->GetID());
 					$parent_path = $fields_hash['ParentPath'];
 				}
 			}
 			else {
 				$parent_path = $object->GetDBField('ParentPath');
 			}
 
 			if ( $parent_path ) {
 				$cache_updater = $this->Application->makeClass('kPermCacheUpdater', Array (null, $parent_path));
 				/* @var $cache_updater kPermCacheUpdater */
 
 				$cache_updater->OneStepRun();
 			}
 		}
 
 		/**
 		 * Set cache modification mark if needed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeDeleteFromLive(kEvent $event)
 		{
 			parent::OnBeforeDeleteFromLive($event);
 
 			$id = $event->getEventParam('id');
 
 			// loading anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event
 			$temp_object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $temp_object CategoriesItem */
 
 			$temp_object->Load($id);
 
 			if ( $id == 0 ) {
 				if ( $temp_object->isLoaded() ) {
 					// new category -> update cache (not loaded when "Home" category)
 					$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 				}
 
 				return ;
 			}
 
 			// existing category was edited, check if in-cache fields are modified
 			$live_object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('live_table' => true, 'skip_autoload' => true));
 			/* @var $live_object CategoriesItem */
 
 			$live_object->Load($id);
 			$cached_fields = Array ('l' . $this->Application->GetDefaultLanguageId() . '_Name', 'Filename', 'Template', 'ParentId', 'Priority');
 
 			foreach ($cached_fields as $cached_field) {
 				if ( $live_object->GetDBField($cached_field) != $temp_object->GetDBField($cached_field) ) {
 					// use session instead of REQUEST because of permission editing in category can contain
 					// multiple submits, that changes data before OnSave event occurs
 					$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 					break;
 				}
 			}
 
 			// remember category filename change between temp and live records
 			if ( $temp_object->GetDBField('Filename') != $live_object->GetDBField('Filename') ) {
 				$filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ());
 
 				$filename_changes[ $live_object->GetID() ] = Array (
 					'from' => $live_object->GetDBField('Filename'),
 					'to' => $temp_object->GetDBField('Filename')
 				);
 
 				$this->Application->SetVar($event->Prefix . '_filename_changes', $filename_changes);
 			}
 		}
 
 		/**
 		 * Calls kDBEventHandler::OnSave original event
 		 * Used in proj-cms:StructureEventHandler->OnSave
 		 *
 		 * @param kEvent $event
 		 */
 		function parentOnSave($event)
 		{
 			parent::OnSave($event);
 		}
 
 		/**
 		 * Reset root-category flag when new category is created
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			// 1. for permission editing of Home category
 			$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 
 			parent::OnPreCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// 2. preset template
 			$category_id = $this->Application->GetVar('m_cat_id');
 			$root_category = $this->Application->getBaseCategory();
 
 			if ( $category_id == $root_category ) {
 				$object->SetDBField('Template', $this->_getDefaultDesign());
 			}
 
 			// 3. set default owner
 			$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 		}
 
 		/**
 		 * Checks cache update mark and redirect to cache if needed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSave(kEvent $event)
 		{
 			// get data from live table before it is overwritten by parent OnSave method call
 			$ids = $this->getSelectedIDs($event, true);
 			$is_editing = implode('', $ids);
 			$old_statuses = $is_editing ? $this->_getCategoryStatus($ids) : Array ();
 
 			$object = $event->getObject();
 			/* @var $object CategoriesItem */
 
 			parent::OnSave($event);
 
 			if ( $event->status != kEvent::erSUCCESS ) {
 				return;
 			}
 
 			if ( $this->Application->RecallVar('PermCache_UpdateRequired') ) {
 				$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 			}
 
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 			$this->_resetMenuCache();
 
 			if ( $is_editing ) {
 				// send email event to category owner, when it's status is changed (from admin)
 				$object->SwitchToLive();
 				$new_statuses = $this->_getCategoryStatus($ids);
 				$process_statuses = Array (STATUS_ACTIVE, STATUS_DISABLED);
 
 				foreach ($new_statuses as $category_id => $new_status) {
 					if ( $new_status != $old_statuses[$category_id] && in_array($new_status, $process_statuses) ) {
 						$object->Load($category_id);
 						$email_event = $new_status == STATUS_ACTIVE ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
 						$this->Application->emailUser($email_event, $object->GetDBField('CreatedById'), $object->getEmailParams());
 					}
 				}
 			}
 
 			// change opener stack in case if edited category filename was changed
 			$filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ());
 
 			if ( $filename_changes ) {
 				$opener_stack = $this->Application->makeClass('kOpenerStack');
 				/* @var $opener_stack kOpenerStack */
 
 				list ($template, $params, $index_file) = $opener_stack->pop();
 
 				foreach ($filename_changes as $change_info) {
 					$template = str_ireplace($change_info['from'], $change_info['to'], $template);
 				}
 
 				$opener_stack->push($template, $params, $index_file);
 				$opener_stack->save();
 			}
 		}
 
 		/**
 		 * Returns statuses of given categories
 		 *
 		 * @param Array $category_ids
 		 * @return Array
 		 */
 		function _getCategoryStatus($category_ids)
 		{
 			$config = $this->getUnitConfig();
 			$id_field = $config->getIDField();
 			$table_name = $config->getTableName();
 
 			$sql = 'SELECT Status, ' . $id_field . '
 					FROM ' . $table_name . '
 					WHERE ' . $id_field . ' IN (' . implode(',', $category_ids) . ')';
 
 			return $this->Conn->GetCol($sql, $id_field);
 		}
 
 		/**
 		 * Creates a new item in temp table and
 		 * stores item id in App vars and Session on success
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveCreated(kEvent $event)
 		{
 			$object = $event->getObject( Array ('skip_autoload' => true) );
 			/* @var $object CategoriesItem */
 
 			if ( $object->IsRoot() ) {
 				// don't create root category while saving permissions
 				return;
 			}
 
 			parent::OnPreSaveCreated($event);
 		}
 
 		/**
 		 * Deletes sym link to other category
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sql = 'UPDATE ' . $object->TableName . '
 					SET SymLinkCategoryId = NULL
 					WHERE SymLinkCategoryId = ' . $object->GetID();
 			$this->Conn->Query($sql);
 
 			// delete direct subscriptions to category, that was deleted
 			$sql = 'SELECT SubscriptionId
 					FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions
 					WHERE CategoryId = ' . $object->GetID();
 			$ids = $this->Conn->GetCol($sql);
 
 			if ( $ids ) {
 				$temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event->MasterEvent));
 				/* @var $temp_handler kTempTablesHandler */
 
 				$temp_handler->DeleteItems('system-event-subscription', '', $ids);
 			}
 		}
 
 		/**
 		 * Exclude root categories from deleting
 		 *
 		 * @param kEvent $event
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(kEvent $event, $type)
 		{
 			if ( $event->Name == 'OnMassDelete' && $type == 'before' ) {
 				$ids = $event->getEventParam('ids');
 				if ( !$ids || $this->Application->ConfigValue('AllowDeleteRootCats') ) {
 					return;
 				}
 
 				$root_categories = Array ();
 
 				// get module root categories and exclude them
 				foreach ($this->Application->ModuleInfo as $module_info) {
 					$root_categories[] = $module_info['RootCat'];
 				}
 
 				$root_categories = array_unique($root_categories);
 
 				if ( $root_categories && array_intersect($ids, $root_categories) ) {
 					$event->setEventParam('ids', array_diff($ids, $root_categories));
 					$this->Application->StoreVar('root_delete_error', 1);
 				}
 			}
 		}
 
 		/**
 		 * Checks, that given template exists (physically) in given theme
 		 *
 		 * @param string $template
 		 * @param int $theme_id
 		 * @return bool
 		 */
 		function _templateFound($template, $theme_id = null)
 		{
 			static $init_made = false;
 
 			if (!$init_made) {
 				$this->Application->InitParser(true);
 				$init_made = true;
 			}
 
 			if (!isset($theme_id)) {
 				$theme_id = $this->_getCurrentThemeId();
 			}
 
 			$theme_name = $this->_getThemeName($theme_id);
 
 			return $this->Application->TemplatesCache->TemplateExists('theme:' . $theme_name . '/' . $template);
 		}
 
 		/**
 		 * Removes ".tpl" in template path
 		 *
 		 * @param string $template
 		 * @return string
 		 */
 		function _stripTemplateExtension($template)
 		{
 	//		return preg_replace('/\.[^.\\\\\\/]*$/', '', $template);
 
 			return preg_replace('/^[\\/]{0,1}(.*)\.tpl$/', "$1", $template);
 		}
 
 		/**
 		 * Deletes all selected items.
 		 * Automatically recourse into sub-items using temp handler, and deletes sub-items
 		 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnMassDelete(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$to_delete = Array ();
 			$ids = $this->StoreSelectedIDs($event);
 			$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 
 			if ( $recycle_bin ) {
 				$rb = $this->Application->recallObject('c.recycle', null, Array ('skip_autoload' => true));
 				/* @var $rb CategoriesItem */
 
 				$rb->Load($recycle_bin);
 
 				$cat = $event->getObject(Array ('skip_autoload' => true));
 				/* @var $cat CategoriesItem */
 
 				foreach ($ids as $id) {
 					$cat->Load($id);
 
 					if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $cat->GetDBField('ParentPath')) ) {
 						// already in "Recycle Bin" -> delete for real
 						$to_delete[] = $id;
 						continue;
 					}
 
 					// just move into "Recycle Bin" category
 					$cat->SetDBField('ParentId', $recycle_bin);
 					$cat->Update();
 				}
 
 				$ids = $to_delete;
 			}
 
 			$event->setEventParam('ids', $ids);
 			$this->customProcessing($event, 'before');
 			$ids = $event->getEventParam('ids');
 
 			if ( $ids ) {
 				$recursive_helper = $this->Application->recallObject('RecursiveHelper');
 				/* @var $recursive_helper kRecursiveHelper */
 
 				foreach ($ids as $id) {
 					$recursive_helper->DeleteCategory($id, $event->Prefix);
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 
 			$this->_ensurePermCacheRebuild($event);
 		}
 
 		/**
 		 * Add selected items to clipboard with mode = COPY (CLONE)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCopy($event)
 		{
 			$this->Application->RemoveVar('clipboard');
 
 			$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
 			/* @var $clipboard_helper kClipboardHelper */
 
 			$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Add selected items to clipboard with mode = CUT
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCut($event)
 		{
 			$this->Application->RemoveVar('clipboard');
 
 			$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
 			/* @var $clipboard_helper kClipboardHelper */
 
 			$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Controls all item paste operations. Can occur only with filled clipboard.
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPasteClipboard($event)
 		{
 			$clipboard = unserialize( $this->Application->RecallVar('clipboard') );
 			foreach ($clipboard as $prefix => $clipboard_data) {
 				$paste_event = new kEvent($prefix.':OnPaste', Array('clipboard_data' => $clipboard_data));
 				$this->Application->HandleEvent($paste_event);
 
 				$event->copyFrom($paste_event);
 			}
 		}
 
 		/**
 		 * Checks permission for OnPaste event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function _checkPastePermission($event)
 		{
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$category_id = $this->Application->GetVar('m_cat_id');
 			if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) {
 				// no items left for editing -> no permission
 				return $perm_helper->finalizePermissionCheck($event, false);
 			}
 
 			return true;
 		}
 
 		/**
 		 * Paste categories with sub-items from clipboard
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPaste($event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$clipboard_data = $event->getEventParam('clipboard_data');
 
 			if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) {
 				return;
 			}
 
 			// 1. get ParentId of moved category(-es) before it gets updated!!!)
 			$source_category_id = 0;
 			$config = $event->getUnitConfig();
 			$id_field = $config->getIDField();
 			$table_name = $config->getTableName();
 
 			if ( $clipboard_data['cut'] ) {
 				$sql = 'SELECT ParentId
 						FROM ' . $table_name . '
 						WHERE ' . $id_field . ' = ' . $clipboard_data['cut'][0];
 				$source_category_id = $this->Conn->GetOne($sql);
 			}
 
 			$recursive_helper = $this->Application->recallObject('RecursiveHelper');
 			/* @var $recursive_helper kRecursiveHelper */
 
 			if ( $clipboard_data['cut'] ) {
 				$recursive_helper->MoveCategories($clipboard_data['cut'], $this->Application->GetVar('m_cat_id'));
 			}
 
 			if ( $clipboard_data['copy'] ) {
 				// don't allow to copy/paste system OR theme-linked virtual pages
 
 				$sql = 'SELECT ' . $id_field . '
 						FROM ' . $table_name . '
 						WHERE ' . $id_field . ' IN (' . implode(',', $clipboard_data['copy']) . ') AND (`Type` = ' . PAGE_TYPE_VIRTUAL . ') AND (ThemeId = 0)';
 				$allowed_ids = $this->Conn->GetCol($sql);
 
 				if ( !$allowed_ids ) {
 					return;
 				}
 
 				foreach ($allowed_ids as $id) {
 					$recursive_helper->PasteCategory($id, $event->Prefix);
 				}
 			}
 
 			$priority_helper = $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			if ( $clipboard_data['cut'] ) {
 				$ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $source_category_id);
 
 				if ( $ids ) {
 					$priority_helper->massUpdateChanged($event->Prefix, $ids);
 				}
 			}
 
 			// recalculate priorities of newly pasted categories in destination category
 			$parent_id = $this->Application->GetVar('m_cat_id');
 			$ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id);
 
 			if ( $ids ) {
 				$priority_helper->massUpdateChanged($event->Prefix, $ids);
 			}
 
 			if ( $clipboard_data['cut'] || $clipboard_data['copy'] ) {
 				$this->_ensurePermCacheRebuild($event);
 			}
 		}
 
 		/**
 		 * Ensures, that category permission cache is rebuild when category is added/edited/deleted
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function _ensurePermCacheRebuild(kEvent $event)
 		{
 			$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 		}
 
 		/**
 		 * Occurs when pasting category
 		 *
 		 * @param kEvent $event
 		 */
 		/*function OnCatPaste($event)
 		{
 			$inp_clipboard = $this->Application->RecallVar('ClipBoard');
 			$inp_clipboard = explode('-', $inp_clipboard, 2);
 
 			if($inp_clipboard[0] == 'COPY')
 			{
 				$config = $event->getUnitConfig();
 				$cat_ids = $event->getEventParam('cat_ids');
 				$saved_cat_id = $this->Application->GetVar('m_cat_id');
 
 				$ids_sql = 'SELECT ' . $config->getIDField() . '
 							FROM ' . $config->getTableName() . '
 							WHERE ResourceId IN (%s)';
 
 				$resource_ids_sql = 'SELECT ItemResourceId FROM '.TABLE_PREFIX.'CategoryItems WHERE CategoryId = %s AND PrimaryCat = 1';
 
 				$object = $this->Application->recallObject($event->Prefix.'.item', $event->Prefix, Array('skip_autoload' => true));
 
 				foreach($cat_ids as $source_cat => $dest_cat)
 				{
 					$item_resource_ids = $this->Conn->GetCol( sprintf($resource_ids_sql, $source_cat) );
 					if(!$item_resource_ids) continue;
 
 					$this->Application->SetVar('m_cat_id', $dest_cat);
 					$item_ids = $this->Conn->GetCol( sprintf($ids_sql, implode(',', $item_resource_ids) ) );
 
 					$temp = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 					if($item_ids) $temp->CloneItems($event->Prefix, $event->Special, $item_ids);
 				}
 
 				$this->Application->SetVar('m_cat_id', $saved_cat_id);
 			}
 		}*/
 
 		/**
 		 * Clears clipboard content
 		 *
 		 * @param kEvent $event
 		 */
 		function OnClearClipboard($event)
 		{
 			$this->Application->RemoveVar('clipboard');
 		}
 
 		/**
 		 * Sets correct status for new categories created on front-end
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object CategoriesItem */
 
 			if ( $object->GetDBField('ParentId') <= 0 ) {
 				// no parent category - use current (happens during import)
 				$object->SetDBField('ParentId', $this->Application->GetVar('m_cat_id'));
 			}
 
 			$this->_beforeItemChange($event);
 
 			if ( $this->Application->isAdmin || $event->Prefix == 'st' ) {
 				// don't check category permissions when auto-creating structure pages
 				return ;
 			}
 
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$new_status = false;
 			$category_id = $this->Application->GetVar('m_cat_id');
 
 			if ( $perm_helper->CheckPermission('CATEGORY.ADD', 0, $category_id) ) {
 				$new_status = STATUS_ACTIVE;
 			}
 			else {
 				if ( $perm_helper->CheckPermission('CATEGORY.ADD.PENDING', 0, $category_id) ) {
 					$new_status = STATUS_PENDING;
 				}
 			}
 
 			if ( $new_status ) {
 				$object->SetDBField('Status', $new_status);
 
 				// don't forget to set Priority for suggested from Front-End categories
 				$min_priority = $this->_getNextPriority($object->GetDBField('ParentId'), $object->TableName);
 				$object->SetDBField('Priority', $min_priority);
 			}
 			else {
 				$event->status = kEvent::erPERM_FAIL;
 				return ;
 			}
 		}
 
 		/**
 		 * Returns next available priority for given category from given table
 		 *
 		 * @param int $category_id
 		 * @param string $table_name
 		 * @return int
 		 */
 		function _getNextPriority($category_id, $table_name)
 		{
 			$sql = 'SELECT MIN(Priority)
 					FROM ' . $table_name . '
 					WHERE ParentId = ' . $category_id;
 			return (int)$this->Conn->GetOne($sql) - 1;
 		}
 
 		/**
 		 * Sets correct status for new categories created on front-end
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->_beforeItemChange($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->GetChangedFields() ) {
 				$object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
 			}
 		}
 
 		/**
 		 * Creates needed sql query to load item,
 		 * if no query is defined in config for
 		 * special requested, then use list query
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function ItemPrepareQuery(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sqls = $object->getFormOption('ItemSQLs', Array ());
 			$category_special = $this->_getCategorySpecial($event);
 			$special = isset($sqls[$category_special]) ? $category_special : '';
 
 			// preferred special not found in ItemSQLs -> use analog from ListSQLs
 
 			return isset($sqls[$special]) ? $sqls[$special] : $this->ListPrepareQuery($event);
 		}
 
 		/**
 		 * Creates needed sql query to load list,
 		 * if no query is defined in config for
 		 * special requested, then use default
 		 * query
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function ListPrepareQuery(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$special = $this->_getCategorySpecial($event);
 			$sqls = $object->getFormOption('ListSQLs', Array ());
 
 			return $sqls[array_key_exists($special, $sqls) ? $special : ''];
 		}
 
 		/**
 		 * Performs redirect to correct suggest confirmation template
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCreate(kEvent $event)
 		{
 			parent::OnCreate($event);
 
 			if ( $this->Application->isAdmin || $event->status != kEvent::erSUCCESS ) {
 				// don't sent email or rebuild cache directly after category is created by admin
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$cache_updater = $this->Application->makeClass('kPermCacheUpdater', Array (null, $object->GetDBField('ParentPath')));
 			/* @var $cache_updater kPermCacheUpdater */
 
 			$cache_updater->OneStepRun();
 
 			$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
 
 			$next_template = $is_active ? 'suggest_confirm_template' : 'suggest_pending_confirm_template';
 			$event->redirect = $this->Application->GetVar($next_template);
 			$event->SetRedirectParam('opener', 's');
 
 			// send email events
 			$send_params = $object->getEmailParams();
 			$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
 			$perm_prefix = $event->getUnitConfig()->getPermItemPrefix();
 
 			$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $object->GetDBField('CreatedById'), $send_params);
 			$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix, null, $send_params);
 		}
 
 		/**
 		 * Returns current per-page setting for list
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access protected
 		 */
 		protected function getPerPage(kEvent $event)
 		{
 			if ( !$this->Application->isAdmin ) {
 				$same_special = $event->getEventParam('same_special');
 				$event->setEventParam('same_special', true);
 
 				$per_page = parent::getPerPage($event);
 
 				$event->setEventParam('same_special', $same_special);
 			}
 
 			return parent::getPerPage($event);
 		}
 
 		/**
 		 * Set's correct page for list based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetPagination(kEvent $event)
 		{
 			parent::SetPagination($event);
 
 			if ( !$this->Application->isAdmin ) {
 				$page_var = $event->getEventParam('page_var');
 
 				if ( $page_var !== false ) {
 					$page = $this->Application->GetVar($page_var);
 
 					if ( is_numeric($page) ) {
 						$object = $event->getObject();
 						/* @var $object kDBList */
 
 						$object->SetPage($page);
 					}
 				}
 			}
 		}
 
 		/**
 		 * Apply same processing to each item being selected in grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function iterateItems(kEvent $event)
 		{
 			if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) {
 				parent::iterateItems($event);
 			}
 
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object CategoriesItem */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ( $ids ) {
 				$propagate_category_status = $this->Application->GetVar('propagate_category_status');
 				$status_field = $event->getUnitConfig()->getStatusField(true);
 
 				foreach ($ids as $id) {
 					$object->Load($id);
 					$object->SetDBField($status_field, $event->Name == 'OnMassApprove' ? 1 : 0);
 
 					if ( $object->Update() ) {
 						if ( $propagate_category_status ) {
 							$sql = 'UPDATE ' . $object->TableName . '
 									SET ' . $status_field . ' = ' . $object->GetDBField($status_field) . '
 									WHERE TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight');
 							$this->Conn->Query($sql);
 						}
 
 						$event->status = kEvent::erSUCCESS;
 
 						$email_event = $event->Name == 'OnMassApprove' ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
 						$this->Application->emailUser($email_event, $object->GetDBField('CreatedById'), $object->getEmailParams());
 					}
 					else {
 						$event->status = kEvent::erFAIL;
 						$event->redirect = false;
 						break;
 					}
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 		}
 
 		/**
 		 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access protected
 		 */
 		protected function checkItemStatus(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->isLoaded() ) {
 				return true;
 			}
 
 			if ( $object->GetDBField('Status') != STATUS_ACTIVE && $object->GetDBField('Status') != 4 ) {
 				if ( !$object->GetDBField('DirectLinkEnabled') || !$object->GetDBField('DirectLinkAuthKey') ) {
 					return false;
 				}
 
 				return $this->Application->GetVar('authkey') == $object->GetDBField('DirectLinkAuthKey');
 			}
 
 			return true;
 		}
 
 		/**
 		 * Set's correct sorting for list based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetSorting(kEvent $event)
 		{
 			$types = $event->getEventParam('types');
 			$types = $types ? explode(',', $types) : Array ();
 
 			if ( in_array('search', $types) ) {
 				$event->setPseudoClass('_List');
 
 				$object = $event->getObject();
 				/* @var $object kDBList */
 
 				// 1. no user sorting - sort by relevance
 				$default_sortings = parent::_getDefaultSorting($event);
 				$default_sorting = key($default_sortings['Sorting']) . ',' . current($default_sortings['Sorting']);
 
 				if ( $object->isMainList() ) {
 					$sort_by = $this->Application->GetVar('sort_by', '');
 
 					if ( !$sort_by ) {
 						$this->Application->SetVar('sort_by', 'Relevance,desc|' . $default_sorting);
 					}
 					elseif ( strpos($sort_by, 'Relevance,') !== false ) {
 						$this->Application->SetVar('sort_by', $sort_by . '|' . $default_sorting);
 					}
 				}
 				else {
 					$sorting_settings = $this->getListSetting($event, 'Sortings');
 					$sort_by = trim(getArrayValue($sorting_settings, 'Sort1') . ',' . getArrayValue($sorting_settings, 'Sort1_Dir'), ',');
 
 					if ( !$sort_by ) {
 						$event->setEventParam('sort_by', 'Relevance,desc|' . $default_sorting);
 					}
 					elseif ( strpos($sort_by, 'Relevance,') !== false ) {
 						$event->setEventParam('sort_by', $sort_by . '|' . $default_sorting);
 					}
 				}
 
 				$this->_removeForcedSortings($event);
 			}
 
 			parent::SetSorting($event);
 		}
 
 		/**
 		 * Removes forced sortings
 		 *
 		 * @param kEvent $event
 		 */
 		protected function _removeForcedSortings(kEvent $event)
 		{
 			$config = $event->getUnitConfig();
 
 			foreach ($config->getListSortingSpecials() as $special) {
 				$list_sortings = $config->getListSortingsBySpecial($special);
 				unset($list_sortings['ForcedSorting']);
 				$config->setListSortingsBySpecial('', $list_sortings);
 			}
 		}
 
 		/**
 		 * Default sorting in search results only comes from relevance field
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getDefaultSorting(kEvent $event)
 		{
 			$types = $event->getEventParam('types');
 			$types = $types ? explode(',', $types) : Array ();
 
 			return in_array('search', $types) ? Array () : parent::_getDefaultSorting($event);
 		}
 
 		// ============= for cms page processing =======================
 
 		/**
 		 * Returns default design template
 		 *
 		 * @return string
 		 */
 		function _getDefaultDesign()
 		{
 			$default_design = trim($this->Application->ConfigValue('cms_DefaultDesign'), '/');
 
 			if (!$default_design) {
 				// theme-based alias for default design
 				return '#default_design#';
 			}
 
 			if (strpos($default_design, '#') === false) {
 				// real template, not alias, so prefix with "/"
 				return '/' . $default_design;
 			}
 
 			// alias
 			return $default_design;
 		}
 
 		/**
 		 * Returns default design based on given virtual template (used from kApplication::Run)
 		 *
 		 * @param string $t
 		 * @return string
 		 * @access public
 		 */
 		public function GetDesignTemplate($t = null)
 		{
 			if ( !isset($t) ) {
 				$t = $this->Application->GetVar('t');
 			}
 
 			$page = $this->Application->recallObject($this->Prefix . '.-virtual', null, Array ('page' => $t));
 			/* @var $page CategoriesItem */
 
 			if ( $page->isLoaded() ) {
 				$real_t = $page->GetDBField('CachedTemplate');
 				$this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId'));
 
 				if ( $page->GetDBField('FormId') ) {
 					$this->Application->SetVar('form_id', $page->GetDBField('FormId'));
 				}
 			}
 			else {
 				$not_found = $this->Application->ConfigValue('ErrorTemplate');
 				$real_t = $not_found ? $not_found : 'error_notfound';
 
 				$themes_helper = $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				$theme_id = $this->Application->GetVar('m_theme');
 				$category_id = $themes_helper->getPageByTemplate($real_t, $theme_id);
 				$this->Application->SetVar('m_cat_id', $category_id);
 
 				header('HTTP/1.0 404 Not Found');
 			}
 
 			// replace alias in form #alias_name# to actual template used in this theme
 			if ( $this->Application->isAdmin ) {
 				$themes_helper = $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				// only, used when in "Design Mode"
 				$this->Application->SetVar('theme.current_id', $themes_helper->getCurrentThemeId());
 			}
 
 			$theme = $this->Application->recallObject('theme.current');
 			/* @var $theme kDBItem */
 
 			$template = $theme->GetField('TemplateAliases', $real_t);
 
 			if ( $template ) {
 				return $template;
 			}
 
 			return $real_t;
 		}
 
 		/**
 		 * Sets category id based on found template (used from kApplication::Run)
 		 *
 		 * @deprecated
 		 */
 		/*function SetCatByTemplate()
 		{
 			$t = $this->Application->GetVar('t');
 			$page = $this->Application->recallObject($this->Prefix . '.-virtual');
 
 			if ( $page->isLoaded() ) {
 				$this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId'));
 			}
 		}*/
 
 		/**
 		 * Prepares template paths
 		 *
 		 * @param kEvent $event
 		 */
 		function _beforeItemChange($event)
 		{
 			$object = $event->getObject();
 			/* @var $object CategoriesItem */
 
 			$object->checkFilename();
 			$object->generateFilename();
 
 			$now = time();
 
 			if ( !$this->Application->isDebugMode() && strpos($event->Special, 'rebuild') === false ) {
 				$object->SetDBField('Type', $object->GetOriginalField('Type'));
 				$object->SetDBField('Protected', $object->GetOriginalField('Protected'));
 
 				if ( $object->GetDBField('Protected') ) {
 					// some fields are read-only for protected pages, when debug mode is off
 					$object->SetDBField('AutomaticFilename', $object->GetOriginalField('AutomaticFilename'));
 					$object->SetDBField('Filename', $object->GetOriginalField('Filename'));
 					$object->SetDBField('Status', $object->GetOriginalField('Status'));
 				}
 			}
 
 			$is_admin = $this->Application->isAdminUser;
 
 			if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField('CreatedById')) ) {
 				$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 			}
 
 			if ($object->GetChangedFields()) {
 				$object->SetDBField('Modified_date', $now);
 				$object->SetDBField('Modified_time', $now);
 			}
 
 			$object->setRequired('PageCacheKey', $object->GetDBField('OverridePageCacheKey'));
 			$object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') ));
 
 			if ($object->GetDBField('Type') == PAGE_TYPE_TEMPLATE) {
 				if (!$this->_templateFound($object->GetDBField('Template'), $object->GetDBField('ThemeId'))) {
 					$object->SetError('Template', 'template_file_missing', 'la_error_TemplateFileMissing');
 				}
 			}
 
 			$this->_saveTitleField($object, 'Title');
 			$this->_saveTitleField($object, 'MenuTitle');
 
 			$root_category = $this->Application->getBaseCategory();
 
 			if ( file_exists(FULL_PATH . '/themes') && ($object->GetDBField('ParentId') == $root_category) && ($object->GetDBField('Template') == CATEGORY_TEMPLATE_INHERIT) ) {
 				// there are themes + creating top level category
 				$object->SetError('Template', 'no_inherit');
 			}
 
 			if ( !$this->Application->isAdminUser && $object->isVirtualField('cust_RssSource') ) {
 				// only administrator can set/change "cust_RssSource" field
 
 				if ($object->GetDBField('cust_RssSource') != $object->GetOriginalField('cust_RssSource')) {
 					$object->SetError('cust_RssSource', 'not_allowed', 'la_error_OperationNotAllowed');
 				}
 			}
 
 			if ( !$object->GetDBField('DirectLinkAuthKey') ) {
 				$key_parts = Array (
 					$object->GetID(),
 					$object->GetDBField('ParentId'),
 					$object->GetField('Name'),
 					'b38'
 				);
 
 				$object->SetDBField('DirectLinkAuthKey', substr( md5( implode(':', $key_parts) ), 0, 20 ));
 			}
 		}
 
 		/**
 		 * Sets page name to requested field in case when:
 		 * 1. page was auto created (through theme file rebuild)
 		 * 2. requested field is empty
 		 *
 		 * @param kDBItem $object
 		 * @param string $field
 		 * @author Alex
 		 */
 		function _saveTitleField(&$object, $field)
 		{
 			$value = $object->GetField($field, 'no_default'); // current value of target field
 
 			$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 			/* @var $ml_formatter kMultiLanguage */
 
 			$src_field = $ml_formatter->LangFieldName('Name');
 			$dst_field = $ml_formatter->LangFieldName($field);
 
 			$dst_field_not_changed = $object->GetOriginalField($dst_field) == $value;
 
 			if ($value == '' || preg_match('/^_Auto: (.*)/', $value) || (($object->GetOriginalField($src_field) == $value) && $dst_field_not_changed)) {
 				// target field is empty OR target field value starts with "_Auto: " OR (source field value
 				// before change was equals to current target field value AND target field value wasn't changed)
 				$object->SetField($dst_field, $object->GetField($src_field));
 			}
 		}
 
 		/**
 		 * Don't allow to delete system pages, when not in debug mode
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemDelete(kEvent $event)
 		{
 			parent::OnBeforeItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->GetDBField('Protected') && !$this->Application->isDebugMode(false) ) {
 				$event->status = kEvent::erFAIL;
 			}
 		}
 
 		/**
 		 * Creates category based on given TPL file
 		 *
 		 * @param CategoriesItem $object
 		 * @param string $template
 		 * @param int $theme_id
 		 * @param int $system_mode
 		 * @param array $template_info
 		 * @return bool
 		 */
 		function _prepareAutoPage(&$object, $template, $theme_id = null, $system_mode = SMS_MODE_AUTO, $template_info = Array ())
 		{
 			$template = $this->_stripTemplateExtension($template);
 
 			if ($system_mode == SMS_MODE_AUTO) {
 				$page_type = $this->_templateFound($template, $theme_id) ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL;
 			}
 			else {
 				$page_type = $system_mode == SMS_MODE_FORCE ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL;
 			}
 
 			if (($page_type == PAGE_TYPE_TEMPLATE) && ($template_info === false)) {
 				// do not auto-create system pages, when browsing through site
 				return false;
 			}
 
 			if (!isset($theme_id)) {
 				$theme_id = $this->_getCurrentThemeId();
 			}
 
 			$root_category = $this->Application->getBaseCategory();
 			$page_category = $this->Application->GetVar('m_cat_id');
 			if (!$page_category) {
 				$page_category = $root_category;
 				$this->Application->SetVar('m_cat_id', $page_category);
 			}
 
 			if (($page_type == PAGE_TYPE_VIRTUAL) && (strpos($template, '/') !== false)) {
 				// virtual page, but have "/" in template path -> create it's path
 				$category_path = explode('/', $template);
 				$template = array_pop($category_path);
 
 				$page_category = $this->_getParentCategoryFromPath($category_path, $root_category, $theme_id);
 			}
 
 			$page_name = ($page_type == PAGE_TYPE_TEMPLATE) ? '_Auto: ' . $template : $template;
 			$page_description = '';
 
 			if ($page_type == PAGE_TYPE_TEMPLATE) {
 				$design_template = strtolower($template); // leading "/" not added !
 				if ($template_info) {
 					if (array_key_exists('name', $template_info) && $template_info['name']) {
 						$page_name = $template_info['name'];
 					}
 
 					if (array_key_exists('desc', $template_info) && $template_info['desc']) {
 						$page_description = $template_info['desc'];
 					}
 
 					if (array_key_exists('section', $template_info) && $template_info['section']) {
 						// this will override any global "m_cat_id"
 						$page_category = $this->_getParentCategoryFromPath(explode('||', $template_info['section']), $root_category, $theme_id);
 					}
 				}
 			}
 			else {
 				$design_template = $this->_getDefaultDesign(); // leading "/" added !
 			}
 
 			$object->Clear();
 			$object->SetDBField('ParentId', $page_category);
 			$object->SetDBField('Type', $page_type);
 			$object->SetDBField('Protected', 1); // $page_type == PAGE_TYPE_TEMPLATE
 
 			$object->SetDBField('IsMenu', 0);
 			$object->SetDBField('ThemeId', $theme_id);
 
 			// put all templates to then end of list (in their category)
 			$min_priority = $this->_getNextPriority($page_category, $object->TableName);
 			$object->SetDBField('Priority', $min_priority);
 
 			$object->SetDBField('Template', $design_template);
 			$object->SetDBField('CachedTemplate', $design_template);
 
 			$primary_language = $this->Application->GetDefaultLanguageId();
 			$current_language = $this->Application->GetVar('m_lang');
 			$object->SetDBField('l' . $primary_language . '_Name', $page_name);
 			$object->SetDBField('l' . $current_language . '_Name', $page_name);
 			$object->SetDBField('l' . $primary_language . '_Description', $page_description);
 			$object->SetDBField('l' . $current_language . '_Description', $page_description);
 
 			return $object->Create();
 		}
 
 		function _getParentCategoryFromPath($category_path, $base_category, $theme_id = null)
 		{
 			static $category_ids = Array ();
 
 			if (!$category_path) {
 				return $base_category;
 			}
 
 			if (array_key_exists(implode('||', $category_path), $category_ids)) {
 				return $category_ids[ implode('||', $category_path) ];
 			}
 
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			$object = $this->Application->recallObject($this->Prefix . '.rebuild-path', null, Array ('skip_autoload' => true));
 			/* @var $object CategoriesItem */
 
 			$parent_id = $base_category;
 
 			$filenames_helper = $this->Application->recallObject('FilenamesHelper');
 			/* @var $filenames_helper kFilenamesHelper */
 
 			$safe_category_path = array_map(Array (&$filenames_helper, 'replaceSequences'), $category_path);
 
 			foreach ($category_path as $category_order => $category_name) {
 				$this->Application->SetVar('m_cat_id', $parent_id);
 
 				// get virtual category first, when possible
 				$sql = 'SELECT ' . $object->IDField . '
 						FROM ' . $object->TableName . '
 						WHERE
 							(
 								Filename = ' . $this->Conn->qstr($safe_category_path[$category_order]) . ' OR
 								Filename = ' . $this->Conn->qstr( $filenames_helper->replaceSequences('_Auto: ' . $category_name) ) . '
 							) AND
 							(ParentId = ' . $parent_id . ') AND
 							(ThemeId = 0 OR ThemeId = ' . $theme_id . ')
 						ORDER BY ThemeId ASC';
 				$parent_id = $this->Conn->GetOne($sql);
 
 				if ($parent_id === false) {
 					// page not found
 					$template = implode('/', array_slice($safe_category_path, 0, $category_order + 1));
 
 					// don't process system templates in sub-categories
 					$system = $this->_templateFound($template, $theme_id) && (strpos($template, '/') === false);
 
 					if (!$this->_prepareAutoPage($object, $category_name, $theme_id, $system ? SMS_MODE_FORCE : false)) {
 						// page was not created
 						break;
 					}
 
 					$parent_id = $object->GetID();
 				}
 			}
 
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 			$category_ids[ implode('||', $category_path) ] = $parent_id;
 
 			return $parent_id;
 		}
 
 		/**
 		 * Returns theme name by it's id. Used in structure page creation.
 		 *
 		 * @param int $theme_id
 		 * @return string
 		 */
 		function _getThemeName($theme_id)
 		{
 			static $themes = null;
 
 			if (!isset($themes)) {
 				$theme_config = $this->Application->getUnitConfig('theme');
 				$id_field = $theme_config->getIDField();
 				$table_name = $theme_config->getTableName();
 
 				$sql = 'SELECT Name, ' . $id_field . '
 						FROM ' . $table_name . '
 						WHERE Enabled = 1';
 				$themes = $this->Conn->GetCol($sql, $id_field);
 			}
 
 			return array_key_exists($theme_id, $themes) ? $themes[$theme_id] : false;
 		}
 
 		/**
 		 * Resets SMS-menu cache
 		 *
 		 * @param kEvent $event
 		 */
 		function OnResetCMSMenuCache($event)
 		{
 			if ($this->Application->GetVar('ajax') == 'yes') {
 				$event->status = kEvent::erSTOP;
 			}
 
 			$this->_resetMenuCache();
 			$event->SetRedirectParam('action_completed', 1);
 		}
 
 		/**
 		 * Performs reset of category-related caches (menu, structure dropdown, template mapping)
 		 *
 		 * @return void
 		 * @access protected
 		 */
 		protected function _resetMenuCache()
 		{
 			// reset cms menu cache (all variables are automatically rebuild, when missing)
 			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 				$this->Application->rebuildCache('master:cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime);
 				$this->Application->rebuildCache('master:StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime);
 				$this->Application->rebuildCache('master:template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime);
 			}
 			else {
 				$this->Application->rebuildDBCache('cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime);
 				$this->Application->rebuildDBCache('StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime);
 				$this->Application->rebuildDBCache('template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime);
 			}
 		}
 
 		/**
 		 * Updates structure config
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			if (defined('IS_INSTALL') && IS_INSTALL) {
 				// skip any processing, because Categories table doesn't exists until install is finished
 				$this->addViewPermissionJoin($event);
 
 				return ;
 			}
 
 			$site_config_helper = $this->Application->recallObject('SiteConfigHelper');
 			/* @var $site_config_helper SiteConfigHelper */
 
 			$settings = $site_config_helper->getSettings();
 
 			$root_category = $this->Application->getBaseCategory();
 
 			$config = $event->getUnitConfig();
 
 			// set root category
 			$config->addSectionAdjustments(Array (
 				'in-portal:browse' => Array (
 				'url' => Array ('m_cat_id' => $root_category),
 				'late_load' => Array ('m_cat_id' => $root_category),
 				'onclick' => 'checkCatalog(' . $root_category . ')',
 				),
 				'in-portal:browse_site' => Array (
 				'url' => Array ('editing_mode' => $settings['default_editing_mode']),
 				)
 			));
 
 			// prepare structure dropdown
 			$category_helper = $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$fields = $config->getFields();
 
 			$fields['ParentId']['default'] = (int)$this->Application->GetVar('m_cat_id');
 			$fields['ParentId']['options'] = $category_helper->getStructureTreeAsOptions();
 
 			// limit design list by theme
 			$theme_id = $this->_getCurrentThemeId();
 			$design_sql = $fields['Template']['options_sql'];
 			$design_sql = str_replace('(tf.FilePath = "/designs")', '(' . implode(' OR ', $this->getDesignFolders()) . ')' . ' AND (t.ThemeId = ' . $theme_id . ')', $design_sql);
 			$fields['Template']['options_sql'] = $design_sql;
 
 			// adds "Inherit From Parent" option to "Template" field
 			$fields['Template']['options'] = Array (CATEGORY_TEMPLATE_INHERIT => $this->Application->Phrase('la_opt_InheritFromParent'));
 
 			$config->setFields($fields);
 
 			if ($this->Application->isAdmin) {
 				// don't sort by Front-End sorting fields
 				$config_mapping = $config->getConfigMapping();
 				$remove_keys = Array ('DefaultSorting1Field', 'DefaultSorting2Field', 'DefaultSorting1Dir', 'DefaultSorting2Dir');
 
 				foreach ($remove_keys as $remove_key) {
 					unset($config_mapping[$remove_key]);
 				}
 
 				$config->setConfigMapping($config_mapping);
 			}
 			else {
 				// sort by parent path on Front-End only
 				$config->setListSortingsBySpecial('', Array (
 					'ForcedSorting' => Array ('CurrentSort' => 'asc'),
 				));
 			}
 
 			$this->addViewPermissionJoin($event);
 
 			// add grids for advanced view (with primary category column)
 			foreach (Array ('Default', 'Radio') as $process_grid) {
 				$grid_data = $config->getGridByName($process_grid);
 				$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_parent_category_td', 'filter_block' => 'grid_like_filter');
 				$config->addGrids($grid_data, $process_grid . 'ShowAll');
 			}
 		}
 
 		/**
 		 * Adds permission table table JOIN clause only, when advanced catalog view permissions enabled.
 		 *
 		 * @param kEvent $event Event.
 		 *
 		 * @return self
 		 * @access protected
 		 */
 		protected function addViewPermissionJoin(kEvent $event)
 		{
 			if ( $this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) {
 				$join_clause = 'LEFT JOIN ' . TABLE_PREFIX . 'CategoryPermissionsCache perm ON perm.CategoryId = %1$s.CategoryId';
 			}
 			else {
 				$join_clause = '';
 			}
 
 			$config = $event->getUnitConfig();
 
 			foreach ( $config->getListSQLSpecials() as $special ) {
 				$list_sql = str_replace('{PERM_JOIN}', $join_clause, $config->getListSQLsBySpecial($special));
 				$config->setListSQLsBySpecial($special, $list_sql);
 			}
 
 			return $this;
 		}
 
 		/**
 		 * Returns folders, that can contain design templates
 		 *
 		 * @return array
 		 * @access protected
 		 */
 		protected function getDesignFolders()
 		{
 			$ret = Array ('tf.FilePath = "/designs"', 'tf.FilePath = "/platform/designs"');
 
 			foreach ($this->Application->ModuleInfo as $module_info) {
 				$ret[] = 'tf.FilePath = "/' . $module_info['TemplatePath'] . 'designs"';
 			}
 
 			return array_unique($ret);
 		}
 
 		/**
 		 * Removes this item and it's children (recursive) from structure dropdown
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			if ( !$this->Application->isAdmin ) {
 				// calculate priorities dropdown only for admin
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// remove this category & it's children from dropdown
 			$sql = 'SELECT ' . $object->IDField . '
 					FROM ' . $event->getUnitConfig()->getTableName() . '
 					WHERE ParentPath LIKE "' . $object->GetDBField('ParentPath') . '%"';
 			$remove_categories = $this->Conn->GetCol($sql);
 
 			$options = $object->GetFieldOption('ParentId', 'options');
 
 			foreach ($remove_categories as $remove_category) {
 				unset($options[$remove_category]);
 			}
 
 			$object->SetFieldOption('ParentId', 'options', $options);
 		}
 
 		/**
 		 * Occurs after creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(kEvent $event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object CategoriesItem */
 
 			// need to update path after category is created, so category is included in that path
 			$fields_hash = $object->buildParentBasedFields();
 			$this->Conn->doUpdate($fields_hash, $object->TableName, $object->IDField . ' = ' . $object->GetID());
 			$object->SetDBFieldsFromHash($fields_hash);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterRebuildThemes($event)
 		{
 			$sql = 'SELECT t.ThemeId, CONCAT( tf.FilePath, \'/\', tf.FileName ) AS Path, tf.FileMetaInfo
 					FROM ' . TABLE_PREFIX . 'ThemeFiles AS tf
 					LEFT JOIN ' . TABLE_PREFIX . 'Themes AS t ON t.ThemeId = tf.ThemeId
 					WHERE t.Enabled = 1 AND tf.FileType = 1
 					AND (
 						SELECT COUNT(CategoryId)
 						FROM ' . TABLE_PREFIX . 'Categories c
 						WHERE CONCAT(\'/\', c.Template, \'.tpl\') = CONCAT( tf.FilePath, \'/\', tf.FileName ) AND (c.ThemeId = t.ThemeId)
 					) = 0 ';
 			$files = $this->Conn->Query($sql, 'Path');
 			if ( !$files ) {
 				// all possible pages are already created
 				return;
 			}
 
 			kUtil::setResourceLimit();
 
 			$dummy = $this->Application->recallObject($event->Prefix . '.rebuild', NULL, Array ('skip_autoload' => true));
 			/* @var $dummy CategoriesItem */
 
 			$error_count = 0;
 			foreach ($files as $a_file => $file_info) {
 				$status = $this->_prepareAutoPage($dummy, $a_file, $file_info['ThemeId'], SMS_MODE_FORCE, unserialize($file_info['FileMetaInfo'])); // create system page
 				if ( !$status ) {
 					$error_count++;
 				}
 			}
 
 			if ( $this->Application->ConfigValue('CategoryPermissionRebuildMode') == CategoryPermissionRebuild::SILENT ) {
 				$updater = $this->Application->makeClass('kPermCacheUpdater');
 				/* @var $updater kPermCacheUpdater */
 
 				$updater->OneStepRun();
 			}
 
 			$this->_resetMenuCache();
 
 			if ( $error_count ) {
 				// allow user to review error after structure page creation
 				$event->MasterEvent->redirect = false;
 			}
 		}
 
 		/**
 		 * Processes OnMassMoveUp, OnMassMoveDown events
 		 *
 		 * @param kEvent $event
 		 */
 		function OnChangePriority($event)
 		{
 			$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
 			$event->CallSubEvent('priority:' . $event->Name);
 
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 			$this->_resetMenuCache();
 		}
 
 		/**
 		 * Completely recalculates priorities in current category
 		 *
 		 * @param kEvent $event
 		 */
 		function OnRecalculatePriorities($event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
 			$event->CallSubEvent('priority:' . $event->Name);
 
 			$this->_resetMenuCache();
 		}
 
 		/**
 		 * Update Preview Block for FCKEditor
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdatePreviewBlock($event)
 		{
 			$event->status = kEvent::erSTOP;
 			$string = $this->Application->unescapeRequestVariable($this->Application->GetVar('preview_content'));
 
 			$category_helper = $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$string = $category_helper->replacePageIds($string);
 
 			$this->Application->StoreVar('_editor_preview_content_', $string);
 		}
 
 		/**
 		 * Makes simple search for categories
 		 * based on keywords string
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSimpleSearch($event)
 		{
 			$event->redirect = false;
 			$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 
 			$keywords = $this->Application->unescapeRequestVariable(trim($this->Application->GetVar('keywords')));
 
 			$query_object = $this->Application->recallObject('kHTTPQuery');
 			/* @var $query_object kHTTPQuery */
 
 			$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 			if ( !isset($query_object->Get['keywords']) && !isset($query_object->Post['keywords']) && $this->Conn->Query($sql) ) {
 				// used when navigating by pages or changing sorting in search results
 				return;
 			}
 
 			if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length'))
 			{
 				$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
 				$this->Application->SetVar('keywords_too_short', 1);
 				return; // if no or too short keyword entered, doing nothing
 			}
 
 			$this->Application->StoreVar('keywords', $keywords);
 
 			$this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search
 
 			$keywords = strtr($keywords, Array('%' => '\\%', '_' => '\\_'));
 
 			$event->setPseudoClass('_List');
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$config = $event->getUnitConfig();
 
 			$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
 			$lang = $this->Application->GetVar('m_lang');
 			$items_table = $config->getTableName();
 			$module_name = 'In-Portal';
 
 			$sql = 'SELECT *
 					FROM ' . $this->Application->getUnitConfig('confs')->getTableName() . '
 					WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1';
 			$search_config = $this->Conn->Query($sql, 'FieldName');
 
 			$field_list = array_keys($search_config);
 
 			$join_clauses = Array();
 
 			// field processing
 			$weight_sum = 0;
 
 			$alias_counter = 0;
 
 			$custom_fields = $config->getCustomFields();
 			if ($custom_fields) {
 				$custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName();
 				$join_clauses[] = '	LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
 			}
 
 			// what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table))
 			$search_config_map = Array();
 
 			foreach ($field_list as $key => $field) {
 				$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
 				$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
 
 				// processing multilingual fields
 				if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
 					$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 					$field_list[$key] = 'l'.$lang.'_'.$field;
 
 					if (!isset($search_config[$field]['ForeignField'])) {
 						$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 				}
 
 				// processing fields from other tables
 				$foreign_field = $search_config[$field]['ForeignField'];
 
 				if ( $foreign_field ) {
 					$exploded = explode(':', $foreign_field, 2);
 					if ($exploded[0] == 'CALC') {
 						// ignoring having type clauses in simple search
 						unset($field_list[$key]);
 						continue;
 					}
 					else {
 						$multi_lingual = false;
 						if ($exploded[0] == 'MULTI') {
 							$multi_lingual = true;
 							$foreign_field = $exploded[1];
 						}
 
 						$exploded = explode('.', $foreign_field);	// format: table.field_name
 						$foreign_table = TABLE_PREFIX.$exploded[0];
 
 						$alias_counter++;
 						$alias = 't'.$alias_counter;
 
 						if ($multi_lingual) {
 							$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
 							$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 							$search_config_map[ $field_list[$key] ] = $field;
 							$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 						}
 						else {
 							$field_list[$key] = $alias.'.'.$exploded[1];
 							$search_config_map[ $field_list[$key] ] = $field;
 						}
 
 						$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
 						$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 						$join_clauses[] = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 											ON '.$join_clause;
 					}
 				}
 				else {
 					// processing fields from local table
 					if ($search_config[$field]['CustomFieldId']) {
 						$local_table = 'custom_data';
 
 						// search by custom field value on current language
 						$custom_field_id = array_search($field_list[$key], $custom_fields);
 						$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
 
 						// search by custom field value on primary language
 						$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 
 					$field_list[$key] = $local_table.'.'.$field_list[$key];
 					$search_config_map[ $field_list[$key] ] = $field;
 				}
 			}
 
 			// keyword string processing
 			$search_helper = $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$where_clause = Array ();
 			foreach ($field_list as $field) {
 				if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
 					// local real field
 					$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
 					if ($filter_data) {
 						$where_clause[] = $filter_data['value'];
 					}
 				}
 				elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
 					$custom_field_name = 'cust_' . $search_config_map[$field];
 					$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
 					if ($filter_data) {
 						$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
 					}
 				}
 				else {
 					$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
 				}
 			}
 
 			$where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below!
 
 			$where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')';
 
 			if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
 				$sub_search_ids = $event->MasterEvent->getEventParam('ResultIds');
 
 				if ( $sub_search_ids !== false ) {
 					if ( $sub_search_ids ) {
 						$where_clause .= 'AND (' . $items_table . '.ResourceId IN (' . implode(',', $sub_search_ids) . '))';
 					}
 					else {
 						$where_clause .= 'AND FALSE';
 					}
 				}
 			}
 
 			// exclude template based sections from search results (ie. registration)
 			if ( $this->Application->ConfigValue('ExcludeTemplateSectionsFromSearch') ) {
 				$where_clause .= ' AND ' . $items_table . '.ThemeId = 0';
 			}
 
 			// making relevance clause
 			$positive_words = $search_helper->getPositiveKeywords($keywords);
 			$this->Application->StoreVar('highlight_keywords', serialize($positive_words));
 			$revelance_parts = Array();
 			reset($search_config);
 
 			foreach ($positive_words as $keyword_index => $positive_word) {
 				$positive_word = $search_helper->transformWildcards($positive_word);
 				$positive_words[$keyword_index] = $this->Conn->escape($positive_word);
 			}
 
 			foreach ($field_list as $field) {
 
 				if (!array_key_exists($field, $search_config_map)) {
 					$map_key = $search_config_map[$items_table . '.' . $field];
 				}
 				else {
 					$map_key = $search_config_map[$field];
 				}
 
 				$config_elem = $search_config[ $map_key ];
 				$weight = $config_elem['Priority'];
 
 				// search by whole words only ([[:<:]] - word boundary)
 				/*$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
 				foreach ($positive_words as $keyword) {
 					$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
 				}*/
 
 				// search by partial word matches too
 				$revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
 				foreach ($positive_words as $keyword) {
 					$revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
 				}
 			}
 
 			$revelance_parts = array_unique($revelance_parts);
 
 			$conf_postfix = $config->getSearchConfigPostfix();
 			$rel_keywords	= $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix)	/ 100;
 			$rel_pop		= $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix)		/ 100;
 			$rel_rating		= $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix)	/ 100;
 			$relevance_clause = '('.implode(' + ', $revelance_parts).') / '.$weight_sum.' * '.$rel_keywords;
 			if ($rel_pop && $object->isField('Hits')) {
 				$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 			}
 			if ($rel_rating && $object->isField('CachedRating')) {
 				$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 			}
 
 			// building final search query
 			if (!$this->Application->GetVar('do_not_drop_search_table')) {
 				$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
 				$this->Application->SetVar('do_not_drop_search_table', true);
 			}
 
 			$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
 			if ($search_table_exists) {
 				$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
 			}
 			else {
 				$select_intro = 'CREATE TABLE '.$search_table.' AS ';
 			}
 
 			$edpick_clause = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0';
 
 			$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
 								'.$items_table.'.'.$config->getIDField().' AS ItemId,
 								'.$items_table.'.ResourceId,
 								'.$config->getItemType().' AS ItemType,
 								 '.$edpick_clause.' AS EdPick
 						FROM '.$object->TableName.'
 						'.implode(' ', $join_clauses).'
 						WHERE '.$where_clause.'
 						GROUP BY '.$items_table.'.'.$config->getIDField().' ORDER BY Relevance DESC';
 
 			$this->Conn->Query($sql);
 
 			if ( !$search_table_exists ) {
 				$sql = 'ALTER TABLE ' . $search_table . '
 						ADD INDEX (ResourceId),
 						ADD INDEX (Relevance)';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSubSearch($event)
 		{
 			// keep search results from other items after doing a sub-search on current item type
 			$this->Application->SetVar('do_not_drop_search_table', true);
 
 			$ids = Array ();
 			$search_table = TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search';
 			$sql = 'SHOW TABLES LIKE "' . $search_table . '"';
 
 			if ( $this->Conn->Query($sql) ) {
 				$item_type = $event->getUnitConfig()->getItemType();
 
 				// 1. get ids to be used as search bounds
 				$sql = 'SELECT DISTINCT ResourceId
 						FROM ' . $search_table . '
 						WHERE ItemType = ' . $item_type;
 				$ids = $this->Conn->GetCol($sql);
 
 				// 2. delete previously found ids
 				$sql = 'DELETE FROM ' . $search_table . '
 						WHERE ItemType = ' . $item_type;
 				$this->Conn->Query($sql);
 			}
 
 			$event->setEventParam('ResultIds', $ids);
 			$event->CallSubEvent('OnSimpleSearch');
 		}
 
 		/**
 		 * Make record to search log
 		 *
 		 * @param string $keywords
 		 * @param int $search_type 0 - simple search, 1 - advanced search
 		 */
 		function saveToSearchLog($keywords, $search_type = 0)
 		{
 			// don't save keywords for each module separately, just one time
 			// static variable can't help here, because each module uses it's own class instance !
 			if (!$this->Application->GetVar('search_logged')) {
 				$sql = 'UPDATE '.TABLE_PREFIX.'SearchLogs
 						SET Indices = Indices + 1
 						WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search
 		        $this->Conn->Query($sql);
 		        if ($this->Conn->getAffectedRows() == 0) {
 		            $fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type);
 		        	$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLogs');
 		        }
 
 		        $this->Application->SetVar('search_logged', 1);
 			}
 		}
 
 		/**
 		 * Load item if id is available
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function LoadItem(kEvent $event)
 		{
 			if ( !$this->_isVirtual($event) ) {
 				parent::LoadItem($event);
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$id = $this->getPassedID($event);
 
 			if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) {
 				// object is already loaded by same id
 				return;
 			}
 
 			if ( $object->Load($id, null, true) ) {
 				$actions = $this->Application->recallObject('kActions');
 				/* @var $actions Params */
 
 				$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
 			}
 			else {
 				$object->setID($id);
 			}
 		}
 
 		/**
 		 * Returns constrain for priority calculations
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @see PriorityEventHandler
 		 * @access protected
 		 */
 		protected function OnGetConstrainInfo(kEvent $event)
 		{
 			$constrain = ''; // for OnSave
 
 			$event_name = $event->getEventParam('original_event');
 			$actual_event_name = $event->getEventParam('actual_event');
 
 			if ( $actual_event_name == 'OnSavePriorityChanges' || $event_name == 'OnAfterItemLoad' || $event_name == 'OnAfterItemDelete' ) {
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$constrain = 'ParentId = ' . $object->GetDBField('ParentId');
 			}
 			elseif ( $actual_event_name == 'OnPreparePriorities' ) {
 				$constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id');
 			}
 			elseif ( $event_name == 'OnSave' ) {
 				$constrain = '';
 			}
 			else {
 				$constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id');
 			}
 
 			$event->setEventParam('constrain_info', Array ($constrain, ''));
 		}
 
 		/**
-		 * Parses category part of url, build main part of url
-		 *
-		 * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE.
-		 * @param string $prefix Prefix, that listener uses for system integration
-		 * @param Array $params Params, that are used for url building or created during url parsing.
-		 * @param Array $url_parts Url parts to parse (only for parsing).
-		 * @param bool $keep_events Keep event names in resulting url (only for building).
-		 * @return bool|string|Array Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener.
-		 */
-		public function CategoryRewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix, &$params, &$url_parts, $keep_events = false)
-		{
-			if ($rewrite_mode == REWRITE_MODE_BUILD) {
-				return $this->_buildMainUrl($prefix, $params, $keep_events);
-			}
-
-			if ( $this->_parseFriendlyUrl($url_parts, $params) ) {
-				// friendly urls work like exact match only!
-				return false;
-			}
-
-			$this->_parseCategory($url_parts, $params);
-
-			return true;
-		}
-
-		/**
-		 * Build main part of every url
-		 *
-		 * @param string $prefix_special
-		 * @param Array $params
-		 * @param bool $keep_events
-		 * @return string
-		 */
-		protected function _buildMainUrl($prefix_special, &$params, $keep_events)
-		{
-			$ret = '';
-			list ($prefix) = explode('.', $prefix_special);
-
-			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
-			/* @var $rewrite_processor kRewriteUrlProcessor */
-
-			$processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events);
-			if ($processed_params === false) {
-				return '';
-			}
-
-			// add language
-			if ($processed_params['m_lang'] && ($processed_params['m_lang'] != $rewrite_processor->primaryLanguageId)) {
-				$language_name = $this->Application->getCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]');
-				if ($language_name === false) {
-					$sql = 'SELECT PackName
-							FROM ' . TABLE_PREFIX . 'Languages
-							WHERE LanguageId = ' . $processed_params['m_lang'];
-					$language_name = $this->Conn->GetOne($sql);
-
-					$this->Application->setCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]', $language_name);
-				}
-
-				$ret .= $language_name . '/';
-			}
-
-			// add theme
-			if ($processed_params['m_theme'] && ($processed_params['m_theme'] != $rewrite_processor->primaryThemeId)) {
-				$theme_name = $this->Application->getCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]');
-				if ($theme_name === false) {
-					$sql = 'SELECT Name
-							FROM ' . TABLE_PREFIX . 'Themes
-							WHERE ThemeId = ' . $processed_params['m_theme'];
-					$theme_name = $this->Conn->GetOne($sql);
-
-					$this->Application->setCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]', $theme_name);
-
-				}
-
-				$ret .= $theme_name . '/';
-			}
-
-			// inject custom url parts made by other rewrite listeners just after language/theme url parts
-			if ($params['inject_parts']) {
-				$ret .= implode('/', $params['inject_parts']) . '/';
-			}
-
-			// add category
-			if ($processed_params['m_cat_id'] > 0 && $params['pass_category']) {
-				$category_filename = $this->Application->getCategoryCache($processed_params['m_cat_id'], 'filenames');
-
-				preg_match('/^Content\/(.*)/i', $category_filename, $regs);
-
-				if ($regs) {
-					$template = array_key_exists('t', $params) ? $params['t'] : false;
-
-					if (strtolower($regs[1]) == strtolower($template)) {
-						// we could have category path like "Content/<template_path>" in this case remove template
-						$params['pass_template'] = false;
-					}
-
-					$ret .= $regs[1] . '/';
-				}
-
-				$params['category_processed'] = true;
-			}
-
-			// reset category page
-			$force_page_adding = false;
-			if (array_key_exists('reset', $params) && $params['reset']) {
-				unset($params['reset']);
-
-				if ($processed_params['m_cat_id']) {
-					$processed_params['m_cat_page'] = 1;
-					$force_page_adding = true;
-				}
-			}
-
-			if ((array_key_exists('category_processed', $params) && $params['category_processed'] && ($processed_params['m_cat_page'] > 1)) || $force_page_adding) {
-				// category name was added before AND category page number found
-				$ret = rtrim($ret, '/') . '_' . $processed_params['m_cat_page'] . '/';
-			}
-
-			$template = array_key_exists('t', $params) ? $params['t'] : false;
-			$category_template = ($processed_params['m_cat_id'] > 0) && $params['pass_category'] ? $this->Application->getCategoryCache($processed_params['m_cat_id'], 'category_designs') : '';
-
-			if ((strtolower($template) == '__default__') && ($processed_params['m_cat_id'] == 0)) {
-				// for "Home" category set template to index when not set
-				$template = 'index';
-			}
-
-			// remove template from url if it is category index cached template
-			if ( ($template == $category_template) || (mb_strtolower($template) == '__default__') ) {
-				// given template is also default template for this category OR '__default__' given
-				$params['pass_template'] = false;
-			}
-
-			// remove template from url if it is site homepage on primary language & theme
-			if ( ($template == 'index') && $processed_params['m_lang'] == $rewrite_processor->primaryLanguageId && $processed_params['m_theme'] == $rewrite_processor->primaryThemeId ) {
-				// given template is site homepage on primary language & theme
-				$params['pass_template'] = false;
-			}
-
-			if ($template && $params['pass_template']) {
-				$ret .= $template . '/';
-			}
-
-			return mb_strtolower( rtrim($ret, '/') );
-		}
-
-		/**
-		 * Checks if whole url_parts matches a whole In-CMS page
-		 *
-		 * @param Array $url_parts
-		 * @param Array $vars
-		 * @return bool
-		 */
-		protected function _parseFriendlyUrl($url_parts, &$vars)
-		{
-			if (!$url_parts) {
-				return false;
-			}
-
-			$sql = 'SELECT CategoryId, NamedParentPath
-					FROM ' . TABLE_PREFIX . 'Categories
-					WHERE FriendlyURL = ' . $this->Conn->qstr(implode('/', $url_parts));
-			$friendly = $this->Conn->GetRow($sql);
-
-			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
-			/* @var $rewrite_processor kRewriteUrlProcessor */
-
-			if ($friendly) {
-				$vars['m_cat_id'] = $friendly['CategoryId'];
-				$vars['t'] = preg_replace('/^Content\//i', '', $friendly['NamedParentPath']);
-
-				while ($url_parts) {
-					$rewrite_processor->partParsed( array_shift($url_parts) );
-				}
-
-				return true;
-			}
-
-			return false;
-		}
-
-		/**
-		 * Extracts category part from url
-		 *
-		 * @param Array $url_parts
-		 * @param Array $vars
-		 * @return bool
-		 */
-		protected function _parseCategory($url_parts, &$vars)
-		{
-			if (!$url_parts) {
-				return false;
-			}
-
-			$res = false;
-			$url_part = array_shift($url_parts);
-
-			$category_id = 0;
-			$last_category_info = false;
-			$category_path = $url_part == 'content' ? '' : 'content';
-
-			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
-			/* @var $rewrite_processor kRewriteUrlProcessor */
-
-			do {
-				$category_path = trim($category_path . '/' . $url_part, '/');
-				// bb_<topic_id> -> forums/bb_2
-				if ( !preg_match('/^bb_[\d]+$/', $url_part) && preg_match('/(.*)_([\d]+)$/', $category_path, $rets) ) {
-					$category_path = $rets[1];
-					$vars['m_cat_page'] = $rets[2];
-				}
-
-				$sql = 'SELECT CategoryId, SymLinkCategoryId, NamedParentPath
-						FROM ' . TABLE_PREFIX . 'Categories
-						WHERE (LOWER(NamedParentPath) = ' . $this->Conn->qstr($category_path) . ') AND (ThemeId = ' . $vars['m_theme'] . ' OR ThemeId = 0)';
-				$category_info = $this->Conn->GetRow($sql);
-
-				if ($category_info !== false) {
-					$last_category_info = $category_info;
-					$rewrite_processor->partParsed($url_part);
-
-					$url_part = array_shift($url_parts);
-					$res = true;
-				}
-			} while ($category_info !== false && $url_part);
-
-			if ($last_category_info) {
-				// this category is symlink to other category, so use it's url instead
-				// (used in case if url prior to symlink adding was indexed by spider or was bookmarked)
-				if ($last_category_info['SymLinkCategoryId']) {
-					$sql = 'SELECT CategoryId, NamedParentPath
-							FROM ' . TABLE_PREFIX . 'Categories
-							WHERE (CategoryId = ' . $last_category_info['SymLinkCategoryId'] . ')';
-					$category_info = $this->Conn->GetRow($sql);
-
-					if ($category_info) {
-						// web symlinked category was found use it
-						// TODO: maybe 302 redirect should be made to symlinked category url (all other url parts should stay)
-						$last_category_info = $category_info;
-					}
-				}
-
-				// 1. Set virtual page as template, this will be replaced to physical template later in kApplication::Run.
-				// 2. Don't set CachedTemplate field as template here, because we will loose original page associated with it's cms blocks!
-				$vars['t'] = mb_strtolower( preg_replace('/^Content\//i', '', $last_category_info['NamedParentPath']));
-
-				$vars['m_cat_id'] = $last_category_info['CategoryId'];
-				$vars['is_virtual'] = true; // for template from POST, strange code there!
-			}
-			/*else {
-				$vars['m_cat_id'] = 0;
-			}*/
-
-			return $res;
-		}
-
-		/**
 		 * Set's new unique resource id to user
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemValidate(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$resource_id = $object->GetDBField('ResourceId');
 
 			if ( !$resource_id ) {
 				$object->SetDBField('ResourceId', $this->Application->NextResourceId());
 			}
 		}
 
 		/**
 		 * Occurs before an item has been cloned
 		 * Id of newly created item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeClone(kEvent $event)
 		{
 			parent::OnBeforeClone($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('ResourceId', 0); // this will reset it
 
 		}
 	}
Index: branches/5.3.x/core/units/helpers/mod_rewrite_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/mod_rewrite_helper.php	(revision 16170)
+++ branches/5.3.x/core/units/helpers/mod_rewrite_helper.php	(nonexistent)
@@ -1,356 +0,0 @@
-<?php
-/**
-* @version	$Id$
-* @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!');
-
-	class CategoryItemRewrite extends kHelper {
-
-		/**
-		 * Builds/parses category item part of url
-		 *
-		 * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE.
-		 * @param string $prefix Prefix, that listener uses for system integration
-		 * @param Array $params Params, that are used for url building or created during url parsing.
-		 * @param Array $url_parts Url parts to parse (only for parsing).
-		 * @param bool $keep_events Keep event names in resulting url (only for building).
-		 * @return bool Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener.
-		 * @access public
-		 */
-		public function RewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix, &$params, &$url_parts, $keep_events = false)
-		{
-			if ($rewrite_mode == REWRITE_MODE_BUILD) {
-				return $this->_buildCategoryItemUrl($prefix, $params, $keep_events);
-			}
-
-			$module_prefix = $this->_parseCategoryItemUrl($url_parts, $params, $prefix);
-
-			if ($module_prefix !== false) {
-				$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
-				/* @var $rewrite_processor kRewriteUrlProcessor */
-
-				$params['pass'][] = $module_prefix;
-				$rewrite_processor->setModulePrefix($module_prefix);
-			}
-
-			return true;
-		}
-
-		/**
-		 * Build category item part of url
-		 *
-		 * @param string $prefix_special
-		 * @param Array $params
-		 * @param bool $keep_events
-		 * @return string
-		 * @access protected
-		 */
-		protected function _buildCategoryItemUrl($prefix_special, &$params, $keep_events)
-		{
-			static $default_per_page = Array ();
-
-			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
-			/* @var $rewrite_processor kRewriteUrlProcessor */
-
-			$ret = '';
-			list ($prefix) = explode('.', $prefix_special);
-			$processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events);
-
-			if ($processed_params === false) {
-				return '';
-			}
-
-			if ( isset($params[$prefix_special . '_event']) && $params[$prefix_special . '_event'] ) {
-				$params['events[' . $prefix_special . ']'] = $params[$prefix_special . '_event'];
-				unset($params[$prefix_special . '_event']);
-			}
-
-			if (!array_key_exists($prefix, $default_per_page)) {
-				$list_helper = $this->Application->recallObject('ListHelper');
-				/* @var $list_helper ListHelper */
-
-				$default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix);
-			}
-
-			if ($processed_params[$prefix_special . '_id']) {
-				$category_id = array_key_exists('m_cat_id', $params) ? $params['m_cat_id'] : $this->Application->GetVar('m_cat_id');
-
-				// if template is also item template of category, then remove template
-				$template = array_key_exists('t', $params) ? $params['t'] : false;
-				$item_template = $rewrite_processor->GetItemTemplate($category_id, $prefix);
-
-				if ($template == $item_template || strtolower($template) == '__default__') {
-					// given template is also default template for this category item or '__default__' given
-					$params['pass_template'] = false;
-				}
-
-				// get item's filename
-				if ($prefix == 'bb') {
-					$ret .= 'bb_' . $processed_params[$prefix_special . '_id'] . '/';
-				}
-				else {
-					$filename = $this->getFilename($prefix, $processed_params[$prefix_special . '_id'], $category_id);
-					if ($filename !== false) {
-						$ret .= $filename . '/';
-					}
-				}
-			} else {
-				if ($processed_params[$prefix_special . '_Page'] == 1) {
-					// when printing category items and we are on the 1st page -> there is no information about
-					// category item prefix and $params['pass_category'] will not be added automatically
-					$params['pass_category'] = true;
-				}
-				elseif ($processed_params[$prefix_special . '_Page'] > 1) {
-					// $ret .= $processed_params[$prefix_special . '_Page'] . '/';
-					$params['page'] = $processed_params[$prefix_special . '_Page'];
-				}
-
-				$per_page = $processed_params[$prefix_special . '_PerPage'];
-
-				if ($per_page && ($per_page != $default_per_page[$prefix])) {
-					$params['per_page'] = $processed_params[$prefix_special . '_PerPage'];
-				}
-			}
-
-			return mb_strtolower( rtrim($ret, '/') );
-		}
-
-		/**
-		 * Builds/parses review part of url
-		 *
-		 * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE.
-		 * @param string $prefix_special Prefix, that listener uses for system integration
-		 * @param Array $params Params, that are used for url building or created during url parsing.
-		 * @param Array $url_parts Url parts to parse (only for parsing).
-		 * @param bool $keep_events Keep event names in resulting url (only for building).
-		 * @return bool Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener.
-		 * @access public
-		 */
-		public function ReviewRewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix_special, &$params, &$url_parts, $keep_events = false)
-		{
-			static $default_per_page = Array ();
-
-			if ( $rewrite_mode != REWRITE_MODE_BUILD ) {
-				// don't parse anything
-				return true;
-			}
-
-			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
-			/* @var $rewrite_processor kRewriteUrlProcessor */
-
-			$ret = '';
-			list ($prefix) = explode('.', $prefix_special);
-			$processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events);
-
-			if ($processed_params === false) {
-				return '';
-			}
-
-			if (!array_key_exists($prefix, $default_per_page)) {
-				$list_helper = $this->Application->recallObject('ListHelper');
-				/* @var $list_helper ListHelper */
-
-				$default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix);
-			}
-
-			if ($processed_params[$prefix_special . '_id']) {
-				return false;
-			}
-			else {
-				if ($processed_params[$prefix_special . '_Page'] == 1) {
-					// when printing category items and we are on the 1st page -> there is no information about
-					// category item prefix and $params['pass_category'] will not be added automatically
-					$params['pass_category'] = true;
-				}
-				elseif ($processed_params[$prefix_special . '_Page'] > 1) {
-					// $ret .= $processed_params[$prefix_special . '_Page'] . '/';
-					$params['page'] = $processed_params[$prefix_special . '_Page'];
-				}
-
-				$per_page = $processed_params[$prefix_special . '_PerPage'];
-
-				if ($per_page && ($per_page != $default_per_page[$prefix])) {
-					$params['per_page'] = $processed_params[$prefix_special . '_PerPage'];
-				}
-			}
-
-			return mb_strtolower( rtrim($ret, '/') );
-		}
-
-		/**
-		 * 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
-		 * @access protected
-		 */
-		protected function getFilename($prefix, $id, $category_id = null)
-		{
-			if ($prefix == 'c') {
-				throw new Exception('Method "<strong>' . __FUNCTION__ . '</strong>" no longer work with "<strong>c</strong>" prefix. Please use "<strong>getCategoryCache</strong>" method instead');
-			}
-
-			$category_id = isset($category_id) ? $category_id : $this->Application->GetVar('m_cat_id');
-
-			$cache_key = 'filenames[%' . $this->Application->incrementCacheSerial($prefix, $id, false) . '%]:' . (int)$category_id;
-			$filename = $this->Application->getCache($cache_key);
-
-			if ($filename === false) {
-				$this->Conn->nextQueryCachable = true;
-				$config = $this->Application->getUnitConfig($prefix);
-
-				$sql = 'SELECT ResourceId
-						FROM ' . $config->getTableName() . '
-						WHERE ' . $config->getIDField() . ' = ' . $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->Application->setCache($cache_key, $filename);
-				}
-			}
-
-			return $filename;
-		}
-
-		/**
-		 * Sets template and id, corresponding to category item given in url
-		 *
-		 * @param Array $url_parts
-		 * @param Array $vars
-		 * @param string $prefix Prefix, that listener uses for system integration
-		 *
-		 * @return boolean|string
-		 * @access protected
-		 */
-		protected function _parseCategoryItemUrl(&$url_parts, &$vars, $prefix)
-		{
-			if ( !$url_parts ) {
-				return false;
-			}
-
-			$item_filename = end($url_parts);
-			if ( is_numeric($item_filename) ) {
-				// this page, don't process here
-				return false;
-			}
-
-			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
-			/* @var $rewrite_processor kRewriteUrlProcessor */
-
-			if ( $prefix == 'bb' && preg_match('/^bb_([\d]+)/', $item_filename, $regs) ) {
-				// process topics separately, because they don't use item filenames
-				array_pop($url_parts);
-				$rewrite_processor->partParsed($item_filename, 'rtl');
-
-				return $this->_parseTopicUrl($regs[1], $vars);
-			}
-
-			$cat_item = $this->findCategoryItem((int)$vars['m_cat_id'], $item_filename, $prefix);
-
-			if ( $cat_item !== false ) {
-				// item found
-				$module_prefix = $cat_item['ItemPrefix'];
-				$item_template = $rewrite_processor->GetItemTemplate($cat_item, $module_prefix, $vars['m_theme']);
-
-				// converting ResourceId to corresponding Item id
-				$module_config = $this->Application->getUnitConfig($module_prefix);
-
-				$sql = 'SELECT ' . $module_config->getIDField() . '
-						FROM ' . $module_config->getTableName() . '
-					 	WHERE ResourceId = ' . $cat_item['ItemResourceId'];
-				$item_id = $this->Conn->GetOne($sql);
-
-				if ( $item_id ) {
-					array_pop($url_parts);
-					$rewrite_processor->partParsed($item_filename, 'rtl');
-
-					if ( $item_template ) {
-						// when template is found in category -> set it
-						$vars['t'] = $item_template;
-					}
-
-					// we have category item id
-					$vars[$module_prefix . '_id'] = $item_id;
-
-					return $module_prefix;
-				}
-			}
-
-			return false;
-		}
-
-		/**
-		 * Locating the item in CategoryItems by filename to detect its ItemPrefix and its category ParentPath.
-		 *
-		 * @param integer $category_id Category.
-		 * @param string  $filename    Filename.
-		 * @param string  $prefix      Prefix, that listener uses for system integration
-		 *
-		 * @return string
-		 */
-		protected function findCategoryItem($category_id, $filename, $prefix)
-		{
-			static $cache = array();
-
-			$cache_key = $category_id . ':' . $filename;
-
-			if ( !isset($cache[$cache_key]) ) {
-				$sql = 'SELECT ci.ItemResourceId, ci.ItemPrefix, c.ParentPath, ci.CategoryId
-						FROM ' . TABLE_PREFIX . 'CategoryItems AS ci
-						LEFT JOIN ' . TABLE_PREFIX . 'Categories AS c ON c.CategoryId = ci.CategoryId
-						WHERE (ci.CategoryId = ' . $category_id . ') AND (ci.Filename = ' . $this->Conn->qstr($filename) . ')';
-				$cache[$cache_key] = $this->Conn->GetRow($sql);
-			}
-
-			$category_item = $cache[$cache_key];
-
-			return $category_item && $category_item['ItemPrefix'] == $prefix ? $category_item : false;
-		}
-
-		/**
-		 * Set's template and topic id corresponding to topic given in url
-		 *
-		 * @param int $topic_id
-		 * @param Array $vars
-		 * @return string
-		 * @access protected
-		 */
-		protected function _parseTopicUrl($topic_id, &$vars)
-		{
-			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
-			/* @var $rewrite_processor kRewriteUrlProcessor */
-
-			$sql = 'SELECT c.ParentPath, c.CategoryId
-					FROM ' . TABLE_PREFIX . 'Categories AS c
-					WHERE c.CategoryId = ' . (int)$vars['m_cat_id'];
-			$cat_item = $this->Conn->GetRow($sql);
-
-			$item_template = $rewrite_processor->GetItemTemplate($cat_item, 'bb', $vars['m_theme']);
-
-			if ($item_template) {
-				$vars['t'] = $item_template;
-			}
-
-			$vars['bb_id'] = $topic_id;
-
-			return 'bb';
-		}
-	}

Property changes on: branches/5.3.x/core/units/helpers/mod_rewrite_helper.php
___________________________________________________________________
Deleted: cvs2svn:cvs-rev
## -1 +0,0 ##
-1.11.2.9
\ No newline at end of property
Deleted: svn:eol-style
## -1 +0,0 ##
-LF
\ No newline at end of property
Deleted: svn:keywords
## -1 +0,0 ##
-Id
\ No newline at end of property
Index: branches/5.3.x/core/units/helpers/helpers_config.php
===================================================================
--- branches/5.3.x/core/units/helpers/helpers_config.php	(revision 16170)
+++ branches/5.3.x/core/units/helpers/helpers_config.php	(revision 16171)
@@ -1,78 +1,77 @@
 <?php
 /**
 * @version	$Id$
 * @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!');
 
 	$config = Array (
 
 		'Prefix' => 'helpers',
 		'EventHandlerClass' => Array ('class' => 'kEventHandler', 'file' => '', 'build_event' => 'OnBuild'),
 
 		'RegisterClasses' => Array (
 			Array ('pseudo' => 'kMultiLanguageHelper', 'class' => 'kMultiLanguageHelper', 'file' => 'multilanguage_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'SearchHelper', 'class' => 'kSearchHelper', 'file' => 'search_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'SectionsHelper', 'class' => 'kSectionsHelper', 'file' => 'sections_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'PermissionsHelper', 'class' => 'kPermissionsHelper', 'file' => 'permissions_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'kModulesHelper', 'class' => 'kModulesHelper', 'file' => 'modules_helper.php', 'build_event' => ''),
-			Array ('pseudo' => 'CategoryItemRewrite', 'class' => 'CategoryItemRewrite', 'file' => 'mod_rewrite_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'RecursiveHelper', 'class' => 'kRecursiveHelper', 'file' => 'recursive_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'FilenamesHelper', 'class' => 'kFilenamesHelper', 'file' => 'filenames_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'ClipboardHelper', 'class' => 'kClipboardHelper', 'file' => 'clipboard_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'ColumnPickerHelper', 'class' => 'kColumnPickerHelper', 'file' => 'col_picker_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'ThemesHelper', 'class' => 'kThemesHelper', 'file' => 'themes_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'CaptchaHelper', 'class' => 'kCaptchaHelper', 'file' => 'captcha_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'PriorityHelper', 'class' => 'kPriorityHelper', 'file' => 'priority_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'CurlHelper', 'class' => 'kCurlHelper', 'file' => 'curl_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'CountHelper', 'class' => 'kCountHelper', 'file' => 'count_helper.php', 'build_event' => ''),
 
 			Array ('pseudo' => 'ImageHelper', 'class' => 'ImageHelper', 'file' => 'image_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'FileHelper', 'class' => 'FileHelper', 'file' => 'file_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'CategoryHelper', 'class' => 'CategoryHelper', 'file' => 'category_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'kNavigationBar', 'class' => 'kNavigationBar', 'file' => 'navigation_bar.php', 'build_event' => ''),
 			Array ('pseudo' => 'CSVHelper', 'class' => 'kCSVHelper', 'file' => 'csv_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'ChartHelper', 'class' => 'kChartHelper', 'file' => 'chart_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'RatingHelper', 'class' => 'RatingHelper', 'file' => 'rating_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'FCKHelper', 'class' => 'fckFCKHelper', 'file' => 'fck_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'SpamHelper', 'class' => 'SpamHelper', 'file' => 'spam_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'TemplateHelper', 'class' => 'TemplateHelper', 'file' => 'template_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'MailingListHelper', 'class' => 'MailingListHelper', 'file' => 'mailing_list_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'JSONHelper', 'class' => 'JSONHelper', 'file' => 'json_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'LanguageImportHelper', 'class' => 'LanguageImportHelper', 'file' => 'language_import_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'SkinHelper', 'class' => 'SkinHelper', 'file' => 'skin_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'SiteConfigHelper', 'class' => 'SiteConfigHelper', 'file' => 'site_config_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'MenuHelper', 'class' => 'MenuHelper', 'file' => 'menu_helper.php', 'build_event' => ''),
 
 			Array ('pseudo' => 'InpCustomFieldsHelper', 'class' => 'InpCustomFieldsHelper', 'file' => 'custom_fields_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'CountryStatesHelper', 'class' => 'kCountryStatesHelper', 'file' => 'country_states_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'BracketsHelper', 'class' => 'kBracketsHelper', 'file' => 'brackets_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'kXMLHelper', 'class' => 'kXMLHelper', 'file' => 'xml_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'CatItemExportHelper', 'class' => 'kCatDBItemExportHelper', 'file' => 'cat_dbitem_export_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'kEmailTemplateHelper', 'class' => 'kEmailTemplateHelper', 'file' => 'email_template_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'ListHelper', 'class' => 'ListHelper', 'file' => 'list_helper.php', 'build_event' => ''),
 
 			Array ('pseudo' => 'FormSubmissionHelper', 'class' => 'FormSubmissionHelper', 'file' => 'form_submission_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'MailboxHelper', 'class' => 'MailboxHelper', 'file' => 'mailbox_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'POP3Helper', 'class' => 'POP3Helper', 'file' => 'pop3_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'MimeDecodeHelper', 'class' => 'MimeDecodeHelper', 'file' => 'mime_decode_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'UserHelper', 'class' => 'UserHelper', 'file' => 'user_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'SiteHelper', 'class' => 'SiteHelper', 'file' => 'site_helper.php', 'build_event' => ''),
 
 			Array ('pseudo' => 'DeploymentHelper', 'class' => 'DeploymentHelper', 'file' => 'deployment_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'PageHelper', 'class' => 'PageHelper', 'file' => 'page_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'BackupHelper', 'class' => 'BackupHelper', 'file' => 'backup_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'AjaxFormHelper', 'class' => 'AjaxFormHelper', 'file' => 'ajax_form_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'kCronHelper', 'class' => 'kCronHelper', 'file' => 'cron_helper.php', 'build_event' => ''),
 			Array ('pseudo' => 'kUploadHelper', 'class' => 'kUploadHelper', 'file' => 'upload_helper.php', 'build_event' => ''),
 		),
 	);
Index: branches/5.3.x/core/units/reviews/reviews_config.php
===================================================================
--- branches/5.3.x/core/units/reviews/reviews_config.php	(revision 16170)
+++ branches/5.3.x/core/units/reviews/reviews_config.php	(revision 16171)
@@ -1,216 +1,214 @@
 <?php
 /**
 * @version	$Id$
 * @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!');
 
 $config = Array (
 	'Prefix' => 'rev',
 
 	'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'),
 	'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'),
 	'EventHandlerClass' => Array ('class' => 'ReviewsEventHandler', 'file' => 'reviews_event_handler.php', 'build_event' => 'OnBuild'),
 	'TagProcessorClass' => Array ('class' => 'ReviewsTagProcessor', 'file' => 'reviews_tag_processor.php', 'build_event' => 'OnBuild'),
 	'AutoLoad' => true,
 
 	'QueryString' => Array (
 		1 => 'id',
 		2 => 'Page',
 		3 => 'PerPage',
 		4 => 'event',
 		5 => 'mode',
 	),
 
-	'RewriteListener' => 'CategoryItemRewrite:ReviewRewriteListener',
-
 	'ParentPrefix' => 'p', // replace all usage of rev to "p-rev" and then remove this param from here and Prefix too
 	'ConfigMapping' => Array (
 		'PerPage' => 'Comm_Perpage_Reviews',
 
 		'ReviewDelayInterval' => 'product_ReviewDelay_Value',
 		'ReviewDelayValue' => 'product_ReviewDelay_Interval',
 	),
 
 	'IDField' => 'ReviewId',
 	'StatusField' => Array ('Status'), // field, that is affected by Approve/Decline events
 	'TableName' => TABLE_PREFIX.'CatalogReviews',
 	'ParentTableKey' => 'ResourceId', // linked field in master table
 	'ForeignKey' => 'ItemId', // linked field in sub-table
 
 	'AutoDelete' => true,
 	'AutoClone' => true,
 
 	'TitlePresets' => Array (
 		'reviews_edit' => Array ('format' => "!la_title_Editing_Review!"),
 
 		'reviews' => Array (
 			'toolbar_buttons' => Array ('edit', 'delete', 'approve', 'decline', 'view', 'dbl-click'),
 		),
 	),
 
 	'CalculatedFields' => Array (
 		'' => Array (
 			'ReviewedBy' => 'CASE %1$s.CreatedById WHEN ' . USER_ROOT . ' THEN "root" WHEN ' . USER_GUEST . ' THEN "Guest" ELSE IF(CONCAT(pu.FirstName, pu.LastName) <> "", CONCAT(pu.FirstName, " ", pu.LastName), pu.Username) END',
 		),
 
 		'products' => Array (
 			'ReviewedBy' => 'CASE %1$s.CreatedById WHEN ' . USER_ROOT . ' THEN "root" WHEN ' . USER_GUEST . ' THEN "Guest" ELSE IF(CONCAT(pu.FirstName, pu.LastName) <> "", CONCAT(pu.FirstName, " ", pu.LastName), pu.Username) END',
 			'ItemName' => 'pr.l1_Name',
 			'ProductId' => 'pr.ProductId',
 		),
 
 		'product' => Array (
 			'ReviewedBy' => 'CASE %1$s.CreatedById WHEN ' . USER_ROOT . ' THEN "root" WHEN ' . USER_GUEST . ' THEN "Guest" ELSE IF(CONCAT(pu.FirstName, pu.LastName) <> "", CONCAT(pu.FirstName, " ", pu.LastName), pu.Username) END',
 			'ItemName' => 'pr.l1_Name',
 			'ProductId' => 'pr.ProductId',
 		),
 	),
 
 	// key - special, value - list select sql
 	'ListSQLs' => Array (
 		'' => '	SELECT %1$s.* %2$s
 				FROM %1$s
 				LEFT JOIN ' . TABLE_PREFIX . 'Users pu ON pu.PortalUserId = %1$s.CreatedById',
 
 		'products' => '	SELECT %1$s.* %2$s
 						FROM %1$s
 						LEFT JOIN ' . TABLE_PREFIX . 'Products pr ON pr.ResourceId = %1$s.ItemId
 						LEFT JOIN ' . TABLE_PREFIX . 'Users pu ON	pu.PortalUserId = %1$s.CreatedById',
 
 		'product' => '	SELECT %1$s.* %2$s
 						FROM %1$s
 						LEFT JOIN ' . TABLE_PREFIX . 'Products pr ON pr.ResourceId = %1$s.ItemId
 						LEFT JOIN ' . TABLE_PREFIX . 'Users pu ON	pu.PortalUserId = %1$s.CreatedById',
 	),
 
 	'ListSortings' => Array (
 		'' => Array (
 			'ForcedSorting' => Array ('Priority' => 'desc'),
 			'Sorting' => Array ('ReviewId' => 'desc'),
 		)
 	),
 
 	'Fields' => Array (
 		'ReviewId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
 		'CreatedOn' => Array (
 			'type' => 'int',
 			'formatter' => 'kDateFormatter', 'default' => '#NOW#',
 		),
 		'ReviewText' => Array (
 			'type' => 'string',
 			'formatter' => 'kFormatter',
 			'using_fck' => 1, 'default' => null, 'required' => 1,
 		),
 		'Rating' => Array (
 			'type' => 'int',
 			'formatter' => 'kOptionsFormatter',
 			'options' => Array (
 				0 => 'lu_None',
 				1 => 'lu_Rating_1',
 				2 => 'lu_Rating_2',
 				3 => 'lu_Rating_3',
 				4 => 'lu_Rating_4',
 				5 => 'lu_Rating_5'),
 			'use_phrases' => 1,
 			'min_value_inc' => 0, 'max_value_inc' => 5, 'not_null' => 1, 'default' => 0,
 		),
 		'IPAddress' => Array (
 			'type' => 'string',
 			'max_value_inc' => 15, 'not_null' =>1, 'default' => '',
 		),
 		'ItemId' => Array (
 			'type' => 'int',
 			'not_null' => 1, 'default' => 0
 		),
 		'CreatedById' => Array (
 			'type' => 'int',
 			'formatter' => 'kLEFTFormatter',
 			'options' => Array (USER_ROOT => 'root', USER_GUEST => 'Guest'),
 			'left_sql' => 'SELECT %s FROM ' . TABLE_PREFIX . 'Users WHERE %s',
 			'left_key_field' => 'PortalUserId', 'left_title_field' => USER_TITLE_FIELD,
 			'required' => 1, 'default' => NULL,
 			'error_msgs' => Array ('invalid_option' => '!la_error_UserNotFound!'),
 		),
 		'ItemType' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
 		'Priority' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
 		'Status' => Array (
 			'type' => 'int',
 			'formatter' => 'kOptionsFormatter', 'use_phrases' => 1,
 			'options' => Array (
 				0 => 'la_Disabled',
 				1 => 'la_Active',
 				2 => 'la_Pending',
 			),
 			'not_null' =>1, 'default' => 2,
 		),
 		'TextFormat' => Array (
 			'type' => 'int',
 			'formatter' => 'kOptionsFormatter',
 			'options' => Array (0 => 'la_text', 1 => 'la_html'), 'use_phrases' => 1,
 			'not_null' => 1, 'default' => 0,
 		),
 		'Module' => Array ('type' => 'string', 'not_null' => 1, 'default' => ''),
 		'HelpfulCount' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0),
 		'NotHelpfulCount' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0)
 	),
 
 	'VirtualFields' => Array (
 		'ReviewedBy' => Array ('type' => 'string', 'default' => ''),
 		'CatalogItemName' => Array ('type' => 'string', 'default' => ''),
 		'CatalogItemId' => Array ('type' => 'int', 'default' => 0),
 		'CatalogItemCategory' => Array ('type' => 'int', 'default' => 0),
 
 		'ItemName' => Array ('type' => 'string', 'default' => ''),
 		'ProductId' => Array ('type' => 'int', 'default' => 0),
 	),
 
 	'Grids' => Array (
 		'Default' => Array (
 			'Icons' => Array (
 				'default' => 'icon16_item.png',
 				0 => 'icon16_disabled.png',
 				1 => 'icon16_item.png',
 				2 => 'icon16_pending.png',
 			),
 			'Fields' => Array (
 				'ReviewId' => Array ('title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 60, ),
 				'ReviewText' => Array ('filter_block' => 'grid_like_filter', 'width' => 210, 'first_chars' => 200, ),
 				'ReviewedBy' => Array ( 'title' => 'la_col_ReviewedBy', 'filter_block' => 'grid_like_filter', 'width' => 100, ),
 				'CreatedOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145, ),
 				'Status' => Array ('filter_block' => 'grid_options_filter', 'width' => 80, ),
 				'Rating' => Array ('filter_block' => 'grid_options_filter', 'width' => 80, ),
 				'HelpfulCount' => Array ('filter_block' => 'grid_range_filter'),
 				'NotHelpfulCount' => Array ('filter_block' => 'grid_range_filter'),
 			),
 		),
 
 		'ReviewsSection' => Array (
 			'Icons' => Array (
 				'default' => 'icon16_item.png',
 				0 => 'icon16_disabled.png',
 				1 => 'icon16_item.png',
 				2 => 'icon16_pending.png',
 			),
 			'Fields' => Array (
 				'ReviewId' => Array ('title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 60, ),
 				'ReviewText' => Array ('data_block' => 'grid_reviewtext_td', 'filter_block' => 'grid_like_filter', 'width' => 210, 'first_chars' => 200, ),
 				'ReviewedBy' => Array ( 'title' => 'la_col_ReviewedBy', 'filter_block' => 'grid_like_filter', 'width' => 100, ),
 				'CreatedOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145, ),
 				'Status' => Array ('filter_block' => 'grid_options_filter', 'width' => 80, ),
 				'Rating' => Array ('filter_block' => 'grid_options_filter', 'width' => 80, ),
 				'HelpfulCount' => Array ('filter_block' => 'grid_range_filter'),
 				'NotHelpfulCount' => Array ('filter_block' => 'grid_range_filter'),
 			),
 		),
 	),
-);
\ No newline at end of file
+);
Index: branches/5.3.x/core/units/general/general_config.php
===================================================================
--- branches/5.3.x/core/units/general/general_config.php	(revision 16170)
+++ branches/5.3.x/core/units/general/general_config.php	(revision 16171)
@@ -1,43 +1,40 @@
 <?php
 /**
 * @version	$Id$
 * @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!');
 
 $config = new kUnitConfig('m', null, false);
 
 $config->setEventHandlerClass(Array ('class' => 'kEventHandler', 'file' => '', 'build_event' => 'OnBuild'));
 //$config->setTagProcessorClass(Array ('class' => 'kMainTagProcessor', 'file' => '', 'build_event' => 'OnBuild'));
 
 $config->setQueryString(Array (
 	1 => 'cat_id',
 	2 => 'cat_page',
 	3 => 'lang',
 	4 => 'theme',
 	5 => 'opener',
 	6 => 'wid',
 ));
 
 $config->setTitleField('CachedNavbar');
 $config->setTitlePhrase('la_Text_Category');
 $config->setCatalogTabIcon('icon16_section.png');
 $config->setItemType(1);
 $config->setTableName(TABLE_PREFIX . 'Categories');
 $config->setCatalogItem(true);
 $config->setPortalStyleEnv(true);
 
-$config->setRewritePriority(100);
-$config->setRewriteListener('c_EventHandler:CategoryRewriteListener');
-
 $config->setPermTabText('In-Portal');
 $config->setPermSection(Array ('search' => 'in-portal:configuration_search', 'custom' => 'in-portal:configuration_custom'));
Index: branches/5.3.x/core/units/general/MainRouter.php
===================================================================
--- branches/5.3.x/core/units/general/MainRouter.php	(nonexistent)
+++ branches/5.3.x/core/units/general/MainRouter.php	(revision 16171)
@@ -0,0 +1,305 @@
+<?php
+/**
+* @version	$Id$
+* @package	In-Portal
+* @copyright	Copyright (C) 1997 - 2015 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!');
+
+class MainRouter extends AbstractRouter
+{
+
+	/**
+	 * Returns unit config's prefix, that is associated with this router.
+	 *
+	 * @return string
+	 */
+	public function getPrefix()
+	{
+		return 'm';
+	}
+
+	/**
+	 * Returns weight of this router among other routers.
+	 *
+	 * @return float
+	 */
+	public function getWeight()
+	{
+		return 100;
+	}
+
+	/**
+	 * Builds url part.
+	 *
+	 * @return boolean Return true to continue to next router; return false not to rewrite given prefix.
+	 */
+	protected function build()
+	{
+		$ret = '';
+
+		$rewrite_processor = $this->getUrlProcessor();
+
+		$build_params = $this->extractBuildParams();
+
+		if ( $build_params === false ) {
+			return '';
+		}
+
+		// Add language.
+		if ( $build_params['m_lang'] && ($build_params['m_lang'] != $rewrite_processor->primaryLanguageId) ) {
+			$cache_key = 'language_names[%LangIDSerial:' . $build_params['m_lang'] . '%]';
+			$language_name = $this->Application->getCache($cache_key);
+
+			if ( $language_name === false ) {
+				$sql = 'SELECT PackName
+						FROM ' . TABLE_PREFIX . 'Languages
+						WHERE LanguageId = ' . $build_params['m_lang'];
+				$language_name = $this->Conn->GetOne($sql);
+
+				$this->Application->setCache($cache_key, $language_name);
+			}
+
+			$ret .= $language_name . '/';
+		}
+
+		// Add theme.
+		if ( $build_params['m_theme'] && ($build_params['m_theme'] != $rewrite_processor->primaryThemeId) ) {
+			$cache_key = 'theme_names[%ThemeIDSerial:' . $build_params['m_theme'] . '%]';
+			$theme_name = $this->Application->getCache($cache_key);
+
+			if ( $theme_name === false ) {
+				$sql = 'SELECT Name
+						FROM ' . TABLE_PREFIX . 'Themes
+						WHERE ThemeId = ' . $build_params['m_theme'];
+				$theme_name = $this->Conn->GetOne($sql);
+
+				$this->Application->setCache($cache_key, $theme_name);
+			}
+
+			$ret .= $theme_name . '/';
+		}
+
+		// Inject custom url parts made by other routers just after language/theme url parts.
+		$inject_parts = $this->getBuildParam('inject_parts', false);
+
+		if ( $inject_parts ) {
+			$ret .= implode('/', $inject_parts) . '/';
+		}
+
+		// Add category.
+		if ( $build_params['m_cat_id'] > 0 && $this->getBuildParam('pass_category', false) ) {
+			$category_filename = $this->Application->getCategoryCache($build_params['m_cat_id'], 'filenames');
+
+			preg_match('/^Content\/(.*)/i', $category_filename, $regs);
+
+			if ( $regs ) {
+				$template = $this->getBuildParam('t', false);
+
+				if ( strtolower($regs[1]) == strtolower($template) ) {
+					// We could have category path like "Content/<template_path>" in this case remove template.
+					$this->setBuildParam('pass_template', false);
+				}
+
+				$ret .= $regs[1] . '/';
+			}
+
+			$this->setBuildParam('category_processed', true);
+		}
+
+		// Reset category page.
+		$force_page_adding = false;
+		$reset_category_page = $this->getBuildParam('reset', false);
+
+		if ( $reset_category_page ) {
+			$this->setBuildParam('reset');
+
+			if ( $build_params['m_cat_id'] ) {
+				$build_params['m_cat_page'] = 1;
+				$force_page_adding = true;
+			}
+		}
+
+		$category_processed = $this->getBuildParam('category_processed', false);
+
+		if ( ($category_processed && ($build_params['m_cat_page'] > 1)) || $force_page_adding ) {
+			// Category name was added before AND category page number found.
+			$ret = rtrim($ret, '/') . '_' . $build_params['m_cat_page'] . '/';
+		}
+
+		$template = $this->getBuildParam('t', false);
+
+		if ( ($build_params['m_cat_id'] > 0) && $this->getBuildParam('pass_category', false) ) {
+			$category_template = $this->Application->getCategoryCache($build_params['m_cat_id'], 'category_designs');
+		}
+		else {
+			$category_template = '';
+		}
+
+		if ( (strtolower($template) == '__default__') && ($build_params['m_cat_id'] == 0) ) {
+			// For "Home" category set template to index when not set.
+			$template = 'index';
+		}
+
+		// Remove template from url if it is category index cached template.
+		if ( ($template == $category_template) || (mb_strtolower($template) == '__default__') ) {
+			// Given template is also default template for this category OR '__default__' given.
+			$this->setBuildParam('pass_template', false);
+		}
+
+		// Remove template from url if it is site homepage on primary language & theme.
+		if ( $template == 'index'
+			&& $build_params['m_lang'] == $rewrite_processor->primaryLanguageId
+			&& $build_params['m_theme'] == $rewrite_processor->primaryThemeId
+		) {
+			// Given template is site homepage on primary language & theme.
+			$this->setBuildParam('pass_template', false);
+		}
+
+		if ( $template && $this->getBuildParam('pass_template', false) ) {
+			$ret .= $template . '/';
+		}
+
+		return mb_strtolower(rtrim($ret, '/'));
+	}
+
+	/**
+	 * Parses url part.
+	 *
+	 * @param array $url_parts Url parts to parse.
+	 * @param array $params    Parameters, that are used for url building or created during url parsing.
+	 *
+	 * @return boolean Return true to continue to next router; return false to stop processing at this router.
+	 */
+	public function parse(array &$url_parts, array &$params)
+	{
+		if ( $this->parseFriendlyUrl($url_parts, $params) ) {
+			// Friendly urls work like exact match only!
+			return false;
+		}
+
+		$this->parseCategory($url_parts, $params);
+
+		return true;
+	}
+
+	/**
+	 * Checks if whole url_parts matches a whole In-CMS page
+	 *
+	 * @param array $url_parts Url parts.
+	 * @param array $params    Params.
+	 *
+	 * @return boolean
+	 */
+	protected function parseFriendlyUrl(array $url_parts, array &$params)
+	{
+		if ( !$url_parts ) {
+			return false;
+		}
+
+		$sql = 'SELECT CategoryId, NamedParentPath
+				FROM ' . TABLE_PREFIX . 'Categories
+				WHERE FriendlyURL = ' . $this->Conn->qstr(implode('/', $url_parts));
+		$friendly = $this->Conn->GetRow($sql);
+
+		if ( $friendly ) {
+			$params['m_cat_id'] = $friendly['CategoryId'];
+			$params['t'] = preg_replace('/^Content\//i', '', $friendly['NamedParentPath']);
+
+			while ( $url_parts ) {
+				$this->partParsed(array_shift($url_parts));
+			}
+
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Extracts category part from url
+	 *
+	 * @param array $url_parts Url parts.
+	 * @param array $params    Params.
+	 *
+	 * @return boolean
+	 */
+	protected function parseCategory(array $url_parts, array &$params)
+	{
+		if ( !$url_parts ) {
+			return false;
+		}
+
+		$res = false;
+		$url_part = array_shift($url_parts);
+
+		$category_id = 0;
+		$last_category_info = false;
+		$category_path = $url_part == 'content' ? '' : 'content';
+
+		$rewrite_processor = $this->getUrlProcessor();
+
+		do {
+			$category_path = trim($category_path . '/' . $url_part, '/');
+
+			// Part: "bb_<topic_id>" matches "forums/bb_2".
+			if ( !preg_match('/^bb_[\d]+$/', $url_part) && preg_match('/(.*)_([\d]+)$/', $category_path, $regs) ) {
+				$category_path = $regs[1];
+				$params['m_cat_page'] = $regs[2];
+			}
+
+			$sql = 'SELECT CategoryId, SymLinkCategoryId, NamedParentPath
+					FROM ' . TABLE_PREFIX . 'Categories
+					WHERE
+						(LOWER(NamedParentPath) = ' . $this->Conn->qstr($category_path) . ')
+						AND
+						(ThemeId = ' . $params['m_theme'] . ' OR ThemeId = 0)';
+			$category_info = $this->Conn->GetRow($sql);
+
+			if ( $category_info !== false ) {
+				$last_category_info = $category_info;
+				$rewrite_processor->partParsed($url_part);
+
+				$url_part = array_shift($url_parts);
+				$res = true;
+			}
+		} while ( $category_info !== false && $url_part );
+
+		if ( $last_category_info ) {
+			// This category is symlink to other category, so use it's url instead
+			// (used in case if url prior to symlink adding was indexed by spider or was bookmarked).
+			if ( $last_category_info['SymLinkCategoryId'] ) {
+				$sql = 'SELECT CategoryId, NamedParentPath
+						FROM ' . TABLE_PREFIX . 'Categories
+						WHERE (CategoryId = ' . $last_category_info['SymLinkCategoryId'] . ')';
+				$category_info = $this->Conn->GetRow($sql);
+
+				if ( $category_info ) {
+					// Web symlinked category was found use it.
+					// TODO: maybe 302 redirect should be made to symlinked category url
+					// (all other url parts should stay).
+					$last_category_info = $category_info;
+				}
+			}
+
+			// 1. Set virtual page as template, this will be replaced to physical template later in kApplication::Run.
+			// 2. Don't set CachedTemplate field as template here, because we will loose original
+			// page associated with it's cms blocks!
+			$params['t'] = mb_strtolower(preg_replace('/^Content\//i', '', $last_category_info['NamedParentPath']));
+
+			$params['m_cat_id'] = $last_category_info['CategoryId'];
+			$params['is_virtual'] = true; // For template from POST, strange code there!
+		}
+
+		return $res;
+	}
+
+}

Property changes on: branches/5.3.x/core/units/general/MainRouter.php
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/core/install/cache/class_structure.php
===================================================================
--- branches/5.3.x/core/install/cache/class_structure.php	(revision 16170)
+++ branches/5.3.x/core/install/cache/class_structure.php	(revision 16171)
@@ -1,2537 +1,2561 @@
 <?php
 // @codingStandardsIgnoreFile
 
 /**
  * This file is automatically @generated. Use 'php tools/build_class_map.php' to rebuild it.
  */
 return array(
 	'cache_format' => 2,
 	'classes' => array(
+		'AbstractCategoryItemRouter' => '/core/kernel/utility/Router/AbstractCategoryItemRouter.php',
+		'AbstractReviewRouter' => '/core/kernel/utility/Router/AbstractReviewRouter.php',
+		'AbstractRouter' => '/core/kernel/utility/Router/AbstractRouter.php',
 		'AdminEventsHandler' => '/core/units/admin/admin_events_handler.php',
 		'AdminTagProcessor' => '/core/units/admin/admin_tag_processor.php',
 		'AjaxFormHelper' => '/core/units/helpers/ajax_form_helper.php',
 		'ApcCacheHandler' => '/core/kernel/utility/cache.php',
 		'BackupHelper' => '/core/units/helpers/backup_helper.php',
 		'BaseSession' => '/core/kernel/session/session.php',
 		'BaseSessionStorage' => '/core/kernel/session/session_storage.php',
 		'CacheSettings' => '/core/kernel/startup.php',
 		'CaptchaEventHandler' => '/core/units/captcha/captcha_eh.php',
 		'CategoriesEventHandler' => '/core/units/categories/categories_event_handler.php',
 		'CategoriesItem' => '/core/units/categories/categories_item.php',
 		'CategoriesTagProcessor' => '/core/units/categories/categories_tag_processor.php',
 		'CategoryHelper' => '/core/units/helpers/category_helper.php',
-		'CategoryItemRewrite' => '/core/units/helpers/mod_rewrite_helper.php',
 		'CategoryItemsEventHandler' => '/core/units/category_items/category_items_event_handler.php',
 		'CategoryItemsTagProcessor' => '/core/units/category_items/category_items_tag_processor.php',
 		'CategoryPermissionRebuild' => '/core/kernel/constants.php',
 		'ChangeLog' => '/core/kernel/constants.php',
 		'ChangeLogEventHandler' => '/core/units/logs/change_logs/change_log_eh.php',
 		'ChangeLogTagProcessor' => '/core/units/logs/change_logs/change_log_tp.php',
 		'ColumnSet' => '/core/units/helpers/col_picker_helper.php',
 		'ConfigSearchEventHandler' => '/core/units/config_search/config_search_event_handler.php',
 		'ConfigSearchTagProcessor' => '/core/units/config_search/config_search_tag_processor.php',
 		'ConfigurationEventHandler' => '/core/units/configuration/configuration_event_handler.php',
 		'ConfigurationItem' => '/core/units/configuration/configuration.php',
 		'ConfigurationTagProcessor' => '/core/units/configuration/configuration_tag_processor.php',
 		'ConfigurationValidator' => '/core/units/configuration/configuration_validator.php',
 		'ContentEventHandler' => '/core/units/content/content_eh.php',
 		'ContentTagProcessor' => '/core/units/content/content_tp.php',
 		'CoreUpgrades' => '/core/install/upgrades.php',
 		'CountryStateEventHandler' => '/core/units/country_states/country_state_eh.php',
 		'CssMinifyHelper' => '/core/units/helpers/minifiers/css_minify_helper.php',
 		'CustomDataEventHandler' => '/core/units/custom_data/custom_data_event_handler.php',
 		'CustomFieldsEventHandler' => '/core/units/custom_fields/custom_fields_event_handler.php',
 		'CustomFieldsTagProcessor' => '/core/units/custom_fields/custom_fields_tag_processor.php',
 		'Debugger' => '/core/kernel/utility/debugger.php',
 		'DebuggerUtil' => '/core/kernel/utility/debugger.php',
 		'DeploymentHelper' => '/core/units/helpers/deployment_helper.php',
 		'DraftEventHandler' => '/core/units/forms/drafts/draft_eh.php',
 		'EditPickerHelper' => '/core/units/helpers/controls/edit_picker_helper.php',
 		'EmailDelivery' => '/core/kernel/constants.php',
 		'EmailLogEventHandler' => '/core/units/logs/email_logs/email_log_eh.php',
 		'EmailLogStatus' => '/core/kernel/constants.php',
 		'EmailLogTagProcessor' => '/core/units/logs/email_logs/email_log_tp.php',
 		'EmailQueueEventHandler' => '/core/units/email_queue/email_queue_eh.php',
 		'EmailQueueTagProcessor' => '/core/units/email_queue/email_queue_tp.php',
 		'EmailTemplate' => '/core/kernel/constants.php',
 		'EmailTemplateEventHandler' => '/core/units/email_templates/email_template_eh.php',
 		'EmailTemplateTagProcessor' => '/core/units/email_templates/email_template_tp.php',
 		'FakeCacheHandler' => '/core/kernel/utility/cache.php',
 		'FavoritesEventHandler' => '/core/units/favorites/favorites_eh.php',
 		'FckEventHandler' => '/core/units/fck/fck_eh.php',
 		'FckTagProcessor' => '/core/units/fck/fck_tp.php',
 		'FileEventHandler' => '/core/units/files/file_eh.php',
 		'FileHelper' => '/core/units/helpers/file_helper.php',
 		'FileTagProcessor' => '/core/units/files/file_tp.php',
 		'FormFieldEventHandler' => '/core/units/forms/form_fields/form_field_eh.php',
 		'FormFieldsTagProcessor' => '/core/units/forms/form_fields/form_fields_tp.php',
 		'FormSubmissionHelper' => '/core/units/helpers/form_submission_helper.php',
 		'FormSubmissionTagProcessor' => '/core/units/forms/form_submissions/form_submission_tp.php',
 		'FormSubmissionsEventHandler' => '/core/units/forms/form_submissions/form_submissions_eh.php',
 		'FormsEventHandler' => '/core/units/forms/forms/forms_eh.php',
 		'FormsTagProcessor' => '/core/units/forms/forms/forms_tp.php',
 		'GeoCodeHelper' => '/core/units/helpers/geocode_helper.php',
 		'GroupTagProcessor' => '/core/units/groups/group_tp.php',
 		'GroupsEventHandler' => '/core/units/groups/groups_event_handler.php',
 		'IDBConnection' => '/core/kernel/db/i_db_connection.php',
 		'ImageEventHandler' => '/core/units/images/image_event_handler.php',
 		'ImageHelper' => '/core/units/helpers/image_helper.php',
 		'ImageTagProcessor' => '/core/units/images/image_tag_processor.php',
 		'ImagesItem' => '/core/units/images/images.php',
 		'InPortalPrerequisites' => '/core/install/prerequisites.php',
 		'InpCustomFieldsHelper' => '/core/units/helpers/custom_fields_helper.php',
 		'Intechnic\\InPortal\\Core\\kernel\\utility\\ClassDiscovery\\ClassDetector' => '/core/kernel/utility/ClassDiscovery/ClassDetector.php',
 		'Intechnic\\InPortal\\Core\\kernel\\utility\\ClassDiscovery\\ClassMapBuilder' => '/core/kernel/utility/ClassDiscovery/ClassMapBuilder.php',
 		'Intechnic\\InPortal\\Core\\kernel\\utility\\ClassDiscovery\\CodeFolderFilterIterator' => '/core/kernel/utility/ClassDiscovery/CodeFolderFilterIterator.php',
 		'ItemFilterEventHandler' => '/core/units/filters/item_filter_eh.php',
 		'ItemFilterTagProcessor' => '/core/units/filters/item_filter_tp.php',
 		'JSONHelper' => '/core/units/helpers/json_helper.php',
 		'JsMinifyHelper' => '/core/units/helpers/minifiers/js_minify_helper.php',
 		'Language' => '/core/kernel/constants.php',
 		'LanguageImportHelper' => '/core/units/helpers/language_import_helper.php',
 		'LanguagesEventHandler' => '/core/units/languages/languages_event_handler.php',
 		'LanguagesItem' => '/core/units/languages/languages_item.php',
 		'LanguagesTagProcessor' => '/core/units/languages/languages_tag_processor.php',
 		'LeftJoinOptimizer' => '/core/kernel/db/dblist.php',
 		'ListHelper' => '/core/units/helpers/list_helper.php',
 		'LoginResult' => '/core/kernel/constants.php',
 		'MInputHelper' => '/core/units/helpers/controls/minput_helper.php',
 		'MailboxHelper' => '/core/units/helpers/mailbox_helper.php',
 		'MailingList' => '/core/kernel/constants.php',
 		'MailingListEventHandler' => '/core/units/mailing_lists/mailing_list_eh.php',
 		'MailingListHelper' => '/core/units/helpers/mailing_list_helper.php',
 		'MailingListTagProcessor' => '/core/units/mailing_lists/mailing_list_tp.php',
+		'MainRouter' => '/core/units/general/MainRouter.php',
 		'MaintenanceMode' => '/core/kernel/startup.php',
 		'MassImageResizer' => '/core/units/admin/admin_events_handler.php',
 		'MemcacheCacheHandler' => '/core/kernel/utility/cache.php',
 		'MenuHelper' => '/core/units/helpers/menu_helper.php',
 		'MimeDecodeHelper' => '/core/units/helpers/mime_decode_helper.php',
 		'MinifyHelper' => '/core/units/helpers/minifiers/minify_helper.php',
 		'ModuleDeploymentLog' => '/core/kernel/constants.php',
 		'ModuleDeploymentLogEventHandler' => '/core/units/logs/module_deployment_logs/module_deployment_log_eh.php',
 		'ModulesEventHandler' => '/core/units/modules/modules_event_handler.php',
 		'ModulesTagProcessor' => '/core/units/modules/modules_tag_processor.php',
 		'NParser' => '/core/kernel/nparser/nparser.php',
 		'NParserCompiler' => '/core/kernel/nparser/compiler.php',
 		'POP3Helper' => '/core/units/helpers/pop3_helper.php',
 		'PageHelper' => '/core/units/helpers/page_helper.php',
 		'PageRevisionEventHandler' => '/core/units/page_revisions/page_revision_eh.php',
 		'PageRevisionTagProcessor' => '/core/units/page_revisions/page_revision_tp.php',
 		'Params' => '/core/kernel/utility/params.php',
 		'ParserException' => '/core/kernel/nparser/nparser.php',
 		'PasswordHash' => '/core/kernel/utility/php_pass.php',
 		'PasswordHashingMethod' => '/core/kernel/constants.php',
 		'PermissionTypeEventHandler' => '/core/units/permission_types/permission_type_eh.php',
 		'PermissionsEventHandler' => '/core/units/permissions/permissions_event_handler.php',
 		'PermissionsTagProcessor' => '/core/units/permissions/permissions_tag_processor.php',
 		'PhraseTagProcessor' => '/core/units/phrases/phrase_tp.php',
 		'PhrasesEventHandler' => '/core/units/phrases/phrases_event_handler.php',
 		'PriorityEventHandler' => '/core/units/priorites/priority_eh.php',
 		'PromoBlockEventHandler' => '/core/units/promo_blocks/promo_block_eh.php',
 		'PromoBlockGroupEventHandler' => '/core/units/promo_block_groups/promo_block_group_eh.php',
 		'PromoBlockGroupTagProcessor' => '/core/units/promo_block_groups/promo_block_group_tp.php',
 		'PromoBlockTagProcessor' => '/core/units/promo_blocks/promo_block_tp.php',
 		'PromoBlockType' => '/core/kernel/constants.php',
 		'RatingHelper' => '/core/units/helpers/rating_helper.php',
 		'RelatedSearchEventHandler' => '/core/units/related_searches/related_searches_event_handler.php',
 		'RelatedSearchTagProcessor' => '/core/units/related_searches/related_searches_tag_processor.php',
 		'RelationshipEventHandler' => '/core/units/relationship/relationship_event_handler.php',
 		'RelationshipTagProcessor' => '/core/units/relationship/relationship_tp.php',
 		'ReviewsEventHandler' => '/core/units/reviews/reviews_event_handler.php',
 		'ReviewsTagProcessor' => '/core/units/reviews/reviews_tag_processor.php',
 		'ScheduledTask' => '/core/kernel/constants.php',
 		'ScheduledTaskEventHandler' => '/core/units/scheduled_tasks/scheduled_task_eh.php',
 		'SelectorsEventHandler' => '/core/units/selectors/selectors_event_handler.php',
 		'SelectorsItem' => '/core/units/selectors/selectors_item.php',
 		'SelectorsTagProcessor' => '/core/units/selectors/selectors_tag_processor.php',
 		'Session' => '/core/kernel/session/inp_session.php',
 		'SessionLogEventHandler' => '/core/units/logs/session_logs/session_log_eh.php',
 		'SessionStorage' => '/core/kernel/session/inp_session_storage.php',
 		'SiteConfigEventHandler' => '/core/units/sections/site_config_eh.php',
 		'SiteConfigHelper' => '/core/units/helpers/site_config_helper.php',
 		'SiteConfigTagProcessor' => '/core/units/sections/site_config_tp.php',
 		'SiteDomainEventHandler' => '/core/units/site_domains/site_domain_eh.php',
 		'SiteHelper' => '/core/units/helpers/site_helper.php',
 		'SkinEventHandler' => '/core/units/skins/skin_eh.php',
 		'SkinHelper' => '/core/units/helpers/skin_helper.php',
 		'SpamHelper' => '/core/units/helpers/spam_helper.php',
 		'SpamReportEventHandler' => '/core/units/spam_reports/spam_report_eh.php',
 		'SpamReportTagProcessor' => '/core/units/spam_reports/spam_report_tp.php',
 		'StatisticsEventHandler' => '/core/units/statistics/statistics_event_handler.php',
 		'StatisticsTagProcessor' => '/core/units/statistics/statistics_tag_processor.php',
 		'StorageEngine' => '/core/kernel/constants.php',
 		'StylesheetsEventHandler' => '/core/units/stylesheets/stylesheets_event_handler.php',
 		'StylesheetsItem' => '/core/units/stylesheets/stylesheets_item.php',
 		'SubmissionFormField' => '/core/kernel/constants.php',
 		'SubmissionLogEventHandler' => '/core/units/forms/submission_log/submission_log_eh.php',
 		'SubmissionLogTagProcessor' => '/core/units/forms/submission_log/submission_log_tp.php',
 		'SystemEventSubscriptionEventHandler' => '/core/units/system_event_subscriptions/system_event_subscription_eh.php',
 		'SystemEventSubscriptionTagProcessor' => '/core/units/system_event_subscriptions/system_event_subscription_tp.php',
 		'SystemLogEventHandler' => '/core/units/logs/system_logs/system_log_eh.php',
 		'SystemLogTagProcessor' => '/core/units/logs/system_logs/system_log_tp.php',
 		'TemplateHelper' => '/core/units/helpers/template_helper.php',
 		'TemplatesCache' => '/core/kernel/nparser/template_cache.php',
 		'ThemeFileEventHandler' => '/core/units/theme_files/theme_file_eh.php',
 		'ThemeItem' => '/core/units/themes/theme_item.php',
 		'ThemesEventHandler' => '/core/units/themes/themes_eh.php',
 		'ThemesTagProcessor' => '/core/units/themes/themes_tag_processor.php',
 		'ThesaurusEventHandler' => '/core/units/thesaurus/thesaurus_eh.php',
 		'ThesaurusTagProcessor' => '/core/units/thesaurus/thesaurus_tp.php',
 		'TranslationSaveMode' => '/core/kernel/constants.php',
 		'TranslatorEventHandler' => '/core/units/translator/translator_event_handler.php',
 		'TranslatorTagProcessor' => '/core/units/translator/translator_tp.php',
 		'UnitConfigDecorator' => '/core/units/admin/admin_events_handler.php',
 		'UserGroupsEventHandler' => '/core/units/user_groups/user_groups_eh.php',
 		'UserHelper' => '/core/units/helpers/user_helper.php',
 		'UserProfileEventHandler' => '/core/units/user_profile/user_profile_eh.php',
 		'UserProfileTagProcessor' => '/core/units/user_profile/user_profile_tp.php',
 		'UserType' => '/core/kernel/constants.php',
 		'UsersEventHandler' => '/core/units/users/users_event_handler.php',
 		'UsersItem' => '/core/units/users/users_item.php',
 		'UsersSyncronize' => '/core/units/users/users_syncronize.php',
 		'UsersSyncronizeManager' => '/core/units/users/users_syncronize.php',
 		'UsersTagProcessor' => '/core/units/users/users_tag_processor.php',
 		'VisitsEventHandler' => '/core/units/visits/visits_event_handler.php',
 		'VisitsList' => '/core/units/visits/visits_list.php',
 		'VisitsTagProcessor' => '/core/units/visits/visits_tag_processor.php',
 		'XCacheCacheHandler' => '/core/kernel/utility/cache.php',
 		'XMLIterator' => '/core/units/helpers/xml_helper5.php',
 		'_BlockTag' => '/core/kernel/nparser/ntags.php',
 		'_Tag_Cache' => '/core/kernel/nparser/ntags.php',
 		'_Tag_Capture' => '/core/kernel/nparser/ntags.php',
 		'_Tag_Comment' => '/core/kernel/nparser/ntags.php',
 		'_Tag_Compress' => '/core/kernel/nparser/ntags.php',
 		'_Tag_DefaultParam' => '/core/kernel/nparser/ntags.php',
 		'_Tag_DefineElement' => '/core/kernel/nparser/ntags.php',
 		'_Tag_If' => '/core/kernel/nparser/ntags.php',
 		'_Tag_IfDataExists' => '/core/kernel/nparser/ntags.php',
 		'_Tag_IfNot' => '/core/kernel/nparser/ntags.php',
 		'_Tag_Include' => '/core/kernel/nparser/ntags.php',
 		'_Tag_Param' => '/core/kernel/nparser/ntags.php',
 		'_Tag_RenderElement' => '/core/kernel/nparser/ntags.php',
 		'_Tag_RenderElements' => '/core/kernel/nparser/ntags.php',
 		'_Tag_SetParam' => '/core/kernel/nparser/ntags.php',
 		'clsCachedPermissions' => '/core/units/categories/cache_updater.php',
 		'clsRecursionStack' => '/core/units/categories/cache_updater.php',
 		'fckFCKHelper' => '/core/units/helpers/fck_helper.php',
 		'kApplication' => '/core/kernel/application.php',
 		'kArray' => '/core/kernel/utility/params.php',
 		'kBase' => '/core/kernel/kbase.php',
 		'kBracketsHelper' => '/core/units/helpers/brackets_helper.php',
 		'kCCDateFormatter' => '/core/kernel/utility/formatters/ccdate_formatter.php',
 		'kCSSDefaults' => '/core/units/pdf/css_defaults.php',
 		'kCSVHelper' => '/core/units/helpers/csv_helper.php',
 		'kCache' => '/core/kernel/utility/cache.php',
 		'kCacheHandler' => '/core/kernel/utility/cache.php',
 		'kCacheManager' => '/core/kernel/managers/cache_manager.php',
 		'kCaptchaHelper' => '/core/units/helpers/captcha_helper.php',
 		'kCatDBEventHandler' => '/core/kernel/db/cat_event_handler.php',
 		'kCatDBItem' => '/core/kernel/db/cat_dbitem.php',
 		'kCatDBItemExportHelper' => '/core/units/helpers/cat_dbitem_export_helper.php',
 		'kCatDBList' => '/core/kernel/db/cat_dblist.php',
 		'kCatDBTagProcessor' => '/core/kernel/db/cat_tag_processor.php',
 		'kChangesFormatter' => '/core/units/logs/change_logs/changes_formatter.php',
 		'kChartHelper' => '/core/units/helpers/chart_helper.php',
 		'kClipboardHelper' => '/core/units/helpers/clipboard_helper.php',
 		'kColumnPickerHelper' => '/core/units/helpers/col_picker_helper.php',
 		'kCookieHasher' => '/core/kernel/utility/cookie_hasher.php',
 		'kCountHelper' => '/core/units/helpers/count_helper.php',
 		'kCountryStatesHelper' => '/core/units/helpers/country_states_helper.php',
 		'kCronField' => '/core/units/helpers/cron_helper.php',
 		'kCronHelper' => '/core/units/helpers/cron_helper.php',
 		'kCurlHelper' => '/core/units/helpers/curl_helper.php',
 		'kCustomFieldFormatter' => '/core/kernel/utility/formatters/customfield_formatter.php',
 		'kDBBase' => '/core/kernel/kbase.php',
 		'kDBConnection' => '/core/kernel/db/db_connection.php',
 		'kDBConnectionDebug' => '/core/kernel/db/db_connection.php',
 		'kDBEventHandler' => '/core/kernel/db/db_event_handler.php',
 		'kDBItem' => '/core/kernel/db/dbitem.php',
 		'kDBList' => '/core/kernel/db/dblist.php',
 		'kDBLoadBalancer' => '/core/kernel/db/db_load_balancer.php',
 		'kDBTagProcessor' => '/core/kernel/db/db_tag_processor.php',
 		'kDateFormatter' => '/core/kernel/utility/formatters/date_formatter.php',
 		'kEmail' => '/core/kernel/utility/email.php',
 		'kEmailSendingHelper' => '/core/kernel/utility/email_send.php',
 		'kEmailTemplateHelper' => '/core/units/helpers/email_template_helper.php',
 		'kErrorHandlerStack' => '/core/kernel/utility/logger.php',
 		'kEvent' => '/core/kernel/utility/event.php',
 		'kEventHandler' => '/core/kernel/event_handler.php',
 		'kEventManager' => '/core/kernel/event_manager.php',
 		'kExceptionHandlerStack' => '/core/kernel/utility/logger.php',
 		'kFactory' => '/core/kernel/utility/factory.php',
 		'kFactoryException' => '/core/kernel/utility/factory.php',
 		'kFilenamesHelper' => '/core/units/helpers/filenames_helper.php',
 		'kFilesizeFormatter' => '/core/kernel/utility/formatters/filesize_formatter.php',
 		'kFormatter' => '/core/kernel/utility/formatters/formatter.php',
 		'kHTTPQuery' => '/core/kernel/utility/http_query.php',
 		'kHandlerStack' => '/core/kernel/utility/logger.php',
 		'kHelper' => '/core/kernel/kbase.php',
 		'kHookManager' => '/core/kernel/managers/hook_manager.php',
 		'kInstallToolkit' => '/core/install/install_toolkit.php',
 		'kInstallator' => '/core/install.php',
 		'kLEFTFormatter' => '/core/kernel/utility/formatters/left_formatter.php',
 		'kLogger' => '/core/kernel/utility/logger.php',
 		'kMainTagProcessor' => '/core/kernel/processors/main_processor.php',
 		'kModulesHelper' => '/core/units/helpers/modules_helper.php',
 		'kMultiLanguage' => '/core/kernel/utility/formatters/multilang_formatter.php',
 		'kMultiLanguageHelper' => '/core/units/helpers/multilanguage_helper.php',
 		'kMultipleFilter' => '/core/kernel/utility/filters.php',
 		'kMySQLQuery' => '/core/kernel/db/db_connection.php',
 		'kMySQLQueryCol' => '/core/kernel/db/db_connection.php',
 		'kNavigationBar' => '/core/units/helpers/navigation_bar.php',
 		'kNoPermissionException' => '/core/kernel/kbase.php',
 		'kOpenerStack' => '/core/kernel/utility/opener_stack.php',
 		'kOptionsFormatter' => '/core/kernel/utility/formatters/options_formatter.php',
 		'kPDFElemFactory' => '/core/units/pdf/pdf_helper.php',
 		'kPDFElement' => '/core/units/pdf/pdf_helper.php',
 		'kPDFHelper' => '/core/units/pdf/pdf_helper.php',
 		'kPDFImage' => '/core/units/pdf/pdf_image.php',
 		'kPDFLine' => '/core/units/pdf/pdf_helper.php',
 		'kPDFRenderer' => '/core/units/pdf/pdf_renderer.php',
 		'kPDFStylesheet' => '/core/units/pdf/pdf_styles.php',
 		'kPDFTable' => '/core/units/pdf/pdf_table.php',
 		'kPDFTableRow' => '/core/units/pdf/pdf_table.php',
 		'kPDFTextElement' => '/core/units/pdf/pdf_text.php',
 		'kPasswordFormatter' => '/core/kernel/utility/formatters/password_formatter.php',
 		'kPermCacheUpdater' => '/core/units/categories/cache_updater.php',
 		'kPermissionsHelper' => '/core/units/helpers/permissions_helper.php',
 		'kPhraseCache' => '/core/kernel/languages/phrases_cache.php',
 		'kPictureFormatter' => '/core/kernel/utility/formatters/upload_formatter.php',
 		'kPlainUrlProcessor' => '/core/kernel/managers/plain_url_processor.php',
 		'kPriorityHelper' => '/core/units/helpers/priority_helper.php',
 		'kRecursiveHelper' => '/core/units/helpers/recursive_helper.php',
 		'kRedirectException' => '/core/kernel/kbase.php',
 		'kRequestManager' => '/core/kernel/managers/request_manager.php',
 		'kRewriteUrlProcessor' => '/core/kernel/managers/rewrite_url_processor.php',
 		'kScheduledTaskManager' => '/core/kernel/managers/scheduled_task_manager.php',
 		'kSearchHelper' => '/core/units/helpers/search_helper.php',
 		'kSectionsHelper' => '/core/units/helpers/sections_helper.php',
 		'kSerializedFormatter' => '/core/kernel/utility/formatters/serialized_formatter.php',
 		'kSocket' => '/core/kernel/utility/socket.php',
 		'kSubscriptionAnalyzer' => '/core/units/system_event_subscriptions/system_event_subscription_tp.php',
 		'kSubscriptionItem' => '/core/kernel/managers/subscription_manager.php',
 		'kSubscriptionManager' => '/core/kernel/managers/subscription_manager.php',
 		'kSystemConfig' => '/core/kernel/utility/system_config.php',
 		'kSystemConfigException' => '/core/kernel/utility/system_config.php',
 		'kTCPDFRenderer' => '/core/units/pdf/pdf_renderer_tcpdf.php',
 		'kTagProcessor' => '/core/kernel/processors/tag_processor.php',
 		'kTempHandlerSubTable' => '/core/kernel/utility/temp_handler.php',
 		'kTempHandlerTable' => '/core/kernel/utility/temp_handler.php',
 		'kTempHandlerTopTable' => '/core/kernel/utility/temp_handler.php',
 		'kTempTablesHandler' => '/core/kernel/utility/temp_handler.php',
 		'kThemesHelper' => '/core/units/helpers/themes_helper.php',
 		'kUnitConfig' => '/core/kernel/utility/unit_config.php',
 		'kUnitConfigCloner' => '/core/kernel/utility/unit_config_cloner.php',
 		'kUnitConfigReader' => '/core/kernel/utility/unit_config_reader.php',
 		'kUnitFormatter' => '/core/kernel/utility/formatters/unit_formatter.php',
 		'kUpgradeHelper' => '/core/install/upgrade_helper.php',
 		'kUploadFormatter' => '/core/kernel/utility/formatters/upload_formatter.php',
 		'kUploadHelper' => '/core/units/helpers/upload_helper.php',
 		'kUploaderException' => '/core/units/helpers/upload_helper.php',
 		'kUrlManager' => '/core/kernel/managers/url_manager.php',
 		'kUrlProcessor' => '/core/kernel/managers/url_processor.php',
 		'kUtil' => '/core/kernel/globals.php',
 		'kValidator' => '/core/kernel/utility/validator.php',
 		'kXMLHelper' => '/core/units/helpers/xml_helper.php',
 		'kXMLNode' => '/core/units/helpers/xml_helper.php',
 		'kXMLNode5' => '/core/units/helpers/xml_helper5.php',
 		'kZendPDFRenderer' => '/core/units/pdf/pdf_renderer_zend.php',
 		'kiCacheable' => '/core/kernel/interfaces/cacheable.php',
 	),
 	'class_info' => array(
+		'AbstractCategoryItemRouter' => array(
+			'type' => 1,
+			'modifiers' => 1,
+			'extends' => array(
+				0 => 'AbstractRouter',
+			),
+		),
+		'AbstractReviewRouter' => array(
+			'type' => 1,
+			'modifiers' => 1,
+			'extends' => array(
+				0 => 'AbstractRouter',
+			),
+		),
+		'AbstractRouter' => array(
+			'type' => 1,
+			'modifiers' => 1,
+			'extends' => array(
+				0 => 'kBase',
+			),
+		),
 		'AdminEventsHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'AdminTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'AjaxFormHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'ApcCacheHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kCacheHandler',
 			),
 		),
 		'BackupHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'BaseSession' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'BaseSessionStorage' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBBase',
 			),
 		),
 		'CacheSettings' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'CaptchaEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kEventHandler',
 			),
 		),
 		'CategoriesEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'CategoriesItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'CategoriesTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'CategoryHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
-		'CategoryItemRewrite' => array(
-			'type' => 1,
-			'modifiers' => 0,
-			'extends' => array(
-				0 => 'kHelper',
-			),
-		),
 		'CategoryItemsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'CategoryItemsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'CategoryPermissionRebuild' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'ChangeLog' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'ChangeLogEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ChangeLogTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'ColumnSet' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'ConfigSearchEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ConfigSearchTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'ConfigurationEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ConfigurationItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'ConfigurationTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'ConfigurationValidator' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kValidator',
 			),
 		),
 		'ContentEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ContentTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'CoreUpgrades' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kUpgradeHelper',
 			),
 		),
 		'CountryStateEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'CssMinifyHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'CustomDataEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'CustomFieldsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'CustomFieldsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'Debugger' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'DebuggerUtil' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'DeploymentHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'DraftEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'EditPickerHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'EmailDelivery' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'EmailLogEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'EmailLogStatus' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'EmailLogTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'EmailQueueEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'EmailQueueTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'EmailTemplate' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'EmailTemplateEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'EmailTemplateTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'FakeCacheHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kCacheHandler',
 			),
 		),
 		'FavoritesEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'FckEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'FckTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'FileEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'FileHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'FileTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'FormFieldEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'FormFieldsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'FormSubmissionHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'FormSubmissionTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'FormSubmissionsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'FormsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'FormsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'GeoCodeHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'GroupTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'GroupsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'IDBConnection' => array(
 			'type' => 2,
 		),
 		'ImageEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ImageHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'ImageTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'ImagesItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'InPortalPrerequisites' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'InpCustomFieldsHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'Intechnic\\InPortal\\Core\\kernel\\utility\\ClassDiscovery\\ClassDetector' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'PhpParser\\NodeVisitorAbstract',
 			),
 		),
 		'Intechnic\\InPortal\\Core\\kernel\\utility\\ClassDiscovery\\ClassMapBuilder' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'Intechnic\\InPortal\\Core\\kernel\\utility\\ClassDiscovery\\CodeFolderFilterIterator' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'RecursiveFilterIterator',
 			),
 		),
 		'ItemFilterEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ItemFilterTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'JSONHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'JsMinifyHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'Language' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'LanguageImportHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'LanguagesEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'LanguagesItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'LanguagesTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'LeftJoinOptimizer' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'ListHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'LoginResult' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'MInputHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'MailboxHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'MailingList' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'MailingListEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'MailingListHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'MailingListTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
+		'MainRouter' => array(
+			'type' => 1,
+			'modifiers' => 0,
+			'extends' => array(
+				0 => 'AbstractRouter',
+			),
+		),
 		'MaintenanceMode' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'MassImageResizer' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'MemcacheCacheHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kCacheHandler',
 			),
 		),
 		'MenuHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'MimeDecodeHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'MinifyHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'ModuleDeploymentLog' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'ModuleDeploymentLogEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ModulesEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ModulesTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'NParser' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'NParserCompiler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'POP3Helper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'PageHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'PageRevisionEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'PageRevisionTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'Params' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'ParserException' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'Exception',
 			),
 		),
 		'PasswordHash' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'PasswordHashingMethod' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'PermissionTypeEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'PermissionsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'PermissionsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'PhraseTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'PhrasesEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'PriorityEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'PromoBlockEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'PromoBlockGroupEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'PromoBlockGroupTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'PromoBlockTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'PromoBlockType' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'RatingHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'RelatedSearchEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'RelatedSearchTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'RelationshipEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'RelationshipTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'ReviewsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ReviewsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'ScheduledTask' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'ScheduledTaskEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SelectorsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SelectorsItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'SelectorsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'Session' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'BaseSession',
 			),
 		),
 		'SessionLogEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SessionStorage' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'BaseSessionStorage',
 			),
 		),
 		'SiteConfigEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kEventHandler',
 			),
 		),
 		'SiteConfigHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'SiteConfigTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kTagProcessor',
 			),
 		),
 		'SiteDomainEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SiteHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'SkinEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SkinHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'SpamHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'SpamReportEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SpamReportTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'StatisticsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'StatisticsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'StorageEngine' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'StylesheetsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'StylesheetsItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'SubmissionFormField' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'SubmissionLogEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SubmissionLogTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'SystemEventSubscriptionEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SystemEventSubscriptionTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'SystemLogEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'SystemLogTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'TemplateHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'TemplatesCache' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'ThemeFileEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ThemeItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'ThemesEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ThemesTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'ThesaurusEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'ThesaurusTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'TranslationSaveMode' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'TranslatorEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'TranslatorTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'UnitConfigDecorator' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'UserGroupsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'UserHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'UserProfileEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'UserProfileTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'UserType' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'UsersEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'UsersItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'UsersSyncronize' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'UsersSyncronizeManager' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'UsersTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'VisitsEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'VisitsList' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBList',
 			),
 		),
 		'VisitsTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'XCacheCacheHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kCacheHandler',
 			),
 		),
 		'XMLIterator' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'Iterator',
 			),
 		),
 		'_BlockTag' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'_Tag_Cache' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_Capture' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_Tag_DefineElement',
 			),
 		),
 		'_Tag_Comment' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_Compress' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_DefaultParam' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_DefineElement' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_If' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_IfDataExists' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_IfNot' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_Tag_If',
 			),
 		),
 		'_Tag_Include' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_Param' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_RenderElement' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_Tag_DefineElement',
 			),
 		),
 		'_Tag_RenderElements' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'_Tag_SetParam' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => '_BlockTag',
 			),
 		),
 		'clsCachedPermissions' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'clsRecursionStack' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'fckFCKHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kApplication' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kiCacheable',
 			),
 		),
 		'kArray' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'kiCacheable',
 			),
 		),
 		'kBase' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kBracketsHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCCDateFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kCSSDefaults' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kCSVHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCache' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kCacheHandler' => array(
 			'type' => 1,
 			'modifiers' => 1,
 		),
 		'kCacheManager' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'kiCacheable',
 			),
 		),
 		'kCaptchaHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCatDBEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBEventHandler',
 			),
 		),
 		'kCatDBItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBItem',
 			),
 		),
 		'kCatDBItemExportHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCatDBList' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBList',
 			),
 		),
 		'kCatDBTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBTagProcessor',
 			),
 		),
 		'kChangesFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kChartHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kClipboardHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kColumnPickerHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCookieHasher' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kCountHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCountryStatesHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCronField' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kCronHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCurlHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kCustomFieldFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kDBBase' => array(
 			'type' => 1,
 			'modifiers' => 1,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kDBConnection' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'IDBConnection',
 			),
 		),
 		'kDBConnectionDebug' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBConnection',
 			),
 		),
 		'kDBEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kEventHandler',
 			),
 		),
 		'kDBItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBBase',
 			),
 		),
 		'kDBList' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kDBBase',
 				1 => 'Iterator',
 				2 => 'Countable',
 			),
 		),
 		'kDBLoadBalancer' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'IDBConnection',
 			),
 		),
 		'kDBTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kTagProcessor',
 			),
 		),
 		'kDateFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kEmail' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kEmailSendingHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kEmailTemplateHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kErrorHandlerStack' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHandlerStack',
 			),
 		),
 		'kEvent' => array(
 			'type' => 1,
 			'modifiers' => 2,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kEventHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kEventManager' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'kiCacheable',
 			),
 		),
 		'kExceptionHandlerStack' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHandlerStack',
 			),
 		),
 		'kFactory' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'kiCacheable',
 			),
 		),
 		'kFactoryException' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'Exception',
 			),
 		),
 		'kFilenamesHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kFilesizeFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kHTTPQuery' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'Params',
 			),
 		),
 		'kHandlerStack' => array(
 			'type' => 1,
 			'modifiers' => 1,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kHookManager' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'kiCacheable',
 			),
 		),
 		'kInstallToolkit' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kInstallator' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kLEFTFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kLogger' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kMainTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kTagProcessor',
 			),
 		),
 		'kModulesHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kMultiLanguage' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kMultiLanguageHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kMultipleFilter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kMySQLQuery' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'Iterator',
 				1 => 'Countable',
 				2 => 'SeekableIterator',
 			),
 		),
 		'kMySQLQueryCol' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kMySQLQuery',
 			),
 		),
 		'kNavigationBar' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kNoPermissionException' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kRedirectException',
 			),
 		),
 		'kOpenerStack' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kOptionsFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kPDFElemFactory' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kPDFElement' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kPDFHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kPDFImage' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kPDFElement',
 			),
 		),
 		'kPDFLine' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kPDFElement',
 			),
 		),
 		'kPDFRenderer' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kPDFStylesheet' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kPDFTable' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kPDFElement',
 			),
 		),
 		'kPDFTableRow' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kPDFElement',
 			),
 		),
 		'kPDFTextElement' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kPDFElement',
 			),
 		),
 		'kPasswordFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kPermCacheUpdater' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kPermissionsHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kPhraseCache' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kPictureFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kUploadFormatter',
 			),
 		),
 		'kPlainUrlProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kUrlProcessor',
 			),
 		),
 		'kPriorityHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kRecursiveHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kRedirectException' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'Exception',
 			),
 		),
 		'kRequestManager' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kRewriteUrlProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kUrlProcessor',
 			),
 		),
 		'kScheduledTaskManager' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'kiCacheable',
 			),
 		),
 		'kSearchHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kSectionsHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kSerializedFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kSocket' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kSubscriptionAnalyzer' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kSubscriptionItem' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kSubscriptionManager' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kSystemConfig' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kSystemConfigException' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'Exception',
 			),
 		),
 		'kTCPDFRenderer' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kPDFRenderer',
 			),
 		),
 		'kTagProcessor' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kTempHandlerSubTable' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kTempHandlerTable',
 			),
 		),
 		'kTempHandlerTable' => array(
 			'type' => 1,
 			'modifiers' => 1,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kTempHandlerTopTable' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kTempHandlerTable',
 			),
 		),
 		'kTempTablesHandler' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kThemesHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kUnitConfig' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kUnitConfigCloner' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'kiCacheable',
 			),
 		),
 		'kUnitConfigReader' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 				1 => 'kiCacheable',
 			),
 		),
 		'kUnitFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kUpgradeHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kUploadFormatter' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kFormatter',
 			),
 		),
 		'kUploadHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kUploaderException' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'Exception',
 			),
 		),
 		'kUrlManager' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kUrlProcessor' => array(
 			'type' => 1,
 			'modifiers' => 1,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kUtil' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kValidator' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kBase',
 			),
 		),
 		'kXMLHelper' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kHelper',
 			),
 		),
 		'kXMLNode' => array(
 			'type' => 1,
 			'modifiers' => 0,
 		),
 		'kXMLNode5' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kXMLNode',
 				1 => 'IteratorAggregate',
 			),
 		),
 		'kZendPDFRenderer' => array(
 			'type' => 1,
 			'modifiers' => 0,
 			'extends' => array(
 				0 => 'kPDFRenderer',
 			),
 		),
 		'kiCacheable' => array(
 			'type' => 2,
 		),
 	),
 );
Index: branches/5.3.x/tools/debug_sample.php
===================================================================
--- branches/5.3.x/tools/debug_sample.php	(revision 16170)
+++ branches/5.3.x/tools/debug_sample.php	(revision 16171)
@@ -1,60 +1,60 @@
 <?php
 /**
 * @version	$Id$
 * @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.
 */
 
 //	define('MAINTENANCE_MODE_FRONT', MaintenanceMode::NONE); // Set to MaintenanceMode::SOFT for SOFT Maintenance mode, set to MaintenanceMode::HARD for HARD Maintenance mode (no DB load)
 //	define('MAINTENANCE_MODE_ADMIN', MaintenanceMode::NONE); // Set to MaintenanceMode::SOFT for SOFT Maintenance mode, set to MaintenanceMode::HARD for HARD Maintenance mode (no DB load)
 //	define('MAINTENANCE_MODE_IPS', '');			// Define IP addresses/hosts, which will be able to continue accessing website
 //	define('DBG_REQUEST_LOG', 1);// Log all user requests to site into "System Log"
 //	define('DBG_ZEND_PRESENT', 0);				// Set to 0 to debug debugger (because debugger automatically got disabled during zend debug sessions)
 //	define('SA_IP', '173.9.192.210'); 			// Define IP addresses, from which super admin are allowed to login
 //	define('DBG_CAPTURE_STATISTICS', 1);		// Capture performance statistics
 //	define('DBG_MAX_SQL_TIME', 2);				// Maximal allowed sql execution time in seconds, all sqls above this become slow sqls
 //	define('DBG_CURL', 1);						// Log all curl requests to CurlLog database table
 //	define('OVERRIDE_EMAIL_RECIPIENTS', '');	// Overwrites email recipients with some-email@domain-name.com or @domain-name.com
 
 	$dbg_options = Array (
 		// !!! DEBUG MODE will be off if IP does not match !!!
 		'DBG_IP'				=>	'192.168.1.1/24;192.168.2.1/24;173.9.192.210',	// !!!REQUIRED!!! Define IP addreses, which are allowed to use debugger (semicolon separated)
 		'DEBUG_MODE'			=>	1,				// Debug mode is allowed/disabled (note: set DBG_IP to use this one)
 //		'DBG_LOCAL_BASE_PATH'	=>	'w:',			// Folder name on mapped drive, where site resides
 //		'DBG_TOOLBAR_BUTTONS'	=>	1,				// Show "Show Debugger" & "Refresh Frame" buttons (on front)
 
 //		'DBG_USE_HIGHLIGHT'		=>	0,				// Use "highlight_string" php function for debugger output formatting
 //		'DBG_RAISE_ON_WARNINGS'	=>	1,				// Show debugger output in case of any non-fatal error
 		'DBG_SQL_PROFILE'		=>	defined('IS_INSTALL') && IS_INSTALL ? 0 : 1,	// Profile SQL queries
 //		'DBG_SQL_EXPLAIN'		=>	1,				// Explain every SQL query, that is retrieving data
 		'DBG_SQL_FAILURE'		=>	isset($GLOBALS['pathtoroot']) && defined('IS_INSTALL') && IS_INSTALL ? 0 : 1,	// treat sql errors as fatal errors except for installation process
 //		'DBG_SQL_SERVERINFO'	=>	1,				// Display database server info next each sql query in debugger
 
 		'DBG_SHOW_HTTPQUERY'	=>	1,				// Show http query content (parsed user submit, GPC)
 		'DBG_SHOW_SESSIONDATA'	=>	1,				// Show session data (at script finish)
 //		'DBG_SHOW_PERSISTENTDATA'	=>	1,			// Show persistent session data (at script finish)
 
 //		'DBG_FORCE_THEME'		=>	1,				// Use this theme_id instead of one in url
 		'DBG_PHRASES'			=>	1,				// Add ability to translate phrases on the fly
 //		'DBG_WINDOW_WIDTH'		=>	700,			// Set custom debugger layer width (in pixels)
 
 //		'DBG_REDIRECT'			=>	1,				// Show links with redirect url instead of performing it (useful in events debugging)
 
 //		'DBG_VALIDATE_CONFIGS'	=>	1,				// Check that config fields match ones from database
 //		'DBG_SHOW_TAGS'			=>	1,				// Show tags beeing processed
 //		'DBG_SHOW_TREE_PRIORITY'=>	1,				// Show tree node priority
 //		'DBG_SKIP_AJAX'			=>	1,				// Don't debug AJAX requests
 //		'DBG_PAYMENT_GW'		=>	1,				// All requests to payment gateways goes in TEST MODE
 //		'DBG_IMAGE_RECOVERY'	=>	1,				// Don't replace missing images with noimage.gif
 //		'DBG_SQL_MODE'			=>	'TRADITIONAL',	// Extra control over sql syntax & data from MySQL server side
 //		'DBG_RESET_ROOT'		=>	1,				// Shows "root" user password reset link on Admin Console login screen
-//		'DBG_CACHE_URLS'		=>	0,				// Cache urls, that are build by rewrite listeners
+//		'DBG_CACHE_URLS'		=>	0,				// Cache urls, that are build by routers
 //		'DBG_SHORTCUT'			=>	'F12'			// Defines debugger activation shortcut (any symbols or Ctrl/Alt/Shift are allowed, e.g. Ctrl+Alt+F12)
-	);
\ No newline at end of file
+	);