Index: branches/5.0.x/core/units/categories/categories_event_handler.php
===================================================================
--- branches/5.0.x/core/units/categories/categories_event_handler.php	(revision 12888)
+++ branches/5.0.x/core/units/categories/categories_event_handler.php	(revision 12889)
@@ -1,2402 +1,2407 @@
 <?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');
 
 			$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');
 			}
 
 			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') != -1) {
 				// 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
 									(IsSystem = 1 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 . '.-new', 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)
 		{
 			$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('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');
 			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 kDBItem */
 
 			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)) {
 				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)) {
 				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 (IsSystem = 0) 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 */
 
 			$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)) {
 				return;
 			}
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ($ids) {
 				$status_field = array_shift( $this->Application->getUnitOption($event->Prefix,'StatusField') );
 
 				foreach ($ids as $id) {
 					$object->Load($id);
 
 					switch ($event->Name) {
 						case 'OnMassApprove':
 							$object->SetDBField($status_field, 1);
 							break;
 
 						case 'OnMassDecline':
 							$object->SetDBField($status_field, 0);
 							break;
 					}
 
 					if ($this->Application->GetVar('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);
 					}
 
 					if ($object->Update()) {
 						$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 {
 				$real_t = $this->_getDefaultDesign();
 			}
 
 			// 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();
 
 			$object->SetDBField('Modified_date', $now);
 			$object->SetDBField('Modified_time', $now);
 			$object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') ));
 
 			if ($object->GetDBField('IsSystem') == 1) {
 				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');
 
 			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');
-			if ($value == '' || preg_match('/^_Auto: (.*)/', $value)) {
-				$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
-				/* @var $ml_formatter kMultiLanguage */
-
-				$object->SetField(
-					$ml_formatter->LangFieldName($field),
-					$object->GetField( $ml_formatter->LangFieldName('Name') )
-				);
+			$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('IsSystem') && !$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) {
 				$system = $this->_templateFound($template, $theme_id) ? 1 : 0;
 			}
 			else {
 				$system = $system_mode == SMS_MODE_FORCE ? 1 : 0;
 			}
 
 			if ($system && $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');
 			$page_category = $this->Application->GetVar('m_cat_id');
 			if (!$page_category) {
 				$page_category = $root_category;
 				$this->Application->SetVar('m_cat_id', $page_category);
 			}
 
 			if (!$system && 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 = $system ? '_Auto: ' . $template : $template;
 			$page_description = '';
 
 			if ($system) {
 				$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('IsSystem', $system);
 
 			$object->SetDBField('IsMenu', 0);
 			$object->SetDBField('ThemeId', $theme_id); // $system ? $theme_id : 0
 
 			// 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 . '.-item', 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
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache
 					WHERE VarName IN ("cms_menu", "StructureTree")';
 			$this->Conn->Query($sql);
 
 			// build structure template mapping
 			$sql = 'SELECT
 						IF(c.IsSystem, 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;
 			}
 
 			$fields_hash = Array (
 				'VarName' => 'template_mapping',
 				'Data' => serialize($mapping),
 				'Cached' => adodb_mktime(),
 			);
 
 			$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache', 'REPLACE');
 		}
 
 		/**
 		 * 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');
 
 			// 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 . '.-dummy', 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)
 		{
 			$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)
 		{
 			$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_words[$keyword_index] = mysql_real_escape_string($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);
 			}
 		}
 	}
\ No newline at end of file