Index: branches/5.1.x/core/kernel/db/cat_event_handler.php
===================================================================
--- branches/5.1.x/core/kernel/db/cat_event_handler.php	(revision 13986)
+++ branches/5.1.x/core/kernel/db/cat_event_handler.php	(revision 13987)
@@ -1,2678 +1,2678 @@
 <?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 kCatDBEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standart permission mapping
 	 *
 	 */
 	function mapPermissions()
 	{
 		parent::mapPermissions();
 		$permissions = Array(
 			'OnSaveSettings' => Array ('self' => 'add|edit|advanced:import'),
 			'OnResetSettings' => Array ('self' => 'add|edit|advanced:import'),
 			'OnBeforeDeleteOriginal' => Array ('self' => 'edit|advanced:approve'),
 
 			'OnCopy' => Array ('self' => true),
 			'OnDownloadFile' => Array ('self' => 'view'),
 			'OnCancelAction' => Array ('self' => true),
 			'OnItemBuild' => Array ('self' => true),
 			'OnMakeVote' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Load item if id is available
 	 *
 	 * @param kEvent $event
 	 */
 	function LoadItem(&$event)
 	{
 		$object =& $event->getObject();
 		$id = $this->getPassedID($event);
 		if ($object->Load($id)) {
 			$actions =& $this->Application->recallObject('kActions');
 			$actions->Set($event->Prefix_Special.'_id', $object->GetID() );
 
 			$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 			if ($use_pending_editing && $event->Special != 'original') {
 				$this->Application->SetVar($event->Prefix.'.original_id', $object->GetDBField('OrgId'));
 			}
 		}
 		else {
 			$object->setID($id);
 		}
 	}
 
 	/**
 	 * Checks permissions of user
 	 *
 	 * @param kEvent $event
 	 */
 	function CheckPermission(&$event)
 	{
 		if (!$this->Application->isAdmin) {
 			if ($event->Name == 'OnSetSortingDirect') {
 				// allow sorting on front event without view permission
 				return true;
 			}
 		}
 
 		if ($event->Name == 'OnExport') {
 			// save category_id before doing export
 			$this->Application->LinkVar('m_cat_id');
 		}
 
 		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]['CategoryId'], $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['CategoryId'], $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);
 		}
 
 		$export_events = Array ('OnSaveSettings', 'OnResetSettings', 'OnExportBegin');
 		if (in_array($event->Name, $export_events)) {
 			// when import settings before selecting target import category
 			return $this->Application->CheckPermission('in-portal:main_import.view');
 		}
 
 		if ($event->Name == 'OnProcessSelected') {
 			if ($this->Application->RecallVar('dst_field') == 'ImportCategory') {
 				// when selecting target import category
 				return $this->Application->CheckPermission('in-portal:main_import.view');
 			}
 		}
 
 		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)
 	{
 		$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		// when saving data from temp table to live table check by data from temp table
 		$item_ids = $this->_getPermissionCheckIDs($event);
 		$items = $perm_helper->GetCategoryItemData($event->Prefix, $item_ids, $event->Name == 'OnSave');
 
 		if (!$items) {
 			// when item not present in temp table, then permission is not checked, because there are no data in db to check
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			list ($id, $fields_hash) = each($items_info);
 
 			if (array_key_exists('CategoryId', $fields_hash)) {
 				$item_category = $fields_hash['CategoryId'];
 			}
 			else {
 				$item_category = $this->Application->GetVar('m_cat_id');
 			}
 
 			$items[$id] = Array (
 				'CreatedById' => $this->Application->RecallVar('use_id'),
 				'CategoryId' => $item_category,
 			);
 		}
 
 		return $items;
 	}
 
 	/**
 	 * 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');
 		$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');
 		$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
 		$this->clearSelectedIDs($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;
 	}
 
 	/**
 	 * Performs category item paste
 	 *
 	 * @param kEvent $event
 	 */
 	function OnPaste(&$event)
 	{
 		if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event)) {
 			$event->status = erFAIL;
 			return;
 		}
 
 		$clipboard_data = $event->getEventParam('clipboard_data');
 
 		if (!$clipboard_data['cut'] && !$clipboard_data['copy']) {
 			return false;
 		}
 
 		if ($clipboard_data['copy']) {
 			$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp kTempTablesHandler */
 
 			$this->Application->SetVar('ResetCatBeforeClone', 1); // used in "kCatDBEventHandler::OnBeforeClone"
 			$temp->CloneItems($event->Prefix, $event->Special, $clipboard_data['copy']);
 		}
 
 		if ($clipboard_data['cut']) {
 			$object =& $this->Application->recallObject($event->getPrefixSpecial().'.item', $event->Prefix, Array('skip_autoload' => true));
 
 			foreach ($clipboard_data['cut'] as $id) {
 				$object->Load($id);
 				$object->MoveToCat();
 			}
 		}
 	}
 
 	/**
 	 * Deletes all selected items.
 	 * Automatically recurse 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
 	 */
 	function OnMassDelete(&$event)
 	{
 		if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 			$event->status = erFAIL;
 			return;
 		}
 
 		$event->status=erSUCCESS;
 
 		$ids = $this->StoreSelectedIDs($event);
 
 		$to_delete = array();
 		if ($recycle_bin = $this->Application->ConfigValue('RecycleBinFolder')) {
 			$rb =& $this->Application->recallObject('c.recycle', null, array('skip_autoload' => true));
 			$rb->Load($recycle_bin);
 			$object =& $this->Application->recallObject($event->Prefix.'.recycleitem', null, Array ('skip_autoload' => true));
 			foreach ($ids as $id) {
 				$object->Load($id);
 				if (preg_match('/^'.preg_quote($rb->GetDBField('ParentPath'),'/').'/', $object->GetDBField('ParentPath'))) {
 					$to_delete[] = $id;
 					continue;
 				}
 				$object->MoveToCat($recycle_bin);
 			}
 			$ids = $to_delete;
 		}
 
 		$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 
 		$event->setEventParam('ids', $ids);
 		$this->customProcessing($event, 'before');
 		$ids = $event->getEventParam('ids');
 
 		if($ids)
 		{
 			$temp->DeleteItems($event->Prefix, $event->Special, $ids);
 		}
 		$this->clearSelectedIDs($event);
 	}
 
 
 	/**
 	 * Return type clauses for list bulding on front
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function getTypeClauses(&$event)
 	{
 		$types = $event->getEventParam('types');
 		$types = $types ? explode(',', $types) : Array ();
 
 		$except_types = $event->getEventParam('except');
 		$except_types = $except_types ? explode(',', $except_types) : Array ();
 
 		$type_clauses = Array();
 
 		$user_id = $this->Application->RecallVar('user_id');
 		$owner_field = $this->getOwnerField($event->Prefix);
 
 		$type_clauses['my_items']['include'] = '%1$s.'.$owner_field.' = '.$user_id;
 		$type_clauses['my_items']['except'] = '%1$s.'.$owner_field.' <> '.$user_id;
 		$type_clauses['my_items']['having_filter'] = false;
 
 		$type_clauses['pick']['include'] = '%1$s.EditorsPick = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
 		$type_clauses['pick']['except'] = '%1$s.EditorsPick! = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
 		$type_clauses['pick']['having_filter'] = false;
 
 		$type_clauses['hot']['include'] = '`IsHot` = 1 AND PrimaryCat = 1';
 		$type_clauses['hot']['except'] = '`IsHot`! = 1 AND PrimaryCat = 1';
 		$type_clauses['hot']['having_filter'] = true;
 
 		$type_clauses['pop']['include'] = '`IsPop` = 1 AND PrimaryCat = 1';
 		$type_clauses['pop']['except'] = '`IsPop`! = 1 AND PrimaryCat = 1';
 		$type_clauses['pop']['having_filter'] = true;
 
 		$type_clauses['new']['include'] = '`IsNew` = 1 AND PrimaryCat = 1';
 		$type_clauses['new']['except'] = '`IsNew`! = 1 AND PrimaryCat = 1';
 		$type_clauses['new']['having_filter'] = true;
 
 		$type_clauses['displayed']['include'] = '';
 		$displayed = $this->Application->GetVar($event->Prefix.'_displayed_ids');
 		if ($displayed) {
 			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 			$type_clauses['displayed']['except'] = '%1$s.'.$id_field.' NOT IN ('.$displayed.')';
 		}
 		else {
 			$type_clauses['displayed']['except'] = '';
 		}
 		$type_clauses['displayed']['having_filter'] = false;
 
 		if (in_array('search', $types) || in_array('search', $except_types)) {
 			$event_mapping = Array (
 				'simple'		=>	'OnSimpleSearch',
 				'subsearch'		=>	'OnSubSearch',
 				'advanced'		=>	'OnAdvancedSearch'
 			);
 
 			$type = $this->Application->GetVar('search_type', 'simple');
 
 			if ($keywords = $event->getEventParam('keyword_string')) {
 				// processing keyword_string param of ListProducts tag
 				$this->Application->SetVar('keywords', $keywords);
 				$type = 'simple';
 			}
 
 			$search_event = $event_mapping[$type];
 			$this->$search_event($event);
 
 			$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 			$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 			if ($this->Conn->Query($sql)) {
 				$search_res_ids = $this->Conn->GetCol('SELECT ResourceId FROM '.$search_table);
 			}
 
 			if (isset($search_res_ids) && $search_res_ids) {
 				$type_clauses['search']['include'] = '%1$s.ResourceId IN ('.implode(',', $search_res_ids).') AND PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')';
 				$type_clauses['search']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $search_res_ids).') AND PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')';
 			}
 			else {
 				$type_clauses['search']['include'] = '0';
 				$type_clauses['search']['except'] = '1';
 			}
 
 			$type_clauses['search']['having_filter'] = false;
 		}
 
 		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->getUnitOption('rel', 'TableName');
 			$item_type = (int)$this->Application->getUnitOption($event->Prefix, 'ItemType');
 
 			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));
 			$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).') AND PrimaryCat = 1';
 				$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
 			}
 			else {
 				$type_clauses['related']['include'] = '0';
 				$type_clauses['related']['except'] = '1';
 			}
 			$type_clauses['related']['having_filter'] = false;
 		}
 
 		if (in_array('favorites', $types) || in_array('favorites', $except_types)) {
 			$sql = 'SELECT ResourceId
 					FROM '.$this->Application->getUnitOption('fav', 'TableName').'
 					WHERE PortalUserId = '.$this->Application->RecallVar('user_id');
 			$favorite_ids = $this->Conn->GetCol($sql);
 			if ($favorite_ids) {
 				$type_clauses['favorites']['include'] = '%1$s.ResourceId IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
 				$type_clauses['favorites']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
 			}
 			else {
 				$type_clauses['favorites']['include'] = 0;
 				$type_clauses['favorites']['except'] = 1;
 			}
 			$type_clauses['favorites']['having_filter'] = false;
 		}
 
 		return $type_clauses;
 	}
 
 	/**
 	 * Returns SQL clause, that will help to select only data from specified category & it's children
 	 *
 	 * @param int $category_id
 	 * @return string
 	 */
 	function getCategoryLimitClause($category_id)
 	{
 		if (!$category_id) {
 			return false;
 		}
 
 		$tree_indexes = $this->Application->getTreeIndex($category_id);
 		if (!$tree_indexes) {
 			// id of non-existing category was given
 			return 'FALSE';
 		}
 
 		return TABLE_PREFIX.'Category.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight'];
 	}
 
 	/**
 	 * Apply filters to list
 	 *
 	 * @param kEvent $event
 	 */
 	function SetCustomQuery(&$event)
 	{
 		parent::SetCustomQuery($event);
 
 		$object =& $event->getObject();
 		/* @var $object kDBList */
 
 		// add category filter if needed
 		if ($event->Special != 'showall' && $event->Special != 'user') {
 			if ($event->getEventParam('parent_cat_id') !== false) {
 				$parent_cat_id = $event->getEventParam('parent_cat_id');
 			}
 			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->findModule('Name', 'Core', 'RootCat');
+				$parent_cat_id = $this->Application->getBaseCategory();
 			}
 
 			if ((string)$parent_cat_id != 'any') {
 				if ($event->getEventParam('recursive')) {
 					$filter_clause = $this->getCategoryLimitClause($parent_cat_id);
 					if ($filter_clause !== false) {
 						$object->addFilter('category_filter', $filter_clause);
 					}
 
 					$object->addFilter('primary_filter', 'PrimaryCat = 1');
 				}
 				else {
 					$object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.CategoryId = '.$parent_cat_id );
 				}
 			}
 			else {
 				$object->addFilter('primary_filter', 'PrimaryCat = 1');
 			}
 		}
 		else {
 			$object->addFilter('primary_filter', 'PrimaryCat = 1');
 
 			// if using recycle bin don't show items from there
 			$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 			if ($recycle_bin) {
 				$object->addFilter('recyclebin_filter', TABLE_PREFIX.'CategoryItems.CategoryId <> '.$recycle_bin);
 			}
 		}
 
 		if ($event->Special == 'user') {
 			$editable_user = $this->Application->GetVar('u_id');
 			$object->addFilter('owner_filter', '%1$s.'.$this->getOwnerField($event->Prefix).' = '.$editable_user);
 		}
 
 		// add permission filter
 		if ($this->Application->RecallVar('user_id') == USER_ROOT) {
 			// for "root" CATEGORY.VIEW permission is checked for items lists too
 			$view_perm = 1;
 		}
 		else {
 			// for any real user itemlist view permission is checked instead of CATEGORY.VIEW
 			$count_helper =& $this->Application->recallObject('CountHelper');
 			/* @var $count_helper kCountHelper */
 
 			list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($event->Prefix, 'perm');
 			$object->addFilter('perm_filter2', $view_filter);
 		}
 
 		$object->addFilter('perm_filter', 'perm.PermId = '.$view_perm);
 
 
 		$types = $event->getEventParam('types');
 		$this->applyItemStatusFilter($object, $types);
 
 		$except_types = $event->getEventParam('except');
 		$type_clauses = $this->getTypeClauses($event);
 
 		// convert prepared type clauses into list filters
 		$includes_or_filter =& $this->Application->makeClass('kMultipleFilter');
 		$includes_or_filter->setType(FLT_TYPE_OR);
 
 		$excepts_and_filter =& $this->Application->makeClass('kMultipleFilter');
 		$excepts_and_filter->setType(FLT_TYPE_AND);
 
 		$includes_or_filter_h =& $this->Application->makeClass('kMultipleFilter');
 		$includes_or_filter_h->setType(FLT_TYPE_OR);
 
 		$excepts_and_filter_h =& $this->Application->makeClass('kMultipleFilter');
 		$excepts_and_filter_h->setType(FLT_TYPE_AND);
 
 		if ($types) {
 			$types_array = explode(',', $types);
 			for ($i = 0; $i < sizeof($types_array); $i++) {
 				$type = trim($types_array[$i]);
 				if (isset($type_clauses[$type])) {
 					if ($type_clauses[$type]['having_filter']) {
 						$includes_or_filter_h->removeFilter('filter_'.$type);
 						$includes_or_filter_h->addFilter('filter_'.$type, $type_clauses[$type]['include']);
 					}else {
 						$includes_or_filter->removeFilter('filter_'.$type);
 						$includes_or_filter->addFilter('filter_'.$type, $type_clauses[$type]['include']);
 					}
 				}
 			}
 		}
 
 		if ($except_types) {
 			$except_types_array = explode(',', $except_types);
 			for ($i = 0; $i < sizeof($except_types_array); $i++) {
 				$type = trim($except_types_array[$i]);
 				if (isset($type_clauses[$type])) {
 					if ($type_clauses[$type]['having_filter']) {
 						$excepts_and_filter_h->removeFilter('filter_'.$type);
 						$excepts_and_filter_h->addFilter('filter_'.$type, $type_clauses[$type]['except']);
 					}else {
 						$excepts_and_filter->removeFilter('filter_'.$type);
 						$excepts_and_filter->addFilter('filter_'.$type, $type_clauses[$type]['except']);
 					}
 				}
 			}
 		}
 
 		/*if (!$this->Application->isAdminUser) {
 			$object->addFilter('expire_filter', '%1$s.Expire IS NULL OR %1$s.Expire > UNIX_TIMESTAMP()');
 		}*/
 
 		/*$list_type = $event->getEventParam('ListType');
 		switch($list_type)
 		{
 			case 'favorites':
 				$fav_table = $this->Application->getUnitOption('fav','TableName');
 				$user_id =& $this->Application->RecallVar('user_id');
 
 				$sql = 'SELECT DISTINCT f.ResourceId
 						FROM '.$fav_table.' f
 						LEFT JOIN '.$object->TableName.' p ON p.ResourceId = f.ResourceId
 						WHERE f.PortalUserId = '.$user_id;
 				$ids = $this->Conn->GetCol($sql);
 				if(!$ids) $ids = Array(-1);
 				$object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.PrimaryCat = 1');
 				$object->addFilter('favorites_filter', '%1$s.`ResourceId` IN ('.implode(',',$ids).')');
 				break;
 			case 'search':
 				$search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 				$sql = '	SELECT DISTINCT ResourceId
 							FROM '.$search_results_table.'
 							WHERE ItemType=11';
 				$ids = $this->Conn->GetCol($sql);
 				if(!$ids) $ids = Array(-1);
 				$object->addFilter('search_filter', '%1$s.`ResourceId` IN ('.implode(',',$ids).')');
 				break;
 		}		*/
 
 		$object->addFilter('includes_filter', $includes_or_filter);
 		$object->addFilter('excepts_filter', $excepts_and_filter);
 
 		$object->addFilter('includes_filter_h', $includes_or_filter_h, HAVING_FILTER);
 		$object->addFilter('excepts_filter_h', $excepts_and_filter_h, HAVING_FILTER);
 	}
 
 	/**
 	 * Adds filter that filters out items with non-required statuses
 	 *
 	 * @param kDBList $object
 	 * @param string $types
 	 */
 	function applyItemStatusFilter(&$object, $types)
 	{
 		// Link1 (before modifications) [Status = 1, OrgId = NULL], Link2 (after modifications) [Status = -2, OrgId = Link1_ID]
 		$pending_editing = $this->Application->getUnitOption($object->Prefix, 'UsePendingEditing');
 
 		if (!$this->Application->isAdminUser) {
 			$types = explode(',', $types);
 			if (in_array('my_items', $types)) {
 				$allow_statuses = Array (STATUS_ACTIVE, STATUS_PENDING, STATUS_PENDING_EDITING);
 				$object->addFilter('status_filter', '%1$s.Status IN ('.implode(',', $allow_statuses).')');
 
 				if ($pending_editing) {
 					$user_id = $this->Application->RecallVar('user_id');
 					$this->applyPendingEditingFilter($object, $user_id);
 				}
 			}
 			else {
 				$object->addFilter('status_filter', '(%1$s.Status = ' . STATUS_ACTIVE . ') AND (' . TABLE_PREFIX . 'Category.Status = ' . STATUS_ACTIVE . ')');
 				if ($pending_editing) {
 					// if category item uses pending editing abilities, then in no cases show pending copies on front
 					$object->addFilter('original_filter', '%1$s.OrgId = 0 OR %1$s.OrgId IS NULL');
 				}
 			}
 		}
 		else {
 			if ($pending_editing) {
 				$this->applyPendingEditingFilter($object);
 			}
 		}
 	}
 
 	/**
 	 * Adds filter, that removes live items if they have pending editing copies
 	 *
 	 * @param kDBList $object
 	 * @param int $user_id
 	 */
 	function applyPendingEditingFilter(&$object, $user_id = null)
 	{
 		$sql = 'SELECT OrgId
 				FROM '.$object->TableName.'
 				WHERE Status = '.STATUS_PENDING_EDITING.' AND OrgId IS NOT NULL';
 
 		if (isset($user_id)) {
 			$owner_field = $this->getOwnerField($object->Prefix);
 			$sql .= ' AND '.$owner_field.' = '.$user_id;
 		}
 
 		$pending_ids = $this->Conn->GetCol($sql);
 		if ($pending_ids) {
 			$object->addFilter('no_original_filter', '%1$s.'.$object->IDField.' NOT IN ('.implode(',', $pending_ids).')');
 		}
 	}
 
 	/**
 	 * Adds calculates fields for item statuses
 	 *
 	 * @param kCatDBItem $object
 	 * @param kEvent $event
 	 */
 	function prepareObject(&$object, &$event)
 	{
 		$this->prepareItemStatuses($event);
 
 		$object->addCalculatedField('CachedNavbar', 'l'.$this->Application->GetVar('m_lang').'_CachedNavbar');
 
 		if ($event->Special == 'export' || $event->Special == 'import') {
 			$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 			$export_helper->prepareExportColumns($event);
 		}
 	}
 
 	/**
 	 * Creates calculated fields for all item statuses based on config settings
 	 *
 	 * @param kEvent $event
 	 */
 	function prepareItemStatuses(&$event)
 	{
 		$object =& $event->getObject( Array('skip_autoload' => true) );
 
 		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
 		if (!$property_map) {
 			return ;
 		}
 
 		// new items
 		$object->addCalculatedField('IsNew', '	IF(%1$s.NewItem = 2,
 													IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '.
 														$this->Application->ConfigValue($property_map['NewDays']).
 														'*3600*24), 1, 0),
 													%1$s.NewItem
 												)');
 
 		// hot items (cache updated every hour)
 		if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$serial_name = $this->Application->incrementCacheSerial($event->Prefix, null, false);
 			$hot_limit = $this->Application->getCache($property_map['HotLimit'] . '[%' . $serial_name . '%]');
 		}
 		else {
 			$hot_limit = $this->Application->getDBCache($property_map['HotLimit']);
 		}
 
 		if ($hot_limit === false) {
 			$hot_limit = $this->CalculateHotLimit($event);
 		}
 
 		$object->addCalculatedField('IsHot', '	IF(%1$s.HotItem = 2,
 													IF(%1$s.'.$property_map['ClickField'].' >= '.$hot_limit.', 1, 0),
 													%1$s.HotItem
 												)');
 
 		// popular items
 		$object->addCalculatedField('IsPop', '	IF(%1$s.PopItem = 2,
 													IF(%1$s.CachedVotesQty >= '.
 														$this->Application->ConfigValue($property_map['MinPopVotes']).
 														' AND %1$s.CachedRating >= '.
 														$this->Application->ConfigValue($property_map['MinPopRating']).
 														', 1, 0),
 													%1$s.PopItem)');
 
 	}
 
 	function CalculateHotLimit(&$event)
 	{
 		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
 
 		if (!$property_map) {
 			return;
 		}
 
 		$click_field = $property_map['ClickField'];
 
 		$last_hot = $this->Application->ConfigValue($property_map['MaxHotNumber']) - 1;
 		$sql = 'SELECT '.$click_field.' FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
 				ORDER BY '.$click_field.' DESC
 				LIMIT '.$last_hot.', 1';
 		$res = $this->Conn->GetCol($sql);
 		$hot_limit = (double)array_shift($res);
 
 		if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$serial_name = $this->Application->incrementCacheSerial($event->Prefix, null, false);
 			$this->Application->setCache($property_map['HotLimit'] . '[%' . $serial_name . '%]', $hot_limit);
 		}
 		else {
 			$this->Application->setDBCache($property_map['HotLimit'], $hot_limit, 3600);
 		}
 
 		return $hot_limit;
 	}
 
 	/**
 	 * Moves item to preferred category, updates item hits
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeItemUpdate(&$event)
 	{
 		parent::OnBeforeItemUpdate($event);
 
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		// update hits field
 		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
 		if ($property_map) {
 			$click_field = $property_map['ClickField'];
 
 			if( $this->Application->isAdminUser && ($this->Application->GetVar($click_field.'_original') !== false) &&
 				floor($this->Application->GetVar($click_field.'_original')) != $object->GetDBField($click_field) )
 			{
 				$sql = 'SELECT MAX('.$click_field.') FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
 						WHERE FLOOR('.$click_field.') = '.$object->GetDBField($click_field);
 				$hits = ( $res = $this->Conn->GetOne($sql) ) ? $res + 0.000001 : $object->GetDBField($click_field);
 				$object->SetDBField($click_field, $hits);
 			}
 		}
 
 		// change category
 		$target_category = $object->GetDBField('CategoryId');
 		if ($object->GetOriginalField('CategoryId') != $target_category) {
 			$object->MoveToCat($target_category);
 		}
 	}
 
 	/**
 	 * Load price from temp table if product mode is temp table
 	 *
 	 * @param kEvent $event
 	 */
 	function OnAfterItemLoad(&$event)
 	{
 		$special = substr($event->Special, -6);
 
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		if ($special == 'import' || $special == 'export') {
 			$image_data = $object->getPrimaryImageData();
 
 			if ($image_data) {
 				$thumbnail_image = $image_data[$image_data['LocalThumb'] ? 'ThumbPath' : 'ThumbUrl'];
 				if ($image_data['SameImages']) {
 					$full_image = '';
 				}
 				else {
 					$full_image = $image_data[$image_data['LocalImage'] ? 'LocalPath' : 'Url'];
 				}
 				$object->SetDBField('ThumbnailImage', $thumbnail_image);
 				$object->SetDBField('FullImage', $full_image);
 				$object->SetDBField('ImageAlt', $image_data['AltName']);
 			}
 		}
 
 		// substituiting pending status value for pending editing
 		if ($object->HasField('OrgId') && $object->GetDBField('OrgId') > 0 && $object->GetDBField('Status') == -2) {
 			$options = $object->Fields['Status']['options'];
 			foreach ($options as $key => $val) {
 				if ($key == 2) $key = -2;
 				$new_options[$key] = $val;
 			}
 			$object->Fields['Status']['options'] = $new_options;
 		}
 
 		// linking existing images for item with virtual fields
 		$image_helper =& $this->Application->recallObject('ImageHelper');
 		/* @var $image_helper ImageHelper */
 
 		$image_helper->LoadItemImages($object);
 
 		// linking existing files for item with virtual fields
 		$file_helper =& $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$file_helper->LoadItemFiles($object);
 
 		// set item's additional categories to virtual field (used in editing)
 		$item_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
 		$object->SetDBField('MoreCategories', $item_categories ? '|'.implode('|', $item_categories).'|' : '');
 	}
 
 	function OnAfterItemUpdate(&$event)
 	{
 		$this->CalculateHotLimit($event);
 
 		if ( substr($event->Special, -6) == 'import') {
 			$this->setCustomExportColumns($event);
 		}
 
 		if (!$this->Application->isAdminUser) {
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$image_helper =& $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			// process image upload in virtual fields
 			$image_helper->SaveItemImages($object);
 
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			// process file upload in virtual fields
 			$file_helper->SaveItemFiles($object);
 
 			if ($event->Special != '-item') {
 				// don't touch categories during cloning
 				$this->processAdditionalCategories($object, 'update');
 			}
 		}
 	}
 
 	/**
 	 * sets values for import process
 	 *
 	 * @param kEvent $event
 	 */
 	function OnAfterItemCreate(&$event)
 	{
 		if ( substr($event->Special, -6) == 'import') {
 			$this->setCustomExportColumns($event);
 		}
 
 		if (!$this->Application->isAdminUser) {
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$image_helper =& $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			// process image upload in virtual fields
 			$image_helper->SaveItemImages($object);
 
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			// process file upload in virtual fields
 			$file_helper->SaveItemFiles($object);
 
 			if ($event->Special != '-item') {
 				// don't touch categories during cloning
 				$this->processAdditionalCategories($object, 'create');
 			}
 		}
 	}
 
 	/**
 	 * 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.'SearchLog
 					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.'SearchLog');
 	        }
 
 	        $this->Application->SetVar('search_logged', 1);
 		}
 	}
 
 	/**
 	 * Makes simple search for category items
 	 * 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 = unhtmlentities( trim($this->Application->GetVar('keywords')) );
 
 		$query_object =& $this->Application->recallObject('HTTPQuery');
 		$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 		if(!isset($query_object->Get['keywords']) &&
 			!isset($query_object->Post['keywords']) &&
 			$this->Conn->Query($sql))
 		{
 			return; // used when navigating by pages or changing sorting in search results
 		}
 		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
 
 		$event->setPseudoClass('_List');
 
 		$object =& $event->getObject();
 		/* @var $object kDBList */
 
 		$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
 		$lang = $this->Application->GetVar('m_lang');
 		$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
 		$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
 
 		$sql = 'SELECT *
 				FROM ' . $this->Application->getUnitOption('confs', 'TableName') . '
 				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 = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 		if ($custom_fields) {
 			$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
 			$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) {
 			$options = $object->getFieldOptions($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 (getArrayValue($options, '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
 			if ($foreign_field = $search_config[$field]['ForeignField']) {
 				$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) . ')';
 
 		$search_scope = $this->Application->GetVar('search_scope');
 		if ($search_scope == 'category') {
 			$category_id = $this->Application->GetVar('m_cat_id');
 			$category_filter = $this->getCategoryLimitClause($category_id);
 			if ($category_filter !== false) {
 				$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON '.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$items_table.'.ResourceId';
 				$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'Category ON '.TABLE_PREFIX.'Category.CategoryId = '.TABLE_PREFIX.'CategoryItems.CategoryId';
 
 				$where_clause = '('.$this->getCategoryLimitClause($category_id).') AND '.$where_clause;
 			}
 		}
 
 		$where_clause = $where_clause.' AND '.$items_table.'.Status=1';
 
 		if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
 			if ($event->MasterEvent->getEventParam('ResultIds')) {
 				$where_clause .= ' AND '.$items_table.'.ResourceId IN ('.implode(',', $event->MasterEvent->getEventParam('ResultIds')).')';
 			}
 		}
 
 		// 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'];
 			$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 = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
 		$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 && isset($object->Fields['Hits'])) {
 			$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 		}
 		if ($rel_rating && isset($object->Fields['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 = $this->Application->getUnitOption($event->Prefix.'.EditorsPick', 'Fields') ? $items_table.'.EditorsPick' : '0';
 
 
 		$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
 							'.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' AS ItemId,
 							'.$items_table.'.ResourceId,
 							'.$this->Application->getUnitOption($event->Prefix, 'ItemType').' AS ItemType,
 							 '.$edpick_clause.' AS EdPick
 					FROM '.$object->TableName.'
 					'.implode(' ', $join_clauses).'
 					WHERE '.$where_clause.'
 					GROUP BY '.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField');
 
 		$res = $this->Conn->Query($sql);
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSubSearch(&$event)
 	{
 		$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 		$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 		if($this->Conn->Query($sql))
 		{
 			$sql = 'SELECT DISTINCT ResourceId FROM '.$search_table;
 			$ids = $this->Conn->GetCol($sql);
 		}
 		$event->setEventParam('ResultIds', $ids);
 		$event->CallSubEvent('OnSimpleSearch');
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 * @todo Change all hardcoded Products table & In-Commerce module usage to dynamic usage from item config !!!
 	 */
 	function OnAdvancedSearch(&$event)
 	{
 		$query_object =& $this->Application->recallObject('HTTPQuery');
 		if(!isset($query_object->Post['andor']))
 		{
 			return; // used when navigating by pages or changing sorting in search results
 		}
 
 		$this->Application->RemoveVar('keywords');
 		$this->Application->RemoveVar('Search_Keywords');
 
 		$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
 
 		$sql = 'SELECT *
 				FROM '.$this->Application->getUnitOption('confs', 'TableName').'
 				WHERE (ModuleName = '.$this->Conn->qstr($module_name).') AND (AdvancedSearch = 1)';
 		$search_config = $this->Conn->Query($sql);
 
 		$lang = $this->Application->GetVar('m_lang');
 		$object =& $event->getObject();
 		$object->SetPage(1);
 
 		$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
 
 		$search_keywords = $this->Application->GetVar('value'); // will not be changed
 
 		$keywords = $this->Application->GetVar('value'); // will be changed down there
 		$verbs = $this->Application->GetVar('verb');
 		$glues = $this->Application->GetVar('andor');
 
 		$and_conditions = Array();
 		$or_conditions = Array();
 		$and_having_conditions = Array();
 		$or_having_conditions = Array();
 		$join_clauses = Array();
 		$highlight_keywords = Array();
 		$relevance_parts = Array();
 
 		$alias_counter = 0;
 
 		$custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 		if ($custom_fields) {
 			$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
 			$join_clauses[] = '	LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
 		}
 
 		$search_log = '';
 		$weight_sum = 0;
 
 		// processing fields and preparing conditions
 		foreach ($search_config as $record) {
 			$field = $record['FieldName'];
 			$join_clause = '';
 			$condition_mode = 'WHERE';
 
 			// field processing
 
 			$options = $object->getFieldOptions($field);
 			$local_table = TABLE_PREFIX.$record['TableName'];
 			$weight_sum += $record['Priority']; // counting weight sum; used when making relevance clause
 
 			// processing multilingual fields
 			if (getArrayValue($options, 'formatter') == 'kMultiLanguage') {
 				$field_name = 'l'.$lang.'_'.$field;
 			}
 			else {
 				$field_name = $field;
 			}
 
 			// processing fields from other tables
 			if ($foreign_field = $record['ForeignField']) {
 				$exploded = explode(':', $foreign_field, 2);
 				if($exploded[0] == 'CALC')
 				{
 					$user_groups = $this->Application->RecallVar('UserGroups');
 					$field_name = str_replace('{PREFIX}', TABLE_PREFIX, $exploded[1]);
 					$join_clause = str_replace('{PREFIX}', TABLE_PREFIX, $record['JoinClause']);
 					$join_clause = str_replace('{USER_GROUPS}', $user_groups, $join_clause);
 					$join_clause = ' LEFT JOIN '.$join_clause;
 
 					$condition_mode = 'HAVING';
 				}
 				else {
 					$exploded = explode('.', $foreign_field);
 					$foreign_table = TABLE_PREFIX.$exploded[0];
 
 					if($record['CustomFieldId']) {
 						$exploded[1] = 'l'.$lang.'_'.$exploded[1];
 					}
 
 					$alias_counter++;
 					$alias = 't'.$alias_counter;
 
 					$field_name = $alias.'.'.$exploded[1];
 					$join_clause = str_replace('{ForeignTable}', $alias, $record['JoinClause']);
 					$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 					if($record['CustomFieldId'])
 					{
 						$join_clause .= ' AND '.$alias.'.CustomFieldId='.$record['CustomFieldId'];
 					}
 					$join_clause = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 										ON '.$join_clause;
 				}
 			}
 			else
 			{
 				// processing fields from local table
 				if ($record['CustomFieldId']) {
 					$local_table = 'custom_data';
 					$field_name = 'l'.$lang.'_cust_'.array_search($field_name, $custom_fields);
 				}
 
 				$field_name = $local_table.'.'.$field_name;
 			}
 
 			$condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
 			if ($record['CustomFieldId'] && strlen($condition)) {
 				// search in primary value of custom field + value in current language
 				$field_name = $local_table.'.'.'l'.$this->Application->GetDefaultLanguageId().'_cust_'.array_search($field, $custom_fields);
 				$primary_condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
 				$condition = '('.$condition.' OR '.$primary_condition.')';
 			}
 
 			if ($condition) {
 				if ($join_clause) {
 					$join_clauses[] = $join_clause;
 				}
 
 				$relevance_parts[] = 'IF('.$condition.', '.$record['Priority'].', 0)';
 				if ($glues[$field] == 1) { // and
 					if ($condition_mode == 'WHERE') {
 						$and_conditions[] = $condition;
 					}
 					else {
 						$and_having_conditions[] = $condition;
 					}
 				}
 				else { // or
 					if ($condition_mode == 'WHERE') {
 						$or_conditions[] = $condition;
 					}
 					else {
 						$or_having_conditions[] = $condition;
 					}
 				}
 
 				// create search log record
 				$search_log_data = Array('search_config' => $record, 'verb' => getArrayValue($verbs, $field), 'value' => ($record['FieldType'] == 'range') ? $search_keywords[$field.'_from'].'|'.$search_keywords[$field.'_to'] : $search_keywords[$field]);
 				$search_log[] = $this->Application->Phrase('la_Field').' "'.$this->getHuman('Field', $search_log_data).'" '.$this->getHuman('Verb', $search_log_data).' '.$this->Application->Phrase('la_Value').' '.$this->getHuman('Value', $search_log_data).' '.$this->Application->Phrase($glues[$field] == 1 ? 'lu_And' : 'lu_Or');
 			}
 		}
 
 		if ($search_log) {
 			$search_log = implode('<br />', $search_log);
 	    	$search_log = preg_replace('/(.*) '.preg_quote($this->Application->Phrase('lu_and'), '/').'|'.preg_quote($this->Application->Phrase('lu_or'), '/').'$/is', '\\1', $search_log);
 	    	$this->saveToSearchLog($search_log, 1); // advanced search
 		}
 
 		$this->Application->StoreVar('highlight_keywords', serialize($highlight_keywords));
 
 		// making relevance clause
 		if($relevance_parts)
 		{
 			$conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
 			$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(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords;
 			$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 			$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 		}
 		else
 		{
 			$relevance_clause = '0';
 		}
 
 		// building having clause
 		if($or_having_conditions)
 		{
 			$and_having_conditions[] = '('.implode(' OR ', $or_having_conditions).')';
 		}
 		$having_clause = implode(' AND ', $and_having_conditions);
 		$having_clause = $having_clause ? ' HAVING '.$having_clause : '';
 
 		// building where clause
 		if($or_conditions)
 		{
 			$and_conditions[] = '('.implode(' OR ', $or_conditions).')';
 		}
 //		$and_conditions[] = $items_table.'.Status = 1';
 		$where_clause = implode(' AND ', $and_conditions);
 		if(!$where_clause)
 		{
 			if($having_clause)
 			{
 				$where_clause = '1';
 			}
 			else
 			{
 				$where_clause = '0';
 				$this->Application->SetVar('adv_search_error', 1);
 			}
 		}
 		$where_clause .= ' AND '.$items_table.'.Status = 1';
 
 		// building final search query
 		$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 
 		$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
 
 		$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 		$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
 		$pick_field = isset($fields['EditorsPick']) ? $items_table.'.EditorsPick' : '0';
 
 		$sql = '	CREATE TABLE '.$search_table.'
 					SELECT 	'.$relevance_clause.' AS Relevance,
 							'.$items_table.'.'.$id_field.' AS ItemId,
 							'.$items_table.'.ResourceId AS ResourceId,
 							11 AS ItemType,
 							'.$pick_field.' AS EdPick
 					FROM '.$items_table.'
 					'.implode(' ', $join_clauses).'
 					WHERE '.$where_clause.'
 					GROUP BY '.$items_table.'.'.$id_field.
 					$having_clause;
 
 		$res = $this->Conn->Query($sql);
 	}
 
 	function getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, &$highlight_keywords)
 	{
 		$field = $record['FieldName'];
 
 		$condition_patterns = Array (
 			'any'			=> '%s LIKE %s',
 			'contains'		=> '%s LIKE %s',
 			'notcontains'	=> '(NOT (%1$s LIKE %2$s) OR %1$s IS NULL)',
 			'is'			=> '%s = %s',
 			'isnot'			=> '(%1$s != %2$s OR %1$s IS NULL)'
 		);
 
 		$condition = '';
 		switch ($record['FieldType']) {
 			case 'select':
 				$keywords[$field] = unhtmlentities( $keywords[$field] );
 				if ($keywords[$field]) {
 					$condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] ));
 				}
 				break;
 
 			case 'multiselect':
 				$keywords[$field] = unhtmlentities( $keywords[$field] );
 				if ($keywords[$field]) {
 					$condition = Array ();
 					$values = explode('|', substr($keywords[$field], 1, -1));
 					foreach ($values as $selected_value) {
 						$condition[] = sprintf($condition_patterns['contains'], $field_name, $this->Conn->qstr('%|'.$selected_value.'|%'));
 					}
 					$condition = '('.implode(' OR ', $condition).')';
 				}
 				break;
 
 			case 'text':
 				$keywords[$field] = unhtmlentities( $keywords[$field] );
 
 				if (mb_strlen($keywords[$field]) >= $this->Application->ConfigValue('Search_MinKeyword_Length')) {
 					$highlight_keywords[] = $keywords[$field];
 					if (in_array($verbs[$field], Array('any', 'contains', 'notcontains'))) {
 						$keywords[$field] = '%'.strtr($keywords[$field], Array('%' => '\\%', '_' => '\\_')).'%';
 					}
 					$condition = sprintf($condition_patterns[$verbs[$field]], $field_name, $this->Conn->qstr( $keywords[$field] ));
 				}
 				break;
 
 			case 'boolean':
 				if ($keywords[$field] != -1) {
 					$property_mappings = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings');
 					$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
 
 					switch ($field) {
 						case 'HotItem':
 							$hot_limit_var = getArrayValue($property_mappings, 'HotLimit');
 							if ($hot_limit_var) {
 								$hot_limit = (int)$this->Application->getDBCache($hot_limit_var);
 
 								$condition = 	'IF('.$items_table.'.HotItem = 2,
 												IF('.$items_table.'.Hits >= '.
 												$hot_limit.
 												', 1, 0), '.$items_table.'.HotItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'PopItem':
 							$votes2pop_var = getArrayValue($property_mappings, 'VotesToPop');
 							$rating2pop_var = getArrayValue($property_mappings, 'RatingToPop');
 							if ($votes2pop_var && $rating2pop_var) {
 								$condition = 'IF('.$items_table.'.PopItem = 2, IF('.$items_table.'.CachedVotesQty >= '.
 												$this->Application->ConfigValue($votes2pop_var).
 												' AND '.$items_table.'.CachedRating >= '.
 												$this->Application->ConfigValue($rating2pop_var).
 												', 1, 0), '.$items_table.'.PopItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'NewItem':
 							$new_days_var = getArrayValue($property_mappings, 'NewDays');
 							if ($new_days_var) {
 								$condition = 	'IF('.$items_table.'.NewItem = 2,
 												IF('.$items_table.'.CreatedOn >= (UNIX_TIMESTAMP() - '.
 												$this->Application->ConfigValue($new_days_var).
 												'*3600*24), 1, 0), '.$items_table.'.NewItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'EditorsPick':
 							$condition = $items_table.'.EditorsPick = '.$keywords[$field];
 							break;
 					}
 				}
 				break;
 
 			case 'range':
 				$range_conditions = Array();
 				if ($keywords[$field.'_from'] && !preg_match("/[^0-9]/i", $keywords[$field.'_from'])) {
 					$range_conditions[] = $field_name.' >= '.$keywords[$field.'_from'];
 				}
 				if ($keywords[$field.'_to'] && !preg_match("/[^0-9]/i", $keywords[$field.'_to'])) {
 					$range_conditions[] = $field_name.' <= '.$keywords[$field.'_to'];
 				}
 				if ($range_conditions) {
 					$condition = implode(' AND ', $range_conditions);
 				}
 				break;
 
 			case 'date':
 				if ($keywords[$field]) {
 					if (in_array($keywords[$field], Array('today', 'yesterday'))) {
 						$current_time = getdate();
 						$day_begin = adodb_mktime(0, 0, 0, $current_time['mon'], $current_time['mday'], $current_time['year']);
 						$time_mapping = Array('today' => $day_begin, 'yesterday' => ($day_begin - 86400));
 						$min_time = $time_mapping[$keywords[$field]];
 					}
 					else {
 						$time_mapping = Array (
 							'last_week' => 604800, 'last_month' => 2628000, 'last_3_months' => 7884000,
 							'last_6_months' => 15768000, 'last_year' => 31536000,
 						);
 						$min_time = adodb_mktime() - $time_mapping[$keywords[$field]];
 					}
 					$condition = $field_name.' > '.$min_time;
 				}
 				break;
 		}
 
 		return $condition;
 	}
 
 	function getHuman($type, $search_data)
 	{
 		$type = ucfirst(strtolower($type));
 		extract($search_data);
 
 		switch ($type) {
 			case 'Field':
 				return $this->Application->Phrase($search_config['DisplayName']);
 				break;
 
 			case 'Verb':
 				return $verb ? $this->Application->Phrase('lu_advsearch_'.$verb) : '';
 				break;
 
 			case 'Value':
 				switch ($search_config['FieldType']) {
 					case 'date':
 						$values = Array(0 => 'lu_comm_Any', 'today' => 'lu_comm_Today',
 										'yesterday' => 'lu_comm_Yesterday', 'last_week' => 'lu_comm_LastWeek',
 										'last_month' => 'lu_comm_LastMonth', 'last_3_months' => 'lu_comm_Last3Months',
 										'last_6_months' => 'lu_comm_Last6Months', 'last_year' => 'lu_comm_LastYear');
 						$ret = $this->Application->Phrase($values[$value]);
 						break;
 
 					case 'range':
 						$value = explode('|', $value);
 						return $this->Application->Phrase('lu_comm_From').' "'.$value[0].'" '.$this->Application->Phrase('lu_comm_To').' "'.$value[1].'"';
 						break;
 
 					case 'boolean':
 						$values = Array(1 => 'lu_comm_Yes', 0 => 'lu_comm_No', -1 => 'lu_comm_Both');
 						$ret = $this->Application->Phrase($values[$value]);
 						break;
 
 					default:
 						$ret = $value;
 						break;
 
 				}
 				return '"'.$ret.'"';
 				break;
 		}
 	}
 
 
 
 	/**
 	 * Set's correct page for list
 	 * based on data provided with event
 	 *
 	 * @param kEvent $event
 	 * @access private
 	 * @see OnListBuild
 	 */
 	function SetPagination(&$event)
 	{
 		$object =& $event->getObject();
 		/* @var $object kDBList */
 
 		// get PerPage (forced -> session -> config -> 10)
 		$object->SetPerPage( $this->getPerPage($event) );
 
 		// main lists on Front-End have special get parameter for page
 		$page = $object->mainList ? $this->Application->GetVar('page') : false;
 
 		if (!$page) {
 			// page is given in "env" variable for given prefix
 			$page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page');
 		}
 
 		if (!$page && $event->Special) {
 			// when not part of env, then variables like "prefix.special_Page" are
 			// replaced (by PHP) with "prefix_special_Page", so check for that too
 			$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
 		}
 
 		if (!$object->mainList) {
 			// main lists doesn't use session for page storing
 			$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
 
 			if (!$page) {
 				if ($this->Application->RewriteURLs()) {
 					// when page not found by prefix+special, then try to search it without special at all
 					$page = $this->Application->GetVar($event->Prefix . '_Page');
 
 					if (!$page) {
 						// page not found in request -> get from session
 						$page = $this->Application->RecallVar($event->Prefix . '_Page');
 					}
 
 					if ($page) {
 						// page found in request -> store in session
 						$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
 					}
 				}
 				else {
 					// page not found in request -> get from session
 					$page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page');
 				}
 			}
 			else {
 				// page found in request -> store in session
 				$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
 			}
 
 			if ( !$event->getEventParam('skip_counting') ) {
 				// when stored page is larger, then maximal list page number
 				// (such case is also processed in kDBList::Query method)
 				$pages = $object->GetTotalPages();
 
 				if ($page > $pages) {
 					$page = 1;
 					$this->Application->StoreVar($event->getPrefixSpecial().'_Page', 1, true);
 				}
 			}
 		}
 
 		$object->SetPage($page);
 	}
 
 /* === RELATED TO IMPORT/EXPORT: BEGIN === */
 
 	/**
 	 * Shows export dialog
 	 *
 	 * @param kEvent $event
 	 */
 	function OnExport(&$event)
 	{
 		$selected_ids = $this->StoreSelectedIDs($event);
 		if (implode(',', $selected_ids) == '') {
 			// K4 fix when no ids found bad selected ids array is formed
 			$selected_ids = false;
 		}
 
 		$selected_cats_ids = $this->Application->GetVar('export_categories');
 
 		$this->Application->StoreVar($event->Prefix.'_export_ids', $selected_ids ? implode(',', $selected_ids) : '' );
 		$this->Application->StoreVar($event->Prefix.'_export_cats_ids', $selected_cats_ids);
 
 		$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 		/* @var $export_helper kCatDBItemExportHelper */
 
 		$redirect_params = Array (
 			$this->Prefix.'.export_event' => 'OnNew',
 			'pass' => 'all,'.$this->Prefix.'.export'
 		);
 
 		$event->setRedirectParams($redirect_params);
 	}
 
 	/**
 	 * Performs each export step & displays progress percent
 	 *
 	 * @param kEvent $event
 	 */
 	function OnExportProgress(&$event)
 	{
 		$export_object =& $this->Application->recallObject('CatItemExportHelper');
 		/* @var $export_object kCatDBItemExportHelper */
 
 		$event = new kEvent($event->getPrefixSpecial().':OnDummy');
 
 		$action_method = 'perform'.ucfirst($event->Special);
 		$field_values = $export_object->$action_method($event);
 
 		// finish code is done from JS now
 		if ($field_values['start_from'] == $field_values['total_records']) {
 			if ($event->Special == 'import') {
 				$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 				$url_params = Array(
 					't' => 'catalog/catalog',
 					'm_cat_id' => $this->Application->RecallVar('ImportCategory'),
 					'anchor' => 'tab-' . $event->Prefix,
 				);
 				$this->Application->EventManager->openerStackChange($url_params);
 
 				$event->SetRedirectParam('opener', 'u');
 			}
 			elseif ($event->Special == 'export') {
 				$event->redirect = $export_object->getModuleName($event) . '/' . $event->Special . '_finish';
 				$event->SetRedirectParam('pass', 'all');
 			}
 
 			return ;
 		}
 
 		$export_options = $export_object->loadOptions($event);
 		echo $export_options['start_from']  * 100 / $export_options['total_records'];
 
 		$event->status = erSTOP;
 	}
 
 	/**
 	 * Returns specific to each item type columns only
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function getCustomExportColumns(&$event)
 	{
 		return Array(	'__VIRTUAL__ThumbnailImage'	=>	'ThumbnailImage',
 						'__VIRTUAL__FullImage' 		=>	'FullImage',
 						'__VIRTUAL__ImageAlt'		=>	'ImageAlt');
 	}
 
 	/**
 	 * Sets non standart virtual fields (e.g. to other tables)
 	 *
 	 * @param kEvent $event
 	 */
 	function setCustomExportColumns(&$event)
 	{
 		$this->restorePrimaryImage($event);
 	}
 
 	/**
 	 * Create/Update primary image record in info found in imported data
 	 *
 	 * @param kEvent $event
 	 */
 	function restorePrimaryImage(&$event)
 	{
 		$object =& $event->getObject();
 
 		$has_image_info = $object->GetDBField('ImageAlt') && ($object->GetDBField('ThumbnailImage') || $object->GetDBField('FullImage'));
 		if (!$has_image_info) {
 			return false;
 		}
 
 		$image_data = $object->getPrimaryImageData();
 
 		$image =& $this->Application->recallObject('img', null, Array('skip_autoload' => true));
 		if ($image_data) {
 			$image->Load($image_data['ImageId']);
 		}
 		else {
 			$image->Clear();
 			$image->SetDBField('Name', 'main');
 			$image->SetDBField('DefaultImg', 1);
 			$image->SetDBField('ResourceId', $object->GetDBField('ResourceId'));
 		}
 
 		$image->SetDBField('AltName', $object->GetDBField('ImageAlt'));
 
 		if ($object->GetDBField('ThumbnailImage')) {
 			$thumbnail_field = $this->isURL( $object->GetDBField('ThumbnailImage') ) ? 'ThumbUrl' : 'ThumbPath';
 			$image->SetDBField($thumbnail_field, $object->GetDBField('ThumbnailImage') );
 			$image->SetDBField('LocalThumb', $thumbnail_field == 'ThumbPath' ? 1 : 0);
 		}
 
 		if (!$object->GetDBField('FullImage')) {
 			$image->SetDBField('SameImages', 1);
 		}
 		else {
 			$image->SetDBField('SameImages', 0);
 			$full_field = $this->isURL( $object->GetDBField('FullImage') ) ? 'Url' : 'LocalPath';
 			$image->SetDBField($full_field, $object->GetDBField('FullImage') );
 			$image->SetDBField('LocalImage', $full_field == 'LocalPath' ? 1 : 0);
 		}
 
 		if ($image->isLoaded()) {
 			$image->Update();
 		}
 		else {
 			$image->Create();
 		}
 	}
 
 	function isURL($path)
 	{
 		return preg_match('#(http|https)://(.*)#', $path);
 	}
 
 	/**
 	 * Prepares item for import/export operations
 	 *
 	 * @param kEvent $event
 	 */
 	function OnNew(&$event)
 	{
 		parent::OnNew($event);
 
 		if ($event->Special == 'import' || $event->Special == 'export') {
 			$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 			$export_helper->setRequiredFields($event);
 		}
 	}
 
 	/**
 	 * Process items selected in item_selector
 	 *
 	 * @param kEvent $event
 	 */
 	function OnProcessSelected(&$event)
 	{
 		$selected_ids = $this->Application->GetVar('selected_ids');
 
 		$dst_field = $this->Application->RecallVar('dst_field');
 
 		if ($dst_field == 'ItemCategory') {
 			// Item Edit -> Categories Tab -> New Categories
 			$object =& $event->getObject();
 			$category_ids = explode(',', $selected_ids['c']);
 			foreach ($category_ids as $category_id) {
 				$object->assignToCategory($category_id);
 			}
 		}
 
 		if ($dst_field == 'ImportCategory') {
 			// Tools -> Import -> Item Import -> Select Import Category
 			$this->Application->StoreVar('ImportCategory', $selected_ids['c']);
 //			$this->Application->StoreVar($event->getPrefixSpecial().'_ForceNotValid', 1); // not to loose import/export values on form refresh
 
 			$url_params = Array (
 				$event->getPrefixSpecial() . '_id' => 0,
 				$event->getPrefixSpecial() . '_event' => 'OnExportBegin',
 //				'm_opener' => 's',
 			);
 
 			$this->Application->EventManager->openerStackChange($url_params);
 		}
 
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Saves Import/Export settings to session
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSaveSettings(&$event)
 	{
 		$event->redirect = false;
 		$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 		if ($items_info) {
 			list($id, $field_values) = each($items_info);
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$object->SetFieldsFromHash($field_values);
 			$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
 			$field_values['ImportSource'] = 2;
 			$field_values['ImportLocalFilename'] = $object->GetDBField('ImportFilename');
 			$items_info[$id] = $field_values;
 
 			$this->Application->StoreVar($event->getPrefixSpecial().'_ItemsInfo', serialize($items_info));
 		}
 	}
 
 	/**
 	 * Saves Import/Export settings to session
 	 *
 	 * @param kEvent $event
 	 */
 	function OnResetSettings(&$event)
 	{
-		$this->Application->StoreVar('ImportCategory', $this->Application->findModule('Name', 'Core', 'RootCat'));
+		$this->Application->StoreVar('ImportCategory', $this->Application->getBaseCategory());
 	}
 
 	function OnCancelAction(&$event)
 	{
 		$event->redirect_params = Array('pass' => 'all,'.$event->GetPrefixSpecial());
 		$event->redirect = $this->Application->GetVar('cancel_template');
 	}
 
 /* === RELATED TO IMPORT/EXPORT: END === */
 
 	/**
 	 * Stores item's owner login into separate field together with id
 	 *
 	 * @param kEvent $event
 	 * @param string $id_field
 	 * @param string $cached_field
 	 */
 	function cacheItemOwner(&$event, $id_field, $cached_field)
 	{
 		$object =& $event->getObject();
 
 		$user_id = $object->GetDBField($id_field);
 		$options = $object->GetFieldOptions($id_field);
 		if (isset($options['options'][$user_id])) {
 			$object->SetDBField($cached_field, $options['options'][$user_id]);
 		}
 		else {
 			$id_field = $this->Application->getUnitOption('u', 'IDField');
 			$table_name = $this->Application->getUnitOption('u', 'TableName');
 
 			$sql = 'SELECT Login
 					FROM '.$table_name.'
 					WHERE '.$id_field.' = '.$user_id;
 			$object->SetDBField($cached_field, $this->Conn->GetOne($sql));
 		}
 	}
 
 	/**
 	 * Saves item beeing edited into temp table
 	 *
 	 * @param kEvent $event
 	 */
 	function OnPreSave(&$event)
 	{
 		parent::OnPreSave($event);
 		$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 		if ($event->status == erSUCCESS && $use_pending_editing) {
 			// decision: clone or not clone
 
 			$object =& $event->getObject();
 			if ($object->GetID() == 0 || $object->GetDBField('OrgId') > 0) {
 				// new items or cloned items shouldn't be cloned again
 				return true;
 			}
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			$owner_field = $this->getOwnerField($event->Prefix);
 
 			if ($perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix) == 2) {
 
 				// 1. clone original item
 				$temp_handler =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 				$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($object->GetID()), null, null, null, true);
 				$ci_table = $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems');
 
 				// 2. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
 				$sql = 'SELECT ResourceId
 						FROM '.$object->TableName.'
 						WHERE '.$object->IDField.' = '.$cloned_ids[0];
 				$clone_resource_id = $this->Conn->GetOne($sql);
 
 				$sql = 'DELETE FROM '.$ci_table.'
 						WHERE ItemResourceId = '.$clone_resource_id.' AND PrimaryCat = 1';
 				$this->Conn->Query($sql);
 
 				// 3. copy main item categoryitems to cloned item
 				$sql = '	INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
 							SELECT CategoryId, '.$clone_resource_id.' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
 							FROM '.$ci_table.'
 							WHERE ItemResourceId = '.$object->GetDBField('ResourceId');
 				$this->Conn->Query($sql);
 
 				// 4. put cloned id to OrgId field of item being cloned
 				$sql = 'UPDATE '.$object->TableName.'
 						SET OrgId = '.$object->GetID().'
 						WHERE '.$object->IDField.' = '.$cloned_ids[0];
 				$this->Conn->Query($sql);
 
 				// 5. substitute id of item being cloned with clone id
 				$this->Application->SetVar($event->getPrefixSpecial().'_id', $cloned_ids[0]);
 
 				$selected_ids = $this->getSelectedIDs($event, true);
 				$selected_ids[ array_search($object->GetID(), $selected_ids) ] = $cloned_ids[0];
 				$this->StoreSelectedIDs($event, $selected_ids);
 
 				// 6. delete original item from temp table
 				$temp_handler->DeleteItems($event->Prefix, $event->Special, Array($object->GetID()));
 			}
 		}
 	}
 
 	/**
 	 * Sets default expiration based on module setting
 	 *
 	 * @param kEvent $event
 	 */
 	function OnPreCreate(&$event)
 	{
 		parent::OnPreCreate($event);
 
 		if ($event->status == erSUCCESS) {
 			$object =& $event->getObject();
 			$owner_field = $this->getOwnerField($event->Prefix);
 
 			$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
 		}
 	}
 
 	/**
 	 * Occures before original item of item in pending editing got deleted (for hooking only)
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeDeleteOriginal(&$event)
 	{
 
 	}
 
 	/**
 	 * Occures before an item is cloneded
 	 * Id of ORIGINAL item is passed as event' 'id' param
 	 * Do not call object' Update method in this event, just set needed fields!
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeClone(&$event)
 	{
 		if ($this->Application->GetVar('ResetCatBeforeClone')) {
 			$object =& $event->getObject();
 			$object->SetDBField('CategoryId', null);
 		}
 	}
 
 	/**
 	 * Set status for new category item based on user permission in category
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeItemCreate(&$event)
 	{
 		if ($this->Application->isAdminUser) {
 			// don't set permission-based status, when creating categories in admin
 			return true;
 		}
 
 		$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 		if ($use_pending_editing) {
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$primary_category = $object->GetDBField('CategoryId') > 0 ? $object->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id');
 			$item_status = $perm_helper->AddCheckPermission($primary_category, $event->Prefix);
 			if ($item_status == STATUS_DISABLED) {
 				$event->status = erFAIL;
 				return false;
 			}
 			else {
 				$object->SetDBField('Status', $item_status);
 			}
 		}
 	}
 
 	/**
 	 * Creates category item & redirects to confirmation template (front-end only)
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCreate(&$event)
 	{
 		parent::OnCreate($event);
 		$this->SetFrontRedirectTemplate($event, 'suggest');
 	}
 
 	/**
 	 * Returns item's categories (allows to exclude primary category)
 	 *
 	 * @param int $resource_id
 	 * @param bool $with_primary
 	 * @return Array
 	 */
 	function getItemCategories($resource_id, $with_primary = false)
 	{
 		$sql = 'SELECT CategoryId
 				FROM '.TABLE_PREFIX.'CategoryItems
 				WHERE (ItemResourceId = '.$resource_id.')';
 
 		if (!$with_primary) {
 			$sql .= ' AND (PrimaryCat = 0)';
 		}
 
 		return $this->Conn->GetCol($sql);
 	}
 
 	/**
 	 * Adds new and removes old additional categories from category item
 	 *
 	 * @param kCatDBItem $object
 	 */
 	function processAdditionalCategories(&$object, $mode)
 	{
 		if (!array_key_exists('MoreCategories', $object->VirtualFields)) {
 			// given category item doesn't require such type of processing
 			return ;
 		}
 
 		$process_categories = $object->GetDBField('MoreCategories');
 		if ($process_categories === '') {
 			// field was not in submit & have default value (when no categories submitted, then value is null)
 			return ;
 		}
 
 		if ($mode == 'create') {
 			// prevents first additional category to become primary
 			$object->assignPrimaryCategory();
 		}
 
 		$process_categories = $process_categories ? explode('|', substr($process_categories, 1, -1)) : Array ();
 		$existing_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
 
 		$add_categories = array_diff($process_categories, $existing_categories);
 		foreach ($add_categories as $category_id) {
 			$object->assignToCategory($category_id);
 		}
 
 		$remove_categories = array_diff($existing_categories, $process_categories);
 		foreach ($remove_categories as $category_id) {
 			$object->removeFromCategory($category_id);
 		}
 	}
 
 	/**
 	 * Creates category item & redirects to confirmation template (front-end only)
 	 *
 	 * @param kEvent $event
 	 */
 	function OnUpdate(&$event)
 	{
 		$use_pending = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 		if ($this->Application->isAdminUser || !$use_pending) {
 			parent::OnUpdate($event);
 			$this->SetFrontRedirectTemplate($event, 'modify');
 			return ;
 		}
 
 		$object =& $event->getObject(Array('skip_autoload' => true));
 		/* @var $object kCatDBItem */
 
 		$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 		if ($items_info) {
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$temp_handler =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$owner_field = $this->getOwnerField($event->Prefix);
 
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			foreach ($items_info as $id => $field_values) {
 				$object->Load($id);
 				$edit_perm = $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix);
 
 				if ($use_pending && !$object->GetDBField('OrgId') && ($edit_perm == STATUS_PENDING)) {
 					// pending editing enabled + not pending copy -> get/create pending copy & save changes to it
 					$original_id = $object->GetID();
 					$original_resource_id = $object->GetDBField('ResourceId');
 					$file_helper->PreserveItemFiles($field_values);
 
 					$object->Load($original_id, 'OrgId');
 					if (!$object->isLoaded()) {
 						// 1. user has no pending copy of live item -> clone live item
 						$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($original_id), null, null, null, true);
 
 						$object->Load($cloned_ids[0]);
 						$object->SetFieldsFromHash($field_values);
 
 						// 1a. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
 						$ci_table = $this->Application->getUnitOption('ci', 'TableName');
 						$sql = 'DELETE FROM '.$ci_table.'
 								WHERE ItemResourceId = '.$object->GetDBField('ResourceId').' AND PrimaryCat = 1';
 						$this->Conn->Query($sql);
 
 						// 1b. copy main item categoryitems to cloned item
 						$sql = 'INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
 								SELECT CategoryId, '.$object->GetDBField('ResourceId').' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
 								FROM '.$ci_table.'
 								WHERE ItemResourceId = '.$original_resource_id;
 						$this->Conn->Query($sql);
 
 						// 1c. put cloned id to OrgId field of item being cloned
 						$object->SetDBField('Status', STATUS_PENDING_EDITING);
 						$object->SetDBField('OrgId', $original_id);
 					}
 					else {
 						// 2. user has pending copy of live item -> just update field values
 						$object->SetFieldsFromHash($field_values);
 					}
 
 					// update id in request (used for redirect in mod-rewrite mode)
 					$this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetID());
 				}
 				else {
 					// 3. already editing pending copy -> just update field values
 					$object->SetFieldsFromHash($field_values);
 				}
 
 				if ($object->Update()) {
 					$event->status = erSUCCESS;
 				}
 				else {
 					$event->status = erFAIL;
 					$event->redirect = false;
 					break;
 				}
 			}
 		}
 
 		$this->SetFrontRedirectTemplate($event, 'modify');
 	}
 
 	/**
 	 * Sets next template to one required for front-end after adding/modifying item
 	 *
 	 * @param kEvent $event
 	 * @param string $template_key - {suggest,modify}
 	 */
 	function SetFrontRedirectTemplate(&$event, $template_key)
 	{
 		if ($this->Application->isAdminUser || $event->status != erSUCCESS) {
 			return ;
 		}
 
 		// prepare redirect template
 		$object =& $event->getObject();
 		$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
 
 		$next_template = $is_active ? 'confirm_template' : 'pending_confirm_template';
 		$event->redirect = $this->Application->GetVar($template_key.'_'.$next_template);
 		$event->SetRedirectParam('opener', 's');
 
 		// send email events
 		$perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix');
 		switch ($event->Name) {
 			case 'OnCreate':
 				$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
 				$owner_field = $this->getOwnerField($event->Prefix);
 
 				$this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix); // there are no ADD.PENDING event for admin :(
 				$this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField($owner_field));
 				break;
 
 			case 'OnUpdate':
 				$event_suffix = $is_active ? 'MODIFY' : 'MODIFY.PENDING';
 				$this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix); // there are no ADD.PENDING event for admin :(
 				$this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField('ModifiedById'));
 				break;
 		}
 	}
 
 	/**
 	 * Apply same processing to each item beeing selected in grid
 	 *
 	 * @param kEvent $event
 	 * @access private
 	 */
 	function iterateItems(&$event)
 	{
 		if ($event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline') {
 			return parent::iterateItems($event);
 		}
 
 		if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 			$event->status = erFAIL;
 			return;
 		}
 
 		$object =& $event->getObject( Array('skip_autoload' => true) );
 		$ids = $this->StoreSelectedIDs($event);
 
 		if ($ids) {
 			foreach ($ids as $id) {
 				$object->Load($id);
 
 				switch ($event->Name) {
 					case 'OnMassApprove':
 						$ret = $object->ApproveChanges();
 						break;
 
 					case 'OnMassDecline':
 						$ret = $object->DeclineChanges();
 						break;
 				}
 
 				if (!$ret) {
 					$event->status = erFAIL;
 					$event->redirect = false;
 					break;
 				}
 			}
 		}
 
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Deletes items & preserves clean env
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDelete(&$event)
 	{
 		parent::OnDelete($event);
 
 		if ($event->status == erSUCCESS && !$this->Application->isAdmin) {
 			$event->SetRedirectParam('pass', 'm');
 			$event->SetRedirectParam('m_cat_id', 0);
 		}
 	}
 
 	/**
 	 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 */
 	function checkItemStatus(&$event)
 	{
 		$object =& $event->getObject();
 		if (!$object->isLoaded()) {
 			$this->_errorNotFound($event);
 
 			return true;
 		}
 
 		$status = $object->GetDBField('Status');
 		$user_id = $this->Application->RecallVar('user_id');
 		$owner_field = $this->getOwnerField($event->Prefix);
 
 		if (($status == STATUS_PENDING_EDITING || $status == STATUS_PENDING) && ($object->GetDBField($owner_field) == $user_id)) {
 			return true;
 		}
 
 		return $status == STATUS_ACTIVE;
 	}
 
 	/**
 	 * Set's correct sorting for list
 	 * based on data provided with event
 	 *
 	 * @param kEvent $event
 	 * @access private
 	 * @see OnListBuild
 	 */
 	function SetSorting(&$event)
 	{
 		if (!$this->Application->isAdmin) {
 			$event->setEventParam('same_special', true);
 		}
 
 		parent::SetSorting($event);
 	}
 
 	/**
 	 * Returns current per-page setting for list
 	 *
 	 * @param kEvent $event
 	 * @return int
 	 */
 	function getPerPage(&$event)
 	{
 		if (!$this->Application->isAdmin) {
 			$event->setEventParam('same_special', true);
 		}
 
 		return parent::getPerPage($event);
 	}
 
 	function getOwnerField($prefix)
 	{
 		$owner_field = $this->Application->getUnitOption($prefix, 'OwnerField');
 		if (!$owner_field) {
 			$owner_field = 'CreatedById';
 		}
 
 		return $owner_field;
 	}
 
 	/**
 	 * Creates virtual image fields for item
 	 *
 	 * @param kEvent $event
 	 */
 	function OnAfterConfigRead(&$event)
 	{
 		parent::OnAfterConfigRead($event);
 
 		if (defined('IS_INSTALL') && IS_INSTALL) {
 			return ;
 		}
 
 		$file_helper =& $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$file_helper->createItemFiles($event->Prefix, true); // create image fields
 		$file_helper->createItemFiles($event->Prefix, false); // create file fields
 
 		// add EditorsPick to ForcedSorting if needed
 		$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
 		if (array_key_exists('ForceEditorPick', $config_mapping) && $this->Application->ConfigValue($config_mapping['ForceEditorPick'])) {
 			$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings');
 
 			$new_forced_sorting = Array ('EditorsPick' => 'DESC');
 
 			if (array_key_exists('ForcedSorting', $list_sortings[''])) {
 				foreach ($list_sortings['']['ForcedSorting'] as $sort_field => $sort_order) {
 					$new_forced_sorting[$sort_field] = $sort_order;
 				}
 			}
 			$list_sortings['']['ForcedSorting'] = $new_forced_sorting;
 
 			$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
 		}
 
 		// add grids for advanced view (with primary category column)
 		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 		$process_grids = Array ('Default', 'Radio');
 		foreach ($process_grids as $process_grid) {
 			$grid_data = $grids[$process_grid];
 			$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_primary_category_td', 'filter_block' => 'grid_like_filter');
 			$grids[$process_grid . 'ShowAll'] = $grid_data;
 		}
 		$this->Application->setUnitOption($this->Prefix, 'Grids', $grids);
 
 		// add options for CategoryId field (quick way to select item's primary category)
 		$category_helper =& $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
 
 		$virtual_fields['CategoryId']['default'] = (int)$this->Application->GetVar('m_cat_id');
 		$virtual_fields['CategoryId']['options'] = $category_helper->getStructureTreeAsOptions();
 
 		$this->Application->setUnitOption($event->Prefix, 'VirtualFields', $virtual_fields);
 	}
 
 	/**
 	 * Returns file contents associated with item
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDownloadFile(&$event)
 	{
 		$object =& $event->getObject();
 		/* @var $object kDBItem */
 
 		$event->status = erSTOP;
 
 		$field = $this->Application->GetVar('field');
 		if (!preg_match('/^File([\d]+)/', $field)) {
 			return ;
 		}
 
 		$file_helper =& $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$filename = $object->GetField($field, 'full_path');
 		$file_helper->DownloadFile($filename);
 	}
 
 	/**
 	 * Saves user's vote
 	 *
 	 * @param kEvent $event
 	 */
 	function OnMakeVote(&$event)
 	{
 		$event->status = erSTOP;
 
 		if ($this->Application->GetVar('ajax') != 'yes') {
 			// this is supposed to call from AJAX only
 			return ;
 		}
 
 		$rating_helper =& $this->Application->recallObject('RatingHelper');
 		/* @var $rating_helper RatingHelper */
 
 		$object =& $event->getObject( Array ('skip_autoload' => true) );
 		/* @var $object kDBItem */
 
 		$object->Load( $this->Application->GetVar('id') );
 
 		echo $rating_helper->makeVote($object);
 	}
 
 	/**
 	 * [HOOK] Allows to add cloned subitem to given prefix
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCloneSubItem(&$event)
 	{
 		parent::OnCloneSubItem($event);
 
 		if ($event->MasterEvent->Prefix == 'fav') {
 			$clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones');
 			$subitem_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix;
 
 			$clones[$subitem_prefix]['ParentTableKey'] = 'ResourceId';
 			$clones[$subitem_prefix]['ForeignKey'] = 'ResourceId';
 
 			$this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones);
 		}
 	}
 }
\ No newline at end of file
Index: branches/5.1.x/core/kernel/application.php
===================================================================
--- branches/5.1.x/core/kernel/application.php	(revision 13986)
+++ branches/5.1.x/core/kernel/application.php	(revision 13987)
@@ -1,3235 +1,3249 @@
 <?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.
 */
 
 /**
 * Basic class for Kernel4-based Application
 *
 * This class is a Facade for any other class which needs to deal with Kernel4 framework.<br>
 * The class incapsulates the main run-cycle of the script, provide access to all other objects in the framework.<br>
 * <br>
 * The class is a singleton, which means that there could be only one instance of kApplication in the script.<br>
 * This could be guranteed by NOT calling the class constuctor directly, but rather calling kApplication::Instance() method,
 * which returns an instance of the application. The method gurantees that it will return exactly the same instance for any call.<br>
 * See singleton pattern by GOF.
 * @package kernel4
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kApplication {
 
 	/**
 	 * Is true, when Init method was called already, prevents double initialization
 	 *
 	 * @var bool
 	 */
 	var $InitDone = false;
 
 	/**
 	* Holds internal NParser object
 	* @access private
 	* @var NParser
 	*/
 	var $Parser;
 
 	/**
 	* Holds parser output buffer
 	* @access private
 	* @var string
 	*/
 	var $HTML;
 
 	/**
 	 * Prevents request from beeing proceeded twice in case if application init is called mere then one time
 	 *
 	 * @var bool
 	 * @todo This is not good anyway (by Alex)
 	 */
 	var $RequestProcessed = false;
 
 	/**
 	* The main Factory used to create
 	* almost any class of kernel and
 	* modules
 	*
 	* @access private
 	* @var kFactory
 	*/
 	var $Factory;
 
 	/**
 	 * All ConfigurationValues table content (hash) here
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $ConfigHash = Array();
 
 	/**
 	 * Ids of config variables used in current run (for caching)
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $ConfigCacheIds = array();
 
 	/**
 	 * Template names, that will be used instead of regular templates
 	 *
 	 * @var Array
 	 */
 	var $ReplacementTemplates = Array ();
 
 	/**
 	 * Mod-Rewrite listeners used during url building and parsing
 	 *
 	 * @var Array
 	 */
 	var $RewriteListeners = Array ();
 
 	/**
 	 * Reference to debugger
 	 *
 	 * @var Debugger
 	 */
 	var $Debugger = null;
 
 	/**
 	 * Holds all phrases used
 	 * in code and template
 	 *
 	 * @var PhrasesCache
 	 */
 	var $Phrases;
 
 	/**
 	 * Modules table content, key - module name
 	 *
 	 * @var Array
 	 */
 	var $ModuleInfo = Array();
 
 	/**
 	 * Holds DBConnection
 	 *
 	 * @var kDBConnection
 	 */
 	var $Conn = null;
 
 	/**
 	 * Maintains list of user-defined error handlers
 	 *
 	 * @var Array
 	 */
 	var $errorHandlers = Array();
 
 
 	// performance needs:
 	/**
 	 * Holds a refererence to httpquery
 	 *
 	 * @var kHttpQuery
 	 */
 	var $HttpQuery = null;
 
 	/**
 	 * Holds a reference to UnitConfigReader
 	 *
 	 * @var kUnitConfigReader
 	 */
 	var $UnitConfigReader = null;
 
 	/**
 	 * Holds a reference to Session
 	 *
 	 * @var Session
 	 */
 	var $Session = null;
 
 	/**
 	 * Holds a ref to kEventManager
 	 *
 	 * @var kEventManager
 	 */
 	var $EventManager = null;
 
 	/**
 	 * Ref to itself, needed because everybody used to write $this->Application, even inside kApplication
 	 *
 	 * @var kApplication
 	 */
 	var $Application = null;
 
 	/**
 	 * Ref for TemplatesChache
 	 *
 	 * @var TemplatesCache
 	 */
 	var $TemplatesCache = null;
 
 	/**
 	 * Physical template name mapping to their template names based on structure
 	 *
 	 * @var Array
 	 */
 	var $structureTemplateMapping = Array ();
 
 	var $CompilationCache = array(); //used when compiling templates
 	var $CachedProcessors = array(); //used when running compiled templates
 
 	var $LambdaElements = 1; // for autonumbering unnamed RenderElements [any better place for this prop? KT]
 
 	/**
 	 * Holds current NParser tag while parsing, can be used in error messages to display template file and line
 	 *
 	 * @var _BlockTag
 	 */
 	var $CurrentNTag = null;
 
 	/**
 	 * Object of memory caching class
 	 *
 	 * @var kCache
 	 */
 	var $memoryCache = null;
 
 	/**
 	 * Tells, that administrator has authentificated in administrative console
 	 * Should be used to manipulate data change OR data restrictioning!
 	 *
 	 * @var bool
 	 */
 	var $isAdminUser = false;
 
 	/**
 	 * Tells, that admin version of "index.php" was used, nothing more!
 	 * Should be used to manipulate data display!
 	 *
 	 * @var bool
 	 */
 	var $isAdmin = false;
 
  	/**
 	 * Instance of site domain object
 	 *
 	 * @var kDBItem
 	 */
 	var $siteDomain = null;
 
 	/**
 	* Returns kApplication instance anywhere in the script.
  	*
  	* This method should be used to get single kApplication object instance anywhere in the
  	* Kernel-based application. The method is guranteed to return the SAME instance of kApplication.
  	* Anywhere in the script you could write:
  	* <code>
  	*		$application =& kApplication::Instance();
  	* </code>
  	* or in an object:
  	* <code>
  	*		$this->Application =& kApplication::Instance();
  	* </code>
  	* to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class.
  	* To use descendand of standard kApplication class in your project you would need to define APPLICATION_CLASS constant
  	* BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would
  	* create and return default KernelApplication instance.
  	* @static
  	* @access public
 	* @return kApplication
 	*/
 	function &Instance()
 	{
 		static $instance = false;
 
 		if (!$instance) {
 			$class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication';
 			$instance = new $class();
 			$instance->Application =& $instance;
 		}
 
 		return $instance;
 	}
 
 	/**
 	* Initializes the Application
  	*
  	* @access public
 	* @see kHTTPQuery
 	* @see Session
 	* @see TemplatesCache
 	* @return bool Was Init actually made now or before
 	*/
 	function Init()
 	{
 		if($this->InitDone) {
 			return false;
 		}
 
 		$this->isAdmin = constOn('ADMIN');
 
 		if (!constOn('SKIP_OUT_COMPRESSION')) {
 			ob_start(); // collect any output from method (other then tags) into buffer
 		}
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) {
 			$this->Debugger->appendMemoryUsage('Application before Init:');
 		}
 		if (!$this->isDebugMode() && !constOn('DBG_ZEND_PRESENT')) {
 			error_reporting(0);
 			ini_set('display_errors', 0);
 		}
 		if (!constOn('DBG_ZEND_PRESENT')) {
 			$error_handler = set_error_handler( Array (&$this, 'handleError') );
 			if ($error_handler) {
 				// wrap around previous error handler, if any was set
 				$this->errorHandlers[] = $error_handler;
 			}
 		}
 
 		$this->Conn = new kDBConnection(SQL_TYPE, Array(&$this, 'handleSQLError') );
 		$this->Conn->debugMode = $this->isDebugMode();
 		$this->Conn->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB);
 
 		$this->Factory = new kFactory();
 		$this->registerDefaultClasses();
 		$this->Phrases = new PhrasesCache();
 		$this->memoryCache =& $this->Factory->makeClass('Cache');
 		$this->EventManager =& $this->Factory->makeClass('EventManager');
 		$this->Factory->Storage['EventManager'] =& $this->EventManager;
 		$this->RegisterDefaultBuildEvents();
 		$this->SetDefaultConstants();
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 			$this->Debugger->appendTimestamp('Before UnitConfigReader');
 		}
 
 		$this->UnitConfigReader =& $this->recallObject('kUnitConfigReader');
 		$this->UnitConfigReader->scanModules(MODULES_PATH);
 		$this->registerModuleConstants();
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 			$this->Debugger->appendTimestamp('After UnitConfigReader');
 		}
 
 		define('MOD_REWRITE', $this->ConfigValue('UseModRewrite') && !$this->isAdmin ? 1 : 0);
 
 		$this->HttpQuery =& $this->recallObject('HTTPQuery');
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 			$this->Debugger->appendTimestamp('Processed HTTPQuery initial');
 		}
 
 		$this->Session =& $this->recallObject('Session');
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 			$this->Debugger->appendTimestamp('Processed Session');
 		}
 
 		if (!$this->RecallVar('UserGroups')) {
 			$user_groups = trim($this->Session->GetField('GroupList'), ',');
 			if (!$user_groups) {
 				$user_groups = $this->ConfigValue('User_GuestGroup');
 			}
 
 			$this->Session->SetField('GroupList', $user_groups);
 			$this->StoreVar('UserGroups', $user_groups, true); // true for optional
 		}
 
 		$this->LoadStructureTemplateMapping();
 		$this->HttpQuery->AfterInit();
 
 		$this->Session->ValidateExpired();
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 			$this->Debugger->appendTimestamp('Processed HTTPQuery AfterInit');
 		}
 
 		$this->LoadCache();
 		$this->InitConfig();
 
 		$site_timezone = $this->ConfigValue('Config_Site_Time');
 
 		if ($site_timezone) {
 			putenv('TZ=' . $site_timezone);
 		}
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 			$this->Debugger->appendTimestamp('Loaded cache and phrases');
 		}
 
 		$this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there
 
 		$this->UnitConfigReader->AfterConfigRead();
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 			$this->Debugger->appendTimestamp('Processed AfterConfigRead');
 		}
 
 		if ($this->GetVar('m_cat_id') === false) {
 			$this->SetVar('m_cat_id', 0);
 		}
 
 		if (!$this->RecallVar('curr_iso')) {
 			$this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional
 		}
 
 		$visit_id = $this->RecallVar('visit_id');
 
 		if ($visit_id !== false) {
 			$this->SetVar('visits_id', $visit_id);
 		}
 
 		$language =& $this->recallObject( 'lang.current', null, Array('live_table' => true) );
 		if (preg_match('/utf-8/', $language->GetDBField('Charset'))) {
 			setlocale(LC_ALL, 'en_US.UTF-8');
 			mb_internal_encoding('UTF-8');
 		}
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 			$this->Debugger->profileFinish('kernel4_startup');
 		}
 
 		$this->InitDone = true;
 
 		$this->HandleEvent( new kEvent('adm:OnStartup') );
 
 		return true;
 	}
 
 	/**
 	 * Returns module information. Searches module by requested field
 	 *
 	 * @param string $field
 	 * @param mixed $value
 	 * @param string field value to returns, if not specified, then return all fields
 	 * @param string field to return
 	 * @return Array
 	 */
 	function findModule($field, $value, $return_field = null)
 	{
 		$found = false;
 		foreach ($this->ModuleInfo as $module_name => $module_info) {
 			if (strtolower($module_info[$field]) == strtolower($value)) {
 				$found = true;
 				break;
 			}
 		}
 
 		if ($found) {
 			return isset($return_field) ? $module_info[$return_field] : $module_info;
 		}
 
 		return false;
 	}
 
 	function refreshModuleInfo()
 	{
 		if (defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules')) {
 			$this->registerModuleConstants();
 			return false;
 		}
 
 		$modules_helper =& $this->recallObject('ModulesHelper');
 		/* @var $modules_helper kModulesHelper */
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'Modules
 				WHERE Loaded = 1
 				ORDER BY LoadOrder';
 		$this->ModuleInfo = $this->Conn->Query($sql, 'Name');
 
 		$sql = 'SELECT *
 				FROM '.TABLE_PREFIX.'Modules
 				WHERE '.$modules_helper->getWhereClause().'
 				ORDER BY LoadOrder';
 		$this->ModuleInfo = $this->Conn->Query($sql, 'Name');
 
 		$this->registerModuleConstants();
 	}
 
 	/**
 	 * Checks if passed language id if valid and sets it to primary otherwise
 	 *
 	 */
 	function VerifyLanguageId()
 	{
 		$language_id = $this->GetVar('m_lang');
 
 		if (!$language_id) {
 			$language_id = 'default';
 		}
 
 		$this->SetVar('lang.current_id', $language_id);
 		$this->SetVar('m_lang', $language_id);
 
 		$lang_mode = $this->GetVar('lang_mode');
 		$this->SetVar('lang_mode', '');
 
 		$lang =& $this->recallObject('lang.current');
 		/* @var $lang kDBItem */
 
 		if (!$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled'))) {
 			if (!defined('IS_INSTALL')) {
 				$this->ApplicationDie('Unknown or disabled language');
 			}
 		}
 
 		$this->SetVar('lang_mode',$lang_mode);
 	}
 
 	/**
 	 * Checks if passed theme id if valid and sets it to primary otherwise
 	 *
 	 */
 	function VerifyThemeId()
 	{
 		if ($this->isAdmin) {
 			safeDefine('THEMES_PATH', '/core/admin_templates');
 			return;
 		}
 
 		$path = $this->GetFrontThemePath();
 		if ($path === false) {
 			$this->ApplicationDie('No Primary Theme Selected or Current Theme is Unknown or Disabled');
 		}
 		safeDefine('THEMES_PATH', $path);
 
 		/*$theme_id = $this->GetVar('m_theme');
 		if (!$theme_id) {
 			$theme_id =  $this->GetDefaultThemeId();
 			if (!$theme_id) {
 				if (!defined('IS_INSTALL')) $this->ApplicationDie('No Primary Theme Selected');
 			}
 		}
 		$this->SetVar('m_theme', $theme_id);
 		$this->SetVar('theme.current_id', $theme_id ); // KOSTJA: this is to fool theme' getPassedId
 		$theme =& $this->recallObject('theme.current');
 		if (!$theme->IsLoaded() || !$theme->GetDBField('Enabled')) {
 		if (!defined('IS_INSTALL')) $this->ApplicationDie('Unknown or disabled theme');
 		}
 		safeDefine('THEMES_PATH', '/themes/'.$theme->GetDBField('Name'));*/
 	}
 
 	function GetFrontThemePath($force=0)
 	{
 		static $path=null;
 		if (!$force && isset($path)) return $path;
 
 		$theme_id = $this->GetVar('m_theme');
 		if (!$theme_id) {
 //			$theme_id =  $this->GetDefaultThemeId(1); //1 to force front-end mode!
 			$theme_id = 'default';
 		}
 		$this->SetVar('m_theme', $theme_id);
 		$this->SetVar('theme.current_id', $theme_id ); // KOSTJA: this is to fool theme' getPassedId
 		$theme =& $this->recallObject('theme.current');
 		if (!$theme->IsLoaded() || !$theme->GetDBField('Enabled')) {
 			return false;
 		}
 		$path = '/themes/'.$theme->GetDBField('Name');
 		return $path;
 	}
 
 	function GetDefaultLanguageId($init = false)
 	{
 		$cache_key = 'primary_language_info[%LangSerial%]';
 		$language_info = $this->getCache($cache_key);
 
 		if ($language_info === false) {
 			// cache primary language info first
 			$table = $this->getUnitOption('lang', 'TableName');
 			$id_field = $this->getUnitOption('lang', 'IDField');
 
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT ' . $id_field . ', IF(AdminInterfaceLang, "Admin", "Front") AS LanguageKey
 					FROM ' . $table . '
 					WHERE (AdminInterfaceLang = 1 OR PrimaryLang = 1) AND (Enabled = 1)';
 			$language_info = $this->Conn->GetCol($sql, 'LanguageKey');
 
 			if ($language_info !== false) {
 				$this->setCache($cache_key, $language_info);
 			}
 		}
 
 		$language_key = ($this->isAdmin && $init) || count($language_info) == 1 ? 'Admin' : 'Front';
 
 		if (array_key_exists($language_key, $language_info) && $language_info[$language_key] > 0) {
 			// get from cache
 			return $language_info[$language_key];
 		}
 
 		$language_id = $language_info && array_key_exists($language_key, $language_info) ? $language_info[$language_key] : false;
 
 		if (!$language_id && defined('IS_INSTALL') && IS_INSTALL) {
 			$language_id = 1;
 		}
 
 		return $language_id;
 	}
 
 	function GetDefaultThemeId($force_front=0)
 	{
 		static $theme_id = 0;
 
 		if ($theme_id > 0) {
 			return $theme_id;
 		}
 
 		if (constOn('DBG_FORCE_THEME')) {
 			$theme_id = DBG_FORCE_THEME;
 		}
 		elseif (!$force_front && $this->isAdmin) {
 			$theme_id = 999;
 		}
 		else {
 			$cache_key = 'primary_theme[%ThemeSerial%]';
 			$theme_id = $this->getCache($cache_key);
 
 			if ($theme_id === false) {
 				$this->Conn->nextQueryCachable = true;
 				$sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . '
 						FROM ' . $this->getUnitOption('theme', 'TableName') . '
 						WHERE (PrimaryTheme = 1) AND (Enabled = 1)';
 				$theme_id = $this->Conn->GetOne($sql);
 
 				if ($theme_id !== false) {
 					$this->setCache($cache_key, $theme_id);
 				}
 			}
 		}
 
 		return $theme_id;
 	}
 
 	/**
 	 * Returns site primary currency ISO code
 	 *
 	 * @return string
 	 */
 	function GetPrimaryCurrency()
 	{
 		$cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId');
 		$currency_iso = $this->getCache($cache_key);
 
 		if ($currency_iso === false) {
 			if ($this->isModuleEnabled('In-Commerce')) {
 				$this->Conn->nextQueryCachable = true;
 				$currency_id = $this->siteDomainField('PrimaryCurrencyId');
 
 				$sql = 'SELECT ISO
 						FROM ' . $this->getUnitOption('curr', 'TableName') . '
 						WHERE ' . ($currency_id > 0 ? 'CurrencyId = ' . $currency_id : 'IsPrimary = 1');
 				$currency_iso = $this->Conn->GetOne($sql);
 			}
 			else {
 				$currency_iso = 'USD';
 			}
 
 			$this->setCache($cache_key, $currency_iso);
 		}
 
 		return $currency_iso;
 	}
 
 	/**
 	 * Returns site domain field. When none of site domains are found false is returned.
 	 *
 	 * @param string $field
 	 * @param bool $formatted
 	 * @param string $format
 	 */
 	function siteDomainField($field, $formatted = false, $format = null)
 	{
 		if ($this->isAdmin) {
 			// don't apply any filtering in administrative console
 			return false;
 		}
 
 		if (!$this->siteDomain) {
 			$this->siteDomain =& $this->recallObject('site-domain.current');
 			/* @var $site_domain kDBItem */
 		}
 
 		if ($this->siteDomain->isLoaded()) {
 			return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field);
 		}
 
 		return false;
 	}
 
 	/**
 	* Registers default classes such as ItemController, GridController and LoginController
 	*
 	* Called automatically while initializing Application
 	* @access private
 	* @return void
 	*/
 	function RegisterDefaultClasses()
 	{
 		$this->registerClass('kTempTablesHandler', KERNEL_PATH . '/utility/temp_handler.php');
 		$this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php', 'EventManager');
 		$this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php');
 
 		$this->registerClass('kArray', KERNEL_PATH . '/utility/params.php');
 		$this->registerClass('Params', KERNEL_PATH . '/utility/params.php');
 		$this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions');
 		$this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php', 'Cache', 'Params');
 		$this->registerClass('kHTTPQuery', KERNEL_PATH . '/utility/http_query.php', 'HTTPQuery', 'Params');
 
 		$this->registerClass('kHelper', KERNEL_PATH . '/kbase.php');
 		$this->registerClass('kMultipleFilter', KERNEL_PATH . '/utility/filters.php');
 
 		$this->registerClass('Session', KERNEL_PATH . '/session/session.php');
 		$this->registerClass('SessionStorage', KERNEL_PATH . '/session/session.php');
 		$this->registerClass('InpSession', KERNEL_PATH . '/session/inp_session.php', 'Session');
 		$this->registerClass('InpSessionStorage', KERNEL_PATH . '/session/inp_session.php', 'SessionStorage');
 
 		$this->registerClass('kTagProcessor', KERNEL_PATH . '/processors/tag_processor.php');
 		$this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php','m_TagProcessor', 'kTagProcessor');
 
 		$this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php');
 		$this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php');
 		$this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php');
 		$this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php', null, 'kTagProcessor');
 		$this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php');
 		$this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php');
 		$this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php');
 		$this->registerClass('kCatDBTagProcessor', KERNEL_PATH . '/db/cat_tag_processor.php');
 
 		$this->registerClass('NParser', KERNEL_PATH . '/nparser/nparser.php');
 		$this->registerClass('TemplatesCache', KERNEL_PATH . '/nparser/template_cache.php', null, Array ('kHelper', 'kDBTagProcessor'));
 
 		$this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender', 'kHelper');
 		$this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket');
 
 		if (file_exists(MODULES_PATH . '/in-commerce/units/currencies/currency_rates.php')) {
 			$this->registerClass('kCurrencyRates', MODULES_PATH . '/in-commerce/units/currencies/currency_rates.php');
 		}
 
 		// do not move to config - this helper is used before configs are read
 		$this->registerClass('kModulesHelper', KERNEL_PATH . '/../units/helpers/modules_helper.php', 'ModulesHelper');
 	}
 
 	function RegisterDefaultBuildEvents()
 	{
 		$event_manager =& $this->recallObject('EventManager');
 		$event_manager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild');
 	}
 
 	/**
 	 * Returns cached category informaton by given cache name. All given category
 	 * information is recached, when at least one of 4 caches is missing.
 	 *
 	 * @param int $category_id
 	 * @param string $name cache name = {filenames, category_designs, category_tree}
 	 * @return string
 	 */
 	function getCategoryCache($category_id, $name)
 	{
 		$serial_name = '[%CIDSerial:' . $category_id . '%]';
 		$cache_key = $name . $serial_name;
 		$ret = $this->getCache($cache_key);
 
 		if ($ret === false) {
 			if (!$category_id) {
 				// don't query database for "Home" category (ID = 0), because it doesn't exist in database
 				return false;
 			}
 
 			// this allows to save 2 sql queries for each category
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT NamedParentPath, CachedTemplate, TreeLeft, TreeRight
 					FROM ' . TABLE_PREFIX . 'Category
 					WHERE CategoryId = ' . (int)$category_id;
 			$category_data = $this->Conn->GetRow($sql);
 
 			if ($category_data !== false) {
 				// only direct links to category pages work (symlinks, container pages and so on won't work)
 				$this->setCache('filenames' . $serial_name,				$category_data['NamedParentPath']);
 				$this->setCache('category_designs' . $serial_name,		ltrim($category_data['CachedTemplate'], '/'));
 				$this->setCache('category_tree' . $serial_name,			$category_data['TreeLeft'] . ';' . $category_data['TreeRight']);
 			}
 		}
 
 		return $this->getCache($cache_key);
 	}
 
 	/**
 	 * Returns item's filename that corresponds id passed. If possible, then get it from cache
 	 *
 	 * @param string $prefix
 	 * @param int $id
 	 * @param int $category_id
 	 * @return string
 	 */
 	function getFilename($prefix, $id, $category_id = null)
 	{
 		if ($prefix == 'c') {
 			trigger_error('Method "<strong>' . __FUNCTION__ . '</strong>" no longer work with "<strong>c</strong>" prefix. Please use "<strong>getCategoryCache</strong>" method instead.', E_USER_ERROR);
 			return false;
 		}
 
 		$category_id = isset($category_id) ? $category_id : $this->GetVar('m_cat_id');
 
 		$cache_key = 'filenames[%' . $this->incrementCacheSerial($prefix, $id, false) . '%]:' . (int)$category_id;
 		$filename = $this->getCache($cache_key);
 
 		if ($filename === false) {
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT ResourceId
 					FROM ' . $this->getUnitOption($prefix, 'TableName') . '
 					WHERE ' . $this->getUnitOption($prefix, 'IDField') . ' = ' . $this->Conn->qstr($id);
 			$resource_id = $this->Conn->GetOne($sql);
 
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT Filename
 					FROM ' . TABLE_PREFIX . 'CategoryItems
 					WHERE (ItemResourceId = ' . $resource_id . ') AND (CategoryId = ' . (int)$category_id . ')';
 			$filename = $this->Conn->GetOne($sql);
 
 			if ($filename !== false) {
 				$this->setCache($cache_key, $filename);
 			}
 		}
 
 		return $filename;
 	}
 
 	/**
 	 * Returns caching type (none, memory, temporary)
 	 *
 	 * @return int
 	 */
 	function isCachingType($caching_type)
 	{
 		return $this->memoryCache->getCachingType() == $caching_type;
 	}
 
 	/**
 	 * Increments serial based on prefix and it's ID (optional)
 	 *
 	 * @param string $prefix
 	 * @param int $id ID (value of IDField) or ForeignKeyField:ID
 	 * @param bool $increment
 	 */
 	function incrementCacheSerial($prefix, $id = null, $increment = true)
 	{
 		$pascal_case_prefix = implode('', array_map('ucfirst', explode('-', $prefix)));
 		$serial_name = $pascal_case_prefix . (isset($id) ? 'IDSerial:' . $id : 'Serial');
 
 		if ($increment) {
 			if (defined('DEBUG_MODE') && DEBUG_MODE && $this->isDebugMode()) {
 				$this->Application->Debugger->appendHTML('Incrementing serial: <strong>' . $serial_name . '</strong>.');
 			}
 
 			$this->setCache($serial_name, (int)$this->getCache($serial_name) + 1);
 
 			if (!defined('IS_INSTALL') || !IS_INSTALL) {
 				// delete cached mod-rewrite urls related to given prefix and id
 				$delete_clause = isset($id) ? $prefix . ':' . $id : $prefix;
 
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls
 						WHERE Prefixes LIKE ' . $this->Conn->qstr('%|' . $delete_clause . '|%');
 				$this->Conn->Query($sql);
 			}
 		}
 
 		return $serial_name;
 	}
 
 	/**
 	 * Adds new value to cache $cache_name and identified by key $key
 	 *
 	 * @param int $key key name to add to cache
 	 * @param mixed $value value of chached record
 	 * @param int $expiration when value expires (0 - doesn't expire)
 	 */
 	function setCache($key, $value, $expiration = 0)
 	{
 		return $this->memoryCache->setCache($key, $value, $expiration);
 	}
 
 	/**
 	 * Sets value to database cache
 	 *
 	 * @param string $name
 	 * @param mixed $value
 	 * @param int $expiration
 	 */
 	function setDBCache($name, &$value, $expiration = false)
 	{
 		if ((int)$expiration <= 0) {
 			$expiration = -1;
 		}
 
 		$fields_hash = Array (
 			'VarName' => $name,
 			'Data' => &$value,
 			'Cached' => adodb_mktime(),
 			'LifeTime' => (int)$expiration,
 		);
 
 		$this->Conn->nextQueryCachable = true;
 		$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache', 'REPLACE');
 	}
 
 	/**
 	 * Returns cached $key value from cache named $cache_name
 	 *
 	 * @param int $key key name from cache
 	 * @param bool $store_locally store data locally after retrieved
 	 * @return mixed
 	 */
 	function getCache($key, $store_locally = true)
 	{
 		return $this->memoryCache->getCache($key, $store_locally);
 	}
 
 	/**
 	 * Returns value from database cache
 	 *
 	 * @param string $name key name
 	 * @return mixed
 	 */
 	function getDBCache($name)
 	{
 		$this->Conn->nextQueryCachable = true;
 
 		$sql = 'SELECT Data, Cached, LifeTime
 				FROM ' . TABLE_PREFIX . 'Cache
 				WHERE VarName = ' . $this->Conn->qstr($name);
 		$data = $this->Conn->GetRow($sql);
 
 		if ($data) {
 			$lifetime = (int)$data['LifeTime']; // in seconds
 			if (($lifetime > 0) && ($data['Cached'] + $lifetime < adodb_mktime())) {
 				// delete expired
 				$this->Conn->nextQueryCachable = true;
 
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache
 						WHERE VarName = ' . $this->Conn->qstr($name);
 				$this->Conn->Query($sql);
 
 				return false;
 			}
 
 			return $data['Data'];
 		}
 
 		return false;
 	}
 
 	/**
 	 * Deletes key from cache
 	 *
 	 * @param string $key
 	 */
 	function deleteCache($key)
 	{
 		$this->memoryCache->delete($key);
 	}
 
 	/**
 	 * Deletes key from database cache
 	 *
 	 * @param string $name
 	 */
 	function deleteDBCache($name)
 	{
 		$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache
 				WHERE VarName = ' . $this->Conn->qstr($name);
 		$this->Conn->Query($sql);
 	}
 
 	/**
 	* Defines default constants if it's not defined before - in config.php
 	*
 	* @access private
 	*/
 	function SetDefaultConstants() // it's defined in startup.php - can be removed??
 	{
 		safeDefine('SERVER_NAME', $_SERVER['HTTP_HOST']);
 	}
 
 	/**
 	 * Registers each module specific constants if any found
 	 *
 	 */
 	function registerModuleConstants()
 	{
 		if (file_exists(KERNEL_PATH.'/constants.php')) {
 			k4_include_once(KERNEL_PATH.'/constants.php');
 		}
 
 		if (!$this->ModuleInfo) {
 			return false;
 		}
 
 		foreach ($this->ModuleInfo as $module_name => $module_info) {
 			$contants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php';
 
 			if (file_exists($contants_file)) {
 				k4_include_once($contants_file);
 			}
 		}
 
 		return true;
 	}
 
 	function ProcessRequest()
 	{
 		$event_manager =& $this->recallObject('EventManager');
 		/* @var $event_manager kEventManager */
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_SHOW_HTTPQUERY')) {
 			$this->Debugger->appendHTML('HTTPQuery:');
 			$this->Debugger->dumpVars($this->HttpQuery->_Params);
 		}
 
 		$event_manager->ProcessRequest();
 		$event_manager->RunRegularEvents(reBEFORE);
 		$this->RequestProcessed =  true;
 	}
 
 	/**
 	* Actually runs the parser against current template and stores parsing result
 	*
 	* This method gets t variable passed to the script, loads the template given in t variable and
 	* parses it. The result is store in {@link $this->HTML} property.
 	* @access public
 	* @return void
 	*/
 	function Run()
 	{
 		if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) {
 			$this->Debugger->appendMemoryUsage('Application before Run:');
 		}
 
 		if ($this->isAdminUser) {
 			// for permission checking in events & templates
 			$this->LinkVar('module');				// for common configuration templates
 			$this->LinkVar('module_key');			// for common search templates
 			$this->LinkVar('section');				// for common configuration templates
 
 			if ($this->GetVar('m_opener') == 'p') {
 				$this->LinkVar('main_prefix');		// window prefix, that opened selector
 				$this->LinkVar('dst_field');		// field to set value choosed in selector
 			}
 
 			if ($this->GetVar('ajax') == 'yes' && !$this->GetVar('debug_ajax')) {
 				// hide debug output from ajax requests automatically
 				define('DBG_SKIP_REPORTING', 1);
 			}
 		}
 		elseif ($this->GetVar('admin')) {
 			// viewing front-end through admin's frame
 			$admin_session =& $this->Application->recallObject('Session.admin');
 			$user = (int)$admin_session->RecallVar('user_id'); // in case, when no valid admin session found
 			$perm_helper =& $this->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
-			if ($perm_helper->CheckUserPermission($user, 'CATEGORY.MODIFY', 0, $this->ModuleInfo['Core']['RootCat'])) {
+			if ($perm_helper->CheckUserPermission($user, 'CATEGORY.MODIFY', 0, $this->getBaseCategory())) {
 				// user can edit cms blocks
 				$editing_mode = $this->GetVar('editing_mode');
 				define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE);
 			}
 		}
 
 		safeDefine('EDITING_MODE', ''); // user can't edit anything
 		$this->Phrases->setPhraseEditing();
 
 		if (!$this->RequestProcessed) $this->ProcessRequest();
 
 		$this->InitParser();
 		$t = $this->GetVar('t');
 
 		if (!$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin) {
 			$cms_handler =& $this->recallObject('st_EventHandler');
 			/* @var $cms_handler CategoriesEventHandler */
 
 			$t = ltrim($cms_handler->GetDesignTemplate(), '/');
 
 			if (defined('DEBUG_MODE') && $this->isDebugMode()) {
 				$this->Debugger->appendHTML('<strong>Design Template</strong>: ' . $t . '; <strong>CategoryID</strong>: ' . $this->GetVar('m_cat_id'));
 			}
 		}
 		/*else {
 			$cms_handler->SetCatByTemplate();
 		}*/
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) {
 			$this->Debugger->appendMemoryUsage('Application before Parsing:');
 		}
 
 		$this->HTML = $this->Parser->Run($t);
 
 		if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) {
 			$this->Debugger->appendMemoryUsage('Application after Parsing:');
 		}
 	}
 
 	function InitParser($theme_name = false)
 	{
 		if( !is_object($this->Parser) ) {
 			$this->Parser =& $this->recallObject('NParser');
 			$this->TemplatesCache =& $this->recallObject('TemplatesCache');
 		}
 
 		$this->TemplatesCache->forceThemeName = $theme_name;
 	}
 
 	/**
 	* Send the parser results to browser
 	*
 	* Actually send everything stored in {@link $this->HTML}, to the browser by echoing it.
 	* @access public
 	* @return void
 	*/
 	function Done()
 	{
 		$this->HandleEvent( new kEvent('adm:OnBeforeShutdown') );
 
 		$debug_mode = defined('DEBUG_MODE') && $this->isDebugMode();
 
 		if ($debug_mode && constOn('DBG_PROFILE_MEMORY')) {
 			$this->Debugger->appendMemoryUsage('Application before Done:');
 		}
 
 		if ($debug_mode) {
 			$this->EventManager->RunRegularEvents(reAFTER);
 			$this->Session->SaveData();
 
 			if (constOn('DBG_CACHE')) {
 				$this->memoryCache->printStatistics();
 			}
 
 			$this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true);
 		}
 		else {
 			// send "Set-Cookie" header before any output is made
 			$this->Session->SetSession();
 
 			$this->HTML = ob_get_clean() . $this->HTML;
 		}
 
 		if ($this->UseOutputCompression()) {
 			$compression_level = $this->ConfigValue('OutputCompressionLevel');
 			if ($compression_level < 0 || $compression_level > 9) {
 				$compression_level = 7;
 			}
 
 			header('Content-Encoding: gzip');
 			echo gzencode($this->HTML, $compression_level);
 		}
 		else {
 			echo $this->HTML;
 		}
 
 		$this->UpdateCache();
 		flush();
 
 		if (!$debug_mode) {
 			$this->EventManager->RunRegularEvents(reAFTER);
 			$this->Session->SaveData();
 		}
 
 		if (defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin) {
 			$this->_storeStatistics();
 		}
 	}
 
 	/**
 	 * Stores script execution statistics to database
 	 *
 	 */
 	function _storeStatistics()
 	{
 		global $start;
 
 		$script_time = getmicrotime() - $start;
 		$query_statistics = $this->Conn->getQueryStatistics(); // time & count
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'StatisticsCapture
 				WHERE TemplateName = ' . $this->Conn->qstr( $this->GetVar('t') );
 		$data = $this->Conn->GetRow($sql);
 
 		if ($data) {
 			$this->_updateAverageStatistics($data, 'ScriptTime', $script_time);
 			$this->_updateAverageStatistics($data, 'SqlTime', $query_statistics['time']);
 			$this->_updateAverageStatistics($data, 'SqlCount', $query_statistics['count']);
 
 			$data['Hits']++;
 			$data['LastHit'] = adodb_mktime();
 
 			$this->Conn->doUpdate($data, TABLE_PREFIX . 'StatisticsCapture', 'StatisticsId = ' . $data['StatisticsId']);
 		}
 		else {
 			$data['ScriptTimeMin'] = $data['ScriptTimeAvg'] = $data['ScriptTimeMax'] = $script_time;
 			$data['SqlTimeMin'] = $data['SqlTimeAvg'] = $data['SqlTimeMax'] = $query_statistics['time'];
 			$data['SqlCountMin'] = $data['SqlCountAvg'] = $data['SqlCountMax'] = $query_statistics['count'];
 			$data['TemplateName'] = $this->GetVar('t');
 			$data['Hits'] = 1;
 			$data['LastHit'] = adodb_mktime();
 			$this->Conn->doInsert($data, TABLE_PREFIX . 'StatisticsCapture');
 		}
 	}
 
 	/**
 	 * Calculates average time for statistics
 	 *
 	 * @param Array $data
 	 * @param string $field_prefix
 	 * @param float $current_value
 	 */
 	function _updateAverageStatistics(&$data, $field_prefix, $current_value)
 	{
 		$data[$field_prefix . 'Avg'] = (($data['Hits'] * $data[$field_prefix . 'Avg']) + $current_value) / ($data['Hits'] + 1);
 
 		if ($current_value < $data[$field_prefix . 'Min']) {
 			$data[$field_prefix . 'Min'] = $current_value;
 		}
 
 		if ($current_value > $data[$field_prefix . 'Max']) {
 			$data[$field_prefix . 'Max'] = $current_value;
 		}
 	}
 
 	function logSlowQuery($slow_sql, $time)
 	{
 		$query_crc = crc32($slow_sql);
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'SlowSqlCapture
 				WHERE QueryCrc = ' . $query_crc;
 		$data = $this->Conn->Query($sql, null, true);
 
 		if ($data) {
 			$this->_updateAverageStatistics($data, 'Time', $time);
 
 			$template_names = explode(',', $data['TemplateNames']);
 			array_push($template_names, $this->GetVar('t'));
 			$data['TemplateNames'] = implode(',', array_unique($template_names));
 
 			$data['Hits']++;
 			$data['LastHit'] = adodb_mktime();
 
 			$this->Conn->doUpdate($data, TABLE_PREFIX . 'SlowSqlCapture', 'CaptureId = ' . $data['CaptureId']);
 		}
 		else {
 			$data['TimeMin'] = $data['TimeAvg'] = $data['TimeMax'] = $time;
 			$data['SqlQuery'] = $slow_sql;
 			$data['QueryCrc'] = $query_crc;
 			$data['TemplateNames'] = $this->GetVar('t');
 			$data['Hits'] = 1;
 			$data['LastHit'] = adodb_mktime();
 
 			$this->Conn->doInsert($data, TABLE_PREFIX . 'SlowSqlCapture');
 		}
 	}
 
 	/**
 	 * Checks if output compression options is available
 	 *
 	 * @return string
 	 */
 	function UseOutputCompression()
 	{
 		if (constOn('IS_INSTALL') || constOn('DBG_ZEND_PRESENT') || constOn('SKIP_OUT_COMPRESSION')) return false;
 		return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');
 	}
 
 	//	Facade
 
 	/**
 	* Returns current session id (SID)
 	* @access public
 	* @return longint
 	*/
 	function GetSID()
 	{
 		$session =& $this->recallObject('Session');
 		return $session->GetID();
 	}
 
 	function DestroySession()
 	{
 		$session =& $this->recallObject('Session');
 		$session->Destroy();
 	}
 
 	/**
 	* Returns variable passed to the script as GET/POST/COOKIE
 	*
 	* @access public
 	* @param string $name Name of variable to retrieve
 	* @param int $default default value returned in case if varible not present
 	* @return mixed
 	*/
 	function GetVar($name, $default = false)
 	{
 		return isset($this->HttpQuery->_Params[$name]) ? $this->HttpQuery->_Params[$name] : $default;
 	}
 
 	/**
 	* Returns ALL variables passed to the script as GET/POST/COOKIE
 	*
 	* @access public
 	* @return array
 	*/
 	function GetVars()
 	{
 		return $this->HttpQuery->GetParams();
 	}
 
 	/**
 	* Set the variable 'as it was passed to the script through GET/POST/COOKIE'
 	*
 	* This could be useful to set the variable when you know that
 	* other objects would relay on variable passed from GET/POST/COOKIE
 	* or you could use SetVar() / GetVar() pairs to pass the values between different objects.<br>
 	*
 	* This method is formerly known as $this->Session->SetProperty.
 	* @param string $var Variable name to set
 	* @param mixed $val Variable value
 	* @access public
 	* @return void
 	*/
 	function SetVar($var,$val)
 	{
 		return $this->HttpQuery->Set($var, $val);
 	}
 
 	/**
 	 * Deletes kHTTPQuery variable
 	 *
 	 * @param string $var
 	 * @todo think about method name
 	 */
 	function DeleteVar($var)
 	{
 		return $this->HttpQuery->Remove($var);
 	}
 
 	/**
 	 * Deletes Session variable
 	 *
 	 * @param string $var
 	 */
 	function RemoveVar($var)
 	{
 		return $this->Session->RemoveVar($var);
 	}
 
 	function RemovePersistentVar($var)
 	{
 		return $this->Session->RemovePersistentVar($var);
 	}
 
 	/**
 	 * Restores Session variable to it's db version
 	 *
 	 * @param string $var
 	 */
 	function RestoreVar($var)
 	{
 		return $this->Session->RestoreVar($var);
 	}
 
 	/**
 	* Returns session variable value
 	*
 	* Return value of $var variable stored in Session. An optional default value could be passed as second parameter.
 	*
 	* @see SimpleSession
 	* @access public
 	* @param string $var Variable name
 	* @param mixed $default Default value to return if no $var variable found in session
 	* @return mixed
 	*/
 	function RecallVar($var,$default=false)
 	{
 		return $this->Session->RecallVar($var,$default);
 	}
 
 	function RecallPersistentVar($var, $default = false)
 	{
 		return $this->Session->RecallPersistentVar($var, $default);
 	}
 
 	/**
 	* Stores variable $val in session under name $var
 	*
 	* Use this method to store variable in session. Later this variable could be recalled.
 	* @see RecallVar
 	* @access public
 	* @param string $var Variable name
 	* @param mixed $val Variable value
 	*/
 	function StoreVar($var, $val, $optional = false)
 	{
 		$session =& $this->recallObject('Session');
 		$this->Session->StoreVar($var, $val, $optional);
 	}
 
 	function StorePersistentVar($var, $val, $optional = false)
 	{
 		$this->Session->StorePersistentVar($var, $val, $optional);
 	}
 
 	function StoreVarDefault($var, $val, $optional=false)
 	{
 		$session =& $this->recallObject('Session');
 		$this->Session->StoreVarDefault($var, $val, $optional);
 	}
 
 	/**
 	* Links HTTP Query variable with session variable
 	*
 	* If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session.
 	* This method could be used for making sure that GetVar will return query or session value for given
 	* variable, when query variable should overwrite session (and be stored there for later use).<br>
 	* This could be used for passing item's ID into popup with multiple tab -
 	* in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id').
 	* After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session
 	* @access public
 	* @param string $var HTTP Query (GPC) variable name
 	* @param mixed $ses_var Session variable name
 	* @param mixed $default Default variable value
 	*/
 	function LinkVar($var, $ses_var = null, $default = '', $optional = false)
 	{
 		if (!isset($ses_var)) $ses_var = $var;
 		if ($this->GetVar($var) !== false) {
 			$this->StoreVar($ses_var, $this->GetVar($var), $optional);
 		}
 		else {
 			$this->SetVar($var, $this->RecallVar($ses_var, $default));
 		}
 	}
 
 	/**
 	* Returns variable from HTTP Query, or from session if not passed in HTTP Query
 	*
 	* The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed.
 	* Returns the default value if variable does not exist in session and was not passed in HTTP Query
 	*
 	* @see LinkVar
 	* @access public
 	* @param string $var HTTP Query (GPC) variable name
 	* @param mixed $ses_var Session variable name
 	* @param mixed $default Default variable value
 	* @return mixed
 	*/
 	function GetLinkedVar($var, $ses_var = null, $default = '')
 	{
 		$this->LinkVar($var, $ses_var, $default);
 		return $this->GetVar($var);
 	}
 
 	function AddBlock($name, $tpl)
 	{
 		$this->cache[$name] = $tpl;
 	}
 
 	function ProcessParsedTag($prefix, $tag, $params)
 	{
 		$processor = $this->Parser->GetProcessor($prefix);
 
 		return $processor->ProcessParsedTag($tag, $params, $prefix);
 	}
 
 	/**
 	* Return ADODB Connection object
 	*
 	* Returns ADODB Connection object already connected to the project database, configurable in config.php
 	* @access public
 	* @return kDBConnection
 	*/
 	function &GetADODBConnection()
 	{
 		return $this->Conn;
 	}
 
 	/**
 	 * Allows to parse given block name or include template
 	 *
 	 * @param Array $params Parameters to pass to block. Reserved parameter "name" used to specify block name.
 	 * @param Array $pass_params Forces to pass current parser params to this block/template. Use with cauntion, because you can accidently pass "block_no_data" parameter.
 	 * @param bool $as_template
 	 * @return string
 	 */
 	function ParseBlock($params, $pass_params = 0, $as_template = false)
 	{
 		if (substr($params['name'], 0, 5) == 'html:') {
 			return substr($params['name'], 6);
 		}
 
 		return $this->Parser->ParseBlock($params, $pass_params, $as_template);
 	}
 
 	/**
 	 * Checks, that we have given block defined
 	 *
 	 * @param string $name
 	 * @return bool
 	 */
 	function ParserBlockFound($name)
 	{
 		return $this->Parser->blockFound($name);
 	}
 
 	/**
 	 * Allows to include template with a given name and given parameters
 	 *
 	 * @param Array $params Parameters to pass to template. Reserved parameter "name" used to specify template name.
 	 * @return string
 	 */
 	function IncludeTemplate($params)
 	{
 		return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0);
 	}
 
 	/**
 	 * Returns index file, that could be passed as parameter to method, as parameter to tag and as constant or not passed at all
 	 *
 	 * @param string $prefix
 	 * @param string $index_file
 	 * @param Array $params
 	 * @return string
 	 */
 	function getIndexFile($prefix, $index_file, &$params)
 	{
 		if (isset($params['index_file'])) {
 			$index_file = $params['index_file'];
 			unset($params['index_file']);
 			return $index_file;
 		}
 
 		if (isset($index_file)) {
 			return $index_file;
 		}
 
 		if (defined('INDEX_FILE')) {
 			return INDEX_FILE;
 		}
 
 		$cut_prefix = trim(BASE_PATH, '/').'/'.trim($prefix, '/');
 		return trim(preg_replace('/'.preg_quote($cut_prefix, '/').'(.*)/', '\\1', $_SERVER['PHP_SELF']), '/');
 	}
 
 	/**
 	* Return href for template
 	*
 	* @access public
 	* @param string $t Template path
 	* @var string $prefix index.php prefix - could be blank, 'admin'
 	*/
 	function HREF($t, $prefix = '', $params = null, $index_file = null)
 	{
 		static $theme_id = null;
 
 		if (!isset($theme_id)) {
 			$theme_id = $this->GetVar('m_theme');
 		}
 
 		if (!$t) {
 			// when template not specified, use current
 			$t = $this->GetVar('t');
 		}
 
 		$t = preg_replace('/^Content\//i', '', $t);
 
 		if (substr($t, -4) == '.tpl') {
 			// cut template extension (deprecated link format)
 			$t = substr($t, 0, strlen($t) - 4);
 		}
 
 		if (substr($t, 0, 3) == 'id:') {
 			// link to structure page using it's id
 			$params['m_cat_id'] = substr($t, 3);
 			$t = $this->structureTemplateMapping[$t];
 		}
 
 		if (array_key_exists('use_section', $params)) {
 			$use_section = $params['use_section'];
 			unset($params['use_section']);
 		}
 
 		if (isset($use_section) && $use_section && array_key_exists($t . ':' . $theme_id, $this->structureTemplateMapping)) {
 			// structure template corresponding to given physical template
 			$t = $this->structureTemplateMapping[$t . ':' . $theme_id];
 			unset($params['use_section']);
 		}
 
 		if (preg_match('/external:(.*)/', $t, $rets)) {
 			// external url
 			return $rets[1];
 		}
 
 		if ($this->isAdmin && $prefix == '') $prefix = ADMIN_DIRECTORY;
 		if ($this->isAdmin && $prefix == '_FRONT_END_') $prefix = '';
 
 		$index_file = $this->getIndexFile($prefix, $index_file, $params);
 
 		if (isset($params['_auto_prefix_'])) {
 			unset($params['_auto_prefix_']); // this is parser-related param, do not need to pass it here
 		}
 
 		$ssl = isset($params['__SSL__']) ? $params['__SSL__'] : null;
 		if ($ssl !== null) {
 			$session =& $this->recallObject('Session');
 			/* @var $session Session */
 
 			$target_url = rtrim($this->BaseURL('', $ssl, false), '/');
 			$cookie_url = trim($session->CookieDomain . $session->CookiePath, '/.');
 
 			// set session to GET_ONLY, to pass sid only if sid is REAL AND session is set
 			if (!preg_match('#' . preg_quote($cookie_url) . '#', $target_url) && $session->SessionSet) {
 				// when SSL<->NON-SSL redirect to different domain pass SID in url
  				$session->SetMode(smGET_ONLY);
  			}
 		}
 
 		if (isset($params['opener']) && $params['opener'] == 'u') {
 			$wid = $this->Application->GetVar('m_wid');
 			$stack_name = rtrim('opener_stack_'.$wid, '_');
 			$opener_stack = $this->RecallVar($stack_name);
 
 			if ($opener_stack && $opener_stack != serialize(Array())) {
 				$opener_stack = unserialize($opener_stack);
 				list($index_file, $env) = explode('|', $opener_stack[count($opener_stack) - 1]);
 				$ret = $this->BaseURL($prefix, $ssl).$index_file.'?'.ENV_VAR_NAME.'='.$env;
 				if ( getArrayValue($params,'escape') ) $ret = addslashes($ret);
 
 				if (isset($params['m_opener']) && $params['m_opener'] == 'u') {
 					array_pop($opener_stack);
 					if (!$opener_stack) {
 						$this->RemoveVar($stack_name);
 						// remove popups last templates, because popup is closing now
 						$this->RemoveVar('last_template_'.$wid);
 						$this->RemoveVar('last_template_popup_'.$wid);
 
 						// don't save popups last templates again :)
 						$this->SetVar('skip_last_template', 1);
 					}
 					else {
 						$this->StoreVar($stack_name, serialize($opener_stack));
 					}
 
 					/*// store window relations
 					$window_relations = $this->Application->RecallVar('window_relations');
 					$window_relations = $window_relations ? unserialize($window_relations) : Array ();
 					if (array_key_exists($wid, $window_relations)) {
 						unset($window_relations[$wid]);
 						$this->Application->StoreVar('window_relations', serialize($window_relations));
 					}*/
 				}
 				return $ret;
 			}
 			else {
 				//define('DBG_REDIRECT', 1);
 				$t = $this->GetVar('t');
 			}
 		}
 
 		$pass = isset($params['pass']) ? $params['pass'] : '';
 
 		// pass events with url
 		$pass_events = false;
 		if( isset($params['pass_events']) )
 		{
 			$pass_events = $params['pass_events'];
 			unset($params['pass_events']);
 		}
 
 		$map_link = '';
 		if( isset($params['anchor']) )
 		{
 			$map_link = '#'.$params['anchor'];
 			unset($params['anchor']);
 		}
 
 		if ( isset($params['no_amp']) )
 		{
 			$params['__URLENCODE__'] = $params['no_amp'];
 			unset($params['no_amp']);
 		}
 
 		$no_rewrite = false;
 		if( isset($params['__NO_REWRITE__']) )
 		{
 			$no_rewrite = true;
 			unset($params['__NO_REWRITE__']);
 		}
 
 		$force_rewrite = false;
 		if( isset($params['__MOD_REWRITE__']) )
 		{
 			$force_rewrite = true;
 			unset($params['__MOD_REWRITE__']);
 		}
 
 		$force_no_sid = false;
 		if( isset($params['__NO_SID__']) )
 		{
 			$force_no_sid = true;
 			unset($params['__NO_SID__']);
 		}
 
 		// append pass through variables to each link to be build
 		// $params = array_merge_recursive2($this->getPassThroughVariables($params), $params);
 		$params = array_merge($this->getPassThroughVariables($params), $params);
 
 		if ($force_rewrite || ($this->RewriteURLs($ssl) && !$no_rewrite)) {
 			static $rewrite_listeners_done = false;
 
 			if (!$rewrite_listeners_done) {
 				$mod_rewrite_helper =& $this->recallObject('ModRewriteHelper');
 				/* @var $mod_rewrite_helper kModRewriteHelper */
 
 				$mod_rewrite_helper->initRewriteListeners();
 
 				$rewrite_listeners_done = true;
 			}
 
 			$session =& $this->recallObject('Session');
 
 			if ($session->NeedQueryString() && !$force_no_sid) {
 				$params['sid'] = $this->GetSID();
 			}
 
 			$url = $this->BuildEnv_NEW($t, $params, $pass, $pass_events);
 			$ret = $this->BaseURL($prefix, $ssl).$url.$map_link;
 		}
 		else {
 			unset($params['pass_category']); // we don't need to pass it when mod_rewrite is off
 			$env = $this->BuildEnv($t, $params, $pass, $pass_events);
 			$ret = $this->BaseURL($prefix, $ssl).$index_file.'?'.$env.$map_link;
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns variables with values that should be passed throught with this link + variable list
 	 *
 	 * @param Array $params
 	 * @return Array
 	 */
 	function getPassThroughVariables(&$params)
 	{
 		static $cached_pass_through = null;
 
 		if (isset($params['no_pass_through']) && $params['no_pass_through']) {
 			unset($params['no_pass_through']);
 			return Array();
 		}
 
 		// because pass through is not changed during script run, then we can cache it
 		if (is_null($cached_pass_through)) {
 
 			$cached_pass_through = Array();
 			$pass_through = $this->Application->GetVar('pass_through');
 
 			if ($pass_through) {
 				// names of variables to pass to each link
 				$cached_pass_through['pass_through'] = $pass_through;
 				$pass_through = explode(',', $pass_through);
 				foreach ($pass_through as $pass_through_var) {
 					$cached_pass_through[$pass_through_var] = $this->Application->GetVar($pass_through_var);
 				}
 			}
 
 		}
 
 		return $cached_pass_through;
 	}
 
 
 	/**
 	 * Returns sorted array of passed prefixes (to build url from)
 	 *
 	 * @param string $pass
 	 * @return Array
 	 */
 	function getPassInfo($pass = 'all')
 	{
 		if (!$pass) $pass = 'all';
 		$pass = trim(
 				preg_replace(
 					'/(?<=,|\\A)all(?=,|\\z)/',
 					trim($this->GetVar('passed'), ','),
 					trim($pass, ',')
 				),
 		 ',');
 
 		if (!$pass) {
 			return Array();
 		}
 
 		$pass_info = array_unique( explode(',', $pass) ); // array( prefix[.special], prefix[.special] ...
 
 		// we need to keep that sorting despite the sorting below, because this sorts prefixes with same priority by name
 		sort($pass_info, SORT_STRING); // to be prefix1,prefix1.special1,prefix1.special2,prefix3.specialX
 
 		foreach ($pass_info as $prefix) {
 			list ($prefix_only, ) = explode('.', $prefix, 2);
 			$sorted[$prefix] = $this->getUnitOption($prefix_only, 'RewritePriority', 0);
 		}
 
 		asort($sorted, SORT_NUMERIC);
 		$pass_info = array_keys($sorted);
 
 		// ensure that "m" prefix is at the beginning
 		$main_index = array_search('m', $pass_info);
 		if ($main_index !== false) {
 			unset($pass_info[$main_index]);
 			array_unshift($pass_info, 'm');
 		}
 		return $pass_info;
 	}
 
 	function BuildEnv_NEW($t, $params, $pass='all', $pass_events = false)
 	{
 		if ($this->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin'])) {
 			$params['admin'] = 1;
 
 			if (!array_key_exists('editing_mode', $params)) {
 				$params['editing_mode'] = EDITING_MODE;
 			}
 		}
 
 		$ret = '';
 		$env = '';
 
 		$encode = false;
 
 		if (isset($params['__URLENCODE__'])) {
 			$encode = $params['__URLENCODE__'];
 			unset($params['__URLENCODE__']);
 		}
 
 		if (isset($params['__SSL__'])) {
 			unset($params['__SSL__']);
 		}
 
 		$catalog_item_found = false;
 		$pass_info = $this->getPassInfo($pass);
 
 		if ($pass_info) {
 			if ($pass_info[0] == 'm') {
 				array_shift($pass_info);
 			}
 
 			$inject_parts = Array (); // url parts for beginning of url
 			$params['t'] = $t; // make template available for rewrite listeners
 			$params['pass_template'] = true; // by default we keep given template in resulting url
 
 			if (!array_key_exists('pass_category', $params)) {
 				$params['pass_category'] = false; // by default we don't keep categories in url
 			}
 
 			foreach ($pass_info as $pass_index => $pass_element) {
 				list ($prefix) = explode('.', $pass_element);
 				$catalog_item = $this->findModule('Var', $prefix) && $this->getUnitOption($prefix, 'CatalogItem');
 
 				if (array_key_exists($prefix, $this->RewriteListeners)) {
 					// if next prefix is same as current, but with special => exclude current prefix from url
 					$next_prefix = array_key_exists($pass_index + 1, $pass_info) ? $pass_info[$pass_index + 1] : false;
 					if ($next_prefix) {
 						$next_prefix = substr($next_prefix, 0, strlen($prefix) + 1);
 						if ($prefix . '.' == $next_prefix) {
 							continue;
 						}
 					}
 
 					// rewrited url part
 					$url_part = $this->BuildModuleEnv_NEW($pass_element, $params, $pass_events);
 
 					if (is_string($url_part) && $url_part) {
 						$ret .= $url_part . '/';
 
 						if ($catalog_item) {
 							// pass category later only for catalog items
 							$catalog_item_found = true;
 						}
 					}
 					elseif (is_array($url_part)) {
 						// rewrite listener want to insert something at the beginning of url too
 						if ($url_part[0]) {
 							$inject_parts[] = $url_part[0];
 						}
 
 						if ($url_part[1]) {
 							$ret .= $url_part[1] . '/';
 						}
 
 						if ($catalog_item) {
 							// pass category later only for catalog items
 							$catalog_item_found = true;
 						}
 					} elseif ($url_part === false) {
 						// rewrite listener decided not to rewrite given $pass_element
 						$env .= ':' . $this->BuildModuleEnv($pass_element, $params, $pass_events);
 					}
 				}
 				else {
 					$env .= ':' . $this->BuildModuleEnv($pass_element, $params, $pass_events);
 				}
 			}
 
 			if ($catalog_item_found || preg_match('/c\.[-\d]*/', implode(',', $pass_info))) {
 				// "c" prefix is present -> keep category
 				$params['pass_category'] = true;
 			}
 
 			$params['inject_parts'] = $inject_parts;
 
 			$ret = $this->BuildModuleEnv_NEW('m', $params, $pass_events) . '/' . $ret;
 			$cat_processed = array_key_exists('category_processed', $params) && $params['category_processed'];
 
 			// remove tempporary parameters used by listeners
 			unset($params['t'], $params['inject_parts'], $params['pass_template'], $params['pass_category'], $params['category_processed']);
 
 			if (array_key_exists('url_ending', $params)) {
 				$ret = trim($ret, '/') . $params['url_ending'];
 				unset($params['url_ending']);
 			}
 			else {
 				$ret = trim($ret, '/') . MOD_REWRITE_URL_ENDING;
 			}
 
 			if ($env) {
 				$params[ENV_VAR_NAME] = ltrim($env, ':');
 			}
 		}
 
 		unset($params['pass'], $params['opener'], $params['m_event']);
 
 		if (array_key_exists('escape', $params) && $params['escape']) {
 			$ret = addslashes($ret);
 			unset($params['escape']);
 		}
 
 		$ret = str_replace('%2F', '/', urlencode($ret));
 
 		$params_str = '';
 		$join_string = $encode ? '&' : '&amp;';
 
 		foreach ($params as $param => $value) {
 			$params_str .= $join_string . $param . '=' . $value;
 		}
 
 		if ($params_str) {
 			$ret .= '?' . substr($params_str, strlen($join_string));
 		}
 
 		if ($encode) {
 			$ret = str_replace('\\', '%5C', $ret);
 		}
 
 		return $ret;
 	}
 
 	function BuildModuleEnv_NEW($prefix_special, &$params, $keep_events = false)
 	{
 		list ($prefix) = explode('.', $prefix_special);
 
 		$url_parts = Array ();
 		$listener = $this->RewriteListeners[$prefix][0];
 
 		$ret = $listener[0]->$listener[1](REWRITE_MODE_BUILD, $prefix_special, $params, $url_parts, $keep_events);
 
 		return $ret;
 	}
 
 	/**
 	 * Builds env part that corresponds prefix passed
 	 *
 	 * @param string $prefix_special item's prefix & [special]
 	 * @param Array $params url params
 	 * @param bool $pass_events
 	 */
 	function BuildModuleEnv($prefix_special, &$params, $pass_events = false)
 	{
 		list($prefix) = explode('.', $prefix_special);
 		$query_vars = $this->getUnitOption($prefix, 'QueryString');
 
 		//if pass events is off and event is not implicity passed
 		if( !$pass_events && !isset($params[$prefix_special.'_event']) ) {
 			$params[$prefix_special.'_event'] = ''; // remove event from url if requested
 			//otherwise it will use value from get_var
 		}
 
 		if(!$query_vars) return '';
 
 		$tmp_string = Array(0 => $prefix_special);
 		foreach($query_vars as $index => $var_name)
 		{
 			//if value passed in params use it, otherwise use current from application
 			$var_name = $prefix_special.'_'.$var_name;
 			$tmp_string[$index] =  isset( $params[$var_name] ) ? $params[$var_name] : $this->GetVar($var_name);
 			if ( isset($params[$var_name]) ) unset( $params[$var_name] );
 		}
 
 		$escaped = array();
 		foreach ($tmp_string as $tmp_val) {
 			$escaped[] = str_replace(Array('-',':'), Array('\-','\:'), $tmp_val);
 		}
 
 		$ret = implode('-', $escaped);
 		if ($this->getUnitOption($prefix, 'PortalStyleEnv') == true)
 		{
 			$ret = preg_replace('/^([a-zA-Z]+)-([0-9]+)-(.*)/','\\1\\2-\\3', $ret);
 		}
 		return $ret;
 	}
 
 	function BuildEnv($t, $params, $pass='all', $pass_events = false, $env_var = true)
 	{
 		if ($this->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin'])) {
 			$params['admin'] = 1;
 
 			if (!array_key_exists('editing_mode', $params)) {
 				$params['editing_mode'] = EDITING_MODE;
 			}
 		}
 
 		$session =& $this->recallObject('Session');
 		$ssl = isset($params['__SSL__']) ? $params['__SSL__'] : 0;
 		$sid = $session->NeedQueryString() && !$this->RewriteURLs($ssl) ? $this->GetSID() : '';
 //		if (getArrayValue($params,'admin') == 1) $sid = $this->GetSID();
 
 		$ret = '';
 		if ($env_var) {
 			$ret = ENV_VAR_NAME.'=';
 		}
 
 		$ret .=	$sid . '-'; // SID-TEMPLATE
 
 		$encode = false;
 		if (isset($params['__URLENCODE__'])) {
 			$encode = $params['__URLENCODE__'];
 			unset($params['__URLENCODE__']);
 		}
 
 		if (isset($params['__SSL__'])) {
 			unset($params['__SSL__']);
 		}
 
 		$env_string = '';
 		$category_id = isset($params['m_cat_id']) ? $params['m_cat_id'] : $this->GetVar('m_cat_id');
 
 		$item_id = false;
 		$pass_info = $this->getPassInfo($pass);
 		if ($pass_info) {
 			if ($pass_info[0] == 'm') array_shift($pass_info);
 			foreach ($pass_info as $pass_element) {
 				list($prefix) = explode('.', $pass_element);
 				$require_rewrite = $this->findModule('Var', $prefix);
 				if ($require_rewrite) {
 					$item_id = isset($params[$pass_element.'_id']) ? $params[$pass_element.'_id'] : $this->GetVar($pass_element.'_id');
 				}
 				$env_string .= ':'.$this->BuildModuleEnv($pass_element, $params, $pass_events);
 			}
 		}
 
 		if (strtolower($t) == '__default__') {
 			if (is_numeric($item_id)) {
 				$mod_rw_helper =& $this->Application->recallObject('ModRewriteHelper');
 				/* @var $mod_rw_helper kModRewriteHelper */
 
 				$t = $mod_rw_helper->GetItemTemplate($category_id, $pass_element); // $pass_element should be the last processed element
 				// $t = $this->getCategoryCache($category_id, 'item_templates');
 			}
 			elseif ($category_id) {
 				$t = strtolower(preg_replace('/^Content\//i', '', $this->getCategoryCache($category_id, 'filenames') ));
 			}
 			else {
 				$t = 'index';
 			}
 		}
 
 		$ret .= $t.':'.$this->BuildModuleEnv('m', $params, $pass_events).$env_string;
 
 		unset($params['pass'], $params['opener'], $params['m_event']);
 
 		if (array_key_exists('escape', $params) && $params['escape']) {
 			$ret = addslashes($ret);
 			unset($params['escape']);
 		}
 
 		$join_string = $encode ? '&' : '&amp;';
 		$params_str = '';
 		foreach ($params as $param => $value)
 		{
 			$params_str .= $join_string.$param.'='.$value;
 		}
 		$ret .= $params_str;
 
 		if ($encode) {
 			$ret = str_replace('\\', '%5C', $ret);
 		}
 		return $ret;
 	}
 
 	function BaseURL($prefix = '', $ssl = null, $add_port = true)
 	{
 		if ($ssl === null) {
 			// stay on same encryption level
 			return PROTOCOL . SERVER_NAME . ($add_port && defined('PORT') ? ':' . PORT : '') . rtrim(BASE_PATH, '/') . $prefix . '/';
 		}
 
 		if ($ssl) {
 			// going from http:// to https://
 			$base_url = $this->isAdmin ? $this->ConfigValue('AdminSSL_URL') : false;
 
 			if (!$base_url) {
 				$ssl_url = $this->siteDomainField('SSLUrl');
 				$base_url = $ssl_url !== false ? $ssl_url : $this->ConfigValue('SSL_URL');
 			}
 
 			return rtrim($base_url, '/') . $prefix . '/';
 		}
 
 		// going from https:// to http://
 		$domain = $this->siteDomainField('DomainName');
 
 		if ($domain === false) {
 			$domain = DOMAIN;
 		}
 
 		return 'http://' . $domain . ($add_port && defined('PORT') ? ':' . PORT : '') . rtrim($this->ConfigValue('Site_Path'), '/') . $prefix . '/';
 	}
 
 	function Redirect($t = '', $params = Array(), $prefix = '', $index_file = null)
 	{
 		$js_redirect = getArrayValue($params, 'js_redirect');
 
 		if ($t == '' || $t === true) {
 			$t = $this->GetVar('t');
 		}
 
 		// pass prefixes and special from previous url
 		if (array_key_exists('js_redirect', $params)) {
 			unset($params['js_redirect']);
 		}
 
 		// allows to send custom responce code along with redirect header
 		if (array_key_exists('response_code', $params)) {
 			$response_code = (int)$params['response_code'];
 			unset($params['response_code']);
 		}
 		else {
 			$response_code = 302; // Found
 		}
 
 		if (!array_key_exists('pass', $params)) {
 			$params['pass'] = 'all';
 		}
 
 		if ($this->GetVar('ajax') == 'yes' && $t == $this->GetVar('t')) {
 			// redirects to the same template as current
 			$params['ajax'] = 'yes';
 		}
 
 		$params['__URLENCODE__'] = 1;
 		$location = $this->HREF($t, $prefix, $params, $index_file);
 
 		if ($this->isDebugMode() && (constOn('DBG_REDIRECT') || (constOn('DBG_RAISE_ON_WARNINGS') && $this->Application->Debugger->WarningCount))) {
 			$this->Debugger->appendTrace();
 			echo '<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 true;
 			}
 			else {
 				if ($this->GetVar('ajax') == 'yes' && $t != $this->GetVar('t')) {
 					// redirection to other then current template during ajax request
 					echo '#redirect#' . $location;
 				}
 				elseif (headers_sent() != '') {
 					// some output occured -> redirect using javascript
 					echo '<script type="text/javascript">window.location.href = \'' . $location . '\';</script>';
 				}
 				else {
 					// no output before -> redirect using HTTP header
 
 //					header('HTTP/1.1 302 Found');
 					header('Location: ' . $location, true, $response_code);
 				}
 			}
 		}
 
 		ob_end_flush();
 		// session expiration is called from session initialization,
 		// that's why $this->Session may be not defined here
 		$session =& $this->Application->recallObject('Session');
 		/* @var $session Session */
 
 		$this->HandleEvent( new kEvent('adm:OnBeforeShutdown') );
 		$session->SaveData();
 		exit;
 	}
 
 	function Phrase($label, $allow_editing = true, $use_admin = false)
 	{
 		return $this->Phrases->GetPhrase($label, $allow_editing, $use_admin);
 	}
 
 	/**
 	 * Replace language tags in exclamation marks found in text
 	 *
 	 * @param string $text
 	 * @param bool $force_escape force escaping, not escaping of resulting string
 	 * @return string
 	 * @access public
 	 */
 	function ReplaceLanguageTags($text, $force_escape=null)
 	{
 		// !!!!!!!!
 //		if( !is_object($this->Phrases) ) $this->Debugger->appendTrace();
 		return $this->Phrases->ReplaceLanguageTags($text,$force_escape);
 	}
 
 	/**
 	 * Checks if user is logged in, and creates
 	 * user object if so. User object can be recalled
 	 * later using "u.current" prefix_special. Also you may
 	 * get user id by getting "u.current_id" variable.
 	 *
 	 * @access private
 	 */
 	function ValidateLogin()
 	{
 		$session =& $this->recallObject('Session');
 		$user_id = $session->GetField('PortalUserId');
 
 		if (!$user_id && $user_id != USER_ROOT) {
 			$user_id = USER_GUEST;
 		}
 
 		$this->SetVar('u.current_id', $user_id);
 
 		if (!$this->isAdmin) {
 			// needed for "profile edit", "registration" forms ON FRONT ONLY
 			$this->SetVar('u_id', $user_id);
 		}
 
 		$this->StoreVar('user_id', $user_id, $user_id == USER_GUEST); // storing Guest user_id (-2) is optional
 
 		$this->isAdminUser = $this->isAdmin && $this->LoggedIn();
 
 		if ($this->GetVar('expired') == 1) {
 			// this parameter is set only from admin
 			$user =& $this->recallObject('u.current');
 			$user->SetError('ValidateLogin', 'session_expired', 'la_text_sess_expired');
 		}
 
 		if (($user_id != USER_GUEST) && constOn('DBG_REQUREST_LOG') ) {
 			$http_query =& $this->recallObject('HTTPQuery');
 			$http_query->writeRequestLog(DBG_REQUREST_LOG);
 		}
 
 		if ($user_id != USER_GUEST) {
 			// normal users + root
 			$this->LoadPersistentVars();
 		}
 	}
 
 	/**
 	 * Loads current user persistent session data
 	 *
 	 */
 	function LoadPersistentVars()
 	{
 		$this->Session->LoadPersistentVars();
 	}
 
 	function LoadCache()
 	{
 		// TODO: maybe language part isn't required, since same phrase from different languages have one ID now
 		$cache_key = $this->GetVar('t') . $this->GetVar('m_theme') . $this->GetVar('m_lang') . $this->isAdmin;
 
 		$sql = 'SELECT PhraseList, ConfigVariables
 				FROM ' . TABLE_PREFIX . 'PhraseCache
 				WHERE Template = ' . $this->Conn->qstr( md5($cache_key) );
 		$res = $this->Conn->GetRow($sql);
 
 		if ($res) {
 			$this->Caches['PhraseList'] = $res['PhraseList'] ? explode(',', $res['PhraseList']) : Array ();
 			$config_ids = $res['ConfigVariables'] ? explode(',', $res['ConfigVariables']) : Array ();
 
 			if (isset($this->Caches['ConfigVariables'])) {
 				$config_ids = array_diff($config_ids, $this->Caches['ConfigVariables']);
 			}
 		}
 		else {
 			$config_ids = Array ();
 		}
 
 		$this->Phrases->Init('phrases');
 		$this->Caches['ConfigVariables'] = $config_ids;
 		$this->ConfigCacheIds = $config_ids;
 	}
 
 	/**
 	 * Loads template mapping for Front-End
 	 *
 	 */
 	function LoadStructureTemplateMapping()
 	{
 		if (!$this->isAdmin) {
 			$category_helper =& $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$this->structureTemplateMapping = $category_helper->getTemplateMapping();
 		}
 	}
 
 	function UpdateCache()
 	{
 		$update = false;
 		//something changed
 		$update = $update || $this->Phrases->NeedsCacheUpdate();
 		$update = $update || (count($this->ConfigCacheIds) && $this->ConfigCacheIds != $this->Caches['ConfigVariables']);
 
 		if ($update) {
 			$cache_key = $this->GetVar('t').$this->GetVar('m_theme').$this->GetVar('m_lang').$this->isAdmin;
 			$query = sprintf("REPLACE %s (PhraseList, CacheDate, Template, ConfigVariables)
 												VALUES (%s, %s, %s, %s)",
 												TABLE_PREFIX.'PhraseCache',
 												$this->Conn->qstr(join(',', $this->Phrases->Ids)),
 												adodb_mktime(),
 												$this->Conn->qstr(md5($cache_key)),
 												$this->Conn->qstr(implode(',', array_unique($this->ConfigCacheIds))));
 			$this->Conn->Query($query);
 		}
 	}
 
 	function InitConfig()
 	{
 		if (isset($this->Caches['ConfigVariables']) && count($this->Caches['ConfigVariables']) > 0) {
 			$sql = 'SELECT VariableValue, VariableName
 					FROM ' . TABLE_PREFIX . 'ConfigurationValues
 				 	WHERE VariableId IN (' . implode(',', $this->Caches['ConfigVariables']) . ')';
 			$this->ConfigHash = array_merge($this->ConfigHash, $this->Conn->GetCol($sql, 'VariableName'));
 		}
 	}
 
 	/**
 	 * Returns configuration option value by name
 	 *
 	 * @param string $name
 	 * @return string
 	 */
 	function ConfigValue($name)
 	{
 		if ($name == 'Smtp_AdminMailFrom') {
 			$res = $this->siteDomainField('AdminEmail');
 
 			if ($res) {
 				return $res;
 			}
 		}
 
 		$res = array_key_exists($name, $this->ConfigHash) ? $this->ConfigHash[$name] : false;
 		if ($res !== false) {
 			return $res;
 		}
 
 		if (defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('ConfigurationValues')) {
 			return false;
 		}
 
 		$sql = 'SELECT VariableId, VariableValue
 				FROM '.TABLE_PREFIX.'ConfigurationValues
 				WHERE VariableName = '.$this->Conn->qstr($name);
 		$res = $this->Conn->GetRow($sql);
 
 		if ($res !== false) {
 			$this->ConfigHash[$name] = $res['VariableValue'];
 			$this->ConfigCacheIds[] = $res['VariableId'];
 			return $res['VariableValue'];
 		}
 
 		return false;
 	}
 
 	function UpdateConfigCache()
 	{
 		if ($this->ConfigCacheIds) {
 
 		}
 	}
 
 	/**
 	 * Allows to process any type of event
 	 *
 	 * @param kEvent $event
 	 * @access public
 	 * @author Alex
 	 */
 	function HandleEvent(&$event, $params=null, $specificParams=null)
 	{
 		if ( isset($params) ) {
 			$event = new kEvent( $params, $specificParams );
 		}
 
 		if (!isset($this->EventManager)) {
 			$this->EventManager =& $this->recallObject('EventManager');
 		}
 
 		$this->EventManager->HandleEvent($event);
 	}
 
 	/**
 	 * Registers new class in the factory
 	 *
 	 * @param string $real_class Real name of class as in class declaration
 	 * @param string $file Filename in what $real_class is declared
 	 * @param string $pseudo_class Name under this class object will be accessed using getObject method
 	 * @param Array $dependecies List of classes required for this class functioning
 	 * @access public
 	 * @author Alex
 	 */
 	function registerClass($real_class, $file, $pseudo_class = null, $dependecies = Array() )
 	{
 		$this->Factory->registerClass($real_class, $file, $pseudo_class, $dependecies);
 	}
 
 	/**
 	 * Add $class_name to required classes list for $depended_class class.
 	 * All required class files are included before $depended_class file is included
 	 *
 	 * @param string $depended_class
 	 * @param string $class_name
 	 * @author Alex
 	 */
 	function registerDependency($depended_class, $class_name)
 	{
 		$this->Factory->registerDependency($depended_class, $class_name);
 	}
 
 	/**
 	 * Registers Hook from subprefix event to master prefix event
 	 *
 	 * @param string $hookto_prefix
 	 * @param string $hookto_special
 	 * @param string $hookto_event
 	 * @param string $mode
 	 * @param string $do_prefix
 	 * @param string $do_special
 	 * @param string $do_event
 	 * @param string $conditional
 	 * @access public
 	 * @todo take care of a lot parameters passed
 	 * @author Kostja
 	 */
 	function registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional)
 	{
 		$event_manager =& $this->recallObject('EventManager');
 		$event_manager->registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional);
 	}
 
 	/**
 	 * Allows one TagProcessor tag act as other TagProcessor tag
 	 *
 	 * @param Array $tag_info
 	 * @author Kostja
 	 */
 	function registerAggregateTag($tag_info)
 	{
 		$aggregator =& $this->recallObject('TagsAggregator', 'kArray');
 		$aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], Array($tag_info['LocalPrefix'], $tag_info['LocalTagName'], getArrayValue($tag_info, 'LocalSpecial')));
 	}
 
 	/**
 	 * Returns object using params specified,
 	 * creates it if is required
 	 *
 	 * @param string $name
 	 * @param string $pseudo_class
 	 * @param Array $event_params
 	 * @return Object
 	 * @author Alex
 	 */
 	function &recallObject($name,$pseudo_class=null,$event_params=Array())
 	{
 		$result =& $this->Factory->getObject($name, $pseudo_class, $event_params);
 		return $result;
 	}
 
 	/**
 	 * Returns object using Variable number of params,
 	 * all params starting with 4th are passed to object consturctor
 	 *
 	 * @param string $name
 	 * @param string $pseudo_class
 	 * @param Array $event_params
 	 * @return Object
 	 * @author Alex
 	 */
 	function &recallObjectP($name,$pseudo_class=null,$event_params=Array())
 	{
 		$func_args = func_get_args();
 		$result =& ref_call_user_func_array( Array(&$this->Factory, 'getObjectP'), $func_args );
 		return $result;
 	}
 
 	/**
 	 * Returns tag processor for prefix specified
 	 *
 	 * @param string $prefix
 	 * @return kDBTagProcessor
 	 */
 	function &recallTagProcessor($prefix)
 	{
 		$this->InitParser(); // because kDBTagProcesor is in NParser dependencies
 		$result =& $this->recallObject($prefix . '_TagProcessor');
 
 		return $result;
 	}
 
 	/**
 	 * Checks if object with prefix passes was already created in factory
 	 *
 	 * @param string $name object presudo_class, prefix
 	 * @return bool
 	 * @author Kostja
 	 */
 	function hasObject($name)
 	{
 		return isset($this->Factory->Storage[$name]);
 	}
 
 	/**
 	 * Removes object from storage by given name
 	 *
 	 * @param string $name Object's name in the Storage
 	 * @author Kostja
 	 */
 	function removeObject($name)
 	{
 		$this->Factory->DestroyObject($name);
 	}
 
 	/**
 	 * Get's real class name for pseudo class,
 	 * includes class file and creates class
 	 * instance
 	 *
 	 * @param string $pseudo_class
 	 * @return Object
 	 * @access public
 	 * @author Alex
 	 */
 	function &makeClass($pseudo_class)
 	{
 		$func_args = func_get_args();
 		$result =& ref_call_user_func_array( Array(&$this->Factory, 'makeClass'), $func_args);
 
 		return $result;
 	}
 
 	/**
 	 * Checks if application is in debug mode
 	 *
 	 * @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant
 	 * @return bool
 	 * @author Alex
 	 * @access public
 	 */
 	function isDebugMode($check_debugger = true)
 	{
 		$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
 		if ($check_debugger) {
 			$debug_mode = $debug_mode && is_object($this->Debugger);
 		}
 		return $debug_mode;
 	}
 
 	/**
 	 * Apply url rewriting used by mod_rewrite or not
 	 *
 	 * @param bool $ssl Force ssl link to be build
 	 * @return bool
 	 */
 	function RewriteURLs($ssl = false)
 	{
 		// case #1,#4:
 		//			we want to create https link from http mode
 		//			we want to create https link from https mode
 		//			conditions: ($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')
 
 		// case #2,#3:
 		//			we want to create http link from https mode
 		//			we want to create http link from http mode
 		//			conditions: !$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')
 
 		$allow_rewriting =
 			(!$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')) // always allow mod_rewrite for http
 			|| // or allow rewriting for redirect TO httpS or when already in httpS
 			(($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')); // but only if it's allowed in config!
 		return constOn('MOD_REWRITE') && $allow_rewriting;
 	}
 
 	/**
 	 * Reads unit (specified by $prefix)
 	 * option specified by $option
 	 *
 	 * @param string $prefix
 	 * @param string $option
 	 * @param mixed $default
 	 * @return string
 	 * @access public
 	 * @author Alex
 	 */
 	function getUnitOption($prefix, $option, $default = false)
 	{
 		/*if (!isset($this->UnitConfigReader)) {
 			$this->UnitConfigReader =& $this->recallObject('kUnitConfigReader');
 		}*/
 		return $this->UnitConfigReader->getUnitOption($prefix, $option, $default);
 	}
 
 	/**
 	 * Set's new unit option value
 	 *
 	 * @param string $prefix
 	 * @param string $name
 	 * @param string $value
 	 * @author Alex
 	 * @access public
 	 */
 	function setUnitOption($prefix, $option, $value)
 	{
 //		$unit_config_reader =& $this->recallObject('kUnitConfigReader');
 		return $this->UnitConfigReader->setUnitOption($prefix,$option,$value);
 	}
 
 	/**
 	 * Read all unit with $prefix options
 	 *
 	 * @param string $prefix
 	 * @return Array
 	 * @access public
 	 * @author Alex
 	 */
 	function getUnitOptions($prefix)
 	{
 //		$unit_config_reader =& $this->recallObject('kUnitConfigReader');
 		return $this->UnitConfigReader->getUnitOptions($prefix);
 	}
 
 	/**
 	 * Returns true if config exists and is allowed for reading
 	 *
 	 * @param string $prefix
 	 * @return bool
 	 */
 	function prefixRegistred($prefix)
 	{
 		/*if (!isset($this->UnitConfigReader)) {
 			$this->UnitConfigReader =& $this->recallObject('kUnitConfigReader');
 		}*/
 		return $this->UnitConfigReader->prefixRegistred($prefix);
 	}
 
 	/**
 	 * Splits any mixing of prefix and
 	 * special into correct ones
 	 *
 	 * @param string $prefix_special
 	 * @return Array
 	 * @access public
 	 * @author Alex
 	 */
 	function processPrefix($prefix_special)
 	{
 		return $this->Factory->processPrefix($prefix_special);
 	}
 
 	/**
 	 * Set's new event for $prefix_special
 	 * passed
 	 *
 	 * @param string $prefix_special
 	 * @param string $event_name
 	 * @access public
 	 */
 	function setEvent($prefix_special,$event_name)
 	{
 		$event_manager =& $this->recallObject('EventManager');
 		$event_manager->setEvent($prefix_special,$event_name);
 	}
 
 
 	/**
 	 * SQL Error Handler
 	 *
 	 * @param int $code
 	 * @param string $msg
 	 * @param string $sql
 	 * @return bool
 	 * @access private
 	 * @author Alex
 	 */
 	function handleSQLError($code, $msg, $sql)
 	{
 		if ( isset($this->Debugger) )
 		{
 			$errorLevel = constOn('DBG_SQL_FAILURE') && !defined('IS_INSTALL') ? E_USER_ERROR : E_USER_WARNING;
 			$this->Debugger->appendTrace();
 
 			$error_msg = '<span class="debug_error">'.$msg.' ('.$code.')</span><br><a href="javascript:$Debugger.SetClipboard(\''.htmlspecialchars($sql).'\');"><b>SQL</b></a>: '.$this->Debugger->formatSQL($sql);
 			$long_id = $this->Debugger->mapLongError($error_msg);
 			trigger_error( mb_substr($msg.' ('.$code.') ['.$sql.']',0,1000).' #'.$long_id, $errorLevel);
 			return true;
 		}
 		else
 		{
 			//$errorLevel = constOn('IS_INSTALL') ? E_USER_WARNING : E_USER_ERROR;
 			$errorLevel = E_USER_WARNING;
 			trigger_error('<b>SQL Error</b> in sql: '.$sql.', code <b>'.$code.'</b> ('.$msg.')', $errorLevel);
 			/*echo '<b>xProcessing SQL</b>: '.$sql.'<br>';
 			echo '<b>Error ('.$code.'):</b> '.$msg.'<br>';*/
 			return $errorLevel == E_USER_ERROR ? false : true;
 		}
 	}
 
 	/**
 	 * Default error handler
 	 *
 	 * @param int $errno
 	 * @param string $errstr
 	 * @param string $errfile
 	 * @param int $errline
 	 * @param Array $errcontext
 	 */
 	function handleError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '')
 	{
 		if (defined('SILENT_LOG') && SILENT_LOG) {
 			if ( !(defined('DBG_IGNORE_STRICT_ERRORS') && DBG_IGNORE_STRICT_ERRORS && defined('E_STRICT') && ($errno == E_STRICT)) ) {
 				$fp = fopen(FULL_PATH.'/silent_log.txt','a');
 				$time = adodb_date('d/m/Y H:i:s');
 				fwrite($fp, '['.$time.'] #'.$errno.': '.strip_tags($errstr).' in ['.$errfile.'] on line '.$errline."\n");
 				fclose($fp);
 			}
 		}
 
 		$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
 		$skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING;
 
 		if (!$this->errorHandlers || ($debug_mode && $skip_reporting)) {
 			// when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request)
 			$ignore_fatal_errors = defined('DBG_IGNORE_FATAL_ERRORS') && DBG_IGNORE_FATAL_ERRORS;
 
 			if (($errno == E_USER_ERROR) && !$ignore_fatal_errors) {
 				echo ('	<div style="background-color: #FEFFBF; margin: auto; padding: 10px; border: 2px solid red; text-align: center">
 							<strong>Fatal Error: </strong>'."$errstr in $errfile on line $errline".'
 						</div>');
 				exit;
 			}
 
 			if (!$this->errorHandlers) {
 				return true;
 			}
 		}
 
 		$res = false;
 		$i = 0; // while (not foreach) because it is array of references in some cases
 		$eh_count = count($this->errorHandlers);
 		while ($i < $eh_count) {
 			if ( is_array($this->errorHandlers[$i]) ) {
 				$object =& $this->errorHandlers[$i][0];
 				$method = $this->errorHandlers[$i][1];
 				$res = $object->$method($errno, $errstr, $errfile, $errline, $errcontext);
 			}
 			else {
 				$function = $this->errorHandlers[$i];
 				$res = $function($errno, $errstr, $errfile, $errline, $errcontext);
 			}
 			$i++;
 		}
 		return $res;
 	}
 
 	/**
 	 * Returns & blocks next ResourceId available in system
 	 *
 	 * @return int
 	 * @access public
 	 * @author Alex
 	 */
 	function NextResourceId()
 	{
 		$table_name = TABLE_PREFIX.'IdGenerator';
 
 		$this->Conn->Query('LOCK TABLES '.$table_name.' WRITE');
 		$this->Conn->Query('UPDATE '.$table_name.' SET lastid = lastid + 1');
 		$id = $this->Conn->GetOne('SELECT lastid FROM '.$table_name);
 		if($id === false)
 		{
 			$this->Conn->Query('INSERT INTO '.$table_name.' (lastid) VALUES (2)');
 			$id = 2;
 		}
 		$this->Conn->Query('UNLOCK TABLES');
 		return $id - 1;
 	}
 
 	/**
 	 * Returns genealogical main prefix for subtable prefix passes
 	 * OR prefix, that has been found in REQUEST and some how is parent of passed subtable prefix
 	 *
 	 * @param string $current_prefix
 	 * @param string $real_top if set to true will return real topmost prefix, regardless of its id is passed or not
 	 * @return string
 	 * @access public
 	 * @author Kostja / Alex
 	 */
 	function GetTopmostPrefix($current_prefix, $real_top = false)
 	{
 		// 1. get genealogical tree of $current_prefix
 		$prefixes = Array ($current_prefix);
 		while ( $parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix') ) {
 			if (!$this->prefixRegistred($parent_prefix)) {
 				// stop searching, when parent prefix is not registered
 				break;
 			}
 
 			$current_prefix = $parent_prefix;
 			array_unshift($prefixes, $current_prefix);
 		}
 
 		if ($real_top) {
 			return $current_prefix;
 		}
 
 		// 2. find what if parent is passed
 		$passed = explode(',', $this->GetVar('all_passed'));
 		foreach ($prefixes as $a_prefix) {
 			if (in_array($a_prefix, $passed)) {
 				return $a_prefix;
 			}
 		}
 
 		return $current_prefix;
 	}
 
 	/**
 	 * Triggers email event of type Admin
 	 *
 	 * @param string $email_event_name
 	 * @param int $to_user_id
 	 * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text
 	 * @return kEvent
 	 */
 	function &EmailEventAdmin($email_event_name, $to_user_id = null, $send_params = Array ())
 	{
 		$event =& $this->EmailEvent($email_event_name, EVENT_TYPE_ADMIN, $to_user_id, $send_params);
 
 		return $event;
 	}
 
 	/**
 	 * Triggers email event of type User
 	 *
 	 * @param string $email_event_name
 	 * @param int $to_user_id
 	 * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text
 	 * @return kEvent
 	 */
 	function &EmailEventUser($email_event_name, $to_user_id = null, $send_params = Array ())
 	{
 		$event =& $this->EmailEvent($email_event_name, EVENT_TYPE_FRONTEND, $to_user_id, $send_params);
 
 		return $event;
 	}
 
 	/**
 	 * Triggers general email event
 	 *
 	 * @param string $email_event_name
 	 * @param int $email_event_type (0 for User, 1 for Admin)
 	 * @param int $to_user_id
 	 * @param array $send_params associative array of direct send params,
 	 *  possible keys: to_email, to_name, from_email, from_name, message, message_text
 	 * @return kEvent
 	 */
 	function &EmailEvent($email_event_name, $email_event_type, $to_user_id = null, $send_params = Array ())
 	{
 		$params = Array (
 			'EmailEventName' => $email_event_name,
 			'EmailEventToUserId' => $to_user_id,
 			'EmailEventType' => $email_event_type,
 			'DirectSendParams' => $send_params,
 		);
 
 		if (array_key_exists('use_special', $send_params)) {
 			$event_str = 'emailevents.' . $send_params['use_special'] . ':OnEmailEvent';
 		}
 		else {
 			$event_str = 'emailevents:OnEmailEvent';
 		}
 
 		$this->HandleEvent($event, $event_str, $params);
 
 		return $event;
 	}
 
 	/**
 	 * Allows to check if user in this session is logged in or not
 	 *
 	 * @return bool
 	 */
 	function LoggedIn()
 	{
 		// no session during expiration process
 		return is_null($this->Session) ? false : $this->Session->LoggedIn();
 	}
 
 	/**
 	 * Check current user permissions based on it's group permissions in specified category
 	 *
 	 * @param string $name permission name
 	 * @param int $cat_id category id, current used if not specified
 	 * @param int $type permission type {1 - system, 0 - per category}
 	 * @return int
 	 */
 	function CheckPermission($name, $type = 1, $cat_id = null)
 	{
 		$perm_helper =& $this->recallObject('PermissionsHelper');
 		return $perm_helper->CheckPermission($name, $type, $cat_id);
 	}
 
 	/**
 	 * Set's any field of current visit
 	 *
 	 * @param string $field
 	 * @param mixed $value
 	 */
 	function setVisitField($field, $value)
 	{
 		if ($this->isAdmin || !$this->ConfigValue('UseVisitorTracking')) {
 			// admin logins are not registred in visits list
 			return ;
 		}
 
 		$visit =& $this->recallObject('visits', null, Array ('raise_warnings' => 0));
 		/* @var $visit kDBItem */
 
 		if ($visit->isLoaded()) {
 			$visit->SetDBField($field, $value);
 			$visit->Update();
 		}
 	}
 
 	/**
 	 * Allows to check if in-portal is installed
 	 *
 	 * @return bool
 	 */
 	function isInstalled()
 	{
 		return $this->InitDone && (count($this->ModuleInfo) > 0);
 	}
 
 	/**
 	 * Allows to determine if module is installed & enabled
 	 *
 	 * @param string $module_name
 	 * @return bool
 	 */
 	function isModuleEnabled($module_name)
 	{
 		return $this->findModule('Name', $module_name) !== false;
 
 	}
 
 	function reportError($class, $method)
 	{
 		$this->Debugger->appendTrace();
 		trigger_error('depricated method <b>'.$class.'->'.$method.'(...)</b>', E_USER_ERROR);
 	}
 
 	/**
 	 * Returns Window ID of passed prefix main prefix (in edit mode)
 	 *
 	 * @param string $prefix
 	 * @return mixed
 	 */
 	function GetTopmostWid($prefix)
 	{
 		$top_prefix = $this->GetTopmostPrefix($prefix);
 		$mode = $this->GetVar($top_prefix.'_mode');
 		return $mode != '' ? substr($mode, 1) : '';
 	}
 
 	/**
 	 * Get temp table name
 	 *
 	 * @param string $table
 	 * @param mixed $wid
 	 * @return string
 	 */
 	function GetTempName($table, $wid = '')
 	{
 		if (preg_match('/prefix:(.*)/', $wid, $regs)) {
 			$wid = $this->GetTopmostWid($regs[1]);
 		}
 
 		return TABLE_PREFIX.'ses_'.$this->GetSID().($wid ? '_'.$wid : '').'_edit_'.$table;
 	}
 
 	function GetTempTablePrefix($wid = '')
 	{
 		if (preg_match('/prefix:(.*)/', $wid, $regs)) {
 			$wid = $this->GetTopmostWid($regs[1]);
 		}
 
 		return TABLE_PREFIX.'ses_'.$this->GetSID().($wid ? '_'.$wid : '').'_edit_';
 	}
 
 	function IsTempTable($table)
 	{
 		return preg_match('/'.TABLE_PREFIX.'ses_'.$this->GetSID().'(_[\d]+){0,1}_edit_(.*)/',$table);
 	}
 
 	/**
 	 * Checks, that given prefix is in temp mode
 	 *
 	 * @param string $prefix
 	 * @return bool
 	 */
 	function IsTempMode($prefix, $special = '')
 	{
 		$top_prefix = $this->Application->GetTopmostPrefix($prefix);
 
 		$var_names = Array (
 			$top_prefix,
 			rtrim($top_prefix . '_' . $special, '_'), // from post
 			rtrim($top_prefix . '.' . $special, '.'), // assembled locally
 		);
 
 		$var_names = array_unique($var_names);
 
 		$temp_mode = false;
 		foreach ($var_names as $var_name) {
 			$value = $this->Application->GetVar($var_name . '_mode');
 			if ($value && (substr($value, 0, 1) == 't')) {
 				$temp_mode = true;
 				break;
 			}
 		}
 
 		return $temp_mode;
 	}
 
 	/**
 	 * Return live table name based on temp table name
 	 *
 	 * @param string $temp_table
 	 * @return string
 	 */
 	function GetLiveName($temp_table)
 	{
 		if( preg_match('/'.TABLE_PREFIX.'ses_'.$this->GetSID().'(_[\d]+){0,1}_edit_(.*)/',$temp_table, $rets) )
 		{
 			// cut wid from table end if any
 			return $rets[2];
 		}
 		else
 		{
 			return $temp_table;
 		}
 	}
 
 	function CheckProcessors($processors)
 	{
 		foreach ($processors as $a_processor)
 		{
 			if (!isset($this->CachedProcessors[$a_processor])) {
 				$this->CachedProcessors[$a_processor] =& $this->recallObject($a_processor.'_TagProcessor');
 			}
 		}
 	}
 
 	function ApplicationDie($message = '')
 	{
 		$message = ob_get_clean().$message;
 		if ($this->isDebugMode()) {
 			$message .= $this->Debugger->printReport(true);
 		}
 
 		echo $this->UseOutputCompression() ? gzencode($message, DBG_COMPRESSION_LEVEL) : $message;
 		exit;
 	}
 
 
 	/* moved from MyApplication */
 
 	function getUserGroups($user_id)
 	{
 		switch ($user_id) {
 			case USER_ROOT:
 				$user_groups = $this->ConfigValue('User_LoggedInGroup');
 				break;
 
 			case USER_GUEST:
 				$user_groups = $this->ConfigValue('User_LoggedInGroup') . ',' . $this->ConfigValue('User_GuestGroup');
 				break;
 
 			default:
 				$sql = 'SELECT GroupId
 						FROM ' . TABLE_PREFIX . 'UserGroup
 						WHERE PortalUserId = ' . (int)$user_id;
 				$res = $this->Conn->GetCol($sql);
 
 				$user_groups = Array( $this->ConfigValue('User_LoggedInGroup') );
 				if ($res) {
 					$user_groups = array_merge($user_groups, $res);
 				}
 
 				$user_groups = implode(',', $user_groups);
 		}
 
 		return $user_groups;
 	}
 
 
 	/**
 	 * Allows to detect if page is browsed by spider (293 agents supported)
 	 *
 	 * @return bool
 	 */
 	function IsSpider()
 	{
 		static $is_spider = null;
 
 		if (!isset($is_spider)) {
 			$user_agent = trim($_SERVER['HTTP_USER_AGENT']);
 			$robots = file(FULL_PATH.'/core/robots_list.txt');
 			foreach ($robots as $robot_info) {
 				$robot_info = explode("\t", $robot_info, 3);
 				if ($user_agent == trim($robot_info[2])) {
 					$is_spider = true;
 					break;
 				}
 			}
 		}
 
 		return $is_spider;
 	}
 
 	/**
 	 * Allows to detect table's presense in database
 	 *
 	 * @param string $table_name
 	 * @return bool
 	 */
 	function TableFound($table_name)
 	{
 		return $this->Conn->TableFound($table_name);
 	}
 
 	/**
 	 * Returns counter value
 	 *
 	 * @param string $name counter name
 	 * @param Array $params counter parameters
 	 * @param string $query_name specify query name directly (don't generate from parmeters)
 	 * @param bool $multiple_results
 	 * @return mixed
 	 */
 	function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false)
 	{
 		$count_helper =& $this->Application->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		return $count_helper->getCounter($name, $params, $query_name, $multiple_results);
 	}
 
 	/**
 	 * Resets counter, whitch are affected by one of specified tables
 	 *
 	 * @param string $tables comma separated tables list used in counting sqls
 	 */
 	function resetCounters($tables)
 	{
 		if (constOn('IS_INSTALL')) {
 			return ;
 		}
 
 		$count_helper =& $this->Application->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		return $count_helper->resetCounters($tables);
 	}
 
 	/**
 	 * Sends XML header + optionally displays xml heading
 	 *
 	 * @param string $xml_version
 	 * @return string
 	 * @author Alex
 	 */
 	function XMLHeader($xml_version = false)
 	{
 		$lang =& $this->recallObject('lang.current');
 		header('Content-type: text/xml; charset='.$lang->GetDBField('Charset'));
 
 		return $xml_version ? '<?xml version="'.$xml_version.'" encoding="'.$lang->GetDBField('Charset').'"?>' : '';
 	}
 
 	/**
 	 * Returns category tree
 	 *
 	 * @param int $category_id
 	 * @return Array
 	 */
 	function getTreeIndex($category_id)
 	{
 		$tree_index = $this->getCategoryCache($category_id, 'category_tree');
 
 		if ($tree_index) {
 			$ret = Array ();
 			list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index);
 
 			return $ret;
 		}
 
 		return false;
 	}
 
+	/**
+	 * Base category of all categories
+	 * Usually replaced category, with ID = 0 in category-related operations.
+	 *
+	 * @return int
+	 */
+	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'];
+	}
+
 }
\ No newline at end of file
Index: branches/5.1.x/core/units/categories/categories_tag_processor.php
===================================================================
--- branches/5.1.x/core/units/categories/categories_tag_processor.php	(revision 13986)
+++ branches/5.1.x/core/units/categories/categories_tag_processor.php	(revision 13987)
@@ -1,1977 +1,1971 @@
 <?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.
 */
 
 class CategoriesTagProcessor extends kDBTagProcessor {
 
 	function SubCatCount($params)
 	{
 		$object =& $this->getObject($params);
 
 		if (isset($params['today']) && $params['today']) {
 			$sql = 'SELECT COUNT(*)
 					FROM '.$object->TableName.'
 					WHERE (ParentPath LIKE "'.$object->GetDBField('ParentPath').'%") AND (CreatedOn > '.(adodb_mktime() - 86400).')';
 			return $this->Conn->GetOne($sql) - 1;
 		}
 
 		return $object->GetDBField('CachedDescendantCatsQty');
 	}
 
 	/**
 	 * Returns category count in system
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function CategoryCount($params)
 	{
 		$count_helper =& $this->Application->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		$today_only = isset($params['today']) && $params['today'];
 		return $count_helper->CategoryCount($today_only);
 	}
 
 	function IsNew($params)
 	{
 		$object =& $this->getObject($params);
 		return $object->GetDBField('IsNew') ? 1 : 0;
 	}
 
 	function IsPick($params)
 	{
 		return $this->IsEditorsPick($params);
 	}
 
 	/**
 	 * Returns item's editors pick status (using not formatted value)
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsEditorsPick($params)
 	{
 		$object =& $this->getObject($params);
 
 		return $object->GetDBField('EditorsPick') == 1;
 	}
 
 	function ItemIcon($params)
 	{
 		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 		$grid = $grids[ $params['grid'] ];
 
 		if (!array_key_exists('Icons', $grid)) {
 			return '';
 		}
 
 		$icons = $grid['Icons'];
 		$icon_prefix = array_key_exists('icon_prefix', $params)? $params['icon_prefix'] : 'icon16_';
 
 		if (array_key_exists('name', $params)) {
 			$icon_name = $params['name'];
 			return array_key_exists($icon_name, $icons) ? $icons[$icon_name] : '';
 		}
 
 		$object =& $this->getObject($params);
 		/* @var $object kDBList */
 
 		if ($object->GetDBField('ThemeId') > 0) {
 			if (!$object->GetDBField('IsMenu')) {
 				return $icon_prefix . 'section_menuhidden_system.png';
 			}
 			return $icon_prefix . 'section_system.png';
 		}
 
 		$status = $object->GetDBField('Status');
 
 		if ($status == STATUS_DISABLED) {
 			return $icon_prefix . 'section_disabled.png';
 		}
 
 		if (!$object->GetDBField('IsMenu')) {
 			return $icon_prefix . 'section_menuhidden.png';
 		}
 
 		if ($status == STATUS_PENDING) {
 			return $icon_prefix . 'section_pending.png';
 		}
 
 		if ($object->GetDBField('IsNew') && ($icon_prefix == 'icon16_')) {
 			return $icon_prefix . 'section_new.png'; // show gris icon only in grids
 		}
 
 		return $icon_prefix . 'section.png';
 	}
 
 	function ItemCount($params)
 	{
 		$object =& $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$ci_table = $this->Application->getUnitOption('ci', 'TableName');
 
 		$sql = 'SELECT COUNT(*)
 				FROM ' . $object->TableName . ' c
 				LEFT JOIN ' . $ci_table . ' ci ON c.CategoryId = ci.CategoryId
 				WHERE (c.TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight') . ') AND NOT (ci.CategoryId IS NULL)';
 		return $this->Conn->GetOne($sql);
 	}
 
 	function ListCategories($params)
 	{
 		return $this->PrintList2($params);
 	}
 
 	function RootCategoryName($params)
 	{
 		return $this->Application->ProcessParsedTag('m', 'RootCategoryName', $params);
 	}
 
 	function CheckModuleRoot($params)
 	{
 		$module_name = getArrayValue($params, 'module') ? $params['module'] : 'In-Commerce';
 		$module_root_cat = $this->Application->findModule('Name', $module_name, 'RootCat');
 
 		$additional_cats = $this->SelectParam($params, 'add_cats');
 		if ($additional_cats) {
 			$additional_cats = explode(',', $additional_cats);
 		}
 		else {
 			$additional_cats = array();
 		}
 
 		if ($this->Application->GetVar('m_cat_id') == $module_root_cat || in_array($this->Application->GetVar('m_cat_id'), $additional_cats)) {
 			$home_template = getArrayValue($params, 'home_template');
 			if (!$home_template) return;
 			$this->Application->Redirect($home_template, Array('pass'=>'all'));
 		};
 	}
 
 	function CategoryPath($params)
 	{
 		$category_helper =& $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		return $category_helper->NavigationBar($params);
 	}
 
 	/**
 	 * Shows category path to specified category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function FieldCategoryPath($params)
 	{
 		$object =& $this->getObject();
 		/* @var $object kDBItem */
 
 		$field = $this->SelectParam($params, 'name,field');
 		$category_id = $object->GetDBField($field);
 
 		if ($category_id) {
 			$params['cat_id'] = $category_id;
 			return $this->CategoryPath($params);
 		}
 
 		return '';
 	}
 
 	function CurrentCategoryName($params)
 	{
 		$cat_object =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List');
 		$sql = 'SELECT '.$this->getTitleField().'
 				FROM '.$cat_object->TableName.'
 				WHERE CategoryId = '.(int)$this->Application->GetVar('m_cat_id');
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Returns current category name
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Find where it's used
 	 */
 	function CurrentCategory($params)
 	{
 		return $this->CurrentCategoryName($params);
 	}
 
 	function getTitleField()
 	{
 		$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 		return $ml_formatter->LangFieldName('Name');
 	}
 
 	/**
 	 * Returns symlinked category for given category
 	 *
 	 * @param $category_id
 	 */
 	function getCategorySymLink($category_id)
 	{
 		if (!$category_id) {
 			// don't bother to get symlink for "Home" category
 			return $category_id;
 		}
 
 		$cache_key = 'category_symlinks[%CSerial%]';
 		$cache = $this->Application->getCache($cache_key);
 
 		if ($cache === false) {
 			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 			// get symlinked categories, that are not yet deleted
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT c1.SymLinkCategoryId, c1.' . $id_field . '
 					FROM ' . $table_name . ' c1
 					JOIN ' . $table_name . ' c2 ON c1.SymLinkCategoryId = c2.' . $id_field;
 			$cache = $this->Conn->GetCol($sql, $id_field);
 
 			$this->Application->setCache($cache_key, $cache);
 		}
 
 		return array_key_exists($category_id, $cache) ? $cache[$category_id] : $category_id;
 	}
 
 	function CategoryLink($params)
 	{
 		$category_id = getArrayValue($params, 'cat_id');
 
 		if ($category_id === false) {
 			$category_id = $this->Application->GetVar($this->getPrefixSpecial() . '_id');
 		}
 
 		if ("$category_id" == 'Root') {
 			$category_id = $this->Application->findModule('Name', $params['module'], 'RootCat');
 		}
 		elseif ("$category_id" == 'current') {
 			$category_id = $this->Application->GetVar('m_cat_id');
 		}
 
 		if (!array_key_exists('direct_link', $params) || !$params['direct_link']) {
 			$category_id = $this->getCategorySymLink( (int)$category_id );
 		}
 		else {
 			unset($params['direct_link']);
 		}
 
 		unset($params['cat_id'], $params['module']);
 
 		$new_params = Array ('pass' => 'm', 'm_cat_id' => $category_id, 'pass_category' => 1);
 		$params = array_merge_recursive2($params, $new_params);
 
 		return $this->Application->ProcessParsedTag('m', 't', $params);
 	}
 
 	function CategoryList($params)
 	{
 		//$object =& $this->Application->recallObject( $this->getPrefixSpecial() , $this->Prefix.'_List', $params );
 		$object =& $this->GetList($params);
 
 
 		if ($object->RecordsCount == 0)
 		{
 			if (isset($params['block_no_cats'])) {
 				$params['name'] = $params['block_no_cats'];
 				return $this->Application->ParseBlock($params);
 			}
 			else {
 				return '';
 			}
 		}
 
 		if (isset($params['block'])) {
 			return $this->PrintList($params);
 		}
 		else {
 			$params['block'] = $params['block_main'];
 			if (isset($params['block_row_start'])) {
 				$params['row_start_block'] = $params['block_row_start'];
 			}
 
 			if (isset($params['block_row_end'])) {
 				$params['row_end_block'] = $params['block_row_end'];
 			}
 			return $this->PrintList2($params);
 		}
 	}
 
 	function Meta($params)
 	{
 		$object =& $this->Application->recallObject($this->Prefix); // .'.-item'
 		/* @var $object CategoriesItem */
 
 		$meta_type = $params['name'];
 		if ($object->isLoaded()) {
 			// 1. get module prefix by current category
 			$category_helper =& $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$category_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 			$module_info = $category_helper->getCategoryModule($params,  $category_path);
 
 			// In-Edit & Proj-CMS module prefixes doesn't have custom field with item template
 			if ($module_info && $module_info['Var'] != 'adm' && $module_info['Var'] != 'st') {
 
 				// 2. get item template by current category & module prefix
 				$mod_rewrite_helper = $this->Application->recallObject('ModRewriteHelper');
 				/* @var $mod_rewrite_helper kModRewriteHelper */
 
 				$category_params = Array (
 				'CategoryId' => $object->GetID(),
 				'ParentPath' => $object->GetDBField('ParentPath'),
 				);
 
 				$item_template = $mod_rewrite_helper->GetItemTemplate($category_params, $module_info['Var']);
 
 				if ($this->Application->GetVar('t') == $item_template) {
 					// we are located on item's details page
 					$item =& $this->Application->recallObject($module_info['Var']);
 					/* @var $item kCatDBItem */
 
 					// 3. get item's meta data
 					$value = $item->GetField('Meta'.$meta_type);
 					if ($value) {
 						return $value;
 					}
 				}
 
 				// 4. get category meta data
 				$value = $object->GetField('Meta'.$meta_type);
 				if ($value) {
 					return $value;
 				}
 			}
 		}
 
 		// 5. get default meta data
 		switch ($meta_type) {
 			case 'Description':
 				$config_name = 'Category_MetaDesc';
 				break;
 			case 'Keywords':
 				$config_name = 'Category_MetaKey';
 				break;
 		}
 
 		return $this->Application->ConfigValue($config_name);
 	}
 
 	function BuildListSpecial($params)
 	{
 		if (($this->Special != '') && !is_numeric($this->Special)) {
 			// When recursive category list is printed (like in sitemap), then special
 			// should be generated even if it's already present. Without it list on this
 			// level will erase list on previous level, because it will be stored in same object.
 			return $this->Special;
 		}
 
 		if ( isset($params['parent_cat_id']) ) {
 			$parent_cat_id = $params['parent_cat_id'];
 		}
 		else {
 			$parent_cat_id = $this->Application->GetVar($this->Prefix.'_id');
 			if (!$parent_cat_id) {
 				$parent_cat_id = $this->Application->GetVar('m_cat_id');
 			}
 			if (!$parent_cat_id) {
 				$parent_cat_id = 0;
 			}
 		}
 
 		$list_unique_key = $this->getUniqueListKey($params);
 		// check for "admin" variable, because we are parsing front-end template from admin when using template editor feature
 		if ($this->Application->GetVar('admin') || !$this->Application->isAdmin) {
 			// add parent category to special, when on Front-End,
 			// because there can be many category lists on same page
 			$list_unique_key .= $parent_cat_id;
 		}
 
 		if ($list_unique_key == '') {
 			return parent::BuildListSpecial($params);
 		}
 
 		return crc32($list_unique_key);
 	}
 
 	function IsCurrent($params)
 	{
 		$object =& $this->getObject($params);
 		if ($object->GetID() == $this->Application->GetVar('m_cat_id')) {
 			return true;
 		}
 		else {
 			return false;
 		}
 	}
 
 	/**
 	 * Substitutes category in last template base on current category
 	 * This is required becasue when you navigate catalog using AJAX, last_template is not updated
 	 * but when you open item edit from catalog last_template is used to build opener_stack
 	 * So, if we don't substitute m_cat_id in last_template, after saving item we'll get redirected
 	 * to the first category we've opened, not the one we navigated to using AJAX
 	 *
 	 * @param Array $params
 	 */
 	function UpdateLastTemplate($params)
 	{
 		$category_id = $this->Application->GetVar('m_cat_id');
 
 		$wid = $this->Application->GetVar('m_wid');
 		list($index_file, $env) = explode('|', $this->Application->RecallVar(rtrim('last_template_'.$wid, '_')), 2);
 
 		$vars_backup = Array ();
 		$vars = $this->Application->HttpQuery->processQueryString( str_replace('%5C', '\\', $env) );
 
 		foreach ($vars as $var_name => $var_value) {
 			$vars_backup[$var_name] = $this->Application->GetVar($var_name);
 			$this->Application->SetVar($var_name, $var_value);
 		}
 
 		// update required fields
 		$this->Application->SetVar('m_cat_id', $category_id);
 		$this->Application->Session->SaveLastTemplate($params['template']);
 
 		foreach ($vars_backup as $var_name => $var_value) {
 			$this->Application->SetVar($var_name, $var_value);
 		}
 	}
 
 	function GetParentCategory($params)
 	{
 		$parent_id = 0;
 		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 		$table = $this->Application->getUnitOption($this->Prefix,'TableName');
 		$cat_id = $this->Application->GetVar('m_cat_id');
 		if ($cat_id > 0) {
 			$sql = 'SELECT ParentId
 					FROM '.$table.'
 					WHERE '.$id_field.' = '.$cat_id;
 			$parent_id = $this->Conn->GetOne($sql);
 		}
 		return $parent_id;
 	}
 
 	function InitCacheUpdater($params)
 	{
 		safeDefine('CACHE_PERM_CHUNK_SIZE', 30);
 
 		$continue = $this->Application->GetVar('continue');
 		$total_cats = (int) $this->Conn->GetOne('SELECT COUNT(*) FROM '.TABLE_PREFIX.'Category');
 
 		if ($continue === false && $total_cats > CACHE_PERM_CHUNK_SIZE) {
 			// first step, if category count > CACHE_PERM_CHUNK_SIZE, then ask for cache update
 			return true;
 		}
 
 		if ($continue === false) {
 			// if we don't have to ask, then assume user selected "Yes" in permcache update dialog
 			$continue = 1;
 		}
 
 		$updater =& $this->Application->recallObject('kPermCacheUpdater', null, Array('continue' => $continue));
 		/* @var $updater kPermCacheUpdater */
 		if ($continue === '0') { // No in dialog
 			$updater->clearData();
 			$this->Application->Redirect($params['destination_template']);
 		}
 
 		$ret = false; // don't ask for update
 		if ($continue == 1) {  // Initial run
 			$updater->setData();
 		}
 		if ($continue == 2) { // Continuing
 			// called from AJAX request => returns percent
 			$needs_more = true;
 			while ($needs_more && $updater->iteration <= CACHE_PERM_CHUNK_SIZE) {
 				// until proceeeded in this step category count exceeds category per step limit
 				$needs_more = $updater->DoTheJob();
 			}
 
 			if ($needs_more) {
 				// still some categories are left for next step
 				$updater->setData();
 			}
 			else {
 				// all done, update left tree and redirect
 				$updater->SaveData();
 				$this->Application->RemoveVar('PermCache_UpdateRequired');
 				$this->Application->StoreVar('RefreshStructureTree', 1);
 				$this->Application->Redirect($params['destination_template']);
 			}
 
 			$ret = $updater->getDonePercent();
 		}
 		return $ret;
 	}
 
 	/**
 	 * Parses warning block, but with style="display: none;". Used during permissions saving from AJAX
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SaveWarning($params)
 	{
 		if ($this->Prefix == 'st') {
 			// don't use this method for other prefixes then Category, that use this tag processor
 			return parent::SaveWarning($params);
 		}
 
 		$main_prefix = getArrayValue($params, 'main_prefix');
 		if ($main_prefix && $main_prefix != '$main_prefix') {
 			$top_prefix = $main_prefix;
 		}
 		else {
 			$top_prefix = $this->Application->GetTopmostPrefix($this->Prefix);
 		}
 
 		$temp_tables = substr($this->Application->GetVar($top_prefix.'_mode'), 0, 1) == 't';
 		$modified = $this->Application->RecallVar($top_prefix.'_modified');
 
 		if (!$temp_tables) {
 			$this->Application->RemoveVar($top_prefix.'_modified');
 			return '';
 		}
 
 		$block_name = $this->SelectParam($params, 'render_as,name');
 		if ($block_name) {
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $block_name;
 			$block_params['edit_mode'] = $temp_tables ? 1 : 0;
 			$block_params['display'] = $temp_tables && $modified ? 1 : 0;
 			return $this->Application->ParseBlock($block_params);
 		}
 		else {
 			return $temp_tables && $modified ? 1 : 0;
 		}
 		return ;
 	}
 
 	/**
 	 * Allows to detect if this prefix has something in clipboard
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function HasClipboard($params)
 	{
 		$clipboard = $this->Application->RecallVar('clipboard');
 		if ($clipboard) {
 			$clipboard = unserialize($clipboard);
 			foreach ($clipboard as $prefix => $clipboard_data) {
 				foreach ($clipboard_data as $mode => $ids) {
 					if (count($ids)) return 1;
 				}
 			}
 		}
 		return 0;
 	}
 
 	/**
 	 * Allows to detect if root category being edited
 	 *
 	 * @param Array $params
 	 */
 	function IsRootCategory($params)
 	{
 		$object =& $this->getObject($params);
 		return $object->IsRoot();
 	}
 
 	/**
 	 * Returns home category id
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function HomeCategory($params)
 	{
-		static $root_category = null;
-
-		if (!isset($root_category)) {
-			$root_category = $this->Application->findModule('Name', 'Core', 'RootCat');
-		}
-
-		return $root_category;
+		return $this->Application->getBaseCategory();
 	}
 
 	/**
 	 * Used for disabling "Home" and "Up" buttons in category list
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function ModuleRootCategory($params)
 	{
-		return $this->Application->GetVar('m_cat_id') == $this->HomeCategory($params);
+		return $this->Application->GetVar('m_cat_id') == $this->Application->getBaseCategory();
 	}
 
 	function CatalogItemCount($params)
 	{
 		$params['skip_quering'] = true;
 		$object =& $this->GetList($params);
 
 		if (!$object->Counted) {
 			$object->CountRecs();
 		}
 
 		return $object->NoFilterCount != $object->RecordsCount ? $object->RecordsCount.' / '.$object->NoFilterCount : $object->RecordsCount;
 	}
 
 	function InitCatalog($params)
 	{
 		$tab_prefixes = $this->Application->GetVar('tp'); // {all, <prefixes_list>, none}
 		if ($tab_prefixes === false) $tab_prefixes = 'all';
 		$skip_prefixes = isset($params['skip_prefixes']) && $params['skip_prefixes'] ? explode(',', $params['skip_prefixes']) : Array();
 		$replace_main = isset($params['replace_m']) && $params['replace_m'];
 
 		// get all prefixes available
 		$prefixes = Array();
 		foreach ($this->Application->ModuleInfo as $module_name => $module_data) {
 			$prefix = $module_data['Var'];
 
 			if ($prefix == 'adm'/* || $prefix == 'm'*/) continue;
 
 			if ($prefix == 'm' && $replace_main) {
 				$prefix = 'c';
 			}
 
 			$prefixes[] = $prefix;
 		}
 
 		if ($tab_prefixes == 'none') {
 			$skip_prefixes = array_unique(array_merge($skip_prefixes, $prefixes));
 			unset($skip_prefixes[ array_search($replace_main ? 'c' : 'm', $skip_prefixes) ]);
 		}
 		elseif ($tab_prefixes != 'all') {
 			// prefix list here
 			$tab_prefixes = explode(',', $tab_prefixes); // list of prefixes that should stay
 			$skip_prefixes = array_unique(array_merge($skip_prefixes, array_diff($prefixes, $tab_prefixes)));
 		}
 
 		$params['name'] = $params['render_as'];
 		$params['skip_prefixes'] = implode(',', $skip_prefixes);
 		return $this->Application->ParseBlock($params);
 	}
 
 	/**
 	 * Determines, that printed category/menu item is currently active (will also match parent category)
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsActive($params)
 	{
 		static $current_path = null;
 
 		if (!isset($current_path)) {
 			$sql = 'SELECT ParentPath
 					FROM ' . TABLE_PREFIX . 'Category
 					WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id');
 			$current_path = $this->Conn->GetOne($sql);
 		}
 
 		if (array_key_exists('parent_path', $params)) {
 			$test_path = $params['parent_path'];
 		}
 		else {
 			$template = $params['template'];
 			if ($template) {
 				// when using from "c:CachedMenu" tag
 				$sql = 'SELECT ParentPath
 						FROM ' . TABLE_PREFIX . 'Category
 						WHERE NamedParentPath = ' . $this->Conn->qstr('Content/' . $template);
 				$test_path = $this->Conn->GetOne($sql);
 			}
 			else {
 				// when using from "c:PrintList" tag
 				$cat_id = array_key_exists('cat_id', $params) && $params['cat_id'] ? $params['cat_id'] : false;
 				if ($cat_id === false) {
 					// category not supplied -> get current from PrintList
 					$category =& $this->getObject($params);
 				}
 				else {
 					if ("$cat_id" == 'Root') {
 						$cat_id = $this->Application->findModule('Name', $params['module'], 'RootCat');
 					}
 
 					$category =& $this->Application->recallObject($this->Prefix . '.-c' . $cat_id, $this->Prefix, Array ('skip_autoload' => true));
 					$category->Load($cat_id);
 				}
 
 				$test_path = $category->GetDBField('ParentPath');
 			}
 		}
 
 		return strpos($current_path, $test_path) !== false;
 	}
 
 	/**
 	 * Checks if user have one of required permissions
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function HasPermission($params)
 	{
 		$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		$params['raise_warnings'] = 0;
 		$object =& $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$params['cat_id'] = $object->isLoaded() ? $object->GetDBField('ParentPath') : $this->Application->GetVar('m_cat_id');
 		return $perm_helper->TagPermissionCheck($params);
 	}
 
 	/**
 	 * Prepares name for field with event in it (used only on front-end)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SubmitName($params)
 	{
 		return 'events[' . $this->Prefix . '][' . $params['event'] . ']';
 	}
 
 	/**
 	 * Returns last modification date of items in category / system
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function LastUpdated($params)
 	{
 		$category_id = (int)$this->Application->GetVar('m_cat_id');
 		$local = array_key_exists('local', $params) && ($category_id > 0) ? $params['local'] : false;
 
 		$serial_name = $this->Application->incrementCacheSerial('c', $local ? $category_id : null, false);
 		$cache_key = 'category_last_updated[%' . $serial_name . '%]';
 
 		$row_data = $this->Application->getCache($cache_key);
 
 		if ($row_data === false) {
 			if ($local && ($category_id > 0)) {
 				// scan only current category & it's children
 				list ($tree_left, $tree_right) = $this->Application->getTreeIndex($category_id);
 
 				$sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate
 		        		FROM ' . TABLE_PREFIX . 'Category
 		        		WHERE TreeLeft BETWEEN ' . $tree_left . ' AND ' . $tree_right;
 			}
 			else {
 				// scan all categories in system
 				$sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate
 		       			FROM ' . TABLE_PREFIX . 'Category';
 			}
 
 			$this->Conn->nextQueryCachable = true;
 			$row_data = $this->Conn->GetRow($sql);
 			$this->Application->setCache($cache_key, $row_data);
 		}
 
 		if (!$row_data) {
 			return '';
 		}
 
 		$date = $row_data[ $row_data['NewDate'] > $row_data['ModDate'] ? 'NewDate' : 'ModDate' ];
 
 		// format date
 		$format = isset($params['format']) ? $params['format'] : '_regional_DateTimeFormat';
 		if (preg_match("/_regional_(.*)/", $format, $regs)) {
 			$lang =& $this->Application->recallObject('lang.current');
 			if ($regs[1] == 'DateTimeFormat') {
 				// combined format
 				$format = $lang->GetDBField('DateFormat') . ' ' . $lang->GetDBField('TimeFormat');
 			}
 			else {
 				// simple format
 				$format = $lang->GetDBField($regs[1]);
 			}
 		}
 
 		return adodb_date($format, $date);
 	}
 
 	function CategoryItemCount($params)
 	{
 		$object =& $this->getObject($params);
 		/* @var $object kDBList */
 
 		$params['cat_id'] = $object->GetID();
 
 		$count_helper =& $this->Application->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		return $count_helper->CategoryItemCount($params['prefix'], $params);
 	}
 
 	/**
 	 * Returns prefix + any word (used for shared between categories per page settings)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function VarName($params)
 	{
 		return $this->Prefix.'_'.$params['type'];
 	}
 
 	/**
 	 * Checks if current category is valid symbolic link to another category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function IsCategorySymLink($params)
 	{
 		$object =& $this->getObject($params);
 		/* @var $object kDBList */
 
 		$sym_category_id = $object->GetDBField('SymLinkCategoryId');
 
 		if (is_null($sym_category_id))
 		{
 			return false;
 		}
 
 		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 		$sql = 'SELECT '.$id_field.'
 				FROM '.$table_name.'
 				WHERE '.$id_field.' = '.$sym_category_id;
 
 		return $this->Conn->GetOne($sql)? true : false;
 	}
 
 	/**
 	 * Returns module prefix based on root category for given
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function GetModulePrefix($params)
 	{
 		$object =& $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$parent_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 
 		$category_helper =& $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		$module_info = $category_helper->getCategoryModule($params, $parent_path);
 		return $module_info['Var'];
 	}
 
 	function ImageSrc($params)
 	{
 		list ($ret, $tag_processed) = $this->processAggregatedTag('ImageSrc', $params, $this->getPrefixSpecial());
 		return $tag_processed ? $ret : false;
 	}
 
 	function PageLink($params)
 	{
 		$params['m_cat_page'] = $this->Application->GetVar($this->getPrefixSpecial() . '_Page');
 
 		return parent::PageLink($params);
 	}
 
 	/**
 	 * Returns spelling suggestions against search keyword
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SpellingSuggestions($params)
 	{
 		$keywords = unhtmlentities( trim($this->Application->GetVar('keywords')) );
 		if (!$keywords) {
 			return ;
 		}
 
 		// 1. try to get already cached suggestion
 		$cache_key = 'search.suggestion[%SpellingDictionary%]:' . $keywords;
 		$suggestion = $this->Application->getCache($cache_key);
 
 		if ($suggestion !== false) {
 			return $suggestion;
 		}
 
 		$table_name = $this->Application->getUnitOption('spelling-dictionary', 'TableName');
 
 		// 2. search suggestion in database
 		$this->Conn->nextQueryCachable = true;
 		$sql = 'SELECT SuggestedCorrection
 				FROM ' . $table_name . '
 				WHERE MisspelledWord = ' . $this->Conn->qstr($keywords);
 		$suggestion = $this->Conn->GetOne($sql);
 
 		if ($suggestion !== false) {
 			$this->Application->setCache($cache_key, $suggestion);
 			return $suggestion;
 		}
 
 		// 3. suggestion not found in database, ask webservice
 		$app_id = $this->Application->ConfigValue('YahooApplicationId');
 		$url = 'http://search.yahooapis.com/WebSearchService/V1/spellingSuggestion?appid=' . $app_id . '&query=';
 
 		$curl_helper =& $this->Application->recallObject('CurlHelper');
 		/* @var $curl_helper kCurlHelper */
 
 		$xml_data = $curl_helper->Send($url . urlencode($keywords));
 
 		$xml_helper =& $this->Application->recallObject('kXMLHelper');
 		/* @var $xml_helper kXMLHelper */
 
 		$root_node =& $xml_helper->Parse($xml_data);
 
 		$result = $root_node->FindChild('RESULT');
 		/* @var $result kXMLNode */
 
 		if (is_object($result)) {
 			// webservice responded -> save in local database
 			$fields_hash = Array (
 			'MisspelledWord' => $keywords,
 			'SuggestedCorrection' => $result->Data,
 			);
 
 			$this->Conn->doInsert($fields_hash, $table_name);
 			$this->Application->setCache($cache_key, $result->Data);
 
 			return $result->Data;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Shows link for searching by suggested word
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SuggestionLink($params)
 	{
 		$params['keywords'] = $this->SpellingSuggestions($params);
 
 		return $this->Application->ProcessParsedTag('m', 'Link', $params);
 	}
 
 	function InitCatalogTab($params)
 	{
 		$tab_params['mode'] = $this->Application->GetVar('tm'); // single/multi selection possible
 		$tab_params['special'] = $this->Application->GetVar('ts'); // use special for this tab
 		$tab_params['dependant'] = $this->Application->GetVar('td'); // is grid dependant on categories grid
 
 		// set default params (same as in catalog)
 		if ($tab_params['mode'] === false) $tab_params['mode'] = 'multi';
 		if ($tab_params['special'] === false) $tab_params['special'] = '';
 		if ($tab_params['dependant'] === false) $tab_params['dependant'] = 'yes';
 
 		// pass params to block with tab content
 		$params['name'] = $params['render_as'];
 		$special = $tab_params['special'] ? $tab_params['special'] : $this->Special;
 		$params['prefix'] = trim($this->Prefix.'.'.$special, '.');
 
 		$prefix_append = $this->Application->GetVar('prefix_append');
 		if ($prefix_append) {
 			$params['prefix'] .= $prefix_append;
 		}
 
 		$default_grid = array_key_exists('default_grid', $params) ? $params['default_grid'] : 'Default';
 		$radio_grid = array_key_exists('radio_grid', $params) ? $params['radio_grid'] : 'Radio';
 
 		$params['cat_prefix'] = trim('c.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.');
 		$params['tab_mode'] = $tab_params['mode'];
 		$params['grid_name'] = ($tab_params['mode'] == 'multi') ? $default_grid : $radio_grid;
 		$params['tab_dependant'] = $tab_params['dependant'];
 		$params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name
 
 		if ($special == 'showall' || $special == 'user') {
 			$params['grid_name'] .= 'ShowAll';
 		}
 
 		// use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag
 		return $this->Application->ParseBlock($params, 1);
 	}
 
 	/**
 	 * Show CachedNavbar of current item primary category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CategoryName($params)
 	{
 		// show category cachednavbar of
 		$object =& $this->getObject($params);
 		$category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId');
 
 		$cache_key = 'category_paths[%CIDSerial:' . $category_id . '%][%PhrasesSerial%][Adm:' . (int)$this->Application->isAdmin . ']';
 		$category_path = $this->Application->getCache($cache_key);
 
 		if ($category_path === false) {
 			// not chached
 			if ($category_id > 0) {
 				$cached_navbar = $object->GetField('CachedNavbar');
 
 				if ($category_id == $object->GetDBField('ParentId')) {
 					// parent category cached navbar is one element smaller, then current ones
 					$cached_navbar = explode('&|&', $cached_navbar);
 					array_pop($cached_navbar);
 					$cached_navbar = implode('&|&', $cached_navbar);
 				}
 				else {
 					// no relation with current category object -> query from db
 					$language_id = (int)$this->Application->GetVar('m_lang');
 					if (!$language_id) {
 						$language_id = 1;
 					}
 
 					$sql = 'SELECT l' . $language_id . '_CachedNavbar
 							FROM ' . $object->TableName . '
 							WHERE ' . $object->IDField . ' = ' . $category_id;
 					$cached_navbar = $this->Conn->GetOne($sql);
 				}
 
 				$cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $cached_navbar);
 
 				$category_path = trim($this->CategoryName( Array('cat_id' => 0) ).' > '.str_replace('&|&', ' > ', $cached_navbar), ' > ');
 			}
 			else {
 				$category_path = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 			}
 
 			$this->Application->setCache($cache_key, $category_path);
 		}
 
 		return $category_path;
 	}
 
 	// structure related
 
 	/**
 	 * Returns page object based on requested params
 	 *
 	 * @param Array $params
 	 * @return PagesItem
 	 */
 	function &_getPage($params)
 	{
 		$page =& $this->Application->recallObject($this->Prefix . '.-virtual', null, $params);
 		/* @var $page kDBItem */
 
 		// 1. load by given id
 		$page_id = array_key_exists('page_id', $params) ? $params['page_id'] : false;
 		if ($page_id) {
 			if ($page_id != $page->GetID()) {
 				// load if different
 				$page->Load($page_id);
 			}
 
 			return $page;
 		}
 
 		// 2. load by template
 		$template = array_key_exists('page', $params) ? $params['page'] : '';
 		if (!$template) {
 			$template = $this->Application->GetVar('t');
 		}
 
 		// different path in structure AND design template differes from requested template
 		$structure_path_match = strtolower( $page->GetDBField('NamedParentPath') ) == strtolower('Content/' . $template);
 		$design_match = $page->GetDBField('CachedTemplate') == $template;
 
 		if (!$structure_path_match && !$design_match) {
 			// Same sql like in "c:getPassedID". Load, when current page object doesn't match requested page object
 			$themes_helper =& $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$page_id = $themes_helper->getPageByTemplate($template);
 
 			$page->Load($page_id);
 		}
 
 		return $page;
 	}
 
 	/**
 	 * Returns requested content block content of current or specified page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ContentBlock($params)
 	{
 		$num = getArrayValue($params, 'num');
 		if (!$num) {
 			return 'NO CONTENT NUM SPECIFIED';
 		}
 
 		$page =& $this->_getPage($params);
 		/* @var $page kDBItem */
 
 		if (!$page->isLoaded()) {
 			// page is not created yet => all blocks are empty
 			return '';
 		}
 
 		$page_id = $page->GetID();
 
 		$content =& $this->Application->recallObject('content.-block', null, Array ('skip_autoload' => true));
 		/* @var $content kDBItem */
 
 		$data = Array ('PageId' => $page_id, 'ContentNum' => $num);
 		$content->Load($data);
 
 		if (!$content->isLoaded()) {
 			// bug: missing content blocks are created even if user have no SMS-management rights
 			$content->SetFieldsFromHash($data);
 			$content->Create();
 		}
 
 		$edit_code_before = $edit_code_after = '';
 
 		if (EDITING_MODE == EDITING_MODE_CONTENT) {
 			$bg_color = isset($params['bgcolor']) ? $params['bgcolor'] : '#ffffff';
 			$url_params = Array (
 				'pass'			=>	'm,c,content',
 				'm_opener'		=>	'd',
 				'c_id'			=>	$page->GetID(),
 				'content_id'	=>	$content->GetID(),
 				'front'			=>	1,
 				'admin'			=>	1,
 				'__URLENCODE__'	=>	1,
 				'__NO_REWRITE__'=>	1,
 				'escape'		=>	1,
 				'index_file' => 'index.php',
 //				'bgcolor' => $bg_color,
 //				'__FORCE_SID__' => 1
 			);
 
 			// link from Front-End to admin, don't remove "index.php"
 			$edit_url = $this->Application->HREF('categories/edit_content', ADMIN_DIRECTORY, $url_params, 'index.php');
 			$edit_code_before = '
 				<div class="cms-edit-btn-container">
 					<div class="cms-edit-btn" onclick="$form_name=\'kf_cont_'.$content->GetID().'\'; std_edit_item(\'content\', \'categories/edit_content\');">
 						<div class="cms-btn-image">
 							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/content_mode.png" width="15" height="16" alt=""/>
 						</div>
 						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_EditContent', false, true) . ' '.(defined('DEBUG_MODE') && DEBUG_MODE ? " - #{$num}" : '').'</div>
 					</div>
 					<div class="cms-btn-content">';
 
 			$edit_form = '<form method="POST" style="display: inline; margin: 0px" name="kf_cont_'.$content->GetID().'" id="kf_cont_'.$content->GetID().'" action="'.$edit_url.'">';
 			$edit_form .= '<input type="hidden" name="c_id" value="'.$page->GetID().'"/>';
 			$edit_form .= '<input type="hidden" name="content_id" value="'.$content->GetID().'"/>';
 			$edit_form .= '<input type="hidden" name="front" value="1"/>';
 			$edit_form .= '<input type="hidden" name="bgcolor" value="'.$bg_color.'"/>';
 			$edit_form .= '<input type="hidden" name="m_lang" value="'.$this->Application->GetVar('m_lang').'"/>';
 			$edit_form .= '</form>';
 
 			$edit_code_after = '</div></div>';
 
 			if (array_key_exists('forms_later', $params) && $params['forms_later']) {
 				$all_forms = $this->Application->GetVar('all_forms');
 				$this->Application->SetVar('all_forms', $all_forms . $edit_form);
 			}
 			else {
 				$edit_code_after .= $edit_form;
 			}
 		}
 
 		if ($this->Application->GetVar('_editor_preview_') == 1) {
 			$data = $this->Application->RecallVar('_editor_preview_content_');
 		} else {
 			$data = $content->GetField('Content');
 		}
 
 		$data = $edit_code_before . $this->_transformContentBlockData($data, $params) . $edit_code_after;
 
 		if ($data != '') {
 			$this->Application->Parser->DataExists = true;
 		}
 
 		return $data;
 	}
 
 	/**
 	 * Apply all kinds of content block data transformations without rewriting ContentBlock tag
 	 *
 	 * @param string $data
 	 * @return string
 	 */
 	function _transformContentBlockData(&$data, $params)
 	{
 		return $data;
 	}
 
 	/**
 	 * Returns current page name or page based on page/page_id parameters
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used?
 	 */
 	function PageName($params)
 	{
 		$page =& $this->_getPage($params);
 
 		return $page->GetDBField('Name');
 	}
 
 	/**
 	 * Returns current/given page information
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PageInfo($params)
 	{
 		$page =& $this->_getPage($params);
 
 		switch ($params['type']) {
 			case 'title':
 				$db_field = 'Title';
 				break;
 
 			case 'htmlhead_title':
 				$db_field = 'Name';
 				break;
 
 			case 'meta_title':
 				$db_field = 'MetaTitle';
 				break;
 
 			case 'menu_title':
 				$db_field = 'MenuTitle';
 				break;
 
 			case 'meta_keywords':
 				$db_field = 'MetaKeywords';
 				$cat_field = 'Keywords';
 				break;
 
 			case 'meta_description':
 				$db_field = 'MetaDescription';
 				$cat_field = 'Description';
 				break;
 
 			case 'tracking':
 			case 'index_tools':
 				if (!EDITING_MODE) {
 					$tracking = $page->GetDBField('IndexTools');
 					return $tracking ? $tracking : $this->Application->ConfigValue('cms_DefaultTrackingCode');
 				}
 				// no break here on purpose
 
 			default:
 				return '';
 		}
 
 		$default = isset($params['default']) ? $params['default'] : '';
 		$val = $page->GetField($db_field);
 		if (!$default) {
 			if ($this->Application->isModuleEnabled('In-Portal')) {
 				if (!$val && ($params['type'] == 'meta_keywords' || $params['type'] == 'meta_description')) {
 					// take category meta if it's not set for the page
 					return $this->Application->ProcessParsedTag('c', 'Meta', Array('name' => $cat_field));
 				}
 			}
 		}
 
 		if (isset($params['force_default']) && $params['force_default']) {
 			return $default;
 		}
 
 		if (preg_match('/^_Auto:/', $val)) {
 			$val = $default;
 
 			/*if ($db_field == 'Title') {
 				$page->SetDBField($db_field, $default);
 				$page->Update();
 			}*/
 		}
 		elseif ($page->GetID() == false) {
 			return $default;
 		}
 
 		return $val;
 	}
 
 	/**
 	 * Includes admin css and js, that are required for cms usage on Front-Edn
 	 *
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function EditingScripts($params)
 	{
 		if ($this->Application->GetVar('admin_scripts_included') || !EDITING_MODE) {
 			return ;
 		}
 
 		$this->Application->SetVar('admin_scripts_included', 1);
 		$js_url = $this->Application->BaseURL() . 'core/admin_templates/js';
 
 		$minify_helper =& $this->Application->recallObject('MinifyHelper');
 		/* @var $minify_helper MinifyHelper */
 
 		$to_compress = Array (
 			$js_url . '/jquery/thickbox/thickbox.css',
 			$js_url . '/../incs/cms.css',
 		);
 
 		$css_compressed = $minify_helper->CompressScriptTag( Array ('files' => implode('|', $to_compress)) );
 
 		$ret = '<link rel="stylesheet" href="' . $css_compressed . '" type="text/css" media="screen"/>' . "\n";
 
 		if (EDITING_MODE == EDITING_MODE_DESIGN) {
 			$ret .= '	<style type="text/css" media="all">
 							div.movable-element .movable-header { cursor: move; }
 						</style>';
 		}
 
 
 		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery.pack.js"></script>' . "\n";
 		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery-ui.custom.min.js"></script>' . "\n";
 
 		$to_compress = Array (
 			$js_url . '/is.js',
 			$js_url . '/application.js',
 			$js_url . '/script.js',
 			$js_url . '/jquery/thickbox/thickbox.js',
 			$js_url . '/template_manager.js',
 		);
 
 		$js_compressed = $minify_helper->CompressScriptTag( Array ('files' => implode('|', $to_compress)) );
 
 		$ret .= '<script type="text/javascript" src="' . $js_compressed . '"></script>' . "\n";
 		$ret .= '<script language="javascript">' . "\n";
 		$ret .= "TB.pathToImage = '" . $js_url . "/jquery/thickbox/loadingAnimation.gif';" . "\n";
 
 		$template = $this->Application->GetVar('t');
 		$theme_id = $this->Application->GetVar('m_theme');
 
 		$url_params = Array ('block' => '#BLOCK#', 'theme-file_event' => '#EVENT#', 'theme_id' => $theme_id, 'source' => $template, 'pass' => 'all,theme-file', 'front' => 1, 'm_opener' => 'd', '__NO_REWRITE__' => 1, 'no_amp' => 1);
 		$edit_template_url = $this->Application->HREF('themes/template_edit', ADMIN_DIRECTORY, $url_params, 'index.php');
 
 		$url_params = Array ('theme-file_event' => 'OnSaveLayout', 'source' => $template, 'pass' => 'all,theme-file', '__NO_REWRITE__' => 1, 'no_amp' => 1);
 		$save_layout_url = $this->Application->HREF('index', '', $url_params);
 
 		$this_url = $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', '__NO_REWRITE__' => 1, 'no_amp' => 1));
 		$ret .= "var aTemplateManager = new TemplateManager('" . $edit_template_url . "', '" . $this_url . "', '" . $save_layout_url . "', " . (int)EDITING_MODE . ");\n";
 		$ret .= "var main_title = '" . addslashes( $this->Application->ConfigValue('Site_Name') ) . "';" . "\n";
 
 		$use_popups = (int)$this->Application->ConfigValue('UsePopups');
 		$ret .= "var \$use_popups = " . ($use_popups > 0 ? 'true' : 'false') . ";\n";
 		$ret .= "var \$modal_windows = " . ($use_popups == 2 ? 'true' : 'false') . ";\n";
 
 		if (EDITING_MODE != EDITING_MODE_BROWSE) {
 			$ret .= "var base_url = '" . $this->Application->BaseURL() . "';" . "\n";
 			$ret .= 'TB.closeHtml = \'<img src="' . $js_url . '/../img/close_window15.gif" width="15" height="15" style="border-width: 0px;" alt="close"/><br/>\';' . "\n";
 
 			$url_params = Array('m_theme' => '', 'pass' => 'm', 'm_opener' => 'r', '__NO_REWRITE__' => 1, 'no_amp' => 1);
 			$browse_url = $this->Application->HREF('catalog/catalog', ADMIN_DIRECTORY, $url_params, 'index.php');
 			$browse_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $browse_url);
 
 			$ret .= '
 				var topmost = window.top;
 
 				topmost.document.title = document.title + \' - ' . addslashes($this->Application->Phrase('la_AdministrativeConsole', false)) . '\';
 				t = \''.$this->Application->GetVar('t').'\';
 
 				if (window.parent.frames["menu"] != undefined) {
 					if ( $.isFunction(window.parent.frames["menu"].SyncActive) ) {
 						window.parent.frames["menu"].SyncActive("' . $browse_url . '");
 					}
 				}
 			';
 		}
 
 		$ret .= '</script>' . "\n";
 
 		if (EDITING_MODE != EDITING_MODE_BROWSE) {
 			// add form, so admin scripts could work
 			$ret .= '<form id="kernel_form" name="kernel_form" enctype="multipart/form-data" method="post" action="' . $browse_url . '">
 						<input type="hidden" name="MAX_FILE_SIZE" id="MAX_FILE_SIZE" value="' . MAX_UPLOAD_SIZE . '" />
 						<input type="hidden" name="sid" id="sid" value="' . $this->Application->GetSID() . '" />
 					</form>';
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Prints "Edit Page" button on cms page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function EditPage($params)
 	{
 		if (!EDITING_MODE) {
 			return '';
 		}
 
 		$display_mode = array_key_exists('mode', $params) ? $params['mode'] : false;
 		$edit_code = '';
 
 		$page =& $this->_getPage($params);
 
 		if (!$page->isLoaded() || (($display_mode != 'end') && (EDITING_MODE == EDITING_MODE_BROWSE))) {
 			// when "EditingScripts" tag is not used, make sure, that scripts are also included
 			return $this->EditingScripts($params);
 		}
 
 		// show "EditPage" button only for pages, that exists in structure
 		if ($display_mode != 'end') {
 			$edit_btn = '';
 
 			if (EDITING_MODE == EDITING_MODE_CONTENT) {
 				$url_params = Array(
 					'pass'			=>	'm,c',
 					'm_opener'		=>	'd',
 					'c_id'			=>	$page->GetID(),
 					'c_mode'		=>	't',
 					'c_event'		=>	'OnEdit',
 					'front'			=>	1,
 					'__URLENCODE__'	=>	1,
 					'__NO_REWRITE__'=>	1,
 					'index_file' => 'index.php',
 				);
 
 				$edit_url = $this->Application->HREF('categories/categories_edit', ADMIN_DIRECTORY, $url_params);
 
 				$edit_btn .= '
 					<div class="cms-section-properties-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onmouseover="window.status=\'' . addslashes($edit_url) . '\'; return true" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'c\', \'categories/categories_edit\');">
 						<div class="cms-btn-image">
 							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png" width="15" height="16" alt=""/>
 						</div>
 						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SectionProperties', false, true) . '</div>
 					</div>' . "\n";
 			} elseif (EDITING_MODE == EDITING_MODE_DESIGN) {
 				$url_params = Array(
 					'pass'			=>	'm,theme,theme-file',
 					'm_opener'		=>	'd',
 					'theme_id'		=>	$this->Application->GetVar('m_theme'),
 					'theme_mode'	=>	't',
 					'theme_event'	=>	'OnEdit',
 					'theme-file_id' =>	$this->_getThemeFileId(),
 					'front'			=>	1,
 					'__URLENCODE__'	=>	1,
 					'__NO_REWRITE__'=>	1,
 					'index_file' => 'index.php',
 				);
 
 				$edit_url = $this->Application->HREF('themes/file_edit', ADMIN_DIRECTORY, $url_params);
 
 				$edit_btn .= '
 					<div class="cms-layout-btn-container"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . '>
 						<div class="cms-save-layout-btn" onclick="aTemplateManager.saveLayout(); return false;">
 							<div class="cms-btn-image">
 								<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/save_button.gif" width="16" height="16" alt=""/>
 							</div>
 							<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SaveChanges', false, true) . '</div>
 						</div>
 						<div class="cms-cancel-layout-btn" onclick="aTemplateManager.cancelLayout(); return false;">
 							<div class="cms-btn-image">
 								<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/cancel_button.gif" width="16" height="16" alt=""/>
 							</div>
 							<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_Cancel', false, true) . '</div>
 						</div>
 					</div>
 
 					<div class="cms-section-properties-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onmouseover="window.status=\'' . addslashes($edit_url) . '\'; return true" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'theme\', \'themes/file_edit\');">
 						<div class="cms-btn-image">
 							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png" width="15" height="16" alt=""/>
 						</div>
 						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SectionTemplate', false, true) . '</div>
 					</div>' . "\n";
 			}
 
 			if ($display_mode == 'start') {
 				// button with border around the page
 				$edit_code .= '<div class="cms-section-properties-btn-container">' . $edit_btn . '<div class="cms-btn-content">';
 
 			}
 			else {
 				// button without border around the page
 				$edit_code .= $edit_btn;
 			}
 		}
 
 		if ($display_mode == 'end') {
 			// draw border around the page
 			$edit_code .= '</div></div>';
 		}
 
 		if ($display_mode != 'end') {
 			$edit_code .= '<form method="POST" style="display: inline; margin: 0px" name="kf_'.$page->GetID().'" id="kf_'.$page->GetID().'" action="'.$edit_url.'"></form>';
 
 			// when "EditingScripts" tag is not used, make sure, that scripts are also included
 			$edit_code .= $this->EditingScripts($params);
 		}
 
 		return $edit_code;
 	}
 
 	function _getThemeFileId()
 	{
 		$template = $this->Application->GetVar('t');
 
 		if (!$this->Application->TemplatesCache->TemplateExists($template) && !$this->Application->isAdmin) {
 			$cms_handler =& $this->Application->recallObject($this->Prefix . '_EventHandler');
 			/* @var $cms_handler CategoriesEventHandler */
 
 			$template = ltrim($cms_handler->GetDesignTemplate(), '/');
 		}
 
 		$file_path = dirname($template) == '.' ? '' : '/' . dirname($template);
 		$file_name = basename($template);
 
 		$sql = 'SELECT FileId
 				FROM ' . TABLE_PREFIX . 'ThemeFiles
 				WHERE (ThemeId = ' . (int)$this->Application->GetVar('m_theme') . ') AND (FilePath = ' . $this->Conn->qstr($file_path) . ') AND (FileName = ' . $this->Conn->qstr($file_name . '.tpl') . ')';
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Builds site menu
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CachedMenu($params)
 	{
 		$menu_helper =& $this->Application->recallObject('MenuHelper');
 		/* @var $menu_helper MenuHelper */
 
 		return $menu_helper->menuTag($this->getPrefixSpecial(), $params);
 	}
 
 	/**
 	 * Trick to allow some kind of output formatting when using CachedMenu tag
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function SplitColumn($params)
 	{
 		return $this->Application->GetVar($params['i']) > ceil($params['total'] / $params['columns']);
 	}
 
 	/**
 	 * Returns direct children count of given category
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function HasSubCats($params)
 	{
 		$sql = 'SELECT COUNT(*)
 				FROM ' . TABLE_PREFIX . 'Category
 				WHERE ParentId = ' . $params['cat_id'];
 
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Prints sub-pages of given/current page.
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo This could be reached by using "parent_cat_id" parameter. Only difference here is new block parameter "path". Need to rewrite.
 	 */
 	function PrintSubPages($params)
 	{
 		$list =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List', $params);
 		/* @var $list kDBList */
 
 		$category_id = array_key_exists('category_id', $params) ? $params['category_id'] : $this->Application->GetVar('m_cat_id');
 
 		$list->addFilter('current_pages', TABLE_PREFIX . 'CategoryItems.CategoryId = ' . $category_id);
 		$list->Query();
 		$list->GoFirst();
 
 		$o = '';
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		while (!$list->EOL()) {
 			$block_params['path'] = $list->GetDBField('Path');
 			$o .= $this->Application->ParseBlock($block_params);
 
 			$list->GoNext();
 		}
 
 		return $o;
 	}
 
 	/**
 	 * Builds link for browsing current page on Front-End
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PageBrowseLink($params)
 	{
 		$object =& $this->getObject($params);
 
 		$themes_helper =& $this->Application->recallObject('ThemesHelper');
 		/* @var $themes_helper kThemesHelper */
 
 		$site_config_helper =& $this->Application->recallObject('SiteConfigHelper');
 		/* @var $site_config_helper SiteConfigHelper */
 
 		$settings = $site_config_helper->getSettings();
 
 		$url_params = Array (
 			'm_cat_id' => $object->GetID(),
 			'm_theme' => $themes_helper->getCurrentThemeId(),
 			'editing_mode' => $settings['default_editing_mode'],
 			'pass' => 'm',
 			'admin' => 1,
 			'index_file' => 'index.php'
 		);
 
 		if ($this->Application->ConfigValue('UseModRewrite')) {
 			$url_params['__MOD_REWRITE__'] = 1;
 		}
 
 		return $this->Application->HREF($object->GetDBField('NamedParentPath'), '_FRONT_END_', $url_params);
 	}
 
 	/**
 	 * Builds link to cms page (used?)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ContentPageLink($params)
 	{
 		$object =& $this->getObject($params);
 		$params['t'] = $object->GetDBField('NamedParentPath');
 		$params['m_cat_id'] = 0;
 
 		return $this->Application->ProcessParsedTag('m', 'Link', $params);
 	}
 
 	/**
 	 * Prepares cms page description for search result page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SearchDescription($params)
 	{
 		$object =& $this->getObject($params);
 		$desc =  $object->GetField('MetaDescription');
 		if (!$desc) {
 			$sql = 'SELECT *
 					FROM ' . TABLE_PREFIX . 'PageContent
 					WHERE PageId = ' . $object->GetID() . ' AND ContentNum = 1';
 			$content = $this->Conn->GetRow($sql);
 
 			if ($content['l'.$this->Application->GetVar('m_lang').'_Content']) {
 				$desc = $content['l'.$this->Application->GetVar('m_lang').'_Content'];
 			}
 			else {
 				$desc = $content['l'.$this->Application->GetDefaultLanguageId().'_Content'];
 			}
 		}
 
 		return mb_substr($desc, 0, 300).(mb_strlen($desc) > 300 ? '...' : '');
 	}
 
 	/**
 	 * Simplified version of "c:CategoryLink" for "c:PrintList"
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used? Needs refactoring.
 	 */
 	function EnterCatLink($params)
 	{
 		$object =& $this->getObject($params);
 
 		$url_params = Array ('pass' => 'm', 'm_cat_id' => $object->GetID());
 		return $this->Application->HREF($params['template'], '', $url_params);
 	}
 
 	/**
 	 * Simplified version of "c:CategoryPath", that do not use blocks for rendering
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used? Maybe needs to be removed.
 	 */
 	function PagePath($params)
 	{
 		$object =& $this->getObject($params);
 		$path = $object->GetField('CachedNavbar');
 		if ($path) {
 			$items = explode('&|&', $path);
 			array_shift($items);
 			return implode(' -&gt; ', $items);
 		}
 
 		return '';
 	}
 
 	/**
 	 * Returns configuration variable value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Needs to be replaced with "m:GetConfig" tag; Not used now (were used on structure_edit.tpl).
 	 */
 	function AllowManualFilenames($params)
 	{
 		return $this->Application->ConfigValue('ProjCMSAllowManualFilenames');
 	}
 
 	/**
 	 * Draws path to current page (each page can be link to it)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CurrentPath($params)
 	{
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $block_params['render_as'];
 
 		$object =& $this->Application->recallObject($this->Prefix);
 		/* @var $object kDBItem */
 
 		$category_ids = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 
 		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 		$language = (int)$this->Application->GetVar('m_lang');
 
 		if (!$language) {
 			$language = 1;
 		}
 
 		$sql = 'SELECT l'.$language.'_Name AS Name, NamedParentPath
 				FROM '.$table_name.'
 				WHERE '.$id_field.' IN ('.implode(',', $category_ids).')';
 		$categories_data = $this->Conn->Query($sql);
 
 		$ret = '';
 		foreach ($categories_data as $index => $category_data) {
 			if ($category_data['Name'] == 'Content') {
 				continue;
 			}
 			$block_params['title'] = $category_data['Name'];
 			$block_params['template'] = preg_replace('/^Content\//i', '', $category_data['NamedParentPath']);
 			$block_params['is_first'] = $index == 1; // because Content is 1st element
 			$block_params['is_last'] = $index == count($categories_data) - 1;
 
 			$ret .= $this->Application->ParseBlock($block_params);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Synonim to PrintList2 for "onlinestore" theme
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ListPages($params)
 	{
 		return $this->PrintList2($params);
 	}
 
 	/**
 	 * Returns information about parser element locations in template
 	 *
 	 * @param Array $params
 	 * @return mixed
 	 */
 	function BlockInfo($params)
 	{
 		if (!EDITING_MODE) {
 			return '';
 		}
 
 		$template_helper =& $this->Application->recallObject('TemplateHelper');
 		/* @var $template_helper TemplateHelper */
 
 		return $template_helper->blockInfo( $params['name'] );
 	}
 
 	/**
 	 * Hide all editing tabs except permission tab, when editing "Home" (ID = 0) category
 	 *
 	 * @param Array $params
 	 */
 	function ModifyUnitConfig($params)
 	{
 		$root_category = $this->Application->RecallVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 		if (!$root_category) {
 			return ;
 		}
 
 		$edit_tab_presets = $this->Application->getUnitOption($this->Prefix, 'EditTabPresets');
 		$edit_tab_presets['Default'] = Array (
 			'permissions' => $edit_tab_presets['Default']['permissions'],
 		);
 		$this->Application->setUnitOption($this->Prefix, 'EditTabPresets', $edit_tab_presets);
 	}
 
 	/**
 	 * Prints catalog export templates
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PrintCatalogExportTemplates($params)
 	{
 		$prefixes = explode(',', $params['prefixes']);
 
 		$ret = Array ();
 		foreach ($prefixes as $prefix) {
 			if ($this->Application->prefixRegistred($prefix)) {
 				$module_path = $this->Application->getUnitOption($prefix, 'ModuleFolder') . '/';
 				$module_name = $this->Application->findModule('Path', $module_path, 'Name');
 
 				$ret[$prefix] = mb_strtolower($module_name) . '/export';
 			}
 		}
 
 		$json_helper =& $this->Application->recallObject('JSONHelper');
 		/* @var $json_helper JSONHelper */
 
 		return $json_helper->encode($ret);
 	}
 
 	/**
 	 * Checks, that "view in browse mode" functionality available
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function BrowseModeAvailable($params)
 	{
 		$valid_special = $params['Special'] != 'user';
 		$not_selector = $this->Application->GetVar('type') != 'item_selector';
 
 		return $valid_special && $not_selector;
 	}
 
 	/**
 	 * Returns a link for editing product
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ItemEditLink($params)
 	{
 		$object =& $this->getObject();
 		/* @var $object kDBList */
 
 		$edit_template = $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePath') . '/' . $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePrefix') . 'edit';
 
 		$url_params = Array (
 			'm_opener'				=>	'd',
 			$this->Prefix.'_mode'	=>	't',
 			$this->Prefix.'_event'	=>	'OnEdit',
 			$this->Prefix.'_id'		=>	$object->GetID(),
 			'm_cat_id'				=>	$object->GetDBField('ParentId'),
 			'pass'					=>	'all,'.$this->Prefix,
 			'no_pass_through'		=>	1,
 		);
 
 		return $this->Application->HREF($edit_template,'', $url_params);
 	}
 
 	function RelevanceIndicator($params)
 	{
 		$object =& $this->getObject($params);
 
 		$search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 		$sql = 'SELECT Relevance
 				FROM '.$search_results_table.'
 				WHERE ResourceId = '.$object->GetDBField('ResourceId');
 
     	$percents_off = (int)(100 - (100 * $this->Conn->GetOne($sql)));
     	$percents_off = ($percents_off < 0) ? 0 : $percents_off;
     	if ($percents_off) {
         	$params['percent_off'] = $percents_off;
     		$params['percent_on'] = 100 - $percents_off;
         	$params['name'] = $this->SelectParam($params, 'relevance_normal_render_as,block_relevance_normal');
     	}
     	else {
     		$params['name'] = $this->SelectParam($params, 'relevance_full_render_as,block_relevance_full');
     	}
     	return $this->Application->ParseBlock($params);
 	}
 
 	/**
 	 * Returns list of categories, that have category add/edit permission
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function AllowedCategoriesJSON($params)
 	{
 		if ($this->Application->RecallVar('user_id') == USER_ROOT) {
 			$categories = true;
 		}
 		else {
 			$object =& $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$perm_prefix = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix');
 			$categories = $perm_helper->getPermissionCategories($perm_prefix . '.' . ($object->IsNewItem() ? 'ADD' : 'MODIFY'));
 		}
 
 		$json_helper =& $this->Application->recallObject('JSONHelper');
 		/* @var $json_helper JSONHelper */
 
 		return $json_helper->encode($categories);
 	}
 
 	function PageEditable($params)
 	{
 		if ($this->Application->isDebugMode()) {
 			return true;
 		}
 
 		$object =& $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return !$object->GetDBField('Protected');
 	}
 }
\ No newline at end of file
Index: branches/5.1.x/core/units/categories/categories_event_handler.php
===================================================================
--- branches/5.1.x/core/units/categories/categories_event_handler.php	(revision 13986)
+++ branches/5.1.x/core/units/categories/categories_event_handler.php	(revision 13987)
@@ -1,2441 +1,2441 @@
 <?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 standart permission mapping
 		 *
 		 */
 		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 permissions of user
 		 *
 		 * @param kEvent $event
 		 */
 		function CheckPermission(&$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 == '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
 			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
 
 			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
 		 */
 		function OnEdit(&$event)
 		{
 			$category_id = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
-			$home_category = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$home_category = $this->Application->getBaseCategory();
 
 			$this->Application->StoreVar('IsRootCategory_'.$this->Application->GetVar('m_wid'), ($category_id === '0') || ($category_id == $home_category));
 
 			parent::OnEdit($event);
 		}
 
 		/**
 		 * 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();
 
 			$this->finalizePopup($event);
 		}
 
 		/**
 		 * Apply system filter to categories list
 		 *
 		 * @param kEvent $event
 		 */
 		function SetCustomQuery(&$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.'Category
 							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 ($event->getEventParam('parent_cat_id') !== false) {
 				$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->findModule('Name', 'Core', 'RootCat');
+				$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', TABLE_PREFIX.'Category.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']);
 					}
 				}
 				else {
 					$object->addFilter('parent_filter', 'ParentId = '.$parent_cat_id);
 				}
 			}
 
 			$object->addFilter('perm_filter', 'PermId = 1'); // check for CATEGORY.VIEW permission
 			if ($this->Application->RecallVar('user_id') != USER_ROOT) {
 				// apply permission filters to all users except "root"
 				$groups = explode(',',$this->Application->RecallVar('UserGroups'));
 				foreach ($groups as $group) {
 					$view_filters[] = 'FIND_IN_SET('.$group.', acl)';
 				}
 				$view_filter = implode(' OR ', $view_filters);
 				$object->addFilter('perm_filter2', $view_filter);
 			}
 
 			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 ();
 
 			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->getUnitOption('rel', 'TableName');
 				$item_type = (int)$this->Application->getUnitOption($event->Prefix, 'ItemType');
 
 				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));
 				$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 '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
 								WHERE CategoryId = '.$parent_cat_id
 							);
 
 				$sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'Relationship
 						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.'Relationship
 						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');
 				$resource_id = $this->Conn->GetOne('
 								SELECT ResourceId FROM '.$this->Application->getUnitOption('p', 'TableName').'
 								WHERE ProductId = '.$product_id
 							);
 
 				$sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'Relationship
 						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.'Relationship
 						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'
 				);
 
 				$type = $this->Application->GetVar('search_type', 'simple');
 
 				if ($keywords = $event->getEventParam('keyword_string')) {
 					// processing keyword_string param of ListProducts tag
 					$this->Application->SetVar('keywords', $keywords);
 					$type = 'simple';
 				}
 
 				$search_event = $event_mapping[$type];
 				$this->$search_event($event);
 
 				$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 				$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 				if ($this->Conn->Query($sql)) {
 					$search_res_ids = $this->Conn->GetCol('SELECT ResourceId FROM '.$search_table);
 				}
 
 				if (isset($search_res_ids) && $search_res_ids) {
 					$type_clauses['search']['include'] = '%1$s.ResourceId IN ('.implode(',', $search_res_ids).')';
 					$type_clauses['search']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $search_res_ids).')';
 				}
 				else {
 					$type_clauses['search']['include'] = '0';
 					$type_clauses['search']['except'] = '1';
 				}
 
 				$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));
 		}
 
 		/**
 		 * Returns current theme id
 		 *
 		 * @return int
 		 */
 		function _getCurrentThemeId()
 		{
 			$themes_helper =& $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			return (int)$themes_helper->getCurrentThemeId();
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function getPassedID(&$event)
 		{
 			if (($event->Special == 'page') || ($event->Special == '-virtual') || ($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)) {
 					$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
 							FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
 							WHERE
 								(
 									(NamedParentPath = ' . $this->Conn->qstr($template) . ') OR
 									(NamedParentPath = ' . $this->Conn->qstr('Content/' . $template) . ') OR
 									(`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplate = ' . $this->Conn->qstr($template) . ')
 								) AND (ThemeId = ' . $this->_getCurrentThemeId() . ' OR ThemeId = 0)';
 
 					$page_id = $this->Conn->GetOne($sql);
 				}
 				else {
 					$page_id = $page_by_template[$template];
 				}
 
 				if ($page_id === false && EDITING_MODE) {
 					// create missing pages, when in editing mode
 					$object =& $this->Application->recallObject($this->Prefix . '.rebuild', null, Array('skip_autoload' => true));
 					/* @var $object kDBItem */
 
 					$created = $this->_prepareAutoPage($object, $template, null, SMS_MODE_AUTO, false); // create virtual (not system!) page
 					if ($created) {
 						if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild') || !$this->Application->isAdmin) {
 							$updater =& $this->Application->recallObject('kPermCacheUpdater');
 							/* @var $updater kPermCacheUpdater */
 
 							$updater->OneStepRun();
 						}
 
 						$this->_resetMenuCache();
 
 						$this->Application->RemoveVar('PermCache_UpdateRequired');
 
 						$page_id = $object->GetID();
 						$this->Application->SetVar('m_cat_id', $page_id);
 					}
 				}
 
 				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
 		 */
 		function prepareObject(&$object, &$event)
 		{
 			if ($event->Special != '-virtual') {
 				$object =& $event->getObject( Array('skip_autoload' => true) );
 
 				$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
 					)');
 			}
 		}
 
 		/**
 		 * Set correct parent path for newly created categories
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterCopyToLive(&$event)
 		{
 			$parent_path = false;
 			$object =& $this->Application->recallObject($event->Prefix.'.-item', null, Array('skip_autoload' => true, 'live_table' => true));
 			$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 = Array('ParentPath' => $object->buildParentPath());
 					$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->recallObject('kPermCacheUpdater', null, array('strict_path' => $parent_path));
 				$cache_updater->OneStepRun();
 				$cache_updater->StrictPath = false;
 			}
 		}
 
 		/**
 		 * Set cache modification mark if needed
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeDeleteFromLive(&$event)
 		{
 			$id = $event->getEventParam('id');
 
 			// loding anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event
 			$temp_object =& $event->getObject( Array('skip_autoload' => true) );
 			$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));
 			$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;
 				}
 			}
 		}
 
 		/**
 		 * 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
 		 */
 		function OnPreCreate(&$event)
 		{
 			// 1. for permission editing of Home category
 			$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 
 			parent::OnPreCreate($event);
 
 			$object =& $event->getObject();
 
 			// 2. preset template
 			$category_id = $this->Application->GetVar('m_cat_id');
-			$root_category = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$root_category = $this->Application->getBaseCategory();
 			if ($category_id == $root_category) {
 				$object->SetDBField('Template', $this->_getDefaultDesign());
 			}
 
 			$priority_helper =& $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			// 3. prepare priorities dropdown
 			$priority_helper->preparePriorities($event, true, 'ParentId = ' . $category_id);
 
 			// 4. set default owner
 			$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 		}
 
 		/**
 		 * Checks cache update mark and redirect to cache if needed
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSave(&$event)
 		{
 			$ids = $this->getSelectedIDs($event, true);
 			$is_editing = implode('', $ids);
 
 			if ($is_editing) {
 				$old_statuses = $this->_getCategoryStatus($ids);
 			}
 
 			$object =& $event->getObject();
 			/* @var $object CategoriesItem */
 
 			/*if ($object->IsRoot()) {
 				$event->setEventParam('master_ids', Array(0));
 				$this->RemoveRequiredFields($object);
 			}*/
 
 			parent::OnSave($event);
 
 			if ($event->status != erSUCCESS) {
 				return ;
 			}
 
 			// 1. update priorities
 			$tmp = $this->Application->RecallVar('priority_changes'.$this->Application->GetVar('m_wid'));
 			$changes = $tmp ? unserialize($tmp) : Array ();
 			$changed_ids = array_keys($changes);
 
 			$priority_helper =& $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			$priority_helper->updatePriorities($event, $changes, Array (0 => $event->getEventParam('ids')));
 
 			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) {
 				// 2. 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->EmailEventUser($email_event, $object->GetDBField('CreatedById'));
 					}
 				}
 			}
 		}
 
 		/**
 		 * Returns statuses of given categories
 		 *
 		 * @param Array $category_ids
 		 * @return Array
 		 */
 		function _getCategoryStatus($category_ids)
 		{
 			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 			$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 succsess
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreSaveCreated(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object CategoriesItem */
 
 			if ($object->IsRoot()) {
 				// don't create root category while saving permissions
 				return ;
 			}
 
 			$priority_helper =& $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			$category_id = $this->Application->GetVar('m_cat_id');
 			$priority_helper->preparePriorities($event, true, 'ParentId = ' . $category_id);
 
 			parent::OnPreSaveCreated($event);
 		}
 
 		/**
 		 * Deletes sym link to other category
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemDelete(&$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);
 		}
 
 
 		/**
 		* Exclude root categories from deleting
 		*
 		* @param kEvent $event
 		*/
 		function customProcessing(&$event, $type)
 		{
 			if ($event->Name == 'OnMassDelete' && $type == 'before') {
 				$ids = $event->getEventParam('ids');
 				if (!$ids || $this->Application->ConfigValue('AllowDeleteRootCats')) {
 					return ;
 				}
 
 				// 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);
 				}
 			}
 
 			$change_events = Array ('OnPreSave', 'OnPreSaveCreated', 'OnUpdate', 'OnSave');
 			if ($type == 'after' && in_array($event->Name, $change_events)) {
 				$object =& $event->getObject();
 
 				$tmp = $this->Application->RecallVar('priority_changes'.$this->Application->GetVar('m_wid'));
 				$changes = $tmp ? unserialize($tmp) : array();
 
 				if (!isset($changes[$object->GetID()])) {
 					$changes[$object->GetId()]['old'] = $object->GetID() == 0 ? 'new' : $object->GetDBField('OldPriority');
 				}
 
 				if ($changes[$object->GetId()]['old'] == $object->GetDBField('Priority')) return ;
 
 				$changes[$object->GetId()]['new'] = $object->GetDBField('Priority');
 				$changes[$object->GetId()]['parent'] = $object->GetDBField('ParentId');
 
 				$this->Application->StoreVar('priority_changes'.$this->Application->GetVar('m_wid'), serialize($changes));
 			}
 		}
 
 		/**
 		 * 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 recurse 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
 		 */
 		function OnMassDelete(&$event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = erFAIL;
 				return;
 			}
 
 			$ids = $this->StoreSelectedIDs($event);
 			$to_delete = array();
 			if ($recycle_bin = $this->Application->ConfigValue('RecycleBinFolder')) {
 				$rb =& $this->Application->recallObject('c.recycle', null, Array ('skip_autoload' => true));
 				$rb->Load($recycle_bin);
 				$cat =& $event->getObject(Array('skip_autoload' => true));
 				foreach ($ids as $id) {
 					$cat->Load($id);
 					if (preg_match('/^'.preg_quote($rb->GetDBField('ParentPath'),'/').'/', $cat->GetDBField('ParentPath'))) {
 						$to_delete[] = $id;
 						continue;
 					}
 					$cat->SetDBField('ParentId', $recycle_bin);
 					$cat->Update();
 				}
 				$ids = $to_delete;
 				$event->redirect = 'categories/cache_updater';
 			}
 
 			$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);
 
 			// update priorities
 			$priority_helper =& $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			// after deleting categories, all priorities should be recalculated
 			$parent_id = $this->Application->GetVar('m_cat_id');
 			$priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id);
 
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 			$this->_resetMenuCache();
 		}
 
 		/**
 		 * 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');
 			$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');
 			$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Controls all item paste operations. Can occur only with filled clipbord.
 		 *
 		 * @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->redirect = $paste_event->redirect;
 				$event->redirect_params = $paste_event->redirect_params;
 				$event->status = $paste_event->status;
 			}
 		}
 
 		/**
 		 * 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 subitems from clipboard
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPaste(&$event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event)) {
 				$event->status = erFAIL;
 				return ;
 			}
 
 			$clipboard_data = $event->getEventParam('clipboard_data');
 
 			if (!$clipboard_data['cut'] && !$clipboard_data['copy']) {
 				return false;
 			}
 
 			// 1. get ParentId of moved category(-es) before it gets updated!!!)
 			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
 
 			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']) {
 				$priority_helper->recalculatePriorities($event, 'ParentId = '.$source_category_id);
 			}
 
 			// recalculate priorities of newly pasted categories in destination category
 			$parent_id = $this->Application->GetVar('m_cat_id');
 			$priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id);
 
 			if ($clipboard_data['cut'] || $clipboard_data['copy']) {
 				// rebuild with progress bar
 				if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) {
 					$updater =& $this->Application->recallObject('kPermCacheUpdater');
 					/* @var $updater kPermCacheUpdater */
 
 					$updater->OneStepRun();
 				}
 				else {
 					$event->redirect = 'categories/cache_updater';
 				}
 
 				$this->_resetMenuCache();
 				$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')
 			{
 				$saved_cat_id = $this->Application->GetVar('m_cat_id');
 				$cat_ids = $event->getEventParam('cat_ids');
 
 				$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 				$table = $this->Application->getUnitOption($event->Prefix, 'TableName');
 				$ids_sql = 'SELECT '.$id_field.' FROM '.$table.' 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');
 					if($item_ids) $temp->CloneItems($event->Prefix, $event->Special, $item_ids);
 				}
 
 				$this->Application->SetVar('m_cat_id', $saved_cat_id);
 			}
 		}*/
 
 		/**
 		 * Cleares 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
 		 */
 		function OnBeforeItemCreate(&$event)
 		{
 			$this->_beforeItemChange($event);
 
 			if ($this->Application->isAdminUser || $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 =& $event->getObject();
 				/* @var $object kDBItem */
 
 				$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);
 
 				/*if (!$this->Application->isAdminUser) {
 					$object->SetDBField('IsMenu', 0); // add all suggested categories as non-menu
 				}*/
 			}
 			else {
 				$event->status = 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
 		 */
 		function OnBeforeItemUpdate(&$event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			if ($object->GetChangedFields()) {
 				$object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
 			}
 
 			$this->_beforeItemChange($event);
 		}
 
 		/**
 		 * Performs redirect to correct suggest confirmation template
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCreate(&$event)
 		{
 			parent::OnCreate($event);
 
 			if ($this->Application->isAdminUser || $event->status != erSUCCESS) {
 				// don't sent email or rebuild cache directly after category is created by admin
 				return ;
 			}
 
 			$object =& $event->getObject();
 
 			$cache_updater =& $this->Application->recallObject('kPermCacheUpdater', null, array('strict_path' => $object->GetDBField('ParentPath')));
 			$cache_updater->OneStepRun();
 			$cache_updater->StrictPath = false;
 
 			$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
 			$perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix');
 
 			$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
 			$this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix);
 			$this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField('CreatedById'));
 		}
 
 		/**
 		 * Returns current per-page setting for list
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function getPerPage(&$event)
 		{
 			if (!$this->Application->isAdmin) {
 				$event->setEventParam('same_special', true);
 			}
 
 			return parent::getPerPage($event);
 		}
 
 		/**
 		 * Set's correct page for list
 		 * based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @access private
 		 * @see OnListBuild
 		 */
 		function SetPagination(&$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();
 						$object->SetPage($page);
 					}
 				}
 			}
 		}
 
 		/**
 		 * Apply same processing to each item beeing selected in grid
 		 *
 		 * @param kEvent $event
 		 * @access private
 		 */
 		function iterateItems(&$event)
 		{
 			if ($event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline') {
 				return parent::iterateItems($event);
 			}
 
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = erFAIL;
 				return;
 			}
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ($ids) {
 				$propagate_category_status = $this->Application->GetVar('propagate_category_status');
 				$status_field = array_shift( $this->Application->getUnitOption($event->Prefix,'StatusField') );
 
 				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 = erSUCCESS;
 
 						$email_event = $event->Name == 'OnMassApprove' ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
 						$this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById'));
 					}
 					else {
 						$event->status = 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
 		 */
 		function checkItemStatus(&$event)
 		{
 			$status_fields = $this->Application->getUnitOption($event->Prefix,'StatusField');
 			if (!$status_fields) {
 				return true;
 			}
 
 			$status_field = array_shift($status_fields);
 			if ($status_field == 'Status' || $status_field == 'Enabled') {
 				$object =& $event->getObject();
 				if (!$object->isLoaded()) {
 					return true;
 				}
 
 				return $object->GetDBField($status_field) == STATUS_ACTIVE || $object->GetDBField($status_field) == 4;
 			}
 			return true;
 		}
 
 		// ============= 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)
 		 *
 		 * @return string
 		 */
 		function GetDesignTemplate($t = null)
 		{
 			if (!isset($t)) {
 				$t = $this->Application->GetVar('t');
 			}
 
 			$page =& $this->Application->recallObject($this->Prefix . '.-virtual', null, Array ('page' => $t));
 			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
 			$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 kDBItem */
 
 			$now = adodb_mktime();
 
 			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'));
 				}
 			}
 
 			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->findModule('Name', 'Core', 'RootCat');
+			$root_category = $this->Application->getBaseCategory();
 
 			if (($object->GetDBField('ParentId') == $root_category) && ($object->GetDBField('Template') == CATEGORY_TEMPLATE_INHERIT)) {
 				$object->SetError('Template', 'no_inherit');
 			}
 
 			if (!$this->Application->isAdminUser) {
 				// 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_NotAllowed');
 				}
 			}
 		}
 
 		/**
 		 * Sets page name to requested field in case when:
 		 * 1. page was auto created (through theme file rebuld)
 		 * 2. requested field is emtpy
 		 *
 		 * @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
 		 */
 		function OnBeforeItemDelete(&$event)
 		{
 			$object =& $event->getObject();
 
 			if ( $object->GetDBField('Protected') && !$this->Application->isDebugMode() ) {
 				$event->status = erFAIL;
 			}
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param StructureItem $object
 		 * @param string $template
 		 */
 		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 autocreate system pages, when browsing through site
 				return false;
 			}
 
 			if (!isset($theme_id)) {
 				$theme_id = $this->_getCurrentThemeId();
 			}
 
-			$root_category = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$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 kDBItem */
 
 			$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)) {
 				$id_field = $this->Application->getUnitOption('theme', 'IDField');
 				$table_name = $this->Application->getUnitOption('theme', 'TableName');
 
 				$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 = erSTOP;
 			}
 
 			$this->_resetMenuCache();
 		}
 
 		function _resetMenuCache()
 		{
 			// reset cms menu cache (all variables are automatically rebuild, when missing)
 			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 				$this->Application->deleteCache('master:cms_menu');
 				$this->Application->deleteCache('master:StructureTree');
 				$this->Application->deleteCache('master:template_mapping');
 			}
 			else {
 				$this->Application->deleteDBCache('cms_menu');
 				$this->Application->deleteDBCache('StructureTree');
 				$this->Application->deleteDBCache('template_mapping');
 			}
 		}
 
 		/**
 		 * Updates structure config
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterConfigRead(&$event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			if (defined('IS_INSTALL') && IS_INSTALL) {
 				// skip any processing, because Category table doesn't exists until install is finished
 				return ;
 			}
 
 			$site_config_helper =& $this->Application->recallObject('SiteConfigHelper');
 			/* @var $site_config_helper SiteConfigHelper */
 
 			$settings = $site_config_helper->getSettings();
 
-			$root_category = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$root_category = $this->Application->getBaseCategory();
 
 			// set root category
 			$section_ajustments = $this->Application->getUnitOption($event->Prefix, 'SectionAdjustments');
 
 			$section_ajustments['in-portal:browse'] = Array (
 				'url' => Array ('m_cat_id' => $root_category),
 				'late_load' => Array ('m_cat_id' => $root_category),
 				'onclick' => 'checkCatalog(' . $root_category . ')',
 			);
 
 			$section_ajustments['in-portal:browse_site'] = Array (
 				'url' => Array ('editing_mode' => $settings['default_editing_mode']),
 			);
 
 			$this->Application->setUnitOption($event->Prefix, 'SectionAdjustments', $section_ajustments);
 
 			// prepare structure dropdown
 			$category_helper =& $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
 
 			$fields['ParentId']['default'] = (int)$this->Application->GetVar('m_cat_id');
 			$fields['ParentId']['options'] = $category_helper->getStructureTreeAsOptions();
 
 			// limit design list by theme
 			$design_folders = Array ('tf.FilePath = "/designs"', 'tf.FilePath = "/platform/designs"');
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				$design_folders[] = 'tf.FilePath = "/' . $module_info['TemplatePath'] . 'designs"';
 			}
 			$design_folders = array_unique($design_folders);
 
 			$theme_id = $this->_getCurrentThemeId();
 			$design_sql = $fields['Template']['options_sql'];
 			$design_sql = str_replace('(tf.FilePath = "/designs")', '(' . implode(' OR ', $design_folders) . ')' . ' 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'));
 
 			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
 
 			if ($this->Application->isAdmin) {
 				// don't sort by Front-End sorting fields
 				$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
 				$remove_keys = Array ('DefaultSorting1Field', 'DefaultSorting2Field', 'DefaultSorting1Dir', 'DefaultSorting2Dir');
 				foreach ($remove_keys as $remove_key) {
 					unset($config_mapping[$remove_key]);
 				}
 				$this->Application->setUnitOption($event->Prefix, 'ConfigMapping', $config_mapping);
 			}
 			else {
 				// sort by parent path on Front-End only
 				$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings');
 				$list_sortings['']['ForcedSorting'] = Array ("CurrentSort" => 'asc');
 				$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
 			}
 
 			// add grids for advanced view (with primary category column)
 			$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 			$process_grids = Array ('Default', 'Radio');
 			foreach ($process_grids as $process_grid) {
 				$grid_data = $grids[$process_grid];
 				$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_parent_category_td', 'filter_block' => 'grid_like_filter');
 				$grids[$process_grid . 'ShowAll'] = $grid_data;
 			}
 			$this->Application->setUnitOption($this->Prefix, 'Grids', $grids);
 		}
 
 		/**
 		 * Removes this item and it's children (recursive) from structure dropdown
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemLoad(&$event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			if (!$this->Application->isAdmin) {
 				// calculate priorities dropdown only for admin
 				return ;
 			}
 
 			$object =& $event->getObject();
 
 			// remove this category & it's children from dropdown
 			$sql = 'SELECT '.$object->IDField.'
 					FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
 					WHERE ParentPath LIKE "'.$object->GetDBField('ParentPath').'%"';
 			$remove_categories = $this->Conn->GetCol($sql);
 
 			$field_options = $object->GetFieldOptions('ParentId');
 			foreach ($remove_categories as $remove_category) {
 				unset($field_options['options'][$remove_category]);
 			}
 			$object->SetFieldOptions('ParentId', $field_options);
 
 			$priority_helper =& $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			$priority_helper->preparePriorities($event, false, 'ParentId = '.$object->GetDBField('ParentId'));
 
 			// storing prioriry right after load for comparing when updating
 			$object->SetDBField('OldPriority', $object->GetDBField('Priority'));
 		}
 
 		/**
 		 * Builds list
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnListBuild(&$event)
 		{
 			parent::OnListBuild($event);
 
 			if (!$this->Application->isAdminUser) {
 				return ;
 			}
 
 			$priority_helper =& $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			$priority_helper->preparePriorities($event, false, 'ParentId = '.$this->Application->GetVar('m_cat_id'));
 		}
 
 		/**
 		 * 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.'Theme AS t ON t.ThemeId = tf.ThemeId
 					WHERE t.Enabled = 1 AND tf.FileType = 1
 					AND (
 						SELECT COUNT(CategoryId)
 						FROM ' . TABLE_PREFIX . 'Category 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 ;
 			}
 
 			set_time_limit(0);
 			ini_set('memory_limit', -1);
 
 			$dummy =& $this->Application->recallObject($event->Prefix . '.rebuild', null, Array ('skip_autoload' => true));
 			/* @var $dummy kDBItem */
 
 			$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('QuickCategoryPermissionRebuild')) {
 				$updater =& $this->Application->recallObject('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)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = erFAIL;
 				return;
 			}
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ($ids) {
 				$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 				$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
 				$parent_id = $this->Application->GetVar('m_cat_id');
 
 				$sql = 'SELECT Priority, '.$id_field.'
 						FROM '.$table_name.'
 						WHERE '.$id_field.' IN ('.implode(',', $ids).')';
 				$priorities = $this->Conn->GetCol($sql, $id_field);
 
 				$priority_helper =& $this->Application->recallObject('PriorityHelper');
 				/* @var $priority_helper kPriorityHelper */
 
 				foreach ($ids as $id) {
 					$new_priority = $priorities[$id] + ($event->Name == 'OnMassMoveUp' ? +1 : -1);
 
 					$changes = Array (
 						$id	=>	Array ('old' => $priorities[$id], 'new' => $new_priority, 'parent' => $parent_id),
 					);
 
 					$sql = 'UPDATE '.$table_name.'
 							SET Priority = '.$new_priority.'
 							WHERE '.$id_field.' = '.$id;
 					$this->Conn->Query($sql);
 
 					$priority_helper->updatePriorities($event, $changes, Array ($id => $id));
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 			$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 = erFAIL;
 				return;
 			}
 
 			$priority_helper =& $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			$parent_id = $this->Application->GetVar('m_cat_id');
 			$priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id);
 			$this->_resetMenuCache();
 		}
 
 		/**
 		 * Update Preview Block for FCKEditor
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdatePreviewBlock(&$event)
 		{
 			$event->status = erSTOP;
 			$string = unhtmlentities($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 = unhtmlentities( trim($this->Application->GetVar('keywords')) );
 
 			$query_object =& $this->Application->recallObject('HTTPQuery');
 			$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 			if(!isset($query_object->Get['keywords']) &&
 				!isset($query_object->Post['keywords']) &&
 				$this->Conn->Query($sql))
 			{
 				return; // used when navigating by pages or changing sorting in search results
 			}
 			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 */
 
 			$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
 			$lang = $this->Application->GetVar('m_lang');
 			$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
 			$module_name = 'In-Portal';
 
 			$sql = 'SELECT *
 					FROM ' . $this->Application->getUnitOption('confs', 'TableName') . '
 					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 = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 			if ($custom_fields) {
 				$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
 				$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) {
 				$options = $object->getFieldOptions($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 (getArrayValue($options, '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
 				if ($foreign_field = $search_config[$field]['ForeignField']) {
 					$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) . ')';
 
 			$where_clause = $where_clause.' AND '.$items_table.'.Status=1';
 
 			if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
 				if ($event->MasterEvent->getEventParam('ResultIds')) {
 					$where_clause .= ' AND '.$items_table.'.ResourceId IN ('.implode(',', $event->MasterEvent->getEventParam('ResultIds')).')';
 				}
 			}
 
 			// 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'];
 				$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 = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
 			$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 && isset($object->Fields['Hits'])) {
 				$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 			}
 			if ($rel_rating && isset($object->Fields['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 = $this->Application->getUnitOption($event->Prefix.'.EditorsPick', 'Fields') ? $items_table.'.EditorsPick' : '0';
 
 
 			$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
 								'.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' AS ItemId,
 								'.$items_table.'.ResourceId,
 								'.$this->Application->getUnitOption($event->Prefix, 'ItemType').' AS ItemType,
 								 '.$edpick_clause.' AS EdPick
 						FROM '.$object->TableName.'
 						'.implode(' ', $join_clauses).'
 						WHERE '.$where_clause.'
 						GROUP BY '.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField');
 
 			$res = $this->Conn->Query($sql);
 		}
 
 		/**
 		 * 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.'SearchLog
 						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.'SearchLog');
 		        }
 
 		        $this->Application->SetVar('search_logged', 1);
 			}
 		}
 
 		/**
 		 * Load item if id is available
 		 *
 		 * @param kEvent $event
 		 */
 		function LoadItem(&$event)
 		{
 			if ($event->Special != '-virtual') {
 				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');
 				$actions->Set($event->Prefix_Special.'_id', $object->GetID() );
 			}
 			else {
 				$object->setID($id);
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.1.x/core/units/helpers/category_helper.php
===================================================================
--- branches/5.1.x/core/units/helpers/category_helper.php	(revision 13986)
+++ branches/5.1.x/core/units/helpers/category_helper.php	(revision 13987)
@@ -1,599 +1,599 @@
 <?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 CategoryHelper extends kHelper {
 
 		/**
 		 * Structure tree for ParentId field in category or category items
 		 *
 		 * @var Array
 		 */
 		var $_structureTree = null;
 
 		/**
 		 * ID of primary language (only for caching)
 		 *
 		 * @var int
 		 */
 		var $_primaryLanguageId = false;
 
 		/**
 		 * Prints category path using given blocks. Also supports used defined path elements at the end.
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function NavigationBar($params)
 		{
 			$params['is_first'] = 1;
 			$main_category_id = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id');
 
 			if (array_key_exists('shift', $params) && $params['shift']) {
 				$home_element = '';
 				$params['shift']--;
 			}
 			else {
 				$home_element = $this->getHomeCategoryPath($params, $main_category_id);
 				unset($params['is_first']);
 			}
 
 			if (!getArrayValue($params, 'titles') && !getArrayValue($params, 'templates')) {
 				// no static templates given, show only category path
 				return $home_element . $this->getCategoryPath($main_category_id, $params);
 			}
 
 			$navigation_parts = $this->getNavigationParts($params['titles'], $params['templates']);
 
 			$ret = '';
 			$block_params = Array (); //$params; // sort of TagProcessor:prepareTagParams
 			$block_params['no_editing'] = 1;
 			$block_params['category'] = 0;
 			$block_params['separator'] = $params['separator'];
 			$show_category = getArrayValue($params, 'show_category');
 
 			$current_template = $this->Application->GetVar('t');
 			$physical_template = array_search($current_template, $this->Application->structureTemplateMapping);
 
 			if ($physical_template !== false) {
 				// replace menu template name with it's actual template name on disk
 				list ($current_template) = explode(':', $physical_template, 2);
 			}
 
 			foreach ($navigation_parts as $template => $title) {
 				$block_params['template'] = $template;
 
 				if ($title == '__item__') {
 					if ($show_category) {
 						$ret .= $this->getCategoryPath($main_category_id, $params);
 						$show_category = false;
 					}
 
 					$category_path = $this->getCategoryParentPath($main_category_id);
 					$module_info = $this->getCategoryModule($params, array_keys($category_path));
 					if (!$module_info) {
 						continue;
 					}
 
 					$module_prefix = $module_info['Var'];
 					$object =& $this->Application->recallObject($module_prefix);
 					/* @var $object kCatDBItem */
 
 					$title_field = $this->Application->getUnitOption($module_prefix, 'TitleField');
 					$block_params['title'] = $object->GetField($title_field);
 					$block_params['prefix'] = $module_prefix;
 					$block_params['current'] = 0;
 
 					$block_params['name'] = $this->SelectParam($params, 'module_item_render_as,render_as');
 				}
 				else {
 					$block_params['current'] = ($template == $current_template);
 					$block_params['title'] = $this->Application->Phrase($title);
 
 					$block_params['name'] = $template == $current_template ? $params['current_render_as'] : $params['render_as'];
 				}
 
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 
 			if ($show_category) {
 				$params['no_current'] = true;
 				return $home_element . ($show_category ? $this->getCategoryPath($main_category_id, $params) : '') . $ret;
 			}
 
 			return $home_element . $ret;
 		}
 
 		/**
 		 * Get navigation parts
 		 *
 		 * @param Array $titles
 		 * @param Array $templates
 		 * @return Array
 		 */
 		function getNavigationParts($titles, $templates)
 		{
 			$titles = explode(',', $titles);
 			$templates = explode(',', $templates);
 
 			$ret = Array ();
 			foreach ($templates as $template_pos => $template) {
 				$ret[$template] = $titles[$template_pos];
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Renders path to given category using given blocks.
 		 *
 		 * @param int $main_category_id
 		 * @param Array $params
 		 * @return string
 		 */
 		function getCategoryPath($main_category_id, $params)
 		{
 			$category_path = $this->getCategoryParentPath($main_category_id);
 			if (!$category_path) {
 				// in "Home" category
 				return '';
 			}
 
 			if (array_key_exists('shift', $params) && $params['shift']) {
 				array_splice($category_path, 0, $params['shift']);
 			}
 
 			$module_info = $this->getCategoryModule($params, array_keys($category_path));
 
 			$module_category_id = $module_info['RootCat'];
 			$module_item_id = $this->Application->GetVar($module_info['Var'].'_id');
 
 			$ret = '';
 			$block_params['category'] = 1;
 			$block_params['no_editing'] = 1;
 
 			if (array_key_exists('is_first', $params)) {
 				$block_params['is_first'] = $params['is_first'];
 			}
 
 			$block_params['separator'] = $params['separator'];
 			$no_current = isset($params['no_current']) && $params['no_current'];
 
 			$backup_category_id = $this->Application->GetVar('c_id');
 			foreach ($category_path as $category_id => $category_name) {
 				$block_params['cat_id'] = $category_id;
 				$block_params['cat_name'] = $block_params['title'] = $category_name;
 
 				if ($no_current) {
 					$block_params['current'] = 0;
 				}
 				else {
 					$block_params['current'] = ($main_category_id == $category_id) && !$module_item_id ? 1 : 0;
 				}
 
 				$block_params['is_module_root'] = $category_id == $module_category_id ? 1 : 0;
 				$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 
 				// which block to parse as current ?
 				if ($block_params['is_module_root']) {
 					$block_params['name'] = $this->SelectParam($params, 'module_root_render_as,render_as');
 					$block_params['module_index'] = $module_info['TemplatePath'].'index';
 				}
 
 				if ($block_params['current']) {
 					$block_params['name'] = $this->SelectParam($params, 'current_render_as,render_as');
 				}
 
 				$this->Application->SetVar('c_id', $category_id);
 				$ret .= $this->Application->ParseBlock($block_params);
 
 				if (array_key_exists('is_first', $block_params)) {
 					unset($block_params['is_first']);
 				}
 			}
 
 			$this->Application->SetVar('c_id', $backup_category_id);
 
 			return $ret;
 		}
 
 		/**
 		 * Returns module information based on given module name or current category (relative to module root categories)
 		 *
 		 * @param Array $params
 		 * @param Array $category_ids category parent path (already as array)
 		 * @return Array
 		 */
 		function getCategoryModule($params, $category_ids)
 		{
 			if (isset($params['module'])) {
 				// get module by name specified
 				$module_info = $this->Application->findModule('Name', $params['module']);
 
 			}
 			elseif ($category_ids) {
 				// get module by category path
 				$module_root_categories = $this->getModuleRootCategories();
 				$common_categories = array_intersect($category_ids, $module_root_categories);
 				$module_category_id = array_shift($common_categories); // get 1st common category
 				$module_info = $this->Application->findModule('RootCat', $module_category_id);
 			}
 
 			return $module_info;
 		}
 
 		/**
 		 * Renders path to top catalog category
 		 *
 		 * @param Array $params
 		 * @param int $current_category
 		 * @return string
 		 */
 		function getHomeCategoryPath($params, $current_category)
 		{
-			$block_params['cat_id'] = $this->Application->findModule('Name', 'Core', 'RootCat'); // 0;
+			$block_params['cat_id'] = $this->Application->getBaseCategory();
 			$block_params['no_editing'] = 1;
 			$block_params['current'] = $current_category == $block_params['cat_id'] ? 1 : 0;
 			$block_params['separator'] = $params['separator'];
 			$block_params['is_first'] = $params['is_first'];
 			$block_params['cat_name'] = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 			$block_params['name'] = $this->SelectParam($params, 'root_cat_render_as,render_as');
 
 			return $this->Application->ParseBlock($block_params);
 		}
 
 		/**
 		 * Returns root categories from all modules
 		 *
 		 * @return Array
 		 */
 		function getModuleRootCategories()
 		{
 			static $root_categories = null;
 
 			if (!isset($root_categories)) {
 				$root_categories = Array ();
 				foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 					array_push($root_categories, $module_info['RootCat']);
 				}
 
 				$root_categories = array_unique($root_categories);
 			}
 
 			return $root_categories;
 		}
 
 		/**
 		 * Returns given category's parent path as array of id=>name elements
 		 *
 		 * @param int $main_category_id
 		 * @return Array
 		 */
 		function getCategoryParentPath($main_category_id)
 		{
 			if ($main_category_id == 0) {
 				// don't query path for "Home" category
 				return Array ();
 			}
 
 			$cache_key = 'parent_paths_named[%CIDSerial:' . $main_category_id . '%]';
 			$cached_path = $this->Application->getCache($cache_key);
 
 			if ($cached_path === false) {
 				$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 				$navbar_field = $ml_formatter->LangFieldName('CachedNavBar');
 
 				$id_field = $this->Application->getUnitOption('c', 'IDField');
 				$table_name = $this->Application->getUnitOption('c', 'TableName');
 
 				$this->Conn->nextQueryCachable = true;
 				$sql = 'SELECT '.$navbar_field.', ParentPath
 						FROM '.$table_name.'
 						WHERE '.$id_field.' = '.$main_category_id;
 				$category_data = $this->Conn->GetRow($sql);
 
 				$cached_path = Array ();
-				$skip_category = $this->Application->findModule('Name', 'Core', 'RootCat');
+				$skip_category = $this->Application->getBaseCategory();
 
 				if ($category_data) {
 					$category_names = explode('&|&', $category_data[$navbar_field]);
 					$category_ids = explode('|', substr($category_data['ParentPath'], 1, -1));
 
 					foreach ($category_ids as $category_index => $category_id) {
 						if ($category_id == $skip_category) {
 							continue;
 						}
 
 						$cached_path[$category_id] = $category_names[$category_index];
 					}
 				}
 
 				$this->Application->setCache($cache_key, $cached_path);
 			}
 
 			return $cached_path;
 		}
 
 		 /**
 		 * Not tag, method for parameter
 		 * selection from list in this TagProcessor
 		 *
 		 * @param Array $params
 		 * @param string $possible_names
 		 * @return string
 		 * @access public
 		 */
 		function SelectParam($params, $possible_names)
 		{
 			if (!is_array($params)) return;
 			if (!is_array($possible_names))
 
 			$possible_names = explode(',', $possible_names);
 			foreach ($possible_names as $name)
 			{
 				if( isset($params[$name]) ) return $params[$name];
 			}
 			return false;
 		}
 
 		/**
 		 * Converts multi-dimensional category structure in one-dimensional option array (category_id=>category_name)
 		 *
 		 * @param Array $data
 		 * @param int $parent_category_id
 		 * @param int_type $language_id
 		 * @param int $theme_id
 		 * @param int $level
 		 * @return Array
 		 */
 		function _printChildren(&$data, $parent_category_id, $language_id, $theme_id, $level = 0)
 		{
 			if ($data['ThemeId'] != $theme_id && $data['ThemeId'] != 0) {
 				// don't show system templates from different themes
 				return Array ();
 			}
 
 			$category_language = $data['l' . $language_id . '_Name'] ? $language_id : $this->_primaryLanguageId;
 			$ret = Array($parent_category_id => str_repeat('-', $level).' '.$data['l' . $category_language . '_Name']);
 
 			if ($data['children']) {
 				$level++;
 				foreach ($data['children'] as $category_id => $category_data) {
 					$ret = array_merge_recursive2($ret, $this->_printChildren($data['children'][$category_id], $category_id, $language_id, $theme_id, $level));
 				}
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns information about children under parent path (recursive)
 		 *
 		 * @param int $parent_category_id
 		 * @param int $language_count
 		 * @return Array
 		 */
 		function _getChildren($parent_category_id, $language_count)
 		{
 			static $items_by_parent = null, $parent_mapping = null;
 
 			if ( !isset($items_by_parent) ) {
 				$fields = $items_by_parent = Array ();
 
 				for ($i = 1; $i <= $language_count; $i++) {
 					$fields[] = 'l' . $i . '_Name';
 				}
 
 				$sql = 'SELECT CategoryId AS id, ' . implode(', ', $fields) . ', ParentId, ThemeId
 						FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
 						ORDER BY Priority DESC';
 				$items = $this->Conn->Query($sql, 'id');
 
 				foreach ($items as $item_id => $item_data) {
 					$item_parent_id = $item_data['ParentId'];
 					unset($item_data['ParentId']);
 
 					if ( !array_key_exists($item_parent_id, $items_by_parent) ) {
 						$items_by_parent[$item_parent_id] = Array ();
 					}
 
 					$item_data['children'] = false;
 					$parent_mapping[$item_id] = $item_parent_id;
 					$items_by_parent[$item_parent_id][$item_id] = $item_data;
 				}
 			}
 
 			$data = $items_by_parent[ $parent_mapping[$parent_category_id] ][$parent_category_id];
 			$categories = array_key_exists($parent_category_id, $items_by_parent) ? $items_by_parent[$parent_category_id] : Array ();
 
 			foreach ($categories as $category_id => $category_data) {
 				if ($category_id == $parent_category_id) {
 					// don't process myself - prevents recursion
 					continue;
 				}
 
 				$data['children'][$category_id] = $this->_getChildren($category_id, $language_count);
 			}
 
 			return $data;
 		}
 
 		/**
 		 * Generates OR retrieves from cache structure tree
 		 *
 		 * @return Array
 		 */
 		function &_getStructureTree()
 		{
 			// get cached version of structure tree
 			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 				$data = $this->Application->getCache('master:StructureTree', false);
 			}
 			else {
 				$data = $this->Application->getDBCache('StructureTree');
 			}
 
 			if ($data) {
 				$data = unserialize($data);
 
 				return $data;
 			}
 
 			// generate structure tree from scratch
 			$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$language_count = $ml_helper->getLanguageCount();
 
-			$root_category = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$root_category = $this->Application->getBaseCategory();
 			$data = $this->_getChildren($root_category, $language_count);
 
 			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 				$this->Application->setCache('master:StructureTree', serialize($data));
 			}
 			else {
 				$this->Application->setDBCache('StructureTree', serialize($data));
 			}
 
 			return $data;
 		}
 
 		function getTemplateMapping()
 		{
 			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 				$data = $this->Application->getCache('master:template_mapping', false);
 			}
 			else {
 				$data = $this->Application->getDBCache('template_mapping');
 			}
 
 			if ($data) {
 				return unserialize($data);
 			}
 
 			$sql = 'SELECT
 						IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', CONCAT(c.Template, ":", c.ThemeId), CONCAT("id:", c.CategoryId)) AS SrcTemplate,
 						LOWER(
 							IF(
 								c.SymLinkCategoryId IS NOT NULL,
 								(SELECT cc.NamedParentPath FROM ' . TABLE_PREFIX . 'Category AS cc WHERE cc.CategoryId = c.SymLinkCategoryId),
 							 	c.NamedParentPath
 							 )
 						) AS DstTemplate,
 						c.UseExternalUrl, c.ExternalUrl
 					FROM ' . TABLE_PREFIX . 'Category AS c
 					WHERE c.Status = ' . STATUS_ACTIVE;
 			$pages = $this->Conn->Query($sql, 'SrcTemplate');
 
 			$mapping = Array ();
 			$base_url = $this->Application->BaseURL();
 
 			foreach ($pages as $src_template => $page) {
 				// process external url, before placing in cache
 				if ($page['UseExternalUrl']) {
 					$external_url = $page['ExternalUrl'];
 
 					if (!preg_match('/^(.*?):\/\/(.*)$/', $external_url)) {
 						// url without protocol will be relative url to our site
 						$external_url = $base_url . $external_url;
 					}
 
 					$dst_template = 'external:' . $external_url;
 				}
 				else {
 					$dst_template = preg_replace('/^Content\//i', '', $page['DstTemplate']);
 				}
 
 				$mapping[$src_template] = $dst_template;
 			}
 
 			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 				$data = $this->Application->setCache('master:template_mapping', serialize($mapping));
 			}
 			else {
 				$this->Application->setDBCache('template_mapping', serialize($mapping));
 			}
 
 			return $mapping;
 		}
 
 		/**
 		 * Returns category structure as field option list
 		 *
 		 * @return Array
 		 */
 		function getStructureTreeAsOptions()
 		{
 			if ((defined('IS_INSTALL') && IS_INSTALL) || !$this->Application->isAdmin) {
 				// no need to create category structure during install
 				// OR on Front-End, because it's not used there
 				return Array ();
 			}
 
 			if (isset($this->_structureTree)) {
 				return $this->_structureTree;
 			}
 
 			$themes_helper =& $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$data = $this->_getStructureTree();
 
 			$theme_id = (int)$themes_helper->getCurrentThemeId();
-			$root_category = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$root_category = $this->Application->getBaseCategory();
 
 			$this->_primaryLanguageId = $this->Application->GetDefaultLanguageId();
 			$this->_structureTree = $this->_printChildren($data, $root_category, $this->Application->GetVar('m_lang'), $theme_id);
 
 			return $this->_structureTree;
 		}
 
 		/**
 		 * Replace links like "@@ID@@" to actual template names in given text
 		 *
 		 * @param string $text
 		 * @return string
 		 */
 		function replacePageIds($text)
 		{
 			if (!preg_match_all('/@@(\\d+)@@/', $text, $regs)) {
 				return $text;
 			}
 
 			$page_ids = $regs[1];
 
 			$sql = 'SELECT NamedParentPath, CategoryId
 					FROM ' . TABLE_PREFIX . 'Category
 					WHERE CategoryId IN (' . implode(',', $page_ids) . ')';
 			$templates = $this->Conn->GetCol($sql, 'CategoryId');
 
 			foreach ($page_ids as $page_id) {
 				if (!array_key_exists($page_id, $templates)) {
 					// internal page was deleted, but link to it was found in given content block data
 					continue;
 				}
 
 				$url_params = Array ('m_cat_id' => $page_id, 'pass' => 'm');
 				$page_url = $this->Application->HREF(strtolower($templates[$page_id]), '', $url_params);
 				/*if ($this->Application->isAdmin) {
 					$page_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $page_url);
 				}*/
 
 				$text = str_replace('@@' . $page_id . '@@', $page_url, $text);
 			}
 
 			return $text;
 		}
 	}
\ No newline at end of file
Index: branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php
===================================================================
--- branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php	(revision 13986)
+++ branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php	(revision 13987)
@@ -1,1468 +1,1468 @@
 <?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!');
 
 	define('EXPORT_STEP', 100); // export by 200 items (e.g. links)
 	define('IMPORT_STEP', 20); // export by 200 items (e.g. links)
 	define('IMPORT_CHUNK', 10240); // 10240); //30720); //50120); // 5 KB
 
 	define('IMPORT_TEMP', 1);
 	define('IMPORT_LIVE', 2);
 
 	class kCatDBItemExportHelper extends kHelper {
 
 		var $false = false;
 
 		var $cache = Array();
 
 		/**
 		 * Allows to find out what items are new in cache
 		 *
 		 * @var Array
 		 */
 		var $cacheStatus = Array();
 
 		var $cacheTable = '';
 
 		var $exportFields = Array();
 
 		/**
 		 * Export options
 		 *
 		 * @var Array
 		 */
 		var $exportOptions = Array();
 
 		/**
 		 * Item beeing currenly exported
 		 *
 		 * @var kCatDBItem
 		 */
 		var $curItem = null;
 
 		/**
 		 * Dummy category object
 		 *
 		 * @var CategoriesItem
 		 */
 		var $dummyCategory = null;
 
 		/**
 		 * Pointer to opened file
 		 *
 		 * @var resource
 		 */
 		var $filePointer = null;
 
 		/**
 		 * Custom fields definition of current item
 		 *
 		 * @var Array
 		 */
 		var $customFields = Array();
 
 		function kCatDBItemExportHelper()
 		{
 			parent::kHelper();
 			$this->cacheTable = TABLE_PREFIX.'ImportCache';
 		}
 
 		/**
 		 * Returns value from cache if found or false otherwise
 		 *
 		 * @param string $type
 		 * @param int $key
 		 * @return mixed
 		 */
 		function getFromCache($type, $key)
 		{
 			return getArrayValue($this->cache, $type, $key);
 		}
 
 		/**
 		 * Adds value to be cached
 		 *
 		 * @param string $type
 		 * @param int $key
 		 * @param mixed $value
 		 */
 		function addToCache($type, $key, $value, $is_new = true)
 		{
 //			if (!isset($this->cache[$type])) $this->cache[$type] = Array();
 			$this->cache[$type][$key] = $value;
 			if ($is_new) {
 				$this->cacheStatus[$type][$key] = true;
 			}
 		}
 
 		function storeCache($cache_types)
 		{
 			$cache_types = explode(',', $cache_types);
 
 			$values_sql = '';
 			foreach ($cache_types as $cache_type) {
 				$sql_mask = '('.$this->Conn->qstr($cache_type).',%s,%s),';
 				$cache = getArrayValue($this->cacheStatus, $cache_type);
 				if (!$cache) $cache = Array();
 				foreach ($cache as $var_name => $cache_status) {
 					$var_value = $this->cache[$cache_type][$var_name];
 					$values_sql .= sprintf($sql_mask, $this->Conn->qstr($var_name), $this->Conn->qstr($var_value) );
 				}
 			}
 
 			$values_sql = substr($values_sql, 0, -1);
 
 			if ($values_sql) {
 				$sql = 'INSERT INTO '.$this->cacheTable.'(`CacheName`,`VarName`,`VarValue`) VALUES '.$values_sql;
 				$this->Conn->Query($sql);
 			}
 
 		}
 
 		function loadCache()
 		{
 			$sql = 'SELECT * FROM '.$this->cacheTable;
 			$records = $this->Conn->Query($sql);
 
 			$this->cache = Array();
 			foreach ($records as $record) {
 				$this->addToCache($record['CacheName'], $record['VarName'], $record['VarValue'], false);
 			}
 		}
 
 		/**
 		 * Fill required fields with dummy values
 		 *
 		 * @param kEvent $event
 		 */
 		function fillRequiredFields(&$event, &$object, $set_status = false)
 		{
 			if ($object == $this->false) {
 				$object =& $event->getObject();
 			}
 
 			$has_empty = false;
 			$fields = array_keys($object->Fields);
 			foreach ($fields as $field_name)
 			{
 				$field_options =& $object->Fields[$field_name];
 				if (isset($object->VirtualFields[$field_name]) || !getArrayValue($field_options, 'required') ) continue;
 				if ( $object->GetDBField($field_name) ) continue;
 
 				$formatter_class = getArrayValue($field_options, 'formatter');
 				if ($formatter_class) // not tested
 				{
 					$formatter =& $this->Application->recallObject($formatter_class);
 					$sample_value = $formatter->GetSample($field_name, $field_options, $object);
 				}
 
 				$has_empty = true;
 				$object->SetField($field_name, isset($sample_value) && $sample_value ? $sample_value : 'no value');
 			}
 			$object->UpdateFormattersSubFields();
 
 			if ($set_status && $has_empty) {
 				$object->SetDBField('Status', 0);
 			}
 		}
 
 		/**
 		 * Verifies that all user entered export params are correct
 		 *
 		 * @param kEvent $event
 		 */
 		function verifyOptions(&$event)
 		{
 			if ($this->Application->RecallVar($event->getPrefixSpecial().'_ForceNotValid'))
 			{
 				$this->Application->StoreVar($event->getPrefixSpecial().'_ForceNotValid', 0);
 				return false;
 			}
 
 			$this->fillRequiredFields($event, $this->false);
 
 			$object =& $event->getObject();
 			$cross_unique_fields = Array('FieldsSeparatedBy', 'FieldsEnclosedBy');
 			if (($object->GetDBField('CategoryFormat') == 1) || ($event->Special == 'import')) // in one field
 			{
 				$object->setRequired('CategorySeparator', true);
 				$cross_unique_fields[] = 'CategorySeparator';
 			}
 
 			$ret = $object->Validate();
 
 			// check if cross unique fields has no same values
 			foreach ($cross_unique_fields as $field_index => $field_name)
 			{
 				if (getArrayValue($object->FieldErrors, $field_name, 'pseudo') == 'required') continue;
 
 				$check_fields = $cross_unique_fields;
 				unset($check_fields[$field_index]);
 
 				foreach ($check_fields as $check_field)
 				{
 					if ($object->GetDBField($field_name) == $object->GetDBField($check_field))
 					{
 						$object->SetError($check_field, 'unique');
 					}
 				}
 			}
 
 			if ($event->Special == 'import')
 			{
 				$this->exportOptions = $this->loadOptions($event);
 
 				$automatic_fields = ($object->GetDBField('FieldTitles') == 1);
 				$object->setRequired('ExportColumns', !$automatic_fields);
 				$category_prefix = '__CATEGORY__';
 				if ( $automatic_fields && ($this->exportOptions['SkipFirstRow']) ) {
 					$this->openFile($event);
 					$this->exportOptions['ExportColumns'] = $this->readRecord();
 
 					if (!$this->exportOptions['ExportColumns']) {
 						$this->exportOptions['ExportColumns'] = Array ();
 					}
 
 					$this->closeFile();
 
 					// remove additional (non-parseble columns)
 					foreach ($this->exportOptions['ExportColumns'] as $field_index => $field_name) {
 						if (!$this->validateField($field_name, $object)) {
 							unset($this->exportOptions['ExportColumns'][$field_index]);
 						}
 					}
 					$category_prefix = '';
 				}
 
 				// 1. check, that we have column definitions
 				if (!$this->exportOptions['ExportColumns']) {
 					$object->setError('ExportColumns', 'required');
 					$ret = false;
 				}
 				else {
 					// 1.1. check that all required fields are present in imported file
 					$missing_columns = Array();
 					foreach ($object->Fields as $field_name => $field_options) {
 						if ($object->skipField($field_name)) continue;
 						if (getArrayValue($field_options, 'required') && !in_array($field_name, $this->exportOptions['ExportColumns']) ) {
 							$missing_columns[] = $field_name;
 							$object->setError('ExportColumns', 'required_fields_missing', 'la_error_RequiredColumnsMissing');
 							$ret = false;
 						}
 					}
 
 					if (!$ret && $this->Application->isDebugMode()) {
 						$this->Application->Debugger->appendHTML('Missing required for import/export:');
 						$this->Application->Debugger->dumpVars($missing_columns);
 					}
 				}
 
 
 				// 2. check, that we have only mixed category field or only separated category fields
 				$category_found['mixed'] = false;
 				$category_found['separated'] = false;
 
 				foreach ($this->exportOptions['ExportColumns'] as $import_field) {
 					if (preg_match('/^'.$category_prefix.'Category(Path|[0-9]+)/', $import_field, $rets)) {
 						$category_found[$rets[1] == 'Path' ? 'mixed' : 'separated'] = true;
 					}
 				}
 				if ($category_found['mixed'] && $category_found['separated']) {
 					$object->SetError('ExportColumns', 'unique_category', 'la_error_unique_category_field');
 					$ret = false;
 				}
 
 				// 3. check, that duplicates check fields are selected & present in imported fields
 				if ($this->exportOptions['ReplaceDuplicates']) {
 					if ($this->exportOptions['CheckDuplicatesMethod'] == 1) {
 						$check_fields = Array($object->IDField);
 					}
 					else {
 						$check_fields = $this->exportOptions['DuplicateCheckFields'] ? explode('|', substr($this->exportOptions['DuplicateCheckFields'], 1, -1)) : Array();
 						$object =& $event->getObject();
 
 						$language_id = $this->Application->GetDefaultLanguageId();
 						foreach ($check_fields as $index => $check_field) {
 							foreach ($object->Fields as $field_name => $field_options) {
 								if ($field_name == 'l'.$language_id.'_'.$check_field) {
 									$check_fields[$index] = 'l'.$language_id.'_'.$check_field;
 									break;
 								}
 							}
 						}
 					}
 					$this->exportOptions['DuplicateCheckFields'] = $check_fields;
 
 					if (!$check_fields) {
 						$object->setError('CheckDuplicatesMethod', 'required');
 						$ret = false;
 					}
 					else {
 						foreach ($check_fields as $check_field) {
 							$check_field = preg_replace('/^cust_(.*)/', 'Custom_\\1', $check_field);
 							if (!in_array($check_field, $this->exportOptions['ExportColumns'])) {
 								$object->setError('ExportColumns', 'required');
 								$ret = false;
 								break;
 							}
 						}
 					}
 				}
 				$this->saveOptions($event);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns filename to read import data from
 		 *
 		 * @return string
 		 */
 		function getImportFilename()
 		{
 			if ($this->exportOptions['ImportSource'] == 1)
 			{
 				$ret = $this->exportOptions['ImportFilename']; // ['name']; commented by Kostja
 			}
 			else {
 				$ret = $this->exportOptions['ImportLocalFilename'];
 			}
 			return EXPORT_PATH.'/'.$ret;
 		}
 
 		/**
 		 * Returns filename to write export data to
 		 *
 		 * @return string
 		 */
 		function getExportFilename()
 		{
 			$extension = $this->getFileExtension();
 			$filename = preg_replace('/(.*)\.' . $extension . '$/', '\1', $this->exportOptions['ExportFilename']) . '.' . $extension;
 
 			return EXPORT_PATH . DIRECTORY_SEPARATOR . $filename;
 		}
 
 		/**
 		 * Opens file required for export/import operations
 		 *
 		 * @param kEvent $event
 		 */
 		function openFile(&$event)
 		{
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->CheckFolder(EXPORT_PATH);
 
 			if ($event->Special == 'export') {
 				$write_mode = ($this->exportOptions['start_from'] == 0) ? 'w' : 'a';
 				$this->filePointer = fopen($this->getExportFilename(), $write_mode);
 			}
 			else {
 				$this->filePointer = fopen($this->getImportFilename(), 'r');
 			}
 
 			// skip UTF-8 BOM Modifier
 			$first_chars = fread($this->filePointer, 3);
 			if (bin2hex($first_chars) != 'efbbbf') {
 				fseek($this->filePointer, 0);
 			}
 		}
 
 		/**
 		 * Closes opened file
 		 *
 		 */
 		function closeFile()
 		{
 			fclose($this->filePointer);
 		}
 
 		function getCustomSQL()
 		{
 			$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 
 			$custom_sql = '';
 			foreach ($this->customFields as $custom_id => $custom_name) {
 				$custom_sql .= 'custom_data.'.$ml_formatter->LangFieldName('cust_'.$custom_id).' AS cust_'.$custom_name.', ';
 			}
 
 			return substr($custom_sql, 0, -2);
 		}
 
 		function getPlainExportSQL($count_only = false) {
 			if ($count_only && isset($this->exportOptions['ForceCountSQL'])) {
 				$sql = $this->exportOptions['ForceCountSQL'];
 			}
 			elseif (!$count_only && isset($this->exportOptions['ForceSelectSQL'])) {
 				$sql = $this->exportOptions['ForceSelectSQL'];
 			}
 			else {
 				$items_list =& $this->Application->recallObject($this->curItem->Prefix.'.export-items-list', $this->curItem->Prefix.'_List');
 				$items_list->SetPerPage(-1);
 
 				if ($options['export_ids'] != '') {
 					$items_list->AddFilter('export_ids', $items_list->TableName.'.'.$items_list->IDField.' IN ('.implode(',',$options['export_ids']).')');
 				}
 
 				if ($count_only) {
 					$sql = $items_list->getCountSQL( $items_list->GetSelectSQL(true,false) );
 				}
 				else {
 					$sql = $items_list->GetSelectSQL();
 				}
 			}
 
 			if (!$count_only)
 			{
 				$sql .= ' LIMIT '.$this->exportOptions['start_from'].','.EXPORT_STEP;
 			}
 //			else {
 //				$sql = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
 //			}
 
 			return $sql;
 		}
 
 		function getExportSQL($count_only = false)
 		{
 			if (!$this->Application->getUnitOption($this->curItem->Prefix, 'CatalogItem')) {
 				return $this->GetPlainExportSQL($count_only); // in case this is not a CategoryItem
 			}
 
 			if ($this->exportOptions['export_ids'] === false)
 			{
 				// get links from current category & all it's subcategories
 				$join_clauses = Array();
 
 				$custom_sql = $this->getCustomSQL();
 				if ($custom_sql) {
 					$custom_table = $this->Application->getUnitOption($this->curItem->Prefix.'-cdata', 'TableName');
 					$join_clauses[$custom_table.' custom_data'] = 'custom_data.ResourceId = item_table.ResourceId';
 				}
 
 				$join_clauses[TABLE_PREFIX.'CategoryItems ci'] = 'ci.ItemResourceId = item_table.ResourceId';
 				$join_clauses[TABLE_PREFIX.'Category c'] = 'c.CategoryId = ci.CategoryId';
 
 				$sql = 'SELECT item_table.*, ci.CategoryId'.($custom_sql ? ', '.$custom_sql : '').'
 						FROM '.$this->curItem->TableName.' item_table';
 
 				foreach ($join_clauses as $table_name => $join_expression) {
 					$sql .= ' LEFT JOIN '.$table_name.' ON '.$join_expression;
 				}
 				$sql .= ' WHERE ';
 
 				if ($this->exportOptions['export_cats_ids'][0] == 0)
 				{
 					$sql .= '1';
 				}
 				else {
 					foreach ($this->exportOptions['export_cats_ids'] as $category_id) {
 						$sql .= '(c.ParentPath LIKE "%|'.$category_id.'|%") OR ';
 					}
 					$sql = substr($sql, 0, -4);
 				}
 
 				$sql .= ' ORDER BY ci.PrimaryCat DESC'; // NEW
 			}
 			else {
 				// get only selected links
 				$sql = 'SELECT item_table.*, '.$this->exportOptions['export_cats_ids'][0].' AS CategoryId
 						FROM '.$this->curItem->TableName.' item_table
 						WHERE '.$this->curItem->IDField.' IN ('.implode(',', $this->exportOptions['export_ids']).')';
 			}
 
 			if (!$count_only)
 			{
 				$sql .= ' LIMIT '.$this->exportOptions['start_from'].','.EXPORT_STEP;
 			}
 			else {
 				$sql = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
 			}
 
 			return $sql;
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function performExport(&$event)
 		{
 			$this->exportOptions = $this->loadOptions($event);
 			$this->exportFields = $this->exportOptions['ExportColumns'];
 			$this->curItem =& $event->getObject( Array('skip_autoload' => true) );
 			$this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 			$this->openFile($event);
 
 			if ($this->exportOptions['start_from'] == 0) // first export step
 			{
 				if (!getArrayValue($this->exportOptions, 'IsBaseCategory')) {
 					$this->exportOptions['IsBaseCategory'] = 0;
 				}
 
 				if ($this->exportOptions['IsBaseCategory'] ) {
 					$sql = 'SELECT ParentPath
 							FROM '.TABLE_PREFIX.'Category
 							WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id');
 					$parent_path = $this->Conn->GetOne($sql);
 					$parent_path = explode('|', substr($parent_path, 1, -1));
-					if ($parent_path && $parent_path[0] == $this->Application->findModule('Name', 'Core', 'RootCat')) {
+					if ($parent_path && $parent_path[0] == $this->Application->getBaseCategory()) {
 						array_shift($parent_path);
 					}
 
 					$this->exportOptions['BaseLevel'] = count($parent_path); // level to cut from other categories
 				}
 
 				// 1. export field titles if required
 				if ($this->exportOptions['IncludeFieldTitles'])
 				{
 					$data_array = Array();
 					foreach ($this->exportFields as $export_field)
 					{
 						$data_array = array_merge($data_array, $this->getFieldCaption($export_field));
 					}
 					$this->writeRecord($data_array);
 				}
 				$this->exportOptions['total_records'] = $this->Conn->GetOne( $this->getExportSQL(true) );
 			}
 
 			// 2. export data
 			$records = $this->Conn->Query( $this->getExportSQL() );
 			$records_exported = 0;
 			foreach ($records as $record_info) {
 				$this->curItem->Clear();
 				$this->curItem->SetDBFieldsFromHash($record_info);
 				$this->setCurrentID();
 				$this->curItem->raiseEvent('OnAfterItemLoad', $this->curItem->GetID() );
 
 				$data_array = Array();
 				foreach ($this->exportFields as $export_field)
 				{
 					$data_array = array_merge($data_array, $this->getFieldValue($export_field) );
 				}
 				$this->writeRecord($data_array);
 				$records_exported++;
 			}
 			$this->closeFile();
 
 			$this->exportOptions['start_from'] += $records_exported;
 			$this->saveOptions($event);
 
 			return $this->exportOptions;
 		}
 
 		function getItemFields()
 		{
 			// just in case dummy user selected automtic mode & moved columns too :(
 			return array_merge($this->curItem->Fields['AvailableColumns']['options'], $this->curItem->Fields['ExportColumns']['options']);
 		}
 
 		/**
 		 * Checks if field really belongs to importable field list
 		 *
 		 * @param string $field_name
 		 * @param kCatDBItem $object
 		 * @return bool
 		 */
 		function validateField($field_name, &$object)
 		{
 			// 1. convert custom field
 			$field_name = preg_replace('/^Custom_(.*)/', '__CUSTOM__\\1', $field_name);
 
 			// 2. convert category field (mixed version & serparated version)
 			$field_name = preg_replace('/^Category(Path|[0-9]+)/', '__CATEGORY__Category\\1', $field_name);
 
 			$valid_fields = $object->getPossibleExportColumns();
 			return isset($valid_fields[$field_name]) || isset($valid_fields['__VIRTUAL__'.$field_name]);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function performImport(&$event)
 		{
 			if (!$this->exportOptions) {
 				// load import options in case if not previously loaded in verification function
 				$this->exportOptions = $this->loadOptions($event);
 			}
 
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 			$this->Application->SetVar('m_cat_id', (int)$this->Application->RecallVar('ImportCategory') );
 
 			$this->openFile($event);
 
 			$bytes_imported = 0;
 			if ($this->exportOptions['start_from'] == 0) // first export step
 			{
 				// 1st time run
 				if ($this->exportOptions['SkipFirstRow']) {
 					$this->readRecord();
 					$this->exportOptions['start_from'] = ftell($this->filePointer);
 					$bytes_imported = ftell($this->filePointer);
 				}
 
 				$current_category_id = $this->Application->GetVar('m_cat_id');
 				if ($current_category_id > 0) {
 					$sql = 'SELECT ParentPath FROM '.TABLE_PREFIX.'Category WHERE CategoryId = '.$current_category_id;
 					$this->exportOptions['ImportCategoryPath'] = $this->Conn->GetOne($sql);
 				}
 				else {
 					$this->exportOptions['ImportCategoryPath'] = '';
 				}
 				$this->exportOptions['total_records'] = filesize($this->getImportFilename());
 			}
 			else {
 				$this->loadCache();
 			}
 
 			$this->exportFields = $this->exportOptions['ExportColumns'];
 			$this->addToCache('category_parent_path', $this->Application->GetVar('m_cat_id'), $this->exportOptions['ImportCategoryPath']);
 
 			// 2. import data
 			$this->dummyCategory =& $this->Application->recallObject('c.-tmpitem', 'c', Array('skip_autoload' => true));
 			fseek($this->filePointer, $this->exportOptions['start_from']);
 
 			$items_processed = 0;
 			while (($bytes_imported < IMPORT_CHUNK && $items_processed < IMPORT_STEP) && !feof($this->filePointer)) {
 				$data = $this->readRecord();
 				if ($data) {
 					if ($this->exportOptions['ReplaceDuplicates']) {
 						// set fields used as keys for replace duplicates code
 						$this->resetImportObject($event, IMPORT_TEMP, $data);
 					}
 
 					$this->processCurrentItem($event, $data);
 				}
 				$bytes_imported = ftell($this->filePointer) - $this->exportOptions['start_from'];
 				$items_processed++;
 			}
 
 			$this->closeFile();
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 
 			$this->exportOptions['start_from'] += $bytes_imported;
 			$this->storeCache('new_ids');
 
 			$this->saveOptions($event);
 
 			if ($this->exportOptions['start_from'] == $this->exportOptions['total_records']) {
 				$this->Conn->Query('TRUNCATE TABLE '.$this->cacheTable);
 			}
 
 			return $this->exportOptions;
 		}
 
 		function setCurrentID()
 		{
 			$this->curItem->setID( $this->curItem->GetDBField($this->curItem->IDField) );
 		}
 
 		function setFieldValue($field_index, $value)
 		{
 			if (empty($value)) {
 				$value = null;
 			}
 
 			$field_name = getArrayValue($this->exportFields, $field_index);
 			if ($field_name == 'ResourceId') {
 				return false;
 			}
 
 			if (substr($field_name, 0, 7) == 'Custom_') {
 				$field_name = 'cust_'.substr($field_name, 7);
 				$this->curItem->SetField($field_name, $value);
 			}
 			elseif ($field_name == 'CategoryPath' || $field_name == '__CATEGORY__CategoryPath') {
 				$this->curItem->CategoryPath = $value ? explode($this->exportOptions['CategorySeparator'], $value) : Array();
 			}
 			elseif (substr($field_name, 0, 8) == 'Category') {
 				$this->curItem->CategoryPath[ (int)substr($field_name, 8) - 1 ] = $value;
 			}
 			elseif (substr($field_name, 0, 20) == '__CATEGORY__Category') {
 				$this->curItem->CategoryPath[ (int)substr($field_name, 20) ] = $value;
 			}
 			elseif (substr($field_name, 0, 11) == '__VIRTUAL__') {
 				$field_name = substr($field_name, 11);
 				$this->curItem->SetField($field_name, $value);
 			}
 			else {
 				$this->curItem->SetField($field_name, $value);
 			}
 
 			$pseudo_error = getArrayValue($this->curItem->FieldErrors, $field_name, 'pseudo');
 			if ($pseudo_error) {
 				$this->curItem->SetDBField($field_name, null);
 				unset($this->curItem->FieldErrors[$field_name]);
 			}
 		}
 
 		function resetImportObject(&$event, $object_type, $record_data = null)
 		{
 			switch ($object_type) {
 				case IMPORT_TEMP:
 					$this->curItem =& $event->getObject( Array('skip_autoload' => true) );
 					break;
 
 				case IMPORT_LIVE:
 					$this->curItem =& $this->Application->recallObject($event->Prefix.'.-tmpitem'.$event->Special, $event->Prefix, Array('skip_autoload' => true));
 					break;
 			}
 			$this->curItem->Clear();
 			$this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 
 			if (isset($record_data)) {
 				$this->setImportData($record_data);
 			}
 		}
 
 		function setImportData($record_data)
 		{
 			foreach ($record_data as $field_index => $field_value) {
 				$this->setFieldValue($field_index, $field_value);
 			}
 			$this->setCurrentID();
 		}
 
 
 		function getItemCategory()
 		{
 			static $lang_prefix = null;
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			$category_id = $this->getFromCache('category_names', implode(':', $this->curItem->CategoryPath));
 			if ($category_id) {
 				$this->Application->SetVar('m_cat_id', $category_id);
 				return $category_id;
 			}
 
 			if (is_null($lang_prefix)) {
 				$lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_';
 			}
 
 			foreach ($this->curItem->CategoryPath as $category_index => $category_name) {
 				if (!$category_name) continue;
 				$category_key = crc32( implode(':', array_slice($this->curItem->CategoryPath, 0, $category_index + 1) ) );
 
 				$category_id = $this->getFromCache('category_names', $category_key);
 				if ($category_id === false) {
 					// get parent category path to search only in it
 					$current_category_id = $this->Application->GetVar('m_cat_id');
 //					$parent_path = $this->getParentPath($current_category_id);
 
 					// get category id from database by name
 					$sql = 'SELECT CategoryId
 							FROM '.TABLE_PREFIX.'Category
 							WHERE ('.$lang_prefix.'Name = '.$this->Conn->qstr($category_name).') AND (ParentId = '.(int)$current_category_id.')';
 					$category_id = $this->Conn->GetOne($sql);
 
 					if ($category_id === false) {
 						// category not in db -> create
 						$category_fields = Array(	$lang_prefix.'Name' => $category_name, $lang_prefix.'Description' => $category_name,
 													'Status' => STATUS_ACTIVE, 'ParentId' => $current_category_id, 'AutomaticFilename' => 1
 											);
 						$this->dummyCategory->SetDBFieldsFromHash($category_fields);
 						if ($this->dummyCategory->Create()) {
 							$category_id = $this->dummyCategory->GetID();
 							$this->addToCache('category_parent_path', $category_id, $this->dummyCategory->GetDBField('ParentPath'));
 							$this->addToCache('category_names', $category_key, $category_id);
 						}
 					}
 					else {
 						$this->addToCache('category_names', $category_key, $category_id);
 					}
 				}
 
 				if ($category_id) {
 					$this->Application->SetVar('m_cat_id', $category_id);
 				}
 			}
 			if (!$this->curItem->CategoryPath) {
 				$category_id = $backup_category_id;
 			}
 
 			return $category_id;
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function processCurrentItem(&$event, $record_data)
 		{
 			$save_method = 'Create';
 			$load_keys = Array();
 
 			// create/update categories
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			// perform replace duplicates code
 			if ($this->exportOptions['ReplaceDuplicates']) {
 				// get replace keys first, then reset current item to empty one
 				$category_id = $this->getItemCategory();
 				if ($this->exportOptions['CheckDuplicatesMethod'] == 1) {
 					if ($this->curItem->GetID()) {
 						$load_keys = Array($this->curItem->IDField => $this->curItem->GetID());
 					}
 				}
 				else {
 					$key_fields = $this->exportOptions['DuplicateCheckFields'];
 					foreach ($key_fields as $key_field) {
 						$load_keys[$key_field] = $this->curItem->GetDBField($key_field);
 					}
 				}
 
 				$this->resetImportObject($event, IMPORT_LIVE);
 
 				if (count($load_keys)) {
 					$where_clause = '';
 					$language_id = (int)$this->Application->GetVar('m_lang');
 
 					if (!$language_id) {
 						$language_id = 1;
 					}
 
 					foreach ($load_keys as $field_name => $field_value) {
 						if (preg_match('/^cust_(.*)/', $field_name, $regs)) {
 							$custom_id = array_search($regs[1], $this->customFields);
 							$field_name = 'l'.$language_id.'_cust_'.$custom_id;
 							$where_clause .= '(custom_data.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND ';
 						}
 						else {
 							$where_clause .= '(item_table.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND ';
 						}
 
 					}
 					$where_clause = substr($where_clause, 0, -5);
 
 					$item_id = $this->getFromCache('new_ids', crc32($where_clause));
 					if (!$item_id) {
 						if ($this->exportOptions['CheckDuplicatesMethod'] == 2) {
 							// by other fields
 							$parent_path = $this->getParentPath($category_id);
 							$where_clause = '(c.ParentPath LIKE "'.$parent_path.'%") AND '.$where_clause;
 						}
 
 						$cdata_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
 						$sql = 'SELECT '.$this->curItem->IDField.'
 								FROM '.$this->curItem->TableName.' item_table
 								LEFT JOIN '.$cdata_table.' custom_data ON custom_data.ResourceId = item_table.ResourceId
 								LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON ci.ItemResourceId = item_table.ResourceId
 								LEFT JOIN '.TABLE_PREFIX.'Category c ON c.CategoryId = ci.CategoryId
 								WHERE '.$where_clause;
 						$item_id = $this->Conn->GetOne($sql);
 					}
 					$save_method = $item_id && $this->curItem->Load($item_id) ? 'Update' : 'Create';
 					if ($save_method == 'Update') {
 						// replace id from csv file with found id (only when ID is found in cvs file)
 						if (in_array($this->curItem->IDField, $this->exportFields)) {
 							$record_data[ array_search($this->curItem->IDField, $this->exportFields) ] = $item_id;
 						}
 					}
 				}
 
 				$this->setImportData($record_data);
 			}
 			else {
 				$this->resetImportObject($event, IMPORT_LIVE, $record_data);
 				$category_id = $this->getItemCategory();
 			}
 
 			// create main record
 			if ($save_method == 'Create') {
 				$this->fillRequiredFields($this->false, $this->curItem, true);
 			}
 
 //			$sql_start = getmicrotime();
 			if (!$this->curItem->$save_method()) {
 				$this->Application->SetVar('m_cat_id', $backup_category_id);
 				return false;
 			}
 //			$sql_end = getmicrotime();
 //			$this->saveLog('SQL ['.$save_method.'] Time: '.($sql_end - $sql_start).'s');
 
 			if ($load_keys && ($save_method == 'Create') && $this->exportOptions['ReplaceDuplicates']) {
 				// map new id to old id
 				$this->addToCache('new_ids', crc32($where_clause), $this->curItem->GetID() );
 			}
 
 			// assign item to categories
 			$this->curItem->assignToCategory($category_id, false);
 
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 			return true;
 		}
 
 		/*function saveLog($msg)
 		{
 			static $first_time = true;
 
 			$fp = fopen(FULL_PATH.'/sqls.log', $first_time ? 'w' : 'a');
 			fwrite($fp, $msg."\n");
 			fclose($fp);
 
 			$first_time = false;
 		}*/
 
 		/**
 		 * Returns category parent path, if possible, then from cache
 		 *
 		 * @param int $category_id
 		 * @return string
 		 */
 		function getParentPath($category_id)
 		{
 			$parent_path = $this->getFromCache('category_parent_path', $category_id);
 			if ($parent_path === false) {
 				$sql = 'SELECT ParentPath
 						FROM '.TABLE_PREFIX.'Category
 						WHERE CategoryId = '.$category_id;
 				$parent_path = $this->Conn->GetOne($sql);
 				$this->addToCache('category_parent_path', $category_id, $parent_path);
 			}
 			return $parent_path;
 		}
 
 		function getFileExtension()
 		{
 			return $this->exportOptions['ExportFormat'] == 1 ? 'csv' : 'xml';
 		}
 
 		function getLineSeparator($option = 'LineEndings')
 		{
 			return $this->exportOptions[$option] == 1 ? "\r\n" : "\n";
 		}
 
 		/**
 		 * Returns field caption for any exported field
 		 *
 		 * @param string $field
 		 * @return string
 		 */
 		function getFieldCaption($field)
 		{
 			if (substr($field, 0, 10) == '__CUSTOM__')
 			{
 				$ret = 'Custom_'.substr($field, 10, strlen($field) );
 			}
 			elseif (substr($field, 0, 12) == '__CATEGORY__')
 			{
 				return $this->getCategoryTitle();
 			}
 			elseif (substr($field, 0, 11) == '__VIRTUAL__') {
 				$ret = substr($field, 11);
 			}
 			else
 			{
 				$ret = $field;
 			}
 
 			return Array($ret);
 		}
 
 		/**
 		 * Returns requested field value (including custom fields and category fields)
 		 *
 		 * @param string $field
 		 * @return string
 		 */
 		function getFieldValue($field)
 		{
 			if (substr($field, 0, 10) == '__CUSTOM__') {
 				$field = 'cust_'.substr($field, 10, strlen($field));
 				$ret = $this->curItem->GetField($field);
 			}
 			elseif (substr($field, 0, 12) == '__CATEGORY__') {
 				return $this->getCategoryPath();
 			}
 			elseif (substr($field, 0, 11) == '__VIRTUAL__') {
 				$field = substr($field, 11);
 				$ret = $this->curItem->GetField($field);
 			}
 			else
 			{
 				$ret = $this->curItem->GetField($field);
 			}
 
 			$ret = str_replace("\r\n", $this->getLineSeparator('LineEndingsInside'), $ret);
 			return Array($ret);
 		}
 
 		/**
 		 * Returns category field(-s) caption based on export mode
 		 *
 		 * @return string
 		 */
 		function getCategoryTitle()
 		{
 			// category path in separated fields
 			$category_count = $this->getMaxCategoryLevel();
 			if ($this->exportOptions['CategoryFormat'] == 1)
 			{
 				// category path in one field
 				return $category_count ? Array('CategoryPath') : Array();
 			}
 			else
 			{
 				$i = 0;
 				$ret = Array();
 				while ($i < $category_count) {
 					$ret[] = 'Category'.($i + 1);
 					$i++;
 				}
 				return $ret;
 			}
 		}
 
 		/**
 		 * Returns category path in required format for current link
 		 *
 		 * @return string
 		 */
 		function getCategoryPath()
 		{
 			$category_id = $this->curItem->GetDBField('CategoryId');
 			$category_path = $this->getFromCache('category_path', $category_id);
 			if (!$category_path)
 			{
 				$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 				$sql = 'SELECT '.$ml_formatter->LangFieldName('CachedNavbar').'
 						FROM '.TABLE_PREFIX.'Category
 						WHERE CategoryId = '.$category_id;
 				$category_path = $this->Conn->GetOne($sql);
 
 				$category_path = $category_path ? explode('&|&', $category_path) : Array();
 				if ($category_path && strtolower($category_path[0]) == 'content') {
 					array_shift($category_path);
 				}
 
 				if ($this->exportOptions['IsBaseCategory']) {
 					$i = $this->exportOptions['BaseLevel'];
 					while ($i > 0) {
 						array_shift($category_path);
 						$i--;
 					}
 				}
 
 				$category_count = $this->getMaxCategoryLevel();
 				if ($this->exportOptions['CategoryFormat'] == 1) {
 					// category path in single field
 					$category_path = $category_count ? Array( implode($this->exportOptions['CategorySeparator'], $category_path) ) : Array();
 				}
 				else {
 					// category path in separated fields
 					$levels_used = count($category_path);
 					if ($levels_used < $category_count)
 					{
 						$i = 0;
 						while ($i < $category_count - $levels_used) {
 							$category_path[] = '';
 							$i++;
 						}
 					}
 				}
 				$this->addToCache('category_path', $category_id, $category_path);
 			}
 
 			return $category_path;
 		}
 
 		/**
 		 * Get maximal category deep level from links beeing exported
 		 *
 		 * @return int
 		 */
 		function getMaxCategoryLevel()
 		{
 			static $max_level = -1;
 
 			if ($max_level != -1)
 			{
 				return $max_level;
 			}
 
 			$sql = 'SELECT IF(c.CategoryId IS NULL, 0, MAX( LENGTH(c.ParentPath) - LENGTH( REPLACE(c.ParentPath, "|", "") ) - 1 ))
 					FROM '.$this->curItem->TableName.' item_table
 					LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON item_table.ResourceId = ci.ItemResourceId
 					LEFT JOIN '.TABLE_PREFIX.'Category c ON c.CategoryId = ci.CategoryId
 					WHERE (ci.PrimaryCat = 1) AND ';
 
 			$where_clause = '';
 			if ($this->exportOptions['export_ids'] === false) {
 				// get links from current category & all it's subcategories
 				if ($this->exportOptions['export_cats_ids'][0] == 0) {
 					$where_clause = 1;
 				}
 				else {
 					foreach ($this->exportOptions['export_cats_ids'] as $category_id) {
 						$where_clause .= '(c.ParentPath LIKE "%|'.$category_id.'|%") OR ';
 					}
 					$where_clause = substr($where_clause, 0, -4);
 				}
 			}
 			else {
 				// get only selected links
 				$where_clause = $this->curItem->IDField.' IN ('.implode(',', $this->exportOptions['export_ids']).')';
 			}
 
 			$max_level = $this->Conn->GetOne($sql.'('.$where_clause.')');
 
 			if ($this->exportOptions['IsBaseCategory'] ) {
 				$max_level -= $this->exportOptions['BaseLevel'];
 			}
 
 			return $max_level;
 		}
 
 		/**
 		 * Saves one record to export file
 		 *
 		 * @param Array $fields_hash
 		 */
 		function writeRecord($fields_hash)
 		{
 			fputcsv2($this->filePointer, $fields_hash, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy'], $this->getLineSeparator() );
 		}
 
 		function readRecord()
 		{
 			return fgetcsv($this->filePointer, 10000, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy']);
 		}
 
 		function saveOptions(&$event, $options = null)
 		{
 			if (!isset($options)) {
 				$options = $this->exportOptions;
 			}
 			$this->Application->StoreVar($event->getPrefixSpecial().'_options', serialize($options) );
 		}
 
 		function loadOptions(&$event)
 		{
 			return unserialize($this->Application->RecallVar($event->getPrefixSpecial().'_options'));
 		}
 
 		/**
 		 * Sets correct available & export fields
 		 *
 		 * @param kEvent $event
 		 */
 		function prepareExportColumns(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 
 			if (!array_key_exists('ExportColumns', $object->Fields)) {
 				// import/export prefix was used (see kDBEventHandler::prepareObject) but object don't plan to be imported/exported
 				return ;
 			}
 
 			$available_columns = Array();
 
 			if ($this->Application->getUnitOption($event->Prefix, 'CatalogItem')) {
 				// category field (mixed)
 				$available_columns['__CATEGORY__CategoryPath'] = 'CategoryPath';
 
 				if ($event->Special == 'import') {
 					// category field (separated fields)
 					$max_level = $this->Application->ConfigValue('MaxImportCategoryLevels');
 					$i = 0;
 					while ($i < $max_level) {
 						$available_columns['__CATEGORY__Category'.($i + 1)] = 'Category'.($i + 1);
 						$i++;
 					}
 				}
 			}
 
 			// db fields
 			foreach ($object->Fields as $field_name => $field_options)
 			{
 				if (!$object->skipField($field_name))
 				{
 					$available_columns[$field_name] = $field_name.(getArrayValue($field_options, 'required') ? '*' : '');
 				}
 			}
 
 			$handler =& $this->Application->recallObject($event->Prefix.'_EventHandler');
 			$available_columns = array_merge_recursive2($available_columns, $handler->getCustomExportColumns($event));
 
 			// custom fields
 			foreach ($object->customFields as $custom_id => $custom_name)
 			{
 				$available_columns['__CUSTOM__'.$custom_name] = $custom_name;
 			}
 
 			// columns already in use
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info)
 			{
 				list($item_id, $field_values) = each($items_info);
 				$export_keys = $field_values['ExportColumns'];
 				$export_keys = $export_keys ? explode('|', substr($export_keys, 1, -1) ) : Array();
 			}
 			else {
 				$export_keys = Array();
 			}
 
 			$export_columns = Array();
 			foreach ($export_keys as $field_key)
 			{
 				$field_name = $this->getExportField($field_key);
 				$export_columns[$field_key] = $field_name;
 				unset($available_columns[$field_key]);
 			}
 
 			$options = $object->GetFieldOptions('ExportColumns');
 			$options['options'] = $export_columns;
 			$object->SetFieldOptions('ExportColumns', $options);
 
 			$options = $object->GetFieldOptions('AvailableColumns');
 			$options['options'] = $available_columns;
 			$object->SetFieldOptions('AvailableColumns', $options);
 
 			$this->updateImportFiles($event);
 			$this->PrepareExportPresets($event);
 		}
 
 		function PrepareExportPresets(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$options = $object->GetFieldOptions('ExportPresets');
 
 			$export_settings = $this->Application->RecallPersistentVar('export_settings');
 			if (!$export_settings) return ;
 			$export_settings = unserialize($export_settings);
 
 			if (!isset($export_settings[$event->Prefix])) return ;
 
 
 			$export_presets = array(''=>'');
 			foreach ($export_settings[$event->Prefix] as $key => $val) {
 				$export_presets[implode('|', $val['ExportColumns'])] = $key;
 			}
 
 			$options['options'] = $export_presets;
 			$object->SetFieldOptions('ExportPresets', $options);
 		}
 
 		function getExportField($field_key)
 		{
 			$prepends = Array('__CUSTOM__', '__CATEGORY__');
 			foreach ($prepends as $prepend)
 			{
 				if (substr($field_key, 0, strlen($prepend) ) == $prepend)
 				{
 					$field_key = substr($field_key, strlen($prepend), strlen($field_key) );
 					break;
 				}
 			}
 			return $field_key;
 		}
 
 		/**
 		 * Updates uploaded files list
 		 *
 		 * @param kEvent $event
 		 */
 		function updateImportFiles(&$event)
 		{
 			if ($event->Special != 'import') {
 				return false;
 			}
 
 			$object =& $event->getObject();
 
 			$import_filenames = Array();
 
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->CheckFolder(EXPORT_PATH);
 
 			if ($folder_handle = opendir(EXPORT_PATH)) {
 				while (false !== ($file = readdir($folder_handle))) {
 					if (is_dir(EXPORT_PATH.'/'.$file) || substr($file, 0, 1) == '.' || strtolower($file) == 'cvs' || strtolower($file) == 'dummy' || filesize(EXPORT_PATH.'/'.$file) == 0) continue;
 
 					$file_size = formatSize( filesize(EXPORT_PATH.'/'.$file) );
 					$import_filenames[$file] = $file.' ('.$file_size.')';
 				}
 				closedir($folder_handle);
 			}
 
 			$options = $object->GetFieldOptions('ImportLocalFilename');
 			$options['options'] = $import_filenames;
 			$object->SetFieldOptions('ImportLocalFilename', $options);
 		}
 
 		/**
 		 * Returns module folder
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
 		function getModuleName(&$event)
 		{
 			$module_path = $this->Application->getUnitOption($event->Prefix, 'ModuleFolder') . '/';
 			$module_name = $this->Application->findModule('Path', $module_path, 'Name');
 
 			return mb_strtolower($module_name);
 		}
 
 		/**
 		 * Export form validation & processing
 		 *
 		 * @param kEvent $event
 		 */
 		function OnExportBegin(&$event)
 		{
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if (!$items_info)
 			{
 				$items_info = unserialize( $this->Application->RecallVar($event->getPrefixSpecial().'_ItemsInfo') );
 				$this->Application->SetVar($event->getPrefixSpecial(true), $items_info);
 			}
 
 			list($item_id, $field_values) = each($items_info);
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$object->SetFieldsFromHash($field_values);
 			$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
 
 			$object->setID($item_id);
 			$this->setRequiredFields($event);
 
 			$export_object =& $this->Application->recallObject('CatItemExportHelper');
 
 			// save export/import options
 			if ($event->Special == 'export')
 			{
 				$export_ids = $this->Application->RecallVar($event->Prefix.'_export_ids');
 				$export_cats_ids = $this->Application->RecallVar($event->Prefix.'_export_cats_ids');
 
 				// used for multistep export
 				$field_values['export_ids'] = $export_ids ? explode(',', $export_ids) : false;
 				$field_values['export_cats_ids'] = $export_cats_ids ? explode(',', $export_cats_ids) : Array( $this->Application->GetVar('m_cat_id') );
 			}
 
 			$field_values['ExportColumns'] = $field_values['ExportColumns'] ? explode('|', substr($field_values['ExportColumns'], 1, -1) ) : Array();
 			$field_values['start_from'] = 0;
 
 			$this->Application->HandleEvent($nevent, $event->Prefix.':OnBeforeExportBegin', array('options'=>$field_values));
 			$field_values = $nevent->getEventParam('options');
 
 			$export_object->saveOptions($event, $field_values);
 
 			if( $export_object->verifyOptions($event) )
 			{
 				if ($this->_getExportSavePreset($object)) {
 					$name = $object->GetDBField('ExportPresetName');
 
 					$export_settings = $this->Application->RecallPersistentVar('export_settings');
 					$export_settings = $export_settings ? unserialize($export_settings) : array();
 					$export_settings[$event->Prefix][$name] = $field_values;
 					$this->Application->StorePersistentVar('export_settings', serialize($export_settings));
 				}
 
 				$progress_t = $this->Application->RecallVar('export_progress_t');
 				if ($progress_t) {
 					$this->Application->RemoveVar('export_progress_t');
 				}
 				else {
 					$progress_t = $export_object->getModuleName($event).'/'.$event->Special.'_progress';
 				}
 				$event->redirect = $progress_t;
 
 				if ($event->Special == 'import') {
 					$import_category = (int)$this->Application->RecallVar('ImportCategory');
 
 					// in future could use module root category if import category will be unavailable :)
 					$event->SetRedirectParam('m_cat_id', $import_category); // for template permission checking
 					$this->Application->StoreVar('m_cat_id', $import_category); // for event permission checking
 				}
 			}
 			else
 			{
 				// make uploaded file local & change source selection
 				$filename = getArrayValue($field_values, 'ImportFilename');
 				if ($filename) {
 					$export_object->updateImportFiles($event);
 					$object->SetDBField('ImportSource', 2);
 					$field_values['ImportSource'] = 2;
 					$object->SetDBField('ImportLocalFilename', $filename);
 					$field_values['ImportLocalFilename'] = $filename;
 					$export_object->saveOptions($event, $field_values);
 				}
 
 				$event->status = erFAIL;
 				$event->redirect = false;
 			}
 		}
 
 		/**
 		 * Returns export save preset name, when used at all
 		 *
 		 * @param kDBItem $object
 		 * @return string
 		 */
 		function _getExportSavePreset(&$object)
 		{
 			if (!array_key_exists('ExportSavePreset', $object->Fields)) {
 				return '';
 			}
 
 			return $object->GetDBField('ExportSavePreset');
 		}
 
 		/**
 		 * set required fields based on import or export params
 		 *
 		 * @param kEvent $event
 		 */
 		function setRequiredFields(&$event)
 		{
 			$required_fields['common'] = Array('FieldsSeparatedBy', 'LineEndings', 'CategoryFormat');
 
 			$required_fields['export'] = Array('ExportFormat', 'ExportFilename','ExportColumns');
 
 			$object =& $event->getObject();
 			if ($this->_getExportSavePreset($object)) {
 				$required_fields['export'][] = 'ExportPresetName';
 			}
 
 			$required_fields['import'] = Array('FieldTitles', 'ImportSource', 'CheckDuplicatesMethod'); // ImportFilename, ImportLocalFilename
 
 			if ($event->Special == 'import')
 			{
 				$import_source = Array(1 => 'ImportFilename', 2 => 'ImportLocalFilename');
 				$used_field = $import_source[ $object->GetDBField('ImportSource') ];
 
 				$required_fields[$event->Special][] = $used_field;
 				$object->Fields[$used_field]['error_field'] = 'ImportSource';
 
 				if ($object->GetDBField('FieldTitles') == 2) $required_fields[$event->Special][] = 'ExportColumns'; // manual field titles
 			}
 
 			$required_fields = array_merge($required_fields['common'], $required_fields[$event->Special]);
 			foreach ($required_fields as $required_field) {
 				$object->setRequired($required_field, true);
 			}
 		}
 
 	}
\ No newline at end of file
Index: branches/5.1.x/core/units/helpers/menu_helper.php
===================================================================
--- branches/5.1.x/core/units/helpers/menu_helper.php	(revision 13986)
+++ branches/5.1.x/core/units/helpers/menu_helper.php	(revision 13987)
@@ -1,382 +1,382 @@
 <?php
 /**
 * @version	$Id: menu_helper.php 12951 2009-12-19 10:04:11Z alex $
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class MenuHelper extends kHelper {
 
 		/**
 		 * Cached version of site menu
 		 *
 		 * @var Array
 		 */
 		var $Menu = null;
 
 		/**
 		 * Parent path mapping used in CachedMenu tag
 		 *
 		 * @var Array
 		 */
 		var $parentPaths = Array ();
 
 		/**
 		 * Builds site menu
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function menuTag($prefix_special, $params)
 		{
 			list ($menu, $root_path) = $this->_prepareMenu();
 			$cat = $this->_getCategoryId($params);
 
 			$parent_path = array_key_exists($cat, $this->parentPaths) ? $this->parentPaths[$cat] : '';
 			$parent_path = str_replace($root_path, '', $parent_path); // menu starts from module path
 
 			$levels = explode('|', trim($parent_path, '|'));
 
 			if ($levels[0] === '') {
 				$levels = Array ();
 			}
 
 			if (array_key_exists('level', $params) && $params['level'] > count($levels)) {
 				// current level is deeper, then requested level
 				return ;
 			}
 
 			$level = max(array_key_exists('level', $params) ? $params['level'] - 1 : count($levels) - 1, 0);
 			$parent = array_key_exists($level, $levels) ? $levels[$level] : 0;
 
 			$cur_menu =& $menu;
 			$menu_path = array_slice($levels, 0, $level + 1);
 
 			foreach ($menu_path as $elem) {
 				$cur_menu =& $cur_menu['c' . $elem]['sub_items'];
 			}
 
 			$block_params = $this->prepareTagParams($prefix_special, $params);
 			$block_params['name'] = $params['render_as'];
 
 			$this->Application->SetVar('cur_parent_path', $parent_path);
 			$real_cat_id = $this->Application->GetVar('m_cat_id');
 
 			if (!is_array($cur_menu) || !$cur_menu) {
 				// no menus on this level
 				return '';
 			}
 
 			$ret = '';
 			$cur_item = 1;
 			$cur_menu = $this->_removeNonMenuItems($cur_menu);
 			$block_params['total_items'] = count($cur_menu);
 
 			foreach ($cur_menu as $page) {
 				$block_params = array_merge_recursive2(
 					$block_params,
 					$this->_prepareMenuItem($page, $real_cat_id, $root_path)
 				);
 
 				$block_params['is_last'] = $cur_item == $block_params['total_items'];
 				$block_params['is_first'] = $cur_item == 1;
 
 				// bug #1: this breaks active section highlighting when 2 menu levels are printed on same page (both visible)
 				// bug #2: people doesn't pass cat_id parameter to m_Link tags in their blocks, so this line helps them; when removed their links will lead to nowhere
 				$this->Application->SetVar('m_cat_id', $page['CategoryId']);
 
 				$ret .= $this->Application->ParseBlock($block_params);
 				$cur_item++;
 			}
 
 			$this->Application->SetVar('m_cat_id', $real_cat_id);
 
 			return $ret;
 		}
 
 		/**
 		 * Builds cached menu version
 		 *
 		 * @return Array
 		 */
 		function _prepareMenu()
 		{
 			static $root_cat = null;
 			static $root_path = null;
 
 			if (!$root_cat) {
-				$root_cat = $this->Application->ModuleInfo['Core']['RootCat'];
+				$root_cat = $this->Application->getBaseCategory();
 				$cache_key = 'parent_paths[%CIDSerial:' . $root_cat . '%]';
 				$root_path = $this->Application->getCache($cache_key);
 
 				if ($root_path === false) {
 					$this->Conn->nextQueryCachable = true;
 					$sql = 'SELECT ParentPath
 							FROM ' . TABLE_PREFIX . 'Category
 							WHERE CategoryId = ' . $root_cat;
 					$root_path = $this->Conn->GetOne($sql);
 					$this->Application->setCache($cache_key, $root_path);
 				}
 			}
 
 			if (!$this->Menu) {
 				if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 					$menu = $this->Application->getCache('master:cms_menu', false);
 				}
 				else {
 					$menu = $this->Application->getDBCache('cms_menu');
 				}
 
 				if ($menu) {
 					$menu = unserialize($menu);
 					$this->parentPaths = $menu['parentPaths'];
 				}
 				else {
 					$menu = $this->_altBuildMenuStructure(Array ('CategoryId' => $root_cat, 'ParentPath' => $root_path));
 					$menu['parentPaths'] = $this->parentPaths;
 
 					if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 						$this->Application->setCache('master:cms_menu', serialize($menu));
 					}
 					else {
 						$to_cache = serialize($menu); // setDBCache's 2nd parameter passed by reference!
 						$this->Application->setDBCache('cms_menu', $to_cache);
 					}
 				}
 
 				unset($menu['parentPaths']);
 				$this->Menu = $menu;
 			}
 
 			return Array ($this->Menu, $root_path);
 		}
 
 		/**
 		 * Returns category id based tag parameters
 		 *
 		 * @param Array $params
 		 * @return int
 		 */
 		function _getCategoryId($params)
 		{
 			$cat = isset($params['category_id']) && $params['category_id'] != '' ? $params['category_id'] : $this->Application->GetVar('m_cat_id');
 			if ("$cat" == 'parent') {
 				$this_category =& $this->Application->recallObject('c');
 				/* @var $this_category kDBItem */
 
 				$cat = $this_category->GetDBField('ParentId');
 			}
 			elseif ($cat == 0) {
-				$cat = $this->Application->ModuleInfo['Core']['RootCat'];
+				$cat = $this->Application->getBaseCategory();
 			}
 
 			return $cat;
 		}
 
 		/**
 		 * Prepares cms menu item block parameters
 		 *
 		 * @param Array $page
 		 * @param int $real_cat_id
 		 * @param string $root_path
 		 * @return Array
 		 */
 		function _prepareMenuItem($page, $real_cat_id, $root_path)
 		{
 			static $language_id = null;
 			static $primary_language_id = null;
 			static $template = null;
 
 			if (!isset($language_id)) {
 				$language_id = $this->Application->GetVar('m_lang');
 				$primary_language_id = $this->Application->GetDefaultLanguageId();
 				$template = $this->Application->GetVar('t');
 			}
 
 			$active = $category_active = false;
 			$title = $page['l' . $language_id . '_ItemName'] ? $page['l' . $language_id . '_ItemName'] : $page['l' . $primary_language_id . '_ItemName'];
 
 			if ($page['ItemType'] == 'cat') {
 				if (array_key_exists($real_cat_id, $this->parentPaths)) {
 					$active = strpos($this->parentPaths[$real_cat_id], $page['ParentPath']) !== false;
 				}
 				elseif ($page['ItemPath'] == $template) {
 					// physical template in menu
 					$active = true;
 				}
 
 				$category_active = $page['CategoryId'] == $real_cat_id;
 			}
 
 			/*if ($page['ItemType'] == 'cat_index') {
 				$check_path = str_replace($root_path, '', $page['ParentPath']);
 				$active = strpos($parent_path, $check_path) !== false;
 			}
 
 			if ($page['ItemType'] == 'page') {
 				$active = $page['ItemPath'] == preg_replace('/^Content\//i', '', $this->Application->GetVar('t'));
 			}*/
 
 			$block_params = Array (
 				'title' => $title,
 				'template' => $page['ItemPath'],
 				'active' => $active,
 				'category_active' => $category_active, // new
 				'parent_path' => $page['ParentPath'],
 				'parent_id' => $page['ParentId'],
 				'cat_id' => $page['CategoryId'],
 				'item_type' => $page['ItemType'],
 				'page_id' => $page['ItemId'],
 				'use_section' => ($page['Type'] == PAGE_TYPE_TEMPLATE) && ($page['ItemPath'] != 'index'),
 				'has_sub_menu' => isset($page['sub_items']) && count($page['sub_items']) > 0,
 				'external_url' => $page['UseExternalUrl'] ? $page['ExternalUrl'] : false, // for backward compatibility
 				'menu_icon' => $page['UseMenuIconUrl'] ? $page['MenuIconUrl'] : false,
 			);
 
 			return $block_params;
 		}
 
 		/**
 		 * Returns only items, that are visible in menu
 		 *
 		 * @param Array $menu
 		 * @return Array
 		 */
 		function _removeNonMenuItems($menu)
 		{
 			$theme_id = $this->Application->GetVar('m_theme');
 
 			foreach ($menu as $menu_index => $menu_item) {
 				// $menu_index is in "cN" format, where N is category id
 				if (!$menu_item['IsMenu'] || ($menu_item['ThemeId'] != $theme_id && $menu_item['ThemeId'] != 0)) {
 					// don't show sections, that are not from menu OR system templates from other themes
 					unset($menu[$menu_index]);
 				}
 			}
 
 			return $menu;
 		}
 
 		/**
 		 * Builds cache for children of given category (no matter, what menu status is)
 		 *
 		 * @param Array $parent
 		 * @return Array
 		 */
 		function _altBuildMenuStructure($parent)
 		{
 			static $lang_part = null;
 
 			if (!isset($lang_part)) {
 				$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
 				/* @var $ml_helper kMultiLanguageHelper */
 
 				$lang_part = '';
 
 				for ($i = 1; $i <= $ml_helper->languageCount; $i++) {
 					$lang_part .= 'c.l' . $i . '_MenuTitle AS l' . $i . '_ItemName,' . "\n";
 				}
 			}
 
 			// Sub-categories from current category
 			$items = $this->getSubCategories( $parent['CategoryId'] );
 
 			// sort menu items
 			uasort($items, Array (&$this, '_menuSort'));
 
 			// store menu items
 			$the_items = Array();
 			foreach ($items as $index => $an_item) {
 				$the_items[ $an_item['ItemId'] ] = $an_item;
 
 				$this->parentPaths[ $an_item['CategoryId'] ] = $an_item['ParentPath'];
 			}
 
 			// process submenus of each menu
 			$items = $the_items;
 			foreach ($items as $key => $menu_item) {
 				if ($menu_item['CategoryId'] == $parent['CategoryId']) {
 					// don't process myself - prevents recursion
 					continue;
 				}
 
 				$sub_items = $this->_altBuildMenuStructure($menu_item);
 
 				if ($sub_items) {
 					$items[$key]['sub_items'] = $sub_items;
 				}
 			}
 
 			return $items;
 		}
 
 		function getSubCategories($parent_id)
 		{
 			static $items_by_parent = null;
 
 			if (!isset($items_by_parent)) {
 				$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
 				/* @var $ml_helper kMultiLanguageHelper */
 
 				$lang_part = '';
 				$items_by_parent = Array ();
 
 				for ($i = 1; $i <= $ml_helper->languageCount; $i++) {
 					$lang_part .= 'c.l' . $i . '_MenuTitle AS l' . $i . '_ItemName,' . "\n";
 				}
 
 				// Sub-categories from current category
 				$sql = 'SELECT
 							c.CategoryId AS CategoryId,
 							CONCAT(\'c\', c.CategoryId) AS ItemId,
 							c.Priority AS ItemPriority,
 							' . $lang_part . '
 
 							IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', c.Template, CONCAT("id:", c.CategoryId)) AS ItemPath,
 							c.ParentPath AS ParentPath,
 							c.ParentId As ParentId,
 							\'cat\' AS ItemType,
 							c.IsMenu, c.Type, c.ThemeId, c.UseExternalUrl, c.ExternalUrl, c.UseMenuIconUrl, c.MenuIconUrl
 						FROM ' . TABLE_PREFIX . 'Category AS c
 						WHERE c.Status = ' . STATUS_ACTIVE;
 				$items = $this->Conn->Query($sql, 'ItemId');
 
 				foreach ($items as $item_id => $item_data) {
 					$item_parent_id = $item_data['ParentId'];
 
 					if ( !array_key_exists($item_parent_id, $items_by_parent) ) {
 						$items_by_parent[$item_parent_id] = Array ();
 					}
 
 					$items_by_parent[$item_parent_id][$item_id] = $item_data;
 				}
 			}
 
 			return array_key_exists($parent_id, $items_by_parent) ? $items_by_parent[$parent_id] : Array ();
 		}
 
 		/**
 		 * Method for sorting pages by priority in decending order
 		 *
 		 * @param Array $a
 		 * @param Array $b
 		 * @return int
 		 */
 		function _menuSort($a, $b)
 		{
 			if ($a['ItemPriority'] == $b['ItemPriority']) {
 				return 0;
 			}
 
 			return ($a['ItemPriority'] < $b['ItemPriority']) ? 1 : -1; //descending
 		}
 	}
Index: branches/5.1.x/core/units/helpers/permissions_helper.php
===================================================================
--- branches/5.1.x/core/units/helpers/permissions_helper.php	(revision 13986)
+++ branches/5.1.x/core/units/helpers/permissions_helper.php	(revision 13987)
@@ -1,778 +1,781 @@
 <?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 kPermissionsHelper extends kHelper {
 
 		/**
 		 * Current set of permissions for group being edited
 		 *
 		 * @var Array
 		 */
 		var $Permissions = Array();
 
 		function LoadPermissions($group_id, $cat_id, $type = 1, $prefix = '')
 		{
 			$perm_table = $this->Application->getUnitOption('perm', 'TableName');
 			$perm_table = $this->Application->GetTempName($perm_table, 'prefix:'.$prefix);
 			$sql = 'SELECT *
 					FROM '.$perm_table.'
 					WHERE (GroupId = '.$group_id.') AND (CatId = '.$cat_id.') AND (Type = '.$type.')';
 			$permissions = $this->Conn->Query($sql, 'Permission');
 
 			$this->Permissions = Array();
 			foreach ($permissions as $perm_name => $perm_options) {
 				$perm_record['value'] = $perm_options['PermissionValue'];
 				$perm_record['id'] = $perm_options['PermissionId'];
 				$this->Permissions[$perm_name] = $perm_record;
 			}
 		}
 
 		function getPermissionValue($perm_name)
 		{
 			 return isset($this->Permissions[$perm_name]) ? $this->Permissions[$perm_name]['value'] : 0;
 		}
 
 		function getPermissionID($perm_name)
 		{
 			return isset($this->Permissions[$perm_name]) ? $this->Permissions[$perm_name]['id'] : 0;
 		}
 
 		/**
 		 * This is old permission like ADMIN or LOGIN
 		 *
 		 * @param string $section_name
 		 * @param string $perm_name
 		 * @return bool
 		 */
 		function isOldPermission($section_name, $perm_name)
 		{
 			return $section_name == 'in-portal:root' && $perm_name != 'view';
 		}
 
 		/**
 		 * Returns permission names to check based on event name and item prefix (main item or subitem)
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 */
 		function getPermissionByEvent(&$event, $perm_mapping)
 		{
 			$top_prefix = $event->getEventParam('top_prefix');
 
 			$pefix_type = ($top_prefix == $event->Prefix) ? 'self' : 'subitem';
 			$perm_mapping = getArrayValue($perm_mapping, $event->Name);
 
 			if (!$perm_mapping[$pefix_type]) {
 				trigger_error('Permission mappings not defined for event <b>'.$top_prefix.' <- '.$event->Prefix.':'.$event->Name.'</b>', E_USER_ERROR);
 			}
 
 			if ($perm_mapping[$pefix_type] === true) {
 				// event is defined in mapping but is not checked by permissions
 				return true;
 			}
 
 			return explode('|', $perm_mapping[$pefix_type]);
 		}
 
 		/**
 		 * Common event permission checking method
 		 *
 		 * @param kEvent $event
 		 */
 		function CheckEventPermission(&$event, $perm_mapping)
 		{
 			$section = $event->getSection();
 			if (preg_match('/^CATEGORY:(.*)/', $section)) {
 				return $this->CheckEventCategoryPermission($event, $perm_mapping);
 			}
 
 			$top_prefix = $event->getEventParam('top_prefix');
 			$check_perms = $this->getPermissionByEvent($event, $perm_mapping);
 
 			if ($check_perms === true) {
 				// event is defined in mapping but is not checked by permissions
 				return true;
 			}
 
 			$perm_status = false;
 			foreach ($check_perms as $perm_name) {
 				// check if at least one of required permissions is set
 				if ($perm_name == 'debug' && $this->Application->isDebugMode(false)) {
 					// universal "debug" permission
 					return true;
 				}
 
 				$perm_name = $section.'.'.$perm_name;
 				$perm_status = $this->CheckPermission($perm_name, 1);
 				if (($perm_name == $section.'.add') && $perm_status && ($top_prefix == $event->Prefix)) {
 					// main item, add permission allowed, but ID is > 0, then deny permission
 					// how to get id here
 				}
 
 				if ($perm_status) {
 					return $perm_status;
 				}
 			}
 
 			return $this->finalizePermissionCheck($event, $perm_status);
 		}
 
 		/**
 		 * Returns owner + primary category for each item (used for permission checking)
 		 *
 		 * @param string $prefix
 		 * @param string $ids
 		 * @param bool $temp_mode
 		 * @return Array
 		 * @author Alex
 		 */
 		function GetCategoryItemData($prefix, $ids, $temp_mode = false)
 		{
 			if (is_array($ids)) {
 				$ids = implode(',', $ids);
 			}
 			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
 			$ci_table = $this->Application->getUnitOption('ci', 'TableName');
 
 			if ($temp_mode) {
 				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $prefix);
 				$ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $prefix);
 			}
 
 			$owner_field = $this->Application->getUnitOption($prefix, 'OwnerField');
 			if (!$owner_field) {
 				$owner_field = 'CreatedById';
 			}
 
 			$sql = 'SELECT item_table.'.$id_field.', item_table.'.$owner_field.' AS CreatedById, ci.CategoryId
 					FROM '.$table_name.' item_table
 					LEFT JOIN '.$ci_table.' ci ON ci.ItemResourceId = item_table.ResourceId
 					WHERE item_table.'.$id_field.' IN ('.$ids.') AND (ci.PrimaryCat = 1)';
 			return $this->Conn->Query($sql, $id_field);
 		}
 
 		/**
 		 * Check category-based permissions for category items
 		 *
 		 * @param kEvent $event
 		 */
 		function _frontCheckEventCategoryPermission(&$event, $event_perm_mapping)
 		{
 			// mapping between specific permissions and common permissions
 			static $perm_mapping = Array(
 				'add' => 'ADD', 'add.pending' => 'ADD.PENDING', 'edit' => 'MODIFY',
 				'edit.pending' => 'MODIFY.PENDING', 'delete' => 'DELETE', 'view' => 'VIEW',
 				'debug' => 'DEBUG'
 			);
 
 			$top_prefix = $event->getEventParam('top_prefix');
 			$event_handler =& $this->Application->recallObject($event->Prefix.'_EventHandler');
 			/* @var $event_handler kCatDBEventHandler */
 
 			$raise_warnings = $event->getEventParam('raise_warnings');
 			$event->setEventParam('raise_warnings', 0);
 			if ($event->Prefix != $top_prefix) {
 				$top_event = new kEvent($top_prefix.':'.$event->Name);
 				$id = $event_handler->getPassedID($top_event);
 			}
 			else {
 				$id = $event_handler->getPassedID($event);
 			}
 			$event->setEventParam('raise_warnings', $raise_warnings);
 
 			$owner_id = USER_ROOT; // owner is root if not detected
 			if (!$id) {
 				// item being created -> check by current (before editing started, saved in OnPreCreate event) category permissions
 				// note: category in session is placed on catalog data import start
 				$category_id = $this->Application->isAdmin ? $this->Application->RecallVar('m_cat_id') : $this->Application->GetVar('m_cat_id');
 			}
 			elseif ($top_prefix == 'c' || $top_prefix == 'st') {
 				$category_id = $id;
 			}
 			else {
 				// item being edited -> check by it's primary category permissions
 				$items_info = $this->GetCategoryItemData($top_prefix, $id);
 
 				if ($items_info) {
 					$category_id = $items_info[$id]['CategoryId'];
 					$owner_id = $items_info[$id]['CreatedById'];
 				}
 				else {
 					// item wasn't found in database
 					$category_id = $this->Application->GetVar('m_cat_id');
 				}
 			}
 
 			// specific permission check for pending & owner permissions: begin
 			$uploader_events = Array ('OnUploadFile', 'OnDeleteFile', 'OnViewFile');
 			if (in_array($event->Name, $uploader_events)) {
 				// don't recall target object during uploader-related, because OnItemLoad will use incorrect
 				// $user_id in Firefox (during Flash problems session will be used from Internet Exploere)
 				$new_item = false;
 			}
 			else {
 				$new_item = $this->Application->isAdminUser && $event_handler->isNewItemCreate($event) ? true : false;
 				$check_status = $this->checkCombinedPermissions($event, $owner_id, (int)$category_id, $new_item);
 			}
 
 			if (isset($check_status)) {
 				return $this->finalizePermissionCheck($event, $check_status);
 			}
 			// specific permission check for pending & owner permissions: end
 
 			$perm_status = false;
 			$check_perms = $this->getPermissionByEvent($event, $event_perm_mapping);
 
 			if ($check_perms === true) {
 				// event is defined in mapping but is not checked by permissions
 				return true;
 			}
 
 			$item_prefix = $this->Application->getUnitOption($top_prefix, 'PermItemPrefix');
 			foreach ($check_perms as $perm_name) {
 				// check if at least one of required permissions is set
 				if (!isset($perm_mapping[$perm_name])) {
 					// not mapped permission (e.g. advanced:approve) -> skip
 					continue;
 				}
 
 				if ($perm_name == 'debug' && $this->Application->isDebugMode(false)) {
 					// universal "debug" permission
 					return true;
 				}
 
 				$perm_name = $item_prefix.'.'.$perm_mapping[$perm_name];
 				$perm_status = $this->CheckPermission($perm_name, 0, (int)$category_id);
 
 				if ($perm_status) {
 					return $perm_status;
 				}
 			}
 
 			return $this->finalizePermissionCheck($event, $perm_status);
 		}
 
 		/**
 		 * Finalizes permission checking (with additional debug output, when in debug mode)
 		 *
 		 * @param kEvent $event
 		 * @param bool $perm_status
 		 * @return bool
 		 */
 		function finalizePermissionCheck(&$event, $perm_status)
 		{
 			if (!$perm_status) {
 				if (MOD_REWRITE) {
 //					$event->SetRedirectParam('m_cat_id', 0); // category means nothing on admin login screen
 					$event->SetRedirectParam('next_template', urlencode('external:' . $_SERVER['REQUEST_URI']));
 				}
 				else {
 					$event->SetRedirectParam('next_template', $this->Application->GetVar('t'));
 				}
 
 				if ($this->Application->isDebugMode()) {
 					// for debugging purposes
 					$event->SetRedirectParam('section', $event->getSection());
 					$event->SetRedirectParam('main_prefix', $event->getEventParam('top_prefix'));
 					$event->SetRedirectParam('event_name', $event->Name);
 				}
 
 				$event->status = erPERM_FAIL;
 			}
 
 			return $perm_status;
 		}
 
 		/**
 		 * Allows to check combined permissions (*.owner, *.pending) for add/modify/delete operations from admin & front-end
 		 *
 		 * @param kEvent $event
 		 * @param int $owner_id
 		 * @param int $category_id
 		 * @param bool $new_item
 		 * @return mixed
 		 */
 		function checkCombinedPermissions(&$event, $owner_id, $category_id, $new_item = false)
 		{
 			$ret = null; // true/false when used, null when not used
 			$top_prefix = $event->getEventParam('top_prefix');
 
 			// check admin permission
 			if (substr($event->Name, 0, 9) == 'OnPreSave') {
 				if ($new_item) {
 					$ret = $this->AddCheckPermission($category_id, $top_prefix);
 				}
 				else {
 					// add & modify because $new_item is false, when item is aready created & then saved in temp table (even with 0 id)
 					$ret =	$this->AddCheckPermission($category_id, $top_prefix) ||
 							$this->ModifyCheckPermission($owner_id, $category_id, $top_prefix);
 				}
 			}
 
 			// check front-end permissions
 			switch ($event->Name) {
 				case 'OnCreate':
 					$ret = $this->AddCheckPermission($category_id, $top_prefix);
 					break;
 
 				case 'OnUpdate':
 					$ret = $this->ModifyCheckPermission($owner_id, $category_id, $top_prefix);
 					break;
 
 				case 'OnDelete':
 				case 'OnMassDelete':
 					$ret = $this->DeleteCheckPermission($owner_id, $category_id, $top_prefix);
 					break;
 			}
 
 			if ($ret === 0) {
 				// permission check failed (user has no permission)
 				$event->status = erPERM_FAIL;
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Simplified permission check for category items, when adding/editing them from advanced view.
 		 *
 		 * @param kEvent $event
 		 * @return mixed
 		 */
 		function CheckEventCategoryPermission(&$event, $event_perm_mapping)
 		{
 			if (!$this->Application->isAdmin) {
 				// check front-end permission by old scheme
 				return $this->_frontCheckEventCategoryPermission($event, $event_perm_mapping);
 			}
 
 			if (substr($event->Name, 0, 9) == 'OnPreSave') {
 				// check separately, because permission mapping is not defined for OnPreSave* events
 				$check_perms = Array ('add', 'edit');
 			}
 			else {
 				$check_perms = $this->getPermissionByEvent($event, $event_perm_mapping);
 			}
 
 			if ($check_perms === true) {
 				// event is defined in mapping but is not checked by permissions
 				return true;
 			}
 
 			// 1. most of events does require admin login only
 			$perm_status = $this->Application->isAdminUser;
 
 			// 2. in case, when event require more, then "view" right, then restrict it to temporary tables only
 			if (!in_array('view', $check_perms)) {
 				$perm_status = $perm_status && $this->Application->IsTempMode($event->Prefix, $event->Special);
 			}
 
 			return $this->finalizePermissionCheck($event, $perm_status);
 		}
 
 		function TagPermissionCheck($params, $is_owner = false)
 		{
 			$perm_prefix = getArrayValue($params, 'perm_prefix');
 			$perm_event = getArrayValue($params, 'perm_event');
 			$permission_groups = getArrayValue($params, 'permissions');
 
 			if ($permission_groups && !$perm_event) {
 				// check permissions by permission names in current category
 				$permission_groups = explode('|', $permission_groups);
 				$group_has_permission = false;
 
 				$perm_category = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id');
 
 				if ($perm_prefix) {
 					// use primary category of item with id from {perm_prefix}_id as base for permission checking
 					$perm_category = $this->getPrimaryCategory($perm_prefix);
 				}
 
 				$is_system = isset($params['system']) && $params['system'] ? 1 : 0;
 				foreach ($permission_groups as $permission_group) {
 					$permissions = explode(',', $permission_group);
 					$has_permission = true;
 					foreach ($permissions as $permission) {
 						$owner_checked = (strpos($permission, '.OWNER.') !== false) ? $is_owner : true;
 						$has_permission = $has_permission && $this->CheckPermission($permission, $is_system, $perm_category) && $owner_checked;
 					}
 					$group_has_permission = $group_has_permission || $has_permission;
 
 					if ($group_has_permission) {
 						return true;
 					}
 				}
 				return false;
 			}
 			elseif ($perm_event) {
 				// check permission by event name
 				list ($prefix, $event) = explode(':', $perm_event);
 				$event_handler =& $this->Application->recallObject($prefix.'_EventHandler');
 				return $event_handler->CheckPermission( new kEvent($perm_event) );
 			}
 
 			return true;
 		}
 
 		/**
 		 * Returns item's primary category (get item_id from request)
 		 *
 		 * @param string $prefix
 		 * @return int
 		 */
 		function getPrimaryCategory($prefix)
 		{
 			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
 			$id = $this->Application->GetVar($prefix.'_id');
 
 			if (!$id) {
 				return $this->Application->GetVar('m_cat_id');
 			}
 
 			$sql = 'SELECT ResourceId
 					FROM '.$table_name.'
 					WHERE '.$id_field.' = '.(int)$id;
 			$resource_id = $this->Conn->GetOne($sql);
 
 			$sql = 'SELECT CategoryId
 					FROM '.$this->Application->getUnitOption('ci', 'TableName').'
 					WHERE ItemResourceId = '.$resource_id.' AND PrimaryCat = 1';
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Returns no permission template to redirect to
 		 *
 		 * @param Array $params
 		 * @return Array
 		 */
 		function getPermissionTemplate($params)
 		{
 			$t = $this->Application->GetVar('t');
 			if ($next_t = getArrayValue($params, 'next_template')) {
 				$t = $next_t;
 			}
 
 			$redirect_params = $this->Application->HttpQuery->getRedirectParams(true);
 
 			if (array_key_exists('pass_category', $params)) {
 				$redirect_params['pass_category'] = $params['pass_cateogry'];
 			}
 
 			if (MOD_REWRITE) {
 				// TODO: $next_t variable is ignored !!! (is anyone using m_RequireLogin tag with "next_template" parameter?)
 				$redirect_params = Array (
 					'm_cat_id' => 0, // category means nothing on admin login screen
 					'next_template' => urlencode('external:' . $_SERVER['REQUEST_URI']),
 				);
 			}
 			else {
 				$redirect_params['next_template'] = $t;
 			}
 
 			if ($this->Application->isAdmin) {
 				$redirect_params['m_wid'] = ''; // remove wid, otherwise parent window may add wid to its name breaking all the frameset (for <a> targets)
 				$redirect_params['pass'] = 'm'; // don't pass any other (except "m") prefixes to admin login template
 			}
 
 			if (!$this->Application->LoggedIn()) {
 				$redirect_template = array_key_exists('login_template', $params) ? $params['login_template'] : '';
 
 				if (!$redirect_template && $this->Application->isAdmin) {
 					$redirect_template = 'login';
 				}
 			}
 			else {
 				if (array_key_exists('no_permissions_template', $params)) {
 					$redirect_template = $params['no_permissions_template'];
 				}
 				else {
 					$redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate');
 				}
 
 				if ($this->Application->isDebugMode()) {
 					$redirect_params['from_template'] = 1;
 					$redirect_params['perms'] = $params[ isset($params['permissions']) ? 'permissions' : 'perm_event' ];
 				}
 			}
 
 			if (isset($params['index_file']) && $params['index_file']) {
 				$redirect_params['index_file'] = $params['index_file'];
 			}
 
 			return Array ($redirect_template, $redirect_params);
 		}
 
 		/**
 		 * Check current user permissions based on it's group permissions in specified category (for non-system permissions) or just checks if system permission is set
 		 *
 		 * @param string $name permission name
 		 * @param int $cat_id category id, current used if not specified
 		 * @param int $type permission type {1 - system, 0 - per category}
 		 * @return int
 		 */
 		function CheckPermission($name, $type = 1, $cat_id = null)
 		{
 			$user_id = $this->Application->RecallVar('user_id');
 			return $this->CheckUserPermission($user_id, $name, $type, $cat_id);
 		}
 
 		function CheckUserPermission($user_id, $name, $type = 1, $cat_id = null)
 		{
 			if ($user_id == USER_ROOT) {
 				// "root" is allowed anywhere
 				return $name == 'SYSTEM_ACCESS.READONLY' ? 0 : 1;
 			}
 
+			if (!isset($cat_id)) {
+				$cat_id = $this->Application->GetVar('m_cat_id');
+			}
+
 			if ($type == 1) {
 				// "system" permission are always checked per "Home" category (ID = 0)
 				$cat_id = 0;
 			}
-
-			if (!isset($cat_id)) {
-				$cat_id = $this->Application->GetVar('m_cat_id');
+			elseif ("$cat_id" === "0") {
+				$cat_id = $this->Application->getBaseCategory();
 			}
 
 			// perm cache is build only based on records in db, that's why if permission is not explicitly denied, then
 			// that (perm cache creator) code thinks that it is allowed & adds corresponding record and code below will
 			// return incorrect results
 
 			if ($user_id == $this->Application->RecallVar('user_id')) {
 				$groups = explode(',', $this->Application->RecallVar('UserGroups'));
 			}
 			else { // checking not current user
 				$sql = 'SELECT GroupId
 						FROM '.TABLE_PREFIX.'UserGroup
 						WHERE (PortalUserId = '.$user_id.') AND ( (MembershipExpires IS NULL) OR ( MembershipExpires >= UNIX_TIMESTAMP() ) )';
 				$groups = $this->Conn->GetCol($sql);
 				array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup') );
 			}
 
 			$cache_key = $name . '|' . $type . '|' . $cat_id . '|' . implode(',', $groups);
 			$perm_value = $this->Application->getCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key);
 
 			if ($perm_value !== false) {
 				return $perm_value;
 			}
 
 			if (preg_match('/(.*)\.VIEW$/', $name) && ($type == 0)) {
 				// cached view permission of category: begin
 				if (strpos($cat_id, '|') !== false) {
 					$category_path = explode('|', substr($cat_id, 1, -1));
 					$cat_id = end($category_path);
 				}
 
 				$sql = 'SELECT PermissionConfigId
 						FROM '.TABLE_PREFIX.'PermissionConfig
 						WHERE PermissionName = '.$this->Conn->qstr($name);
 				$perm_id = $this->Conn->GetOne($sql);
 
 				$sql = 'SELECT PermId
 						FROM '.TABLE_PREFIX.'PermCache
 						WHERE (PermId = '.$perm_id.') AND (CategoryId = '.(int)$cat_id.')';
 
 				$view_filters = Array();
 				foreach ($groups as $group) {
 					$view_filters[] = 'FIND_IN_SET('.$group.', ACL)';
 				}
 				$sql .= ' AND ('.implode(' OR ', $view_filters).')';
 				$perm_value = $this->Conn->GetOne($sql) ? 1 : 0;
 
 				$this->Application->setCache('permissions[%CPermSerial%]:' . $cache_key, $perm_value);
 				return $perm_value;
 				// cached view permission of category: end
 			}
 
 			if (is_numeric($cat_id) && $cat_id == 0) {
 				$cat_hierarchy = Array(0);
 			}
 			else {
 				if (strpos($cat_id, '|') !== false) {
 					$cat_hierarchy = $cat_id;
 				}
 				else {
 					$sql = 'SELECT ParentPath
 							FROM '.$this->Application->getUnitOption('c', 'TableName').'
 							WHERE CategoryId = '.$cat_id;
 					$cat_hierarchy = $this->Conn->GetOne($sql);
 					if ($cat_hierarchy === false) {
 						// category was deleted, but refrence to it stays in other tables -> data integrity is broken
-						$cat_hierarchy = '|' . $this->Application->findModule('Name', 'Core', 'RootCat') . '|';
+						$cat_hierarchy = '|' . $this->Application->getBaseCategory() . '|';
 					}
 				}
 
 				$cat_hierarchy = explode('|', substr($cat_hierarchy, 1, -1));
 				$cat_hierarchy = array_reverse($cat_hierarchy);
 				array_push($cat_hierarchy, 0);
 			}
 
 			$perm_value = 0;
 			$groups = implode(',',$groups);
 			foreach ($cat_hierarchy as $category_id) {
 				$sql = 'SELECT SUM(PermissionValue)
 						FROM '.TABLE_PREFIX.'Permissions
 						WHERE Permission = "'.$name.'" AND CatId = '.$category_id.' AND GroupId IN ('.$groups.') AND Type = '.$type;
 				$res = $this->Conn->GetOne($sql);
 				if ($res !== false && !is_null($res)) {
 					$perm_value = $res ? 1 : 0;
 					break;
 				}
 			}
 
 			$this->Application->setCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key, $perm_value);
 
 			return $perm_value;
 		}
 
 		/**
 		 * Returns categories, where given permission is set to "1"
 		 *
 		 * @param string $permission_name
 		 * @return Array
 		 */
 		function getPermissionCategories($permission_name)
 		{
 			$groups = $this->Application->RecallVar('UserGroups');
 
 			// get categories, where given permission is explicitely defined
 			$sql = 'SELECT SUM(PermissionValue), CatId
 					FROM ' . TABLE_PREFIX . 'Permissions
 					WHERE Permission = "' . $permission_name . '" AND GroupId IN (' . $groups . ') AND Type = 0
 					GROUP BY CatId';
 			$permissions = $this->Conn->GetCol($sql, 'CatId');
 
 			// get all categories along with their parent path
 			$sql = 'SELECT ParentPath, CategoryId
 					FROM ' . TABLE_PREFIX . 'Category';
 			$parent_paths = $this->Conn->GetCol($sql, 'CategoryId');
 
 			foreach ($parent_paths as $category_id => $parent_path) {
 				if (array_key_exists($category_id, $permissions)) {
 					// permission for given category is set explicitly
 					continue;
 				}
 
 				$perm_value = 0;
 				$parent_path = explode('|', substr($parent_path, 1, -1));
 				$parent_path = array_reverse($parent_path);
 				array_push($parent_path, 0);
 
 				foreach ($parent_path as $parent_category_id) {
 					if (array_key_exists($parent_category_id, $permissions)) {
 						$perm_value = $permissions[$parent_category_id] ? 1 : 0;
 						break;
 					}
 				}
 
 				$permissions[$category_id] = $perm_value;
 			}
 
 			// remove categories, where given permissions is denied
 			foreach ($permissions as $category_id => $perm_value) {
 				if (!$perm_value) {
 					unset($permissions[$category_id]);
 				}
 			}
 
 			return array_keys($permissions);
 		}
 
 		/**
 		 * Allows to check MODIFY & OWNER.MODFY +/- PENDING permission combinations on item
 		 *
 		 * @param int $owner_id user_id, that is owner of the item
 		 * @param int $category_id primary category of item
 		 * @param string $prefix prefix of item
 		 * @return int {0 - no MODIFY permission, 1 - has MODIFY permission, 2 - has MODIFY.PENDING permission}
 		 */
 		function ModifyCheckPermission($owner_id, $category_id, $prefix)
 		{
 			$perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix');
 
 			$live_modify = $this->CheckPermission($perm_prefix.'.MODIFY', ptCATEGORY, $category_id);
 			if ($live_modify) {
 				return 1;
 			}
 			else if ($this->CheckPermission($perm_prefix.'.MODIFY.PENDING', ptCATEGORY, $category_id)) {
 				return 2;
 			}
 
 			if ($owner_id == $this->Application->RecallVar('user_id')) {
 				// user is item's OWNER -> check this permissions first
 				$live_modify = $this->CheckPermission($perm_prefix.'.OWNER.MODIFY', ptCATEGORY, $category_id);
 				if ($live_modify) {
 					return 1;
 				}
 				else if ($this->CheckPermission($perm_prefix.'.OWNER.MODIFY.PENDING', ptCATEGORY, $category_id)) {
 					return 2;
 				}
 			}
 
 			return 0;
 		}
 
 		/**
 		 * Allows to check DELETE & OWNER.DELETE permission combinations on item
 		 *
 		 * @param int $owner_id user_id, that is owner of the item
 		 * @param int $category_id primary category of item
 		 * @param string $prefix prefix of item
 		 * @return int {0 - no DELETE permission, 1 - has DELETE/OWNER.DELETE permission}
 		 */
 		function DeleteCheckPermission($owner_id, $category_id, $prefix)
 		{
 			$perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix');
 
 			$live_delete = $this->CheckPermission($perm_prefix.'.DELETE', ptCATEGORY, $category_id);
 			if ($live_delete) {
 				return 1;
 			}
 
 			if ($owner_id == $this->Application->RecallVar('user_id')) {
 				// user is item's OWNER -> check this permissions first
 				$live_delete = $this->CheckPermission($perm_prefix.'.OWNER.DELETE', ptCATEGORY, $category_id);
 				if ($live_delete) {
 					return 1;
 				}
 			}
 
 			return 0;
 		}
 
 		/**
 		 * Allows to check ADD +/- PENDING permission combinations on item
 		 *
 		 * @param int $category_id primary category of item
 		 * @param string $prefix prefix of item
 		 * @return int {0 - no ADD permission, 1 - has ADD permission, 2 - has ADD.PENDING permission}
 		 */
 		function AddCheckPermission($category_id, $prefix)
 		{
 			$perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix');
 
 			$live_add = $this->CheckPermission($perm_prefix.'.ADD', ptCATEGORY, $category_id);
 			if ($live_add) {
 				return 1;
 			}
 			else if ($this->CheckPermission($perm_prefix.'.ADD.PENDING', ptCATEGORY, $category_id)) {
 				return 2;
 			}
 
 			return 0;
 		}
 	}
\ No newline at end of file
Index: branches/5.1.x/core/units/content/content_eh.php
===================================================================
--- branches/5.1.x/core/units/content/content_eh.php	(revision 13986)
+++ branches/5.1.x/core/units/content/content_eh.php	(revision 13987)
@@ -1,37 +1,37 @@
 <?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 ContentEventHandler extends kDBEventHandler {
 
 		/**
 		 * Checks permissions of user
 		 *
 		 * @param kEvent $event
 		 */
 		function CheckPermission(&$event)
 		{
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$user_id = $this->Application->RecallVar('user_id');
 
 			// user can change top category
-			$top_category = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$top_category = $this->Application->getBaseCategory();
 			$perm_status = $perm_helper->CheckUserPermission($user_id, 'CATEGORY.MODIFY', 0, $top_category);
 
 			return $perm_helper->finalizePermissionCheck($event, $perm_status);
 		}
 	}
\ No newline at end of file
Index: branches/5.1.x/core/units/admin/admin_tag_processor.php
===================================================================
--- branches/5.1.x/core/units/admin/admin_tag_processor.php	(revision 13986)
+++ branches/5.1.x/core/units/admin/admin_tag_processor.php	(revision 13987)
@@ -1,1135 +1,1135 @@
 <?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 AdminTagProcessor extends kDBTagProcessor {
 
 		function SetConst($params)
 		{
 			$name = $this->SelectParam($params, 'name,const');
 			safeDefine($name, $params['value']);
 		}
 
 		/**
 		 * Allows to execute js script after the page is fully loaded
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function AfterScript($params)
 		{
 			$after_script = $this->Application->GetVar('after_script');
 			if ($after_script) {
 				return '<script type="text/javascript">'.$after_script.'</script>';
 			}
 			return '';
 		}
 
 		/**
 		 * Returns section title with #section# keyword replaced with current section
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function GetSectionTitle($params)
 		{
 			if (array_key_exists('default', $params)) {
 				return $params['default'];
 			}
 
 			return $this->Application->Phrase( replaceModuleSection($params['phrase']) );
 		}
 
 		/**
 		 * Returns section icon with #section# keyword replaced with current section
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function GetSectionIcon($params)
 		{
 			return replaceModuleSection($params['icon']);
 		}
 
 		/**
 		 * Returns version of module by name
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function ModuleVersion($params)
 		{
 			return $this->Application->findModule('Name', $params['module'], 'Version');
 		}
 
 		/**
 		 * Used in table form section drawing
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function DrawTree($params)
 		{
 			static $deep_level = 0;
 
 			// when processings, then sort children by priority (key of children array)
 			$ret = '';
 			$section_name = $params['section_name'];
 			$params['name'] = $this->SelectParam($params, 'name,render_as,block');
 			$sections_helper =& $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$params['children_count'] = isset($section_data['children']) ? count($section_data['children']) : 0;
 			$params['deep_level'] = $deep_level++;
 			$template = $section_data['url']['t'];
 			unset($section_data['url']['t']);
 			$section_data['section_url'] = $this->Application->HREF($template, '', $section_data['url']);
 			$ret .= $this->Application->ParseBlock( array_merge_recursive2($params, $section_data) );
 			if (!isset($section_data['children'])) {
 				return $ret;
 			}
 
 			ksort($section_data['children'], SORT_NUMERIC);
 			foreach ($section_data['children'] as $section_name) {
 				if (!$sections_helper->sectionVisible($section_name)) {
 					continue;
 				}
 
 				$params['section_name'] = $section_name;
 				$ret .= $this->DrawTree($params);
 				$deep_level--;
 			}
 
 
 			return $ret;
 		}
 
 
 		function SectionInfo($params)
 		{
 			$section = $params['section'];
 			if ($section == '#session#') {
 				$section = $this->Application->RecallVar('section');
 			}
 
 			$sections_helper =& $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section);
 			if (!$section_data) {
 				trigger_error('Use of undefined section "<strong>' . $section . '</strong>" in "<strong>' . __METHOD__ . '</strong>"', E_USER_ERROR);
 				return '';
 			}
 
 			if (array_key_exists('parent', $params) && $params['parent']) {
 				do {
 					$section = $section_data['parent'];
 					$section_data =& $sections_helper->getSectionData($section);
 				} while (array_key_exists('use_parent_header', $section_data) && $section_data['use_parent_header']);
 			}
 
 			$info = $params['info'];
 			switch ($info) {
 				case 'module_path':
 					if (isset($params['module']) && $params['module']) {
 						$module = $params['module'];
 					}
 					elseif (isset($section_data['icon_module'])) {
 						$module = $section_data['icon_module'];
 					}
 					else {
 						$module = '#session#';
 					}
 					$res = $this->ModulePath(array('module' => $module));
 					break;
 
 				case 'perm_section':
 					$res = $sections_helper->getPermSection($section);
 					break;
 
 				case 'label':
 					if ($section && ($section != 'in-portal:root')) {
 						// don't translate label for top section, because it's already translated
 						$no_editing = array_key_exists('no_editing', $params) ? $params['no_editing'] : false;
 
 						$res = $this->Application->Phrase($section_data['label'], !$no_editing);
 					}
 					else {
 						$res = '';
 					}
 					break;
 
 				default:
 					$res = $section_data[$info];
 					break;
 			}
 
 			if (array_key_exists('as_label', $params) && $params['as_label']) {
 				$res = $this->Application->Phrase($res);
 			}
 
 			return $res;
 		}
 
 
 		function PrintSection($params)
 		{
 			$section_name = $params['section_name'];
 			if ($section_name == '#session#') {
 				$section_name = $this->Application->RecallVar('section');
 			}
 
 			$sections_helper =& $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			if (isset($params['use_first_child']) && $params['use_first_child']) {
 				$section_name = $sections_helper->getFirstChild($section_name, true);
 			}
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$params['name'] = $this->SelectParam($params, 'name,render_as,block');
 			$params['section_name'] = $section_name;
 
 			$template = $section_data['url']['t'];
 			unset($section_data['url']['t']);
 
 			$section_data['section_url'] = $this->Application->HREF($template, '', $section_data['url']);
 			$ret = $this->Application->ParseBlock( array_merge_recursive2($params, $section_data) );
 
 			return $ret;
 		}
 
 		/**
 		 * Used in XML drawing for tree
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PrintSections($params)
 		{
 			// when processings, then sort children by priority (key of children array)
 			$ret = '';
 			$section_name = $params['section_name'];
 			if ($section_name == '#session#') {
 				$section_name = $this->Application->RecallVar('section');
 			}
 
 			$sections_helper =& $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$params['name'] = $this->SelectParam($params, 'name,render_as,block');
 			if (!isset($section_data['children'])) {
 				return '';
 			}
 
 			ksort($section_data['children'], SORT_NUMERIC);
 			foreach ($section_data['children'] as $section_name) {
 				$params['section_name'] = $section_name;
 				$section_data =& $sections_helper->getSectionData($section_name);
 
 				if (!$sections_helper->sectionVisible($section_name)) {
 					continue;
 				}
 				else {
 					$show_mode = $section_data['show_mode'];
 					$section_data['debug_only'] = ($show_mode == smDEBUG) || ($show_mode == smSUPER_ADMIN) ? 1 : 0;
 				}
 
 				if (isset($section_data['tabs_only']) && $section_data['tabs_only']) {
 					$perm_status = false;
 					$folder_label = $section_data['label'];
 					ksort($section_data['children'], SORT_NUMERIC);
 					foreach ($section_data['children'] as $priority => $section_name) {
 						// if only tabs in this section & none of them have permission, then skip section too
 
 						$section_name = $sections_helper->getPermSection($section_name);
 						$perm_status = $this->Application->CheckPermission($section_name.'.view', 1);
 						if ($perm_status) {
 							break;
 						}
 					}
 					if (!$perm_status) {
 						// no permission for all tabs -> don't display tree node either
 						continue;
 					}
 
 					$params['section_name'] = $section_name;
 					$section_data =& $sections_helper->getSectionData($section_name);
 					$section_data['label'] = $folder_label; // use folder label in tree
 					$section_data['is_tab'] = 1;
 				}
 				else  {
 					$section_name = $sections_helper->getPermSection($section_name);
 					if (!$this->Application->CheckPermission($section_name.'.view', 1)) continue;
 				}
 
 				$params['children_count'] = isset($section_data['children']) ? count($section_data['children']) : 0;
 
 				// remove template, so it doesn't appear as additional parameter in url
 				$template = $section_data['url']['t'];
 				unset($section_data['url']['t']);
 
 				$section_data['section_url'] = $this->Application->HREF($template, '', $section_data['url']);
 
 				$late_load = getArrayValue($section_data, 'late_load');
 				if ($late_load) {
 					$t = $late_load['t'];
 					unset($late_load['t']);
 					$section_data['late_load'] = $this->Application->HREF($t, '', $late_load);
 					$params['children_count'] = 99;
 				}
 				else {
 					$section_data['late_load'] = '';
 				}
 
 				// restore template
 				$section_data['url']['t'] = $template;
 
 				$ret .= $this->Application->ParseBlock( array_merge_recursive2($params, $section_data) );
 				$params['section_name'] = $section_name;
 			}
 
 			return preg_replace("/\r\n|\n/", '', $ret);
 		}
 
 		function ListSectionPermissions($params)
 		{
 			$section_name = isset($params['section_name']) ? $params['section_name'] : $this->Application->GetVar('section_name');
 			$sections_helper =& $this->Application->recallObject('SectionsHelper');
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$block_params = array_merge_recursive2($section_data, Array('name' => $params['render_as'], 'section_name' => $section_name));
 
 			$ret = '';
 			foreach ($section_data['permissions'] as $perm_name) {
 				if (preg_match('/^advanced:(.*)/', $perm_name) != $params['type']) continue;
 				$block_params['perm_name'] = $perm_name;
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 			return $ret;
 		}
 
 		function ModuleInclude($params)
 		{
 			foreach ($params as $param_name => $param_value) {
 				$params[$param_name] = replaceModuleSection($param_value);
 			}
 
 			$m =& $this->Application->recallObject('m_TagProcessor');
 			return $m->ModuleInclude($params);
 		}
 
 		function TodayDate($params)
 		{
 			return date($params['format']);
 		}
 
 		function TreeEditWarrning($params)
 		{
 			$ret = $this->Application->Phrase($params['label']);
 			$ret = str_replace(Array('&lt;', '&gt;', 'br/', 'br /', "\n", "\r"), Array('<', '>', 'br', 'br', '', ''), $ret);
 			if (getArrayValue($params, 'escape')) {
 				$ret = addslashes($ret);
 			}
 			$ret = str_replace('<br>', '\n', $ret);
 			return $ret;
 		}
 
 		/**
 		 * Draws section tabs using block name passed
 		 *
 		 * @param Array $params
 		 */
 		function ListTabs($params)
 		{
 			$sections_helper =& $this->Application->recallObject('SectionsHelper');
 			$section_data =& $sections_helper->getSectionData($params['section_name']);
 
 			$ret = '';
 			$block_params = Array('name' => $params['render_as']);
 			ksort($section_data['children'], SORT_NUMERIC);
 			foreach ($section_data['children'] as $priority => $section_name) {
 				if (!$this->Application->CheckPermission($section_name.'.view', 1)) continue;
 
 				$tab_data =& $sections_helper->getSectionData($section_name);
 				$block_params['t'] = $tab_data['url']['t'];
 				$block_params['title'] = $tab_data['label'];
 				$block_params['main_prefix'] = $section_data['SectionPrefix'];
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 
 
 			return $ret;
 		}
 
 		/**
 		 * Returns list of module item tabs that have view permission in current category
 		 *
 		 * @param Array $params
 		 */
 		function ListCatalogTabs($params)
 		{
 			$ret = '';
 			$special = isset($params['special']) ? $params['special'] : '';
 			$replace_main = isset($params['replace_m']) && $params['replace_m'];
 			$skip_prefixes = isset($params['skip_prefixes']) ? explode(',', $params['skip_prefixes']) : Array();
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				$prefix = $module_info['Var'];
 
 				if ($prefix == 'm' && $replace_main) {
 					$prefix = 'c';
 				}
 
 				if (in_array($prefix, $skip_prefixes) || !$this->Application->prefixRegistred($prefix) || !$this->Application->getUnitOption($prefix, 'CatalogItem')) {
 					continue;
 				}
 
 				$icon = $this->Application->getUnitOption($prefix, 'CatalogTabIcon');
 				if (strpos($icon, ':') !== false) {
 					list ($icon_module, $icon) = explode(':', $icon, 2);
 				}
 				else {
 					$icon_module = 'core';
 				}
 
 				$label = $this->Application->getUnitOption($prefix, $params['title_property']);
 				$block_params['title'] = $label;
 				$block_params['prefix'] = $prefix;
 				$block_params['icon_module'] = $icon_module;
 				$block_params['icon'] = $icon;
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 			return $ret;
 		}
 
 		/**
 		 * Renders inividual catalog tab based on prefix and title_property given
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function CatalogTab($params)
 		{
 			$icon = $this->Application->getUnitOption($params['prefix'], 'CatalogTabIcon');
 			if (strpos($icon, ':') !== false) {
 				list ($icon_module, $icon) = explode(':', $icon, 2);
 			}
 			else {
 				$icon_module = 'core';
 			}
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 			$block_params['icon_module'] = $icon_module;
 			$block_params['icon'] = $icon;
 			$block_params['title'] = $this->Application->getUnitOption($params['prefix'], $params['title_property']);
 
 			return $this->Application->ParseBlock($block_params);
 		}
 
 		/**
 		 * Allows to construct link for opening any type of catalog item selector
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function SelectorLink($params)
 		{
 			$mode = 'catalog';
 			if (isset($params['mode'])) { // {catalog, advanced_view}
 				$mode = $params['mode'];
 				unset($params['mode']);
 			}
 
 			$params['t'] = 'catalog/item_selector/item_selector_'.$mode;
-			$params['m_cat_id'] = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$params['m_cat_id'] = $this->Application->getBaseCategory();
 
 			$default_params = Array('no_amp' => 1, 'pass' => 'all,'.$params['prefix']);
 			unset($params['prefix']);
 
 			$pass_through = Array();
 			if (isset($params['tabs_dependant'])) { // {yes, no}
 				$pass_through['td'] = $params['tabs_dependant'];
 				unset($params['tabs_dependant']);
 			}
 
 			if (isset($params['selection_mode'])) { // {single, multi}
 				$pass_through['tm'] = $params['selection_mode'];
 				unset($params['selection_mode']);
 			}
 
 			if (isset($params['tab_prefixes'])) { // {all, none, <comma separated prefix list>}
 				$pass_through['tp'] = $params['tab_prefixes'];
 				unset($params['tab_prefixes']);
 			}
 
 			if ($pass_through) {
 				// add pass_through to selector url if any
 				$params['pass_through'] = implode(',', array_keys($pass_through));
 				$params = array_merge_recursive2($params, $pass_through);
 			}
 
 			// user can override default parameters (except pass_through of course)
 			$params = array_merge_recursive2($default_params, $params);
 
 			$main_processor =& $this->Application->recallObject('m_TagProcessor');
 	    	return $main_processor->T($params);
 		}
 
 		function TimeFrame($params)
 		{
 			$w = adodb_date('w');
 			$m = adodb_date('m');
 			$y = adodb_date('Y');
 			//FirstDayOfWeek is 0 for Sunday and 1 for Monday
 			$fdow = $this->Application->ConfigValue('FirstDayOfWeek');
 			if ($fdow && $w == 0) $w = 7;
 			$today_start = adodb_mktime(0,0,0,adodb_date('m'),adodb_date('d'),$y);
 			$first_day_of_this_week = $today_start - ($w - $fdow)*86400;
 			$first_day_of_this_month = adodb_mktime(0,0,0,$m,1,$y);
 			$this_quater = ceil($m/3);
 			$this_quater_start = adodb_mktime(0,0,0,$this_quater*3-2,1,$y);
 
 			switch ($params['type']) {
 				case 'last_week_start':
 					$timestamp = $first_day_of_this_week - 86400*7;
 					break;
 				case 'last_week_end':
 					$timestamp = $first_day_of_this_week - 1;
 					break;
 
 				case 'last_month_start':
 					$timestamp = $m == 1 ? adodb_mktime(0,0,0,12,1,$y-1) : adodb_mktime(0,0,0,$m-1,1,$y);
 					break;
 				case 'last_month_end':
 					$timestamp = $first_day_of_this_month = adodb_mktime(0,0,0,$m,1,$y) - 1;
 					break;
 
 				case 'last_quater_start':
 					$timestamp = $this_quater == 1 ? adodb_mktime(0,0,0,10,1,$y-1) : adodb_mktime(0,0,0,($this_quater-1)*3-2,1,$y);
 					break;
 				case 'last_quater_end':
 					$timestamp = $this_quater_start - 1;
 					break;
 
 				case 'last_6_months_start':
 					$timestamp = $m <= 6 ? adodb_mktime(0,0,0,$m+6,1,$y-1) : adodb_mktime(0,0,0,$m-6,1,$y);
 					break;
 
 				case 'last_year_start':
 					$timestamp = adodb_mktime(0,0,0,1,1,$y-1);
 					break;
 				case 'last_year_end':
 					$timestamp = adodb_mktime(23,59,59,12,31,$y-1);
 					break;
 			}
 
 
 			if (isset($params['format'])) {
 				$format = $params['format'];
 				if(preg_match("/_regional_(.*)/", $format, $regs))
 				{
 					$lang =& $this->Application->recallObject('lang.current');
 					$format = $lang->GetDBField($regs[1]);
 				}
 				return adodb_date($format, $timestamp);
 			}
 
 			return $timestamp;
 
 		}
 
 		/**
 		 * Redirect to cache rebuild template, when required by installator
 		 *
 		 * @param Array $params
 		 */
 		function CheckPermCache($params)
 		{
 			// we have separate session between install wizard and admin console, so store in cache
 			$global_mark = $this->Application->getDBCache('ForcePermCacheUpdate');
 			$local_mark = $this->Application->RecallVar('PermCache_UpdateRequired');
 
 			if ($global_mark || $local_mark) {
 				$this->Application->RemoveVar('PermCache_UpdateRequired');
 
 				if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) {
 					$updater =& $this->Application->recallObject('kPermCacheUpdater');
 					/* @var $updater kPermCacheUpdater */
 
 					$updater->OneStepRun();
 				}
 				else {
 					// update with progress bar
 					return true;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Checks if current protocol is SSL
 		 *
 		 * @param Array $params
 		 * @return int
 		 */
 		function IsSSL($params)
 		{
 			return (PROTOCOL == 'https://')? 1 : 0;
 		}
 
 		function PrintColumns($params)
 		{
 			$picker_helper =& $this->Application->RecallObject('ColumnPickerHelper');
 			$picker_helper->SetGridName($this->Application->GetLinkedVar('grid_name'));
 			/* @var $picker_helper kColumnPickerHelper */
 
 			$main_prefix = $this->Application->RecallVar('main_prefix');
 			$cols = $picker_helper->LoadColumns($main_prefix);
 
 			$this->Application->Phrases->AddCachedPhrase('__FREEZER__', '-------------');
 
 			$o = '';
 			if (isset($params['hidden']) && $params['hidden']) {
 				foreach ($cols['hidden_fields'] as $col) {
 					$title = $this->Application->Phrase($cols['titles'][$col]);
 					$o .= "<option value='$col'>".$title;
 				}
 			}
 			else {
 				foreach ($cols['order'] as $col) {
 					if (in_array($col, $cols['hidden_fields'])) continue;
 					$title = $this->Application->Phrase($cols['titles'][$col]);
 					$o .= "<option value='$col'>".$title;
 				}
 			}
 			return $o;
 		}
 
 		/**
 		 * Allows to set popup size (key - current template name)
 		 *
 		 * @param Array $params
 		 */
 		function SetPopupSize($params)
 		{
 			$width = $params['width'];
 			$height = $params['height'];
 
 			if ($this->Application->GetVar('ajax') == 'yes') {
 				// during AJAX request just output size
 				die($width.'x'.$height);
 			}
 
 			if (!$this->UsePopups($params)) {
 				return ;
 			}
 
 			$t = $this->Application->GetVar('t');
 			$sql = 'SELECT *
 					FROM '.TABLE_PREFIX.'PopupSizes
 					WHERE TemplateName = '.$this->Conn->qstr($t);
 			$popup_info = $this->Conn->GetRow($sql);
 			if (!$popup_info) {
 				// create new popup size record
 				$fields_hash = 	Array (
 										'TemplateName'	=>	$t,
 										'PopupWidth'	=>	$width,
 										'PopupHeight'	=>	$height,
 								);
 				$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'PopupSizes');
 			}
 			elseif ($popup_info['PopupWidth'] != $width || $popup_info['PopupHeight'] != $height) {
 				// popup found and size in tag differs from one in db -> update in db
 				$fields_hash = 	Array (
 										'PopupWidth'	=>	$width,
 										'PopupHeight'	=>	$height,
 								);
 				$this->Conn->doUpdate($fields_hash, TABLE_PREFIX.'PopupSizes', 'PopupId = '.$popup_info['PopupId']);
 			}
 		}
 
 		/**
 		 * Returns popup size (by template), if not cached, then parse template to get value
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function GetPopupSize($params)
 		{
 			$t = $this->Application->GetVar('template_name');
 			$sql = 'SELECT *
 					FROM '.TABLE_PREFIX.'PopupSizes
 					WHERE TemplateName = '.$this->Conn->qstr($t);
 			$popup_info = $this->Conn->GetRow($sql);
 			if (!$popup_info) {
 				$this->Application->InitParser();
 				$this->Application->ParseBlock(array('name' => $t)); // dies when SetPopupSize tag found & in ajax requrest
 				return '750x400'; // tag SetPopupSize not found in template -> use default size
 			}
 			return $popup_info['PopupWidth'].'x'.$popup_info['PopupHeight'];
 		}
 
 		/**
 		 * Allows to check if popups are generally enabled OR to check for "popup" or "modal" mode is enabled
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function UsePopups($params)
 		{
 			if ($this->Application->GetVar('_force_popup')) {
 				return true;
 			}
 
 			$use_popups = (int)$this->Application->ConfigValue('UsePopups');
 
 			if (array_key_exists('mode', $params)) {
 				$mode_mapping = Array ('popup' => 1, 'modal' => 2);
 				return $use_popups == $mode_mapping[ $params['mode'] ];
 			}
 
 			return $use_popups;
 		}
 
 		function UseToolbarLabels($params)
 		{
 			return (int)$this->Application->ConfigValue('UseToolbarLabels');
 		}
 
 		/**
 		 * Checks if debug mode enabled (optionally) and specified constant is on
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function ConstOn($params)
 		{
 			$constant_name = $this->SelectParam($params, 'name,const');
 			$debug_mode = isset($params['debug_mode']) && $params['debug_mode'] ? $this->Application->isDebugMode() : true;
 			return $debug_mode && constOn($constant_name);
 		}
 
 		/**
 		 * Builds link to last template in main frame of admin
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function MainFrameLink($params)
 		{
 			$persistent = isset($params['persistent']) && $params['persistent'];
 			if ($persistent && $this->Application->ConfigValue('RememberLastAdminTemplate')) {
 				// check last_template in persistent session
 				$last_template = $this->Application->RecallPersistentVar('last_template_popup');
 			}
 			else {
 				// check last_template in session
 				$last_template = $this->Application->RecallVar('last_template_popup'); // because of m_opener=s there
 			}
 
 			if (!$last_template) {
 				$params['persistent'] = 1;
 				return $persistent ? false : $this->MainFrameLink($params);
 			}
 
 			list($index_file, $env) = explode('|', $last_template);
 			$vars = $this->Application->HttpQuery->processQueryString($env, 'pass');
 			$recursion_templates = Array ('login', 'index', 'no_permission');
 
 			if (isset($vars['admin']) && $vars['admin'] == 1) {
 				// index template doesn't begin recursion on front-end (in admin frame)
 				$vars['m_theme'] = '';
 
 				if (isset($params['m_opener']) && $params['m_opener'] == 'r') {
 					// front-end link for highlighting purposes
 					$vars['t'] = 'index';
-					$vars['m_cat_id'] = $this->Application->findModule('Name', 'Core', 'RootCat');
+					$vars['m_cat_id'] = $this->Application->getBaseCategory();
 				}
 
 				unset($recursion_templates[ array_search('index', $recursion_templates)]);
 			}
 
 			if (in_array($vars['t'], $recursion_templates)) {
 				// prevents redirect recursion OR old in-portal pages
 				$params['persistent'] = 1;
 				return $persistent ? false : $this->MainFrameLink($params);
 			}
 
 			$vars = array_merge_recursive2($vars, $params);
 			$t = $vars['t'];
 			unset($vars['t'], $vars['persistent']);
 
 			// substitute language in link to current (link will work, even when language will be changed)
 			$vars['m_lang'] = $this->Application->GetVar('m_lang');
 
 			return $this->Application->HREF($t, '', $vars, $index_file);
 		}
 
 		/**
 		 * Returns menu frame width or 200 in case, when invalid width specified in config
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function MenuFrameWidth($params)
 		{
 			$width = (int)$this->Application->ConfigValue('MenuFrameWidth');
 
 			return $width > 0 ? $width : 200;
 		}
 
 		function AdminSkin($params)
 		{
 			$skin_helper =& $this->Application->recallObject('SkinHelper');
 			/* @var $skin_helper SkinHelper */
 
 			return $skin_helper->AdminSkinTag($params);
 		}
 
 		function PrintCompileErrors($params)
 		{
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 
 			$errors = $this->Application->RecallVar('compile_errors');
 			if (!$errors) {
 				return ;
 			}
 
 			$ret = '';
 			$errors = unserialize($errors);
 
 			foreach ($errors as $an_error) {
 				$block_params['file'] = str_replace(FULL_PATH, '', $an_error['file']);
 				$block_params['line'] = $an_error['line'];
 				$block_params['message'] = $an_error['msg'];
 
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 
 			$this->Application->RemoveVar('compile_errors');
 
 			return $ret;
 		}
 
 		function CompileErrorCount($params)
 		{
 			$errors = $this->Application->RecallVar('compile_errors');
 			if (!$errors) {
 				return 0;
 			}
 
 			return count( unserialize($errors) );
 		}
 
 		function ExportData($params)
 		{
 			$export_helper =& $this->Application->recallObject('CSVHelper');
 			/* @var $export_helper kCSVHelper */
 			$result = $export_helper->ExportData( $this->SelectParam($params, 'var,name,field') );
 			return ($result === false) ? '' : $result;
 		}
 
 		function ImportData($params)
 		{
 			$import_helper =& $this->Application->recallObject('CSVHelper');
 			/* @var $import_helper kCSVHelper */
 			$result = $import_helper->ImportData( $this->SelectParam($params, 'var,name,field') );
 			return ($result === false) ? '' : $result;
 		}
 
 		function PrintCSVNotImportedLines($params)
 		{
 			$import_helper =& $this->Application->recallObject('CSVHelper');
 			/* @var $import_helper kCSVHelper */
 			return  $import_helper->GetNotImportedLines();
 		}
 
 		/**
 		 * Returns input field name to
 		 * be placed on form (for correct
 		 * event processing)
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access public
 		 */
 		function InputName($params)
 		{
 			list($id, $field) = $this->prepareInputName($params);
 
 			$ret = $this->getPrefixSpecial().'[0]['.$field.']'; // 0 always, as has no idfield
 			if( getArrayValue($params, 'as_preg') ) $ret = preg_quote($ret, '/');
 			return $ret;
 		}
 
 		/**
 		 * Returns list of all backup file dates formatted
 		 * in passed block
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access public
 		 */
 		function PrintBackupDates($params)
 		{
 	 		$datearray = $this->getDirList($this->Application->ConfigValue('Backup_Path'));
 	 		$ret = '';
 	        foreach($datearray as $key => $value)
 	        {
 	        	$params['backuptimestamp'] = $value['filedate'];
 	        	$params['backuptime'] = date('F j, Y, g:i a', $value['filedate']);
 	        	$params['backupsize'] = round($value['filesize']/1024/1024, 2); // MBytes
 	        	$ret .= $this->Application->ParseBlock($params);
 	        }
 	        return $ret;
 		}
 
 		function getDirList ($dirName)
 		{
 		    $file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->CheckFolder($dirName);
 
 			$fileinfo = array();
 		    $d = dir($dirName);
 
 		    while($entry = $d->read())
 		    {
 		        if ($entry != "." && $entry != "..")
 		        {
 		            if (!is_dir($dirName."/".$entry) && strpos($entry, 'dump') !== false)
 		            {
 		                $fileinfo[]= Array('filedate' => $this->chopchop($entry),
 		                					'filesize' => filesize($dirName. '/'. $entry)
 		                					);
 		            }
 		        }
 		    }
 		    $d->close();
 		    rsort($fileinfo);
 
 		    return $fileinfo;
 
 		}
 
 		function chopchop ($filename)
 		{
 			 $p = pathinfo($filename);
 			 $ext = '.'.$p['extension'];
 		     $filename;
 		     $filename = str_replace('dump', '',$filename);
 		     $filename = str_replace($ext, '', $filename);
 
 		     return $filename;
 		}
 
 		/**
 		 * Returns phpinfo() output
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PrintPHPinfo($params)
 		{
 			ob_start();
 			phpinfo();
 
 			return ob_get_clean();
 		}
 
 		function PrintSqlCols($params)
 		{
 			$a_data = unserialize($this->Application->GetVar('sql_rows'));
 			$ret = '';
 			$block = $params['render_as'];
 			foreach ($a_data AS $a_row)
 			{
 				foreach ($a_row AS $col => $value)
 				{
 					$ret .= $this->Application->ParseBlock(Array('name'=>$block, 'value'=>$col));
 				}
 				break;
 			}
 			return $ret;
 		}
 
 		function PrintSqlRows($params)
 		{
 			$a_data = unserialize($this->Application->GetVar('sql_rows'));
 			$ret = '';
 			$block = $params['render_as'];
 			foreach ($a_data AS $a_row)
 			{
 				$cells = '';
 				foreach ($a_row AS $col => $value)
 				{
 					$cells .= '<td>'.$value.'</td>';
 				}
 				$ret .= $this->Application->ParseBlock(Array('name'=>$block, 'cells'=>$cells));
 			}
 			return $ret;
 		}
 
 		/**
 		 * Prints available and enabled import sources using given block
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PrintImportSources($params)
  		{
  			$sql = 'SELECT *
  					FROM ' . TABLE_PREFIX . 'ImportScripts
  					WHERE (Status = ' . STATUS_ACTIVE . ') AND (Type = "CSV")';
  			$import_sources = $this->Conn->Query($sql);
 
  			$block_params = $this->prepareTagParams($params);
  			$block_params['name'] = $params['render_as'];
 
  			$ret = '';
  			foreach ($import_sources as $import_source) {
  				$block_params['script_id'] = $import_source['ImportId'];
  				$block_params['script_module'] = mb_strtolower($import_source['Module']);
  				$block_params['script_name'] = $import_source['Name'];
  				$block_params['script_prefix'] = $import_source['Prefix'];
  				$block_params['module_path'] = $this->Application->findModule('Name', $import_source['Module'], 'Path');
 
  				$ret .= $this->Application->ParseBlock($block_params);
  			}
 
  			return $ret;
  		}
 
  		/**
  		 * Checks, that new window should be opened in "incs/close_popup" template instead of refreshing parent window
  		 *
  		 * @param Array $params
  		 * @return bool
  		 */
  		function OpenNewWindow($params)
  		{
 			if (!$this->UsePopups($params)) {
 				return false;
 			}
 
  			$diff = array_key_exists('diff', $params) ? $params['diff'] : 0;
  			$wid = $this->Application->GetVar('m_wid');
 
 			$stack_name = rtrim('opener_stack_' . $wid, '_');
 			$opener_stack = $this->Application->RecallVar($stack_name);
 			$opener_stack = $opener_stack ? unserialize($opener_stack) : Array ();
 
 			return count($opener_stack) >= 2 - $diff;
  		}
 
 		/**
 		 * Allows to dynamically change current language in template
 		 *
 		 * @param Array $params
 		 */
 		function SetLanguage($params)
 		{
 			$this->Application->SetVar('m_lang', $params['language_id']);
 			$this->Application->Phrases->Init('');
 
 			$this->Application->Phrases->LanguageId = $params['language_id'];
 			$this->Application->Phrases->LoadPhrases( $this->Application->Caches['PhraseList'] );
 		}
 
 		/**
 		 * Performs HTTP Authentification for administrative console
 		 *
 		 * @param Array $params
 		 */
 		function HTTPAuth($params)
 		{
 			if (!$this->Application->ConfigValue('UseHTTPAuth')) {
 				// http authentification not required
 				return true;
 			}
 
 			$super_admin_ips = defined('SA_IP') ? SA_IP : false;
 			$auth_bypass_ips = $this->Application->ConfigValue('HTTPAuthBypassIPs');
 
 			if (($auth_bypass_ips && ipMatch($auth_bypass_ips)) || ($super_admin_ips && ipMatch($super_admin_ips))) {
 				// user ip is in ip bypass list
 				return true;
 			}
 
 			if (!array_key_exists('PHP_AUTH_USER', $_SERVER)) {
 				// ask user to authentificate, when not authentificated before
 				return $this->_httpAuthentificate();
 			}
 			else {
 				// validate user credentials (browsers remembers user/password
 				// and sends them each time page is visited, so no need to save
 				// authentification result in session)
 				if ($this->Application->ConfigValue('HTTPAuthUsername') != $_SERVER['PHP_AUTH_USER']) {
 					// incorrect username
 					return $this->_httpAuthentificate();
 				}
 
 				$password_formatter =& $this->Application->recallObject('kPasswordFormatter');
 				/* @var $password_formatter kPasswordFormatter */
 
 				$password = $password_formatter->EncryptPassword($_SERVER['PHP_AUTH_PW'], 'b38');
 
 				if ($this->Application->ConfigValue('HTTPAuthPassword') != $password) {
 					// incorrect password
 					return $this->_httpAuthentificate();
 				}
 			}
 
 			return true;
 		}
 
 		/**
 		 * Ask user to authentificate
 		 *
 		 * @return false
 		 */
 		function _httpAuthentificate()
 		{
 			$realm = strip_tags( $this->Application->ConfigValue('Site_Name') );
 			header('WWW-Authenticate: Basic realm="' . $realm . '"');
 			header('HTTP/1.0 401 Unauthorized');
 
 			return false;
 		}
 
 		/**
 		 * Checks, that we are using memory cache
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function MemoryCacheEnabled($params)
 		{
 			return $this->Application->isCachingType(CACHING_TYPE_MEMORY);
 		}
 	}
\ No newline at end of file
Index: branches/5.1.x/core/install/install_toolkit.php
===================================================================
--- branches/5.1.x/core/install/install_toolkit.php	(revision 13986)
+++ branches/5.1.x/core/install/install_toolkit.php	(revision 13987)
@@ -1,1037 +1,1037 @@
 <?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!');
 
 	/**
 	 * Upgrade sqls are located using this mask
 	 *
 	 */
 	define('UPGRADES_FILE', FULL_PATH.'/%sinstall/upgrades.%s');
 
 	/**
 	 * Prerequisit check classes are located using this mask
 	 *
 	 */
 	define('PREREQUISITE_FILE', FULL_PATH.'/%sinstall/prerequisites.php');
 
 	/**
 	 * Format of version identificator in upgrade files (normal, beta, release candidate)
 	 *
 	 */
 	define('VERSION_MARK', '# ===== v ([\d]+\.[\d]+\.[\d]+|[\d]+\.[\d]+\.[\d]+-B[\d]+|[\d]+\.[\d]+\.[\d]+-RC[\d]+) =====');
 
 	if (!defined('GET_LICENSE_URL')) {
 		/**
 		 * Url used for retrieving user licenses from Intechnic licensing server
 		 *
 		 */
 		define('GET_LICENSE_URL', 'http://www.intechnic.com/myaccount/license.php');
 	}
 
 	/**
 	 * Misc functions, that are required during installation, when
 	 *
 	 */
 	class kInstallToolkit {
 
 		/**
 		 * Reference to kApplication class object
 		 *
 		 * @var kApplication
 		 */
 		var $Application = null;
 
 		/**
 		 * Connection to database
 		 *
 		 * @var kDBConnection
 		 */
 		var $Conn = null;
 
 		/**
 		 * Path to config.php
 		 *
 		 * @var string
 		 */
 		var $INIFile = '';
 
 		/**
 		 * Parsed data from config.php
 		 *
 		 * @var Array
 		 */
 		var $systemConfig = Array ();
 
 		/**
 		 * Path, used by system to store data on filesystem
 		 *
 		 * @var string
 		 */
 		var $defaultWritablePath = '';
 
 		/**
 		 * Installator instance
 		 *
 		 * @var kInstallator
 		 */
 		var $_installator = null;
 
 		function kInstallToolkit()
 		{
 			$this->defaultWritablePath = DIRECTORY_SEPARATOR . 'system';
 
 			if (class_exists('kApplication')) {
 				// auto-setup in case of separate module install
 				$this->Application =& kApplication::Instance();
 				$this->Conn =& $this->Application->GetADODBConnection();
 			}
 
 			$this->INIFile = FULL_PATH . $this->defaultWritablePath . DIRECTORY_SEPARATOR . 'config.php';
 
 			$this->systemConfig = $this->ParseConfig(true);
 		}
 
 		/**
 		 * Sets installator
 		 *
 		 * @param kInstallator $instance
 		 */
 		function setInstallator(&$instance)
 		{
 			$this->_installator =& $instance;
 		}
 
 		/**
 		 * Checks prerequisities before module install or upgrade
 		 *
 		 * @param string $module_path
 		 * @param string $versions
 		 * @param string $mode upgrade mode = {install, standalone, upgrade}
 		 */
 		function CheckPrerequisites($module_path, $versions, $mode)
 		{
 			static $prerequisit_classes = Array ();
 
 			$prerequisites_file = sprintf(PREREQUISITE_FILE, $module_path);
 			if (!file_exists($prerequisites_file) || !$versions) {
 				return Array ();
 			}
 
 			if (!isset($prerequisit_classes[$module_path])) {
 				// save class name, because 2nd time
 				// (in after call $prerequisite_class variable will not be present)
 				include_once $prerequisites_file;
 				$prerequisit_classes[$module_path] = $prerequisite_class;
 			}
 
 			$prerequisite_object = new $prerequisit_classes[$module_path]();
 			if (method_exists($prerequisite_object, 'setToolkit')) {
 				$prerequisite_object->setToolkit($this);
 			}
 
 			// some errors possible
 			return $prerequisite_object->CheckPrerequisites($versions, $mode);
 		}
 
 		/**
 		 * Processes one license, received from server
 		 *
 		 * @param string $file_data
 		 */
 		function processLicense($file_data)
 		{
 			$modules_helper =& $this->Application->recallObject('ModulesHelper');
 			/* @var $modules_helper kModulesHelper */
 
 			$file_data = explode('Code==:', $file_data);
 			$file_data[0] = str_replace('In-Portal License File - do not edit!' . "\n", '', $file_data[0]);
 			$file_data = array_map('trim', $file_data);
 
 			if ($modules_helper->verifyLicense($file_data[0])) {
 				$this->setSystemConfig('Intechnic', 'License', $file_data[0]);
 				if (array_key_exists(1, $file_data)) {
 					$this->setSystemConfig('Intechnic', 'LicenseCode', $file_data[1]);
 				}
 				else {
 					$this->setSystemConfig('Intechnic', 'LicenseCode');
 				}
 				$this->SaveConfig();
 			}
 			else {
 				// invalid license received from licensing server
 				$this->_installator->errorMessage = 'Invalid License File';
 			}
 		}
 
 		/**
 		 * Saves given configuration values to database
 		 *
 		 * @param Array $config
 		 */
 		function saveConfigValues($config)
 		{
 			foreach ($config as $config_var => $value) {
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'ConfigurationValues
 						SET VariableValue = ' . $this->Conn->qstr($value) . '
 						WHERE VariableName = ' . $this->Conn->qstr($config_var);
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Sets module version to passed
 		 *
 		 * @param string $module_name
 		 * @param string $module_path
 		 * @param string $version
 		 */
 		function SetModuleVersion($module_name, $module_path = false, $version = false)
 		{
 			if ($version === false) {
 				if (!$module_path) {
 					trigger_error('Module path must be given to "SetModuleVersion" method to auto-detect version', E_USER_ERROR);
 					return ;
 				}
 
 				$version = $this->GetMaxModuleVersion($module_path);
 			}
 
 			// get table prefix from config, because application may not be available here
 			$table_prefix = $this->getSystemConfig('Database', 'TablePrefix');
 
 			if ($module_name == 'kernel') {
 				$module_name = 'in-portal';
 			}
 
 			// don't use "adodb_mktime" here, because it's not yet included
 			$sql = 'UPDATE ' . $table_prefix . 'Modules
 					SET Version = "' . $version . '", BuildDate = ' . time() . '
 					WHERE LOWER(Name) = "' . strtolower($module_name) . '"';
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Sets module root category to passed
 		 *
 		 * @param string $module_name
 		 * @param string $category_id
 		 */
 		function SetModuleRootCategory($module_name, $category_id = 0)
 		{
 			// get table prefix from config, because application may not be available here
 			$table_prefix = $this->getSystemConfig('Database', 'TablePrefix');
 
 			if ($module_name == 'kernel') {
 				$module_name = 'in-portal';
 			}
 
 			$sql = 'UPDATE ' . $table_prefix . 'Modules
 					SET RootCat = ' . $category_id . '
 					WHERE LOWER(Name) = "' . strtolower($module_name) . '"';
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Returns maximal version of given module by scanning it's upgrade scripts
 		 *
 		 * @param string $module_name
 		 * @return string
 		 */
 		function GetMaxModuleVersion($module_path)
 		{
 			$module_path = rtrim(mb_strtolower($module_path), '/');
 			$upgrades_file = sprintf(UPGRADES_FILE, $module_path . '/', 'sql');
 
 			if (!file_exists($upgrades_file)) {
 				// no upgrade file
 				return '5.0.0';
 			}
 
 			$sqls = file_get_contents($upgrades_file);
 			$versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs);
 			if (!$versions_found) {
 				// upgrades file doesn't contain version definitions
 				return '5.0.0';
 			}
 
 			return end($regs[1]);
 		}
 
 		/**
 		 * Runs SQLs from file
 		 *
 		 * @param string $filename
 		 * @param mixed $replace_from
 		 * @param mixed $replace_to
 		 */
 		function RunSQL($filename, $replace_from = null, $replace_to = null)
 		{
 			if (!file_exists(FULL_PATH.$filename)) {
 				return ;
 			}
 
 			$sqls = file_get_contents(FULL_PATH.$filename);
 			if (!$this->RunSQLText($sqls, $replace_from, $replace_to)) {
 				if (is_object($this->_installator)) {
 					$this->_installator->Done();
 				}
 				else {
 					if (isset($this->Application)) {
 						$this->Application->Done();
 					}
 
 					exit;
 				}
 			}
 		}
 
 		/**
 		 * Runs SQLs from string
 		 *
 		 * @param string $sqls
 		 * @param mixed $replace_from
 		 * @param mixed $replace_to
 		 */
 		function RunSQLText(&$sqls, $replace_from = null, $replace_to = null, $start_from = 0)
 		{
 			$table_prefix = $this->getSystemConfig('Database', 'TablePrefix');
 
 			// add prefix to all tables
 			if (strlen($table_prefix) > 0) {
 				$replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO ');
 				foreach ($replacements as $replacement) {
 					$sqls = str_replace($replacement, $replacement . $table_prefix, $sqls);
 				}
 			}
 
 			$sqls = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sqls);
 			$sqls = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sqls);
 			$sqls = str_replace('<%TABLE_PREFIX%>', $table_prefix, $sqls);
 
 			$primary_language = is_object($this->Application) ? $this->Application->GetDefaultLanguageId() : 1;
 			$sqls = str_replace('<%PRIMARY_LANGUAGE%>', $primary_language, $sqls);
 
 			if (isset($replace_from) && isset($replace_to)) {
 				// replace something additionally, e.g. module root category
 				$sqls = str_replace($replace_from, $replace_to, $sqls);
 			}
 
 			$sqls = str_replace("\r\n", "\n", $sqls);  // convert to linux line endings
 			$no_comment_sqls = preg_replace("/#\s([^;]*?)\n/is", '', $sqls); // remove all comments "#" on new lines
 
 			if ($no_comment_sqls === null) {
 				// "ini.pcre.backtrack-limit" reached and error happened
 				$sqls = explode(";\n", $sqls . "\n"); // ensures that last sql won't have ";" in it
 				$sqls = array_map('trim', $sqls);
 
 				// remove all comments "#" on new lines (takes about 2 seconds for 53000 sqls)
 				$sqls = preg_replace("/#\s([^;]*?)/", '', $sqls);
 			}
 			else {
 				$sqls = explode(";\n", $no_comment_sqls . "\n"); // ensures that last sql won't have ";" in it
 				$sqls = array_map('trim', $sqls);
 			}
 
 			$sql_count = count($sqls);
 			$db_collation = $this->getSystemConfig('Database', 'DBCollation');
 
 			for ($i = $start_from; $i < $sql_count; $i++) {
 				$sql = $sqls[$i];
 				if (!$sql || (substr($sql, 0, 1) == '#')) {
 					continue; // usually last line
 				}
 
 				if (substr($sql, 0, 13) == 'CREATE TABLE ' && $db_collation) {
 					// it is CREATE TABLE statement -> add collation
 					$sql .= ' COLLATE \'' . $db_collation . '\'';
 				}
 
 				$this->Conn->Query($sql);
 				if ($this->Conn->getErrorCode() != 0) {
 					if (is_object($this->_installator)) {
 		  				$this->_installator->errorMessage = 'Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg().'<br /><br />Last Database Query:<br /><textarea cols="70" rows="10" readonly>'.htmlspecialchars($sql).'</textarea>';
 		  				$this->_installator->LastQueryNum = $i + 1;
 					}
 	  				return false;
 	    		}
 			}
 			return true;
 		}
 
 		/**
 		 * Performs clean language import from given xml file
 		 *
 		 * @param string $lang_file
 		 * @param bool $upgrade
 		 * @todo Import for "core/install/english.lang" (322KB) takes 18 seconds to work on Windows
 		 */
 		function ImportLanguage($lang_file, $upgrade = false)
 		{
 			$lang_file = FULL_PATH.$lang_file.'.lang';
 			if (!file_exists($lang_file)) {
 				return ;
 			}
 
 			$language_import_helper =& $this->Application->recallObject('LanguageImportHelper');
 			/* @var $language_import_helper LanguageImportHelper */
 
 			$language_import_helper->performImport($lang_file, '|0|1|2|', '', $upgrade ? LANG_SKIP_EXISTING : LANG_OVERWRITE_EXISTING);
 		}
 
 		/**
 		 * Converts module version in format X.Y.Z[-BN/-RCM] to signle integer
 		 *
 		 * @param string $version
 		 * @return int
 		 */
 		function ConvertModuleVersion($version)
 		{
 			if (preg_match('/(.*)-(B|RC)([\d]+)/', $version, $regs)) {
 				// -B<M> or RC-<N>
 				$parts = explode('.', $regs[1]);
 
 				$parts[] = $regs[2] == 'B' ? 1 : 2; // B reliases goes before RC releases
 				$parts[] = $regs[3];
 			}
 			else {
 				// releases without B/RC marks go after any B/RC releases
 				$parts = explode('.', $version . '.3.100');
 			}
 
 			$bin = '';
 
 			foreach ($parts as $part_index => $part) {
 				if ($part_index == 3) {
 					// version type only can be 1/2/3 (11 in binary form), so don't use padding at all
 					$pad_count = 2;
 				}
 				else {
 					$pad_count = 8;
 				}
 
 				$bin .= str_pad(decbin($part), $pad_count, '0', STR_PAD_LEFT);
 			}
 
 			return bindec($bin);
 		}
 
 		/**
 		 * Returns themes, found in system
 		 *
 		 * @param bool $rebuild
 		 * @return int
 		 */
 		function getThemes($rebuild = false)
 		{
 			if ($rebuild) {
 				$this->rebuildThemes();
 			}
 
 			$id_field = $this->Application->getUnitOption('theme', 'IDField');
 			$table_name = $this->Application->getUnitOption('theme', 'TableName');
 
 			$sql = 'SELECT Name, ' . $id_field . '
 					FROM ' . $table_name . '
 					ORDER BY Name ASC';
 			return $this->Conn->GetCol($sql, $id_field);
 		}
 
 		function ParseConfig($parse_section = false)
 		{
 			if (!file_exists($this->INIFile)) {
 				return Array ();
 			}
 
 			if (file_exists($this->INIFile) && !is_readable($this->INIFile)) {
 				die('Could Not Open Ini File');
 			}
 
 			$contents = file($this->INIFile);
 
 			if ($contents && $contents[0] == '<' . '?' . 'php die() ?' . ">\n") {
 				// format of "config.php" file before 5.1.0 version
 				array_shift($contents);
 
 				return $this->parseIniString(implode('', $contents), $parse_section);
 			}
 
 			$_CONFIG = Array ();
 	    	require($this->INIFile);
 
 	    	if ($parse_section) {
 	    		return $_CONFIG;
 	    	}
 
 	    	$ret = Array ();
 
 	    	foreach ($_CONFIG as $section => $section_variables) {
 	    		$ret = array_merge($ret, $section_variables);
 	    	}
 
 	    	return $ret;
 		}
 
 		/**
 		 * Equivalent for "parse_ini_string" function available since PHP 5.3.0
 		 *
 		 * @param string $ini
 		 * @param bool $process_sections
 		 * @param int $scanner_mode
 		 * @return Array
 		 */
 		function parseIniString($ini, $process_sections = false, $scanner_mode = null)
 		{
 			# Generate a temporary file.
 			$tempname = tempnam('/tmp', 'ini');
 			$fp = fopen($tempname, 'w');
 			fwrite($fp, $ini);
 			$ini = parse_ini_file($tempname, !empty($process_sections));
 			fclose($fp);
 			@unlink($tempname);
 
 			return $ini;
 		}
 
 		function SaveConfig($silent = false)
 		{
 			if (!is_writable($this->INIFile) && !is_writable(dirname($this->INIFile))) {
 				trigger_error('Cannot write to "' . $this->INIFile . '" file.', $silent ? E_USER_WARNING : E_USER_ERROR);
 				return ;
 			}
 
 			$fp = fopen($this->INIFile, 'w');
 			fwrite($fp, '<' . '?' . 'php' . "\n\n");
 
 			foreach ($this->systemConfig as $section_name => $section_data) {
 				foreach ($section_data as $key => $value) {
 					fwrite($fp, '$_CONFIG[\'' . $section_name . '\'][\'' . $key . '\'] = \'' . addslashes($value) . '\';' . "\n");
 				}
 
 				fwrite($fp, "\n");
 			}
 
 			fclose($fp);
 		}
 
 		/**
 		 * Sets value to system config (yet SaveConfig must be called to write it to file)
 		 *
 		 * @param string $section
 		 * @param string $key
 		 * @param string $value
 		 */
 		function setSystemConfig($section, $key, $value = null)
 		{
 			if (isset($value)) {
 				if (!array_key_exists($section, $this->systemConfig)) {
 					// create section, when missing
 					$this->systemConfig[$section] = Array ();
 				}
 
 				// create key in section
 				$this->systemConfig[$section][$key] = $value;
 				return ;
 			}
 
 			unset($this->systemConfig[$section][$key]);
 		}
 
 		/**
 		 * Returns information from system config
 		 *
 		 * @return string
 		 */
 		function getSystemConfig($section, $key)
 		{
 			if (!array_key_exists($section, $this->systemConfig)) {
 				return false;
 			}
 
 			if (!array_key_exists($key, $this->systemConfig[$section])) {
 				return false;
 			}
 
 			return $this->systemConfig[$section][$key] ? $this->systemConfig[$section][$key] : false;
 		}
 
 		/**
 		 * Checks if system config is present and is not empty
 		 *
 		 * @return bool
 		 */
 		function systemConfigFound()
 		{
 			return file_exists($this->INIFile) && $this->systemConfig;
 		}
 
 		/**
 		 * Checks if given section is present in config
 		 *
 		 * @param string $section
 		 * @return bool
 		 */
 		function sectionFound($section)
 		{
 			return array_key_exists($section, $this->systemConfig);
 		}
 
 		/**
 		 * Returns formatted module name based on it's root folder
 		 *
 		 * @param string $module_folder
 		 * @return string
 		 */
 		function getModuleName($module_folder)
 		{
 			return implode('-', array_map('ucfirst', explode('-', $module_folder)));
 		}
 
 		/**
 		 * Returns information about module (based on "install/module_info.xml" file)
 		 *
 		 * @param string $module_name
 		 * @return Array
 		 */
 		function getModuleInfo($module_name)
 		{
 			if ($module_name == 'core') {
 				$info_file = FULL_PATH . '/' . $module_name . '/install/module_info.xml';
 			}
 			else {
 				$info_file = MODULES_PATH . '/' . $module_name . '/install/module_info.xml';
 			}
 
 			if (!file_exists($info_file)) {
 				return Array ();
 			}
 
 			$xml_helper =& $this->Application->recallObject('kXMLHelper');
 			/* @var $xml_helper kXMLHelper */
 
 			$root_node =& $xml_helper->Parse( file_get_contents($info_file) );
 
 			if (!is_object($root_node) || !preg_match('/^kxmlnode/i', get_class($root_node)) || ($root_node->Name == 'ERROR')) {
 				// non-valid xml file
 				return Array ();
 			}
 
 			$ret = Array ();
 			$current_node =& $root_node->firstChild;
 
 			do {
 				$ret[ strtolower($current_node->Name) ] = trim($current_node->Data);
 			} while (($current_node =& $current_node->NextSibling()));
 
 			return $ret;
 		}
 
 		/**
 		 * Returns nice module string to be used on install/upgrade screens
 		 *
 		 * @param string $module_name
 		 * @param string $version_string
 		 * @return string
 		 */
 		function getModuleString($module_name, $version_string)
 		{
 			// image (if exists) <description> (<name> <version>)
 
 			$ret = Array ();
 			$module_info = $this->getModuleInfo($module_name);
 
 			if (array_key_exists('name', $module_info) && $module_info['name']) {
 				$module_name = $module_info['name'];
 			}
 			else {
 				$module_name = $this->getModuleName($module_name);
 			}
 
 			if (array_key_exists('image', $module_info) && $module_info['image']) {
 				$image_src = $module_info['image'];
 
 				if (!preg_match('/^(http|https):\/\//', $image_src)) {
 					// local image -> make absolute url
 					$image_src = $this->Application->BaseURL() . $image_src;
 				}
 
 				$ret[] = '<img src="' . $image_src . '" alt="' . htmlspecialchars($module_name) . '" title="' . htmlspecialchars($module_name) . '" style="vertical-align:middle; margin: 3px 0 3px 5px"/>';
 			}
 
 			if (array_key_exists('description', $module_info) && $module_info['description']) {
 				$ret[] = $module_info['description'];
 			}
 			else {
 				$ret[] = $module_name;
 			}
 
 			$ret[] = '(' . $module_name . ' ' . $version_string . ')';
 
 			return implode(' ', $ret);
 		}
 
 		/**
 		 * Creates module root category in "Home" category using given data and returns it
 		 *
 		 * @param string $name
 		 * @param string $description
 		 * @param string $category_template
 		 * @param string $category_icon
 		 * @return kDBItem
 		 */
 		function &createModuleCategory($name, $description, $category_template = null, $category_icon = null)
 		{
 			static $fields = null;
 
 			if (!isset($fields)) {
 				$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 
 				$fields['name'] = $ml_formatter->LangFieldName('Name');
 				$fields['description'] = $ml_formatter->LangFieldName('Description');
 			}
 
 			$category =& $this->Application->recallObject('c', null, Array ('skip_autoload' => true));
 			/* @var $category kDBItem */
 
 			$category_fields = Array (
 				$fields['name'] => $name, 'Filename' => $name, 'AutomaticFilename' => 1,
 				$fields['description'] => $description, 'Status' => STATUS_ACTIVE, 'Priority' => -9999,
 
 				// prevents empty link to module category on spearate module install
 				'NamedParentPath' => 'Content/' . $name,
 			);
 
-			$category_fields['ParentId'] = $this->Application->findModule('Name', 'Core', 'RootCat');
+			$category_fields['ParentId'] = $this->Application->getBaseCategory();
 
 			if (isset($category_template)) {
 				$category_fields['Template'] = $category_template;
 				$category_fields['CachedTemplate'] = $category_template;
 			}
 
 			if (isset($category_icon)) {
 				$category_fields['UseMenuIconUrl'] = 1;
 				$category_fields['MenuIconUrl'] = $category_icon;
 			}
 
 			$category->Clear();
 			$category->SetDBFieldsFromHash($category_fields);
 
 			$category->Create();
 
 			$priority_helper =& $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			$event = new kEvent('c:OnListBuild');
 
 			// ensure, that newly created category has proper value in Priority field
 			$priority_helper->recalculatePriorities($event, 'ParentId = ' . $category_fields['ParentId']);
 
 			// update Priority field in object, becase "CategoriesItem::Update" method will be called
 			// from "kInstallToolkit::setModuleItemTemplate" and otherwise will set 0 to Priority field
 			$sql = 'SELECT Priority
 					FROM ' . $category->TableName . '
 					WHERE ' . $category->IDField . ' = ' . $category->GetID();
 			$category->SetDBField('Priority', $this->Conn->GetOne($sql));
 
 			return $category;
 		}
 
 		/**
 		 * Sets category item template into custom field for given prefix
 		 *
 		 * @param kDBItem $category
 		 * @param string $prefix
 		 * @param string $item_template
 		 */
 		function setModuleItemTemplate(&$category, $prefix, $item_template)
 		{
 			$this->Application->removeObject('c-cdata');
 
 			// recreate all fields, because custom fields are added during install script
 			$category->defineFields();
 			$category->prepareConfigOptions(); // creates ml fields
 
 			$category->SetDBField('cust_' . $prefix  .'_ItemTemplate', $item_template);
 			$category->Update();
 		}
 
 		/**
 		 * Link custom field records with search config records + create custom field columns
 		 *
 		 * @param string $module_folder
 		 * @param int $item_type
 		 */
 		function linkCustomFields($module_folder, $prefix, $item_type)
 		{
 			$module_folder = strtolower($module_folder);
 			$module_name = $module_folder;
 
 			if ($module_folder == 'kernel') {
 				$module_name = 'in-portal';
 				$module_folder = 'core';
 			}
 
 			$db =& $this->Application->GetADODBConnection();
 
 			$sql = 'SELECT FieldName, CustomFieldId
 					FROM ' . TABLE_PREFIX . 'CustomField
 					WHERE Type = ' . $item_type . ' AND IsSystem = 0'; // config is not read here yet :( $this->Application->getUnitOption('p', 'ItemType');
 			$custom_fields = $db->GetCol($sql, 'CustomFieldId');
 
 			foreach ($custom_fields as $cf_id => $cf_name) {
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'SearchConfig
 						SET CustomFieldId = ' . $cf_id . '
 						WHERE (TableName = "CustomField") AND (LOWER(ModuleName) = "' . $module_name . '") AND (FieldName = ' . $db->qstr($cf_name) . ')';
 				$db->Query($sql);
 			}
 
 			$this->Application->refreshModuleInfo(); // this module configs are now processed
 
 			// because of configs was read only from installed before modules (in-portal), then reread configs
 			$unit_config_reader =& $this->Application->recallObject('kUnitConfigReader');
 			/* @var $unit_config_reader kUnitConfigReader */
 
 			$unit_config_reader->scanModules(MODULES_PATH . DIRECTORY_SEPARATOR . $module_folder);
 
 			// create correct columns in CustomData table
 			$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
 			$ml_helper->createFields($prefix . '-cdata', true);
 		}
 
 		/**
 		 * Deletes cache, useful after separate module install and installator last step
 		 *
 		 */
 		function deleteCache($refresh_permissions = false)
 		{
 			$this->Application->HandleEvent($event, 'adm:OnResetConfigsCache');
 			$this->Application->HandleEvent($event, 'adm:OnResetSections');
 			$this->Application->HandleEvent($event, 'c:OnResetCMSMenuCache');
 
 			$this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls');
 
 			if ($refresh_permissions) {
 				if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) {
 					// refresh permission without progress bar
 					$updater =& $this->Application->recallObject('kPermCacheUpdater');
 					/* @var $updater kPermCacheUpdater */
 
 					$updater->OneStepRun();
 				}
 				else {
 					// refresh permissions with ajax progress bar (when available)
 					$this->Application->setDBCache('ForcePermCacheUpdate', 1);
 				}
 			}
 		}
 
 		/**
 		 * Deletes all temp tables (from active sessions too)
 		 *
 		 */
 		function deleteEditTables()
 		{
 			$table_prefix = $this->getSystemConfig('Database', 'TablePrefix');
 
 			$tables = $this->Conn->GetCol('SHOW TABLES');
 			$mask_edit_table = '/' . $table_prefix . 'ses_(.*)_edit_(.*)/';
 			$mask_search_table = '/' . $table_prefix . 'ses_(.*?)_(.*)/';
 
 			foreach ($tables as $table) {
 				if ( preg_match($mask_edit_table, $table, $rets) || preg_match($mask_search_table, $table, $rets) ) {
 					$this->Conn->Query('DROP TABLE IF EXISTS ' . $table);
 				}
 			}
 		}
 
 		/**
 		 * Perform redirect after separate module install
 		 *
 		 * @param string $module_folder
 		 * @param bool $refresh_permissions
 		 */
 		function finalizeModuleInstall($module_folder, $refresh_permissions = false)
 		{
 			$this->SetModuleVersion(basename($module_folder), $module_folder);
 
 			if (!$this->Application->GetVar('redirect')) {
 				return ;
 			}
 
 			$themes_helper =& $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$module_name = $this->Application->findModule('Path', rtrim($module_folder, '/') . '/', 'Name');
 			$themes_helper->syncronizeModule($module_name);
 
 			$this->deleteCache($refresh_permissions);
 
 			$url_params = Array (
 				'pass' => 'm', 'admin' => 1,
 				'RefreshTree' => 1, 'index_file' => 'index.php',
 			);
 			$this->Application->Redirect('modules/modules_list', $url_params);
 		}
 
 		/**
 		 * Performs rebuild of themes
 		 *
 		 */
 		function rebuildThemes()
 		{
 			$this->Application->HandleEvent($themes_event, 'adm:OnRebuildThemes');
 		}
 
 		/**
 		 * Checks that file is writable by group or others
 		 *
 		 * @param string $file
 		 * @return boolean
 		 */
 		function checkWritePermissions($file)
 		{
 			if (DIRECTORY_SEPARATOR == '\\') {
 				// windows doen't allow to check permissions (always returns null)
 				return null;
 			}
 
 			$permissions = fileperms($file);
 			return $permissions & 0x0010 || $permissions & 0x0002;
 		}
 
 		/**
 		 * Upgrades primary skin to the latest version
 		 *
 		 * @param Array $module_info
 		 * @return string
 		 */
 		function upgradeSkin($module_info)
 		{
 			$upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'css');
 			$data = file_get_contents($upgrades_file);
 
 			// get all versions with their positions in file
 			$versions = Array ();
 			preg_match_all('/(' . VERSION_MARK . ')/s', $data, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
 			$from_version_int = $this->ConvertModuleVersion($module_info['FromVersion']);
 
 			foreach ($matches as $index => $match) {
 				$version_int = $this->ConvertModuleVersion($match[2][0]);
 
 				if ($version_int < $from_version_int) {
 					// only process versions, that were released after currently used version
 					continue;
 				}
 
 				$start_pos = $match[0][1] + strlen($match[0][0]);
 				$end_pos = array_key_exists($index + 1, $matches) ? $matches[$index + 1][0][1] : mb_strlen($data);
 				$patch_data = str_replace("\r\n", "\n", substr($data, $start_pos, $end_pos - $start_pos));
 
 				$versions[] = Array (
 					'Version' => $match[2][0],
 					// fixes trimmed leading spaces by modern text editor
 					'Data' => ltrim( str_replace("\n\n", "\n \n", $patch_data) ),
 				);
 			}
 
 			if (!$versions) {
 				// not skin changes -> quit
 				return true;
 			}
 
 			$primary_skin =& $this->Application->recallObject('skin.primary', null, Array ('skip_autoload' => true));
 			/* @var $primary_skin kDBItem */
 
 			$primary_skin->Load(1, 'IsPrimary');
 
 			if (!$primary_skin->isLoaded()) {
 				// we always got primary skin, but just in case
 				return ;
 			}
 
 			$temp_handler =& $this->Application->recallObject('skin_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			// clone current skin
 			$cloned_ids = $temp_handler->CloneItems('skin', '', Array ($primary_skin->GetID()));
 
 			if (!$cloned_ids) {
 				// can't clone
 				return ;
 			}
 
 			$skin =& $this->Application->recallObject('skin.tmp', null, Array ('skip_autoload' => true));
 			/* @var $skin kDBItem */
 
 			$skin->Load( $cloned_ids[0] );
 
 			// save css to temp file (for patching)
 			$skin_file = tempnam('/tmp', 'skin_css_');
 			$fp = fopen($skin_file, 'w');
 			fwrite($fp, str_replace("\r\n", "\n", $skin->GetDBField('CSS')));
 			fclose($fp);
 
 			$output = Array ();
 			$patch_file = tempnam('/tmp', 'skin_patch_');
 
 			foreach ($versions as $version_info) {
 				// for each left version get it's patch and apply to temp file
 				$fp = fopen($patch_file, 'w');
 				fwrite($fp, $version_info['Data']);
 				fclose($fp);
 
 				$output[ $version_info['Version'] ] = shell_exec('patch ' . $skin_file . ' ' . $patch_file . ' 2>&1') . "\n";
 			}
 
 			// place temp file content into cloned skin
 			$skin->SetDBField('Name', 'Upgraded to ' . $module_info['ToVersion']);
 			$skin->SetDBField('CSS', file_get_contents($skin_file));
 			$skin->Update();
 
 			unlink($skin_file);
 			unlink($patch_file);
 
 			$has_errors = false;
 
 			foreach ($output as $version => $version_output) {
 				$version_errors = trim( preg_replace("/(^|\n)(patching file .*?|Hunk #.*?\.)(\n|$)/m", '', $version_output) );
 
 				if ($version_errors) {
 					$has_errors = true;
 					$output[$version] = trim( preg_replace("/(^|\n)(patching file .*?)(\n|$)/m", '', $output[$version]) );
 				}
 				else {
 					unset($output[$version]);
 				}
 			}
 
 			if (!$has_errors) {
 				// copy patched css back to primary skin
 				$primary_skin->SetDBField('CSS', $skin->GetDBField('CSS'));
 				$primary_skin->Update();
 
 				// delete temporary skin record
 				$temp_handler->DeleteItems('skin', '', Array ($skin->GetID()));
 
 				return true;
 			}
 
 			// put clean skin from new version
 			$skin->SetDBField('CSS', file_get_contents(FULL_PATH . '/core/admin_templates/incs/style_template.css'));
 			$skin->Update();
 
 			// return output in case of errors
 			return $output;
 		}
 	}
\ No newline at end of file