Index: branches/5.1.x/core/kernel/db/cat_event_handler.php
===================================================================
--- branches/5.1.x/core/kernel/db/cat_event_handler.php	(revision 13204)
+++ branches/5.1.x/core/kernel/db/cat_event_handler.php	(revision 13205)
@@ -1,2658 +1,2658 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kCatDBEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standart permission mapping
 	 *
 	 */
 	function mapPermissions()
 	{
 		parent::mapPermissions();
 		$permissions = Array(
 			'OnSaveSettings' => Array ('self' => 'add|edit|advanced:import'),
 			'OnResetSettings' => Array ('self' => 'add|edit|advanced:import'),
 			'OnBeforeDeleteOriginal' => Array ('self' => 'edit|advanced:approve'),
 
 			'OnCopy' => Array ('self' => true),
 			'OnDownloadFile' => Array ('self' => 'view'),
 			'OnCancelAction' => Array ('self' => true),
 			'OnItemBuild' => Array ('self' => true),
 			'OnMakeVote' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Load item if id is available
 	 *
 	 * @param kEvent $event
 	 */
 	function LoadItem(&$event)
 	{
 		$object =& $event->getObject();
 		$id = $this->getPassedID($event);
 		if ($object->Load($id)) {
 			$actions =& $this->Application->recallObject('kActions');
 			$actions->Set($event->Prefix_Special.'_id', $object->GetID() );
 
 			$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 			if ($use_pending_editing && $event->Special != 'original') {
 				$this->Application->SetVar($event->Prefix.'.original_id', $object->GetDBField('OrgId'));
 			}
 		}
 		else {
 			$object->setID($id);
 		}
 	}
 
 	/**
 	 * Checks permissions of user
 	 *
 	 * @param kEvent $event
 	 */
 	function CheckPermission(&$event)
 	{
 		if (!$this->Application->isAdmin) {
 			if ($event->Name == 'OnSetSortingDirect') {
 				// allow sorting on front event without view permission
 				return true;
 			}
 		}
 
 		if ($event->Name == 'OnExport') {
 			// save category_id before doing export
 			$this->Application->LinkVar('m_cat_id');
 		}
 
 		if (in_array($event->Name, $this->_getMassPermissionEvents())) {
 			$items = $this->_getPermissionCheckInfo($event);
 
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			if (($event->Name == 'OnSave') && array_key_exists(0, $items)) {
 				// adding new item (ID = 0)
 				$perm_value = $perm_helper->AddCheckPermission($items[0]['CategoryId'], $event->Prefix) > 0;
 			}
 			else {
 				// leave only items, that can be edited
 				$ids = Array ();
 				$check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission';
 				foreach ($items as $item_id => $item_data) {
 					if ($perm_helper->$check_method($item_data['CreatedById'], $item_data['CategoryId'], $event->Prefix) > 0) {
 						$ids[] = $item_id;
 					}
 				}
 
 				if (!$ids) {
 					// no items left for editing -> no permission
 					return $perm_helper->finalizePermissionCheck($event, false);
 				}
 
 				$perm_value = true;
 				$event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method
 			}
 
 			return $perm_helper->finalizePermissionCheck($event, $perm_value);
 		}
 
 		$export_events = Array ('OnSaveSettings', 'OnResetSettings', 'OnExportBegin');
 		if (in_array($event->Name, $export_events)) {
 			// when import settings before selecting target import category
 			return $this->Application->CheckPermission('in-portal:main_import.view');
 		}
 
 		if ($event->Name == 'OnProcessSelected') {
 			if ($this->Application->RecallVar('dst_field') == 'ImportCategory') {
 				// when selecting target import category
 				return $this->Application->CheckPermission('in-portal:main_import.view');
 			}
 		}
 
 		return parent::CheckPermission($event);
 	}
 
 	/**
 	 * Returns events, that require item-based (not just event-name based) permission check
 	 *
 	 * @return Array
 	 */
 	function _getMassPermissionEvents()
 	{
 		return Array (
 			'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove',
 			'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown',
 			'OnCut',
 		);
 	}
 
 	/**
 	 * Returns category item IDs, that require permission checking
 	 *
 	 * @param kEvent $event
 	 * @return string
 	 */
 	function _getPermissionCheckIDs(&$event)
 	{
 		if ($event->Name == 'OnSave') {
 			$selected_ids = implode(',', $this->getSelectedIDs($event, true));
 			if (!$selected_ids) {
 				$selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave)
 			}
 		}
 		else {
 			// OnEdit, OnMassDelete events, when items are checked in grid
 			$selected_ids = implode(',', $this->StoreSelectedIDs($event));
 		}
 
 		return $selected_ids;
 	}
 
 	/**
 	 * Returns information used in permission checking
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function _getPermissionCheckInfo(&$event)
 	{
 		$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		// when saving data from temp table to live table check by data from temp table
 		$item_ids = $this->_getPermissionCheckIDs($event);
 		$items = $perm_helper->GetCategoryItemData($event->Prefix, $item_ids, $event->Name == 'OnSave');
 
 		if (!$items) {
 			// when item not present in temp table, then permission is not checked, because there are no data in db to check
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			list ($id, $fields_hash) = each($items_info);
 
 			if (array_key_exists('CategoryId', $fields_hash)) {
 				$item_category = $fields_hash['CategoryId'];
 			}
 			else {
 				$item_category = $this->Application->GetVar('m_cat_id');
 			}
 
 			$items[$id] = Array (
 				'CreatedById' => $this->Application->RecallVar('use_id'),
 				'CategoryId' => $item_category,
 			);
 		}
 
 		return $items;
 	}
 
 	/**
 	 * Add selected items to clipboard with mode = COPY (CLONE)
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCopy(&$event)
 	{
 		$this->Application->RemoveVar('clipboard');
 		$clipboard_helper =& $this->Application->recallObject('ClipboardHelper');
 		$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Add selected items to clipboard with mode = CUT
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCut(&$event)
 	{
 		$this->Application->RemoveVar('clipboard');
 		$clipboard_helper =& $this->Application->recallObject('ClipboardHelper');
 		$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Checks permission for OnPaste event
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 */
 	function _checkPastePermission(&$event)
 	{
 		$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		$category_id = $this->Application->GetVar('m_cat_id');
 		if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) {
 			// no items left for editing -> no permission
 			return $perm_helper->finalizePermissionCheck($event, false);
 		}
 
 		return true;
 	}
 
 	/**
 	 * Performs category item paste
 	 *
 	 * @param kEvent $event
 	 */
 	function OnPaste(&$event)
 	{
 		if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event)) {
 			$event->status = erFAIL;
 			return;
 		}
 
 		$clipboard_data = $event->getEventParam('clipboard_data');
 
 		if (!$clipboard_data['cut'] && !$clipboard_data['copy']) {
 			return false;
 		}
 
 		if ($clipboard_data['copy']) {
 			$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp kTempTablesHandler */
 
 			$this->Application->SetVar('ResetCatBeforeClone', 1); // used in "kCatDBEventHandler::OnBeforeClone"
 			$temp->CloneItems($event->Prefix, $event->Special, $clipboard_data['copy']);
 		}
 
 		if ($clipboard_data['cut']) {
 			$object =& $this->Application->recallObject($event->getPrefixSpecial().'.item', $event->Prefix, Array('skip_autoload' => true));
 
 			foreach ($clipboard_data['cut'] as $id) {
 				$object->Load($id);
 				$object->MoveToCat();
 			}
 		}
 	}
 
 	/**
 	 * Deletes all selected items.
 	 * Automatically recurse into sub-items using temp handler, and deletes sub-items
 	 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 	 *
 	 * @param kEvent $event
 	 */
 	function OnMassDelete(&$event)
 	{
 		if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 			$event->status = erFAIL;
 			return;
 		}
 
 		$event->status=erSUCCESS;
 
 		$ids = $this->StoreSelectedIDs($event);
 
 		$to_delete = array();
 		if ($recycle_bin = $this->Application->ConfigValue('RecycleBinFolder')) {
 			$rb =& $this->Application->recallObject('c.recycle', null, array('skip_autoload' => true));
 			$rb->Load($recycle_bin);
 			$object =& $this->Application->recallObject($event->Prefix.'.recycleitem', null, Array ('skip_autoload' => true));
 			foreach ($ids as $id) {
 				$object->Load($id);
 				if (preg_match('/^'.preg_quote($rb->GetDBField('ParentPath'),'/').'/', $object->GetDBField('ParentPath'))) {
 					$to_delete[] = $id;
 					continue;
 				}
 				$object->MoveToCat($recycle_bin);
 			}
 			$ids = $to_delete;
 		}
 
 		$temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 
 		$event->setEventParam('ids', $ids);
 		$this->customProcessing($event, 'before');
 		$ids = $event->getEventParam('ids');
 
 		if($ids)
 		{
 			$temp->DeleteItems($event->Prefix, $event->Special, $ids);
 		}
 		$this->clearSelectedIDs($event);
 	}
 
 
 	/**
 	 * Return type clauses for list bulding on front
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function getTypeClauses(&$event)
 	{
 		$types = $event->getEventParam('types');
 		$types = $types ? explode(',', $types) : Array ();
 
 		$except_types = $event->getEventParam('except');
 		$except_types = $except_types ? explode(',', $except_types) : Array ();
 
 		$type_clauses = Array();
 
 		$user_id = $this->Application->RecallVar('user_id');
 		$owner_field = $this->getOwnerField($event->Prefix);
 
 		$type_clauses['my_items']['include'] = '%1$s.'.$owner_field.' = '.$user_id;
 		$type_clauses['my_items']['except'] = '%1$s.'.$owner_field.' <> '.$user_id;
 		$type_clauses['my_items']['having_filter'] = false;
 
 		$type_clauses['pick']['include'] = '%1$s.EditorsPick = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
 		$type_clauses['pick']['except'] = '%1$s.EditorsPick! = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
 		$type_clauses['pick']['having_filter'] = false;
 
 		$type_clauses['hot']['include'] = '`IsHot` = 1 AND PrimaryCat = 1';
 		$type_clauses['hot']['except'] = '`IsHot`! = 1 AND PrimaryCat = 1';
 		$type_clauses['hot']['having_filter'] = true;
 
 		$type_clauses['pop']['include'] = '`IsPop` = 1 AND PrimaryCat = 1';
 		$type_clauses['pop']['except'] = '`IsPop`! = 1 AND PrimaryCat = 1';
 		$type_clauses['pop']['having_filter'] = true;
 
 		$type_clauses['new']['include'] = '`IsNew` = 1 AND PrimaryCat = 1';
 		$type_clauses['new']['except'] = '`IsNew`! = 1 AND PrimaryCat = 1';
 		$type_clauses['new']['having_filter'] = true;
 
 		$type_clauses['displayed']['include'] = '';
 		$displayed = $this->Application->GetVar($event->Prefix.'_displayed_ids');
 		if ($displayed) {
 			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 			$type_clauses['displayed']['except'] = '%1$s.'.$id_field.' NOT IN ('.$displayed.')';
 		}
 		else {
 			$type_clauses['displayed']['except'] = '';
 		}
 		$type_clauses['displayed']['having_filter'] = false;
 
 		if (in_array('search', $types) || in_array('search', $except_types)) {
 			$event_mapping = Array (
 				'simple'		=>	'OnSimpleSearch',
 				'subsearch'		=>	'OnSubSearch',
 				'advanced'		=>	'OnAdvancedSearch'
 			);
 
 			$type = $this->Application->GetVar('search_type', 'simple');
 
 			if ($keywords = $event->getEventParam('keyword_string')) {
 				// processing keyword_string param of ListProducts tag
 				$this->Application->SetVar('keywords', $keywords);
 				$type = 'simple';
 			}
 
 			$search_event = $event_mapping[$type];
 			$this->$search_event($event);
 
 			$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 			$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 			if ($this->Conn->Query($sql)) {
 				$search_res_ids = $this->Conn->GetCol('SELECT ResourceId FROM '.$search_table);
 			}
 
 			if (isset($search_res_ids) && $search_res_ids) {
 				$type_clauses['search']['include'] = '%1$s.ResourceId IN ('.implode(',', $search_res_ids).') AND PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')';
 				$type_clauses['search']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $search_res_ids).') AND PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')';
 			}
 			else {
 				$type_clauses['search']['include'] = '0';
 				$type_clauses['search']['except'] = '1';
 			}
 
 			$type_clauses['search']['having_filter'] = false;
 		}
 
 		if (in_array('related', $types) || in_array('related', $except_types)) {
 
 			$related_to = $event->getEventParam('related_to');
 			if (!$related_to) {
 				$related_prefix = $event->Prefix;
 			}
 			else {
 				$sql = 'SELECT Prefix
 						FROM '.TABLE_PREFIX.'ItemTypes
 						WHERE ItemName = '.$this->Conn->qstr($related_to);
 				$related_prefix = $this->Conn->GetOne($sql);
 			}
 
 			$rel_table = $this->Application->getUnitOption('rel', 'TableName');
 			$item_type = (int)$this->Application->getUnitOption($event->Prefix, 'ItemType');
 
 			if ($item_type == 0) {
 				trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING);
 			}
 
 			// process case, then this list is called inside another list
 			$prefix_special = $event->getEventParam('PrefixSpecial');
 			if (!$prefix_special) {
 				$prefix_special = $this->Application->Parser->GetParam('PrefixSpecial');
 			}
 
 			$id = false;
 			if ($prefix_special !== false) {
 				$processed_prefix = $this->Application->processPrefix($prefix_special);
 				if ($processed_prefix['prefix'] == $related_prefix) {
 					// printing related categories within list of items (not on details page)
 					$list =& $this->Application->recallObject($prefix_special);
 					/* @var $list kDBList */
 
 					$id = $list->GetID();
 				}
 			}
 
 			if ($id === false) {
 				// printing related categories for single item (possibly on details page)
 				if ($related_prefix == 'c') {
 					$id = $this->Application->GetVar('m_cat_id');
 				}
 				else {
 					$id = $this->Application->GetVar($related_prefix . '_id');
 				}
 			}
 
 			$p_item =& $this->Application->recallObject($related_prefix.'.current', null, Array('skip_autoload' => true));
 			$p_item->Load( (int)$id );
 
 			$p_resource_id = $p_item->GetDBField('ResourceId');
 
 			$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
 					WHERE
 						(Enabled = 1)
 						AND (
 								(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 								OR
 								(Type = 1
 									AND (
 											(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 											OR
 											(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
 										)
 								)
 						)';
 
 			$related_ids_array = $this->Conn->Query($sql);
 			$related_ids = Array();
 
 			foreach ($related_ids_array as $key => $record) {
 				$related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ];
 			}
 
 			if (count($related_ids) > 0) {
 				$type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
 				$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
 			}
 			else {
 				$type_clauses['related']['include'] = '0';
 				$type_clauses['related']['except'] = '1';
 			}
 			$type_clauses['related']['having_filter'] = false;
 		}
 
 		if (in_array('favorites', $types) || in_array('favorites', $except_types)) {
 			$sql = 'SELECT ResourceId
 					FROM '.$this->Application->getUnitOption('fav', 'TableName').'
 					WHERE PortalUserId = '.$this->Application->RecallVar('user_id');
 			$favorite_ids = $this->Conn->GetCol($sql);
 			if ($favorite_ids) {
 				$type_clauses['favorites']['include'] = '%1$s.ResourceId IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
 				$type_clauses['favorites']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
 			}
 			else {
 				$type_clauses['favorites']['include'] = 0;
 				$type_clauses['favorites']['except'] = 1;
 			}
 			$type_clauses['favorites']['having_filter'] = false;
 		}
 
 		return $type_clauses;
 	}
 
 	/**
 	 * Returns SQL clause, that will help to select only data from specified category & it's children
 	 *
 	 * @param int $category_id
 	 * @return string
 	 */
 	function getCategoryLimitClause($category_id)
 	{
 		if (!$category_id) {
 			return false;
 		}
 
 		$tree_indexes = $this->Application->getTreeIndex($category_id);
 		if (!$tree_indexes) {
 			// id of non-existing category was given
 			return 'FALSE';
 		}
 
 		return TABLE_PREFIX.'Category.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight'];
 	}
 
 	/**
 	 * Apply filters to list
 	 *
 	 * @param kEvent $event
 	 */
 	function SetCustomQuery(&$event)
 	{
 		parent::SetCustomQuery($event);
 
 		$object =& $event->getObject();
 		/* @var $object kDBList */
 
 		// add category filter if needed
 		if ($event->Special != 'showall' && $event->Special != 'user') {
 			if ($event->getEventParam('parent_cat_id') !== false) {
 				$parent_cat_id = $event->getEventParam('parent_cat_id');
 			}
 			else {
 				$parent_cat_id = $this->Application->GetVar('c_id');
 				if (!$parent_cat_id) {
 					$parent_cat_id = $this->Application->GetVar('m_cat_id');
 				}
 				if (!$parent_cat_id) {
 					$parent_cat_id = 0;
 				}
 			}
 
 			if ("$parent_cat_id" == '0') {
 				// replace "0" category with "Content" category id (this way template
 				$parent_cat_id = $this->Application->findModule('Name', 'Core', 'RootCat');
 			}
 
 			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') == -1) {
 			// for "root" CATEGORY.VIEW permission is checked for items lists too
 			$view_perm = 1;
 		}
 		else {
 			// for any real user itemlist view permission is checked instead of CATEGORY.VIEW
 			$count_helper =& $this->Application->recallObject('CountHelper');
 			/* @var $count_helper kCountHelper */
 
 			list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($event->Prefix, 'perm');
 			$object->addFilter('perm_filter2', $view_filter);
 		}
 
 		$object->addFilter('perm_filter', 'perm.PermId = '.$view_perm);
 
 
 		$types = $event->getEventParam('types');
 		$this->applyItemStatusFilter($object, $types);
 
 		$except_types = $event->getEventParam('except');
 		$type_clauses = $this->getTypeClauses($event);
 
 		// convert prepared type clauses into list filters
 		$includes_or_filter =& $this->Application->makeClass('kMultipleFilter');
 		$includes_or_filter->setType(FLT_TYPE_OR);
 
 		$excepts_and_filter =& $this->Application->makeClass('kMultipleFilter');
 		$excepts_and_filter->setType(FLT_TYPE_AND);
 
 		$includes_or_filter_h =& $this->Application->makeClass('kMultipleFilter');
 		$includes_or_filter_h->setType(FLT_TYPE_OR);
 
 		$excepts_and_filter_h =& $this->Application->makeClass('kMultipleFilter');
 		$excepts_and_filter_h->setType(FLT_TYPE_AND);
 
 		if ($types) {
 			$types_array = explode(',', $types);
 			for ($i = 0; $i < sizeof($types_array); $i++) {
 				$type = trim($types_array[$i]);
 				if (isset($type_clauses[$type])) {
 					if ($type_clauses[$type]['having_filter']) {
 						$includes_or_filter_h->removeFilter('filter_'.$type);
 						$includes_or_filter_h->addFilter('filter_'.$type, $type_clauses[$type]['include']);
 					}else {
 						$includes_or_filter->removeFilter('filter_'.$type);
 						$includes_or_filter->addFilter('filter_'.$type, $type_clauses[$type]['include']);
 					}
 				}
 			}
 		}
 
 		if ($except_types) {
 			$except_types_array = explode(',', $except_types);
 			for ($i = 0; $i < sizeof($except_types_array); $i++) {
 				$type = trim($except_types_array[$i]);
 				if (isset($type_clauses[$type])) {
 					if ($type_clauses[$type]['having_filter']) {
 						$excepts_and_filter_h->removeFilter('filter_'.$type);
 						$excepts_and_filter_h->addFilter('filter_'.$type, $type_clauses[$type]['except']);
 					}else {
 						$excepts_and_filter->removeFilter('filter_'.$type);
 						$excepts_and_filter->addFilter('filter_'.$type, $type_clauses[$type]['except']);
 					}
 				}
 			}
 		}
 
 		/*if (!$this->Application->isAdminUser) {
 			$object->addFilter('expire_filter', '%1$s.Expire IS NULL OR %1$s.Expire > UNIX_TIMESTAMP()');
 		}*/
 
 		/*$list_type = $event->getEventParam('ListType');
 		switch($list_type)
 		{
 			case 'favorites':
 				$fav_table = $this->Application->getUnitOption('fav','TableName');
 				$user_id =& $this->Application->RecallVar('user_id');
 
 				$sql = 'SELECT DISTINCT f.ResourceId
 						FROM '.$fav_table.' f
 						LEFT JOIN '.$object->TableName.' p ON p.ResourceId = f.ResourceId
 						WHERE f.PortalUserId = '.$user_id;
 				$ids = $this->Conn->GetCol($sql);
 				if(!$ids) $ids = Array(-1);
 				$object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.PrimaryCat = 1');
 				$object->addFilter('favorites_filter', '%1$s.`ResourceId` IN ('.implode(',',$ids).')');
 				break;
 			case 'search':
 				$search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 				$sql = '	SELECT DISTINCT ResourceId
 							FROM '.$search_results_table.'
 							WHERE ItemType=11';
 				$ids = $this->Conn->GetCol($sql);
 				if(!$ids) $ids = Array(-1);
 				$object->addFilter('search_filter', '%1$s.`ResourceId` IN ('.implode(',',$ids).')');
 				break;
 		}		*/
 
 		$object->addFilter('includes_filter', $includes_or_filter);
 		$object->addFilter('excepts_filter', $excepts_and_filter);
 
 		$object->addFilter('includes_filter_h', $includes_or_filter_h, HAVING_FILTER);
 		$object->addFilter('excepts_filter_h', $excepts_and_filter_h, HAVING_FILTER);
 	}
 
 	/**
 	 * Adds filter that filters out items with non-required statuses
 	 *
 	 * @param kDBList $object
 	 * @param string $types
 	 */
 	function applyItemStatusFilter(&$object, $types)
 	{
 		// Link1 (before modifications) [Status = 1, OrgId = NULL], Link2 (after modifications) [Status = -2, OrgId = Link1_ID]
 		$pending_editing = $this->Application->getUnitOption($object->Prefix, 'UsePendingEditing');
 
 		if (!$this->Application->isAdminUser) {
 			$types = explode(',', $types);
 			if (in_array('my_items', $types)) {
 				$allow_statuses = Array (STATUS_ACTIVE, STATUS_PENDING, STATUS_PENDING_EDITING);
 				$object->addFilter('status_filter', '%1$s.Status IN ('.implode(',', $allow_statuses).')');
 
 				if ($pending_editing) {
 					$user_id = $this->Application->RecallVar('user_id');
 					$this->applyPendingEditingFilter($object, $user_id);
 				}
 			}
 			else {
 				$object->addFilter('status_filter', '(%1$s.Status = ' . STATUS_ACTIVE . ') AND (' . TABLE_PREFIX . 'Category.Status = ' . STATUS_ACTIVE . ')');
 				if ($pending_editing) {
 					// if category item uses pending editing abilities, then in no cases show pending copies on front
 					$object->addFilter('original_filter', '%1$s.OrgId = 0 OR %1$s.OrgId IS NULL');
 				}
 			}
 		}
 		else {
 			if ($pending_editing) {
 				$this->applyPendingEditingFilter($object);
 			}
 		}
 	}
 
 	/**
 	 * Adds filter, that removes live items if they have pending editing copies
 	 *
 	 * @param kDBList $object
 	 * @param int $user_id
 	 */
 	function applyPendingEditingFilter(&$object, $user_id = null)
 	{
 		$sql = 'SELECT OrgId
 				FROM '.$object->TableName.'
 				WHERE Status = '.STATUS_PENDING_EDITING.' AND OrgId IS NOT NULL';
 
 		if (isset($user_id)) {
 			$owner_field = $this->getOwnerField($object->Prefix);
 			$sql .= ' AND '.$owner_field.' = '.$user_id;
 		}
 
 		$pending_ids = $this->Conn->GetCol($sql);
 		if ($pending_ids) {
 			$object->addFilter('no_original_filter', '%1$s.'.$object->IDField.' NOT IN ('.implode(',', $pending_ids).')');
 		}
 	}
 
 	/**
 	 * Adds calculates fields for item statuses
 	 *
 	 * @param kCatDBItem $object
 	 * @param kEvent $event
 	 */
 	function prepareObject(&$object, &$event)
 	{
 		$this->prepareItemStatuses($event);
 
 		$object->addCalculatedField('CachedNavbar', 'l'.$this->Application->GetVar('m_lang').'_CachedNavbar');
 
 		if ($event->Special == 'export' || $event->Special == 'import') {
 			$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 			$export_helper->prepareExportColumns($event);
 		}
 	}
 
 	/**
 	 * Creates calculated fields for all item statuses based on config settings
 	 *
 	 * @param kEvent $event
 	 */
 	function prepareItemStatuses(&$event)
 	{
 		$object =& $event->getObject( Array('skip_autoload' => true) );
 
 		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
 		if (!$property_map) {
 			return ;
 		}
 
 		// new items
 		$object->addCalculatedField('IsNew', '	IF(%1$s.NewItem = 2,
 													IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '.
 														$this->Application->ConfigValue($property_map['NewDays']).
 														'*3600*24), 1, 0),
 													%1$s.NewItem
 												)');
 
 		// hot items (cache updated every hour)
 		if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$serial_name = $this->Application->incrementCacheSerial($event->Prefix, null, false);
 			$hot_limit = $this->Application->getCache($property_map['HotLimit'] . '[%' . $serial_name . '%]');
 		}
 		else {
 			$hot_limit = $this->Application->getDBCache($property_map['HotLimit']);
 		}
 
 		if ($hot_limit === false) {
 			$hot_limit = $this->CalculateHotLimit($event);
 		}
 
 		$object->addCalculatedField('IsHot', '	IF(%1$s.HotItem = 2,
 													IF(%1$s.'.$property_map['ClickField'].' >= '.$hot_limit.', 1, 0),
 													%1$s.HotItem
 												)');
 
 		// popular items
 		$object->addCalculatedField('IsPop', '	IF(%1$s.PopItem = 2,
 													IF(%1$s.CachedVotesQty >= '.
 														$this->Application->ConfigValue($property_map['MinPopVotes']).
 														' AND %1$s.CachedRating >= '.
 														$this->Application->ConfigValue($property_map['MinPopRating']).
 														', 1, 0),
 													%1$s.PopItem)');
 
 	}
 
 	function CalculateHotLimit(&$event)
 	{
 		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
 
 		if (!$property_map) {
 			return;
 		}
 
 		$click_field = $property_map['ClickField'];
 
 		$last_hot = $this->Application->ConfigValue($property_map['MaxHotNumber']) - 1;
 		$sql = 'SELECT '.$click_field.' FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
 				ORDER BY '.$click_field.' DESC
 				LIMIT '.$last_hot.', 1';
 		$res = $this->Conn->GetCol($sql);
 		$hot_limit = (double)array_shift($res);
 
 		if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$serial_name = $this->Application->incrementCacheSerial($event->Prefix, null, false);
 			$this->Application->setCache($property_map['HotLimit'] . '[%' . $serial_name . '%]', $hot_limit);
 		}
 		else {
 			$this->Application->setDBCache($property_map['HotLimit'], $hot_limit, 3600);
 		}
 
 		return $hot_limit;
 	}
 
 	/**
 	 * Moves item to preferred category, updates item hits
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeItemUpdate(&$event)
 	{
 		parent::OnBeforeItemUpdate($event);
 
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		// update hits field
 		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
 		if ($property_map) {
 			$click_field = $property_map['ClickField'];
 
 			if( $this->Application->isAdminUser && ($this->Application->GetVar($click_field.'_original') !== false) &&
 				floor($this->Application->GetVar($click_field.'_original')) != $object->GetDBField($click_field) )
 			{
 				$sql = 'SELECT MAX('.$click_field.') FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
 						WHERE FLOOR('.$click_field.') = '.$object->GetDBField($click_field);
 				$hits = ( $res = $this->Conn->GetOne($sql) ) ? $res + 0.000001 : $object->GetDBField($click_field);
 				$object->SetDBField($click_field, $hits);
 			}
 		}
 
 		// change category
 		$target_category = $object->GetDBField('CategoryId');
 		if ($object->GetOriginalField('CategoryId') != $target_category) {
 			$object->MoveToCat($target_category);
 		}
 	}
 
 	/**
 	 * Load price from temp table if product mode is temp table
 	 *
 	 * @param kEvent $event
 	 */
 	function OnAfterItemLoad(&$event)
 	{
 		$special = substr($event->Special, -6);
 
 		$object =& $event->getObject();
 		/* @var $object kCatDBItem */
 
 		if ($special == 'import' || $special == 'export') {
 			$image_data = $object->getPrimaryImageData();
 
 			if ($image_data) {
 				$thumbnail_image = $image_data[$image_data['LocalThumb'] ? 'ThumbPath' : 'ThumbUrl'];
 				if ($image_data['SameImages']) {
 					$full_image = '';
 				}
 				else {
 					$full_image = $image_data[$image_data['LocalImage'] ? 'LocalPath' : 'Url'];
 				}
 				$object->SetDBField('ThumbnailImage', $thumbnail_image);
 				$object->SetDBField('FullImage', $full_image);
 				$object->SetDBField('ImageAlt', $image_data['AltName']);
 			}
 		}
 
 		// substituiting pending status value for pending editing
 		if ($object->HasField('OrgId') && $object->GetDBField('OrgId') > 0 && $object->GetDBField('Status') == -2) {
 			$options = $object->Fields['Status']['options'];
 			foreach ($options as $key => $val) {
 				if ($key == 2) $key = -2;
 				$new_options[$key] = $val;
 			}
 			$object->Fields['Status']['options'] = $new_options;
 		}
 
 		// linking existing images for item with virtual fields
 		$image_helper =& $this->Application->recallObject('ImageHelper');
 		/* @var $image_helper ImageHelper */
 
 		$image_helper->LoadItemImages($object);
 
 		// linking existing files for item with virtual fields
 		$file_helper =& $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$file_helper->LoadItemFiles($object);
 
 		// set item's additional categories to virtual field (used in editing)
 		$item_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
 		$object->SetDBField('MoreCategories', $item_categories ? '|'.implode('|', $item_categories).'|' : '');
 	}
 
 	function OnAfterItemUpdate(&$event)
 	{
 		$this->CalculateHotLimit($event);
 
 		if ( substr($event->Special, -6) == 'import') {
 			$this->setCustomExportColumns($event);
 		}
 
 		if (!$this->Application->isAdminUser) {
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$image_helper =& $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			// process image upload in virtual fields
 			$image_helper->SaveItemImages($object);
 
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			// process file upload in virtual fields
 			$file_helper->SaveItemFiles($object);
 
 			if ($event->Special != '-item') {
 				// don't touch categories during cloning
 				$this->processAdditionalCategories($object, 'update');
 			}
 		}
 	}
 
 	/**
 	 * sets values for import process
 	 *
 	 * @param kEvent $event
 	 */
 	function OnAfterItemCreate(&$event)
 	{
 		if ( substr($event->Special, -6) == 'import') {
 			$this->setCustomExportColumns($event);
 		}
 
 		if (!$this->Application->isAdminUser) {
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$image_helper =& $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			// process image upload in virtual fields
 			$image_helper->SaveItemImages($object);
 
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			// process file upload in virtual fields
 			$file_helper->SaveItemFiles($object);
 
 			if ($event->Special != '-item') {
 				// don't touch categories during cloning
 				$this->processAdditionalCategories($object, 'create');
 			}
 		}
 	}
 
 	/**
 	 * Make record to search log
 	 *
 	 * @param string $keywords
 	 * @param int $search_type 0 - simple search, 1 - advanced search
 	 */
 	function saveToSearchLog($keywords, $search_type = 0)
 	{
 		// don't save keywords for each module separately, just one time
 		// static variable can't help here, because each module uses it's own class instance !
 		if (!$this->Application->GetVar('search_logged')) {
 			$sql = 'UPDATE '.TABLE_PREFIX.'SearchLog
 					SET Indices = Indices + 1
 					WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search
 	        $this->Conn->Query($sql);
 	        if ($this->Conn->getAffectedRows() == 0) {
 	            $fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type);
 	        	$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLog');
 	        }
 
 	        $this->Application->SetVar('search_logged', 1);
 		}
 	}
 
 	/**
 	 * Makes simple search for category items
 	 * based on keywords string
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSimpleSearch(&$event)
 	{
 		$event->redirect = false;
 		$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 
 		$keywords = unhtmlentities( trim($this->Application->GetVar('keywords')) );
 
 		$query_object =& $this->Application->recallObject('HTTPQuery');
 		$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 		if(!isset($query_object->Get['keywords']) &&
 			!isset($query_object->Post['keywords']) &&
 			$this->Conn->Query($sql))
 		{
 			return; // used when navigating by pages or changing sorting in search results
 		}
 		if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length'))
 		{
 			$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
 			$this->Application->SetVar('keywords_too_short', 1);
 			return; // if no or too short keyword entered, doing nothing
 		}
 
 		$this->Application->StoreVar('keywords', $keywords);
 
 		$this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search
 
 		$event->setPseudoClass('_List');
 
 		$object =& $event->getObject();
 		/* @var $object kDBList */
 
 		$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
 		$lang = $this->Application->GetVar('m_lang');
 		$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
 		$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
 
 		$sql = 'SELECT *
 				FROM ' . $this->Application->getUnitOption('confs', 'TableName') . '
 				WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1';
 		$search_config = $this->Conn->Query($sql, 'FieldName');
 
 		$field_list = array_keys($search_config);
 
 		$join_clauses = Array();
 
 		// field processing
 		$weight_sum = 0;
 
 		$alias_counter = 0;
 
 		$custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 		if ($custom_fields) {
 			$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
 			$join_clauses[] = '	LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
 		}
 
 		// what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table))
 		$search_config_map = Array();
 
 		foreach ($field_list as $key => $field) {
 			$options = $object->getFieldOptions($field);
 			$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
 			$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
 
 			// processing multilingual fields
 			if (getArrayValue($options, 'formatter') == 'kMultiLanguage') {
 				$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 				$field_list[$key] = 'l'.$lang.'_'.$field;
 
 				if (!isset($search_config[$field]['ForeignField'])) {
 					$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
 					$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 				}
 			}
 
 			// processing fields from other tables
 			if ($foreign_field = $search_config[$field]['ForeignField']) {
 				$exploded = explode(':', $foreign_field, 2);
 				if ($exploded[0] == 'CALC') {
 					// ignoring having type clauses in simple search
 					unset($field_list[$key]);
 					continue;
 				}
 				else {
 					$multi_lingual = false;
 					if ($exploded[0] == 'MULTI') {
 						$multi_lingual = true;
 						$foreign_field = $exploded[1];
 					}
 
 					$exploded = explode('.', $foreign_field);	// format: table.field_name
 					$foreign_table = TABLE_PREFIX.$exploded[0];
 
 					$alias_counter++;
 					$alias = 't'.$alias_counter;
 
 					if ($multi_lingual) {
 						$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
 						$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 						$search_config_map[ $field_list[$key] ] = $field;
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 					else {
 						$field_list[$key] = $alias.'.'.$exploded[1];
 						$search_config_map[ $field_list[$key] ] = $field;
 					}
 
 					$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
 					$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 					$join_clauses[] = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 										ON '.$join_clause;
 				}
 			}
 			else {
 				// processing fields from local table
 				if ($search_config[$field]['CustomFieldId']) {
 					$local_table = 'custom_data';
 
 					// search by custom field value on current language
 					$custom_field_id = array_search($field_list[$key], $custom_fields);
 					$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
 
 					// search by custom field value on primary language
 					$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
 					$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 				}
 
 				$field_list[$key] = $local_table.'.'.$field_list[$key];
 				$search_config_map[ $field_list[$key] ] = $field;
 			}
 		}
 
 		// keyword string processing
 		$search_helper =& $this->Application->recallObject('SearchHelper');
 		/* @var $search_helper kSearchHelper */
 
 		$where_clause = Array ();
 		foreach ($field_list as $field) {
 			if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
 				// local real field
 				$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
 				if ($filter_data) {
 					$where_clause[] = $filter_data['value'];
 				}
 			}
 			elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
 				$custom_field_name = 'cust_' . $search_config_map[$field];
 				$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
 				if ($filter_data) {
 					$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
 				}
 			}
 			else {
 				$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
 			}
 		}
 
 		$where_clause = '(' . implode(') OR (', $where_clause) . ')';
 
 		$search_scope = $this->Application->GetVar('search_scope');
 		if ($search_scope == 'category') {
 			$category_id = $this->Application->GetVar('m_cat_id');
 			$category_filter = $this->getCategoryLimitClause($category_id);
 			if ($category_filter !== false) {
 				$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON '.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$items_table.'.ResourceId';
 				$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'Category ON '.TABLE_PREFIX.'Category.CategoryId = '.TABLE_PREFIX.'CategoryItems.CategoryId';
 
 				$where_clause = '('.$this->getCategoryLimitClause($category_id).') AND '.$where_clause;
 			}
 		}
 
 		$where_clause = $where_clause.' AND '.$items_table.'.Status=1';
 
 		if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
 			if ($event->MasterEvent->getEventParam('ResultIds')) {
 				$where_clause .= ' AND '.$items_table.'.ResourceId IN ('.implode(',', $event->MasterEvent->getEventParam('ResultIds')).')';
 			}
 		}
 
 		// making relevance clause
 		$positive_words = $search_helper->getPositiveKeywords($keywords);
 		$this->Application->StoreVar('highlight_keywords', serialize($positive_words));
 		$revelance_parts = Array();
 		reset($search_config);
 
 		foreach ($positive_words as $keyword_index => $positive_word) {
 			$positive_word = $search_helper->transformWildcards($positive_word);
 			$positive_words[$keyword_index] = $this->Conn->escape($positive_word);
 		}
 
 		foreach ($field_list as $field) {
 
 			if (!array_key_exists($field, $search_config_map)) {
 				$map_key = $search_config_map[$items_table . '.' . $field];
 			}
 			else {
 				$map_key = $search_config_map[$field];
 			}
 
 			$config_elem = $search_config[ $map_key ];
 			$weight = $config_elem['Priority'];
 			$revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
 			foreach ($positive_words as $keyword) {
 				$revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
 			}
 		}
 
 		$revelance_parts = array_unique($revelance_parts);
 
 		$conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
 		$rel_keywords	= $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix)	/ 100;
 		$rel_pop		= $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix)		/ 100;
 		$rel_rating		= $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix)	/ 100;
 		$relevance_clause = '('.implode(' + ', $revelance_parts).') / '.$weight_sum.' * '.$rel_keywords;
 		if ($rel_pop && isset($object->Fields['Hits'])) {
 			$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 		}
 		if ($rel_rating && isset($object->Fields['CachedRating'])) {
 			$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 		}
 
 		// building final search query
 		if (!$this->Application->GetVar('do_not_drop_search_table')) {
 			$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
 			$this->Application->SetVar('do_not_drop_search_table', true);
 		}
 
 
 		$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
 		if ($search_table_exists) {
 			$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
 		}
 		else {
 			$select_intro = 'CREATE TABLE '.$search_table.' AS ';
 		}
 
 		$edpick_clause = $this->Application->getUnitOption($event->Prefix.'.EditorsPick', 'Fields') ? $items_table.'.EditorsPick' : '0';
 
 
 		$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
 							'.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' AS ItemId,
 							'.$items_table.'.ResourceId,
 							'.$this->Application->getUnitOption($event->Prefix, 'ItemType').' AS ItemType,
 							 '.$edpick_clause.' AS EdPick
 					FROM '.$object->TableName.'
 					'.implode(' ', $join_clauses).'
 					WHERE '.$where_clause.'
 					GROUP BY '.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField');
 
 		$res = $this->Conn->Query($sql);
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSubSearch(&$event)
 	{
 		$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 		$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 		if($this->Conn->Query($sql))
 		{
 			$sql = 'SELECT DISTINCT ResourceId FROM '.$search_table;
 			$ids = $this->Conn->GetCol($sql);
 		}
 		$event->setEventParam('ResultIds', $ids);
 		$event->CallSubEvent('OnSimpleSearch');
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 * @todo Change all hardcoded Products table & In-Commerce module usage to dynamic usage from item config !!!
 	 */
 	function OnAdvancedSearch(&$event)
 	{
 		$query_object =& $this->Application->recallObject('HTTPQuery');
 		if(!isset($query_object->Post['andor']))
 		{
 			return; // used when navigating by pages or changing sorting in search results
 		}
 
 		$this->Application->RemoveVar('keywords');
 		$this->Application->RemoveVar('Search_Keywords');
 
 		$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
 
 		$sql = 'SELECT *
 				FROM '.$this->Application->getUnitOption('confs', 'TableName').'
 				WHERE (ModuleName = '.$this->Conn->qstr($module_name).') AND (AdvancedSearch = 1)';
 		$search_config = $this->Conn->Query($sql);
 
 		$lang = $this->Application->GetVar('m_lang');
 		$object =& $event->getObject();
 		$object->SetPage(1);
 
 		$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
 
 		$search_keywords = $this->Application->GetVar('value'); // will not be changed
 
 		$keywords = $this->Application->GetVar('value'); // will be changed down there
 		$verbs = $this->Application->GetVar('verb');
 		$glues = $this->Application->GetVar('andor');
 
 		$and_conditions = Array();
 		$or_conditions = Array();
 		$and_having_conditions = Array();
 		$or_having_conditions = Array();
 		$join_clauses = Array();
 		$highlight_keywords = Array();
 		$relevance_parts = Array();
 
 		$alias_counter = 0;
 
 		$custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 		if ($custom_fields) {
 			$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
 			$join_clauses[] = '	LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
 		}
 
 		$search_log = '';
 		$weight_sum = 0;
 
 		// processing fields and preparing conditions
 		foreach ($search_config as $record) {
 			$field = $record['FieldName'];
 			$join_clause = '';
 			$condition_mode = 'WHERE';
 
 			// field processing
 
 			$options = $object->getFieldOptions($field);
 			$local_table = TABLE_PREFIX.$record['TableName'];
 			$weight_sum += $record['Priority']; // counting weight sum; used when making relevance clause
 
 			// processing multilingual fields
 			if (getArrayValue($options, 'formatter') == 'kMultiLanguage') {
 				$field_name = 'l'.$lang.'_'.$field;
 			}
 			else {
 				$field_name = $field;
 			}
 
 			// processing fields from other tables
 			if ($foreign_field = $record['ForeignField']) {
 				$exploded = explode(':', $foreign_field, 2);
 				if($exploded[0] == 'CALC')
 				{
 					$user_groups = $this->Application->RecallVar('UserGroups');
 					$field_name = str_replace('{PREFIX}', TABLE_PREFIX, $exploded[1]);
 					$join_clause = str_replace('{PREFIX}', TABLE_PREFIX, $record['JoinClause']);
 					$join_clause = str_replace('{USER_GROUPS}', $user_groups, $join_clause);
 					$join_clause = ' LEFT JOIN '.$join_clause;
 
 					$condition_mode = 'HAVING';
 				}
 				else {
 					$exploded = explode('.', $foreign_field);
 					$foreign_table = TABLE_PREFIX.$exploded[0];
 
 					if($record['CustomFieldId']) {
 						$exploded[1] = 'l'.$lang.'_'.$exploded[1];
 					}
 
 					$alias_counter++;
 					$alias = 't'.$alias_counter;
 
 					$field_name = $alias.'.'.$exploded[1];
 					$join_clause = str_replace('{ForeignTable}', $alias, $record['JoinClause']);
 					$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 					if($record['CustomFieldId'])
 					{
 						$join_clause .= ' AND '.$alias.'.CustomFieldId='.$record['CustomFieldId'];
 					}
 					$join_clause = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 										ON '.$join_clause;
 				}
 			}
 			else
 			{
 				// processing fields from local table
 				if ($record['CustomFieldId']) {
 					$local_table = 'custom_data';
 					$field_name = 'l'.$lang.'_cust_'.array_search($field_name, $custom_fields);
 				}
 
 				$field_name = $local_table.'.'.$field_name;
 			}
 
 			$condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
 			if ($record['CustomFieldId'] && strlen($condition)) {
 				// search in primary value of custom field + value in current language
 				$field_name = $local_table.'.'.'l'.$this->Application->GetDefaultLanguageId().'_cust_'.array_search($field, $custom_fields);
 				$primary_condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
 				$condition = '('.$condition.' OR '.$primary_condition.')';
 			}
 
 			if ($condition) {
 				if ($join_clause) {
 					$join_clauses[] = $join_clause;
 				}
 
 				$relevance_parts[] = 'IF('.$condition.', '.$record['Priority'].', 0)';
 				if ($glues[$field] == 1) { // and
 					if ($condition_mode == 'WHERE') {
 						$and_conditions[] = $condition;
 					}
 					else {
 						$and_having_conditions[] = $condition;
 					}
 				}
 				else { // or
 					if ($condition_mode == 'WHERE') {
 						$or_conditions[] = $condition;
 					}
 					else {
 						$or_having_conditions[] = $condition;
 					}
 				}
 
 				// create search log record
 				$search_log_data = Array('search_config' => $record, 'verb' => getArrayValue($verbs, $field), 'value' => ($record['FieldType'] == 'range') ? $search_keywords[$field.'_from'].'|'.$search_keywords[$field.'_to'] : $search_keywords[$field]);
 				$search_log[] = $this->Application->Phrase('la_Field').' "'.$this->getHuman('Field', $search_log_data).'" '.$this->getHuman('Verb', $search_log_data).' '.$this->Application->Phrase('la_Value').' '.$this->getHuman('Value', $search_log_data).' '.$this->Application->Phrase($glues[$field] == 1 ? 'lu_And' : 'lu_Or');
 			}
 		}
 
 		if ($search_log) {
 			$search_log = implode('<br />', $search_log);
 	    	$search_log = preg_replace('/(.*) '.preg_quote($this->Application->Phrase('lu_and'), '/').'|'.preg_quote($this->Application->Phrase('lu_or'), '/').'$/is', '\\1', $search_log);
 	    	$this->saveToSearchLog($search_log, 1); // advanced search
 		}
 
 		$this->Application->StoreVar('highlight_keywords', serialize($highlight_keywords));
 
 		// making relevance clause
 		if($relevance_parts)
 		{
 			$conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
 			$rel_keywords	= $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix)	/ 100;
 			$rel_pop		= $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix)		/ 100;
 			$rel_rating		= $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix)	/ 100;
 			$relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords;
 			$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 			$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 		}
 		else
 		{
 			$relevance_clause = '0';
 		}
 
 		// building having clause
 		if($or_having_conditions)
 		{
 			$and_having_conditions[] = '('.implode(' OR ', $or_having_conditions).')';
 		}
 		$having_clause = implode(' AND ', $and_having_conditions);
 		$having_clause = $having_clause ? ' HAVING '.$having_clause : '';
 
 		// building where clause
 		if($or_conditions)
 		{
 			$and_conditions[] = '('.implode(' OR ', $or_conditions).')';
 		}
 //		$and_conditions[] = $items_table.'.Status = 1';
 		$where_clause = implode(' AND ', $and_conditions);
 		if(!$where_clause)
 		{
 			if($having_clause)
 			{
 				$where_clause = '1';
 			}
 			else
 			{
 				$where_clause = '0';
 				$this->Application->SetVar('adv_search_error', 1);
 			}
 		}
 		$where_clause .= ' AND '.$items_table.'.Status = 1';
 
 		// building final search query
 		$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 
 		$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
 
 		$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 		$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
 		$pick_field = isset($fields['EditorsPick']) ? $items_table.'.EditorsPick' : '0';
 
 		$sql = '	CREATE TABLE '.$search_table.'
 					SELECT 	'.$relevance_clause.' AS Relevance,
 							'.$items_table.'.'.$id_field.' AS ItemId,
 							'.$items_table.'.ResourceId AS ResourceId,
 							11 AS ItemType,
 							'.$pick_field.' AS EdPick
 					FROM '.$items_table.'
 					'.implode(' ', $join_clauses).'
 					WHERE '.$where_clause.'
 					GROUP BY '.$items_table.'.'.$id_field.
 					$having_clause;
 
 		$res = $this->Conn->Query($sql);
 	}
 
 	function getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, &$highlight_keywords)
 	{
 		$field = $record['FieldName'];
 
 		$condition_patterns = Array (
 			'any'			=> '%s LIKE %s',
 			'contains'		=> '%s LIKE %s',
 			'notcontains'	=> '(NOT (%1$s LIKE %2$s) OR %1$s IS NULL)',
 			'is'			=> '%s = %s',
 			'isnot'			=> '(%1$s != %2$s OR %1$s IS NULL)'
 		);
 
 		$condition = '';
 		switch ($record['FieldType']) {
 			case 'select':
 				$keywords[$field] = unhtmlentities( $keywords[$field] );
 				if ($keywords[$field]) {
 					$condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] ));
 				}
 				break;
 
 			case 'multiselect':
 				$keywords[$field] = unhtmlentities( $keywords[$field] );
 				if ($keywords[$field]) {
 					$condition = Array ();
 					$values = explode('|', substr($keywords[$field], 1, -1));
 					foreach ($values as $selected_value) {
 						$condition[] = sprintf($condition_patterns['contains'], $field_name, $this->Conn->qstr('%|'.$selected_value.'|%'));
 					}
 					$condition = '('.implode(' OR ', $condition).')';
 				}
 				break;
 
 			case 'text':
 				$keywords[$field] = unhtmlentities( $keywords[$field] );
 
 				if (mb_strlen($keywords[$field]) >= $this->Application->ConfigValue('Search_MinKeyword_Length')) {
 					$highlight_keywords[] = $keywords[$field];
 					if (in_array($verbs[$field], Array('any', 'contains', 'notcontains'))) {
 						$keywords[$field] = '%'.strtr($keywords[$field], Array('%' => '\\%', '_' => '\\_')).'%';
 					}
 					$condition = sprintf($condition_patterns[$verbs[$field]], $field_name, $this->Conn->qstr( $keywords[$field] ));
 				}
 				break;
 
 			case 'boolean':
 				if ($keywords[$field] != -1) {
 					$property_mappings = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings');
 					$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
 
 					switch ($field) {
 						case 'HotItem':
 							$hot_limit_var = getArrayValue($property_mappings, 'HotLimit');
 							if ($hot_limit_var) {
 								$hot_limit = (int)$this->Application->getDBCache($hot_limit_var);
 
 								$condition = 	'IF('.$items_table.'.HotItem = 2,
 												IF('.$items_table.'.Hits >= '.
 												$hot_limit.
 												', 1, 0), '.$items_table.'.HotItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'PopItem':
 							$votes2pop_var = getArrayValue($property_mappings, 'VotesToPop');
 							$rating2pop_var = getArrayValue($property_mappings, 'RatingToPop');
 							if ($votes2pop_var && $rating2pop_var) {
 								$condition = 'IF('.$items_table.'.PopItem = 2, IF('.$items_table.'.CachedVotesQty >= '.
 												$this->Application->ConfigValue($votes2pop_var).
 												' AND '.$items_table.'.CachedRating >= '.
 												$this->Application->ConfigValue($rating2pop_var).
 												', 1, 0), '.$items_table.'.PopItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'NewItem':
 							$new_days_var = getArrayValue($property_mappings, 'NewDays');
 							if ($new_days_var) {
 								$condition = 	'IF('.$items_table.'.NewItem = 2,
 												IF('.$items_table.'.CreatedOn >= (UNIX_TIMESTAMP() - '.
 												$this->Application->ConfigValue($new_days_var).
 												'*3600*24), 1, 0), '.$items_table.'.NewItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'EditorsPick':
 							$condition = $items_table.'.EditorsPick = '.$keywords[$field];
 							break;
 					}
 				}
 				break;
 
 			case 'range':
 				$range_conditions = Array();
 				if ($keywords[$field.'_from'] && !preg_match("/[^0-9]/i", $keywords[$field.'_from'])) {
 					$range_conditions[] = $field_name.' >= '.$keywords[$field.'_from'];
 				}
 				if ($keywords[$field.'_to'] && !preg_match("/[^0-9]/i", $keywords[$field.'_to'])) {
 					$range_conditions[] = $field_name.' <= '.$keywords[$field.'_to'];
 				}
 				if ($range_conditions) {
 					$condition = implode(' AND ', $range_conditions);
 				}
 				break;
 
 			case 'date':
 				if ($keywords[$field]) {
 					if (in_array($keywords[$field], Array('today', 'yesterday'))) {
 						$current_time = getdate();
 						$day_begin = adodb_mktime(0, 0, 0, $current_time['mon'], $current_time['mday'], $current_time['year']);
 						$time_mapping = Array('today' => $day_begin, 'yesterday' => ($day_begin - 86400));
 						$min_time = $time_mapping[$keywords[$field]];
 					}
 					else {
 						$time_mapping = Array (
 							'last_week' => 604800, 'last_month' => 2628000, 'last_3_months' => 7884000,
 							'last_6_months' => 15768000, 'last_year' => 31536000,
 						);
 						$min_time = adodb_mktime() - $time_mapping[$keywords[$field]];
 					}
 					$condition = $field_name.' > '.$min_time;
 				}
 				break;
 		}
 
 		return $condition;
 	}
 
 	function getHuman($type, $search_data)
 	{
 		$type = ucfirst(strtolower($type));
 		extract($search_data);
 
 		switch ($type) {
 			case 'Field':
 				return $this->Application->Phrase($search_config['DisplayName']);
 				break;
 
 			case 'Verb':
 				return $verb ? $this->Application->Phrase('lu_advsearch_'.$verb) : '';
 				break;
 
 			case 'Value':
 				switch ($search_config['FieldType']) {
 					case 'date':
 						$values = Array(0 => 'lu_comm_Any', 'today' => 'lu_comm_Today',
 										'yesterday' => 'lu_comm_Yesterday', 'last_week' => 'lu_comm_LastWeek',
 										'last_month' => 'lu_comm_LastMonth', 'last_3_months' => 'lu_comm_Last3Months',
 										'last_6_months' => 'lu_comm_Last6Months', 'last_year' => 'lu_comm_LastYear');
 						$ret = $this->Application->Phrase($values[$value]);
 						break;
 
 					case 'range':
 						$value = explode('|', $value);
 						return $this->Application->Phrase('lu_comm_From').' "'.$value[0].'" '.$this->Application->Phrase('lu_comm_To').' "'.$value[1].'"';
 						break;
 
 					case 'boolean':
 						$values = Array(1 => 'lu_comm_Yes', 0 => 'lu_comm_No', -1 => 'lu_comm_Both');
 						$ret = $this->Application->Phrase($values[$value]);
 						break;
 
 					default:
 						$ret = $value;
 						break;
 
 				}
 				return '"'.$ret.'"';
 				break;
 		}
 	}
 
 
 
 	/**
 	 * Set's correct page for list
 	 * based on data provided with event
 	 *
 	 * @param kEvent $event
 	 * @access private
 	 * @see OnListBuild
 	 */
 	function SetPagination(&$event)
 	{
 		$object =& $event->getObject();
 		/* @var $object kDBList */
 
 		// get PerPage (forced -> session -> config -> 10)
 		$object->SetPerPage( $this->getPerPage($event) );
 
 		// main lists on Front-End have special get parameter for page
 		$page = $object->mainList ? $this->Application->GetVar('page') : false;
 
 		if (!$page) {
 			// page is given in "env" variable for given prefix
 			$page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page');
 		}
 
 		if (!$page && $event->Special) {
 			// when not part of env, then variables like "prefix.special_Page" are
 			// replaced (by PHP) with "prefix_special_Page", so check for that too
 			$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
 		}
 
 		if (!$object->mainList) {
 			// main lists doesn't use session for page storing
 			$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
 
 			if (!$page) {
 				if ($this->Application->RewriteURLs()) {
 					// when page not found by prefix+special, then try to search it without special at all
 					$page = $this->Application->GetVar($event->Prefix . '_Page');
 
 					if (!$page) {
 						// page not found in request -> get from session
 						$page = $this->Application->RecallVar($event->Prefix . '_Page');
 					}
 
 					if ($page) {
 						// page found in request -> store in session
 						$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
 					}
 				}
 				else {
 					// page not found in request -> get from session
 					$page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page');
 				}
 			}
 			else {
 				// page found in request -> store in session
 				$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
 			}
 
 			if ( !$event->getEventParam('skip_counting') ) {
 				// when stored page is larger, then maximal list page number
 				// (such case is also processed in kDBList::Query method)
 				$pages = $object->GetTotalPages();
 
 				if ($page > $pages) {
 					$page = 1;
 					$this->Application->StoreVar($event->getPrefixSpecial().'_Page', 1, true);
 				}
 			}
 		}
 
 		$object->SetPage($page);
 	}
 
 /* === RELATED TO IMPORT/EXPORT: BEGIN === */
 
 	/**
 	 * Shows export dialog
 	 *
 	 * @param kEvent $event
 	 */
 	function OnExport(&$event)
 	{
 		$selected_ids = $this->StoreSelectedIDs($event);
 		if (implode(',', $selected_ids) == '') {
 			// K4 fix when no ids found bad selected ids array is formed
 			$selected_ids = false;
 		}
 
 		$selected_cats_ids = $this->Application->GetVar('export_categories');
 
 		$this->Application->StoreVar($event->Prefix.'_export_ids', $selected_ids ? implode(',', $selected_ids) : '' );
 		$this->Application->StoreVar($event->Prefix.'_export_cats_ids', $selected_cats_ids);
 
 		$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 		/* @var $export_helper kCatDBItemExportHelper */
 
 		$redirect_params = Array (
 			$this->Prefix.'.export_event' => 'OnNew',
 			'pass' => 'all,'.$this->Prefix.'.export'
 		);
 
 		$event->setRedirectParams($redirect_params);
 	}
 
 	/**
 	 * Performs each export step & displays progress percent
 	 *
 	 * @param kEvent $event
 	 */
 	function OnExportProgress(&$event)
 	{
 		$export_object =& $this->Application->recallObject('CatItemExportHelper');
 		/* @var $export_object kCatDBItemExportHelper */
 
 		$event = new kEvent($event->getPrefixSpecial().':OnDummy');
 
 		$action_method = 'perform'.ucfirst($event->Special);
 		$field_values = $export_object->$action_method($event);
 
 		// finish code is done from JS now
 		if ($field_values['start_from'] == $field_values['total_records']) {
 			if ($event->Special == 'import') {
 				$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 				$url_params = Array(
 					't' => 'catalog/catalog',
 					'm_cat_id' => $this->Application->RecallVar('ImportCategory'),
 					'anchor' => 'tab-' . $event->Prefix,
 				);
 				$this->Application->EventManager->openerStackChange($url_params);
 
 				$event->SetRedirectParam('opener', 'u');
 			}
 			elseif ($event->Special == 'export') {
-				$event->redirect = $this->Application->getUnitOption($event->Prefix, 'ModuleFolder') . '/' . $event->Special . '_finish';
+				$event->redirect = $export_object->getModuleName($event) . '/' . $event->Special . '_finish';
 				$event->SetRedirectParam('pass', 'all');
 			}
 
 			return ;
 		}
 
 		$export_options = $export_object->loadOptions($event);
 		echo $export_options['start_from']  * 100 / $export_options['total_records'];
 
 		$event->status = erSTOP;
 	}
 
 	/**
 	 * Returns specific to each item type columns only
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function getCustomExportColumns(&$event)
 	{
 		return Array(	'__VIRTUAL__ThumbnailImage'	=>	'ThumbnailImage',
 						'__VIRTUAL__FullImage' 		=>	'FullImage',
 						'__VIRTUAL__ImageAlt'		=>	'ImageAlt');
 	}
 
 	/**
 	 * Sets non standart virtual fields (e.g. to other tables)
 	 *
 	 * @param kEvent $event
 	 */
 	function setCustomExportColumns(&$event)
 	{
 		$this->restorePrimaryImage($event);
 	}
 
 	/**
 	 * Create/Update primary image record in info found in imported data
 	 *
 	 * @param kEvent $event
 	 */
 	function restorePrimaryImage(&$event)
 	{
 		$object =& $event->getObject();
 
 		$has_image_info = $object->GetDBField('ImageAlt') && ($object->GetDBField('ThumbnailImage') || $object->GetDBField('FullImage'));
 		if (!$has_image_info) {
 			return false;
 		}
 
 		$image_data = $object->getPrimaryImageData();
 
 		$image =& $this->Application->recallObject('img', null, Array('skip_autoload' => true));
 		if ($image_data) {
 			$image->Load($image_data['ImageId']);
 		}
 		else {
 			$image->Clear();
 			$image->SetDBField('Name', 'main');
 			$image->SetDBField('DefaultImg', 1);
 			$image->SetDBField('ResourceId', $object->GetDBField('ResourceId'));
 		}
 
 		$image->SetDBField('AltName', $object->GetDBField('ImageAlt'));
 
 		if ($object->GetDBField('ThumbnailImage')) {
 			$thumbnail_field = $this->isURL( $object->GetDBField('ThumbnailImage') ) ? 'ThumbUrl' : 'ThumbPath';
 			$image->SetDBField($thumbnail_field, $object->GetDBField('ThumbnailImage') );
 			$image->SetDBField('LocalThumb', $thumbnail_field == 'ThumbPath' ? 1 : 0);
 		}
 
 		if (!$object->GetDBField('FullImage')) {
 			$image->SetDBField('SameImages', 1);
 		}
 		else {
 			$image->SetDBField('SameImages', 0);
 			$full_field = $this->isURL( $object->GetDBField('FullImage') ) ? 'Url' : 'LocalPath';
 			$image->SetDBField($full_field, $object->GetDBField('FullImage') );
 			$image->SetDBField('LocalImage', $full_field == 'LocalPath' ? 1 : 0);
 		}
 
 		if ($image->isLoaded()) {
 			$image->Update();
 		}
 		else {
 			$image->Create();
 		}
 	}
 
 	function isURL($path)
 	{
 		return preg_match('#(http|https)://(.*)#', $path);
 	}
 
 	/**
 	 * Prepares item for import/export operations
 	 *
 	 * @param kEvent $event
 	 */
 	function OnNew(&$event)
 	{
 		parent::OnNew($event);
 
 		if ($event->Special == 'import' || $event->Special == 'export') {
 			$export_helper =& $this->Application->recallObject('CatItemExportHelper');
 			$export_helper->setRequiredFields($event);
 		}
 	}
 
 	/**
 	 * Process items selected in item_selector
 	 *
 	 * @param kEvent $event
 	 */
 	function OnProcessSelected(&$event)
 	{
 		$selected_ids = $this->Application->GetVar('selected_ids');
 
 		$dst_field = $this->Application->RecallVar('dst_field');
 
 		if ($dst_field == 'ItemCategory') {
 			// Item Edit -> Categories Tab -> New Categories
 			$object =& $event->getObject();
 			$category_ids = explode(',', $selected_ids['c']);
 			foreach ($category_ids as $category_id) {
 				$object->assignToCategory($category_id);
 			}
 		}
 
 		if ($dst_field == 'ImportCategory') {
 			// Tools -> Import -> Item Import -> Select Import Category
 			$this->Application->StoreVar('ImportCategory', $selected_ids['c']);
 //			$this->Application->StoreVar($event->getPrefixSpecial().'_ForceNotValid', 1); // not to loose import/export values on form refresh
 
 			$url_params = Array (
 				$event->getPrefixSpecial() . '_id' => 0,
 				$event->getPrefixSpecial() . '_event' => 'OnExportBegin',
 //				'm_opener' => 's',
 			);
 
 			$this->Application->EventManager->openerStackChange($url_params);
 		}
 
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Saves Import/Export settings to session
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSaveSettings(&$event)
 	{
 		$event->redirect = false;
 		$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 		if ($items_info) {
 			list($id, $field_values) = each($items_info);
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$object->SetFieldsFromHash($field_values);
 			$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
 			$field_values['ImportSource'] = 2;
 			$field_values['ImportLocalFilename'] = $object->GetDBField('ImportFilename');
 			$items_info[$id] = $field_values;
 
 			$this->Application->StoreVar($event->getPrefixSpecial().'_ItemsInfo', serialize($items_info));
 		}
 	}
 
 	/**
 	 * Saves Import/Export settings to session
 	 *
 	 * @param kEvent $event
 	 */
 	function OnResetSettings(&$event)
 	{
 		$this->Application->StoreVar('ImportCategory', $this->Application->findModule('Name', 'Core', 'RootCat'));
 	}
 
 	function OnCancelAction(&$event)
 	{
 		$event->redirect_params = Array('pass' => 'all,'.$event->GetPrefixSpecial());
 		$event->redirect = $this->Application->GetVar('cancel_template');
 	}
 
 /* === RELATED TO IMPORT/EXPORT: END === */
 
 	/**
 	 * Stores item's owner login into separate field together with id
 	 *
 	 * @param kEvent $event
 	 * @param string $id_field
 	 * @param string $cached_field
 	 */
 	function cacheItemOwner(&$event, $id_field, $cached_field)
 	{
 		$object =& $event->getObject();
 
 		$user_id = $object->GetDBField($id_field);
 		$options = $object->GetFieldOptions($id_field);
 		if (isset($options['options'][$user_id])) {
 			$object->SetDBField($cached_field, $options['options'][$user_id]);
 		}
 		else {
 			$id_field = $this->Application->getUnitOption('u', 'IDField');
 			$table_name = $this->Application->getUnitOption('u', 'TableName');
 
 			$sql = 'SELECT Login
 					FROM '.$table_name.'
 					WHERE '.$id_field.' = '.$user_id;
 			$object->SetDBField($cached_field, $this->Conn->GetOne($sql));
 		}
 	}
 
 	/**
 	 * Saves item beeing edited into temp table
 	 *
 	 * @param kEvent $event
 	 */
 	function OnPreSave(&$event)
 	{
 		parent::OnPreSave($event);
 		$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 		if ($event->status == erSUCCESS && $use_pending_editing) {
 			// decision: clone or not clone
 
 			$object =& $event->getObject();
 			if ($object->GetID() == 0 || $object->GetDBField('OrgId') > 0) {
 				// new items or cloned items shouldn't be cloned again
 				return true;
 			}
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			$owner_field = $this->getOwnerField($event->Prefix);
 
 			if ($perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix) == 2) {
 
 				// 1. clone original item
 				$temp_handler =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 				$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($object->GetID()), null, null, null, true);
 				$ci_table = $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems');
 
 				// 2. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
 				$sql = 'SELECT ResourceId
 						FROM '.$object->TableName.'
 						WHERE '.$object->IDField.' = '.$cloned_ids[0];
 				$clone_resource_id = $this->Conn->GetOne($sql);
 
 				$sql = 'DELETE FROM '.$ci_table.'
 						WHERE ItemResourceId = '.$clone_resource_id.' AND PrimaryCat = 1';
 				$this->Conn->Query($sql);
 
 				// 3. copy main item categoryitems to cloned item
 				$sql = '	INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
 							SELECT CategoryId, '.$clone_resource_id.' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
 							FROM '.$ci_table.'
 							WHERE ItemResourceId = '.$object->GetDBField('ResourceId');
 				$this->Conn->Query($sql);
 
 				// 4. put cloned id to OrgId field of item being cloned
 				$sql = 'UPDATE '.$object->TableName.'
 						SET OrgId = '.$object->GetID().'
 						WHERE '.$object->IDField.' = '.$cloned_ids[0];
 				$this->Conn->Query($sql);
 
 				// 5. substitute id of item being cloned with clone id
 				$this->Application->SetVar($event->getPrefixSpecial().'_id', $cloned_ids[0]);
 
 				$selected_ids = $this->getSelectedIDs($event, true);
 				$selected_ids[ array_search($object->GetID(), $selected_ids) ] = $cloned_ids[0];
 				$this->StoreSelectedIDs($event, $selected_ids);
 
 				// 6. delete original item from temp table
 				$temp_handler->DeleteItems($event->Prefix, $event->Special, Array($object->GetID()));
 			}
 		}
 	}
 
 	/**
 	 * Sets default expiration based on module setting
 	 *
 	 * @param kEvent $event
 	 */
 	function OnPreCreate(&$event)
 	{
 		parent::OnPreCreate($event);
 
 		if ($event->status == erSUCCESS) {
 			$object =& $event->getObject();
 			$owner_field = $this->getOwnerField($event->Prefix);
 
 			$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
 		}
 	}
 
 	/**
 	 * Occures before original item of item in pending editing got deleted (for hooking only)
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeDeleteOriginal(&$event)
 	{
 
 	}
 
 	/**
 	 * Occures before an item is cloneded
 	 * Id of ORIGINAL item is passed as event' 'id' param
 	 * Do not call object' Update method in this event, just set needed fields!
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeClone(&$event)
 	{
 		if ($this->Application->GetVar('ResetCatBeforeClone')) {
 			$object =& $event->getObject();
 			$object->SetDBField('CategoryId', null);
 		}
 	}
 
 	/**
 	 * Set status for new category item based on user permission in category
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeItemCreate(&$event)
 	{
 		if ($this->Application->isAdminUser) {
 			// don't set permission-based status, when creating categories in admin
 			return true;
 		}
 
 		$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 		if ($use_pending_editing) {
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$primary_category = $object->GetDBField('CategoryId') > 0 ? $object->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id');
 			$item_status = $perm_helper->AddCheckPermission($primary_category, $event->Prefix);
 			if ($item_status == STATUS_DISABLED) {
 				$event->status = erFAIL;
 				return false;
 			}
 			else {
 				$object->SetDBField('Status', $item_status);
 			}
 		}
 	}
 
 	/**
 	 * Creates category item & redirects to confirmation template (front-end only)
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCreate(&$event)
 	{
 		parent::OnCreate($event);
 		$this->SetFrontRedirectTemplate($event, 'suggest');
 	}
 
 	/**
 	 * Returns item's categories (allows to exclude primary category)
 	 *
 	 * @param int $resource_id
 	 * @param bool $with_primary
 	 * @return Array
 	 */
 	function getItemCategories($resource_id, $with_primary = false)
 	{
 		$sql = 'SELECT CategoryId
 				FROM '.TABLE_PREFIX.'CategoryItems
 				WHERE (ItemResourceId = '.$resource_id.')';
 
 		if (!$with_primary) {
 			$sql .= ' AND (PrimaryCat = 0)';
 		}
 
 		return $this->Conn->GetCol($sql);
 	}
 
 	/**
 	 * Adds new and removes old additional categories from category item
 	 *
 	 * @param kCatDBItem $object
 	 */
 	function processAdditionalCategories(&$object, $mode)
 	{
 		if (!array_key_exists('MoreCategories', $object->VirtualFields)) {
 			// given category item doesn't require such type of processing
 			return ;
 		}
 
 		$process_categories = $object->GetDBField('MoreCategories');
 		if ($process_categories === '') {
 			// field was not in submit & have default value (when no categories submitted, then value is null)
 			return ;
 		}
 
 		if ($mode == 'create') {
 			// prevents first additional category to become primary
 			$object->assignPrimaryCategory();
 		}
 
 		$process_categories = $process_categories ? explode('|', substr($process_categories, 1, -1)) : Array ();
 		$existing_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
 
 		$add_categories = array_diff($process_categories, $existing_categories);
 		foreach ($add_categories as $category_id) {
 			$object->assignToCategory($category_id);
 		}
 
 		$remove_categories = array_diff($existing_categories, $process_categories);
 		foreach ($remove_categories as $category_id) {
 			$object->removeFromCategory($category_id);
 		}
 	}
 
 	/**
 	 * Creates category item & redirects to confirmation template (front-end only)
 	 *
 	 * @param kEvent $event
 	 */
 	function OnUpdate(&$event)
 	{
 		$use_pending = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
 		if ($this->Application->isAdminUser || !$use_pending) {
 			parent::OnUpdate($event);
 			$this->SetFrontRedirectTemplate($event, 'modify');
 			return ;
 		}
 
 		$object =& $event->getObject(Array('skip_autoload' => true));
 		/* @var $object kCatDBItem */
 
 		$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 		if ($items_info) {
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$temp_handler =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$owner_field = $this->getOwnerField($event->Prefix);
 
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			foreach ($items_info as $id => $field_values) {
 				$object->Load($id);
 				$edit_perm = $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix);
 
 				if ($use_pending && !$object->GetDBField('OrgId') && ($edit_perm == STATUS_PENDING)) {
 					// pending editing enabled + not pending copy -> get/create pending copy & save changes to it
 					$original_id = $object->GetID();
 					$original_resource_id = $object->GetDBField('ResourceId');
 					$file_helper->PreserveItemFiles($field_values);
 
 					$object->Load($original_id, 'OrgId');
 					if (!$object->isLoaded()) {
 						// 1. user has no pending copy of live item -> clone live item
 						$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($original_id), null, null, null, true);
 
 						$object->Load($cloned_ids[0]);
 						$object->SetFieldsFromHash($field_values);
 
 						// 1a. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
 						$ci_table = $this->Application->getUnitOption('ci', 'TableName');
 						$sql = 'DELETE FROM '.$ci_table.'
 								WHERE ItemResourceId = '.$object->GetDBField('ResourceId').' AND PrimaryCat = 1';
 						$this->Conn->Query($sql);
 
 						// 1b. copy main item categoryitems to cloned item
 						$sql = 'INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
 								SELECT CategoryId, '.$object->GetDBField('ResourceId').' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
 								FROM '.$ci_table.'
 								WHERE ItemResourceId = '.$original_resource_id;
 						$this->Conn->Query($sql);
 
 						// 1c. put cloned id to OrgId field of item being cloned
 						$object->SetDBField('Status', STATUS_PENDING_EDITING);
 						$object->SetDBField('OrgId', $original_id);
 					}
 					else {
 						// 2. user has pending copy of live item -> just update field values
 						$object->SetFieldsFromHash($field_values);
 					}
 
 					// update id in request (used for redirect in mod-rewrite mode)
 					$this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetID());
 				}
 				else {
 					// 3. already editing pending copy -> just update field values
 					$object->SetFieldsFromHash($field_values);
 				}
 
 				if ($object->Update()) {
 					$event->status = erSUCCESS;
 				}
 				else {
 					$event->status = erFAIL;
 					$event->redirect = false;
 					break;
 				}
 			}
 		}
 
 		$this->SetFrontRedirectTemplate($event, 'modify');
 	}
 
 	/**
 	 * Sets next template to one required for front-end after adding/modifying item
 	 *
 	 * @param kEvent $event
 	 * @param string $template_key - {suggest,modify}
 	 */
 	function SetFrontRedirectTemplate(&$event, $template_key)
 	{
 		if ($this->Application->isAdminUser || $event->status != erSUCCESS) {
 			return ;
 		}
 
 		// prepare redirect template
 		$object =& $event->getObject();
 		$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
 
 		$next_template = $is_active ? 'confirm_template' : 'pending_confirm_template';
 		$event->redirect = $this->Application->GetVar($template_key.'_'.$next_template);
 		$event->SetRedirectParam('opener', 's');
 
 		// send email events
 		$perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix');
 		switch ($event->Name) {
 			case 'OnCreate':
 				$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
 				$owner_field = $this->getOwnerField($event->Prefix);
 
 				$this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix); // there are no ADD.PENDING event for admin :(
 				$this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField($owner_field));
 				break;
 
 			case 'OnUpdate':
 				$event_suffix = $is_active ? 'MODIFY' : 'MODIFY.PENDING';
 				$this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix); // there are no ADD.PENDING event for admin :(
 				$this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField('ModifiedById'));
 				break;
 		}
 	}
 
 	/**
 	 * Apply same processing to each item beeing selected in grid
 	 *
 	 * @param kEvent $event
 	 * @access private
 	 */
 	function iterateItems(&$event)
 	{
 		if ($event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline') {
 			return parent::iterateItems($event);
 		}
 
 		if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 			$event->status = erFAIL;
 			return;
 		}
 
 		$object =& $event->getObject( Array('skip_autoload' => true) );
 		$ids = $this->StoreSelectedIDs($event);
 
 		if ($ids) {
 			foreach ($ids as $id) {
 				$object->Load($id);
 
 				switch ($event->Name) {
 					case 'OnMassApprove':
 						$ret = $object->ApproveChanges();
 						break;
 
 					case 'OnMassDecline':
 						$ret = $object->DeclineChanges();
 						break;
 				}
 
 				if (!$ret) {
 					$event->status = erFAIL;
 					$event->redirect = false;
 					break;
 				}
 			}
 		}
 
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Deletes items & preserves clean env
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDelete(&$event)
 	{
 		parent::OnDelete($event);
 
 		if ($event->status == erSUCCESS && !$this->Application->isAdmin) {
 			$event->SetRedirectParam('pass', 'm');
 			$event->SetRedirectParam('m_cat_id', 0);
 		}
 	}
 
 	/**
 	 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 */
 	function checkItemStatus(&$event)
 	{
 		$object =& $event->getObject();
 		if (!$object->isLoaded()) {
 			$this->_errorNotFound($event);
 
 			return true;
 		}
 
 		$status = $object->GetDBField('Status');
 		$user_id = $this->Application->RecallVar('user_id');
 		$owner_field = $this->getOwnerField($event->Prefix);
 
 		if (($status == STATUS_PENDING_EDITING || $status == STATUS_PENDING) && ($object->GetDBField($owner_field) == $user_id)) {
 			return true;
 		}
 
 		return $status == STATUS_ACTIVE;
 	}
 
 	/**
 	 * Set's correct sorting for list
 	 * based on data provided with event
 	 *
 	 * @param kEvent $event
 	 * @access private
 	 * @see OnListBuild
 	 */
 	function SetSorting(&$event)
 	{
 		if (!$this->Application->isAdmin) {
 			$event->setEventParam('same_special', true);
 		}
 
 		parent::SetSorting($event);
 	}
 
 	/**
 	 * Returns current per-page setting for list
 	 *
 	 * @param kEvent $event
 	 * @return int
 	 */
 	function getPerPage(&$event)
 	{
 		if (!$this->Application->isAdmin) {
 			$event->setEventParam('same_special', true);
 		}
 
 		return parent::getPerPage($event);
 	}
 
 	function getOwnerField($prefix)
 	{
 		$owner_field = $this->Application->getUnitOption($prefix, 'OwnerField');
 		if (!$owner_field) {
 			$owner_field = 'CreatedById';
 		}
 
 		return $owner_field;
 	}
 
 	/**
 	 * Creates virtual image fields for item
 	 *
 	 * @param kEvent $event
 	 */
 	function OnAfterConfigRead(&$event)
 	{
 		parent::OnAfterConfigRead($event);
 
 		if (defined('IS_INSTALL') && IS_INSTALL) {
 			return ;
 		}
 
 		$file_helper =& $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$file_helper->createItemFiles($event->Prefix, true); // create image fields
 		$file_helper->createItemFiles($event->Prefix, false); // create file fields
 
 		// add EditorsPick to ForcedSorting if needed
 		$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
 		if (array_key_exists('ForceEditorPick', $config_mapping) && $this->Application->ConfigValue($config_mapping['ForceEditorPick'])) {
 			$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings');
 
 			$new_forced_sorting = Array ('EditorsPick' => 'DESC');
 
 			if (array_key_exists('ForcedSorting', $list_sortings[''])) {
 				foreach ($list_sortings['']['ForcedSorting'] as $sort_field => $sort_order) {
 					$new_forced_sorting[$sort_field] = $sort_order;
 				}
 			}
 			$list_sortings['']['ForcedSorting'] = $new_forced_sorting;
 
 			$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
 		}
 
 		// add grids for advanced view (with primary category column)
 		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 		$process_grids = Array ('Default', 'Radio');
 		foreach ($process_grids as $process_grid) {
 			$grid_data = $grids[$process_grid];
 			$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_primary_category_td', 'filter_block' => 'grid_like_filter');
 			$grids[$process_grid . 'ShowAll'] = $grid_data;
 		}
 		$this->Application->setUnitOption($this->Prefix, 'Grids', $grids);
 
 		// add options for CategoryId field (quick way to select item's primary category)
 		$category_helper =& $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
 
 		$virtual_fields['CategoryId']['default'] = (int)$this->Application->GetVar('m_cat_id');
 		$virtual_fields['CategoryId']['options'] = $category_helper->getStructureTreeAsOptions();
 
 		$this->Application->setUnitOption($event->Prefix, 'VirtualFields', $virtual_fields);
 	}
 
 	/**
 	 * Returns file contents associated with item
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDownloadFile(&$event)
 	{
 		$object =& $event->getObject();
 		/* @var $object kDBItem */
 
 		$event->status = erSTOP;
 
 		$field = $this->Application->GetVar('field');
 		if (!preg_match('/^File([\d]+)/', $field)) {
 			return ;
 		}
 
 		$file_helper =& $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$filename = $object->GetField($field, 'full_path');
 		$file_helper->DownloadFile($filename);
 	}
 
 	/**
 	 * Saves user's vote
 	 *
 	 * @param kEvent $event
 	 */
 	function OnMakeVote(&$event)
 	{
 		$event->status = erSTOP;
 
 		if ($this->Application->GetVar('ajax') != 'yes') {
 			// this is supposed to call from AJAX only
 			return ;
 		}
 
 		$rating_helper =& $this->Application->recallObject('RatingHelper');
 		/* @var $rating_helper RatingHelper */
 
 		$object =& $event->getObject( Array ('skip_autoload' => true) );
 		/* @var $object kDBItem */
 
 		$object->Load( $this->Application->GetVar('id') );
 
 		echo $rating_helper->makeVote($object);
 	}
 }
\ No newline at end of file
Index: branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php
===================================================================
--- branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php	(revision 13204)
+++ branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php	(revision 13205)
@@ -1,1452 +1,1455 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	define('EXPORT_STEP', 100); // export by 200 items (e.g. links)
 	define('IMPORT_STEP', 20); // export by 200 items (e.g. links)
 	define('IMPORT_CHUNK', 10240); // 10240); //30720); //50120); // 5 KB
 
 	define('IMPORT_TEMP', 1);
 	define('IMPORT_LIVE', 2);
 
 	class kCatDBItemExportHelper extends kHelper {
 
 		var $false = false;
 
 		var $cache = Array();
 
 		/**
 		 * Allows to find out what items are new in cache
 		 *
 		 * @var Array
 		 */
 		var $cacheStatus = Array();
 
 		var $cacheTable = '';
 
 		var $exportFields = Array();
 
 		/**
 		 * Export options
 		 *
 		 * @var Array
 		 */
 		var $exportOptions = Array();
 
 		/**
 		 * Item beeing currenly exported
 		 *
 		 * @var kCatDBItem
 		 */
 		var $curItem = null;
 
 		/**
 		 * Dummy category object
 		 *
 		 * @var CategoriesItem
 		 */
 		var $dummyCategory = null;
 
 		/**
 		 * Pointer to opened file
 		 *
 		 * @var resource
 		 */
 		var $filePointer = null;
 
 		/**
 		 * Custom fields definition of current item
 		 *
 		 * @var Array
 		 */
 		var $customFields = Array();
 
 		function kCatDBItemExportHelper()
 		{
 			parent::kHelper();
 			$this->cacheTable = TABLE_PREFIX.'ImportCache';
 		}
 
 		/**
 		 * Returns value from cache if found or false otherwise
 		 *
 		 * @param string $type
 		 * @param int $key
 		 * @return mixed
 		 */
 		function getFromCache($type, $key)
 		{
 			return getArrayValue($this->cache, $type, $key);
 		}
 
 		/**
 		 * Adds value to be cached
 		 *
 		 * @param string $type
 		 * @param int $key
 		 * @param mixed $value
 		 */
 		function addToCache($type, $key, $value, $is_new = true)
 		{
 //			if (!isset($this->cache[$type])) $this->cache[$type] = Array();
 			$this->cache[$type][$key] = $value;
 			if ($is_new) {
 				$this->cacheStatus[$type][$key] = true;
 			}
 		}
 
 		function storeCache($cache_types)
 		{
 			$cache_types = explode(',', $cache_types);
 
 			$values_sql = '';
 			foreach ($cache_types as $cache_type) {
 				$sql_mask = '('.$this->Conn->qstr($cache_type).',%s,%s),';
 				$cache = getArrayValue($this->cacheStatus, $cache_type);
 				if (!$cache) $cache = Array();
 				foreach ($cache as $var_name => $cache_status) {
 					$var_value = $this->cache[$cache_type][$var_name];
 					$values_sql .= sprintf($sql_mask, $this->Conn->qstr($var_name), $this->Conn->qstr($var_value) );
 				}
 			}
 
 			$values_sql = substr($values_sql, 0, -1);
 
 			if ($values_sql) {
 				$sql = 'INSERT INTO '.$this->cacheTable.'(`CacheName`,`VarName`,`VarValue`) VALUES '.$values_sql;
 				$this->Conn->Query($sql);
 			}
 
 		}
 
 		function loadCache()
 		{
 			$sql = 'SELECT * FROM '.$this->cacheTable;
 			$records = $this->Conn->Query($sql);
 
 			$this->cache = Array();
 			foreach ($records as $record) {
 				$this->addToCache($record['CacheName'], $record['VarName'], $record['VarValue'], false);
 			}
 		}
 
 		/**
 		 * Fill required fields with dummy values
 		 *
 		 * @param kEvent $event
 		 */
 		function fillRequiredFields(&$event, &$object, $set_status = false)
 		{
 			if ($object == $this->false) {
 				$object =& $event->getObject();
 			}
 
 			$has_empty = false;
 			$fields = array_keys($object->Fields);
 			foreach ($fields as $field_name)
 			{
 				$field_options =& $object->Fields[$field_name];
 				if (isset($object->VirtualFields[$field_name]) || !getArrayValue($field_options, 'required') ) continue;
 				if ( $object->GetDBField($field_name) ) continue;
 
 				$formatter_class = getArrayValue($field_options, 'formatter');
 				if ($formatter_class) // not tested
 				{
 					$formatter =& $this->Application->recallObject($formatter_class);
 					$sample_value = $formatter->GetSample($field_name, $field_options, $object);
 				}
 
 				$has_empty = true;
 				$object->SetField($field_name, isset($sample_value) && $sample_value ? $sample_value : 'no value');
 			}
 			$object->UpdateFormattersSubFields();
 
 			if ($set_status && $has_empty) {
 				$object->SetDBField('Status', 0);
 			}
 		}
 
 		/**
 		 * Verifies that all user entered export params are correct
 		 *
 		 * @param kEvent $event
 		 */
 		function verifyOptions(&$event)
 		{
 			if ($this->Application->RecallVar($event->getPrefixSpecial().'_ForceNotValid'))
 			{
 				$this->Application->StoreVar($event->getPrefixSpecial().'_ForceNotValid', 0);
 				return false;
 			}
 
 			$this->fillRequiredFields($event, $this->false);
 
 			$object =& $event->getObject();
 			$cross_unique_fields = Array('FieldsSeparatedBy', 'FieldsEnclosedBy');
 			if (($object->GetDBField('CategoryFormat') == 1) || ($event->Special == 'import')) // in one field
 			{
 				$object->setRequired('CategorySeparator', true);
 				$cross_unique_fields[] = 'CategorySeparator';
 			}
 
 			$ret = $object->Validate();
 
 			// check if cross unique fields has no same values
 			foreach ($cross_unique_fields as $field_index => $field_name)
 			{
 				if (getArrayValue($object->FieldErrors, $field_name, 'pseudo') == 'required') continue;
 
 				$check_fields = $cross_unique_fields;
 				unset($check_fields[$field_index]);
 
 				foreach ($check_fields as $check_field)
 				{
 					if ($object->GetDBField($field_name) == $object->GetDBField($check_field))
 					{
 						$object->SetError($check_field, 'unique');
 					}
 				}
 			}
 
 			if ($event->Special == 'import')
 			{
 				$this->exportOptions = $this->loadOptions($event);
 
 				$automatic_fields = ($object->GetDBField('FieldTitles') == 1);
 				$object->setRequired('ExportColumns', !$automatic_fields);
 				$category_prefix = '__CATEGORY__';
 				if ( $automatic_fields && ($this->exportOptions['SkipFirstRow']) ) {
 					$this->openFile($event);
 					$this->exportOptions['ExportColumns'] = $this->readRecord();
 
 					if (!$this->exportOptions['ExportColumns']) {
 						$this->exportOptions['ExportColumns'] = Array ();
 					}
 
 					$this->closeFile();
 
 					// remove additional (non-parseble columns)
 					foreach ($this->exportOptions['ExportColumns'] as $field_index => $field_name) {
 						if (!$this->validateField($field_name, $object)) {
 							unset($this->exportOptions['ExportColumns'][$field_index]);
 						}
 					}
 					$category_prefix = '';
 				}
 
 				// 1. check, that we have column definitions
 				if (!$this->exportOptions['ExportColumns']) {
 					$object->setError('ExportColumns', 'required');
 					$ret = false;
 				}
 				else {
 					// 1.1. check that all required fields are present in imported file
 					$missing_columns = Array();
 					foreach ($object->Fields as $field_name => $field_options) {
 						if ($object->skipField($field_name)) continue;
 						if (getArrayValue($field_options, 'required') && !in_array($field_name, $this->exportOptions['ExportColumns']) ) {
 							$missing_columns[] = $field_name;
 							$object->setError('ExportColumns', 'required_fields_missing', 'la_error_RequiredColumnsMissing');
 							$ret = false;
 						}
 					}
 
 					if (!$ret && $this->Application->isDebugMode()) {
 						$this->Application->Debugger->appendHTML('Missing required for import/export:');
 						$this->Application->Debugger->dumpVars($missing_columns);
 					}
 				}
 
 
 				// 2. check, that we have only mixed category field or only separated category fields
 				$category_found['mixed'] = false;
 				$category_found['separated'] = false;
 
 				foreach ($this->exportOptions['ExportColumns'] as $import_field) {
 					if (preg_match('/^'.$category_prefix.'Category(Path|[0-9]+)/', $import_field, $rets)) {
 						$category_found[$rets[1] == 'Path' ? 'mixed' : 'separated'] = true;
 					}
 				}
 				if ($category_found['mixed'] && $category_found['separated']) {
 					$object->SetError('ExportColumns', 'unique_category', 'la_error_unique_category_field');
 					$ret = false;
 				}
 
 				// 3. check, that duplicates check fields are selected & present in imported fields
 				if ($this->exportOptions['ReplaceDuplicates']) {
 					if ($this->exportOptions['CheckDuplicatesMethod'] == 1) {
 						$check_fields = Array($object->IDField);
 					}
 					else {
 						$check_fields = $this->exportOptions['DuplicateCheckFields'] ? explode('|', substr($this->exportOptions['DuplicateCheckFields'], 1, -1)) : Array();
 						$object =& $event->getObject();
 
 						$language_id = $this->Application->GetDefaultLanguageId();
 						foreach ($check_fields as $index => $check_field) {
 							foreach ($object->Fields as $field_name => $field_options) {
 								if ($field_name == 'l'.$language_id.'_'.$check_field) {
 									$check_fields[$index] = 'l'.$language_id.'_'.$check_field;
 									break;
 								}
 							}
 						}
 					}
 					$this->exportOptions['DuplicateCheckFields'] = $check_fields;
 
 					if (!$check_fields) {
 						$object->setError('CheckDuplicatesMethod', 'required');
 						$ret = false;
 					}
 					else {
 						foreach ($check_fields as $check_field) {
 							$check_field = preg_replace('/^cust_(.*)/', 'Custom_\\1', $check_field);
 							if (!in_array($check_field, $this->exportOptions['ExportColumns'])) {
 								$object->setError('ExportColumns', 'required');
 								$ret = false;
 								break;
 							}
 						}
 					}
 				}
 				$this->saveOptions($event);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns filename to read import data from
 		 *
 		 * @return string
 		 */
 		function getImportFilename()
 		{
 			if ($this->exportOptions['ImportSource'] == 1)
 			{
 				$ret = $this->exportOptions['ImportFilename']; // ['name']; commented by Kostja
 			}
 			else {
 				$ret = $this->exportOptions['ImportLocalFilename'];
 			}
 			return EXPORT_PATH.'/'.$ret;
 		}
 
 		/**
 		 * Returns filename to write export data to
 		 *
 		 * @return string
 		 */
 		function getExportFilename()
 		{
 			return EXPORT_PATH.'/'.$this->exportOptions['ExportFilename'].'.'.$this->getFileExtension();
 		}
 
 		/**
 		 * Opens file required for export/import operations
 		 *
 		 * @param kEvent $event
 		 */
 		function openFile(&$event)
 		{
 			if ($event->Special == 'export') {
 				$write_mode = ($this->exportOptions['start_from'] == 0) ? 'w' : 'a';
 				$this->filePointer = fopen($this->getExportFilename(), $write_mode);
 			}
 			else {
 				$this->filePointer = fopen($this->getImportFilename(), 'r');
 			}
 
 			// skip UTF-8 BOM Modifier
 			$first_chars = fread($this->filePointer, 3);
 			if (bin2hex($first_chars) != 'efbbbf') {
 				fseek($this->filePointer, 0);
 			}
 		}
 
 		/**
 		 * Closes opened file
 		 *
 		 */
 		function closeFile()
 		{
 			fclose($this->filePointer);
 		}
 
 		function getCustomSQL()
 		{
 			$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 
 			$custom_sql = '';
 			foreach ($this->customFields as $custom_id => $custom_name) {
 				$custom_sql .= 'custom_data.'.$ml_formatter->LangFieldName('cust_'.$custom_id).' AS cust_'.$custom_name.', ';
 			}
 
 			return substr($custom_sql, 0, -2);
 		}
 
 		function getPlainExportSQL($count_only = false) {
 			if ($count_only && isset($this->exportOptions['ForceCountSQL'])) {
 				$sql = $this->exportOptions['ForceCountSQL'];
 			}
 			elseif (!$count_only && isset($this->exportOptions['ForceSelectSQL'])) {
 				$sql = $this->exportOptions['ForceSelectSQL'];
 			}
 			else {
 				$items_list =& $this->Application->recallObject($this->curItem->Prefix.'.export-items-list', $this->curItem->Prefix.'_List');
 				$items_list->SetPerPage(-1);
 
 				if ($options['export_ids'] != '') {
 					$items_list->AddFilter('export_ids', $items_list->TableName.'.'.$items_list->IDField.' IN ('.implode(',',$options['export_ids']).')');
 				}
 
 				if ($count_only) {
 					$sql = $items_list->getCountSQL( $items_list->GetSelectSQL(true,false) );
 				}
 				else {
 					$sql = $items_list->GetSelectSQL();
 				}
 			}
 
 			if (!$count_only)
 			{
 				$sql .= ' LIMIT '.$this->exportOptions['start_from'].','.EXPORT_STEP;
 			}
 //			else {
 //				$sql = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
 //			}
 
 			return $sql;
 		}
 
 		function getExportSQL($count_only = false)
 		{
 			if (!$this->Application->getUnitOption($this->curItem->Prefix, 'CatalogItem')) {
 				return $this->GetPlainExportSQL($count_only); // in case this is not a CategoryItem
 			}
 
 			if ($this->exportOptions['export_ids'] === false)
 			{
 				// get links from current category & all it's subcategories
 				$join_clauses = Array();
 
 				$custom_sql = $this->getCustomSQL();
 				if ($custom_sql) {
 					$custom_table = $this->Application->getUnitOption($this->curItem->Prefix.'-cdata', 'TableName');
 					$join_clauses[$custom_table.' custom_data'] = 'custom_data.ResourceId = item_table.ResourceId';
 				}
 
 				$join_clauses[TABLE_PREFIX.'CategoryItems ci'] = 'ci.ItemResourceId = item_table.ResourceId';
 				$join_clauses[TABLE_PREFIX.'Category c'] = 'c.CategoryId = ci.CategoryId';
 
 				$sql = 'SELECT item_table.*, ci.CategoryId'.($custom_sql ? ', '.$custom_sql : '').'
 						FROM '.$this->curItem->TableName.' item_table';
 
 				foreach ($join_clauses as $table_name => $join_expression) {
 					$sql .= ' LEFT JOIN '.$table_name.' ON '.$join_expression;
 				}
 				$sql .= ' WHERE ';
 
 				if ($this->exportOptions['export_cats_ids'][0] == 0)
 				{
 					$sql .= '1';
 				}
 				else {
 					foreach ($this->exportOptions['export_cats_ids'] as $category_id) {
 						$sql .= '(c.ParentPath LIKE "%|'.$category_id.'|%") OR ';
 					}
 					$sql = substr($sql, 0, -4);
 				}
 
 				$sql .= ' ORDER BY ci.PrimaryCat DESC'; // NEW
 			}
 			else {
 				// get only selected links
 				$sql = 'SELECT item_table.*, '.$this->exportOptions['export_cats_ids'][0].' AS CategoryId
 						FROM '.$this->curItem->TableName.' item_table
 						WHERE '.$this->curItem->IDField.' IN ('.implode(',', $this->exportOptions['export_ids']).')';
 			}
 
 			if (!$count_only)
 			{
 				$sql .= ' LIMIT '.$this->exportOptions['start_from'].','.EXPORT_STEP;
 			}
 			else {
 				$sql = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
 			}
 
 			return $sql;
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function performExport(&$event)
 		{
 			$this->exportOptions = $this->loadOptions($event);
 			$this->exportFields = $this->exportOptions['ExportColumns'];
 			$this->curItem =& $event->getObject( Array('skip_autoload' => true) );
 			$this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 			$this->openFile($event);
 
 			if ($this->exportOptions['start_from'] == 0) // first export step
 			{
 				if (!getArrayValue($this->exportOptions, 'IsBaseCategory')) {
 					$this->exportOptions['IsBaseCategory'] = 0;
 				}
 
 				if ($this->exportOptions['IsBaseCategory'] ) {
 					$sql = 'SELECT ParentPath
 							FROM '.TABLE_PREFIX.'Category
 							WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id');
 					$parent_path = $this->Conn->GetOne($sql);
 					$parent_path = explode('|', substr($parent_path, 1, -1));
 					if ($parent_path && $parent_path[0] == $this->Application->findModule('Name', 'Core', 'RootCat')) {
 						array_shift($parent_path);
 					}
 
 					$this->exportOptions['BaseLevel'] = count($parent_path); // level to cut from other categories
 				}
 
 				// 1. export field titles if required
 				if ($this->exportOptions['IncludeFieldTitles'])
 				{
 					$data_array = Array();
 					foreach ($this->exportFields as $export_field)
 					{
 						$data_array = array_merge($data_array, $this->getFieldCaption($export_field));
 					}
 					$this->writeRecord($data_array);
 				}
 				$this->exportOptions['total_records'] = $this->Conn->GetOne( $this->getExportSQL(true) );
 			}
 
 			// 2. export data
 			$records = $this->Conn->Query( $this->getExportSQL() );
 			$records_exported = 0;
 			foreach ($records as $record_info) {
 				$this->curItem->Clear();
 				$this->curItem->SetDBFieldsFromHash($record_info);
 				$this->setCurrentID();
 				$this->curItem->raiseEvent('OnAfterItemLoad', $this->curItem->GetID() );
 
 				$data_array = Array();
 				foreach ($this->exportFields as $export_field)
 				{
 					$data_array = array_merge($data_array, $this->getFieldValue($export_field) );
 				}
 				$this->writeRecord($data_array);
 				$records_exported++;
 			}
 			$this->closeFile();
 
 			$this->exportOptions['start_from'] += $records_exported;
 			$this->saveOptions($event);
 
 			return $this->exportOptions;
 		}
 
 		function getItemFields()
 		{
 			// just in case dummy user selected automtic mode & moved columns too :(
 			return array_merge($this->curItem->Fields['AvailableColumns']['options'], $this->curItem->Fields['ExportColumns']['options']);
 		}
 
 		/**
 		 * Checks if field really belongs to importable field list
 		 *
 		 * @param string $field_name
 		 * @param kCatDBItem $object
 		 * @return bool
 		 */
 		function validateField($field_name, &$object)
 		{
 			// 1. convert custom field
 			$field_name = preg_replace('/^Custom_(.*)/', '__CUSTOM__\\1', $field_name);
 
 			// 2. convert category field (mixed version & serparated version)
 			$field_name = preg_replace('/^Category(Path|[0-9]+)/', '__CATEGORY__Category\\1', $field_name);
 
 			$valid_fields = $object->getPossibleExportColumns();
 			return isset($valid_fields[$field_name]) || isset($valid_fields['__VIRTUAL__'.$field_name]);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function performImport(&$event)
 		{
 			if (!$this->exportOptions) {
 				// load import options in case if not previously loaded in verification function
 				$this->exportOptions = $this->loadOptions($event);
 			}
 
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 			$this->Application->SetVar('m_cat_id', (int)$this->Application->RecallVar('ImportCategory') );
 
 			$this->openFile($event);
 
 			$bytes_imported = 0;
 			if ($this->exportOptions['start_from'] == 0) // first export step
 			{
 				// 1st time run
 				if ($this->exportOptions['SkipFirstRow']) {
 					$this->readRecord();
 					$this->exportOptions['start_from'] = ftell($this->filePointer);
 					$bytes_imported = ftell($this->filePointer);
 				}
 
 				$current_category_id = $this->Application->GetVar('m_cat_id');
 				if ($current_category_id > 0) {
 					$sql = 'SELECT ParentPath FROM '.TABLE_PREFIX.'Category WHERE CategoryId = '.$current_category_id;
 					$this->exportOptions['ImportCategoryPath'] = $this->Conn->GetOne($sql);
 				}
 				else {
 					$this->exportOptions['ImportCategoryPath'] = '';
 				}
 				$this->exportOptions['total_records'] = filesize($this->getImportFilename());
 			}
 			else {
 				$this->loadCache();
 			}
 
 			$this->exportFields = $this->exportOptions['ExportColumns'];
 			$this->addToCache('category_parent_path', $this->Application->GetVar('m_cat_id'), $this->exportOptions['ImportCategoryPath']);
 
 			// 2. import data
 			$this->dummyCategory =& $this->Application->recallObject('c.-tmpitem', 'c', Array('skip_autoload' => true));
 			fseek($this->filePointer, $this->exportOptions['start_from']);
 
 			$items_processed = 0;
 			while (($bytes_imported < IMPORT_CHUNK && $items_processed < IMPORT_STEP) && !feof($this->filePointer)) {
 				$data = $this->readRecord();
 				if ($data) {
 					if ($this->exportOptions['ReplaceDuplicates']) {
 						// set fields used as keys for replace duplicates code
 						$this->resetImportObject($event, IMPORT_TEMP, $data);
 					}
 
 					$this->processCurrentItem($event, $data);
 				}
 				$bytes_imported = ftell($this->filePointer) - $this->exportOptions['start_from'];
 				$items_processed++;
 			}
 
 			$this->closeFile();
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 
 			$this->exportOptions['start_from'] += $bytes_imported;
 			$this->storeCache('new_ids');
 
 			$this->saveOptions($event);
 
 			if ($this->exportOptions['start_from'] == $this->exportOptions['total_records']) {
 				$this->Conn->Query('TRUNCATE TABLE '.$this->cacheTable);
 			}
 
 			return $this->exportOptions;
 		}
 
 		function setCurrentID()
 		{
 			$this->curItem->setID( $this->curItem->GetDBField($this->curItem->IDField) );
 		}
 
 		function setFieldValue($field_index, $value)
 		{
 			if (empty($value)) {
 				$value = null;
 			}
 
 			$field_name = getArrayValue($this->exportFields, $field_index);
 			if ($field_name == 'ResourceId') {
 				return false;
 			}
 
 			if (substr($field_name, 0, 7) == 'Custom_') {
 				$field_name = 'cust_'.substr($field_name, 7);
 				$this->curItem->SetField($field_name, $value);
 			}
 			elseif ($field_name == 'CategoryPath' || $field_name == '__CATEGORY__CategoryPath') {
 				$this->curItem->CategoryPath = $value ? explode($this->exportOptions['CategorySeparator'], $value) : Array();
 			}
 			elseif (substr($field_name, 0, 8) == 'Category') {
 				$this->curItem->CategoryPath[ (int)substr($field_name, 8) - 1 ] = $value;
 			}
 			elseif (substr($field_name, 0, 20) == '__CATEGORY__Category') {
 				$this->curItem->CategoryPath[ (int)substr($field_name, 20) ] = $value;
 			}
 			elseif (substr($field_name, 0, 11) == '__VIRTUAL__') {
 				$field_name = substr($field_name, 11);
 				$this->curItem->SetField($field_name, $value);
 			}
 			else {
 				$this->curItem->SetField($field_name, $value);
 			}
 
 			$pseudo_error = getArrayValue($this->curItem->FieldErrors, $field_name, 'pseudo');
 			if ($pseudo_error) {
 				$this->curItem->SetDBField($field_name, null);
 				unset($this->curItem->FieldErrors[$field_name]);
 			}
 		}
 
 		function resetImportObject(&$event, $object_type, $record_data = null)
 		{
 			switch ($object_type) {
 				case IMPORT_TEMP:
 					$this->curItem =& $event->getObject( Array('skip_autoload' => true) );
 					break;
 
 				case IMPORT_LIVE:
 					$this->curItem =& $this->Application->recallObject($event->Prefix.'.-tmpitem'.$event->Special, $event->Prefix, Array('skip_autoload' => true));
 					break;
 			}
 			$this->curItem->Clear();
 			$this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
 
 			if (isset($record_data)) {
 				$this->setImportData($record_data);
 			}
 		}
 
 		function setImportData($record_data)
 		{
 			foreach ($record_data as $field_index => $field_value) {
 				$this->setFieldValue($field_index, $field_value);
 			}
 			$this->setCurrentID();
 		}
 
 
 		function getItemCategory()
 		{
 			static $lang_prefix = null;
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			$category_id = $this->getFromCache('category_names', implode(':', $this->curItem->CategoryPath));
 			if ($category_id) {
 				$this->Application->SetVar('m_cat_id', $category_id);
 				return $category_id;
 			}
 
 			if (is_null($lang_prefix)) {
 				$lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_';
 			}
 
 			foreach ($this->curItem->CategoryPath as $category_index => $category_name) {
 				if (!$category_name) continue;
 				$category_key = crc32( implode(':', array_slice($this->curItem->CategoryPath, 0, $category_index + 1) ) );
 
 				$category_id = $this->getFromCache('category_names', $category_key);
 				if ($category_id === false) {
 					// get parent category path to search only in it
 					$current_category_id = $this->Application->GetVar('m_cat_id');
 //					$parent_path = $this->getParentPath($current_category_id);
 
 					// get category id from database by name
 					$sql = 'SELECT CategoryId
 							FROM '.TABLE_PREFIX.'Category
 							WHERE ('.$lang_prefix.'Name = '.$this->Conn->qstr($category_name).') AND (ParentId = '.(int)$current_category_id.')';
 					$category_id = $this->Conn->GetOne($sql);
 
 					if ($category_id === false) {
 						// category not in db -> create
 						$category_fields = Array(	$lang_prefix.'Name' => $category_name, $lang_prefix.'Description' => $category_name,
 													'Status' => STATUS_ACTIVE, 'ParentId' => $current_category_id, 'AutomaticFilename' => 1
 											);
 						$this->dummyCategory->SetDBFieldsFromHash($category_fields);
 						if ($this->dummyCategory->Create()) {
 							$category_id = $this->dummyCategory->GetID();
 							$this->addToCache('category_parent_path', $category_id, $this->dummyCategory->GetDBField('ParentPath'));
 							$this->addToCache('category_names', $category_key, $category_id);
 						}
 					}
 					else {
 						$this->addToCache('category_names', $category_key, $category_id);
 					}
 				}
 
 				if ($category_id) {
 					$this->Application->SetVar('m_cat_id', $category_id);
 				}
 			}
 			if (!$this->curItem->CategoryPath) {
 				$category_id = $backup_category_id;
 			}
 
 			return $category_id;
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function processCurrentItem(&$event, $record_data)
 		{
 			$save_method = 'Create';
 			$load_keys = Array();
 
 			// create/update categories
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			// perform replace duplicates code
 			if ($this->exportOptions['ReplaceDuplicates']) {
 				// get replace keys first, then reset current item to empty one
 				$category_id = $this->getItemCategory();
 				if ($this->exportOptions['CheckDuplicatesMethod'] == 1) {
 					if ($this->curItem->GetID()) {
 						$load_keys = Array($this->curItem->IDField => $this->curItem->GetID());
 					}
 				}
 				else {
 					$key_fields = $this->exportOptions['DuplicateCheckFields'];
 					foreach ($key_fields as $key_field) {
 						$load_keys[$key_field] = $this->curItem->GetDBField($key_field);
 					}
 				}
 
 				$this->resetImportObject($event, IMPORT_LIVE);
 
 				if (count($load_keys)) {
 					$where_clause = '';
 					$language_id = (int)$this->Application->GetVar('m_lang');
 
 					if (!$language_id) {
 						$language_id = 1;
 					}
 
 					foreach ($load_keys as $field_name => $field_value) {
 						if (preg_match('/^cust_(.*)/', $field_name, $regs)) {
 							$custom_id = array_search($regs[1], $this->customFields);
 							$field_name = 'l'.$language_id.'_cust_'.$custom_id;
 							$where_clause .= '(custom_data.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND ';
 						}
 						else {
 							$where_clause .= '(item_table.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND ';
 						}
 
 					}
 					$where_clause = substr($where_clause, 0, -5);
 
 					$item_id = $this->getFromCache('new_ids', crc32($where_clause));
 					if (!$item_id) {
 						if ($this->exportOptions['CheckDuplicatesMethod'] == 2) {
 							// by other fields
 							$parent_path = $this->getParentPath($category_id);
 							$where_clause = '(c.ParentPath LIKE "'.$parent_path.'%") AND '.$where_clause;
 						}
 
 						$cdata_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
 						$sql = 'SELECT '.$this->curItem->IDField.'
 								FROM '.$this->curItem->TableName.' item_table
 								LEFT JOIN '.$cdata_table.' custom_data ON custom_data.ResourceId = item_table.ResourceId
 								LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON ci.ItemResourceId = item_table.ResourceId
 								LEFT JOIN '.TABLE_PREFIX.'Category c ON c.CategoryId = ci.CategoryId
 								WHERE '.$where_clause;
 						$item_id = $this->Conn->GetOne($sql);
 					}
 					$save_method = $item_id && $this->curItem->Load($item_id) ? 'Update' : 'Create';
 					if ($save_method == 'Update') {
 						// replace id from csv file with found id (only when ID is found in cvs file)
 						if (in_array($this->curItem->IDField, $this->exportFields)) {
 							$record_data[ array_search($this->curItem->IDField, $this->exportFields) ] = $item_id;
 						}
 					}
 				}
 
 				$this->setImportData($record_data);
 			}
 			else {
 				$this->resetImportObject($event, IMPORT_LIVE, $record_data);
 				$category_id = $this->getItemCategory();
 			}
 
 			// create main record
 			if ($save_method == 'Create') {
 				$this->fillRequiredFields($this->false, $this->curItem, true);
 			}
 
 //			$sql_start = getmicrotime();
 			if (!$this->curItem->$save_method()) {
 				$this->Application->SetVar('m_cat_id', $backup_category_id);
 				return false;
 			}
 //			$sql_end = getmicrotime();
 //			$this->saveLog('SQL ['.$save_method.'] Time: '.($sql_end - $sql_start).'s');
 
 			if ($load_keys && ($save_method == 'Create') && $this->exportOptions['ReplaceDuplicates']) {
 				// map new id to old id
 				$this->addToCache('new_ids', crc32($where_clause), $this->curItem->GetID() );
 			}
 
 			// assign item to categories
 			$this->curItem->assignToCategory($category_id, false);
 
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 			return true;
 		}
 
 		/*function saveLog($msg)
 		{
 			static $first_time = true;
 
 			$fp = fopen(FULL_PATH.'/sqls.log', $first_time ? 'w' : 'a');
 			fwrite($fp, $msg."\n");
 			fclose($fp);
 
 			$first_time = false;
 		}*/
 
 		/**
 		 * Returns category parent path, if possible, then from cache
 		 *
 		 * @param int $category_id
 		 * @return string
 		 */
 		function getParentPath($category_id)
 		{
 			$parent_path = $this->getFromCache('category_parent_path', $category_id);
 			if ($parent_path === false) {
 				$sql = 'SELECT ParentPath
 						FROM '.TABLE_PREFIX.'Category
 						WHERE CategoryId = '.$category_id;
 				$parent_path = $this->Conn->GetOne($sql);
 				$this->addToCache('category_parent_path', $category_id, $parent_path);
 			}
 			return $parent_path;
 		}
 
 		function getFileExtension()
 		{
 			return $this->exportOptions['ExportFormat'] == 1 ? 'csv' : 'xml';
 		}
 
 		function getLineSeparator($option = 'LineEndings')
 		{
 			return $this->exportOptions[$option] == 1 ? "\r\n" : "\n";
 		}
 
 		/**
 		 * Returns field caption for any exported field
 		 *
 		 * @param string $field
 		 * @return string
 		 */
 		function getFieldCaption($field)
 		{
 			if (substr($field, 0, 10) == '__CUSTOM__')
 			{
 				$ret = 'Custom_'.substr($field, 10, strlen($field) );
 			}
 			elseif (substr($field, 0, 12) == '__CATEGORY__')
 			{
 				return $this->getCategoryTitle();
 			}
 			elseif (substr($field, 0, 11) == '__VIRTUAL__') {
 				$ret = substr($field, 11);
 			}
 			else
 			{
 				$ret = $field;
 			}
 
 			return Array($ret);
 		}
 
 		/**
 		 * Returns requested field value (including custom fields and category fields)
 		 *
 		 * @param string $field
 		 * @return string
 		 */
 		function getFieldValue($field)
 		{
 			if (substr($field, 0, 10) == '__CUSTOM__') {
 				$field = 'cust_'.substr($field, 10, strlen($field));
 				$ret = $this->curItem->GetField($field);
 			}
 			elseif (substr($field, 0, 12) == '__CATEGORY__') {
 				return $this->getCategoryPath();
 			}
 			elseif (substr($field, 0, 11) == '__VIRTUAL__') {
 				$field = substr($field, 11);
 				$ret = $this->curItem->GetField($field);
 			}
 			else
 			{
 				$ret = $this->curItem->GetField($field);
 			}
 
 			$ret = str_replace("\r\n", $this->getLineSeparator('LineEndingsInside'), $ret);
 			return Array($ret);
 		}
 
 		/**
 		 * Returns category field(-s) caption based on export mode
 		 *
 		 * @return string
 		 */
 		function getCategoryTitle()
 		{
 			// category path in separated fields
 			$category_count = $this->getMaxCategoryLevel();
 			if ($this->exportOptions['CategoryFormat'] == 1)
 			{
 				// category path in one field
 				return $category_count ? Array('CategoryPath') : Array();
 			}
 			else
 			{
 				$i = 0;
 				$ret = Array();
 				while ($i < $category_count) {
 					$ret[] = 'Category'.($i + 1);
 					$i++;
 				}
 				return $ret;
 			}
 		}
 
 		/**
 		 * Returns category path in required format for current link
 		 *
 		 * @return string
 		 */
 		function getCategoryPath()
 		{
 			$category_id = $this->curItem->GetDBField('CategoryId');
 			$category_path = $this->getFromCache('category_path', $category_id);
 			if (!$category_path)
 			{
 				$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 				$sql = 'SELECT '.$ml_formatter->LangFieldName('CachedNavbar').'
 						FROM '.TABLE_PREFIX.'Category
 						WHERE CategoryId = '.$category_id;
 				$category_path = $this->Conn->GetOne($sql);
 
 				$category_path = $category_path ? explode('&|&', $category_path) : Array();
 				if ($category_path && strtolower($category_path[0]) == 'content') {
 					array_shift($category_path);
 				}
 
 				if ($this->exportOptions['IsBaseCategory']) {
 					$i = $this->exportOptions['BaseLevel'];
 					while ($i > 0) {
 						array_shift($category_path);
 						$i--;
 					}
 				}
 
 				$category_count = $this->getMaxCategoryLevel();
 				if ($this->exportOptions['CategoryFormat'] == 1) {
 					// category path in single field
 					$category_path = $category_count ? Array( implode($this->exportOptions['CategorySeparator'], $category_path) ) : Array();
 				}
 				else {
 					// category path in separated fields
 					$levels_used = count($category_path);
 					if ($levels_used < $category_count)
 					{
 						$i = 0;
 						while ($i < $category_count - $levels_used) {
 							$category_path[] = '';
 							$i++;
 						}
 					}
 				}
 				$this->addToCache('category_path', $category_id, $category_path);
 			}
 
 			return $category_path;
 		}
 
 		/**
 		 * Get maximal category deep level from links beeing exported
 		 *
 		 * @return int
 		 */
 		function getMaxCategoryLevel()
 		{
 			static $max_level = -1;
 
 			if ($max_level != -1)
 			{
 				return $max_level;
 			}
 
 			$sql = 'SELECT IF(c.CategoryId IS NULL, 0, MAX( LENGTH(c.ParentPath) - LENGTH( REPLACE(c.ParentPath, "|", "") ) - 1 ))
 					FROM '.$this->curItem->TableName.' item_table
 					LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON item_table.ResourceId = ci.ItemResourceId
 					LEFT JOIN '.TABLE_PREFIX.'Category c ON c.CategoryId = ci.CategoryId
 					WHERE (ci.PrimaryCat = 1) AND ';
 
 			$where_clause = '';
 			if ($this->exportOptions['export_ids'] === false) {
 				// get links from current category & all it's subcategories
 				if ($this->exportOptions['export_cats_ids'][0] == 0) {
 					$where_clause = 1;
 				}
 				else {
 					foreach ($this->exportOptions['export_cats_ids'] as $category_id) {
 						$where_clause .= '(c.ParentPath LIKE "%|'.$category_id.'|%") OR ';
 					}
 					$where_clause = substr($where_clause, 0, -4);
 				}
 			}
 			else {
 				// get only selected links
 				$where_clause = $this->curItem->IDField.' IN ('.implode(',', $this->exportOptions['export_ids']).')';
 			}
 
 			$max_level = $this->Conn->GetOne($sql.'('.$where_clause.')');
 
 			if ($this->exportOptions['IsBaseCategory'] ) {
 				$max_level -= $this->exportOptions['BaseLevel'];
 			}
 
 			return $max_level;
 		}
 
 		/**
 		 * Saves one record to export file
 		 *
 		 * @param Array $fields_hash
 		 */
 		function writeRecord($fields_hash)
 		{
 			fputcsv2($this->filePointer, $fields_hash, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy'], $this->getLineSeparator() );
 		}
 
 		function readRecord()
 		{
 			return fgetcsv($this->filePointer, 10000, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy']);
 		}
 
 		function saveOptions(&$event, $options = null)
 		{
 			if (!isset($options)) {
 				$options = $this->exportOptions;
 			}
 			$this->Application->StoreVar($event->getPrefixSpecial().'_options', serialize($options) );
 		}
 
 		function loadOptions(&$event)
 		{
 			return unserialize($this->Application->RecallVar($event->getPrefixSpecial().'_options'));
 		}
 
 		/**
 		 * Sets correct available & export fields
 		 *
 		 * @param kEvent $event
 		 */
 		function prepareExportColumns(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 
 			if (!array_key_exists('ExportColumns', $object->Fields)) {
 				// import/export prefix was used (see kDBEventHandler::prepareObject) but object don't plan to be imported/exported
 				return ;
 			}
 
 			$available_columns = Array();
 
 			if ($this->Application->getUnitOption($event->Prefix, 'CatalogItem')) {
 				// category field (mixed)
 				$available_columns['__CATEGORY__CategoryPath'] = 'CategoryPath';
 
 				if ($event->Special == 'import') {
 					// category field (separated fields)
 					$max_level = $this->Application->ConfigValue('MaxImportCategoryLevels');
 					$i = 0;
 					while ($i < $max_level) {
 						$available_columns['__CATEGORY__Category'.($i + 1)] = 'Category'.($i + 1);
 						$i++;
 					}
 				}
 			}
 
 			// db fields
 			foreach ($object->Fields as $field_name => $field_options)
 			{
 				if (!$object->skipField($field_name))
 				{
 					$available_columns[$field_name] = $field_name.(getArrayValue($field_options, 'required') ? '*' : '');
 				}
 			}
 
 			$handler =& $this->Application->recallObject($event->Prefix.'_EventHandler');
 			$available_columns = array_merge_recursive2($available_columns, $handler->getCustomExportColumns($event));
 
 			// custom fields
 			foreach ($object->customFields as $custom_id => $custom_name)
 			{
 				$available_columns['__CUSTOM__'.$custom_name] = $custom_name;
 			}
 
 			// columns already in use
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info)
 			{
 				list($item_id, $field_values) = each($items_info);
 				$export_keys = $field_values['ExportColumns'];
 				$export_keys = $export_keys ? explode('|', substr($export_keys, 1, -1) ) : Array();
 			}
 			else {
 				$export_keys = Array();
 			}
 
 			$export_columns = Array();
 			foreach ($export_keys as $field_key)
 			{
 				$field_name = $this->getExportField($field_key);
 				$export_columns[$field_key] = $field_name;
 				unset($available_columns[$field_key]);
 			}
 
 			$options = $object->GetFieldOptions('ExportColumns');
 			$options['options'] = $export_columns;
 			$object->SetFieldOptions('ExportColumns', $options);
 
 			$options = $object->GetFieldOptions('AvailableColumns');
 			$options['options'] = $available_columns;
 			$object->SetFieldOptions('AvailableColumns', $options);
 
 			$this->updateImportFiles($event);
 			$this->PrepareExportPresets($event);
 		}
 
 		function PrepareExportPresets(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$options = $object->GetFieldOptions('ExportPresets');
 
 			$export_settings = $this->Application->RecallPersistentVar('export_settings');
 			if (!$export_settings) return ;
 			$export_settings = unserialize($export_settings);
 
 			if (!isset($export_settings[$event->Prefix])) return ;
 
 
 			$export_presets = array(''=>'');
 			foreach ($export_settings[$event->Prefix] as $key => $val) {
 				$export_presets[implode('|', $val['ExportColumns'])] = $key;
 			}
 
 			$options['options'] = $export_presets;
 			$object->SetFieldOptions('ExportPresets', $options);
 		}
 
 		function getExportField($field_key)
 		{
 			$prepends = Array('__CUSTOM__', '__CATEGORY__');
 			foreach ($prepends as $prepend)
 			{
 				if (substr($field_key, 0, strlen($prepend) ) == $prepend)
 				{
 					$field_key = substr($field_key, strlen($prepend), strlen($field_key) );
 					break;
 				}
 			}
 			return $field_key;
 		}
 
 		/**
 		 * Updates uploaded files list
 		 *
 		 * @param kEvent $event
 		 */
 		function updateImportFiles(&$event)
 		{
 			if ($event->Special != 'import') {
 				return false;
 			}
 
 			$object =& $event->getObject();
 
 			$import_filenames = Array();
 
 			if ($folder_handle = opendir(EXPORT_PATH)) {
 				while (false !== ($file = readdir($folder_handle))) {
 					if (is_dir(EXPORT_PATH.'/'.$file) || substr($file, 0, 1) == '.' || strtolower($file) == 'cvs' || strtolower($file) == 'dummy' || filesize(EXPORT_PATH.'/'.$file) == 0) continue;
 
 					$file_size = formatSize( filesize(EXPORT_PATH.'/'.$file) );
 					$import_filenames[$file] = $file.' ('.$file_size.')';
 				}
 				closedir($folder_handle);
 			}
 
 			$options = $object->GetFieldOptions('ImportLocalFilename');
 			$options['options'] = $import_filenames;
 			$object->SetFieldOptions('ImportLocalFilename', $options);
 		}
 
 		/**
 		 * Returns module folder
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
-		function getModuleFolder(&$event)
+		function getModuleName(&$event)
 		{
-			return $this->Application->getUnitOption($event->Prefix, 'ModuleFolder');
+			$module_path = $this->Application->getUnitOption($event->Prefix, 'ModuleFolder') . '/';
+			$module_name = $this->Application->findModule('Path', $module_path, 'Name');
+
+			return mb_strtolower($module_name);
 		}
 
 		/**
 		 * Export form validation & processing
 		 *
 		 * @param kEvent $event
 		 */
 		function OnExportBegin(&$event)
 		{
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if (!$items_info)
 			{
 				$items_info = unserialize( $this->Application->RecallVar($event->getPrefixSpecial().'_ItemsInfo') );
 				$this->Application->SetVar($event->getPrefixSpecial(true), $items_info);
 			}
 
 			list($item_id, $field_values) = each($items_info);
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			$object->SetFieldsFromHash($field_values);
 			$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
 
 			$object->setID($item_id);
 			$this->setRequiredFields($event);
 
 			$export_object =& $this->Application->recallObject('CatItemExportHelper');
 
 			// save export/import options
 			if ($event->Special == 'export')
 			{
 				$export_ids = $this->Application->RecallVar($event->Prefix.'_export_ids');
 				$export_cats_ids = $this->Application->RecallVar($event->Prefix.'_export_cats_ids');
 
 				// used for multistep export
 				$field_values['export_ids'] = $export_ids ? explode(',', $export_ids) : false;
 				$field_values['export_cats_ids'] = $export_cats_ids ? explode(',', $export_cats_ids) : Array( $this->Application->GetVar('m_cat_id') );
 			}
 
 			$field_values['ExportColumns'] = $field_values['ExportColumns'] ? explode('|', substr($field_values['ExportColumns'], 1, -1) ) : Array();
 			$field_values['start_from'] = 0;
 
 			$this->Application->HandleEvent($nevent, $event->Prefix.':OnBeforeExportBegin', array('options'=>$field_values));
 			$field_values = $nevent->getEventParam('options');
 
 			$export_object->saveOptions($event, $field_values);
 
 			if( $export_object->verifyOptions($event) )
 			{
 				if ($this->_getExportSavePreset($object)) {
 					$name = $object->GetDBField('ExportPresetName');
 
 					$export_settings = $this->Application->RecallPersistentVar('export_settings');
 					$export_settings = $export_settings ? unserialize($export_settings) : array();
 					$export_settings[$event->Prefix][$name] = $field_values;
 					$this->Application->StorePersistentVar('export_settings', serialize($export_settings));
 				}
 
 				$progress_t = $this->Application->RecallVar('export_progress_t');
 				if ($progress_t) {
 					$this->Application->RemoveVar('export_progress_t');
 				}
 				else {
-					$progress_t = $export_object->getModuleFolder($event).'/'.$event->Special.'_progress';
+					$progress_t = $export_object->getModuleName($event).'/'.$event->Special.'_progress';
 				}
 				$event->redirect = $progress_t;
 
 				if ($event->Special == 'import') {
 					$import_category = (int)$this->Application->RecallVar('ImportCategory');
 
 					// in future could use module root category if import category will be unavailable :)
 					$event->SetRedirectParam('m_cat_id', $import_category); // for template permission checking
 					$this->Application->StoreVar('m_cat_id', $import_category); // for event permission checking
 				}
 			}
 			else
 			{
 				// make uploaded file local & change source selection
 				$filename = getArrayValue($field_values, 'ImportFilename');
 				if ($filename) {
 					$export_object->updateImportFiles($event);
 					$object->SetDBField('ImportSource', 2);
 					$field_values['ImportSource'] = 2;
 					$object->SetDBField('ImportLocalFilename', $filename);
 					$field_values['ImportLocalFilename'] = $filename;
 					$export_object->saveOptions($event, $field_values);
 				}
 
 				$event->status = erFAIL;
 				$event->redirect = false;
 			}
 		}
 
 		/**
 		 * Returns export save preset name, when used at all
 		 *
 		 * @param kDBItem $object
 		 * @return string
 		 */
 		function _getExportSavePreset(&$object)
 		{
 			if (!array_key_exists('ExportSavePreset', $object->Fields)) {
 				return '';
 			}
 
 			return $object->GetDBField('ExportSavePreset');
 		}
 
 		/**
 		 * set required fields based on import or export params
 		 *
 		 * @param kEvent $event
 		 */
 		function setRequiredFields(&$event)
 		{
 			$required_fields['common'] = Array('FieldsSeparatedBy', 'LineEndings', 'CategoryFormat');
 
 			$required_fields['export'] = Array('ExportFormat', 'ExportFilename','ExportColumns');
 
 			$object =& $event->getObject();
 			if ($this->_getExportSavePreset($object)) {
 				$required_fields['export'][] = 'ExportPresetName';
 			}
 
 			$required_fields['import'] = Array('FieldTitles', 'ImportSource', 'CheckDuplicatesMethod'); // ImportFilename, ImportLocalFilename
 
 			if ($event->Special == 'import')
 			{
 				$import_source = Array(1 => 'ImportFilename', 2 => 'ImportLocalFilename');
 				$used_field = $import_source[ $object->GetDBField('ImportSource') ];
 
 				$required_fields[$event->Special][] = $used_field;
 				$object->Fields[$used_field]['error_field'] = 'ImportSource';
 
 				if ($object->GetDBField('FieldTitles') == 2) $required_fields[$event->Special][] = 'ExportColumns'; // manual field titles
 			}
 
 			$required_fields = array_merge($required_fields['common'], $required_fields[$event->Special]);
 			foreach ($required_fields as $required_field) {
 				$object->setRequired($required_field, true);
 			}
 		}
 
 	}
\ No newline at end of file