<?php
/**
* @version	$Id: db_tag_processor.php 15368 2012-05-29 12:51:06Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

defined('FULL_PATH') or die('restricted access!');

class kDBTagProcessor extends kTagProcessor {

	/**
	 * 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_NOTICE);
			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 refresh intervals defined</span> for prefix <strong>'.$this->Prefix.'</strong>, but <strong>DrawAutoRefreshMenu</strong> tag used', E_USER_NOTICE);
			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 $interval minutes
	 * @param string $hour_text Text for hours
	 * @param string $min_text Text for minutes
	 * @return string
	 */
	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))."'";
		}

		$object =& $this->GetList($params);

		$o = '';
		$i = 0;

		foreach ($grid_config as $field => $options) {
			$i++;
			$block_params = $this->prepareTagParams($params);
			$block_params = array_merge($block_params, $options);

			$block_params['block_name'] = array_key_exists($mode . '_block', $block_params) ? $block_params[$mode . '_block'] : $def_block;
			$block_params['name'] = $force_block ? $force_block : $block_params['block_name'];
			$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) {
				// column picker width overrides width from unit config
				$block_params['width'] = $w;
			}

			$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));

			$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', Array ());

		if ( getArrayValue($names_mapping, $this->Prefix, $list_name) === false ) {
			$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'];
		$main_list = array_key_exists('main_list', $params) && $params['main_list'];

		$names_mapping = $this->Application->GetVar('NamesToSpecialMapping', Array ());

		if ( !array_key_exists($this->Prefix, $names_mapping) ) {
			// create prefix-based array to special mapping storage
			$names_mapping[$this->Prefix] = Array ();
		}

		if ( $list_name && !$requery ) {
			// list with "list_name" parameter
			if ( !array_key_exists($list_name, $names_mapping[$this->Prefix]) ) {
				// special missing -> generate one
				$special = $main_list ? $this->Special : $this->BuildListSpecial($params);
			}
			else {
				// get special, formed during list initialization
				$special = $names_mapping[$this->Prefix][$list_name];
			}
		}
		else {
			// list without "list_name" parameter
			$special = $main_list ? $this->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(new kEvent($prefix_special . ':OnListBuild', $params));
			}

			if ( array_key_exists('offset', $params) ) {
				$list->SetOffset($list->GetOffset() + $params['offset']); // apply custom offset
			}

			$list->Query($requery);

			if ( array_key_exists('offset', $params) ) {
				$list->SetOffset($list->GetOffset() - $params['offset']); // remove custom offset
			}
		}

		$this->Init($this->Prefix, $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;
	}

	function CombinedSortingDropDownName($params)
	{
		$list =& $this->GetList($params);

		return $list->getPrefixSpecial() . '_CombinedSorting';
	}

	/**
	 * 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->GetSelectedCount()); // 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() && !$this->Application->isCachingType(CACHING_TYPE_MEMORY);

		$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->GetSelectedCount() - 1);
			$block_params['last_row'] = ($i + (($i + 1) % $columns) >= $list->GetSelectedCount() - 1);
			$block_params['not_last'] = !$block_params['is_last']; // for front-end

			if ( $cache_mod_rw ) {
				$serial_name = $this->Application->incrementCacheSerial($this->Prefix, $list->GetDBField($id_field), false);

				if ( $this->Prefix == 'c' ) {
					// for listing subcategories in category
					$this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('NamedParentPath'));
					$this->Application->setCache('category_tree[%CIDSerial:' . $list->GetDBField($id_field) . '%]', $list->GetDBField('TreeLeft') . ';' . $list->GetDBField('TreeRight'));
				}
				else {
					// for listing items in category
					$this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('Filename'));

					$serial_name = $this->Application->incrementCacheSerial('c', $list->GetDBField('CategoryId'), false);
					$this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('CategoryFilename'));
				}
			}

			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) : (!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);
			array_push($displayed, $list->GetDBField($id_field));

			if ( $direction == 'V' && $list->GetSelectedCount() % $columns > 0 && $column_number == ($columns - 1) && ceil(($i + 1) / $columns) > $list->GetSelectedCount() % ceil($list->GetSelectedCount() / $columns) ) {
				// if vertical output, then draw empty cells vertically, not horizontally
				$o .= $block_empty_cell_params['name'] ? $this->Application->ParseBlock($block_empty_cell_params) : '<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) : (!isset($params['no_table']) ? '</tr>' : '');
			}

			if ( $this->Special && $this->Application->hasObject($this->Prefix) ) {
				// object, produced by "kDBList::linkToParent" method, that otherwise would keep it's id
				$item = $this->Application->recallObject($this->Prefix);
				/* @var $item kDBBase */

				if ( $item instanceof kDBItem ) {
					$this->Application->removeObject($this->Prefix);
				}
			}

			$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) : '<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) : '</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;
	}

	/**
	 * Returns ID of previous record (related to current) in list.
	 * Use only on item detail pages.
	 *
	 * @param Array $params
	 * @return int
	 * @access protected
	 */
	protected function PreviousResource($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$list_helper = $this->Application->recallObject('ListHelper');
		/* @var $list_helper ListHelper */

		$select_clause = $this->Application->getUnitOption($object->Prefix, 'NavigationSelectClause', null);

		return $list_helper->getNavigationResource($object, $params['list'], false, $select_clause);
	}

	/**
	 * Returns ID of next record (related to current) in list.
	 * Use only on item detail pages.
	 *
	 * @param Array $params
	 * @return int
	 * @access protected
	 */
	protected function NextResource($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$list_helper = $this->Application->recallObject('ListHelper');
		/* @var $list_helper ListHelper */

		$select_clause = $this->Application->getUnitOption($object->Prefix, 'NavigationSelectClause', null);

		return $list_helper->getNavigationResource($object, $params['list'], true, $select_clause);
	}

	/**
	 * Allows to modify block params & current list record before PrintList parses record
	 *
	 * @param kDBList $object
	 * @param Array $block_params
	 * @return void
	 * @access protected
	 */
	protected function PrepareListElementParams(&$object, &$block_params)
	{
//		$fields_hash =& $object->getCurrentRecord();

	}

	/**
	 * Renders given block name, when there there is more data in list, then are displayed right now
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected 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->isCounted() ) {
			$has_next_page = $list->GetPage() < $list->GetTotalPages();
		}
		else {
			// selected more, then on the page -> has more
			$has_next_page = $list->GetPerPage() < $list->GetRecordsCount();
		}

		if ( $has_next_page ) {
			$block_params = Array ('name' => $this->SelectParam($params, 'render_as,block'));

			return $this->Application->ParseBlock($block_params);
		}

		return '';
	}

	function PageLink($params)
	{
		static $default_per_page = Array ();

		$object =& $this->GetList($params);
		/* @var $object kDBList */

		// process sorting
		if ($object->isMainList()) {
			if (!array_key_exists('sort_by', $params)) {
				$sort_by = $this->Application->GetVar('sort_by');

				if ($sort_by !== false) {
					$params['sort_by'] = $sort_by;
				}
			}
		}

		$prefix_special = $this->getPrefixSpecial();

		// process page
		$page = array_key_exists('page', $params) ? $params['page'] : $this->Application->GetVar($prefix_special . '_Page');

		if (!$page) {
			// ensure, that page is always present
			if ($object->isMainList()) {
				$params[$prefix_special . '_Page'] = $this->Application->GetVar('page', 1);
			}
			else {
				$params[$prefix_special . '_Page'] = 1;
			}
		}

		if (array_key_exists('page', $params)) {
			$params[$prefix_special . '_Page'] = $params['page'];
			unset($params['page']);
		}

		// process per-page
		$per_page = array_key_exists('per_page', $params) ? $params['per_page'] : $this->Application->GetVar($prefix_special . '_PerPage');

		if (!$per_page) {
			// ensure, that per-page is always present
			list ($prefix, ) = explode('.', $prefix_special);

			if (!array_key_exists($prefix, $default_per_page)) {
				$list_helper = $this->Application->recallObject('ListHelper');
				/* @var $list_helper ListHelper */

				$default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix);
			}

			if ($object->isMainList()) {
				$params[$prefix_special . '_PerPage'] = $this->Application->GetVar('per_page', $default_per_page[$prefix]);
			}
			else {
				$params[$prefix_special . '_PerPage'] = $default_per_page[$prefix];
			}
		}

		if (array_key_exists('per_page', $params)) {
			$params[$prefix_special . '_PerPage'] = $params['per_page'];
			unset($params['per_page']);
		}

		if (!array_key_exists('pass', $params)) {
			$params['pass'] = 'm,' . $prefix_special;
		}

		// process template
		$t = array_key_exists('template', $params) ? $params['template'] : '';
		unset($params['template']);

		if (!$t) {
			$t = $this->Application->GetVar('t');
		}

		return $this->Application->HREF($t, '', $params);
	}

	/**
	 * Deprecated
	 *
	 * @param array $params
	 * @return int
	 * @deprecated Parameter "column_width" of "PrintList" tag does that
	 */
	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, $field_currency = '')
	{
		if ( $currency == 'selected' ) {
			return $this->Application->RecallVar('curr_iso');
		}

		if ( $currency == 'primary' || $currency == '' ) {
			return $this->Application->GetPrimaryCurrency();
		}

		// explicit currency
		return $currency == 'field' && $field_currency ? $field_currency : $currency;
	}

	/**
	 * Convert primary currency to selected (if they are the same, converter will just return)
	 *
	 * @param float $value
	 * @param string $target_iso
	 * @param string $source_iso
	 * @return float
	 */
	function ConvertCurrency($value, $target_iso, $source_iso = 'PRIMARY')
	{
		$converter = $this->Application->recallObject('CurrencyRates');
		/* @var $converter CurrencyRates */

		return $converter->Convert($value, $source_iso, $target_iso);
	}

	function AddCurrencySymbol($value, $iso, $decimal_tag = '')
	{
		$converter = $this->Application->recallObject('CurrencyRates');
		/* @var $converter CurrencyRates */

		return $converter->AddCurrencySymbol($value, $iso, $decimal_tag);
	}

	/**
	 * 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);
		/* @var $object kDBItem */

		if (array_key_exists('db', $params) && $params['db']) {
			$value = $object->GetDBField($field);
		}
		else {
			if (array_key_exists('currency', $params) && $params['currency']) {
				$source_iso = isset($params['currency_field']) ? $object->GetDBField($params['currency_field']) : 'PRIMARY';
				$target_iso = $this->GetISO($params['currency'], $source_iso);
				$original = $object->GetDBField($field);

				$value = $this->ConvertCurrency($original, $target_iso, $source_iso);

				$object->SetDBField($field, $value);
				$object->SetFieldOption($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']) {
				$decimal_tag = isset($params['decimal_tag']) ? $params['decimal_tag'] : '';
				$value = $this->AddCurrencySymbol($value, $target_iso, $decimal_tag);
				$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 = preg_replace('/\s+?(\S+)?$/', '', mb_substr($stripped_value, 0, $first_chars + 1)) . ' ...';
			}
		}

		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 FieldHintLabel($params)
	{
		if ( isset($params['direct_label']) && $params['direct_label'] ) {
			$label = $params['direct_label'];
			$hint = $this->Application->Phrase($label, false);
		}
		else {
			$label = $params['title_label'];
			$hint = $this->Application->Phrase('hint:' . $label, false);
		}

		return $hint != strtoupper('!' . $label . '!') ? $hint : ''; // $hint
	}

	/**
	 * Returns formatted date + time on current language
	 *
	 * @param $params
	 */
	function DateField($params)
	{
		$field = $this->SelectParam($params, 'name,field');

		if ($field) {
			$object = $this->getObject($params);
			/* @var $object kDBItem */

			$timestamp = $object->GetDBField($field);
		}
		else {
			$timestamp = $params['value'];
		}

		$date = $timestamp;

		// prepare phrase replacements
		$replacements = Array (
			'l' => 'la_WeekDay',
			'D' => 'la_WeekDay',
			'M' => 'la_Month',
			'F' => 'la_Month',
		);

		// cases allow to append phrase suffix based on requested case (e.g. Genitive)
		$case_suffixes = array_key_exists('case_suffixes', $params) ? $params['case_suffixes'] : false;

		if ($case_suffixes) {
			// apply case suffixes (for russian language only)
			$case_suffixes = explode(',', $case_suffixes);

			foreach ($case_suffixes as $case_suffux) {
				list ($replacement_name, $case_suffix_value) = explode('=', $case_suffux, 2);
				$replacements[$replacement_name] .= $case_suffix_value;
			}
		}

		$format = array_key_exists('format', $params) ? $params['format'] : false;

		if (preg_match('/_regional_(.*)/', $format, $regs)) {
			$language = $this->Application->recallObject('lang.current');
			/* @var $language kDBItem */

			$format = $language->GetDBField($regs[1]);
		}
		elseif (!$format) {
			$format = null;
		}

		// escape formats, that are resolved to words by adodb_date
		foreach ($replacements as $format_char => $phrase_prefix) {
			if (strpos($format, $format_char) === false) {
				unset($replacements[$format_char]);
				continue;
			}

			$replacements[$format_char] = $this->Application->Phrase($phrase_prefix . adodb_date($format_char, $date));
			$format = str_replace($format_char, '#' . ord($format_char) . '#', $format);
		}

		$date_formatted = adodb_date($format, $date);

		// unescape formats, that are resolved to words by adodb_date
		foreach ($replacements as $format_char => $format_replacement) {
			$date_formatted = str_replace('#' . ord($format_char) . '#', $format_replacement, $date_formatted);
		}

		return $date_formatted;
	}

	function SetField($params)
	{
		// <inp2:SetField field="Value" src=p:cust_{$custom_name}"/>

		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$dst_field = $this->SelectParam($params, 'name,field');
		list($prefix_special, $src_field) = explode(':', $params['src']);

		$src_object = $this->Application->recallObject($prefix_special);
		/* @var $src_object kDBItem */

		$object->SetDBField($dst_field, $src_object->GetDBField($src_field));
	}

	/**
	 * Depricated
	 *
	 * @param Array $params
	 * @return string
	 * @deprecated parameter "as_label" of "Field" tag does the same
	 */
	function PhraseField($params)
	{
		$field_label = $this->Field($params);
		$translation = $this->Application->Phrase( $field_label );
		return $translation;
	}

	function Error($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$field = $this->SelectParam($params, 'name,field');

		return $object->GetErrorMsg($field, false);
	}

	function HasError($params)
	{
		if ($params['field'] == 'any') {
			$object = $this->getObject($params);
			/* @var $object kDBItem */

			$skip_fields = array_key_exists('except', $params) ? $params['except'] : false;
			$skip_fields = $skip_fields ? explode(',', $skip_fields) : Array();

			return $object->HasErrors($skip_fields);
		}
		else {
			$res = false;
			$fields = explode(',', $this->SelectParam($params, 'field,fields'));

			foreach ($fields as $field) {
				// call kDBTagProcessor::Error instead of kDBItem::GetErrorPseudo to have ability to override Error tag
				$params['field'] = $field;
				$res = $res || ($this->Error($params) != '');
			}

			return $res;
		}
	}

	/**
	 * Renders error message block, when there are errors on a form
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected function ErrorWarning($params)
	{
		if ( !isset($params['field']) ) {
			$params['field'] = 'any';
		}

		if ( $this->HasError($params) ) {
			$params['prefix'] = $this->getPrefixSpecial();

			return $this->Application->ParseBlock($params);
		}

		return '';
	}

	function IsRequired($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$field = $params['field'];
		$formatter_class = $object->GetFieldOption($field, 'formatter');

		if ( $formatter_class == 'kMultiLanguage' ) {
			$formatter = $this->Application->recallObject($formatter_class);
			/* @var $formatter kMultiLanguage */

			$field = $formatter->LangFieldName($field);
		}

		return $object->isRequired($field);
	}

	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;
	}

	/**
	 * Prints list a all possible field options
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected function PredefinedOptions($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBList */

		$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'] : '';
			$empty_label = isset($params['empty_label']) ? $params['empty_label'] : '';

			if ( $empty_label ) {
				if ( mb_substr($empty_label, 0, 1) == '+' ) {
					// using plain text instead of phrase label
					$empty_label = mb_substr($empty_label, 1);
				}
				else {
					$empty_label = $this->Application->Phrase($empty_label, false);
				}
			}

			// don't use other array merge function, because they will reset keys !!!
			$options = kUtil::array_merge_recursive(Array ($empty_value => $empty_label), $options);
		}

		$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);
		}

		$index = 0;
		$option_count = count($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 : '');
				$block_params['is_last'] = $index == $option_count - 1;
				$o .= $this->Application->ParseBlock($block_params);

				$index++;
			}
		}
		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 : '');
				$block_params['is_last'] = $index == $option_count - 1;
				$o .= $this->Application->ParseBlock($block_params);

				$index++;
			}
		}
		return $o;
	}

	function PredefinedSearchOptions($params)
	{
		$object =& $this->GetList($params);
		/* @var $object kDBList */

		$params['value'] = $this->SearchField($params);

		return $this->PredefinedOptions($params);
	}

	function Format($params, $object = null)
	{
		$field = $this->SelectParam($params, 'name,field');

        if ( !isset($object) ) {
            $object = $this->getObject($params);
            /* @var $object kDBItem */
        }

        $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);
			/* @var $formatter kFormatter */

			$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->GetPage();
				break;

			case 'total':
				$ret = $object->GetTotalPages();
				break;

			case 'prev':
				$ret = $object->GetPage() > 1 ? $object->GetPage() - 1 : false;
				break;

			case 'next':
				$ret = $object->GetPage() < $object->GetTotalPages() ? $object->GetPage() + 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->GetPage(); // 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 ) {
			// display 1st page as selected in case if we have no pages at all
			$total_pages = 1;
		}

		$o = '';

		// what are these 2 lines for?
		$this->Application->SetVar($prefix_special . '_event', '');
		$this->Application->SetVar($prefix_special . '_id', '');

		$current_page = $list->GetPage(); //  $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($params);

			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);
				}
			}

			$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);
			}
		}
		else {
			$no_prev_page_block = $this->SelectParam($params, 'no_prev_page_render_as,block_no_prev_page');

			if ( $no_prev_page_block ) {
				$block_params['name'] = $no_prev_page_block;
				$o .= $this->Application->ParseBlock($block_params);
			}
		}

		$total_records = $list->GetRecordsCount();
		$separator_params['name'] = $this->SelectParam($params, 'separator_render_as,block_separator');

		for ($i = $split_start; $i <= $split_end; $i++) {
			$from_record = ($i - 1) * $list->GetPerPage();
			$to_record = $from_record + $list->GetPerPage();

			if ( $to_record > $total_records ) {
				$to_record = $total_records;
			}

			$block_params['from_record'] = $from_record + 1;
			$block_params['to_record'] = $to_record;

			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);

			if ( $this->SelectParam($params, 'separator_render_as,block_separator') && $i < $split_end ) {
				$o .= $this->Application->ParseBlock($separator_params);
			}
		}

		if ( $current_page < $total_pages ) {
			$next_block_params = $this->prepareTagParams($params);
			$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);
			}

			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);
				}
			}
		}
		else {
			$no_next_page_block = $this->SelectParam($params, 'no_next_page_render_as,block_no_next_page');

			if ( $no_next_page_block ) {
				$block_params['name'] = $no_next_page_block;
				$o .= $this->Application->ParseBlock($block_params);
			}
		}

		$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);
	}

	function PerPageBar($params)
	{
		$object =& $this->GetList($params);

		$ret = '';
		$per_pages = explode(';', $params['per_pages']);
		$block_params = $this->prepareTagParams($params);
		$block_params['name'] = $params['render_as'];

		foreach ($per_pages as $per_page) {
			$block_params['per_page'] = $per_page;
			$this->Application->SetVar($this->getPrefixSpecial() . '_PerPage', $per_page);
			$block_params['selected'] = $per_page == $object->GetPerPage();

			$ret .= $this->Application->ParseBlock($block_params, 1);
		}

		$this->Application->SetVar($this->getPrefixSpecial() . '_PerPage', $object->GetPerPage());

		return $ret;
	}

	/**
	 * 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)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$field = $this->SelectParam($params, 'name,field');
		$formatter_class = $object->GetFieldOption($field, 'formatter');

		if ($formatter_class == 'kMultiLanguage') {
			$formatter = $this->Application->recallObject($formatter_class);
			/* @var $formatter kMultiLanguage */

			$force_primary = $object->GetFieldOption($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', Array ());
		$sorting_prefix = getArrayValue($list_sortings, $this->Special) ? $this->Special : '';

		$user_sorting_start = 0;
		$forced_sorting = getArrayValue($list_sortings, $sorting_prefix, 'ForcedSorting');

		return $forced_sorting ? count($forced_sorting) : $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);

		$list_helper = $this->Application->recallObject('ListHelper');
		/* @var $list_helper ListHelper */

		return $list_helper->hasUserSorting($list);
	}

	/**
	 * Gets information of sorting field at "pos" position,
	 * like sorting field name (type="field") or sorting direction (type="direction")
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected function OrderInfo($params)
	{
		$user_sorting_start = $this->getUserSortIndex() + --$params['pos'];
		$list =& $this->GetList($params);

		if ( $params['type'] == 'field' ) {
			return $list->GetOrderField($user_sorting_start);
		}

		if ( $params['type'] == 'direction' ) {
			return $list->GetOrderDirection($user_sorting_start);
		}

		return '';
	}

	/**
	 * Checks if sorting field/direction matches passed field/direction parameter
	 *
	 * @param Array $params
	 * @return bool
	 * @access protected
	 */
	protected function IsOrder($params)
	{
		$params['type'] = isset($params['field']) ? 'field' : 'direction';
		$value = $this->OrderInfo($params);

		if ( isset($params['field']) ) {
			return $params['field'] == $value;
		}
		elseif ( isset($params['direction']) ) {
			return $params['direction'] == $value;
		}

		return false;
	}

	/**
	 * Returns list per-page
	 *
	 * @param Array $params
	 * @return int
	 */
	function PerPage($params)
	{
		$object =& $this->GetList($params);

		return $object->GetPerPage();
	}

	/**
	 * Checks if list perpage matches value specified
	 *
	 * @param Array $params
	 * @return bool
	 */
	function PerPageEquals($params)
	{
		$object =& $this->GetList($params);

		return $object->GetPerPage() == $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
	 * @deprecated
	 */
	function FieldEquals($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		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 ( !isset($grid['Icons']) ) {
			return '';
		}

		$icons = $grid['Icons'];

		if ( isset($params['name']) ) {
			$icon_name = $params['name'];

			return isset($icons[$icon_name]) ? $icons[$icon_name] : '';
		}

		$status_fields = $this->Application->getUnitOption($this->Prefix, 'StatusField', Array ());
		/* @var $status_fields Array */

		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 isset($icons[$icon]) ? $icons[$icon] : $icons['default'];
	}

	/**
	 * Generates bluebar title + initializes prefixes used on page
	 *
	 * @param Array $params
	 * @return string
	 */
	function SectionTitle($params)
	{
		$preset_name = kUtil::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 = kUtil::array_merge_recursive($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;
		/* @var $prefixes Array */

		if ($prefixes) {
			// extract tag_params 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($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;
	}

	/**
	 * Returns information about list
	 *
	 * @param kDBList $object
	 * @param string $info_type
	 * @return string
	 * @access protected
	 */
	protected 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':
				if ( $object->GetRecordsCount(false) != $object->GetRecordsCount() ) {
					$of_phrase = $this->Application->Phrase('lc_of');
					return $object->GetRecordsCount() . ' ' . $of_phrase . ' ' . $object->GetRecordsCount(false);
				}

				return $object->GetRecordsCount();
				break;
		}

		return $object->GetField($info_type);
	}

	function GridInfo($params)
	{
		$object =& $this->GetList($params);
		/* @var $object kDBList */

		switch ( $params['type'] ) {
			case 'filtered':
				return $object->GetRecordsCount();

			case 'total':
				return $object->GetRecordsCount(false);

			case 'from':
				return $object->GetRecordsCount() ? $object->GetOffset() + 1 : 0; //0-based

			case 'to':
				$record_count = $object->GetRecordsCount();
				return $object->GetPerPage(true) != -1 ? min($object->GetOffset() + $object->GetPerPage(), $record_count) : $record_count;

			case 'total_pages':
				return $object->GetTotalPages();

			case 'needs_pagination':
				return ($object->GetPerPage(true) != -1) && (($object->GetRecordsCount() > $object->GetPerPage()) || ($object->GetPage() > 1));
		}

		return false;
	}

	/**
	 * 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);
		/* @var $object kDBItem */

		$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';
		}

		switch ($element_type) {
			case 'select':
			case 'multiselect':
			case 'radio':
				if ($object->GetDBField('DirectOptions')) {
					// used for custom fields
					$options = $object->GetDBField('DirectOptions');
				}
				else {
					// used for configuration
					$options = $helper->GetValuesHash( $object->GetDBField($params['value_list_field']) );
				}

				$object->SetFieldOption($field, 'options', $options);
				break;

			case 'text':
			case 'textarea':
			case 'upload':
				$params['field_params'] = $helper->ParseConfigSQL($object->GetDBField($params['value_list_field']));
				break;

			case 'password':
			case 'checkbox':
			default:
				break;
		}

		if (!$element_type) {
			throw new Exception('Element type missing for "<strong>' . $object->GetDBField('VariableName') . '</strong>" configuration variable');
			return '';
		}

		$params['name'] = $params['blocks_prefix'] . $element_type;

		// use $pass_params to pass 'SourcePrefix' parameter from PrintList to CustomInputName tag
		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 modified & is in TempTables mode, then parse block with name passed;
	 * remove modification mark if not in TempTables mode
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected 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);

		return $list->GetRecordsCount();
	}

	/**
	 * 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'].']';
		}

		if (array_key_exists('as_preg', $params) && $params['as_preg']) {
			$ret = preg_quote($ret, '/');
		}

		return $ret;
	}

	/**
	 * Return range filter field value
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected 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('formatted', $params) && $params['formatted'] ) {
				$object =& $this->GetList($params);
				$formatter_class = $object->GetFieldOption($field, 'formatter');

				if ( $formatter_class ) {
					$formatter = $this->Application->recallObject($formatter_class);
					/* @var $formatter kFormatter */

					$ret = $formatter->Format($ret, $field, $object);
				}
			}

			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)
	{
		$object =& $this->GetList($params);

        return $this->Format($params, $object);
	}

	/**
	 * Returns error of range field
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected 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
	 *
	 * @param Array $params
	 * @access public
	 * @return kDBItem|kDBList
	 */
	function getObject($params = Array())
	{
		$object = $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix, $params);
		/* @var $object kDBItem */

		if ( isset($params['requery']) && $params['requery'] ) {
			$this->Application->HandleEvent(new kEvent($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'];
	}

	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( kUtil::array_merge_recursive($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');
		/* @var $export_object kCatDBItemExportHelper */

		$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
	 * @return string
	 * @access protected
	 */
	protected function ExportPath($params)
	{
		$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'] ) {
			$path = str_replace(FULL_PATH . '/', $this->Application->BaseURL(), $path);
		}

		return $path . $filename;
	}

	function FieldTotal($params)
	{
		$list =& $this->GetList($params);
		$field = $this->SelectParam($params, 'field,name');
		$total_function = array_key_exists('function', $params) ? $params['function'] : $list->getTotalFunction($field);

		if (array_key_exists('function_only', $params) && $params['function_only']) {
			return $total_function;
		}

		if (array_key_exists('currency', $params) && $params['currency']) {
			$iso = $this->GetISO($params['currency']);
			$original = $list->getTotal($field, $total_function);
			$value = $this->ConvertCurrency($original, $iso);
			$list->setTotal($field, $total_function, $value);
		}

		$value = $list->GetFormattedTotal($field, $total_function);

		if (array_key_exists('currency', $params) && $params['currency']) {
			$value = $this->AddCurrencySymbol($value, $iso);
		}

		return $value;
	}

	/**
	 * 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 = $this->Application->GetFrontThemePath() . '/inc';

		if ( file_exists(FULL_PATH . $theme_path . '/style.css') ) {
			$url_params = Array (
				'events[fck]' => 'OnGetsEditorStyles',
				'no_pass_through' => 1, 'pass' => 'm', 'no_amp' => 1
			);

			$styles_css = $this->Application->HREF('index', '_FRONT_END_', $url_params, 'index.php');
		}
		else {
			$theme_path = rtrim(EDITOR_PATH, '/');
			$styles_css = $this->Application->BaseURL($theme_path) . 'style.css';
		}

		$styles_js = $this->Application->BaseURL($theme_path) . 'styles.js';

		/*$page_id = $this->Application->GetVar('c_id');
		$content_id = $this->Application->GetVar('content_id');
		$preview_url = '';

		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 . 'ckeditor.php');

		$oCKeditor = new CKeditor(BASE_PATH . EDITOR_PATH);

//		$oFCKeditor->FullUrl    = $this->Application->BaseURL();
//		$oFCKeditor->BaseUrl    = BASE_PATH . '/'; // used by custom document plugin
//		$oFCKeditor->PreviewUrl	= $preview_url; // used by custom MyPreview plugin

		$oCKeditor->lateLoad = array_key_exists('late_load', $params) && $params['late_load'];

		$width = $params['width'];
		$height = $params['height'];

		if ( preg_match('/^[\d]+$/', $width) ) {
			$width .= 'px';
		}

		if ( preg_match('/^[\d]+$/', $height) ) {
			$height .= 'px';
		}

		$oCKeditor->textareaAttributes = Array (
			'style' => 'width: ' . $width . '; height: ' . $height . ';'
		);

		if ( file_exists(SYSTEM_PRESET_PATH . DIRECTORY_SEPARATOR . 'inp_ckconfig.js') ) {
			$file_helper = $this->Application->recallObject('FileHelper');
			/* @var $file_helper FileHelper */

			$config_js = $file_helper->pathToUrl(SYSTEM_PRESET_PATH . DIRECTORY_SEPARATOR . 'inp_ckconfig.js');
		}
		else {
			$config_js = $this->Application->BaseURL() . 'core/admin_templates/js/inp_ckconfig.js';
		}

		$fck_helper = $this->Application->recallObject('FCKHelper');
		/* @var $fck_helper fckFCKHelper */

		$transit_params = $fck_helper->getTransitParams($params);

		$oCKeditor->config = Array (
			'toolbar' => $this->Application->isDebugMode() ? 'DebugMode' : 'Default', //  $page_id && $content_id ? 'Advanced' : 'Default',

			'baseHref' => $this->Application->BaseURL( rtrim(EDITOR_PATH, '/') ),

//			'ProjectPath' => BASE_PATH . '/', // used by custom MyPreview plugin

			'customConfig' => $config_js,
			'stylesSet' => 'portal:' . $styles_js,
			'contentsCss' => $styles_css,
//			'DefaultStyleLabel' => $this->Application->Phrase('la_editor_default_style'), // not ported to ckeditor
			'Admin' => 1, // for custom file browser to work
			'K4' => 1, // for custom file browser to work
//			'PreviewUrl' => $preview_url,
//			'BaseUrl' => BASE_PATH . '/', // used by custom document plugin & by file browser
			'language' => $this->_getFCKLanguage(),
			'height' => $height, // editor area height
		);

		if ( isset($transit_params['bgcolor']) && $transit_params['bgcolor'] ) {
			$oCKeditor->config['extraCss'] = 'body { background-color: ' . $transit_params['bgcolor'] . '; }';
		}

		foreach ($transit_params as $param_name => $param_value) {
			if ( !$param_value ) {
				continue;
			}

			$param_key = str_replace(' ', '', ucwords(str_replace('_', ' ', $param_name)));
			$param_key[0] = strtolower($param_key[0]);

			$oCKeditor->config[$param_key] = $param_value;
		}

		$oCKeditor->returnOutput = true;

		$events = Array (
			'configLoaded' => 'function(ev) { ev.editor.addCss(ev.editor.config.extraCss); }',
		);

		return $oCKeditor->editor($name, $value, Array (), $events);
	}

	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
	 * @access protected
	 */
	protected function ItemLink($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		if ( !isset($params['pass']) ) {
			$params['pass'] = 'm';
		}

		$params[ $object->getPrefixSpecial() . '_id' ] = $object->GetID();

		return $this->Application->ProcessParsedTag('m', 'T', $params);
	}

	/**
	 * Creates a button for editing item in Admin Console
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected function AdminEditButton($params)
	{
		if ( EDITING_MODE != EDITING_MODE_CONTENT ) {
			return '';
		}

		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$item_prefix = isset($params['item_prefix']) ? $params['item_prefix'] : $this->Prefix;

		if ( isset($params['template']) ) {
			$template = $params['template'];
		}
		else {
			$admin_template_prefix = $this->Application->getUnitOption($item_prefix, 'AdminTemplatePrefix');
			$template = $this->Application->getUnitOption($item_prefix, 'AdminTemplatePath') . '/' . $admin_template_prefix . 'edit';

			if ( !$admin_template_prefix ) {
				throw new InvalidArgumentException('Automatic admin editing template detection failed because of missing "AdminTemplatePrefix" unit config option in "' . $this->Prefix . '" unit config');
			}
		}

		$form_name = 'kf_' . str_replace('-', '_', $item_prefix) . '_' . $object->GetID();
		$button_icon = isset($params['button_icon']) ? $params['button_icon'] : 'content_mode.png';
		$button_class = isset($params['button_class']) ? $params['button_class'] : 'admin-edit-btn';
		$button_title = isset($params['button_title']) ? $params['button_title'] : 'la_btn_AdminEditItem';

		if ( substr($button_title, 0, 1) == '+' ) {
			$button_title = substr($button_title, 1);
		}
		else {
			$button_title = $this->Application->Phrase($button_title, false, true);
		}

		$icon_url = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/' . $button_icon;
		$button_code = '<button style="background-image: url(' . $icon_url . ');"  onclick="$form_name=\'' . addslashes($form_name) . '\'; std_edit_item(\'' . addslashes($item_prefix) . '\', \'' . addslashes($template) . '\');" class="cms-btn-new ' . $button_class . '">' . $button_title . '</button>';

		if ( !isset($params['pass']) ) {
			$params['pass'] = 'm,' . $item_prefix;
		}

		$params['m_opener'] = 'd';
		$params[$item_prefix . '_id'] = $object->GetID();

		if ( !isset($params['temp_mode']) || (isset($params['temp_mode']) && $params['temp_mode']) ) {
			$params[$item_prefix . '_mode'] = 't';
			$params[$item_prefix . '_event'] = 'OnEdit';
		}

		$params['front'] = 1; // to make opener stack work properly
		$params['__URLENCODE__'] = 1; // don't use "&amp;"
		$params['__NO_REWRITE__'] = 1; // since admin link
//		$params['escape'] = 1; // needed?

		unset($params['button_icon'], $params['button_class'], $params['button_title'], $params['template'], $params['item_prefix'], $params['temp_mode']);

		// link from Front-End to Admin, don't remove "index.php"
		$edit_url = $this->Application->HREF($template, ADMIN_DIRECTORY, $params, 'index.php');
		$edit_form = '<form method="POST" style="display: inline; margin: 0px" name="' . $form_name . '" id="' . $form_name . '" action="' . $edit_url . '"></form>';

		if ( isset($params['forms_later']) && $params['forms_later'] ) {
			$all_forms = $this->Application->GetVar('all_forms');
			$this->Application->SetVar('all_forms', $all_forms . $edit_form);
		}
		else {
			$button_code .= $edit_form;
		}

		return $button_code;
	}

	/**
	 * 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($params);
		/* @var $object kDBItem */

		$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->GetPerPage(true) == $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
	 * @access protected
	 */
	protected 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 = kUtil::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_NOTICE);
			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', Array ());
		}

		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;
	}

	/**
	 * Checks, that requested option is checked inside field value
	 *
	 * @param Array $params
	 * @return bool
	 */
	function Selected($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$field = $this->SelectParam($params, 'name,field');
		$value = $object->GetDBField($field);

		if (strpos($value, '|') !== false) {
			$value = explode('|', substr($value, 1, -1));
			return in_array($params['value'], $value);
		}

		return $value;
	}

	/**
	 * Displays option name by it's value
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected function OptionValue($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

		$value = $params['value'];
		$field = $this->SelectParam($params, 'name,field');

		$field_options = $object->GetFieldOptions($field);

		if ( isset($field_options['options'][$value]) ) {
			$value = $field_options['options'][$value];
			$use_phrases = isset($field_options['use_phrases']) ? $field_options['use_phrases'] : false;

			return $use_phrases ? $this->Application->Phrase($value) : $value;
		}

		return '';
	}

	/**
	 * Returns/sets form name for current object
	 *
	 * @param Array $params
	 * @return string
	 */
	function FormName($params)
	{
		$form_name = $this->SelectParam($params, 'name,form,form_name');

		if ( $form_name ) {
			$prefix = $this->getPrefixSpecial();

			if ( $this->Application->hasObject( $this->getPrefixSpecial() ) ) {
				$object = $this->getObject($params);
				/* @var $object kDBItem */

				if ( $object->getFormName() != $form_name ) {
					trigger_error('Setting form to "<strong>' . $form_name . '</strong>" failed, since object "<strong>' . $this->getPrefixSpecial() . '</strong>" is created before FormName tag (e.g. in event or another tag).', E_USER_WARNING);
				}
			}
			else {
				$forms = $this->Application->GetVar('forms', Array ());
				$forms[ $this->getPrefixSpecial() ] = $form_name;
				$this->Application->SetVar('forms', $forms);
			}

			return '';
		}

		$object = $this->getObject($params);
		/* @var $object kDBItem */

		return $object->getFormName();
	}

	/**
	 * Just reloads the object using given parameters
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected function ReloadItem($params)
	{
		$params['requery'] = 1;

		$object = $this->getObject($params);
		/* @var $object kDBItem */

		return '';
	}
}