Index: branches/RC/themes/default2009/platform/designs/default_design.des.tpl
===================================================================
--- branches/RC/themes/default2009/platform/designs/default_design.des.tpl	(revision 11962)
+++ branches/RC/themes/default2009/platform/designs/default_design.des.tpl	(revision 11963)
@@ -1,123 +1,123 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <inp2:m_CheckSSL/>
 <inp2:m_include template="platform/elements/side_boxes.elm" strip_nl="1"/>
 <inp2:m_include template="platform/elements/content_boxes.elm" strip_nl="1"/>
 <inp2:m_include template="platform/elements/forms.elm" strip_nl="1"/>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 	<head>
 		<inp2:m_DefineElement name="cms_page_title">
 			<inp2:st_PageInfo type="htmlhead_title"/>
 		</inp2:m_DefineElement>
 
 		<title><inp2:m_GetConfig name="Site_Name"/> :: <inp2:m_RenderElement name="page_title" default_element="cms_page_title" no_editing="1"/></title>
 
 		<!--## Include module specific HEADER (META INFORMATION inside) template ##-->
 		<inp2:m_ModuleInclude template="elements/html_head.elm" in-portal_template="platform/elements/html_head.elm"/>
 
 		<inp2:st_EditingScripts/>
 		<!--## /Include module specific HEADER template ##-->
 	</head>
 	<body>
 		<inp2:st_EditPage mode="start"/>
 		<div align="left">
 			<div align="left" style="width:100%">
 				<table class="fullwidth">
 					<tr>
 						<td>
 							<inp2:m_include template="platform/elements/header.elm"/>
 						</td>
 					</tr>
 					<tr>
 						<td>
 							<inp2:m_include template="platform/elements/menu.elm"/>
 						</td>
 					</tr>
 				</table>
 
 				<img src="<inp2:m_TemplatesBase module="In-Portal"/>img/s.gif" width="1" height="1" alt=""/><br />
 				<img src="<inp2:m_TemplatesBase module="In-Portal"/>img/grey_pix.gif" width="100%" height="1" alt=""/><br />
 				<img src="<inp2:m_TemplatesBase module="In-Portal"/>img/s.gif" width="1" height="1" alt=""/><br />
 
 				<table class="fullwidth">
 					<tr>
 						<!-- SIDEBAR -->
 						<td style="width: 200px;" valign="top">
-							<div class="movable-area">
-								<inp2:m_DefineElement name="cms_sidebar">
+							<inp2:m_DefineElement name="cms_sidebar">
+								<div class="movable-area">
 									<div class="movable-element">
 										<inp2:m_RenderElement name="platform/elements/side_boxes/login.elm" design="blue_box"/>
 									</div>
 									<div class="movable-element">
 										<inp2:m_RenderElement name="platform/elements/side_boxes/search.elm" design="blue_box"/>
 									</div>
-								</inp2:m_DefineElement>
+								</div>
+							</inp2:m_DefineElement>
 
-								<inp2:m_RenderElement name="sidebar" default_element="cms_sidebar" layout_view="1"/>
-							</div>
+							<inp2:m_RenderElement name="sidebar" default_element="cms_sidebar" layout_view="1"/>
 						</td>
 						<!-- /SIDEBAR -->
 
 						<!-- SEPARATOR -->
 						<td width="3" class="vertical-separator" style="width: 3px;">
 							<img src="<inp2:m_TemplatesBase module="In-Portal"/>img/s.gif" width="3" height="1" alt=""/><br />
 						</td>
 						<!-- /SEPARATOR -->
 
 						<!-- CONTENT -->
 						<td style="width: auto;" valign="top">
-							<div class="movable-area">
-								<inp2:m_DefineElement name="cms_content">
+							<inp2:m_DefineElement name="cms_content">
+								<div class="movable-area">
 									<div class="movable-element">
 										<inp2:m_include template="platform/elements/navigation_bar.elm"/>
 									</div>
 
 									<div class="movable-element">
 										<inp2:m_RenderElement design="content_box">
 											<inp2:m_Capture to_var="header">
 												<inp2:st_PageInfo type="title"/>
 											</inp2:m_Capture>
 
 											<inp2:st_ContentBlock num="1"/>
 										</inp2:m_RenderElement>
 									</div>
-								</inp2:m_DefineElement>
+								</div>
+							</inp2:m_DefineElement>
 
-								<inp2:m_RenderElement name="content" default_element="cms_content" layout_view="1"/>
-							</div>
+							<inp2:m_RenderElement name="content" default_element="cms_content" layout_view="1"/>
 							<br />
 						</td>
 						<!-- /CONTENT -->
 
 						<!--## REMOVE THIS LINE TO UNCOMMENT
 						<!-- SEPARATOR -->
 						<td width="3" class="vertical-separator">
 							<img src="<inp2:m_TemplatesBase module="In-Portal"/>img/s.gif" width="3" height="1" alt=""/><br />
 						</td>
 						<!-- /SEPARATOR -->
 
 						<!-- RIGHT-SIDEBAR WITH BANNER -->
 						<td style="width: 200px;" valign="top">
 							<div class="movable-area">
 								<div class="movable-element">
 									<inp2:m_include template="platform/elements/banners/banner_right.elm"/>
 								</div>
 							</div>
 						</td>
 						<!-- /RIGHT-SIDEBAR WITH BANNER -->
 						REMOVE THIS LINE TO UNCOMMENT ##-->
 					</tr>
 				</table>
 
 				<table class="fullwidth">
 					<tr>
 						<td >
 							<inp2:m_include template="platform/elements/footer.elm"/>
 						</td>
 					</tr>
 				</table>
 			</div>
 		</div>
 		<inp2:st_EditPage mode="end"/>
 	</body>
 </html>
\ No newline at end of file
Index: branches/RC/themes/default2009/index.tpl
===================================================================
--- branches/RC/themes/default2009/index.tpl	(revision 11962)
+++ branches/RC/themes/default2009/index.tpl	(revision 11963)
@@ -1,56 +1,58 @@
 <!--##
 <NAME>Home</NAME>
 <DESC>Welcome page</DESC>
 <SECTION></SECTION>
 ##-->
 
-<!--## 0PAGE TITLE ELEMENT ##-->
+<!--## PAGE TITLE ELEMENT ##-->
 <inp2:m_DefineElement name="page_title">
 	<inp2:m_phrase name="lu_title_WelcomeTitle"/>
 </inp2:m_DefineElement>
 <!--## //PAGE TITLE ELEMENT ##-->
 
 <!--## SIDE-BAR ELEMENT ##-->
 <inp2:m_DefineElement name="sidebar">
-	<div class="movable-element">
-		<inp2:m_RenderElement name="platform/elements/side_boxes/login.elm" design="blue_box"/>
-	</div>
-	<div class="movable-element">
-		<inp2:m_RenderElement name="platform/elements/side_boxes/search.elm" design="blue_box"/>
-	</div>
-	<div class="movable-element">
-		<inp2:m_RenderElement name="platform/elements/side_boxes/recommend_site.elm" design="blue_box"/>
-	</div>
-	<div class="movable-element">
-		<inp2:m_RenderElement name="platform/elements/side_boxes/mailing_list.elm" design="blue_box"/>
+	<div class="movable-area">
+		<div class="movable-element">
+			<inp2:m_RenderElement name="platform/elements/side_boxes/login.elm" design="blue_box"/>
+		</div>
+		<div class="movable-element">
+			<inp2:m_RenderElement name="platform/elements/side_boxes/search.elm" design="blue_box"/>
+		</div>
+		<div class="movable-element">
+			<inp2:m_RenderElement name="platform/elements/side_boxes/recommend_site.elm" design="blue_box"/>
+		</div>
+		<div class="movable-element">
+			<inp2:m_RenderElement name="platform/elements/side_boxes/mailing_list.elm" design="blue_box"/>
+		</div>
 	</div>
 </inp2:m_DefineElement>
 <!--## /SIDE-BAR ELEMENT ##-->
 
 <!--## MAIN CONTENT ##-->
 <inp2:m_DefineElement name="content">
-	<div class="movable-element">
-		<inp2:m_include template="platform/elements/navigation_bar.elm"/>
-	</div>
-
-	<div class="movable-element">
-		<inp2:m_RenderElement design="content_box">
-			<inp2:m_Capture to_var="header">
-				<inp2:m_phrase name="lu_title_WelcomeTitle"/>
-			</inp2:m_Capture>
-
-			<inp2:st_ContentBlock num="1"/>
-		</inp2:m_RenderElement>
-	</div>
-
-	<div class="movable-element">
-		<!--## INCLUDE HOME PAGE ELEMENTS FOR EACH MODULE ##-->
-		<inp2:m_ModuleInclude template="elements/content_boxes/home_page_items.elm" data_exists="1"/>
-		<!--## /HOME PAGE ELEMENTS ##-->
+	<div class="movable-area">
+		<div class="movable-element">
+			<inp2:m_include template="platform/elements/navigation_bar.elm"/>
+		</div>
+		<div class="movable-element">
+			<inp2:m_RenderElement design="content_box">
+				<inp2:m_Capture to_var="header">
+					<inp2:m_phrase name="lu_title_WelcomeTitle"/>
+				</inp2:m_Capture>
+
+				<inp2:st_ContentBlock num="1"/>
+			</inp2:m_RenderElement>
+		</div>
+		<div class="movable-element">
+			<!--## INCLUDE HOME PAGE ELEMENTS FOR EACH MODULE ##-->
+			<inp2:m_ModuleInclude template="elements/content_boxes/home_page_items.elm" data_exists="1"/>
+			<!--## /HOME PAGE ELEMENTS ##-->
+		</div>
 	</div>
 </inp2:m_DefineElement>
 <!--## /MAIN CONTENT ##-->
 
 <!--## DESIGN TEMPLATE ##-->
 <inp2:m_include template="platform/designs/default_design.des" pass_params="1"/>
 <!--## /DESIGN TEMPLATE ##-->
\ No newline at end of file
Index: branches/RC/core/units/theme_files/theme_file_eh.php
===================================================================
--- branches/RC/core/units/theme_files/theme_file_eh.php	(revision 11962)
+++ branches/RC/core/units/theme_files/theme_file_eh.php	(revision 11963)
@@ -1,194 +1,220 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.net/license/ for copyright notices and details.
 */
 
 	class ThemeFileEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standart permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnLoadBlock' => Array ('subitem' => true),
 				'OnSaveBlock' => Array ('subitem' => true),
+				'OnSaveLayout' => Array ('subitem' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Changes permission section to one from REQUEST, not from config
 		 *
 		 * @param kEvent $event
 		 */
 		function CheckPermission(&$event)
 		{
 			if ($event->Name == 'OnLoadBlock' || $event->Name == 'OnSaveBlock') {
 				return $this->Application->LoggedIn() && $this->Application->IsAdmin();
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Loads template contents into virtual field
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemLoad(&$event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$filename = $this->_getTemplatePath($object);
 
 			if (file_exists($filename)) {
 				$object->SetDBField('FileContents', file_get_contents($filename));
 			}
 			else {
 				$object->SetError('FileContents', 'template_file_missing', 'la_error_TemplateFileMissing');
 			}
 		}
 
 		/**
 		 * Trim contents of edited template
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeItemUpdate(&$event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$file_data = $object->GetDBField('FileContents');
 
 			$file_data = str_replace("\r\n", "\n", $file_data);
 			$file_data = str_replace("\r", "\n", $file_data);
 
 			$object->SetDBField('FileContents', trim($file_data));
 		}
 
 		/**
 		 * Saves updated content to template
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemUpdate(&$event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$filename = $this->_getTemplatePath($object);
 
 			if (file_exists($filename) && is_writable($filename)) {
 				$fp = fopen($filename, 'w');
 				fwrite($fp, $object->GetDBField('FileContents'));
 				fclose($fp);
 
 				$themes_helper =& $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				$meta_info = $themes_helper->parseTemplateMetaInfo($filename);
 				$file_description = array_key_exists('desc', $meta_info) ? $meta_info['desc'] : '';
 
 				$object->SetDBField('Description', $file_description);
 				$object->SetDBField('FileMetaInfo', serialize($meta_info));
 				$object->Update();
 			}
 		}
 
 		/**
 		 * Returns full path to template file
 		 *
 		 * @param kDBItem $object
 		 * @return string
 		 */
 		function _getTemplatePath(&$object)
 		{
 			$theme =& $this->Application->recallObject('theme');
 			/* @var $theme kDBItem */
 
 			$path = FULL_PATH . '/themes/' . $theme->GetDBField('Name');
 
 			$path .= $object->GetDBField('FilePath') . '/' . $object->GetDBField('FileName');
 
 			return $path;
 		}
 
 		/**
 		 * Loads block data based on it's name in request
 		 *
 		 * @param kEvent $event
 		 */
 		function OnLoadBlock(&$event)
 		{
 			parent::OnNew($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$template_helper =& $this->Application->recallObject('TemplateHelper');
 			/* @var $template_helper TemplateHelper */
 
 			$template_helper->InitHelper($object);
 
 			$object->SetDBField('FileName', $template_helper->blockInfo('template_file'));
 			$object->SetDBField('BlockPosition', $template_helper->blockInfo('start_pos') . ' - ' . $template_helper->blockInfo('end_pos'));
 			$object->SetDBField('FileContents', $template_helper->blockInfo('content'));
 
 		}
 
 		/**
 		 * Saves changed template block
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveBlock(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info) {
 				list ($id, $field_values) = each($items_info);
 				$object->SetFieldsFromHash($field_values);
 				$object->setID($id);
 			}
 
 			$status = $object->Validate();
 
 			$template_helper =& $this->Application->recallObject('TemplateHelper');
 			/* @var $template_helper TemplateHelper */
 
 			$template_helper->InitHelper($object);
 
 			$status = $status && $template_helper->saveBlock($object);
 
 			if ($status) {
 				$event->SetRedirectParam('opener', 'u');
 			}
 			else {
 				$event->status = erFAIL;
 			}
 		}
+
+		/**
+		 * Saves layout on given template
+		 *
+		 * @param kEvent $event
+		 */
+		function OnSaveLayout(&$event)
+		{
+			$event->status = erSTOP;
+			if (($this->Application->GetVar('ajax') != 'yes') || (EDITING_MODE != EDITING_MODE_LAYOUT)) {
+				return ;
+			}
+
+			$target_order = $this->Application->GetVar('target_order');
+
+			$template_helper =& $this->Application->recallObject('TemplateHelper');
+			/* @var $template_helper TemplateHelper */
+
+			if ($template_helper->moveTemplateElements($target_order)) {
+				echo 'OK';
+				return ;
+			}
+
+			echo 'FAILED';
+		}
 	}
\ No newline at end of file
Index: branches/RC/core/units/categories/categories_tag_processor.php
===================================================================
--- branches/RC/core/units/categories/categories_tag_processor.php	(revision 11962)
+++ branches/RC/core/units/categories/categories_tag_processor.php	(revision 11963)
@@ -1,2061 +1,2077 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.net/license/ for copyright notices and details.
 */
 
 class CategoriesTagProcessor extends kDBTagProcessor {
 
 	/**
 	 * Cached version of site menu
 	 *
 	 * @var Array
 	 */
 	var $Menu = null;
 
 	/**
 	 * Parent path mapping used in CachedMenu tag
 	 *
 	 * @var Array
 	 */
 	var $ParentPaths = Array ();
 
 	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)
 	{
 		// only for categories, not structure
 		if ($this->Prefix != 'c') {
 			return parent::ItemIcon($params);
 		}
 
 		$object =& $this->getObject($params);
 		if ($object->GetDBField('IsMenu')) {
 			$status = $object->GetDBField('Status');
 			if ($status == 1) {
 				$ret = $object->GetDBField('IsNew') ? 'icon16_cat_new.gif' : 'icon16_folder.gif';
 			}
 			else {
 				$ret = $status ? 'icon16_cat_pending.gif' : 'icon16_cat_disabled.gif';
 			}
 		}
 		else {
 			$ret = 'icon16_folder-red.gif';
 		}
 
 		return $ret;
 	}
 
 	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 = '.$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');
 	}
 
 	function getCategorySymLink($category_id)
 	{
 		static $cache = null;
 
 		if (!isset($cache)) {
 			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 			$sql = 'SELECT SymLinkCategoryId, '.$id_field.'
 					FROM '.$table_name.'
 					WHERE SymLinkCategoryId IS NOT NULL';
 			$cache = $this->Conn->GetCol($sql, $id_field);
 		}
 
 		if (isset($cache[$category_id])) {
 
 			//check if sym. link category is valid
 			$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.' = '.$cache[$category_id];
 
 			$category_id = $this->Conn->GetOne($sql)? $cache[$category_id] : $category_id;
 		}
 
 		return $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');
 		}
 
 		$category_id = $this->getCategorySymLink($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 ( 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;
 			}
 		}
 
 		$no_special = isset($params['no_special']) && $params['no_special'];
 		if ($no_special) return $this->Special;
 
 		$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)
 	{
 		$object =& $this->GetList($params);
 		if (!$object->Counted) {
 			$object->CountRecs();
 		}
 		return $object->NoFilterCount != $object->RecordsCount ? $object->RecordsCount.' / '.$object->NoFilterCount : $object->RecordsCount;
 	}
 
 	/**
 	 * Print grid pagination using
 	 * block names specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PrintPages($params)
 	{
 		if ($this->Application->Parser->GetParam('no_special')) {
 			$params['no_special'] = $this->Application->Parser->GetParam('no_special');
 		}
 		return parent::PrintPages($params);
 	}
 
 	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, 1);
 	}
 
 	/**
 	 * 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 = ' . $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';
 		}
 
 		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
 					$sql = 'SELECT l' . $this->Application->GetVar('m_lang') . '_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_CMS) {
 			$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
 			);
 
 			$additional_css = '';
 
 			if (isset($params['float'])) {
 				$additional_css .= 'position: relative; width: 100%; float: ' . $params['float'] . ';';
 			}
 
 			// 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" style="' . $additional_css . '" onclick="$form_name=\'kf_cont_'.$content->GetID().'\'; open_popup(\'content\', \'OnEdit\', \'categories/edit_content\');">
 						<div class="cms-btn-image">
 							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/content_mode.gif" width="15" height="16" alt=""/>
 						</div>
 						<div class="cms-btn-text">Edit '.(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);
 
 		if ($params['type'] == 'index_tools') {
 			$page_info = $page->GetDBField('IndexTools');
 			if ($page_info) {
 				return $page_info;
 			}
 			else {
 				if (PROTOCOL == 'https://') {
 					return $this->Application->ConfigValue('cms_DefaultIndextoolsCode_SSL');
 				}
 				else {
 					return $this->Application->ConfigValue('cms_DefaultIndextoolsCode');
 				}
 			}
 		}
 
 		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;
 
 			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_LAYOUT) {
 			$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/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, '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_amp' => 1);
+		$save_layout_url = $this->Application->HREF('index', '', $url_params);
+
 		$this_url = $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', 'no_amp' => 1));
-		$ret .= "var aTemplateManager = new TemplateManager('" . $edit_template_url . "', '" . $this_url . "', " . (int)EDITING_MODE . ");\n";
+		$ret .= "var aTemplateManager = new TemplateManager('" . $edit_template_url . "', '" . $this_url . "', '" . $save_layout_url . "', " . (int)EDITING_MODE . ");\n";
 
 		$ret .= "var \$modal_windows = " . ($this->Application->ConfigValue('UseModalWindows') ? 'true' : 'false') . ";\n";
 		$ret .= "var main_title = '" . addslashes( $this->Application->ConfigValue('Site_Name') ) . "';" . "\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_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 + \' - '.$this->Application->Phrase('la_AdministrativeConsole').'\';
 			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";
 
 		// 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()) {
 			// 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') {
 			$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', $url_params);
 
-			$edit_btn = '
+			$edit_btn = '';
+
+			if (EDITING_MODE == EDITING_MODE_LAYOUT) {
+				$edit_btn .= '
+					<div class="cms-save-layout-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onclick="aTemplateManager.saveLayout(); return false;">
+						<div class="cms-btn-image">
+							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/content_mode.gif" width="15" height="16" alt=""/>
+						</div>
+						<div class="cms-btn-text">Save Layout</div>
+					</div>' . "\n";
+			}
+
+			$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().'\'; open_popup(\'c\', \'OnEdit\', \'categories/categories_edit\');">
 					<div class="cms-btn-image">
 						<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/content_mode.gif" width="15" height="16" alt=""/>
 					</div>
 					<div class="cms-btn-text">Section Properties</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;
 	}
 
 	/**
 	 * Builds cached menu version
 	 *
 	 * @return Array
 	 */
 	function _prepareMenu()
 	{
 		static $root_cat = null;
 		static $root_path = null;
 
 		if (!$root_cat) {
 			$root_cat = $this->Application->ModuleInfo['Core']['RootCat'];
 			$root_path = $this->Conn->GetOne('SELECT ParentPath FROM '.TABLE_PREFIX.'Category WHERE CategoryId = '.$root_cat);
 		}
 
 		if (!$this->Menu) {
 			$menu = $this->Conn->GetRow('SELECT Data, Cached FROM '.TABLE_PREFIX.'Cache WHERE VarName = "cms_menu"');
 			if ($menu && $menu['Cached'] > 0) {
 				$menu = unserialize($menu['Data']);
 				$this->ParentPaths = $menu['ParentPaths'];
 			}
 			else {
 				$menu = $this->_altBuildMenuStructure(array('CategoryId' => $root_cat, 'ParentPath' => $root_path));
 				$menu['ParentPaths'] = $this->ParentPaths;
 				$this->Conn->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("cms_menu", '.$this->Conn->qstr(serialize($menu)).', '.adodb_mktime().')');
 			}
 			unset($menu['ParentPaths']);
 			$this->Menu = $menu;
 		}
 
 		return Array ($this->Menu, $root_path);
 	}
 
 	/**
 	 * Returns category id based tag parameters
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function _getCategoryId($params)
 	{
 		$cat = isset($params['category_id']) && $params['category_id'] != '' ? $params['category_id'] : $this->Application->GetVar('m_cat_id');
 		if ("$cat" == 'parent') {
 			$this_category =& $this->Application->recallObject('c');
 			/* @var $this_category kDBItem */
 
 			$cat = $this_category->GetDBField('ParentId');
 		}
 		else if ($cat == 0) {
 			$cat = $this->Application->ModuleInfo['Core']['RootCat'];
 		}
 
 		return $cat;
 	}
 
 	/**
 	 * Prepares cms menu item block parameters
 	 *
 	 * @param Array $page
 	 * @param int $real_cat_id
 	 * @param string $root_path
 	 * @return Array
 	 */
 	function _prepareMenuItem($page, $real_cat_id, $root_path)
 	{
 		static $language_id = null;
 		static $primary_language_id = null;
 
 		if (!isset($language_id)) {
 			$language_id = $this->Application->GetVar('m_lang');
 			$primary_language_id = $this->Application->GetDefaultLanguageId();
 		}
 
 		$title = $page['l'.$language_id.'_ItemName'] ? $page['l'.$language_id.'_ItemName'] : $page['l'.$primary_language_id.'_ItemName'];
 		$active = false;
 		$category_active = false;
 
 		if ($page['ItemType'] == 'cat') {
 			if ( isset($this->ParentPaths[$real_cat_id])) {
 				$active = strpos($this->ParentPaths[$real_cat_id], $page['ParentPath']) !== false;
 			}
 			$category_active = $page['CategoryId'] == $real_cat_id;
 		}
 
 		if ($page['ItemType'] == 'cat_index') {
 			$check_path = str_replace($root_path, '', $page['ParentPath']);
 			$active = strpos($parent_path, $check_path) !== false;
 		}
 
 		if ($page['ItemType'] == 'page') {
 			$active = $page['ItemPath'] == preg_replace('/^Content\//i', '', $this->Application->GetVar('t'));
 		}
 
 		$block_params = Array (
 		'title'=> $title,
 		'template'=> preg_replace('/^Content\//i', '', $page['ItemPath']),
 		'active'=>$active,
 		'category_active' => $category_active, // new
 		'parent_path'=>$page['ParentPath'],
 		'parent_id'=>$page['ParentId'],
 		'cat_id'=>$page['CategoryId'],
 		'is_index'=>$page['IsIndex'],
 		'item_type'=>$page['ItemType'],
 		'page_id'=>$page['ItemId'],
 		'has_sub_menu' => isset($page['sub_items']) && count($page['sub_items']) > 0,
 		'external_url' => $page['UseExternalUrl'] ? $page['ExternalUrl'] : false,
 		'menu_icon' => $page['UseMenuIconUrl'] ? $page['MenuIconUrl'] : false,
 
 		);
 
 		return $block_params;
 	}
 
 	/**
 	 * Builds site menu
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CachedMenu($params)
 	{
 		list ($menu, $root_path) = $this->_prepareMenu();
 		$cat = $this->_getCategoryId($params);
 
 		$parent_path = isset($this->ParentPaths[$cat]) ? $this->ParentPaths[$cat] : '';
 		$parent_path = str_replace($root_path, '', $parent_path); //menu starts from module path
 		$levels = explode('|',trim($parent_path,'|'));
 		if ($levels[0] === '') $levels = array();
 		if (isset($params['level']) && $params['level'] > count($levels)) return ;
 
 		$level = max(isset($params['level']) ? $params['level']-1 : count($levels)-1, 0);
 		$parent = isset($levels[$level]) ? $levels[$level] : 0;
 
 		$cur_menu =& $menu;
 		$menu_path = array_slice($levels, 0, $level+1);
 		foreach ($menu_path as $elem) {
 			$cur_menu =& $cur_menu['c'.$elem]['sub_items'];
 		}
 
 		$ret = '';
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		$this->Application->SetVar('cur_parent_path', $parent_path);
 		$real_cat_id = $this->Application->GetVar('m_cat_id');
 		if (is_array($cur_menu) && $cur_menu) {
 			$cur_item = 1;
 			$cur_menu = $this->_removeNonMenuItems($cur_menu);
 			$block_params['total_items'] = count($cur_menu);
 
 			foreach ($cur_menu as $page) {
 				$block_params = array_merge_recursive2(
 				$block_params,
 				$this->_prepareMenuItem($page, $real_cat_id, $root_path)
 				);
 
 				$block_params['is_last'] = $cur_item == $block_params['total_items'];
 				$block_params['is_first'] = $cur_item == 1;
 
 				// bug #1: this breaks active section highlighting when 2 menu levels are printed on same page (both visible)
 				// bug #2: people doesn't pass cat_id parameter to m_Link tags in their blocks, so this line helps them; when removed their links will lead to nowhere
 				$this->Application->SetVar('m_cat_id', $page['CategoryId']);
 
 				$ret .= $this->Application->ParseBlock($block_params, 1);
 				$cur_item++;
 			}
 
 			$this->Application->SetVar('m_cat_id', $real_cat_id);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns only items, that are visible in menu
 	 *
 	 * @param Array $menu
 	 * @return Array
 	 */
 	function _removeNonMenuItems($menu)
 	{
 		foreach ($menu as $menu_index => $menu_item) {
 			// $menu_index is in "cN" format, where N is category id
 			if (!$menu_item['IsMenu']) {
 				unset($menu[$menu_index]);
 			}
 		}
 
 		return $menu;
 	}
 
 	/**
 	 * 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, 1);
 
 			$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);
 
 		$template = $object->GetDBField('NamedParentPath');
 		$url_params = Array ('admin' => 1, 'pass' => 'm', 'm_cat_id' => $object->GetID(), 'index_file' => 'index.php');
 
 		return $this->Application->HREF($template, '_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);
 	}
 
 	/**
 	 * Builds cache for children of given category (no matter, what menu status is)
 	 *
 	 * @param Array $parent
 	 * @return Array
 	 */
 	function _altBuildMenuStructure($parent)
 	{
 		static $languages_count = null;
 
 		if (!isset($languages_count)) {
 			$sql = 'SELECT COUNT(*)
 					FROM ' . TABLE_PREFIX . 'Language';
 			$languages_count = ceil($this->Conn->GetOne($sql) / 5) * 5;
 		}
 
 		$items = Array ();
 
 		$lang_part = '';
 		for ($i = 1; $i <= $languages_count; $i++) {
 //			$lang_part .= 'c.l' . $i . '_Name AS l' . $i . '_ItemName,' . "\n";
 			$lang_part .= 'c.l' . $i . '_MenuTitle AS l' . $i . '_ItemName,' . "\n";
 		}
 
 		// Sub-categories from current category
 		$query = 'SELECT
 								c.CategoryId AS CategoryId,
 								CONCAT(\'c\', c.CategoryId) AS ItemId,
 								c.Priority AS ItemPriority,
 								' . $lang_part . '
 								LOWER( IF(IsIndex = 2, (
 											SELECT cc.NamedParentPath FROM ' . TABLE_PREFIX . 'Category AS cc
 											WHERE
 												cc.ParentId = c.CategoryId
 												AND
 												cc.Status IN (1,4)
 												AND
 												cc.IsIndex = 1
 										),
 									 c.NamedParentPath) ) AS ItemPath,
 								0 AS IsIndex,
 								c.ParentPath AS ParentPath,
 								c.ParentId As ParentId,
 								\'cat\' AS ItemType,
 								c.IsMenu, c.UseExternalUrl, c.ExternalUrl, c.UseMenuIconUrl, c.MenuIconUrl
 							FROM ' . TABLE_PREFIX . 'Category AS c
 							WHERE
 								 c.Status IN (1,4) AND
 								 #c.IsMenu = 1 AND
 								 c.ParentId = ' . $parent['CategoryId'];
 		$items = array_merge($items, $this->Conn->Query($query, 'ItemId'));
 
 		uasort($items, Array (&$this, '_menuSort'));
 
 		$the_items = array();
 		foreach ($items as $an_item) {
 			$the_items[ $an_item['ItemId'] ] = $an_item;
 			$this->ParentPaths[ $an_item['CategoryId'] ] = $an_item['ParentPath'];
 		}
 
 		$items = $the_items;
 		foreach ($items as $key => $menu_item) {
 			if ($menu_item['CategoryId'] == $parent['CategoryId']) {
 				continue;
 			}
 
 			$sub_items = $this->_altBuildMenuStructure($menu_item);
 			if ($sub_items) {
 				$items[$key]['sub_items'] = $sub_items;
 			}
 		}
 
 		return $items;
 	}
 
 	/**
 	 * Method for sorting pages by priority in decending order
 	 *
 	 * @param Array $a
 	 * @param Array $b
 	 * @return int
 	 */
 	function _menuSort($a, $b)
 	{
 		if ($a['ItemPriority'] == $b['ItemPriority']) {
 			return 0;
 		}
 
 		return ($a['ItemPriority'] < $b['ItemPriority']) ? 1 : -1; //descending
 	}
 
 	/**
 	 * 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 = $this->Application->GetVar('m_lang');
 
 		$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);
 	}
 }
\ No newline at end of file
Index: branches/RC/core/units/general/helpers/template_helper.php
===================================================================
--- branches/RC/core/units/general/helpers/template_helper.php	(revision 11962)
+++ branches/RC/core/units/general/helpers/template_helper.php	(revision 11963)
@@ -1,334 +1,448 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.net/license/ for copyright notices and details.
 */
 
 	class TemplateHelper extends kHelper {
 
 		/**
 		 * parser element location information
 		 *
 		 * @var Array
 		 */
 		var $_blockLocation = Array ();
 
 		/**
 		 * Block name, that will be used
 		 *
 		 * @var string
 		 */
 		var $_blockName = '';
 
 		/**
 		 * Function name, that represents compiled block
 		 *
 		 * @var sting
 		 */
 		var $_functionName = '';
 
 		/**
 		 * Errors found during template parsing
 		 *
 		 * @var Array
 		 */
 		var $_parseErrors = Array ();
 
 		/**
 		 * Source template, that is being edited
 		 *
 		 * @var string
 		 */
 		var $_sourceTemplate = '';
 
 		var $_initMade = false;
 
 		/**
 		 * Performs init ot helper
 		 *
 		 * @param kDBItem $object
 		 */
 		function InitHelper(&$object)
 		{
 			if ($this->_initMade) {
 				return ;
 			}
 
 			define('DBG_IGNORE_FATAL_ERRORS', 1);
 
 			// 1. get block information
 			$block_info = $this->Application->GetVar('block');
 			list ($this->_blockName, $this->_functionName) = explode(':', $block_info);
 
 			$this->_parseTemplate($object);
 
 			if (array_key_exists($this->_functionName, $this->Application->Parser->ElementLocations)) {
 				$this->_blockLocation = $this->Application->Parser->ElementLocations[$this->_functionName];
 			}
 
 			$this->_initMade = true;
 		}
 
 		function _getSourceTemplate()
 		{
 			// get source template
 			$t = $this->Application->GetVar('source');
 
 			if (!$this->Application->TemplatesCache->TemplateExists($t)) {
 				$cms_handler =& $this->Application->recallObject('st_EventHandler');
 				/* @var $cms_handler StructureEventHandler */
 
 				$t = ltrim($cms_handler->GetDesignTemplate($t), '/');
 			}
 
 			$this->_sourceTemplate = $t;
 		}
 
 		function _getThemeName()
 		{
 			$theme_id = (int)$this->Application->GetVar('theme_id');
 
 			$sql = 'SELECT Name
 					FROM ' . $this->Application->getUnitOption('theme', 'TableName') . '
 					WHERE ' . $this->Application->getUnitOption('theme', 'IDField') . ' = ' . $theme_id;
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Render source template to get parse errors OR it's element locations
 		 *
 		 * @param kDBItem $object
 		 * @param string $append
 		 * @return bool
 		 */
 		function _parseTemplate(&$object, $append = '')
 		{
 			// 1. set internal error handler to catch all parsing errors
 			$error_handlers = $this->Application->errorHandlers;
 			$this->Application->errorHandlers = Array (
 				Array (&$this, '_saveError'),
 			);
 
 			// 2. parse template
 			$this->Application->InitParser( $this->_getThemeName() ); // we have no parser when saving block content
 
 			$this->_getSourceTemplate();
 
 			// design templates have leading "/" in the beginning
 			$this->Application->Parser->Run($this->_sourceTemplate . $append);
 
 			// 3. restore original error handler
 			$this->Application->errorHandlers = $error_handlers;
 
 			if ($this->_parseErrors) {
 				if ($this->_isMainTemplate()) {
 					// 3.1. delete temporary file, that was parsed
 					$filename = $this->_getTemplateFile(false, $append . '.tpl');
 					if (!unlink($filename)) {
 						$error_file = $this->_getTemplateFile(true, $append . '.tpl');
 						$object->SetError('FileContents', 'template_delete_failed', '+Failed to delete temporary template "<strong>' . $error_file . '</strong>"');
 						return false;
 					}
 				}
 				else {
 					// 3.2. restore backup
 					if (!rename($this->_getTemplateFile(false, '.tpl.bak'), $this->_getTemplateFile(false))) {
 						$error_file = $this->_getTemplateFile(true);
 						$object->SetError('FileContents', 'template_restore_failed', '+Failed to restore template "<strong>' . $error_file . '</strong>" from backup.');
 						return false;
 					}
 				}
 
 				return false;
 			}
 
 			return true;
 		}
 
 		/**
+		 * Move elements in template and save changes, when possible
+		 *
+		 * @param Array $target_order
+		 * @return bool
+		 */
+		function moveTemplateElements($target_order)
+		{
+			// 2. parse template
+			$this->Application->InitParser(); // we have no parser when saving block content
+
+			$this->_getSourceTemplate();
+
+			$filename = $this->Application->TemplatesCache->GetRealFilename($this->_sourceTemplate) . '.tpl';
+			if (!is_writable($filename)) {
+				// we can't save changes, don't bother calculating new template contents
+				return false;
+			}
+
+			$data = file_get_contents($filename);
+
+			$line_ending = strpos($data, "\r") !== false ? "\r\n" : "\n";
+
+			// 1. get location of movable areas
+			$mask = '';
+			$start_pos = 0;
+			$elements = Array ();
+			$areas = $this->_getDivPairs($data, 'movable-area');
+			foreach ($areas as $area_index => $area) {
+				// 1.1. get locations of all movable elements inside given area
+				$area_content = substr($area['data'], $area['open_len'], -$area['close_len']);
+				$elements = array_merge($elements, $this->_getDivPairs($area_content, 'movable-element', $area_index, $area['open_pos'] + $area['open_len']));
+
+				// 1.2. prepare mask to place movable elements into (don't include movable area div ifself)
+				$mask .= "\t" . substr($data, $start_pos, $area['open_pos'] + $area['open_len'] - $start_pos) . $line_ending . "\t\t" . '#AREA' . $area_index . '#' . $line_ending;
+				$start_pos = $area['close_pos'] - $area['close_len'];
+			}
+			$mask = trim($mask . "\t" . substr($data, $area['close_pos'] - $area['close_len']));
+
+			if (!$elements) {
+				// no elements found
+				return false;
+			}
+
+			foreach ($areas as $area_index => $area) {
+				$area_content = '';
+				$target_elements = $target_order[$area_index];
+				foreach ($target_order[$area_index] as $old_location) {
+					$area_content .= $elements[$old_location]['data'] . $line_ending . "\t\t";
+				}
+
+				$mask = str_replace('#AREA' . $area_index . '#', trim($area_content), $mask);
+			}
+
+			$fp = fopen($filename, 'w');
+			fwrite($fp, $mask);
+			fclose($fp);
+
+			return true;
+		}
+
+		/**
+		 * Extracts div pairs with given class from given text
+		 *
+		 * @param string $data
+		 * @param string $class
+		 * @param int $area
+		 * @param int $offset
+		 * @return Array
+		 */
+		function _getDivPairs(&$data, $class, $area = null, $offset = 0)
+		{
+			preg_match_all('/(<div[^>]*>)|(<\/div>)/s', $data, $divs, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
+
+			$skip_count = 0;
+			$pairs = Array ();
+
+			foreach ($divs as $div) {
+				if (strpos($div[0][0], '/') === false) {
+					// opening div
+					if (strpos($div[0][0], $class) !== false) {
+						$pair = Array ('open_pos' => $div[0][1], 'open_len' => strlen($div[0][0]));
+						$skip_count = 0;
+					}
+					else {
+						$skip_count++;
+					}
+				}
+				else {
+					// closing div
+					if ($skip_count == 0) {
+						$pair['close_len'] = strlen($div[0][0]);
+						$pair['close_pos'] = $div[0][1] + $pair['close_len'];
+						$pair['data'] = substr($data, $pair['open_pos'], $pair['close_pos'] - $pair['open_pos']);
+
+						if (isset($area)) {
+							$pair['open_pos'] += $offset;
+							$pair['close_pos'] += $offset;
+							// index indicates area
+							$pairs['a' . $area . 'e' . count($pairs)] = $pair;
+						}
+						else {
+							$pairs[] = $pair;
+						}
+					}
+					else {
+						$skip_count--;
+					}
+				}
+			}
+
+			return $pairs;
+		}
+
+		/**
 		 * Returns information about parser element locations in template
 		 *
 		 * @param Array $params
 		 * @return mixed
 		 */
 		function blockInfo($info_type)
 		{
 			switch ($info_type) {
 				case 'block_name':
 					return $this->_blockName;
 					break;
 
 				case 'function_name':
 					return $this->_functionName;
 					break;
 
 				case 'start_pos':
 				case 'end_pos':
 				case 'template':
 					if (!array_key_exists($info_type, $this->_blockLocation)) {
 						// invalid block name
 						return 'invalid block name';
 					}
 
 					return $this->_blockLocation[$info_type];
 					break;
 
 				case 'template_file':
 					return $this->_getTemplateFile(true);
 					break;
 
 				case 'content':
 					$template_body = file_get_contents( $this->_getTemplateFile() );
 					$length = $this->_blockLocation['end_pos'] - $this->_blockLocation['start_pos'];
 
 					return substr($template_body, $this->_blockLocation['start_pos'], $length);
 					break;
 			}
 
 			return 'undefined';
 		}
 
 		/**
 		 * Main template being edited (parse copy, instead of original)
 		 *
 		 * @return bool
 		 */
 		function _isMainTemplate()
 		{
 			return $this->_blockLocation['template'] == $this->_sourceTemplate;
 		}
 
 		/**
 		 * Returns filename, that contains template, where block is located
 		 *
 		 * @return string
 		 */
 		function _getTemplateFile($relative = false, $extension = '.tpl')
 		{
 			$filename = $this->Application->TemplatesCache->GetRealFilename( $this->_blockLocation['template'] ) . $extension;
 
 			if ($relative) {
 				$filename = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $filename, 1);
 			}
 
 			return $filename;
 		}
 
 		/**
 		 * Saves new version of block to template, where it's located
 		 *
 		 * @param kDBItem $object
 		 */
 		function saveBlock(&$object)
 		{
 			$main_template = $this->_isMainTemplate();
 			$filename = $this->_getTemplateFile(false);
 
 			// 1. get new template content
 			$new_template_body = $this->_getNewTemplateContent($object, $filename, $lines_before);
 			if (is_bool($new_template_body) && ($new_template_body === true)) {
 				// when nothing changed -> stop processing
 				return true;
 			}
 
 			// 2. backup original template
 			if (!$main_template && !copy($filename, $filename . '.bak')) {
 				// backup failed
 				$error_file = $this->_getTemplateFile(true, '.tpl.bak');
 				$object->SetError('FileContents', 'template_backup_failed', '+Failed to create backup template "<strong>' . $error_file . '</strong>" backup.');
 				return false;
 			}
 
 			// 3. save changed template
 			$save_filename = $this->_getTemplateFile(false, $main_template ? '.tmp.tpl' : '.tpl');
 			$fp = fopen($save_filename, 'w');
 			if (!$fp) {
 				// backup template create failed OR existing template save
 				$error_file = $this->_getTemplateFile(true, $main_template ? '.tmp.tpl' : '.tpl');
 				$object->SetError('FileContents', 'template_changes_save_failed', '+Failed to save template "<strong>' . $error_file . '</strong>" changes.');
 				return false;
 			}
 			fwrite($fp, $new_template_body);
 			fclose($fp);
 
 			// 3. parse template to check for errors
 			$this->_parseTemplate($object, $main_template ? '.tmp' : '');
 
 			if ($this->_parseErrors) {
 				$error_msg = Array ();
 				foreach ($this->_parseErrors as $error_data) {
 					if (preg_match('/line ([\d]+)/', $error_data['msg'], $regs)) {
 						// another line number inside message -> patch it
 						$error_data['msg'] = str_replace('line ' . $regs[1], 'line ' . ($regs[1] - $lines_before), $error_data['msg']);
 					}
 
 					$error_msg[] = $error_data['msg'] . ' at line ' . ($error_data['line'] - $lines_before);
 				}
 
 				$object->SetError('FileContents', 'template_syntax_error', '+Template syntax errors:<br/>' . implode('<br/>', $error_msg));
 				return false;
 			}
 
 			if ($main_template) {
 				// 4.1. replace original file with temporary
 				if (!rename($this->_getTemplateFile(false, '.tmp.tpl'), $filename)) {
 					// failed to save new content to original template
 					$error_file = $this->_getTemplateFile(true);
 					$object->SetError('FileContents', 'template_save_failed', '+Failed to save template "<strong>' . $error_file . '</strong>".');
 					return false;
 				}
 			}
 			else {
 				// 4.2. delete backup
 				unlink( $this->_getTemplateFile(false, '.tpl.bak') );
 			}
 
 			return true;
 		}
 
 		/**
 		 * Returns new template content of "true", when nothing is changed
 		 *
 		 * @param kDBItem $object
 		 * @param string $filename
 		 * @param int $lines_before
 		 * @return mixed
 		 */
 		function _getNewTemplateContent(&$object, $filename, &$lines_before)
 		{
 			$new_content = $object->GetDBField('FileContents');
 
 			$template_body = file_get_contents($filename);
 			$lines_before = substr_count(substr($template_body, 0,  $this->_blockLocation['start_pos']), "\n");
 
 			$new_template_body = 	substr($template_body, 0,  $this->_blockLocation['start_pos']) .
 									$new_content .
 									substr($template_body, $this->_blockLocation['end_pos']);
 
 			return crc32($template_body) == crc32($new_template_body) ? true : $new_template_body;
 		}
 
 		function _saveError($errno, $errstr, $errfile, $errline, $errcontext)
 		{
 			if ($errno != E_USER_ERROR) {
 				// ignore all minor errors, except fatals from parser
 				return true;
 			}
 
 			/*if (defined('E_STRICT') && ($errno == E_STRICT)) {
 				// always ignore strict errors here (specially when not in debug mode)
 				return true;
 			}*/
 
 			$this->_parseErrors[] = Array ('msg' => $errstr, 'file' => $errfile, 'line' => $errline);
 			return true;
 		}
 	}
\ No newline at end of file
Index: branches/RC/core/admin_templates/tree.tpl
===================================================================
--- branches/RC/core/admin_templates/tree.tpl	(revision 11962)
+++ branches/RC/core/admin_templates/tree.tpl	(revision 11963)
@@ -1,165 +1,170 @@
 <inp2:m_Set skip_last_template="1"/>
 <inp2:m_include t="incs/header" nobody="yes" noform="yes"/>
 <inp2:m_NoDebug/>
 
 <body class="tree-body" onresize="onTreeFrameResize();">
 
 <script type="text/javascript">
 	var $last_width = null;
 
 	function credits(url) {
 		openwin(url, 'credits', 280, 520);
 	}
 
 	function onTreeFrameResize() {
-		var $width = $('#sub_frameset', window.parent.document).attr('cols').split(',')[0];
+		var $frameset = $('#sub_frameset', window.parent.document);
+		if (!$frameset.length) {
+			return ;
+		}
+
+		var $width = $frameset.attr('cols').split(',')[0];
 		if (($width <= 0) || ($width == $last_width)) {
 			// don't save zero width
 			return ;
 		}
 
 		getFrame('head').$FrameResizer.OpenWidth = $width;
 
 		$.get(
 			'<inp2:m_Link template="index" adm_event="OnSaveMenuFrameWidth" pass="m,adm" js_escape="1" no_amp="1"/>',
 			{width: $width}
 		);
 
 		$last_width = $width;
 	}
 </script>
 
 <script src="js/tree.js"></script>
 
 <table style="height: 100%; width: 100%; border-right: 1px solid #777; border-bottom: 1px solid #777;">
 	<tr>
 		<td colspan="2" style="vertical-align: top; padding: 5px;">
 			<inp2:m_DefineElement name="xml_node" icon_module="">
 				<inp2:m_if check="m_ParamEquals" param="children_count" value="0">
 					<item href="<inp2:m_param name="section_url" js_escape="1"/>" priority="<inp2:m_param name="priority"/>" onclick="<inp2:m_param name="onclick" js_escape="1"/>" icon="<inp2:$SectionPrefix_ModulePath module="$icon_module"/>img/icons/icon24_<inp2:m_param name="icon"/>.gif"><inp2:m_phrase name="$label" escape="1"/></item>
 				<inp2:m_else/>
 					<folder href="<inp2:m_param name="section_url" js_escape="1"/>" priority="<inp2:m_param name="priority"/>" container="<inp2:m_param name="container"/>" onclick="<inp2:m_param name="onclick" js_escape="1"/>" name="<inp2:m_phrase name="$label" escape="1"/>" icon="<inp2:$SectionPrefix_ModulePath module="$icon_module"/>img/icons/icon24_<inp2:m_param name="icon"/>.gif" load_url="<inp2:m_param name="late_load" js_escape="1"/>"><inp2:adm_PrintSections render_as="xml_node" section_name="$section_name"/></folder>
 				</inp2:m_if>
 			</inp2:m_DefineElement>
 
 			<table class="tree">
 				<tbody id="tree">
 				</tbody>
 			</table>
 			<script type="text/javascript">
 				var TREE_ICONS_PATH = 'img/tree';
 				var TREE_SHOW_PRIORITY = <inp2:m_if check="adm_ConstOn" name="DBG_SHOW_TREE_PRIORITY" debug_mode="1">1<inp2:m_else/>0</inp2:m_if>;
 				<inp2:m_DefineElement name="root_node">
 					var the_tree = new TreeFolder('tree', '<inp2:m_param name="label"/>', '<inp2:m_param name="section_url"/>', '<inp2:$SectionPrefix_ModulePath module="$icon_module"/>img/icons/icon24_<inp2:m_param name="icon"/>.gif', null, null, '<inp2:m_param name="priority"/>', '<inp2:m_param name="container"/>');
 				</inp2:m_DefineElement>
 				<inp2:adm_PrintSection render_as="root_node" section_name="in-portal:root"/>
 
 				the_tree.AddFromXML('<tree><inp2:adm_PrintSections render_as="xml_node" section_name="in-portal:root"/></tree>');
 
 				<inp2:m_if check="adm_MainFrameLink">
 					var fld = the_tree.locateItemByURL('<inp2:adm_MainFrameLink m_opener="r" no_amp="1"/>');
 					if (fld) {
 						fld.highLight();
 					}
 					else {
 						the_tree.highLight();
 					}
 				<inp2:m_else/>
 					the_tree.highLight();
 				</inp2:m_if>
 			</script>
 		</td>
 	</tr>
 </table>
 
 <script type="text/javascript">
 	function checkCatalog($cat_id) {
 		var $ret = checkEditMode(false);
 		var $right_frame = getFrame('main');
 
 		if ($ret && $right_frame.$is_catalog) {
 			$right_frame.$Catalog.go_to_cat($cat_id);
 			return 1; // this opens folder, but disables click
 		}
 
 		return $ret;
 	}
 
 	function setCatalogTab($prefix) {
 		var $ret = checkEditMode();
 
 		if ($ret) {
 			var $right_frame = getFrame('main');
 			var $catalog_type = (typeof $right_frame.$Catalog != 'undefined') ? $right_frame.$Catalog.type : '';
 
 			if ($catalog_type == 'AdvancedView') {
 				$right_frame.$Catalog.switchTab($prefix);
 				return 1; // this opens folder, but disables click
 			}
 		}
 
 		return $ret;
 	}
 
 	function checkEditMode($reset_toolbar)
 	{
 		if (!isset($reset_toolbar)) {
 			$reset_toolbar = true;
 		}
 
 		if ($reset_toolbar) {
 			getFrame('head').$('#extra_toolbar').html('');
 		}
 
 		var $phrase = "<inp2:adm_TreeEditWarrning label='la_EditingInProgress' escape='1'/>";
 		if (getFrame('main').$edit_mode) {
 			return confirm($phrase) ? true : false;
 		}
 
 		return true;
 	}
 
 	function ReloadFolder(url, with_late_load)
 	{
 		if (!with_late_load) with_late_load = false;
 		var fld = the_tree.locateItemByURL(url, with_late_load);
 		if (fld) {
 			fld.reload();
 		}
 	}
 
 	function ShowStructure($url, $visible)
 	{
 		var fld = the_tree.locateItemByURL($url, true);
 		if (fld) {
 			if ($visible) {
 				fld.expand();
 			}
 			else {
 				fld.collapse();
 			}
 
 		}
 	}
 
 	function SyncActive(url) {
 		var fld = the_tree.locateItemByURL(url);
 		if (fld) {
 			fld.highLight();
 		}
 	}
 
 	<inp2:m_if check="m_GetConfig" name="DebugOnlyFormConfigurator">
 		<inp2:m_ifnot check="m_IsDebugMode">
 			<inp2:m_DefineElement name="forms_node">
 				var $forms_node = the_tree.locateItemByURL('<inp2:m_param name="section_url" js_escape="1"/>');
 				$forms_node.Container = true;
 			</inp2:m_DefineElement>
 			<inp2:adm_PrintSection render_as="forms_node" section_name="in-portal:forms"/>
 		</inp2:m_ifnot>
 	</inp2:m_if>
 </script>
 
 <!--## when form is on top, then 100% height is broken ##-->
 <inp2:m_RenderElement name="kernel_form"/>
 <inp2:m_include t="incs/footer"/>
\ No newline at end of file
Index: branches/RC/core/admin_templates/js/template_manager.js
===================================================================
--- branches/RC/core/admin_templates/js/template_manager.js	(revision 11962)
+++ branches/RC/core/admin_templates/js/template_manager.js	(revision 11963)
@@ -1,125 +1,190 @@
-function TemplateManager ($edit_url, $browse_url, $edting_mode) {
+function TemplateManager ($edit_url, $browse_url, $save_layout_url, $edting_mode) {
 	this._editUrl = $edit_url;
 	this.browseUrl = $browse_url;
+	this._saveLayoutUrl = $save_layout_url;
 	this.editingMode = $edting_mode; // from 1 to 4
 
 	this._blocks = {};
 
 	this._blockOrder = Array ();
 
 	var $template_manager = this;
 
 	$(document).ready(
 		function() {
 			$template_manager.searchBlocks();
 
 			if (!$template_manager.editingMode) {
 				return ;
 			}
 
 			// show special toolbar when in any of 4 browse modes
 			var $head_frame = getFrame('head');
 			var $extra_toolbar = $head_frame.$('div.front-extra-toolbar').clone(); // clone to keep original untouched
 
 			$('a', $extra_toolbar).each(
 				function() {
 					// cut from end, because IE7 adds base_href to beginning of href
 					var $editing_mode = $(this).attr('href');
 					$editing_mode = $editing_mode.substr($editing_mode.length - 1, 1);
 
 					$(this).attr('href', $template_manager.browseUrl.replace('#EDITING_MODE#', $editing_mode));
 
 					if ($editing_mode == $template_manager.editingMode) {
 						$(this).parents('td:first').addClass('button-active').prevAll('td:first').addClass('button-active');
 					}
 				}
 			);
 
 			$head_frame.$('#extra_toolbar').html( $extra_toolbar.html() );
 
 			if ($template_manager.editingMode == 2) {
 				// Layout Mode
+
+				$template_manager.renumberMovableElements();
+
 				$('div.movable-area').sortable(
 					{
 						placeholder: 'move-helper',
 						handle: '.movable-header',
 						items: 'div.movable-element',
 						connectWith: ['div.movable-area'],
 						start: function(e, ui) {
 							ui.placeholder.height( ui.item.height() );
 						}
 					}
 				);
 			}
+
+			$('div.cms-edit-btn')
+			.mouseover(
+				function(e) {
+					$(this).css('opacity', 1);
+				}
+			)
+			.mouseout(
+				function(e) {
+					$(this).css('opacity', 0.5);
+				}
+			);
+		}
+	);
+}
+
+TemplateManager.prototype.renumberMovableElements = function () {
+	var $area_index = 0;
+	// 1. dynamically assign IDs to all movable elements
+	$('div.movable-area').each(
+		function() {
+			var $element_index = 0;
+			$('div.movable-element', this).each(
+				function() {
+					$(this).attr('id', 'target_order_a' + $area_index + 'e' + $element_index);
+					$element_index++;
+				}
+			);
+
+			$area_index++;
+		}
+	);
+}
+
+TemplateManager.prototype.saveLayout = function () {
+	// prepare order string
+	var $sort_order = [];
+	$('div.movable-area').each(
+		function($area_index) {
+			var $order = $(this).sortable('serialize').replace(/target_order\[\]/g, 'target_order[' + $area_index + '][]');
+			if ($order) {
+				$sort_order.push($order);
+			}
+		}
+	);
+	$sort_order = $sort_order.join('&');
+
+	// save order string
+	var $me = this;
+	var $url = this._saveLayoutUrl + '&' + $sort_order;
+	$.get(
+		$url,
+		function(data) {
+			// only, when data was saved renumber movable elements
+			if (data == 'OK') {
+				alert('New Layout Saved');
+				$me.renumberMovableElements();
+			}
+			else {
+				alert('Failed to Save New Layout');
+			}
 		}
 	);
 }
 
 TemplateManager.prototype.onBtnClick = function ($e, $element) {
 	var $id = $element.id.replace(/_btn$/, '');
 	var $block_info = this._blocks[$id];
 	var $url = this._editUrl.replace('#BLOCK#', $block_info.block_name + ':' + $block_info.function_name).replace('#EVENT#', 'OnLoadBlock');
 
 	openSelector('theme-file', $url);
 
 	$e.stopPropagation();
 }
 
 TemplateManager.prototype.onMouseOver = function ($e, $element) {
 	$($element).addClass('block-edit-btn-container-over');
 	$e.stopPropagation();
 }
 
 TemplateManager.prototype.onMouseOut = function ($e, $element) {
 	$($element).removeClass('block-edit-btn-container-over');
 	$e.stopPropagation();
 }
 
 TemplateManager.prototype.searchBlocks = function () {
 	$('div').each (
 		function () {
 			var $id = $(this).attr('id');
 
 			if (!$id || $id.match(/parser_block\[.*\].*_btn$/) || !$id.match(/parser_block\[.*\]/)) {
 				// skip other divs
 				return true;
 			}
 
 			TemplateManager.prototype.registerBlock.call(aTemplateManager, this);
 		}
 	);
 }
 
 TemplateManager.prototype.registerBlock = function ($element) {
 	var $params = $element.getAttribute('params').split(':');
 
 	this._blocks[$element.id] = {
 		block_name: $params[0],
 		function_name: $params[1]
 	};
 
 	var $btn = document.getElementById($element.id + '_btn');
 
 	$($btn).bind(
 		'click',
 		function(ev) {
 			TemplateManager.prototype.onBtnClick.call(aTemplateManager, ev, this);
 		}
 	);
 
 	$($element).bind(
 		'mouseover',
 		function(ev) {
 			TemplateManager.prototype.onMouseOver.call(aTemplateManager, ev, this);
 		}
 	);
 
 	$($element).bind(
 		'mouseout',
 		function(ev) {
 			TemplateManager.prototype.onMouseOut.call(aTemplateManager, ev, this);
 		}
 	);
 
 	this._blockOrder.push($element.id);
 }
\ No newline at end of file
Index: branches/RC/core/admin_templates/incs/cms.css
===================================================================
--- branches/RC/core/admin_templates/incs/cms.css	(revision 11962)
+++ branches/RC/core/admin_templates/incs/cms.css	(revision 11963)
@@ -1,77 +1,88 @@
 /* === Common styles for "Section Properties" and "Edit" buttons === */
 div.cms-btn-image {
 	float: left;
 	height: 15px;
 	vertical-align: middle;
 }
 
 div.cms-btn-text {
 	margin-left: 3px;
 	float: left;
 	white-space: nowrap;
 	vertical-align: middle;
 }
 
 div.cms-btn-content {
 	padding: 5px;
 }
 
-div.cms-section-properties-btn, div.cms-edit-btn {
+div.cms-section-properties-btn, div.cms-edit-btn, div.cms-save-layout-btn {
 	padding: 2px 5px;
 	font-family: Arial, Verdana;
 	font-size: 13px;
 	font-weight: normal;
 	width: auto;
 	position: absolute;
 	color: black;
 	cursor: pointer;
 	-moz-border-radius: 10px;
 
 	margin-top: -10px;
 	z-index: 99;
 }
 
 /* === Styles for "Section Properties" button === */
 div.cms-section-properties-btn {
 	float: right;
 	position: relative;
 
 	margin-right: -10px;
 	border: 2px solid #A1D0A1;
 	background-color: #CCFF00;
 }
 
 div.cms-section-properties-btn-container {
 	border: 1px dashed #A1D0A1;
 	margin: 10px;
 }
 
 /* === Styles for "Edit" button === */
 div.cms-edit-btn-container {
 	border: 1px dashed #FF6E00;
 }
 
 div.cms-edit-btn {
 	margin-left: -10px;
 	border: 2px solid #FF6E00;
 	background-color: #FFCC00;
+	opacity: 0.5;
+}
+
+/* === Styles for "Save Layout" button === */
+div.cms-save-layout-btn {
+	float: left;
+	position: absolute;
+
+	margin-left: -10px;
+	border: 2px solid #A1D0A1;
+	background-color: #CCFF00;
 }
 
 /* === Styles for Template Editor === */
 div.block-edit-btn-container {
 	border: 1px dashed transparent;
 }
 
 div.block-edit-btn-container-over {
 	border-color: #FF6E00;
 }
 
 /* === Styles for element moving/sorting in theme === */
 div.movable-area {
 	min-height: 200px;
 }
 
 .move-helper {
 	border: 3px dashed #666;
 	/*width: auto !important;*/
 }
\ No newline at end of file