<?php
/**
* @version	$Id: categories_tag_processor.php 15390 2012-06-12 16:25:51Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2011 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 CategoriesTagProcessor extends kDBTagProcessor {

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

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

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

		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'];
		$icon_prefix = array_key_exists('icon_prefix', $params)? $params['icon_prefix'] : 'icon16_';

		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('ThemeId') > 0) {
			if (!$object->GetDBField('IsMenu')) {
				return $icon_prefix . 'section_menuhidden_system.png';
			}
			return $icon_prefix . 'section_system.png';
		}

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

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

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

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

		if ($object->GetDBField('IsNew') && ($icon_prefix == 'icon16_')) {
			return $icon_prefix . 'section_new.png'; // show gris icon only in grids
		}

		return $icon_prefix . 'section.png';
	}

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

		$ci_table = $this->Application->getUnitOption('ci', 'TableName');

		$module_prefixes = implode(',', $this->Conn->qstrArray($this->_getModulePrefixes()));

		$sql = 'SELECT COUNT(*)
				FROM ' . $object->TableName . ' c
				JOIN ' . $ci_table . ' ci ON c.CategoryId = ci.CategoryId
				WHERE (c.TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight') . ') AND (ci.ItemPrefix IN (' . $module_prefixes . '))';

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

	function _getModulePrefixes()
	{
		$ret = Array ();

		foreach ($this->Application->ModuleInfo as $module_info) {
			$ret[] = $module_info['Var'];
		}

		return array_unique($ret);
	}

	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)
	{
		$navigation_bar = $this->Application->recallObject('kNavigationBar');
		/* @var $navigation_bar kNavigationBar */

		return $navigation_bar->build($params);
	}

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

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

		if ($category_id) {
			$params['cat_id'] = $category_id;

			$navigation_bar = $this->Application->recallObject('kNavigationBar');
			/* @var $navigation_bar kNavigationBar */

			return $navigation_bar->build($params);
		}

		return '';
	}

	function CurrentCategoryName($params)
	{
		$cat_object = $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List');
		/* @var $cat_object kDBList */

		$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');
		/* @var $ml_formatter kMultiLanguage */

		return $ml_formatter->LangFieldName('Name');
	}

	/**
	 * Returns symlinked category for given category
	 *
	 * @param int $category_id
	 * @return int
	 */
	function getCategorySymLink($category_id)
	{
		if (!$category_id) {
			// don't bother to get symlink for "Home" category
			return $category_id;
		}

		$cache_key = 'category_symlinks[%CSerial%]';
		$cache = $this->Application->getCache($cache_key);

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

			// get symlinked categories, that are not yet deleted
			$this->Conn->nextQueryCachable = true;
			$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);

			$this->Application->setCache($cache_key, $cache);
		}

		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);
		}
		else {
			unset($params['direct_link']);
		}

		$virtual_template = $this->Application->getVirtualPageTemplate($category_id);

		if ( ($virtual_template !== false) && preg_match('/external:(.*)/', $virtual_template, $rets) ) {
			// external url (return here, instead of always replacing $params['t'] for kApplication::HREF to find it)
			return $rets[1];
		}

		unset($params['cat_id'], $params['module']);
		$new_params = Array ('pass' => 'm', 'm_cat_id' => $category_id, 'pass_category' => 1);
		$params = array_merge($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->GetRecordsCount() == 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
				$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
				/* @var $rewrite_processor kRewriteUrlProcessor */

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

				$item_template = $rewrite_processor->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->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 = $this->Application->getBaseCategory();
		$category_id = $this->Application->GetVar('m_cat_id');

		if ($category_id != $parent_id) {
			$sql = 'SELECT ParentId
					FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . '
					WHERE ' . $this->Application->getUnitOption($this->Prefix, 'IDField') . ' = ' . $category_id;
			$parent_id = $this->Conn->GetOne($sql);
		}

		return $parent_id;
	}

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

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

		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->makeClass('kPermCacheUpdater', Array($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, update left tree and redirect
				$updater->SaveData();

				$this->Application->HandleEvent(new kEvent('c:OnResetCMSMenuCache'));

				$this->Application->RemoveVar('PermCache_UpdateRequired');
				$this->Application->StoreVar('RefreshStructureTree', 1);
				$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
	 * @access protected
	 */
	protected function SaveWarning($params)
	{
		if ( $this->Prefix == 'st' ) {
			// don't use this method for other prefixes then Categories, 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);
		}

		return $temp_tables && $modified ? 1 : 0;
	}

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

		return $object->IsRoot();
	}

	/**
	 * Returns home category id
	 *
	 * @param Array $params
	 * @return int
	 */
	function HomeCategory($params)
	{
		return $this->Application->getBaseCategory();
	}

	/**
	 * 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->Application->getBaseCategory();
	}

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

		return $object->GetRecordsCount(false) != $object->GetRecordsCount() ? $object->GetRecordsCount().' / '.$object->GetRecordsCount(false) : $object->GetRecordsCount();
	}

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

			if ( $template ) {
				// when using from "c:CachedMenu" tag
				$sql = 'SELECT ParentPath
						FROM ' . TABLE_PREFIX . 'Categories
						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));
					/* @var $category CategoriesItem */

					$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 = (int)$this->Application->GetVar('m_cat_id');
		$local = array_key_exists('local', $params) && ($category_id > 0) ? $params['local'] : false;

		$serial_name = $this->Application->incrementCacheSerial('c', $local ? $category_id : null, false);
		$cache_key = 'category_last_updated[%' . $serial_name . '%]';

		$row_data = $this->Application->getCache($cache_key);

		if ( $row_data === false ) {
			if ( $local && ($category_id > 0) ) {
				// scan only current category & it's children
				list ($tree_left, $tree_right) = $this->Application->getTreeIndex($category_id);

				$sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate
		        		FROM ' . TABLE_PREFIX . 'Categories
		        		WHERE TreeLeft BETWEEN ' . $tree_left . ' AND ' . $tree_right;
			}
			else {
				// scan all categories in system
				$sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate
		       			FROM ' . TABLE_PREFIX . 'Categories';
			}

			$this->Conn->nextQueryCachable = true;
			$row_data = $this->Conn->GetRow($sql);
			$this->Application->setCache($cache_key, $row_data);
		}

		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');
			/* @var $lang LanguagesItem */

			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)
	{
		$params['m_cat_page'] = $this->Application->GetVar($this->getPrefixSpecial() . '_Page');

		return parent::PageLink($params);
	}

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

		// 1. try to get already cached suggestion
		$cache_key = 'search.suggestion[%SpellingDictionary%]:' . $keywords;
		$suggestion = $this->Application->getCache($cache_key);

		if ( $suggestion !== false ) {
			return $suggestion;
		}

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

		// 2. search suggestion in database
		$this->Conn->nextQueryCachable = true;
		$sql = 'SELECT SuggestedCorrection
				FROM ' . $table_name . '
				WHERE MisspelledWord = ' . $this->Conn->qstr($keywords);
		$suggestion = $this->Conn->GetOne($sql);

		if ( $suggestion !== false ) {
			$this->Application->setCache($cache_key, $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);
		/* @var $root_node kXMLNode */

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

		$category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId');

		$cache_key = 'category_paths[%CIDSerial:' . $category_id . '%][%PhrasesSerial%][Adm:' . (int)$this->Application->isAdmin . ']';
		$category_path = $this->Application->getCache($cache_key);

		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->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
			}

			$this->Application->setCache($cache_key, $category_path);
		}

		return $category_path;
	}

	// structure related

	/**
	 * Returns page object based on requested params
	 *
	 * @param Array $params
	 * @return CategoriesItem
	 */
	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 = mb_strtolower( $page->GetDBField('NamedParentPath') ) == mb_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 ) {
			$name = getArrayValue($params, 'name');

			if ( $name ) {
				$num = kUtil::crc32($name);
			}
		}

		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_helper = $this->Application->recallObject('PageHelper');
		/* @var $page_helper PageHelper */

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

		if ( !$page_helper->loadContentBlock($content, $page, $num) && EDITING_MODE ) {
			$page_helper->createNewContentBlock($page->GetID(), $num);
			$page_helper->loadContentBlock($content, $page, $num);
		}

		$edit_code_before = $edit_code_after = '';

		if ( EDITING_MODE == EDITING_MODE_CONTENT ) {
			$button_code = $this->Application->ProcessParsedTag($content->getPrefixSpecial(), 'AdminEditButton', $params);

			$edit_code_before = '
				<div class="cms-edit-btn-container">
					' . $button_code . '
					<div class="cms-btn-content">';

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

		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
	 * @param Array $params
	 * @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':
				// TODO: rename column to SectionTitle
				$db_field = 'Name'; // "Section Title" - title to show on page (e.g. in <h1> tag)
				break;

			case 'htmlhead_title':
				// TODO: rename column to HtmlTitle
				$db_field = 'Title'; // "Title (on Page)" - in <title> html tag
				break;

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

			case 'menu_title':
				$db_field = 'MenuTitle'; // "Title (Menu Item)" - in menu and navigation bar
				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
	 * @access protected
	 */
	protected 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';

		$minify_helper = $this->Application->recallObject('MinifyHelper');
		/* @var $minify_helper MinifyHelper */

		$to_compress = Array (
			$js_url . '/jquery/thickbox/thickbox.css',
			$js_url . '/../incs/cms.css',
			$js_url . '/../img/toolbar/toolbar-sprite.css',
		);

		$css_compressed = $minify_helper->CompressScriptTag(Array ('files' => implode('|', $to_compress), 'templates_base' => $js_url . '/../'));

		$ret = '<link rel="stylesheet" href="' . $css_compressed . '" type="text/css" media="screen"/>' . "\n";

		$ret .= '	<!--[if IE]>
					<link rel="stylesheet" href="' . $js_url . '/../incs/cms_ie.css' . '" type="text/css" media="screen"/>
					<![endif]-->';

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

		$to_compress = Array (
			$js_url . '/is.js',
			$js_url . '/application.js',
			$js_url . '/script.js',
			$js_url . '/toolbar.js',
			$js_url . '/jquery/thickbox/thickbox.js',
			$js_url . '/template_manager.js',
		);

		$js_compressed = $minify_helper->CompressScriptTag( Array ('files' => implode('|', $to_compress)) );

		$ret .= '<script type="text/javascript" src="' . $js_compressed . '"></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);

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

		$url_params = Array(
			'pass'			=>	'm,c',
			'c_id'			=>	$page->GetID(),
			'c_event'		=>	'OnGetPageInfo',
			'__URLENCODE__'	=>	1,
			'__NO_REWRITE__'=>	1,
			'index_file' => 'index.php',
		);

		$page_helper = $this->Application->recallObject('PageHelper');
		/* @var $page_helper PageHelper */

		$class_params = Array (
			'pageId' => $page->GetID(),
			'pageInfo' => $page_helper->getPageInfo( $page->GetID() ),
			'editUrl' => $edit_template_url,
			'browseUrl' => $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', '__NO_REWRITE__' => 1, 'no_amp' => 1)),
			'saveLayoutUrl' => $save_layout_url,
			'editingMode' => (int)EDITING_MODE,
		);

		$ret .= "var aTemplateManager = new TemplateManager(" . json_encode($class_params) . ");\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 $visible_toolbar_buttons = true' . ";\n";
			$ret .= 'var $use_toolbarlabels = ' . ($this->Application->ConfigValue('UseToolbarLabels') ? 'true' : 'false') . ";\n";;

			$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 .= '
				set_window_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 ( $this->Application->GetVar('preview') ) {
			// prevents draft preview function to replace last template in session and break page/content block editing process
			$this->Application->SetVar('skip_last_template', 1);
		}

		if (!EDITING_MODE) {
			return '';
		}

		$display_mode = array_key_exists('mode', $params) ? $params['mode'] : false;
		unset($params['mode']);
		$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 = $edit_url = '';

			if ( EDITING_MODE == EDITING_MODE_CONTENT ) {
				$item_prefix = isset($params['item_prefix']) ? $params['item_prefix'] : '';
				unset($params['item_prefix']);

				if ( $item_prefix ) {
					$params['button_class'] = 'cms-section-properties-btn';
					$edit_btn = $this->Application->ProcessParsedTag($item_prefix, 'AdminEditButton', $params) . "\n";
				}
				else {
					$edit_btn = $this->AdminEditButton($params) . "\n"; // "st" object must be loaded before this
				}
			}
			elseif ( EDITING_MODE == EDITING_MODE_DESIGN ) {
				$url_params = Array(
					'pass'			=>	'm,theme,theme-file',
					'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,
					'index_file' => 'index.php',
				);

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

				$button1_icon = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/save_button.gif';
				$button1_title = $this->Application->Phrase('la_btn_SaveChanges', false, true);
				$button1_code = '<button style="background-image: url(' . $button1_icon . '); onclick="aTemplateManager.saveLayout(); return false;" class="cms-btn-new cms-save-layout-btn">' . $button1_title . '</button>';

				$button2_icon = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/cancel_button.gif';
				$button2_title = $this->Application->Phrase('la_btn_Cancel', false, true);
				$button2_code = '<button style="background-image: url(' . $button2_icon . '); onclick="aTemplateManager.cancelLayout(); return false;" class="cms-btn-new cms-cancel-layout-btn">' . $button2_title . '</button>';

				$button3_icon = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png';
				$button3_title = $this->Application->Phrase('la_btn_SectionTemplate', false, true);
				$button3_code = '<button style="background-image: url(' . $button3_icon . ');' . ($display_mode === false ? ' margin: 0px;' : '') . '" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'theme\', \'themes/file_edit\');" class="cms-btn-new cms-section-properties-btn">' . $button3_title . '</button>';

				$edit_btn .= '<div class="cms-layout-btn-container"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . '>' . $button1_code . $button2_code . '</div>' . $button3_code . "\n";
			}

			if ( $display_mode == 'start' ) {
				// button with border around the page
				if ( EDITING_MODE == EDITING_MODE_CONTENT ) {
					$tabs = "\n" . str_repeat("\t", 9);
					$base_url = $this->Application->BaseURL();
					$toolbar_hidden = $this->Application->GetVar('toolbar_hidden');

					$edit_code .= '
						<div>
							<div id="cms-editing-notice">
								<div class="top">
									<a href="#" id="cms-close-editing-notice"></a>
									<span prev_editors=""></span>
								</div>
								<div class="bottom"></div>
							</div>

							<div id="cms-revision-dropdown">
								<div class="top"></div>
								<div class="bottom"></div>
							</div>
						</div>';

					if ( $this->Application->ConfigValue('EnablePageContentRevisionControl') ) {
						$edit_code .= '<div id="cms-revision-toolbar-layer"' . ($toolbar_hidden ? ' style="top: -56px;"' : '') . '>
											<div id="cms-revision-toolbar">
												<script type="text/javascript">
													var a_toolbar = new ToolBar(undefined, undefined, "' . $base_url . '#MODULE#/admin_templates/img/");
													' . $this->toolbarButton('select', 'la_ToolTip_Save', $tabs) . $this->toolbarButton('delete', 'la_ToolTip_Discard', $tabs) . $tabs . 'a_toolbar.AddButton( new ToolBarSeparator("sep1") );';

						if ( $this->Application->CheckAdminPermission('CATEGORY.REVISION.MODERATE', 0) ) {
							$edit_code .= $this->toolbarButton('approve', 'la_ToolTip_Publish', $tabs) . $this->toolbarButton('decline', 'la_ToolTip_Decline', $tabs) . $tabs . 'a_toolbar.AddButton( new ToolBarSeparator("sep2") );';
						}

						$edit_code .= $this->toolbarButton('preview', 'la_ToolTip_Preview', $tabs);

						if ( $this->Application->CheckAdminPermission('CATEGORY.REVISION.HISTORY.VIEW', 0) ) {
							$edit_code .= $this->toolbarButton('history', 'la_ToolTip_History', $tabs);
						}

						$edit_code .= $tabs . 'a_toolbar.Render();' . "\n";

						$revision = $this->Application->recallObject('page-revision.current');
						/* @var $revision kDBItem */

						if ( !$revision->GetDBField('IsDraft') ) {
							$edit_code .= $tabs . 'a_toolbar.DisableButton("select");' . $tabs . 'a_toolbar.DisableButton("delete");' . $tabs . 'a_toolbar.DisableButton("preview");';
						}

						if ( $revision->GetDBField('Status') == STATUS_ACTIVE || $revision->GetDBField('IsDraft') ) {
							$edit_code .= $tabs . 'a_toolbar.DisableButton("approve");';
						}

						if ( $revision->GetDBField('Status') == STATUS_DISABLED || $revision->GetDBField('IsLive') || $revision->GetDBField('IsDraft') ) {
							$edit_code .= $tabs . 'a_toolbar.DisableButton("decline");';
						}

						$publishing_tools = $this->Application->Phrase('la_btn_PublishingTools', false, true);

						$edit_code .= substr($tabs, 0, -1) . '</script>

									<div id="cms-current-revision-info">
										<span class="revision-title"></span>
										<div class="draft-saved"></div>
									</div>

									<a href="#" id="cms-close-toolbar"></a>
									<div class="cms-clear"></div>
								</div>

								<a href="#" id="cms-toggle-revision-toolbar"' . ($toolbar_hidden ? '' : ' class="opened"') . '><span>' . $publishing_tools . '</span></a>
							</div>' . "\n";
					}
				}

				$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') {
			if ( EDITING_MODE == EDITING_MODE_CONTENT ) {
				$url_params = Array(
					'pass'			=>	'm',
					'm_opener'		=>	'd',
					'm_cat_id'		=>	$page->GetID(),
					'__URLENCODE__'	=>	1,
					'__NO_REWRITE__'=>	1,
					'front'			=>	1,
					'index_file' => 'index.php',
				);

				$revision = $this->Application->GetVar('revision');

				if ( $revision ) {
					$url_params['revision'] = $revision;
				}

				$page_admin_url = $this->Application->HREF('', ADMIN_DIRECTORY, $url_params);
				$edit_code .= '<form method="POST" style="display: inline; margin: 0px" name="kf_revisions_'.$page->GetID().'" id="kf_revisions_'.$page->GetID().'" action="' . $page_admin_url . '">
					<input type="hidden" name="revision" value="' . $this->Application->GetVar('revision', 0) . '"/>
				</form>';
			}

			if ( $edit_url ) {
				$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 toolbarButton($name, $title, $tabs)
	{
		$phrase = $this->Application->Phrase($title, false, true);

		return $tabs . 'a_toolbar.AddButton( new ToolBarButton("' . $name . '", "' . htmlspecialchars($phrase) . '") );';
	}

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

	/**
	 * 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 */

		$params['item_prefix'] = 'c';

		if ( $this->Prefix == 'st' ) {
			$params['button_icon'] = 'section_properties.png';
			$params['button_class'] = 'cms-section-properties-btn';
			$params['button_title'] = 'la_btn_SectionProperties';
		}

		return parent::AdminEditButton($params);
	}

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

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

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

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

	/**
	 * Builds a link for securely accessing a page later (even if it will not be publicly accessible)
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected function DirectLink($params)
	{
		$object = $this->getObject($params);
		/* @var $object kDBItem */

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

		$url_params = Array (
			'm_cat_id' => $object->GetID(),
			'm_theme' => $themes_helper->getCurrentThemeId(),
			'pass' => 'm',
			'authkey' => $object->GetDBField('DirectLinkAuthKey'),
			'__SSL__' => 0,
			'__NO_SID__' => 0,
		);

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

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

		$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)) {
				$module_path = $this->Application->getUnitOption($prefix, 'ModuleFolder') . '/';
				$module_name = $this->Application->findModule('Path', $module_path, 'Name');

				$ret[$prefix] = mb_strtolower($module_name) . '/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($params);
		/* @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);
		/* @var $object kDBItem */

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

	/**
	 * Returns list of categories, that have category add/edit permission
	 *
	 * @param Array $params
	 * @return string
	 */
	function AllowedCategoriesJSON($params)
	{
		if ($this->Application->RecallVar('user_id') == USER_ROOT) {
			$categories = true;
		}
		else {
			$object = $this->getObject($params);
			/* @var $object kDBItem */

			$perm_helper = $this->Application->recallObject('PermissionsHelper');
			/* @var $perm_helper kPermissionsHelper */

			$perm_prefix = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix');
			$categories = $perm_helper->getPermissionCategories($perm_prefix . '.' . ($object->IsNewItem() ? 'ADD' : 'MODIFY'));
		}

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

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

	function PageEditable($params)
	{
		if ($this->Application->isDebugMode()) {
			return true;
		}

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

		return !$object->GetDBField('Protected');
	}

	/**
	 * Returns element for "__item__" navigation bar part
	 *
	 * @param Array $params
	 * @return string
	 * @access protected
	 */
	protected function CategoryItemElement($params)
	{
		$category_helper = $this->Application->recallObject('CategoryHelper');
		/* @var $category_helper CategoryHelper */

		$navigation_bar = $this->Application->recallObject('kNavigationBar');
		/* @var $navigation_bar kNavigationBar */

		$category_id = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id');
		$parent_path = explode('|', substr($navigation_bar->getParentPath($category_id), 1, -1));
		array_shift($parent_path); // remove "Content" category
		$module_info = $category_helper->getCategoryModule($params, $parent_path);

		if ( !$module_info ) {
			return '';
		}

		$module_prefix = $module_info['Var'];

		$object = $this->Application->recallObject($module_prefix);
		/* @var $object kCatDBItem */

		$title_field = $this->Application->getUnitOption($module_prefix, 'TitleField');
		$block_params = $this->prepareTagParams($params);
		$block_params['name'] = $params['render_as'];

		$block_params['title'] = $object->GetField($title_field);
		$block_params['prefix'] = $module_prefix;

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