Index: branches/5.0.x/core/kernel/db/db_tag_processor.php
===================================================================
--- branches/5.0.x/core/kernel/db/db_tag_processor.php	(revision 13232)
+++ branches/5.0.x/core/kernel/db/db_tag_processor.php	(revision 13233)
@@ -1,2561 +1,2561 @@
 <?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 kDBTagProcessor extends kTagProcessor {
 
 	/**
 	* Description
 	*
 	* @var kDBConnection
 	* @access public
 	*/
 	var $Conn;
 
 	function kDBTagProcessor()
 	{
 		parent::kBase();
 		$this->Conn =& $this->Application->GetADODBConnection();
 	}
 
 
 	/**
 	 * Returns true if "new" button was pressed in toolbar
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsNewMode($params)
 	{
 		$object =& $this->getObject($params);
 		return $object->GetID() <= 0;
 	}
 
 	/**
 	 * Returns view menu name for current prefix
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function GetItemName($params)
 	{
 		$item_name = $this->Application->getUnitOption($this->Prefix, 'ViewMenuPhrase');
 		return $this->Application->Phrase($item_name);
 	}
 
 	function ViewMenu($params)
 	{
 		$block_params = $params;
 		unset($block_params['block']);
 		$block_params['name'] = $params['block'];
 
 		$list =& $this->GetList($params);
 		$block_params['PrefixSpecial'] = $list->getPrefixSpecial();
 		return $this->Application->ParseBlock($block_params);
 	}
 
 	function SearchKeyword($params)
 	{
 		$list =& $this->GetList($params);
 
 		return $this->Application->RecallVar($list->getPrefixSpecial() . '_search_keyword');
 	}
 
 	/**
 	 * Draw filter menu content (for ViewMenu) based on filters defined in config
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function DrawFilterMenu($params)
 	{
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['spearator_block'];
 		$separator = $this->Application->ParseBlock($block_params);
 		$filter_menu = $this->Application->getUnitOption($this->Prefix,'FilterMenu');
 		if (!$filter_menu) {
 			trigger_error('<span class="debug_error">no filters defined</span> for prefix <b>'.$this->Prefix.'</b>, but <b>DrawFilterMenu</b> tag used', E_USER_WARNING);
 			return '';
 		}
 
 		// Params: label, filter_action, filter_status
 		$block_params['name'] = $params['item_block'];
 
 		$view_filter = $this->Application->RecallVar($this->getPrefixSpecial().'_view_filter');
 		if ($view_filter === false) {
 			$event_params = Array ('prefix' => $this->Prefix, 'special' => $this->Special, 'name' => 'OnRemoveFilters');
 			$this->Application->HandleEvent( new kEvent($event_params) );
 			$view_filter = $this->Application->RecallVar($this->getPrefixSpecial().'_view_filter');
 		}
 		$view_filter = unserialize($view_filter);
 
 		$filters = Array();
 		$prefix_special = $this->getPrefixSpecial();
 
 		foreach ($filter_menu['Filters'] as $filter_key => $filter_params) {
 			$group_params = isset($filter_params['group_id']) ? $filter_menu['Groups'][ $filter_params['group_id'] ] : Array();
 			if (!isset($group_params['element_type'])) {
 				$group_params['element_type'] = 'checkbox';
 			}
 
 			if (!$filter_params) {
 				$filters[] = $separator;
 				continue;
 			}
 
 			$block_params['label'] = addslashes( $this->Application->Phrase($filter_params['label']) );
 			if (getArrayValue($view_filter,$filter_key)) {
 				$submit = 0;
 				if (isset($params['old_style'])) {
 					$status = $group_params['element_type'] == 'checkbox' ? 1 : 2;
 				}
 				else {
 					$status = $group_params['element_type'] == 'checkbox' ? '[\'img/check_on.gif\']' : '[\'img/menu_dot.gif\']';
 				}
 			}
 			else {
 				$submit = 1;
 				$status = 'null';
 			}
 			$block_params['filter_action'] = 'set_filter("'.$prefix_special.'","'.$filter_key.'","'.$submit.'",'.$params['ajax'].');';
 			$block_params['filter_status'] = $status; // 1 - checkbox, 2 - radio, 0 - no image
 			$filters[] = $this->Application->ParseBlock($block_params);
 		}
 
 		return implode('', $filters);
 	}
 
 	/**
 	 * Draws auto-refresh submenu in View Menu.
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function DrawAutoRefreshMenu($params)
 	{
 		$refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals');
 		if (!$refresh_intervals) {
 			trigger_error('<span class="debug_error">no filters defined</span> for prefix <strong>'.$this->Prefix.'</strong>, but <strong>DrawAutoRefreshMenu</strong> tag used', E_USER_WARNING);
 			return '';
 		}
 
 		$refresh_intervals = explode(',', $refresh_intervals);
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		$current_refresh_interval = $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_refresh_interval.'.$view_name);
 		if ($current_refresh_interval === false) {
 			// if no interval was selected before, then choose 1st interval
 			$current_refresh_interval = $refresh_intervals[0];
 		}
 
 		$ret = '';
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		foreach ($refresh_intervals as $refresh_interval) {
 			$block_params['label'] = $this->_formatInterval($refresh_interval);
 			$block_params['refresh_interval'] = $refresh_interval;
 			$block_params['selected'] = $current_refresh_interval == $refresh_interval;
 			$ret .= $this->Application->ParseBlock($block_params);
 		}
 		return $ret;
 	}
 
 	/**
 	 * Tells, that current grid is using auto refresh
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function UseAutoRefresh($params)
 	{
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		return $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_auto_refresh.'.$view_name);
 	}
 
 	/**
 	 * Returns current grid refresh interval
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function AutoRefreshInterval($params)
 	{
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		return $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_refresh_interval.'.$view_name);
 	}
 
 	/**
 	 * Formats time interval using given text for hours and minutes
 	 *
 	 * @param int $intervalmMinutes
 	 * @param string $hour_text Text for hours
 	 * @param string $min_text Text for minutes
 	 * @return unknown
 	 */
 	function _formatInterval($interval, $hour_text = 'h', $min_text = 'min')
 	{
 		// 65
 		$minutes = $interval % 60;
 		$hours = ($interval - $minutes) / 60;
 
 		$ret = '';
 		if ($hours) {
 			$ret .= $hours.$hour_text.' ';
 		}
 
 		if ($minutes) {
 			$ret .= $minutes.$min_text;
 		}
 
 		return $ret;
 	}
 
 	function IterateGridFields($params)
 	{
 		$mode = $params['mode'];
 		$def_block = isset($params['block']) ? $params['block'] : '';
 		$force_block = isset($params['force_block']) ? $params['force_block'] : false;
 
 		$grids = $this->Application->getUnitOption($this->Prefix,'Grids');
 		$grid_config = $grids[$params['grid']]['Fields'];
 
 		$picker_helper =& $this->Application->RecallObject('ColumnPickerHelper');
 		/* @var $picker_helper kColumnPickerHelper */
 		$picker_helper->ApplyPicker($this->getPrefixSpecial(), $grid_config, $params['grid']);
 
 		if ($mode == 'fields') {
 			return "'".join("','", array_keys($grid_config))."'";
 		}
 
 		$std_params['pass_params'] = 'true';
 		$std_params['PrefixSpecial'] = $this->getPrefixSpecial();
 
 		$object =& $this->GetList($params);
 
 		$o = '';
 		$i = 0;
 		foreach ($grid_config as $field => $options) {
 			$i++;
 			$block_params = Array();
 			$block_params['name'] = $force_block ? $force_block : (isset($options[$mode.'_block']) ? $options[$mode.'_block'] : $def_block);
 			$block_params['field'] = $field;
 			$block_params['sort_field'] = isset($options['sort_field']) ? $options['sort_field'] : $field;
 			$block_params['filter_field'] = isset($options['filter_field']) ? $options['filter_field'] : $field;
 
 			$w = $picker_helper->GetWidth($field);
 			if ($w) $options['width'] = $w;
 
 			/*if (isset($options['filter_width'])) {
 				$block_params['filter_width'] = $options['filter_width'];
 			}
 			elseif (isset($options['width'])) {
 				if (isset($options['filter_block']) && preg_match('/range/', $options['filter_block'])) {
 					if ($options['width'] < 60) {
 						$options['width'] = 60;
 						$block_params['filter_width'] = 20;
 					}
 					else {
 						$block_params['filter_width'] = $options['width'] - 40;
 					}
 				}
 				else {
 					$block_params['filter_width'] = max($options['width']-10, 20);
 				}
 			}*/
 			/*if (isset($block_params['filter_width'])) $block_params['filter_width'] .= 'px';
 
 
 			if (isset($options['filter_block']) && preg_match('/range/', $options['filter_block'])) {
 					$block_params['filter_width'] = '20px';
 			}
 			else {
 				$block_params['filter_width'] = '97%';
 //				$block_params['filter_width'] = max($options['width']-10, 20);
 			}*/
 
 
 			$field_options = $object->GetFieldOptions($field);
 			if (array_key_exists('use_phrases', $field_options)) {
 				$block_params['use_phrases'] = $field_options['use_phrases'];
 			}
 
 			$block_params['is_last'] = ($i == count($grid_config));
 			$block_params = array_merge($std_params, $options, $block_params);
 			$o.= $this->Application->ParseBlock($block_params, 1);
 		}
 		return $o;
 	}
 
 	function PickerCRC($params)
 	{
 		/* @var $picker_helper kColumnPickerHelper */
 		$picker_helper =& $this->Application->RecallObject('ColumnPickerHelper');
 		$picker_helper->SetGridName($params['grid']);
 		$data = $picker_helper->LoadColumns($this->getPrefixSpecial());
 		return $data['crc'];
 	}
 
 	function FreezerPosition($params)
 	{
 		/* @var $picker_helper kColumnPickerHelper */
 		$picker_helper =& $this->Application->RecallObject('ColumnPickerHelper');
 		$picker_helper->SetGridName($params['grid']);
 		$data = $picker_helper->LoadColumns($this->getPrefixSpecial());
 		$freezer_pos = array_search('__FREEZER__', $data['order']);
 		return $freezer_pos === false || in_array('__FREEZER__', $data['hidden_fields']) ? 1 : ++$freezer_pos;
 	}
 
 	function GridFieldsCount($params)
 	{
 		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 		$grid_config = $grids[$params['grid']]['Fields'];
 
 		return count($grid_config);
 	}
 
 	/**
 	 * Prints list content using block specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PrintList($params)
 	{
 		$params['no_table'] = 1;
 		return $this->PrintList2($params);
 	}
 
 	function InitList($params)
 	{
 		$list_name = isset($params['list_name']) ? $params['list_name'] : '';
 
 		$names_mapping = $this->Application->GetVar('NamesToSpecialMapping');
 
 		if( !getArrayValue($names_mapping, $this->Prefix, $list_name) )
 		{
 			$list =& $this->GetList($params);
 		}
 	}
 
 	function BuildListSpecial($params)
 	{
 		return $this->Special;
 	}
 
 	/**
 	 * Returns key, that identifies each list on template (used internally, not tag)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function getUniqueListKey($params)
 	{
 		$types = array_key_exists('types', $params) ? $params['types'] : '';
 		$except = array_key_exists('except', $params) ? $params['except'] : '';
 		$list_name = array_key_exists('list_name', $params) ? $params['list_name'] : '';
 
 		if (!$list_name) {
 			$list_name = $this->Application->Parser->GetParam('list_name');
 		}
 
 		return $types . $except . $list_name;
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param Array $params
 	 * @return kDBList
 	 */
 	function &GetList($params)
 	{
 		$list_name = $this->SelectParam($params, 'list_name,name');
 		if (!$list_name) {
 			$list_name = $this->Application->Parser->GetParam('list_name');
 		}
 
 		$requery = isset($params['requery']) && $params['requery'];
 		if ($list_name && !$requery) {
 			$names_mapping = $this->Application->GetVar('NamesToSpecialMapping');
 
 			$special = is_array($names_mapping) && isset($names_mapping[$this->Prefix]) && isset($names_mapping[$this->Prefix][$list_name]) ? $names_mapping[$this->Prefix][$list_name] : false;
 //			$special = getArrayValue($names_mapping, $this->Prefix, $list_name);
 			if (!$special) {
 				$special = $this->BuildListSpecial($params);
 			}
 		}
 		else {
 			$special = $this->BuildListSpecial($params);
 		}
 
 		$prefix_special = rtrim($this->Prefix.'.'.$special, '.');
 		$params['skip_counting'] = true;
 
 		$list =& $this->Application->recallObject( $prefix_special, $this->Prefix.'_List', $params);
 		/* @var $list kDBList */
 
 		if (!array_key_exists('skip_quering', $params) || !$params['skip_quering']) {
 			if ($requery) {
 				$this->Application->HandleEvent($an_event, $prefix_special.':OnListBuild', $params);
 			}
 
 			if (array_key_exists('offset', $params)) {
 				$list->Offset += $params['offset']; // apply custom offset
 			}
 
 			$list->Query($requery);
 
 			if (array_key_exists('offset', $params)) {
 				$list->Offset -= $params['offset']; // remove custom offset
 			}
 		}
 
 		$this->Special = $special;
 
 		if ($list_name) {
 			$names_mapping[$this->Prefix][$list_name] = $special;
 			$this->Application->SetVar('NamesToSpecialMapping', $names_mapping);
 		}
 
 		return $list;
 	}
 
 	function ListMarker($params)
 	{
 		$list =& $this->GetList($params);
 		$ret = $list->getPrefixSpecial();
 
 		if (array_key_exists('as_preg', $params) && $params['as_preg']) {
 			$ret = preg_quote($ret, '/');
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Prepares name for field with event in it (used only on front-end)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SubmitName($params)
 	{
 		$list =& $this->GetList($params);
 
 		$prefix_special = $list->getPrefixSpecial();
 
 		return 'events['.$prefix_special.']['.$params['event'].']';
 	}
 
 	/**
 	 * Prints list content using block specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PrintList2($params)
 	{
 		$per_page = $this->SelectParam($params, 'per_page,max_items');
 		if ($per_page !== false) $params['per_page'] = $per_page;
 
 		$list =& $this->GetList($params);
 		$o = '';
 
 		$direction 	= (isset($params['direction']) && $params['direction']=="H")?"H":"V";
 		$columns	= (isset($params['columns'])) ? $params['columns'] : 1;
 
 		$id_field = (isset($params['id_field'])) ? $params['id_field'] : $this->Application->getUnitOption($this->Prefix, 'IDField');
 
 		if ($columns > 1 && $direction == 'V') {
 			$records_left = array_splice($list->Records, $list->SelectedCount); // because we have 1 more record for "More..." link detection (don't need to sort it)
 			$list->Records = $this->LinearToVertical($list->Records, $columns, $list->GetPerPage());
 			$list->Records = array_merge($list->Records, $records_left);
 		}
 
 		$list->GoFirst();
 
 		$block_params=$this->prepareTagParams($params);
 		$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 		$block_params['pass_params'] = 'true';
 		$block_params['column_width'] = $params['column_width'] = 100 / $columns;
 		$block_start_row_params = $this->prepareTagParams($params);
 		$block_start_row_params['name'] = $this->SelectParam($params, 'row_start_render_as,block_row_start,row_start_block');
 
 		$block_end_row_params=$this->prepareTagParams($params);
 		$block_end_row_params['name'] = $this->SelectParam($params, 'row_end_render_as,block_row_end,row_end_block');
 
 		$block_empty_cell_params = $this->prepareTagParams($params);
 		$block_empty_cell_params['name'] = $this->SelectParam($params, 'empty_cell_render_as,block_empty_cell,empty_cell_block');
 
 		$i=0;
 
 		$backup_id=$this->Application->GetVar($this->Prefix."_id");
 		$displayed = array();
 		$column_number = 1;
 
 		$cache_mod_rw = $this->Application->getUnitOption($this->Prefix, 'CacheModRewrite') && $this->Application->RewriteURLs();
 		$limit = isset($params['limit']) ? $params['limit'] : false;
 
 		while (!$list->EOL() && (!$limit || $i<$limit))
 		{
 			$this->Application->SetVar( $this->getPrefixSpecial().'_id', $list->GetDBField($id_field) ); // for edit/delete links using GET
 			$this->Application->SetVar( $this->Prefix.'_id', $list->GetDBField($id_field) );
 			$block_params['is_last'] = ($i == $list->SelectedCount - 1);
 			$block_params['last_row'] = ($i + (($i+1) % $columns) >= $list->SelectedCount - 1);
 			$block_params['not_last'] = !$block_params['is_last']; // for front-end
 
 			if ($cache_mod_rw) {
 				if ($this->Prefix == 'c') {
 					// for listing subcategories in category
 					$this->Application->setCache('filenames', $this->Prefix.'_'.$list->GetDBField($id_field), $list->GetDBField('NamedParentPath'));
 					$this->Application->setCache('category_tree', $list->GetDBField($id_field), $list->GetDBField('TreeLeft') . ';' . $list->GetDBField('TreeRight'));
 				} else {
 					// for listing items in category
 					$this->Application->setCache('filenames', 'c_'.$list->GetDBField('CategoryId'), $list->GetDBField('CategoryFilename'));
 					$this->Application->setCache('filenames', $this->Prefix.'_'.$list->GetDBField($id_field), $list->GetDBField('Filename'));
 				}
 			}
 
 			if ($i % $columns == 0) {
 				// record in this iteration is first in row, then open row
 				$column_number = 1;
 				$o.= $block_start_row_params['name'] ?
 						$this->Application->ParseBlock($block_start_row_params, 1) :
 						(!isset($params['no_table']) ? '<tr>' : '');
 			}
 			else {
 				$column_number++;
 			}
 
 			$block_params['first_col'] = $column_number == 1 ? 1 : 0;
 			$block_params['last_col'] = $column_number == $columns ? 1 : 0;
 
 			$block_params['column_number'] = $column_number;
 			$block_params['num'] = ($i+1);
 
 			$this->PrepareListElementParams($list, $block_params); // new, no need to rewrite PrintList
 			$o.= $this->Application->ParseBlock($block_params, 1);
 			array_push($displayed, $list->GetDBField($id_field));
 
 			if($direction == 'V' && $list->SelectedCount % $columns > 0 && $column_number == ($columns - 1) && ceil(($i + 1) / $columns) > $list->SelectedCount % ceil($list->SelectedCount / $columns)) {
 				// if vertical output, then draw empty cells vertically, not horizontally
 				$o .= $block_empty_cell_params['name'] ? $this->Application->ParseBlock($block_empty_cell_params, 1) : '<td>&nbsp;</td>';
 				$i++;
 			}
 
 			if (($i + 1) % $columns == 0) {
 				// record in next iteration is first in row too, then close this row
 				$o.= $block_end_row_params['name'] ?
 						$this->Application->ParseBlock($block_end_row_params, 1) :
 						(!isset($params['no_table']) ? '</tr>' : '');
 			}
 
 			$list->GoNext();
 			$i++;
 		}
 
 		// append empty cells in place of missing cells in last row
 		while ($i % $columns != 0) {
 			// until next cell will be in new row append empty cells
 			$o .= $block_empty_cell_params['name'] ? $this->Application->ParseBlock($block_empty_cell_params, 1) : '<td>&nbsp;</td>';
 
 			if (($i+1) % $columns == 0) {
 				// record in next iteration is first in row too, then close this row
 				$o .= $block_end_row_params['name'] ? $this->Application->ParseBlock($block_end_row_params, 1) : '</tr>';
 			}
 			$i++;
 		}
 
 		$cur_displayed = $this->Application->GetVar($this->Prefix.'_displayed_ids');
 		if (!$cur_displayed) {
 			$cur_displayed = Array();
 		}
 		else {
 			$cur_displayed = explode(',', $cur_displayed);
 		}
 
 		$displayed = array_unique(array_merge($displayed, $cur_displayed));
 		$this->Application->SetVar($this->Prefix.'_displayed_ids', implode(',',$displayed));
 
 		$this->Application->SetVar( $this->Prefix.'_id', $backup_id);
 		$this->Application->SetVar( $this->getPrefixSpecial().'_id', '');
 
 		if (isset($params['more_link_render_as'])) {
 			$block_params = $params;
 			$params['render_as'] = $params['more_link_render_as'];
 			$o .= $this->MoreLink($params);
 		}
 
 		return $o;
 	}
 
 	/**
 	 * Allows to modify block params & current list record before PrintList parses record
 	 *
 	 * @param kDBList $object
 	 * @param Array $block_params
 	 */
 	function PrepareListElementParams(&$object, &$block_params)
 	{
 //		$fields_hash =& $object->getCurrentRecord();
 
 	}
 
 	function MoreLink($params)
 	{
 		$per_page = $this->SelectParam($params, 'per_page,max_items');
 		if ($per_page !== false) $params['per_page'] = $per_page;
 		$list =& $this->GetList($params);
 		if ($list->PerPage < $list->RecordsCount) {
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 
 			return $this->Application->ParseBlock($block_params);
 		}
 	}
 
 	function NotLastItem($params)
 	{
 		$object =& $this->getList($params); // maybe we should use $this->GetList($params) instead
 		return ($object->CurrentIndex < min($object->PerPage == -1 ? $object->RecordsCount : $object->PerPage, $object->RecordsCount) - 1);
 	}
 
 	function PageLink($params)
 	{
 		$t = isset($params['template']) ? $params['template'] : '';
 		unset($params['template']);
 
 		if (!$t) $t = $this->Application->GetVar('t');
 
 		if (isset($params['page'])) {
 			$this->Application->SetVar($this->getPrefixSpecial().'_Page', $params['page']);
 			unset($params['page']);
 		}
 
 		if (!isset($params['pass'])) {
 			$params['pass'] = 'm,'.$this->getPrefixSpecial();
 		}
 
 		return $this->Application->HREF($t, '', $params);
 	}
 
 	function ColumnWidth($params)
 	{
 		$columns = $this->Application->Parser->GetParam('columns');
 
 		return round(100/$columns).'%';
 	}
 
 	/**
 	 * Append prefix and special to tag
 	 * params (get them from tagname) like
 	 * they were really passed as params
 	 *
 	 * @param Array $tag_params
 	 * @return Array
 	 * @access protected
 	 */
 	function prepareTagParams($tag_params = Array())
 	{
 		$ret = $tag_params;
 		$ret['Prefix'] = $this->Prefix;
 		$ret['Special'] = $this->Special;
 		$ret['PrefixSpecial'] = $this->getPrefixSpecial();
 		return $ret;
 	}
 
 	function GetISO($currency)
 	{
 		if ($currency == 'selected') {
 			$iso = $this->Application->RecallVar('curr_iso');
 		}
 		elseif ($currency == 'primary' || $currency == '') {
 			$iso = $this->Application->GetPrimaryCurrency();
 		}
 		else { //explicit currency
 			$iso = $currency;
 		}
 		return $iso;
 	}
 
 	function ConvertCurrency($value, $iso)
 	{
 		$converter =& $this->Application->recallObject('kCurrencyRates');
 		// convery primary currency to selected (if they are the same, converter will just return)
 		$value = $converter->Convert($value, 'PRIMARY', $iso);
 		return $value;
 	}
 
 	function AddCurrencySymbol($value, $iso)
 	{
 		$currency =& $this->Application->recallObject('curr.-'.$iso, null, Array('skip_autoload' => true));
 		if( !$currency->isLoaded() ) $currency->Load($iso, 'ISO');
 
 		$symbol = $currency->GetDBField('Symbol');
 		if (!$symbol) $symbol = $currency->GetDBField('ISO').'&nbsp;';
 		if ($currency->GetDBField('SymbolPosition') == 0) {
 			$value = $symbol.$value;
 		}
 		if ($currency->GetDBField('SymbolPosition') == 1) {
 			$value = $value.$symbol;
 		}
 		return $value;
 	}
 
 	/**
 	 * Get's requested field value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function Field($params)
 	{
 		$field = $this->SelectParam($params, 'name,field');
 
 		if (!$this->Application->isAdmin) {
 			// apply htmlspecialchars on all field values on Front-End
 			$params['no_special'] = 'no_special';
 		}
 
 		$object =& $this->getObject($params);
 
 		if (array_key_exists('db', $params) && $params['db']) {
 			$value = $object->GetDBField($field);
 		}
 		else {
 			if (array_key_exists('currency', $params) && $params['currency']) {
 				$iso = $this->GetISO($params['currency']);
 				$original = $object->GetDBField($field);
 				$value = $this->ConvertCurrency($original, $iso);
 				$object->SetDBField($field, $value);
 				$object->Fields[$field]['converted'] = true;
 			}
 
 			$format = array_key_exists('format', $params) ? $params['format'] : false;
 			if (!$format || $format == '$format') {
 				$format = null;
 			}
 
 			$value = $object->GetField($field, $format);
 
 			if (array_key_exists('negative', $params) && $params['negative']) {
 				if (strpos($value, '-') === 0) {
 					$value = substr($value, 1);
 				}
 				else {
 					$value = '-' . $value;
 				}
 			}
 
 			if (array_key_exists('currency', $params) && $params['currency']) {
 				$value = $this->AddCurrencySymbol($value, $iso);
 				$params['no_special'] = 1;
 			}
 		}
 
 		if (!array_key_exists('no_special', $params) || !$params['no_special']) {
 			// when no_special parameter NOT SET apply htmlspecialchars
 			$value = htmlspecialchars($value);
 		}
 
 		if (array_key_exists('checked', $params) && $params['checked']) {
 			$value = ($value == ( isset($params['value']) ? $params['value'] : 1)) ? 'checked' : '';
 		}
 
 		if (array_key_exists('plus_or_as_label', $params) && $params['plus_or_as_label']) {
 			$value = substr($value, 0,1) == '+' ? substr($value, 1) : $this->Application->Phrase($value);
 		}
 		elseif (array_key_exists('as_label', $params) && $params['as_label']) {
 			$value = $this->Application->Phrase($value);
 		}
 
 		$first_chars = $this->SelectParam($params,'first_chars,cut_first');
 
 		if ($first_chars) {
 			$stripped_value = strip_tags($value, $this->SelectParam($params, 'allowed_tags'));
 
 			if (mb_strlen($stripped_value) > $first_chars) {
 				$value = mb_substr($stripped_value, 0, $first_chars) . ' ...';
 			}
 		}
 
 		if (array_key_exists('nl2br', $params) && $params['nl2br']) {
 			$value = nl2br($value);
 		}
 
 		if ($value != '') {
 			$this->Application->Parser->DataExists = true;
 		}
 
 		if (array_key_exists('currency', $params) && $params['currency']) {
 			// restoring value in original currency, for other Field tags to work properly
 			$object->SetDBField($field, $original);
 		}
 
 		return $value;
 	}
 
 	function SetField($params)
 	{
 		// <inp2:SetField field="Value" src=p:cust_{$custom_name}"/>
 
 		$object =& $this->getObject($params);
 		$dst_field = $this->SelectParam($params, 'name,field');
 
 		list($prefix_special, $src_field) = explode(':', $params['src']);
 		$src_object =& $this->Application->recallObject($prefix_special);
 		$object->SetDBField($dst_field, $src_object->GetDBField($src_field));
 	}
 
 	function PhraseField($params)
 	{
 		$field_label = $this->Field($params);
 		$translation = $this->Application->Phrase( $field_label );
 		return $translation;
 	}
 
 	function Error($params)
 	{
 		$field = $this->SelectParam($params, 'name,field');
 		$object =& $this->getObject($params);
 		$msg = $object->GetErrorMsg($field, false);
 		return $msg;
 	}
 
 	function HasError($params)
 	{
 		if ($params['field'] == 'any')
 		{
 			$object =& $this->getObject($params);
 
 			$skip_fields = array_key_exists('except', $params) ? $params['except'] : false;
 			$skip_fields = $skip_fields ? explode(',', $skip_fields) : Array();
 
 			return $object->HasErrors($skip_fields);
 		}
 		else
 		{
 			$fields = $this->SelectParam($params, 'field,fields');
 			$fields = explode(',', $fields);
 			$res = false;
 			foreach($fields as $field)
 			{
 				$params['field'] = $field;
 				$res = $res || ($this->Error($params) != '');
 			}
 			return $res;
 		}
 	}
 
 	function ErrorWarning($params)
 	{
 		if (!isset($params['field'])) {
 			$params['field'] = 'any';
 		}
 		if ($this->HasError($params)) {
 			$params['prefix'] = $this->getPrefixSpecial();
 			return $this->Application->ParseBlock($params);
 		}
 	}
 
 	function IsRequired($params)
 	{
 		$field = $params['field'];
 		$object =& $this->getObject($params);;
 
 		$formatter_class = getArrayValue($object->Fields, $field, 'formatter');
 		if ($formatter_class == 'kMultiLanguage')
 		{
 			$formatter =& $this->Application->recallObject($formatter_class);
 			$field = $formatter->LangFieldName($field);
 		}
 
 		$options = $object->GetFieldOptions($field);
 
 		return array_key_exists('required', $options) ? $options['required'] : false;
 	}
 
 	function FieldOption($params)
 	{
 		$object =& $this->getObject($params);;
 		$options = $object->GetFieldOptions($params['field']);
 		$ret =  isset($options[$params['option']]) ? $options[$params['option']] : '';
 		if (isset($params['as_label']) && $params['as_label']) $ret = $this->Application->ReplaceLanguageTags($ret);
 		return $ret;
 	}
 
 	function PredefinedOptions($params)
 	{
 		$object =& $this->getObject($params);
 
 		$field = $params['field'];
 		$value = array_key_exists('value', $params) ? $params['value'] : $object->GetDBField($field);
 		$field_options = $object->GetFieldOptions($field);
 		if (!array_key_exists('options', $field_options) || !is_array($field_options['options'])) {
 			trigger_error('Options not defined for <strong>'.$object->Prefix.'</strong> field <strong>'.$field.'</strong>', E_USER_WARNING);
 			return '';
 		}
 
 		$options = $field_options['options'];
 
 		if (array_key_exists('has_empty', $params) && $params['has_empty']) {
 			$empty_value = array_key_exists('empty_value', $params) ? $params['empty_value'] : '';
 			$options = array_merge_recursive2(Array ($empty_value => ''), $options); // don't use other array merge function, because they will reset keys !!!
 		}
 
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 		$block_params['pass_params'] = 'true';
 		if (method_exists($object, 'EOL') && count($object->Records) == 0) {
 			// for drawing grid column filter
 			$block_params['field_name'] = '';
 		}
 		else {
 			$block_params['field_name'] = $this->InputName($params); // depricated (produces warning when used as grid filter), but used in Front-End (submission create), admin (submission view)
 		}
 
 		$selected_param_name = array_key_exists('selected_param', $params) ? $params['selected_param'] : false;
 		if (!$selected_param_name) {
 			$selected_param_name = $params['selected'];
 		}
 		$selected = $params['selected'];
 
 		$o = '';
 		if (array_key_exists('no_empty', $params) && $params['no_empty'] && !getArrayValue($options, '')) {
 			// removes empty option, when present (needed?)
 			array_shift($options);
 		}
 
 		if (strpos($value, '|') !== false) {
 			// multiple checkboxes OR multiselect
 			$value = explode('|', substr($value, 1, -1) );
 			foreach ($options as $key => $val) {
 				$block_params['key'] = $key;
 				$block_params['option'] = $val;
 				$block_params[$selected_param_name] = ( in_array($key, $value) ? ' '.$selected : '');
 				$o .= $this->Application->ParseBlock($block_params, 1);
 			}
 		}
 		else {
 			// single selection radio OR checkboxes OR dropdown
 			foreach ($options as $key => $val) {
 				$block_params['key'] = $key;
 				$block_params['option'] = $val;
 				$block_params[$selected_param_name] = (strlen($key) == strlen($value) && ($key == $value) ? ' '.$selected : '');
 				$o .= $this->Application->ParseBlock($block_params, 1);
 			}
 		}
 		return $o;
 	}
 
 	function PredefinedSearchOptions($params)
 	{
 		$object =& $this->GetList($params);
 		/* @var $object kDBList */
 
 		$params['value'] = $this->SearchField($params);
 
 		return $this->PredefinedOptions($params);
 	}
 
 	function Format($params)
 	{
 		$field = $this->SelectParam($params, 'name,field');
 		$object =& $this->getObject($params);
 
 		$options = $object->GetFieldOptions($field);
 
 		$format = $options[ $this->SelectParam($params, 'input_format') ? 'input_format' : 'format' ];
 
 		$formatter_class = array_key_exists('formatter', $options) ? $options['formatter'] : false;
 
 		if ($formatter_class) {
 			$formatter =& $this->Application->recallObject($formatter_class);
 			$human_format = array_key_exists('human', $params) ? $params['human'] : false;
 			$edit_size = array_key_exists('edit_size', $params) ? $params['edit_size'] : false;
 			$sample = array_key_exists('sample', $params) ? $params['sample'] : false;
 			if ($sample) {
 				return $formatter->GetSample($field, $options, $object);
 			}
 			elseif ($human_format || $edit_size) {
 				$format = $formatter->HumanFormat($format);
 				return $edit_size ? strlen($format) : $format;
 			}
 		}
 
 		return $format;
 	}
 
 	/**
 	 * Returns grid padination information
 	 * Can return links to pages
 	 *
 	 * @param Array $params
 	 * @return mixed
 	 */
 	function PageInfo($params)
 	{
 		$object =& $this->GetList($params);
 		/* @var $object kDBList */
 
 		$type = $params['type'];
 		unset($params['type']); // remove parameters used only by current tag
 
 		$ret = '';
 		switch ($type) {
 			case 'current':
 				$ret = $object->Page;
 				break;
 
 			case 'total':
 				$ret = $object->GetTotalPages();
 				break;
 
 			case 'prev':
 				$ret = $object->Page > 1 ? $object->Page - 1 : false;
 				break;
 
 			case 'next':
 				$ret = $object->Page < $object->GetTotalPages() ? $object->Page + 1 : false;
 				break;
 		}
 
 		if ($ret && isset($params['as_link']) && $params['as_link']) {
 			unset($params['as_link']); // remove parameters used only by current tag
 			$params['page']	= $ret;
 			$current_page = $object->Page; // backup current page
 			$ret = $this->PageLink($params);
 			$this->Application->SetVar($object->getPrefixSpecial().'_Page', $current_page); // restore page
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Print grid pagination using
 	 * block names specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PrintPages($params)
 	{
 		$list =& $this->GetList($params);
 		$prefix_special = $list->getPrefixSpecial();
 		$total_pages = $list->GetTotalPages();
 
 		if ($total_pages > 1) $this->Application->Parser->DataExists = true;
 
 		if($total_pages == 0) $total_pages = 1; // display 1st page as selected in case if we have no pages at all
 		$o = '';
 
 		// what are these 2 lines for?
 		$this->Application->SetVar($prefix_special.'_event','');
 		$this->Application->SetVar($prefix_special.'_id','');
 
 		$current_page = $list->Page; //  $this->Application->RecallVar($prefix_special.'_Page');
 
 		$block_params = $this->prepareTagParams($params);
 
 		$split = ( isset($params['split'] ) ? $params['split'] : 10 );
 
 		$split_start = $current_page - ceil($split/2);
 		if ($split_start < 1){
 			$split_start = 1;
 		}
 		$split_end = $split_start + $split-1;
 
 		if ($split_end > $total_pages) {
 			$split_end = $total_pages;
 			$split_start = max($split_end - $split + 1, 1);
 		}
 
 		if ($current_page > 1){
 			$prev_block_params = $this->prepareTagParams();
 
 			if ($total_pages > $split){
 				$prev_block_params['page'] = max($current_page-$split, 1);
 				$prev_block_params['name'] = $this->SelectParam($params, 'prev_page_split_render_as,prev_page_split_block');
 				if ($prev_block_params['name']){
 					$o .= $this->Application->ParseBlock($prev_block_params, 1);
 				}
 			}
 
 			$prev_block_params['name'] = 'page';
 			$prev_block_params['page'] = $current_page-1;
 			$prev_block_params['name'] = $this->SelectParam($params, 'prev_page_render_as,block_prev_page,prev_page_block');
 			if ($prev_block_params['name']) {
 				$this->Application->SetVar($this->getPrefixSpecial().'_Page', $current_page-1);
 				$o .= $this->Application->ParseBlock($prev_block_params, 1);
 			}
 		}
 		else {
 			if ( $no_prev_page_block = $this->SelectParam($params, 'no_prev_page_render_as,block_no_prev_page') ) {
 				$block_params['name'] = $no_prev_page_block;
 				$o .= $this->Application->ParseBlock($block_params, 1);
 			}
 		}
 
 		$separator_params['name'] = $this->SelectParam($params, 'separator_render_as,block_separator');
 		for ($i = $split_start; $i <= $split_end; $i++)
 		{
 			if ($i == $current_page) {
 				$block = $this->SelectParam($params, 'current_render_as,active_render_as,block_current,active_block');
 			}
 			else {
 				$block = $this->SelectParam($params, 'link_render_as,inactive_render_as,block_link,inactive_block');
 			}
 
 			$block_params['name'] = $block;
 			$block_params['page'] = $i;
 			$this->Application->SetVar($this->getPrefixSpecial().'_Page', $i);
 			$o .= $this->Application->ParseBlock($block_params, 1);
 
 			if ($this->SelectParam($params, 'separator_render_as,block_separator')
 				&& $i < $split_end)
 			{
 				$o .= $this->Application->ParseBlock($separator_params, 1);
 			}
 		}
 
 		if ($current_page < $total_pages){
 			$next_block_params = $this->prepareTagParams();
 			$next_block_params['page']=$current_page+1;
 			$next_block_params['name'] = $this->SelectParam($params, 'next_page_render_as,block_next_page,next_page_block');
 			if ($next_block_params['name']){
 				$this->Application->SetVar($this->getPrefixSpecial().'_Page', $current_page+1);
 				$o .= $this->Application->ParseBlock($next_block_params, 1);
 			}
 			if ($total_pages > $split){
 				$next_block_params['page']=min($current_page+$split, $total_pages);
 				$next_block_params['name'] = $this->SelectParam($params, 'next_page_split_render_as,next_page_split_block');
 				if ($next_block_params['name']){
 					$o .= $this->Application->ParseBlock($next_block_params, 1);
 				}
 			}
 		}
 		else {
 			if ( $no_next_page_block = $this->SelectParam($params, 'no_next_page_render_as,block_no_next_page') ) {
 				$block_params['name'] = $no_next_page_block;
 				$o .= $this->Application->ParseBlock($block_params, 1);
 			}
 		}
 
 		$this->Application->SetVar($this->getPrefixSpecial().'_Page', $current_page);
 		return $o;
 	}
 
 	/**
 	 * Print grid pagination using
 	 * block names specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PaginationBar($params)
 	{
 		return $this->PrintPages($params);
 	}
 
 
 	/**
 	 * Returns field name (processed by kMultiLanguage formatter
 	 * if required) and item's id from it's IDField or field required
 	 *
 	 * @param Array $params
 	 * @return Array (id,field)
 	 * @access private
 	 */
 	function prepareInputName($params)
 	{
 		$field = $this->SelectParam($params, 'name,field');
 		$object =& $this->getObject($params);
 
 		$formatter_class = getArrayValue($object->Fields, $field, 'formatter');
 		if ($formatter_class == 'kMultiLanguage') {
 			$formatter =& $this->Application->recallObject($formatter_class);
 			/* @var $formatter kMultiLanguage */
 
 			$force_primary = isset($object->Fields[$field]['force_primary']) && $object->Fields[$field]['force_primary'];
 			$field = $formatter->LangFieldName($field, $force_primary);
 		}
 
 		if (array_key_exists('force_id', $params)) {
 			$id = $params['force_id'];
 		}
 		else {
 			$id_field = array_key_exists('IdField', $params) ? $params['IdField'] : false;
 			$id = $id_field ? $object->GetDBField($id_field) : $object->GetID();
 		}
 
 		return Array($id, $field);
 	}
 
 
 	/**
 	 * Returns input field name to
 	 * be placed on form (for correct
 	 * event processing)
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function InputName($params)
 	{
 		list($id, $field) = $this->prepareInputName($params);
 
 		$ret = $this->getPrefixSpecial().'['.$id.']['.$field.']';
 
 		if (array_key_exists('as_preg', $params) && $params['as_preg']) {
 			$ret = preg_quote($ret, '/');
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Allows to override various field options through hidden fields with specific names in submit.
 	 * This tag generates this special names
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @author Alex
 	 */
 	function FieldModifier($params)
 	{
 		list($id, $field) = $this->prepareInputName($params);
 
 		$ret = 'field_modifiers['.$this->getPrefixSpecial().']['.$field.']['.$params['type'].']';
 
 		if (array_key_exists('as_preg', $params) && $params['as_preg']) {
 			$ret = preg_quote($ret, '/');
 		}
 
 		if (isset($params['value'])) {
 			$object =& $this->getObject($params);
 			$field_modifiers[$field][$params['type']] = $params['value'];
 			$object->ApplyFieldModifiers($field_modifiers);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns index where 1st changable sorting field begins
 	 *
 	 * @return int
 	 * @access private
 	 */
 	function getUserSortIndex()
 	{
 		$list_sortings = $this->Application->getUnitOption($this->Prefix, 'ListSortings');
 		$sorting_prefix = getArrayValue($list_sortings, $this->Special) ? $this->Special : '';
 
 		$user_sorting_start = 0;
 		if ( $forced_sorting = getArrayValue($list_sortings, $sorting_prefix, 'ForcedSorting') ) {
 			$user_sorting_start = count($forced_sorting);
 		}
 		return $user_sorting_start;
 	}
 
 	/**
 	 * Returns order direction for given field
 	 *
 	 *
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function Order($params)
 	{
 		$field = $params['field'];
 		$user_sorting_start = $this->getUserSortIndex();
 
 		$list =& $this->GetList($params);
 
 		if ($list->GetOrderField($user_sorting_start) == $field)
 		{
 			return strtolower($list->GetOrderDirection($user_sorting_start));
 		}
 		elseif($this->Application->ConfigValue('UseDoubleSorting') && $list->GetOrderField($user_sorting_start+1) == $field)
 		{
 			return '2_'.strtolower($list->GetOrderDirection($user_sorting_start+1));
 		}
 		else
 		{
 			return 'no';
 		}
 	}
 
 	/**
 	 * Detects, that current sorting is not default
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function OrderChanged($params)
 	{
 		$list =& $this->GetList($params);
 		$user_sorting_start = $this->getUserSortIndex();
 
 		$sorting_configs = $this->Application->getUnitOption($this->Prefix, 'ConfigMapping', Array ());
 		$list_sortings = $this->Application->getUnitOption($this->Prefix, 'ListSortings', Array ());
 		$sorting_prefix = getArrayValue($list_sortings, $this->Special) ? $this->Special : '';
 
 		if (array_key_exists('DefaultSorting1Field', $sorting_configs)) {
 			$list_sortings[$sorting_prefix]['Sorting'] = Array (
 				$this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']),
 				$this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']),
 			);
 		}
 
 		$sorting = getArrayValue($list_sortings, $sorting_prefix, 'Sorting');
 		$sort_fields = is_array($sorting) ? array_keys($sorting) : Array ();
 
 		for ($order_number = 0; $order_number < 2; $order_number++) {
 			// currect sorting in list
 			$sorting_pos = $user_sorting_start + $order_number;
 			$current_order_field = $list->GetOrderField($sorting_pos, true);
 			$current_order_direction = $list->GetOrderDirection($sorting_pos, true);
 
 			if (!$current_order_field || !$current_order_direction) {
 				// no sorting defined for this sorting position
 				continue;
 			}
 
 			// user sorting found
 			if (array_key_exists($order_number, $sort_fields)) {
 				// default sorting found
 				$default_order_field = $sort_fields[$order_number];
 				$default_order_direction = $sorting[$default_order_field];
 
 				if ($current_order_field != $default_order_field || $current_order_direction != $default_order_direction) {
 					// #1. user sorting differs from default sorting -> changed
 					return true;
 				}
 			}
 			else {
 				// #2. user sorting + no default sorting -> changed
 				return true;
 			}
 		}
 
 		// #3. user sorting match default or not defined -> not changed
 		return false;
 	}
 
 	/**
 	 * Get's information of sorting field at "pos" position,
 	 * like sorting field name (type="field") or sorting direction (type="direction")
 	 *
 	 * @param Array $params
 	 * @return mixed
 	 */
 	function OrderInfo($params)
 	{
 		$user_sorting_start = $this->getUserSortIndex() + --$params['pos'];
 		$list =& $this->GetList($params);
 //		$object =& $this->Application->recallObject( $this->getPrefixSpecial() );
 
 		if($params['type'] == 'field') return $list->GetOrderField($user_sorting_start);
 		if($params['type'] == 'direction') return $list->GetOrderDirection($user_sorting_start);
 	}
 
 	/**
 	 * Checks if sorting field/direction matches passed field/direction parameter
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsOrder($params)
 	{
 		$params['type'] = isset($params['field']) ? 'field' : 'direction';
 		$value = $this->OrderInfo($params);
 
 		if( isset($params['field']) ) return $params['field'] == $value;
 		if( isset($params['direction']) ) return $params['direction'] == $value;
 	}
 
 	/**
 	 * Returns list perpage
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function PerPage($params)
 	{
 		$object =& $this->getObject($params);
 		return $object->PerPage;
 	}
 
 	/**
 	 * Checks if list perpage matches value specified
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function PerPageEquals($params)
 	{
 		$object =& $this->getObject($params);
 		return $object->PerPage == $params['value'];
 	}
 
 	function SaveEvent($params)
 	{
 		// SaveEvent is set during OnItemBuild, but we may need it before any other tag calls OnItemBuild
 		$object =& $this->getObject($params);
 		return $this->Application->GetVar($this->getPrefixSpecial().'_SaveEvent');
 	}
 
 	function NextId($params)
 	{
 		$object =& $this->getObject($params);
 
 		$wid = $this->Application->GetTopmostWid($this->Prefix);
 		$session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_');
 		$ids = explode(',', $this->Application->RecallVar($session_name));
 
 		$cur_id = $object->GetID();
 
 		$i = array_search($cur_id, $ids);
 		if ($i !== false) {
 			return $i < count($ids) - 1 ? $ids[$i + 1] : '';
 		}
 		return '';
 	}
 
 	function PrevId($params)
 	{
 		$object =& $this->getObject($params);
 
 		$wid = $this->Application->GetTopmostWid($this->Prefix);
 		$session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_');
 		$ids = explode(',', $this->Application->RecallVar($session_name));
 
 		$cur_id = $object->GetID();
 
 		$i = array_search($cur_id, $ids);
 		if ($i !== false) {
 			return $i > 0 ? $ids[$i - 1] : '';
 		}
 		return '';
 	}
 
 	function IsSingle($params)
 	{
 		return ($this->NextId($params) === '' && $this->PrevId($params) === '');
 	}
 
 	function IsLast($params)
 	{
 		return ($this->NextId($params) === '');
 	}
 
 	function IsFirst($params)
 	{
 		return ($this->PrevId($params) === '');
 	}
 
 	/**
 	 * Checks if field value is equal to proposed one
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function FieldEquals($params)
 	{
 		$object =& $this->getObject($params);
 
 		return $object->GetDBField( $this->SelectParam($params, 'name,field') ) == $params['value'];
 	}
 
 	/**
 	 * Checks, that grid has icons defined and they should be shown
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function UseItemIcons($params)
 	{
 		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 		return array_key_exists('Icons', $grids[ $params['grid'] ]);
 	}
 
 	/**
 	 * Returns corresponding to grid layout selector column width
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function GridSelectorColumnWidth($params)
 	{
 		$width = 0;
 		if ($params['selector']) {
 			$width += $params['selector_width'];
 		}
 
 		if ($this->UseItemIcons($params)) {
 			$width += $params['icon_width'];
 		}
 
 		return $width;
 	}
 
 	/**
 	 * Returns grids item selection mode (checkbox, radio, )
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function GridSelector($params)
 	{
 		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 
 		return array_key_exists('Selector', $grids[ $params['grid'] ]) ? $grids[ $params['grid'] ]['Selector'] : $params['default'];
 	}
 
 	function ItemIcon($params)
 	{
 		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 		$grid = $grids[ $params['grid'] ];
 
 		if (!array_key_exists('Icons', $grid)) {
 			return '';
 		}
 
 		$icons = $grid['Icons'];
 
 		if (array_key_exists('name', $params)) {
 			$icon_name = $params['name'];
 			return array_key_exists($icon_name, $icons) ? $icons[$icon_name] : '';
 		}
 
 		$status_fields = $this->Application->getUnitOption($this->Prefix, 'StatusField');
 
 		if (!$status_fields) {
 			return $icons['default'];
 		}
 
 		$object =& $this->getObject($params);
 		/* @var $object kDBList */
 
 		$icon = '';
 
 		foreach ($status_fields as $status_field) {
 			$icon .= $object->GetDBField($status_field) . '_';
 		}
 
 		$icon = rtrim($icon, '_');
 
 		return array_key_exists($icon, $icons) ? $icons[$icon] : $icons['default'];
 	}
 
 	/**
 	 * Generates bluebar title + initializes prefixes used on page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SectionTitle($params)
 	{
 		$preset_name = replaceModuleSection($params['title_preset']);
 		$title_presets = $this->Application->getUnitOption($this->Prefix,'TitlePresets');
 		$title_info = array_key_exists($preset_name, $title_presets) ? $title_presets[$preset_name] : false;
 
 		if ($title_info === false) {
 			$title = str_replace('#preset_name#', $preset_name, $params['title']);
 			if ($this->Application->ConfigValue('UseSmallHeader') && isset($params['group_title']) && $params['group_title']) {
 				$title .= ' - '.$params['group_title'];
 			}
 			return $title;
 		}
 
 		if (array_key_exists('default', $title_presets) && $title_presets['default']) {
 			// use default labels + custom labels specified in preset used
 			$title_info = array_merge_recursive2($title_presets['default'], $title_info);
 		}
 
 		$title = $title_info['format'];
 
 		// 1. get objects in use for title construction
 		$objects = Array();
 		$object_status = Array();
 		$status_labels = Array();
 
 		$prefixes = array_key_exists('prefixes', $title_info) ? $title_info['prefixes'] : false;
 		$all_tag_params = array_key_exists('tag_params', $title_info) ? $title_info['tag_params'] : false;
 
 		if ($prefixes) {
 			// extract tag_perams passed directly to SectionTitle tag for specific prefix
 			foreach ($params as $tp_name => $tp_value) {
 				if (preg_match('/(.*)\[(.*)\]/', $tp_name, $regs)) {
 					$all_tag_params[ $regs[1] ][ $regs[2] ] = $tp_value;
 					unset($params[$tp_name]);
 				}
 			}
 
 			$tag_params = Array();
 			foreach ($prefixes as $prefix_special) {
 				$prefix_data = $this->Application->processPrefix($prefix_special);
 				$prefix_data['prefix_special'] = rtrim($prefix_data['prefix_special'],'.');
 
 				if ($all_tag_params) {
 					$tag_params = getArrayValue($all_tag_params, $prefix_data['prefix_special']);
 					if (!$tag_params) {
 						$tag_params = Array();
 					}
 				}
 
 				$tag_params = array_merge_recursive2($params, $tag_params);
 				$objects[ $prefix_data['prefix_special'] ] =& $this->Application->recallObject($prefix_data['prefix_special'], $prefix_data['prefix'], $tag_params);
 				$object_status[ $prefix_data['prefix_special'] ] = $objects[ $prefix_data['prefix_special'] ]->IsNewItem() ? 'new' : 'edit';
 
 				// a. set object's status field (adding item/editing item) for each object in title
 				if (getArrayValue($title_info[ $object_status[ $prefix_data['prefix_special'] ].'_status_labels' ],$prefix_data['prefix_special'])) {
 					$status_labels[ $prefix_data['prefix_special'] ] = $title_info[ $object_status[ $prefix_data['prefix_special'] ].'_status_labels' ][ $prefix_data['prefix_special'] ];
 					$title = str_replace('#'.$prefix_data['prefix_special'].'_status#', $status_labels[ $prefix_data['prefix_special'] ], $title);
 				}
 
 				// b. setting object's titlefield value (in titlebar ONLY) to default in case if object beeing created with no titlefield filled in
 				if ($object_status[ $prefix_data['prefix_special'] ] == 'new') {
 					$new_value = $this->getInfo( $objects[ $prefix_data['prefix_special'] ], 'titlefield' );
 					if(!$new_value && getArrayValue($title_info['new_titlefield'],$prefix_data['prefix_special']) ) $new_value = $this->Application->Phrase($title_info['new_titlefield'][ $prefix_data['prefix_special'] ]);
 					$title = str_replace('#'.$prefix_data['prefix_special'].'_titlefield#', $new_value, $title);
 				}
 			}
 		}
 
 		// replace to section title
 		$section = array_key_exists('section', $params) ? $params['section'] : false;
 		if ($section) {
 			$sections_helper =& $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section);
 			$title = str_replace('#section_label#', '!' . $section_data['label'] . '!', $title);
 		}
 
 		// 2. replace phrases if any found in format string
 		$title = $this->Application->ReplaceLanguageTags($title, false);
 
 		// 3. find and replace any replacement vars
 		preg_match_all('/#(.*_.*)#/Uis',$title,$rets);
 		if ($rets[1]) {
 			$replacement_vars = array_keys( array_flip($rets[1]) );
 			foreach ($replacement_vars as $replacement_var) {
 				$var_info = explode('_',$replacement_var,2);
 				$object =& $objects[ $var_info[0] ];
 				$new_value = $this->getInfo($object,$var_info[1]);
 				$title = str_replace('#'.$replacement_var.'#', $new_value, $title);
 			}
 		}
 
 		// replace trailing spaces inside title preset + '' occurences into single space
 		$title = preg_replace('/[ ]*\'\'[ ]*/', ' ', $title);
 
 		if ($this->Application->ConfigValue('UseSmallHeader') && isset($params['group_title']) && $params['group_title']) {
 			$title .= ' - '.$params['group_title'];
 		}
 
 		$first_chars = $this->SelectParam($params, 'first_chars,cut_first');
 
 		if ($first_chars && !preg_match('/<a href="(.*)".*>(.*)<\/a>/', $title)) {
 			// don't cut titles, that contain phrase translation links
 			$stripped_title = strip_tags($title, $this->SelectParam($params, 'allowed_tags'));
 
 			if (mb_strlen($stripped_title) > $first_chars) {
 				$title = mb_substr($stripped_title, 0, $first_chars) . ' ...';
 			}
 		}
 
 		return $title;
 	}
 
 	function getInfo(&$object, $info_type)
 	{
 		switch ($info_type)
 		{
 			case 'titlefield':
 				$field = $this->Application->getUnitOption($object->Prefix,'TitleField');
 				return $field !== false ? $object->GetField($field) : 'TitleField Missing';
 				break;
 
 			case 'recordcount':
 				$of_phrase = $this->Application->Phrase('la_of');
 				return $object->NoFilterCount != $object->RecordsCount ? $object->RecordsCount.' '.$of_phrase.' '.$object->NoFilterCount : $object->RecordsCount;
 				break;
 
 			default:
 				return $object->GetField($info_type);
 				break;
 		}
 	}
 
 	function GridInfo($params)
 	{
 		$object =& $this->GetList($params);
 		/* @var $object kDBList */
 
 		switch ($params['type']) {
 			case 'filtered':
 				return $object->GetRecordsCount();
 			case 'total':
 				return $object->GetNoFilterCount();
 			case 'from':
 				return $object->RecordsCount ? $object->Offset+1 : 0; //0-based
 			case 'to':
 				return $object->PerPage > 0 ? min($object->Offset + $object->PerPage, $object->RecordsCount) : $object->RecordsCount;
 			case 'total_pages':
 				return $object->GetTotalPages();
 			case 'needs_pagination':
 				return ($object->PerPage != -1) && (($object->RecordsCount > $object->PerPage) || ($object->Page > 1));
 		}
 	}
 
 	/**
 	 * Parses block depending on its element type.
 	 * For radio and select elements values are taken from 'value_list_field' in key1=value1,key2=value2
 	 * format. key=value can be substituted by <SQL>SELECT f1 AS OptionName, f2 AS OptionValue... FROM <PREFIX>TableName </SQL>
 	 * where prefix is TABLE_PREFIX
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ConfigFormElement($params)
 	{
 		$object =& $this->getObject($params);
 		$field = $params['field'];
 
 		$helper =& $this->Application->recallObject('InpCustomFieldsHelper');
 		/* @var $helper InpCustomFieldsHelper */
 
 		$element_type = $object->GetDBField($params['element_type_field']);
 
 		if($element_type == 'label') $element_type = 'text';
 		$params['name'] = $params['blocks_prefix'].$element_type;
 
 		switch ($element_type) {
 			case 'select':
 			case 'multiselect':
 			case 'radio':
 				$field_options = $object->GetFieldOptions($field, 'options');
 
 				if ($object->GetDBField('DirectOptions')) {
 					// used for custom fields
 					$field_options['options'] = $object->GetDBField('DirectOptions');
 				}
 				else {
 					// used for configuration
 					$field_options['options'] = $helper->GetValuesHash($object->GetDBField($params['value_list_field']), ',');
 				}
 
 				$object->SetFieldOptions($field, $field_options);
 				break;
 
 			case 'text':
 			case 'textarea':
 				$params['field_params'] = $helper->ParseConfigSQL($object->GetDBField($params['value_list_field']));
 				break;
 
 			case 'password':
 			case 'checkbox':
 			default:
 				break;
 		}
 		return $this->Application->ParseBlock($params, 1);
 	}
 
 	/**
 	 * Get's requested custom field value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function CustomField($params)
 	{
 		$params['name'] = 'cust_'.$this->SelectParam($params, 'name,field');
 		return $this->Field($params);
 	}
 
 	function CustomFieldLabel($params)
 	{
 		$object =& $this->getObject($params);
 
 		$field = $this->SelectParam($params, 'name,field');
 
 		$sql = 'SELECT FieldLabel
 				FROM '.$this->Application->getUnitOption('cf', 'TableName').'
 				WHERE FieldName = '.$this->Conn->qstr($field);
 		return $this->Application->Phrase($this->Conn->GetOne($sql));
 	}
 
 	/**
 	 * transposes 1-dimensional array elements for vertical alignment according to given columns and per_page parameters
 	 *
 	 * @param array $arr
 	 * @param int $columns
 	 * @param int $per_page
 	 * @return array
 	 */
 	function LinearToVertical(&$arr, $columns, $per_page)
 	{
 		$rows = $columns;
 		// in case if after applying per_page limit record count less then
 		// can fill requrested column count, then fill as much as we can
 		$cols = min(ceil($per_page / $columns), ceil(count($arr) / $columns));
 		$imatrix = array();
 		for ($row = 0; $row < $rows; $row++) {
 			for ($col = 0; $col < $cols; $col++) {
 				$source_index = $row * $cols + $col;
 				if (!isset($arr[$source_index])) {
 					// in case if source array element count is less then element count in one row
 					continue;
 				}
 				$imatrix[$col * $rows + $row] = $arr[$source_index];
 			}
 		}
 
 		ksort($imatrix);
 		return array_values($imatrix);
 	}
 
 	/**
 	 * If data was modfied & is in TempTables mode, then parse block with name passed;
 	 * remove modification mark if not in TempTables mode
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 * @author Alexey
 	 */
 	function SaveWarning($params)
 	{
 		$main_prefix = array_key_exists('main_prefix', $params) ? $params['main_prefix'] : false;
 		if ($main_prefix) {
 			$top_prefix = $main_prefix;
 		}
 		else {
 			$top_prefix = $this->Application->GetTopmostPrefix($this->Prefix);
 		}
 
 		$temp_tables = substr($this->Application->GetVar($top_prefix.'_mode'), 0, 1) == 't';
 		$modified = $this->Application->RecallVar($top_prefix.'_modified');
 
 		if ($temp_tables && $modified) {
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $this->SelectParam($params, 'render_as,name');
 			$block_params['edit_mode'] = $temp_tables ? 1 : 0;
 			return $this->Application->ParseBlock($block_params);
 		}
 		$this->Application->RemoveVar($top_prefix.'_modified');
 		return '';
 	}
 
 	/**
 	 * Returns list record count queries (on all pages)
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function TotalRecords($params)
 	{
 		$list =& $this->GetList($params);
 		if (!$list->Counted) $list->CountRecs();
 		return $list->RecordsCount;
 	}
 
 	/**
 	 * Range filter field name
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SearchInputName($params)
 	{
 		$field = $this->SelectParam($params, 'field,name');
 
 		$ret = 'custom_filters['.$this->getPrefixSpecial().']['.$params['grid'].']['.$field.']['.$params['filter_type'].']';
 
 		if (isset($params['type'])) {
 			$ret .= '['.$params['type'].']';
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Return range filter field value
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SearchField($params) // RangeValue
 	{
 		$field = $this->SelectParam($params, 'field,name');
 
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		$custom_filter = $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_custom_filter.'.$view_name/*, ALLOW_DEFAULT_SETTINGS*/);
 		$custom_filter = $custom_filter ? unserialize($custom_filter) : Array();
 
 		if (isset($custom_filter[ $params['grid'] ][$field])) {
 			$ret = $custom_filter[ $params['grid'] ][$field][ $params['filter_type'] ]['submit_value'];
 			if (isset($params['type'])) {
 				$ret = $ret[ $params['type'] ];
 			}
 
 			if (!array_key_exists('no_special', $params) || !$params['no_special']) {
 				$ret = htmlspecialchars($ret);
 			}
 
 			return $ret;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Tells, that at least one of search filters is used by now
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function SearchActive($params)
 	{
 		if ($this->Application->RecallVar($this->getPrefixSpecial() . '_search_keyword')) {
 			// simple search filter is used
 			return true;
 		}
 
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		$custom_filter = $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_custom_filter.'.$view_name/*, ALLOW_DEFAULT_SETTINGS*/);
 		$custom_filter = $custom_filter ? unserialize($custom_filter) : Array();
 
 		return array_key_exists($params['grid'], $custom_filter);
 	}
 
 	function SearchFormat($params)
 	{
 		$field = $params['field'];
 		$object =& $this->GetList($params);
 
 		$options = $object->GetFieldOptions($field);
 		$format = $options[ $this->SelectParam($params, 'input_format') ? 'input_format' : 'format' ];
 		$formatter_class = array_key_exists('formatter', $options) ? $options['formatter'] : false;
 
 		if ($formatter_class) {
 			$formatter =& $this->Application->recallObject($formatter_class);
 			$human_format = array_key_exists('human', $params) ? $params['human'] : false;
 			$edit_size = array_key_exists('edit_size', $params) ? $params['edit_size'] : false;
 			$sample = array_key_exists('sample', $params) ? $params['sample'] : false;
 
 			if ($sample) {
 				return $formatter->GetSample($field, $options, $object);
 			}
 			elseif ($human_format || $edit_size) {
 				$format = $formatter->HumanFormat($format);
 				return $edit_size ? strlen($format) : $format;
 			}
 		}
 
 		return $format;
 	}
 
 	/**
 	 * Returns error of range field
 	 *
 	 * @param unknown_type $params
 	 * @return unknown
 	 */
 	function SearchError($params)
 	{
 		$field = $this->SelectParam($params, 'field,name');
 
 		$error_var_name = $this->getPrefixSpecial().'_'.$field.'_error';
 		$pseudo = $this->Application->RecallVar($error_var_name);
 		if ($pseudo) {
 			$this->Application->RemoveVar($error_var_name);
 		}
 
 		$object =& $this->Application->recallObject($this->Prefix.'.'.$this->Special.'-item', null, Array('skip_autoload' => true));
 		/* @var $object kDBItem */
 
 		$object->SetError($field, $pseudo);
 		return $object->GetErrorMsg($field, false);
 	}
 
 	/**
 	 * Returns object used in tag processor
 	 *
 	 * @access public
 	 * @return kDBBase
 	 */
 	function &getObject($params = Array())
 	{
 		$object =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix, $params);
 		if (isset($params['requery']) && $params['requery']) {
 			$this->Application->HandleEvent($q_event, $this->getPrefixSpecial().':LoadItem', $params);
 		}
 		return $object;
 	}
 
 	/**
 	 * Checks if object propery value matches value passed
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function PropertyEquals($params)
 	{
 		$object =& $this->getObject($params);
 		$property_name = $this->SelectParam($params, 'name,var,property');
 		return $object->$property_name == $params['value'];
 	}
 
 	/**
 	 * Group list records by header, saves internal order in group
 	 *
 	 * @param Array $records
 	 * @param string $heading_field
 	 */
 	function groupRecords(&$records, $heading_field)
 	{
 		$sorted = Array();
 		$i = 0; $record_count = count($records);
 		while ($i < $record_count) {
 			$sorted[ $records[$i][$heading_field] ][] = $records[$i];
 			$i++;
 		}
 
 		$records = Array();
 		foreach ($sorted as $heading => $heading_records) {
 			$records = array_merge_recursive($records, $heading_records);
 		}
 	}
 
 	function DisplayOriginal($params)
 	{
 		return false;
 	}
 
 	/*function MultipleEditing($params)
 	{
 		$wid = $this->Application->GetTopmostWid($this->Prefix);
 		$session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_');
 		$selected_ids = explode(',', $this->Application->RecallVar($session_name));
 
 		$ret = '';
 		if ($selected_ids) {
 			$selected_ids = explode(',', $selected_ids);
 			$object =& $this->getObject( array_merge_recursive2($params, Array('skip_autoload' => true)) );
 			$params['name'] = $params['render_as'];
 			foreach ($selected_ids as $id) {
 				$object->Load($id);
 				$ret .= $this->Application->ParseBlock($params);
 			}
 		}
 
 		return $ret;
 	}*/
 
 	/**
 	 * Returns import/export process percent
 	 *
 	 * @param Array $params
 	 * @return int
 	 * @deprecated Please convert to event-model, not tag based
 	 */
 	function ExportStatus($params)
 	{
 		$export_object =& $this->Application->recallObject('CatItemExportHelper');
 
 		$event = new kEvent($this->getPrefixSpecial().':OnDummy');
 
 		$action_method = 'perform'.ucfirst($this->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 ($this->Special == 'import') {
 				// this is used?
 				$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 				$this->Application->Redirect('categories/cache_updater', Array('m_opener' => 'r', 'pass' => 'm', 'continue' => 1, 'no_amp' => 1));
 			}
 			elseif ($this->Special == 'export') {
 				// used for orders export in In-Commerce
 				$finish_t = $this->Application->RecallVar('export_finish_t');
 				$this->Application->Redirect($finish_t, Array('pass' => 'all'));
 				$this->Application->RemoveVar('export_finish_t');
 			}
 		}
 
 		$export_options = $export_object->loadOptions($event);
 		return $export_options['start_from']  * 100 / $export_options['total_records'];
 	}
 
 	/**
 	 * Returns path where exported category items should be saved
 	 *
 	 * @param Array $params
 	 */
 	function ExportPath($params)
 	{
-		$ret = EXPORT_PATH.'/';
+		$export_options = unserialize($this->Application->RecallVar($this->getPrefixSpecial().'_options'));
+		$extension = $export_options['ExportFormat'] == 1 ? 'csv' : 'xml';
+		$filename = preg_replace('/(.*)\.' . $extension . '$/', '\1', $export_options['ExportFilename']) . '.' . $extension;
 
+		$path = EXPORT_PATH . '/';
 		if (array_key_exists('as_url', $params) && $params['as_url']) {
-			$ret = str_replace( FULL_PATH.'/', $this->Application->BaseURL(), $ret);
+			$path = str_replace( FULL_PATH . '/', $this->Application->BaseURL(), $path);
 		}
-
-		$export_options = unserialize($this->Application->RecallVar($this->getPrefixSpecial().'_options'));
-		$ret .= $export_options['ExportFilename'].'.'.($export_options['ExportFormat'] == 1 ? 'csv' : 'xml');
-
-		return $ret;
+			
+		return $path . $filename;
 	}
 
 	function FieldTotal($params)
 	{
 		$list =& $this->GetList($params);
 
 		return $list->GetFormattedTotal($this->SelectParam($params, 'field,name'), $params['function']);
 	}
 
 	/**
 	 * Returns FCKEditor locale, that matches default site language
 	 *
 	 * @return string
 	 */
 	function _getFCKLanguage()
 	{
 		static $language_code = null;
 
 		if (!isset($language_code)) {
 			$language_code = 'en'; // defaut value
 
 			if ($this->Application->isAdmin) {
 				$language_id = $this->Application->Phrases->LanguageId;
 			}
 			else {
 				$language_id = $this->Application->GetDefaultLanguageId(); // $this->Application->GetVar('m_lang');
 			}
 
 			$sql = 'SELECT Locale
 					FROM '. $this->Application->getUnitOption('lang', 'TableName') . '
 					WHERE LanguageId = ' . $language_id;
 			$locale = strtolower( $this->Conn->GetOne($sql) );
 
 			if (file_exists(FULL_PATH . EDITOR_PATH . 'editor/lang/' . $locale . '.js')) {
 				// found language file, that exactly matches locale name (e.g. "en")
 				$language_code = $locale;
 			}
 			else {
 				$locale = explode('-', $locale);
 				if (file_exists(FULL_PATH . EDITOR_PATH . 'editor/lang/' . $locale[0] . '.js')) {
 					// language file matches first part of locale (e.g. "ru-RU")
 					$language_code = $locale[0];
 				}
 			}
 		}
 
 		return $language_code;
 	}
 
 
 	function FCKEditor($params)
 	{
 		$params['no_special'] = 1;
 		$params['format'] = array_key_exists('format', $params) ? $params['format'] . ';fck_ready' : 'fck_ready';
 		$value = $this->Field($params);
 		$name = array_key_exists('name', $params) ? $params['name'] : $this->InputName($params);
 
 		$theme_path = substr($this->Application->GetFrontThemePath(), 1) . '/inc/';
 		if (!file_exists(FULL_PATH . '/' . $theme_path . 'style.css')) {
 			$theme_path = EDITOR_PATH;
 		}
 
 		$styles_xml = $this->Application->BaseURL() . $theme_path . 'styles.xml';
 		$styles_css = $this->Application->BaseURL() . $theme_path . 'style.css';
 
 		$bgcolor = array_key_exists('bgcolor', $params) ? $params['bgcolor'] : $this->Application->GetVar('bgcolor');
 		if (!$bgcolor) {
 			$bgcolor = '#ffffff';
 		}
 
 		$preview_url = '';
 		$page_id = $this->Application->GetVar('c_id');
 		$content_id = $this->Application->GetVar('content_id');
 		if ($page_id && $content_id) {
 			// editing content block from Front-End, not category in admin
 			$sql = 'SELECT NamedParentPath
 					FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
 					WHERE ' . $this->Application->getUnitOption('c', 'IDField') . ' = ' . (int)$page_id;
 			$template = strtolower( $this->Conn->GetOne($sql) );
 
 			$url_params = Array ('m_cat_id' => $page_id, 'no_amp' => 1, 'editing_mode' => EDITING_MODE_CONTENT, 'pass' => 'm');
 			$preview_url = $this->Application->HREF($template, '_FRONT_END_', $url_params, 'index.php');
 			$preview_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $preview_url);
 		}
 
 		include_once(FULL_PATH . EDITOR_PATH . 'fckeditor.php');
 
 		$oFCKeditor = new FCKeditor($name);
 		$oFCKeditor->FullUrl    = $this->Application->BaseURL();
 		$oFCKeditor->BaseUrl    = BASE_PATH . '/';
 		$oFCKeditor->BasePath	= BASE_PATH . EDITOR_PATH;
 		$oFCKeditor->Width		= $params['width'] ;
 		$oFCKeditor->Height		= $params['height'] ;
 		$oFCKeditor->ToolbarSet	= $page_id && $content_id ? 'Advanced' : 'Default';
 		$oFCKeditor->Value		= $value;
 		$oFCKeditor->PreviewUrl	= $preview_url;
 		$oFCKeditor->DefaultLanguage = $this->_getFCKLanguage();
 		$oFCKeditor->LateLoad = array_key_exists('late_load', $params) && $params['late_load'];
 		$oFCKeditor->Config = Array (
 			//'UserFilesPath' => $pathtoroot.'kernel/user_files',
 			'ProjectPath' => BASE_PATH . '/',
 			'CustomConfigurationsPath' => $this->Application->BaseURL() . 'core/admin_templates/js/inp_fckconfig.js',
 			'StylesXmlPath' => $styles_xml,
 			'EditorAreaCSS' => $styles_css,
 			'DefaultStyleLabel' => $this->Application->Phrase('la_editor_default_style'),
 //				'Debug' => 1,
 			'Admin' => 1,
 			'K4' => 1,
 			'newBgColor' => $bgcolor,
 			'PreviewUrl' => $preview_url,
 			'BaseUrl' => BASE_PATH . '/',
 			'DefaultLanguage' => $this->_getFCKLanguage(),
 			'EditorAreaStyles' => 'body { background-color: '.$bgcolor.' }',
 		);
 
 		return $oFCKeditor->CreateHtml();
 	}
 
 	function IsNewItem($params)
 	{
 		$object =& $this->getObject($params);
 		return $object->IsNewItem();
 	}
 
 	/**
 	 * Creates link to an item including only it's id
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ItemLink($params)
 	{
 		$object =& $this->getObject($params);
 
 		if (!isset($params['pass'])) {
 			$params['pass'] = 'm';
 		}
 
 		$params[$object->getPrefixSpecial().'_id'] = $object->GetID();
 
 		$m =& $this->Application->recallObject('m_TagProcessor');
 		return $m->t($params);
 	}
 
 	/**
 	 * Calls OnNew event from template, when no other event submitted
 	 *
 	 * @param Array $params
 	 */
 	function PresetFormFields($params)
 	{
 		$prefix = $this->getPrefixSpecial();
 		if (!$this->Application->GetVar($prefix.'_event')) {
 			$this->Application->HandleEvent(new kEvent($prefix.':OnNew'));
 		}
 	}
 
 	function PrintSerializedFields($params)
 	{
 		$object =& $this->getObject();
 		$field = $this->SelectParam($params, 'field');
 		$data = unserialize($object->GetDBField($field));
 		$o = '';
 		$std_params['name'] = $params['render_as'];
 		$std_params['field'] = $params['field'];
 		$std_params['pass_params'] = true;
 		foreach ($data as $key => $row) {
 			$block_params = array_merge($std_params, $row, array('key'=>$key));
 			$o .= $this->Application->ParseBlock($block_params);
 		}
 		return $o;
 	}
 	/**
 	 * Checks if current prefix is main item
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsTopmostPrefix($params)
 	{
 		return $this->Prefix == $this->Application->GetTopmostPrefix($this->Prefix);
 	}
 
 	function PermSection($params)
 	{
 		$section = $this->SelectParam($params, 'section,name');
 		$perm_sections = $this->Application->getUnitOption($this->Prefix, 'PermSection');
 		return isset($perm_sections[$section]) ? $perm_sections[$section] : '';
 	}
 
 	function PerPageSelected($params)
 	{
 		$list =& $this->GetList($params);
 		return $list->PerPage == $params['per_page'] ? $params['selected'] : '';
 	}
 
 	/**
 	 * Returns prefix + generated sepcial + any word
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function VarName($params)
 	{
 		$list =& $this->GetList($params);
 
 		return $list->getPrefixSpecial().'_'.$params['type'];
 	}
 
 	/**
 	 * Returns edit tabs by specified preset name or false in case of error
 	 *
 	 * @param string $preset_name
 	 * @return mixed
 	 */
 	function getEditTabs($preset_name)
 	{
 		$presets = $this->Application->getUnitOption($this->Prefix, 'EditTabPresets');
 
 		if (!$presets || !isset($presets[$preset_name]) || count($presets[$preset_name]) == 0) {
 			return false;
 		}
 
 		return count($presets[$preset_name]) > 1 ? $presets[$preset_name] : false;
 	}
 
 	/**
 	 * Detects if specified preset has tabs in it
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function HasEditTabs($params)
 	{
 		return $this->getEditTabs($params['preset_name']) ? true : false;
 	}
 
 	/**
 	 * Sorts edit tabs based on their priority
 	 *
 	 * @param Array $tab_a
 	 * @param Array $tab_b
 	 * @return int
 	 */
 	function sortEditTabs($tab_a, $tab_b)
 	{
 		if ($tab_a['priority'] == $tab_b['priority']) {
 			return 0;
 		}
 
 		return $tab_a['priority'] < $tab_b['priority'] ? -1 : 1;
 	}
 
 	/**
 	 * Prints edit tabs based on preset name specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PrintEditTabs($params)
 	{
 		$edit_tabs = $this->getEditTabs($params['preset_name']);
 		if (!$edit_tabs) {
 			return ;
 		}
 		usort($edit_tabs, Array (&$this, 'sortEditTabs'));
 
 		$ret = '';
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		foreach ($edit_tabs as $tab_info) {
 			$block_params['title'] = $tab_info['title'];
 			$block_params['template'] = $tab_info['t'];
 			$ret .= $this->Application->ParseBlock($block_params);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Performs image resize to required dimensions and returns resulting url (cached resized image)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ImageSrc($params)
 	{
 		$max_width = isset($params['MaxWidth']) ? $params['MaxWidth'] : false;
 		$max_height = isset($params['MaxHeight']) ? $params['MaxHeight'] : false;
 
 		$logo_filename = isset($params['LogoFilename']) ? $params['LogoFilename'] : false;
 		$logo_h_margin = isset($params['LogoHMargin']) ? $params['LogoHMargin'] : false;
 		$logo_v_margin = isset($params['LogoVMargin']) ? $params['LogoVMargin'] : false;
 		$object =& $this->getObject($params);
 
 		$field = $this->SelectParam($params, 'name,field');
 		return $object->GetField($field, 'resize:'.$max_width.'x'.$max_height.';wm:'.$logo_filename.'|'.$logo_h_margin.'|'.$logo_v_margin);
 	}
 
 	/**
 	 * Allows to retrieve given setting from unit config
 	 *
 	 * @param Array $params
 	 * @return mixed
 	 */
 	function UnitOption($params)
 	{
 		return $this->Application->getUnitOption($this->Prefix, $params['name']);
 	}
 
 	/**
 	 * Returns list of allowed toolbar buttons or false, when all is allowed
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function VisibleToolbarButtons($params)
 	{
 		$preset_name = replaceModuleSection($params['title_preset']);
 		$title_presets = $this->Application->getUnitOption($this->Prefix, 'TitlePresets');
 
 		if (!array_key_exists($preset_name, $title_presets)) {
 			trigger_error('Title preset not specified or missing (in tag "<strong>' . $this->getPrefixSpecial() . ':' . __METHOD__ . '</strong>")', E_USER_WARNING);
 			return false;
 		}
 
 		$preset_info = $title_presets[$preset_name];
 		if (!array_key_exists('toolbar_buttons', $preset_info) || !is_array($preset_info['toolbar_buttons'])) {
 			return false;
 		}
 
 		// always add search buttons
 		array_push($preset_info['toolbar_buttons'], 'search', 'search_reset_alt');
 		$toolbar_buttons = array_map('addslashes', $preset_info['toolbar_buttons']);
 
 		return $toolbar_buttons ? "'" . implode("', '", $toolbar_buttons) . "'" : 'false';
 	}
 
 	/**
 	 * Checks, that "To" part of at least one of range filters is used
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function RangeFiltersUsed($params)
 	{
 		$search_helper =& $this->Application->recallObject('SearchHelper');
 		/* @var $search_helper kSearchHelper */
 
 		return $search_helper->rangeFiltersUsed($this->getPrefixSpecial(), $params['grid']);
 	}
 
 	/**
 	 * This is abstract tag, used to modify unit config data based on template, where it's used.
 	 * Tag is called from "combined_header" block in admin only.
 	 *
 	 * @param Array $params
 	 */
 	function ModifyUnitConfig($params)
 	{
 
 	}
 
 	/**
 	 * Checks, that field is visible on edit form
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function FieldVisible($params)
 	{
 		$check_field = $params['field'];
 		$fields = $this->Application->getUnitOption($this->Prefix, 'Fields');
 
 		if (!array_key_exists($check_field, $fields)) {
 			// field not found in real fields array -> it's 100% virtual then
 			$fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields');
 		}
 
 		if (!array_key_exists($check_field, $fields)) {
 			$params['field'] = 'Password';
 			return $check_field == 'VerifyPassword' ? $this->FieldVisible($params) : true;
 		}
 
 		$show_mode = array_key_exists('show_mode', $fields[$check_field]) ? $fields[$check_field]['show_mode'] : true;
 
 		if ($show_mode === smDEBUG) {
 			return defined('DEBUG_MODE') && DEBUG_MODE;
 		}
 
 		return $show_mode;
 	}
 
 	/**
 	 * Checks, that there area visible fields in given section on edit form
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function FieldsVisible($params)
 	{
 		if (!$params['fields']) {
 			return true;
 		}
 
 		$check_fields = explode(',', $params['fields']);
 		$fields = $this->Application->getUnitOption($this->Prefix, 'Fields');
 		$virtual_fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields');
 
 		foreach ($check_fields as $check_field) {
 			// when at least one field in subsection is visible, then subsection is visible too
 
 			if (array_key_exists($check_field, $fields)) {
 				$show_mode = array_key_exists('show_mode', $fields[$check_field]) ? $fields[$check_field]['show_mode'] : true;
 			}
 			else {
 				$show_mode = array_key_exists('show_mode', $virtual_fields[$check_field]) ? $virtual_fields[$check_field]['show_mode'] : true;
 			}
 
 			if (($show_mode === true) || (($show_mode === smDEBUG) && (defined('DEBUG_MODE') && DEBUG_MODE))) {
 				// field is visible
 				return true;
 			}
 		}
 
 		return false;
 	}
 }
\ No newline at end of file
Index: branches/5.0.x/core/units/helpers/csv_helper.php
===================================================================
--- branches/5.0.x/core/units/helpers/csv_helper.php	(revision 13232)
+++ branches/5.0.x/core/units/helpers/csv_helper.php	(revision 13233)
@@ -1,374 +1,375 @@
 <?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!');
 
 	safeDefine('EXPORT_STEP', 100); // export by 100 items
 	safeDefine('IMPORT_STEP', 10);
 
 	class kCSVHelper extends kHelper {
 
 		var $PrefixSpecial;
 		var $grid;
 
 		var $delimiter_mapping = Array(0 => "\t", 1 => ',', 2 => ';', 3 => ' ', 4 => ':');
 		var $enclosure_mapping = Array(0 => '"', 1 => "'");
 		var $separator_mapping = Array(0 => "\n", 1 => "\r\n");
 
 		function ExportStep()
 		{
 			$export_data = $this->Application->RecallVar('export_data');
 			$export_rand = $this->Application->RecallVar('export_rand');
 			$get_rand = $this->Application->GetVar('export_rand');
 
 			if($export_data && $export_rand == $get_rand) {
 				$export_data = unserialize($export_data);
 				$first_step = false;
 			}
 			else {
 				// first step
 				$export_data = Array();
 				$export_data['prefix'] = $this->PrefixSpecial;
 				$export_data['grid'] = $this->grid;
 				$export_data['file_name'] = EXPORT_PATH.'/'.$this->ValidateFileName(EXPORT_PATH, 'export_'.$export_data['prefix'].'.csv');
 				$export_data['step'] = EXPORT_STEP;
 				$export_data['delimiter'] = $this->delimiter_mapping[(int)$this->Application->ConfigValue('CSVExportDelimiter')];
 				$export_data['enclosure'] = $this->enclosure_mapping[(int)$this->Application->ConfigValue('CSVExportEnclosure')];
 				$export_data['record_separator'] = $this->separator_mapping[(int)$this->Application->ConfigValue('CSVExportSeparator')];
 				$export_data['page'] = 1;
 
 				$lang_object =& $this->Application->recallObject('lang.current');
 				/* @var $lang_object LanguagesItem */
 				$export_data['source_encoding'] = strtoupper( $lang_object->GetDBField('Charset') );
 				$export_data['encoding'] = $this->Application->ConfigValue('CSVExportEncoding') ? false : 'UTF-16LE';
 
 				$this->Application->StoreVar('export_rand', $get_rand);
 
 				$first_step = true;
 			}
 
 			$file = fopen($export_data['file_name'], $first_step ? 'w' : 'a');
 
 			$prefix_elems = split('\.|_', $export_data['prefix']);
 			$grids = $this->Application->getUnitOption($prefix_elems[0], 'Grids');
 			$grid_config = $grids[ $export_data['grid'] ]['Fields'];
 
 			$list_params = Array('per_page' => $export_data['step'], 'grid' => $export_data['grid']);
 			$list =& $this->Application->recallObject(rtrim(implode('.', $prefix_elems), '.'), $prefix_elems[0].'_List', $list_params);
 			/* @var $list kDBList */
 			$list->SetPage($export_data['page']);
 
 			$list->Query();
 			$list->GoFirst();
 
 			$picker_helper =& $this->Application->RecallObject('ColumnPickerHelper');
 			/* @var $picker_helper kColumnPickerHelper */
 			$picker_helper->ApplyPicker(rtrim(implode('.', $prefix_elems), '.'), $grid_config, $export_data['grid']);
 
 			if($first_step) {
 				// if UTF-16, write Unicode marker
 				if($export_data['encoding'] == 'UTF-16LE') {
 					fwrite($file, chr(0xFF).chr(0xFE));
 				}
 
 				// inserting header line
 				$headers = Array();
 				foreach($grid_config as $field_name => $field_data) {
 					$header = $this->Application->Phrase( $field_data['title'] );
 					array_push($headers, $header);
 				}
 				$csv_line = getcsvline($headers, $export_data['delimiter'], $export_data['enclosure'], $export_data['record_separator']);
 				if($export_data['encoding']) {
 					$csv_line = mb_convert_encoding($csv_line, $export_data['encoding'], $export_data['source_encoding']);
 				}
 				fwrite($file, $csv_line);
 			}
 
 			while(!$list->EOL())
 			{
 				$data = Array();
 				foreach($grid_config as $field_name => $field_data) {
 					if(isset($field_data['export_field'])) {
 						$field_name = $field_data['export_field'];
 					}
 					$value = $list->GetField($field_name, isset($field_data['format']) ? $field_data['format'] : null);
 					$value = str_replace("\r\n", "\n", $value);
 					$value = str_replace("\r", "\n", $value);
 					array_push($data, $value);
 				}
 				if($export_data['encoding'] == 'UTF-16LE') {
 					fwrite($file, chr(0xFF).chr(0xFE));
 				}
 				$csv_line = getcsvline($data, $export_data['delimiter'], $export_data['enclosure'], $export_data['record_separator']);
 				if($export_data['encoding']) {
 					$csv_line = mb_convert_encoding($csv_line, $export_data['encoding'], $export_data['source_encoding']);
 				}
 				fwrite($file, $csv_line);
 
 				$list->GoNext();
 			}
 
 			$records_processed = $export_data['page'] * $export_data['step'];
 			$percent_complete = min($records_processed / $list->RecordsCount * 100, 100);
 
 			fclose($file);
 
 			if ($records_processed >= $list->RecordsCount) {
 				$this->Application->StoreVar('export_data', serialize($export_data));
 				$this->Application->Redirect($this->Application->GetVar('finish_template'));
 			}
 
 			echo $percent_complete;
 
 			$export_data['page']++;
 			$this->Application->StoreVar('export_data', serialize($export_data));
 		}
 
 		function ValidateFileName($path, $name)
 		{
 			$parts = pathinfo($name);
 			$ext = '.'.$parts['extension'];
 			$filename = mb_substr($parts['basename'], 0, -mb_strlen($ext));
 			$new_name = $filename.$ext;
 			while ( file_exists($path.'/'.$new_name) )
 			{
 		   		if ( preg_match('/('.preg_quote($filename, '/').'_)([0-9]*)('.preg_quote($ext, '/').')/', $new_name, $regs) ) {
 						$new_name = $regs[1].($regs[2]+1).$regs[3];
 					}
 					else {
 						$new_name = $filename.'_1'.$ext;
 					}
 			}
 			return $new_name;
 		}
 
 		function ExportData($name)
 		{
 			$export_data = unserialize($this->Application->RecallVar('export_data'));
 			return isset($export_data[$name]) ? $export_data[$name] : false;
 		}
 
 		function GetCSV()
 		{
 			safeDefine('DBG_SKIP_REPORTING', 1);
 
-			$export_data = unserialize($this->Application->RecallVar('export_data'));
+			$export_data = unserialize($this->Application->RecallVar('export_data'));			
+			$filename = preg_replace('/(.*)\.csv$/', '\1', basename($export_data['file_name'])) . '.csv';
+			
 			header('Content-type: text/csv');
-			$filename = rtrim(basename($export_data['file_name']), '.csv').'.csv';
-			header('Content-Disposition: attachment; filename="'.$filename.'"');
+			header('Content-Disposition: attachment; filename="' . $filename . '"');
 			readfile($export_data['file_name']);
 			die();
 		}
 
 		function ImportStart($filename)
 		{
 			if(!file_exists($filename) || !is_file($filename)) return 'cant_open_file';
 
 			$import_data = Array();
 
 			$lang_object =& $this->Application->recallObject('lang.current');
 			/* @var $lang_object LanguagesItem */
 			$import_data['source_encoding'] = strtoupper( $lang_object->GetDBField('Charset') );
 			$import_data['encoding'] = $this->Application->ConfigValue('CSVExportEncoding') ? false : 'UTF-16LE';
 			$import_data['errors'] = '';
 
 			// convert file in case of UTF-16LE
 			if($import_data['source_encoding'] != $import_data['encoding']) {
 				copy($filename, $filename.'.orginal');
 				$file_content = file_get_contents($filename);
 				$file = fopen($filename, 'w');
 				fwrite($file, mb_convert_encoding(str_replace(chr(0xFF).chr(0xFE), '', $file_content), $import_data['source_encoding'], $import_data['encoding']));
 				fclose($file);
 
 			}
 
 			$import_data['prefix'] = $this->PrefixSpecial;
 			$import_data['grid'] = $this->grid;
 			$import_data['file'] = $filename;
 			$import_data['total_lines'] = count(file($filename));
 			if(!$import_data['total_lines']) $import_data['total_lines'] = 1;
 			unset($file_content);
 			$import_data['lines_processed'] = 0;
 			$import_data['delimiter'] = $this->delimiter_mapping[(int)$this->Application->ConfigValue('CSVExportDelimiter')];
 			$import_data['enclosure'] = $this->enclosure_mapping[(int)$this->Application->ConfigValue('CSVExportEnclosure')];
 			$import_data['step'] = IMPORT_STEP;
 			$import_data['not_imported_lines'] = '';
 			$import_data['added'] = 0;
 			$import_data['updated'] = 0;
 
 			$file = fopen($filename, 'r');
 			// getting first line for headers
 			$headers = fgetcsv($file, 8192, $import_data['delimiter'], $import_data['enclosure']);
 			fclose($file);
 
 			$prefix_elems = split('\.|_', $import_data['prefix']);
 			$grids = $this->Application->getUnitOption($prefix_elems[0], 'Grids');
 			$grid_config = $grids[ $import_data['grid'] ]['Fields'];
 
 			$field_list = Array();
 			foreach($grid_config as $field_name => $field_data) {
 				if(isset($field_data['export_field'])) {
 					$field_name = $field_data['export_field'];
 				}
 				$field_label = $this->Application->Phrase( $field_data['title'] );
 				$field_pos = array_search($field_label, $headers);
 				if($field_pos !== false) {
 					$field_list[$field_pos] = $field_name;
 				}
 			}
 
 			if(!count($field_list)) return 'no_matching_columns';
 			$import_data['field_list'] = $field_list;
 
 			// getting key list
 			$field_positions = Array();
 			$config_key_list = $this->Application->getUnitOption($prefix_elems[0], 'ImportKeys');
 			if(!$config_key_list) $config_key_list = Array();
 			array_unshift($config_key_list, Array($this->Application->getUnitOption($prefix_elems[0], 'IDField')));
 
 			$key_list = Array();
 			foreach($config_key_list as $arr_key => $import_key) {
 				$key_list[$arr_key] = is_array($import_key) ? $import_key : Array($import_key);
 
 				foreach($key_list[$arr_key] as $key_field) {
 					$field_positions[$key_field] = array_search($key_field, $import_data['field_list']);
 					if($field_positions[$key_field] === false) {
 						// no such key field combination in imported file
 						unset($key_list[$arr_key]);
 						break;
 					}
 				}
 			}
 			$import_data['key_list'] = $key_list;
 			$import_data['field_positions'] = $field_positions;
 
 			$this->Application->StoreVar('import_data', serialize($import_data));
 			return true;
 		}
 
 		function ImportStep()
 		{
 			$import_data = unserialize($this->Application->RecallVar('import_data'));
 			$prefix_elems = split('\.|_', $import_data['prefix']);
 
 			$object =& $this->Application->recallObject($prefix_elems[0].'.-csvimport', $prefix_elems[0], Array('skip_autoload' => true, 'populate_ml_fields' => true));
 			/* @var $object kDBItem */
 
 			$file = fopen($import_data['file'], 'r');
 			$eof = false;
 			// skipping lines that has been already imported
 			for($i = 0; $i < $import_data['lines_processed'] + 1; $i++) {
 				if(feof($file)) break;
 				fgets($file, 8192);
 			}
 
 			$import_event = new kEvent($prefix_elems[0].'.-csvimport:OnBeforeCSVLineImport');
 
 			for($i = 0; $i < $import_data['step']; $i++) {
 				if(feof($file)) break;
 				$data = fgetcsv($file, 8192, $import_data['delimiter'], $import_data['enclosure']);
 				if(!$data) continue;
 
 				$object->Clear();
 				$action = 'Create';
 
 				// 1. trying to load object by keys
 				foreach($import_data['key_list'] as $key) {
 					$fail = false;
 					$key_array = Array();
 					foreach($key as $key_field) {
 						if(!isset($data[ $import_data['field_positions'][$key_field] ])) {
 							$fail = true;
 							break;
 						}
 						$key_array[$key_field] = $data[ $import_data['field_positions'][$key_field] ];
 					}
 					if($fail) continue;
 					if($object->Load($key_array)) {
 						$action = 'Update';
 						break;
 					}
 				}
 
 				// 2. set object fields
 				foreach($import_data['field_list'] as $position => $field_name) {
 					if(isset($data[$position])) {
 						$object->SetField($field_name, $data[$position]);
 					}
 				}
 
 				// 3. validate item and run event
 				$status = $object->Validate();
 				$import_event->status = $status ? erSUCCESS : erFAIL;
 				$this->Application->HandleEvent($import_event);
 
 				if($import_event->status == erSUCCESS && $object->$action()) {
 					$import_data[ ($action == 'Create') ? 'added' : 'updated' ]++;
 				}
 				else {
 					$msg = '';
 					foreach ($object->FieldErrors as $field => $info) {
 						if (!$info['pseudo']) continue;
 						$msg .= "$field: {$info['pseudo']} ";
 					}
 					$import_data['errors'] .= ($i + $import_data['lines_processed'] + 1).": $msg\n";
 					$import_data['not_imported_lines'] .= ','.($i + $import_data['lines_processed'] + 1);
 				}
 			}
 
 			$import_data['lines_processed'] += $import_data['step'];
 
 			$import_data['not_imported_lines'] = ltrim($import_data['not_imported_lines'], ',');
 			$this->Application->StoreVar('import_data', serialize($import_data));
 
 			$feof = feof($file);
 			fclose($file);
 
 			if($feof) {
 				$this->Application->Redirect($this->Application->GetVar('finish_template'));
 			}
 			else {
 				$percent_complete = floor($import_data['lines_processed'] / $import_data['total_lines'] * 100);
 				if($percent_complete > 99) $percent_complete = 99;
 				echo $percent_complete;
 			}
 		}
 
 		function ImportData($name)
 		{
 			$import_data = unserialize($this->Application->RecallVar('import_data'));
 			return isset($import_data[$name]) ? $import_data[$name] : false;
 		}
 
 		function GetNotImportedLines()
 		{
 			$import_data = unserialize($this->Application->RecallVar('import_data'));
 
 			if(!$import_data['not_imported_lines']) return false;
 			$line_numbers = explode(',', $import_data['not_imported_lines']);
 			$line_numbers[] = 0; // include header row in output
 
 			$file = fopen($import_data['file'], 'r');
 			$eof = false;
 			$result = '';
 			for($i = 0; $i <= max($line_numbers); $i++) {
 				if(feof($file)) break;
 				$line = fgets($file, 8192);
 				if(in_array($i, $line_numbers)) {
 					$result .= $i.':'.$line;
 				}
 			}
 			return $result."\n\n".$import_data['errors'];
 		}
 	}
\ No newline at end of file
Index: branches/5.0.x/core/units/helpers/cat_dbitem_export_helper.php
===================================================================
--- branches/5.0.x/core/units/helpers/cat_dbitem_export_helper.php	(revision 13232)
+++ branches/5.0.x/core/units/helpers/cat_dbitem_export_helper.php	(revision 13233)
@@ -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();
+			$extension = $this->getFileExtension();
+			$filename = preg_replace('/(.*)\.' . $extension . '$/', '\1', $this->exportOptions['ExportFilename']) . '.' . $extension;
+			
+			return EXPORT_PATH . DIRECTORY_SEPARATOR . $filename;
 		}
 
 		/**
 		 * Opens file required for export/import operations
 		 *
 		 * @param kEvent $event
 		 */
 		function openFile(&$event)
 		{
 			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)
 		{
 			return $this->Application->getUnitOption($event->Prefix, 'ModuleFolder');
 		}
 
 		/**
 		 * 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';
 				}
 				$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