<?php
/**
* @version	$Id: categories_tag_processor.php 13341 2010-04-06 08:16:22Z 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.
*/

class CategoriesTagProcessor extends kDBTagProcessor {

	function SubCatCount($params)
	{
		$object =& $this->getObject($params);

		if (isset($params['today']) && $params['today']) {
			$sql = 'SELECT COUNT(*)
					FROM '.$object->TableName.'
					WHERE (ParentPath LIKE "'.$object->GetDBField('ParentPath').'%") AND (CreatedOn > '.(adodb_mktime() - 86400).')';
			return $this->Conn->GetOne($sql) - 1;
		}

		return $object->GetDBField('CachedDescendantCatsQty');
	}

	/**
	 * Returns category count in system
	 *
	 * @param Array $params
	 * @return int
	 */
	function CategoryCount($params)
	{
		$count_helper =& $this->Application->recallObject('CountHelper');
		/* @var $count_helper kCountHelper */

		$today_only = isset($params['today']) && $params['today'];
		return $count_helper->CategoryCount($today_only);
	}

	function IsNew($params)
	{
		$object =& $this->getObject($params);
		return $object->GetDBField('IsNew') ? 1 : 0;
	}

	function IsPick($params)
	{
		return $this->IsEditorsPick($params);
	}

	/**
	 * Returns item's editors pick status (using not formatted value)
	 *
	 * @param Array $params
	 * @return bool
	 */
	function IsEditorsPick($params)
	{
		$object =& $this->getObject($params);

		return $object->GetDBField('EditorsPick') == 1;
	}

	function ItemIcon($params)
	{
		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
		$grid = $grids[ $params['grid'] ];

		if (!array_key_exists('Icons', $grid)) {
			return '';
		}

		$icons = $grid['Icons'];

		if (array_key_exists('name', $params)) {
			$icon_name = $params['name'];
			return array_key_exists($icon_name, $icons) ? $icons[$icon_name] : '';
		}

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

		if ($object->GetDBField('CreatedBySystem')) {
			if (!$object->GetDBField('IsMenu')) {
				return 'icon16_section_menuhidden_system.png';
			}
			return 'icon16_section_system.png';
		}

		$status = $object->GetDBField('Status');

		if ($status == STATUS_DISABLED) {
			return 'icon16_section_disabled.png';
		}

		if (!$object->GetDBField('IsMenu')) {
			return 'icon16_section_menuhidden.png';
		}

		if ($status == STATUS_PENDING) {
			return 'icon16_section_pending.png';
		}

		if ($object->GetDBField('IsNew')) {
			return 'icon16_section_new.png';
		}

		return 'icon16_section.png';
	}

	function ItemCount($params)
	{
		$object =& $this->getObject($params);
		$ci_table = $this->Application->getUnitOption('l-ci', 'TableName');

		$sql = 'SELECT COUNT(*)
				FROM ' . $object->TableName . ' c
				LEFT JOIN ' . $ci_table . ' ci ON c.CategoryId = ci.CategoryId
				WHERE (c.TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight') . ') AND NOT (ci.CategoryId IS NULL)';
		return $this->Conn->GetOne($sql);
	}

	function ListCategories($params)
	{
		return $this->PrintList2($params);
	}

	function RootCategoryName($params)
	{
		return $this->Application->ProcessParsedTag('m', 'RootCategoryName', $params);
	}

	function CheckModuleRoot($params)
	{
		$module_name = getArrayValue($params, 'module') ? $params['module'] : 'In-Commerce';
		$module_root_cat = $this->Application->findModule('Name', $module_name, 'RootCat');

		$additional_cats = $this->SelectParam($params, 'add_cats');
		if ($additional_cats) {
			$additional_cats = explode(',', $additional_cats);
		}
		else {
			$additional_cats = array();
		}

		if ($this->Application->GetVar('m_cat_id') == $module_root_cat || in_array($this->Application->GetVar('m_cat_id'), $additional_cats)) {
			$home_template = getArrayValue($params, 'home_template');
			if (!$home_template) return;
			$this->Application->Redirect($home_template, Array('pass'=>'all'));
		};
	}

	function CategoryPath($params)
	{
		$category_helper =& $this->Application->recallObject('CategoryHelper');
		/* @var $category_helper CategoryHelper */

		return $category_helper->NavigationBar($params);
	}

	/**
	 * Shows category path to specified category
	 *
	 * @param Array $params
	 * @return string
	 */
	function FieldCategoryPath($params)
	{
		$object =& $this->getObject();
		/* @var $object kDBItem */

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

		if ($category_id) {
			$params['cat_id'] = $category_id;
			return $this->CategoryPath($params);
		}

		return '';
	}

	function CurrentCategoryName($params)
	{
		$cat_object =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List');
		$sql = 'SELECT '.$this->getTitleField().'
				FROM '.$cat_object->TableName.'
				WHERE CategoryId = '.(int)$this->Application->GetVar('m_cat_id');
		return $this->Conn->GetOne($sql);
	}

	/**
	 * Returns current category name
	 *
	 * @param Array $params
	 * @return string
	 * @todo Find where it's used
	 */
	function CurrentCategory($params)
	{
		return $this->CurrentCategoryName($params);
	}

	function getTitleField()
	{
		$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
		return $ml_formatter->LangFieldName('Name');
	}

	/**
	 * Returns symlinked category for given category
	 *
	 * @param $category_id
	 */
	function getCategorySymLink($category_id)
	{
		static $cache = null;

		if (!$category_id) {
			// don't bother to get symlink for "Home" category
			return $category_id;
		}

		if (!isset($cache)) {
			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');

			// get symlinked categories, that are not yet deleted
			$sql = 'SELECT c1.SymLinkCategoryId, c1.' . $id_field . '
					FROM ' . $table_name . ' c1
					JOIN ' . $table_name . ' c2 ON c1.SymLinkCategoryId = c2.' . $id_field;
			$cache = $this->Conn->GetCol($sql, $id_field);
		}

		return array_key_exists($category_id, $cache) ? $cache[$category_id] : $category_id;
	}

	function CategoryLink($params)
	{
		$category_id = getArrayValue($params, 'cat_id');

		if ($category_id === false) {
			$category_id = $this->Application->GetVar($this->getPrefixSpecial() . '_id');
		}

		if ("$category_id" == 'Root') {
			$category_id = $this->Application->findModule('Name', $params['module'], 'RootCat');
		}
		elseif ("$category_id" == 'current') {
			$category_id = $this->Application->GetVar('m_cat_id');
		}

		if (!array_key_exists('direct_link', $params) || !$params['direct_link']) {
			$category_id = $this->getCategorySymLink( (int)$category_id );
		}

		unset($params['cat_id'], $params['module']);

		$new_params = Array ('pass' => 'm', 'm_cat_id' => $category_id, 'pass_category' => 1);
		$params = array_merge_recursive2($params, $new_params);

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

	function CategoryList($params)
	{
		//$object =& $this->Application->recallObject( $this->getPrefixSpecial() , $this->Prefix.'_List', $params );
		$object =& $this->GetList($params);


		if ($object->RecordsCount == 0)
		{
			if (isset($params['block_no_cats'])) {
				$params['name'] = $params['block_no_cats'];
				return $this->Application->ParseBlock($params);
			}
			else {
				return '';
			}
		}

		if (isset($params['block'])) {
			return $this->PrintList($params);
		}
		else {
			$params['block'] = $params['block_main'];
			if (isset($params['block_row_start'])) {
				$params['row_start_block'] = $params['block_row_start'];
			}

			if (isset($params['block_row_end'])) {
				$params['row_end_block'] = $params['block_row_end'];
			}
			return $this->PrintList2($params);
		}
	}

	function Meta($params)
	{
		$object =& $this->Application->recallObject($this->Prefix); // .'.-item'
		/* @var $object CategoriesItem */

		$meta_type = $params['name'];
		if ($object->isLoaded()) {
			// 1. get module prefix by current category
			$category_helper =& $this->Application->recallObject('CategoryHelper');
			/* @var $category_helper CategoryHelper */

			$category_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
			$module_info = $category_helper->getCategoryModule($params,  $category_path);

			// In-Edit & Proj-CMS module prefixes doesn't have custom field with item template
			if ($module_info && $module_info['Var'] != 'adm' && $module_info['Var'] != 'st') {

				// 2. get item template by current category & module prefix
				$mod_rewrite_helper = $this->Application->recallObject('ModRewriteHelper');
				/* @var $mod_rewrite_helper kModRewriteHelper */

				$category_params = Array (
				'CategoryId' => $object->GetID(),
				'ParentPath' => $object->GetDBField('ParentPath'),
				);

				$item_template = $mod_rewrite_helper->GetItemTemplate($category_params, $module_info['Var']);

				if ($this->Application->GetVar('t') == $item_template) {
					// we are located on item's details page
					$item =& $this->Application->recallObject($module_info['Var']);
					/* @var $item kCatDBItem */

					// 3. get item's meta data
					$value = $item->GetField('Meta'.$meta_type);
					if ($value) {
						return $value;
					}
				}

				// 4. get category meta data
				$value = $object->GetField('Meta'.$meta_type);
				if ($value) {
					return $value;
				}
			}
		}

		// 5. get default meta data
		switch ($meta_type) {
			case 'Description':
				$config_name = 'Category_MetaDesc';
				break;
			case 'Keywords':
				$config_name = 'Category_MetaKey';
				break;
		}

		return $this->Application->ConfigValue($config_name);
	}

	function BuildListSpecial($params)
	{
		if (($this->Special != '') && !is_numeric($this->Special)) {
			// When recursive category list is printed (like in sitemap), then special
			// should be generated even if it's already present. Without it list on this
			// level will erase list on previous level, because it will be stored in same object.
			return $this->Special;
		}

		if ( isset($params['parent_cat_id']) ) {
			$parent_cat_id = $params['parent_cat_id'];
		}
		else {
			$parent_cat_id = $this->Application->GetVar($this->Prefix.'_id');
			if (!$parent_cat_id) {
				$parent_cat_id = $this->Application->GetVar('m_cat_id');
			}
			if (!$parent_cat_id) {
				$parent_cat_id = 0;
			}
		}

		$list_unique_key = $this->getUniqueListKey($params);
		// check for "admin" variable, because we are parsing front-end template from admin when using template editor feature
		if ($this->Application->GetVar('admin') || !$this->Application->isAdmin) {
			// add parent category to special, when on Front-End,
			// because there can be many category lists on same page
			$list_unique_key .= $parent_cat_id;
		}

		if ($list_unique_key == '') {
			return parent::BuildListSpecial($params);
		}

		return crc32($list_unique_key);
	}

	function IsCurrent($params)
	{
		$object =& $this->getObject($params);
		if ($object->GetID() == $this->Application->GetVar('m_cat_id')) {
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Substitutes category in last template base on current category
	 * This is required becasue when you navigate catalog using AJAX, last_template is not updated
	 * but when you open item edit from catalog last_template is used to build opener_stack
	 * So, if we don't substitute m_cat_id in last_template, after saving item we'll get redirected
	 * to the first category we've opened, not the one we navigated to using AJAX
	 *
	 * @param Array $params
	 */
	function UpdateLastTemplate($params)
	{
		$category_id = $this->Application->GetVar('m_cat_id');

		$wid = $this->Application->GetVar('m_wid');
		list($index_file, $env) = explode('|', $this->Application->RecallVar(rtrim('last_template_'.$wid, '_')), 2);

		$vars_backup = Array ();
		$vars = $this->Application->HttpQuery->processQueryString( str_replace('%5C', '\\', $env) );

		foreach ($vars as $var_name => $var_value) {
			$vars_backup[$var_name] = $this->Application->GetVar($var_name);
			$this->Application->SetVar($var_name, $var_value);
		}

		// update required fields
		$this->Application->SetVar('m_cat_id', $category_id);
		$this->Application->Session->SaveLastTemplate($params['template']);

		foreach ($vars_backup as $var_name => $var_value) {
			$this->Application->SetVar($var_name, $var_value);
		}
	}

	function GetParentCategory($params)
	{
		$parent_id = 0;
		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
		$table = $this->Application->getUnitOption($this->Prefix,'TableName');
		$cat_id = $this->Application->GetVar('m_cat_id');
		if ($cat_id > 0) {
			$sql = 'SELECT ParentId
					FROM '.$table.'
					WHERE '.$id_field.' = '.$cat_id;
			$parent_id = $this->Conn->GetOne($sql);
		}
		return $parent_id;
	}

	function InitCacheUpdater($params)
	{
		safeDefine('CACHE_PERM_CHUNK_SIZE', 30);

		$continue = $this->Application->GetVar('continue');
		$total_cats = (int) $this->Conn->GetOne('SELECT COUNT(*) FROM '.TABLE_PREFIX.'Category');

		if ($continue === false && $total_cats > CACHE_PERM_CHUNK_SIZE) {
			// first step, if category count > CACHE_PERM_CHUNK_SIZE, then ask for cache update
			return true;
		}

		if ($continue === false) {
			// if we don't have to ask, then assume user selected "Yes" in permcache update dialog
			$continue = 1;
		}

		$updater =& $this->Application->recallObject('kPermCacheUpdater', null, Array('continue' => $continue));
		/* @var $updater kPermCacheUpdater */
		if ($continue === '0') { // No in dialog
			$updater->clearData();
			$this->Application->Redirect($params['destination_template']);
		}

		$ret = false; // don't ask for update
		if ($continue == 1) {  // Initial run
			$updater->setData();
		}
		if ($continue == 2) { // Continuing
			// called from AJAX request => returns percent
			$needs_more = true;
			while ($needs_more && $updater->iteration <= CACHE_PERM_CHUNK_SIZE) {
				// until proceeeded in this step category count exceeds category per step limit
				$needs_more = $updater->DoTheJob();
			}

			if ($needs_more) {
				// still some categories are left for next step
				$updater->setData();
			}
			else {
				// all done -> redirect
				$updater->SaveData();
				$this->Application->RemoveVar('PermCache_UpdateRequired');
				$this->Application->Redirect($params['destination_template']);
			}

			$ret = $updater->getDonePercent();
		}
		return $ret;
	}

	/**
	 * Parses warning block, but with style="display: none;". Used during permissions saving from AJAX
	 *
	 * @param Array $params
	 * @return string
	 */
	function SaveWarning($params)
	{
		if ($this->Prefix == 'st') {
			// don't use this method for other prefixes then Category, that use this tag processor
			return parent::SaveWarning($params);
		}

		$main_prefix = getArrayValue($params, 'main_prefix');
		if ($main_prefix && $main_prefix != '$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) {
			$this->Application->RemoveVar($top_prefix.'_modified');
			return '';
		}

		$block_name = $this->SelectParam($params, 'render_as,name');
		if ($block_name) {
			$block_params = $this->prepareTagParams($params);
			$block_params['name'] = $block_name;
			$block_params['edit_mode'] = $temp_tables ? 1 : 0;
			$block_params['display'] = $temp_tables && $modified ? 1 : 0;
			return $this->Application->ParseBlock($block_params);
		}
		else {
			return $temp_tables && $modified ? 1 : 0;
		}
		return ;
	}

	/**
	 * Allows to detect if this prefix has something in clipboard
	 *
	 * @param Array $params
	 * @return bool
	 */
	function HasClipboard($params)
	{
		$clipboard = $this->Application->RecallVar('clipboard');
		if ($clipboard) {
			$clipboard = unserialize($clipboard);
			foreach ($clipboard as $prefix => $clipboard_data) {
				foreach ($clipboard_data as $mode => $ids) {
					if (count($ids)) return 1;
				}
			}
		}
		return 0;
	}

	/**
	 * Allows to detect if root category being edited
	 *
	 * @param Array $params
	 */
	function IsRootCategory($params)
	{
		$object =& $this->getObject($params);
		return $object->IsRoot();
	}

	/**
	 * Returns home category id
	 *
	 * @param Array $params
	 * @return int
	 */
	function HomeCategory($params)
	{
		static $root_category = null;

		if (!isset($root_category)) {
			$root_category = $this->Application->findModule('Name', 'Core', 'RootCat');
		}

		return $root_category;
	}

	/**
	 * Used for disabling "Home" and "Up" buttons in category list
	 *
	 * @param Array $params
	 * @return bool
	 */
	function ModuleRootCategory($params)
	{
		return $this->Application->GetVar('m_cat_id') == $this->HomeCategory($params);
	}

	function CatalogItemCount($params)
	{
		$params['skip_quering'] = true;
		$object =& $this->GetList($params);

		if (!$object->Counted) {
			$object->CountRecs();
		}

		return $object->NoFilterCount != $object->RecordsCount ? $object->RecordsCount.' / '.$object->NoFilterCount : $object->RecordsCount;
	}

	function InitCatalog($params)
	{
		$tab_prefixes = $this->Application->GetVar('tp'); // {all, <prefixes_list>, none}
		if ($tab_prefixes === false) $tab_prefixes = 'all';
		$skip_prefixes = isset($params['skip_prefixes']) && $params['skip_prefixes'] ? explode(',', $params['skip_prefixes']) : Array();
		$replace_main = isset($params['replace_m']) && $params['replace_m'];

		// get all prefixes available
		$prefixes = Array();
		foreach ($this->Application->ModuleInfo as $module_name => $module_data) {
			$prefix = $module_data['Var'];

			if ($prefix == 'adm'/* || $prefix == 'm'*/) continue;

			if ($prefix == 'm' && $replace_main) {
				$prefix = 'c';
			}

			$prefixes[] = $prefix;
		}

		if ($tab_prefixes == 'none') {
			$skip_prefixes = array_unique(array_merge($skip_prefixes, $prefixes));
			unset($skip_prefixes[ array_search($replace_main ? 'c' : 'm', $skip_prefixes) ]);
		}
		elseif ($tab_prefixes != 'all') {
			// prefix list here
			$tab_prefixes = explode(',', $tab_prefixes); // list of prefixes that should stay
			$skip_prefixes = array_unique(array_merge($skip_prefixes, array_diff($prefixes, $tab_prefixes)));
		}

		$params['name'] = $params['render_as'];
		$params['skip_prefixes'] = implode(',', $skip_prefixes);
		return $this->Application->ParseBlock($params);
	}

	/**
	 * Determines, that printed category/menu item is currently active (will also match parent category)
	 *
	 * @param Array $params
	 * @return bool
	 */
	function IsActive($params)
	{
		static $current_path = null;

		if (!isset($current_path)) {
			$sql = 'SELECT ParentPath
					FROM ' . TABLE_PREFIX . 'Category
					WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id');
			$current_path = $this->Conn->GetOne($sql);
		}

		if (array_key_exists('parent_path', $params)) {
			$test_path = $params['parent_path'];
		}
		else {
			$template = $params['template'];
			if ($template) {
				// when using from "c:CachedMenu" tag
				$sql = 'SELECT ParentPath
						FROM ' . TABLE_PREFIX . 'Category
						WHERE NamedParentPath = ' . $this->Conn->qstr('Content/' . $template);
				$test_path = $this->Conn->GetOne($sql);
			}
			else {
				// when using from "c:PrintList" tag
				$cat_id = array_key_exists('cat_id', $params) && $params['cat_id'] ? $params['cat_id'] : false;
				if ($cat_id === false) {
					// category not supplied -> get current from PrintList
					$category =& $this->getObject($params);
				}
				else {
					if ("$cat_id" == 'Root') {
						$cat_id = $this->Application->findModule('Name', $params['module'], 'RootCat');
					}

					$category =& $this->Application->recallObject($this->Prefix . '.-c' . $cat_id, $this->Prefix, Array ('skip_autoload' => true));
					$category->Load($cat_id);
				}

				$test_path = $category->GetDBField('ParentPath');
			}
		}

		return strpos($current_path, $test_path) !== false;
	}

	/**
	 * Checks if user have one of required permissions
	 *
	 * @param Array $params
	 * @return bool
	 */
	function HasPermission($params)
	{
		$perm_helper =& $this->Application->recallObject('PermissionsHelper');
		/* @var $perm_helper kPermissionsHelper */

		$params['raise_warnings'] = 0;
		$object =& $this->getObject($params);
		/* @var $object kDBItem */

		$params['cat_id'] = $object->isLoaded() ? $object->GetDBField('ParentPath') : $this->Application->GetVar('m_cat_id');
		return $perm_helper->TagPermissionCheck($params);
	}

	/**
	 * Prepares name for field with event in it (used only on front-end)
	 *
	 * @param Array $params
	 * @return string
	 */
	function SubmitName($params)
	{
		return 'events['.$this->Prefix.']['.$params['event'].']';
	}

	/**
	 * Returns last modification date of items in category / system
	 *
	 * @param Array $params
	 * @return string
	 */
	function LastUpdated($params)
	{
		$category_id = $this->Application->GetVar('m_cat_id');
		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');

		if (isset($params['local']) && $params['local'] && $category_id > 0) {
			// scan only current category & it's children
			$sql = 'SELECT TreeLeft, TreeRight
	        		FROM '.TABLE_PREFIX.'Category
	        		WHERE CategoryId = '.$category_id;
			$tree_info = $this->Conn->GetRow($sql);

			$sql = 'SELECT MAX(c.Modified) AS ModDate, MAX(c.CreatedOn) AS NewDate
	        		FROM '.TABLE_PREFIX.'Category c
	        		WHERE c.TreeLeft BETWEEN '.$tree_info['TreeLeft'].' AND '.$tree_info['TreeRight'];
		}
		else {
			// scan all categories in system
			$sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate
	       			FROM '.$table_name;
		}

		$row_data = $this->Conn->GetRow($sql);
		if (!$row_data) {
			return '';
		}

		$date = $row_data[ $row_data['NewDate'] > $row_data['ModDate'] ? 'NewDate' : 'ModDate' ];

		// format date
		$format = isset($params['format']) ? $params['format'] : '_regional_DateTimeFormat';
		if (preg_match("/_regional_(.*)/", $format, $regs)) {
			$lang =& $this->Application->recallObject('lang.current');
			if ($regs[1] == 'DateTimeFormat') {
				// combined format
				$format = $lang->GetDBField('DateFormat').' '.$lang->GetDBField('TimeFormat');
			}
			else {
				// simple format
				$format = $lang->GetDBField($regs[1]);
			}
		}

		return adodb_date($format, $date);
	}

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

		$params['cat_id'] = $object->GetID();

		$count_helper =& $this->Application->recallObject('CountHelper');
		/* @var $count_helper kCountHelper */

		return $count_helper->CategoryItemCount($params['prefix'], $params);
	}

	/**
	 * Returns prefix + any word (used for shared between categories per page settings)
	 *
	 * @param Array $params
	 * @return string
	 */
	function VarName($params)
	{
		return $this->Prefix.'_'.$params['type'];
	}

	/**
	 * Checks if current category is valid symbolic link to another category
	 *
	 * @param Array $params
	 * @return string
	 */
	function IsCategorySymLink($params)
	{
		$object =& $this->getObject($params);
		/* @var $object kDBList */

		$sym_category_id = $object->GetDBField('SymLinkCategoryId');

		if (is_null($sym_category_id))
		{
			return false;
		}

		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');

		$sql = 'SELECT '.$id_field.'
				FROM '.$table_name.'
				WHERE '.$id_field.' = '.$sym_category_id;

		return $this->Conn->GetOne($sql)? true : false;
	}

	/**
	 * Returns module prefix based on root category for given
	 *
	 * @param Array $params
	 * @return string
	 */
	function GetModulePrefix($params)
	{
		$object =& $this->getObject($params);
		/* @var $object kDBItem */

		$parent_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));

		$category_helper =& $this->Application->recallObject('CategoryHelper');
		/* @var $category_helper CategoryHelper */

		$module_info = $category_helper->getCategoryModule($params, $parent_path);
		return $module_info['Var'];
	}

	function ImageSrc($params)
	{
		list ($ret, $tag_processed) = $this->processAggregatedTag('ImageSrc', $params, $this->getPrefixSpecial());
		return $tag_processed ? $ret : false;
	}

	function PageLink($params)
	{
		$t = isset($params['template']) ? $params['template'] : '';
		unset($params['template']);

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

		if (isset($params['page'])) {
			$this->Application->SetVar($this->getPrefixSpecial().'_Page', $params['page']);
			unset($params['page']);
		}

		$params['m_cat_page'] = $this->Application->GetVar($this->getPrefixSpecial().'_Page');

		if (!isset($params['pass'])) {
			$params['pass'] = 'm,'.$this->getPrefixSpecial();
		}

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

	/**
	 * Returns spelling suggestions against search keyword
	 *
	 * @param Array $params
	 * @return string
	 */
	function SpellingSuggestions($params)
	{
		$keywords = unhtmlentities( trim($this->Application->GetVar('keywords')) );
		if (!$keywords) {
			return ;
		}

		// 1. try to get already cached suggestion
		$suggestion = $this->Application->getCache('search.suggestion', $keywords);
		if ($suggestion !== false) {
			return $suggestion;
		}

		$table_name = $this->Application->getUnitOption('spelling-dictionary', 'TableName');

		// 2. search suggestion in database
		$sql = 'SELECT SuggestedCorrection
				FROM ' . $table_name . '
				WHERE MisspelledWord = ' . $this->Conn->qstr($keywords);
		$suggestion = $this->Conn->GetOne($sql);
		if ($suggestion !== false) {
			$this->Application->setCache('search.suggestion', $keywords, $suggestion);
			return $suggestion;
		}

		// 3. suggestion not found in database, ask webservice
		$app_id = $this->Application->ConfigValue('YahooApplicationId');
		$url = 'http://search.yahooapis.com/WebSearchService/V1/spellingSuggestion?appid=' . $app_id . '&query=';

		$curl_helper =& $this->Application->recallObject('CurlHelper');
		/* @var $curl_helper kCurlHelper */

		$xml_data = $curl_helper->Send($url . urlencode($keywords));

		$xml_helper =& $this->Application->recallObject('kXMLHelper');
		/* @var $xml_helper kXMLHelper */

		$root_node =& $xml_helper->Parse($xml_data);

		$result = $root_node->FindChild('RESULT');
		/* @var $result kXMLNode */

		if (is_object($result)) {
			// webservice responded -> save in local database
			$fields_hash = Array (
			'MisspelledWord' => $keywords,
			'SuggestedCorrection' => $result->Data,
			);

			$this->Conn->doInsert($fields_hash, $table_name);
			$this->Application->setCache('search.suggestion', $keywords, $result->Data);

			return $result->Data;
		}

		return '';
	}

	/**
	 * Shows link for searching by suggested word
	 *
	 * @param Array $params
	 * @return string
	 */
	function SuggestionLink($params)
	{
		$params['keywords'] = $this->SpellingSuggestions($params);

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

	function InitCatalogTab($params)
	{
		$tab_params['mode'] = $this->Application->GetVar('tm'); // single/multi selection possible
		$tab_params['special'] = $this->Application->GetVar('ts'); // use special for this tab
		$tab_params['dependant'] = $this->Application->GetVar('td'); // is grid dependant on categories grid

		// set default params (same as in catalog)
		if ($tab_params['mode'] === false) $tab_params['mode'] = 'multi';
		if ($tab_params['special'] === false) $tab_params['special'] = '';
		if ($tab_params['dependant'] === false) $tab_params['dependant'] = 'yes';

		// pass params to block with tab content
		$params['name'] = $params['render_as'];
		$special = $tab_params['special'] ? $tab_params['special'] : $this->Special;
		$params['prefix'] = trim($this->Prefix.'.'.$special, '.');

		$prefix_append = $this->Application->GetVar('prefix_append');
		if ($prefix_append) {
			$params['prefix'] .= $prefix_append;
		}

		$default_grid = array_key_exists('default_grid', $params) ? $params['default_grid'] : 'Default';
		$radio_grid = array_key_exists('radio_grid', $params) ? $params['radio_grid'] : 'Radio';

		$params['cat_prefix'] = trim('c.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.');
		$params['tab_mode'] = $tab_params['mode'];
		$params['grid_name'] = ($tab_params['mode'] == 'multi') ? $default_grid : $radio_grid;
		$params['tab_dependant'] = $tab_params['dependant'];
		$params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name

		if ($special == 'showall' || $special == 'user') {
			$params['grid_name'] .= 'ShowAll';
		}

		// use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag
		return $this->Application->ParseBlock($params, 1);
	}

	/**
	 * Show CachedNavbar of current item primary category
	 *
	 * @param Array $params
	 * @return string
	 */
	function CategoryName($params)
	{
		// show category cachednavbar of
		$object =& $this->getObject($params);
		$category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId');

		$category_path = $this->Application->getCache('category_paths', $category_id);
		if ($category_path === false) {
			// not chached
			if ($category_id > 0) {
				$cached_navbar = $object->GetField('CachedNavbar');

				if ($category_id == $object->GetDBField('ParentId')) {
					// parent category cached navbar is one element smaller, then current ones
					$cached_navbar = explode('&|&', $cached_navbar);
					array_pop($cached_navbar);
					$cached_navbar = implode('&|&', $cached_navbar);
				}
				else {
					// no relation with current category object -> query from db
					$language_id = (int)$this->Application->GetVar('m_lang');
					if (!$language_id) {
						$language_id = 1;
					}

					$sql = 'SELECT l' . $language_id . '_CachedNavbar
							FROM ' . $object->TableName . '
							WHERE ' . $object->IDField . ' = ' . $category_id;
					$cached_navbar = $this->Conn->GetOne($sql);
				}

				$cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $cached_navbar);

				$category_path = trim($this->CategoryName( Array('cat_id' => 0) ).' > '.str_replace('&|&', ' > ', $cached_navbar), ' > ');
			}
			else {
				$category_path = $this->Application->Phrase( $this->Application->ConfigValue('Root_Name') );
			}
			$this->Application->setCache('category_paths', $category_id, $category_path);
		}
		return $category_path;
	}

	// structure related

	/**
	 * Returns page object based on requested params
	 *
	 * @param Array $params
	 * @return PagesItem
	 */
	function &_getPage($params)
	{
		$page =& $this->Application->recallObject($this->Prefix . '.-virtual', null, $params);
		/* @var $page kDBItem */

		// 1. load by given id
		$page_id = array_key_exists('page_id', $params) ? $params['page_id'] : false;
		if ($page_id) {
			if ($page_id != $page->GetID()) {
				// load if different
				$page->Load($page_id);
			}

			return $page;
		}

		// 2. load by template
		$template = array_key_exists('page', $params) ? $params['page'] : '';
		if (!$template) {
			$template = $this->Application->GetVar('t');
		}

		// different path in structure AND design template differes from requested template
		$structure_path_match = strtolower( $page->GetDBField('NamedParentPath') ) == strtolower('Content/' . $template);
		$design_match = $page->GetDBField('CachedTemplate') == $template;

		if (!$structure_path_match && !$design_match) {
			// Same sql like in "c:getPassedID". Load, when current page object doesn't match requested page object
			$themes_helper =& $this->Application->recallObject('ThemesHelper');
			/* @var $themes_helper kThemesHelper */

			$page_id = $themes_helper->getPageByTemplate($template);

			$page->Load($page_id);
		}

		return $page;
	}

	/**
	 * Returns requested content block content of current or specified page
	 *
	 * @param Array $params
	 * @return string
	 */
	function ContentBlock($params)
	{
		$num = getArrayValue($params, 'num');
		if (!$num) {
			return 'NO CONTENT NUM SPECIFIED';
		}

		$page =& $this->_getPage($params);
		/* @var $page kDBItem */

		if (!$page->isLoaded()) {
			// page is not created yet => all blocks are empty
			return '';
		}

		$page_id = $page->GetID();

		$content =& $this->Application->recallObject('content.-block', null, Array ('skip_autoload' => true));
		/* @var $content kDBItem */

		$data = Array ('PageId' => $page_id, 'ContentNum' => $num);
		$content->Load($data);

		if (!$content->isLoaded()) {
			// bug: missing content blocks are created even if user have no SMS-management rights
			$content->SetFieldsFromHash($data);
			$content->Create();
		}

		$edit_code_before = $edit_code_after = '';

		if (EDITING_MODE == EDITING_MODE_CONTENT) {
			$bg_color = isset($params['bgcolor']) ? $params['bgcolor'] : '#ffffff';
			$url_params = Array (
				'pass'			=>	'm,c,content',
				'm_opener'		=>	'd',
				'c_id'			=>	$page->GetID(),
				'content_id'	=>	$content->GetID(),
				'front'			=>	1,
				'admin'			=>	1,
				'__URLENCODE__'	=>	1,
				'__NO_REWRITE__'=>	1,
				'escape'		=>	1,
				'index_file' => 'index.php',
//				'bgcolor' => $bg_color,
//				'__FORCE_SID__' => 1
			);

			// link from Front-End to admin, don't remove "index.php"
			$edit_url = $this->Application->HREF('categories/edit_content', ADMIN_DIRECTORY, $url_params, 'index.php');
			$edit_code_before = '
				<div class="cms-edit-btn-container">
					<div class="cms-edit-btn" onclick="$form_name=\'kf_cont_'.$content->GetID().'\'; std_edit_item(\'content\', \'categories/edit_content\');">
						<div class="cms-btn-image">
							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/content_mode.png" width="15" height="16" alt=""/>
						</div>
						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_EditContent', false, true) . ' '.(defined('DEBUG_MODE') && DEBUG_MODE ? " - #{$num}" : '').'</div>
					</div>
					<div class="cms-btn-content">';

			$edit_form = '<form method="POST" style="display: inline; margin: 0px" name="kf_cont_'.$content->GetID().'" id="kf_cont_'.$content->GetID().'" action="'.$edit_url.'">';
			$edit_form .= '<input type="hidden" name="c_id" value="'.$page->GetID().'"/>';
			$edit_form .= '<input type="hidden" name="content_id" value="'.$content->GetID().'"/>';
			$edit_form .= '<input type="hidden" name="front" value="1"/>';
			$edit_form .= '<input type="hidden" name="bgcolor" value="'.$bg_color.'"/>';
			$edit_form .= '<input type="hidden" name="m_lang" value="'.$this->Application->GetVar('m_lang').'"/>';
			$edit_form .= '</form>';

			$edit_code_after = '</div></div>';

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

		if ($this->Application->GetVar('_editor_preview_') == 1) {
			$data = $this->Application->RecallVar('_editor_preview_content_');
		} else {
			$data = $content->GetField('Content');
		}

		$data = $edit_code_before . $this->_transformContentBlockData($data, $params) . $edit_code_after;

		if ($data != '') {
			$this->Application->Parser->DataExists = true;
		}

		return $data;
	}

	/**
	 * Apply all kinds of content block data transformations without rewriting ContentBlock tag
	 *
	 * @param string $data
	 * @return string
	 */
	function _transformContentBlockData(&$data, $params)
	{
		return $data;
	}

	/**
	 * Returns current page name or page based on page/page_id parameters
	 *
	 * @param Array $params
	 * @return string
	 * @todo Used?
	 */
	function PageName($params)
	{
		$page =& $this->_getPage($params);

		return $page->GetDBField('Name');
	}

	/**
	 * Returns current/given page information
	 *
	 * @param Array $params
	 * @return string
	 */
	function PageInfo($params)
	{
		$page =& $this->_getPage($params);

		switch ($params['type']) {
			case 'title':
				$db_field = 'Title';
				break;

			case 'htmlhead_title':
				$db_field = 'Name';
				break;

			case 'meta_title':
				$db_field = 'MetaTitle';
				break;

			case 'meta_keywords':
				$db_field = 'MetaKeywords';
				$cat_field = 'Keywords';
				break;

			case 'meta_description':
				$db_field = 'MetaDescription';
				$cat_field = 'Description';
				break;

			case 'tracking':
			case 'index_tools':
				if (!EDITING_MODE) {
					$tracking = $page->GetDBField('IndexTools');
					return $tracking ? $tracking : $this->Application->ConfigValue('cms_DefaultTrackingCode');
				}
				// no break here on purpose

			default:
				return '';
		}

		$default = isset($params['default']) ? $params['default'] : '';
		$val = $page->GetField($db_field);
		if (!$default) {
			if ($this->Application->isModuleEnabled('In-Portal')) {
				if (!$val && ($params['type'] == 'meta_keywords' || $params['type'] == 'meta_description')) {
					// take category meta if it's not set for the page
					return $this->Application->ProcessParsedTag('c', 'Meta', Array('name' => $cat_field));
				}
			}
		}

		if (isset($params['force_default']) && $params['force_default']) {
			return $default;
		}

		if (preg_match('/^_Auto:/', $val)) {
			$val = $default;

			/*if ($db_field == 'Title') {
				$page->SetDBField($db_field, $default);
				$page->Update();
			}*/
		}
		elseif ($page->GetID() == false) {
			return $default;
		}

		return $val;
	}

	/**
	 * Includes admin css and js, that are required for cms usage on Front-Edn
	 *
	 *
	 * @param Array $params
	 * @return string
	 */
	function EditingScripts($params)
	{
		if ($this->Application->GetVar('admin_scripts_included') || !EDITING_MODE) {
			return ;
		}

		$this->Application->SetVar('admin_scripts_included', 1);

		$js_url = $this->Application->BaseURL() . 'core/admin_templates/js';

		$ret = '<link rel="stylesheet" href="' . $js_url . '/jquery/thickbox/thickbox.css" type="text/css" media="screen"/>' . "\n";
		$ret .= '<link rel="stylesheet" href="' . $js_url . '/../incs/cms.css" type="text/css" media="screen"/>' . "\n";

		if (EDITING_MODE == EDITING_MODE_DESIGN) {
			$ret .= '	<style type="text/css" media="all">
							div.movable-element .movable-header { cursor: move; }
						</style>';
		}

		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery.pack.js"></script>' . "\n";
		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery-ui.custom.min.js"></script>' . "\n";
		$ret .= '<script type="text/javascript" src="' . $js_url . '/is.js"></script>' . "\n";
		$ret .= '<script type="text/javascript" src="' . $js_url . '/application.js"></script>' . "\n";
		$ret .= '<script type="text/javascript" src="' . $js_url . '/script.js"></script>' . "\n";
		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/thickbox/thickbox.js"></script>' . "\n";
		$ret .= '<script type="text/javascript" src="' . $js_url . '/template_manager.js"></script>' . "\n";
		$ret .= '<script language="javascript">' . "\n";
		$ret .= "TB.pathToImage = '" . $js_url . "/jquery/thickbox/loadingAnimation.gif';" . "\n";

		$template = $this->Application->GetVar('t');
		$theme_id = $this->Application->GetVar('m_theme');

		$url_params = Array ('block' => '#BLOCK#', 'theme-file_event' => '#EVENT#', 'theme_id' => $theme_id, 'source' => $template, 'pass' => 'all,theme-file', 'front' => 1, 'm_opener' => 'd', '__NO_REWRITE__' => 1, 'no_amp' => 1);
		$edit_template_url = $this->Application->HREF('themes/template_edit', ADMIN_DIRECTORY, $url_params, 'index.php');

		$url_params = Array ('theme-file_event' => 'OnSaveLayout', 'source' => $template, 'pass' => 'all,theme-file', '__NO_REWRITE__' => 1, 'no_amp' => 1);
		$save_layout_url = $this->Application->HREF('index', '', $url_params);

		$this_url = $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', '__NO_REWRITE__' => 1, 'no_amp' => 1));
		$ret .= "var aTemplateManager = new TemplateManager('" . $edit_template_url . "', '" . $this_url . "', '" . $save_layout_url . "', " . (int)EDITING_MODE . ");\n";
		$ret .= "var main_title = '" . addslashes( $this->Application->ConfigValue('Site_Name') ) . "';" . "\n";

		$use_popups = (int)$this->Application->ConfigValue('UsePopups');
		$ret .= "var \$use_popups = " . ($use_popups > 0 ? 'true' : 'false') . ";\n";
		$ret .= "var \$modal_windows = " . ($use_popups == 2 ? 'true' : 'false') . ";\n";

		if (EDITING_MODE != EDITING_MODE_BROWSE) {
			$ret .= "var base_url = '" . $this->Application->BaseURL() . "';" . "\n";
			$ret .= 'TB.closeHtml = \'<img src="' . $js_url . '/../img/close_window15.gif" width="15" height="15" style="border-width: 0px;" alt="close"/><br/>\';' . "\n";

			$url_params = Array('m_theme' => '', 'pass' => 'm', 'm_opener' => 'r', '__NO_REWRITE__' => 1, 'no_amp' => 1);
			$browse_url = $this->Application->HREF('catalog/catalog', ADMIN_DIRECTORY, $url_params, 'index.php');
			$browse_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $browse_url);

			$ret .= '
				var topmost = window.top;

				topmost.document.title = document.title + \' - ' . addslashes($this->Application->Phrase('la_AdministrativeConsole', false)) . '\';
				t = \''.$this->Application->GetVar('t').'\';

				if (window.parent.frames["menu"] != undefined) {
					if ( $.isFunction(window.parent.frames["menu"].SyncActive) ) {
						window.parent.frames["menu"].SyncActive("' . $browse_url . '");
					}
				}
			';
		}

		$ret .= '</script>' . "\n";

		if (EDITING_MODE != EDITING_MODE_BROWSE) {
			// add form, so admin scripts could work
			$ret .= '<form id="kernel_form" name="kernel_form" enctype="multipart/form-data" method="post" action="' . $browse_url . '">
						<input type="hidden" name="MAX_FILE_SIZE" id="MAX_FILE_SIZE" value="' . MAX_UPLOAD_SIZE . '" />
						<input type="hidden" name="sid" id="sid" value="' . $this->Application->GetSID() . '" />
					</form>';
		}

		return $ret;
	}

	/**
	 * Prints "Edit Page" button on cms page
	 *
	 * @param Array $params
	 * @return string
	 */
	function EditPage($params)
	{
		if (!EDITING_MODE) {
			return '';
		}

		$display_mode = array_key_exists('mode', $params) ? $params['mode'] : false;
		$edit_code = '';

		$page =& $this->_getPage($params);

		if (!$page->isLoaded() || (($display_mode != 'end') && (EDITING_MODE == EDITING_MODE_BROWSE))) {
			// when "EditingScripts" tag is not used, make sure, that scripts are also included
			return $this->EditingScripts($params);
		}

		// show "EditPage" button only for pages, that exists in structure
		if ($display_mode != 'end') {
			$edit_btn = '';

			if (EDITING_MODE == EDITING_MODE_CONTENT) {
				$url_params = Array(
					'pass'			=>	'm,c',
					'm_opener'		=>	'd',
					'c_id'			=>	$page->GetID(),
					'c_mode'		=>	't',
					'c_event'		=>	'OnEdit',
					'front'			=>	1,
					'__URLENCODE__'	=>	1,
					'__NO_REWRITE__'=>	1,
					'escape'		=>	1,
					'index_file' => 'index.php',
				);

				$edit_url = $this->Application->HREF('categories/categories_edit', ADMIN_DIRECTORY, $url_params);

				$edit_btn .= '
					<div class="cms-section-properties-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onmouseover="window.status=\''.$edit_url.'\'; return true" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'c\', \'categories/categories_edit\');">
						<div class="cms-btn-image">
							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png" width="15" height="16" alt=""/>
						</div>
						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SectionProperties', false, true) . '</div>
					</div>' . "\n";
			} elseif (EDITING_MODE == EDITING_MODE_DESIGN) {
				$url_params = Array(
					'pass'			=>	'm,theme',
					'm_opener'		=>	'd',
					'theme_id'		=>	$this->Application->GetVar('m_theme'),
					'theme_mode'	=>	't',
					'theme_event'	=>	'OnEdit',
					'theme-file_id' =>	$this->_getThemeFileId(),
					'front'			=>	1,
					'__URLENCODE__'	=>	1,
					'__NO_REWRITE__'=>	1,
					'escape'		=>	1,
					'index_file' => 'index.php',
				);

				$edit_url = $this->Application->HREF('themes/file_edit', ADMIN_DIRECTORY, $url_params);

				$edit_btn .= '
					<div class="cms-layout-btn-container"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . '>
						<div class="cms-save-layout-btn" onclick="aTemplateManager.saveLayout(); return false;">
							<div class="cms-btn-image">
								<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/save_button.gif" width="16" height="16" alt=""/>
							</div>
							<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SaveChanges', false, true) . '</div>
						</div>
						<div class="cms-cancel-layout-btn" onclick="aTemplateManager.cancelLayout(); return false;">
							<div class="cms-btn-image">
								<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/cancel_button.gif" width="16" height="16" alt=""/>
							</div>
							<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_Cancel', false, true) . '</div>
						</div>
					</div>

					<div class="cms-section-properties-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onmouseover="window.status=\''.$edit_url.'\'; return true" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'theme\', \'themes/file_edit\');">
						<div class="cms-btn-image">
							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png" width="15" height="16" alt=""/>
						</div>
						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SectionTemplate', false, true) . '</div>
					</div>' . "\n";
			}

			if ($display_mode == 'start') {
				// button with border around the page
				$edit_code .= '<div class="cms-section-properties-btn-container">' . $edit_btn . '<div class="cms-btn-content">';

			}
			else {
				// button without border around the page
				$edit_code .= $edit_btn;
			}
		}

		if ($display_mode == 'end') {
			// draw border around the page
			$edit_code .= '</div></div>';
		}

		if ($display_mode != 'end') {
			$edit_code .= '<form method="POST" style="display: inline; margin: 0px" name="kf_'.$page->GetID().'" id="kf_'.$page->GetID().'" action="'.$edit_url.'"></form>';

			// when "EditingScripts" tag is not used, make sure, that scripts are also included
			$edit_code .= $this->EditingScripts($params);
		}

		return $edit_code;
	}

	function _getThemeFileId()
	{
		$template = $this->Application->GetVar('t');

		if (!$this->Application->TemplatesCache->TemplateExists($template) && !$this->Application->isAdmin) {
			$cms_handler =& $this->Application->recallObject($this->Prefix . '_EventHandler');
			/* @var $cms_handler CategoriesEventHandler */

			$template = ltrim($cms_handler->GetDesignTemplate(), '/');
		}

		$file_path = dirname($template) == '.' ? '' : '/' . dirname($template);
		$file_name = basename($template);

		$sql = 'SELECT FileId
				FROM ' . TABLE_PREFIX . 'ThemeFiles
				WHERE (ThemeId = ' . (int)$this->Application->GetVar('m_theme') . ') AND (FilePath = ' . $this->Conn->qstr($file_path) . ') AND (FileName = ' . $this->Conn->qstr($file_name . '.tpl') . ')';
		return $this->Conn->GetOne($sql);
	}

	/**
	 * Builds site menu
	 *
	 * @param Array $params
	 * @return string
	 */
	function CachedMenu($params)
	{
		$menu_helper =& $this->Application->recallObject('MenuHelper');
		/* @var $menu_helper MenuHelper */

		return $menu_helper->menuTag($this->getPrefixSpecial(), $params);
	}

	/**
	 * Trick to allow some kind of output formatting when using CachedMenu tag
	 *
	 * @param Array $params
	 * @return bool
	 */
	function SplitColumn($params)
	{
		return $this->Application->GetVar($params['i']) > ceil($params['total'] / $params['columns']);
	}

	/**
	 * Returns direct children count of given category
	 *
	 * @param Array $params
	 * @return int
	 */
	function HasSubCats($params)
	{
		$sql = 'SELECT COUNT(*)
				FROM ' . TABLE_PREFIX . 'Category
				WHERE ParentId = ' . $params['cat_id'];

		return $this->Conn->GetOne($sql);
	}

	/**
	 * Prints sub-pages of given/current page.
	 *
	 * @param Array $params
	 * @return string
	 * @todo This could be reached by using "parent_cat_id" parameter. Only difference here is new block parameter "path". Need to rewrite.
	 */
	function PrintSubPages($params)
	{
		$list =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List', $params);
		/* @var $list kDBList */

		$category_id = array_key_exists('category_id', $params) ? $params['category_id'] : $this->Application->GetVar('m_cat_id');

		$list->addFilter('current_pages', TABLE_PREFIX . 'CategoryItems.CategoryId = ' . $category_id);
		$list->Query();
		$list->GoFirst();

		$o = '';
		$block_params = $this->prepareTagParams($params);
		$block_params['name'] = $params['render_as'];

		while (!$list->EOL()) {
			$block_params['path'] = $list->GetDBField('Path');
			$o .= $this->Application->ParseBlock($block_params);

			$list->GoNext();
		}

		return $o;
	}

	/**
	 * Builds link for browsing current page on Front-End
	 *
	 * @param Array $params
	 * @return string
	 */
	function PageBrowseLink($params)
	{
		$object =& $this->getObject($params);

		$themes_helper =& $this->Application->recallObject('ThemesHelper');
		/* @var $themes_helper kThemesHelper */

		$site_config_helper =& $this->Application->recallObject('SiteConfigHelper');
		/* @var $site_config_helper SiteConfigHelper */

		$settings = $site_config_helper->getSettings();

		$url_params = Array (
			'm_cat_id' => $object->GetID(),
			'm_theme' => $themes_helper->getCurrentThemeId(),
			'editing_mode' => $settings['default_editing_mode'],
			'pass' => 'm',
			'admin' => 1,
			'index_file' => 'index.php'
		);

		if ($this->Application->ConfigValue('UseModRewrite')) {
			$url_params['__MOD_REWRITE__'] = 1;
		}

		return $this->Application->HREF($object->GetDBField('NamedParentPath'), '_FRONT_END_', $url_params);
	}

	/**
	 * Builds link to cms page (used?)
	 *
	 * @param Array $params
	 * @return string
	 */
	function ContentPageLink($params)
	{
		$object =& $this->getObject($params);
		$params['t'] = $object->GetDBField('NamedParentPath');
		$params['m_cat_id'] = 0;

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

	/**
	 * Prepares cms page description for search result page
	 *
	 * @param Array $params
	 * @return string
	 */
	function SearchDescription($params)
	{
		$object =& $this->getObject($params);
		$desc =  $object->GetField('MetaDescription');
		if (!$desc) {
			$sql = 'SELECT *
					FROM ' . TABLE_PREFIX . 'PageContent
					WHERE PageId = ' . $object->GetID() . ' AND ContentNum = 1';
			$content = $this->Conn->GetRow($sql);

			if ($content['l'.$this->Application->GetVar('m_lang').'_Content']) {
				$desc = $content['l'.$this->Application->GetVar('m_lang').'_Content'];
			}
			else {
				$desc = $content['l'.$this->Application->GetDefaultLanguageId().'_Content'];
			}
		}

		return mb_substr($desc, 0, 300).(mb_strlen($desc) > 300 ? '...' : '');
	}

	/**
	 * Simplified version of "c:CategoryLink" for "c:PrintList"
	 *
	 * @param Array $params
	 * @return string
	 * @todo Used? Needs refactoring.
	 */
	function EnterCatLink($params)
	{
		$object =& $this->getObject($params);

		$url_params = Array ('pass' => 'm', 'm_cat_id' => $object->GetID());
		return $this->Application->HREF($params['template'], '', $url_params);
	}

	/**
	 * Simplified version of "c:CategoryPath", that do not use blocks for rendering
	 *
	 * @param Array $params
	 * @return string
	 * @todo Used? Maybe needs to be removed.
	 */
	function PagePath($params)
	{
		$object =& $this->getObject($params);
		$path = $object->GetField('CachedNavbar');
		if ($path) {
			$items = explode('&|&', $path);
			array_shift($items);
			return implode(' -&gt; ', $items);
		}

		return '';
	}

	/**
	 * Returns configuration variable value
	 *
	 * @param Array $params
	 * @return string
	 * @todo Needs to be replaced with "m:GetConfig" tag; Not used now (were used on structure_edit.tpl).
	 */
	function AllowManualFilenames($params)
	{
		return $this->Application->ConfigValue('ProjCMSAllowManualFilenames');
	}

	/**
	 * Draws path to current page (each page can be link to it)
	 *
	 * @param Array $params
	 * @return string
	 */
	function CurrentPath($params)
	{
		$block_params = $this->prepareTagParams($params);
		$block_params['name'] = $block_params['render_as'];

		$object =& $this->Application->recallObject($this->Prefix);
		/* @var $object kDBItem */

		$category_ids = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));

		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');

		$language = (int)$this->Application->GetVar('m_lang');

		if (!$language) {
			$language = 1;
		}

		$sql = 'SELECT l'.$language.'_Name AS Name, NamedParentPath
				FROM '.$table_name.'
				WHERE '.$id_field.' IN ('.implode(',', $category_ids).')';
		$categories_data = $this->Conn->Query($sql);

		$ret = '';
		foreach ($categories_data as $index => $category_data) {
			if ($category_data['Name'] == 'Content') {
				continue;
			}
			$block_params['title'] = $category_data['Name'];
			$block_params['template'] = preg_replace('/^Content\//i', '', $category_data['NamedParentPath']);
			$block_params['is_first'] = $index == 1; // because Content is 1st element
			$block_params['is_last'] = $index == count($categories_data) - 1;

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

		return $ret;
	}

	/**
	 * Synonim to PrintList2 for "onlinestore" theme
	 *
	 * @param Array $params
	 * @return string
	 */
	function ListPages($params)
	{
		return $this->PrintList2($params);
	}

	/**
	 * Returns information about parser element locations in template
	 *
	 * @param Array $params
	 * @return mixed
	 */
	function BlockInfo($params)
	{
		if (!EDITING_MODE) {
			return '';
		}

		$template_helper =& $this->Application->recallObject('TemplateHelper');
		/* @var $template_helper TemplateHelper */

		return $template_helper->blockInfo( $params['name'] );
	}

	/**
	 * Hide all editing tabs except permission tab, when editing "Home" (ID = 0) category
	 *
	 * @param Array $params
	 */
	function ModifyUnitConfig($params)
	{
		$root_category = $this->Application->RecallVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
		if (!$root_category) {
			return ;
		}

		$edit_tab_presets = $this->Application->getUnitOption($this->Prefix, 'EditTabPresets');
		$edit_tab_presets['Default'] = Array (
			'permissions' => $edit_tab_presets['Default']['permissions'],
		);
		$this->Application->setUnitOption($this->Prefix, 'EditTabPresets', $edit_tab_presets);
	}

	/**
	 * Prints catalog export templates
	 *
	 * @param Array $params
	 * @return string
	 */
	function PrintCatalogExportTemplates($params)
	{
		$prefixes = explode(',', $params['prefixes']);

		$ret = Array ();
		foreach ($prefixes as $prefix) {
			if ($this->Application->prefixRegistred($prefix)) {
				$ret[$prefix] = $this->Application->getUnitOption($prefix, 'ModuleFolder') . '/export';
			}
		}

		$json_helper =& $this->Application->recallObject('JSONHelper');
		/* @var $json_helper JSONHelper */

		return $json_helper->encode($ret);
	}

	/**
	 * Checks, that "view in browse mode" functionality available
	 *
	 * @param Array $params
	 * @return bool
	 */
	function BrowseModeAvailable($params)
	{
		$valid_special = $params['Special'] != 'user';
		$not_selector = $this->Application->GetVar('type') != 'item_selector';

		return $valid_special && $not_selector;
	}

	/**
	 * Returns a link for editing product
	 *
	 * @param Array $params
	 * @return string
	 */
	function ItemEditLink($params)
	{
		$object =& $this->getObject();
		/* @var $object kDBList */

		$edit_template = $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePath') . '/' . $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePrefix') . 'edit';

		$url_params = Array (
			'm_opener'				=>	'd',
			$this->Prefix.'_mode'	=>	't',
			$this->Prefix.'_event'	=>	'OnEdit',
			$this->Prefix.'_id'		=>	$object->GetID(),
			'm_cat_id'				=>	$object->GetDBField('ParentId'),
			'pass'					=>	'all,'.$this->Prefix,
			'no_pass_through'		=>	1,
		);

		return $this->Application->HREF($edit_template,'', $url_params);
	}

	function RelevanceIndicator($params)
	{
		$object =& $this->getObject($params);

		$search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
		$sql = 'SELECT Relevance
				FROM '.$search_results_table.'
				WHERE ResourceId = '.$object->GetDBField('ResourceId');

    	$percents_off = (int)(100 - (100 * $this->Conn->GetOne($sql)));
    	$percents_off = ($percents_off < 0) ? 0 : $percents_off;
    	if ($percents_off) {
        	$params['percent_off'] = $percents_off;
    		$params['percent_on'] = 100 - $percents_off;
        	$params['name'] = $this->SelectParam($params, 'relevance_normal_render_as,block_relevance_normal');
    	}
    	else {
    		$params['name'] = $this->SelectParam($params, 'relevance_full_render_as,block_relevance_full');
    	}
    	return $this->Application->ParseBlock($params);
	}
}