Index: branches/5.2.x/core/kernel/db/cat_dbitem.php
===================================================================
--- branches/5.2.x/core/kernel/db/cat_dbitem.php	(revision 14718)
+++ branches/5.2.x/core/kernel/db/cat_dbitem.php	(revision 14719)
@@ -1,645 +1,621 @@
 <?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 kCatDBItem extends kDBItem {
 
 	/**
 	 * Category path, needed for import
 	 *
 	 * @var Array
 	 */
 	var $CategoryPath = Array();
 
 	/**
 	 * Use automatic filename generation
 	 *
 	 * @var bool
 	 */
 	var $useFilenames = true;
 
 	/**
 	 * Use pending editing abilities during item (delegated by permissions)
 	 *
 	 * @var bool
 	 */
 	var $usePendingEditing = false;
 
-	function Clear($new_id = null)
+	/**
+	 * Removes all data from an object
+	 *
+	 * @param int $new_id
+	 * @return bool
+	 * @access public
+	 */
+	public function Clear($new_id = null)
 	{
 		parent::Clear($new_id);
 		$this->CategoryPath = Array();
 	}
 
 	/**
 	 * Set's prefix and special
 	 *
 	 * @param string $prefix
 	 * @param string $special
 	 * @access public
 	 */
 	function Init($prefix, $special)
 	{
 		parent::Init($prefix, $special);
 
 		$this->usePendingEditing = $this->Application->getUnitOption($this->Prefix, 'UsePendingEditing');
 	}
 
 	/**
-	 * Creates a record in the database table with current item' values
-	 *
-	 * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE!
-	 * @param bool $system_create
-	 * @return bool
-	 * @access public
-	 */
-	public function Create($force_id = false, $system_create = false)
-	{
-		$ret = parent::Create($force_id, $system_create);
-
-		if ($ret) {
-			// TODO: move to OnAfterItemCreate method
-			$this->assignPrimaryCategory();
-		}
-
-		return $ret;
-	}
-
-	/**
 	 * Assigns primary category for the item
 	 *
 	 * @access public
 	 */
-	function assignPrimaryCategory()
+	public function assignPrimaryCategory()
 	{
 		if ( $this->GetDBField('CategoryId') <= 0 ) {
 			// set primary category in item object
 			$this->SetDBField('CategoryId', $this->Application->GetVar('m_cat_id'));
 		}
 
 		$this->assignToCategory($this->GetDBField('CategoryId'), true);
 	}
 
 	/**
 	 * Updates previously loaded record with current item' values
 	 *
 	 * @access public
 	 * @param int $id Primary Key Id to update
 	 * @param bool $system_update
 	 * @return bool
 	 * @access public
 	 */
 	public function Update($id = null, $system_update = false)
 	{
-		$this->VirtualFields['ResourceId'] = Array ();
-
-		if ( $this->GetChangedFields() ) {
-			$now = adodb_mktime();
-			$this->SetDBField('Modified_date', $now);
-			$this->SetDBField('Modified_time', $now);
-			$this->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
-		}
-
 		if ( $this->useFilenames ) {
 			$this->checkFilename();
 			$this->generateFilename();
 		}
 
 		$ret = parent::Update($id, $system_update);
 
 		if ( $ret ) {
 			$filename = $this->useFilenames ? (string)$this->GetDBField('Filename') : '';
 			$sql = 'UPDATE ' . $this->CategoryItemsTable() . '
 					SET Filename = ' . $this->Conn->qstr($filename) . '
 					WHERE ItemResourceId = ' . $this->GetDBField('ResourceId');
 			$this->Conn->Query($sql);
 		}
 
-		unset($this->VirtualFields['ResourceId']);
-
 		return $ret;
 	}
 
 	/**
 	 * Returns CategoryItems table based on current item mode (temp/live)
 	 *
 	 * @return string
 	 */
 	function CategoryItemsTable()
 	{
 		$table = TABLE_PREFIX.'CategoryItems';
 		if ($this->Application->IsTempTable($this->TableName)) {
 			$table = $this->Application->GetTempName($table, 'prefix:'.$this->Prefix);
 		}
 
 		return $table;
 	}
 
 	function checkFilename()
 	{
 		if( !$this->GetDBField('AutomaticFilename') )
 		{
 			$filename = $this->GetDBField('Filename');
 			$this->SetDBField('Filename', $this->stripDisallowed($filename) );
 		}
 	}
 
 	function Copy($cat_id=null)
 	{
 		if (!isset($cat_id)) $cat_id = $this->Application->GetVar('m_cat_id');
 		$this->NameCopy($cat_id);
 		return $this->Create($cat_id);
 	}
 
 	/**
 	 * Sets new name for item in case if it is being copied in same table
 	 *
 	 * @param array $master Table data from TempHandler
 	 * @param int $foreign_key ForeignKey value to filter name check query by
 	 * @param string $title_field FieldName to alter, by default - TitleField of the prefix
 	 * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name
 	 * @access public
 	 */
 	public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s')
 	{
 		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
 		if (!$title_field) return;
 
 		$new_name = $this->GetDBField($title_field);
 		$cat_id = (int)$this->Application->GetVar('m_cat_id');
 		$original_checked = false;
 		do {
 			if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) {
 				$new_name = 'Copy '.( (int)$regs[1] + 1 ).' of '.$regs[2];
 			}
 			elseif ($original_checked) {
 				$new_name = 'Copy of '.$new_name;
 			}
 			$query = 'SELECT '.$title_field.' FROM '.$this->TableName.'
 								LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON
 								('.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$this->TableName.'.ResourceId)
 								WHERE ('.TABLE_PREFIX.'CategoryItems.CategoryId = '.$cat_id.') AND '.
 								$title_field.' = '.$this->Conn->qstr($new_name);
 			$res = $this->Conn->GetOne($query);
 			$original_checked = true;
 		} while ($res !== false);
 		$this->SetDBField($title_field, $new_name);
 
 		// this is needed, because Create will create items in its own CategoryId (if it's set),
 		// but we need to create it in target Paste category @see{kCatDBItem::Create} and its primary_category detection
 		$this->SetDBField('CategoryId', $cat_id);
 	}
 
 	/**
 	 * Changes item primary category to given/current category
 	 *
 	 * @param int $category_id
 	 */
 	function MoveToCat($category_id = null)
 	{
 //		$this->NameCopy();
 		if (!isset($category_id)) {
 			$category_id = $this->Application->GetVar('m_cat_id');
 		}
 
 		$table_name = TABLE_PREFIX . 'CategoryItems';
 		if ($this->IsTempTable()) {
 			$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix);
 		}
 
 		// check if the item already exists in destination category
 		$sql = 'SELECT PrimaryCat
 				FROM ' . $table_name . '
 				WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')';
 		$is_primary = $this->Conn->GetOne($sql);
 
 		// if it's not found is_primary will be FALSE, if it's found but not primary it will be int 0
 		$exists = $is_primary !== false;
 
 		if ($exists) {
 			// if the item already exists in destination category
 			if ($is_primary) {
 				// do nothing when we paste to primary
 				return ;
 			}
 
 			// if it's not primary - delete it from destination category, as we will move it from current primary below
 			$sql = 'DELETE FROM ' . $table_name . '
 					WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')';
 			$this->Conn->Query($sql);
 		}
 
 		// change category id in existing primary category record
 		$sql = 'UPDATE ' . $table_name . '
 				SET CategoryId = ' . (int)$category_id . '
 				WHERE (ItemResourceId = ' . $this->GetDBField('ResourceId') . ') AND (PrimaryCat = 1)';
 		$this->Conn->Query($sql);
 
 		$this->Update();
 	}
 
 	/**
 	 * When item is deleted, then also delete it from all categories
 	 *
 	 * @param int $id
 	 * @return bool
 	 * @access public
 	 */
 	public function Delete($id = null)
 	{
 		if ( isset($id) ) {
 			$this->setID($id);
 		}
 
 		$this->Load($this->GetID());
 
 		$ret = parent::Delete();
 
 		if ( $ret ) {
 			// TODO: move to OnAfterItemDelete method
 			$query = '	DELETE FROM ' . $this->CategoryItemsTable() . '
 						WHERE ItemResourceId = ' . $this->GetDBField('ResourceId');
 			$this->Conn->Query($query);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Deletes item from categories
 	 *
 	 * @param Array $delete_category_ids
 	 * @author Alex
 	 */
 	function DeleteFromCategories($delete_category_ids)
 	{
 		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); // because item was loaded before by ResourceId
 
 		$ci_table = $this->Application->getUnitOption($this->Prefix . '-ci', 'TableName');
 		$resource_id = $this->GetDBField('ResourceId');
 
 		$item_cats_sql = '	SELECT CategoryId
 							FROM %s
 							WHERE ItemResourceId = %s';
 
 		$delete_category_items_sql = '	DELETE FROM %s
 										WHERE ItemResourceId = %s AND CategoryId IN (%s)';
 
 		$category_ids = $this->Conn->GetCol( sprintf($item_cats_sql, $ci_table, $resource_id) );
 		$cats_left = array_diff($category_ids, $delete_category_ids);
 
 		if ( !$cats_left ) {
 			$sql = 'SELECT %s
 					FROM %s
 					WHERE ResourceId = %s';
 			$ids = $this->Conn->GetCol(sprintf($sql, $id_field, $this->TableName, $resource_id));
 
 			$temp_handler =& $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems($this->Prefix, $this->Special, $ids);
 		}
 		else {
 			$this->Conn->Query( sprintf($delete_category_items_sql, $ci_table, $resource_id, implode(',', $delete_category_ids)) );
 
 			$sql = 'SELECT CategoryId
 					FROM %s
 					WHERE PrimaryCat = 1 AND ItemResourceId = %s';
 			$primary_cat_id = $this->Conn->GetCol(sprintf($sql, $ci_table, $resource_id));
 
 			if ( count($primary_cat_id) == 0 ) {
 				$sql = 'UPDATE %s
 						SET PrimaryCat = 1
 						WHERE (CategoryId = %s) AND (ItemResourceId = %s)';
 				$this->Conn->Query( sprintf($sql, $ci_table, reset($cats_left), $resource_id) );
 			}
 		}
 	}
 
 	/**
 	 * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result
 	 *
 	 * @param string $filename
 	 * @return string
 	 */
 	function stripDisallowed($filename)
 	{
 		$filenames_helper =& $this->Application->recallObject('FilenamesHelper');
 		/* @var $filenames_helper kFilenamesHelper */
 
 		$table = $this->IsTempTable() ? $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems', 'prefix:'.$this->Prefix) : TABLE_PREFIX.'CategoryItems';
 
 		return $filenames_helper->stripDisallowed($table, 'ItemResourceId', $this->GetDBField('ResourceId'), $filename);
 	}
 
 	/* commented out because it's called only from stripDisallowed body, which is moved to helper
 
 	function checkAutoFilename($filename)
 	{
 		$filenames_helper =& $this->Application->recallObject('FilenamesHelper');
 		return $filenames_helper->checkAutoFilename($this->TableName, $this->IDField, $this->GetID(), $filename);
 	}*/
 
 	/**
 	 * Generate item's filename based on it's title field value
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function generateFilename()
 	{
 		if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) {
 			return ;
 		}
 
 		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
 
 		if ( preg_match('/l([\d]+)_(.*)/', $title_field, $regs) ) {
 			// if title field is multilingual, then use it's name from primary language
 			$title_field = 'l' . $this->Application->GetDefaultLanguageId() . '_' . $regs[2];
 		}
 
 		$name = $this->stripDisallowed( $this->GetDBField($title_field) );
 
 		if ( $name != $this->GetDBField('Filename') ) {
 			$this->SetDBField('Filename', $name);
 		}
 	}
 
 	/**
 	 * Adds item to other category
 	 *
 	 * @param int $category_id
 	 * @param bool $is_primary
 	 * @return void
 	 * @access public
 	 */
 	public function assignToCategory($category_id, $is_primary = false)
 	{
 		$table = $this->CategoryItemsTable();
 		$key_clause = '(ItemResourceId = ' . $this->GetDBField('ResourceId') . ')';
 
 		// get all categories, where item is in
 		$sql = 'SELECT PrimaryCat, CategoryId
 				FROM ' . $table . '
 				WHERE ' . $key_clause;
 		$item_categories = $this->Conn->GetCol($sql, 'CategoryId');
 
 		$primary_found = $item_category_id = false;
 
 		if ( $item_categories ) {
 			// find primary category
 			foreach ($item_categories as $item_category_id => $primary_found) {
 				if ( $primary_found ) {
 					break;
 				}
 			}
 		}
 
 		if ( $primary_found && ($item_category_id == $category_id) && !$is_primary ) {
 			// want to make primary category as non-primary :(
 			return;
 		}
 		elseif ( !$primary_found ) {
 			$is_primary = true;
 		}
 
 		if ( $is_primary && $item_categories ) {
 			// reset primary mark from all other categories
 			$sql = 'UPDATE ' . $table . '
 					SET PrimaryCat = 0
 					WHERE ' . $key_clause;
 			$this->Conn->Query($sql);
 		}
 
 		// UPDATE & INSERT instead of REPLACE because CategoryItems table has no primary key defined in database
 		if ( isset($item_categories[$category_id]) ) {
 			$sql = 'UPDATE ' . $table . '
 					SET PrimaryCat = ' . ($is_primary ? 1 : 0) . '
 					WHERE ' . $key_clause . ' AND (CategoryId = ' . $category_id . ')';
 			$this->Conn->Query($sql);
 		}
 		else {
 			$fields_hash = 	Array(
 				'CategoryId'		=>	$category_id,
 				'ItemResourceId'	=>	$this->GetField('ResourceId'),
 				'PrimaryCat'		=>	$is_primary ? 1 : 0,
 				'ItemPrefix'		=>	$this->Prefix,
 				'Filename'			=>	$this->useFilenames ? (string)$this->GetDBField('Filename') : '', // because some prefixes does not use filenames,
 			);
 
 			$this->Conn->doInsert($fields_hash, $table);
 		}
 
 		// to ensure filename update after adding to another category
 		// this is critical since there may be an item with same filename in newly added category!
 		$this->Update();
 	}
 
 	/**
 	 * Removes item from category specified
 	 *
 	 * @param int $category_id
 	 */
 	function removeFromCategory($category_id)
 	{
 		$sql = 'DELETE FROM '.TABLE_PREFIX.'CategoryItems WHERE (CategoryId = %s) AND (ItemResourceId = %s)';
 		$this->Conn->Query( sprintf($sql, $category_id, $this->GetDBField('ResourceId')) );
 	}
 
 	/**
 	 * Returns list of columns, that could exist in imported file
 	 *
 	 * @return Array
 	 */
 	function getPossibleExportColumns()
 	{
 		static $columns = null;
 		if (!is_array($columns)) {
 			$columns = array_merge($this->Fields['AvailableColumns']['options'], $this->Fields['ExportColumns']['options']);
 		}
 		return $columns;
 	}
 
 	/**
 	 * Returns item's primary image data
 	 *
 	 * @return Array
 	 */
 	function getPrimaryImageData()
 	{
 		$sql = 'SELECT *
 				FROM '.TABLE_PREFIX.'Images
 				WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (DefaultImg = 1)';
 		$image_data = $this->Conn->GetRow($sql);
 		if (!$image_data) {
 			// 2. no primary image, then get image with name "main"
 			$sql = 'SELECT *
 				FROM '.TABLE_PREFIX.'Images
 				WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (Name = "main")';
 			$image_data = $this->Conn->GetRow($sql);
 		}
 		return $image_data;
 	}
 
 	function ChangeStatus($new_status, $pending_editing = false)
 	{
 		$status_field = array_shift( $this->Application->getUnitOption($this->Prefix,'StatusField') );
 		if ($new_status != $this->GetDBField($status_field)) {
 			// status was changed
 			$this->sendEmailEvents($new_status, $pending_editing);
 		}
 		$this->SetDBField($status_field, $new_status);
 
 		return $this->Update();
 	}
 
 	function sendEmailEvents($new_status, $pending_editing = false)
 	{
 		$owner_field = $this->Application->getUnitOption($this->Prefix, 'OwnerField');
 		if (!$owner_field) {
 			$owner_field = 'CreatedById';
 		}
 
 		$event_name = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix');
 		if ($pending_editing) {
 			$event_name .= '.MODIFY';
 		}
 
 		$event_name .= $new_status == STATUS_ACTIVE ? '.APPROVE' : '.DENY';
 		$this->Application->EmailEventUser($event_name, $this->GetDBField($owner_field));
 	}
 
 	/**
 	 * Approves changes made to category item
 	 *
 	 * @return bool
 	 */
 	function ApproveChanges()
 	{
 		$original_id = $this->GetDBField('OrgId');
 
 		if ( !($this->usePendingEditing && $original_id) ) {
 			// non-pending copy of original link
 			return $this->ChangeStatus(STATUS_ACTIVE);
 		}
 
 		if ( $this->raiseEvent('OnBeforeDeleteOriginal', null, Array ('original_id' => $original_id)) ) {
 			// delete original item, because changes made in pending copy (this item) got to be approved in this method
 			$temp_handler =& $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems($this->Prefix, $this->Special, Array ($original_id));
 
 			$this->SetDBField('OrgId', 0);
 			return $this->ChangeStatus(STATUS_ACTIVE, true);
 		}
 
 		return false;
 	}
 
 	/**
 	 * Decline changes made to category item
 	 *
 	 * @return bool
 	 */
 	function DeclineChanges()
 	{
 		$original_id = $this->GetDBField('OrgId');
 
 		if ( !($this->usePendingEditing && $original_id) ) {
 			// non-pending copy of original link
 			return $this->ChangeStatus(STATUS_DISABLED);
 		}
 
 		// delete this item, because changes made in pending copy (this item) will be declined in this method
 		$temp_handler =& $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 		/* @var $temp_handler kTempTablesHandler */
 
 		$temp_handler->DeleteItems($this->Prefix, $this->Special, Array ($this->GetID()));
 
 		$this->sendEmailEvents(STATUS_DISABLED, true);
 		// original item is not changed here, because it is already enabled (thrus pending copy is visible to item's owner or admin with permission)
 		return true;
 	}
 
 	function RegisterHit()
 	{
 		$already_viewed = $this->Application->RecallVar($this->getPrefixSpecial().'_already_viewed');
 		$already_viewed = $already_viewed ? unserialize($already_viewed) : Array ();
 
 		$id = $this->GetID();
 		if (!in_array($id, $already_viewed)) {
 			$property_map = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings');
 			if (!$property_map) {
 				return ;
 			}
 			$hits_field = $property_map['ClickField'];
 			$new_hits = $this->GetDBField($hits_field) + 1;
 
 			$sql = 'SELECT MAX('.$hits_field.')
 					FROM '.$this->TableName.'
 					WHERE FLOOR('.$hits_field.') = '.$new_hits;
 			$max_hits = $this->Conn->GetOne($sql);
 			if ($max_hits) {
 				$new_hits = $max_hits + 0.000001;
 			}
 
 			$fields_hash = Array (
 				$hits_field => $new_hits,
 			);
 			$this->Conn->doUpdate($fields_hash, $this->TableName, $this->IDField.' = '.$id);
 
 			array_push($already_viewed, $id);
 			$this->Application->StoreVar($this->getPrefixSpecial().'_already_viewed', serialize($already_viewed));
 		}
 	}
 
 	/**
 	 * Returns part of SQL WHERE clause identifying the record, ex. id = 25
 	 *
 	 * @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method
 	 * @param Array $keys_hash alternative, then item id, keys hash to load item by
 	 * @see kDBItem::Load()
 	 * @see kDBItem::Update()
 	 * @see kDBItem::Delete()
 	 * @return string
 	 * @access protected
 	 */
 	protected function GetKeyClause($method = null, $keys_hash = null)
 	{
 		if ($method == 'load') {
 			// for item with many categories makes primary to load
 			$ci_table = TABLE_PREFIX . 'CategoryItems';
 
 			if ($this->IsTempTable()) {
 				$ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $this->Prefix);
 			}
 
 			$primary_category_clause = Array ('`' . $ci_table . '`.`PrimaryCat`' => 1);
 
 			if (!isset($keys_hash)) {
 				$keys_hash = Array ($this->IDField => $this->ID);
 			}
 
 			// merge primary category clause in any case to be sure, that
 			// CategoryId field will always contain primary category of item
 			$keys_hash = array_merge($keys_hash, $primary_category_clause);
 		}
 
 		return parent::GetKeyClause($method, $keys_hash);
 	}
 
 }
\ No newline at end of file
Index: branches/5.2.x/core/kernel/db/cat_event_handler.php
===================================================================
--- branches/5.2.x/core/kernel/db/cat_event_handler.php	(revision 14718)
+++ branches/5.2.x/core/kernel/db/cat_event_handler.php	(revision 14719)
@@ -1,2868 +1,2892 @@
 <?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 standard 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),
 			'OnReviewHelpful' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Load item if id is available
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	function LoadItem(&$event)
 	{
 		$object =& $event->getObject();
 		/* @var $object kDBItem */
 
 		$id = $this->getPassedID($event);
 
 		if ( $object->Load($id) ) {
 			$actions =& $this->Application->recallObject('kActions');
 			/* @var $actions Params */
 
 			$actions->Set($event->getPrefixSpecial() . '_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 user permission to execute given $event
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access public
 	 */
 	public 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
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCopy(&$event)
 	{
 		$this->Application->RemoveVar('clipboard');
 
 		$clipboard_helper =& $this->Application->recallObject('ClipboardHelper');
 		/* @var $clipboard_helper kClipboardHelper */
 
 		$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Add selected items to clipboard with mode = CUT
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCut(&$event)
 	{
 		$this->Application->RemoveVar('clipboard');
 		$clipboard_helper =& $this->Application->recallObject('ClipboardHelper');
 		/* @var $clipboard_helper kClipboardHelper */
 
 		$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * 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
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnPaste(&$event)
 	{
 		if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) {
 			$event->status = kEvent::erFAIL;
 			return;
 		}
 
 		$clipboard_data = $event->getEventParam('clipboard_data');
 
 		if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) {
 			return;
 		}
 
 		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));
 			/* @var $object kCatDBItem */
 
 			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
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnMassDelete(&$event)
 	{
 		if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 			$event->status = kEvent::erFAIL;
 			return;
 		}
 
 		$ids = $this->StoreSelectedIDs($event);
 
 		$to_delete = Array ();
 		$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 
 		if ( $recycle_bin ) {
 			$rb =& $this->Application->recallObject('c.recycle', null, array ('skip_autoload' => true));
 			/* @var $rb CategoriesItem */
 
 			$rb->Load($recycle_bin);
 
 			$object =& $this->Application->recallObject($event->Prefix . '.recycleitem', null, Array ('skip_autoload' => true));
 			/* @var $object kCatDBItem */
 
 			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_handler =& $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 		/* @var $temp_handler kTempTablesHandler */
 
 		$event->setEventParam('ids', $ids);
 		$this->customProcessing($event, 'before');
 		$ids = $event->getEventParam('ids');
 
 		if ( $ids ) {
 			$temp_handler->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'
 			);
 
 			$keywords = $event->getEventParam('keyword_string');
 			$type = $this->Application->GetVar('search_type', 'simple');
 
 			if ( $keywords ) {
 				// processing keyword_string param of ListProducts tag
 				$this->Application->SetVar('keywords', $keywords);
 				$type = 'simple';
 			}
 
 			$search_event = $event_mapping[$type];
 			$this->$search_event($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBList */
 
 			$search_sql = '	FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search
 							search_result LEFT JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
 			$sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL());
 
 			$object->SetSelectSQL($sql);
 
 			$object->addCalculatedField('Relevance', 'search_result.Relevance');
 			$object->AddOrderField('search_result.Relevance', 'desc', true);
 
 			$type_clauses['search']['include'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')';
 			$type_clauses['search']['except'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')';
 			$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));
 			/* @var $p_item kCatDBItem */
 
 			$p_item->Load( (int)$id );
 
 			$p_resource_id = $p_item->GetDBField('ResourceId');
 
 			$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
 					WHERE
 						(Enabled = 1)
 						AND (
 								(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 								OR
 								(Type = 1
 									AND (
 											(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 											OR
 											(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
 										)
 								)
 						)';
 
 			$related_ids_array = $this->Conn->Query($sql);
 			$related_ids = Array();
 
 			foreach ($related_ids_array as $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 any custom changes to list's sql query
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 * @see kDBEventHandler::OnListBuild()
 	 */
 	protected 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->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);
 
 		$search_helper =& $this->Application->recallObject('SearchHelper');
 		/* @var $search_helper kSearchHelper */
 
 		$search_helper->SetComplexFilter($event, $type_clauses, $types, $except_types);
 	}
 
 	/**
 	 * 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 kDBItem|kDBList $object
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected 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');
 			/* @var $export_helper kCatDBItemExportHelper */
 
 			$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)');
 
 	}
 
 	/**
 	 * Calculates hot limit for current item's table
 	 *
 	 * @param kEvent $event
 	 * @return float
 	 * @access protected
 	 */
 	protected function CalculateHotLimit(&$event)
 	{
 		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
 
 		if ( !$property_map ) {
 			return 0.00;
 		}
 
 		$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
 	 * @return void
 	 * @access protected
 	 */
 	protected 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);
 		}
 	}
 
 	/**
 	 * Occurs after loading item, 'id' parameter
 	 * allows to get id of item that was loaded
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemLoad(&$event)
 	{
 		parent::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']);
 			}
 		}
 
 		// substituting pending status value for pending editing
 		if ( $object->HasField('OrgId') && $object->GetDBField('OrgId') > 0 && $object->GetDBField('Status') == -2 ) {
 			$new_options = Array ();
 			$options = $object->GetFieldOption('Status', 'options', false, Array ());
 
 			foreach ($options as $key => $val) {
 				if ( $key == 2 ) {
 					$key = -2;
 				}
 
 				$new_options[$key] = $val;
 			}
 
 			$object->SetFieldOption('Status', 'options', $new_options);
 		}
 
 		if ( !$this->Application->isAdmin ) {
 			// 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);
 		}
 
 		if ( $object->isVirtualField('MoreCategories') ) {
 			// 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) . '|' : '');
 		}
 	}
 
 	/**
 	 * Occurs after updating item
 	 *
 	 * @param kEvent $event
 	 * @access public
 	 */
 	function OnAfterItemUpdate(&$event)
 	{
 		$this->CalculateHotLimit($event);
 
 		if ( substr($event->Special, -6) == 'import' ) {
 			$this->setCustomExportColumns($event);
 		}
 
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		if ( !$this->Application->isAdmin ) {
 			$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');
 			}
 		}
 
 		$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 
 		if ( $this->Application->isAdminUser && $recycle_bin ) {
 			$sql = 'SELECT CategoryId
 					FROM ' . $this->Application->getUnitOption('ci', 'TableName') . '
 					WHERE ItemResourceId = ' . $object->GetDBField('ResourceId') . ' AND PrimaryCat = 1';
 			$primary_category = $this->Conn->GetOne($sql);
 
 			if ( $primary_category == $recycle_bin ) {
 				$event->CallSubEvent('OnAfterItemDelete');
 			}
 		}
+
+		if ( $object->GetChangedFields() ) {
+			$now = adodb_mktime();
+			$object->SetDBField('Modified_date', $now);
+			$object->SetDBField('Modified_time', $now);
+			$object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
+		}
 	}
 
 	/**
 	 * Sets values for import process
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemCreate(&$event)
 	{
 		parent::OnAfterItemCreate($event);
 
+		$object =& $event->getObject();
+		/* @var $object kCatDBItem */
+
 		if ( substr($event->Special, -6) == 'import' ) {
 			$this->setCustomExportColumns($event);
 		}
 
-		if ( !$this->Application->isAdmin ) {
-			$object =& $event->getObject();
-			/* @var $object kCatDBItem */
+		$object->assignPrimaryCategory();
 
+		if ( !$this->Application->isAdmin ) {
 			$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 = kUtil::unhtmlentities( trim($this->Application->GetVar('keywords')) );
 
 		$query_object =& $this->Application->recallObject('HTTPQuery');
 		/* @var $query_object kHTTPQuery */
 
 		$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 		if(!isset($query_object->Get['keywords']) &&
 			!isset($query_object->Post['keywords']) &&
 			$this->Conn->Query($sql))
 		{
 			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) {
 			$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
 			$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
 
 			// processing multilingual fields
 			if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
 				$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 				$field_list[$key] = 'l'.$lang.'_'.$field;
 
 				if (!isset($search_config[$field]['ForeignField'])) {
 					$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
 					$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 				}
 			}
 
 			// processing fields from other tables
 			$foreign_field = $search_config[$field]['ForeignField'];
 
 			if ( $foreign_field ) {
 				$exploded = explode(':', $foreign_field, 2);
 				if ($exploded[0] == 'CALC') {
 					// ignoring having type clauses in simple search
 					unset($field_list[$key]);
 					continue;
 				}
 				else {
 					$multi_lingual = false;
 					if ($exploded[0] == 'MULTI') {
 						$multi_lingual = true;
 						$foreign_field = $exploded[1];
 					}
 
 					$exploded = explode('.', $foreign_field);	// format: table.field_name
 					$foreign_table = TABLE_PREFIX.$exploded[0];
 
 					$alias_counter++;
 					$alias = 't'.$alias_counter;
 
 					if ($multi_lingual) {
 						$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
 						$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 						$search_config_map[ $field_list[$key] ] = $field;
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 					else {
 						$field_list[$key] = $alias.'.'.$exploded[1];
 						$search_config_map[ $field_list[$key] ] = $field;
 					}
 
 					$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
 					$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 					$join_clauses[] = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 										ON '.$join_clause;
 				}
 			}
 			else {
 				// processing fields from local table
 				if ($search_config[$field]['CustomFieldId']) {
 					$local_table = 'custom_data';
 
 					// search by custom field value on current language
 					$custom_field_id = array_search($field_list[$key], $custom_fields);
 					$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
 
 					// search by custom field value on primary language
 					$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
 					$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 				}
 
 				$field_list[$key] = $local_table.'.'.$field_list[$key];
 				$search_config_map[ $field_list[$key] ] = $field;
 			}
 		}
 
 		// keyword string processing
 		$search_helper =& $this->Application->recallObject('SearchHelper');
 		/* @var $search_helper kSearchHelper */
 
 		$where_clause = Array ();
 		foreach ($field_list as $field) {
 			if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
 				// local real field
 				$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
 				if ($filter_data) {
 					$where_clause[] = $filter_data['value'];
 				}
 			}
 			elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
 				$custom_field_name = 'cust_' . $search_config_map[$field];
 				$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
 				if ($filter_data) {
 					$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
 				}
 			}
 			else {
 				$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
 			}
 		}
 
 		$where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below!
 
 		$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 = ' . STATUS_ACTIVE . ')';
 
 		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'];
 
 			// search by whole words only ([[:<:]] - word boundary)
 			/*$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
 			foreach ($positive_words as $keyword) {
 				$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
 			}*/
 
 			// search by partial word matches too
 			$revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
 			foreach ($positive_words as $keyword) {
 				$revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
 			}
 		}
 
 		$revelance_parts = array_unique($revelance_parts);
 
 		$conf_postfix = $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 && $object->isField('Hits')) {
 			$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 		}
 		if ($rel_rating && $object->isField('CachedRating')) {
 			$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 		}
 
 		// building final search query
 		if (!$this->Application->GetVar('do_not_drop_search_table')) {
 			$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
 			$this->Application->SetVar('do_not_drop_search_table', true);
 		}
 
 
 		$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
 		if ($search_table_exists) {
 			$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
 		}
 		else {
 			$select_intro = 'CREATE TABLE '.$search_table.' AS ';
 		}
 
 		$edpick_clause = $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').' ORDER BY Relevance DESC';
 
 		$this->Conn->Query($sql);
 
 		if ( !$search_table_exists ) {
 			$sql = 'ALTER TABLE ' . $search_table . '
 					ADD INDEX (ResourceId),
 					ADD INDEX (Relevance)';
 			$this->Conn->Query($sql);
 		}
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSubSearch(&$event)
 	{
 		$ids = Array ();
 		$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');
 		/* @var $query_object kHTTPQuery */
 
 		if ( !isset($query_object->Post['andor']) ) {
 			// used when navigating by pages or changing sorting in search results
 			return;
 		}
 
 		$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();
 		/* @var $object kDBList */
 
 		$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
 			$local_table = TABLE_PREFIX.$record['TableName'];
 			$weight_sum += $record['Priority']; // counting weight sum; used when making relevance clause
 
 			// processing multilingual fields
 			if ( $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
 				$field_name = 'l'.$lang.'_'.$field;
 			}
 			else {
 				$field_name = $field;
 			}
 
 			// processing fields from other tables
 			$foreign_field = $record['ForeignField'];
 
 			if ( $foreign_field ) {
 				$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] = kUtil::unhtmlentities( $keywords[$field] );
 				if ($keywords[$field]) {
 					$condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] ));
 				}
 				break;
 
 			case 'multiselect':
 				$keywords[$field] = kUtil::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] = kUtil::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($this->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;
 	}
 
 	/**
 	 * Returns human readable representation of searched data to be placed in search log
 	 * @param string $type
 	 * @param Array $search_data
 	 * @return string
 	 * @access protected
 	 */
 	protected function getHuman($type, $search_data)
 	{
 		// all 3 variables are retrieved from $search_data array
 		/* @var $search_config Array */
 		/* @var $verb string */
 		/* @var $value string */
 
 		$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;
 		}
 
 		return '';
 	}
 
 	/**
 	 * 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->isMainList() ? $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->isMainList()) {
 			// 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);
 
 				$event->SetRedirectParam('m_cat_id', $this->Application->RecallVar('ImportCategory'));
 				$event->SetRedirectParam('anchor', 'tab-' . $event->Prefix);
 
 				$event->redirect = 'catalog/catalog';
 			}
 			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 = kEvent::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
 	 * @return void
 	 * @access protected
 	 */
 	protected function restorePrimaryImage(&$event)
 	{
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		$has_image_info = $object->GetDBField('ImageAlt') && ($object->GetDBField('ThumbnailImage') || $object->GetDBField('FullImage'));
 		if ( !$has_image_info ) {
 			return ;
 		}
 
 		$image_data = $object->getPrimaryImageData();
 
 		$image =& $this->Application->recallObject('img', null, Array ('skip_autoload' => true));
 		/* @var $image kDBItem */
 
 		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();
 		}
 	}
 
 	/**
 	 * Detects if image url is specified in a given path (instead of path on disk)
 	 *
 	 * @param string $path
 	 * @return bool
 	 * @access protected
 	 */
 	protected 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');
 			/* @var $export_helper kCatDBItemExportHelper */
 
 			$export_helper->setRequiredFields($event);
 		}
 	}
 
 	/**
 	 * Process items selected in item_selector
 	 *
 	 * @param kEvent $event
 	 */
 	function OnProcessSelected(&$event)
 	{
 		$dst_field = $this->Application->RecallVar('dst_field');
 		$selected_ids = $this->Application->GetVar('selected_ids');
 
 		if ( $dst_field == 'ItemCategory' ) {
 			// Item Edit -> Categories Tab -> New Categories
 			$object =& $event->getObject();
 			/* @var $object kCatDBItem */
 
 			$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']);
 
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_id', 0);
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_event', 'OnExportBegin');
 		}
 
 		$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));
 			/* @var $object kDBItem */
 
 			$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->getBaseCategory());
 	}
 
 	/**
 	 * Cancels item editing
 	 * @param kEvent $event
 	 * @return void
 	 * @todo Used?
 	 */
 	function OnCancelAction(&$event)
 	{
 		$event->setRedirectParams(Array ('pass' => 'all,' . $event->getPrefixSpecial()), true);
 		$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();
 		/* @var $object kDBItem */
 
 		$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 edited item into temp table
 	 * If there is no id, new item is created in temp table
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnPreSave(&$event)
 	{
 		parent::OnPreSave($event);
 
 		$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 
 		if ( $event->status == kEvent::erSUCCESS && $use_pending_editing ) {
 			// decision: clone or not clone
 
 			$object =& $event->getObject();
 			/* @var $object kCatDBItem */
 
 			if ( $object->GetID() == 0 || $object->GetDBField('OrgId') > 0 ) {
 				// new items or cloned items shouldn't be cloned again
 				return ;
 			}
 
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$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');
 				/* @var $temp_handler 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 item's owner field
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnPreCreate(&$event)
 	{
 		parent::OnPreCreate($event);
 
 		if ( $event->status != kEvent::erSUCCESS ) {
 			return ;
 		}
 
 		$object =& $event->getObject();
 		/* @var $object kDBItem */
 
 		$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)
 	{
 
 	}
 
 	/**
 	 * Occurs before an item has been cloned
 	 * Id of newly created item is passed as event' 'id' param
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeClone(&$event)
 	{
 		parent::OnBeforeClone($event);
 
 		if ( $this->Application->GetVar('ResetCatBeforeClone') ) {
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('CategoryId', null);
 		}
 	}
 
 	/**
 	 * Set status for new category item based on user permission in category
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemCreate(&$event)
 	{
 		parent::OnBeforeItemCreate($event);
 
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		$is_admin = $this->Application->isAdminUser;
 		$owner_field = $this->getOwnerField($event->Prefix);
 
 		if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField($owner_field)) ) {
 			// Front-end OR owner not specified -> set to currently logged-in user
 			$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
 		}
 
-		if ( $object->Validate() ) {
-			$object->SetDBField('ResourceId', $this->Application->NextResourceId());
-		}
-
 		if ( !$this->Application->isAdmin ) {
 			$this->setItemStatusByPermission($event);
 		}
 	}
 
 	/**
 	 * Sets category item status based on user permissions (only on Front-end)
 	 *
 	 * @param kEvent $event
 	 */
 	function setItemStatusByPermission(&$event)
 	{
 		$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 
 		if (!$use_pending_editing) {
 			return ;
 		}
 
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		$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 = kEvent::erFAIL;
 		}
 		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
 	 * @param int $mode
 	 */
 	function processAdditionalCategories(&$object, $mode)
 	{
 		if ( !$object->isVirtualField('MoreCategories') ) {
 			// 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 = kEvent::erSUCCESS;
 				}
 				else {
 					$event->status = kEvent::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 != kEvent::erSUCCESS ) {
 			return;
 		}
 
 		// prepare redirect template
 		$object =& $event->getObject();
 		/* @var $object kDBItem */
 
 		$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');
 		$owner_field = $this->getOwnerField($event->Prefix);
 		$owner_id = $object->GetDBField($owner_field);
 
 		switch ( $event->Name ) {
 			case 'OnCreate':
 				$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
 				$this->Application->EmailEventAdmin($perm_prefix . '.' . $event_suffix); // there are no ADD.PENDING event for admin :(
 				$this->Application->EmailEventUser($perm_prefix . '.' . $event_suffix, $owner_id);
 				break;
 
 			case 'OnUpdate':
 				$event_suffix = $is_active ? 'MODIFY' : 'MODIFY.PENDING';
 				$user_id = is_numeric($object->GetDBField('ModifiedById')) ? $object->GetDBField('ModifiedById') : $owner_id;
 				$this->Application->EmailEventAdmin($perm_prefix . '.' . $event_suffix); // there are no ADD.PENDING event for admin :(
 				$this->Application->EmailEventUser($perm_prefix . '.' . $event_suffix, $user_id);
 				break;
 		}
 	}
 
 	/**
 	 * Apply same processing to each item being selected in grid
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function iterateItems(&$event)
 	{
 		if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) {
 			parent::iterateItems($event);
 		}
 
 		if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 			$event->status = kEvent::erFAIL;
 			return ;
 		}
 
 		$object =& $event->getObject(Array ('skip_autoload' => true));
 		/* @var $object kCatDBItem */
 
 		$ids = $this->StoreSelectedIDs($event);
 
 		if ( $ids ) {
 			foreach ($ids as $id) {
 				$ret = true;
 				$object->Load($id);
 
 				switch ( $event->Name ) {
 					case 'OnMassApprove':
 						$ret = $object->ApproveChanges();
 						break;
 
 					case 'OnMassDecline':
 						$ret = $object->DeclineChanges();
 						break;
 				}
 
 				if ( !$ret ) {
 					$event->status = kEvent::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 == kEvent::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();
 		/* @var $object kDBItem */
 
 		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);
 	}
 
 	/**
 	 * Returns owner field for given prefix
 	 *
 	 * @param $prefix
 	 * @return string
 	 * @access protected
 	 */
 	protected function getOwnerField($prefix)
 	{
 		return $this->Application->getUnitOption($prefix, 'OwnerField', 'CreatedById');
 	}
 
 	/**
 	 * Creates virtual image fields for item
 	 *
 	 * @param kEvent $event
 	 */
 	function OnAfterConfigRead(&$event)
 	{
 		parent::OnAfterConfigRead($event);
 
 		if (defined('IS_INSTALL') && IS_INSTALL) {
 			return ;
 		}
 
 		if ( !$this->Application->isAdmin ) {
 			$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
 		}
 
 		$this->changeSortings($event);
 
 		// 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);
 	}
 
 	function changeSortings(&$event)
 	{
 		$remove_sortings = Array ();
 
 		if ( !$this->Application->isAdmin ) {
 			// remove Pick sorting on Front-end, when not required
 			$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping', Array ());
 
 			if ( !isset($config_mapping['ForceEditorPick']) || !$this->Application->ConfigValue($config_mapping['ForceEditorPick']) ) {
 				$remove_sortings[] = 'EditorsPick';
 			}
 		}
 		else {
 			// remove all forced sortings in Admin Console
 			$remove_sortings = array_merge($remove_sortings, Array ('Priority', 'EditorsPick'));
 		}
 
 		if ( !$remove_sortings ) {
 			return;
 		}
 
 		$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
 		/* @var $list_sortings Array */
 
 		foreach ($list_sortings as $special => $sorting_fields) {
 			foreach ($remove_sortings as $sorting_field) {
 				unset($list_sortings[$special]['ForcedSorting'][$sorting_field]);
 			}
 		}
 
 		$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
 	}
 
 	/**
 	 * Returns file contents associated with item
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDownloadFile(&$event)
 	{
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		$event->status = kEvent::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 = kEvent::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 kCatDBItem */
 
 		$object->Load( $this->Application->GetVar('id') );
 
 		echo $rating_helper->makeVote($object);
 	}
 
 	/**
 	 * Marks review as useful
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnReviewHelpful(&$event)
 	{
 		$review_id = (int)$this->Application->GetVar('review_id');
 
 		if ( !$review_id ) {
 			return;
 		}
 
 		$spam_helper =& $this->Application->recallObject('SpamHelper');
 		/* @var $spam_helper SpamHelper */
 
 		$spam_helper->InitHelper($review_id, 'ReviewHelpful', strtotime('+1 month') - strtotime('now'));
 
 		if ( $spam_helper->InSpamControl() ) {
 			return;
 		}
 
 		$field = (int)$this->Application->GetVar('helpful') ? 'HelpfulCount' : 'NotHelpfulCount';
 
 		$sql = 'UPDATE ' . $this->Application->getUnitOption('rev', 'TableName') . '
 				SET ' . $field . ' = ' . $field . ' + 1
 				WHERE ' . $this->Application->getUnitOption('rev', 'IDField') . ' = ' . $review_id;
 		$this->Conn->Query($sql);
 
 		if ( $this->Conn->getAffectedRows() ) {
 			// db was changed -> review with such ID exists
 			$spam_helper->AddToSpamControl();
 		}
 	}
 
 	/**
 	 * [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);
 		}
 	}
+
+	/**
+	 * Set's new unique resource id to user
+	 *
+	 * @param kEvent $event
+	 * @return void
+	 * @access protected
+	 */
+	protected function OnAfterItemValidate(kEvent &$event)
+	{
+		$object =& $event->getObject();
+		/* @var $object kDBItem */
+
+		$resource_id = $object->GetDBField('ResourceId');
+
+		if ( !$resource_id ) {
+			$object->SetDBField('ResourceId', $this->Application->NextResourceId());
+		}
+	}
 }
\ No newline at end of file
Index: branches/5.2.x/core/units/categories/categories_event_handler.php
===================================================================
--- branches/5.2.x/core/units/categories/categories_event_handler.php	(revision 14718)
+++ branches/5.2.x/core/units/categories/categories_event_handler.php	(revision 14719)
@@ -1,2788 +1,2846 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CategoriesEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnRebuildCache' => Array ('self' => 'add|edit'),
 				'OnCopy' => Array ('self' => true),
 				'OnCut' => Array ('self' => 'edit'),
 				'OnPasteClipboard' => Array ('self' => true),
 				'OnPaste' => Array ('self' => 'add|edit', 'subitem' => 'edit'),
 
 				'OnRecalculatePriorities' => Array ('self' => 'add|edit'), // category ordering
 				'OnItemBuild' => Array ('self' => true), // always allow to view individual categories (regardless of CATEGORY.VIEW right)
 				'OnUpdatePreviewBlock' => Array ('self' => true), // for FCKEditor integration
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Categories are sorted using special sorting event
 		 *
 		 */
 		function mapEvents()
 		{
 			parent::mapEvents();
 
 			$events_map = Array (
 				'OnMassMoveUp' => 'OnChangePriority',
 				'OnMassMoveDown' => 'OnChangePriority',
 			);
 
 			$this->eventMethods = array_merge($this->eventMethods, $events_map);
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(&$event)
 		{
 			if ($event->Name == 'OnResetCMSMenuCache') {
 				// events from "Tools -> System Tools" section are controlled via that section "edit" permission
 
 				$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				$perm_value = $this->Application->CheckPermission('in-portal:service.edit');
 
 				return $perm_helper->finalizePermissionCheck($event, $perm_value);
 			}
 
 			if (!$this->Application->isAdmin) {
 				if ($event->Name == 'OnSetSortingDirect') {
 					// allow sorting on front event without view permission
 					return true;
 				}
 
 				if ($event->Name == 'OnItemBuild') {
 					$category_id = $this->getPassedID($event);
 					if ($category_id == 0) {
 						return true;
 					}
 				}
 			}
 
 			if (in_array($event->Name, $this->_getMassPermissionEvents())) {
 				$items = $this->_getPermissionCheckInfo($event);
 
 				$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				if (($event->Name == 'OnSave') && array_key_exists(0, $items)) {
 					// adding new item (ID = 0)
 					$perm_value = $perm_helper->AddCheckPermission($items[0]['ParentId'], $event->Prefix) > 0;
 				}
 				else {
 					// leave only items, that can be edited
 					$ids = Array ();
 					$check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission';
 					foreach ($items as $item_id => $item_data) {
 						if ($perm_helper->$check_method($item_data['CreatedById'], $item_data['ParentId'], $event->Prefix) > 0) {
 							$ids[] = $item_id;
 						}
 					}
 
 					if (!$ids) {
 						// no items left for editing -> no permission
 						return $perm_helper->finalizePermissionCheck($event, false);
 					}
 
 					$perm_value = true;
 					$event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method
 				}
 
 				return $perm_helper->finalizePermissionCheck($event, $perm_value);
 			}
 
 			if ($event->Name == 'OnRecalculatePriorities') {
 				$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				$category_id = $this->Application->GetVar('m_cat_id');
 
 				return $perm_helper->AddCheckPermission($category_id, $event->Prefix) || $perm_helper->ModifyCheckPermission(0, $category_id, $event->Prefix);
 			}
 
 			if ($event->Name == 'OnPasteClipboard') {
 				// forces permission check to work by current category for "Paste In Category" operation
 				$category_id = $this->Application->GetVar('m_cat_id');
 				$this->Application->SetVar('c_id', $category_id);
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Returns events, that require item-based (not just event-name based) permission check
 		 *
 		 * @return Array
 		 */
 		function _getMassPermissionEvents()
 		{
 			return Array (
 				'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove',
 				'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown',
 				'OnCut',
 			);
 		}
 
 		/**
 		 * Returns category item IDs, that require permission checking
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
 		function _getPermissionCheckIDs(&$event)
 		{
 			if ($event->Name == 'OnSave') {
 				$selected_ids = implode(',', $this->getSelectedIDs($event, true));
 				if (!$selected_ids) {
 					$selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave)
 				}
 			}
 			else {
 				// OnEdit, OnMassDelete events, when items are checked in grid
 				$selected_ids = implode(',', $this->StoreSelectedIDs($event));
 			}
 
 			return $selected_ids;
 		}
 
 		/**
 		 * Returns information used in permission checking
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 */
 		function _getPermissionCheckInfo(&$event)
 		{
 			// when saving data from temp table to live table check by data from temp table
 			$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->getBaseCategory();
 
 			$this->Application->StoreVar('IsRootCategory_'.$this->Application->GetVar('m_wid'), ($category_id === '0') || ($category_id == $home_category));
 
 			parent::OnEdit($event);
 
 			if ($event->status == kEvent::erSUCCESS) {
 				// keep "Section Properties" link (in browse modes) clean
 				$this->Application->DeleteVar('admin');
 			}
 		}
 
 		/**
 		 * Adds selected link to listing
 		 *
 		 * @param kEvent $event
 		 */
 		function OnProcessSelected(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$selected_ids = $this->Application->GetVar('selected_ids');
 
 			$this->RemoveRequiredFields($object);
 			$object->SetDBField($this->Application->RecallVar('dst_field'), $selected_ids['c']);
 			$object->Update();
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Apply system filter to categories list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(&$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->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', '%1$s.ParentId = '.$parent_cat_id);
 				}
 			}
 
 			$object->addFilter('perm_filter', TABLE_PREFIX . 'PermCache.PermId = 1'); // check for CATEGORY.VIEW permission
 			if ($this->Application->RecallVar('user_id') != USER_ROOT) {
 				// apply permission filters to all users except "root"
 				$view_filters = Array ();
 				$groups = explode(',',$this->Application->RecallVar('UserGroups'));
 
 				foreach ($groups as $group) {
 					$view_filters[] = 'FIND_IN_SET('.$group.', ' . TABLE_PREFIX . 'PermCache.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));
 				/* @var $p_item kCatDBItem */
 
 				$p_item->Load( (int)$id );
 
 				$p_resource_id = $p_item->GetDBField('ResourceId');
 
 				$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
 						WHERE
 							(Enabled = 1)
 							AND (
 									(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 									OR
 									(Type = 1
 										AND (
 												(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 												OR
 												(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
 											)
 									)
 							)';
 
 				$related_ids_array = $this->Conn->Query($sql);
 				$related_ids = Array();
 
 				foreach ($related_ids_array as $key => $record) {
 					$related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ];
 				}
 
 				if (count($related_ids) > 0) {
 					$type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).')';
 					$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).')';
 				}
 				else {
 					$type_clauses['related']['include'] = '0';
 					$type_clauses['related']['except'] = '1';
 				}
 
 				$type_clauses['related']['having_filter'] = false;
 			}
 
 			if (in_array('category_related', $type_clauses)) {
 				$object->removeFilter('parent_filter');
 				$resource_id = $this->Conn->GetOne('
 								SELECT ResourceId FROM '.$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'
 				);
 
 				$keywords = $event->getEventParam('keyword_string');
 				$type = $this->Application->GetVar('search_type', 'simple');
 
 				if ( $keywords ) {
 					// processing keyword_string param of ListProducts tag
 					$this->Application->SetVar('keywords', $keywords);
 					$type = 'simple';
 				}
 
 				$search_event = $event_mapping[$type];
 				$this->$search_event($event);
 
 				$object =& $event->getObject();
 				/* @var $object kDBList */
 
 				$search_sql = '	FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search
 								search_result LEFT JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
 				$sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL());
 
 				$object->SetSelectSQL($sql);
 
 				$object->addCalculatedField('Relevance', 'search_result.Relevance');
 				$object->AddOrderField('search_result.Relevance', 'desc', true);
 
 				$type_clauses['search']['include'] = '1';
 				$type_clauses['search']['except'] = '0';
 				$type_clauses['search']['having_filter'] = false;
 			}
 
 			$search_helper =& $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$search_helper->SetComplexFilter($event, $type_clauses, implode(',', $types), implode(',', $except_types));
 		}
 
 		/**
 		 * 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 CategoriesItem */
 
 					$created = $this->_prepareAutoPage($object, $template, null, SMS_MODE_AUTO); // create virtual (not system!) page
 					if ($created) {
 						if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild') || !$this->Application->isAdmin) {
 							$updater =& $this->Application->makeClass('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
 		 * @return void
 		 * @access protected
 		 */
 		protected 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)
 		{
 			$object =& $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true, 'live_table' => true));
 			/* @var $object CategoriesItem */
 
 			$parent_path = false;
 			$object->Load( $event->getEventParam('id') );
 
 			if ( $event->getEventParam('temp_id') == 0 ) {
 				if ( $object->isLoaded() ) {
 					// update path only for real categories (not including "Home" root category)
 					$fields_hash = 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->makeClass('kPermCacheUpdater', Array (null, $parent_path));
 				/* @var $cache_updater kPermCacheUpdater */
 
 				$cache_updater->OneStepRun();
 			}
 		}
 
 		/**
 		 * Set cache modification mark if needed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeDeleteFromLive(&$event)
 		{
 			parent::OnBeforeDeleteFromLive($event);
 
 			$id = $event->getEventParam('id');
 
 			// loading anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event
 			$temp_object =& $event->getObject(Array ('skip_autoload' => true));
 			/* @var $temp_object CategoriesItem */
 
 			$temp_object->Load($id);
 
 			if ( $id == 0 ) {
 				if ( $temp_object->isLoaded() ) {
 					// new category -> update cache (not loaded when "Home" category)
 					$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 				}
 
 				return ;
 			}
 
 			// existing category was edited, check if in-cache fields are modified
 			$live_object =& $this->Application->recallObject($event->Prefix . '.-item', null, Array ('live_table' => true, 'skip_autoload' => true));
 			/* @var $live_object CategoriesItem */
 
 			$live_object->Load($id);
 			$cached_fields = Array ('l' . $this->Application->GetDefaultLanguageId() . '_Name', 'Filename', 'Template', 'ParentId', 'Priority');
 
 			foreach ($cached_fields as $cached_field) {
 				if ( $live_object->GetDBField($cached_field) != $temp_object->GetDBField($cached_field) ) {
 					// use session instead of REQUEST because of permission editing in category can contain
 					// multiple submits, that changes data before OnSave event occurs
 					$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 					break;
 				}
 			}
 
 			// remember category filename change between temp and live records
 			if ( $temp_object->GetDBField('Filename') != $live_object->GetDBField('Filename') ) {
 				$filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ());
 
 				$filename_changes[ $live_object->GetID() ] = Array (
 					'from' => $live_object->GetDBField('Filename'),
 					'to' => $temp_object->GetDBField('Filename')
 				);
 
 				$this->Application->SetVar($event->Prefix . '_filename_changes', $filename_changes);
 			}
 		}
 
 		/**
 		 * Calls kDBEventHandler::OnSave original event
 		 * Used in proj-cms:StructureEventHandler->OnSave
 		 *
 		 * @param kEvent $event
 		 */
 		function parentOnSave(&$event)
 		{
 			parent::OnSave($event);
 		}
 
 		/**
 		 * Reset root-category flag when new category is created
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(&$event)
 		{
 			// 1. for permission editing of Home category
 			$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 
 			parent::OnPreCreate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			// 2. preset template
 			$category_id = $this->Application->GetVar('m_cat_id');
 			$root_category = $this->Application->getBaseCategory();
 
 			if ( $category_id == $root_category ) {
 				$object->SetDBField('Template', $this->_getDefaultDesign());
 			}
 
 			// 3. set default owner
 			$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 		}
 
 		/**
 		 * Checks cache update mark and redirect to cache if needed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSave(&$event)
 		{
 			// get data from live table before it is overwritten by parent OnSave method call
 			$ids = $this->getSelectedIDs($event, true);
 			$is_editing = implode('', $ids);
 			$old_statuses = $is_editing ? $this->_getCategoryStatus($ids) : Array ();
 
 			$object =& $event->getObject();
 			/* @var $object CategoriesItem */
 
 			parent::OnSave($event);
 
 			if ( $event->status != kEvent::erSUCCESS ) {
 				return;
 			}
 
 			if ( $this->Application->RecallVar('PermCache_UpdateRequired') ) {
 				$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 			}
 
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 			$this->_resetMenuCache();
 
 			if ( $is_editing ) {
 				// send email event to category owner, when it's status is changed (from admin)
 				$object->SwitchToLive();
 				$new_statuses = $this->_getCategoryStatus($ids);
 				$process_statuses = Array (STATUS_ACTIVE, STATUS_DISABLED);
 
 				foreach ($new_statuses as $category_id => $new_status) {
 					if ( $new_status != $old_statuses[$category_id] && in_array($new_status, $process_statuses) ) {
 						$object->Load($category_id);
 						$email_event = $new_status == STATUS_ACTIVE ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
 						$this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById'));
 					}
 				}
 			}
 
 			// change opener stack in case if edited category filename was changed
 			$filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ());
 
 			if ( $filename_changes ) {
 				$opener_stack =& $this->Application->makeClass('kOpenerStack');
 				/* @var $opener_stack kOpenerStack */
 
 				list ($template, $params, $index_file) = $opener_stack->pop();
 
 				foreach ($filename_changes as $change_info) {
 					$template = str_ireplace($change_info['from'], $change_info['to'], $template);
 				}
 
 				$opener_stack->push($template, $params, $index_file);
 				$opener_stack->save();
 			}
 		}
 
 		/**
 		 * Returns statuses of given categories
 		 *
 		 * @param Array $category_ids
 		 * @return Array
 		 */
 		function _getCategoryStatus($category_ids)
 		{
 			$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 success
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected 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;
 			}
 
 			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
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(&$event, $type)
 		{
 			if ( $event->Name == 'OnMassDelete' && $type == 'before' ) {
 				$ids = $event->getEventParam('ids');
 				if ( !$ids || $this->Application->ConfigValue('AllowDeleteRootCats') ) {
 					return;
 				}
 
 				$root_categories = Array ();
 
 				// get module root categories and exclude them
 				foreach ($this->Application->ModuleInfo as $module_info) {
 					$root_categories[] = $module_info['RootCat'];
 				}
 
 				$root_categories = array_unique($root_categories);
 
 				if ( $root_categories && array_intersect($ids, $root_categories) ) {
 					$event->setEventParam('ids', array_diff($ids, $root_categories));
 					$this->Application->StoreVar('root_delete_error', 1);
 				}
 			}
 		}
 
 		/**
 		 * Checks, that given template exists (physically) in given theme
 		 *
 		 * @param string $template
 		 * @param int $theme_id
 		 * @return bool
 		 */
 		function _templateFound($template, $theme_id = null)
 		{
 			static $init_made = false;
 
 			if (!$init_made) {
 				$this->Application->InitParser(true);
 				$init_made = true;
 			}
 
 			if (!isset($theme_id)) {
 				$theme_id = $this->_getCurrentThemeId();
 			}
 
 			$theme_name = $this->_getThemeName($theme_id);
 
 			return $this->Application->TemplatesCache->TemplateExists('theme:' . $theme_name . '/' . $template);
 		}
 
 		/**
 		 * Removes ".tpl" in template path
 		 *
 		 * @param string $template
 		 * @return string
 		 */
 		function _stripTemplateExtension($template)
 		{
 	//		return preg_replace('/\.[^.\\\\\\/]*$/', '', $template);
 
 			return preg_replace('/^[\\/]{0,1}(.*)\.tpl$/', "$1", $template);
 		}
 
 		/**
 		 * Deletes all selected items.
 		 * Automatically recourse into sub-items using temp handler, and deletes sub-items
 		 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnMassDelete(&$event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return ;
 			}
 
 			$to_delete = Array ();
 			$ids = $this->StoreSelectedIDs($event);
 			$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 
 			if ( $recycle_bin ) {
 				$rb =& $this->Application->recallObject('c.recycle', null, Array ('skip_autoload' => true));
 				/* @var $rb CategoriesItem */
 
 				$rb->Load($recycle_bin);
 
 				$cat =& $event->getObject(Array ('skip_autoload' => true));
 				/* @var $cat CategoriesItem */
 
 				foreach ($ids as $id) {
 					$cat->Load($id);
 					if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $cat->GetDBField('ParentPath')) ) {
 						$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);
 
 			$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');
 			/* @var $clipboard_helper kClipboardHelper */
 
 			$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Add selected items to clipboard with mode = CUT
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCut(&$event)
 		{
 			$this->Application->RemoveVar('clipboard');
 
 			$clipboard_helper =& $this->Application->recallObject('ClipboardHelper');
 			/* @var $clipboard_helper kClipboardHelper */
 
 			$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Controls all item paste operations. Can occur only with filled clipboard.
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPasteClipboard(&$event)
 		{
 			$clipboard = unserialize( $this->Application->RecallVar('clipboard') );
 			foreach ($clipboard as $prefix => $clipboard_data) {
 				$paste_event = new kEvent($prefix.':OnPaste', Array('clipboard_data' => $clipboard_data));
 				$this->Application->HandleEvent($paste_event);
 
 				$event->copyFrom($paste_event);
 			}
 		}
 
 		/**
 		 * Checks permission for OnPaste event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function _checkPastePermission(&$event)
 		{
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$category_id = $this->Application->GetVar('m_cat_id');
 			if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) {
 				// no items left for editing -> no permission
 				return $perm_helper->finalizePermissionCheck($event, false);
 			}
 
 			return true;
 		}
 
 		/**
 		 * Paste categories with sub-items from clipboard
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPaste(&$event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$clipboard_data = $event->getEventParam('clipboard_data');
 
 			if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) {
 				return;
 			}
 
 			// 1. get ParentId of moved category(-es) before it gets updated!!!)
 			$source_category_id = 0;
 			$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'] ) {
 				$ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $source_category_id);
 
 				if ( $ids ) {
 					$priority_helper->massUpdateChanged($event->Prefix, $ids);
 				}
 			}
 
 			// recalculate priorities of newly pasted categories in destination category
 			$parent_id = $this->Application->GetVar('m_cat_id');
 			$ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id);
 
 			if ( $ids ) {
 				$priority_helper->massUpdateChanged($event->Prefix, $ids);
 			}
 
 			if ( $clipboard_data['cut'] || $clipboard_data['copy'] ) {
 				// rebuild with progress bar
 				if ( $this->Application->ConfigValue('QuickCategoryPermissionRebuild') ) {
 					$updater =& $this->Application->makeClass('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);
 			}
 		}*/
 
 		/**
 		 * Clears clipboard content
 		 *
 		 * @param kEvent $event
 		 */
 		function OnClearClipboard(&$event)
 		{
 			$this->Application->RemoveVar('clipboard');
 		}
 
 		/**
 		 * Sets correct status for new categories created on front-end
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(&$event)
 		{
 			parent::OnBeforeItemCreate($event);
 
+			$object =& $event->getObject();
+			/* @var $object CategoriesItem */
+
+			if ( $object->GetDBField('ParentId') <= 0 ) {
+				// no parent category - use current (happens during import)
+				$object->SetDBField('ParentId', $this->Application->GetVar('m_cat_id'));
+			}
+
 			$this->_beforeItemChange($event);
 
 			if ( $this->Application->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);
 			}
 			else {
 				$event->status = kEvent::erPERM_FAIL;
 				return ;
 			}
 		}
 
 		/**
 		 * Returns next available priority for given category from given table
 		 *
 		 * @param int $category_id
 		 * @param string $table_name
 		 * @return int
 		 */
 		function _getNextPriority($category_id, $table_name)
 		{
 			$sql = 'SELECT MIN(Priority)
 					FROM ' . $table_name . '
 					WHERE ParentId = ' . $category_id;
 			return (int)$this->Conn->GetOne($sql) - 1;
 		}
 
 		/**
 		 * Sets correct status for new categories created on front-end
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(&$event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
+			$this->_beforeItemChange($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 != kEvent::erSUCCESS) {
 				// don't sent email or rebuild cache directly after category is created by admin
 				return ;
 			}
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$cache_updater =& $this->Application->makeClass('kPermCacheUpdater', Array (null, $object->GetDBField('ParentPath')));
 			/* @var $cache_updater kPermCacheUpdater */
 
 			$cache_updater->OneStepRun();
 
 			$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
 
 			$next_template = $is_active ? 'suggest_confirm_template' : 'suggest_pending_confirm_template';
 			$event->redirect = $this->Application->GetVar($next_template);
 			$event->SetRedirectParam('opener', 's');
 
 			// send email events
 			$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();
 						/* @var $object kDBList */
 
 						$object->SetPage($page);
 					}
 				}
 			}
 		}
 
 		/**
 		 * Apply same processing to each item being selected in grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function iterateItems(&$event)
 		{
 			if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) {
 				parent::iterateItems($event);
 			}
 
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$object =& $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object CategoriesItem */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ( $ids ) {
 				$propagate_category_status = $this->Application->GetVar('propagate_category_status');
 				$status_field = 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 = kEvent::erSUCCESS;
 
 						$email_event = $event->Name == 'OnMassApprove' ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
 						$this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById'));
 					}
 					else {
 						$event->status = kEvent::erFAIL;
 						$event->redirect = false;
 						break;
 					}
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 		}
 
 		/**
 		 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function checkItemStatus(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->isLoaded() ) {
 				return true;
 			}
 
 			if ( $object->GetDBField('Status') != STATUS_ACTIVE && $object->GetDBField('Status') != 4 ) {
 				if ( !$object->GetDBField('DirectLinkEnabled') || !$object->GetDBField('DirectLinkAuthKey') ) {
 					return false;
 				}
 
 				return $this->Application->GetVar('authkey') == $object->GetDBField('DirectLinkAuthKey');
 			}
 
 			return true;
 		}
 
 		// ============= for cms page processing =======================
 
 		/**
 		 * Returns default design template
 		 *
 		 * @return string
 		 */
 		function _getDefaultDesign()
 		{
 			$default_design = trim($this->Application->ConfigValue('cms_DefaultDesign'), '/');
 
 			if (!$default_design) {
 				// theme-based alias for default design
 				return '#default_design#';
 			}
 
 			if (strpos($default_design, '#') === false) {
 				// real template, not alias, so prefix with "/"
 				return '/' . $default_design;
 			}
 
 			// alias
 			return $default_design;
 		}
 
 		/**
 		 * Returns default design based on given virtual template (used from kApplication::Run)
 		 *
 		 * @param string $t
 		 * @return string
 		 * @access public
 		 */
 		public function GetDesignTemplate($t = null)
 		{
 			if ( !isset($t) ) {
 				$t = $this->Application->GetVar('t');
 			}
 
 			$page =& $this->Application->recallObject($this->Prefix . '.-virtual', null, Array ('page' => $t));
 			/* @var $page CategoriesItem */
 
 			if ( $page->isLoaded() ) {
 				$real_t = $page->GetDBField('CachedTemplate');
 				$this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId'));
 
 				if ( $page->GetDBField('FormId') ) {
 					$this->Application->SetVar('form_id', $page->GetDBField('FormId'));
 				}
 			}
 			else {
 				$not_found = $this->Application->ConfigValue('ErrorTemplate');
 				$real_t = $not_found ? $not_found : 'error_notfound';
 
 				$themes_helper =& $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				$theme_id = $this->Application->GetVar('m_theme');
 				$category_id = $themes_helper->getPageByTemplate($real_t, $theme_id);
 				$this->Application->SetVar('m_cat_id', $category_id);
 
 				header('HTTP/1.0 404 Not Found');
 			}
 
 			// replace alias in form #alias_name# to actual template used in this theme
 			$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 */
+			/* @var $object CategoriesItem */
+
+			$object->checkFilename();
+			$object->generateFilename();
 
 			$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'));
 				}
 			}
 
+			$is_admin = $this->Application->isAdminUser;
+
+			if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField('CreatedById')) ) {
+				$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
+			}
+
 			if ($object->GetChangedFields()) {
 				$object->SetDBField('Modified_date', $now);
 				$object->SetDBField('Modified_time', $now);
 			}
 
 			$object->setRequired('PageCacheKey', $object->GetDBField('OverridePageCacheKey'));
 			$object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') ));
 
 			if ($object->GetDBField('Type') == PAGE_TYPE_TEMPLATE) {
 				if (!$this->_templateFound($object->GetDBField('Template'), $object->GetDBField('ThemeId'))) {
 					$object->SetError('Template', 'template_file_missing', 'la_error_TemplateFileMissing');
 				}
 			}
 
 			$this->_saveTitleField($object, 'Title');
 			$this->_saveTitleField($object, 'MenuTitle');
 
 			$root_category = $this->Application->getBaseCategory();
 
 			if ( file_exists(FULL_PATH . '/themes') && ($object->GetDBField('ParentId') == $root_category) && ($object->GetDBField('Template') == CATEGORY_TEMPLATE_INHERIT) ) {
 				// there are themes + creating top level category
 				$object->SetError('Template', 'no_inherit');
 			}
 
 			if ( !$this->Application->isAdminUser && $object->isVirtualField('cust_RssSource') ) {
 				// only administrator can set/change "cust_RssSource" field
 
 				if ($object->GetDBField('cust_RssSource') != $object->GetOriginalField('cust_RssSource')) {
 					$object->SetError('cust_RssSource', 'not_allowed', 'la_error_OperationNotAllowed');
 				}
 			}
 
 			if ( !$object->GetDBField('DirectLinkAuthKey') ) {
 				$key_parts = Array (
 					$object->GetID(),
 					$object->GetDBField('ParentId'),
 					$object->GetField('Name'),
 					'b38'
 				);
 
 				$object->SetDBField('DirectLinkAuthKey', substr( md5( implode(':', $key_parts) ), 0, 20 ));
 			}
 		}
 
 		/**
 		 * Sets page name to requested field in case when:
 		 * 1. page was auto created (through theme file rebuild)
 		 * 2. requested field is empty
 		 *
 		 * @param kDBItem $object
 		 * @param string $field
 		 * @author Alex
 		 */
 		function _saveTitleField(&$object, $field)
 		{
 			$value = $object->GetField($field, 'no_default'); // current value of target field
 
 			$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 			/* @var $ml_formatter kMultiLanguage */
 
 			$src_field = $ml_formatter->LangFieldName('Name');
 			$dst_field = $ml_formatter->LangFieldName($field);
 
 			$dst_field_not_changed = $object->GetOriginalField($dst_field) == $value;
 
 			if ($value == '' || preg_match('/^_Auto: (.*)/', $value) || (($object->GetOriginalField($src_field) == $value) && $dst_field_not_changed)) {
 				// target field is empty OR target field value starts with "_Auto: " OR (source field value
 				// before change was equals to current target field value AND target field value wasn't changed)
 				$object->SetField($dst_field, $object->GetField($src_field));
 			}
 		}
 
 		/**
 		 * Don't allow to delete system pages, when not in debug mode
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeItemDelete(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->GetDBField('Protected') && !$this->Application->isDebugMode() ) {
 				$event->status = kEvent::erFAIL;
 			}
 		}
 
 		/**
 		 * Creates category based on given TPL file
 		 *
 		 * @param CategoriesItem $object
 		 * @param string $template
 		 * @param int $theme_id
 		 * @param int $system_mode
 		 * @param array $template_info
 		 * @return bool
 		 */
 		function _prepareAutoPage(&$object, $template, $theme_id = null, $system_mode = SMS_MODE_AUTO, $template_info = Array ())
 		{
 			$template = $this->_stripTemplateExtension($template);
 
 			if ($system_mode == SMS_MODE_AUTO) {
 				$page_type = $this->_templateFound($template, $theme_id) ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL;
 			}
 			else {
 				$page_type = $system_mode == SMS_MODE_FORCE ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL;
 			}
 
 			if (($page_type == PAGE_TYPE_TEMPLATE) && ($template_info === false)) {
 				// do not auto-create system pages, when browsing through site
 				return false;
 			}
 
 			if (!isset($theme_id)) {
 				$theme_id = $this->_getCurrentThemeId();
 			}
 
 			$root_category = $this->Application->getBaseCategory();
 			$page_category = $this->Application->GetVar('m_cat_id');
 			if (!$page_category) {
 				$page_category = $root_category;
 				$this->Application->SetVar('m_cat_id', $page_category);
 			}
 
 			if (($page_type == PAGE_TYPE_VIRTUAL) && (strpos($template, '/') !== false)) {
 				// virtual page, but have "/" in template path -> create it's path
 				$category_path = explode('/', $template);
 				$template = array_pop($category_path);
 
 				$page_category = $this->_getParentCategoryFromPath($category_path, $root_category, $theme_id);
 			}
 
 			$page_name = ($page_type == PAGE_TYPE_TEMPLATE) ? '_Auto: ' . $template : $template;
 			$page_description = '';
 
 			if ($page_type == PAGE_TYPE_TEMPLATE) {
 				$design_template = strtolower($template); // leading "/" not added !
 				if ($template_info) {
 					if (array_key_exists('name', $template_info) && $template_info['name']) {
 						$page_name = $template_info['name'];
 					}
 
 					if (array_key_exists('desc', $template_info) && $template_info['desc']) {
 						$page_description = $template_info['desc'];
 					}
 
 					if (array_key_exists('section', $template_info) && $template_info['section']) {
 						// this will override any global "m_cat_id"
 						$page_category = $this->_getParentCategoryFromPath(explode('||', $template_info['section']), $root_category, $theme_id);
 					}
 				}
 			}
 			else {
 				$design_template = $this->_getDefaultDesign(); // leading "/" added !
 			}
 
 			$object->Clear();
 			$object->SetDBField('ParentId', $page_category);
 			$object->SetDBField('Type', $page_type);
 			$object->SetDBField('Protected', 1); // $page_type == PAGE_TYPE_TEMPLATE
 
 			$object->SetDBField('IsMenu', 0);
 			$object->SetDBField('ThemeId', $theme_id);
 
 			// put all templates to then end of list (in their category)
 			$min_priority = $this->_getNextPriority($page_category, $object->TableName);
 			$object->SetDBField('Priority', $min_priority);
 
 			$object->SetDBField('Template', $design_template);
 			$object->SetDBField('CachedTemplate', $design_template);
 
 			$primary_language = $this->Application->GetDefaultLanguageId();
 			$current_language = $this->Application->GetVar('m_lang');
 			$object->SetDBField('l' . $primary_language . '_Name', $page_name);
 			$object->SetDBField('l' . $current_language . '_Name', $page_name);
 			$object->SetDBField('l' . $primary_language . '_Description', $page_description);
 			$object->SetDBField('l' . $current_language . '_Description', $page_description);
 
 			return $object->Create();
 		}
 
 		function _getParentCategoryFromPath($category_path, $base_category, $theme_id = null)
 		{
 			static $category_ids = Array ();
 
 			if (!$category_path) {
 				return $base_category;
 			}
 
 			if (array_key_exists(implode('||', $category_path), $category_ids)) {
 				return $category_ids[ implode('||', $category_path) ];
 			}
 
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			$object =& $this->Application->recallObject($this->Prefix . '.rebuild-path', null, Array ('skip_autoload' => true));
 			/* @var $object CategoriesItem */
 
 			$parent_id = $base_category;
 
 			$filenames_helper =& $this->Application->recallObject('FilenamesHelper');
 			/* @var $filenames_helper kFilenamesHelper */
 
 			$safe_category_path = array_map(Array (&$filenames_helper, 'replaceSequences'), $category_path);
 
 			foreach ($category_path as $category_order => $category_name) {
 				$this->Application->SetVar('m_cat_id', $parent_id);
 
 				// get virtual category first, when possible
 				$sql = 'SELECT ' . $object->IDField . '
 						FROM ' . $object->TableName . '
 						WHERE
 							(
 								Filename = ' . $this->Conn->qstr($safe_category_path[$category_order]) . ' OR
 								Filename = ' . $this->Conn->qstr( $filenames_helper->replaceSequences('_Auto: ' . $category_name) ) . '
 							) AND
 							(ParentId = ' . $parent_id . ') AND
 							(ThemeId = 0 OR ThemeId = ' . $theme_id . ')
 						ORDER BY ThemeId ASC';
 				$parent_id = $this->Conn->GetOne($sql);
 
 				if ($parent_id === false) {
 					// page not found
 					$template = implode('/', array_slice($safe_category_path, 0, $category_order + 1));
 
 					// don't process system templates in sub-categories
 					$system = $this->_templateFound($template, $theme_id) && (strpos($template, '/') === false);
 
 					if (!$this->_prepareAutoPage($object, $category_name, $theme_id, $system ? SMS_MODE_FORCE : false)) {
 						// page was not created
 						break;
 					}
 
 					$parent_id = $object->GetID();
 				}
 			}
 
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 			$category_ids[ implode('||', $category_path) ] = $parent_id;
 
 			return $parent_id;
 		}
 
 		/**
 		 * Returns theme name by it's id. Used in structure page creation.
 		 *
 		 * @param int $theme_id
 		 * @return string
 		 */
 		function _getThemeName($theme_id)
 		{
 			static $themes = null;
 
 			if (!isset($themes)) {
 				$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 = kEvent::erSTOP;
 			}
 
 			$this->_resetMenuCache();
 			$event->SetRedirectParam('action_completed', 1);
 		}
 
 		/**
 		 * Performs reset of category-related caches (menu, structure dropdown, template mapping)
 		 *
 		 * @return void
 		 * @access protected
 		 */
 		protected function _resetMenuCache()
 		{
 			// reset cms menu cache (all variables are automatically rebuild, when missing)
 			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 				$this->Application->rebuildCache('master:cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime);
 				$this->Application->rebuildCache('master:StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime);
 				$this->Application->rebuildCache('master:template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime);
 			}
 			else {
 				$this->Application->rebuildDBCache('cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime);
 				$this->Application->rebuildDBCache('StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime);
 				$this->Application->rebuildDBCache('template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime);
 			}
 		}
 
 		/**
 		 * Updates structure config
 		 *
 		 * @param kEvent $event
 		 */
 		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->getBaseCategory();
 
 			// set root category
 			$section_adjustments = $this->Application->getUnitOption($event->Prefix, 'SectionAdjustments');
 
 			$section_adjustments['in-portal:browse'] = Array (
 				'url' => Array ('m_cat_id' => $root_category),
 				'late_load' => Array ('m_cat_id' => $root_category),
 				'onclick' => 'checkCatalog(' . $root_category . ')',
 			);
 
 			$section_adjustments['in-portal:browse_site'] = Array (
 				'url' => Array ('editing_mode' => $settings['default_editing_mode']),
 			);
 
 			$this->Application->setUnitOption($event->Prefix, 'SectionAdjustments', $section_adjustments);
 
 			// 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
 			$theme_id = $this->_getCurrentThemeId();
 			$design_sql = $fields['Template']['options_sql'];
 			$design_sql = str_replace('(tf.FilePath = "/designs")', '(' . implode(' OR ', $this->getDesignFolders()) . ')' . ' AND (t.ThemeId = ' . $theme_id . ')', $design_sql);
 			$fields['Template']['options_sql'] = $design_sql;
 
 			// adds "Inherit From Parent" option to "Template" field
 			$fields['Template']['options'] = Array (CATEGORY_TEMPLATE_INHERIT => $this->Application->Phrase('la_opt_InheritFromParent'));
 
 			$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', Array ());
 				$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);
 		}
 
 		/**
 		 * Returns folders, that can contain design templates
 		 *
 		 * @return array
 		 * @access protected
 		 */
 		protected function getDesignFolders()
 		{
 			$ret = Array ('tf.FilePath = "/designs"', 'tf.FilePath = "/platform/designs"');
 
 			foreach ($this->Application->ModuleInfo as $module_info) {
 				$ret[] = 'tf.FilePath = "/' . $module_info['TemplatePath'] . 'designs"';
 			}
 
 			return array_unique($ret);
 		}
 
 		/**
 		 * Removes this item and it's children (recursive) from structure dropdown
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(&$event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			if ( !$this->Application->isAdmin ) {
 				// calculate priorities dropdown only for admin
 				return;
 			}
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			// remove this category & it's children from dropdown
 			$sql = 'SELECT ' . $object->IDField . '
 					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
 					WHERE ParentPath LIKE "' . $object->GetDBField('ParentPath') . '%"';
 			$remove_categories = $this->Conn->GetCol($sql);
 
 			$options = $object->GetFieldOption('ParentId', 'options');
 			foreach ($remove_categories as $remove_category) {
 				unset($options[$remove_category]);
 			}
 			$object->SetFieldOption('ParentId', 'options', $options);
 		}
 
 		/**
+		 * Occurs after creating item
+		 *
+		 * @param kEvent $event
+		 * @return void
+		 * @access protected
+		 */
+		protected function OnAfterItemCreate(&$event)
+		{
+			parent::OnAfterItemCreate($event);
+
+			$object =& $event->getObject();
+			/* @var $object CategoriesItem */
+
+			// need to update path after category is created, so category is included in that path
+			$parent_path = $object->buildParentPath();
+
+			$sql = 'UPDATE ' . $object->TableName . '
+					SET ParentPath = ' . $this->Conn->qstr($parent_path) . '
+					WHERE CategoryId = ' . $object->GetID();
+			$this->Conn->Query($sql);
+
+			$object->SetDBField('ParentPath', $parent_path);
+		}
+
+		/**
 		 * 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 CategoriesItem */
 
 			$error_count = 0;
 			foreach ($files as $a_file => $file_info) {
 				$status = $this->_prepareAutoPage($dummy, $a_file, $file_info['ThemeId'], SMS_MODE_FORCE, unserialize($file_info['FileMetaInfo'])); // create system page
 				if (!$status) {
 					$error_count++;
 				}
 			}
 
 			if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) {
 				$updater =& $this->Application->makeClass('kPermCacheUpdater');
 				/* @var $updater kPermCacheUpdater */
 
 				$updater->OneStepRun();
 			}
 
 			$this->_resetMenuCache();
 
 			if ($error_count) {
 				// allow user to review error after structure page creation
 				$event->MasterEvent->redirect = false;
 			}
 		}
 
 		/**
 		 * Processes OnMassMoveUp, OnMassMoveDown events
 		 *
 		 * @param kEvent $event
 		 */
 		function OnChangePriority(&$event)
 		{
 			$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
 			$event->CallSubEvent('priority:' . $event->Name);
 
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 			$this->_resetMenuCache();
 		}
 
 		/**
 		 * Completely recalculates priorities in current category
 		 *
 		 * @param kEvent $event
 		 */
 		function OnRecalculatePriorities(&$event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
 			$event->CallSubEvent('priority:' . $event->Name);
 
 			$this->_resetMenuCache();
 		}
 
 		/**
 		 * Update Preview Block for FCKEditor
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdatePreviewBlock(&$event)
 		{
 			$event->status = kEvent::erSTOP;
 			$string = kUtil::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 = kUtil::unhtmlentities( trim($this->Application->GetVar('keywords')) );
 
 			$query_object =& $this->Application->recallObject('HTTPQuery');
 			/* @var $query_object kHTTPQuery */
 
 			$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 			if ( !isset($query_object->Get['keywords']) && !isset($query_object->Post['keywords']) && $this->Conn->Query($sql) ) {
 				// used when navigating by pages or changing sorting in search results
 				return;
 			}
 
 			if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length'))
 			{
 				$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
 				$this->Application->SetVar('keywords_too_short', 1);
 				return; // if no or too short keyword entered, doing nothing
 			}
 
 			$this->Application->StoreVar('keywords', $keywords);
 
 			$this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search
 
 			$keywords = strtr($keywords, Array('%' => '\\%', '_' => '\\_'));
 
 			$event->setPseudoClass('_List');
 
 			$object =& $event->getObject();
 			/* @var $object kDBList */
 
 			$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) {
 				$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
 				$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
 
 				// processing multilingual fields
 				if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
 					$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 					$field_list[$key] = 'l'.$lang.'_'.$field;
 
 					if (!isset($search_config[$field]['ForeignField'])) {
 						$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 				}
 
 				// processing fields from other tables
 				$foreign_field = $search_config[$field]['ForeignField'];
 
 				if ( $foreign_field ) {
 					$exploded = explode(':', $foreign_field, 2);
 					if ($exploded[0] == 'CALC') {
 						// ignoring having type clauses in simple search
 						unset($field_list[$key]);
 						continue;
 					}
 					else {
 						$multi_lingual = false;
 						if ($exploded[0] == 'MULTI') {
 							$multi_lingual = true;
 							$foreign_field = $exploded[1];
 						}
 
 						$exploded = explode('.', $foreign_field);	// format: table.field_name
 						$foreign_table = TABLE_PREFIX.$exploded[0];
 
 						$alias_counter++;
 						$alias = 't'.$alias_counter;
 
 						if ($multi_lingual) {
 							$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
 							$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 							$search_config_map[ $field_list[$key] ] = $field;
 							$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 						}
 						else {
 							$field_list[$key] = $alias.'.'.$exploded[1];
 							$search_config_map[ $field_list[$key] ] = $field;
 						}
 
 						$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
 						$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 						$join_clauses[] = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 											ON '.$join_clause;
 					}
 				}
 				else {
 					// processing fields from local table
 					if ($search_config[$field]['CustomFieldId']) {
 						$local_table = 'custom_data';
 
 						// search by custom field value on current language
 						$custom_field_id = array_search($field_list[$key], $custom_fields);
 						$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
 
 						// search by custom field value on primary language
 						$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 
 					$field_list[$key] = $local_table.'.'.$field_list[$key];
 					$search_config_map[ $field_list[$key] ] = $field;
 				}
 			}
 
 			// keyword string processing
 			$search_helper =& $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$where_clause = Array ();
 			foreach ($field_list as $field) {
 				if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
 					// local real field
 					$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
 					if ($filter_data) {
 						$where_clause[] = $filter_data['value'];
 					}
 				}
 				elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
 					$custom_field_name = 'cust_' . $search_config_map[$field];
 					$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
 					if ($filter_data) {
 						$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
 					}
 				}
 				else {
 					$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
 				}
 			}
 
 			$where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below!
 
 			$where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')';
 
 			if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
 				if ($event->MasterEvent->getEventParam('ResultIds')) {
 					$where_clause .= ' AND '.$items_table.'.ResourceId IN ('.implode(',', $event->MasterEvent->getEventParam('ResultIds')).')';
 				}
 			}
 
 			// exclude template based sections from search results (ie. registration)
 			if ( $this->Application->ConfigValue('ExcludeTemplateSectionsFromSearch') ) {
 				$where_clause .= ' AND ' . $items_table . '.ThemeId = 0';
 			}
 
 			// making relevance clause
 			$positive_words = $search_helper->getPositiveKeywords($keywords);
 			$this->Application->StoreVar('highlight_keywords', serialize($positive_words));
 			$revelance_parts = Array();
 			reset($search_config);
 
 			foreach ($positive_words as $keyword_index => $positive_word) {
 				$positive_word = $search_helper->transformWildcards($positive_word);
 				$positive_words[$keyword_index] = $this->Conn->escape($positive_word);
 			}
 
 			foreach ($field_list as $field) {
 
 				if (!array_key_exists($field, $search_config_map)) {
 					$map_key = $search_config_map[$items_table . '.' . $field];
 				}
 				else {
 					$map_key = $search_config_map[$field];
 				}
 
 				$config_elem = $search_config[ $map_key ];
 				$weight = $config_elem['Priority'];
 
 				// search by whole words only ([[:<:]] - word boundary)
 				/*$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
 				foreach ($positive_words as $keyword) {
 					$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
 				}*/
 
 				// search by partial word matches too
 				$revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
 				foreach ($positive_words as $keyword) {
 					$revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
 				}
 			}
 
 			$revelance_parts = array_unique($revelance_parts);
 
 			$conf_postfix = $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 && $object->isField('Hits')) {
 				$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 			}
 			if ($rel_rating && $object->isField('CachedRating')) {
 				$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 			}
 
 			// building final search query
 			if (!$this->Application->GetVar('do_not_drop_search_table')) {
 				$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
 				$this->Application->SetVar('do_not_drop_search_table', true);
 			}
 
 
 			$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
 			if ($search_table_exists) {
 				$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
 			}
 			else {
 				$select_intro = 'CREATE TABLE '.$search_table.' AS ';
 			}
 
 			$edpick_clause = $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').' ORDER BY Relevance DESC';
 
 			$this->Conn->Query($sql);
 
 			if ( !$search_table_exists ) {
 				$sql = 'ALTER TABLE ' . $search_table . '
 						ADD INDEX (ResourceId),
 						ADD INDEX (Relevance)';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * 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
 		 * @return void
 		 * @access protected
 		 */
 		protected 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');
 				/* @var $actions Params */
 
 				$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
 			}
 			else {
 				$object->setID($id);
 			}
 		}
 
 		/**
 		 * Returns constrain for priority calculations
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @see PriorityEventHandler
 		 * @access protected
 		 */
 		protected function OnGetConstrainInfo(&$event)
 		{
 			$constrain = ''; // for OnSave
 
 			$event_name = $event->getEventParam('original_event');
 			$actual_event_name = $event->getEventParam('actual_event');
 
 			if ( $actual_event_name == 'OnSavePriorityChanges' || $event_name == 'OnAfterItemLoad' || $event_name == 'OnAfterItemDelete' ) {
 				$object =& $event->getObject();
 				/* @var $object kDBItem */
 
 				$constrain = 'ParentId = ' . $object->GetDBField('ParentId');
 			}
 			elseif ( $actual_event_name == 'OnPreparePriorities' ) {
 				$constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id');
 			}
 			elseif ( $event_name == 'OnSave' ) {
 				$constrain = '';
 			}
 			else {
 				$constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id');
 			}
 
 			$event->setEventParam('constrain_info', Array ($constrain, ''));
 		}
 
 		/**
 		 * Parses category part of url, build main part of url
 		 *
 		 * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE.
 		 * @param string $prefix Prefix, that listener uses for system integration
 		 * @param Array $params Params, that are used for url building or created during url parsing.
 		 * @param Array $url_parts Url parts to parse (only for parsing).
 		 * @param bool $keep_events Keep event names in resulting url (only for building).
 		 * @return bool|string|Array Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener.
 		 */
 		public function CategoryRewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix, &$params, &$url_parts, $keep_events = false)
 		{
 			if ($rewrite_mode == REWRITE_MODE_BUILD) {
 				return $this->_buildMainUrl($prefix, $params, $keep_events);
 			}
 
 			if ( $this->_parseFriendlyUrl($url_parts, $params) ) {
 				// friendly urls work like exact match only!
 				return false;
 			}
 
 			$this->_parseCategory($url_parts, $params);
 
 			return true;
 		}
 
 		/**
 		 * Build main part of every url
 		 *
 		 * @param string $prefix_special
 		 * @param Array $params
 		 * @param bool $keep_events
 		 * @return string
 		 */
 		protected function _buildMainUrl($prefix_special, &$params, $keep_events)
 		{
 			$ret = '';
 			list ($prefix) = explode('.', $prefix_special);
 
 			$rewrite_processor =& $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			$processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events);
 			if ($processed_params === false) {
 				return '';
 			}
 
 			// add language
 			if ($processed_params['m_lang'] && ($processed_params['m_lang'] != $rewrite_processor->primaryLanguageId)) {
 				$language_name = $this->Application->getCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]');
 				if ($language_name === false) {
 					$sql = 'SELECT PackName
 							FROM ' . TABLE_PREFIX . 'Language
 							WHERE LanguageId = ' . $processed_params['m_lang'];
 					$language_name = $this->Conn->GetOne($sql);
 
 					$this->Application->setCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]', $language_name);
 				}
 
 				$ret .= $language_name . '/';
 			}
 
 			// add theme
 			if ($processed_params['m_theme'] && ($processed_params['m_theme'] != $rewrite_processor->primaryThemeId)) {
 				$theme_name = $this->Application->getCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]');
 				if ($theme_name === false) {
 					$sql = 'SELECT Name
 							FROM ' . TABLE_PREFIX . 'Theme
 							WHERE ThemeId = ' . $processed_params['m_theme'];
 					$theme_name = $this->Conn->GetOne($sql);
 
 					$this->Application->setCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]', $theme_name);
 
 				}
 
 				$ret .= $theme_name . '/';
 			}
 
 			// inject custom url parts made by other rewrite listeners just after language/theme url parts
 			if ($params['inject_parts']) {
 				$ret .= implode('/', $params['inject_parts']) . '/';
 			}
 
 			// add category
 			if ($processed_params['m_cat_id'] > 0 && $params['pass_category']) {
 				$category_filename = $this->Application->getCategoryCache($processed_params['m_cat_id'], 'filenames');
 
 				preg_match('/^Content\/(.*)/i', $category_filename, $regs);
 
 				if ($regs) {
 					$template = array_key_exists('t', $params) ? $params['t'] : false;
 
 					if (strtolower($regs[1]) == strtolower($template)) {
 						// we could have category path like "Content/<template_path>" in this case remove template
 						$params['pass_template'] = false;
 					}
 
 					$ret .= $regs[1] . '/';
 				}
 
 				$params['category_processed'] = true;
 			}
 
 			// reset category page
 			$force_page_adding = false;
 			if (array_key_exists('reset', $params) && $params['reset']) {
 				unset($params['reset']);
 
 				if ($processed_params['m_cat_id']) {
 					$processed_params['m_cat_page'] = 1;
 					$force_page_adding = true;
 				}
 			}
 
 			if ((array_key_exists('category_processed', $params) && $params['category_processed'] && ($processed_params['m_cat_page'] > 1)) || $force_page_adding) {
 				// category name was added before AND category page number found
 				$ret = rtrim($ret, '/') . '_' . $processed_params['m_cat_page'] . '/';
 			}
 
 			$template = array_key_exists('t', $params) ? $params['t'] : false;
 			$category_template = ($processed_params['m_cat_id'] > 0) && $params['pass_category'] ? $this->Application->getCategoryCache($processed_params['m_cat_id'], 'category_designs') : '';
 
 			if ((strtolower($template) == '__default__') && ($processed_params['m_cat_id'] == 0)) {
 				// for "Home" category set template to index when not set
 				$template = 'index';
 			}
 
 			// remove template from url if it is category index cached template OR site homepage
 			if (($template == $category_template) || (mb_strtolower($template) == '__default__') || ($template == 'index')) {
 				// given template is also default template for this category OR '__default__' given OR site homepage
 				$params['pass_template'] = false;
 			}
 
 			if ($template && $params['pass_template']) {
 				$ret .= $template . '/';
 			}
 
 			return mb_strtolower( rtrim($ret, '/') );
 		}
 
 		/**
 		 * Checks if whole url_parts matches a whole In-CMS page
 		 *
 		 * @param Array $url_parts
 		 * @param Array $vars
 		 * @return bool
 		 */
 		protected function _parseFriendlyUrl($url_parts, &$vars)
 		{
 			if (!$url_parts) {
 				return false;
 			}
 
 			$sql = 'SELECT CategoryId, NamedParentPath
 					FROM ' . TABLE_PREFIX . 'Category
 					WHERE FriendlyURL = ' . $this->Conn->qstr(implode('/', $url_parts));
 			$friendly = $this->Conn->GetRow($sql);
 
 			$rewrite_processor =& $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			if ($friendly) {
 				$vars['m_cat_id'] = $friendly['CategoryId'];
 				$vars['t'] = preg_replace('/^Content\//i', '', $friendly['NamedParentPath']);
 
 				while ($url_parts) {
 					$rewrite_processor->partParsed( array_shift($url_parts) );
 				}
 
 				return true;
 			}
 
 			return false;
 		}
 
 		/**
 		 * Extracts category part from url
 		 *
 		 * @param Array $url_parts
 		 * @param Array $vars
 		 * @return bool
 		 */
 		protected function _parseCategory($url_parts, &$vars)
 		{
 			if (!$url_parts) {
 				return false;
 			}
 
 			$res = false;
 			$url_part = array_shift($url_parts);
 
 			$category_id = 0;
 			$last_category_info = false;
 			$category_path = $url_part == 'content' ? '' : 'content';
 
 			$rewrite_processor =& $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			do {
 				$category_path = trim($category_path . '/' . $url_part, '/');
 				// bb_<topic_id> -> forums/bb_2
 				if ( !preg_match('/^bb_[\d]+$/', $url_part) && preg_match('/(.*)_([\d]+)$/', $category_path, $rets) ) {
 					$category_path = $rets[1];
 					$vars['m_cat_page'] = $rets[2];
 				}
 
 				$sql = 'SELECT CategoryId, SymLinkCategoryId, NamedParentPath
 						FROM ' . TABLE_PREFIX . 'Category
 						WHERE (LOWER(NamedParentPath) = ' . $this->Conn->qstr($category_path) . ') AND (ThemeId = ' . $vars['m_theme'] . ' OR ThemeId = 0)';
 				$category_info = $this->Conn->GetRow($sql);
 
 				if ($category_info !== false) {
 					$last_category_info = $category_info;
 					$rewrite_processor->partParsed($url_part);
 
 					$url_part = array_shift($url_parts);
 					$res = true;
 				}
 			} while ($category_info !== false && $url_part);
 
 			if ($last_category_info) {
 				// this category is symlink to other category, so use it's url instead
 				// (used in case if url prior to symlink adding was indexed by spider or was bookmarked)
 				if ($last_category_info['SymLinkCategoryId']) {
 					$sql = 'SELECT CategoryId, NamedParentPath
 							FROM ' . TABLE_PREFIX . 'Category
 							WHERE (CategoryId = ' . $last_category_info['SymLinkCategoryId'] . ')';
 					$category_info = $this->Conn->GetRow($sql);
 
 					if ($category_info) {
 						// web symlinked category was found use it
 						// TODO: maybe 302 redirect should be made to symlinked category url (all other url parts should stay)
 						$last_category_info = $category_info;
 					}
 				}
 
 				// 1. Set virtual page as template, this will be replaced to physical template later in kApplication::Run.
 				// 2. Don't set CachedTemplate field as template here, because we will loose original page associated with it's cms blocks!
 				$vars['t'] = mb_strtolower( preg_replace('/^Content\//i', '', $last_category_info['NamedParentPath']), 'UTF-8' );
 
 				$vars['m_cat_id'] = $last_category_info['CategoryId'];
 				$vars['is_virtual'] = true; // for template from POST, strange code there!
 			}
 			else {
 				$vars['m_cat_id'] = 0;
 			}
 
 			return $res;
 		}
+
+		/**
+		 * Set's new unique resource id to user
+		 *
+		 * @param kEvent $event
+		 * @return void
+		 * @access protected
+		 */
+		protected function OnAfterItemValidate(kEvent &$event)
+		{
+			$object =& $event->getObject();
+			/* @var $object kDBItem */
+
+			$resource_id = $object->GetDBField('ResourceId');
+
+			if ( !$resource_id ) {
+				$object->SetDBField('ResourceId', $this->Application->NextResourceId());
+			}
+		}
 	}
\ No newline at end of file
Index: branches/5.2.x/core/units/categories/categories_item.php
===================================================================
--- branches/5.2.x/core/units/categories/categories_item.php	(revision 14718)
+++ branches/5.2.x/core/units/categories/categories_item.php	(revision 14719)
@@ -1,304 +1,255 @@
 <?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 CategoriesItem extends kDBItem
 	{
-
-		/**
-		 * Creates a record in the database table with current item' values
-		 *
-		 * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE!
-		 * @param bool $system_create
-		 * @return bool
-		 * @access public
-		 */
-		public function Create($force_id = false, $system_create = false)
-		{
-			// set parent category first, so filename generation could use it
-			$parent_category = $this->GetDBField('ParentId') > 0 ? $this->GetDBField('ParentId') : $this->Application->GetVar('m_cat_id');
-			$this->SetDBField('ParentId', $parent_category);
-
-			$this->checkFilename();
-			$this->generateFilename();
-
-			if ($this->Validate()) {
-				// TODO: such approach will not respect changes from CategoryEventHandler::OnBeforeItemCreate event
-				$this->SetDBField('ResourceId', $this->Application->NextResourceId());
-			}
-
-			// TODO: move to CategoryEventHandler::OnBeforeItemCreate
-			$is_admin = $this->Application->isAdminUser;
-
-			if ((!$this->IsTempTable() && !$is_admin) || ($is_admin && !$this->GetDBField('CreatedById'))) {
-				$this->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
-			}
-
-			$ret = parent::Create($force_id, $system_create);
-
-			if ($ret) {
-				// TODO: move to CategoryEventHandler::OnAfterItemCreate method
-				$sql = 'UPDATE %s SET ParentPath = %s WHERE CategoryId = %s';
-				$parent_path = $this->buildParentPath();
-				$this->Conn->Query( sprintf($sql, $this->TableName, $this->Conn->qstr($parent_path), $this->GetID() ) );
-
-				$this->SetDBField('ParentPath', $parent_path);
-			}
-
-			return $ret;
-
-		}
-
 		/**
-		 * Updates previously loaded record with current item' values
+		 * Builds parent path for this category
 		 *
-		 * @access public
-		 * @param int $id Primary Key Id to update
-		 * @param bool $system_update
-		 * @return bool
+		 * @return string
 		 * @access public
 		 */
-		public function Update($id = null, $system_update = false)
-		{
-			$this->checkFilename();
-			$this->generateFilename();
-
-			return parent::Update($id, $system_update);
-		}
-
-		function buildParentPath()
+		public function buildParentPath()
 		{
 			$parent_id = $this->GetDBField('ParentId');
 
-			if ($parent_id == 0) {
+			if ( $parent_id == 0 ) {
 				$parent_path = '|';
 			}
 			else {
-				$cat_table = $this->Application->getUnitOption($this->Prefix, 'TableName');
-				$sql = 'SELECT ParentPath FROM '.$cat_table.' WHERE CategoryId = %s';
-				$parent_path = $this->Conn->GetOne( sprintf($sql, $parent_id) );
+				$sql = 'SELECT ParentPath
+						FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . ' WHERE
+						CategoryId = ' . $parent_id;
+				$parent_path = $this->Conn->GetOne($sql);
 			}
 
-			return $parent_path.$this->GetID().'|';
+			return $parent_path . $this->GetID() . '|';
 		}
 
 		/**
 		 * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result
 		 *
 		 * @param string $string
 		 * @return string
+		 * @access protected
 		 */
-		function stripDisallowed($string)
+		protected function stripDisallowed($string)
 		{
 			$filenames_helper =& $this->Application->recallObject('FilenamesHelper');
 			/* @var $filenames_helper kFilenamesHelper */
 
 			$string = $filenames_helper->replaceSequences($string);
 
 			return $this->checkAutoFilename($string);
 		}
 
-		function checkFilename()
+		public function checkFilename()
 		{
-			if ($this->GetDBField('AutomaticFilename')) {
+			if ( $this->GetDBField('AutomaticFilename') ) {
 				// filename will be generated from scratch, don't check anything here
-				return ;
+				return;
 			}
-			elseif ($this->GetDBField('Type') == PAGE_TYPE_TEMPLATE) {
+			elseif ( $this->GetDBField('Type') == PAGE_TYPE_TEMPLATE ) {
 				// system page with AutomaticFilename checkbox unchecked -> compatibility with Proj-CMS <= 4.3.9 (when "/" were allowed in Filename)
-				return ;
+				return;
 			}
 
 			$filename = $this->GetDBField('Filename');
 			$this->SetDBField('Filename', $this->stripDisallowed($filename));
 		}
 
-		function checkAutoFilename($filename)
+		protected function checkAutoFilename($filename)
 		{
 			static $current_theme = null;
 
 			if (!$filename) {
 				return $filename;
 			}
 
 			if (!isset($current_theme)) {
 				$themes_helper =& $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				$current_theme = (int)$themes_helper->getCurrentThemeId();
 			}
 
 			$escape_char = $this->Application->ConfigValue('FilenameSpecialCharReplacement');
 
 			$item_id = !$this->GetID() ? 0 : $this->GetID();
 			$item_theme = $this->GetDBField('ThemeId');
 
 			if (!$item_theme) {
 				// user creates category manually, that's why ThemeId = 0 -> use current admin theme instead
 				$item_theme = $current_theme;
 			}
 
 			$unique_clause = '(Filename = %s) AND (ThemeId = ' . $item_theme . ' OR ThemeId = 0) AND (ParentId = ' . $this->GetDBField('ParentId') . ')';
 
 			$sql_mask = '	SELECT ' . $this->IDField . '
 							FROM %s
 							WHERE ' . sprintf($unique_clause, $this->Conn->qstr($filename));
 
 			// check temp table
 			$sql_temp = sprintf($sql_mask, $this->TableName);
 			$found_temp_ids = $this->Conn->GetCol($sql_temp);
 
 			// check live table
 			$sql_live = sprintf($sql_mask, $this->Application->GetLiveName($this->TableName));
 			$found_live_ids = $this->Conn->GetCol($sql_live);
 
 			$found_item_ids = array_unique( array_merge($found_temp_ids, $found_live_ids) );
 
 			$has_page = preg_match('/(.*)_([\d]+)([a-z]*)$/', $filename, $rets);
 			$duplicates_found = (count($found_item_ids) > 1) || ($found_item_ids && $found_item_ids[0] != $item_id);
 
 			if ($duplicates_found || $has_page) {// other category has same filename as ours OR we have filename, that ends with _number
 				$append = $duplicates_found ? $escape_char . 'a' : '';
 				if ($has_page) {
 					$filename = $rets[1].'_'.$rets[2];
 					$append = $rets[3] ? $rets[3] : $escape_char . 'a';
 				}
 
 				// check live & temp table
 				$sql_temp = '	SELECT ' . $this->IDField . '
 								FROM ' . $this->TableName . '
 								WHERE ' . $unique_clause . ' AND (' . $this->IDField . ' != ' . $item_id . ')';
 
 				$sql_live = '	SELECT ' . $this->IDField . '
 								FROM ' . $this->Application->GetLiveName($this->TableName) . '
 								WHERE ' . $unique_clause . ' AND (' . $this->IDField . ' != ' . $item_id . ')';
 
 				while ( $this->Conn->GetOne( sprintf($sql_temp, $this->Conn->qstr($filename.$append)) ) > 0 ||
 				$this->Conn->GetOne( sprintf($sql_live, $this->Conn->qstr($filename.$append)) ) > 0 )
 				{
 					if (mb_substr($append, -1) == 'z') $append .= 'a';
 					$append = mb_substr($append, 0, mb_strlen($append) - 1) . chr( ord( mb_substr($append, -1) ) + 1 );
 				}
 
 				return $filename . $append;
 			}
 
 			return $filename;
 		}
 
 		/**
 		 * Generate item's filename based on it's title field value
 		 *
 		 * @return void
-		 * @access protected
+		 * @access public
 		 */
-		protected function generateFilename()
+		public function generateFilename()
 		{
 			if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) {
 				return ;
 			}
 
 			$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 			/* @var $ml_formatter kMultiLanguage */
 
 			$name = $this->stripDisallowed( $this->GetDBField($ml_formatter->LangFieldName('Name', true)) );
 
 			if ( $name != $this->GetDBField('Filename') ) {
 				$this->SetDBField('Filename', $name);
 			}
 		}
 
 		/**
 		 * Allows to detect if root category being edited
 		 *
 		 * @return int
+		 * @access public
 		 */
-		function IsRoot()
+		public function IsRoot()
 		{
 			return $this->Application->RecallVar('IsRootCategory_'.$this->Application->GetVar('m_wid'));
 		}
 
+
 		/**
 		 * Sets correct name to Home category while editing it
 		 *
 		 * @return bool
+		 * @access public
 		 */
-		function IsNewItem()
+		public function IsNewItem()
 		{
-			if ($this->IsRoot() && $this->Prefix == 'c') {
+			if ( $this->IsRoot() && $this->Prefix == 'c' ) {
 				$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
 				$category_name = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 				$this->SetDBField($title_field, $category_name);
+
 				return false;
 			}
+
 			return parent::IsNewItem();
 		}
 
 		/**
 		 * Sets new name for item in case if it is being copied in same table
 		 *
 		 * @param array $master Table data from TempHandler
 		 * @param int $foreign_key ForeignKey value to filter name check query by
 		 * @param string $title_field FieldName to alter, by default - TitleField of the prefix
 		 * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name
 		 * @access public
 		 */
 		public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s')
 		{
 			if (!isset($title_field)) {
 				$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
 				if (!$title_field || isset($this->CalculatedFields[$title_field]) ) return;
 			}
 
 			$new_name = $this->GetDBField($title_field);
 			$cat_id = $this->Application->GetVar('m_cat_id');
 			$this->SetDBField('ParentId', $cat_id);
 			$original_checked = false;
 			do {
 				if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) {
 					$new_name = 'Copy '.($regs[1]+1).' of '.$regs[2];
 				}
 				elseif ($original_checked) {
 					$new_name = 'Copy of '.$new_name;
 				}
 
 				// if we are cloning in temp table this will look for names in temp table,
 				// since object' TableName contains correct TableName (for temp also!)
 				// if we are cloning live - look in live
 				$query = '	SELECT ' . $title_field . '
 							FROM ' . $this->TableName . '
 							WHERE ParentId = ' . (int)$cat_id . ' AND ' . $title_field . ' = ' . $this->Conn->qstr($new_name);
 
 				$foreign_key_field = getArrayValue($master, 'ForeignKey');
 				$foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[ $master['ParentPrefix'] ] : $foreign_key_field;
 
 				if ($foreign_key_field && isset($foreign_key)) {
 					$query .= ' AND '.$foreign_key_field.' = '.$foreign_key;
 				}
 
 				$res = $this->Conn->GetOne($query);
 
 				/*// if not found in live table, check in temp table if applicable
 				if ($res === false && $object->Special == 'temp') {
 					$query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).'
 										WHERE '.$name_field.' = '.$this->Conn->qstr($new_name);
 					$res = $this->Conn->GetOne($query);
 				}*/
 
 				$original_checked = true;
 			} while ($res !== false);
 			$this->SetDBField($title_field, $new_name);
 		}
 	}
\ No newline at end of file
Index: branches/5.2.x/core/units/users/users_event_handler.php
===================================================================
--- branches/5.2.x/core/units/users/users_event_handler.php	(revision 14718)
+++ branches/5.2.x/core/units/users/users_event_handler.php	(revision 14719)
@@ -1,1795 +1,1796 @@
 <?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 UsersEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 			$permissions = Array (
 				// admin
 				'OnSetPersistantVariable'	=>	Array('self' => 'view'), // because setting to logged in user only
 				'OnUpdateRootPassword'		=>	Array('self' => true),
 				'OnUpdatePassword'			=>	Array('self' => true),
 				'OnSaveSelected'			=>	Array ('self' => 'view'),
 				'OnGeneratePassword'			=>	Array ('self' => 'view'),
 
 				// front
 				'OnRefreshForm'				=>	Array('self' => true),
 
 				'OnForgotPassword'			=>	Array('self' => true),
 
 				'OnSubscribeQuery'			=>	Array('self' => true),
 				'OnSubscribeUser'			=>	Array('self' => true),
 
 				'OnRecommend'				=>	Array('self' => true),
 
 				'OnItemBuild'				=>	Array('self' => true),
 				'OnMassResetSettings'	=> Array('self' => 'edit'),
 				'OnMassCloneUsers'	=> Array('self' => 'add'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Builds item (loads if needed)
 		 *
 		 * Pattern: Prototype Manager
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		function OnItemBuild(&$event)
 		{
 			parent::OnItemBuild($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $event->Special == 'forgot' || $object->getFormName() == 'registration' ) {
 				$this->_makePasswordRequired($event);
 			}
 		}
 
 		/**
 		 * Shows only admins when required
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBList */
 
 			if ($event->Special == 'regular') {
 				$object->addFilter('primary_filter', '%1$s.UserType = ' . UserType::USER);
 			}
 
 			if ($event->Special == 'admins') {
 				$object->addFilter('primary_filter', '%1$s.UserType = ' . UserType::ADMIN);
 			}
 
 			if (!$this->Application->isAdminUser) {
 				$object->addFilter('status_filter', '%1$s.Status = '.STATUS_ACTIVE);
 			}
 
 			if ($event->Special == 'online') {
 				$object->addFilter('online_users_filter', 's.PortalUserId IS NOT NULL');
 			}
 
 			if ($event->Special == 'group') {
 				$group_id = $this->Application->GetVar('g_id');
 
 				if ($group_id !== false) {
 					// show only users, that user doesn't belong to current group
 					$sql = 'SELECT PortalUserId
 							FROM ' . $this->Application->GetTempName(TABLE_PREFIX.'UserGroup', 'prefix:g') . '
 							WHERE GroupId = ' . (int)$group_id;
 					$user_ids = $this->Conn->GetCol($sql);
 
 					if ($user_ids) {
 						$object->addFilter('already_member_filter', '%1$s.PortalUserId NOT IN (' . implode(',', $user_ids) . ')');
 					}
 				}
 			}
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(&$event)
 		{
 			if ( $event->Name == 'OnLogin' || $event->Name == 'OnLogout' ) {
 				// permission is checked in OnLogin event directly
 				return true;
 			}
 
 			if ( $event->Name == 'OnResetRootPassword' ) {
 				return defined('DBG_RESET_ROOT') && DBG_RESET_ROOT;
 			}
 
 			if ( $event->Name == 'OnLoginAs' ) {
 				$admin_session =& $this->Application->recallObject('Session.admin');
 				/* @var $admin_session Session */
 
 				return $admin_session->LoggedIn();
 			}
 
 			if ( !$this->Application->isAdminUser ) {
 				$user_id = $this->Application->RecallVar('user_id');
 				$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 				if ( $event->Name == 'OnCreate' && $user_id == USER_GUEST ) {
 					// "Guest" can create new users
 					return true;
 				}
 
 				if ( $event->Name == 'OnUpdate' && $user_id > 0 ) {
 					$user_dummy =& $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
 					/* @var $user_dummy UsersItem */
 
 					foreach ($items_info as $id => $field_values) {
 						if ( $id != $user_id ) {
 							// registered users can update their record only
 							return false;
 						}
 
 						$user_dummy->Load($id);
 						$status_field = array_shift($this->Application->getUnitOption($event->Prefix, 'StatusField'));
 
 						if ( $user_dummy->GetDBField($status_field) != STATUS_ACTIVE ) {
 							// not active user is not allowed to update his record (he could not activate himself manually)
 							return false;
 						}
 
 						if ( isset($field_values[$status_field]) && $user_dummy->GetDBField($status_field) != $field_values[$status_field] ) {
 							// user can't change status by himself
 							return false;
 						}
 					}
 
 					return true;
 				}
 
 				if ( $event->Name == 'OnResetLostPassword' && $event->Special == 'forgot' && $user_id == USER_GUEST ) {
 					// non-logged in users can reset their password, when reset code is valid
 					return is_numeric($this->getPassedID($event));
 				}
 
 				if ( $event->Name == 'OnUpdate' && $user_id <= 0 ) {
 					// guests are not allowed to update their record, because they don't have it :)
 					return false;
 				}
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Handles session expiration (redirects to valid template)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSessionExpire(&$event)
 		{
 			$this->Application->resetCounters('UserSession');
 
 			// place 2 of 2 (also in kHTTPQuery::getRedirectParams)
 			$admin_url_params = Array (
 				'm_cat_id' => 0, // category means nothing on admin login screen
 				'm_wid' => '', // remove wid, otherwise parent window may add wid to its name breaking all the frameset (for <a> targets)
 				'pass' => 'm', // don't pass any other (except "m") prefixes to admin session expiration template
 				'expired' => 1, // expiration mark to show special error on login screen
 				'no_pass_through' => 1, // this way kApplication::HREF won't add them again
 			);
 
 			if ($this->Application->isAdmin) {
 				$this->Application->Redirect('index', $admin_url_params, '', 'index.php');
 			}
 
 			if ($this->Application->GetVar('admin') == 1) {
 				// Front-End showed in admin's right frame
 				$session_admin =& $this->Application->recallObject('Session.admin');
 				/* @var $session_admin Session */
 
 				if (!$session_admin->LoggedIn()) {
 					// front-end session created from admin session & both expired
 					$this->Application->DeleteVar('admin');
 					$this->Application->Redirect('index', $admin_url_params, '', 'admin/index.php');
 				}
 			}
 
 			// Front-End session expiration
 			$get = $this->Application->HttpQuery->getRedirectParams();
 			$t = $this->Application->GetVar('t');
 			$get['js_redirect'] = $this->Application->ConfigValue('UseJSRedirect');
 			$this->Application->Redirect($t ? $t : 'index', $get);
 		}
 
 		/**
 		 * [AGENT] Deletes expired sessions
 		 *
 		 * @param kEvent $event
 		 */
 		function OnDeleteExpiredSessions(&$event)
 		{
 			if (defined('IS_INSTALL') && IS_INSTALL) {
 				return ;
 			}
 
 			$this->Application->Session->DeleteExpired();
 		}
 
 		/**
 		 * Checks user data and logs it in if allowed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnLogin(&$event)
 		{
 			$object =& $event->getObject( Array ('form_name' => 'login') );
 			/* @var $object kDBItem */
 
 			$object->SetFieldsFromHash( $this->getSubmittedFields($event) );
 			$username = $object->GetDBField('UserLogin');
 			$password = $object->GetDBField('UserPassword');
 			$remember_login = $object->GetDBField('UserRememberLogin') == 1;
 
 			$user_helper =& $this->Application->recallObject('UserHelper');
 			/* @var $user_helper UserHelper */
 
 			$user_helper->event =& $event;
 			$result = $user_helper->loginUser($username, $password, false, $remember_login);
 
 			if ($result != LoginResult::OK) {
 				$event->status = kEvent::erFAIL;
 				$object->SetError('UserLogin', $result == LoginResult::NO_PERMISSION ? 'no_permission' : 'invalid_password');
 			}
 		}
 
 		/**
 		 * [HOOK] Auto-Logins Front-End user when "Remember Login" cookie is found
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAutoLoginUser(&$event)
 		{
 			$remember_login_cookie = $this->Application->GetVar('remember_login');
 
 			if (!$remember_login_cookie || $this->Application->isAdmin || $this->Application->LoggedIn()) {
 				return ;
 			}
 
 			$user_helper =& $this->Application->recallObject('UserHelper');
 			/* @var $user_helper UserHelper */
 
 			$user_helper->loginUser('', '', false, false, $remember_login_cookie);
 		}
 
 		/**
 		 * Called when user logs in using old in-portal
 		 *
 		 * @param kEvent $event
 		 */
 		function OnInpLogin(&$event)
 		{
 			$sync_manager =& $this->Application->recallObject('UsersSyncronizeManager', null, Array(), Array ('InPortalSyncronize'));
 			/* @var $sync_manager UsersSyncronizeManager */
 
 			$sync_manager->performAction('LoginUser', $event->getEventParam('user'), $event->getEventParam('pass') );
 
 			if ($event->redirect && is_string($event->redirect)) {
 				// some real template specified instead of true
 				$this->Application->Redirect($event->redirect, $event->getRedirectParams());
 			}
 		}
 
 		/**
 		 * Called when user logs in using old in-portal
 		 *
 		 * @param kEvent $event
 		 */
 		function OnInpLogout(&$event)
 		{
 			$sync_manager =& $this->Application->recallObject('UsersSyncronizeManager', null, Array(), Array ('InPortalSyncronize'));
 			/* @var $sync_manager UsersSyncronizeManager */
 
 			$sync_manager->performAction('LogoutUser');
 		}
 
 		/**
 		 * Performs user logout
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnLogout(&$event)
 		{
 			$user_helper =& $this->Application->recallObject('UserHelper');
 			/* @var $user_helper UserHelper */
 
 			$user_helper->event =& $event;
 			$user_helper->logoutUser();
 		}
 
 		/**
 		 * Redirects user after successful registration to confirmation template (on Front only)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(&$event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$this->afterItemChanged($event);
 
 			$this->assignToPrimaryGroup($event);
 		}
 
 		/**
 		 * Performs user registration
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCreate(&$event)
 		{
 			if ( $this->Application->isAdmin ) {
 				parent::OnCreate($event);
 
 				return ;
 			}
 
 			$object =& $event->getObject( Array('form_name' => 'registration') );
 			/* @var $object UsersItem */
 
 			$field_values = $this->getSubmittedFields($event);
 			$user_email = getArrayValue($field_values, 'Email');
 			$subscriber_id = $user_email ? $this->getSubscriberByEmail($user_email) : false;
 
 			if ( $subscriber_id ) {
 				// update existing subscriber
 				$object->Load($subscriber_id);
 				$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_NewGroup'));
 				$this->Application->SetVar($event->getPrefixSpecial(true), Array ($object->GetID() => $field_values));
 			}
 
 			$object->SetFieldsFromHash($field_values);
 
 			$status = $object->isLoaded() ? $object->Update() : $object->Create();
 
 			if ( !$status ) {
 				$event->status = kEvent::erFAIL;
 				$event->redirect = false;
 				$object->setID( (int)$object->GetID() );
 			}
 
 			$this->setNextTemplate($event, true);
 
 			if ( ($event->status == kEvent::erSUCCESS) && $event->redirect ) {
 				$this->assignToPrimaryGroup($event);
 
 				$object->SendEmailEvents();
 				$this->autoLoginUser($event);
 			}
 		}
 
 		/**
 		 * Returns subscribed user ID by given e-mail address
 		 *
 		 * @param string $email
 		 * @return int|bool
 		 * @access protected
 		 */
 		protected function getSubscriberByEmail($email)
 		{
 			$verify_user =& $this->Application->recallObject('u.verify', null, Array ('skip_autoload' => true));
 			/* @var $verify_user UsersItem */
 
 			$verify_user->Load($email, 'Email');
 
 			return $verify_user->isLoaded() && $verify_user->isSubscriberOnly() ? $verify_user->GetID() : false;
 		}
 
 		/**
 		 * Login user if possible, if not then redirect to corresponding template
 		 *
 		 * @param kEvent $event
 		 */
 		function autoLoginUser(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object UsersItem */
 
 			$this->Application->SetVar('u.current_id', $object->GetID());
 
 			if ( $object->GetDBField('Status') == STATUS_ACTIVE ) {
 				$user_helper =& $this->Application->recallObject('UserHelper');
 				/* @var $user_helper UserHelper */
 
 				if ( $user_helper->checkLoginPermission() ) {
 					$user_helper->loginUserById( $object->GetID() );
 				}
 			}
 		}
 
 		/**
 		 * Set's new unique resource id to user
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(&$event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$this->beforeItemChanged($event);
 
 			$cs_helper =& $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$object =& $event->getObject();
 			/* @var $object UsersItem */
 
 			if ( !$object->isSubscriberOnly() ) {
 				// don't checck state-to-country relations for subscribers
 				$cs_helper->CheckStateField($event, 'State', 'Country');
 			}
 
 			$this->_makePasswordRequired($event);
 
 			$cs_helper->PopulateStates($event, 'State', 'Country');
 
 			if ( $this->Application->ConfigValue('Email_As_Login') ) {
 				$error_msgs = $object->GetFieldOption('Email', 'error_msgs');
 				$error_msgs['unique'] = '!lu_user_and_email_already_exist!';
 				$object->SetFieldOption('Email', 'error_msgs', $error_msgs);
 			}
 
 			$this->setUserGroup($object);
 
 			$user_helper =& $this->Application->recallObject('UserHelper');
 			/* @var $user_helper UserHelper */
 
 			if ( !$user_helper->checkBanRules($object) ) {
 				$object->SetError('Login', 'banned');
 			}
 
 			$object->SetDBField('IPAddress', $_SERVER['REMOTE_ADDR']);
 		}
 
 		/**
 		 * Sets primary group of the user
 		 *
 		 * @param kDBItem $object
 		 */
 		protected function setUserGroup(&$object)
 		{
 			if ($object->Special == 'subscriber') {
 				$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_SubscriberGroup'));
 
 				return ;
 			}
 
 			// set primary group to user
 			if ( !$this->Application->isAdminUser ) {
 				$group_id = $object->GetDBField('PrimaryGroupId');
 
 				if ($group_id) {
 					// check, that group is allowed for Front-End
 					$sql = 'SELECT GroupId
 							FROM ' . TABLE_PREFIX . 'PortalGroup
 							WHERE GroupId = ' . (int)$group_id . ' AND FrontRegistration = 1';
 					$group_id = $this->Conn->GetOne($sql);
 				}
 
 				if (!$group_id) {
 					// when group not selected OR not allowed -> use default group
 					$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_NewGroup'));
 				}
 			}
 		}
 
 		/**
 		 * Assigns a user to it's primary group
 		 *
 		 * @param kEvent $event
 		 */
 		protected function assignToPrimaryGroup(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$primary_group_id = $object->GetDBField('PrimaryGroupId');
 
 			if ($primary_group_id) {
 				$ug_table = TABLE_PREFIX . 'UserGroup';
 
 				if ( $object->IsTempTable() ) {
 					$ug_table = $this->Application->GetTempName($ug_table, 'prefix:' . $event->Prefix);
 				}
 
 				$fields_hash = Array (
 					'PortalUserId' => $object->GetID(),
 					'GroupId' => $primary_group_id,
 				);
 
 				$this->Conn->doInsert($fields_hash, $ug_table, 'REPLACE');
 			}
 		}
 
 		/**
 		 * Set's new unique resource id to user
 		 *
 		 * @param kEvent $event
+		 * @return void
+		 * @access protected
 		 */
-		function OnAfterItemValidate(&$event)
+		protected function OnAfterItemValidate(kEvent &$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$resource_id = $object->GetDBField('ResourceId');
 
-			if (!$resource_id) {
+			if ( !$resource_id ) {
 				$object->SetDBField('ResourceId', $this->Application->NextResourceId());
 			}
 		}
 
-
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnRecommend(&$event)
 		{
 			$object =& $event->getObject( Array ('form_name' => 'recommend') );
 			/* @var $object kDBItem */
 
 			$object->SetFieldsFromHash( $this->getSubmittedFields($event) );
 
 			if ( !$object->ValidateField('RecommendEmail') ) {
 				$event->status = kEvent::erFAIL;
 
 				return ;
 			}
 
 	    	$send_params = Array (
 		    	'to_email' => $object->GetDBField('RecommendEmail'),
 				'to_name' => $object->GetDBField('RecommendEmail'),
 	    	);
 
 			$user_id = $this->Application->RecallVar('user_id');
 			$email_event =& $this->Application->EmailEventUser('USER.SUGGEST', $user_id, $send_params);
 			$email_event =& $this->Application->EmailEventAdmin('USER.SUGGEST');
 
 			if ( $email_event->status == kEvent::erSUCCESS ) {
 				$event->SetRedirectParam('pass', 'all');
 				$event->redirect = $this->Application->GetVar('template_success');
 			}
 			else {
 				$event->status = kEvent::erFAIL;
 				$object->SetError('RecommendEmail', 'send_error');
 			}
 		}
 
 		/**
 		 * Saves address changes and mades no redirect
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdateAddress(&$event)
 		{
 			$object =& $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 			if ( $items_info ) {
 				list ($id, $field_values) = each($items_info);
 
 				if ( $id > 0 ) {
 					$object->Load($id);
 				}
 
 				$object->SetFieldsFromHash($field_values);
 				$object->setID($id);
 				$object->Validate();
 			}
 
 			$cs_helper =& $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$cs_helper->PopulateStates($event, 'State', 'Country');
 
 			$event->redirect = false;
 		}
 
 		/**
 		 * Validate subscriber's email & store it to session -> redirect to confirmation template
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSubscribeQuery(&$event)
 		{
 			$object =& $event->getObject( Array ('form_name' => 'subscription') );
 			/* @var $object UsersItem */
 
 			$object->SetFieldsFromHash( $this->getSubmittedFields($event) );
 
 			if ( !$object->ValidateField('SubscriberEmail') ) {
 				$event->status = kEvent::erFAIL;
 
 				return ;
 			}
 
 			$user_email = $object->GetDBField('SubscriberEmail');
 			$object->Load($user_email, 'Email');
 			$event->SetRedirectParam('subscriber_email', $user_email);
 
 			if ( $object->isLoaded() && $object->isSubscribed() ) {
 				$event->redirect = $this->Application->GetVar('unsubscribe_template');
 			}
 			else {
 				$event->redirect = $this->Application->GetVar('subscribe_template');
 			}
 
 			$event->SetRedirectParam('pass', 'm');
 		}
 
 		/**
 		 * Subscribe/Unsubscribe user based on email stored in previous step
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSubscribeUser(&$event)
 		{
 			$object =& $event->getObject( Array ('form_name' => 'subscription') );
 			/* @var $object UsersItem */
 
 			$user_email = $this->Application->GetVar('subscriber_email');
 			$object->SetDBField('SubscriberEmail', $user_email);
 
 			if ( !$object->ValidateField('SubscriberEmail') ) {
 				$event->status = kEvent::erFAIL;
 
 				return ;
 			}
 
 			$this->RemoveRequiredFields($object);
 			$object->Load($user_email, 'Email');
 
 			if ( $object->isLoaded() ) {
 				if ( $object->isSubscribed() ) {
 					if ( $event->getEventParam('no_unsubscribe') ) {
 						// for customization code from FormsEventHandler
 						return ;
 					}
 
 					if ( $object->isSubscriberOnly() ) {
 						$temp_handler =& $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler');
 						/* @var $temp_handler kTempTablesHandler */
 
 						$temp_handler->DeleteItems($event->Prefix, '', Array($object->GetID()));
 					}
 					else {
 						$this->RemoveSubscriberGroup( $object->GetID() );
 					}
 
 					$event->redirect = $this->Application->GetVar('unsubscribe_ok_template');
 				}
 				else {
 					$this->AddSubscriberGroup($object);
 					$event->redirect = $this->Application->GetVar('subscribe_ok_template');
 				}
 			}
 			else {
 				$object->generatePassword();
 				$object->SetDBField('Email', $user_email);
 
 				if ( $object->isRequired('Login') )	{
 					$object->SetDBField('Login', $user_email);
 				}
 
 				$object->SetDBField('Status', STATUS_ACTIVE); // make user subscriber Active by default
 
 				if ( $object->Create() ) {
 					$this->AddSubscriberGroup($object);
 					$event->redirect = $this->Application->GetVar('subscribe_ok_template');
 				}
 			}
 		}
 
 		/**
 		 * Adding user to subscribers group
 		 *
 		 * @param UsersItem $object
 		 */
 		function AddSubscriberGroup(&$object)
 		{
 			if ( !$object->isSubscriberOnly() ) {
 				$fields_hash = Array (
 					'PortalUserId' => $object->GetID(),
 					'GroupId' => $this->Application->ConfigValue('User_SubscriberGroup'),
 				);
 
 				$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'UserGroup');
 			}
 
 			$this->Application->EmailEventAdmin('USER.SUBSCRIBE');
 			$this->Application->EmailEventUser('USER.SUBSCRIBE', $object->GetID());
 		}
 
 		/**
 		 * Removing user from subscribers group
 		 *
 		 * @param int $user_id
 		 */
 		function RemoveSubscriberGroup($user_id)
 		{
 			$group_id = $this->Application->ConfigValue('User_SubscriberGroup');
 
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserGroup
 					WHERE PortalUserId = ' . $user_id . ' AND GroupId = ' . $group_id;
 			$this->Conn->Query($sql);
 
 			$this->Application->EmailEventAdmin('USER.UNSUBSCRIBE');
 			$this->Application->EmailEventUser('USER.UNSUBSCRIBE', $user_id);
 		}
 
 		/**
 		 * Validates forgot password form and sends password reset confirmation e-mail
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 */
 		function OnForgotPassword(&$event)
 		{
 			$object =& $event->getObject( Array ('form_name' => 'forgot_password') );
 			/* @var $object kDBItem */
 
 			$object->SetFieldsFromHash( $this->getSubmittedFields($event) );
 
 			$user_object =& $this->Application->recallObject('u.tmp', null, Array('skip_autoload' => true));
 			/* @var $user_object UsersItem */
 
 			$found = $allow_reset = false;
 			$username = $object->GetDBField('ForgotLogin');
 			$email = $object->GetDBField('ForgotEmail');
 
 			if ( strlen($username) ) {
 				$user_object->Load($username, 'Login');
 			}
 			elseif ( strlen($email) ) {
 				$user_object->Load($email, 'Email');
 			}
 
 			if ( $user_object->isLoaded() ) {
 				$min_pwd_reset_delay = $this->Application->ConfigValue('Users_AllowReset');
 				$found = ($user_object->GetDBField('Status') == STATUS_ACTIVE) && strlen( $user_object->GetDBField('Password') );
 
 				if ( !$user_object->GetDBField('PwResetConfirm') ) {
 					// no reset made -> allow
 					$allow_reset = true;
 				}
 				else {
 					// reset made -> wait N minutes, then allow
 					$allow_reset = adodb_mktime() > $user_object->GetDBField('PwRequestTime') + $min_pwd_reset_delay;
 				}
 			}
 
 			if ($found && $allow_reset) {
 				$this->Application->EmailEventUser('USER.PSWDC', $user_object->GetID());
 				$event->redirect = $this->Application->GetVar('template_success');
 
 				return ;
 			}
 
 			if ( !strlen($username) && !strlen($email) ) {
 				$object->SetError('ForgotLogin', 'required');
 				$object->SetError('ForgotEmail', 'required');
 			}
 			else {
 				if ( strlen($username) ) {
 					$object->SetError('ForgotLogin', $found ? 'reset_denied' : 'unknown_username');
 				}
 
 				if ( strlen($email) ) {
 					$object->SetError('ForgotEmail', $found ? 'reset_denied' : 'unknown_email');
 				}
 			}
 
 			if ( !$object->ValidateField('ForgotLogin') || !$object->ValidateField('ForgotEmail') ) {
 				$event->status = kEvent::erFAIL;
 			}
 		}
 
 		/**
 		 * Updates kDBItem
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function OnUpdate(&$event)
 		{
 			parent::OnUpdate($event);
 
 			if ( !$this->Application->isAdmin ) {
 				$this->setNextTemplate($event);
 			}
 		}
 
 		/**
 		 * Checks state against country
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(&$event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->beforeItemChanged($event);
 
 			$cs_helper =& $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$cs_helper->CheckStateField($event, 'State', 'Country');
 			$cs_helper->PopulateStates($event, 'State', 'Country');
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			if ($event->Special == 'forgot') {
 				$object->SetDBField('PwResetConfirm', '');
 				$object->SetDBField('PwRequestTime_date', NULL);
 				$object->SetDBField('PwRequestTime_time', NULL);
 			}
 
 			$changed_fields = array_keys( $object->GetChangedFields() );
 
 			if ( $changed_fields && !in_array('Modified', $changed_fields) ) {
 				$object->SetDBField('Modified_date', adodb_mktime());
 				$object->SetDBField('Modified_time', adodb_mktime());
 			}
 		}
 
 		/**
 		 * Occurs before item is changed
 		 *
 		 * @param kEvent $event
 		 */
 		function beforeItemChanged(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object UsersItem */
 
 			if ( !$this->Application->isAdmin && $object->getFormName() == 'registration' ) {
 				// sets new user's status based on config options
 				$status_map = Array (1 => STATUS_ACTIVE, 2 => STATUS_DISABLED, 3 => STATUS_PENDING, 4 => STATUS_PENDING);
 				$object->SetDBField('Status', $status_map[ $this->Application->ConfigValue('User_Allow_New') ]);
 
 				if ( $this->Application->ConfigValue('User_Password_Auto') ) {
 					$object->generatePassword( rand(5, 8) );
 				}
 
 				if ( $this->Application->ConfigValue('RegistrationCaptcha') ) {
 					$captcha_helper =& $this->Application->recallObject('CaptchaHelper');
 					/* @var $captcha_helper kCaptchaHelper */
 
 					$captcha_helper->validateCode($event, false);
 				}
 
 				if ( $event->Name == 'OnBeforeItemUpdate' ) {
 					// when a subscriber-only users performs normal registration, then assign him to Member group
 					$this->setUserGroup($object);
 				}
 			}
 		}
 
 		/**
 		 * Sets redirect template based on user status & user request contents
 		 *
 		 * @param kEvent $event
 		 * @param bool $for_registration
 		 */
 		function setNextTemplate(&$event, $for_registration = false)
 		{
 			$event->SetRedirectParam('opener', 's');
 
 			$object =& $event->getObject();
 			/* @var $object UsersItem */
 
 			$next_template = false;
 
 			if ( $object->GetDBField('Status') == STATUS_ACTIVE && $this->Application->GetVar('next_template') ) {
 				$next_template = $this->Application->GetVar('next_template');
 			}
 			elseif ( $for_registration ) {
 				switch ( $this->Application->ConfigValue('User_Allow_New') ) {
 					case 1: // Immediate
 						$next_template = $this->Application->GetVar('registration_confirm_template');
 						break;
 
 					case 3: // Upon Approval
 					case 4: // Email Activation
 						$next_template = $this->Application->GetVar('registration_confirm_pending_template');
 						break;
 				}
 			}
 
 			if ($next_template) {
 				$event->redirect = $next_template;
 			}
 		}
 
 		/**
 		 * Delete users from groups if their membership is expired
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCheckExpiredMembership(&$event)
 		{
 			// send pre-expiration reminders: begin
 			$pre_expiration = adodb_mktime() + $this->Application->ConfigValue('User_MembershipExpirationReminder') * 3600 * 24;
 			$sql = 'SELECT PortalUserId, GroupId
 					FROM '.TABLE_PREFIX.'UserGroup
 					WHERE (MembershipExpires IS NOT NULL) AND (ExpirationReminderSent = 0) AND (MembershipExpires < '.$pre_expiration.')';
 
 			$skip_clause = $event->getEventParam('skip_clause');
 			if ($skip_clause) {
 				$sql .= ' AND !('.implode(') AND !(', $skip_clause).')';
 			}
 
 			$records = $this->Conn->Query($sql);
 			if ($records) {
 				$conditions = Array();
 				foreach ($records as $record) {
 					$this->Application->EmailEventUser('USER.MEMBERSHIP.EXPIRATION.NOTICE', $record['PortalUserId']);
 					$this->Application->EmailEventAdmin('USER.MEMBERSHIP.EXPIRATION.NOTICE');
 					$conditions[] = '(PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'].')';
 				}
 				$sql = 'UPDATE '.TABLE_PREFIX.'UserGroup
 						SET ExpirationReminderSent = 1
 						WHERE '.implode(' OR ', $conditions);
 				$this->Conn->Query($sql);
 			}
 			// send pre-expiration reminders: end
 
 			// remove users from groups with expired membership: begin
 			$sql = 'SELECT PortalUserId
 					FROM '.TABLE_PREFIX.'UserGroup
 					WHERE (MembershipExpires IS NOT NULL) AND (MembershipExpires < '.adodb_mktime().')';
 			$user_ids = $this->Conn->GetCol($sql);
 			if ($user_ids) {
 				foreach ($user_ids as $id) {
 					$this->Application->EmailEventUser('USER.MEMBERSHIP.EXPIRED', $id);
 					$this->Application->EmailEventAdmin('USER.MEMBERSHIP.EXPIRED');
 				}
 			}
 			$sql = 'DELETE FROM '.TABLE_PREFIX.'UserGroup
 					WHERE (MembershipExpires IS NOT NULL) AND (MembershipExpires < '.adodb_mktime().')';
 			$this->Conn->Query($sql);
 			// remove users from groups with expired membership: end
 		}
 
 		/**
 		 * Used to keep user registration form data, while showing affiliate registration form fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnRefreshForm(&$event)
 		{
 			$event->redirect = false;
 			$item_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			list($id, $fields) = each($item_info);
 
 			$object =& $event->getObject( Array ('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$object->setID($id);
 			$object->IgnoreValidation = true;
 			$object->SetFieldsFromHash($fields);
 		}
 
 		/**
 		 * Sets persistant variable
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetPersistantVariable(&$event)
 		{
 			$field =  $this->Application->GetVar('field');
 			$value = $this->Application->GetVar('value');
 			$this->Application->StorePersistentVar($field, $value);
 
 			$force_tab = $this->Application->GetVar('SetTab');
 			if ($force_tab) {
 				$this->Application->StoreVar('force_tab', $force_tab);
 			}
 		}
 
 		/**
 		 * Return user from order by special .ord
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function getPassedID(&$event)
 		{
 			switch ($event->Special) {
 				case 'ord':
 					$order =& $this->Application->recallObject('ord');
 					/* @var $order OrdersItem */
 
 					return $order->GetDBField('PortalUserId');
 					break;
 
 				case 'profile':
 					$id = $this->Application->GetVar('user_id');
 
 					if (!$id) {
 						// if none user_id given use current user id
 						$id = $this->Application->RecallVar('user_id');
 					}
 
 					return $id;
 					break;
 
 				case 'forgot':
 					$user_helper =& $this->Application->recallObject('UserHelper');
 					/* @var $user_helper UserHelper */
 
 					$id = $user_helper->validateUserCode( $this->Application->GetVar('user_key'), 'forgot_password' );
 
 					if ( is_numeric($id) ) {
 						return $id;
 					}
 					break;
 			}
 
 			if ( preg_match('/^(login|register|recommend|subscribe|forgot)/', $event->Special) ) {
 				// this way we can have 2+ objects stating with same special, e.g. "u.login-sidebox" and "u.login-main"
 				return USER_GUEST;
 			}
 
 			return parent::getPassedID($event);
 		}
 
 		/**
 		 * Allows to change root password
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdateRootPassword(&$event)
 		{
 			return $this->OnUpdatePassword($event);
 		}
 
 		/**
 		 * Allows to change root password
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdatePassword(&$event)
 		{
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 			if ( !$items_info ) {
 				return;
 			}
 
 			list ($id, $field_values) = each($items_info);
 			$user_id = $this->Application->RecallVar('user_id');
 
 			if ( $id == $user_id && ($user_id > 0 || $user_id == USER_ROOT) ) {
 				$user_dummy =& $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
 				/* @var $user_dummy kDBItem */
 
 				$user_dummy->Load($id);
 				$status_field = array_shift( $this->Application->getUnitOption($event->Prefix, 'StatusField') );
 
 				if ( $user_dummy->GetDBField($status_field) != STATUS_ACTIVE ) {
 					// not active user is not allowed to update his record (he could not activate himself manually)
 					return ;
 				}
 			}
 
 			if ( $user_id == USER_ROOT ) {
 				$object =& $event->getObject(Array ('skip_autoload' => true));
 				/* @var $object UsersItem */
 
 				// put salt to user's config
 				$field_options = $object->GetFieldOptions('RootPassword');
 				$field_options['salt'] = 'b38';
 
 				// this is internal hack to allow root/root passwords for dev
 				if ( $this->Application->isDebugMode() && $field_values['RootPassword'] == 'root' ) {
 					$field_options['min_length'] = 4;
 				}
 
 				$object->SetFieldOptions('RootPassword', $field_options);
 
 				$verify_options = $object->GetFieldOptions('VerifyRootPassword');
 				$verify_options['salt'] = 'b38';
 				$object->SetFieldOptions('VerifyRootPassword', $verify_options);
 
 				$this->RemoveRequiredFields($object);
 				$object->SetDBField('RootPassword', $this->Application->ConfigValue('RootPass'));
 				$object->SetFieldsFromHash($field_values);
 				$object->setID(-1);
 
 				if ( $object->Validate() ) {
 					// validation on, password match too
 					$fields_hash = Array ('VariableValue' => $object->GetDBField('RootPassword'));
 					$conf_table = $this->Application->getUnitOption('conf', 'TableName');
 					$this->Conn->doUpdate($fields_hash, $conf_table, 'VariableName = "RootPass"');
 					$event->SetRedirectParam('opener', 'u');
 				}
 				else {
 					$event->status = kEvent::erFAIL;
 					$event->redirect = false;
 					return ;
 				}
 			}
 			else {
 				$object =& $event->getObject();
 				$object->SetFieldsFromHash($field_values);
 
 				if ( !$object->Update() ) {
 					$event->status = kEvent::erFAIL;
 					$event->redirect = false;
 				}
 			}
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Resets grid settings, remembered in each user record
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnMassResetSettings(&$event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			$default_user_id = $this->Application->ConfigValue('DefaultSettingsUserId');
 			if ( in_array($default_user_id, $ids) ) {
 				array_splice($ids, array_search($default_user_id, $ids), 1);
 			}
 
 			if ( $ids ) {
 				$q = 'DELETE FROM ' . TABLE_PREFIX . 'PersistantSessionData WHERE PortalUserId IN (' . join(',', $ids) . ') AND
 							 (VariableName LIKE "%_columns_%"
 							 OR
 							 VariableName LIKE "%_filter%"
 							 OR
 							 VariableName LIKE "%_PerPage%")';
 				$this->Conn->Query($q);
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function checkItemStatus(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->isLoaded() ) {
 				return true;
 			}
 
 			$virtual_users = Array (USER_ROOT, USER_GUEST);
 
 			return ($object->GetDBField('Status') == STATUS_ACTIVE) || in_array($object->GetID(), $virtual_users);
 		}
 
 		/**
 		 * Sends approved/declined email event on user status change
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemUpdate(&$event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$this->afterItemChanged($event);
 
 			$object =& $event->getObject();
 			/* @var $object UsersItem */
 
 			if (!$this->Application->isAdmin || $object->IsTempTable()) {
 				return ;
 			}
 
 			$this->sendStatusChangeEvent($object->GetID(), $object->GetOriginalField('Status'), $object->GetDBField('Status'));
 		}
 
 		/**
 		 * Occurs, after item is changed
 		 *
 		 * @param kEvent $event
 		 */
 		protected function afterItemChanged(&$event)
 		{
 			$this->saveUserImages($event);
 
 			$object =& $event->getObject();
 			/* @var $object UsersItem */
 
 			if ( $object->GetDBField('EmailPassword') && $object->GetDBField('Password_plain') ) {
 				$email_passwords = $this->Application->RecallVar('email_passwords');
 				$email_passwords = $email_passwords ? unserialize($email_passwords) : Array ();
 
 				$email_passwords[ $object->GetID() ] = $object->GetDBField('Password_plain');
 				$this->Application->StoreVar('email_passwords', serialize($email_passwords));
 			}
 
 			// update user subscription status (via my profile or new user registration)
 			if ( !$this->Application->isAdmin && !$object->isSubscriberOnly() ) {
 				if ( $object->GetDBField('SubscribeToMailing') && !$object->isSubscribed() ) {
 					$this->AddSubscriberGroup($object);
 				}
 				elseif ( !$object->GetDBField('SubscribeToMailing') && $object->isSubscribed() ) {
 					$this->RemoveSubscriberGroup( $object->GetID() );
 				}
 			}
 		}
 
 		/**
 		 * Stores user's original Status before overwriting with data from temp table
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeDeleteFromLive(&$event)
 		{
 			parent::OnBeforeDeleteFromLive($event);
 
 			$user_id = $event->getEventParam('id');
 			$user_status = $this->Application->GetVar('user_status', Array ());
 
 			if ( $user_id > 0 ) {
 				$user_status[$user_id] = $this->getUserStatus($user_id);
 				$this->Application->SetVar('user_status', $user_status);
 			}
 		}
 
 		/**
 		 * Sends approved/declined email event on user status change (in temp tables during editing)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterCopyToLive(&$event)
 		{
 			parent::OnAfterCopyToLive($event);
 
 			$temp_id = $event->getEventParam('temp_id');
 			$email_passwords = $this->Application->RecallVar('email_passwords');
 
 			if ( $email_passwords ) {
 				$email_passwords = unserialize($email_passwords);
 
 				if ( isset($email_passwords[$temp_id]) ) {
 					$object =& $event->getObject();
 					/* @var $object kDBItem */
 
 					$object->SwitchToLive();
 					$object->Load( $event->getEventParam('id') );
 					$object->SetField('Password', $email_passwords[$temp_id]);
 					$object->SetField('VerifyPassword', $email_passwords[$temp_id]);
 
 					$this->Application->EmailEventUser($temp_id > 0 ? 'USER.NEW.PASSWORD': 'USER.ADD.BYADMIN', $object->GetID());
 
 					unset($email_passwords[$temp_id]);
 					$this->Application->StoreVar('email_passwords', serialize($email_passwords));
 				}
 			}
 
 			if ( $temp_id > 0 ) {
 				// only send status change e-mail on user update
 				$new_status = $this->getUserStatus($temp_id);
 				$user_status = $this->Application->GetVar('user_status');
 
 				$this->sendStatusChangeEvent($temp_id, $user_status[$temp_id], $new_status);
 			}
 		}
 
 		/**
 		 * Returns user status (active, pending, disabled) based on ID and temp mode setting
 		 *
 		 * @param int $user_id
 		 * @return int
 		 */
 		function getUserStatus($user_id)
 		{
 			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 			$sql = 'SELECT Status
 					FROM '.$table_name.'
 					WHERE '.$id_field.' = '.$user_id;
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Sends approved/declined email event on user status change
 		 *
 		 * @param int $user_id
 		 * @param int $prev_status
 		 * @param int $new_status
 		 */
 		function sendStatusChangeEvent($user_id, $prev_status, $new_status)
 		{
 			$status_events = Array (
 				STATUS_ACTIVE	=>	'USER.APPROVE',
 				STATUS_DISABLED	=>	'USER.DENY',
 			);
 			$email_event = isset($status_events[$new_status]) ? $status_events[$new_status] : false;
 
 			if (($prev_status != $new_status) && $email_event) {
 				$this->Application->EmailEventUser($email_event, $user_id);
 				$this->Application->EmailEventAdmin($email_event);
 			}
 
 			// deletes sessions from users, that are no longer active
 			if (($prev_status != $new_status) && ($new_status != STATUS_ACTIVE)) {
 				$sql = 'SELECT SessionKey
 						FROM ' . TABLE_PREFIX . 'UserSession
 						WHERE PortalUserId = ' . $user_id;
 				$session_ids = $this->Conn->GetCol($sql);
 
 				$this->Application->Session->DeleteSessions($session_ids);
 			}
 		}
 
 		/**
 		 * OnAfterConfigRead for users
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterConfigRead(&$event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			$forms = $this->Application->getUnitOption($event->Prefix, 'Forms');
 			$form_fields =& $forms['default']['Fields'];
 
 			// 1. arrange user registration countries
 			$site_helper =& $this->Application->recallObject('SiteHelper');
 			/* @var $site_helper SiteHelper */
 
 			$first_country = $site_helper->getDefaultCountry('', false);
 
 			if ($first_country === false) {
 				$first_country = $this->Application->ConfigValue('User_Default_Registration_Country');
 			}
 
 			if ($first_country) {
 				// update user country dropdown sql
 				$form_fields['Country']['options_sql'] = preg_replace('/ORDER BY (.*)/', 'ORDER BY IF (CountryStateId = '.$first_country.', 1, 0) DESC, \\1', $form_fields['Country']['options_sql']);
 			}
 
 			$max_username = $this->Application->ConfigValue('MaxUserName');
 			$fields['Login']['min_len'] = $this->Application->ConfigValue('Min_UserName');
 			$fields['Login']['max_len'] = $max_username ? $max_username : 255;
 
 			// 2. set default user registration group
 			$form_fields['PrimaryGroupId']['default'] = $this->Application->ConfigValue('User_NewGroup');
 
 			// 3. allow avatar upload on Front-End
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->createItemFiles($event->Prefix, true); // create image fields
 
 			if ($this->Application->isAdminUser) {
 				// 4. when in administrative console, then create all users with Active status
 				$form_fields['Status']['default'] = STATUS_ACTIVE;
 
 				// 5. remove groups tab on editing forms when AdvancedUserManagement config variable not set
 				if (!$this->Application->ConfigValue('AdvancedUserManagement')) {
 					$edit_tab_presets = $this->Application->getUnitOption($event->Prefix, 'EditTabPresets');
 
 					foreach ($edit_tab_presets as $preset_name => $preset_tabs) {
 						if (array_key_exists('groups', $preset_tabs)) {
 							unset($edit_tab_presets[$preset_name]['groups']);
 
 							if (count($edit_tab_presets[$preset_name]) == 1) {
 								// only 1 tab left -> remove it too
 								$edit_tab_presets[$preset_name] = Array ();
 							}
 						}
 					}
 
 					$this->Application->setUnitOption($event->Prefix, 'EditTabPresets', $edit_tab_presets);
 				}
 			}
 
 			if ( !$this->Application->ConfigValue('Email_As_Login') ) {
 				// Login becomes required only, when it's used in registration process
 				$form_fields['Login']['required'] = 1;
 			}
 
 			$this->Application->setUnitOption($event->Prefix, 'Forms', $forms);
 		}
 
 		/**
 		 * OnMassCloneUsers
 		 *
 		 * @param kEvent $event
 		 */
 		function OnMassCloneUsers(&$event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$temp_handler =& $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$ids = $this->StoreSelectedIDs($event);
 			$temp_handler->CloneItems($event->Prefix, '', $ids);
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * When cloning users, reset password (set random)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeClone(&$event)
 		{
 			parent::OnBeforeClone($event);
 
 			$object =& $event->getObject();
 			/* @var $object UsersItem */
 
 			$object->generatePassword();
 			$object->SetDBField('ResourceId', 0); // this will reset it
 
 			// change email because it should be unique
 			$object->NameCopy(Array (), $object->GetID(), 'Email', 'copy%1$s.%2$s');
 		}
 
 		/**
 		 * Saves selected ids to session
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveSelected(&$event)
 		{
 			$this->StoreSelectedIDs($event);
 
 			// remove current ID, otherwise group selector will use it in filters
 			$this->Application->DeleteVar($event->getPrefixSpecial(true) . '_id');
 		}
 
 		/**
 		 * Sets primary group of selected users
 		 *
 		 * @param kEvent $event
 		 */
 		function OnProcessSelected(&$event)
 		{
 			$event->SetRedirectParam('opener', 'u');
 			$user_ids = $this->getSelectedIDs($event, true);
 			$this->clearSelectedIDs($event);
 
 			$dst_field = $this->Application->RecallVar('dst_field');
 			if ($dst_field != 'PrimaryGroupId') {
 				return ;
 			}
 
 			$group_ids = $this->Application->GetVar('g');
 			$primary_group_id = $group_ids ? array_shift( array_keys($group_ids) ) : false;
 
 			if (!$user_ids || !$primary_group_id) {
 				return ;
 			}
 
 			$table_name = $this->Application->getUnitOption('ug', 'TableName');
 
 			// 1. mark group as primary
 			$sql = 'UPDATE ' . TABLE_PREFIX . 'PortalUser
 					SET PrimaryGroupId = ' . $primary_group_id . '
 					WHERE PortalUserId IN (' . implode(',', $user_ids) . ')';
 			$this->Conn->Query($sql);
 
 			$sql = 'SELECT PortalUserId
 					FROM ' . $table_name . '
 					WHERE (GroupId = ' . $primary_group_id . ') AND (PortalUserId IN (' . implode(',', $user_ids) . '))';
 			$existing_members = $this->Conn->GetCol($sql);
 
 			// 2. add new members to a group
 			$new_members = array_diff($user_ids, $existing_members);
 
 			foreach ($new_members as $user_id) {
 				$fields_hash = Array (
 					'GroupId' => $primary_group_id,
 					'PortalUserId' => $user_id,
 				);
 
 				$this->Conn->doInsert($fields_hash, $table_name);
 			}
 		}
 
 		/**
 		 * Loads user images
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(&$event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			// linking existing images for item with virtual fields
 			$image_helper =& $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			$object =& $event->getObject();
 			/* @var $object UsersItem */
 
 			$image_helper->LoadItemImages($object);
 
 			$cs_helper =& $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$cs_helper->PopulateStates($event, 'State', 'Country');
 
 			// get user subscription status
 			$object->SetDBField('SubscribeToMailing', $object->isSubscribed() ? 1 : 0);
 		}
 
 		/**
 		 * Save user images
 		 *
 		 * @param kEvent $event
 		 */
 		function saveUserImages(&$event)
 		{
 			if (!$this->Application->isAdmin) {
 				$image_helper =& $this->Application->recallObject('ImageHelper');
 				/* @var $image_helper ImageHelper */
 
 				$object =& $event->getObject();
 				/* @var $object kDBItem */
 
 				// process image upload in virtual fields
 				$image_helper->SaveItemImages($object);
 			}
 		}
 
 		/**
 		 * Makes password required for new users
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		function OnPreCreate(&$event)
 		{
 			parent::OnPreCreate($event);
 
 			if ( $event->status != kEvent::erSUCCESS ) {
 				return;
 			}
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$user_type = $this->Application->GetVar('user_type');
 
 			if ( $user_type ) {
 				$object->SetDBField('UserType', $user_type);
 
 				if ( $user_type == UserType::ADMIN ) {
 					$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_AdminGroup'));
 				}
 			}
 
 			if ( $this->Application->ConfigValue('User_Password_Auto') ) {
 				$object->SetDBField('EmailPassword', 1);
 			}
 
 			$this->_makePasswordRequired($event);
 		}
 
 		/**
 		 * Makes password required for new users
 		 *
 		 * @param kEvent $event
 		 */
 		function _makePasswordRequired(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$required_fields = Array ('Password', 'Password_plain', 'VerifyPassword', 'VerifyPassword_plain');
 			$object->setRequired($required_fields);
 		}
 
 		/**
 		 * Load item if id is available
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function LoadItem(&$event)
 		{
 			$id = $this->getPassedID($event);
 
 			if ( $id < 0 ) {
 				// when root, guest and so on
 
 				$object =& $event->getObject();
 				/* @var $object kDBItem */
 
 				$object->Clear($id);
 				return ;
 			}
 
 			parent::LoadItem($event);
 		}
 
 		/**
 		 * Occurs just after login (for hooking)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterLogin(&$event)
 		{
 
 		}
 
 		/**
 		 * Occurs just before logout (for hooking)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeLogout(&$event)
 		{
 
 		}
 
 		/**
 		 * Generates password
 		 *
 		 * @param kEvent $event
 		 */
 		function OnGeneratePassword(&$event)
 		{
 			$event->status = kEvent::erSTOP;
 
 			if ( $this->Application->isAdminUser ) {
 				echo kUtil::generatePassword();
 			}
 		}
 
 		/**
 		 * Changes user's password and logges him in
 		 *
 		 * @param kEvent $event
 		 */
 		function OnResetLostPassword(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$event->CallSubEvent('OnUpdate');
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$user_helper =& $this->Application->recallObject('UserHelper');
 		    	/* @var $user_helper UserHelper */
 
 			    $user =& $user_helper->getUserObject();
 				$user->Load( $object->GetID() );
 
 				if ( $user_helper->checkLoginPermission() ) {
 		    		$user_helper->loginUserById( $user->GetID() );
 				}
 			}
 		}
 
 		/**
 		 * Generates new Root password and email it
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnResetRootPassword(&$event)
 		{
 			$password_formatter =& $this->Application->recallObject('kPasswordFormatter');
 			/* @var $password_formatter kPasswordFormatter */
 
 			$new_root_password = kUtil::generatePassword();
 			$new_root_password_encrypted = $password_formatter->EncryptPassword($new_root_password, 'b38');
 
 			$this->Application->SetConfigValue('RootPass', $new_root_password_encrypted);
 			$this->Application->EmailEventAdmin('ROOT.RESET.PASSWORD', null, Array ('password' => $new_root_password));
 
 			$event->SetRedirectParam('reset', 1);
 			$event->SetRedirectParam('pass', 'm');
 		}
 
 		/**
 		 * Perform login of user, selected in Admin Console, on Front-End in a separate window
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnLoginAs(kEvent &$event)
 		{
 			$user_helper =& $this->Application->recallObject('UserHelper');
 			/* @var $user_helper UserHelper */
 
 			$user =& $user_helper->getUserObject();
 			$user->Load( $this->Application->GetVar('user_id') );
 
 			if ( !$user->isLoaded() ) {
 				return ;
 			}
 
 			if ( $user_helper->checkLoginPermission() ) {
 				$user_helper->loginUserById( $user->GetID() );
 			}
 		}
 	}