Index: branches/5.3.x/core/kernel/session/session_storage.php
===================================================================
--- branches/5.3.x/core/kernel/session/session_storage.php	(revision 15697)
+++ branches/5.3.x/core/kernel/session/session_storage.php	(revision 15698)
@@ -1,505 +1,505 @@
 <?php
 
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 /**
  * Implements Session Store in the Database
  *
  */
 class SessionStorage extends kDBBase {
 
 	/**
 	 * Reference to session
 	 *
 	 * @var Session
 	 * @access protected
 	 */
 	protected $Session = null;
 
 	var $Expiration;
 	var $SessionTimeout = 0;
 
 	var $DirectVars = Array ();
 	var $ChangedDirectVars = Array ();
 
 	var $PersistentVars = Array ();
 
 	var $OriginalData = Array ();
 
 	var $TimestampField;
 	var $SessionDataTable;
 	var $DataValueField;
 	var $DataVarField;
 
 	public function Init($prefix, $special)
 	{
 		parent::Init($prefix, $special);
 
 		$this->TableName = 'sessions';
 		$this->IDField = 'sid';
 		$this->TimestampField = 'expire';
 		$this->SessionDataTable = 'SessionData';
 		$this->DataValueField = 'value';
 		$this->DataVarField = 'var';
 	}
 
 	/**
 	 * Sets reference to session
 	 *
 	 * @param Session $session
 	 */
 	public function setSession(&$session)
 	{
 		$this->Session =& $session;
 		$this->SessionTimeout = $session->SessionTimeout;
 	}
 
 	/**
 	 * Calculates browser signature
 	 *
 	 * @return string
 	 */
 	function _getBrowserSignature()
 	{
 		$signature_parts = Array(
 			'HTTP_USER_AGENT', 'SERVER_PROTOCOL',
 			'HTTP_ACCEPT_CHARSET', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE'
 		);
 
 		$ret = '';
 
 		foreach ($signature_parts as $signature_part) {
 			if (array_key_exists($signature_part, $_SERVER)) {
 				$ret .= '&|&' . $_SERVER[$signature_part];
 			}
 		}
 
 		return md5( substr($ret, 3) );
 	}
 
 	function GetSessionDefaults()
 	{
 		$fields_hash = Array (
 			$this->IDField			=>	$this->Session->SID,
 			$this->TimestampField	=>	$this->Session->Expiration,
 		);
 
 		if (!defined('IS_INSTALL') || !IS_INSTALL) {
 			// this column was added only in 5.0.1 version,
 			// so accessing it while database is not upgraded
 			// will result in admin's inability to login inside
 			// installator
 			$fields_hash['BrowserSignature'] = $this->_getBrowserSignature();
 		}
 
 		// default values + values set during this script run
 
 		return array_merge($fields_hash, $this->DirectVars);
 	}
 
 	/**
 	 * Stores session to database
 	 *
 	 * @param bool $to_database
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function StoreSession($to_database = true)
 	{
 		if ( defined('IS_INSTALL') && IS_INSTALL && $to_database && !$this->Application->TableFound($this->TableName, true) ) {
 			return;
 		}
 
 		$fields_hash = $this->GetSessionDefaults();
 
 		if ( $to_database ) {
 			$this->Conn->doInsert($fields_hash, $this->TableName);
 		}
 
 		foreach ($fields_hash as $field_name => $field_value) {
 			$this->SetField($field_name, $field_value);
 		}
 
 		// ensure user groups are stored in a way, that kPermissionsHelper::CheckUserPermission can understand
 		$this->Session->StoreVar('UserGroups', $this->GetField('GroupList'), !$to_database);
 	}
 
 	function DeleteSession()
 	{
 		$this->DeleteSessions( Array ($this->Session->SID), SESSION_LOG_LOGGED_OUT );
 
 		$this->DirectVars = $this->ChangedDirectVars = $this->OriginalData = Array();
 	}
 
 	function UpdateSession($timeout = 0)
 	{
 		$this->SetField($this->TimestampField, $this->Session->Expiration);
 		$query = ' UPDATE '.$this->TableName.' SET '.$this->TimestampField.' = '.$this->Session->Expiration.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($this->Session->SID);
 		$this->Conn->Query($query);
 	}
 
 	function LocateSession($sid)
 	{
 		$sql = 'SELECT *
 				FROM ' . $this->TableName . '
 				WHERE ' . $this->IDField . ' = ' . $this->Conn->qstr($sid);
 		$result = $this->Conn->GetRow($sql);
 
 		if ($result === false) {
 			return false;
 		}
 
 		// perform security checks to ensure, that session is used by it's creator
 		if ($this->Application->ConfigValue('SessionBrowserSignatureCheck') && ($result['BrowserSignature'] != $this->_getBrowserSignature()) && $this->Application->GetVar('flashsid') === false) {
 			return false;
 		}
 
 		if ($this->Application->ConfigValue('SessionIPAddressCheck') && ($result['IpAddress'] != $this->Application->getClientIp())) {
 			// most secure, except for cases where NAT (Network Address Translation)
 			// is used and two or more computers can have same IP address
 			return false;
 		}
 
 		$this->DirectVars = $result;
 		$this->Expiration = $result[$this->TimestampField];
 
 		return true;
 	}
 
 	function GetExpiration()
 	{
 		return $this->Expiration;
 	}
 
 	function LoadData()
 	{
 		$query = 'SELECT '.$this->DataValueField.','.$this->DataVarField.' FROM '.$this->SessionDataTable.' WHERE '.$this->IDField.' = '.$this->Conn->qstr($this->Session->SID);
 
 		$this->OriginalData = $this->Conn->GetCol($query, $this->DataVarField);
 		return $this->OriginalData;
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param string $var_name
 	 * @param mixed $default
 	 * @return mixed
 	 */
 	function GetField($var_name, $default = false)
 	{
 		return isset($this->DirectVars[$var_name]) ? $this->DirectVars[$var_name] : $default;
 		//return $this->Conn->GetOne('SELECT '.$var_name.' FROM '.$this->TableName.' WHERE `'.$this->IDField.'` = '.$this->Conn->qstr($this->Session->GetID()) );
 	}
 
 	function SetField($var_name, $value)
 	{
 		$value_changed = !isset($this->DirectVars[$var_name]) || ($this->DirectVars[$var_name] != $value);
 		if ($value_changed) {
 			$this->DirectVars[$var_name] = $value;
 			$this->ChangedDirectVars[] = $var_name;
 			$this->ChangedDirectVars = array_unique($this->ChangedDirectVars);
 		}
 		//return $this->Conn->Query('UPDATE '.$this->TableName.' SET '.$var_name.' = '.$this->Conn->qstr($value).' WHERE '.$this->IDField.' = '.$this->Conn->qstr($this->Session->GetID()) );
 	}
 
 	/**
 	 * Saves changes in session to database using single REPLACE query
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function SaveData()
 	{
 		if ( !$this->Session->SID ) {
 			// can't save without sid
 			return ;
 		}
 
 		$replace = '';
 		$ses_data = $this->Session->Data->GetParams();
 
 		foreach ($ses_data as $key => $value) {
 			if ( isset($this->OriginalData[$key]) && $this->OriginalData[$key] == $value ) {
 				continue; //skip unchanged session data
 			}
 			else {
 				$replace .= sprintf("(%s, %s, %s),", $this->Conn->qstr($this->Session->SID), $this->Conn->qstr($key), $this->Conn->qstr($value));
 			}
 		}
 
 		$replace = rtrim($replace, ',');
 
 		if ( $replace != '' ) {
 			$query = ' REPLACE INTO ' . $this->SessionDataTable . ' (' . $this->IDField . ', ' . $this->DataVarField . ', ' . $this->DataValueField . ') VALUES ' . $replace;
 			$this->Conn->Query($query);
 		}
 
 		if ( $this->ChangedDirectVars ) {
 			$changes = Array ();
 
 			foreach ($this->ChangedDirectVars as $var) {
 				$changes[] = $var . ' = ' . $this->Conn->qstr($this->DirectVars[$var]);
 			}
 
 			$query = '	UPDATE ' . $this->TableName . '
 						SET ' . implode(',', $changes) . '
 						WHERE ' . $this->IDField . ' = ' . $this->Conn->qstr($this->Session->GetID());
 			$this->Conn->Query($query);
 		}
 	}
 
 	function RemoveFromData($var)
 	{
 		if ($this->Session->SessionSet) {
 			// only, when session is stored in database
 			$sql = 'DELETE FROM ' . $this->SessionDataTable . '
 					WHERE ' . $this->IDField . ' = ' . $this->Conn->qstr($this->Session->SID) . ' AND ' . $this->DataVarField . ' = ' . $this->Conn->qstr($var);
 			$this->Conn->Query($sql);
 		}
 
 		unset($this->OriginalData[$var]);
 	}
 
 	function GetFromData($var, $default = false)
 	{
 		return array_key_exists($var, $this->OriginalData) ? $this->OriginalData[$var] : $default;
 	}
 
 	function GetExpiredSIDs()
 	{
 		$sql = 'SELECT ' . $this->IDField . '
 				FROM ' . $this->TableName . '
 				WHERE ' . $this->TimestampField . ' > ' . adodb_mktime();
 
 		return $this->Conn->GetCol($sql);
 	}
 
 	function DeleteExpired()
 	{
 		$expired_sids = $this->GetExpiredSIDs();
 
 		$this->DeleteSessions($expired_sids);
 
 		return $expired_sids;
 	}
 
 	function DeleteSessions($session_ids, $delete_reason = SESSION_LOG_EXPIRED)
 	{
 		if (!$session_ids) {
 			return ;
 		}
 
-		$log_table = $this->Application->getUnitOption('session-log', 'TableName');
+		$log_table = $this->Application->getUnitConfig('session-log')->getTableName();
 
 		if ($log_table) {
 			// mark session with proper status
 			$sub_sql = 'SELECT ' . $this->TimestampField . ' - ' . $this->SessionTimeout . '
 						FROM ' . $this->TableName . '
 						WHERE ' . $this->IDField . ' = ' . $log_table . '.SessionId';
 
 			$sql = 'UPDATE ' . $log_table . '
 					SET Status = ' . $delete_reason . ', SessionEnd = (' . $sub_sql . ')
 					WHERE Status = ' . SESSION_LOG_ACTIVE . ' AND SessionId IN (' . implode(',', $session_ids) . ')';
  			$this->Conn->Query($sql);
 		}
 
 		$where_clause = ' WHERE ' . $this->IDField . ' IN (' . implode(',', $session_ids) . ')';
 		$sql = 'DELETE FROM ' . $this->SessionDataTable . $where_clause;
 		$this->Conn->Query($sql);
 
 		$sql = 'DELETE FROM ' . $this->TableName . $where_clause;
 		$this->Conn->Query($sql);
 
 		// delete debugger ouputs left of deleted sessions
 		foreach ($session_ids as $session_id) {
 			$debug_file = (defined('RESTRICTED') ? RESTRICTED : WRITEABLE . '/cache') . '/debug_@' . $session_id . '@.txt';
 			if (file_exists($debug_file)) {
 				@unlink($debug_file);
  			}
  		}
  	}
 
 	function LoadPersistentVars()
 	{
 		$user_id = $this->Session->RecallVar('user_id');
 		if ($user_id != USER_GUEST) {
 			// root & normal users
 			$sql = 'SELECT VariableValue, VariableName
 					FROM '.TABLE_PREFIX.'UserPersistentSessionData
 					WHERE PortalUserId = '.$user_id;
 			$this->PersistentVars = (array)$this->Conn->GetCol($sql, 'VariableName');
 		}
 		else {
 			$this->PersistentVars = Array ();
 		}
 	}
 
 	/**
 	 * Stores variable to persistent session
 	 *
 	 * @param string $var_name
 	 * @param mixed $var_value
 	 * @param bool $optional
 	 * @return void
 	 * @access public
 	 */
 	public function StorePersistentVar($var_name, $var_value, $optional = false)
 	{
 		$user_id = $this->Session->RecallVar('user_id');
 		if ( $user_id == USER_GUEST || $user_id === false ) {
 			// -2 (when not logged in), false (when after u:OnLogout event)
 			$this->Session->StoreVar($var_name, $var_value, $optional);
 			return;
 		}
 
 		$this->PersistentVars[$var_name] = $var_value;
 
 		$key_clause = 'PortalUserId = ' . $user_id . ' AND VariableName = ' . $this->Conn->qstr($var_name);
 
 		$sql = 'SELECT VariableName
 				FROM ' . TABLE_PREFIX . 'UserPersistentSessionData
 				WHERE ' . $key_clause;
 		$record_found = $this->Conn->GetOne($sql);
 
 		$fields_hash = Array (
 			'PortalUserId' => $user_id,
 			'VariableName' => $var_name,
 			'VariableValue' => $var_value,
 		);
 
 		if ( $record_found ) {
 			$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'UserPersistentSessionData', $key_clause);
 		}
 		else {
 			$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'UserPersistentSessionData');
 		}
 	}
 
 	/**
 	 * Gets persistent variable
 	 *
 	 * @param string $var_name
 	 * @param mixed $default
 	 * @return mixed
 	 * @access public
 	 */
 	public function RecallPersistentVar($var_name, $default = false)
 	{
 		if ( $this->Session->RecallVar('user_id') == USER_GUEST ) {
 			if ( $default == ALLOW_DEFAULT_SETTINGS ) {
 				$default = null;
 			}
 
 			return $this->Session->RecallVar($var_name, $default);
 		}
 
 		if ( array_key_exists($var_name, $this->PersistentVars) ) {
 			return $this->PersistentVars[$var_name];
 		}
 		elseif ( $default == ALLOW_DEFAULT_SETTINGS ) {
 			$default_user_id = $this->Application->ConfigValue('DefaultSettingsUserId');
 
 			if ( !$default_user_id ) {
 				$default_user_id = USER_ROOT;
 			}
 
 			$sql = 'SELECT VariableValue, VariableName
 					FROM ' . TABLE_PREFIX . 'UserPersistentSessionData
 					WHERE VariableName = ' . $this->Conn->qstr($var_name) . ' AND PortalUserId = ' . $default_user_id;
 			$value = $this->Conn->GetOne($sql);
 			$this->PersistentVars[$var_name] = $value;
 
 			if ( $value !== false ) {
 				$this->StorePersistentVar($var_name, $value); //storing it, so next time we don't load default user setting
 			}
 
 			return $value;
 		}
 
 		return $default;
 	}
 
 	/**
 	 * Removes variable from persistent session
 	 *
 	 * @param string $var_name
 	 * @return void
 	 * @access public
 	 */
 	function RemovePersistentVar($var_name)
 	{
 		unset($this->PersistentVars[$var_name]);
 
 		$user_id = $this->Session->RecallVar('user_id');
 
 		if ( $user_id == USER_GUEST || $user_id === false ) {
 			// -2 (when not logged in), false (when after u:OnLogout event)
 			$this->Session->RemoveVar($var_name);
 		}
 		else {
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserPersistentSessionData
 					WHERE PortalUserId = ' . $user_id . ' AND VariableName = ' . $this->Conn->qstr($var_name);
 			$this->Conn->Query($sql);
 		}
 	}
 
 	/**
 	 * Checks of object has given field
 	 *
 	 * @param string $name
 	 * @return bool
 	 * @access protected
 	 */
 	protected function HasField($name) { }
 
 	/**
 	 * Returns field values
 	 *
 	 * @return Array
 	 * @access protected
 	 */
 	protected function GetFieldValues() { }
 
 	/**
 	 * Returns unformatted field value
 	 *
 	 * @param string $field
 	 * @return string
 	 * @access protected
 	 */
 	protected function GetDBField($field) { }
 
 	/**
 	 * Returns true, when list/item was queried/loaded
 	 *
 	 * @return bool
 	 * @access protected
 	 */
 	protected function isLoaded() { }
 
 	/**
 	 * Returns specified field value from all selected rows.
 	 * Don't affect current record index
 	 *
 	 * @param string $field
 	 * @return Array
 	 * @access protected
 	 */
 	protected function GetCol($field) { }
 
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/db/cat_dbitem.php
===================================================================
--- branches/5.3.x/core/kernel/db/cat_dbitem.php	(revision 15697)
+++ branches/5.3.x/core/kernel/db/cat_dbitem.php	(revision 15698)
@@ -1,621 +1,621 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kCatDBItem extends kDBItem {
 
 	/**
 	 * Category path, needed for import
 	 *
 	 * @var Array
 	 */
 	var $CategoryPath = Array();
 
 	/**
 	 * Use automatic filename generation
 	 *
 	 * @var bool
 	 */
 	var $useFilenames = true;
 
 	/**
 	 * Use pending editing abilities during item (delegated by permissions)
 	 *
 	 * @var bool
 	 */
 	var $usePendingEditing = false;
 
 	/**
 	 * Removes all data from an object
 	 *
 	 * @param int $new_id
 	 * @return bool
 	 * @access public
 	 */
 	public function Clear($new_id = null)
 	{
 		parent::Clear($new_id);
 		$this->CategoryPath = Array();
 	}
 
 	/**
 	 * Set's prefix and special
 	 *
 	 * @param string $prefix
 	 * @param string $special
 	 * @access public
 	 */
 	function Init($prefix, $special)
 	{
 		parent::Init($prefix, $special);
 
-		$this->usePendingEditing = $this->Application->getUnitOption($this->Prefix, 'UsePendingEditing');
+		$this->usePendingEditing = $this->getUnitConfig()->getUsePendingEditing();
 	}
 
 	/**
 	 * Assigns primary category for the item
 	 *
 	 * @access public
 	 */
 	public function assignPrimaryCategory()
 	{
 		if ( $this->GetDBField('CategoryId') <= 0 ) {
 			// set primary category in item object
 			$this->SetDBField('CategoryId', $this->Application->GetVar('m_cat_id'));
 		}
 
 		$this->assignToCategory($this->GetDBField('CategoryId'), true);
 	}
 
 	/**
 	 * Updates previously loaded record with current item' values
 	 *
 	 * @access public
 	 * @param int $id Primary Key Id to update
 	 * @param Array $update_fields
 	 * @param bool $system_update
 	 * @return bool
 	 * @access public
 	 */
 	public function Update($id = null, $update_fields = null, $system_update = false)
 	{
 		if ( $this->useFilenames ) {
 			$this->checkFilename();
 			$this->generateFilename();
 		}
 
 		$ret = parent::Update($id, $update_fields, $system_update);
 
 		if ( $ret ) {
 			$filename = $this->useFilenames ? (string)$this->GetDBField('Filename') : '';
 			$sql = 'UPDATE ' . $this->CategoryItemsTable() . '
 					SET Filename = ' . $this->Conn->qstr($filename) . '
 					WHERE ItemResourceId = ' . $this->GetDBField('ResourceId');
 			$this->Conn->Query($sql);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns CategoryItems table based on current item mode (temp/live)
 	 *
 	 * @return string
 	 */
 	function CategoryItemsTable()
 	{
 		$table = TABLE_PREFIX.'CategoryItems';
 		if ($this->Application->IsTempTable($this->TableName)) {
 			$table = $this->Application->GetTempName($table, 'prefix:'.$this->Prefix);
 		}
 
 		return $table;
 	}
 
 	function checkFilename()
 	{
 		if( !$this->GetDBField('AutomaticFilename') )
 		{
 			$filename = $this->GetDBField('Filename');
 			$this->SetDBField('Filename', $this->stripDisallowed($filename) );
 		}
 	}
 
 	function Copy($cat_id=null)
 	{
 		if (!isset($cat_id)) $cat_id = $this->Application->GetVar('m_cat_id');
 		$this->NameCopy($cat_id);
 		return $this->Create($cat_id);
 	}
 
 	/**
 	 * Sets new name for item in case if it is being copied in same table
 	 *
 	 * @param array $master Table data from TempHandler
 	 * @param int $foreign_key ForeignKey value to filter name check query by
 	 * @param string $title_field FieldName to alter, by default - TitleField of the prefix
 	 * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name
 	 * @access public
 	 */
 	public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s')
 	{
-		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
+		$title_field = $this->getUnitConfig()->getTitleField();
 		if (!$title_field) return;
 
 		$new_name = $this->GetDBField($title_field);
 		$cat_id = (int)$this->Application->GetVar('m_cat_id');
 		$original_checked = false;
 		do {
 			if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) {
 				$new_name = 'Copy '.( (int)$regs[1] + 1 ).' of '.$regs[2];
 			}
 			elseif ($original_checked) {
 				$new_name = 'Copy of '.$new_name;
 			}
 			$query = 'SELECT '.$title_field.' FROM '.$this->TableName.'
 								LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON
 								('.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$this->TableName.'.ResourceId)
 								WHERE ('.TABLE_PREFIX.'CategoryItems.CategoryId = '.$cat_id.') AND '.
 								$title_field.' = '.$this->Conn->qstr($new_name);
 			$res = $this->Conn->GetOne($query);
 			$original_checked = true;
 		} while ($res !== false);
 		$this->SetDBField($title_field, $new_name);
 
 		// this is needed, because Create will create items in its own CategoryId (if it's set),
 		// but we need to create it in target Paste category @see{kCatDBItem::Create} and its primary_category detection
 		$this->SetDBField('CategoryId', $cat_id);
 	}
 
 	/**
 	 * Changes item primary category to given/current category
 	 *
 	 * @param int $category_id
 	 */
 	function MoveToCat($category_id = null)
 	{
 //		$this->NameCopy();
 		if (!isset($category_id)) {
 			$category_id = $this->Application->GetVar('m_cat_id');
 		}
 
 		$table_name = TABLE_PREFIX . 'CategoryItems';
 		if ($this->IsTempTable()) {
 			$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix);
 		}
 
 		// check if the item already exists in destination category
 		$sql = 'SELECT PrimaryCat
 				FROM ' . $table_name . '
 				WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')';
 		$is_primary = $this->Conn->GetOne($sql);
 
 		// if it's not found is_primary will be FALSE, if it's found but not primary it will be int 0
 		$exists = $is_primary !== false;
 
 		if ($exists) {
 			// if the item already exists in destination category
 			if ($is_primary) {
 				// do nothing when we paste to primary
 				return ;
 			}
 
 			// if it's not primary - delete it from destination category, as we will move it from current primary below
 			$sql = 'DELETE FROM ' . $table_name . '
 					WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')';
 			$this->Conn->Query($sql);
 		}
 
 		// change category id in existing primary category record
 		$sql = 'UPDATE ' . $table_name . '
 				SET CategoryId = ' . (int)$category_id . '
 				WHERE (ItemResourceId = ' . $this->GetDBField('ResourceId') . ') AND (PrimaryCat = 1)';
 		$this->Conn->Query($sql);
 
 		$this->Update();
 	}
 
 	/**
 	 * When item is deleted, then also delete it from all categories
 	 *
 	 * @param int $id
 	 * @return bool
 	 * @access public
 	 */
 	public function Delete($id = null)
 	{
 		if ( isset($id) ) {
 			$this->setID($id);
 		}
 
 		$this->Load($this->GetID());
 
 		$ret = parent::Delete();
 
 		if ( $ret ) {
 			// TODO: move to OnAfterItemDelete method
 			$query = '	DELETE FROM ' . $this->CategoryItemsTable() . '
 						WHERE ItemResourceId = ' . $this->GetDBField('ResourceId');
 			$this->Conn->Query($query);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Deletes item from categories
 	 *
 	 * @param Array $delete_category_ids
 	 * @author Alex
 	 */
 	function DeleteFromCategories($delete_category_ids)
 	{
-		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); // because item was loaded before by ResourceId
+		$id_field = $this->getUnitConfig()->getIDField(); // because item was loaded before by ResourceId
 
-		$ci_table = $this->Application->getUnitOption($this->Prefix . '-ci', 'TableName');
+		$ci_table = $this->Application->getUnitConfig($this->Prefix . '-ci')->getTableName();
 		$resource_id = $this->GetDBField('ResourceId');
 
 		$item_cats_sql = '	SELECT CategoryId
 							FROM %s
 							WHERE ItemResourceId = %s';
 
 		$delete_category_items_sql = '	DELETE FROM %s
 										WHERE ItemResourceId = %s AND CategoryId IN (%s)';
 
 		$category_ids = $this->Conn->GetCol( sprintf($item_cats_sql, $ci_table, $resource_id) );
 		$cats_left = array_diff($category_ids, $delete_category_ids);
 
 		if ( !$cats_left ) {
 			$sql = 'SELECT %s
 					FROM %s
 					WHERE ResourceId = %s';
 			$ids = $this->Conn->GetCol(sprintf($sql, $id_field, $this->TableName, $resource_id));
 
 			$temp_handler = $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems($this->Prefix, $this->Special, $ids);
 		}
 		else {
 			$this->Conn->Query( sprintf($delete_category_items_sql, $ci_table, $resource_id, implode(',', $delete_category_ids)) );
 
 			$sql = 'SELECT CategoryId
 					FROM %s
 					WHERE PrimaryCat = 1 AND ItemResourceId = %s';
 			$primary_cat_id = $this->Conn->GetCol(sprintf($sql, $ci_table, $resource_id));
 
 			if ( count($primary_cat_id) == 0 ) {
 				$sql = 'UPDATE %s
 						SET PrimaryCat = 1
 						WHERE (CategoryId = %s) AND (ItemResourceId = %s)';
 				$this->Conn->Query( sprintf($sql, $ci_table, reset($cats_left), $resource_id) );
 			}
 		}
 	}
 
 	/**
 	 * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result
 	 *
 	 * @param string $filename
 	 * @return string
 	 */
 	function stripDisallowed($filename)
 	{
 		$filenames_helper = $this->Application->recallObject('FilenamesHelper');
 		/* @var $filenames_helper kFilenamesHelper */
 
 		$table = $this->IsTempTable() ? $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems', 'prefix:'.$this->Prefix) : TABLE_PREFIX.'CategoryItems';
 
 		return $filenames_helper->stripDisallowed($table, 'ItemResourceId', $this->GetDBField('ResourceId'), $filename);
 	}
 
 	/* commented out because it's called only from stripDisallowed body, which is moved to helper
 
 	function checkAutoFilename($filename)
 	{
 		$filenames_helper = $this->Application->recallObject('FilenamesHelper');
 		return $filenames_helper->checkAutoFilename($this->TableName, $this->IDField, $this->GetID(), $filename);
 	}*/
 
 	/**
 	 * Generate item's filename based on it's title field value
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function generateFilename()
 	{
 		if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) {
 			return ;
 		}
 
-		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
+		$title_field = $this->getUnitConfig()->getTitleField();
 
 		if ( preg_match('/l([\d]+)_(.*)/', $title_field, $regs) ) {
 			// if title field is multilingual, then use it's name from primary language
 			$title_field = 'l' . $this->Application->GetDefaultLanguageId() . '_' . $regs[2];
 		}
 
 		$name = $this->stripDisallowed( $this->GetDBField($title_field) );
 
 		if ( $name != $this->GetDBField('Filename') ) {
 			$this->SetDBField('Filename', $name);
 		}
 	}
 
 	/**
 	 * Adds item to other category
 	 *
 	 * @param int $category_id
 	 * @param bool $is_primary
 	 * @return void
 	 * @access public
 	 */
 	public function assignToCategory($category_id, $is_primary = false)
 	{
 		$table = $this->CategoryItemsTable();
 		$key_clause = '(ItemResourceId = ' . $this->GetDBField('ResourceId') . ')';
 
 		// get all categories, where item is in
 		$sql = 'SELECT PrimaryCat, CategoryId
 				FROM ' . $table . '
 				WHERE ' . $key_clause;
 		$item_categories = $this->Conn->GetCol($sql, 'CategoryId');
 
 		$primary_found = $item_category_id = false;
 
 		if ( $item_categories ) {
 			// find primary category
 			foreach ($item_categories as $item_category_id => $primary_found) {
 				if ( $primary_found ) {
 					break;
 				}
 			}
 		}
 
 		if ( $primary_found && ($item_category_id == $category_id) && !$is_primary ) {
 			// want to make primary category as non-primary :(
 			return;
 		}
 		elseif ( !$primary_found ) {
 			$is_primary = true;
 		}
 
 		if ( $is_primary && $item_categories ) {
 			// reset primary mark from all other categories
 			$sql = 'UPDATE ' . $table . '
 					SET PrimaryCat = 0
 					WHERE ' . $key_clause;
 			$this->Conn->Query($sql);
 		}
 
 		// UPDATE & INSERT instead of REPLACE because CategoryItems table has no primary key defined in database
 		if ( isset($item_categories[$category_id]) ) {
 			$sql = 'UPDATE ' . $table . '
 					SET PrimaryCat = ' . ($is_primary ? 1 : 0) . '
 					WHERE ' . $key_clause . ' AND (CategoryId = ' . $category_id . ')';
 			$this->Conn->Query($sql);
 		}
 		else {
 			$fields_hash = 	Array(
 				'CategoryId'		=>	$category_id,
 				'ItemResourceId'	=>	$this->GetField('ResourceId'),
 				'PrimaryCat'		=>	$is_primary ? 1 : 0,
 				'ItemPrefix'		=>	$this->Prefix,
 				'Filename'			=>	$this->useFilenames ? (string)$this->GetDBField('Filename') : '', // because some prefixes does not use filenames,
 			);
 
 			$this->Conn->doInsert($fields_hash, $table);
 		}
 
 		// to ensure filename update after adding to another category
 		// this is critical since there may be an item with same filename in newly added category!
 		$this->Update();
 	}
 
 	/**
 	 * Removes item from category specified
 	 *
 	 * @param int $category_id
 	 */
 	function removeFromCategory($category_id)
 	{
 		$sql = 'DELETE FROM '.TABLE_PREFIX.'CategoryItems WHERE (CategoryId = %s) AND (ItemResourceId = %s)';
 		$this->Conn->Query( sprintf($sql, $category_id, $this->GetDBField('ResourceId')) );
 	}
 
 	/**
 	 * Returns list of columns, that could exist in imported file
 	 *
 	 * @return Array
 	 */
 	function getPossibleExportColumns()
 	{
 		static $columns = null;
 		if (!is_array($columns)) {
 			$columns = array_merge($this->Fields['AvailableColumns']['options'], $this->Fields['ExportColumns']['options']);
 		}
 		return $columns;
 	}
 
 	/**
 	 * Returns item's primary image data
 	 *
 	 * @return Array
 	 */
 	function getPrimaryImageData()
 	{
 		$sql = 'SELECT *
 				FROM '.TABLE_PREFIX.'CatalogImages
 				WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (DefaultImg = 1)';
 		$image_data = $this->Conn->GetRow($sql);
 		if (!$image_data) {
 			// 2. no primary image, then get image with name "main"
 			$sql = 'SELECT *
 				FROM '.TABLE_PREFIX.'CatalogImages
 				WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (Name = "main")';
 			$image_data = $this->Conn->GetRow($sql);
 		}
 		return $image_data;
 	}
 
 	function ChangeStatus($new_status, $pending_editing = false)
 	{
-		$status_field = $this->getStatusField();
+		$status_field = $this->getUnitConfig()->getStatusField(true);
 
-		if ( $new_status != $this->GetDBField($status_field) ) {
+		if ($new_status != $this->GetDBField($status_field)) {
 			// status was changed
 			$this->sendEmails($new_status, $pending_editing);
 		}
 
 		$this->SetDBField($status_field, $new_status);
 
 		return $this->Update();
 	}
 
 	function sendEmails($new_status, $pending_editing = false)
 	{
-		$owner_field = $this->Application->getUnitOption($this->Prefix, 'OwnerField');
-		if (!$owner_field) {
-			$owner_field = 'CreatedById';
-		}
+		$config = $this->getUnitConfig();
+
+		$owner_field = $config->getOwnerField('CreatedById');
+		$event_name = $config->getPermItemPrefix();
 
-		$event_name = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix');
 		if ($pending_editing) {
 			$event_name .= '.MODIFY';
 		}
 
 		$event_name .= $new_status == STATUS_ACTIVE ? '.APPROVE' : '.DENY';
 		$this->Application->emailUser($event_name, $this->GetDBField($owner_field));
 	}
 
 	/**
 	 * Approves changes made to category item
 	 *
 	 * @return bool
 	 */
 	function ApproveChanges()
 	{
 		$original_id = $this->GetDBField('OrgId');
 
 		if ( !($this->usePendingEditing && $original_id) ) {
 			// non-pending copy of original link
 			return $this->ChangeStatus(STATUS_ACTIVE);
 		}
 
 		if ( $this->raiseEvent('OnBeforeDeleteOriginal', null, Array ('original_id' => $original_id)) ) {
 			// delete original item, because changes made in pending copy (this item) got to be approved in this method
 			$temp_handler = $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems($this->Prefix, $this->Special, Array ($original_id));
 
 			$this->raiseEvent('OnAfterDeleteOriginal', null, Array ('original_id' => $original_id));
 			$this->SetDBField('OrgId', 0);
 
 			return $this->ChangeStatus(STATUS_ACTIVE, true);
 		}
 
 		return false;
 	}
 
 	/**
 	 * Decline changes made to category item
 	 *
 	 * @return bool
 	 */
 	function DeclineChanges()
 	{
 		$original_id = $this->GetDBField('OrgId');
 
 		if ( !($this->usePendingEditing && $original_id) ) {
 			// non-pending copy of original link
 			return $this->ChangeStatus(STATUS_DISABLED);
 		}
 
 		// delete this item, because changes made in pending copy (this item) will be declined in this method
 		$temp_handler = $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 		/* @var $temp_handler kTempTablesHandler */
 
 		$temp_handler->DeleteItems($this->Prefix, $this->Special, Array ($this->GetID()));
 
 		$this->sendEmails(STATUS_DISABLED, true);
 		// original item is not changed here, because it is already enabled (thrus pending copy is visible to item's owner or admin with permission)
 		return true;
 	}
 
 	function RegisterHit()
 	{
 		$already_viewed = $this->Application->RecallVar($this->getPrefixSpecial().'_already_viewed');
 		$already_viewed = $already_viewed ? unserialize($already_viewed) : Array ();
 
 		$id = $this->GetID();
 		if (!in_array($id, $already_viewed)) {
-			$property_map = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings');
+			$property_map = $this->getUnitConfig()->getItemPropertyMappings(Array ());
+
 			if (!$property_map) {
 				return ;
 			}
+
 			$hits_field = $property_map['ClickField'];
 			$new_hits = $this->GetDBField($hits_field) + 1;
 
 			$sql = 'SELECT MAX('.$hits_field.')
 					FROM '.$this->TableName.'
 					WHERE FLOOR('.$hits_field.') = '.$new_hits;
 			$max_hits = $this->Conn->GetOne($sql);
+
 			if ($max_hits) {
 				$new_hits = $max_hits + 0.000001;
 			}
 
-			$fields_hash = Array (
-				$hits_field => $new_hits,
-			);
+			$fields_hash = Array ($hits_field => $new_hits,);
 			$this->Conn->doUpdate($fields_hash, $this->TableName, $this->IDField.' = '.$id);
 
 			array_push($already_viewed, $id);
 			$this->Application->StoreVar($this->getPrefixSpecial().'_already_viewed', serialize($already_viewed));
 		}
 	}
 
 	/**
 	 * Returns part of SQL WHERE clause identifying the record, ex. id = 25
 	 *
 	 * @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method
 	 * @param Array $keys_hash alternative, then item id, keys hash to load item by
 	 * @see kDBItem::Load()
 	 * @see kDBItem::Update()
 	 * @see kDBItem::Delete()
 	 * @return string
 	 * @access protected
 	 */
 	protected function GetKeyClause($method = null, $keys_hash = null)
 	{
 		if ( $method == 'load' && !isset($keys_hash) ) {
 			// for item with many categories makes primary to load
 			$ci_table = TABLE_PREFIX . 'CategoryItems';
 
-			if ( $this->IsTempTable() ) {
+			if ($this->IsTempTable()) {
 				$ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $this->Prefix);
 			}
 
 			// ensures, that CategoryId calculated field has primary category id in it
 			$keys_hash = Array (
 				$this->IDField => $this->ID,
 				'`' . $ci_table . '`.`PrimaryCat`' => 1
 			);
 		}
 
 		return parent::GetKeyClause($method, $keys_hash);
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/db/db_tag_processor.php
===================================================================
--- branches/5.3.x/core/kernel/db/db_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/kernel/db/db_tag_processor.php	(revision 15698)
@@ -1,3142 +1,3179 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kDBTagProcessor extends kTagProcessor {
 
 	/**
 	 * Returns true if "new" button was pressed in toolbar
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsNewMode($params)
 	{
 		$object = $this->getObject($params);
 		return $object->GetID() <= 0;
 	}
 
 	/**
 	 * Returns view menu name for current prefix
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function GetItemName($params)
 	{
-		$item_name = $this->Application->getUnitOption($this->Prefix, 'ViewMenuPhrase');
+		$item_name = $this->getUnitConfig()->getViewMenuPhrase();
+
 		return $this->Application->Phrase($item_name);
 	}
 
 	function ViewMenu($params)
 	{
 		$block_params = $params;
 		unset($block_params['block']);
 		$block_params['name'] = $params['block'];
 
 		$list =& $this->GetList($params);
 		$block_params['PrefixSpecial'] = $list->getPrefixSpecial();
 		return $this->Application->ParseBlock($block_params);
 	}
 
 	function SearchKeyword($params)
 	{
 		$list =& $this->GetList($params);
 
 		return $this->Application->RecallVar($list->getPrefixSpecial() . '_search_keyword');
 	}
 
 	/**
 	 * Draw filter menu content (for ViewMenu) based on filters defined in config
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function DrawFilterMenu($params)
 	{
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['spearator_block'];
 		$separator = $this->Application->ParseBlock($block_params);
-		$filter_menu = $this->Application->getUnitOption($this->Prefix,'FilterMenu');
-		if (!$filter_menu) {
-			trigger_error('<span class="debug_error">no filters defined</span> for prefix <b>'.$this->Prefix.'</b>, but <b>DrawFilterMenu</b> tag used', E_USER_NOTICE);
+		$filter_menu = $this->getUnitConfig()->getFilterMenu();
+
+		if ( !$filter_menu ) {
+			trigger_error('<span class="debug_error">no filters defined</span> for prefix <b>' . $this->Prefix . '</b>, but <b>DrawFilterMenu</b> tag used', E_USER_NOTICE);
+
 			return '';
 		}
 
 		// Params: label, filter_action, filter_status
 		$block_params['name'] = $params['item_block'];
 
-		$view_filter = $this->Application->RecallVar($this->getPrefixSpecial().'_view_filter');
-		if ($view_filter === false) {
+		$view_filter = $this->Application->RecallVar($this->getPrefixSpecial() . '_view_filter');
+		if ( $view_filter === false ) {
 			$event_params = Array ('prefix' => $this->Prefix, 'special' => $this->Special, 'name' => 'OnRemoveFilters');
-			$this->Application->HandleEvent( new kEvent($event_params) );
-			$view_filter = $this->Application->RecallVar($this->getPrefixSpecial().'_view_filter');
+			$this->Application->HandleEvent(new kEvent($event_params));
+			$view_filter = $this->Application->RecallVar($this->getPrefixSpecial() . '_view_filter');
 		}
+
 		$view_filter = unserialize($view_filter);
 
-		$filters = Array();
+		$filters = Array ();
 		$prefix_special = $this->getPrefixSpecial();
 
 		foreach ($filter_menu['Filters'] as $filter_key => $filter_params) {
-			$group_params = isset($filter_params['group_id']) ? $filter_menu['Groups'][ $filter_params['group_id'] ] : Array();
-			if (!isset($group_params['element_type'])) {
+			$group_params = isset($filter_params['group_id']) ? $filter_menu['Groups'][$filter_params['group_id']] : Array ();
+			if ( !isset($group_params['element_type']) ) {
 				$group_params['element_type'] = 'checkbox';
 			}
 
-			if (!$filter_params) {
+			if ( !$filter_params ) {
 				$filters[] = $separator;
 				continue;
 			}
 
-			$block_params['label'] = addslashes( $this->Application->Phrase($filter_params['label']) );
-			if (getArrayValue($view_filter,$filter_key)) {
+			$block_params['label'] = addslashes($this->Application->Phrase($filter_params['label']));
+
+			if ( getArrayValue($view_filter, $filter_key) ) {
 				$submit = 0;
-				if (isset($params['old_style'])) {
+				if ( isset($params['old_style']) ) {
 					$status = $group_params['element_type'] == 'checkbox' ? 1 : 2;
 				}
 				else {
 					$status = $group_params['element_type'] == 'checkbox' ? '[\'img/check_on.gif\']' : '[\'img/menu_dot.gif\']';
 				}
 			}
 			else {
 				$submit = 1;
 				$status = 'null';
 			}
-			$block_params['filter_action'] = 'set_filter("'.$prefix_special.'","'.$filter_key.'","'.$submit.'",'.$params['ajax'].');';
+
+			$block_params['filter_action'] = 'set_filter("' . $prefix_special . '","' . $filter_key . '","' . $submit . '",' . $params['ajax'] . ');';
 			$block_params['filter_status'] = $status; // 1 - checkbox, 2 - radio, 0 - no image
 			$filters[] = $this->Application->ParseBlock($block_params);
 		}
 
 		return implode('', $filters);
 	}
 
 	/**
 	 * Draws auto-refresh submenu in View Menu.
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function DrawAutoRefreshMenu($params)
 	{
 		$refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals');
 		if (!$refresh_intervals) {
 			trigger_error('<span class="debug_error">no refresh intervals defined</span> for prefix <strong>'.$this->Prefix.'</strong>, but <strong>DrawAutoRefreshMenu</strong> tag used', E_USER_NOTICE);
 			return '';
 		}
 
 		$refresh_intervals = explode(',', $refresh_intervals);
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		$current_refresh_interval = $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_refresh_interval.'.$view_name);
 		if ($current_refresh_interval === false) {
 			// if no interval was selected before, then choose 1st interval
 			$current_refresh_interval = $refresh_intervals[0];
 		}
 
 		$ret = '';
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		foreach ($refresh_intervals as $refresh_interval) {
 			$block_params['label'] = $this->_formatInterval($refresh_interval);
 			$block_params['refresh_interval'] = $refresh_interval;
 			$block_params['selected'] = $current_refresh_interval == $refresh_interval;
 			$ret .= $this->Application->ParseBlock($block_params);
 		}
 		return $ret;
 	}
 
 	/**
 	 * Tells, that current grid is using auto refresh
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function UseAutoRefresh($params)
 	{
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		return $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_auto_refresh.'.$view_name);
 	}
 
 	/**
 	 * Returns current grid refresh interval
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function AutoRefreshInterval($params)
 	{
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		return $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_refresh_interval.'.$view_name);
 	}
 
 	/**
 	 * Formats time interval using given text for hours and minutes
 	 *
 	 * @param int $interval minutes
 	 * @param string $hour_text Text for hours
 	 * @param string $min_text Text for minutes
 	 * @return string
 	 */
 	function _formatInterval($interval, $hour_text = 'h', $min_text = 'min')
 	{
 		// 65
 		$minutes = $interval % 60;
 		$hours = ($interval - $minutes) / 60;
 
 		$ret = '';
 		if ($hours) {
 			$ret .= $hours.$hour_text.' ';
 		}
 
 		if ($minutes) {
 			$ret .= $minutes.$min_text;
 		}
 
 		return $ret;
 	}
 
 	function IterateGridFields($params)
 	{
 		$mode = $params['mode'];
 		$def_block = isset($params['block']) ? $params['block'] : '';
 		$force_block = isset($params['force_block']) ? $params['force_block'] : false;
 
-		$grids = $this->Application->getUnitOption($this->Prefix,'Grids');
-		$grid_config = $grids[$params['grid']]['Fields'];
+		$grid = $this->getUnitConfig()->getGridByName($params['grid']);
+		$grid_config = $grid['Fields'];
 
 		$picker_helper = $this->Application->recallObject('ColumnPickerHelper');
 		/* @var $picker_helper kColumnPickerHelper */
 
 		$picker_helper->ApplyPicker($this->getPrefixSpecial(), $grid_config, $params['grid']);
 
-		if ($mode == 'fields') {
-			return "'".join("','", array_keys($grid_config))."'";
+		if ( $mode == 'fields' ) {
+			return "'" . join("','", array_keys($grid_config)) . "'";
 		}
 
 		$object =& $this->GetList($params);
 
 		$o = '';
 		$i = 0;
 
 		foreach ($grid_config as $field => $options) {
 			$i++;
 			$block_params = $this->prepareTagParams($params);
 			$block_params = array_merge($block_params, $options);
 
 			$block_params['block_name'] = array_key_exists($mode . '_block', $block_params) ? $block_params[$mode . '_block'] : $def_block;
 			$block_params['name'] = $force_block ? $force_block : $block_params['block_name'];
 			$block_params['field'] = $field;
 			$block_params['sort_field'] = isset($options['sort_field']) ? $options['sort_field'] : $field;
 			$block_params['filter_field'] = isset($options['filter_field']) ? $options['filter_field'] : $field;
 
 			$w = $picker_helper->GetWidth($field);
 
-			if ($w) {
+			if ( $w ) {
 				// column picker width overrides width from unit config
 				$block_params['width'] = $w;
 			}
 
 			$field_options = $object->GetFieldOptions($field);
-			if (array_key_exists('use_phrases', $field_options)) {
+			if ( array_key_exists('use_phrases', $field_options) ) {
 				$block_params['use_phrases'] = $field_options['use_phrases'];
 			}
 
 			$block_params['is_last'] = ($i == count($grid_config));
 
-			$o.= $this->Application->ParseBlock($block_params, 1);
+			$o .= $this->Application->ParseBlock($block_params, 1);
 		}
 
 		return $o;
 	}
 
 	function PickerCRC($params)
 	{
 		/* @var $picker_helper kColumnPickerHelper */
 		$picker_helper = $this->Application->recallObject('ColumnPickerHelper');
 		$picker_helper->SetGridName($params['grid']);
 		$data = $picker_helper->LoadColumns($this->getPrefixSpecial());
 		return $data['crc'];
 	}
 
 	function FreezerPosition($params)
 	{
 		/* @var $picker_helper kColumnPickerHelper */
 		$picker_helper = $this->Application->recallObject('ColumnPickerHelper');
 		$picker_helper->SetGridName($params['grid']);
 		$data = $picker_helper->LoadColumns($this->getPrefixSpecial());
 		$freezer_pos = array_search('__FREEZER__', $data['order']);
 		return $freezer_pos === false || in_array('__FREEZER__', $data['hidden_fields']) ? 1 : ++$freezer_pos;
 	}
 
 	function GridFieldsCount($params)
 	{
-		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-		$grid_config = $grids[$params['grid']]['Fields'];
+		$grid = $this->getUnitConfig()->getGridByName($params['grid']);
 
-		return count($grid_config);
+		return count($grid['Fields']);
 	}
 
 	/**
 	 * Prints list content using block specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PrintList($params)
 	{
 		$params['no_table'] = 1;
 		return $this->PrintList2($params);
 	}
 
 	function InitList($params)
 	{
 		$list_name = isset($params['list_name']) ? $params['list_name'] : '';
 
 		$names_mapping = $this->Application->GetVar('NamesToSpecialMapping', Array ());
 
 		if ( getArrayValue($names_mapping, $this->Prefix, $list_name) === false ) {
 			$list =& $this->GetList($params);
 		}
 	}
 
 	function BuildListSpecial($params)
 	{
 		return $this->Special;
 	}
 
 	/**
 	 * Returns key, that identifies each list on template (used internally, not tag)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function getUniqueListKey($params)
 	{
 		$types = array_key_exists('types', $params) ? $params['types'] : '';
 		$except = array_key_exists('except', $params) ? $params['except'] : '';
 		$list_name = array_key_exists('list_name', $params) ? $params['list_name'] : '';
 
 		if (!$list_name) {
 			$list_name = $this->Application->Parser->GetParam('list_name');
 		}
 
 		return $types . $except . $list_name;
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param Array $params
 	 * @return kDBList
 	 */
 	function &GetList($params)
 	{
 		$list_name = $this->SelectParam($params, 'list_name,name');
 		if ( !$list_name ) {
 			$list_name = $this->Application->Parser->GetParam('list_name');
 		}
 
 		$requery = isset($params['requery']) && $params['requery'];
 		$main_list = array_key_exists('main_list', $params) && $params['main_list'];
 
 		$names_mapping = $this->Application->GetVar('NamesToSpecialMapping', Array ());
 
 		if ( !array_key_exists($this->Prefix, $names_mapping) ) {
 			// create prefix-based array to special mapping storage
 			$names_mapping[$this->Prefix] = Array ();
 		}
 
 		if ( $list_name && !$requery ) {
 			// list with "list_name" parameter
 			if ( !array_key_exists($list_name, $names_mapping[$this->Prefix]) ) {
 				// special missing -> generate one
 				$special = $main_list ? $this->Special : $this->BuildListSpecial($params);
 			}
 			else {
 				// get special, formed during list initialization
 				$special = $names_mapping[$this->Prefix][$list_name];
 			}
 		}
 		else {
 			// list without "list_name" parameter
 			$special = $main_list ? $this->Special : $this->BuildListSpecial($params);
 		}
 
 		$prefix_special = rtrim($this->Prefix . '.' . $special, '.');
 		$params['skip_counting'] = true;
 
 		$list = $this->Application->recallObject($prefix_special, $this->Prefix . '_List', $params);
 		/* @var $list kDBList */
 
 		if ( !array_key_exists('skip_quering', $params) || !$params['skip_quering'] ) {
 			if ( $requery ) {
 				$this->Application->HandleEvent(new kEvent($prefix_special . ':OnListBuild', $params));
 			}
 
 			if ( array_key_exists('offset', $params) ) {
 				$list->SetOffset($list->GetOffset() + $params['offset']); // apply custom offset
 			}
 
 			$list->Query($requery);
 
 			if ( array_key_exists('offset', $params) ) {
 				$list->SetOffset($list->GetOffset() - $params['offset']); // remove custom offset
 			}
 		}
 
 		$this->Init($this->Prefix, $special);
 
 		if ( $list_name ) {
 			$names_mapping[$this->Prefix][$list_name] = $special;
 			$this->Application->SetVar('NamesToSpecialMapping', $names_mapping);
 		}
 
 		return $list;
 	}
 
 	function ListMarker($params)
 	{
 		$list =& $this->GetList($params);
 		$ret = $list->getPrefixSpecial();
 
 		if (array_key_exists('as_preg', $params) && $params['as_preg']) {
 			$ret = preg_quote($ret, '/');
 		}
 
 		return $ret;
 	}
 
 	function CombinedSortingDropDownName($params)
 	{
 		$list =& $this->GetList($params);
 
 		return $list->getPrefixSpecial() . '_CombinedSorting';
 	}
 
 	/**
 	 * Prepares name for field with event in it (used only on front-end)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SubmitName($params)
 	{
 		$list =& $this->GetList($params);
 
 		$prefix_special = $list->getPrefixSpecial();
 
 		return 'events[' . $prefix_special . '][' . $params['event'] . ']';
 	}
 
 	/**
 	 * Prints list content using block specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PrintList2($params)
 	{
 		$per_page = $this->SelectParam($params, 'per_page,max_items');
 		if ( $per_page !== false ) {
 			$params['per_page'] = $per_page;
 		}
 
 		$list =& $this->GetList($params);
 		$o = '';
 
 		$direction = (isset($params['direction']) && $params['direction'] == "H") ? "H" : "V";
 		$columns = (isset($params['columns'])) ? $params['columns'] : 1;
 
-		$id_field = (isset($params['id_field'])) ? $params['id_field'] : $this->Application->getUnitOption($this->Prefix, 'IDField');
+		$config = $this->getUnitConfig();
+		$id_field = (isset($params['id_field'])) ? $params['id_field'] : $config->getIDField();
 
 		if ( $columns > 1 && $direction == 'V' ) {
 			$records_left = array_splice($list->Records, $list->GetSelectedCount()); // because we have 1 more record for "More..." link detection (don't need to sort it)
 			$list->Records = $this->LinearToVertical($list->Records, $columns, $list->GetPerPage());
 			$list->Records = array_merge($list->Records, $records_left);
 		}
 
 		$list->GoFirst();
 
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 		$block_params['pass_params'] = 'true';
 		$block_params['column_width'] = $params['column_width'] = 100 / $columns;
 		$block_start_row_params = $this->prepareTagParams($params);
 		$block_start_row_params['name'] = $this->SelectParam($params, 'row_start_render_as,block_row_start,row_start_block');
 
 		$block_end_row_params = $this->prepareTagParams($params);
 		$block_end_row_params['name'] = $this->SelectParam($params, 'row_end_render_as,block_row_end,row_end_block');
 
 		$block_empty_cell_params = $this->prepareTagParams($params);
 		$block_empty_cell_params['name'] = $this->SelectParam($params, 'empty_cell_render_as,block_empty_cell,empty_cell_block');
 
 		$i = 0;
 
 		$backup_id = $this->Application->GetVar($this->Prefix . '_id');
 		$displayed = Array ();
 		$column_number = 1;
 
-		$cache_mod_rw = $this->Application->getUnitOption($this->Prefix, 'CacheModRewrite') &&
-						$this->Application->RewriteURLs() && !$this->Application->isCachingType(CACHING_TYPE_MEMORY);
+		$cache_mod_rw = $config->getCacheModRewrite() && $this->Application->RewriteURLs() && !$this->Application->isCachingType(CACHING_TYPE_MEMORY);
 
 		$limit = isset($params['limit']) ? $params['limit'] : false;
 
 		while (!$list->EOL() && (!$limit || $i<$limit)) {
 			$this->Application->SetVar($this->getPrefixSpecial() . '_id', $list->GetDBField($id_field)); // for edit/delete links using GET
 			$this->Application->SetVar($this->Prefix . '_id', $list->GetDBField($id_field));
 			$block_params['is_last'] = ($i == $list->GetSelectedCount() - 1);
 			$block_params['last_row'] = ($i + (($i + 1) % $columns) >= $list->GetSelectedCount() - 1);
 			$block_params['not_last'] = !$block_params['is_last']; // for front-end
 
 			if ( $cache_mod_rw ) {
 				$serial_name = $this->Application->incrementCacheSerial($this->Prefix, $list->GetDBField($id_field), false);
 
 				if ( $this->Prefix == 'c' ) {
 					// for listing subcategories in category
 					$this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('NamedParentPath'));
 					$this->Application->setCache('category_tree[%CIDSerial:' . $list->GetDBField($id_field) . '%]', $list->GetDBField('TreeLeft') . ';' . $list->GetDBField('TreeRight'));
 				}
 				else {
 					// for listing items in category
 					$this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('Filename'));
 
 					$serial_name = $this->Application->incrementCacheSerial('c', $list->GetDBField('CategoryId'), false);
 					$this->Application->setCache('filenames[%' . $serial_name . '%]', $list->GetDBField('CategoryFilename'));
 				}
 			}
 
 			if ( $i % $columns == 0 ) {
 				// record in this iteration is first in row, then open row
 				$column_number = 1;
 				$o .= $block_start_row_params['name'] ? $this->Application->ParseBlock($block_start_row_params) : (!isset($params['no_table']) ? '<tr>' : '');
 			}
 			else {
 				$column_number++;
 			}
 
 			$block_params['first_col'] = $column_number == 1 ? 1 : 0;
 			$block_params['last_col'] = $column_number == $columns ? 1 : 0;
 
 			$block_params['column_number'] = $column_number;
 			$block_params['num'] = ($i + 1);
 
 			$this->PrepareListElementParams($list, $block_params); // new, no need to rewrite PrintList
 			$o .= $this->Application->ParseBlock($block_params);
 			array_push($displayed, $list->GetDBField($id_field));
 
 			if ( $direction == 'V' && $list->GetSelectedCount() % $columns > 0 && $column_number == ($columns - 1) && ceil(($i + 1) / $columns) > $list->GetSelectedCount() % ceil($list->GetSelectedCount() / $columns) ) {
 				// if vertical output, then draw empty cells vertically, not horizontally
 				$o .= $block_empty_cell_params['name'] ? $this->Application->ParseBlock($block_empty_cell_params) : '<td>&nbsp;</td>';
 				$i++;
 			}
 
 			if ( ($i + 1) % $columns == 0 ) {
 				// record in next iteration is first in row too, then close this row
 				$o .= $block_end_row_params['name'] ? $this->Application->ParseBlock($block_end_row_params) : (!isset($params['no_table']) ? '</tr>' : '');
 			}
 
 			if ( $this->Special && $this->Application->hasObject($this->Prefix) ) {
 				// object, produced by "kDBList::linkToParent" method, that otherwise would keep it's id
 				$item = $this->Application->recallObject($this->Prefix);
 				/* @var $item kDBBase */
 
 				if ( $item instanceof kDBItem ) {
 					$this->Application->removeObject($this->Prefix);
 				}
 			}
 
 			$list->GoNext();
 			$i++;
 		}
 
 		// append empty cells in place of missing cells in last row
 		while ($i % $columns != 0) {
 			// until next cell will be in new row append empty cells
 			$o .= $block_empty_cell_params['name'] ? $this->Application->ParseBlock($block_empty_cell_params) : '<td>&nbsp;</td>';
 
 			if ( ($i + 1) % $columns == 0 ) {
 				// record in next iteration is first in row too, then close this row
 				$o .= $block_end_row_params['name'] ? $this->Application->ParseBlock($block_end_row_params) : '</tr>';
 			}
 			$i++;
 		}
 
 		$cur_displayed = $this->Application->GetVar($this->Prefix . '_displayed_ids');
 		if ( !$cur_displayed ) {
 			$cur_displayed = Array ();
 		}
 		else {
 			$cur_displayed = explode(',', $cur_displayed);
 		}
 
 		$displayed = array_unique(array_merge($displayed, $cur_displayed));
 		$this->Application->SetVar($this->Prefix . '_displayed_ids', implode(',', $displayed));
 
 		$this->Application->SetVar($this->Prefix . '_id', $backup_id);
 		$this->Application->SetVar($this->getPrefixSpecial() . '_id', '');
 
 		if ( isset($params['more_link_render_as']) ) {
 			$block_params = $params;
 			$params['render_as'] = $params['more_link_render_as'];
 			$o .= $this->MoreLink($params);
 		}
 
 		return $o;
 	}
 
 	/**
 	 * Returns ID of previous record (related to current) in list.
 	 * Use only on item detail pages.
 	 *
 	 * @param Array $params
 	 * @return int
 	 * @access protected
 	 */
 	protected function PreviousResource($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$list_helper = $this->Application->recallObject('ListHelper');
 		/* @var $list_helper ListHelper */
 
-		$select_clause = $this->Application->getUnitOption($object->Prefix, 'NavigationSelectClause', null);
+		$select_clause = $object->getUnitConfig()->getNavigationSelectClause(null);
 
 		return $list_helper->getNavigationResource($object, $params['list'], false, $select_clause);
 	}
 
 	/**
 	 * Returns ID of next record (related to current) in list.
 	 * Use only on item detail pages.
 	 *
 	 * @param Array $params
 	 * @return int
 	 * @access protected
 	 */
 	protected function NextResource($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$list_helper = $this->Application->recallObject('ListHelper');
 		/* @var $list_helper ListHelper */
 
-		$select_clause = $this->Application->getUnitOption($object->Prefix, 'NavigationSelectClause', null);
+		$select_clause = $object->getUnitConfig()->getNavigationSelectClause(null);
 
 		return $list_helper->getNavigationResource($object, $params['list'], true, $select_clause);
 	}
 
 	/**
 	 * Allows to modify block params & current list record before PrintList parses record
 	 *
 	 * @param kDBList $object
 	 * @param Array $block_params
 	 * @return void
 	 * @access protected
 	 */
 	protected function PrepareListElementParams(&$object, &$block_params)
 	{
 //		$fields_hash =& $object->getCurrentRecord();
 
 	}
 
 	/**
 	 * Renders given block name, when there there is more data in list, then are displayed right now
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function MoreLink($params)
 	{
 		$per_page = $this->SelectParam($params, 'per_page,max_items');
 
 		if ( $per_page !== false ) {
 			$params['per_page'] = $per_page;
 		}
 
 		$list =& $this->GetList($params);
 
 		if ( $list->isCounted() ) {
 			$has_next_page = $list->GetPage() < $list->GetTotalPages();
 		}
 		else {
 			// selected more, then on the page -> has more
 			$has_next_page = $list->GetPerPage() < $list->GetRecordsCount();
 		}
 
 		if ( $has_next_page ) {
 			$block_params = Array ('name' => $this->SelectParam($params, 'render_as,block'));
 
 			return $this->Application->ParseBlock($block_params);
 		}
 
 		return '';
 	}
 
 	function PageLink($params)
 	{
 		static $default_per_page = Array ();
 
 		$object =& $this->GetList($params);
 		/* @var $object kDBList */
 
 		// process sorting
 		if ($object->isMainList()) {
 			if (!array_key_exists('sort_by', $params)) {
 				$sort_by = $this->Application->GetVar('sort_by');
 
 				if ($sort_by !== false) {
 					$params['sort_by'] = $sort_by;
 				}
 			}
 		}
 
 		$prefix_special = $this->getPrefixSpecial();
 
 		// process page
 		$page = array_key_exists('page', $params) ? $params['page'] : $this->Application->GetVar($prefix_special . '_Page');
 
 		if (!$page) {
 			// ensure, that page is always present
 			if ($object->isMainList()) {
 				$params[$prefix_special . '_Page'] = $this->Application->GetVar('page', 1);
 			}
 			else {
 				$params[$prefix_special . '_Page'] = 1;
 			}
 		}
 
 		if (array_key_exists('page', $params)) {
 			$params[$prefix_special . '_Page'] = $params['page'];
 			unset($params['page']);
 		}
 
 		// process per-page
 		$per_page = array_key_exists('per_page', $params) ? $params['per_page'] : $this->Application->GetVar($prefix_special . '_PerPage');
 
 		if (!$per_page) {
 			// ensure, that per-page is always present
 			list ($prefix, ) = explode('.', $prefix_special);
 
 			if (!array_key_exists($prefix, $default_per_page)) {
 				$list_helper = $this->Application->recallObject('ListHelper');
 				/* @var $list_helper ListHelper */
 
 				$default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix);
 			}
 
 			if ($object->isMainList()) {
 				$params[$prefix_special . '_PerPage'] = $this->Application->GetVar('per_page', $default_per_page[$prefix]);
 			}
 			else {
 				$params[$prefix_special . '_PerPage'] = $default_per_page[$prefix];
 			}
 		}
 
 		if (array_key_exists('per_page', $params)) {
 			$params[$prefix_special . '_PerPage'] = $params['per_page'];
 			unset($params['per_page']);
 		}
 
 		if (!array_key_exists('pass', $params)) {
 			$params['pass'] = 'm,' . $prefix_special;
 		}
 
 		// process template
 		$t = array_key_exists('template', $params) ? $params['template'] : '';
 		unset($params['template']);
 
 		if (!$t) {
 			$t = $this->Application->GetVar('t');
 		}
 
 		return $this->Application->HREF($t, '', $params);
 	}
 
 	/**
 	 * Deprecated
 	 *
 	 * @param array $params
 	 * @return int
 	 * @deprecated Parameter "column_width" of "PrintList" tag does that
 	 */
 	function ColumnWidth($params)
 	{
 		$columns = $this->Application->Parser->GetParam('columns');
 
 		return round(100/$columns).'%';
 	}
 
 	/**
 	 * Append prefix and special to tag
 	 * params (get them from tagname) like
 	 * they were really passed as params
 	 *
 	 * @param Array $tag_params
 	 * @return Array
 	 * @access protected
 	 */
 	function prepareTagParams($tag_params = Array())
 	{
 		$ret = $tag_params;
 		$ret['Prefix'] = $this->Prefix;
 		$ret['Special'] = $this->Special;
 		$ret['PrefixSpecial'] = $this->getPrefixSpecial();
 		return $ret;
 	}
 
 	function GetISO($currency, $field_currency = '')
 	{
 		if ( $currency == 'selected' ) {
 			return $this->Application->RecallVar('curr_iso');
 		}
 
 		if ( $currency == 'primary' || $currency == '' ) {
 			return $this->Application->GetPrimaryCurrency();
 		}
 
 		// explicit currency
 		return $currency == 'field' && $field_currency ? $field_currency : $currency;
 	}
 
 	/**
 	 * Convert primary currency to selected (if they are the same, converter will just return)
 	 *
 	 * @param float $value
 	 * @param string $target_iso
 	 * @param string $source_iso
 	 * @return float
 	 */
 	function ConvertCurrency($value, $target_iso, $source_iso = 'PRIMARY')
 	{
 		$converter = $this->Application->recallObject('CurrencyRates');
 		/* @var $converter CurrencyRates */
 
 		return $converter->Convert($value, $source_iso, $target_iso);
 	}
 
 	function AddCurrencySymbol($value, $iso, $decimal_tag = '')
 	{
 		$converter = $this->Application->recallObject('CurrencyRates');
 		/* @var $converter CurrencyRates */
 
 		return $converter->AddCurrencySymbol($value, $iso, $decimal_tag);
 	}
 
 	/**
 	 * Get's requested field value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function Field($params)
 	{
 		$field = $this->SelectParam($params, 'name,field');
 
 		if (!$this->Application->isAdmin) {
 			// apply htmlspecialchars on all field values on Front-End
 			$params['no_special'] = 'no_special';
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		if (array_key_exists('db', $params) && $params['db']) {
 			$value = $object->GetDBField($field);
 		}
 		else {
 			if (array_key_exists('currency', $params) && $params['currency']) {
 				$source_iso = isset($params['currency_field']) ? $object->GetDBField($params['currency_field']) : 'PRIMARY';
 				$target_iso = $this->GetISO($params['currency'], $source_iso);
 				$original = $object->GetDBField($field);
 
 				$value = $this->ConvertCurrency($original, $target_iso, $source_iso);
 
 				$object->SetDBField($field, $value);
 				$object->SetFieldOption($field, 'converted', true);
 			}
 
 			$format = array_key_exists('format', $params) ? $params['format'] : false;
 			if (!$format || $format == '$format') {
 				$format = NULL;
 			}
 
 			$value = $object->GetField($field, $format);
 
 			if (array_key_exists('negative', $params) && $params['negative']) {
 				if (strpos($value, '-') === 0) {
 					$value = substr($value, 1);
 				}
 				else {
 					$value = '-' . $value;
 				}
 			}
 
 			if (array_key_exists('currency', $params) && $params['currency']) {
 				$decimal_tag = isset($params['decimal_tag']) ? $params['decimal_tag'] : '';
 				$value = $this->AddCurrencySymbol($value, $target_iso, $decimal_tag);
 				$params['no_special'] = 1;
 			}
 		}
 
 		if (!array_key_exists('no_special', $params) || !$params['no_special']) {
 			// when no_special parameter NOT SET apply htmlspecialchars
 			$value = htmlspecialchars($value, null, CHARSET);
 		}
 
 		if (array_key_exists('checked', $params) && $params['checked']) {
 			$value = ($value == ( isset($params['value']) ? $params['value'] : 1)) ? 'checked' : '';
 		}
 
 		if (array_key_exists('plus_or_as_label', $params) && $params['plus_or_as_label']) {
 			$value = substr($value, 0,1) == '+' ? substr($value, 1) : $this->Application->Phrase($value);
 		}
 		elseif (array_key_exists('as_label', $params) && $params['as_label']) {
 			$value = $this->Application->Phrase($value);
 		}
 
 		$first_chars = $this->SelectParam($params,'first_chars,cut_first');
 
 		if ($first_chars) {
 			$stripped_value = strip_tags($value, $this->SelectParam($params, 'allowed_tags'));
 
 			if ( mb_strlen($stripped_value) > $first_chars ) {
 				$value = preg_replace('/\s+?(\S+)?$/', '', mb_substr($stripped_value, 0, $first_chars + 1)) . ' ...';
 			}
 		}
 
 		if (array_key_exists('nl2br', $params) && $params['nl2br']) {
 			$value = nl2br($value);
 		}
 
 		if ($value != '') {
 			$this->Application->Parser->DataExists = true;
 		}
 
 		if (array_key_exists('currency', $params) && $params['currency']) {
 			// restoring value in original currency, for other Field tags to work properly
 			$object->SetDBField($field, $original);
 		}
 
 		return $value;
 	}
 
 	function FieldHintLabel($params)
 	{
 		if ( isset($params['direct_label']) && $params['direct_label'] ) {
 			$label = $params['direct_label'];
 			$hint = $this->Application->Phrase($label, false);
 		}
 		else {
 			$label = $params['title_label'];
 			$hint = $this->Application->Phrase('hint:' . $label, false);
 		}
 
 		return $hint != strtoupper('!' . $label . '!') ? $hint : ''; // $hint
 	}
 
 	/**
 	 * Returns formatted date + time on current language
 	 *
 	 * @param $params
 	 */
 	function DateField($params)
 	{
 		$field = $this->SelectParam($params, 'name,field');
 
 		if ($field) {
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$timestamp = $object->GetDBField($field);
 		}
 		else {
 			$timestamp = $params['value'];
 		}
 
 		$date = $timestamp;
 
 		// prepare phrase replacements
 		$replacements = Array (
 			'l' => 'la_WeekDay',
 			'D' => 'la_WeekDay',
 			'M' => 'la_Month',
 			'F' => 'la_Month',
 		);
 
 		// cases allow to append phrase suffix based on requested case (e.g. Genitive)
 		$case_suffixes = array_key_exists('case_suffixes', $params) ? $params['case_suffixes'] : false;
 
 		if ($case_suffixes) {
 			// apply case suffixes (for russian language only)
 			$case_suffixes = explode(',', $case_suffixes);
 
 			foreach ($case_suffixes as $case_suffux) {
 				list ($replacement_name, $case_suffix_value) = explode('=', $case_suffux, 2);
 				$replacements[$replacement_name] .= $case_suffix_value;
 			}
 		}
 
 		$format = array_key_exists('format', $params) ? $params['format'] : false;
 
 		if (preg_match('/_regional_(.*)/', $format, $regs)) {
 			$language = $this->Application->recallObject('lang.current');
 			/* @var $language kDBItem */
 
 			$format = $language->GetDBField($regs[1]);
 		}
 		elseif (!$format) {
 			$format = null;
 		}
 
 		// escape formats, that are resolved to words by adodb_date
 		foreach ($replacements as $format_char => $phrase_prefix) {
 			if (strpos($format, $format_char) === false) {
 				unset($replacements[$format_char]);
 				continue;
 			}
 
 			$replacements[$format_char] = $this->Application->Phrase($phrase_prefix . adodb_date($format_char, $date));
 			$format = str_replace($format_char, '#' . ord($format_char) . '#', $format);
 		}
 
 		$date_formatted = adodb_date($format, $date);
 
 		// unescape formats, that are resolved to words by adodb_date
 		foreach ($replacements as $format_char => $format_replacement) {
 			$date_formatted = str_replace('#' . ord($format_char) . '#', $format_replacement, $date_formatted);
 		}
 
 		return $date_formatted;
 	}
 
 	function SetField($params)
 	{
 		// <inp2:SetField field="Value" src=p:cust_{$custom_name}"/>
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$dst_field = $this->SelectParam($params, 'name,field');
 		list($prefix_special, $src_field) = explode(':', $params['src']);
 
 		$src_object = $this->Application->recallObject($prefix_special);
 		/* @var $src_object kDBItem */
 
 		$object->SetDBField($dst_field, $src_object->GetDBField($src_field));
 	}
 
 	/**
 	 * Depricated
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @deprecated parameter "as_label" of "Field" tag does the same
 	 */
 	function PhraseField($params)
 	{
 		$field_label = $this->Field($params);
 		$translation = $this->Application->Phrase( $field_label );
 		return $translation;
 	}
 
 	function Error($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$field = $this->SelectParam($params, 'name,field');
 
 		return $object->GetErrorMsg($field, false);
 	}
 
 	function HasError($params)
 	{
 		if ($params['field'] == 'any') {
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$skip_fields = array_key_exists('except', $params) ? $params['except'] : false;
 			$skip_fields = $skip_fields ? explode(',', $skip_fields) : Array();
 
 			return $object->HasErrors($skip_fields);
 		}
 		else {
 			$res = false;
 			$fields = explode(',', $this->SelectParam($params, 'field,fields'));
 
 			foreach ($fields as $field) {
 				// call kDBTagProcessor::Error instead of kDBItem::GetErrorPseudo to have ability to override Error tag
 				$params['field'] = $field;
 				$res = $res || ($this->Error($params) != '');
 			}
 
 			return $res;
 		}
 	}
 
 	/**
 	 * Renders error message block, when there are errors on a form
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function ErrorWarning($params)
 	{
 		if ( !isset($params['field']) ) {
 			$params['field'] = 'any';
 		}
 
 		if ( $this->HasError($params) ) {
 			$params['prefix'] = $this->getPrefixSpecial();
 
 			return $this->Application->ParseBlock($params);
 		}
 
 		return '';
 	}
 
 	function IsRequired($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$field = $params['field'];
 		$formatter_class = $object->GetFieldOption($field, 'formatter');
 
 		if ( $formatter_class == 'kMultiLanguage' ) {
 			$formatter = $this->Application->recallObject($formatter_class);
 			/* @var $formatter kMultiLanguage */
 
 			$field = $formatter->LangFieldName($field);
 		}
 
 		return $object->isRequired($field);
 	}
 
 	function FieldOption($params)
 	{
 		$object = $this->getObject($params);;
 		$options = $object->GetFieldOptions($params['field']);
 		$ret =  isset($options[$params['option']]) ? $options[$params['option']] : '';
 		if (isset($params['as_label']) && $params['as_label']) $ret = $this->Application->ReplaceLanguageTags($ret);
 		return $ret;
 	}
 
 	/**
 	 * Prints list a all possible field options
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function PredefinedOptions($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
 		$field = $params['field'];
 		$value = array_key_exists('value', $params) ? $params['value'] : $object->GetDBField($field);
 		$field_options = $object->GetFieldOptions($field);
 		if (!array_key_exists('options', $field_options) || !is_array($field_options['options'])) {
 			trigger_error('Options not defined for <strong>'.$object->Prefix.'</strong> field <strong>'.$field.'</strong>', E_USER_WARNING);
 			return '';
 		}
 
 		$options = $field_options['options'];
 
 		if ( array_key_exists('has_empty', $params) && $params['has_empty'] ) {
 			$empty_value = array_key_exists('empty_value', $params) ? $params['empty_value'] : '';
 			$empty_label = isset($params['empty_label']) ? $params['empty_label'] : '';
 
 			if ( $empty_label ) {
 				if ( mb_substr($empty_label, 0, 1) == '+' ) {
 					// using plain text instead of phrase label
 					$empty_label = mb_substr($empty_label, 1);
 				}
 				else {
 					$empty_label = $this->Application->Phrase($empty_label, false);
 				}
 			}
 
 			// don't use other array merge function, because they will reset keys !!!
 			$options = kUtil::array_merge_recursive(Array ($empty_value => $empty_label), $options);
 		}
 
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 		$block_params['pass_params'] = 'true';
 		if (method_exists($object, 'EOL') && count($object->Records) == 0) {
 			// for drawing grid column filter
 			$block_params['field_name'] = '';
 		}
 		else {
 			$block_params['field_name'] = $this->InputName($params); // depricated (produces warning when used as grid filter), but used in Front-End (submission create), admin (submission view)
 		}
 
 		$selected_param_name = array_key_exists('selected_param', $params) ? $params['selected_param'] : false;
 		if (!$selected_param_name) {
 			$selected_param_name = $params['selected'];
 		}
 		$selected = $params['selected'];
 
 		$o = '';
 		if (array_key_exists('no_empty', $params) && $params['no_empty'] && !getArrayValue($options, '')) {
 			// removes empty option, when present (needed?)
 			array_shift($options);
 		}
 
 		$index = 0;
 		$option_count = count($options);
 
 		if (strpos($value, '|') !== false) {
 			// multiple checkboxes OR multiselect
 			$value = explode('|', substr($value, 1, -1) );
 			foreach ($options as $key => $val) {
 				$block_params['key'] = $key;
 				$block_params['option'] = $val;
 				$block_params[$selected_param_name] = ( in_array($key, $value) ? ' '.$selected : '');
 				$block_params['is_last'] = $index == $option_count - 1;
 				$o .= $this->Application->ParseBlock($block_params);
 
 				$index++;
 			}
 		}
 		else {
 			// single selection radio OR checkboxes OR dropdown
 			foreach ($options as $key => $val) {
 				$block_params['key'] = $key;
 				$block_params['option'] = $val;
 				$block_params[$selected_param_name] = (strlen($key) == strlen($value) && ($key == $value) ? ' '.$selected : '');
 				$block_params['is_last'] = $index == $option_count - 1;
 				$o .= $this->Application->ParseBlock($block_params);
 
 				$index++;
 			}
 		}
 		return $o;
 	}
 
 	function PredefinedSearchOptions($params)
 	{
 		$object =& $this->GetList($params);
 		/* @var $object kDBList */
 
 		$params['value'] = $this->SearchField($params);
 
 		return $this->PredefinedOptions($params);
 	}
 
 	function Format($params, $object = null)
 	{
 		$field = $this->SelectParam($params, 'name,field');
 
         if ( !isset($object) ) {
             $object = $this->getObject($params);
             /* @var $object kDBItem */
         }
 
         $options = $object->GetFieldOptions($field);
 		$format = $options[$this->SelectParam($params, 'input_format') ? 'input_format' : 'format'];
 		$formatter_class = array_key_exists('formatter', $options) ? $options['formatter'] : false;
 
 		if ( $formatter_class ) {
 			$formatter = $this->Application->recallObject($formatter_class);
 			/* @var $formatter kFormatter */
 
 			$human_format = array_key_exists('human', $params) ? $params['human'] : false;
 			$edit_size = array_key_exists('edit_size', $params) ? $params['edit_size'] : false;
 			$sample = array_key_exists('sample', $params) ? $params['sample'] : false;
 
 			if ( $sample ) {
 				return $formatter->GetSample($field, $options, $object);
 			}
 			elseif ( $human_format || $edit_size ) {
 				$format = $formatter->HumanFormat($format);
 
 				return $edit_size ? strlen($format) : $format;
 			}
 		}
 
 		return $format;
 	}
 
 	/**
 	 * Returns grid padination information
 	 * Can return links to pages
 	 *
 	 * @param Array $params
 	 * @return mixed
 	 */
 	function PageInfo($params)
 	{
 		$object =& $this->GetList($params);
 		/* @var $object kDBList */
 
 		$type = $params['type'];
 		unset($params['type']); // remove parameters used only by current tag
 
 		$ret = '';
 		switch ($type) {
 			case 'current':
 				$ret = $object->GetPage();
 				break;
 
 			case 'total':
 				$ret = $object->GetTotalPages();
 				break;
 
 			case 'prev':
 				$ret = $object->GetPage() > 1 ? $object->GetPage() - 1 : false;
 				break;
 
 			case 'next':
 				$ret = $object->GetPage() < $object->GetTotalPages() ? $object->GetPage() + 1 : false;
 				break;
 		}
 
 		if ($ret && isset($params['as_link']) && $params['as_link']) {
 			unset($params['as_link']); // remove parameters used only by current tag
 			$params['page']	= $ret;
 			$current_page = $object->GetPage(); // backup current page
 			$ret = $this->PageLink($params);
 			$this->Application->SetVar($object->getPrefixSpecial().'_Page', $current_page); // restore page
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Print grid pagination using
 	 * block names specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PrintPages($params)
 	{
 		$list =& $this->GetList($params);
 		$prefix_special = $list->getPrefixSpecial();
 		$total_pages = $list->GetTotalPages();
 
 		if ( $total_pages > 1 ) {
 			$this->Application->Parser->DataExists = true;
 		}
 
 		if ( $total_pages == 0 ) {
 			// display 1st page as selected in case if we have no pages at all
 			$total_pages = 1;
 		}
 
 		$o = '';
 
 		// what are these 2 lines for?
 		$this->Application->SetVar($prefix_special . '_event', '');
 		$this->Application->SetVar($prefix_special . '_id', '');
 
 		$current_page = $list->GetPage(); //  $this->Application->RecallVar($prefix_special.'_Page');
 
 		$block_params = $this->prepareTagParams($params);
 
 		$split = (isset($params['split']) ? $params['split'] : 10);
 		$split_start = $current_page - ceil($split / 2);
 
 		if ( $split_start < 1 ) {
 			$split_start = 1;
 		}
 
 		$split_end = $split_start + $split - 1;
 
 		if ( $split_end > $total_pages ) {
 			$split_end = $total_pages;
 			$split_start = max($split_end - $split + 1, 1);
 		}
 
 		if ( $current_page > 1 ) {
 			$prev_block_params = $this->prepareTagParams($params);
 
 			if ( $total_pages > $split ) {
 				$prev_block_params['page'] = max($current_page - $split, 1);
 				$prev_block_params['name'] = $this->SelectParam($params, 'prev_page_split_render_as,prev_page_split_block');
 
 				if ( $prev_block_params['name'] ) {
 					$o .= $this->Application->ParseBlock($prev_block_params);
 				}
 			}
 
 			$prev_block_params['name'] = 'page';
 			$prev_block_params['page'] = $current_page - 1;
 			$prev_block_params['name'] = $this->SelectParam($params, 'prev_page_render_as,block_prev_page,prev_page_block');
 
 			if ( $prev_block_params['name'] ) {
 				$this->Application->SetVar($this->getPrefixSpecial() . '_Page', $current_page - 1);
 				$o .= $this->Application->ParseBlock($prev_block_params);
 			}
 		}
 		else {
 			$no_prev_page_block = $this->SelectParam($params, 'no_prev_page_render_as,block_no_prev_page');
 
 			if ( $no_prev_page_block ) {
 				$block_params['name'] = $no_prev_page_block;
 				$o .= $this->Application->ParseBlock($block_params);
 			}
 		}
 
 		$total_records = $list->GetRecordsCount();
 		$separator_params['name'] = $this->SelectParam($params, 'separator_render_as,block_separator');
 
 		for ($i = $split_start; $i <= $split_end; $i++) {
 			$from_record = ($i - 1) * $list->GetPerPage();
 			$to_record = $from_record + $list->GetPerPage();
 
 			if ( $to_record > $total_records ) {
 				$to_record = $total_records;
 			}
 
 			$block_params['from_record'] = $from_record + 1;
 			$block_params['to_record'] = $to_record;
 
 			if ( $i == $current_page ) {
 				$block = $this->SelectParam($params, 'current_render_as,active_render_as,block_current,active_block');
 			}
 			else {
 				$block = $this->SelectParam($params, 'link_render_as,inactive_render_as,block_link,inactive_block');
 			}
 
 			$block_params['name'] = $block;
 			$block_params['page'] = $i;
 			$this->Application->SetVar($this->getPrefixSpecial() . '_Page', $i);
 			$o .= $this->Application->ParseBlock($block_params);
 
 			if ( $this->SelectParam($params, 'separator_render_as,block_separator') && $i < $split_end ) {
 				$o .= $this->Application->ParseBlock($separator_params);
 			}
 		}
 
 		if ( $current_page < $total_pages ) {
 			$next_block_params = $this->prepareTagParams($params);
 			$next_block_params['page'] = $current_page + 1;
 			$next_block_params['name'] = $this->SelectParam($params, 'next_page_render_as,block_next_page,next_page_block');
 
 			if ( $next_block_params['name'] ) {
 				$this->Application->SetVar($this->getPrefixSpecial() . '_Page', $current_page + 1);
 				$o .= $this->Application->ParseBlock($next_block_params);
 			}
 
 			if ( $total_pages > $split ) {
 				$next_block_params['page'] = min($current_page + $split, $total_pages);
 				$next_block_params['name'] = $this->SelectParam($params, 'next_page_split_render_as,next_page_split_block');
 
 				if ( $next_block_params['name'] ) {
 					$o .= $this->Application->ParseBlock($next_block_params);
 				}
 			}
 		}
 		else {
 			$no_next_page_block = $this->SelectParam($params, 'no_next_page_render_as,block_no_next_page');
 
 			if ( $no_next_page_block ) {
 				$block_params['name'] = $no_next_page_block;
 				$o .= $this->Application->ParseBlock($block_params);
 			}
 		}
 
 		$this->Application->SetVar($this->getPrefixSpecial() . '_Page', $current_page);
 
 		return $o;
 	}
 
 	/**
 	 * Print grid pagination using
 	 * block names specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PaginationBar($params)
 	{
 		return $this->PrintPages($params);
 	}
 
 	function PerPageBar($params)
 	{
 		$object =& $this->GetList($params);
 
 		$ret = '';
 		$per_pages = explode(';', $params['per_pages']);
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		foreach ($per_pages as $per_page) {
 			$block_params['per_page'] = $per_page;
 			$this->Application->SetVar($this->getPrefixSpecial() . '_PerPage', $per_page);
 			$block_params['selected'] = $per_page == $object->GetPerPage();
 
 			$ret .= $this->Application->ParseBlock($block_params, 1);
 		}
 
 		$this->Application->SetVar($this->getPrefixSpecial() . '_PerPage', $object->GetPerPage());
 
 		return $ret;
 	}
 
 	/**
 	 * Returns field name (processed by kMultiLanguage formatter
 	 * if required) and item's id from it's IDField or field required
 	 *
 	 * @param Array $params
 	 * @return Array (id,field)
 	 * @access private
 	 */
 	function prepareInputName($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$field = $this->SelectParam($params, 'name,field');
 		$formatter_class = $object->GetFieldOption($field, 'formatter');
 
 		if ($formatter_class == 'kMultiLanguage') {
 			$formatter = $this->Application->recallObject($formatter_class);
 			/* @var $formatter kMultiLanguage */
 
 			$force_primary = $object->GetFieldOption($field, 'force_primary');
 			$field = $formatter->LangFieldName($field, $force_primary);
 		}
 
 		if (array_key_exists('force_id', $params)) {
 			$id = $params['force_id'];
 		}
 		else {
 			$id_field = array_key_exists('IdField', $params) ? $params['IdField'] : false;
 			$id = $id_field ? $object->GetDBField($id_field) : $object->GetID();
 		}
 
 		return Array($id, $field);
 	}
 
 
 	/**
 	 * Returns input field name to
 	 * be placed on form (for correct
 	 * event processing)
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function InputName($params)
 	{
 		list($id, $field) = $this->prepareInputName($params);
 
 		$ret = $this->getPrefixSpecial().'['.$id.']['.$field.']';
 
 		if (array_key_exists('as_preg', $params) && $params['as_preg']) {
 			$ret = preg_quote($ret, '/');
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Allows to override various field options through hidden fields with specific names in submit.
 	 * This tag generates this special names
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @author Alex
 	 */
 	function FieldModifier($params)
 	{
 		list($id, $field) = $this->prepareInputName($params);
 
 		$ret = 'field_modifiers['.$this->getPrefixSpecial().']['.$field.']['.$params['type'].']';
 
 		if (array_key_exists('as_preg', $params) && $params['as_preg']) {
 			$ret = preg_quote($ret, '/');
 		}
 
 		if (isset($params['value'])) {
 			$object = $this->getObject($params);
 			$field_modifiers[$field][$params['type']] = $params['value'];
 			$object->ApplyFieldModifiers($field_modifiers);
 		}
 
 		return $ret;
 	}
 
 	/**
-	 * Returns index where 1st changable sorting field begins
+	 * Returns index where 1st changeable sorting field begins
 	 *
 	 * @return int
 	 * @access private
 	 */
 	function getUserSortIndex()
 	{
-		$list_sortings = $this->Application->getUnitOption($this->Prefix, 'ListSortings', Array ());
-		$sorting_prefix = getArrayValue($list_sortings, $this->Special) ? $this->Special : '';
+		$list_sortings = $this->getUnitConfig()->getListSortingsBySpecial($this, Array ());
 
 		$user_sorting_start = 0;
-		$forced_sorting = getArrayValue($list_sortings, $sorting_prefix, 'ForcedSorting');
+		$forced_sorting = getArrayValue($list_sortings, 'ForcedSorting');
 
 		return $forced_sorting ? count($forced_sorting) : $user_sorting_start;
 	}
 
 	/**
 	 * Returns order direction for given field
 	 *
 	 *
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function Order($params)
 	{
 		$field = $params['field'];
 		$user_sorting_start = $this->getUserSortIndex();
 
 		$list =& $this->GetList($params);
 
 		if ($list->GetOrderField($user_sorting_start) == $field)
 		{
 			return strtolower($list->GetOrderDirection($user_sorting_start));
 		}
 		elseif($this->Application->ConfigValue('UseDoubleSorting') && $list->GetOrderField($user_sorting_start+1) == $field)
 		{
 			return '2_'.strtolower($list->GetOrderDirection($user_sorting_start+1));
 		}
 		else
 		{
 			return 'no';
 		}
 	}
 
 	/**
 	 * Detects, that current sorting is not default
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function OrderChanged($params)
 	{
 		$list =& $this->GetList($params);
 
 		$list_helper = $this->Application->recallObject('ListHelper');
 		/* @var $list_helper ListHelper */
 
 		return $list_helper->hasUserSorting($list);
 	}
 
 	/**
 	 * Gets information of sorting field at "pos" position,
 	 * like sorting field name (type="field") or sorting direction (type="direction")
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function OrderInfo($params)
 	{
 		$user_sorting_start = $this->getUserSortIndex() + --$params['pos'];
 		$list =& $this->GetList($params);
 
 		if ( $params['type'] == 'field' ) {
 			return $list->GetOrderField($user_sorting_start);
 		}
 
 		if ( $params['type'] == 'direction' ) {
 			return $list->GetOrderDirection($user_sorting_start);
 		}
 
 		return '';
 	}
 
 	/**
 	 * Checks if sorting field/direction matches passed field/direction parameter
 	 *
 	 * @param Array $params
 	 * @return bool
 	 * @access protected
 	 */
 	protected function IsOrder($params)
 	{
 		$params['type'] = isset($params['field']) ? 'field' : 'direction';
 		$value = $this->OrderInfo($params);
 
 		if ( isset($params['field']) ) {
 			return $params['field'] == $value;
 		}
 		elseif ( isset($params['direction']) ) {
 			return $params['direction'] == $value;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Returns list per-page
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function PerPage($params)
 	{
 		$object =& $this->GetList($params);
 
 		return $object->GetPerPage();
 	}
 
 	/**
 	 * Checks if list perpage matches value specified
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function PerPageEquals($params)
 	{
 		$object =& $this->GetList($params);
 
 		return $object->GetPerPage() == $params['value'];
 	}
 
 	function SaveEvent($params)
 	{
 		// SaveEvent is set during OnItemBuild, but we may need it before any other tag calls OnItemBuild
 		$object = $this->getObject($params);
 		return $this->Application->GetVar($this->getPrefixSpecial().'_SaveEvent');
 	}
 
 	function NextId($params)
 	{
 		$object = $this->getObject($params);
 
 		$wid = $this->Application->GetTopmostWid($this->Prefix);
 		$session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_');
 		$ids = explode(',', $this->Application->RecallVar($session_name));
 
 		$cur_id = $object->GetID();
 
 		$i = array_search($cur_id, $ids);
 		if ($i !== false) {
 			return $i < count($ids) - 1 ? $ids[$i + 1] : '';
 		}
 		return '';
 	}
 
 	function PrevId($params)
 	{
 		$object = $this->getObject($params);
 
 		$wid = $this->Application->GetTopmostWid($this->Prefix);
 		$session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_');
 		$ids = explode(',', $this->Application->RecallVar($session_name));
 
 		$cur_id = $object->GetID();
 
 		$i = array_search($cur_id, $ids);
 		if ($i !== false) {
 			return $i > 0 ? $ids[$i - 1] : '';
 		}
 		return '';
 	}
 
 	function IsSingle($params)
 	{
 		return ($this->NextId($params) === '' && $this->PrevId($params) === '');
 	}
 
 	function IsLast($params)
 	{
 		return ($this->NextId($params) === '');
 	}
 
 	function IsFirst($params)
 	{
 		return ($this->PrevId($params) === '');
 	}
 
 	/**
 	 * Checks if field value is equal to proposed one
 	 *
 	 * @param Array $params
 	 * @return bool
 	 * @deprecated
 	 */
 	function FieldEquals($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return $object->GetDBField( $this->SelectParam($params, 'name,field') ) == $params['value'];
 	}
 
 	/**
 	 * Checks, that grid has icons defined and they should be shown
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function UseItemIcons($params)
 	{
-		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-		return array_key_exists('Icons', $grids[ $params['grid'] ]);
+		return array_key_exists('Icons', $this->getUnitConfig()->getGridByName($params['grid']));
 	}
 
 	/**
 	 * Returns corresponding to grid layout selector column width
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function GridSelectorColumnWidth($params)
 	{
 		$width = 0;
 		if ($params['selector']) {
 			$width += $params['selector_width'];
 		}
 
 		if ($this->UseItemIcons($params)) {
 			$width += $params['icon_width'];
 		}
 
 		return $width;
 	}
 
 	/**
 	 * Returns grids item selection mode (checkbox, radio, )
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function GridSelector($params)
 	{
-		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
+		$grid = $this->getUnitConfig()->getGridByName($params['grid']);
 
-		return array_key_exists('Selector', $grids[ $params['grid'] ]) ? $grids[ $params['grid'] ]['Selector'] : $params['default'];
+		return array_key_exists('Selector', $grid) ? $grid['Selector'] : $params['default'];
 	}
 
 	function ItemIcon($params)
 	{
-		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-		$grid = $grids[ $params['grid'] ];
+		$config = $this->getUnitConfig();
+		$grid = $config->getGridByName($params['grid']);
 
 		if ( !isset($grid['Icons']) ) {
 			return '';
 		}
 
 		$icons = $grid['Icons'];
 
 		if ( isset($params['name']) ) {
 			$icon_name = $params['name'];
 
 			return isset($icons[$icon_name]) ? $icons[$icon_name] : '';
 		}
 
-		$status_fields = $this->Application->getUnitOption($this->Prefix, 'StatusField', Array ());
-		/* @var $status_fields Array */
+		$status_fields = $config->getStatusField(false, Array ());
 
 		if ( !$status_fields ) {
 			return $icons['default'];
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
 		$icon = '';
 
 		foreach ($status_fields as $status_field) {
 			$icon .= $object->GetDBField($status_field) . '_';
 		}
 
 		$icon = rtrim($icon, '_');
 
 		return isset($icons[$icon]) ? $icons[$icon] : $icons['default'];
 	}
 
 	/**
 	 * Generates bluebar title + initializes prefixes used on page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SectionTitle($params)
 	{
+		$config = $this->getUnitConfig();
 		$preset_name = kUtil::replaceModuleSection($params['title_preset']);
-		$title_presets = $this->Application->getUnitOption($this->Prefix,'TitlePresets');
-		$title_info = array_key_exists($preset_name, $title_presets) ? $title_presets[$preset_name] : false;
+		$title_info = $config->getTitlePresetByName($preset_name);
 
-		if ($title_info === false) {
+		if ( $title_info === false ) {
 			$title = str_replace('#preset_name#', $preset_name, $params['title']);
-			if ($this->Application->ConfigValue('UseSmallHeader') && isset($params['group_title']) && $params['group_title']) {
-				$title .= ' - '.$params['group_title'];
+
+			if ( $this->Application->ConfigValue('UseSmallHeader') && isset($params['group_title']) && $params['group_title'] ) {
+				$title .= ' - ' . $params['group_title'];
 			}
+
 			return $title;
 		}
 
-		if (array_key_exists('default', $title_presets) && $title_presets['default']) {
+		$default_title_preset = $config->getTitlePresetByName('default');
+
+		if ( $default_title_preset ) {
 			// use default labels + custom labels specified in preset used
-			$title_info = kUtil::array_merge_recursive($title_presets['default'], $title_info);
+			$title_info = kUtil::array_merge_recursive($default_title_preset, $title_info);
 		}
 
 		$title = $title_info['format'];
 
 		// 1. get objects in use for title construction
-		$objects = Array();
-		$object_status = Array();
-		$status_labels = Array();
+		$objects = Array ();
+		$object_status = Array ();
+		$status_labels = Array ();
 
 		$prefixes = array_key_exists('prefixes', $title_info) ? $title_info['prefixes'] : false;
 		$all_tag_params = array_key_exists('tag_params', $title_info) ? $title_info['tag_params'] : false;
 		/* @var $prefixes Array */
 
-		if ($prefixes) {
+		if ( $prefixes ) {
 			// extract tag_params passed directly to SectionTitle tag for specific prefix
 			foreach ($params as $tp_name => $tp_value) {
-				if (preg_match('/(.*)\[(.*)\]/', $tp_name, $regs)) {
-					$all_tag_params[ $regs[1] ][ $regs[2] ] = $tp_value;
+				if ( preg_match('/(.*)\[(.*)\]/', $tp_name, $regs) ) {
+					$all_tag_params[$regs[1]][$regs[2]] = $tp_value;
 					unset($params[$tp_name]);
 				}
 			}
 
-			$tag_params = Array();
+			$tag_params = Array ();
+
 			foreach ($prefixes as $prefix_special) {
 				$prefix_data = $this->Application->processPrefix($prefix_special);
-				$prefix_data['prefix_special'] = rtrim($prefix_data['prefix_special'],'.');
+				$prefix_data['prefix_special'] = rtrim($prefix_data['prefix_special'], '.');
 
-				if ($all_tag_params) {
+				if ( $all_tag_params ) {
 					$tag_params = getArrayValue($all_tag_params, $prefix_data['prefix_special']);
-					if (!$tag_params) {
-						$tag_params = Array();
+
+					if ( !$tag_params ) {
+						$tag_params = Array ();
 					}
 				}
 
 				$tag_params = array_merge($params, $tag_params);
-				$objects[ $prefix_data['prefix_special'] ] = $this->Application->recallObject($prefix_data['prefix_special'], $prefix_data['prefix'], $tag_params);
-				$object_status[ $prefix_data['prefix_special'] ] = $objects[ $prefix_data['prefix_special'] ]->IsNewItem() ? 'new' : 'edit';
+				$objects[$prefix_data['prefix_special']] = $this->Application->recallObject($prefix_data['prefix_special'], $prefix_data['prefix'], $tag_params);
+				$object_status[$prefix_data['prefix_special']] = $objects[$prefix_data['prefix_special']]->IsNewItem() ? 'new' : 'edit';
 
 				// a. set object's status field (adding item/editing item) for each object in title
-				if (getArrayValue($title_info[ $object_status[ $prefix_data['prefix_special'] ].'_status_labels' ],$prefix_data['prefix_special'])) {
-					$status_labels[ $prefix_data['prefix_special'] ] = $title_info[ $object_status[ $prefix_data['prefix_special'] ].'_status_labels' ][ $prefix_data['prefix_special'] ];
-					$title = str_replace('#'.$prefix_data['prefix_special'].'_status#', $status_labels[ $prefix_data['prefix_special'] ], $title);
+				if ( getArrayValue($title_info[$object_status[$prefix_data['prefix_special']] . '_status_labels'], $prefix_data['prefix_special']) ) {
+					$status_labels[$prefix_data['prefix_special']] = $title_info[$object_status[$prefix_data['prefix_special']] . '_status_labels'][$prefix_data['prefix_special']];
+					$title = str_replace('#' . $prefix_data['prefix_special'] . '_status#', $status_labels[$prefix_data['prefix_special']], $title);
 				}
 
 				// b. setting object's titlefield value (in titlebar ONLY) to default in case if object beeing created with no titlefield filled in
-				if ($object_status[ $prefix_data['prefix_special'] ] == 'new') {
-					$new_value = $this->getInfo( $objects[ $prefix_data['prefix_special'] ], 'titlefield' );
-					if(!$new_value && getArrayValue($title_info['new_titlefield'],$prefix_data['prefix_special']) ) $new_value = $this->Application->Phrase($title_info['new_titlefield'][ $prefix_data['prefix_special'] ]);
-					$title = str_replace('#'.$prefix_data['prefix_special'].'_titlefield#', $new_value, $title);
+				if ( $object_status[$prefix_data['prefix_special']] == 'new' ) {
+					$new_value = $this->getInfo($objects[$prefix_data['prefix_special']], 'titlefield');
+
+					if ( !$new_value && getArrayValue($title_info['new_titlefield'], $prefix_data['prefix_special']) ) {
+						$new_value = $this->Application->Phrase($title_info['new_titlefield'][$prefix_data['prefix_special']]);
+					}
+
+					$title = str_replace('#' . $prefix_data['prefix_special'] . '_titlefield#', $new_value, $title);
 				}
 			}
 		}
 
 		// replace to section title
 		$section = array_key_exists('section', $params) ? $params['section'] : false;
-		if ($section) {
+
+		if ( $section ) {
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section);
 			$title = str_replace('#section_label#', '!' . $section_data['label'] . '!', $title);
 		}
 
 		// 2. replace phrases if any found in format string
 		$title = $this->Application->ReplaceLanguageTags($title, false);
 
 		// 3. find and replace any replacement vars
-		preg_match_all('/#(.*_.*)#/Uis',$title,$rets);
-		if ($rets[1]) {
-			$replacement_vars = array_keys( array_flip($rets[1]) );
+		preg_match_all('/#(.*_.*)#/Uis', $title, $rets);
+		if ( $rets[1] ) {
+			$replacement_vars = array_keys(array_flip($rets[1]));
+
 			foreach ($replacement_vars as $replacement_var) {
-				$var_info = explode('_',$replacement_var,2);
-				$object =& $objects[ $var_info[0] ];
-				$new_value = $this->getInfo($object,$var_info[1]);
-				$title = str_replace('#'.$replacement_var.'#', $new_value, $title);
+				$var_info = explode('_', $replacement_var, 2);
+				$object =& $objects[$var_info[0]];
+				$new_value = $this->getInfo($object, $var_info[1]);
+				$title = str_replace('#' . $replacement_var . '#', $new_value, $title);
 			}
 		}
 
-		// replace trailing spaces inside title preset + '' occurences into single space
+		// replace trailing spaces inside title preset + '' occurrences into single space
 		$title = preg_replace('/[ ]*\'\'[ ]*/', ' ', $title);
 
-		if ($this->Application->ConfigValue('UseSmallHeader') && isset($params['group_title']) && $params['group_title']) {
-			$title .= ' - '.$params['group_title'];
+		if ( $this->Application->ConfigValue('UseSmallHeader') && isset($params['group_title']) && $params['group_title'] ) {
+			$title .= ' - ' . $params['group_title'];
 		}
 
 		$first_chars = $this->SelectParam($params, 'first_chars,cut_first');
 
-		if ($first_chars && !preg_match('/<a href="(.*)".*>(.*)<\/a>/', $title)) {
+		if ( $first_chars && !preg_match('/<a href="(.*)".*>(.*)<\/a>/', $title) ) {
 			// don't cut titles, that contain phrase translation links
 			$stripped_title = strip_tags($title, $this->SelectParam($params, 'allowed_tags'));
 
-			if (mb_strlen($stripped_title) > $first_chars) {
+			if ( mb_strlen($stripped_title) > $first_chars ) {
 				$title = mb_substr($stripped_title, 0, $first_chars) . ' ...';
 			}
 		}
 
 		return $title;
 	}
 
 	/**
 	 * Returns information about list
 	 *
 	 * @param kDBList $object
 	 * @param string $info_type
 	 * @return string
 	 * @access protected
 	 */
 	protected function getInfo(&$object, $info_type)
 	{
 		switch ( $info_type ) {
 			case 'titlefield':
-				$field = $this->Application->getUnitOption($object->Prefix, 'TitleField');
+				$field = $object->getUnitConfig()->getTitleField();
+
 				return $field !== false ? $object->GetField($field) : 'TitleField Missing';
 				break;
 
 			case 'recordcount':
 				if ( $object->GetRecordsCount(false) != $object->GetRecordsCount() ) {
 					$of_phrase = $this->Application->Phrase('lc_of');
+
 					return $object->GetRecordsCount() . ' ' . $of_phrase . ' ' . $object->GetRecordsCount(false);
 				}
 
 				return $object->GetRecordsCount();
 				break;
 		}
 
 		return $object->GetField($info_type);
 	}
 
 	function GridInfo($params)
 	{
 		$object =& $this->GetList($params);
 		/* @var $object kDBList */
 
 		switch ( $params['type'] ) {
 			case 'filtered':
 				return $object->GetRecordsCount();
 
 			case 'total':
 				return $object->GetRecordsCount(false);
 
 			case 'from':
 				return $object->GetRecordsCount() ? $object->GetOffset() + 1 : 0; //0-based
 
 			case 'to':
 				$record_count = $object->GetRecordsCount();
 				return $object->GetPerPage(true) != -1 ? min($object->GetOffset() + $object->GetPerPage(), $record_count) : $record_count;
 
 			case 'total_pages':
 				return $object->GetTotalPages();
 
 			case 'needs_pagination':
 				return ($object->GetPerPage(true) != -1) && (($object->GetRecordsCount() > $object->GetPerPage()) || ($object->GetPage() > 1));
 		}
 
 		return false;
 	}
 
 	/**
 	 * Parses block depending on its element type.
 	 * For radio and select elements values are taken from 'value_list_field' in key1=value1,key2=value2
 	 * format. key=value can be substituted by <SQL>SELECT f1 AS OptionName, f2 AS OptionValue... FROM <PREFIX>TableName </SQL>
 	 * where prefix is TABLE_PREFIX
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ConfigFormElement($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$field = $params['field'];
 
 		$helper = $this->Application->recallObject('InpCustomFieldsHelper');
 		/* @var $helper InpCustomFieldsHelper */
 
 		$element_type = $object->GetDBField($params['element_type_field']);
 
 		if ($element_type == 'label') {
 			$element_type = 'text';
 		}
 
 		switch ($element_type) {
 			case 'select':
 			case 'multiselect':
 			case 'radio':
 				if ($object->GetDBField('DirectOptions')) {
 					// used for custom fields
 					$options = $object->GetDBField('DirectOptions');
 				}
 				else {
 					// used for configuration
 					$options = $helper->GetValuesHash( $object->GetDBField($params['value_list_field']) );
 				}
 
 				$object->SetFieldOption($field, 'options', $options);
 				break;
 
 			case 'text':
 			case 'textarea':
 			case 'upload':
 				$params['field_params'] = $helper->ParseConfigSQL($object->GetDBField($params['value_list_field']));
 				break;
 
 			case 'password':
 			case 'checkbox':
 			default:
 				break;
 		}
 
 		if (!$element_type) {
 			throw new Exception('Element type missing for "<strong>' . $object->GetDBField('VariableName') . '</strong>" configuration variable');
 			return '';
 		}
 
 		$params['name'] = $params['blocks_prefix'] . $element_type;
 
 		// use $pass_params to pass 'SourcePrefix' parameter from PrintList to CustomInputName tag
 		return $this->Application->ParseBlock($params, 1);
 	}
 
 	/**
 	 * Get's requested custom field value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function CustomField($params)
 	{
 		$params['name'] = 'cust_'.$this->SelectParam($params, 'name,field');
 		return $this->Field($params);
 	}
 
 	function CustomFieldLabel($params)
 	{
 		$object = $this->getObject($params);
 
 		$field = $this->SelectParam($params, 'name,field');
 
 		$sql = 'SELECT FieldLabel
-				FROM '.$this->Application->getUnitOption('cf', 'TableName').'
-				WHERE FieldName = '.$this->Conn->qstr($field);
+				FROM ' . $this->Application->getUnitConfig('cf')->getTableName() . '
+				WHERE FieldName = ' . $this->Conn->qstr($field);
+
 		return $this->Application->Phrase($this->Conn->GetOne($sql));
 	}
 
 	/**
 	 * transposes 1-dimensional array elements for vertical alignment according to given columns and per_page parameters
 	 *
 	 * @param array $arr
 	 * @param int $columns
 	 * @param int $per_page
 	 * @return array
 	 */
 	function LinearToVertical(&$arr, $columns, $per_page)
 	{
 		$rows = $columns;
 		// in case if after applying per_page limit record count less then
 		// can fill requrested column count, then fill as much as we can
 		$cols = min(ceil($per_page / $columns), ceil(count($arr) / $columns));
 		$imatrix = array();
 		for ($row = 0; $row < $rows; $row++) {
 			for ($col = 0; $col < $cols; $col++) {
 				$source_index = $row * $cols + $col;
 				if (!isset($arr[$source_index])) {
 					// in case if source array element count is less then element count in one row
 					continue;
 				}
 				$imatrix[$col * $rows + $row] = $arr[$source_index];
 			}
 		}
 
 		ksort($imatrix);
 		return array_values($imatrix);
 	}
 
 	/**
 	 * If data was modified & is in TempTables mode, then parse block with name passed;
 	 * remove modification mark if not in TempTables mode
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function SaveWarning($params)
 	{
 		$main_prefix = array_key_exists('main_prefix', $params) ? $params['main_prefix'] : false;
 
 		if ( $main_prefix ) {
 			$top_prefix = $main_prefix;
 		}
 		else {
 			$top_prefix = $this->Application->GetTopmostPrefix($this->Prefix);
 		}
 
 		$temp_tables = substr($this->Application->GetVar($top_prefix . '_mode'), 0, 1) == 't';
 		$modified = $this->Application->RecallVar($top_prefix . '_modified');
 
 		if ( $temp_tables && $modified ) {
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $this->SelectParam($params, 'render_as,name');
 			$block_params['edit_mode'] = $temp_tables ? 1 : 0;
 			return $this->Application->ParseBlock($block_params);
 		}
 
 		$this->Application->RemoveVar($top_prefix . '_modified');
 		return '';
 	}
 
 	/**
 	 * Returns list record count queries (on all pages)
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function TotalRecords($params)
 	{
 		$list =& $this->GetList($params);
 
 		return $list->GetRecordsCount();
 	}
 
 	/**
 	 * Range filter field name
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SearchInputName($params)
 	{
 		$field = $this->SelectParam($params, 'field,name');
 
 		$ret = 'custom_filters['.$this->getPrefixSpecial().']['.$params['grid'].']['.$field.']['.$params['filter_type'].']';
 
 		if (isset($params['type'])) {
 			$ret .= '['.$params['type'].']';
 		}
 
 		if (array_key_exists('as_preg', $params) && $params['as_preg']) {
 			$ret = preg_quote($ret, '/');
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Return range filter field value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function SearchField($params) // RangeValue
 	{
 		$field = $this->SelectParam($params, 'field,name');
 
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial() . '_current_view');
 		$custom_filter = $this->Application->RecallPersistentVar($this->getPrefixSpecial() . '_custom_filter.' . $view_name /*, ALLOW_DEFAULT_SETTINGS*/);
 		$custom_filter = $custom_filter ? unserialize($custom_filter) : Array ();
 
 		if ( isset($custom_filter[$params['grid']][$field]) ) {
 			$ret = $custom_filter[$params['grid']][$field][$params['filter_type']]['submit_value'];
 			if ( isset($params['type']) ) {
 				$ret = $ret[$params['type']];
 			}
 
 			if ( array_key_exists('formatted', $params) && $params['formatted'] ) {
 				$object =& $this->GetList($params);
 				$formatter_class = $object->GetFieldOption($field, 'formatter');
 
 				if ( $formatter_class ) {
 					$formatter = $this->Application->recallObject($formatter_class);
 					/* @var $formatter kFormatter */
 
 					$ret = $formatter->Format($ret, $field, $object);
 				}
 			}
 
 			if ( !array_key_exists('no_special', $params) || !$params['no_special'] ) {
 				$ret = htmlspecialchars($ret, null, CHARSET);
 			}
 
 			return $ret;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Tells, that at least one of search filters is used by now
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function SearchActive($params)
 	{
 		if ($this->Application->RecallVar($this->getPrefixSpecial() . '_search_keyword')) {
 			// simple search filter is used
 			return true;
 		}
 
 		$view_name = $this->Application->RecallVar($this->getPrefixSpecial().'_current_view');
 		$custom_filter = $this->Application->RecallPersistentVar($this->getPrefixSpecial().'_custom_filter.'.$view_name/*, ALLOW_DEFAULT_SETTINGS*/);
 		$custom_filter = $custom_filter ? unserialize($custom_filter) : Array();
 
 		return array_key_exists($params['grid'], $custom_filter);
 	}
 
 	function SearchFormat($params)
 	{
 		$object =& $this->GetList($params);
 
         return $this->Format($params, $object);
 	}
 
 	/**
 	 * Returns error of range field
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function SearchError($params)
 	{
 		$field = $this->SelectParam($params, 'field,name');
 
 		$error_var_name = $this->getPrefixSpecial() . '_' . $field . '_error';
 		$pseudo = $this->Application->RecallVar($error_var_name);
 		if ( $pseudo ) {
 			$this->Application->RemoveVar($error_var_name);
 		}
 
 		$object = $this->Application->recallObject($this->Prefix . '.' . $this->Special . '-item', null, Array ('skip_autoload' => true));
 		/* @var $object kDBItem */
 
 		$object->SetError($field, $pseudo);
 		return $object->GetErrorMsg($field, false);
 	}
 
 	/**
 	 * Returns object used in tag processor
 	 *
 	 * @param Array $params
 	 * @access public
 	 * @return kDBItem|kDBList
 	 */
 	function getObject($params = Array())
 	{
 		$object = $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix, $params);
 		/* @var $object kDBItem */
 
 		if ( isset($params['requery']) && $params['requery'] ) {
 			$this->Application->HandleEvent(new kEvent($this->getPrefixSpecial() . ':LoadItem', $params));
 		}
 
 		return $object;
 	}
 
 	/**
 	 * Checks if object propery value matches value passed
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function PropertyEquals($params)
 	{
 		$object = $this->getObject($params);
 		$property_name = $this->SelectParam($params, 'name,var,property');
 		return $object->$property_name == $params['value'];
 	}
 
 	function DisplayOriginal($params)
 	{
 		return false;
 	}
 
 	/*function MultipleEditing($params)
 	{
 		$wid = $this->Application->GetTopmostWid($this->Prefix);
 		$session_name = rtrim($this->getPrefixSpecial().'_selected_ids_'.$wid, '_');
 		$selected_ids = explode(',', $this->Application->RecallVar($session_name));
 
 		$ret = '';
 		if ($selected_ids) {
 			$selected_ids = explode(',', $selected_ids);
 			$object = $this->getObject( kUtil::array_merge_recursive($params, Array('skip_autoload' => true)) );
 			$params['name'] = $params['render_as'];
 			foreach ($selected_ids as $id) {
 				$object->Load($id);
 				$ret .= $this->Application->ParseBlock($params);
 			}
 		}
 
 		return $ret;
 	}*/
 
 	/**
 	 * Returns import/export process percent
 	 *
 	 * @param Array $params
 	 * @return int
 	 * @deprecated Please convert to event-model, not tag based
 	 */
 	function ExportStatus($params)
 	{
 		$export_object = $this->Application->recallObject('CatItemExportHelper');
 		/* @var $export_object kCatDBItemExportHelper */
 
 		$event = new kEvent($this->getPrefixSpecial().':OnDummy');
 
 		$action_method = 'perform'.ucfirst($this->Special);
 		$field_values = $export_object->$action_method($event);
 
 		// finish code is done from JS now
 		if ($field_values['start_from'] >= $field_values['total_records'])
 		{
 			if ($this->Special == 'import') {
 				// this is used?
 				$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 				$this->Application->Redirect('categories/cache_updater', Array('m_opener' => 'r', 'pass' => 'm', 'continue' => 1, 'no_amp' => 1));
 			}
 			elseif ($this->Special == 'export') {
 				// used for orders export in In-Commerce
 				$finish_t = $this->Application->RecallVar('export_finish_t');
 				$this->Application->Redirect($finish_t, Array('pass' => 'all'));
 				$this->Application->RemoveVar('export_finish_t');
 			}
 		}
 
 		$export_options = $export_object->loadOptions($event);
 		return $export_options['start_from']  * 100 / $export_options['total_records'];
 	}
 
 	/**
 	 * Returns path where exported category items should be saved
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function ExportPath($params)
 	{
 		$export_options = unserialize($this->Application->RecallVar($this->getPrefixSpecial() . '_options'));
 		$extension = $export_options['ExportFormat'] == 1 ? 'csv' : 'xml';
 		$filename = preg_replace('/(.*)\.' . $extension . '$/', '\1', $export_options['ExportFilename']) . '.' . $extension;
 
 		$path = EXPORT_PATH . '/';
 
 		if ( array_key_exists('as_url', $params) && $params['as_url'] ) {
 			$path = str_replace(FULL_PATH . '/', $this->Application->BaseURL(), $path);
 		}
 
 		return $path . $filename;
 	}
 
 	function FieldTotal($params)
 	{
 		$list =& $this->GetList($params);
 		$field = $this->SelectParam($params, 'field,name');
 		$total_function = array_key_exists('function', $params) ? $params['function'] : $list->getTotalFunction($field);
 
 		if (array_key_exists('function_only', $params) && $params['function_only']) {
 			return $total_function;
 		}
 
 		if (array_key_exists('currency', $params) && $params['currency']) {
 			$iso = $this->GetISO($params['currency']);
 			$original = $list->getTotal($field, $total_function);
 			$value = $this->ConvertCurrency($original, $iso);
 			$list->setTotal($field, $total_function, $value);
 		}
 
 		$value = $list->GetFormattedTotal($field, $total_function);
 
 		if (array_key_exists('currency', $params) && $params['currency']) {
 			$value = $this->AddCurrencySymbol($value, $iso);
 		}
 
 		return $value;
 	}
 
 	/**
 	 * Returns FCKEditor locale, that matches default site language
 	 *
 	 * @return string
 	 */
 	function _getFCKLanguage()
 	{
 		static $language_code = null;
 
-		if (!isset($language_code)) {
-			$language_code = 'en'; // defaut value
+		if ( !isset($language_code) ) {
+			$language_code = 'en'; // default value
 
-			if ($this->Application->isAdmin) {
+			if ( $this->Application->isAdmin ) {
 				$language_id = $this->Application->Phrases->LanguageId;
 			}
 			else {
 				$language_id = $this->Application->GetDefaultLanguageId(); // $this->Application->GetVar('m_lang');
 			}
 
 			$sql = 'SELECT Locale
-					FROM '. $this->Application->getUnitOption('lang', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('lang')->getTableName() . '
 					WHERE LanguageId = ' . $language_id;
-			$locale = strtolower( $this->Conn->GetOne($sql) );
+			$locale = strtolower($this->Conn->GetOne($sql));
 
-			if (file_exists(FULL_PATH . EDITOR_PATH . 'editor/lang/' . $locale . '.js')) {
+			if ( file_exists(FULL_PATH . EDITOR_PATH . 'editor/lang/' . $locale . '.js') ) {
 				// found language file, that exactly matches locale name (e.g. "en")
 				$language_code = $locale;
 			}
 			else {
 				$locale = explode('-', $locale);
-				if (file_exists(FULL_PATH . EDITOR_PATH . 'editor/lang/' . $locale[0] . '.js')) {
+
+				if ( file_exists(FULL_PATH . EDITOR_PATH . 'editor/lang/' . $locale[0] . '.js') ) {
 					// language file matches first part of locale (e.g. "ru-RU")
 					$language_code = $locale[0];
 				}
 			}
 		}
 
 		return $language_code;
 	}
 
 
 	function FCKEditor($params)
 	{
 		$params['no_special'] = 1;
 		$params['format'] = array_key_exists('format', $params) ? $params['format'] . ';fck_ready' : 'fck_ready';
 		$value = $this->Field($params);
 		$name = array_key_exists('name', $params) ? $params['name'] : $this->InputName($params);
 
 		$theme_path = $this->Application->GetFrontThemePath() . '/inc';
 
 		if ( file_exists(FULL_PATH . $theme_path . '/style.css') ) {
 			$url_params = Array (
 				'events[fck]' => 'OnGetsEditorStyles',
 				'no_pass_through' => 1, 'pass' => 'm', 'no_amp' => 1
 			);
 
 			$styles_css = $this->Application->HREF('index', '_FRONT_END_', $url_params, 'index.php');
 		}
 		else {
 			$theme_path = rtrim(EDITOR_PATH, '/');
 			$styles_css = $this->Application->BaseURL($theme_path) . 'style.css';
 		}
 
 		$styles_js = $this->Application->BaseURL($theme_path) . 'styles.js';
 
-		/*$page_id = $this->Application->GetVar('c_id');
+		$page_id = $this->Application->GetVar('c_id');
 		$content_id = $this->Application->GetVar('content_id');
 		$preview_url = '';
 
-		if ($page_id && $content_id) {
+		/*if ($page_id && $content_id) {
 			// editing content block from Front-End, not category in admin
+			$categories_config = $this->Application->getUnitConfig('c');
+
 			$sql = 'SELECT NamedParentPath
-					FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
-					WHERE ' . $this->Application->getUnitOption('c', 'IDField') . ' = ' . (int)$page_id;
+					FROM ' . $categories_config->getTableName() . '
+					WHERE ' . $categories_config->getIDField() . ' = ' . (int)$page_id;
 			$template = strtolower( $this->Conn->GetOne($sql) );
 
 			$url_params = Array ('m_cat_id' => $page_id, 'no_amp' => 1, 'editing_mode' => EDITING_MODE_CONTENT, 'pass' => 'm');
 			$preview_url = $this->Application->HREF($template, '_FRONT_END_', $url_params, 'index.php');
 			$preview_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $preview_url);
 		}*/
 
 		include_once(FULL_PATH . EDITOR_PATH . 'ckeditor.php');
 
 		$oCKeditor = new CKeditor(BASE_PATH . EDITOR_PATH);
 
 //		$oFCKeditor->FullUrl    = $this->Application->BaseURL();
 //		$oFCKeditor->BaseUrl    = BASE_PATH . '/'; // used by custom document plugin
 //		$oFCKeditor->PreviewUrl	= $preview_url; // used by custom MyPreview plugin
 
 		$oCKeditor->lateLoad = array_key_exists('late_load', $params) && $params['late_load'];
 
 		$width = $params['width'];
 		$height = $params['height'];
 
 		if ( preg_match('/^[\d]+$/', $width) ) {
 			$width .= 'px';
 		}
 
 		if ( preg_match('/^[\d]+$/', $height) ) {
 			$height .= 'px';
 		}
 
 		$oCKeditor->textareaAttributes = Array (
 			'style' => 'width: ' . $width . '; height: ' . $height . ';'
 		);
 
 		if ( file_exists(SYSTEM_PRESET_PATH . DIRECTORY_SEPARATOR . 'inp_ckconfig.js') ) {
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$config_js = $file_helper->pathToUrl(SYSTEM_PRESET_PATH . DIRECTORY_SEPARATOR . 'inp_ckconfig.js');
 		}
 		else {
 			$config_js = $this->Application->BaseURL() . 'core/admin_templates/js/inp_ckconfig.js';
 		}
 
 		$fck_helper = $this->Application->recallObject('FCKHelper');
 		/* @var $fck_helper fckFCKHelper */
 
 		$transit_params = $fck_helper->getTransitParams($params);
 
 		$oCKeditor->config = Array (
 			'toolbar' => $this->Application->isDebugMode() ? 'DebugMode' : 'Default', //  $page_id && $content_id ? 'Advanced' : 'Default',
 
 			'baseHref' => $this->Application->BaseURL( rtrim(EDITOR_PATH, '/') ),
 
 //			'ProjectPath' => BASE_PATH . '/', // used by custom MyPreview plugin
 
 			'customConfig' => $config_js,
 			'stylesSet' => 'portal:' . $styles_js,
 			'contentsCss' => $styles_css,
 //			'DefaultStyleLabel' => $this->Application->Phrase('la_editor_default_style'), // not ported to ckeditor
 			'Admin' => 1, // for custom file browser to work
 			'K4' => 1, // for custom file browser to work
 //			'PreviewUrl' => $preview_url,
 //			'BaseUrl' => BASE_PATH . '/', // used by custom document plugin & by file browser
 			'language' => $this->_getFCKLanguage(),
 			'height' => $height, // editor area height
 		);
 
 		if ( isset($transit_params['bgcolor']) && $transit_params['bgcolor'] ) {
 			$oCKeditor->config['extraCss'] = 'body { background-color: ' . $transit_params['bgcolor'] . '; }';
 		}
 
 		foreach ($transit_params as $param_name => $param_value) {
 			if ( !$param_value ) {
 				continue;
 			}
 
 			$param_key = str_replace(' ', '', ucwords(str_replace('_', ' ', $param_name)));
 			$param_key[0] = strtolower($param_key[0]);
 
 			$oCKeditor->config[$param_key] = $param_value;
 		}
 
 		$oCKeditor->returnOutput = true;
 
 		$events = Array (
 			'configLoaded' => 'function(ev) { ev.editor.addCss(ev.editor.config.extraCss); }',
 		);
 
 		return $oCKeditor->editor($name, $value, Array (), $events);
 	}
 
 	function IsNewItem($params)
 	{
 		$object = $this->getObject($params);
 		return $object->IsNewItem();
 	}
 
 	/**
 	 * Creates link to an item including only it's id
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function ItemLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		if ( !isset($params['pass']) ) {
 			$params['pass'] = 'm';
 		}
 
 		$params[ $object->getPrefixSpecial() . '_id' ] = $object->GetID();
 
 		return $this->Application->ProcessParsedTag('m', 'T', $params);
 	}
 
 	/**
 	 * Creates a button for editing item in Admin Console
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
+	 * @throws InvalidArgumentException
 	 */
 	protected function AdminEditButton($params)
 	{
 		if ( EDITING_MODE != EDITING_MODE_CONTENT ) {
 			return '';
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$item_prefix = isset($params['item_prefix']) ? $params['item_prefix'] : $this->Prefix;
 
 		if ( isset($params['template']) ) {
 			$template = $params['template'];
 		}
 		else {
-			$admin_template_prefix = $this->Application->getUnitOption($item_prefix, 'AdminTemplatePrefix');
-			$template = $this->Application->getUnitOption($item_prefix, 'AdminTemplatePath') . '/' . $admin_template_prefix . 'edit';
+			$item_config = $this->Application->getUnitConfig($item_prefix);
+
+			$admin_template_prefix = $item_config->getAdminTemplatePrefix();
+			$template = $item_config->getAdminTemplatePath() . '/' . $admin_template_prefix . 'edit';
 
 			if ( !$admin_template_prefix ) {
 				throw new InvalidArgumentException('Automatic admin editing template detection failed because of missing "AdminTemplatePrefix" unit config option in "' . $this->Prefix . '" unit config');
 			}
 		}
 
 		$form_name = 'kf_' . str_replace('-', '_', $item_prefix) . '_' . $object->GetID();
 		$button_icon = isset($params['button_icon']) ? $params['button_icon'] : 'content_mode.png';
 		$button_class = isset($params['button_class']) ? $params['button_class'] : 'admin-edit-btn';
 		$button_title = isset($params['button_title']) ? $params['button_title'] : 'la_btn_AdminEditItem';
 
 		if ( substr($button_title, 0, 1) == '+' ) {
 			$button_title = substr($button_title, 1);
 		}
 		else {
 			$button_title = $this->Application->Phrase($button_title, false, true);
 		}
 
 		$icon_url = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/' . $button_icon;
 		$button_code = '<button style="background-image: url(' . $icon_url . ');"  onclick="$form_name=\'' . addslashes($form_name) . '\'; std_edit_item(\'' . addslashes($item_prefix) . '\', \'' . addslashes($template) . '\');" class="cms-btn-new ' . $button_class . '">' . $button_title . '</button>';
 
 		if ( !isset($params['pass']) ) {
 			$params['pass'] = 'm,' . $item_prefix;
 		}
 
 		$params['m_opener'] = 'd';
 		$params[$item_prefix . '_id'] = $object->GetID();
 
 		if ( !isset($params['temp_mode']) || (isset($params['temp_mode']) && $params['temp_mode']) ) {
 			$params[$item_prefix . '_mode'] = 't';
 			$params[$item_prefix . '_event'] = 'OnEdit';
 		}
 
 		$params['front'] = 1; // to make opener stack work properly
 		$params['__URLENCODE__'] = 1; // don't use "&amp;"
 		$params['__NO_REWRITE__'] = 1; // since admin link
 //		$params['escape'] = 1; // needed?
 
 		unset($params['button_icon'], $params['button_class'], $params['button_title'], $params['template'], $params['item_prefix'], $params['temp_mode']);
 
 		// link from Front-End to Admin, don't remove "index.php"
 		$edit_url = $this->Application->HREF($template, ADMIN_DIRECTORY, $params, 'index.php');
 		$edit_form = '<form method="POST" style="display: inline; margin: 0px" name="' . $form_name . '" id="' . $form_name . '" action="' . $edit_url . '"></form>';
 
 		if ( isset($params['forms_later']) && $params['forms_later'] ) {
 			$all_forms = $this->Application->GetVar('all_forms');
 			$this->Application->SetVar('all_forms', $all_forms . $edit_form);
 		}
 		else {
 			$button_code .= $edit_form;
 		}
 
 		return $button_code;
 	}
 
 	/**
 	 * Calls OnNew event from template, when no other event submitted
 	 *
 	 * @param Array $params
 	 */
 	function PresetFormFields($params)
 	{
 		$prefix = $this->getPrefixSpecial();
 		if ( !$this->Application->GetVar($prefix . '_event') ) {
 			$this->Application->HandleEvent(new kEvent($prefix . ':OnNew'));
 		}
 	}
 
 	function PrintSerializedFields($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$field = $this->SelectParam($params, 'field');
 		$data = unserialize($object->GetDBField($field));
 		$o = '';
 		$std_params['name'] = $params['render_as'];
 		$std_params['field'] = $params['field'];
 		$std_params['pass_params'] = true;
 
 		foreach ($data as $key => $row) {
 			$block_params = array_merge($std_params, $row, array('key'=>$key));
 			$o .= $this->Application->ParseBlock($block_params);
 		}
 
 		return $o;
 	}
 	/**
 	 * Checks if current prefix is main item
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsTopmostPrefix($params)
 	{
 		return $this->Prefix == $this->Application->GetTopmostPrefix($this->Prefix);
 	}
 
 	function PermSection($params)
 	{
 		$section = $this->SelectParam($params, 'section,name');
-		$perm_sections = $this->Application->getUnitOption($this->Prefix, 'PermSection');
-		return isset($perm_sections[$section]) ? $perm_sections[$section] : '';
+
+		return $this->getUnitConfig()->getPermSectionByName($section, '');
 	}
 
 	function PerPageSelected($params)
 	{
 		$list =& $this->GetList($params);
 
 		return $list->GetPerPage(true) == $params['per_page'] ? $params['selected'] : '';
 	}
 
 	/**
 	 * Returns prefix + generated sepcial + any word
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function VarName($params)
 	{
 		$list =& $this->GetList($params);
 
 		return $list->getPrefixSpecial() . '_' . $params['type'];
 	}
 
 	/**
 	 * Returns edit tabs by specified preset name or false in case of error
 	 *
 	 * @param string $preset_name
 	 * @return mixed
 	 */
 	function getEditTabs($preset_name)
 	{
-		$presets = $this->Application->getUnitOption($this->Prefix, 'EditTabPresets');
+		$presets = $this->getUnitConfig()->getEditTabPresets();
 
-		if (!$presets || !isset($presets[$preset_name]) || count($presets[$preset_name]) == 0) {
+		if ( !$presets || !isset($presets[$preset_name]) || count($presets[$preset_name]) == 0 ) {
 			return false;
 		}
 
 		return count($presets[$preset_name]) > 1 ? $presets[$preset_name] : false;
 	}
 
 	/**
 	 * Detects if specified preset has tabs in it
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function HasEditTabs($params)
 	{
 		return $this->getEditTabs($params['preset_name']) ? true : false;
 	}
 
 	/**
 	 * Sorts edit tabs based on their priority
 	 *
 	 * @param Array $tab_a
 	 * @param Array $tab_b
 	 * @return int
 	 */
 	function sortEditTabs($tab_a, $tab_b)
 	{
 		if ($tab_a['priority'] == $tab_b['priority']) {
 			return 0;
 		}
 
 		return $tab_a['priority'] < $tab_b['priority'] ? -1 : 1;
 	}
 
 	/**
 	 * Prints edit tabs based on preset name specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function PrintEditTabs($params)
 	{
 		$edit_tabs = $this->getEditTabs($params['preset_name']);
 		if ( !$edit_tabs ) {
 			return '';
 		}
 		usort($edit_tabs, Array (&$this, 'sortEditTabs'));
 
 		$ret = '';
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		foreach ($edit_tabs as $tab_info) {
 			$block_params['title'] = $tab_info['title'];
 			$block_params['template'] = $tab_info['t'];
 			$ret .= $this->Application->ParseBlock($block_params);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Performs image resize to required dimensions and returns resulting url (cached resized image)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ImageSrc($params)
 	{
 		$max_width = isset($params['MaxWidth']) ? $params['MaxWidth'] : false;
 		$max_height = isset($params['MaxHeight']) ? $params['MaxHeight'] : false;
 
 		$logo_filename = isset($params['LogoFilename']) ? $params['LogoFilename'] : false;
 		$logo_h_margin = isset($params['LogoHMargin']) ? $params['LogoHMargin'] : false;
 		$logo_v_margin = isset($params['LogoVMargin']) ? $params['LogoVMargin'] : false;
 		$object = $this->getObject($params);
 
 		$field = $this->SelectParam($params, 'name,field');
 		return $object->GetField($field, 'resize:'.$max_width.'x'.$max_height.';wm:'.$logo_filename.'|'.$logo_h_margin.'|'.$logo_v_margin);
 	}
 
 	/**
 	 * Allows to retrieve given setting from unit config
 	 *
 	 * @param Array $params
 	 * @return mixed
 	 */
 	function UnitOption($params)
 	{
-		return $this->Application->getUnitOption($this->Prefix, $params['name']);
+		return $this->getUnitConfig()->getSetting($params['name']);
 	}
 
 	/**
 	 * Returns list of allowed toolbar buttons or false, when all is allowed
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function VisibleToolbarButtons($params)
 	{
 		$preset_name = kUtil::replaceModuleSection($params['title_preset']);
-		$title_presets = $this->Application->getUnitOption($this->Prefix, 'TitlePresets');
+		$preset_info = $this->getUnitConfig()->getTitlePresetByName($preset_name);
 
-		if (!array_key_exists($preset_name, $title_presets)) {
+		if ( !$preset_info ) {
 			trigger_error('Title preset not specified or missing (in tag "<strong>' . $this->getPrefixSpecial() . ':' . __METHOD__ . '</strong>")', E_USER_NOTICE);
+
 			return false;
 		}
 
-		$preset_info = $title_presets[$preset_name];
-		if (!array_key_exists('toolbar_buttons', $preset_info) || !is_array($preset_info['toolbar_buttons'])) {
+		if ( !array_key_exists('toolbar_buttons', $preset_info) || !is_array($preset_info['toolbar_buttons']) ) {
 			return false;
 		}
 
 		// always add search buttons
 		array_push($preset_info['toolbar_buttons'], 'search', 'search_reset_alt');
 		$toolbar_buttons = array_map('addslashes', $preset_info['toolbar_buttons']);
 
 		return $toolbar_buttons ? "'" . implode("', '", $toolbar_buttons) . "'" : 'false';
 	}
 
 	/**
 	 * Checks, that "To" part of at least one of range filters is used
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function RangeFiltersUsed($params)
 	{
 		$search_helper = $this->Application->recallObject('SearchHelper');
 		/* @var $search_helper kSearchHelper */
 
 		return $search_helper->rangeFiltersUsed($this->getPrefixSpecial(), $params['grid']);
 	}
 
 	/**
 	 * This is abstract tag, used to modify unit config data based on template, where it's used.
 	 * Tag is called from "combined_header" block in admin only.
 	 *
 	 * @param Array $params
 	 */
 	function ModifyUnitConfig($params)
 	{
 
 	}
 
 	/**
 	 * Checks, that field is visible on edit form
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function FieldVisible($params)
 	{
 		$check_field = $params['field'];
-		$fields = $this->Application->getUnitOption($this->Prefix, 'Fields');
-
-		if (!array_key_exists($check_field, $fields)) {
-			// field not found in real fields array -> it's 100% virtual then
-			$fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields', Array ());
-		}
+		$field_options = $this->_getFieldDefinition($check_field);
 
-		if (!array_key_exists($check_field, $fields)) {
+		if ( !$field_options ) {
 			$params['field'] = 'Password';
+
 			return $check_field == 'VerifyPassword' ? $this->FieldVisible($params) : true;
 		}
 
-		$show_mode = array_key_exists('show_mode', $fields[$check_field]) ? $fields[$check_field]['show_mode'] : true;
+		$show_mode = array_key_exists('show_mode', $field_options) ? $field_options['show_mode'] : true;
 
-		if ($show_mode === smDEBUG) {
+		if ( $show_mode === smDEBUG ) {
 			return defined('DEBUG_MODE') && DEBUG_MODE;
 		}
 
 		return $show_mode;
 	}
 
 	/**
 	 * Checks, that there area visible fields in given section on edit form
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function FieldsVisible($params)
 	{
-		if (!$params['fields']) {
+		if ( !$params['fields'] ) {
 			return true;
 		}
 
 		$check_fields = explode(',', $params['fields']);
-		$fields = $this->Application->getUnitOption($this->Prefix, 'Fields');
-		$virtual_fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields');
 
 		foreach ($check_fields as $check_field) {
 			// when at least one field in subsection is visible, then subsection is visible too
+			$field_options = $this->_getFieldDefinition($check_field);
 
-			if (array_key_exists($check_field, $fields)) {
-				$show_mode = array_key_exists('show_mode', $fields[$check_field]) ? $fields[$check_field]['show_mode'] : true;
+			if ( $field_options ) {
+				$show_mode = array_key_exists('show_mode', $field_options) ? $field_options['show_mode'] : true;
 			}
 			else {
-				$show_mode = array_key_exists('show_mode', $virtual_fields[$check_field]) ? $virtual_fields[$check_field]['show_mode'] : true;
+				$show_mode = true;
 			}
 
-			if (($show_mode === true) || (($show_mode === smDEBUG) && (defined('DEBUG_MODE') && DEBUG_MODE))) {
+			if ( ($show_mode === true) || (($show_mode === smDEBUG) && (defined('DEBUG_MODE') && DEBUG_MODE)) ) {
 				// field is visible
 				return true;
 			}
 		}
 
 		return false;
 	}
 
 	/**
+	 * Returns field definition
+	 *
+	 * @param string $field_name
+	 * @return Array
+	 * @access protected
+	 */
+	protected function _getFieldDefinition($field_name)
+	{
+		$config = $this->getUnitConfig();
+		$ret = $config->getFieldByName($field_name);
+
+		if ( !$ret ) {
+			$ret = $config->getVirtualFieldByName($field_name);
+		}
+
+		return $ret;
+	}
+
+	/**
 	 * Checks, that requested option is checked inside field value
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function Selected($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$field = $this->SelectParam($params, 'name,field');
 		$value = $object->GetDBField($field);
 
 		if (strpos($value, '|') !== false) {
 			$value = explode('|', substr($value, 1, -1));
 			return in_array($params['value'], $value);
 		}
 
 		return $value;
 	}
 
 	/**
 	 * Displays option name by it's value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function OptionValue($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$value = $params['value'];
 		$field = $this->SelectParam($params, 'name,field');
 
 		$field_options = $object->GetFieldOptions($field);
 
 		if ( isset($field_options['options'][$value]) ) {
 			$value = $field_options['options'][$value];
 			$use_phrases = isset($field_options['use_phrases']) ? $field_options['use_phrases'] : false;
 
 			return $use_phrases ? $this->Application->Phrase($value) : $value;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Returns/sets form name for current object
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function FormName($params)
 	{
 		$form_name = $this->SelectParam($params, 'name,form,form_name');
 
 		if ( $form_name ) {
 			$prefix = $this->getPrefixSpecial();
 
 			if ( $this->Application->hasObject( $this->getPrefixSpecial() ) ) {
 				$object = $this->getObject($params);
 				/* @var $object kDBItem */
 
 				if ( $object->getFormName() != $form_name ) {
 					trigger_error('Setting form to "<strong>' . $form_name . '</strong>" failed, since object "<strong>' . $this->getPrefixSpecial() . '</strong>" is created before FormName tag (e.g. in event or another tag).', E_USER_WARNING);
 				}
 			}
 			else {
 				$forms = $this->Application->GetVar('forms', Array ());
 				$forms[ $this->getPrefixSpecial() ] = $form_name;
 				$this->Application->SetVar('forms', $forms);
 			}
 
 			return '';
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return $object->getFormName();
 	}
 
 	/**
 	 * Just reloads the object using given parameters
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function ReloadItem($params)
 	{
 		$params['requery'] = 1;
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return '';
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/db/cat_tag_processor.php
===================================================================
--- branches/5.3.x/core/kernel/db/cat_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/kernel/db/cat_tag_processor.php	(revision 15698)
@@ -1,922 +1,938 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kCatDBTagProcessor extends kDBTagProcessor {
 
 		/**
 		 * Permission Helper
 		 *
 		 * @var kPermissionsHelper
 		 */
 		var $PermHelper = null;
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			$this->PermHelper = $this->Application->recallObject('PermissionsHelper');
 		}
 
 		function ItemIcon($params)
 		{
-			$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-			$grid = $grids[ $params['grid'] ];
+			$config = $this->getUnitConfig();
+			$grid = $config->getGridByName($params['grid']);
 
-			if (!array_key_exists('Icons', $grid)) {
+			if ( !array_key_exists('Icons', $grid) ) {
 				return '';
 			}
 
 			$icons = $grid['Icons'];
 
-			if (array_key_exists('name', $params)) {
+			if ( array_key_exists('name', $params) ) {
 				$icon_name = $params['name'];
+
 				return array_key_exists($icon_name, $icons) ? $icons[$icon_name] : '';
 			}
 
-			$status_fields = $this->Application->getUnitOption($this->Prefix, 'StatusField');
+			$status_field = $config->getStatusField(true);
 
-			if (!$status_fields) {
+			if ( !$status_field ) {
 				return $icons['default'];
 			}
 
 			$object = $this->getObject($params);
 			/* @var $object kDBList */
 
-			$value = $object->GetDBField($status_fields[0]); // sets base status icon
+			$value = $object->GetDBField($status_field); // sets base status icon
+
+			if ( $value == STATUS_ACTIVE ) {
+				/*if ( $object->HasField('IsPop') && $object->GetDBField('IsPop') ) {
+					$value = 'POP';
+				}
+
+				if ( $object->HasField('IsHot') && $object->GetDBField('IsHot') ) {
+					$value = 'HOT';
+				}*/
+
+				if ( $object->HasField('IsNew') && $object->GetDBField('IsNew') ) {
+					$value = 'NEW';
+				}
 
-			if ($value == STATUS_ACTIVE) {
-//				if( $object->HasField('IsPop') && $object->GetDBField('IsPop') ) $value = 'POP';
-//				if( $object->HasField('IsHot') && $object->GetDBField('IsHot') ) $value = 'HOT';
-				if( $object->HasField('IsNew') && $object->GetDBField('IsNew') ) $value = 'NEW';
-//				if( $object->HasField('EditorsPick') && $object->GetDBField('EditorsPick') ) $value = 'PICK';
+				/*if ( $object->HasField('EditorsPick') && $object->GetDBField('EditorsPick') ) {
+					$value = 'PICK';
+				}*/
 			}
 
 			return array_key_exists($value, $icons) ? $icons[$value] : $icons['default'];
 		}
 
 		/**
 		 * Allows to create valid mod-rewrite compatible link to module item
 		 *
 		 * @param Array $params
 		 * @param string $id_prefix
 		 * @return string
 		 */
 		function ItemLink($params, $id_prefix = null)
 		{
 			if ($this->Application->isAdmin) {
 				// link from Admin to Front-end
 				$params['prefix'] = '_FRONT_END_';
 
 				if ( $this->Application->ConfigValue('UseModRewrite') ) {
 					$params['__MOD_REWRITE__'] = 1;
 				}
 				else {
 					$params['index_file'] = 'index.php';
 				}
 			}
 
 			if ( !isset($params['pass']) ) {
 				$params['pass'] = 'm,'.$this->Prefix;
 			}
 
 			// set by PrintList2 tag
 			$item_id = $this->Application->GetVar($this->getPrefixSpecial() . '_id');
 
 			if ( !$item_id && ($this->Special != 'next' && $this->Special != 'previous') ) {
 				// set from page url
 				$item_id = $this->Application->GetVar($this->Prefix . '_id');
 			}
 
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$params['m_cat_page'] = 1;
 			$params['m_cat_id'] = $object->GetDBField('CategoryId');
 			$params['pass_category'] = 1;
 			$params[$this->Prefix . '_id'] = $item_id ? $item_id : $object->GetID();
 
 			return $this->Application->ProcessParsedTag('m', 't', $params);
 		}
 
 		/**
 		 * Builds link for browsing current item on Front-End
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PageBrowseLink($params)
 		{
 			$themes_helper = $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$site_config_helper = $this->Application->recallObject('SiteConfigHelper');
 			/* @var $site_config_helper SiteConfigHelper */
 
 			$settings = $site_config_helper->getSettings();
 
 			$params['editing_mode'] = $settings['default_editing_mode'];
 			$params['m_theme'] = $themes_helper->getCurrentThemeId();
 			$params['admin'] = 1;
 
 			return $this->ItemLink($params);
 		}
 
 		function CategoryPath($params)
 		{
 			if ($this->Application->isAdminUser) {
 				// path for module root category in admin
 				if (!isset($params['cat_id'])) {
 					$params['cat_id'] = $this->Application->RecallVar($params['session_var'], 0);
 				}
 			}
 			else {
 				// path for category item category in front-end
 				$object = $this->getObject($params);
 				$params['cat_id'] = $object->GetDBField('CategoryId');
 			}
 
 			$navigation_bar = $this->Application->recallObject('kNavigationBar');
 			/* @var $navigation_bar kNavigationBar */
 
 			return $navigation_bar->build($params);
 		}
 
 		function BuildListSpecial($params)
 		{
 			if ($this->Special != '') return $this->Special;
 			if ( isset($params['parent_cat_id']) ) {
 				$parent_cat_id = $params['parent_cat_id'];
 			}
 			else {
 				$parent_cat_id = $this->Application->GetVar('c_id');
 				if (!$parent_cat_id) {
 					$parent_cat_id = $this->Application->GetVar('m_cat_id');
 				}
 			}
 
 			$recursive = isset($params['recursive']);
 
 			$list_unique_key = $this->getUniqueListKey($params).$recursive;
 			if ($list_unique_key == '') {
 				return parent::BuildListSpecial($params);
 			}
 
 			return crc32($parent_cat_id.$list_unique_key);
 		}
 
 		function CatalogItemCount($params)
 		{
 			$params['skip_quering'] = true;
 			$object =& $this->GetList($params);
 
 			return $object->GetRecordsCount(false) != $object->GetRecordsCount() ? $object->GetRecordsCount().' / '.$object->GetRecordsCount(false) : $object->GetRecordsCount();
 		}
 
 		function ListReviews($params)
 		{
 			$prefix = $this->Prefix . '-rev';
 
 			$review_tag_processor = $this->Application->recallObject($prefix . '.item_TagProcessor');
 			/* @var $review_tag_processor kDBTagProcessor */
 
 			return $review_tag_processor->PrintList($params);
 		}
 
 		function ReviewCount($params)
 		{
 			$review_tag_processor = $this->Application->recallObject('rev.item_TagProcessor');
 			/* @var $review_tag_processor kDBTagProcessor */
 
 			return $review_tag_processor->TotalRecords($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.'.$special, '.');
 			$params['tab_mode'] = $tab_params['mode'];
 			$params['grid_name'] = ($tab_params['mode'] == 'multi') ? $default_grid : $radio_grid;
 			$params['tab_dependant'] = $tab_params['dependant'];
 			$params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name
 
 			if ($special == 'showall' || $special == 'user') {
 				$params['grid_name'] .= 'ShowAll';
 			}
 
 			// use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag
 			return $this->Application->ParseBlock($params, 1);
 		}
 
 		/**
 		 * Show CachedNavbar of current item primary category
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function CategoryName($params)
 		{
 			// show category cachednavbar of
 			$object = $this->getObject($params);
 			$category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId');
 
 			$cache_key = 'category_paths[%CIDSerial:' . $category_id . '%][%PhrasesSerial%][Adm:' . (int)$this->Application->isAdmin . ']';
 			$category_path = $this->Application->getCache($cache_key);
 
 			if ($category_path === false) {
 				// not chached
 				if ($category_id > 0) {
 					$cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $object->GetField('CachedNavbar'));
 					$category_path = trim($this->CategoryName( Array('cat_id' => 0) ).' > '.str_replace('&|&', ' > ', $cached_navbar), ' > ');
 				}
 				else {
 					$category_path = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 				}
 
 				$this->Application->setCache($cache_key, $category_path);
 			}
 
 			return $category_path;
 		}
 
 		/**
 		 * Allows to determine if original value should be shown
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function DisplayOriginal($params)
 		{
-			// original id found & greather then zero + show original
+			// original id found & greater then zero + show original
 			$display_original = isset($params['display_original']) && $params['display_original'];
 
-			$owner_field = $this->Application->getUnitOption($this->Prefix, 'OwnerField');
-			if (!$owner_field) {
-				$owner_field = 'CreatedById';
-			}
-
 			$object = $this->getObject($params);
+			/* @var $object kCatDBItem */
+
+			$owner_field = $this->getUnitConfig()->getOwnerField('CreatedById');
 			$perm_value = $this->PermHelper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $this->Prefix);
 
 			return $display_original && ($perm_value == 1) && $this->Application->GetVar($this->Prefix.'.original_id');
 		}
 
 		/**
 		 * 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 kCatDBItem */
 
 			// 1. category restriction
 			$params['cat_id'] = $object->isLoaded() ? $object->GetDBField('ParentPath') : $this->Application->GetVar('m_cat_id');
 
 			// 2. owner restriction
-			$owner_field = $this->Application->getUnitOption($this->Prefix, 'OwnerField');
-			if (!$owner_field) {
-				$owner_field = 'CreatedById';
-			}
+			$owner_field = $this->getUnitConfig()->getOwnerField('CreatedById');
+
 			$is_owner = $object->GetDBField($owner_field) == $this->Application->RecallVar('user_id');
 
 			return $perm_helper->TagPermissionCheck($params, $is_owner);
 		}
 
 		/**
 		 * Creates link to current category or to module root category, when current category is home
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function SuggestItemLink($params)
 		{
 			if (!isset($params['cat_id'])) {
 				$params['cat_id'] = $this->Application->GetVar('m_cat_id');
 			}
 
 			if ($params['cat_id'] == 0) {
 				$params['cat_id'] = $this->Application->findModule('Var', $this->Prefix, 'RootCat');
 			}
 
 			$params['m_cat_page'] = 1;
 
 			return $this->Application->ProcessParsedTag('c', 'CategoryLink', $params);
 		}
 
 		/**
 		 * Allows to detect if item has any additional images available
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function HasAdditionalImages($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$cache_key = $object->Prefix . '_additional_images[%' . $this->Application->incrementCacheSerial($object->Prefix, $object->GetID(), false) . '%]';
 			$ret = $this->Application->getCache($cache_key);
 
-			if ($ret === false) {
+			if ( $ret === false ) {
 				$this->Conn->nextQueryCachable = true;
 				$sql = 'SELECT ImageId
-						FROM ' . $this->Application->getUnitOption('img', 'TableName') . '
+						FROM ' . $this->Application->getUnitConfig('img')->getTableName() . '
 						WHERE ResourceId = ' . $object->GetDBField('ResourceId') . ' AND DefaultImg != 1 AND Enabled = 1';
 				$ret = $this->Conn->GetOne($sql) ? 1 : 0;
 
 				$this->Application->setCache($cache_key, $ret);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Checks that item is pending
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function IsPending($params)
 		{
 			$object = $this->getObject($params);
 
 			$pending_status = Array (STATUS_PENDING, STATUS_PENDING_EDITING);
 			return in_array($object->GetDBField('Status'), $pending_status);
 		}
 
 		function IsFavorite($params)
 		{
 			static $favorite_status = Array ();
 
 			$object = $this->getObject($params);
 			/* @var $object kDBList */
 
-			if (!isset($favorite_status[$this->Special])) {
+			if ( !isset($favorite_status[$this->Special]) ) {
 				$resource_ids = $object->GetCol('ResourceId');
 
 				$user_id = $this->Application->RecallVar('user_id');
 				$sql = 'SELECT FavoriteId, ResourceId
-						FROM '.$this->Application->getUnitOption('fav', 'TableName').'
-						WHERE (PortalUserId = '.$user_id.') AND (ResourceId IN ('.implode(',', $resource_ids).'))';
+						FROM ' . $this->Application->getUnitConfig('fav')->getTableName() . '
+						WHERE (PortalUserId = ' . $user_id . ') AND (ResourceId IN (' . implode(',', $resource_ids) . '))';
 				$favorite_status[$this->Special] = $this->Conn->GetCol($sql, 'ResourceId');
 			}
 
 			return isset($favorite_status[$this->Special][$object->GetDBField('ResourceId')]);
 		}
 
 		/**
 		 * 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 FavoriteToggleLink($params)
 		{
 			$fav_prefix = $this->Prefix.'-fav';
 
 			$params['pass'] = implode(',', Array('m', $this->Prefix, $fav_prefix));
 			$params[$fav_prefix.'_event'] = 'OnFavoriteToggle';
 
 			return $this->ItemLink($params);
 		}
 
 		/**
 		 * Checks if item is passed in url
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function ItemAvailable($params)
 		{
 			return $this->Application->GetVar($this->getPrefixSpecial().'_id') > 0;
 		}
 
 		function SortingSelected($params)
 		{
 			$list =& $this->GetList($params);
 			$user_sorting_start = $this->getUserSortIndex();
 
 			// remove language prefix from $current_sorting_field
 			$current_sorting_field = preg_replace('/^l[\d]+_(.*)/', '\\1', $list->GetOrderField($user_sorting_start));
 			$current_sorting = $current_sorting_field . '|' . $list->GetOrderDirection($user_sorting_start);
 
 			return strtolower($current_sorting) == strtolower($params['sorting']) ? $params['selected'] : '';
 		}
 
 		function CombinedSortingDropDownName($params)
 		{
 			$list =& $this->GetList($params);
 
 			if ($list->isMainList()) {
 				return parent::CombinedSortingDropDownName($params);
 			}
 
 			return $list->Prefix . '_CombinedSorting';
 		}
 
 		/**
 		 * Prepares name for field with event in it (used only on front-end)
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function SubmitName($params)
 		{
 			$list =& $this->GetList($params);
 
 			if ($list->isMainList()) {
 				return parent::SubmitName($params);
 			}
 
 			return 'events[' . $list->Prefix . '][' . $params['event'] . ']';
 		}
 
 		/**
 		 * Returns prefix + any word (used for shared between categories per page settings)
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function VarName($params)
 		{
 			$list =& $this->GetList($params);
 
 			if ($list->isMainList()) {
 				return parent::VarName($params);
 			}
 
 			return $list->Prefix . '_' . $params['type'];
 		}
 
 		/**
 		 * Checks if we are viewing module root category
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function IsModuleHome($params)
 		{
 			$root_category = $this->Application->findModule('Var', $this->Prefix, 'RootCat');
 
 			return $root_category == $this->Application->GetVar('m_cat_id');
 		}
 
 		/**
 		 * Dynamic votes indicator
 		 *
 		 * @param Array $params
 		 *
 		 * @return string
 		 */
 		function VotesIndicator($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$rating_helper = $this->Application->recallObject('RatingHelper');
 			/* @var $rating_helper RatingHelper */
 
 			$small_style = array_key_exists('small_style', $params) ? $params['small_style'] : false;
 
 			return $rating_helper->ratingBar($object, true, '', $small_style);
 		}
 
 		function RelevanceIndicator($params)
 		{
 			$object = $this->getObject($params);
 
 			$search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 			$sql = 'SELECT Relevance
 					FROM '.$search_results_table.'
 					WHERE ResourceId = '.$object->GetDBField('ResourceId');
 
 	    	$percents_off = (int)(100 - (100 * $this->Conn->GetOne($sql)));
 	    	$percents_off = ($percents_off < 0) ? 0 : $percents_off;
 	    	if ($percents_off) {
 	        	$params['percent_off'] = $percents_off;
 	    		$params['percent_on'] = 100 - $percents_off;
 	        	$params['name'] = $this->SelectParam($params, 'relevance_normal_render_as,block_relevance_normal');
 	    	}
 	    	else {
 	    		$params['name'] = $this->SelectParam($params, 'relevance_full_render_as,block_relevance_full');
 	    	}
 	    	return $this->Application->ParseBlock($params);
 		}
 
 		function SearchResultField($params)
 		{
 			$ret = $this->Field($params);
 
 			$keywords = unserialize( $this->Application->RecallVar('highlight_keywords') );
 			$opening = $this->Application->ParseBlock( Array('name' => $this->SelectParam($params, 'highlight_opening_render_as,block_highlight_opening')) );
 			$closing = $this->Application->ParseBlock( Array('name' => $this->SelectParam($params, 'highlight_closing_render_as,block_highlight_closing')) );
 
 			foreach ($keywords as $index => $keyword) {
 				$keywords[$index] = preg_quote($keyword, '/');
 			}
 
 			return preg_replace('/('.implode('|', $keywords).')/i', $opening.'\\1'.$closing, $ret);
 		}
 
 		/**
 		 * Shows keywords, that user searched
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function SearchKeywords($params)
 		{
 			$keywords = $this->Application->GetVar('keywords');
 			$sub_search = $this->Application->GetVar('search_type') == 'subsearch';
 
 			return ($keywords !== false) && !$sub_search ? $keywords : $this->Application->RecallVar('keywords');
 		}
 
 		function AdvancedSearchForm($params)
 		{
-			$search_table = $this->Application->getUnitOption('confs', 'TableName');
+			$search_table = $this->Application->getUnitConfig('confs')->getTableName();
 			$module_name = $this->Application->findModule('Var', $this->Prefix, 'Name');
 
 			$sql = 'SELECT *
-					FROM '.$search_table.'
-					WHERE (ModuleName = '.$this->Conn->qstr($module_name).') AND (AdvancedSearch = 1)
+					FROM ' . $search_table . '
+					WHERE (ModuleName = ' . $this->Conn->qstr($module_name) . ') AND (AdvancedSearch = 1)
 					ORDER BY DisplayOrder';
 			$search_config = $this->Conn->Query($sql);
 
 			$ret = '';
+
 			foreach ($search_config as $record) {
 				$params['name'] = $this->SelectParam($params, 'and_or_render_as,and_or_block');
 				$params['field'] = $record['FieldName'];
 				$params['andor'] = $this->Application->ParseBlock($params);
 
-				$params['name'] = $this->SelectParam($params, $record['FieldType'].'_render_as,'.$record['FieldType'].'_block');
+				$params['name'] = $this->SelectParam($params, $record['FieldType'] . '_render_as,' . $record['FieldType'] . '_block');
 				$params['caption'] = $this->Application->Phrase($record['DisplayName']);
 				$ret .= $this->Application->ParseBlock($params);
 			}
+
 			return $ret;
 		}
 
 		/**
 		 * Returns last modification date of items in category / system
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function LastUpdated($params)
 		{
 			$category_id = (int)$this->Application->GetVar('m_cat_id');
 			$local = array_key_exists('local', $params) && ($category_id > 0) ? $params['local'] : false;
 
 			$serial_name1 = $this->Application->incrementCacheSerial('c', $local ? $category_id : null, false);
 			$serial_name2 = $this->Application->incrementCacheSerial($this->Prefix, null, false);
 			$cache_key = 'categoryitems_last_updated[%' . $serial_name1 . '%][%' . $serial_name2 . '%]';
 
 			$row_data = $this->Application->getCache($cache_key);
 
 			if ( $row_data === false ) {
+				$config = $this->getUnitConfig();
+
 				if ( $local && ($category_id > 0) ) {
 					// scan only current category & it's children
 					list ($tree_left, $tree_right) = $this->Application->getTreeIndex($category_id);
 
 					$sql = 'SELECT MAX(item_table.Modified) AS ModDate, MAX(item_table.CreatedOn) AS NewDate
-			        		FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . ' item_table
+			        		FROM ' . $config->getTableName() . ' item_table
 			        		LEFT JOIN ' . TABLE_PREFIX . 'CategoryItems ci ON (item_table.ResourceId = ci.ItemResourceId)
 			        		LEFT JOIN ' . TABLE_PREFIX . 'Categories c ON c.CategoryId = ci.CategoryId
 			        		WHERE c.TreeLeft BETWEEN ' . $tree_left . ' AND ' . $tree_right;
 				}
 				else {
 					// scan all categories in system
 					$sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate
-			       			FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName');
+			       			FROM ' . $config->getTableName();
 				}
 
 				$this->Conn->nextQueryCachable = true;
 				$row_data = $this->Conn->GetRow($sql);
 				$this->Application->setCache($cache_key, $row_data);
 			}
 
 			if ( !$row_data ) {
 				return '';
 			}
 
 			$date = $row_data[$row_data['NewDate'] > $row_data['ModDate'] ? 'NewDate' : 'ModDate'];
 
 			// format date
 			$format = isset($params['format']) ? $params['format'] : '_regional_DateTimeFormat';
 
 			if ( preg_match("/_regional_(.*)/", $format, $regs) ) {
 				$lang = $this->Application->recallObject('lang.current');
 				/* @var $lang LanguagesItem */
 
 				if ( $regs[1] == 'DateTimeFormat' ) {
 					// combined format
 					$format = $lang->GetDBField('DateFormat') . ' ' . $lang->GetDBField('TimeFormat');
 				}
 				else {
 					// simple format
 					$format = $lang->GetDBField($regs[1]);
 				}
 			}
 
 			return adodb_date($format, $date);
 		}
 
 		/**
 		 * Counts category item count in system (not category-dependent)
 		 *
 		 * @param Array $params
 		 * @return int
 		 */
 		function ItemCount($params)
 		{
 			$count_helper = $this->Application->recallObject('CountHelper');
 			/* @var $count_helper kCountHelper */
 
 			$today_only = isset($params['today']) && $params['today'];
 			return $count_helper->ItemCount($this->Prefix, $today_only);
 		}
 
 		function CategorySelector($params)
 		{
 			$category_id = isset($params['category_id']) && is_numeric($params['category_id']) ? $params['category_id'] : false;
 			if ($category_id === false) {
 				// if category id not given use module root category
 				$category_id = $this->Application->findModule('Var', $this->Prefix, 'RootCat');
 			}
 
-			$id_field = $this->Application->getUnitOption('c', 'IDField');
-			$title_field = $this->Application->getUnitOption('c', 'TitleField');
-			$table_name = $this->Application->getUnitOption('c', 'TableName');
+			$category_config = $this->Application->getUnitConfig('c');
+
+			$id_field = $category_config->getIDField();
+			$title_field = $category_config->getTitleField();
+			$table_name = $category_config->getTableName();
 
 			$count_helper = $this->Application->recallObject('CountHelper');
 			/* @var $count_helper kCountHelper */
 
 			list ($view_perm, $view_filter) = $count_helper->GetPermissionClause('c', 'perm_cache');
 
 			// get category list (permission based)
 		  	$sql = 'SELECT c.'.$title_field.' AS CategoryName, c.'.$id_field.', c.l' . $this->Application->GetVar('m_lang') . '_CachedNavbar AS CachedNavbar
 		  			FROM '.$table_name.' c
 		    		INNER JOIN '.TABLE_PREFIX.'CategoryPermissionsCache perm_cache ON c.CategoryId = perm_cache.CategoryId
 		    		WHERE (ParentId = '.$category_id.') AND ('.$view_filter.') AND (perm_cache.PermId = '.$view_perm.') AND (c.Status = '.STATUS_ACTIVE.')
 		    		ORDER BY c.'.$title_field.' ASC';
 			$categories = $this->Conn->Query($sql, $id_field);
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 			$block_params['strip_nl'] = 2;
 
 			$ret = '';
 			foreach ($categories as $category_id => $category_data) {
 				// print category
 				$block_params['separator'] = isset($params['category_id']) ? $params['separator'] : ''; // return original separator, remove separator for top level categories
 				$block_params['category_id'] = $category_id;
 				$block_params['category_name'] = $category_data['CategoryName'];
 
 				$cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $category_data['CachedNavbar']);
 				$block_params['full_path'] = str_replace('&|&', ' > ', $cached_navbar);
 
 				$ret .= $this->Application->ParseBlock($block_params);
 
 				// print it's children
 				$block_params['separator'] = '&nbsp;&nbsp;&nbsp;'.$params['separator'];
 				$ret .= $this->CategorySelector($block_params);
 			}
 
 			return $ret;
 		}
 
 		function PrintMoreCategories($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$category_ids = $this->Field($params);
 			if (!$category_ids) {
 				return '';
 			}
 
 			$category_ids = explode('|', substr($category_ids, 1, -1));
+			$category_config = $this->Application->getUnitConfig('c');
 
-			$id_field = $this->Application->getUnitOption('c', 'IDField');
-			$title_field = $this->Application->getUnitOption('c', 'TitleField');
-			$table_name = $this->Application->getUnitOption('c', 'TableName');
+			$id_field = $category_config->getIDField();
+			$title_field = $category_config->getTitleField();
+			$table_name = $category_config->getTableName();
 
 			$sql = 'SELECT '.$title_field.' AS CategoryName, '.$id_field.', l' . $this->Application->GetVar('m_lang') . '_CachedNavbar AS CachedNavbar
 					FROM '.$table_name.'
 					WHERE '.$id_field.' IN ('.implode(',', $category_ids).')';
 			$categories = $this->Conn->Query($sql, $id_field);
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 
 			$ret = '';
 			foreach ($categories as $category_id => $category_data) {
 				$block_params['category_id'] = $category_id;
 				$block_params['category_name'] = $category_data['CategoryName'];
 
 				$cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $category_data['CachedNavbar']);
 				$block_params['full_path'] = str_replace('&|&', ' > ', $cached_navbar);
 
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 
 			return $ret;
 		}
 
 		function DownloadFileLink($params)
 		{
 			$params[$this->getPrefixSpecial().'_event'] = 'OnDownloadFile';
 
 			return $this->ItemLink($params);
 		}
 
 		function ImageSrc($params)
 		{
 			list ($ret, $tag_processed) = $this->processAggregatedTag('ImageSrc', $params, $this->getPrefixSpecial());
 			return $tag_processed ? $ret : false;
 		}
 
 		/**
 		 * Registers hit for item (one time per session)
 		 *
 		 * @param Array $params
 		 */
 		function RegisterHit($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kCatDBItem */
 
 			if ($object->isLoaded()) {
 				$object->RegisterHit();
 			}
 		}
 
 		/**
 		 * Returns link to item's author public profile
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function ProfileLink($params)
 		{
 			$object = $this->getObject($params);
 			$owner_field = array_key_exists('owner_field', $params) ? $params['owner_field'] : 'CreatedById';
 			$params['user_id'] = $object->GetDBField($owner_field);
 			unset($params['owner_field']);
 
 			return $this->Application->ProcessParsedTag('m', 'Link', $params);
 		}
 
 		/**
 		 * Checks, that "view in browse mode" functionality available
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function BrowseModeAvailable($params)
 		{
 			$valid_special = $valid_special = $params['Special'] != 'user';
 			$not_selector = $this->Application->GetVar('type') != 'item_selector';
 
 			return $valid_special && $not_selector;
 		}
 
 		/**
 		 * Returns a link for editing product
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function ItemEditLink($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBList */
 
-			$edit_template = $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePath') . '/' . $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePrefix') . 'edit';
+			$config = $this->getUnitConfig();
+			$edit_template = $config->getAdminTemplatePath() . '/' . $config->getAdminTemplatePrefix() . '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('CategoryId'),
 				'pass'					=>	'all,'.$this->Prefix,
 				'no_pass_through'		=>	1,
 			);
 
 			return $this->Application->HREF($edit_template,'', $url_params);
 		}
 
 		function LanguageVisible($params)
 		{
 			$field = $this->SelectParam($params, 'name,field');
 
 			preg_match('/l([\d]+)_(.*)/', $field, $regs);
 			$params['name'] = $regs[2];
 
 			return $this->HasLanguageError($params) || $this->Application->GetVar('m_lang') == $regs[1];
 		}
 
 		function HasLanguageError($params)
 		{
 			static $languages = null;
 
-			if (!isset($languages)) {
-				$sql = 'SELECT ' . $this->Application->getUnitOption('lang', 'IDField') . '
-						FROM ' . $this->Application->getUnitOption('lang', 'TableName') . '
+			if ( !isset($languages) ) {
+				$language_config = $this->Application->getUnitConfig('lang');
+
+				$sql = 'SELECT ' . $language_config->getIDField() . '
+						FROM ' . $language_config->getTableName() . '
 						WHERE Enabled = 1';
 				$languages = $this->Conn->GetCol($sql);
 			}
 
 			$field = $this->SelectParam($params, 'name,field');
 
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			foreach ($languages as $language_id) {
 				$check_field = 'l' . $language_id . '_' . $field;
-				if ($object->GetErrorMsg($check_field, false)) {
+
+				if ( $object->GetErrorMsg($check_field, false) ) {
 					return true;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Returns list of categories, that have add/edit permission for current category item type
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function AllowedCategoriesJSON($params)
 		{
-			if ($this->Application->RecallVar('user_id') == USER_ROOT) {
+			if ( $this->Application->RecallVar('user_id') == USER_ROOT ) {
 				$categories = true;
 			}
 			else {
 				$object = $this->getObject($params);
 				/* @var $object kDBItem */
 
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
-				$perm_prefix = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix');
+				$perm_prefix = $this->getUnitConfig()->getPermItemPrefix();
 				$categories = $perm_helper->getPermissionCategories($perm_prefix . '.' . ($object->IsNewItem() ? 'ADD' : 'MODIFY'));
 			}
 
-			$json_helper = $this->Application->recallObject('JSONHelper');
-			/* @var $json_helper JSONHelper */
-
-			return $json_helper->encode($categories);
+			return json_encode($categories);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/kernel/db/dblist.php
===================================================================
--- branches/5.3.x/core/kernel/db/dblist.php	(revision 15697)
+++ branches/5.3.x/core/kernel/db/dblist.php	(revision 15698)
@@ -1,1761 +1,1761 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 /**
 * DBList
 *
 */
 class kDBList extends kDBBase implements Iterator, Countable {
 
 	// kDBList filter types (then, types are divided into classes)
 
 	/**
 	 * Having filter [step1]
 	 *
 	 */
 	const HAVING_FILTER = 1;
 
 	/**
 	 * Where filter [step1]
 	 *
 	 */
 	const WHERE_FILTER = 2;
 
 	/**
 	 * Aggregated filter [step1]
 	 *
 	 */
 	const AGGREGATE_FILTER = 3;
 
 	/**
 	 * System filter [step2, AND]
 	 *
 	 */
 	const FLT_SYSTEM = 1;
 
 	/**
 	 * User filter [step2, OR]
 	 * @deprecated
 	 *
 	 */
 	const FLT_NORMAL = 2;
 
 	/**
 	 * User "Search" filter [step2, OR]
 	 *
 	 */
 	const FLT_SEARCH = 3;
 
 	/**
 	 * User "View Menu" filter [step2, AND]
 	 *
 	 */
 	const FLT_VIEW = 4;
 
 	/**
 	 * User "Custom" (above grid columns) filter [step2, AND]
 	 *
 	 */
 	const FLT_CUSTOM = 5;
 
 	/**
 	 * kMultipleFilter AND filter [step3]
 	 *
 	 */
 	const FLT_TYPE_AND = 'AND';
 
 	/**
 	 * kMultipleFilter OR filter [step3]
 	 *
 	 */
 	const FLT_TYPE_OR = 'OR';
 
 	/**
 	 * Totals for fields specified in config
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $Totals = Array ();
 
 	/**
 	 * Remembers if totals were calculated
 	 *
 	 * @var bool
 	 * @access protected
 	 */
 	protected $TotalsCalculated = false;
 
 	/**
 	* List of "ORDER BY" fields
 	*
 	* @var Array
 	* @access protected
 	*/
 	protected $OrderFields = Array ();
 
 	/**
 	* Counted total number of records in the query - without pagination (system+user filters)
 	*
 	* @var int
 	* @access protected
 	*/
 	protected $RecordsCount = 0;
 
 	/**
 	 * Record count with only system filters applied
 	 *
 	 * @var int
 	 * @access protected
 	 */
 	protected $NoFilterCount = 0;
 
 	/**
 	 * Record count selected to be showed on current page
 	 *
 	 * @var int
 	 * @access protected
 	 */
 	protected $SelectedCount = 0;
 
 	/**
 	 * Array of selected records
 	 *
 	 * @var Array
 	 * @access public
 	 */
 	public $Records;
 
 	/**
 	 * Current record index
 	 *
 	 * @var int
 	 * @access protected
 	 */
 	protected $CurrentIndex = 0;
 
 	/**
 	* List items per-page
 	*
 	* @var int
 	* @access protected
 	*/
 	protected $PerPage;
 
 	/**
 	* Page count in list based on PerPage & RecordsCount attributes
 	*
 	* @var int
 	* @access protected
 	*/
 	protected $TotalPages;
 
 	/**
 	* Current page number - used when forming LIMIT clause of SELECT statement
 	*
 	* @var int
 	* @access protected
 	*/
 	protected $Page;
 
 	/**
 	* Offset for LIMIT clause, calculated in {@link kDBList::PerPage()}
 	*
 	* @var int
 	* @access protected
 	*/
 	protected $Offset;
 
 	/**
 	 * WHERE filter objects
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $WhereFilter = Array ();
 
 	/**
 	 * HAVING filter objects
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $HavingFilter = Array ();
 
 	/**
 	 * AGGREGATED filter objects
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $AggregateFilter = Array ();
 
 	/**
 	 * List of "GROUP BY" fields
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $GroupByFields = Array ();
 
 	/**
 	 * Remembers if list was queried
 	 *
 	 * @var bool
 	 * @access protected
 	 */
 	protected $Queried = false;
 
 	/**
 	 * Remembers if list was counted
 	 *
 	 * @var bool
 	 * @access protected
 	 */
 	protected $Counted = false;
 
 	/**
 	 * Name of the grid, used to display the list
 	 *
 	 * @var string
 	 */
 	var $gridName = '';
 
 	/**
 	 * Identifies this list as main on the page, that allows to react on "page", "per_page" and "sort_by" parameters from url
 	 *
 	 * @var bool
 	 * @access protected
 	 */
 	protected $mainList = false;
 
 	/**
 	 * Holds field errors
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $FieldErrors = Array ();
 
 	/**
 	* Creates kDBList
 	*
 	* @return kDBList
 	* @access public
 	*/
 	public function __construct()
 	{
 		parent::__construct();
 		$this->OrderFields = Array();
 
 
 		$filters = $this->getFilterStructure();
 
 		foreach ($filters as $filter_params) {
 			$filter =& $this->$filter_params['type'];
 			$filter[ $filter_params['class'] ] = $this->Application->makeClass('kMultipleFilter', Array ($filter_params['join_using']));
 		}
 
 		$this->PerPage = -1;
 	}
 
 	function setGridName($grid_name)
 	{
 		$this->gridName = $grid_name;
 	}
 
 	/**
 	 * Returns information about all possible filter types
 	 *
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getFilterStructure()
 	{
 		$filters = 	Array (
 							Array ('type' => 'WhereFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND),
 							Array ('type' => 'WhereFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR),
 							Array ('type' => 'WhereFilter', 'class' => self::FLT_SEARCH, 'join_using' => self::FLT_TYPE_OR),
 							Array ('type' => 'WhereFilter', 'class' => self::FLT_VIEW, 'join_using' => self::FLT_TYPE_AND),
 							Array ('type' => 'WhereFilter', 'class' => self::FLT_CUSTOM, 'join_using' => self::FLT_TYPE_AND),
 
 							Array ('type' => 'HavingFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND),
 							Array ('type' => 'HavingFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR),
 							Array ('type' => 'HavingFilter', 'class' => self::FLT_SEARCH, 'join_using' => self::FLT_TYPE_OR),
 							Array ('type' => 'HavingFilter', 'class' => self::FLT_VIEW, 'join_using' => self::FLT_TYPE_AND),
 							Array ('type' => 'HavingFilter', 'class' => self::FLT_CUSTOM, 'join_using' => self::FLT_TYPE_AND),
 
 							Array ('type' => 'AggregateFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND),
 							Array ('type' => 'AggregateFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR),
 							Array ('type' => 'AggregateFilter', 'class' => self::FLT_VIEW, 'join_using' => self::FLT_TYPE_AND),
 					);
 
 		return $filters;
 	}
 
 	/**
 	 * Adds new or replaces old filter with same name
 	 *
 	 * @param string $name filter name (for internal use)
 	 * @param string $clause where/having clause part (no OR/AND allowed)
 	 * @param int $filter_type is filter having filter or where filter
 	 * @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM
 	 * @access public
 	 */
 	public function addFilter($name, $clause, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM)
 	{
 		$filter_source = Array (
 			self::WHERE_FILTER => 'WhereFilter',
 			self::HAVING_FILTER => 'HavingFilter',
 			self::AGGREGATE_FILTER => 'AggregateFilter'
 		);
 
 		$filter_name = $filter_source[$filter_type];
 
 		$filter =& $this->$filter_name;
 		$filter =& $filter[$filter_scope];
 		/* @var $filter kMultipleFilter */
 
 		$filter->addFilter($name, $clause);
 	}
 
 	/**
 	 * Reads filter content
 	 *
 	 * @param string $name filter name (for internal use)
 	 * @param int $filter_type is filter having filter or where filter
 	 * @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM
 	 * @return string
 	 * @access public
 	 */
 	public function getFilter($name, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM)
 	{
 		$filter_source = Array (
 			self::WHERE_FILTER => 'WhereFilter',
 			self::HAVING_FILTER => 'HavingFilter',
 			self::AGGREGATE_FILTER => 'AggregateFilter'
 		);
 
 		$filter_name = $filter_source[$filter_type];
 
 		$filter =& $this->$filter_name;
 		$filter =& $filter[$filter_scope];
 		/* @var $filter kMultipleFilter */
 
 		return $filter->getFilter($name);
 	}
 
 	/**
 	 * Removes specified filter from filters list
 	 *
 	 * @param string $name filter name (for internal use)
 	 * @param int $filter_type is filter having filter or where filter
 	 * @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM
 	 * @access public
 	 */
 	public function removeFilter($name, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM)
 	{
 		$filter_source = Array (
 			self::WHERE_FILTER => 'WhereFilter',
 			self::HAVING_FILTER => 'HavingFilter',
 			self::AGGREGATE_FILTER => 'AggregateFilter'
 		);
 
 		$filter_name = $filter_source[$filter_type];
 
 		$filter =& $this->$filter_name;
 		$filter =& $filter[$filter_scope];
 		/* @var $filter kMultipleFilter */
 
 		$filter->removeFilter($name);
 	}
 
 	/**
 	 * Clear all filters
 	 *
 	 * @access public
 	 */
 	public function clearFilters()
 	{
 		$filters = $this->getFilterStructure();
 
 		foreach ($filters as $filter_params) {
 			$filter =& $this->$filter_params['type'];
 			$filter[ $filter_params['class'] ]->clearFilters();
 		}
 	}
 
 	/**
 	* Counts the total number of records base on the query resulted from {@link kDBList::GetSelectSQL()}
 	*
 	* The method modifies the query to substitude SELECT part (fields listing) with COUNT(*).
 	* Special care should be applied when working with lists based on grouped queries, all aggregate function fields
 	* like SUM(), AVERAGE() etc. should be added to CountedSQL by using {@link kDBList::SetCountedSQL()}
 	*
 	* @access protected
 	*/
 	protected function CountRecs()
 	{
 		$all_sql = $this->GetSelectSQL(true,false);
 		$sql = $this->getCountSQL($all_sql);
 
 		$this->Counted = true;
 
 		if( $this->GetGroupClause() )
 		{
     		$this->RecordsCount = count( $this->Conn->GetCol($sql) );
 		}
 		else
 		{
 			$this->RecordsCount = (int)$this->Conn->GetOne($sql);
 		}
 
 	    $system_sql = $this->GetSelectSQL(true,true);
 	    if($system_sql == $all_sql) //no need to query the same again
 	    {
 	    	$this->NoFilterCount = $this->RecordsCount;
 	    	return;
 	    }
 
 		$sql = $this->getCountSQL($system_sql);
 		if( $this->GetGroupClause() )
 		{
 			$this->NoFilterCount = count( $this->Conn->GetCol($sql) );
 		}
 		else
 		{
 			$this->NoFilterCount = (int)$this->Conn->GetOne($sql);
 		}
 	}
 
 	/**
 	 * Returns record count in list with/without user filters applied
 	 *
 	 * @param bool $with_filters
 	 * @return int
 	 * @access public
 	 */
 	public function GetRecordsCount($with_filters = true)
 	{
 		if (!$this->Counted) {
 			$this->CountRecs();
 		}
 
 		return $with_filters ? $this->RecordsCount : $this->NoFilterCount;
 	}
 
 	/**
 	 * Returns record count, that were actually selected
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function GetSelectedCount()
 	{
 		return $this->SelectedCount;
 	}
 
 	/**
 	 * Transforms given query into count query (DISTINCT is also processed)
 	 *
 	 * @param string $sql
 	 * @return string
 	 * @access public
 	 */
 	public function getCountSQL($sql)
 	{
 		if ( preg_match("/^\s*SELECT\s+DISTINCT(.*?\s)FROM(?!_)/is",$sql,$regs ) )
 		{
 			return preg_replace("/^\s*SELECT\s+DISTINCT(.*?\s)FROM(?!_)/is", "SELECT COUNT(DISTINCT ".$regs[1].") AS count FROM", $sql);
 		}
 		else
 		{
 			return preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
 		}
 	}
 
 	/**
 	 * Queries the database with SQL resulted from {@link kDBList::GetSelectSQL()} and stores result in {@link kDBList::SelectRS}
 	 *
 	 * All the sorting, pagination, filtration of the list should be set prior to calling Query().
 	 *
 	 * @param bool $force force re-query, when already queried
 	 * @return bool
 	 * @access public
 	 */
 	public function Query($force=false)
 	{
 		if (!$force && $this->Queried) return true;
 		$q = $this->GetSelectSQL();
 
 		//$rs = $this->Conn->SelectLimit($q, $this->PerPage, $this->Offset);
 
 		//in case we have not counted records try to select one more item to find out if we have something more than perpage
 		$limit = $this->Counted ? $this->PerPage : $this->PerPage+1;
 
 		$sql = $q.' '.$this->Conn->getLimitClause($this->Offset,$limit);
 
 		$this->Records = $this->Conn->Query($sql);
 
 		if (!$this->Records && ($this->Page > 1)) {
 			// no records & page > 1, try to reset to 1st page (works only when list in not counted before)
 			$this->Application->StoreVar($this->getPrefixSpecial() . '_Page', 1, true);
 			$this->SetPage(1);
 			$this->Query($force);
 		}
 
 		$this->SelectedCount = count($this->Records);
 		if (!$this->Counted) $this->RecordsCount = $this->SelectedCount;
 		if (!$this->Counted && $this->SelectedCount > $this->PerPage && $this->PerPage != -1) $this->SelectedCount--;
 
 		if ($this->Records === false) {
 			//handle errors here
 			return false;
 		}
 		$this->Queried = true;
 
 		$this->Application->HandleEvent(new kEvent($this->getPrefixSpecial() . ':OnAfterListQuery'));
 
 		return true;
 	}
 
 	/**
 	 * Adds one more record to list virtually and updates all counters
 	 *
 	 * @param Array $record
 	 * @access public
 	 */
 	public function addRecord($record)
 	{
 		$this->Records[] = $record;
 		$this->SelectedCount++;
 		$this->RecordsCount++;
 	}
 
 	/**
 	 * Calculates totals based on config
 	 *
 	 * @access protected
 	 */
 	protected function CalculateTotals()
 	{
-		$fields = Array();
-		$this->Totals = Array();
+		$fields = Array ();
+		$this->Totals = Array ();
 
-		if ($this->gridName) {
-			$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-			$grid_fields = $grids[$this->gridName]['Fields'];
+		if ( $this->gridName ) {
+			$grid = $this->getUnitConfig()->getGridByName($this->gridName);
+			$grid_fields = $grid['Fields'];
 		}
 		else {
 			$grid_fields = $this->Fields;
 		}
 
 		foreach ($grid_fields as $field_name => $field_options) {
-			if ($this->gridName && array_key_exists('totals', $field_options) && $field_options['totals']) {
+			if ( $this->gridName && array_key_exists('totals', $field_options) && $field_options['totals'] ) {
 				$totals = $field_options['totals'];
 			}
-			elseif (array_key_exists('totals', $this->Fields[$field_name]) && $this->Fields[$field_name]['totals']) {
+			elseif ( array_key_exists('totals', $this->Fields[$field_name]) && $this->Fields[$field_name]['totals'] ) {
 				$totals = $this->Fields[$field_name]['totals'];
 			}
 			else {
 				continue;
 			}
 
 			$calculated_field = array_key_exists($field_name, $this->CalculatedFields) && array_key_exists($field_name, $this->VirtualFields);
 			$db_field = !array_key_exists($field_name, $this->VirtualFields);
 
-			if ($calculated_field || $db_field) {
-				$field_expression = $calculated_field ? $this->CalculatedFields[$field_name] : '`'.$this->TableName.'`.`'.$field_name.'`';
-				$fields[$field_name] = $totals.'('.$field_expression.') AS '.$field_name.'_'.$totals;
+			if ( $calculated_field || $db_field ) {
+				$field_expression = $calculated_field ? $this->CalculatedFields[$field_name] : '`' . $this->TableName . '`.`' . $field_name . '`';
+				$fields[$field_name] = $totals . '(' . $field_expression . ') AS ' . $field_name . '_' . $totals;
 			}
 		}
 
-		if (!$fields) {
-			return ;
+		if ( !$fields ) {
+			return;
 		}
 
 		$sql = $this->GetSelectSQL(true, false);
 		$fields = str_replace('%1$s', $this->TableName, implode(', ', $fields));
 
-		if ( preg_match("/DISTINCT(.*?\s)FROM(?!_)/is",$sql,$regs ) )
-		{
-			$sql = preg_replace("/^\s*SELECT DISTINCT(.*?\s)FROM(?!_)/is", 'SELECT '.$fields.' FROM', $sql);
+		if ( preg_match("/DISTINCT(.*?\s)FROM(?!_)/is", $sql, $regs) ) {
+			$sql = preg_replace("/^\s*SELECT DISTINCT(.*?\s)FROM(?!_)/is", 'SELECT ' . $fields . ' FROM', $sql);
 		}
-		else
-		{
-			$sql = preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", 'SELECT '.$fields.' FROM ', $sql);
+		else {
+			$sql = preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", 'SELECT ' . $fields . ' FROM ', $sql);
 		}
 
 		$totals = $this->Conn->Query($sql);
 
-		foreach($totals as $totals_row)
-		{
-			foreach($totals_row as $total_field => $field_value)
-			{
-				if(!isset($this->Totals[$total_field])) $this->Totals[$total_field] = 0;
+		foreach ($totals as $totals_row) {
+			foreach ($totals_row as $total_field => $field_value) {
+				if ( !isset($this->Totals[$total_field]) ) {
+					$this->Totals[$total_field] = 0;
+				}
 				$this->Totals[$total_field] += $field_value;
 			}
 		}
+
 		$this->TotalsCalculated = true;
 	}
 
 	/**
 	 * Returns previously calculated total (not formatted)
 	 *
 	 * @param string $field
 	 * @param string $total_function
 	 * @return float
 	 * @access public
 	 */
 	public function getTotal($field, $total_function)
 	{
 		if (!$this->TotalsCalculated) {
 			$this->CalculateTotals();
 		}
 
 		return $this->Totals[$field . '_' . $total_function];
 	}
 
 	function setTotal($field, $total_function, $value)
 	{
 		$this->Totals[$field . '_' . $total_function] = $value;
 	}
 
 	function getTotalFunction($field)
 	{
-		if ($this->gridName) {
-			$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-			$field_options = $grids[$this->gridName]['Fields'][$field];
+		if ( $this->gridName ) {
+			$grid = $this->getUnitConfig()->getGridByName($this->gridName);
+			$field_options = $grid['Fields'][$field];
 		}
 		else {
 			$field_options = $this->Fields[$field];
 		}
 
-		if ($this->gridName && array_key_exists('totals', $field_options) && $field_options['totals']) {
+		if ( $this->gridName && array_key_exists('totals', $field_options) && $field_options['totals'] ) {
 			return $field_options['totals'];
 		}
-		elseif (array_key_exists('totals', $this->Fields[$field]) && $this->Fields[$field]['totals']) {
+		elseif ( array_key_exists('totals', $this->Fields[$field]) && $this->Fields[$field]['totals'] ) {
 			return $this->Fields[$field]['totals'];
 		}
 
 		return false;
 	}
 
 	/**
 	 * Returns previously calculated total (formatted)
 	 *
 	 * @param string $field
 	 * @param string $total_function
 	 * @return float
 	 * @access public
 	 */
 	function GetFormattedTotal($field, $total_function)
 	{
 		$res = $this->getTotal($field, $total_function);
 		$formatter_class = $this->GetFieldOption($field, 'formatter');
 
 		if ( $formatter_class ) {
 			$formatter = $this->Application->recallObject($formatter_class);
 			/* @var $formatter kFormatter */
 
 			$res = $formatter->Format($res, $field, $this);
 		}
 
 		return $res;
 	}
 
 	/**
 	 * Builds full select query except for LIMIT clause
 	 *
 	 * @param bool $for_counting
 	 * @param bool $system_filters_only
 	 * @param string $keep_clause
 	 * @return string
 	 * @access public
 	 */
 	public function GetSelectSQL($for_counting = false, $system_filters_only = false, $keep_clause = '')
 	{
 		$q = parent::GetSelectSQL($this->SelectClause);
 		$q = !$for_counting ? $this->addCalculatedFields($q, 0) : str_replace('%2$s', '', $q);
 
 		$where = $this->GetWhereClause($for_counting,$system_filters_only);
 		$having = $this->GetHavingClause($for_counting,$system_filters_only);
 		$order = $this->GetOrderClause();
 		$group = $this->GetGroupClause();
 
 		if ( $for_counting ) {
 			$usage_string = $where . '|' . $having . '|' . $order . '|' . $group . '|' . $keep_clause;
 			$optimizer = new LeftJoinOptimizer($q, str_replace('%1$s', $this->TableName, $usage_string));
 			$q = $optimizer->simplify();
 		}
 
 		if (!empty($where)) $q .= ' WHERE ' . $where;
 		if (!empty($group)) $q .= ' GROUP BY ' . $group;
 		if (!empty($having)) $q .= ' HAVING ' . $having;
 		if ( !$for_counting && !empty($order) ) $q .= ' ORDER BY ' . $order;
 
 		return $this->replaceModePrefix( str_replace('%1$s', $this->TableName, $q) );
 	}
 
 	/**
 	 * Replaces all calculated field occurrences with their associated expressions
 	 *
 	 * @param string $clause where clause to extract calculated fields from
 	 * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only
 	 * @param bool $replace_table
 	 * @return string
 	 * @access public
 	 */
 	public function extractCalculatedFields($clause, $aggregated = 1, $replace_table = false)
 	{
 		$fields = $this->getCalculatedFields($aggregated);
 
 		if ( is_array($fields) && count($fields) > 0 ) {
 			foreach ($fields as $field_name => $field_expression) {
 				$clause = preg_replace('/(\\(+)[(,` ]*' . $field_name . '[` ]{1}/', '\1 (' . $field_expression . ') ', $clause);
 				$clause = preg_replace('/[,` ]{1}' . $field_name . '[` ]{1}/', ' (' . $field_expression . ') ', $clause);
 			}
 		}
 
 		return $replace_table ? str_replace('%1$s', $this->TableName, $clause) : $clause;
 	}
 
 	/**
 	 * Returns WHERE clause of the query
 	 *
 	 * @param bool $for_counting merge where filters with having filters + replace field names for having fields with their values
 	 * @param bool $system_filters_only
 	 * @return string
 	 * @access private
 	 */
 	private function GetWhereClause($for_counting=false,$system_filters_only=false)
 	{
 		$where = $this->Application->makeClass('kMultipleFilter');
 		/* @var $where kMultipleFilter */
 
 		$where->addFilter('system_where', $this->WhereFilter[self::FLT_SYSTEM] );
 
 		if (!$system_filters_only) {
 			$where->addFilter('view_where', $this->WhereFilter[self::FLT_VIEW] );
 			$search_w = $this->WhereFilter[self::FLT_SEARCH]->getSQL();
 
 			if ($search_w || $for_counting) { // move search_having to search_where in case search_where isset or we are counting
 				$search_h = $this->extractCalculatedFields( $this->HavingFilter[self::FLT_SEARCH]->getSQL() );
 				$search_w = ($search_w && $search_h) ? $search_w.' OR '.$search_h : $search_w.$search_h;
 				$where->addFilter('search_where', $search_w );
 			}
 
 			// CUSTOM
 			$search_w = $this->WhereFilter[self::FLT_CUSTOM]->getSQL();
 
 			if ($search_w || $for_counting) { // move search_having to search_where in case search_where isset or we are counting
 				$search_h = $this->extractCalculatedFields( $this->HavingFilter[self::FLT_CUSTOM]->getSQL() );
 				$search_w = ($search_w && $search_h) ? $search_w.' AND '.$search_h : $search_w.$search_h;
 				$where->addFilter('custom_where', $search_w );
 			}
 			// CUSTOM
 		}
 
 		if( $for_counting ) // add system_having and view_having to where
 		{
 			$where->addFilter('system_having', $this->extractCalculatedFields($this->HavingFilter[kDBList::FLT_SYSTEM]->getSQL()) );
 			if (!$system_filters_only) $where->addFilter('view_having', $this->extractCalculatedFields( $this->HavingFilter[kDBList::FLT_VIEW]->getSQL() ) );
 		}
 
 		return $where->getSQL();
 	}
 
 	/**
 	* Returns HAVING clause of the query
 	*
 	* @param bool $for_counting don't return having filter in case if this is counting sql
 	* @param bool $system_filters_only return only system having filters
 	* @param int $aggregated 0 - aggregated and having, 1 - having only, 2 - aggregated only
 	* @return string
 	* @access private
 	*/
 	private function GetHavingClause($for_counting=false, $system_filters_only=false, $aggregated = 0)
 	{
 		if ($for_counting) {
 			$aggregate_filter = $this->Application->makeClass('kMultipleFilter');
 			/* @var $aggregate_filter kMultipleFilter */
 
 			$aggregate_filter->addFilter('aggregate_system', $this->AggregateFilter[kDBList::FLT_SYSTEM]);
 			if (!$system_filters_only) {
 				$aggregate_filter->addFilter('aggregate_view', $this->AggregateFilter[kDBList::FLT_VIEW]);
 			}
 			return $this->extractCalculatedFields($aggregate_filter->getSQL(), 2);
 		}
 
 		$having = $this->Application->makeClass('kMultipleFilter');
 		/* @var $having kMultipleFilter */
 
 		$having->addFilter('system_having', $this->HavingFilter[kDBList::FLT_SYSTEM] );
 		if ($aggregated == 0) {
 			if (!$system_filters_only) {
                 $having->addFilter('view_aggregated', $this->AggregateFilter[kDBList::FLT_VIEW] );
             }
             $having->addFilter('system_aggregated', $this->AggregateFilter[kDBList::FLT_SYSTEM]);
 		}
 
 		if (!$system_filters_only) {
 			$having->addFilter('view_having', $this->HavingFilter[kDBList::FLT_VIEW] );
 			$having->addFilter('custom_having', $this->HavingFilter[kDBList::FLT_CUSTOM] );
 			$search_w = $this->WhereFilter[kDBList::FLT_SEARCH]->getSQL();
 			if (!$search_w) {
 				$having->addFilter('search_having', $this->HavingFilter[kDBList::FLT_SEARCH] );
 			}
 		}
 
 		return $having->getSQL();
 	}
 
 	/**
 	* Returns GROUP BY clause of the query
 	*
 	* @return string
 	* @access protected
 	*/
 	protected function GetGroupClause()
 	{
 		return $this->GroupByFields ? implode(',', $this->GroupByFields) : '';
 	}
 
 	/**
 	 * Adds new group by field
 	 *
 	 * @param string $field
 	 * @access public
 	 */
 	public function AddGroupByField($field)
 	{
 		$this->GroupByFields[$field] = $field;
 	}
 
 	/**
 	 * Removes group by field added before
 	 *
 	 * @param string $field
 	 * @access public
 	 */
 	public function RemoveGroupByField($field)
 	{
 		unset($this->GroupByFields[$field]);
 	}
 
 	/**
 	 * Adds order field to ORDER BY clause
 	 *
 	 * @param string $field Field name
 	 * @param string $direction Direction of ordering (asc|desc)
 	 * @param bool $is_expression this is expression, that should not be escapted by "`" symbols
 	 * @return int
 	 * @access public
 	 */
 	public function AddOrderField($field, $direction = 'asc', $is_expression = false)
 	{
 		// original multilanguage field - convert to current lang field
 		$formatter = isset($this->Fields[$field]['formatter']) ? $this->Fields[$field]['formatter'] : false;
 
 		if ($formatter == 'kMultiLanguage' && !isset($this->Fields[$field]['master_field'])) {
 			// for now kMultiLanguage formatter is only supported for real (non-virtual) fields
 			$is_expression = true;
 			$field = $this->getMLSortField($field);
 		}
 
 		if (!isset($this->Fields[$field]) && $field != 'RAND()' && !$is_expression) {
 			trigger_error('<span class="debug_error">Incorrect sorting</span> defined (field = <b>'.$field.'</b>; direction = <b>'.$direction.'</b>) in config for prefix <b>'.$this->Prefix.'</b>', E_USER_NOTICE);
 		}
 
 		$this->OrderFields[] = Array($field, $direction, $is_expression);
 
 		return count($this->OrderFields) - 1;
 	}
 
 	/**
 	 * Sets new order fields, replacing existing ones
 	 *
 	 * @param Array $order_fields
 	 * @return void
 	 * @access public
 	 */
 	public function setOrderFields($order_fields)
 	{
 		$this->OrderFields = $order_fields;
 	}
 
 	/**
 	 * Changes sorting direction for a given sorting field index
 	 *
 	 * @param int $field_index
 	 * @param string $direction
 	 * @return void
 	 * @access public
 	 */
 	public function changeOrderDirection($field_index, $direction)
 	{
 		if ( !isset($this->OrderFields[$field_index]) ) {
 			return;
 		}
 
 		$this->OrderFields[$field_index][1] = $direction;
 	}
 
 	/**
 	 * Returns expression, used to sort given multilingual field
 	 *
 	 * @param string $field
 	 * @return string
 	 */
 	function getMLSortField($field)
 	{
 		$table_name = '`' . $this->TableName . '`';
 		$lang = $this->Application->GetVar('m_lang');
 		$primary_lang = $this->Application->GetDefaultLanguageId();
 
 		$ret = 'IF(COALESCE(%1$s.l' . $lang . '_' . $field . ', ""), %1$s.l' . $lang . '_' . $field . ', %1$s.l' . $primary_lang . '_' . $field . ')';
 
 		return sprintf($ret, $table_name);
 	}
 
 	/**
 	* Removes all order fields
 	*
 	* @access public
 	*/
 	public function ClearOrderFields()
 	{
 		$this->OrderFields = Array();
 	}
 
 	/**
 	* Returns ORDER BY Clause of the query
 	*
 	*	The method builds order by clause by iterating {@link kDBList::OrderFields} array and concatenating it.
 	*
 	* @return string
 	* @access private
 	*/
 	private function GetOrderClause()
 	{
 		$ret = '';
 		foreach ($this->OrderFields as $field) {
 
 			$name = $field[0];
 			$ret .= isset($this->Fields[$name]) && !isset($this->VirtualFields[$name]) ? '`'.$this->TableName.'`.' : '';
 
 			if ($field[0] == 'RAND()' || $field[2]) {
 				$ret .= $field[0].' '.$field[1].',';
 			}
 			else {
 				$ret .= (strpos($field[0], '.') === false ? '`'.$field[0] . '`' : $field[0]) . ' ' . $field[1] . ',';
 			}
 		}
 		$ret = rtrim($ret, ',');
 		return $ret;
 	}
 
 	/**
 	 * Returns order field name in given position
 	 *
 	 * @param int $pos
 	 * @param bool $no_default
 	 * @return string
 	 * @access public
 	 */
 	public function GetOrderField($pos = NULL, $no_default = false)
 	{
 		if ( !(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) && !$no_default ) {
 			$pos = 0;
 		}
 
 		if ( isset($this->OrderFields[$pos][0]) ) {
 			$field = $this->OrderFields[$pos][0];
 			$lang = $this->Application->GetVar('m_lang');
 
 			if ( preg_match('/^IF\(COALESCE\(.*?\.(l' . $lang . '_.*?), ""\),/', $field, $regs) ) {
 				// undo result of kDBList::getMLSortField method
 				return $regs[1];
 			}
 
 			return $field;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Returns list order fields
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function getOrderFields()
 	{
 		return $this->OrderFields;
 	}
 
 	/**
 	 * Returns order field direction in given position
 	 *
 	 * @param int $pos
 	 * @param bool $no_default
 	 * @return string
 	 * @access public
 	 */
 	public function GetOrderDirection($pos = NULL, $no_default = false)
 	{
 		if ( !(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) && !$no_default ) {
 			$pos = 0;
 		}
 
 		return isset($this->OrderFields[$pos][1]) ? $this->OrderFields[$pos][1] : '';
 	}
 
 	/**
 	 * Returns ID of currently processed record
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function GetID()
 	{
 		return $this->Queried ? $this->GetDBField($this->IDField) : null;
 	}
 
 	/**
 	 * Allows kDBTagProcessor.SectionTitle to detect if it's editing or new item creation
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function IsNewItem()
 	{
 		// no such thing as NewItem for lists :)
 		return false;
 	}
 
 	/**
 	 * Return unformatted field value
 	 *
 	 * @param string $name
 	 * @return string
 	 * @access public
 	 */
 	public function GetDBField($name)
 	{
 		$row =& $this->getCurrentRecord();
 
 		if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Queried && !array_key_exists($name, $row)) {
 			if ( $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->appendTrace();
 			}
 
 			trigger_error('Field "<strong>' . $name . '</strong>" doesn\'t exist in prefix <strong>' . $this->getPrefixSpecial() . '</strong>', E_USER_WARNING);
 			return 'NO SUCH FIELD';
 		}
 
 		// return "null" for missing fields, because formatter require such behaviour !
 		return array_key_exists($name, $row) ? $row[$name] : null;
 	}
 
 	/**
 	 * Checks if requested field is present after database query
 	 *
 	 * @param string $name
 	 * @return bool
 	 * @access public
 	 */
 	public function HasField($name)
 	{
 		$row =& $this->getCurrentRecord();
 		return isset($row[$name]);
 	}
 
 	/**
 	 * Returns current record fields
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function GetFieldValues()
 	{
 		$record =& $this->getCurrentRecord();
 
 		return $record;
 	}
 
 	/**
 	 * Returns current record from list
 	 *
 	 * @param int $offset Offset relative to current record index
 	 * @return Array
 	 * @access public
 	 */
 	public function &getCurrentRecord($offset = 0)
 	{
 		$record_index = $this->CurrentIndex + $offset;
 
 		if ($record_index >=0 && $record_index < $this->SelectedCount) {
 			return $this->Records[$record_index];
 		}
 
 		$false = false;
 		return $false;
 	}
 
 	/**
 	 * Goes to record with given index
 	 *
 	 * @param int $index
 	 * @access public
 	 */
 	public function GoIndex($index)
 	{
 		$this->CurrentIndex = $index;
 	}
 
 	/**
 	* Goes to first record
 	*
 	* @access public
 	*/
 	public function GoFirst()
 	{
 		$this->CurrentIndex = 0;
 	}
 
 	/**
 	* Goes to next record
 	*
 	* @access public
 	*/
 	public function GoNext()
 	{
 		$this->CurrentIndex++;
 	}
 
 	/**
 	* Goes to previous record
 	*
 	* @access public
 	*/
 	public function GoPrev()
 	{
 		if ($this->CurrentIndex>0) {
 			$this->CurrentIndex--;
 		}
 	}
 
 	/**
 	* Checks if there is no end of list
 	*
 	* @return bool
 	* @access public
 	*/
 	public function EOL()
 	{
 		return ($this->CurrentIndex >= $this->SelectedCount);
 	}
 
 	/**
 	 * Returns total page count based on list per-page
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function GetTotalPages()
 	{
 		if ( !$this->Counted ) {
 			$this->CountRecs();
 		}
 
 		if ( $this->PerPage == -1 ) {
 			return 1;
 		}
 
 		$integer_part = ($this->RecordsCount - ($this->RecordsCount % $this->PerPage)) / $this->PerPage;
 		$reminder = ($this->RecordsCount % $this->PerPage) != 0; // adds 1 if there is a reminder
 
 		$this->TotalPages = $integer_part + $reminder;
 
 		return $this->TotalPages;
 	}
 
 	/**
 	* Sets number of records to query per page
 	*
 	* @param int $per_page Number of records to display per page
 	* @access public
 	*/
 	public function SetPerPage($per_page)
 	{
 		$this->PerPage = $per_page;
 	}
 
 	/**
 	 * Returns records per page count
 	 *
 	 * @param bool $in_fact
 	 * @return int
 	 * @access public
 	 */
 	public function GetPerPage($in_fact = false)
 	{
 		if ($in_fact) {
 			return $this->PerPage;
 		}
 
 		return $this->PerPage == -1 ? $this->RecordsCount : $this->PerPage;
 	}
 
 	/**
 	* Sets current page in list
 	*
 	* @param int $page
 	* @access public
 	*/
 	public function SetPage($page)
 	{
 		if ($this->PerPage == -1) {
 			$this->Page = 1;
 			return;
 		}
 		if ($page < 1) $page = 1;
 		$this->Offset = ($page-1)*$this->PerPage;
 		if ($this->Counted && $this->Offset > $this->RecordsCount) {
 			$this->SetPage(1);
 		}
 		else {
 			$this->Page = $page;
 		}
 		//$this->GoFirst();
 	}
 
 	/**
 	 * Returns current list page
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function GetPage()
 	{
 		return $this->Page;
 	}
 
 	/**
 	 * Sets list query offset
 	 *
 	 * @param int $offset
 	 * @access public
 	 */
 	public function SetOffset($offset)
 	{
 		$this->Offset = $offset;
 	}
 
 	/**
 	 * Gets list query offset
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function GetOffset()
 	{
 		return $this->Offset;
 	}
 
 	/**
 	* Sets current item field value (doesn't apply formatting)
 	*
 	* @param string $name Name of the field
 	* @param mixed $value Value to set the field to
 	* @access public
 	*/
 	public function SetDBField($name,$value)
 	{
 		$this->Records[$this->CurrentIndex][$name] = $value;
 	}
 
 	/**
 	 * Apply where clause, that links this object to it's parent item
 	 *
 	 * @param string $special
 	 * @access public
 	 */
 	public function linkToParent($special)
 	{
-		$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
-		if ($parent_prefix) {
-			$parent_table_key = $this->Application->getUnitOption($this->Prefix, 'ParentTableKey');
-			if (is_array($parent_table_key)) $parent_table_key = getArrayValue($parent_table_key, $parent_prefix);
-			$foreign_key_field = $this->Application->getUnitOption($this->Prefix, 'ForeignKey');
-			if (is_array($foreign_key_field)) $foreign_key_field = getArrayValue($foreign_key_field, $parent_prefix);
+		$config = $this->getUnitConfig();
+		$parent_prefix = $config->getParentPrefix();
 
-			if (!$parent_table_key || !$foreign_key_field) {
-				return ;
+		if ( $parent_prefix ) {
+			$parent_table_key = $config->getParentTableKey($parent_prefix);
+			$foreign_key_field = $config->getForeignKey($parent_prefix);
+
+			if ( !$parent_table_key || !$foreign_key_field ) {
+				return;
 			}
 
-			$parent_object = $this->Application->recallObject($parent_prefix.'.'.$special);
+			$parent_object = $this->Application->recallObject($parent_prefix . '.' . $special);
 			/* @var $parent_object kDBItem */
 
-			if (!$parent_object->isLoaded()) {
+			if ( !$parent_object->isLoaded() ) {
 				$this->addFilter('parent_filter', 'FALSE');
-				trigger_error('Parent ID not found (prefix: "<strong>' . rtrim($parent_prefix.'.'.$special, '.') . '</strong>"; sub-prefix: "<strong>' . $this->getPrefixSpecial() . '</strong>")', E_USER_NOTICE);
-				return ;
+				trigger_error('Parent ID not found (prefix: "<strong>' . rtrim($parent_prefix . '.' . $special, '.') . '</strong>"; sub-prefix: "<strong>' . $this->getPrefixSpecial() . '</strong>")', E_USER_NOTICE);
+
+				return;
 			}
 
 			// only for list in this case
 			$parent_id = $parent_object->GetDBField($parent_table_key);
 			$this->addFilter('parent_filter', '`' . $this->TableName . '`.`' . $foreign_key_field . '` = ' . $this->Conn->qstr($parent_id));
 		}
 	}
 
 	/**
 	 * Returns true if list was queried (same name as for kDBItem for easy usage)
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function isLoaded()
 	{
 		return $this->Queried && !$this->EOL();
 	}
 
 	/**
 	 * Returns specified field value from all selected rows.
 	 * Don't affect current record index
 	 *
 	 * @param string $field
 	 * @param bool $formatted
 	 * @param string $format
 	 * @return Array
 	 * @access public
 	 */
 	public function GetCol($field, $formatted = false, $format = null)
 	{
 		$i = 0;
 		$ret = Array ();
 
 		if ($formatted && array_key_exists('formatter', $this->Fields[$field])) {
 			$formatter = $this->Application->recallObject($this->Fields[$field]['formatter']);
 			/* @var $formatter kFormatter */
 
 			while ($i < $this->SelectedCount) {
 				$ret[] = $formatter->Format($this->Records[$i][$field], $field, $this, $format);
 				$i++;
 			}
 		}
 		else {
 			while ($i < $this->SelectedCount) {
 				$ret[] = $this->Records[$i][$field];
 				$i++;
 			}
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Set's field error, if pseudo passed not found then create it with message text supplied.
 	 * Don't overwrite existing pseudo translation.
 	 *
 	 * @param string $field
 	 * @param string $pseudo
 	 * @param string $error_label
 	 * @param Array $error_params
 	 * @return bool
 	 * @access public
 	 * @see kSearchHelper::processRangeField()
 	 * @see kDateFormatter::Parse()
 	 */
 	public function SetError($field, $pseudo, $error_label = null, $error_params = null)
 	{
 		$error_field = isset($this->Fields[$field]['error_field']) ? $this->Fields[$field]['error_field'] : $field;
 		$this->FieldErrors[$error_field]['pseudo'] = $pseudo;
 
 		$var_name = $this->getPrefixSpecial() . '_' . $field . '_error';
 		$previous_pseudo = $this->Application->RecallVar($var_name);
 
 		if ( $previous_pseudo ) {
 			// don't set more then one error on field
 			return false;
 		}
 
 		$this->Application->StoreVar($var_name, $pseudo);
 
 		return true;
 	}
 
 	/**
 	 * Returns error pseudo
 	 *
 	 * @param string $field
 	 * @return string
 	 * @access public
 	 * @see kSearchHelper::processRangeField()
 	 */
 	public function GetErrorPseudo($field)
 	{
 		if ( !isset($this->FieldErrors[$field]) ) {
 			return '';
 		}
 
 		return isset($this->FieldErrors[$field]['pseudo']) ? $this->FieldErrors[$field]['pseudo'] : '';
 	}
 
 	/**
 	 * Removes error on field
 	 *
 	 * @param string $field
 	 * @access public
 	 */
 	public function RemoveError($field)
 	{
 		unset( $this->FieldErrors[$field] );
 	}
 
 	/**
 	 * Group list records by header, saves internal order in group
 	 *
 	 * @param string $heading_field
 	 * @access public
 	 */
 	public function groupRecords($heading_field)
 	{
 		$i = 0;
 		$sorted = Array ();
 
 		while ($i < $this->SelectedCount) {
 			$sorted[ $this->Records[$i][$heading_field] ][] = $this->Records[$i];
 			$i++;
 		}
 
 		$this->Records = Array ();
 
 		foreach ($sorted as $heading => $heading_records) {
 			$this->Records = array_merge_recursive($this->Records, $heading_records);
 		}
 	}
 
 	/**
 	 * Reset list (use for requering purposes)
 	 *
 	 * @access public
 	 */
 	public function reset()
 	{
 		$this->Counted = false;
 		$this->clearFilters();
 		$this->ClearOrderFields();
 	}
 
 	/**
 	 * Checks if list was counted
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function isCounted()
 	{
 		return $this->Counted;
 	}
 
 	/**
 	 * Tells, that given list is main
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function isMainList()
 	{
 		return $this->mainList;
 	}
 
 	/**
 	 * Makes given list as main
 	 *
 	 * @access public
 	 */
 	public function becameMain()
 	{
 		$this->mainList = true;
 	}
 
 	/**
 	 * Moves recordset pointer to first element
 	 *
 	 * @return void
 	 * @access public
 	 * @implements Iterator::rewind
 	 */
 	public function rewind()
 	{
 		$this->Query();
 		$this->GoFirst();
 	}
 
 	/**
 	 * Returns value at current position
 	 *
 	 * @return mixed
 	 * @access public
 	 * @implements Iterator::current
 	 */
 	function current()
 	{
 		return $this->getCurrentRecord();
 	}
 
 	/**
 	 * Returns key at current position
 	 *
 	 * @return mixed
 	 * @access public
 	 * @implements Iterator::key
 	 */
 	function key()
 	{
 		return $this->CurrentIndex;
 	}
 
 	/**
 	 * Moves recordset pointer to next position
 	 *
 	 * @return void
 	 * @access public
 	 * @implements Iterator::next
 	 */
 	function next()
 	{
 		$this->GoNext();
 	}
 
 	/**
 	 * Detects if current position is within recordset bounds
 	 *
 	 * @return bool
 	 * @access public
 	 * @implements Iterator::valid
 	 */
 	public function valid()
 	{
 		return !$this->EOL();
 	}
 
 	/**
 	 * Counts recordset rows
 	 *
 	 * @return int
 	 * @access public
 	 * @implements Countable::count
 	 */
 	public function count()
 	{
 		return $this->SelectedCount;
 	}
 }
 
 
 class LeftJoinOptimizer {
 
 	/**
 	 * Input sql for optimization
 	 *
 	 * @var string
 	 * @access private
 	 */
 	private $sql = '';
 
 	/**
 	 * All sql parts, where LEFT JOINed table aliases could be used
 	 *
 	 * @var string
 	 * @access private
 	 */
 	private $usageString = '';
 
 	/**
 	 * List of discovered LEFT JOINs
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	private $joins = Array ();
 
 	/**
 	 * LEFT JOIN relations
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	private $joinRelations = Array ();
 
 	/**
 	 * LEFT JOIN table aliases scheduled for removal
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	private $aliasesToRemove = Array ();
 
 	/**
 	 * Creates new instance of the class
 	 *
 	 * @param string $sql
 	 * @param string $usage_string
 	 */
 	public function __construct($sql, $usage_string)
 	{
 		$this->sql = $sql;
 		$this->usageString = $usage_string;
 
 		$this->parseJoins();
 	}
 
 	/**
 	 * Tries to remove unused LEFT JOINs
 	 *
 	 * @return string
 	 * @access public
 	 */
 	public function simplify()
 	{
 		if ( !$this->joins ) {
 			// no LEFT JOIN used, return unchanged sql
 			return $this->sql;
 		}
 
 		$this->updateRelations();
 		$this->removeAliases();
 
 		return $this->sql;
 	}
 
 	/**
 	 * Discovers LEFT JOINs based on given sql
 	 *
 	 * @return void
 	 * @access private
 	 */
 	private function parseJoins()
 	{
 		if ( !preg_match_all('/LEFT\s+JOIN\s+(.*?|.*?\s+AS\s+.*?|.*?\s+.*?)\s+ON\s+(.*?\n|.*?$)/i', $this->sql, $regs) ) {
 			$this->joins = Array ();
 		}
 
 		// get all LEFT JOIN clause info from sql (without filters)
 		foreach ($regs[1] as $index => $match) {
 			$match_parts = preg_split('/\s+AS\s+|\s+/i', $match, 2);
 			$table_alias = count($match_parts) == 1 ? $match : $match_parts[1];
 
 			$this->joins[$table_alias] = Array (
 				'table' => $match_parts[0],
 				'join_clause' => $regs[0][$index],
 			);
 		}
 	}
 
 	/**
 	 * Detects relations between LEFT JOINs
 	 *
 	 * @return void
 	 * @access private
 	 */
 	private function updateRelations()
 	{
 		foreach ($this->joins as $table_alias => $left_join_info) {
 			$escaped_alias = preg_quote($table_alias, '/');
 
 			foreach ($this->joins as $sub_table_alias => $sub_left_join_info) {
 				if ($table_alias == $sub_table_alias) {
 					continue;
 				}
 
 				if ( $this->matchAlias($escaped_alias, $sub_left_join_info['join_clause']) ) {
 					$this->joinRelations[] = $sub_table_alias . ':' . $table_alias;
 				}
 			}
 		}
 	}
 
 	/**
 	 * Removes scheduled LEFT JOINs, but only if they are not protected
 	 *
 	 * @return void
 	 * @access private
 	 */
 	private function removeAliases()
 	{
 		$this->prepareAliasesRemoval();
 
 		foreach ($this->aliasesToRemove as $to_remove_alias) {
 			if ( !$this->aliasProtected($to_remove_alias) ) {
 				$this->sql = str_replace($this->joins[$to_remove_alias]['join_clause'], '', $this->sql);
 			}
 		}
 	}
 
 	/**
 	 * Schedules unused LEFT JOINs to for removal
 	 *
 	 * @return void
 	 * @access private
 	 */
 	private function prepareAliasesRemoval()
 	{
 		foreach ($this->joins as $table_alias => $left_join_info) {
 			$escaped_alias = preg_quote($table_alias, '/');
 
 			if ( !$this->matchAlias($escaped_alias, $this->usageString) ) {
 				$this->aliasesToRemove[] = $table_alias;
 			}
 		}
 	}
 
 	/**
 	 * Checks if someone wants to remove LEFT JOIN, but it's used by some other LEFT JOIN, that stays
 	 *
 	 * @param string $table_alias
 	 * @return bool
 	 * @access private
 	 */
 	private function aliasProtected($table_alias)
 	{
 		foreach ($this->joinRelations as $relation) {
 			list ($main_alias, $used_alias) = explode(':', $relation);
 
 			if ( ($used_alias == $table_alias) && !in_array($main_alias, $this->aliasesToRemove) ) {
 				return true;
 			}
 		}
 
 		return false;
 	}
 
 	/**
 	 * Matches given escaped alias to a string
 	 *
 	 * @param string $escaped_alias
 	 * @param string $string
 	 * @return bool
 	 * @access private
 	 */
 	private function matchAlias($escaped_alias, $string)
 	{
 		return preg_match('/(`' . $escaped_alias . '`|' . $escaped_alias . ')\./', $string);
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/db/db_event_handler.php
===================================================================
--- branches/5.3.x/core/kernel/db/db_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/kernel/db/db_event_handler.php	(revision 15698)
@@ -1,3558 +1,3575 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	define('EH_CUSTOM_PROCESSING_BEFORE',1);
 	define('EH_CUSTOM_PROCESSING_AFTER',2);
 
 	/**
 	 * Note:
 	 *   1. When addressing variables from submit containing
 	 *	 	Prefix_Special as part of their name use
 	 *	 	$event->getPrefixSpecial(true) instead of
 	 *	 	$event->getPrefixSpecial() as usual. This is due PHP
 	 *	 	is converting "." symbols in variable names during
 	 *	 	submit info "_". $event->getPrefixSpecial optional
 	 *	 	1st parameter returns correct current Prefix_Special
 	 *	 	for variables being submitted such way (e.g. variable
 	 *	 	name that will be converted by PHP: "users.read_only_id"
 	 *	 	will be submitted as "users_read_only_id".
 	 *
 	 *	 2.	When using $this->Application-LinkVar on variables submitted
 	 *		from form which contain $Prefix_Special then note 1st item. Example:
 	 *		LinkVar($event->getPrefixSpecial(true).'_varname',$event->getPrefixSpecial().'_varname')
 	 *
 	 */
 
 	/**
 	 * EventHandler that is used to process
 	 * any database related events
 	 *
 	 */
 	class kDBEventHandler extends kEventHandler {
 
 		/**
 		 * Checks permissions of user
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			$section = $event->getSection();
 
 			if ( !$this->Application->isAdmin ) {
 				$allow_events = Array ('OnSearch', 'OnSearchReset', 'OnNew');
 				if ( in_array($event->Name, $allow_events) ) {
 					// allow search on front
 					return true;
 				}
 			}
 			elseif ( ($event->Name == 'OnPreSaveAndChangeLanguage') && !$this->UseTempTables($event) ) {
 				// allow changing language in grids, when not in editing mode
 				return $this->Application->CheckPermission($section . '.view', 1);
 			}
 
 			if ( !preg_match('/^CATEGORY:(.*)/', $section) ) {
 				// only if not category item events
 				if ( (substr($event->Name, 0, 9) == 'OnPreSave') || ($event->Name == 'OnSave') ) {
 					if ( $this->isNewItemCreate($event) ) {
 						return $this->Application->CheckPermission($section . '.add', 1);
 					}
 					else {
 						return $this->Application->CheckPermission($section . '.add', 1) || $this->Application->CheckPermission($section . '.edit', 1);
 					}
 				}
 			}
 
 			if ( $event->Name == 'OnPreCreate' ) {
 				// save category_id before item create (for item category selector not to destroy permission checking category)
 				$this->Application->LinkVar('m_cat_id');
 			}
 
 			if ( $event->Name == 'OnSaveWidths' ) {
 				return $this->Application->isAdminUser;
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnLoad' => Array ('self' => 'view', 'subitem' => 'view'),
 				'OnItemBuild' => Array ('self' => 'view', 'subitem' => 'view'),
 				'OnSuggestValues' => Array ('self' => 'view', 'subitem' => 'view'),
 
 				'OnBuild' => Array ('self' => true),
 
 				'OnNew' => Array ('self' => 'add', 'subitem' => 'add|edit'),
 				'OnCreate' => Array ('self' => 'add', 'subitem' => 'add|edit'),
 				'OnUpdate' => Array ('self' => 'edit', 'subitem' => 'add|edit'),
 				'OnSetPrimary' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
 				'OnDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
 				'OnDeleteAll' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
 				'OnMassDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
 				'OnMassClone' => Array ('self' => 'add', 'subitem' => 'add|edit'),
 
 				'OnCut' => Array ('self'=>'edit', 'subitem' => 'edit'),
 				'OnCopy' => Array ('self'=>'edit', 'subitem' => 'edit'),
 				'OnPaste' => Array ('self'=>'edit', 'subitem' => 'edit'),
 
 				'OnSelectItems' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
 				'OnProcessSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
 				'OnStoreSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
 				'OnSelectUser' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
 
 				'OnMassApprove' => Array ('self' => 'advanced:approve|edit', 'subitem' => 'advanced:approve|add|edit'),
 				'OnMassDecline' => Array ('self' => 'advanced:decline|edit', 'subitem' => 'advanced:decline|add|edit'),
 				'OnMassMoveUp' => Array ('self' => 'advanced:move_up|edit', 'subitem' => 'advanced:move_up|add|edit'),
 				'OnMassMoveDown' => Array ('self' => 'advanced:move_down|edit', 'subitem' => 'advanced:move_down|add|edit'),
 
 				'OnPreCreate' => Array ('self' => 'add|add.pending', 'subitem' => 'edit|edit.pending'),
 				'OnEdit' => Array ('self' => 'edit|edit.pending', 'subitem' => 'edit|edit.pending'),
 
 				'OnExport' => Array ('self' => 'view|advanced:export'),
 				'OnExportBegin' => Array ('self' => 'view|advanced:export'),
 				'OnExportProgress' => Array ('self' => 'view|advanced:export'),
 
 				'OnSetAutoRefreshInterval' => Array ('self' => true, 'subitem' => true),
 				'OnAutoRefreshToggle' => Array ('self' => true, 'subitem' => true),
 
 				// theese event do not harm, but just in case check them too :)
 				'OnCancelEdit' => Array ('self' => true, 'subitem' => true),
 				'OnCancel' => Array ('self' => true, 'subitem' => true),
 				'OnReset' => Array ('self' => true, 'subitem' => true),
 
 				'OnSetSorting' => Array ('self' => true, 'subitem' => true),
 				'OnSetSortingDirect' => Array ('self' => true, 'subitem' => true),
 				'OnResetSorting' => Array ('self' => true, 'subitem' => true),
 
 				'OnSetFilter' => Array ('self' => true, 'subitem' => true),
 				'OnApplyFilters' => Array ('self' => true, 'subitem' => true),
 				'OnRemoveFilters' => Array ('self' => true, 'subitem' => true),
 				'OnSetFilterPattern' => Array ('self' => true, 'subitem' => true),
 
 				'OnSetPerPage' => Array ('self' => true, 'subitem' => true),
 				'OnSetPage' => Array ('self' => true, 'subitem' => true),
 
 				'OnSearch' => Array ('self' => true, 'subitem' => true),
 				'OnSearchReset' => Array ('self' => true, 'subitem' => true),
 
 				'OnGoBack' => Array ('self' => true, 'subitem' => true),
 
 				// it checks permission itself since flash uploader does not send cookies
 				'OnUploadFile' => Array ('self' => true, 'subitem' => true),
 				'OnDeleteFile' => Array ('self' => true, 'subitem' => true),
 
 				'OnViewFile' => Array ('self' => true, 'subitem' => true),
 				'OnSaveWidths' => Array ('self' => true, 'subitem' => true),
 
 				'OnValidateMInputFields' => Array ('self' => 'view'),
 				'OnValidateField' => Array ('self' => true, 'subitem' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Define alternative event processing method names
 		 *
 		 * @return void
 		 * @see kEventHandler::$eventMethods
 		 * @access protected
 		 */
 		protected function mapEvents()
 		{
 			$events_map = Array (
 				'OnRemoveFilters' => 'FilterAction',
 				'OnApplyFilters' => 'FilterAction',
 				'OnMassApprove' => 'iterateItems',
 				'OnMassDecline' => 'iterateItems',
 				'OnMassMoveUp' => 'iterateItems',
 				'OnMassMoveDown' => 'iterateItems',
 			);
 
 			$this->eventMethods = array_merge($this->eventMethods, $events_map);
 		}
 
 		/**
 		 * Returns ID of current item to be edited
 		 * by checking ID passed in get/post as prefix_id
 		 * or by looking at first from selected ids, stored.
 		 * Returned id is also stored in Session in case
 		 * it was explicitly passed as get/post
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access public
 		 */
 		public function getPassedID(kEvent $event)
 		{
 			if ( $event->getEventParam('raise_warnings') === false ) {
 				$event->setEventParam('raise_warnings', 1);
 			}
 
 			if ( $event->Special == 'previous' || $event->Special == 'next' ) {
 				$object = $this->Application->recallObject($event->getEventParam('item'));
 				/* @var $object kDBItem */
 
 				$list_helper = $this->Application->recallObject('ListHelper');
 				/* @var $list_helper ListHelper */
 
-				$select_clause = $this->Application->getUnitOption($object->Prefix, 'NavigationSelectClause', NULL);
+				$select_clause = $object->getUnitConfig()->getNavigationSelectClause(NULL);
 
 				return $list_helper->getNavigationResource($object, $event->getEventParam('list'), $event->Special == 'next', $select_clause);
 			}
 			elseif ( $event->Special == 'filter' ) {
 				// temporary object, used to print filter options only
 				return 0;
 			}
 
 			if ( preg_match('/^auto-(.*)/', $event->Special, $regs) && $this->Application->prefixRegistred($regs[1]) ) {
 				// <inp2:lang.auto-phrase_Field name="DateFormat"/> - returns field DateFormat value from language (LanguageId is extracted from current phrase object)
 				$main_object = $this->Application->recallObject($regs[1]);
 				/* @var $main_object kDBItem */
 
-				$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-				return $main_object->GetDBField($id_field);
+				return $main_object->GetDBField($event->getUnitConfig()->getIDField());
 			}
 
 			// 1. get id from post (used in admin)
 			$ret = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id');
 			if ( ($ret !== false) && ($ret != '') ) {
 				return $ret;
 			}
 
 			// 2. get id from env (used in front)
 			$ret = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
 			if ( ($ret !== false) && ($ret != '') ) {
 				return $ret;
 			}
 
 			// recall selected ids array and use the first one
 			$ids = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids');
 			if ( $ids != '' ) {
 				$ids = explode(',', $ids);
 				if ( $ids ) {
 					$ret = array_shift($ids);
 				}
 			}
 			else { // if selected ids are not yet stored
 				$this->StoreSelectedIDs($event);
 				return $this->Application->GetVar($event->getPrefixSpecial() . '_id'); // StoreSelectedIDs sets this variable
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Prepares and stores selected_ids string
 		 * in Session and Application Variables
 		 * by getting all checked ids from grid plus
 		 * id passed in get/post as prefix_id
 		 *
 		 * @param kEvent $event
 		 * @param Array $direct_ids
 		 * @return Array
 		 * @access protected
 		 */
 		protected function StoreSelectedIDs(kEvent $event, $direct_ids = NULL)
 		{
 			$wid = $this->Application->GetTopmostWid($event->Prefix);
 			$session_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_');
 
 			$ids = $event->getEventParam('ids');
 			if ( isset($direct_ids) || ($ids !== false) ) {
 				// save ids directly if they given + reset array indexes
 				$resulting_ids = $direct_ids ? array_values($direct_ids) : ($ids ? array_values($ids) : false);
 				if ( $resulting_ids ) {
 					$this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $resulting_ids));
 					$this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', true);
 					$this->Application->SetVar($event->getPrefixSpecial() . '_id', $resulting_ids[0]);
 
 					return $resulting_ids;
 				}
 
 				return Array ();
 			}
 
 			$ret = Array ();
 
 			// May be we don't need this part: ?
 			$passed = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id');
 			if ( $passed !== false && $passed != '' ) {
 				array_push($ret, $passed);
 			}
 
 			$ids = Array ();
 
 			// get selected ids from post & save them to session
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
+
 			if ( $items_info ) {
-				$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
+				$id_field = $event->getUnitConfig()->getIDField();
+
 				foreach ($items_info as $id => $field_values) {
 					if ( getArrayValue($field_values, $id_field) ) {
 						array_push($ids, $id);
 					}
 				}
 				//$ids = array_keys($items_info);
 			}
 
 			$ret = array_unique(array_merge($ret, $ids));
 
 			$this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $ret));
 			$this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', !$ret); // optional when IDs are missing
 
 			// This is critical - otherwise getPassedID will return last ID stored in session! (not exactly true)
 			// this smells... needs to be refactored
 			$first_id = getArrayValue($ret, 0);
 			if ( ($first_id === false) && ($event->getEventParam('raise_warnings') == 1) ) {
 				if ( $this->Application->isDebugMode() ) {
 					$this->Application->Debugger->appendTrace();
 				}
 
 				trigger_error('Requested ID for prefix <strong>' . $event->getPrefixSpecial() . '</strong> <span class="debug_error">not passed</span>', E_USER_NOTICE);
 			}
 
 			$this->Application->SetVar($event->getPrefixSpecial() . '_id', $first_id);
 			return $ret;
 		}
 
 		/**
 		 * Returns stored selected ids as an array
 		 *
 		 * @param kEvent $event
 		 * @param bool $from_session return ids from session (written, when editing was started)
 		 * @return Array
 		 * @access protected
 		 */
 		protected function getSelectedIDs(kEvent $event, $from_session = false)
 		{
 			if ( $from_session ) {
 				$wid = $this->Application->GetTopmostWid($event->Prefix);
 				$var_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_');
 				$ret = $this->Application->RecallVar($var_name);
 			}
 			else {
 				$ret = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids');
 			}
 
 			return explode(',', $ret);
 		}
 
 		/**
 		 * Stores IDs, selected in grid in session
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnStoreSelected(kEvent $event)
 		{
 			$this->StoreSelectedIDs($event);
 
 			$id = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
 
 			if ( $id !== false ) {
 				$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id);
 				$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
 			}
 		}
 
 		/**
 		 * Returns associative array of submitted fields for current item
 		 * Could be used while creating/editing single item -
 		 * meaning on any edit form, except grid edit
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 * @access protected
 		 */
 		protected function getSubmittedFields(kEvent $event)
 		{
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 			$field_values = $items_info ? array_shift($items_info) : Array ();
 
 			return $field_values;
 		}
 
 		/**
 		 * Returns fields, that are not allowed to be changed from request
 		 *
 		 * @param Array $hash
 		 * @return Array
 		 * @access protected
 		 */
 		protected function getRequestProtectedFields($hash)
 		{
 			// by default don't allow changing ID or foreign key from request
+			$config = $this->getUnitConfig();
+
 			$fields = Array ();
-			$fields[] = $this->Application->getUnitOption($this->Prefix, 'IDField');
+			$fields[] = $config->getIDField();
 
-			$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
+			$parent_prefix = $config->getParentPrefix();
 
 			if ( $parent_prefix && !$this->Application->isAdmin ) {
-				$foreign_key = $this->Application->getUnitOption($this->Prefix, 'ForeignKey');
-				$fields[] = is_array($foreign_key) ? $foreign_key[$parent_prefix] : $foreign_key;
+				$fields[] = $config->getForeignKey($parent_prefix);
 			}
 
 			return $fields;
 		}
 
 		/**
 		 * Removes any information about current/selected ids
 		 * from Application variables and Session
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function clearSelectedIDs(kEvent $event)
 		{
 			$prefix_special = $event->getPrefixSpecial();
 
 			$ids = implode(',', $this->getSelectedIDs($event, true));
 			$event->setEventParam('ids', $ids);
 
 			$wid = $this->Application->GetTopmostWid($event->Prefix);
 			$session_name = rtrim($prefix_special . '_selected_ids_' . $wid, '_');
 
 			$this->Application->RemoveVar($session_name);
 			$this->Application->SetVar($prefix_special . '_selected_ids', '');
 
 			$this->Application->SetVar($prefix_special . '_id', ''); // $event->getPrefixSpecial(true) . '_id' too may be
 		}
 
 		/**
 		 * Common builder part for Item & List
 		 *
 		 * @param kDBBase|kDBItem|kDBList $object
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function dbBuild(&$object, kEvent $event)
 		{
 			// for permission checking inside item/list build events
 			$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
 
 			if ( $event->getEventParam('form_name') !== false ) {
 				$form_name = $event->getEventParam('form_name');
 			}
 			else {
 				$request_forms = $this->Application->GetVar('forms', Array ());
 				$form_name = (string)getArrayValue($request_forms, $object->getPrefixSpecial());
 			}
 
-			$object->Configure($event->getEventParam('populate_ml_fields') || $this->Application->getUnitOption($event->Prefix, 'PopulateMlFields'), $form_name);
+			$object->Configure($event->getEventParam('populate_ml_fields') || $event->getUnitConfig()->getPopulateMlFields(), $form_name);
 			$this->PrepareObject($object, $event);
 
 			$parent_event = $event->getEventParam('parent_event');
 
 			if ( is_object($parent_event) ) {
 				$object->setParentEvent($parent_event);
 			}
 
 			// force live table if specified or is original item
 			$live_table = $event->getEventParam('live_table') || $event->Special == 'original';
 
 			if ( $this->UseTempTables($event) && !$live_table ) {
 				$object->SwitchToTemp();
 			}
 
 			$this->Application->setEvent($event->getPrefixSpecial(), '');
 
 			$save_event = $this->UseTempTables($event) && $this->Application->GetTopmostPrefix($event->Prefix) == $event->Prefix ? 'OnSave' : 'OnUpdate';
 			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', $save_event);
 		}
 
 		/**
 		 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access protected
 		 */
 		protected function checkItemStatus(kEvent $event)
 		{
-			$status_fields = $this->Application->getUnitOption($event->Prefix, 'StatusField');
-			if ( !$status_fields ) {
+			$status_field = $event->getUnitConfig()->getStatusField(true);
+
+			if ( !$status_field ) {
 				return true;
 			}
 
-			$status_field = array_shift($status_fields);
-
 			if ( $status_field == 'Status' || $status_field == 'Enabled' ) {
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				if ( !$object->isLoaded() ) {
 					return true;
 				}
 
 				return $object->GetDBField($status_field) == STATUS_ACTIVE;
 			}
 
 			return true;
 		}
 
 		/**
 		 * Shows not found template content
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function _errorNotFound(kEvent $event)
 		{
 			if ( $event->getEventParam('raise_warnings') === 0 ) {
 				// when it's possible, that autoload fails do nothing
 				return;
 			}
 
 			if ( $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->appendTrace();
 			}
 
 			trigger_error('ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in <strong>checkItemStatus</strong>, leading to "404 Not Found"', E_USER_NOTICE);
 
 			$vars = $this->Application->UrlManager->prepare404();
 
 			foreach ($vars as $var_name => $var_value) {
 				$this->Application->SetVar($var_name, $var_value);
 			}
 
 			// in case if missing item is recalled first from event (not from template)
 			$this->Application->QuickRun();
 			$this->Application->Done();
 			exit;
 		}
 
 		/**
 		 * Builds item (loads if needed)
 		 *
 		 * Pattern: Prototype Manager
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function OnItemBuild(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$this->dbBuild($object, $event);
 
 			$sql = $this->ItemPrepareQuery($event);
 			$sql = $this->Application->ReplaceLanguageTags($sql);
 			$object->setSelectSQL($sql);
 
 			// 2. loads if allowed
-			$auto_load = $this->Application->getUnitOption($event->Prefix,'AutoLoad');
+			$auto_load = $event->getUnitConfig()->getAutoLoad();
 			$skip_autoload = $event->getEventParam('skip_autoload');
 
 			if ( $auto_load && !$skip_autoload ) {
 				$perm_status = true;
 				$user_id = $this->Application->InitDone ? $this->Application->RecallVar('user_id') : USER_ROOT;
 				$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
 				$status_checked = false;
 
 				if ( $user_id == USER_ROOT || $this->CheckPermission($event) ) {
 					// don't autoload item, when user doesn't have view permission
 					$this->LoadItem($event);
 
 					$status_checked = true;
 					$editing_mode = defined('EDITING_MODE') ? EDITING_MODE : false;
 
 					if ( $user_id != USER_ROOT && !$this->Application->isAdmin && !($editing_mode || $this->checkItemStatus($event)) ) {
 						// non-root user AND on front-end AND (not editing mode || incorrect status)
 						$perm_status = false;
 					}
 				}
 				else {
 					$perm_status = false;
 				}
 
 				if ( !$perm_status ) {
 					// when no permission to view item -> redirect to no permission template
 					$this->_processItemLoadingError($event, $status_checked);
 				}
 			}
 
 			$actions = $this->Application->recallObject('kActions');
 			/* @var $actions Params */
 
 			$actions->Set($event->getPrefixSpecial() . '_GoTab', '');
 			$actions->Set($event->getPrefixSpecial() . '_GoId', '');
 			$actions->Set('forms[' . $event->getPrefixSpecial() . ']', $object->getFormName());
 		}
 
 		/**
 		 * Processes case, when item wasn't loaded because of lack of permissions
 		 *
 		 * @param kEvent $event
 		 * @param bool $status_checked
 		 * @throws kNoPermissionException
 		 * @return void
 		 * @access protected
 		 */
 		protected function _processItemLoadingError($event, $status_checked)
 		{
 			$current_template = $this->Application->GetVar('t');
 			$redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate');
 			$error_msg = 'ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in <strong>' . ($status_checked ? 'checkItemStatus' : 'CheckPermission') . '</strong>';
 
 			if ( $current_template == $redirect_template ) {
 				// don't perform "no_permission" redirect if already on a "no_permission" template
 				if ( $this->Application->isDebugMode() ) {
 					$this->Application->Debugger->appendTrace();
 				}
 
 				trigger_error($error_msg, E_USER_NOTICE);
 
 				return;
 			}
 
 			if ( MOD_REWRITE ) {
 				$redirect_params = Array (
 					'm_cat_id' => 0,
 					'next_template' => urlencode('external:' . $_SERVER['REQUEST_URI']),
 				);
 			}
 			else {
 				$redirect_params = Array (
 					'next_template' => $current_template,
 				);
 			}
 
 			$exception = new kNoPermissionException($error_msg);
 			$exception->setup($redirect_template, $redirect_params);
 
 			throw $exception;
 		}
 
 		/**
 		 * Build sub-tables array from configs
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnTempHandlerBuild(kEvent $event)
 		{
 			$object = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 			/* @var $object kTempTablesHandler */
 
 			$parent_event = $event->getEventParam('parent_event');
 
 			if ( is_object($parent_event) ) {
 				$object->setParentEvent($parent_event);
 			}
 
 			$object->BuildTables($event->Prefix, $this->getSelectedIDs($event));
 		}
 
 		/**
 		 * Checks, that object used in event should use temp tables
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access protected
 		 */
 		protected function UseTempTables(kEvent $event)
 		{
 			$top_prefix = $this->Application->GetTopmostPrefix($event->Prefix); // passed parent, not always actual
 			$special = ($top_prefix == $event->Prefix) ? $event->Special : $this->getMainSpecial($event);
 
 			return $this->Application->IsTempMode($event->Prefix, $special);
 		}
 
 		/**
 		 * Load item if id is available
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function LoadItem(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$id = $this->getPassedID($event);
 
 			if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) {
 				// object is already loaded by same id
 				return ;
 			}
 
 			if ( $object->Load($id) ) {
 				$actions = $this->Application->recallObject('kActions');
 				/* @var $actions Params */
 
 				$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
 			}
 			else {
 				$object->setID( is_array($id) ? false : $id );
 			}
 		}
 
 		/**
 		 * Builds list
 		 *
 		 * Pattern: Prototype Manager
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function OnListBuild(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			/*if ( $this->Application->isDebugMode() ) {
 				$event_params = http_build_query($event->getEventParams());
 				$this->Application->Debugger->appendHTML('InitList "<strong>' . $event->getPrefixSpecial() . '</strong>" (' . $event_params . ')');
 			}*/
 
 			$this->dbBuild($object, $event);
 
 			if ( !$object->isMainList() && $event->getEventParam('main_list') ) {
 				// once list is set to main, then even "requery" parameter can't remove that
 				/*$passed = $this->Application->GetVar('passed');
 				$this->Application->SetVar('passed', $passed . ',' . $event->Prefix);*/
 
 				$object->becameMain();
 			}
 
 			$object->setGridName($event->getEventParam('grid'));
 
 			$sql = $this->ListPrepareQuery($event);
 			$sql = $this->Application->ReplaceLanguageTags($sql);
 			$object->setSelectSQL($sql);
 
 			$object->reset();
 
 			if ( $event->getEventParam('skip_parent_filter') === false ) {
 				$object->linkToParent($this->getMainSpecial($event));
 			}
 
 			$this->AddFilters($event);
 			$this->SetCustomQuery($event); // new!, use this for dynamic queries based on specials for ex.
 			$this->SetPagination($event);
 			$this->SetSorting($event);
 
 			$actions = $this->Application->recallObject('kActions');
 			/* @var $actions Params */
 
 			$actions->Set('remove_specials[' . $event->getPrefixSpecial() . ']', '0');
 			$actions->Set($event->getPrefixSpecial() . '_GoTab', '');
 		}
 
 		/**
 		 * Returns special of main item for linking with sub-item
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function getMainSpecial(kEvent $event)
 		{
 			$main_special = $event->getEventParam('main_special');
 
 			if ( $main_special === false ) {
 				// main item's special not passed
 
 				if ( substr($event->Special, -5) == '-item' ) {
 					// temp handler added "-item" to given special -> process that here
 					return substr($event->Special, 0, -5);
 				}
 
 				// by default subitem's special is used for main item searching
 				return $event->Special;
 			}
 
 			return $main_special;
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Set's new per-page for grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetPerPage(kEvent $event)
 		{
 			$per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage');
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_PerPage', $per_page);
 			$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
 
 			if ( !$this->Application->isAdminUser ) {
 				$list_helper = $this->Application->recallObject('ListHelper');
 				/* @var $list_helper ListHelper */
 
 				$this->_passListParams($event, 'per_page');
 			}
 		}
 
 		/**
 		 * Occurs when page is changed (only for hooking)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetPage(kEvent $event)
 		{
 			$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_Page', $page);
 			$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
 
 			if ( !$this->Application->isAdminUser ) {
 				$this->_passListParams($event, 'page');
 			}
 		}
 
 		/**
 		 * Passes through main list pagination and sorting
 		 *
 		 * @param kEvent $event
 		 * @param string $skip_var
 		 * @return void
 		 * @access protected
 		 */
 		protected function _passListParams($event, $skip_var)
 		{
 			$param_names = array_diff(Array ('page', 'per_page', 'sort_by'), Array ($skip_var));
 
 			$list_helper = $this->Application->recallObject('ListHelper');
 			/* @var $list_helper ListHelper */
 
 			foreach ($param_names as $param_name) {
 				$value = $this->Application->GetVar($param_name);
 
 				switch ($param_name) {
 					case 'page':
 						if ( $value > 1 ) {
 							$event->SetRedirectParam('page', $value);
 						}
 						break;
 
 					case 'per_page':
 						if ( $value > 0 ) {
 							if ( $value != $list_helper->getDefaultPerPage($event->Prefix) ) {
 								$event->SetRedirectParam('per_page', $value);
 							}
 						}
 						break;
 
 					case 'sort_by':
 						$event->setPseudoClass('_List');
 						$object = $event->getObject(Array ('main_list' => 1));
 						/* @var $object kDBList */
 
 						if ( $list_helper->hasUserSorting($object) ) {
 							$event->SetRedirectParam('sort_by', $value);
 						}
 						break;
 				}
 			}
 		}
 
 		/**
 		 * Set's correct page for list based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetPagination(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			// get PerPage (forced -> session -> config -> 10)
 			$object->SetPerPage($this->getPerPage($event));
 
 			// main lists on Front-End have special get parameter for page
 			$page = $object->isMainList() ? $this->Application->GetVar('page') : false;
 
 			if ( !$page ) {
 				// page is given in "env" variable for given prefix
 				$page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page');
 			}
 
 			if ( !$page && $event->Special ) {
 				// when not part of env, then variables like "prefix.special_Page" are
 				// replaced (by PHP) with "prefix_special_Page", so check for that too
 				$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
 			}
 
 			if ( !$object->isMainList() ) {
 				// main lists doesn't use session for page storing
 				$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
 
 				if ( $page ) {
 					// page found in request -> store in session
 					$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
 				}
 				else {
 					// page not found in request -> get from session
 					$page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page');
 				}
 
 				if ( !$event->getEventParam('skip_counting') ) {
 					// when stored page is larger, then maximal list page number
 					// (such case is also processed in kDBList::Query method)
 					$pages = $object->GetTotalPages();
 
 					if ( $page > $pages ) {
 						$page = 1;
 						$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true);
 					}
 				}
 			}
 
 			$object->SetPage($page);
 		}
 
 		/**
 		 * Returns current per-page setting for list
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access protected
 		 */
 		protected function getPerPage(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$per_page = $event->getEventParam('per_page');
 
 			if ( $per_page ) {
 				// per-page is passed as tag parameter to PrintList, InitList, etc.
-				$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
+				$config_mapping = $event->getUnitConfig()->getConfigMapping();
 
 				// 2. per-page setting is stored in configuration variable
 				if ( $config_mapping ) {
 					// such pseudo per-pages are only defined in templates directly
 					switch ($per_page) {
 						case 'short_list':
 							$per_page = $this->Application->ConfigValue($config_mapping['ShortListPerPage']);
 							break;
 
 						case 'default':
 							$per_page = $this->Application->ConfigValue($config_mapping['PerPage']);
 							break;
 					}
 				}
 
 				return $per_page;
 			}
 
 			if ( !$per_page && $object->isMainList() ) {
 				// main lists on Front-End have special get parameter for per-page
 				$per_page = $this->Application->GetVar('per_page');
 			}
 
 			if ( !$per_page ) {
 				// per-page is given in "env" variable for given prefix
 				$per_page = $this->Application->GetVar($event->getPrefixSpecial() . '_PerPage');
 			}
 
 			if ( !$per_page && $event->Special ) {
 				// when not part of env, then variables like "prefix.special_PerPage" are
 				// replaced (by PHP) with "prefix_special_PerPage", so check for that too
 				$per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage');
 			}
 
 			if ( !$object->isMainList() ) {
 				// per-page given in env and not in main list
 				$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
 
 				if ( $per_page ) {
 					// per-page found in request -> store in session and persistent session
 					$this->setListSetting($event, 'PerPage', $per_page);
 				}
 				else {
 					// per-page not found in request -> get from pesistent session (or session)
 					$per_page = $this->getListSetting($event, 'PerPage');
 				}
 			}
 
 			if ( !$per_page ) {
 				// per page wan't found in request/session/persistent session
 				$list_helper = $this->Application->recallObject('ListHelper');
 				/* @var $list_helper ListHelper */
 
 				// allow to override default per-page value from tag
 				$default_per_page = $event->getEventParam('default_per_page');
 
 				if ( !is_numeric($default_per_page) ) {
 					$default_per_page = $this->Application->ConfigValue('DefaultGridPerPage');
 				}
 
 				$per_page = $list_helper->getDefaultPerPage($event->Prefix, $default_per_page);
 			}
 
 			return $per_page;
 		}
 
 		/**
 		 * Set's correct sorting for list based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetSorting(kEvent $event)
 		{
 			$event->setPseudoClass('_List');
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			if ( $object->isMainList() ) {
 				$sort_by = $this->Application->GetVar('sort_by');
 				$cur_sort1 = $cur_sort1_dir = $cur_sort2 = $cur_sort2_dir = false;
 
 				if ( $sort_by ) {
 					$sortings = explode('|', $sort_by);
 					list ($cur_sort1, $cur_sort1_dir) = explode(',', $sortings[0]);
 
 					if ( isset($sortings[1]) ) {
 						list ($cur_sort2, $cur_sort2_dir) = explode(',', $sortings[1]);
 					}
 				}
 			}
 			else {
 				$sorting_settings = $this->getListSetting($event, 'Sortings');
 
 				$cur_sort1 = getArrayValue($sorting_settings, 'Sort1');
 				$cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir');
 				$cur_sort2 = getArrayValue($sorting_settings, 'Sort2');
 				$cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir');
 			}
 
 			$tag_sort_by = $event->getEventParam('sort_by');
 
 			if ( $tag_sort_by ) {
 				if ( $tag_sort_by == 'random' ) {
 					$object->AddOrderField('RAND()', '');
 				}
 				else {
 					// multiple sortings could be specified at once
 					$tag_sort_by = explode('|', $tag_sort_by);
 
 					foreach ($tag_sort_by as $sorting_element) {
 						list ($by, $dir) = explode(',', $sorting_element);
 						$object->AddOrderField($by, $dir);
 					}
 				}
 			}
 
 			$list_sortings = $this->_getDefaultSorting($event);
 
 			// use default if not specified in session
 			if ( !$cur_sort1 || !$cur_sort1_dir ) {
 				$sorting = getArrayValue($list_sortings, 'Sorting');
 
 				if ( $sorting ) {
 					reset($sorting);
 					$cur_sort1 = key($sorting);
 					$cur_sort1_dir = current($sorting);
 
 					if ( next($sorting) ) {
 						$cur_sort2 = key($sorting);
 						$cur_sort2_dir = current($sorting);
 					}
 				}
 			}
 
 			// always add forced sorting before any user sorting fields
 			$forced_sorting = getArrayValue($list_sortings, 'ForcedSorting');
 			/* @var $forced_sorting Array */
 
 			if ( $forced_sorting ) {
 				foreach ($forced_sorting as $field => $dir) {
 					$object->AddOrderField($field, $dir);
 				}
 			}
 
 			// add user sorting fields
 			if ( $cur_sort1 != '' && $cur_sort1_dir != '' ) {
 				$object->AddOrderField($cur_sort1, $cur_sort1_dir);
 			}
 
 			if ( $cur_sort2 != '' && $cur_sort2_dir != '' ) {
 				$object->AddOrderField($cur_sort2, $cur_sort2_dir);
 			}
 		}
 
 		/**
 		 * Returns default list sortings
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getDefaultSorting(kEvent $event)
 		{
-			$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
-			$sorting_prefix = array_key_exists($event->Special, $list_sortings) ? $event->Special : '';
-			$sorting_configs = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
+			$config = $event->getUnitConfig();
+			$sorting_configs = $config->getConfigMapping();
+			$list_sortings = $config->getListSortingsBySpecial($event);
 
 			if ( $sorting_configs && array_key_exists('DefaultSorting1Field', $sorting_configs) ) {
 				// sorting defined in configuration variables overrides one from unit config
-				$list_sortings[$sorting_prefix]['Sorting'] = Array (
+				$list_sortings['Sorting'] = Array (
 					$this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']),
 					$this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']),
 				);
 
 				// TODO: lowercase configuration variable values in db, instead of here
-				$list_sortings[$sorting_prefix]['Sorting'] = array_map('strtolower', $list_sortings[$sorting_prefix]['Sorting']);
+				$list_sortings['Sorting'] = array_map('strtolower', $list_sortings['Sorting']);
 			}
 
-			return isset($list_sortings[$sorting_prefix]) ? $list_sortings[$sorting_prefix] : Array ();
+			return $list_sortings ? $list_sortings : Array ();
 		}
 
 		/**
 		 * Gets list setting by name (persistent or real session)
 		 *
 		 * @param kEvent $event
 		 * @param string $variable_name
 		 * @return string|Array
 		 * @access protected
 		 */
 		protected function getListSetting(kEvent $event, $variable_name)
 		{
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
 			$storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->getPrefixSpecial();
 
 			// get sorting from persistent session
 			$default_value = $this->Application->isAdmin ? ALLOW_DEFAULT_SETTINGS : false;
 			$variable_value = $this->Application->RecallPersistentVar($storage_prefix . '_' . $variable_name . '.' . $view_name, $default_value);
 
 			/*if ( !$variable_value ) {
 				// get sorting from session
 				$variable_value = $this->Application->RecallVar($storage_prefix . '_' . $variable_name);
 			}*/
 
 			if ( kUtil::IsSerialized($variable_value) ) {
 				$variable_value = unserialize($variable_value);
 			}
 
 			return $variable_value;
 		}
 
 		/**
 		 * Sets list setting by name (persistent and real session)
 		 *
 		 * @param kEvent $event
 		 * @param string $variable_name
 		 * @param string|Array $variable_value
 		 * @return void
 		 * @access protected
 		 */
 		protected function setListSetting(kEvent $event, $variable_name, $variable_value = NULL)
 		{
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
 //			$this->Application->StoreVar($event->getPrefixSpecial() . '_' . $variable_name, $variable_value, true); //true for optional
 
 			if ( isset($variable_value) ) {
 				if ( is_array($variable_value) ) {
 					$variable_value = serialize($variable_value);
 				}
 
 				$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name, $variable_value, true); //true for optional
 			}
 			else {
 				$this->Application->RemovePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name);
 			}
 		}
 
 		/**
 		 * Add filters found in session
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function AddFilters(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$edit_mark = rtrim($this->Application->GetSID() . '_' . $this->Application->GetTopmostWid($event->Prefix), '_');
 
 			// add search filter
 			$filter_data = $this->Application->RecallVar($event->getPrefixSpecial() . '_search_filter');
 
 			if ( $filter_data ) {
 				$filter_data = unserialize($filter_data);
 
 				foreach ($filter_data as $filter_field => $filter_params) {
 					$filter_type = ($filter_params['type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER;
 					$filter_value = str_replace(EDIT_MARK, $edit_mark, $filter_params['value']);
 					$object->addFilter($filter_field, $filter_value, $filter_type, kDBList::FLT_SEARCH);
 				}
 			}
 
 			// add custom filter
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
 			$custom_filters = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_custom_filter.' . $view_name);
 
 			if ( $custom_filters ) {
 				$grid_name = $event->getEventParam('grid');
 				$custom_filters = unserialize($custom_filters);
 
 				if ( isset($custom_filters[$grid_name]) ) {
 					foreach ($custom_filters[$grid_name] as $field_name => $field_options) {
 						list ($filter_type, $field_options) = each($field_options);
 
 						if ( isset($field_options['value']) && $field_options['value'] ) {
 							$filter_type = ($field_options['sql_filter_type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER;
 							$filter_value = str_replace(EDIT_MARK, $edit_mark, $field_options['value']);
 							$object->addFilter($field_name, $filter_value, $filter_type, kDBList::FLT_CUSTOM);
 						}
 					}
 				}
 			}
 
 			// add view filter
 			$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
 
 			if ( $view_filter ) {
 				$view_filter = unserialize($view_filter);
 
 				$temp_filter = $this->Application->makeClass('kMultipleFilter');
 				/* @var $temp_filter kMultipleFilter */
 
-				$filter_menu = $this->Application->getUnitOption($event->Prefix, 'FilterMenu');
+				$filter_menu = $event->getUnitConfig()->getFilterMenu();
 
 				$group_key = 0;
 				$group_count = count($filter_menu['Groups']);
 
 				while ($group_key < $group_count) {
 					$group_info = $filter_menu['Groups'][$group_key];
 
 					$temp_filter->setType(constant('kDBList::FLT_TYPE_' . $group_info['mode']));
 					$temp_filter->clearFilters();
 
 					foreach ($group_info['filters'] as $flt_id) {
 						$sql_key = getArrayValue($view_filter, $flt_id) ? 'on_sql' : 'off_sql';
 
 						if ( $filter_menu['Filters'][$flt_id][$sql_key] != '' ) {
 							$temp_filter->addFilter('view_filter_' . $flt_id, $filter_menu['Filters'][$flt_id][$sql_key]);
 						}
 					}
 
 					$object->addFilter('view_group_' . $group_key, $temp_filter, $group_info['type'], kDBList::FLT_VIEW);
 					$group_key++;
 				}
 			}
 
 			// add item filter
 			if ( $object->isMainList() ) {
 				$this->applyItemFilters($event);
 			}
 		}
 
 		/**
 		 * Applies item filters
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function applyItemFilters($event)
 		{
 			$filter_values = $this->Application->GetVar('filters', Array ());
 
 			if ( !$filter_values ) {
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$where_clause = Array (
 				'ItemPrefix = ' . $this->Conn->qstr($object->Prefix),
 				'FilterField IN (' . implode(',', $this->Conn->qstrArray(array_keys($filter_values))) . ')',
 				'Enabled = 1',
 			);
 
 			$sql = 'SELECT *
-					FROM ' . $this->Application->getUnitOption('item-filter', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('item-filter')->getTableName() . '
 					WHERE (' . implode(') AND (', $where_clause) . ')';
 			$filters = $this->Conn->Query($sql, 'FilterField');
 
 			foreach ($filters as $filter_field => $filter_data) {
 				$filter_value = $filter_values[$filter_field];
 
 				if ( "$filter_value" === '' ) {
 					// ListManager don't pass empty values, but check here just in case
 					continue;
 				}
 
 				$table_name = $object->isVirtualField($filter_field) ? '' : '%1$s.';
 
 				switch ($filter_data['FilterType']) {
 					case 'radio':
 						$filter_value = $table_name . '`' . $filter_field . '` = ' . $this->Conn->qstr($filter_value);
 						break;
 
 					case 'checkbox':
 						$filter_value = explode('|', substr($filter_value, 1, -1));
 						$filter_value = $this->Conn->qstrArray($filter_value, 'escape');
 
 						if ( $object->GetFieldOption($filter_field, 'multiple') ) {
 							$filter_value = $table_name . '`' . $filter_field . '` LIKE "%|' . implode('|%" OR ' . $table_name . '`' . $filter_field . '` LIKE "%|', $filter_value) . '|%"';
 						}
 						else {
 							$filter_value = $table_name . '`' . $filter_field . '` IN (' . implode(',', $filter_value) . ')';
 						}
 						break;
 
 					case 'range':
 						$filter_value = $this->Conn->qstrArray(explode('-', $filter_value));
 						$filter_value = $table_name . '`' . $filter_field . '` BETWEEN ' . $filter_value[0] . ' AND ' . $filter_value[1];
 						break;
 				}
 
 				$object->addFilter('item_filter_' . $filter_field, $filter_value, $object->isVirtualField($filter_field) ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER);
 			}
 		}
 
 		/**
 		 * Set's new sorting for list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetSorting(kEvent $event)
 		{
 			$sorting_settings = $this->getListSetting($event, 'Sortings');
 			$cur_sort1 = getArrayValue($sorting_settings, 'Sort1');
 			$cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir');
 
 			$use_double_sorting = $this->Application->ConfigValue('UseDoubleSorting');
 
 			if ( $use_double_sorting ) {
 				$cur_sort2 = getArrayValue($sorting_settings, 'Sort2');
 				$cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir');
 			}
 
 			$passed_sort1 = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Sort1');
 			if ( $cur_sort1 == $passed_sort1 ) {
 				$cur_sort1_dir = $cur_sort1_dir == 'asc' ? 'desc' : 'asc';
 			}
 			else {
 				if ( $use_double_sorting ) {
 					$cur_sort2 = $cur_sort1;
 					$cur_sort2_dir = $cur_sort1_dir;
 				}
 
 				$cur_sort1 = $passed_sort1;
 				$cur_sort1_dir = 'asc';
 			}
 
 			$sorting_settings = Array ('Sort1' => $cur_sort1, 'Sort1_Dir' => $cur_sort1_dir);
 
 			if ( $use_double_sorting ) {
 				$sorting_settings['Sort2'] = $cur_sort2;
 				$sorting_settings['Sort2_Dir'] = $cur_sort2_dir;
 			}
 
 			$this->setListSetting($event, 'Sortings', $sorting_settings);
 		}
 
 		/**
 		 * Set sorting directly to session (used for category item sorting (front-end), grid sorting (admin, view menu)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetSortingDirect(kEvent $event)
 		{
 			// used on Front-End in category item lists
 			$prefix_special = $event->getPrefixSpecial();
 			$combined = $this->Application->GetVar($event->getPrefixSpecial(true) . '_CombinedSorting');
 
 			if ( $combined ) {
 				list ($field, $dir) = explode('|', $combined);
 
 				if ( $this->Application->isAdmin || !$this->Application->GetVar('main_list') ) {
 					$this->setListSetting($event, 'Sortings', Array ('Sort1' => $field, 'Sort1_Dir' => $dir));
 				}
 				else {
 					$event->setPseudoClass('_List');
 					$this->Application->SetVar('sort_by', $field . ',' . $dir);
 
 					$object = $event->getObject(Array ('main_list' => 1));
 					/* @var $object kDBList */
 
 					$list_helper = $this->Application->recallObject('ListHelper');
 					/* @var $list_helper ListHelper */
 
 					$this->_passListParams($event, 'sort_by');
 
 					if ( $list_helper->hasUserSorting($object) ) {
 						$event->SetRedirectParam('sort_by', $field . ',' . strtolower($dir));
 					}
 
 					$event->SetRedirectParam('pass', 'm');
 				}
 
 				return;
 			}
 
 			// used in "View Menu -> Sort" menu in administrative console
 			$field_pos = $this->Application->GetVar($event->getPrefixSpecial(true) . '_SortPos');
 			$this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos, $prefix_special . '_Sort' . $field_pos);
 			$this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos . '_Dir', $prefix_special . '_Sort' . $field_pos . '_Dir');
 		}
 
 		/**
 		 * Reset grid sorting to default (from config)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnResetSorting(kEvent $event)
 		{
 			$this->setListSetting($event, 'Sortings');
 		}
 
 		/**
 		 * Sets grid refresh interval
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetAutoRefreshInterval(kEvent $event)
 		{
 			$refresh_interval = $this->Application->GetVar('refresh_interval');
 
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
 			$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_interval);
 		}
 
 		/**
 		 * Changes auto-refresh state for grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAutoRefreshToggle(kEvent $event)
 		{
 			$refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals');
 			if ( !$refresh_intervals ) {
 				return;
 			}
 
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
 			$auto_refresh = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name);
 
 			if ( $auto_refresh === false ) {
 				$refresh_intervals = explode(',', $refresh_intervals);
 				$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_intervals[0]);
 			}
 
 			$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name, $auto_refresh ? 0 : 1);
 		}
 
 		/**
 		 * Creates needed sql query to load item,
 		 * if no query is defined in config for
 		 * special requested, then use list query
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function ItemPrepareQuery(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sqls = $object->getFormOption('ItemSQLs', Array ());
 			$special = isset($sqls[$event->Special]) ? $event->Special : '';
 
 			// preferred special not found in ItemSQLs -> use analog from ListSQLs
 
 			return isset($sqls[$special]) ? $sqls[$special] : $this->ListPrepareQuery($event);
 		}
 
 		/**
 		 * Creates needed sql query to load list,
 		 * if no query is defined in config for
 		 * special requested, then use default
 		 * query
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function ListPrepareQuery(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sqls = $object->getFormOption('ListSQLs', Array ());
 
 			return $sqls[array_key_exists($event->Special, $sqls) ? $event->Special : ''];
 		}
 
 		/**
 		 * Apply custom processing to item
 		 *
 		 * @param kEvent $event
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(kEvent $event, $type)
 		{
 
 		}
 
 		/* Edit Events mostly used in Admin */
 
 		/**
 		 * Creates new kDBItem
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCreate(kEvent $event)
 		{
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 			if ( !$items_info ) {
 				return;
 			}
 
 			list($id, $field_values) = each($items_info);
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 			$this->customProcessing($event, 'before');
 
 			// look at kDBItem' Create for ForceCreateId description, it's rarely used and is NOT set by default
 			if ( $object->Create($event->getEventParam('ForceCreateId')) ) {
 				$this->customProcessing($event, 'after');
 				$event->SetRedirectParam('opener', 'u');
 				return;
 			}
 
 			$event->redirect = false;
 			$event->status = kEvent::erFAIL;
 			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
 			$object->setID($id);
 		}
 
 		/**
 		 * Updates kDBItem
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdate(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$this->_update($event);
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Updates data in database based on request
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function _update(kEvent $event)
 		{
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 
 			if ( $items_info ) {
 				foreach ($items_info as $id => $field_values) {
 					$object->Load($id);
 					$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 					$this->customProcessing($event, 'before');
 
 					if ( $object->Update($id) ) {
 						$this->customProcessing($event, 'after');
 						$event->status = kEvent::erSUCCESS;
 					}
 					else {
 						$event->status = kEvent::erFAIL;
 						$event->redirect = false;
 						break;
 					}
 				}
 			}
 		}
 
 		/**
 		 * Delete's kDBItem object
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnDelete(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($this->getPassedID($event)));
 		}
 
 		/**
 		 * Deletes all records from table
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnDeleteAll(kEvent $event)
 		{
-			$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
-					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$config = $event->getUnitConfig();
+
+			$sql = 'SELECT ' . $config->getIDField() . '
+					FROM ' . $config->getTableName();
 			$ids = $this->Conn->GetCol($sql);
 
 			if ( $ids ) {
 				$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 				/* @var $temp_handler kTempTablesHandler */
 
 				$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
 			}
 		}
 
 		/**
 		 * Prepares new kDBItem object
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnNew(kEvent $event)
 		{
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$object->Clear(0);
 			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
 
 			if ( $event->getEventParam('top_prefix') != $event->Prefix ) {
 				// this is subitem prefix, so use main item special
 				$table_info = $object->getLinkedInfo($this->getMainSpecial($event));
 			}
 			else {
 				$table_info = $object->getLinkedInfo();
 			}
 
 			$object->SetDBField($table_info['ForeignKey'], $table_info['ParentId']);
 
 			$event->redirect = false;
 		}
 
 		/**
 		 * Cancels kDBItem Editing/Creation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCancel(kEvent $event)
 		{
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 			if ( $items_info ) {
 				$delete_ids = Array ();
 
 				$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 				/* @var $temp_handler kTempTablesHandler */
 
 				foreach ($items_info as $id => $field_values) {
 					$object->Load($id);
 					// record created for using with selector (e.g. Reviews->Select User), and not validated => Delete it
 					if ( $object->isLoaded() && !$object->Validate() && ($id <= 0) ) {
 						$delete_ids[] = $id;
 					}
 				}
 
 				if ( $delete_ids ) {
 					$temp_handler->DeleteItems($event->Prefix, $event->Special, $delete_ids);
 				}
 			}
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Deletes all selected items.
 		 * Automatically recurse into sub-items using temp handler, and deletes sub-items
 		 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnMassDelete(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return ;
 			}
 
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			$event->setEventParam('ids', $ids);
 			$this->customProcessing($event, 'before');
 			$ids = $event->getEventParam('ids');
 
 			if ( $ids ) {
 				$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Sets window id (of first opened edit window) to temp mark in uls
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function setTempWindowID(kEvent $event)
 		{
 			$prefixes = Array ($event->Prefix, $event->getPrefixSpecial(true));
 
 			foreach ($prefixes as $prefix) {
 				$mode = $this->Application->GetVar($prefix . '_mode');
 
 				if ($mode == 't') {
 					$wid = $this->Application->GetVar('m_wid');
 					$this->Application->SetVar(str_replace('_', '.', $prefix) . '_mode', 't' . $wid);
 					break;
 				}
 			}
 		}
 
 		/**
 		 * Prepare temp tables and populate it
 		 * with items selected in the grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnEdit(kEvent $event)
 		{
 			$this->setTempWindowID($event);
 			$ids = $this->StoreSelectedIDs($event);
 
 			$object = $event->getObject(Array('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$this->Application->RemoveVar($object->getPendingActionVariableName());
 
 			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 			$this->Application->RemoveVar($changes_var_name);
 
 			foreach ($ids as $id) {
 				$object->resetUploads($id);
 			}
 
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->PrepareEdit();
 
 			$event->SetRedirectParam('m_lang', $this->Application->GetDefaultLanguageId());
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_id', array_shift($ids));
 			$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
 
 			$simultaneous_edit_message = $this->Application->GetVar('_simultanious_edit_message');
 
 			if ( $simultaneous_edit_message ) {
 				$event->SetRedirectParam('_simultanious_edit_message', urlencode($simultaneous_edit_message));
 			}
 		}
 
 		/**
 		 * Saves content of temp table into live and
 		 * redirects to event' default redirect (normally grid template)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSave(kEvent $event)
 		{
 			$event->CallSubEvent('OnPreSave');
 
 			if ( $event->status != kEvent::erSUCCESS ) {
 				return;
 			}
 
 			$skip_master = false;
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 
 			if ( !$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$live_ids = $temp_handler->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array ());
 
 				if ( $live_ids === false ) {
 					// coping from table failed, because we have another coping process to same table, that wasn't finished
 					$event->status = kEvent::erFAIL;
 					return;
 				}
 
 				if ( $live_ids ) {
 					// ensure, that newly created item ids are available as if they were selected from grid
 					// NOTE: only works if main item has sub-items !!!
 					$this->StoreSelectedIDs($event, $live_ids);
 				}
 
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$this->SaveLoggedChanges($changes_var_name, $object->ShouldLogChanges());
 			}
 			else {
 				$event->status = kEvent::erFAIL;
 			}
 
 			$this->clearSelectedIDs($event);
 
 			$event->SetRedirectParam('opener', 'u');
 			$this->Application->RemoveVar($event->getPrefixSpecial() . '_modified');
 
 			// all temp tables are deleted here => all after hooks should think, that it's live mode now
 			$this->Application->SetVar($event->Prefix . '_mode', '');
 		}
 
 		/**
 		 * Saves changes made in temporary table to log
 		 *
 		 * @param string $changes_var_name
 		 * @param bool $save
 		 * @return void
 		 * @access public
 		 */
 		public function SaveLoggedChanges($changes_var_name, $save = true)
 		{
 			// 1. get changes, that were made
 			$changes = $this->Application->RecallVar($changes_var_name);
 			$changes = $changes ? unserialize($changes) : Array ();
 			$this->Application->RemoveVar($changes_var_name);
 
 			if (!$changes) {
 				// no changes, skip processing
 				return ;
 			}
 
 			// TODO: 2. optimize change log records (replace multiple changes to same record with one change record)
 
 			$to_increment = Array ();
 
 			// 3. collect serials to reset based on foreign keys
 			foreach ($changes as $index => $rec) {
 				if (array_key_exists('DependentFields', $rec)) {
 
 					foreach ($rec['DependentFields'] as $field_name => $field_value) {
 						// will be "ci|ItemResourceId:345"
 						$to_increment[] = $rec['Prefix'] . '|' . $field_name . ':' . $field_value;
 
 						// also reset sub-item prefix general serial
 						$to_increment[] = $rec['Prefix'];
 					}
 
 					unset($changes[$index]['DependentFields']);
 				}
 
 				unset($changes[$index]['ParentId'], $changes[$index]['ParentPrefix']);
 			}
 
 			// 4. collect serials to reset based on changed ids
 			foreach ($changes as $change) {
 				$to_increment[] = $change['MasterPrefix'] . '|' . $change['MasterId'];
 
 				if ($change['MasterPrefix'] != $change['Prefix']) {
 					// also reset sub-item prefix general serial
 					$to_increment[] = $change['Prefix'];
 
 					// will be "ci|ItemResourceId"
 					$to_increment[] = $change['Prefix'] . '|' . $change['ItemId'];
 				}
 			}
 
 			// 5. reset serials collected before
 			$to_increment = array_unique($to_increment);
 			$this->Application->incrementCacheSerial($this->Prefix);
 
 			foreach ($to_increment as $to_increment_mixed) {
 				if (strpos($to_increment_mixed, '|') !== false) {
 					list ($to_increment_prefix, $to_increment_id) = explode('|', $to_increment_mixed, 2);
 					$this->Application->incrementCacheSerial($to_increment_prefix, $to_increment_id);
 				}
 				else {
 					$this->Application->incrementCacheSerial($to_increment_mixed);
 				}
 			}
 
 			// save changes to database
-			$sesion_log_id = $this->Application->RecallVar('_SessionLogId_');
+			$session_log_id = $this->Application->RecallVar('_SessionLogId_');
 
-			if (!$save || !$sesion_log_id) {
+			if (!$save || !$session_log_id) {
 				// saving changes to database disabled OR related session log missing
 				return ;
 			}
 
 			$add_fields = Array (
 				'PortalUserId' => $this->Application->RecallVar('user_id'),
-				'SessionLogId' => $sesion_log_id,
+				'SessionLogId' => $session_log_id,
 			);
 
-			$change_log_table = $this->Application->getUnitOption('change-log', 'TableName');
+			$change_log_table = $this->Application->getUnitConfig('change-log')->getTableName();
 
 			foreach ($changes as $rec) {
 				$this->Conn->doInsert(array_merge($rec, $add_fields), $change_log_table);
 			}
 
 			$this->Application->incrementCacheSerial('change-log');
 
-			$sql = 'UPDATE ' . $this->Application->getUnitOption('session-log', 'TableName') . '
+			$sql = 'UPDATE ' . $this->Application->getUnitConfig('session-log')->getTableName() . '
 					SET AffectedItems = AffectedItems + ' . count($changes) . '
-					WHERE SessionLogId = ' . $sesion_log_id;
+					WHERE SessionLogId = ' . $session_log_id;
 			$this->Conn->Query($sql);
 
 			$this->Application->incrementCacheSerial('session-log');
 		}
 
 		/**
 		 * Cancels edit
 		 * Removes all temp tables and clears selected ids
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCancelEdit(kEvent $event)
 		{
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->CancelEdit();
 			$this->clearSelectedIDs($event);
 
 			$this->Application->RemoveVar($event->getPrefixSpecial() . '_modified');
 
 			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 			$this->Application->RemoveVar($changes_var_name);
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Allows to determine if we are creating new item or editing already created item
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function isNewItemCreate(kEvent $event)
 		{
 			$object = $event->getObject( Array ('raise_warnings' => 0) );
 			/* @var $object kDBItem */
 
 			return !$object->isLoaded();
 		}
 
 		/**
 		 * Saves edited item into temp table
 		 * If there is no id, new item is created in temp table
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSave(kEvent $event)
 		{
 			// if there is no id - it means we need to create an item
 			if ( is_object($event->MasterEvent) ) {
 				$event->MasterEvent->setEventParam('IsNew', false);
 			}
 
 			if ( $this->isNewItemCreate($event) ) {
 				$event->CallSubEvent('OnPreSaveCreated');
 
 				if ( is_object($event->MasterEvent) ) {
 					$event->MasterEvent->setEventParam('IsNew', true);
 				}
 
 				return ;
 			}
 
 			// don't just call OnUpdate event here, since it maybe overwritten to Front-End specific behavior
 			$this->_update($event);
 		}
 
 		/**
 		 * [HOOK] Saves sub-item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveSubItem(kEvent $event)
 		{
 			$not_created = $this->isNewItemCreate($event);
 
 			$event->CallSubEvent($not_created ? 'OnCreate' : 'OnUpdate');
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID());
 			}
 			else {
 				$event->MasterEvent->status = $event->status;
 			}
 
 			$event->SetRedirectParam('opener', 's');
 		}
 
 		/**
 		 * Saves edited item in temp table and loads
 		 * item with passed id in current template
 		 * Used in Prev/Next buttons
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveAndGo(kEvent $event)
 		{
 			$event->CallSubEvent('OnPreSave');
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$id = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId');
 				$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id);
 			}
 		}
 
 		/**
 		 * Saves edited item in temp table and goes
 		 * to passed tabs, by redirecting to it with OnPreSave event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveAndGoToTab(kEvent $event)
 		{
 			$event->CallSubEvent('OnPreSave');
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab');
 			}
 		}
 
 		/**
 		 * Saves editable list and goes to passed tab,
 		 * by redirecting to it with empty event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdateAndGoToTab(kEvent $event)
 		{
 			$event->setPseudoClass('_List');
 			$event->CallSubEvent('OnUpdate');
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab');
 			}
 		}
 
 		/**
 		 * Prepare temp tables for creating new item
 		 * but does not create it. Actual create is
 		 * done in OnPreSaveCreated
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			$this->setTempWindowID($event);
 			$this->clearSelectedIDs($event);
 			$this->Application->SetVar('m_lang', $this->Application->GetDefaultLanguageId());
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->PrepareEdit();
 
 			$object->setID(0);
 			$this->Application->SetVar($event->getPrefixSpecial() . '_id', 0);
 			$this->Application->SetVar($event->getPrefixSpecial() . '_PreCreate', 1);
 
 			$object->resetUploads();
 
 			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 			$this->Application->RemoveVar($changes_var_name);
 
 			$event->redirect = false;
 		}
 
 		/**
 		 * Creates a new item in temp table and
 		 * stores item id in App vars and Session on success
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveCreated(kEvent $event)
 		{
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$field_values = $this->getSubmittedFields($event);
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 			$this->customProcessing($event, 'before');
 
 			if ( $object->Create() ) {
 				$this->customProcessing($event, 'after');
 				$event->SetRedirectParam($event->getPrefixSpecial(true) . '_id', $object->GetID());
 			}
 			else {
 				$event->status = kEvent::erFAIL;
 				$event->redirect = false;
 				$object->setID(0);
 			}
 		}
 
 		/**
 		 * Reloads form to loose all changes made during item editing
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnReset(kEvent $event)
 		{
 			//do nothing - should reset :)
 			if ( $this->isNewItemCreate($event) ) {
 				// just reset id to 0 in case it was create
 				$object = $event->getObject( Array ('skip_autoload' => true) );
 				/* @var $object kDBItem */
 
 				$object->setID(0);
 				$this->Application->SetVar($event->getPrefixSpecial() . '_id', 0);
 			}
 		}
 
 		/**
 		 * Apply same processing to each item being selected in grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function iterateItems(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return ;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ( $ids ) {
-				$status_field = $object->getStatusField();
-				$order_field = $this->Application->getUnitOption($event->Prefix, 'OrderField');
+				$config = $event->getUnitConfig();
+				$status_field = $config->getStatusField(true);
+				$order_field = $config->getOrderField();
 
 				if ( !$order_field ) {
 					$order_field = 'Priority';
 				}
 
 				foreach ($ids as $id) {
 					$object->Load($id);
 
 					switch ( $event->Name ) {
 						case 'OnMassApprove':
 							$object->SetDBField($status_field, 1);
 							break;
 
 						case 'OnMassDecline':
 							$object->SetDBField($status_field, 0);
 							break;
 
 						case 'OnMassMoveUp':
 							$object->SetDBField($order_field, $object->GetDBField($order_field) + 1);
 							break;
 
 						case 'OnMassMoveDown':
 							$object->SetDBField($order_field, $object->GetDBField($order_field) - 1);
 							break;
 					}
 
 					if ( $object->Update() ) {
 						$event->status = kEvent::erSUCCESS;
 					}
 					else {
 						$event->status = kEvent::erFAIL;
 						$event->redirect = false;
 						break;
 					}
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Clones selected items in list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnMassClone(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ( $ids ) {
 				$temp_handler->CloneItems($event->Prefix, $event->Special, $ids);
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Checks if given value is present in given array
 		 *
 		 * @param Array $records
 		 * @param string $field
 		 * @param mixed $value
 		 * @return bool
 		 * @access protected
 		 */
 		protected function check_array($records, $field, $value)
 		{
 			foreach ($records as $record) {
 				if ($record[$field] == $value) {
 					return true;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Saves data from editing form to database without checking required fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSavePopup(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$this->RemoveRequiredFields($object);
 			$event->CallSubEvent('OnPreSave');
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 /* End of Edit events */
 
 		// III. Events that allow to put some code before and after Update,Load,Create and Delete methods of item
 
 		/**
 		 * Occurs before loading item, 'id' parameter
 		 * allows to get id of item being loaded
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemLoad(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after loading item, 'id' parameter
 		 * allows to get id of item that was loaded
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs before creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->IsTempTable() ) {
 				$this->_proccessPendingActions($event);
 			}
 		}
 
 		/**
 		 * Occurs before updating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after updating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemUpdate(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->IsTempTable() ) {
 				$this->_proccessPendingActions($event);
 			}
 		}
 
 		/**
 		 * Occurs before deleting item, id of item being
 		 * deleted is stored as 'id' event param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemDelete(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after deleting item, id of deleted item
 		 * is stored as 'id' param of event
 		 *
 		 * Also deletes subscriptions to that particual item once it's deleted
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// 1. delete direct subscriptions to item, that was deleted
 			$this->_deleteSubscriptions($event->Prefix, 'ItemId', $object->GetID());
 
-			$sub_items = $this->Application->getUnitOption($event->Prefix, 'SubItems', Array ());
-			/* @var $sub_items Array */
-
 			// 2. delete this item sub-items subscriptions, that reference item, that was deleted
-			foreach ($sub_items as $sub_prefix) {
+			foreach ($event->getUnitConfig()->getSubItems(Array ()) as $sub_prefix) {
 				$this->_deleteSubscriptions($sub_prefix, 'ParentItemId', $object->GetID());
 			}
 		}
 
 		/**
 		 * Deletes all subscriptions, associated with given item
 		 *
 		 * @param string $prefix
 		 * @param string $field
 		 * @param int $value
 		 * @return void
 		 * @access protected
 		 */
 		protected function _deleteSubscriptions($prefix, $field, $value)
 		{
 			$sql = 'SELECT TemplateId
-					FROM ' . $this->Application->getUnitOption('email-template', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('email-template')->getTableName() . '
 					WHERE BindToSystemEvent REGEXP "' . $this->Conn->escape($prefix) . '(\\\\.[^:]*:.*|:.*)"';
 			$email_template_ids = $this->Conn->GetCol($sql);
 
 			if ( !$email_template_ids ) {
 				return;
 			}
 
 			// e-mail events, connected to that unit prefix are found
 			$sql = 'SELECT SubscriptionId
 					FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions
 					WHERE ' . $field . ' = ' . $value . ' AND EmailTemplateId IN (' . implode(',', $email_template_ids) . ')';
 			$ids = $this->Conn->GetCol($sql);
 
 			if ( !$ids ) {
 				return;
 			}
 
 			$temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems('system-event-subscription', '', $ids);
 		}
 
 		/**
 		 * Occurs before validation attempt
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemValidate(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after successful item validation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemValidate(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after an item has been copied to temp
 		 * Id of copied item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterCopyToTemp(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs before an item is deleted from live table when copying from temp
 		 * (temp handler deleted all items from live and then copy over all items from temp)
 		 * Id of item being deleted is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeDeleteFromLive(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs before an item is copied to live table (after all foreign keys have been updated)
 		 * Id of item being copied is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeCopyToLive(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after an item has been copied to live table
 		 * Id of copied item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterCopyToLive(kEvent $event)
 		{
 			$this->_proccessPendingActions($event);
 		}
 
 		/**
 		 * Processing file pending actions (e.g. delete scheduled files)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function _proccessPendingActions(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->getUploaderFields() ) {
 				// this would prevent SQL error when loading "*-ci" prefix object
 				if ( $event->Name == 'OnAfterCopyToLive' ) {
 					$object->SwitchToLive();
 					$object->Load($event->getEventParam('id'));
 
 					$object->processUploads($event->getEventParam('temp_id'));
 				}
 				else {
 					$object->processUploads();
 				}
 			}
 
 			$var_name = $object->getPendingActionVariableName();
 			$schedule = $this->Application->RecallVar($var_name);
 
 			if ( $schedule ) {
 				$schedule = unserialize($schedule);
 
 				foreach ($schedule as $data) {
 					if ( $data['action'] == 'delete' ) {
 						unlink($data['file']);
 					}
 				}
 
 				$this->Application->RemoveVar($var_name);
 			}
 		}
 
 		/**
 		 * Occurs before an item has been cloned
 		 * Id of newly created item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeClone(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after an item has been cloned
 		 * Id of newly created item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterClone(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Occurs after list is queried
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterListQuery(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Ensures that popup will be closed automatically
 		 * and parent window will be refreshed with template
 		 * passed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @deprecated
 		 */
 		protected function finalizePopup(kEvent $event)
 		{
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Create search filters based on search query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSearch(kEvent $event)
 		{
 			$event->setPseudoClass('_List');
 
 			$search_helper = $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$search_helper->performSearch($event);
 		}
 
 		/**
 		 * Clear search keywords
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSearchReset(kEvent $event)
 		{
 			$search_helper = $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$search_helper->resetSearch($event);
 		}
 
 		/**
 		 * Set's new filter value (filter_id meaning from config)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @deprecated
 		 */
 		protected function OnSetFilter(kEvent $event)
 		{
 			$filter_id = $this->Application->GetVar('filter_id');
 			$filter_value = $this->Application->GetVar('filter_value');
 
 			$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
 			$view_filter = $view_filter ? unserialize($view_filter) : Array ();
 
 			$view_filter[$filter_id] = $filter_value;
 
 			$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
 		}
 
 		/**
 		 * Sets view filter based on request
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetFilterPattern(kEvent $event)
 		{
 			$filters = $this->Application->GetVar($event->getPrefixSpecial(true) . '_filters');
 			if ( !$filters ) {
 				return;
 			}
 
 			$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
 			$view_filter = $view_filter ? unserialize($view_filter) : Array ();
 
 			$filters = explode(',', $filters);
 
 			foreach ($filters as $a_filter) {
 				list($id, $value) = explode('=', $a_filter);
 				$view_filter[$id] = $value;
 			}
 
 			$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
 			$event->redirect = false;
 		}
 
 		/**
 		 * Add/Remove all filters applied to list from "View" menu
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function FilterAction(kEvent $event)
 		{
 			$view_filter = Array ();
-			$filter_menu = $this->Application->getUnitOption($event->Prefix, 'FilterMenu');
+			$filter_menu = $event->getUnitConfig()->getFilterMenu();
 
 			switch ($event->Name) {
 				case 'OnRemoveFilters':
 					$filter_value = 1;
 					break;
 
 				case 'OnApplyFilters':
 					$filter_value = 0;
 					break;
 
 				default:
 					$filter_value = 0;
 					break;
 			}
 
 			foreach ($filter_menu['Filters'] as $filter_key => $filter_params) {
 				if ( !$filter_params ) {
 					continue;
 				}
 
 				$view_filter[$filter_key] = $filter_value;
 			}
 
 			$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function OnPreSaveAndOpenTranslator(kEvent $event)
 		{
 			$this->Application->SetVar('allow_translation', true);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$this->RemoveRequiredFields($object);
 			$event->CallSubEvent('OnPreSave');
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$resource_id = $this->Application->GetVar('translator_resource_id');
 
 				if ( $resource_id ) {
 					$t_prefixes = explode(',', $this->Application->GetVar('translator_prefixes'));
 
 					$cdata = $this->Application->recallObject($t_prefixes[1], NULL, Array ('skip_autoload' => true));
 					/* @var $cdata kDBItem */
 
 					$cdata->Load($resource_id, 'ResourceId');
 
 					if ( !$cdata->isLoaded() ) {
 						$cdata->SetDBField('ResourceId', $resource_id);
 						$cdata->Create();
 					}
 
 					$this->Application->SetVar($cdata->getPrefixSpecial() . '_id', $cdata->GetID());
 				}
 
 				$event->redirect = $this->Application->GetVar('translator_t');
 
 				$redirect_params = Array (
 					'pass' => 'all,trans,' . $this->Application->GetVar('translator_prefixes'),
 					'opener' => 's',
 					$event->getPrefixSpecial(true) . '_id' => $object->GetID(),
 					'trans_event'		=>	'OnLoad',
 					'trans_prefix'		=>	$this->Application->GetVar('translator_prefixes'),
 					'trans_field' 		=>	$this->Application->GetVar('translator_field'),
 					'trans_multi_line'	=>	$this->Application->GetVar('translator_multi_line'),
 				);
 
 				$event->setRedirectParams($redirect_params);
 
 				// 1. SAVE LAST TEMPLATE TO SESSION (really needed here, because of tweaky redirect)
 				$last_template = $this->Application->RecallVar('last_template');
 				preg_match('/index4\.php\|' . $this->Application->GetSID() . '-(.*):/U', $last_template, $rets);
 				$this->Application->StoreVar('return_template', $this->Application->GetVar('t'));
 			}
 		}
 
 		/**
 		 * Makes all fields non-required
 		 *
 		 * @param kDBItem $object
 		 * @return void
 		 * @access protected
 		 */
 		protected function RemoveRequiredFields(&$object)
 		{
 			// making all field non-required to achieve successful presave
 			$fields = array_keys( $object->getFields() );
 
 			foreach ($fields as $field) {
 				if ( $object->isRequired($field) ) {
 					$object->setRequired($field, false);
 				}
 			}
 		}
 
 		/**
 		 * Saves selected user in needed field
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSelectUser(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar('u');
 
 			if ( $items_info ) {
 				list ($user_id, ) = each($items_info);
 				$this->RemoveRequiredFields($object);
 
 				$is_new = !$object->isLoaded();
 				$is_main = substr($this->Application->GetVar($event->Prefix . '_mode'), 0, 1) == 't';
 
 				if ( $is_new ) {
 					$new_event = $is_main ? 'OnPreCreate' : 'OnNew';
 					$event->CallSubEvent($new_event);
 					$event->redirect = true;
 				}
 
 				$object->SetDBField($this->Application->RecallVar('dst_field'), $user_id);
 
 				if ( $is_new ) {
 					$object->Create();
 				}
 				else {
 					$object->Update();
 				}
 			}
 
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $object->GetID());
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 /** EXPORT RELATED **/
 
 		/**
 		 * Shows export dialog
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnExport(kEvent $event)
 		{
 			$selected_ids = $this->StoreSelectedIDs($event);
 
 			if ( implode(',', $selected_ids) == '' ) {
 				// K4 fix when no ids found bad selected ids array is formed
 				$selected_ids = false;
 			}
 
 			$this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : '');
 
 			$this->Application->LinkVar('export_finish_t');
 			$this->Application->LinkVar('export_progress_t');
 			$this->Application->StoreVar('export_oroginal_special', $event->Special);
 
 			$export_helper = $this->Application->recallObject('CatItemExportHelper');
 
 			/*list ($index_file, $env) = explode('|', $this->Application->RecallVar('last_template'));
 			$finish_url = $this->Application->BaseURL('/admin') . $index_file . '?' . ENV_VAR_NAME . '=' . $env;
 			$this->Application->StoreVar('export_finish_url', $finish_url);*/
 
 			$redirect_params = Array (
 				$this->Prefix . '.export_event' => 'OnNew',
 				'pass' => 'all,' . $this->Prefix . '.export'
 			);
 
 			$event->setRedirectParams($redirect_params);
 		}
 
 		/**
 		 * Apply some special processing to object being
 		 * recalled before using it in other events that
 		 * call prepareObject
 		 *
 		 * @param kDBItem|kDBList $object
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function prepareObject(&$object, kEvent $event)
 		{
 			if ( $event->Special == 'export' || $event->Special == 'import' ) {
 				$export_helper = $this->Application->recallObject('CatItemExportHelper');
 				/* @var $export_helper kCatDBItemExportHelper */
 
 				$export_helper->prepareExportColumns($event);
 			}
 		}
 
 		/**
 		 * Returns specific to each item type columns only
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 * @access public
 		 */
 		public function getCustomExportColumns(kEvent $event)
 		{
 			return Array();
 		}
 
 		/**
 		 * Export form validation & processing
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnExportBegin(kEvent $event)
 		{
 			$export_helper = $this->Application->recallObject('CatItemExportHelper');
 			/* @var $export_helper kCatDBItemExportHelper */
 
 			$export_helper->OnExportBegin($event);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnExportCancel(kEvent $event)
 		{
 			$this->OnGoBack($event);
 		}
 
 		/**
 		 * Allows configuring export options
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeExportBegin(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Deletes export preset
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnDeleteExportPreset(kEvent $event)
 		{
 			$field_values = $this->getSubmittedFields($event);
 
 			if ( !$field_values ) {
 				return ;
 			}
 
 			$preset_key = $field_values['ExportPresets'];
 			$export_settings = $this->Application->RecallPersistentVar('export_settings');
 
 			if ( !$export_settings ) {
 				return ;
 			}
 
 			$export_settings = unserialize($export_settings);
 
 			if ( !isset($export_settings[$event->Prefix]) ) {
 				return ;
 			}
 
 			$to_delete = '';
 
 			foreach ($export_settings[$event->Prefix] as $key => $val) {
 				if ( implode('|', $val['ExportColumns']) == $preset_key ) {
 					$to_delete = $key;
 					break;
 				}
 			}
 
 			if ( $to_delete ) {
 				unset($export_settings[$event->Prefix][$to_delete]);
 				$this->Application->StorePersistentVar('export_settings', serialize($export_settings));
 			}
 		}
 
 		/**
 		 * Saves changes & changes language
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveAndChangeLanguage(kEvent $event)
 		{
 			if ( $this->UseTempTables($event) ) {
 				$event->CallSubEvent('OnPreSave');
 			}
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$this->Application->SetVar('m_lang', $this->Application->GetVar('language'));
 
 				$data = $this->Application->GetVar('st_id');
 
 				if ( $data ) {
 					$event->SetRedirectParam('st_id', $data);
 				}
 			}
 		}
 
 		/**
 		 * Used to save files uploaded via swfuploader
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUploadFile(kEvent $event)
 		{
 			$event->status = kEvent::erSTOP;
 //			define('DBG_SKIP_REPORTING', 0);
 			$default_msg = "Flash requires that we output something or it won't fire the uploadSuccess event";
 
 			if ( !$this->Application->HttpQuery->Post ) {
 				// Variables {field, id, flashsid} are always submitted through POST!
 				// When file size is larger, then "upload_max_filesize" (in php.ini),
 				// then these variables also are not submitted -> handle such case.
 				header('HTTP/1.0 413 File size exceeds allowed limit');
 				echo $default_msg;
 				return;
 			}
 
 			if ( !$this->_checkFlashUploaderPermission($event) ) {
 				// 403 Forbidden
 				header('HTTP/1.0 403 You don\'t have permissions to upload');
 				echo $default_msg;
 				return;
 			}
 
 			$value = $this->Application->GetVar('Filedata');
 
 			if ( !$value || ($value['error'] != UPLOAD_ERR_OK) ) {
 				// 413 Request Entity Too Large (file uploads disabled OR uploaded file was
 				// to large for web server to accept, see "upload_max_filesize" in php.ini)
 				header('HTTP/1.0 413 File size exceeds allowed limit');
 				echo $default_msg;
 				return;
 			}
 
 			if ( !$this->Application->isAdmin ) {
 				$value = array_map('htmlspecialchars_decode', $value);
 			}
 
 			$tmp_path = WRITEABLE . '/tmp/';
 			$fname = $value['name'];
 			$id = $this->Application->GetVar('id');
 
 			if ( $id ) {
 				$fname = $id . '_' . $fname;
 			}
 
 			$field_name = $this->Application->GetVar('field');
-			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
-			$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
-			$field_options = array_key_exists($field_name, $fields) ? $fields[$field_name] : $virtual_fields[$field_name];
+			$field_options = $this->_getUploadFieldDefinition($event, $field_name);
 
-			$upload_dir = $field_options['upload_dir'];
 			$storage_format = array_key_exists('storage_format', $field_options) ? $field_options['storage_format'] : false;
 
 			if ( !is_writable($tmp_path) ) {
 				// 500 Internal Server Error
 				// check both temp and live upload directory
 				header('HTTP/1.0 500 Write permissions not set on the server');
 				echo $default_msg;
 				return;
 			}
 
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$fname = $file_helper->ensureUniqueFilename($tmp_path, $fname);
 
 			if ( $storage_format ) {
 				$image_helper = $this->Application->recallObject('ImageHelper');
 				/* @var $image_helper ImageHelper */
 
 				move_uploaded_file($value['tmp_name'], $value['tmp_name'] . '.jpg'); // add extension, so ResizeImage can work
 				$url = $image_helper->ResizeImage($value['tmp_name'] . '.jpg', $storage_format);
 				$tmp_name = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', '/', $url);
 				rename($tmp_name, $tmp_path . $fname);
 			}
 			else {
 				move_uploaded_file($value['tmp_name'], $tmp_path . $fname);
 			}
 
 			echo preg_replace('/^' . preg_quote($id, '/') . '_/', '', $fname);
 
 			$this->deleteTempFiles($tmp_path);
 
 			if ( file_exists($tmp_path . 'resized/') ) {
 				$this->deleteTempFiles($tmp_path . 'resized/');
 			}
 		}
 
 		/**
+		 * Returns upload field definition
+		 *
+		 * @param kEvent $event
+		 * @param string $field_name
+		 * @return Array
+		 * @access protected
+		 */
+		protected function _getUploadFieldDefinition(kEvent $event, $field_name)
+		{
+			$config = $event->getUnitConfig();
+			$ret = $config->getFieldByName($field_name);
+
+			if ( !$ret ) {
+				$ret = $config->getVirtualFieldByName($field_name);
+			}
+
+			return $ret;
+		}
+
+		/**
 		 * Delete temporary files, that won't be used for sure
 		 *
 		 * @param string $path
 		 * @return void
 		 * @access protected
 		 */
 		protected function deleteTempFiles($path)
 		{
 			$files = glob($path . '*.*');
 			$max_file_date = strtotime('-1 day');
 
 			foreach ($files as $file) {
 				if (filemtime($file) < $max_file_date) {
 					unlink($file);
 				}
 			}
 		}
 
 		/**
 		 * Checks, that flash uploader is allowed to perform upload
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		protected function _checkFlashUploaderPermission(kEvent $event)
 		{
 			// Flash uploader does NOT send correct cookies, so we need to make our own check
 			$cookie_name = 'adm_' . $this->Application->ConfigValue('SessionCookieName');
 			$this->Application->HttpQuery->Cookie['cookies_on'] = 1;
 			$this->Application->HttpQuery->Cookie[$cookie_name] = $this->Application->GetVar('flashsid');
 
 			// this prevents session from auto-expiring when KeepSessionOnBrowserClose & FireFox is used
 			$this->Application->HttpQuery->Cookie[$cookie_name . '_live'] = $this->Application->GetVar('flashsid');
 
 			$admin_ses = $this->Application->recallObject('Session.admin');
 			/* @var $admin_ses Session */
 
 			if ( $admin_ses->RecallVar('user_id') == USER_ROOT ) {
 				return true;
 			}
 
 			// copy some data from given session to current session
 			$backup_user_id = $this->Application->RecallVar('user_id');
 			$this->Application->StoreVar('user_id', $admin_ses->RecallVar('user_id'));
 
 			$backup_user_groups = $this->Application->RecallVar('UserGroups');
 			$this->Application->StoreVar('UserGroups', $admin_ses->RecallVar('UserGroups'));
 
 			// check permissions using event, that have "add|edit" rule
 			$check_event = new kEvent($event->getPrefixSpecial() . ':OnProcessSelected');
 			$check_event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
 			$allowed_to_upload = $this->CheckPermission($check_event);
 
 			// restore changed data, so nothing gets saved to database
 			$this->Application->StoreVar('user_id', $backup_user_id);
 			$this->Application->StoreVar('UserGroups', $backup_user_groups);
 
 			return $allowed_to_upload;
 		}
 
 		/**
 		 * Remembers, that file should be deleted on item's save from temp table
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnDeleteFile(kEvent $event)
 		{
 			$event->status = kEvent::erSTOP;
 			$filename = $this->_getUploadedFileInfo($event, 'full_path');
 
 			if ( $filename === false ) {
 				return;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$var_name = $object->getPendingActionVariableName();
 			$schedule = $this->Application->RecallVar($var_name);
 			$schedule = $schedule ? unserialize($schedule) : Array ();
 			$schedule[] = Array ('action' => 'delete', 'file' => $filename);
 
 			$this->Application->StoreVar($var_name, serialize($schedule));
 		}
 
 		/**
 		 * Returns url for viewing uploaded file
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnViewFile(kEvent $event)
 		{
 			$event->status = kEvent::erSTOP;
 
 			if ( $this->Application->GetVar('thumb') ) {
 				$object = $event->getObject(Array ('skip_autoload' => true));
 				/* @var $object kDBItem */
 
 				$field = $this->Application->GetVar('field');
 				$url = $this->_getUploadedFileInfo($event, $object->GetFieldOption($field, 'thumb_format'));
 			}
 			else {
 				$url = $this->_getUploadedFileInfo($event, 'full_url');
 			}
 
 			if ( $url === false ) {
 				return;
 			}
 
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$path = $file_helper->urlToPath($url);
 
 			if ( !file_exists($path) ) {
 				exit;
 			}
 
 			header('Content-Length: ' . filesize($path));
 			$this->Application->setContentType(kUtil::mimeContentType($path), false);
 			header('Content-Disposition: inline; filename="' . basename($path) . '"');
 
 			readfile($path);
 		}
 
 		/**
 		 * Returns information about uploaded file
 		 *
 		 * @param kEvent $event
 		 * @param string $format
 		 * @return bool
 		 * @access protected
 		 */
 		protected function _getUploadedFileInfo(kEvent $event, $format)
 		{
 			$file = $this->Application->GetVar('file');
 
 			if ( !$this->Application->isAdmin ) {
 				$file = htmlspecialchars_decode($file);
 			}
 
 			if ( (strpos($file, '../') !== false) || (trim($file) !== $file) ) {
 				// when relative paths or special chars are found template names from url, then it's hacking attempt
 				return false;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$field = $this->Application->GetVar('field');
 			$options = $object->GetFieldOptions($field);
 
 			// set current uploaded file
 			if ( $this->Application->GetVar('tmp') ) {
 				$options['upload_dir'] = WRITEBALE_BASE . '/tmp/';
 				unset($options['include_path']);
 				$object->SetFieldOptions($field, $options);
 
 				$object->SetDBField($field, $this->Application->GetVar('id') . '_' . $file);
 			}
 			else {
 				$object->SetDBField($field, $file);
 			}
 
 			return $object->GetField($field, $format);
 		}
 
 		/**
 		 * Validates MInput control fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnValidateMInputFields(kEvent $event)
 		{
 			$minput_helper = $this->Application->recallObject('MInputHelper');
 			/* @var $minput_helper MInputHelper */
 
 			$minput_helper->OnValidateMInputFields($event);
 		}
 
 		/**
 		 * Validates individual object field and returns the result
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnValidateField(kEvent $event)
 		{
 			$event->status = kEvent::erSTOP;
 			$field = $this->Application->GetVar('field');
 
 			if ( ($this->Application->GetVar('ajax') != 'yes') || !$field ) {
 				return;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 			if ( !$items_info ) {
 				return;
 			}
 
 			list ($id, $field_values) = each($items_info);
 			$object->Load($id);
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 			$object->setID($id);
 
 			$response = Array ('status' => 'OK');
 
 			$event->CallSubEvent($object->isLoaded() ? 'OnBeforeItemUpdate' : 'OnBeforeItemCreate');
 
 			// validate all fields, since "Password_plain" field sets error to "Password" field, which is passed here
 			$error_field = $object->GetFieldOption($field, 'error_field', false, $field);
 
 			if ( !$object->Validate() && $object->GetErrorPseudo($error_field) ) {
 				$response['status'] = $object->GetErrorMsg($error_field);
 			}
 
 			$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
 			/* @var $ajax_form_helper AjaxFormHelper */
 
 			$response['other_errors'] = $ajax_form_helper->getErrorMessages($object);
 
 			$event->status = kEvent::erSTOP; // since event's OnBefore... events can change this event status
 			echo json_encode($response);
 		}
 
 		/**
 		 * Returns auto-complete values for ajax-dropdown
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSuggestValues(kEvent $event)
 		{
 			if ( !$this->Application->isAdminUser ) {
 				// very careful here, because this event allows to
 				// view every object field -> limit only to logged-in admins
 				return;
 			}
 
 			$event->status = kEvent::erSTOP;
 
 			$field = $this->Application->GetVar('field');
 			$cur_value = $this->Application->GetVar('cur_value');
-			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
 
 			$object = $event->getObject();
 
 			if ( !$field || !$cur_value || !$object->isField($field) ) {
 				return;
 			}
 
 			$limit = $this->Application->GetVar('limit');
 			if ( !$limit ) {
 				$limit = 20;
 			}
 
 			$sql = 'SELECT DISTINCT ' . $field . '
-					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+					FROM ' . $event->getUnitConfig()->getTableName() . '
 					WHERE ' . $field . ' LIKE ' . $this->Conn->qstr($cur_value . '%') . '
 					ORDER BY ' . $field . '
 					LIMIT 0,' . $limit;
 			$data = $this->Conn->GetCol($sql);
 
 			$this->Application->XMLHeader();
 
 			echo '<suggestions>';
 
 			foreach ($data as $item) {
 				echo '<item>' . htmlspecialchars($item, null, CHARSET) . '</item>';
 			}
 
 			echo '</suggestions>';
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSaveWidths(kEvent $event)
 		{
 			$event->status = kEvent::erSTOP;
 
 //			$this->Application->setContentType('text/xml');
 
 			$picker_helper = $this->Application->recallObject('ColumnPickerHelper');
 			/* @var $picker_helper kColumnPickerHelper */
 
 			$picker_helper->PreparePicker($event->getPrefixSpecial(), $this->Application->GetVar('grid_name'));
 			$picker_helper->SaveWidths($event->getPrefixSpecial(), $this->Application->GetVar('widths'));
 
 			echo 'OK';
 		}
 
 		/**
 		 * Called from CSV import script after item fields
 		 * are set and validated, but before actual item create/update.
 		 * If event status is kEvent::erSUCCESS, line will be imported,
 		 * else it will not be imported but added to skipped lines
 		 * and displayed in the end of import.
 		 * Event status is preset from import script.
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeCSVLineImport(kEvent $event)
 		{
 			// abstract, for hooking
 		}
 
 		/**
 		 * [HOOK] Allows to add cloned subitem to given prefix
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCloneSubItem(kEvent $event)
 		{
-			$clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones');
+			$sub_item_prefix = $event->Prefix . '-' . preg_replace('/^#/', '', $event->MasterEvent->Prefix);
 
-			$subitem_prefix = $event->Prefix . '-' . preg_replace('/^#/', '', $event->MasterEvent->Prefix);
-			$clones[$subitem_prefix] = Array ('ParentPrefix' => $event->Prefix);
-			$this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones);
+			$event->MasterEvent->getUnitConfig()->addClones(Array (
+				$sub_item_prefix => Array ('ParentPrefix' => $event->Prefix),
+			));
 		}
 
 		/**
 		 * Returns constrain for priority calculations
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @see PriorityEventHandler
 		 * @access protected
 		 */
 		protected function OnGetConstrainInfo(kEvent $event)
 		{
 			$event->setEventParam('constrain_info', Array ('', ''));
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/kernel/db/dbitem.php
===================================================================
--- branches/5.3.x/core/kernel/db/dbitem.php	(revision 15697)
+++ branches/5.3.x/core/kernel/db/dbitem.php	(revision 15698)
@@ -1,1561 +1,1563 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 /**
 * DBItem
 *
 */
 class kDBItem extends kDBBase {
 
 	/**
 	* Description
 	*
 	* @var array Associative array of current item' field values
 	* @access protected
 	*/
 	protected $FieldValues = Array ();
 
 	/**
 	 * Unformatted field values, before parse
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $DirtyFieldValues = Array ();
 
 	/**
 	 * Holds item values after loading (not affected by submit)
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $OriginalFieldValues = Array ();
 
 	/**
 	* If set to true, Update will skip Validation before running
 	*
 	* @var array Associative array of current item' field values
 	* @access public
 	*/
 	public $IgnoreValidation = false;
 
 	/**
 	 * Remembers if object was loaded
 	 *
 	 * @var bool
 	 * @access protected
 	 */
 	protected $Loaded = false;
 
 	/**
 	* Holds item' primary key value
 	*
 	* @var int Value of primary key field for current item
 	* @access protected
 	*/
 	protected $ID;
 
 	/**
 	 * This object is used in cloning operations
 	 *
 	 * @var bool
 	 * @access public
 	 */
 	public $inCloning = false;
 
 	/**
 	 * Validator object reference
 	 *
 	 * @var kValidator
 	 */
 	protected $validator = null;
 
 	/**
 	 * Creates validator object, only when required
 	 *
 	 */
 	public function initValidator()
 	{
 		if ( !is_object($this->validator) ) {
-			$validator_class = $this->Application->getUnitOption($this->Prefix, 'ValidatorClass', 'kValidator');
+			$validator_class = $this->getUnitConfig()->getValidatorClass('kValidator');
 
 			$this->validator = $this->Application->makeClass($validator_class);
 		}
 
 		$this->validator->setDataSource($this);
 	}
 
 	public function SetDirtyField($field_name, $field_value)
 	{
 		$this->DirtyFieldValues[$field_name] = $field_value;
 	}
 
 	public function GetDirtyField($field_name)
 	{
 		return $this->DirtyFieldValues[$field_name];
 	}
 
 	public function GetOriginalField($field_name, $formatted = false, $format=null)
 	{
 		if (array_key_exists($field_name, $this->OriginalFieldValues)) {
 			// item was loaded before
 			$value = $this->OriginalFieldValues[$field_name];
 		}
 		else {
 			// no original fields -> use default field value
 			$value = $this->Fields[$field_name]['default'];
 		}
 
 		if (!$formatted) {
 			return $value;
 		}
 
 		$res = $value;
 		$formatter = $this->GetFieldOption($field_name, 'formatter');
 
 		if ( $formatter ) {
 			$formatter = $this->Application->recallObject($formatter);
 			/* @var $formatter kFormatter */
 
 			$res = $formatter->Format($value, $field_name, $this, $format);
 		}
 
 		return $res;
 	}
 
 	/**
 	 * Sets original field value (useful for custom virtual fields)
 	 *
 	 * @param string $field_name
 	 * @param string $field_value
 	 */
 	public function SetOriginalField($field_name, $field_value)
 	{
 		$this->OriginalFieldValues[$field_name] = $field_value;
 	}
 
 	/**
 	 * Set's default values for all fields
 	 *
 	 * @access public
 	 */
 	public function SetDefaultValues()
 	{
 		parent::SetDefaultValues();
 
 		if ($this->populateMultiLangFields) {
 			$this->PopulateMultiLangFields();
 		}
 
 		foreach ($this->Fields as $field => $field_options) {
 			$default_value = isset($field_options['default']) ? $field_options['default'] : NULL;
 			$this->SetDBField($field, $default_value);
 		}
 	}
 
 	/**
 	* Sets current item field value
 	* (applies formatting)
 	*
 	* @access public
 	* @param string $name Name of the field
 	* @param mixed $value Value to set the field to
 	* @return void
 	*/
 	public function SetField($name,$value)
 	{
 		$options = $this->GetFieldOptions($name);
 		$parsed = $value;
 		if ($value == '') {
 			$parsed = NULL;
 		}
 
 		// kFormatter is always used, to make sure, that numeric value is converted to normal representation
 		// according to regional format, even when formatter is not set (try seting format to 1.234,56 to understand why)
 		$formatter = $this->Application->recallObject(isset($options['formatter']) ? $options['formatter'] : 'kFormatter');
 		/* @var $formatter kFormatter */
 
 		$parsed = $formatter->Parse($value, $name, $this);
 
 		$this->SetDBField($name,$parsed);
 	}
 
 	/**
 	* Sets current item field value
 	* (doesn't apply formatting)
 	*
 	* @access public
 	* @param string $name Name of the field
 	* @param mixed $value Value to set the field to
 	* @return void
 	*/
 	public function SetDBField($name,$value)
 	{
 		$this->FieldValues[$name] = $value;
 	}
 
 	/**
 	 * Set's field error, if pseudo passed not found then create it with message text supplied.
 	 * Don't overwrite existing pseudo translation.
 	 *
 	 * @param string $field
 	 * @param string $pseudo
 	 * @param string $error_label
 	 * @param Array $error_params
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function SetError($field, $pseudo, $error_label = null, $error_params = null)
 	{
 		$this->initValidator();
 
 		return $this->validator->SetError($field, $pseudo, $error_label, $error_params);
 	}
 
 	/**
 	 * Removes error on field
 	 *
 	 * @param string $field
 	 * @access public
 	 */
 	public function RemoveError($field)
 	{
 		if ( !is_object($this->validator) ) {
 			return ;
 		}
 
 		$this->validator->RemoveError($field);
 	}
 
 	/**
 	 * Returns error pseudo
 	 *
 	 * @param string $field
 	 * @return string
 	 */
 	public function GetErrorPseudo($field)
 	{
 		if ( !is_object($this->validator) ) {
 			return '';
 		}
 
 		return $this->validator->GetErrorPseudo($field);
 	}
 
 	/**
 	* Return current item' field value by field name
 	* (doesn't apply formatter)
 	*
 	* @param string $name field name to return
 	* @return mixed
 	* @access public
 	*/
 	public function GetDBField($name)
 	{
 		/*if (!array_key_exists($name, $this->FieldValues) && defined('DEBUG_MODE') && DEBUG_MODE) {
 			$this->Application->Debugger->appendTrace();
 		}*/
 
 		return $this->FieldValues[$name];
 	}
 
 	public function HasField($name)
 	{
 		return array_key_exists($name, $this->FieldValues);
 	}
 
 	public function GetFieldValues()
 	{
 		return $this->FieldValues;
 	}
 
 	/**
 	 * Sets item' fields corresponding to elements in passed $hash values.
 	 *
 	 * The function sets current item fields to values passed in $hash, by matching $hash keys with field names
 	 * of current item. If current item' fields are unknown {@link kDBItem::PrepareFields()} is called before actually setting the fields
 	 *
 	 * @param Array $hash
 	 * @param Array $skip_fields Optional param, field names in target object not to set, other fields will be set
 	 * @param Array $set_fields Optional param, field names in target object to set, other fields will be skipped
 	 * @return void
 	 * @access public
 	 */
 	public function SetFieldsFromHash($hash, $skip_fields = Array (), $set_fields = Array ())
 	{
 		if ( !$set_fields ) {
 			$set_fields = array_keys($hash);
 		}
 
 		if ( $skip_fields ) {
 			$set_fields = array_diff($set_fields, $skip_fields);
 		}
 
 		$set_fields = array_intersect($set_fields, array_keys($this->Fields));
 
 		// used in formatter which work with multiple fields together
 		foreach ($set_fields as $field_name) {
 			$this->SetDirtyField($field_name, $hash[$field_name]);
 		}
 
 		// formats all fields using associated formatters
 		foreach ($set_fields as $field_name) {
 			$this->SetField($field_name, $hash[$field_name]);
 		}
 	}
 
 	/**
 	 * Sets object fields from $hash array
 	 * @param Array $hash
 	 * @param Array|null $skip_fields
 	 * @param Array|null $set_fields
 	 * @return void
 	 * @access public
 	 */
 	public function SetDBFieldsFromHash($hash, $skip_fields = Array (), $set_fields = Array ())
 	{
 		if ( !$set_fields ) {
 			$set_fields = array_keys($hash);
 		}
 
 		if ( $skip_fields ) {
 			$set_fields = array_diff($set_fields, $skip_fields);
 		}
 
 		$set_fields = array_intersect($set_fields, array_keys($this->Fields));
 
 		foreach ($set_fields as $field_name) {
 			$this->SetDBField($field_name, $hash[$field_name]);
 		}
 	}
 
 	/**
 	 * Returns part of SQL WHERE clause identifying the record, ex. id = 25
 	 *
 	 * @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method
 	 * @param Array $keys_hash alternative, then item id, keys hash to load item by
 	 * @see kDBItem::Load()
 	 * @see kDBItem::Update()
 	 * @see kDBItem::Delete()
 	 * @return string
 	 * @access protected
 	 */
 	protected function GetKeyClause($method = null, $keys_hash = null)
 	{
 		if ( !isset($keys_hash) ) {
 			$keys_hash = Array ($this->IDField => $this->ID);
 		}
 
 		$ret = '';
 
 		foreach ($keys_hash as $field => $value) {
 			$value_part = is_null($value) ? ' IS NULL' : ' = ' . $this->Conn->qstr($value);
 
 			$ret .= '(' . (strpos($field, '.') === false ? '`' . $this->TableName . '`.' : '') . $field . $value_part . ') AND ';
 		}
 
 		return substr($ret, 0, -5);
 	}
 
 	/**
 	 * Loads item from the database by given id
 	 *
 	 * @access public
 	 * @param mixed $id item id of keys->values hash to load item by
 	 * @param string $id_field_name Optional parameter to load item by given Id field
 	 * @param bool $cachable cache this query result based on it's prefix serial
 	 * @return bool True if item has been loaded, false otherwise
 	 */
 	public function Load($id, $id_field_name = null, $cachable = false)
 	{
 		if ( isset($id_field_name) ) {
 			$this->IDField = $id_field_name; // set new IDField
 		}
 
 		$keys_sql = '';
 		if (is_array($id)) {
 			$keys_sql = $this->GetKeyClause('load', $id);
 		}
 		else {
 			$this->setID($id);
 			$keys_sql = $this->GetKeyClause('load');
 		}
 
 		if ( isset($id_field_name) ) {
 			// restore original IDField from unit config
-			$this->IDField = $this->Application->getUnitOption($this->Prefix, 'IDField');
+			$this->IDField = $this->getUnitConfig()->getIDField();
 		}
 
 		if (($id === false) || !$keys_sql) {
 			return $this->Clear();
 		}
 
 		if (!$this->raiseEvent('OnBeforeItemLoad', $id)) {
 			return false;
 		}
 
 		$q = $this->GetSelectSQL() . ' WHERE ' . $keys_sql;
 
 		if ($cachable && $this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$serial_name = $this->Application->incrementCacheSerial($this->Prefix == 'st' ? 'c' : $this->Prefix, isset($id_field_name) ? null : $id, false);
 			$cache_key = 'kDBItem::Load_' . crc32(serialize($id) . '-' . $this->IDField) . '[%' . $serial_name . '%]';
 			$field_values = $this->Application->getCache($cache_key, false);
 
 			if ($field_values === false) {
 				$field_values = $this->Conn->GetRow($q);
 
 				if ($field_values !== false) {
 					// only cache, when data was retrieved
 					$this->Application->setCache($cache_key, $field_values);
 				}
 			}
 		}
 		else {
 			$field_values = $this->Conn->GetRow($q);
 		}
 
 		if ($field_values) {
 			$this->FieldValues = array_merge($this->FieldValues, $field_values);
 			$this->OriginalFieldValues = $this->FieldValues;
 		}
 		else {
 			return $this->Clear();
 		}
 
 		if (is_array($id) || isset($id_field_name)) {
 			$this->setID($this->FieldValues[$this->IDField]);
 		}
 
 		$this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example)
 
 		$this->raiseEvent('OnAfterItemLoad', $this->GetID());
 		$this->Loaded = true;
 
 		return true;
 	}
 
 	/**
 	 * Loads object from hash (not db)
 	 *
 	 * @param Array $fields_hash
 	 * @param string $id_field
 	 */
 	public function LoadFromHash($fields_hash, $id_field = null)
 	{
 		if (!isset($id_field)) {
 			$id_field = $this->IDField;
 		}
 
 		$this->Clear();
 
 		if (!$fields_hash || !array_key_exists($id_field, $fields_hash)) {
 			// no data OR id field missing
 			return false;
 		}
 
 		$id = $fields_hash[$id_field];
 
 		if ( !$this->raiseEvent('OnBeforeItemLoad', $id) ) {
 			return false;
 		}
 
 		$this->FieldValues = array_merge($this->FieldValues, $fields_hash);
 		$this->OriginalFieldValues = $this->FieldValues;
 
 		$this->setID($id);
 		$this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example)
 
 		$this->raiseEvent('OnAfterItemLoad', $id);
 
 		$this->Loaded = true;
 
 		return true;
 	}
 
 	/**
 	* Builds select sql, SELECT ... FROM parts only
 	*
 	* @access public
 	* @return string
 	*/
 
 	/**
 	 * Returns SELECT part of list' query
 	 *
 	 * @param string $base_query
 	 * @param bool $replace_table
 	 * @return string
 	 * @access public
 	 */
 	public function GetSelectSQL($base_query = null, $replace_table = true)
 	{
 		if (!isset($base_query)) {
 			$base_query = $this->SelectClause;
 		}
 
 		$base_query = $this->addCalculatedFields($base_query);
 
 		return parent::GetSelectSQL($base_query, $replace_table);
 	}
 
 	public function UpdateFormattersMasterFields()
 	{
 		$this->initValidator(); // used, when called not from kValidator::Validate method
 
 		foreach ($this->Fields as $field => $options) {
 			if ( isset($options['formatter']) ) {
 				$formatter = $this->Application->recallObject($options['formatter']);
 				/* @var $formatter kFormatter */
 
 				$formatter->UpdateMasterFields($field, $this->GetDBField($field), $options, $this);
 			}
 		}
 	}
 
 	/**
 	 * Actually moves uploaded files from temp to live folder
 	 *
 	 * @param int $id
 	 * @return void
 	 * @access public
 	 */
 	public function processUploads($id = NULL)
 	{
 		$changed_fields = Array ();
 		$uploader_fields = $this->getUploaderFields();
 
 		foreach ($uploader_fields as $field) {
 			$formatter = $this->Application->recallObject($this->GetFieldOption($field, 'formatter'));
 			/* @var $formatter kUploadFormatter */
 
 			$changed_fields = array_merge($changed_fields, $formatter->processFlashUpload($this, $field, $id));
 		}
 
 		if ( $changed_fields ) {
 			$this->Update(null, array_unique($changed_fields));
 		}
 	}
 
 	/**
 	 * Removes any info about queued uploaded files
 	 *
 	 * @param int $id
 	 * @return void
 	 * @access public
 	 */
 	public function resetUploads($id = NULL)
 	{
 		$uploader_fields = $this->getUploaderFields();
 
 		foreach ($uploader_fields as $field) {
 			$this->Application->RemoveVar($this->getFileInfoVariableName($field, $id));
 		}
 	}
 
 	/**
 	 * Returns uploader fields
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function getUploaderFields()
 	{
 		$ret = Array ();
 
 		foreach ($this->Fields as $field => $options) {
 			if ( !isset($options['formatter']) ) {
 				continue;
 			}
 
 			$formatter = $this->Application->recallObject($options['formatter']);
 			/* @var $formatter kUploadFormatter */
 
 			if ( $formatter instanceof kUploadFormatter ) {
 				$ret[] = $field;
 			}
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns variable name, used to store pending file actions
 	 *
 	 * @return string
 	 * @access public
 	 */
 	public function getPendingActionVariableName()
 	{
 		$window_id = $this->Application->GetTopmostWid($this->Prefix);
 
 		return $this->Prefix . '_file_pending_actions' . $window_id;
 	}
 
 	/**
 	 * Returns variable name, which stores file information for object/field/window combination
 	 *
 	 * @param string $field_name
 	 * @param int $id
 	 * @return string
 	 * @access public
 	 */
 	public function getFileInfoVariableName($field_name, $id = NULL)
 	{
 		if ( !isset($id) ) {
 			$id = $this->GetID();
 		}
 
 		$window_id = $this->Application->GetTopmostWid($this->Prefix);
 
 		return $this->Prefix . '[' . $id . '][' . $field_name . ']_file_info' . $window_id;
 	}
 
 	/**
 	 * Allows to skip certain fields from getting into sql queries
 	 *
 	 * @param string $field_name
 	 * @param mixed $force_id
 	 * @return bool
 	 */
 	public function skipField($field_name, $force_id = false)
 	{
 		$skip = false;
 
 		// 1. skipping 'virtual' field
 		$skip = $skip || array_key_exists($field_name, $this->VirtualFields);
 
 		// 2. don't write empty field value to db, when "skip_empty" option is set
 		$field_value = array_key_exists($field_name, $this->FieldValues) ? $this->FieldValues[$field_name] : false;
 
 		if (array_key_exists($field_name, $this->Fields)) {
 			$skip_empty = array_key_exists('skip_empty', $this->Fields[$field_name]) ? $this->Fields[$field_name]['skip_empty'] : false;
 		}
 		else {
 			// field found in database, but not declared in unit config
 			$skip_empty = false;
 		}
 
 		$skip = $skip || (!$field_value && $skip_empty);
 
 		// 3. skipping field not in Fields (nor virtual, nor real)
 		$skip = $skip || !array_key_exists($field_name, $this->Fields);
 
 		return $skip;
 	}
 
 	/**
 	 * Updates previously loaded record with current item' values
 	 *
 	 * @access public
 	 * @param int $id Primary Key Id to update
 	 * @param Array $update_fields
 	 * @param bool $system_update
 	 * @return bool
 	 * @access public
 	 */
 	public function Update($id = null, $update_fields = null, $system_update = false)
 	{
 		if ( isset($id) ) {
 			$this->setID($id);
 		}
 
 		if ( !$this->raiseEvent('OnBeforeItemUpdate') ) {
 			return false;
 		}
 
 		if ( !isset($this->ID) ) {
 			// ID could be set inside OnBeforeItemUpdate event, so don't combine this check with previous one
 			return false;
 		}
 
 		// validate before updating
 		if ( !$this->Validate() ) {
 			return false;
 		}
 
 		if ( !$this->FieldValues ) {
 			// nothing to update
 			return true;
 		}
 
 		$sql = '';
 
 		$set_fields = isset($update_fields) ? $update_fields : array_keys($this->FieldValues);
 
 		foreach ($set_fields as $field_name) {
 			if ( $this->skipField($field_name) ) {
 				continue;
 			}
 
 			$field_value = $this->FieldValues[$field_name];
 
 			if ( is_null($field_value) ) {
 				if ( array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null'] ) {
 					// "kFormatter::Parse" methods converts empty values to NULL and for
 					// not-null fields they are replaced with default value here
 					$field_value = $this->Fields[$field_name]['default'];
 				}
 			}
 
 			$sql .= '`' . $field_name . '` = ' . $this->Conn->qstr($field_value) . ', ';
 		}
 
 		$sql = 'UPDATE ' . $this->TableName . '
 				SET ' . substr($sql, 0, -2) . '
 				WHERE ' . $this->GetKeyClause('update');
 
 		if ( $this->Conn->ChangeQuery($sql) === false ) {
 			// there was and sql error
 			$this->SetError($this->IDField, 'sql_error', '#' . $this->Conn->getErrorCode() . ': ' . $this->Conn->getErrorMsg());
 			return false;
 		}
 
 		$affected_rows = $this->Conn->getAffectedRows();
 
 		if ( !$system_update && ($affected_rows > 0) ) {
 			$this->setModifiedFlag(ChangeLog::UPDATE);
 		}
 
 		$this->saveCustomFields();
 		$this->raiseEvent('OnAfterItemUpdate');
 
 		if ( !isset($update_fields) ) {
 			$this->OriginalFieldValues = $this->FieldValues;
 		}
 		else {
 			foreach ($update_fields as $update_field) {
 				$this->OriginalFieldValues[$update_field] = $this->FieldValues[$update_field];
 			}
 		}
 
 		$this->Loaded = true;
 
 		if ( !$this->IsTempTable() ) {
 			$this->Application->resetCounters($this->TableName);
 		}
 
 		return true;
 	}
 
 	/**
 	 * Validates given field
 	 *
 	 * @param string $field
 	 * @return bool
 	 * @access public
 	 */
 	public function ValidateField($field)
 	{
 		$this->initValidator();
 
 		return $this->validator->ValidateField($field);
 	}
 
 	/**
 	 * Validate all item fields based on
 	 * constraints set in each field options
 	 * in config
 	 *
 	 * @return bool
 	 * @access private
 	 */
 	public function Validate()
 	{
 		if ( $this->IgnoreValidation ) {
 			return true;
 		}
 
 		$this->initValidator();
 
 		// will apply any custom validation to the item
 		$this->raiseEvent('OnBeforeItemValidate');
 
 		if ( $this->validator->Validate() ) {
 			// no validation errors
 			$this->raiseEvent('OnAfterItemValidate');
 
 			return true;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Check if item has errors
 	 *
 	 * @param Array $skip_fields fields to skip during error checking
 	 * @return bool
 	 */
 	public function HasErrors($skip_fields = Array ())
 	{
 		if ( !is_object($this->validator) ) {
 			return false;
 		}
 
 		return $this->validator->HasErrors($skip_fields);
 	}
 
 	/**
 	 * Check if value is set for required field
 	 *
 	 * @param string $field field name
 	 * @param Array $params field options from config
 	 * @return bool
 	 * @access public
 	 * @todo Find a way to get rid of direct call from kMultiLanguage::UpdateMasterFields method
 	 */
 	public function ValidateRequired($field, $params)
 	{
 		return $this->validator->ValidateRequired($field, $params);
 	}
 
 	/**
 	 * Return error message for field
 	 *
 	 * @param string $field
 	 * @param bool $force_escape
 	 * @return string
 	 * @access public
 	 */
 	public function GetErrorMsg($field, $force_escape = null)
 	{
 		if ( !is_object($this->validator) ) {
 			return '';
 		}
 
 		return $this->validator->GetErrorMsg($field, $force_escape);
 	}
 
 	/**
 	 * Returns field errors
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function GetFieldErrors()
 	{
 		if ( !is_object($this->validator) ) {
 			return Array ();
 		}
 
 		return $this->validator->GetFieldErrors();
 	}
 
 	/**
 	 * Creates a record in the database table with current item' values
 	 *
 	 * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE!
 	 * @param bool $system_create
 	 * @return bool
 	 * @access public
 	 */
 	public function Create($force_id = false, $system_create = false)
 	{
 		if (!$this->raiseEvent('OnBeforeItemCreate')) {
 			return false;
 		}
 
 		// Validating fields before attempting to create record
 		if (!$this->Validate()) {
 			return false;
 		}
 
 		if (is_int($force_id)) {
 			$this->FieldValues[$this->IDField] = $force_id;
 		}
 		elseif (!$force_id || !is_bool($force_id)) {
 			$this->FieldValues[$this->IDField] = $this->generateID();
 		}
 
 		$fields_sql = '';
 		$values_sql = '';
 		foreach ($this->FieldValues as $field_name => $field_value) {
 			if ($this->skipField($field_name, $force_id)) {
 				continue;
 			}
 
 			if (is_null($field_value)) {
 				if (array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null']) {
 					// "kFormatter::Parse" methods converts empty values to NULL and for
 					// not-null fields they are replaced with default value here
 					$values_sql .= $this->Conn->qstr($this->Fields[$field_name]['default']);
 				}
 				else {
 					$values_sql .= $this->Conn->qstr($field_value);
 				}
 			}
 			else {
 				if (($field_name == $this->IDField) && ($field_value == 0) && !is_int($force_id)) {
 					// don't skip IDField in INSERT statement, just use DEFAULT keyword as it's value
 					$values_sql .= 'DEFAULT';
 				}
 				else {
 					$values_sql .= $this->Conn->qstr($field_value);
 				}
 			}
 
 			$fields_sql .= '`' . $field_name . '`, '; //Adding field name to fields block of Insert statement
 			$values_sql .= ', ';
 		}
 
 		$sql = 'INSERT INTO ' . $this->TableName . ' (' . substr($fields_sql, 0, -2) . ')
 				VALUES (' . substr($values_sql, 0, -2) . ')';
 
 		//Executing the query and checking the result
 		if ($this->Conn->ChangeQuery($sql) === false) {
 			$this->SetError($this->IDField, 'sql_error', '#' . $this->Conn->getErrorCode() . ': ' . $this->Conn->getErrorMsg());
 			return false;
 		}
 
 		$insert_id = $this->Conn->getInsertID();
 		if ($insert_id == 0) {
 			// insert into temp table (id is not auto-increment field)
 			$insert_id = $this->FieldValues[$this->IDField];
 		}
 		$this->setID($insert_id);
 
 		$this->OriginalFieldValues = $this->FieldValues;
 
 		if (!$system_create){
 			$this->setModifiedFlag(ChangeLog::CREATE);
 		}
 
 		$this->saveCustomFields();
 		if (!$this->IsTempTable()) {
 			$this->Application->resetCounters($this->TableName);
 		}
 
 		if ($this->IsTempTable() && ($this->Application->GetTopmostPrefix($this->Prefix) != $this->Prefix) && !is_int($force_id)) {
 			// temp table + subitem = set negative id
 			$this->setTempID();
 		}
 
 		$this->raiseEvent('OnAfterItemCreate');
 		$this->Loaded = true;
 
 		return true;
 	}
 
 	/**
 	 * Deletes the record from database
 	 *
 	 * @param int $id
 	 * @return bool
 	 * @access public
 	 */
 	public function Delete($id = null)
 	{
 		if ( isset($id) ) {
 			$this->setID($id);
 		}
 
 		if ( !$this->raiseEvent('OnBeforeItemDelete') ) {
 			return false;
 		}
 
 		$sql = 'DELETE FROM ' . $this->TableName . '
 				WHERE ' . $this->GetKeyClause('Delete');
 
 		$ret = $this->Conn->ChangeQuery($sql);
 		$affected_rows = $this->Conn->getAffectedRows();
 
 		if ( $affected_rows > 0 ) {
 			$this->setModifiedFlag(ChangeLog::DELETE); // will change affected rows, so get it before this line
 
 			// something was actually deleted
 			$this->raiseEvent('OnAfterItemDelete');
 		}
 
 		if ( !$this->IsTempTable() ) {
 			$this->Application->resetCounters($this->TableName);
 		}
 
 		return $ret;
 	}
 
 	public function PopulateMultiLangFields()
 	{
 		foreach ($this->Fields as $field => $options) {
 			// master field is set only for CURRENT language
 			$formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false;
 
 			if ( ($formatter == 'kMultiLanguage') && isset($options['master_field']) && isset($options['error_field']) ) {
 				// MuliLanguage formatter sets error_field to master_field, but in PopulateMlFields mode,
 				// we display ML fields directly so we set it back to itself, otherwise error won't be displayed
 				unset( $this->Fields[$field]['error_field'] );
 			}
 		}
 	}
 
 	/**
 	 * Sets new name for item in case if it is being copied in same table
 	 *
 	 * @param array $master Table data from TempHandler
 	 * @param int $foreign_key ForeignKey value to filter name check query by
 	 * @param string $title_field FieldName to alter, by default - TitleField of the prefix
 	 * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name
 	 * @access public
 	 */
 	public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s')
 	{
-		if (!isset($title_field)) {
-			$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
-			if (!$title_field || isset($this->CalculatedFields[$title_field]) ) return;
+		if ( !isset($title_field) ) {
+			$title_field = $this->getUnitConfig()->getTitleField();
+
+			if ( !$title_field || isset($this->CalculatedFields[$title_field]) ) {
+				return;
+			}
 		}
 
-		$new_name = $this->GetDBField($title_field);
 		$original_checked = false;
+		$new_name = $this->GetDBField($title_field);
+
 		do {
-			if ( preg_match('/'.sprintf($format, '([0-9]*) *', '(.*)').'/', $new_name, $regs) ) {
-				$new_name = sprintf($format, ($regs[1]+1), $regs[2]);
+			if ( preg_match('/' . sprintf($format, '([0-9]*) *', '(.*)') . '/', $new_name, $regs) ) {
+				$new_name = sprintf($format, ($regs[1] + 1), $regs[2]);
 			}
-			elseif ($original_checked) {
+			elseif ( $original_checked ) {
 				$new_name = sprintf($format, '', $new_name);
 			}
 
 			// if we are cloning in temp table this will look for names in temp table,
 			// since object' TableName contains correct TableName (for temp also!)
 			// if we are cloning live - look in live
-			$query = 'SELECT '.$title_field.' FROM '.$this->TableName.'
-								WHERE '.$title_field.' = '.$this->Conn->qstr($new_name);
+			$sql = 'SELECT ' . $title_field . '
+					FROM ' . $this->TableName . '
+					WHERE ' . $title_field . ' = ' . $this->Conn->qstr($new_name);
 
 			$foreign_key_field = getArrayValue($master, 'ForeignKey');
-			$foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[ $master['ParentPrefix'] ] : $foreign_key_field;
+			$foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[$master['ParentPrefix']] : $foreign_key_field;
 
-			if ($foreign_key_field && isset($foreign_key)) {
-				$query .= ' AND '.$foreign_key_field.' = '.$foreign_key;
+			if ( $foreign_key_field && isset($foreign_key) ) {
+				$sql .= ' AND ' . $foreign_key_field . ' = ' . $foreign_key;
 			}
 
-			$res = $this->Conn->GetOne($query);
+			$res = $this->Conn->GetOne($sql);
 
-			/*// if not found in live table, check in temp table if applicable
-			if ($res === false && $object->Special == 'temp') {
-				$query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).'
-									WHERE '.$name_field.' = '.$this->Conn->qstr($new_name);
-				$res = $this->Conn->GetOne($query);
+			// if not found in live table, check in temp table if applicable
+			/*if ( $res === false && $this->Special == 'temp' ) {
+				$sql = 'SELECT ' . $name_field . '
+						FROM ' . $this->Application->GetTempName($master['TableName']) . '
+						WHERE ' . $name_field . ' = ' . $this->Conn->qstr($new_name);
+				$res = $this->Conn->GetOne($sql);
 			}*/
 
 			$original_checked = true;
-		} while ($res !== false);
+		} while ( $res !== false );
+
 		$this->SetDBField($title_field, $new_name);
 	}
 
 	protected function raiseEvent($name, $id = null, $additional_params = Array())
 	{
 		$additional_params['id'] = isset($id) ? $id : $this->GetID();
 		$event = new kEvent($this->getPrefixSpecial() . ':' . $name, $additional_params);
 
 		if ( is_object($this->parentEvent) ) {
 			$event->MasterEvent = $this->parentEvent;
 		}
 
 		$this->Application->HandleEvent($event);
 
 		return $event->status == kEvent::erSUCCESS;
 	}
 
 	/**
 	 * Set's new ID for item
 	 *
 	 * @param int $new_id
 	 * @access public
 	 */
 	public function setID($new_id)
 	{
 		$this->ID = $new_id;
 		$this->SetDBField($this->IDField, $new_id);
 	}
 
 	/**
 	 * Generate and set new temporary id
 	 *
 	 * @access private
 	 */
 	public function setTempID()
 	{
-		$new_id = (int)$this->Conn->GetOne('SELECT MIN('.$this->IDField.') FROM '.$this->TableName);
-		if($new_id > 0) $new_id = 0;
+		$new_id = (int)$this->Conn->GetOne('SELECT MIN(' . $this->IDField . ') FROM ' . $this->TableName);
+
+		if ( $new_id > 0 ) {
+			$new_id = 0;
+		}
+
 		--$new_id;
 
-		$this->Conn->Query('UPDATE '.$this->TableName.' SET `'.$this->IDField.'` = '.$new_id.' WHERE `'.$this->IDField.'` = '.$this->GetID());
+		$this->Conn->Query('UPDATE ' . $this->TableName . ' SET `' . $this->IDField . '` = ' . $new_id . ' WHERE `' . $this->IDField . '` = ' . $this->GetID());
 
-		if ($this->ShouldLogChanges(true)) {
+		if ( $this->ShouldLogChanges(true) ) {
 			// Updating TempId in ChangesLog, if changes are disabled
 			$ses_var_name = $this->Application->GetTopmostPrefix($this->Prefix) . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 			$changes = $this->Application->RecallVar($ses_var_name);
 			$changes = $changes ? unserialize($changes) : Array ();
 
-			if ($changes) {
+			if ( $changes ) {
 				foreach ($changes as $key => $rec) {
-					if ($rec['Prefix'] == $this->Prefix && $rec['ItemId'] == $this->GetID()) {
+					if ( $rec['Prefix'] == $this->Prefix && $rec['ItemId'] == $this->GetID() ) {
 						// change log for record, that's ID was just updated -> update in change log record too
 						$changes[$key]['ItemId'] = $new_id;
 					}
 
-					if ($rec['MasterPrefix'] == $this->Prefix && $rec['MasterId'] == $this->GetID()) {
+					if ( $rec['MasterPrefix'] == $this->Prefix && $rec['MasterId'] == $this->GetID() ) {
 						// master item id was changed
 						$changes[$key]['MasterId'] = $new_id;
 					}
 
-					if (in_array($this->Prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->Prefix] == $this->GetID()) {
+					if ( in_array($this->Prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->Prefix] == $this->GetID() ) {
 						// change log record of given item's sub item -> update changed id's in dependent fields
 						$changes[$key]['ParentId'][$this->Prefix] = $new_id;
 
-						if (array_key_exists('DependentFields', $rec)) {
+						if ( array_key_exists('DependentFields', $rec) ) {
 							// these are fields from table of $rec['Prefix'] table!
-							// when one of dependent fields goes into idfield of it's parent item, that was changed
-							$parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
-							$parent_table_key = is_array($parent_table_key) ? $parent_table_key[$this->Prefix] : $parent_table_key;
-
-							if ($parent_table_key == $this->IDField) {
-								$foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
-								$foreign_key = is_array($foreign_key) ? $foreign_key[$this->Prefix] : $foreign_key;
+							// when one of dependent fields goes into id field of it's parent item, that was changed
+							$config = $this->Application->getUnitConfig($rec['Prefix']);
+
+							$parent_table_key = $config->getParentTableKey($this->Prefix);
+
+							if ( $parent_table_key == $this->IDField ) {
+								$foreign_key = $config->getForeignKey($this->Prefix);
 
 								$changes[$key]['DependentFields'][$foreign_key] = $new_id;
 							}
 						}
 					}
 				}
 			}
 
 			$this->Application->StoreVar($ses_var_name, serialize($changes));
 		}
 
 		$this->SetID($new_id);
 	}
 
 	/**
 	 * Set's modification flag for main prefix of current prefix to true
 	 *
 	 * @param int $mode
 	 * @access private
 	 */
 	public function setModifiedFlag($mode = null)
 	{
 		$main_prefix = $this->Application->GetTopmostPrefix($this->Prefix);
 		$this->Application->StoreVar($main_prefix . '_modified', '1', true); // true for optional
 
 		if ($this->ShouldLogChanges(true)) {
 			$this->LogChanges($main_prefix, $mode);
 
 			if (!$this->IsTempTable()) {
 				$handler = $this->Application->recallObject($this->Prefix . '_EventHandler');
 				/* @var $handler kDBEventHandler */
 
 				$ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 				$handler->SaveLoggedChanges($ses_var_name, $this->ShouldLogChanges());
 			}
 		}
 	}
 
 	/**
 	 * Determines, that changes made to this item should be written to change log
 	 *
 	 * @param bool $log_changes
 	 * @return bool
 	 */
 	public function ShouldLogChanges($log_changes = null)
 	{
-		if (!isset($log_changes)) {
+		$config = $this->getUnitConfig();
+
+		if ( !isset($log_changes) ) {
 			// specific logging mode no forced -> use global logging settings
-			$log_changes = $this->Application->getUnitOption($this->Prefix, 'LogChanges') || $this->Application->ConfigValue('UseChangeLog');
+			$log_changes = $config->getLogChanges() || $this->Application->ConfigValue('UseChangeLog');
 		}
 
-		return $log_changes && !$this->Application->getUnitOption($this->Prefix, 'ForceDontLogChanges');
+		return $log_changes && !$config->getForceDontLogChanges();
 	}
 
 	protected function LogChanges($main_prefix, $mode)
 	{
 		if ( !$mode ) {
 			return ;
 		}
 
 		$ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 		$changes = $this->Application->RecallVar($ses_var_name);
 		$changes = $changes ? unserialize($changes) : Array ();
 
 		$fields_hash = Array (
 			'Prefix' => $this->Prefix,
 			'ItemId' => $this->GetID(),
 			'OccuredOn' => adodb_mktime(),
 			'MasterPrefix' => $main_prefix,
 			'Action' => $mode,
 		);
 
 		if ( $this->Prefix == $main_prefix ) {
 			// main item
 			$fields_hash['MasterId'] = $this->GetID();
 			$fields_hash['ParentPrefix'] = Array ($main_prefix);
 			$fields_hash['ParentId'] = Array ($main_prefix => $this->GetID());
 		}
 		else {
 			// sub item
 			// collect foreign key values (for serial reset)
-			$foreign_keys = $this->Application->getUnitOption($this->Prefix, 'ForeignKey', Array ());
+			$config = $this->getUnitConfig();
+			$foreign_keys = $config->getForeignKey(null, Array ());
 			$dependent_fields = $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = Array ();
 			/* @var $foreign_keys Array */
 
 			if ( is_array($foreign_keys) ) {
 				foreach ($foreign_keys as $prefix => $field_name) {
 					$dependent_fields[$field_name] = $this->GetDBField($field_name);
 					$fields_hash['ParentPrefix'][] = $prefix;
 					$fields_hash['ParentId'][$prefix] = $this->getParentId($prefix);
 				}
 			}
 			else {
 				$dependent_fields[$foreign_keys] = $this->GetDBField($foreign_keys);
-				$fields_hash['ParentPrefix'] = Array ( $this->Application->getUnitOption($this->Prefix, 'ParentPrefix') );
+				$fields_hash['ParentPrefix'] = Array ( $config->getParentPrefix() );
 				$fields_hash['ParentId'][ $fields_hash['ParentPrefix'][0] ] = $this->getParentId('auto');
 			}
 
 			$fields_hash['DependentFields'] = $dependent_fields;
 
 
 			// works only, when main item is present in url, when sub-item is changed
 			$master_id = $this->Application->GetVar($main_prefix . '_id');
 
 			if ( $master_id === false ) {
 				// works in case of we are not editing topmost item, when sub-item is created/updated/deleted
 				$master_id = $this->getParentId('auto', true);
 			}
 
 			$fields_hash['MasterId'] = $master_id;
 		}
 
 		switch ( $mode ) {
 			case ChangeLog::UPDATE:
 				$to_save = array_merge($this->GetTitleField(), $this->GetChangedFields());
 				break;
 
 			case ChangeLog::CREATE:
 				$to_save = $this->GetTitleField();
 				break;
 
 			case ChangeLog::DELETE:
 				$to_save = array_merge($this->GetTitleField(), $this->GetRealFields());
 				break;
 
 			default:
 				$to_save = Array ();
 				break;
 		}
 
 		$fields_hash['Changes'] = serialize($to_save);
 		$changes[] = $fields_hash;
 
 		$this->Application->StoreVar($ses_var_name, serialize($changes));
 	}
 
 	/**
 	 * Returns current item parent's ID
 	 *
 	 * @param string $parent_prefix
 	 * @param bool $top_most return topmost parent, when used
 	 * @return int
 	 * @access public
 	 */
 	public function getParentId($parent_prefix, $top_most = false)
 	{
 		$current_id = $this->GetID();
 		$current_prefix = $this->Prefix;
+		$current_config = $this->Application->getUnitConfig($current_prefix);
 
-		if ($parent_prefix == 'auto') {
-			$parent_prefix = $this->Application->getUnitOption($current_prefix, 'ParentPrefix');
+		if ( $parent_prefix == 'auto' ) {
+			$parent_prefix = $current_config->getParentPrefix();
 		}
 
-		if (!$parent_prefix) {
+		if ( !$parent_prefix ) {
 			return $current_id;
 		}
 
 		do {
 			// field in this table
-			$foreign_key = $this->Application->getUnitOption($current_prefix, 'ForeignKey');
-			$foreign_key = is_array($foreign_key) ? $foreign_key[$parent_prefix] : $foreign_key;
+			$foreign_key = $current_config->getForeignKey($parent_prefix);
 
 			// get foreign key value for $current_prefix
-			if ($current_prefix == $this->Prefix) {
+			if ( $current_prefix == $this->Prefix ) {
 				$foreign_key_value = $this->GetDBField($foreign_key);
 			}
 			else {
-				$id_field = $this->Application->getUnitOption($current_prefix, 'IDField');
-				$table_name = $this->Application->getUnitOption($current_prefix, 'TableName');
+				$table_name = $current_config->getTableName();
 
-				if ($this->IsTempTable()) {
+				if ( $this->IsTempTable() ) {
 					$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $current_prefix);
 				}
 
 				$sql = 'SELECT ' . $foreign_key . '
 						FROM ' . $table_name . '
-						WHERE ' . $id_field . ' = ' . $current_id;
+						WHERE ' . $current_config->getIDField() . ' = ' . $current_id;
 				$foreign_key_value = $this->Conn->GetOne($sql);
 			}
 
 			// field in parent table
-			$parent_table_key = $this->Application->getUnitOption($current_prefix, 'ParentTableKey');
-			$parent_table_key = is_array($parent_table_key) ? $parent_table_key[$parent_prefix] : $parent_table_key;
+			$parent_table_key = $current_config->getParentTableKey($parent_prefix);
 
-			$parent_id_field = $this->Application->getUnitOption($parent_prefix, 'IDField');
-			$parent_table_name = $this->Application->getUnitOption($parent_prefix, 'TableName');
+			$parent_config = $this->Application->getUnitConfig($parent_prefix);
 
-			if ($this->IsTempTable()) {
+			$parent_id_field = $parent_config->getIDField();
+			$parent_table_name = $parent_config->getTableName();
+
+			if ( $this->IsTempTable() ) {
 				$parent_table_name = $this->Application->GetTempName($parent_table_name, 'prefix:' . $current_prefix);
 			}
 
-			if ($parent_id_field == $parent_table_key) {
-				// sub-item is related by parent item idfield
+			if ( $parent_id_field == $parent_table_key ) {
+				// sub-item is related by parent item id field
 				$current_id = $foreign_key_value;
 			}
 			else {
 				// sub-item is related by other parent item field
 				$sql = 'SELECT ' . $parent_id_field . '
 						FROM ' . $parent_table_name . '
 						WHERE ' . $parent_table_key . ' = ' . $foreign_key_value;
 				$current_id = $this->Conn->GetOne($sql);
 			}
 
 			$current_prefix = $parent_prefix;
+			$current_config = $this->Application->getUnitConfig($current_prefix);
 
-			if (!$top_most) {
+			if ( !$top_most ) {
 				break;
 			}
-		} while ( $parent_prefix = $this->Application->getUnitOption($current_prefix, 'ParentPrefix') );
+		} while ( $parent_prefix = $current_config->getParentPrefix() );
 
 		return $current_id;
 	}
 
 	/**
 	 * Returns title field (if any)
 	 *
 	 * @return Array
 	 */
 	public function GetTitleField()
 	{
-		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
+		$title_field = $this->getUnitConfig()->getTitleField();
 
-		if ($title_field) {
+		if ( $title_field ) {
 			$value = $this->GetField($title_field);
+
 			return $value ? Array ($title_field => $value) : Array ();
 		}
 
 		return Array ();
 	}
 
 	/**
 	 * Returns only fields, that are present in database (no virtual and no calculated fields)
 	 *
 	 * @return Array
 	 */
 	public function GetRealFields()
 	{
 		return array_diff_key($this->FieldValues, $this->VirtualFields, $this->CalculatedFields);
 	}
 
 	/**
 	 * Returns only changed database field
 	 *
 	 * @param bool $include_virtual_fields
 	 * @return Array
 	 */
 	public function GetChangedFields($include_virtual_fields = false)
 	{
 		$changes = Array ();
 		$fields = $include_virtual_fields ? $this->FieldValues : $this->GetRealFields();
 		$diff = array_diff_assoc($fields, $this->OriginalFieldValues);
 
 		foreach ($diff as $field => $new_value) {
 			$old_value = $this->GetOriginalField($field, true);
 			$new_value = $this->GetField($field);
 
 			if ($old_value != $new_value) {
 				// "0.00" and "0.0000" are stored as strings and will differ. Double check to prevent that.
 				$changes[$field] = Array ('old' => $old_value, 'new' => $new_value);
 			}
 		}
 
 		return $changes;
 	}
 
 	/**
 	 * Returns ID of currently processed record
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function GetID()
 	{
 		return $this->ID;
 	}
 
 	/**
 	 * Generates ID for new items before inserting into database
 	 *
 	 * @return int
 	 * @access private
 	 */
 	protected function generateID()
 	{
 		return 0;
 	}
 
 	/**
 	 * Returns true if item was loaded successfully by Load method
 	 *
 	 * @return bool
 	 */
 	public function isLoaded()
 	{
 		return $this->Loaded;
 	}
 
 	/**
 	 * Checks if field is required
 	 *
 	 * @param string $field
 	 * @return bool
 	 */
 	public function isRequired($field)
 	{
 		return isset($this->Fields[$field]['required']) && $this->Fields[$field]['required'];
 	}
 
 	/**
 	 * Sets new required flag to field
 	 *
 	 * @param mixed $fields
 	 * @param bool $is_required
 	 */
 	public function setRequired($fields, $is_required = true)
 	{
 		if ( !is_array($fields) ) {
 			$fields = explode(',', $fields);
 		}
 
 		foreach ($fields as $field) {
 			$this->Fields[$field]['required'] = $is_required;
 		}
 	}
 
 	/**
 	 * Removes all data from an object
 	 *
 	 * @param int $new_id
 	 * @return bool
 	 * @access public
 	 */
 	public function Clear($new_id = null)
 	{
 		$this->Loaded = false;
 		$this->FieldValues = $this->OriginalFieldValues = Array ();
 		$this->SetDefaultValues(); // will wear off kDBItem::setID effect, so set it later
 
 		if ( is_object($this->validator) ) {
 			$this->validator->reset();
 		}
 
 		$this->setID($new_id);
 
 		return $this->Loaded;
 	}
 
 	public function Query($force = false)
 	{
 		throw new Exception('<b>Query</b> method is called in class <strong>' . get_class($this) . '</strong> for prefix <strong>' . $this->getPrefixSpecial() . '</strong>');
 	}
 
 	protected function saveCustomFields()
 	{
 		if ( !$this->customFields || $this->inCloning ) {
 			return true;
 		}
 
 		$cdata_key = rtrim($this->Prefix . '-cdata.' . $this->Special, '.');
 
 		$cdata = $this->Application->recallObject($cdata_key, null, Array ('skip_autoload' => true));
 		/* @var $cdata kDBItem */
 
 		$resource_id = $this->GetDBField('ResourceId');
 		$cdata->Load($resource_id, 'ResourceId');
 		$cdata->SetDBField('ResourceId', $resource_id);
 
 		$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 		/* @var $ml_formatter kMultiLanguage */
 
 		$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 		/* @var $ml_helper kMultiLanguageHelper */
 
 		$languages = $ml_helper->getLanguages();
 
 		foreach ($this->customFields as $custom_id => $custom_name) {
 			$force_primary = $cdata->GetFieldOption('cust_' . $custom_id, 'force_primary');
 
 			if ( $force_primary ) {
 				$cdata->SetDBField($ml_formatter->LangFieldName('cust_' . $custom_id, true), $this->GetDBField('cust_' . $custom_name));
 			}
 			else {
 				foreach ($languages as $language_id) {
 					$cdata->SetDBField('l' . $language_id . '_cust_' . $custom_id, $this->GetDBField('l' . $language_id . '_cust_' . $custom_name));
 				}
 			}
 		}
 
 		return $cdata->isLoaded() ? $cdata->Update() : $cdata->Create();
 	}
 
 	/**
 	 * Returns specified field value from all selected rows.
 	 * Don't affect current record index
 	 *
 	 * @param string $field
 	 * @param bool $formatted
 	 * @param string $format
 	 * @return Array
 	 */
 	public function GetCol($field, $formatted = false, $format = null)
 	{
 		if ($formatted) {
 			return Array (0 => $this->GetField($field, $format));
 		}
 
 		return Array (0 => $this->GetDBField($field));
 	}
 
 	/**
 	 * Set's loaded status of object
 	 *
 	 * @param bool $is_loaded
 	 * @access public
 	 * @todo remove this method, since item can't be marked as loaded externally
 	 */
 	public function setLoaded($is_loaded = true)
 	{
 		$this->Loaded = $is_loaded;
 	}
-
-	/**
-	 * Returns item's first status field
-	 *
-	 * @return string
-	 * @access public
-	 */
-	public function getStatusField()
-	{
-		$status_fields = $this->Application->getUnitOption($this->Prefix, 'StatusField');
-
-		return array_shift($status_fields);
-	}
-
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/db/cat_event_handler.php
===================================================================
--- branches/5.3.x/core/kernel/db/cat_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/kernel/db/cat_event_handler.php	(revision 15698)
@@ -1,3023 +1,3046 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kCatDBEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array(
 			'OnSaveSettings' => Array ('self' => 'add|edit|advanced:import'),
 			'OnResetSettings' => Array ('self' => 'add|edit|advanced:import'),
 			'OnBeforeDeleteOriginal' => Array ('self' => 'edit|advanced:approve'),
 			'OnAfterDeleteOriginal' => Array ('self' => 'edit|advanced:approve'),
 
 			'OnCopy' => Array ('self' => true),
 			'OnDownloadFile' => Array ('self' => 'view'),
 			'OnCancelAction' => Array ('self' => true),
 			'OnItemBuild' => Array ('self' => true),
 			'OnMakeVote' => Array ('self' => true),
 			'OnReviewHelpful' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Load item if id is available
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function LoadItem(kEvent $event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$id = $this->getPassedID($event);
 
 		if ( $object->Load($id) ) {
 			$actions = $this->Application->recallObject('kActions');
 			/* @var $actions Params */
 
 			$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
 
-			$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
+			$use_pending_editing = $event->getUnitConfig()->getUsePendingEditing();
 
 			if ( $use_pending_editing && $event->Special != 'original' ) {
 				$this->Application->SetVar($event->Prefix . '.original_id', $object->GetDBField('OrgId'));
 			}
 		}
 		else {
 			$object->setID($id);
 		}
 	}
 
 	/**
 	 * Checks user permission to execute given $event
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access public
 	 */
 	public function CheckPermission(kEvent $event)
 	{
 		if ( !$this->Application->isAdmin ) {
 			if ( $event->Name == 'OnSetSortingDirect' ) {
 				// allow sorting on front event without view permission
 				return true;
 			}
 		}
 
 		if ( $event->Name == 'OnExport' ) {
 			// save category_id before doing export
 			$this->Application->LinkVar('m_cat_id');
 		}
 
 		if ( in_array($event->Name, $this->_getMassPermissionEvents()) ) {
 			$items = $this->_getPermissionCheckInfo($event);
 
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			if ( ($event->Name == 'OnSave') && array_key_exists(0, $items) ) {
 				// adding new item (ID = 0)
 				$perm_value = $perm_helper->AddCheckPermission($items[0]['CategoryId'], $event->Prefix) > 0;
 			}
 			else {
 				// leave only items, that can be edited
 				$ids = Array ();
 				$check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission';
 				foreach ($items as $item_id => $item_data) {
 					if ( $perm_helper->$check_method($item_data['CreatedById'], $item_data['CategoryId'], $event->Prefix) > 0 ) {
 						$ids[] = $item_id;
 					}
 				}
 
 				if ( !$ids ) {
 					// no items left for editing -> no permission
 					return $perm_helper->finalizePermissionCheck($event, false);
 				}
 
 				$perm_value = true;
 				$event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method
 			}
 
 			return $perm_helper->finalizePermissionCheck($event, $perm_value);
 		}
 
 		$export_events = Array ('OnSaveSettings', 'OnResetSettings', 'OnExportBegin');
 		if ( in_array($event->Name, $export_events) ) {
 			// when import settings before selecting target import category
 			return $this->Application->CheckPermission('in-portal:main_import.view');
 		}
 
 		if ( $event->Name == 'OnProcessSelected' ) {
 			if ( $this->Application->RecallVar('dst_field') == 'ImportCategory' ) {
 				// when selecting target import category
 				return $this->Application->CheckPermission('in-portal:main_import.view');
 			}
 		}
 
 		return parent::CheckPermission($event);
 	}
 
 	/**
 	 * Returns events, that require item-based (not just event-name based) permission check
 	 *
 	 * @return Array
 	 */
 	function _getMassPermissionEvents()
 	{
 		return Array (
 			'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove',
 			'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown',
 			'OnCut',
 		);
 	}
 
 	/**
 	 * Returns category item IDs, that require permission checking
 	 *
 	 * @param kEvent $event
 	 * @return string
 	 */
 	function _getPermissionCheckIDs($event)
 	{
 		if ($event->Name == 'OnSave') {
 			$selected_ids = implode(',', $this->getSelectedIDs($event, true));
 			if (!$selected_ids) {
 				$selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave)
 			}
 		}
 		else {
 			// OnEdit, OnMassDelete events, when items are checked in grid
 			$selected_ids = implode(',', $this->StoreSelectedIDs($event));
 		}
 
 		return $selected_ids;
 	}
 
 	/**
 	 * Returns information used in permission checking
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function _getPermissionCheckInfo($event)
 	{
 		$perm_helper = $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		// when saving data from temp table to live table check by data from temp table
 		$item_ids = $this->_getPermissionCheckIDs($event);
 		$items = $perm_helper->GetCategoryItemData($event->Prefix, $item_ids, $event->Name == 'OnSave');
 
 		if (!$items) {
 			// when item not present in temp table, then permission is not checked, because there are no data in db to check
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			list ($id, $fields_hash) = each($items_info);
 
 			if (array_key_exists('CategoryId', $fields_hash)) {
 				$item_category = $fields_hash['CategoryId'];
 			}
 			else {
 				$item_category = $this->Application->GetVar('m_cat_id');
 			}
 
 			$items[$id] = Array (
 				'CreatedById' => $this->Application->RecallVar('use_id'),
 				'CategoryId' => $item_category,
 			);
 		}
 
 		return $items;
 	}
 
 	/**
 	 * Add selected items to clipboard with mode = COPY (CLONE)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCopy($event)
 	{
 		$this->Application->RemoveVar('clipboard');
 
 		$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
 		/* @var $clipboard_helper kClipboardHelper */
 
 		$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Add selected items to clipboard with mode = CUT
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCut($event)
 	{
 		$this->Application->RemoveVar('clipboard');
 		$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
 		/* @var $clipboard_helper kClipboardHelper */
 
 		$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Checks permission for OnPaste event
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 */
 	function _checkPastePermission($event)
 	{
 		$perm_helper = $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		$category_id = $this->Application->GetVar('m_cat_id');
 		if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) {
 			// no items left for editing -> no permission
 			return $perm_helper->finalizePermissionCheck($event, false);
 		}
 
 		return true;
 	}
 
 	/**
 	 * Performs category item paste
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnPaste($event)
 	{
 		if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) {
 			$event->status = kEvent::erFAIL;
 			return;
 		}
 
 		$clipboard_data = $event->getEventParam('clipboard_data');
 
 		if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) {
 			return;
 		}
 
 		if ( $clipboard_data['copy'] ) {
 			$temp = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 			/* @var $temp kTempTablesHandler */
 
 			$this->Application->SetVar('ResetCatBeforeClone', 1); // used in "kCatDBEventHandler::OnBeforeClone"
 			$temp->CloneItems($event->Prefix, $event->Special, $clipboard_data['copy']);
 		}
 
 		if ( $clipboard_data['cut'] ) {
 			$object = $this->Application->recallObject($event->getPrefixSpecial() . '.item', $event->Prefix, Array ('skip_autoload' => true));
 			/* @var $object kCatDBItem */
 
 			foreach ($clipboard_data['cut'] as $id) {
 				$object->Load($id);
 				$object->MoveToCat();
 			}
 		}
 	}
 
 	/**
 	 * Deletes all selected items.
 	 * Automatically recurse into sub-items using temp handler, and deletes sub-items
 	 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnMassDelete(kEvent $event)
 	{
 		if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 			$event->status = kEvent::erFAIL;
 			return;
 		}
 
 		$ids = $this->StoreSelectedIDs($event);
 
 		$to_delete = Array ();
 		$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 
 		if ( $recycle_bin ) {
 			$rb = $this->Application->recallObject('c.recycle', NULL, array ('skip_autoload' => true));
 			/* @var $rb CategoriesItem */
 
 			$rb->Load($recycle_bin);
 
 			$object = $this->Application->recallObject($event->Prefix . '.recycleitem', NULL, Array ('skip_autoload' => true));
 			/* @var $object kCatDBItem */
 
 			foreach ($ids as $id) {
 				$object->Load($id);
 
 				if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $object->GetDBField('ParentPath')) ) {
 					$to_delete[] = $id;
 					continue;
 				}
 
 				$object->MoveToCat($recycle_bin);
 			}
 
 			$ids = $to_delete;
 		}
 
 		$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 		/* @var $temp_handler kTempTablesHandler */
 
 		$event->setEventParam('ids', $ids);
 		$this->customProcessing($event, 'before');
 		$ids = $event->getEventParam('ids');
 
 		if ( $ids ) {
 			$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
 		}
 
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Return type clauses for list bulding on front
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function getTypeClauses($event)
 	{
 		$types = $event->getEventParam('types');
 		$types = $types ? explode(',', $types) : Array ();
 
 		$except_types = $event->getEventParam('except');
 		$except_types = $except_types ? explode(',', $except_types) : Array ();
 
 		$type_clauses = Array();
 
 		$user_id = $this->Application->RecallVar('user_id');
 		$owner_field = $this->getOwnerField($event->Prefix);
 
 		$type_clauses['my_items']['include'] = '%1$s.'.$owner_field.' = '.$user_id;
 		$type_clauses['my_items']['except'] = '%1$s.'.$owner_field.' <> '.$user_id;
 		$type_clauses['my_items']['having_filter'] = false;
 
 		$type_clauses['pick']['include'] = '%1$s.EditorsPick = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
 		$type_clauses['pick']['except'] = '%1$s.EditorsPick! = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
 		$type_clauses['pick']['having_filter'] = false;
 
 		$type_clauses['hot']['include'] = '`IsHot` = 1 AND PrimaryCat = 1';
 		$type_clauses['hot']['except'] = '`IsHot`! = 1 AND PrimaryCat = 1';
 		$type_clauses['hot']['having_filter'] = true;
 
 		$type_clauses['pop']['include'] = '`IsPop` = 1 AND PrimaryCat = 1';
 		$type_clauses['pop']['except'] = '`IsPop`! = 1 AND PrimaryCat = 1';
 		$type_clauses['pop']['having_filter'] = true;
 
 		$type_clauses['new']['include'] = '`IsNew` = 1 AND PrimaryCat = 1';
 		$type_clauses['new']['except'] = '`IsNew`! = 1 AND PrimaryCat = 1';
 		$type_clauses['new']['having_filter'] = true;
 
 		$type_clauses['displayed']['include'] = '';
 		$displayed = $this->Application->GetVar($event->Prefix.'_displayed_ids');
+
 		if ($displayed) {
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
+			$id_field = $event->getUnitConfig()->getIDField();
 			$type_clauses['displayed']['except'] = '%1$s.'.$id_field.' NOT IN ('.$displayed.')';
 		}
 		else {
 			$type_clauses['displayed']['except'] = '';
 		}
+
 		$type_clauses['displayed']['having_filter'] = false;
 
 		if (in_array('search', $types) || in_array('search', $except_types)) {
 			$event_mapping = Array (
 				'simple'		=>	'OnSimpleSearch',
 				'subsearch'		=>	'OnSubSearch',
 				'advanced'		=>	'OnAdvancedSearch'
 			);
 
 			$keywords = $event->getEventParam('keyword_string');
 			$type = $this->Application->GetVar('search_type', 'simple');
 
 			if ( $keywords ) {
 				// processing keyword_string param of ListProducts tag
 				$this->Application->SetVar('keywords', $keywords);
 				$type = 'simple';
 			}
 
 			$search_event = $event_mapping[$type];
 			$this->$search_event($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
-			$search_sql = '	FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search
-							search_result JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
+			$search_sql = '	FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search search_result
+							JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
 			$sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL());
 
 			$object->SetSelectSQL($sql);
 
 			$object->addCalculatedField('Relevance', 'search_result.Relevance');
 
 			$type_clauses['search']['include'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Categories.Status = '.STATUS_ACTIVE.')';
 			$type_clauses['search']['except'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Categories.Status = '.STATUS_ACTIVE.')';
 			$type_clauses['search']['having_filter'] = false;
 		}
 
 		if (in_array('related', $types) || in_array('related', $except_types)) {
-
 			$related_to = $event->getEventParam('related_to');
+
 			if (!$related_to) {
 				$related_prefix = $event->Prefix;
 			}
 			else {
 				$sql = 'SELECT Prefix
 						FROM '.TABLE_PREFIX.'ItemTypes
 						WHERE ItemName = '.$this->Conn->qstr($related_to);
 				$related_prefix = $this->Conn->GetOne($sql);
 			}
 
-			$rel_table = $this->Application->getUnitOption('rel', 'TableName');
-			$item_type = (int)$this->Application->getUnitOption($event->Prefix, 'ItemType');
+			$rel_table = $this->Application->getUnitConfig('rel')->getTableName();
+			$item_type = (int)$event->getUnitConfig()->getItemType();
 
 			if ($item_type == 0) {
 				trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING);
 			}
 
 			// process case, then this list is called inside another list
 			$prefix_special = $event->getEventParam('PrefixSpecial');
+
 			if (!$prefix_special) {
 				$prefix_special = $this->Application->Parser->GetParam('PrefixSpecial');
 			}
 
 			$id = false;
+
 			if ($prefix_special !== false) {
 				$processed_prefix = $this->Application->processPrefix($prefix_special);
 				if ($processed_prefix['prefix'] == $related_prefix) {
 					// printing related categories within list of items (not on details page)
 					$list = $this->Application->recallObject($prefix_special);
 					/* @var $list kDBList */
 
 					$id = $list->GetID();
 				}
 			}
 
 			if ($id === false) {
 				// printing related categories for single item (possibly on details page)
 				if ($related_prefix == 'c') {
 					$id = $this->Application->GetVar('m_cat_id');
 				}
 				else {
 					$id = $this->Application->GetVar($related_prefix . '_id');
 				}
 			}
 
 			$p_item = $this->Application->recallObject($related_prefix.'.current', NULL, Array('skip_autoload' => true));
 			/* @var $p_item kCatDBItem */
 
 			$p_item->Load( (int)$id );
 
 			$p_resource_id = $p_item->GetDBField('ResourceId');
 
 			$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
 					WHERE
 						(Enabled = 1)
 						AND (
 								(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 								OR
 								(Type = 1
 									AND (
 											(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 											OR
 											(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
 										)
 								)
 						)';
 
 			$related_ids_array = $this->Conn->Query($sql);
 			$related_ids = Array();
 
 			foreach ($related_ids_array as $record) {
 				$related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ];
 			}
 
 			if (count($related_ids) > 0) {
 				$type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
 				$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
 			}
 			else {
 				$type_clauses['related']['include'] = '0';
 				$type_clauses['related']['except'] = '1';
 			}
+
 			$type_clauses['related']['having_filter'] = false;
 		}
 
 		if (in_array('favorites', $types) || in_array('favorites', $except_types)) {
 			$sql = 'SELECT ResourceId
-					FROM '.$this->Application->getUnitOption('fav', 'TableName').'
+					FROM ' . $this->Application->getUnitConfig('fav')->getTableName() . '
 					WHERE PortalUserId = '.$this->Application->RecallVar('user_id');
 			$favorite_ids = $this->Conn->GetCol($sql);
+
 			if ($favorite_ids) {
 				$type_clauses['favorites']['include'] = '%1$s.ResourceId IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
 				$type_clauses['favorites']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
 			}
 			else {
 				$type_clauses['favorites']['include'] = 0;
 				$type_clauses['favorites']['except'] = 1;
 			}
+
 			$type_clauses['favorites']['having_filter'] = false;
 		}
 
 		return $type_clauses;
 	}
 
 	/**
 	 * Returns SQL clause, that will help to select only data from specified category & it's children
 	 *
 	 * @param int $category_id
 	 * @return string
 	 */
 	function getCategoryLimitClause($category_id)
 	{
 		if (!$category_id) {
 			return false;
 		}
 
 		$tree_indexes = $this->Application->getTreeIndex($category_id);
 		if (!$tree_indexes) {
 			// id of non-existing category was given
 			return 'FALSE';
 		}
 
 		return TABLE_PREFIX.'Categories.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight'];
 	}
 
 	/**
 	 * Apply any custom changes to list's sql query
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 * @see kDBEventHandler::OnListBuild()
 	 */
 	protected function SetCustomQuery(kEvent $event)
 	{
 		parent::SetCustomQuery($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBList */
 
 		// add category filter if needed
 		if ($event->Special != 'showall' && $event->Special != 'user') {
 			if ( (string)$event->getEventParam('parent_cat_id') !== '' ) {
 				$parent_cat_id = $event->getEventParam('parent_cat_id');
 			}
 			else {
 				$parent_cat_id = $this->Application->GetVar('c_id');
 				if (!$parent_cat_id) {
 					$parent_cat_id = $this->Application->GetVar('m_cat_id');
 				}
 				if (!$parent_cat_id) {
 					$parent_cat_id = 0;
 				}
 			}
 
 			if ("$parent_cat_id" == '0') {
 				// replace "0" category with "Content" category id (this way template
 				$parent_cat_id = $this->Application->getBaseCategory();
 			}
 
 			if ((string)$parent_cat_id != 'any') {
 				if ($event->getEventParam('recursive')) {
 					$filter_clause = $this->getCategoryLimitClause($parent_cat_id);
 					if ($filter_clause !== false) {
 						$object->addFilter('category_filter', $filter_clause);
 					}
 
 					$object->addFilter('primary_filter', 'PrimaryCat = 1');
 				}
 				else {
 					$object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.CategoryId = '.$parent_cat_id );
 				}
 			}
 			else {
 				$object->addFilter('primary_filter', 'PrimaryCat = 1');
 			}
 		}
 		else {
 			$object->addFilter('primary_filter', 'PrimaryCat = 1');
 
 			// if using recycle bin don't show items from there
 			$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 			if ($recycle_bin) {
 				$object->addFilter('recyclebin_filter', TABLE_PREFIX.'CategoryItems.CategoryId <> '.$recycle_bin);
 			}
 		}
 
 		if ($event->Special == 'user') {
 			$editable_user = $this->Application->GetVar('u_id');
 			$object->addFilter('owner_filter', '%1$s.'.$this->getOwnerField($event->Prefix).' = '.$editable_user);
 		}
 
 		// add permission filter
 		if ($this->Application->RecallVar('user_id') == USER_ROOT) {
 			// for "root" CATEGORY.VIEW permission is checked for items lists too
 			$view_perm = 1;
 		}
 		else {
 			// for any real user itemlist view permission is checked instead of CATEGORY.VIEW
 			$count_helper = $this->Application->recallObject('CountHelper');
 			/* @var $count_helper kCountHelper */
 
 			list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($event->Prefix, 'perm');
 			$object->addFilter('perm_filter2', $view_filter);
 		}
 
 		$object->addFilter('perm_filter', 'perm.PermId = '.$view_perm);
 
 		$types = $event->getEventParam('types');
 		$this->applyItemStatusFilter($object, $types);
 
 		$except_types = $event->getEventParam('except');
 		$type_clauses = $this->getTypeClauses($event);
 
 		$search_helper = $this->Application->recallObject('SearchHelper');
 		/* @var $search_helper kSearchHelper */
 
 		$search_helper->SetComplexFilter($event, $type_clauses, $types, $except_types);
 	}
 
 	/**
 	 * Adds filter that filters out items with non-required statuses
 	 *
 	 * @param kDBList $object
 	 * @param string $types
 	 */
 	function applyItemStatusFilter(&$object, $types)
 	{
 		// Link1 (before modifications) [Status = 1, OrgId = NULL], Link2 (after modifications) [Status = -2, OrgId = Link1_ID]
-		$pending_editing = $this->Application->getUnitOption($object->Prefix, 'UsePendingEditing');
+		$pending_editing = $object->getUnitConfig()->getUsePendingEditing();
 
 		if (!$this->Application->isAdminUser) {
 			$types = explode(',', $types);
 			if (in_array('my_items', $types)) {
 				$allow_statuses = Array (STATUS_ACTIVE, STATUS_PENDING, STATUS_PENDING_EDITING);
 				$object->addFilter('status_filter', '%1$s.Status IN ('.implode(',', $allow_statuses).')');
 
 				if ($pending_editing) {
 					$user_id = $this->Application->RecallVar('user_id');
 					$this->applyPendingEditingFilter($object, $user_id);
 				}
 			}
 			else {
 				$object->addFilter('status_filter', '(%1$s.Status = ' . STATUS_ACTIVE . ') AND (' . TABLE_PREFIX . 'Categories.Status = ' . STATUS_ACTIVE . ')');
 				if ($pending_editing) {
 					// if category item uses pending editing abilities, then in no cases show pending copies on front
 					$object->addFilter('original_filter', '%1$s.OrgId = 0 OR %1$s.OrgId IS NULL');
 				}
 			}
 		}
 		else {
 			if ($pending_editing) {
 				$this->applyPendingEditingFilter($object);
 			}
 		}
 	}
 
 	/**
 	 * Adds filter, that removes live items if they have pending editing copies
 	 *
 	 * @param kDBList $object
 	 * @param int $user_id
 	 */
 	function applyPendingEditingFilter(&$object, $user_id = NULL)
 	{
 		$sql = 'SELECT OrgId
 				FROM '.$object->TableName.'
 				WHERE Status = '.STATUS_PENDING_EDITING.' AND OrgId IS NOT NULL';
 
 		if (isset($user_id)) {
 			$owner_field = $this->getOwnerField($object->Prefix);
 			$sql .= ' AND '.$owner_field.' = '.$user_id;
 		}
 
 		$pending_ids = $this->Conn->GetCol($sql);
 		if ($pending_ids) {
 			$object->addFilter('no_original_filter', '%1$s.'.$object->IDField.' NOT IN ('.implode(',', $pending_ids).')');
 		}
 	}
 
 	/**
 	 * Adds calculates fields for item statuses
 	 *
 	 * @param kDBItem|kDBList $object
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function prepareObject(&$object, kEvent $event)
 	{
 		$this->prepareItemStatuses($event);
 		$object->addCalculatedField('CachedNavbar', 'l' . $this->Application->GetVar('m_lang') . '_CachedNavbar');
 
 		if ( $event->Special == 'export' || $event->Special == 'import' ) {
 			$export_helper = $this->Application->recallObject('CatItemExportHelper');
 			/* @var $export_helper kCatDBItemExportHelper */
 
 			$export_helper->prepareExportColumns($event);
 		}
 	}
 
 	/**
 	 * Creates calculated fields for all item statuses based on config settings
 	 *
 	 * @param kEvent $event
 	 */
 	function prepareItemStatuses($event)
 	{
 		$object = $event->getObject( Array('skip_autoload' => true) );
+		$property_map = $event->getUnitConfig()->getItemPropertyMappings();
 
-		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
 		if (!$property_map) {
 			return ;
 		}
 
 		// new items
 		$object->addCalculatedField('IsNew', '	IF(%1$s.NewItem = 2,
-													IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '.
-														$this->Application->ConfigValue($property_map['NewDays']).
-														'*3600*24), 1, 0),
+													IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - ' . $this->Application->ConfigValue($property_map['NewDays']) . '*3600*24), 1, 0),
 													%1$s.NewItem
 												)');
 
 		// hot items (cache updated every hour)
 		if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$serial_name = $this->Application->incrementCacheSerial($event->Prefix, NULL, false);
 			$hot_limit = $this->Application->getCache($property_map['HotLimit'] . '[%' . $serial_name . '%]');
 		}
 		else {
 			$hot_limit = $this->Application->getDBCache($property_map['HotLimit']);
 		}
 
 		if ($hot_limit === false) {
 			$hot_limit = $this->CalculateHotLimit($event);
 		}
 
 		$object->addCalculatedField('IsHot', '	IF(%1$s.HotItem = 2,
 													IF(%1$s.'.$property_map['ClickField'].' >= '.$hot_limit.', 1, 0),
 													%1$s.HotItem
 												)');
 
 		// popular items
 		$object->addCalculatedField('IsPop', '	IF(%1$s.PopItem = 2,
-													IF(%1$s.CachedVotesQty >= '.
-														$this->Application->ConfigValue($property_map['MinPopVotes']).
-														' AND %1$s.CachedRating >= '.
-														$this->Application->ConfigValue($property_map['MinPopRating']).
-														', 1, 0),
+													IF(%1$s.CachedVotesQty >= ' . $this->Application->ConfigValue($property_map['MinPopVotes']) . ' AND %1$s.CachedRating >= ' . $this->Application->ConfigValue($property_map['MinPopRating']) . ', 1, 0),
 													%1$s.PopItem)');
 
 	}
 
 	/**
 	 * Calculates hot limit for current item's table
 	 *
 	 * @param kEvent $event
 	 * @return float
 	 * @access protected
 	 */
 	protected function CalculateHotLimit($event)
 	{
-		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
+		$config = $event->getUnitConfig();
+		$property_map = $config->getItemPropertyMappings();
 
 		if ( !$property_map ) {
 			return 0.00;
 		}
 
 		$click_field = $property_map['ClickField'];
 
 		$last_hot = $this->Application->ConfigValue($property_map['MaxHotNumber']) - 1;
 		$sql = 'SELECT ' . $click_field . '
-				FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+				FROM ' . $config->getTableName() . '
 				ORDER BY ' . $click_field . ' DESC
 				LIMIT ' . $last_hot . ', 1';
 		$res = $this->Conn->GetCol($sql);
 		$hot_limit = (double)array_shift($res);
 
 		if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 			$serial_name = $this->Application->incrementCacheSerial($event->Prefix, NULL, false);
 			$this->Application->setCache($property_map['HotLimit'] . '[%' . $serial_name . '%]', $hot_limit);
 		}
 		else {
 			$this->Application->setDBCache($property_map['HotLimit'], $hot_limit, 3600);
 		}
 
 		return $hot_limit;
 	}
 
 	/**
 	 * Moves item to preferred category, updates item hits
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemUpdate(kEvent $event)
 	{
 		parent::OnBeforeItemUpdate($event);
 
 		$object = $event->getObject();
 		/* @var $object kCatDBItem */
 
 		// update hits field
-		$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
+		$config = $event->getUnitConfig();
+		$property_map = $config->getUserProfileMapping();
+
 		if ( $property_map ) {
 			$click_field = $property_map['ClickField'];
 
 			if ( $this->Application->isAdminUser && ($this->Application->GetVar($click_field . '_original') !== false) && floor($this->Application->GetVar($click_field . '_original')) != $object->GetDBField($click_field) ) {
 				$sql = 'SELECT MAX(' . $click_field . ')
-						FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+						FROM ' . $config->getTableName() . '
 						WHERE FLOOR(' . $click_field . ') = ' . $object->GetDBField($click_field);
 				$hits = ($res = $this->Conn->GetOne($sql)) ? $res + 0.000001 : $object->GetDBField($click_field);
 
 				$object->SetDBField($click_field, $hits);
 			}
 		}
 
 		// change category
 		$target_category = $object->GetDBField('CategoryId');
+
 		if ( $object->GetOriginalField('CategoryId') != $target_category ) {
 			$object->MoveToCat($target_category);
 		}
 	}
 
 	/**
 	 * Occurs after loading item, 'id' parameter
 	 * allows to get id of item that was loaded
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemLoad(kEvent $event)
 	{
 		parent::OnAfterItemLoad($event);
 
 		$special = substr($event->Special, -6);
 
 		$object = $event->getObject();
 		/* @var $object kCatDBItem */
 
 		if ( $special == 'import' || $special == 'export' ) {
 			$image_data = $object->getPrimaryImageData();
 
 			if ( $image_data ) {
 				$thumbnail_image = $image_data[$image_data['LocalThumb'] ? 'ThumbPath' : 'ThumbUrl'];
 				if ( $image_data['SameImages'] ) {
 					$full_image = '';
 				}
 				else {
 					$full_image = $image_data[$image_data['LocalImage'] ? 'LocalPath' : 'Url'];
 				}
 
 				$object->SetDBField('ThumbnailImage', $thumbnail_image);
 				$object->SetDBField('FullImage', $full_image);
 				$object->SetDBField('ImageAlt', $image_data['AltName']);
 			}
 		}
 
 		// substituting pending status value for pending editing
 		if ( $object->HasField('OrgId') && $object->GetDBField('OrgId') > 0 && $object->GetDBField('Status') == -2 ) {
 			$new_options = Array ();
 			$options = $object->GetFieldOption('Status', 'options', false, Array ());
 
 			foreach ($options as $key => $val) {
 				if ( $key == 2 ) {
 					$key = -2;
 				}
 
 				$new_options[$key] = $val;
 			}
 
 			$object->SetFieldOption('Status', 'options', $new_options);
 		}
 
 		if ( !$this->Application->isAdmin ) {
 			// linking existing images for item with virtual fields
 			$image_helper = $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			$image_helper->LoadItemImages($object);
 
 			// linking existing files for item with virtual fields
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->LoadItemFiles($object);
 		}
 
 		if ( $object->isVirtualField('MoreCategories') ) {
 			// set item's additional categories to virtual field (used in editing)
 			$item_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
 			$object->SetDBField('MoreCategories', $item_categories ? '|' . implode('|', $item_categories) . '|' : '');
 		}
 	}
 
 	/**
 	 * Occurs after updating item
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemUpdate(kEvent $event)
 	{
 		parent::OnAfterItemUpdate($event);
 
 		$this->CalculateHotLimit($event);
 
 		if ( substr($event->Special, -6) == 'import' ) {
 			$this->setCustomExportColumns($event);
 		}
 
 		$object = $event->getObject();
 		/* @var $object kCatDBItem */
 
 		if ( !$this->Application->isAdmin ) {
 			$image_helper = $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			// process image upload in virtual fields
 			$image_helper->SaveItemImages($object);
 
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			// process file upload in virtual fields
 			$file_helper->SaveItemFiles($object);
 
 			if ( $event->Special != '-item' ) {
 				// don't touch categories during cloning
 				$this->processAdditionalCategories($object, 'update');
 			}
 		}
 
 		$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 
 		if ( $this->Application->isAdminUser && $recycle_bin ) {
 			$sql = 'SELECT CategoryId
-					FROM ' . $this->Application->getUnitOption('ci', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('ci')->getTableName() . '
 					WHERE ItemResourceId = ' . $object->GetDBField('ResourceId') . ' AND PrimaryCat = 1';
 			$primary_category = $this->Conn->GetOne($sql);
 
 			if ( $primary_category == $recycle_bin ) {
 				$event->CallSubEvent('OnAfterItemDelete');
 			}
 		}
 
 		if ( $object->GetChangedFields() ) {
 			$now = adodb_mktime();
 			$object->SetDBField('Modified_date', $now);
 			$object->SetDBField('Modified_time', $now);
 			$object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
 		}
 	}
 
 	/**
 	 * Sets values for import process
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemCreate(kEvent $event)
 	{
 		parent::OnAfterItemCreate($event);
 
 		$object = $event->getObject();
 		/* @var $object kCatDBItem */
 
 		if ( substr($event->Special, -6) == 'import' ) {
 			$this->setCustomExportColumns($event);
 		}
 
 		$object->assignPrimaryCategory();
 
 		if ( !$this->Application->isAdmin ) {
 			$image_helper = $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			// process image upload in virtual fields
 			$image_helper->SaveItemImages($object);
 
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			// process file upload in virtual fields
 			$file_helper->SaveItemFiles($object);
 
 			if ( $event->Special != '-item' ) {
 				// don't touch categories during cloning
 				$this->processAdditionalCategories($object, 'create');
 			}
 		}
 	}
 
 	/**
 	 * Make record to search log
 	 *
 	 * @param string $keywords
 	 * @param int $search_type 0 - simple search, 1 - advanced search
 	 */
 	function saveToSearchLog($keywords, $search_type = 0)
 	{
 		// don't save keywords for each module separately, just one time
 		// static variable can't help here, because each module uses it's own class instance !
 		if (!$this->Application->GetVar('search_logged')) {
 			$sql = 'UPDATE '.TABLE_PREFIX.'SearchLogs
 					SET Indices = Indices + 1
 					WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search
 	        $this->Conn->Query($sql);
 	        if ($this->Conn->getAffectedRows() == 0) {
 	            $fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type);
 	        	$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLogs');
 	        }
 
 	        $this->Application->SetVar('search_logged', 1);
 		}
 	}
 
 	/**
 	 * Makes simple search for category items
 	 * based on keywords string
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSimpleSearch($event)
 	{
 		$event->redirect = false;
 		$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 
 		$keywords = htmlspecialchars_decode( trim($this->Application->GetVar('keywords')) );
 
 		$query_object = $this->Application->recallObject('HTTPQuery');
 		/* @var $query_object kHTTPQuery */
 
 		$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 		if(!isset($query_object->Get['keywords']) &&
 			!isset($query_object->Post['keywords']) &&
 			$this->Conn->Query($sql))
 		{
 			return; // used when navigating by pages or changing sorting in search results
 		}
 		if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length'))
 		{
 			$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
 			$this->Application->SetVar('keywords_too_short', 1);
 			return; // if no or too short keyword entered, doing nothing
 		}
 
 		$this->Application->StoreVar('keywords', $keywords);
 
 		$this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search
 
 		$event->setPseudoClass('_List');
 
 		$object = $event->getObject();
 		/* @var $object kDBList */
 
+		$config = $event->getUnitConfig();
+
 		$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
 		$lang = $this->Application->GetVar('m_lang');
-		$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
+		$items_table = $config->getTableName();
 		$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
 
 		$sql = 'SELECT *
-				FROM ' . $this->Application->getUnitOption('confs', 'TableName') . '
+				FROM ' . $this->Application->getUnitConfig('confs')->getTableName() . '
 				WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1';
 		$search_config = $this->Conn->Query($sql, 'FieldName');
 
 		$field_list = array_keys($search_config);
 
 		$join_clauses = Array();
 
 		// field processing
 		$weight_sum = 0;
 
 		$alias_counter = 0;
 
-		$custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
+		$custom_fields = $config->getCustomFields();
 		if ($custom_fields) {
-			$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
+			$custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName();
 			$join_clauses[] = '	LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
 		}
 
 		// what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table))
 		$search_config_map = Array();
 
 		foreach ($field_list as $key => $field) {
 			$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
 			$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
 
 			// processing multilingual fields
 			if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
 				$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 				$field_list[$key] = 'l'.$lang.'_'.$field;
 
 				if (!isset($search_config[$field]['ForeignField'])) {
 					$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
 					$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 				}
 			}
 
 			// processing fields from other tables
 			$foreign_field = $search_config[$field]['ForeignField'];
 
 			if ( $foreign_field ) {
 				$exploded = explode(':', $foreign_field, 2);
 				if ($exploded[0] == 'CALC') {
 					// ignoring having type clauses in simple search
 					unset($field_list[$key]);
 					continue;
 				}
 				else {
 					$multi_lingual = false;
 					if ($exploded[0] == 'MULTI') {
 						$multi_lingual = true;
 						$foreign_field = $exploded[1];
 					}
 
 					$exploded = explode('.', $foreign_field);	// format: table.field_name
 					$foreign_table = TABLE_PREFIX.$exploded[0];
 
 					$alias_counter++;
 					$alias = 't'.$alias_counter;
 
 					if ($multi_lingual) {
 						$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
 						$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 						$search_config_map[ $field_list[$key] ] = $field;
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 					else {
 						$field_list[$key] = $alias.'.'.$exploded[1];
 						$search_config_map[ $field_list[$key] ] = $field;
 					}
 
 					$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
 					$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 					$join_clauses[] = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 										ON '.$join_clause;
 				}
 			}
 			else {
 				// processing fields from local table
 				if ($search_config[$field]['CustomFieldId']) {
 					$local_table = 'custom_data';
 
 					// search by custom field value on current language
 					$custom_field_id = array_search($field_list[$key], $custom_fields);
 					$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
 
 					// search by custom field value on primary language
 					$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
 					$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 				}
 
 				$field_list[$key] = $local_table.'.'.$field_list[$key];
 				$search_config_map[ $field_list[$key] ] = $field;
 			}
 		}
 
 		// keyword string processing
 		$search_helper = $this->Application->recallObject('SearchHelper');
 		/* @var $search_helper kSearchHelper */
 
 		$where_clause = Array ();
 		foreach ($field_list as $field) {
 			if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
 				// local real field
 				$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
 				if ($filter_data) {
 					$where_clause[] = $filter_data['value'];
 				}
 			}
 			elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
 				$custom_field_name = 'cust_' . $search_config_map[$field];
 				$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
 				if ($filter_data) {
 					$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
 				}
 			}
 			else {
 				$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
 			}
 		}
 
 		$where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below!
 
 		$search_scope = $this->Application->GetVar('search_scope');
 		if ($search_scope == 'category') {
 			$category_id = $this->Application->GetVar('m_cat_id');
 			$category_filter = $this->getCategoryLimitClause($category_id);
 			if ($category_filter !== false) {
 				$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON '.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$items_table.'.ResourceId';
 				$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'Categories ON '.TABLE_PREFIX.'Categories.CategoryId = '.TABLE_PREFIX.'CategoryItems.CategoryId';
 
 				$where_clause = '('.$this->getCategoryLimitClause($category_id).') AND '.$where_clause;
 			}
 		}
 
 		$where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')';
 
 		if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
 			$sub_search_ids = $event->MasterEvent->getEventParam('ResultIds');
 
 			if ( $sub_search_ids !== false ) {
 				if ( $sub_search_ids ) {
 					$where_clause .= 'AND (' . $items_table . '.ResourceId IN (' . implode(',', $sub_search_ids) . '))';
 				}
 				else {
 					$where_clause .= 'AND FALSE';
 				}
 			}
 		}
 
 		// making relevance clause
 		$positive_words = $search_helper->getPositiveKeywords($keywords);
 		$this->Application->StoreVar('highlight_keywords', serialize($positive_words));
-		$revelance_parts = Array();
+		$relevance_parts = Array();
 		reset($search_config);
 
 		foreach ($positive_words as $keyword_index => $positive_word) {
 			$positive_word = $search_helper->transformWildcards($positive_word);
 			$positive_words[$keyword_index] = $this->Conn->escape($positive_word);
 		}
 
 		foreach ($field_list as $field) {
 
 			if (!array_key_exists($field, $search_config_map)) {
 				$map_key = $search_config_map[$items_table . '.' . $field];
 			}
 			else {
 				$map_key = $search_config_map[$field];
 			}
 
 			$config_elem = $search_config[ $map_key ];
 			$weight = $config_elem['Priority'];
 
 			// search by whole words only ([[:<:]] - word boundary)
-			/*$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
+			/*$relevance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
 			foreach ($positive_words as $keyword) {
-				$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
+				$relevance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
 			}*/
 
 			// search by partial word matches too
-			$revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
+			$relevance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
 			foreach ($positive_words as $keyword) {
-				$revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
+				$relevance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
 			}
 		}
 
-		$revelance_parts = array_unique($revelance_parts);
+		$relevance_parts = array_unique($relevance_parts);
 
-		$conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
+		$conf_postfix = $config->getSearchConfigPostfix();
 		$rel_keywords	= $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix)	/ 100;
 		$rel_pop		= $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix)		/ 100;
 		$rel_rating		= $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix)	/ 100;
-		$relevance_clause = '('.implode(' + ', $revelance_parts).') / '.$weight_sum.' * '.$rel_keywords;
+		$relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords;
 		if ($rel_pop && $object->isField('Hits')) {
 			$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 		}
 		if ($rel_rating && $object->isField('CachedRating')) {
 			$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 		}
 
 		// building final search query
 		if (!$this->Application->GetVar('do_not_drop_search_table')) {
 			$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
 			$this->Application->SetVar('do_not_drop_search_table', true);
 		}
 
 		$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
 		if ($search_table_exists) {
 			$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
 		}
 		else {
 			$select_intro = 'CREATE TABLE '.$search_table.' AS ';
 		}
 
-		$edpick_clause = $this->Application->getUnitOption($event->Prefix.'.EditorsPick', 'Fields') ? $items_table.'.EditorsPick' : '0';
+		$edpick_clause = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0';
 
 		$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
-							'.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' AS ItemId,
+							'.$items_table.'.'.$config->getIDField().' AS ItemId,
 							'.$items_table.'.ResourceId,
-							'.$this->Application->getUnitOption($event->Prefix, 'ItemType').' AS ItemType,
+							'.$config->getItemType().' AS ItemType,
 							 '.$edpick_clause.' AS EdPick
 					FROM '.$object->TableName.'
 					'.implode(' ', $join_clauses).'
 					WHERE '.$where_clause.'
-					GROUP BY '.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' ORDER BY Relevance DESC';
+					GROUP BY '.$items_table.'.'.$config->getIDField().' ORDER BY Relevance DESC';
 
 		$this->Conn->Query($sql);
 
 		if ( !$search_table_exists ) {
 			$sql = 'ALTER TABLE ' . $search_table . '
 					ADD INDEX (ResourceId),
 					ADD INDEX (Relevance)';
 			$this->Conn->Query($sql);
 		}
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSubSearch($event)
 	{
 		// keep search results from other items after doing a sub-search on current item type
 		$this->Application->SetVar('do_not_drop_search_table', true);
 
 		$ids = Array ();
 		$search_table = TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search';
 		$sql = 'SHOW TABLES LIKE "' . $search_table . '"';
 
 		if ( $this->Conn->Query($sql) ) {
-			$item_type = $this->Application->getUnitOption($event->Prefix, 'ItemType');
+			$item_type = $event->getUnitConfig()->getItemType();
 
 			// 1. get ids to be used as search bounds
 			$sql = 'SELECT DISTINCT ResourceId
 					FROM ' . $search_table . '
 					WHERE ItemType = ' . $item_type;
 			$ids = $this->Conn->GetCol($sql);
 
 			// 2. delete previously found ids
 			$sql = 'DELETE FROM ' . $search_table . '
 					WHERE ItemType = ' . $item_type;
 			$this->Conn->Query($sql);
 		}
 
 		$event->setEventParam('ResultIds', $ids);
 		$event->CallSubEvent('OnSimpleSearch');
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 * @todo Change all hardcoded Products table & In-Commerce module usage to dynamic usage from item config !!!
 	 */
 	function OnAdvancedSearch($event)
 	{
 		$query_object = $this->Application->recallObject('HTTPQuery');
 		/* @var $query_object kHTTPQuery */
 
 		if ( !isset($query_object->Post['andor']) ) {
 			// used when navigating by pages or changing sorting in search results
 			return;
 		}
 
 		$this->Application->RemoveVar('keywords');
 		$this->Application->RemoveVar('Search_Keywords');
 
 		$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
 
 		$sql = 'SELECT *
-				FROM '.$this->Application->getUnitOption('confs', 'TableName').'
+				FROM '.$this->Application->getUnitConfig('confs')->getTableName().'
 				WHERE (ModuleName = '.$this->Conn->qstr($module_name).') AND (AdvancedSearch = 1)';
 		$search_config = $this->Conn->Query($sql);
 
 		$lang = $this->Application->GetVar('m_lang');
 
 		$object = $event->getObject();
 		/* @var $object kDBList */
 
 		$object->SetPage(1);
 
-		$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
+		$config = $event->getUnitConfig();
+		$items_table = $config->getTableName();
 
 		$search_keywords = $this->Application->GetVar('value'); // will not be changed
 
 		$keywords = $this->Application->GetVar('value'); // will be changed down there
 		$verbs = $this->Application->GetVar('verb');
 		$glues = $this->Application->GetVar('andor');
 
 		$and_conditions = Array();
 		$or_conditions = Array();
 		$and_having_conditions = Array();
 		$or_having_conditions = Array();
 		$join_clauses = Array();
 		$highlight_keywords = Array();
 		$relevance_parts = Array();
 
 		$alias_counter = 0;
 
-		$custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
+		$custom_fields = $config->getCustomFields();
 		if ($custom_fields) {
-			$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
+			$custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName();
 			$join_clauses[] = '	LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
 		}
 
 		$search_log = '';
 		$weight_sum = 0;
 
 		// processing fields and preparing conditions
 		foreach ($search_config as $record) {
 			$field = $record['FieldName'];
 			$join_clause = '';
 			$condition_mode = 'WHERE';
 
 			// field processing
 			$local_table = TABLE_PREFIX.$record['TableName'];
 			$weight_sum += $record['Priority']; // counting weight sum; used when making relevance clause
 
 			// processing multilingual fields
 			if ( $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
 				$field_name = 'l'.$lang.'_'.$field;
 			}
 			else {
 				$field_name = $field;
 			}
 
 			// processing fields from other tables
 			$foreign_field = $record['ForeignField'];
 
 			if ( $foreign_field ) {
 				$exploded = explode(':', $foreign_field, 2);
 				if($exploded[0] == 'CALC')
 				{
 					$user_groups = $this->Application->RecallVar('UserGroups');
 					$field_name = str_replace('{PREFIX}', TABLE_PREFIX, $exploded[1]);
 					$join_clause = str_replace('{PREFIX}', TABLE_PREFIX, $record['JoinClause']);
 					$join_clause = str_replace('{USER_GROUPS}', $user_groups, $join_clause);
 					$join_clause = ' LEFT JOIN '.$join_clause;
 
 					$condition_mode = 'HAVING';
 				}
 				else {
 					$exploded = explode('.', $foreign_field);
 					$foreign_table = TABLE_PREFIX.$exploded[0];
 
 					if($record['CustomFieldId']) {
 						$exploded[1] = 'l'.$lang.'_'.$exploded[1];
 					}
 
 					$alias_counter++;
 					$alias = 't'.$alias_counter;
 
 					$field_name = $alias.'.'.$exploded[1];
 					$join_clause = str_replace('{ForeignTable}', $alias, $record['JoinClause']);
 					$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 					if($record['CustomFieldId'])
 					{
 						$join_clause .= ' AND '.$alias.'.CustomFieldId='.$record['CustomFieldId'];
 					}
 					$join_clause = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 										ON '.$join_clause;
 				}
 			}
 			else
 			{
 				// processing fields from local table
 				if ($record['CustomFieldId']) {
 					$local_table = 'custom_data';
 					$field_name = 'l'.$lang.'_cust_'.array_search($field_name, $custom_fields);
 				}
 
 				$field_name = $local_table.'.'.$field_name;
 			}
 
 			$condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
 			if ($record['CustomFieldId'] && strlen($condition)) {
 				// search in primary value of custom field + value in current language
 				$field_name = $local_table.'.'.'l'.$this->Application->GetDefaultLanguageId().'_cust_'.array_search($field, $custom_fields);
 				$primary_condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
 				$condition = '('.$condition.' OR '.$primary_condition.')';
 			}
 
 			if ($condition) {
 				if ($join_clause) {
 					$join_clauses[] = $join_clause;
 				}
 
 				$relevance_parts[] = 'IF('.$condition.', '.$record['Priority'].', 0)';
 				if ($glues[$field] == 1) { // and
 					if ($condition_mode == 'WHERE') {
 						$and_conditions[] = $condition;
 					}
 					else {
 						$and_having_conditions[] = $condition;
 					}
 				}
 				else { // or
 					if ($condition_mode == 'WHERE') {
 						$or_conditions[] = $condition;
 					}
 					else {
 						$or_having_conditions[] = $condition;
 					}
 				}
 
 				// create search log record
 				$search_log_data = Array('search_config' => $record, 'verb' => getArrayValue($verbs, $field), 'value' => ($record['FieldType'] == 'range') ? $search_keywords[$field.'_from'].'|'.$search_keywords[$field.'_to'] : $search_keywords[$field]);
 				$search_log[] = $this->Application->Phrase('la_Field').' "'.$this->getHuman('Field', $search_log_data).'" '.$this->getHuman('Verb', $search_log_data).' '.$this->Application->Phrase('la_Value').' '.$this->getHuman('Value', $search_log_data).' '.$this->Application->Phrase($glues[$field] == 1 ? 'lu_And' : 'lu_Or');
 			}
 		}
 
 		if ($search_log) {
 			$search_log = implode('<br />', $search_log);
 	    	$search_log = preg_replace('/(.*) '.preg_quote($this->Application->Phrase('lu_and'), '/').'|'.preg_quote($this->Application->Phrase('lu_or'), '/').'$/is', '\\1', $search_log);
 	    	$this->saveToSearchLog($search_log, 1); // advanced search
 		}
 
 		$this->Application->StoreVar('highlight_keywords', serialize($highlight_keywords));
 
 		// making relevance clause
 		if($relevance_parts)
 		{
-			$conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
+			$conf_postfix = $config->getSearchConfigPostfix();
 			$rel_keywords	= $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix)	/ 100;
 			$rel_pop		= $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix)		/ 100;
 			$rel_rating		= $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix)	/ 100;
 			$relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords;
 			$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 			$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 		}
 		else
 		{
 			$relevance_clause = '0';
 		}
 
 		// building having clause
 		if($or_having_conditions)
 		{
 			$and_having_conditions[] = '('.implode(' OR ', $or_having_conditions).')';
 		}
 		$having_clause = implode(' AND ', $and_having_conditions);
 		$having_clause = $having_clause ? ' HAVING '.$having_clause : '';
 
 		// building where clause
 		if($or_conditions)
 		{
 			$and_conditions[] = '('.implode(' OR ', $or_conditions).')';
 		}
 //		$and_conditions[] = $items_table.'.Status = 1';
 		$where_clause = implode(' AND ', $and_conditions);
 		if(!$where_clause)
 		{
 			if($having_clause)
 			{
 				$where_clause = '1';
 			}
 			else
 			{
 				$where_clause = '0';
 				$this->Application->SetVar('adv_search_error', 1);
 			}
 		}
 		$where_clause .= ' AND '.$items_table.'.Status = 1';
 
 		// building final search query
 		$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 
 		$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
 
-		$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-		$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
-		$pick_field = isset($fields['EditorsPick']) ? $items_table.'.EditorsPick' : '0';
+		$id_field = $config->getIDField();
+		$pick_field = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0';
 
 		$sql = '	CREATE TABLE '.$search_table.'
 					SELECT 	'.$relevance_clause.' AS Relevance,
 							'.$items_table.'.'.$id_field.' AS ItemId,
 							'.$items_table.'.ResourceId AS ResourceId,
 							11 AS ItemType,
 							'.$pick_field.' AS EdPick
 					FROM '.$items_table.'
 					'.implode(' ', $join_clauses).'
 					WHERE '.$where_clause.'
 					GROUP BY '.$items_table.'.'.$id_field.
 					$having_clause;
 
 		$res = $this->Conn->Query($sql);
 	}
 
 	function getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, &$highlight_keywords)
 	{
 		$field = $record['FieldName'];
 
 		$condition_patterns = Array (
 			'any'			=> '%s LIKE %s',
 			'contains'		=> '%s LIKE %s',
 			'notcontains'	=> '(NOT (%1$s LIKE %2$s) OR %1$s IS NULL)',
 			'is'			=> '%s = %s',
 			'isnot'			=> '(%1$s != %2$s OR %1$s IS NULL)'
 		);
 
 		$condition = '';
 		switch ($record['FieldType']) {
 			case 'select':
 				$keywords[$field] = htmlspecialchars_decode( $keywords[$field] );
 				if ($keywords[$field]) {
 					$condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] ));
 				}
 				break;
 
 			case 'multiselect':
 				$keywords[$field] = htmlspecialchars_decode( $keywords[$field] );
 				if ($keywords[$field]) {
 					$condition = Array ();
 					$values = explode('|', substr($keywords[$field], 1, -1));
 					foreach ($values as $selected_value) {
 						$condition[] = sprintf($condition_patterns['contains'], $field_name, $this->Conn->qstr('%|'.$selected_value.'|%'));
 					}
 					$condition = '('.implode(' OR ', $condition).')';
 				}
 				break;
 
 			case 'text':
 				$keywords[$field] = htmlspecialchars_decode( $keywords[$field] );
 
 				if (mb_strlen($keywords[$field]) >= $this->Application->ConfigValue('Search_MinKeyword_Length')) {
 					$highlight_keywords[] = $keywords[$field];
 					if (in_array($verbs[$field], Array('any', 'contains', 'notcontains'))) {
 						$keywords[$field] = '%'.strtr($keywords[$field], Array('%' => '\\%', '_' => '\\_')).'%';
 					}
 					$condition = sprintf($condition_patterns[$verbs[$field]], $field_name, $this->Conn->qstr( $keywords[$field] ));
 				}
 				break;
 
 			case 'boolean':
 				if ($keywords[$field] != -1) {
-					$property_mappings = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings');
-					$items_table = $this->Application->getUnitOption($this->Prefix, 'TableName');
+					$config = $this->getUnitConfig();
+					$items_table = $config->getTableName();
+					$property_mappings = $config->getItemPropertyMappings();
 
 					switch ($field) {
 						case 'HotItem':
 							$hot_limit_var = getArrayValue($property_mappings, 'HotLimit');
 							if ($hot_limit_var) {
 								$hot_limit = (int)$this->Application->getDBCache($hot_limit_var);
 
 								$condition = 	'IF('.$items_table.'.HotItem = 2,
 												IF('.$items_table.'.Hits >= '.
 												$hot_limit.
 												', 1, 0), '.$items_table.'.HotItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'PopItem':
 							$votes2pop_var = getArrayValue($property_mappings, 'VotesToPop');
 							$rating2pop_var = getArrayValue($property_mappings, 'RatingToPop');
 							if ($votes2pop_var && $rating2pop_var) {
 								$condition = 'IF('.$items_table.'.PopItem = 2, IF('.$items_table.'.CachedVotesQty >= '.
 												$this->Application->ConfigValue($votes2pop_var).
 												' AND '.$items_table.'.CachedRating >= '.
 												$this->Application->ConfigValue($rating2pop_var).
 												', 1, 0), '.$items_table.'.PopItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'NewItem':
 							$new_days_var = getArrayValue($property_mappings, 'NewDays');
 							if ($new_days_var) {
 								$condition = 	'IF('.$items_table.'.NewItem = 2,
 												IF('.$items_table.'.CreatedOn >= (UNIX_TIMESTAMP() - '.
 												$this->Application->ConfigValue($new_days_var).
 												'*3600*24), 1, 0), '.$items_table.'.NewItem) = '.$keywords[$field];
 							}
 							break;
 
 						case 'EditorsPick':
 							$condition = $items_table.'.EditorsPick = '.$keywords[$field];
 							break;
 					}
 				}
 				break;
 
 			case 'range':
 				$range_conditions = Array();
 				if ($keywords[$field.'_from'] && !preg_match("/[^0-9]/i", $keywords[$field.'_from'])) {
 					$range_conditions[] = $field_name.' >= '.$keywords[$field.'_from'];
 				}
 				if ($keywords[$field.'_to'] && !preg_match("/[^0-9]/i", $keywords[$field.'_to'])) {
 					$range_conditions[] = $field_name.' <= '.$keywords[$field.'_to'];
 				}
 				if ($range_conditions) {
 					$condition = implode(' AND ', $range_conditions);
 				}
 				break;
 
 			case 'date':
 				if ($keywords[$field]) {
 					if (in_array($keywords[$field], Array('today', 'yesterday'))) {
 						$current_time = getdate();
 						$day_begin = adodb_mktime(0, 0, 0, $current_time['mon'], $current_time['mday'], $current_time['year']);
 						$time_mapping = Array('today' => $day_begin, 'yesterday' => ($day_begin - 86400));
 						$min_time = $time_mapping[$keywords[$field]];
 					}
 					else {
 						$time_mapping = Array (
 							'last_week' => 604800, 'last_month' => 2628000, 'last_3_months' => 7884000,
 							'last_6_months' => 15768000, 'last_year' => 31536000,
 						);
 						$min_time = adodb_mktime() - $time_mapping[$keywords[$field]];
 					}
 					$condition = $field_name.' > '.$min_time;
 				}
 				break;
 		}
 
 		return $condition;
 	}
 
 	/**
 	 * Returns human readable representation of searched data to be placed in search log
 	 * @param string $type
 	 * @param Array $search_data
 	 * @return string
 	 * @access protected
 	 */
 	protected function getHuman($type, $search_data)
 	{
 		// all 3 variables are retrieved from $search_data array
 		/* @var $search_config Array */
 		/* @var $verb string */
 		/* @var $value string */
 
 		$type = ucfirst(strtolower($type));
 		extract($search_data);
 
 		switch ($type) {
 			case 'Field':
 				return $this->Application->Phrase($search_config['DisplayName']);
 				break;
 
 			case 'Verb':
 				return $verb ? $this->Application->Phrase('lu_advsearch_'.$verb) : '';
 				break;
 
 			case 'Value':
 				switch ($search_config['FieldType']) {
 					case 'date':
 						$values = Array(0 => 'lu_comm_Any', 'today' => 'lu_comm_Today',
 										'yesterday' => 'lu_comm_Yesterday', 'last_week' => 'lu_comm_LastWeek',
 										'last_month' => 'lu_comm_LastMonth', 'last_3_months' => 'lu_comm_Last3Months',
 										'last_6_months' => 'lu_comm_Last6Months', 'last_year' => 'lu_comm_LastYear');
 						$ret = $this->Application->Phrase($values[$value]);
 						break;
 
 					case 'range':
 						$value = explode('|', $value);
 						return $this->Application->Phrase('lu_comm_From').' "'.$value[0].'" '.$this->Application->Phrase('lu_comm_To').' "'.$value[1].'"';
 						break;
 
 					case 'boolean':
 						$values = Array(1 => 'lu_comm_Yes', 0 => 'lu_comm_No', -1 => 'lu_comm_Both');
 						$ret = $this->Application->Phrase($values[$value]);
 						break;
 
 					default:
 						$ret = $value;
 						break;
 
 				}
 				return '"'.$ret.'"';
 				break;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Set's correct page for list based on data provided with event
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 * @see kDBEventHandler::OnListBuild()
 	 */
 	protected function SetPagination(kEvent $event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBList */
 
 		// get PerPage (forced -> session -> config -> 10)
 		$object->SetPerPage($this->getPerPage($event));
 
 		// main lists on Front-End have special get parameter for page
 		$page = $object->isMainList() ? $this->Application->GetVar('page') : false;
 
 		if ( !$page ) {
 			// page is given in "env" variable for given prefix
 			$page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page');
 		}
 
 		if ( !$page && $event->Special ) {
 			// when not part of env, then variables like "prefix.special_Page" are
 			// replaced (by PHP) with "prefix_special_Page", so check for that too
 			$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
 		}
 
 		if ( !$object->isMainList() ) {
 			// main lists doesn't use session for page storing
 			$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
 
 			if ( !$page ) {
 				if ( $this->Application->RewriteURLs() ) {
 					// when page not found by prefix+special, then try to search it without special at all
 					$page = $this->Application->GetVar($event->Prefix . '_Page');
 
 					if ( !$page ) {
 						// page not found in request -> get from session
 						$page = $this->Application->RecallVar($event->Prefix . '_Page');
 					}
 
 					if ( $page ) {
 						// page found in request -> store in session
 						$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
 					}
 				}
 				else {
 					// page not found in request -> get from session
 					$page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page');
 				}
 			}
 			else {
 				// page found in request -> store in session
 				$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
 			}
 
 			if ( !$event->getEventParam('skip_counting') ) {
 				// when stored page is larger, then maximal list page number
 				// (such case is also processed in kDBList::Query method)
 				$pages = $object->GetTotalPages();
 
 				if ( $page > $pages ) {
 					$page = 1;
 					$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true);
 				}
 			}
 		}
 
 		$object->SetPage($page);
 	}
 
 /* === RELATED TO IMPORT/EXPORT: BEGIN === */
 
 	/**
 	 * Shows export dialog
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnExport(kEvent $event)
 	{
 		$selected_ids = $this->StoreSelectedIDs($event);
 		if ( implode(',', $selected_ids) == '' ) {
 			// K4 fix when no ids found bad selected ids array is formed
 			$selected_ids = false;
 		}
 
 		$selected_cats_ids = $this->Application->GetVar('export_categories');
 
 		$this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : '');
 		$this->Application->StoreVar($event->Prefix . '_export_cats_ids', $selected_cats_ids);
 
 		$export_helper = $this->Application->recallObject('CatItemExportHelper');
 		/* @var $export_helper kCatDBItemExportHelper */
 
 		$redirect_params = Array (
 			$this->Prefix . '.export_event' => 'OnNew',
 			'pass' => 'all,' . $this->Prefix . '.export'
 		);
 
 		$event->setRedirectParams($redirect_params);
 	}
 
 	/**
 	 * Performs each export step & displays progress percent
 	 *
 	 * @param kEvent $event
 	 */
 	function OnExportProgress($event)
 	{
 		$export_object = $this->Application->recallObject('CatItemExportHelper');
 		/* @var $export_object kCatDBItemExportHelper */
 
 		$event = new kEvent($event->getPrefixSpecial().':OnDummy');
 
 		$action_method = 'perform'.ucfirst($event->Special);
 		$field_values = $export_object->$action_method($event);
 
 		// finish code is done from JS now
 		if ($field_values['start_from'] == $field_values['total_records']) {
 			if ($event->Special == 'import') {
 				$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 
 				$event->SetRedirectParam('m_cat_id', $this->Application->RecallVar('ImportCategory'));
 				$event->SetRedirectParam('anchor', 'tab-' . $event->Prefix);
 
 				$event->redirect = 'catalog/catalog';
 			}
 			elseif ($event->Special == 'export') {
 				$event->redirect = $export_object->getModuleName($event) . '/' . $event->Special . '_finish';
 				$event->SetRedirectParam('pass', 'all');
 			}
 
 			return ;
 		}
 
 		$export_options = $export_object->loadOptions($event);
 		echo $export_options['start_from']  * 100 / $export_options['total_records'];
 
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Returns specific to each item type columns only
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 * @access protected
 	 */
 	public function getCustomExportColumns(kEvent $event)
 	{
 		return Array (
 			'__VIRTUAL__ThumbnailImage'	=>	'ThumbnailImage',
 			'__VIRTUAL__FullImage' 		=>	'FullImage',
 			'__VIRTUAL__ImageAlt'		=>	'ImageAlt'
 		);
 	}
 
 	/**
 	 * Sets non standart virtual fields (e.g. to other tables)
 	 *
 	 * @param kEvent $event
 	 */
 	function setCustomExportColumns($event)
 	{
 		$this->restorePrimaryImage($event);
 	}
 
 	/**
 	 * Create/Update primary image record in info found in imported data
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function restorePrimaryImage($event)
 	{
 		$object = $event->getObject();
 		/* @var $object kCatDBItem */
 
 		$has_image_info = $object->GetDBField('ImageAlt') && ($object->GetDBField('ThumbnailImage') || $object->GetDBField('FullImage'));
 		if ( !$has_image_info ) {
 			return ;
 		}
 
 		$image_data = $object->getPrimaryImageData();
 
 		$image = $this->Application->recallObject('img', NULL, Array ('skip_autoload' => true));
 		/* @var $image kDBItem */
 
 		if ( $image_data ) {
 			$image->Load($image_data['ImageId']);
 		}
 		else {
 			$image->Clear();
 			$image->SetDBField('Name', 'main');
 			$image->SetDBField('DefaultImg', 1);
 			$image->SetDBField('ResourceId', $object->GetDBField('ResourceId'));
 		}
 
 		$image->SetDBField('AltName', $object->GetDBField('ImageAlt'));
 
 		if ( $object->GetDBField('ThumbnailImage') ) {
 			$thumbnail_field = $this->isURL($object->GetDBField('ThumbnailImage')) ? 'ThumbUrl' : 'ThumbPath';
 			$image->SetDBField($thumbnail_field, $object->GetDBField('ThumbnailImage'));
 			$image->SetDBField('LocalThumb', $thumbnail_field == 'ThumbPath' ? 1 : 0);
 		}
 
 		if ( !$object->GetDBField('FullImage') ) {
 			$image->SetDBField('SameImages', 1);
 		}
 		else {
 			$image->SetDBField('SameImages', 0);
 			$full_field = $this->isURL($object->GetDBField('FullImage')) ? 'Url' : 'LocalPath';
 			$image->SetDBField($full_field, $object->GetDBField('FullImage'));
 			$image->SetDBField('LocalImage', $full_field == 'LocalPath' ? 1 : 0);
 		}
 
 		if ( $image->isLoaded() ) {
 			$image->Update();
 		}
 		else {
 			$image->Create();
 		}
 	}
 
 	/**
 	 * Detects if image url is specified in a given path (instead of path on disk)
 	 *
 	 * @param string $path
 	 * @return bool
 	 * @access protected
 	 */
 	protected function isURL($path)
 	{
 		return preg_match('#(http|https)://(.*)#', $path);
 	}
 
 	/**
 	 * Prepares item for import/export operations
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnNew(kEvent $event)
 	{
 		parent::OnNew($event);
 
 		if ( $event->Special == 'import' || $event->Special == 'export' ) {
 			$export_helper = $this->Application->recallObject('CatItemExportHelper');
 			/* @var $export_helper kCatDBItemExportHelper */
 
 			$export_helper->setRequiredFields($event);
 		}
 	}
 
 	/**
 	 * Process items selected in item_selector
 	 *
 	 * @param kEvent $event
 	 */
 	function OnProcessSelected($event)
 	{
 		$dst_field = $this->Application->RecallVar('dst_field');
 		$selected_ids = $this->Application->GetVar('selected_ids');
 
 		if ( $dst_field == 'ItemCategory' ) {
 			// Item Edit -> Categories Tab -> New Categories
 			$object = $event->getObject();
 			/* @var $object kCatDBItem */
 
 			$category_ids = explode(',', $selected_ids['c']);
 			foreach ($category_ids as $category_id) {
 				$object->assignToCategory($category_id);
 			}
 		}
 
 		if ($dst_field == 'ImportCategory') {
 			// Tools -> Import -> Item Import -> Select Import Category
 			$this->Application->StoreVar('ImportCategory', $selected_ids['c']);
 
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_id', 0);
 			$event->SetRedirectParam($event->getPrefixSpecial() . '_event', 'OnExportBegin');
 		}
 
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Saves Import/Export settings to session
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSaveSettings($event)
 	{
 		$event->redirect = false;
 		$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 		if ( $items_info ) {
 			list($id, $field_values) = each($items_info);
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 			$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
 			$field_values['ImportSource'] = 2;
 			$field_values['ImportLocalFilename'] = $object->GetDBField('ImportFilename');
 			$items_info[$id] = $field_values;
 
 			$this->Application->StoreVar($event->getPrefixSpecial() . '_ItemsInfo', serialize($items_info));
 		}
 	}
 
 	/**
 	 * Saves Import/Export settings to session
 	 *
 	 * @param kEvent $event
 	 */
 	function OnResetSettings($event)
 	{
 		$this->Application->StoreVar('ImportCategory', $this->Application->getBaseCategory());
 	}
 
 	/**
 	 * Cancels item editing
 	 * @param kEvent $event
 	 * @return void
 	 * @todo Used?
 	 */
 	function OnCancelAction($event)
 	{
 		$event->redirect = $this->Application->GetVar('cancel_template');
 		$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
 	}
 
 /* === RELATED TO IMPORT/EXPORT: END === */
 
 	/**
 	 * Stores item's owner login into separate field together with id
 	 *
 	 * @param kEvent $event
 	 * @param string $id_field
 	 * @param string $cached_field
 	 */
 	function cacheItemOwner($event, $id_field, $cached_field)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$object->SetDBField($cached_field, $object->GetField($id_field));
+		$options = $object->GetFieldOptions($id_field);
+
+		if ( isset($options['options'][$user_id]) ) {
+			$object->SetDBField($cached_field, $options['options'][$user_id]);
+		}
+		else {
+			$user_config = $this->Application->getUnitConfig('u');
+			$id_field = $user_config->getIDField();
+			$table_name = $user_config->getTableName();
+
+			$sql = 'SELECT Username
+					FROM ' . $table_name . '
+					WHERE ' . $id_field . ' = ' . $user_id;
+			$object->SetDBField($cached_field, $this->Conn->GetOne($sql));
+		}
 	}
 
 	/**
 	 * Saves edited item into temp table
 	 * If there is no id, new item is created in temp table
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnPreSave(kEvent $event)
 	{
 		parent::OnPreSave($event);
 
-		$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
+		$use_pending_editing = $event->getUnitConfig()->getUsePendingEditing();
 
 		if ( $event->status == kEvent::erSUCCESS && $use_pending_editing ) {
 			// decision: clone or not clone
 
 			$object = $event->getObject();
 			/* @var $object kCatDBItem */
 
 			if ( $object->GetID() == 0 || $object->GetDBField('OrgId') > 0 ) {
 				// new items or cloned items shouldn't be cloned again
 				return ;
 			}
 
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$owner_field = $this->getOwnerField($event->Prefix);
 
 			if ( $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix) == 2 ) {
 
 				// 1. clone original item
 				$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 				/* @var $temp_handler kTempTablesHandler */
 
 				$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array ($object->GetID()), NULL, NULL, NULL, true);
 				$ci_table = $this->Application->GetTempName(TABLE_PREFIX . 'CategoryItems');
 
 				// 2. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
 				$sql = 'SELECT ResourceId
 						FROM ' . $object->TableName . '
 						WHERE ' . $object->IDField . ' = ' . $cloned_ids[0];
 				$clone_resource_id = $this->Conn->GetOne($sql);
 
 				$sql = 'DELETE FROM ' . $ci_table . '
 						WHERE ItemResourceId = ' . $clone_resource_id . ' AND PrimaryCat = 1';
 				$this->Conn->Query($sql);
 
 				// 3. copy main item categoryitems to cloned item
 				$sql = '	INSERT INTO ' . $ci_table . ' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
 							SELECT CategoryId, ' . $clone_resource_id . ' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
 							FROM ' . $ci_table . '
 							WHERE ItemResourceId = ' . $object->GetDBField('ResourceId');
 				$this->Conn->Query($sql);
 
 				// 4. put cloned id to OrgId field of item being cloned
 				$sql = 'UPDATE ' . $object->TableName . '
 						SET OrgId = ' . $object->GetID() . '
 						WHERE ' . $object->IDField . ' = ' . $cloned_ids[0];
 				$this->Conn->Query($sql);
 
 				// 5. substitute id of item being cloned with clone id
 				$this->Application->SetVar($event->getPrefixSpecial() . '_id', $cloned_ids[0]);
 
 				$selected_ids = $this->getSelectedIDs($event, true);
 				$selected_ids[ array_search($object->GetID(), $selected_ids) ] = $cloned_ids[0];
 				$this->StoreSelectedIDs($event, $selected_ids);
 
 				// 6. delete original item from temp table
 				$temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($object->GetID()));
 			}
 		}
 	}
 
 	/**
 	 * Sets item's owner field
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnPreCreate(kEvent $event)
 	{
 		parent::OnPreCreate($event);
 
 		if ( $event->status != kEvent::erSUCCESS ) {
 			return ;
 		}
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$owner_field = $this->getOwnerField($event->Prefix);
 		$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
 	}
 
 	/**
 	 * Occurs before original item of item in pending editing got deleted (for hooking only)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeDeleteOriginal(kEvent $event)
 	{
 
 	}
 
 	/**
 	 * Occurs after original item of item in pending editing got deleted (for hooking only)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterDeleteOriginal(kEvent $event)
 	{
 
 	}
 
 	/**
 	 * Occurs before an item has been cloned
 	 * Id of newly created item is passed as event' 'id' param
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeClone(kEvent $event)
 	{
 		parent::OnBeforeClone($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$object->SetDBField('ResourceId', 0); // this will reset it
 
 		if ( $this->Application->GetVar('ResetCatBeforeClone') ) {
 			$object->SetDBField('CategoryId', NULL);
 		}
 	}
 
 	/**
 	 * Set status for new category item based on user permission in category
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemCreate(kEvent $event)
 	{
 		parent::OnBeforeItemCreate($event);
 
 		$object = $event->getObject();
 		/* @var $object kCatDBItem */
 
 		$is_admin = $this->Application->isAdminUser;
 		$owner_field = $this->getOwnerField($event->Prefix);
 
 		if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField($owner_field)) ) {
 			// Front-end OR owner not specified -> set to currently logged-in user
 			$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
 		}
 
 		if ( !$this->Application->isAdmin ) {
 			$this->setItemStatusByPermission($event);
 		}
 	}
 
 	/**
 	 * Sets category item status based on user permissions (only on Front-end)
 	 *
 	 * @param kEvent $event
 	 */
 	function setItemStatusByPermission($event)
 	{
-		$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
+		$use_pending_editing = $event->getUnitConfig()->getUsePendingEditing();
 
 		if (!$use_pending_editing) {
 			return ;
 		}
 
 		$object = $event->getObject();
 		/* @var $object kCatDBItem */
 
 		$perm_helper = $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		$primary_category = $object->GetDBField('CategoryId') > 0 ? $object->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id');
 		$item_status = $perm_helper->AddCheckPermission($primary_category, $event->Prefix);
 
 		if ($item_status == STATUS_DISABLED) {
 			$event->status = kEvent::erFAIL;
 		}
 		else {
 			$object->SetDBField('Status', $item_status);
 		}
 	}
 
 	/**
 	 * Creates category item & redirects to confirmation template (front-end only)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCreate(kEvent $event)
 	{
 		parent::OnCreate($event);
 
 		$this->SetFrontRedirectTemplate($event, 'suggest');
 	}
 
 	/**
 	 * Returns item's categories (allows to exclude primary category)
 	 *
 	 * @param int $resource_id
 	 * @param bool $with_primary
 	 * @return Array
 	 */
 	function getItemCategories($resource_id, $with_primary = false)
 	{
 		$sql = 'SELECT CategoryId
 				FROM '.TABLE_PREFIX.'CategoryItems
 				WHERE (ItemResourceId = '.$resource_id.')';
 
 		if (!$with_primary) {
 			$sql .= ' AND (PrimaryCat = 0)';
 		}
 
 		return $this->Conn->GetCol($sql);
 	}
 
 	/**
 	 * Adds new and removes old additional categories from category item
 	 *
 	 * @param kCatDBItem $object
 	 * @param int $mode
 	 */
 	function processAdditionalCategories(&$object, $mode)
 	{
 		if ( !$object->isVirtualField('MoreCategories') ) {
 			// given category item doesn't require such type of processing
 			return ;
 		}
 
 		$process_categories = $object->GetDBField('MoreCategories');
 		if ($process_categories === '') {
 			// field was not in submit & have default value (when no categories submitted, then value is null)
 			return ;
 		}
 
 		if ($mode == 'create') {
 			// prevents first additional category to become primary
 			$object->assignPrimaryCategory();
 		}
 
 		$process_categories = $process_categories ? explode('|', substr($process_categories, 1, -1)) : Array ();
 		$existing_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
 
 		$add_categories = array_diff($process_categories, $existing_categories);
 		foreach ($add_categories as $category_id) {
 			$object->assignToCategory($category_id);
 		}
 
 		$remove_categories = array_diff($existing_categories, $process_categories);
 		foreach ($remove_categories as $category_id) {
 			$object->removeFromCategory($category_id);
 		}
 	}
 
 	/**
 	 * Creates category item & redirects to confirmation template (front-end only)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnUpdate(kEvent $event)
 	{
-		$use_pending = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
+		$use_pending = $event->getUnitConfig()->getUsePendingEditing();
 		if ($this->Application->isAdminUser || !$use_pending) {
 			parent::OnUpdate($event);
 			$this->SetFrontRedirectTemplate($event, 'modify');
 			return ;
 		}
 
 		$object = $event->getObject(Array('skip_autoload' => true));
 		/* @var $object kCatDBItem */
 
 		$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 		if ($items_info) {
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$owner_field = $this->getOwnerField($event->Prefix);
 
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			foreach ($items_info as $id => $field_values) {
 				$object->Load($id);
 				$edit_perm = $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix);
 
 				if ($use_pending && !$object->GetDBField('OrgId') && ($edit_perm == STATUS_PENDING)) {
 					// pending editing enabled + not pending copy -> get/create pending copy & save changes to it
 					$original_id = $object->GetID();
 					$original_resource_id = $object->GetDBField('ResourceId');
 					$file_helper->PreserveItemFiles($field_values);
 
 					$object->Load($original_id, 'OrgId');
 					if (!$object->isLoaded()) {
 						// 1. user has no pending copy of live item -> clone live item
 						$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($original_id), NULL, NULL, NULL, true);
 
 						$object->Load($cloned_ids[0]);
 						$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 						// 1a. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
-						$ci_table = $this->Application->getUnitOption('ci', 'TableName');
+						$ci_table = $this->Application->getUnitConfig('ci')->getTableName();
 						$sql = 'DELETE FROM '.$ci_table.'
 								WHERE ItemResourceId = '.$object->GetDBField('ResourceId').' AND PrimaryCat = 1';
 						$this->Conn->Query($sql);
 
 						// 1b. copy main item categoryitems to cloned item
 						$sql = 'INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
 								SELECT CategoryId, '.$object->GetDBField('ResourceId').' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
 								FROM '.$ci_table.'
 								WHERE ItemResourceId = '.$original_resource_id;
 						$this->Conn->Query($sql);
 
 						// 1c. put cloned id to OrgId field of item being cloned
 						$object->SetDBField('Status', STATUS_PENDING_EDITING);
 						$object->SetDBField('OrgId', $original_id);
 					}
 					else {
 						// 2. user has pending copy of live item -> just update field values
 						$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 					}
 
 					// update id in request (used for redirect in mod-rewrite mode)
 					$this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetID());
 				}
 				else {
 					// 3. already editing pending copy -> just update field values
 					$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 				}
 
 				if ($object->Update()) {
 					$event->status = kEvent::erSUCCESS;
 				}
 				else {
 					$event->status = kEvent::erFAIL;
 					$event->redirect = false;
 					break;
 				}
 			}
 		}
 
 		$this->SetFrontRedirectTemplate($event, 'modify');
 	}
 
 	/**
 	 * Sets next template to one required for front-end after adding/modifying item
 	 *
 	 * @param kEvent $event
 	 * @param string $template_key - {suggest,modify}
 	 */
 	function SetFrontRedirectTemplate($event, $template_key)
 	{
 		if ( $this->Application->isAdmin || $event->status != kEvent::erSUCCESS ) {
 			return;
 		}
 
 		// prepare redirect template
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
 
 		$next_template = $is_active ? 'confirm_template' : 'pending_confirm_template';
 		$event->redirect = $this->Application->GetVar($template_key . '_' . $next_template);
 		$event->SetRedirectParam('opener', 's');
 
 		// send email events
-		$perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix');
+		$perm_prefix = $event->getUnitConfig()->getPermItemPrefix();
 		$owner_field = $this->getOwnerField($event->Prefix);
 		$owner_id = $object->GetDBField($owner_field);
 
 		switch ( $event->Name ) {
 			case 'OnCreate':
 				$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
 				$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix); // there are no ADD.PENDING event for admin :(
 				$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $owner_id);
 				break;
 
 			case 'OnUpdate':
 				$event_suffix = $is_active ? 'MODIFY' : 'MODIFY.PENDING';
 				$user_id = is_numeric($object->GetDBField('ModifiedById')) ? $object->GetDBField('ModifiedById') : $owner_id;
 				$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix); // there are no ADD.PENDING event for admin :(
 				$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $user_id);
 				break;
 		}
 	}
 
 	/**
 	 * Apply same processing to each item being selected in grid
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function iterateItems(kEvent $event)
 	{
 		if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) {
 			parent::iterateItems($event);
 		}
 
 		if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 			$event->status = kEvent::erFAIL;
 			return ;
 		}
 
 		$object = $event->getObject(Array ('skip_autoload' => true));
 		/* @var $object kCatDBItem */
 
 		$ids = $this->StoreSelectedIDs($event);
 
 		if ( $ids ) {
 			foreach ($ids as $id) {
 				$ret = true;
 				$object->Load($id);
 
 				switch ( $event->Name ) {
 					case 'OnMassApprove':
 						$ret = $object->ApproveChanges();
 						break;
 
 					case 'OnMassDecline':
 						$ret = $object->DeclineChanges();
 						break;
 				}
 
 				if ( !$ret ) {
 					$event->status = kEvent::erFAIL;
 					$event->redirect = false;
 					break;
 				}
 			}
 		}
 
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Deletes items & preserves clean env
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDelete(kEvent $event)
 	{
 		parent::OnDelete($event);
 
 		if ( $event->status == kEvent::erSUCCESS && !$this->Application->isAdmin ) {
 			$event->SetRedirectParam('pass', 'm');
 			$event->SetRedirectParam('m_cat_id', 0);
 		}
 	}
 
 	/**
 	 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access protected
 	 */
 	protected function checkItemStatus(kEvent $event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ( !$object->isLoaded() ) {
 			if ( $event->Special != 'previous' && $event->Special != 'next' ) {
 				$this->_errorNotFound($event);
 			}
 
 			return true;
 		}
 
 		$status = $object->GetDBField('Status');
 		$user_id = $this->Application->RecallVar('user_id');
 		$owner_field = $this->getOwnerField($event->Prefix);
 
 		if ( ($status == STATUS_PENDING_EDITING || $status == STATUS_PENDING) && ($object->GetDBField($owner_field) == $user_id) ) {
 			return true;
 		}
 
 		return $status == STATUS_ACTIVE;
 	}
 
 	/**
 	 * Set's correct sorting for list based on data provided with event
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 * @see kDBEventHandler::OnListBuild()
 	 */
 	protected function SetSorting(kEvent $event)
 	{
 		if ( !$this->Application->isAdmin ) {
 			$event->setEventParam('same_special', true);
 		}
 
 		$types = $event->getEventParam('types');
 		$types = $types ? explode(',', $types) : Array ();
 
 		if ( in_array('search', $types) ) {
 			$event->setPseudoClass('_List');
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			// 1. no user sorting - sort by relevance
 			$default_sortings = parent::_getDefaultSorting($event);
 			$default_sorting = key($default_sortings['Sorting']) . ',' . current($default_sortings['Sorting']);
 
 			if ( $object->isMainList() ) {
 				$sort_by = $this->Application->GetVar('sort_by', '');
 
 				if ( !$sort_by ) {
 					$this->Application->SetVar('sort_by', 'Relevance,desc|' . $default_sorting);
 				}
 				elseif ( strpos($sort_by, 'Relevance,') !== false ) {
 					$this->Application->SetVar('sort_by', $sort_by . '|' . $default_sorting);
 				}
 			}
 			else {
 				$sorting_settings = $this->getListSetting($event, 'Sortings');
 				$sort_by = trim(getArrayValue($sorting_settings, 'Sort1') . ',' . getArrayValue($sorting_settings, 'Sort1_Dir'), ',');
 
 				if ( !$sort_by ) {
 					$event->setEventParam('sort_by', 'Relevance,desc|' . $default_sorting);
 				}
 				elseif ( strpos($sort_by, 'Relevance,') !== false ) {
 					$event->setEventParam('sort_by', $sort_by . '|' . $default_sorting);
 				}
 			}
 
 			$this->_removeForcedSortings($event);
 		}
 
 		parent::SetSorting($event);
 	}
 
 	/**
 	 * Removes forced sortings
 	 *
 	 * @param kEvent $event
 	 */
 	protected function _removeForcedSortings(kEvent $event)
 	{
-		$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
-		/* @var $list_sortings Array */
+		$config = $event->getUnitConfig();
 
-		foreach ($list_sortings as $special => $sortings) {
-			unset($list_sortings[$special]['ForcedSorting']);
+		foreach ($config->getListSortingSpecials() as $special) {
+			$list_sortings = $config->getListSortingsBySpecial($special);
+			unset($list_sortings['ForcedSorting']);
+			$config->setListSortingsBySpecial('', $list_sortings);
 		}
-
-		$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
 	}
 
 	/**
 	 * Default sorting in search results only comes from relevance field
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 * @access protected
 	 */
 	protected function _getDefaultSorting(kEvent $event)
 	{
 		$types = $event->getEventParam('types');
 		$types = $types ? explode(',', $types) : Array ();
 
 		return in_array('search', $types) ? Array () : parent::_getDefaultSorting($event);
 	}
 
 	/**
 	 * Returns current per-page setting for list
 	 *
 	 * @param kEvent $event
 	 * @return int
 	 * @access protected
 	 */
 	protected function getPerPage(kEvent $event)
 	{
 		if ( !$this->Application->isAdmin ) {
 			$event->setEventParam('same_special', true);
 		}
 
 		return parent::getPerPage($event);
 	}
 
 	/**
 	 * Returns owner field for given prefix
 	 *
 	 * @param $prefix
 	 * @return string
 	 * @access protected
 	 */
 	protected function getOwnerField($prefix)
 	{
-		return $this->Application->getUnitOption($prefix, 'OwnerField', 'CreatedById');
+		return $this->Application->getUnitConfig($prefix)->getOwnerField('CreatedById');
 	}
 
 	/**
 	 * Creates virtual image fields for item
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterConfigRead(kEvent $event)
 	{
 		parent::OnAfterConfigRead($event);
 
 		if (defined('IS_INSTALL') && IS_INSTALL) {
 			return ;
 		}
 
 		if ( !$this->Application->isAdmin ) {
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->createItemFiles($event->Prefix, true); // create image fields
 			$file_helper->createItemFiles($event->Prefix, false); // create file fields
 		}
 
 		$this->changeSortings($event);
 
 		// add grids for advanced view (with primary category column)
-		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-		$process_grids = Array ('Default', 'Radio');
-		foreach ($process_grids as $process_grid) {
-			$grid_data = $grids[$process_grid];
+		$config = $event->getUnitConfig();
+
+		foreach (Array ('Default', 'Radio') as $process_grid) {
+			$grid_data = $config->getGridByName($process_grid);
 			$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_primary_category_td', 'filter_block' => 'grid_like_filter');
-			$grids[$process_grid . 'ShowAll'] = $grid_data;
+			$config->addGrids($grid_data, $process_grid . 'ShowAll');
 		}
-		$this->Application->setUnitOption($this->Prefix, 'Grids', $grids);
 
 		// add options for CategoryId field (quick way to select item's primary category)
 		$category_helper = $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
-		$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
-
+		$virtual_fields = $config->getVirtualFields();
 		$virtual_fields['CategoryId']['default'] = (int)$this->Application->GetVar('m_cat_id');
 		$virtual_fields['CategoryId']['options'] = $category_helper->getStructureTreeAsOptions();
-
-		$this->Application->setUnitOption($event->Prefix, 'VirtualFields', $virtual_fields);
+		$config->setVirtualFields($virtual_fields);
 	}
 
-	function changeSortings($event)
+	function changeSortings(kEvent $event)
 	{
 		$remove_sortings = Array ();
+		$config = $event->getUnitConfig();
 
 		if ( !$this->Application->isAdmin ) {
 			// remove Pick sorting on Front-end, when not required
-			$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping', Array ());
+			$config_mapping = $config->getConfigMapping(Array ());
 
 			if ( !isset($config_mapping['ForceEditorPick']) || !$this->Application->ConfigValue($config_mapping['ForceEditorPick']) ) {
 				$remove_sortings[] = 'EditorsPick';
 			}
 		}
 		else {
 			// remove all forced sortings in Admin Console
 			$remove_sortings = array_merge($remove_sortings, Array ('Priority', 'EditorsPick'));
 		}
 
 		if ( !$remove_sortings ) {
 			return;
 		}
 
-		$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
-		/* @var $list_sortings Array */
+		foreach ($config->getListSortingSpecials() as $special) {
+			$list_sortings = $config->getListSortingsBySpecial($special);
 
-		foreach ($list_sortings as $special => $sorting_fields) {
 			foreach ($remove_sortings as $sorting_field) {
-				unset($list_sortings[$special]['ForcedSorting'][$sorting_field]);
+				unset($list_sortings['ForcedSorting'][$sorting_field]);
 			}
-		}
 
-		$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
+			$config->setListSortingsBySpecial('', $list_sortings);
+		}
 	}
 
 	/**
 	 * Returns file contents associated with item
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDownloadFile($event)
 	{
 		$object = $event->getObject();
 		/* @var $object kCatDBItem */
 
 		$event->status = kEvent::erSTOP;
 
 		$field = $this->Application->GetVar('field');
 		if (!preg_match('/^File([\d]+)/', $field)) {
 			return ;
 		}
 
 		$file_helper = $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$filename = $object->GetField($field, 'full_path');
 		$file_helper->DownloadFile($filename);
 	}
 
 	/**
 	 * Saves user's vote
 	 *
 	 * @param kEvent $event
 	 */
 	function OnMakeVote($event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		if ($this->Application->GetVar('ajax') != 'yes') {
 			// this is supposed to call from AJAX only
 			return ;
 		}
 
 		$rating_helper = $this->Application->recallObject('RatingHelper');
 		/* @var $rating_helper RatingHelper */
 
 		$object = $event->getObject( Array ('skip_autoload' => true) );
 		/* @var $object kCatDBItem */
 
 		$object->Load( $this->Application->GetVar('id') );
 
 		echo $rating_helper->makeVote($object);
 	}
 
 	/**
 	 * Marks review as useful
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnReviewHelpful($event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$review_id = (int)$this->Application->GetVar('review_id');
 
 		if ( !$review_id ) {
 			return;
 		}
 
 		$spam_helper = $this->Application->recallObject('SpamHelper');
 		/* @var $spam_helper SpamHelper */
 
 		$spam_helper->InitHelper($review_id, 'ReviewHelpful', strtotime('+1 month') - strtotime('now'));
 		$field = (int)$this->Application->GetVar('helpful') ? 'HelpfulCount' : 'NotHelpfulCount';
 
+		$review_config = $this->Application->getUnitConfig('rev');
+
 		$sql = 'SELECT ' . $field . '
-				FROM ' . $this->Application->getUnitOption('rev', 'TableName') . '
-				WHERE ' . $this->Application->getUnitOption('rev', 'IDField') . ' = ' . $review_id;
+				FROM ' . $review_config->getTableName() . '
+				WHERE ' . $review_config->getIDField() . ' = ' . $review_id;
 		$count = $this->Conn->GetOne($sql);
 
 		if ( $spam_helper->InSpamControl() ) {
 			if ( $this->Application->GetVar('ajax') == 'yes' ) {
 				echo $count;
 			}
 
 			return;
 		}
 
-		$sql = 'UPDATE ' . $this->Application->getUnitOption('rev', 'TableName') . '
+		$sql = 'UPDATE ' . $review_config->getTableName() . '
 				SET ' . $field . ' = ' . $field . ' + 1
-				WHERE ' . $this->Application->getUnitOption('rev', 'IDField') . ' = ' . $review_id;
+				WHERE ' . $review_config->getIDField() . ' = ' . $review_id;
 		$this->Conn->Query($sql);
 
 		if ( $this->Conn->getAffectedRows() ) {
 			// db was changed -> review with such ID exists
 			$spam_helper->AddToSpamControl();
 		}
 
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			echo $count + 1;
 		}
 	}
 
 	/**
 	 * [HOOK] Allows to add cloned subitem to given prefix
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCloneSubItem(kEvent $event)
 	{
 		parent::OnCloneSubItem($event);
 
 		if ( $event->MasterEvent->Prefix == 'fav' ) {
-			$clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones');
-			$subitem_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix;
+			$master_config = $event->MasterEvent->getUnitConfig();
+
+			$clones = $master_config->getClones();
+			$sub_item_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix;
 
-			$clones[$subitem_prefix]['ParentTableKey'] = 'ResourceId';
-			$clones[$subitem_prefix]['ForeignKey'] = 'ResourceId';
+			$clones[$sub_item_prefix]['ParentTableKey'] = 'ResourceId';
+			$clones[$sub_item_prefix]['ForeignKey'] = 'ResourceId';
 
-			$this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones);
+			$master_config->setClones($clones);
 		}
 	}
 
 	/**
 	 * Set's new unique resource id to user
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemValidate(kEvent $event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$resource_id = $object->GetDBField('ResourceId');
 
 		if ( !$resource_id ) {
 			$object->SetDBField('ResourceId', $this->Application->NextResourceId());
 		}
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/application.php
===================================================================
--- branches/5.3.x/core/kernel/application.php	(revision 15697)
+++ branches/5.3.x/core/kernel/application.php	(revision 15698)
@@ -1,3066 +1,3044 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 /**
 * Basic class for Kernel4-based Application
 *
 * This class is a Facade for any other class which needs to deal with Kernel4 framework.<br>
 * The class encapsulates the main run-cycle of the script, provide access to all other objects in the framework.<br>
 * <br>
 * The class is a singleton, which means that there could be only one instance of kApplication in the script.<br>
 * This could be guaranteed by NOT calling the class constructor directly, but rather calling kApplication::Instance() method,
 * which returns an instance of the application. The method guarantees that it will return exactly the same instance for any call.<br>
 * See singleton pattern by GOF.
 */
 class kApplication implements kiCacheable {
 
 	/**
 	 * Location of module helper class (used in installator too)
 	 */
 	const MODULE_HELPER_PATH = '/../units/helpers/modules_helper.php';
 
 	/**
 	 * Is true, when Init method was called already, prevents double initialization
 	 *
 	 * @var bool
 	 */
 	public $InitDone = false;
 
 	/**
 	 * Holds internal NParser object
 	 *
 	 * @var NParser
 	 * @access public
 	 */
 	public $Parser;
 
 	/**
 	 * Holds parser output buffer
 	 *
 	 * @var string
 	 * @access protected
 	 */
 	protected $HTML = '';
 
 	/**
 	 * The main Factory used to create
 	 * almost any class of kernel and
 	 * modules
 	 *
 	 * @var kFactory
 	 * @access protected
 	 */
 	protected $Factory;
 
 	/**
 	 * Template names, that will be used instead of regular templates
 	 *
 	 * @var Array
 	 * @access public
 	 */
 	public $ReplacementTemplates = Array ();
 
 	/**
 	 * Mod-Rewrite listeners used during url building and parsing
 	 *
 	 * @var Array
 	 * @access public
 	 */
 	public $RewriteListeners = Array ();
 
 	/**
 	 * Reference to debugger
 	 *
 	 * @var Debugger
 	 * @access public
 	 */
 	public $Debugger = null;
 
 	/**
 	 * Holds all phrases used
 	 * in code and template
 	 *
 	 * @var PhrasesCache
 	 * @access public
 	 */
 	public $Phrases;
 
 	/**
 	 * Modules table content, key - module name
 	 *
 	 * @var Array
 	 * @access public
 	 */
 	public $ModuleInfo = Array ();
 
 	/**
 	 * Holds DBConnection
 	 *
 	 * @var kDBConnection
 	 * @access public
 	 */
 	public $Conn = null;
 
 	/**
 	 * Reference to event log
 	 *
 	 * @var Array|kLogger
 	 * @access public
 	 */
 	protected $_logger = Array ();
 
 	// performance needs:
 	/**
 	 * Holds a reference to httpquery
 	 *
 	 * @var kHttpQuery
 	 * @access public
 	 */
 	public $HttpQuery = null;
 
 	/**
 	 * Holds a reference to UnitConfigReader
 	 *
 	 * @var kUnitConfigReader
 	 * @access public
 	 */
 	public $UnitConfigReader = null;
 
 	/**
 	 * Holds a reference to Session
 	 *
 	 * @var Session
 	 * @access public
 	 */
 	public $Session = null;
 
 	/**
 	 * Holds a ref to kEventManager
 	 *
 	 * @var kEventManager
 	 * @access public
 	 */
 	public $EventManager = null;
 
 	/**
 	 * Holds a ref to kUrlManager
 	 *
 	 * @var kUrlManager
 	 * @access public
 	 */
 	public $UrlManager = null;
 
 	/**
 	 * Ref for TemplatesCache
 	 *
 	 * @var TemplatesCache
 	 * @access public
 	 */
 	public $TemplatesCache = null;
 
 	/**
 	 * Holds current NParser tag while parsing, can be used in error messages to display template file and line
 	 *
 	 * @var _BlockTag
 	 * @access public
 	 */
 	public $CurrentNTag = null;
 
 	/**
 	 * Object of unit caching class
 	 *
 	 * @var kCacheManager
 	 * @access public
 	 */
 	public $cacheManager = null;
 
 	/**
 	 * Tells, that administrator has authenticated in administrative console
 	 * Should be used to manipulate data change OR data restrictions!
 	 *
 	 * @var bool
 	 * @access public
 	 */
 	public $isAdminUser = false;
 
 	/**
 	 * Tells, that admin version of "index.php" was used, nothing more!
 	 * Should be used to manipulate data display!
 	 *
 	 * @var bool
 	 * @access public
 	 */
 	public $isAdmin = false;
 
 	/**
 	 * Instance of site domain object
 	 *
 	 * @var kDBItem
 	 * @access public
 	 * @todo move away into separate module
 	 */
 	public $siteDomain = null;
 
 	/**
 	 * Prevent kApplication class to be created directly, only via Instance method
 	 *
 	 * @access private
 	 */
 	private function __construct()
 	{
 
 	}
 
 	final private function __clone() {}
 
 	/**
 	 * Returns kApplication instance anywhere in the script.
 	 *
 	 * This method should be used to get single kApplication object instance anywhere in the
 	 * Kernel-based application. The method is guaranteed to return the SAME instance of kApplication.
 	 * Anywhere in the script you could write:
 	 * <code>
 	 *		$application =& kApplication::Instance();
 	 * </code>
 	 * or in an object:
 	 * <code>
 	 *		$this->Application =& kApplication::Instance();
 	 * </code>
 	 * to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class.
 	 * To use descendant of standard kApplication class in your project you would need to define APPLICATION_CLASS constant
 	 * BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would
 	 * create and return default KernelApplication instance.
 	 *
 	 * Pattern: Singleton
 	 *
 	 * @static
 	 * @return kApplication
 	 * @access public
 	 */
 	public static function &Instance()
 	{
 		static $instance = false;
 
 		if ( !$instance ) {
 			$class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication';
 			$instance = new $class();
 		}
 
 		return $instance;
 	}
 
 	/**
 	 * Initializes the Application
 	 *
 	 * @param string $factory_class
 	 * @return bool Was Init actually made now or before
 	 * @access public
 	 * @see kHTTPQuery
 	 * @see Session
 	 * @see TemplatesCache
 	 */
 	public function Init($factory_class = 'kFactory')
 	{
 		if ( $this->InitDone ) {
 			return false;
 		}
 
 		if ( preg_match('/utf-8/i', CHARSET) ) {
 			setlocale(LC_ALL, 'en_US.UTF-8');
 			mb_internal_encoding('UTF-8');
 		}
 
 		$this->isAdmin = kUtil::constOn('ADMIN');
 
 		if ( !kUtil::constOn('SKIP_OUT_COMPRESSION') ) {
 			ob_start(); // collect any output from method (other then tags) into buffer
 		}
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
 			$this->Debugger->appendMemoryUsage('Application before Init:');
 		}
 
 		$this->_logger = new kLogger($this->_logger);
 		$this->Factory = new $factory_class();
 		$this->registerDefaultClasses();
 
 		$vars = kUtil::parseConfig(true);
 		$db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : ($this->isDebugMode() ? 'kDBConnectionDebug' : 'kDBConnection');
 		$this->Conn = $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array ($this->_logger, 'handleSQLError')));
 		$this->Conn->setup($vars);
 
 		$this->cacheManager = $this->makeClass('kCacheManager');
 		$this->cacheManager->InitCache();
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 			$this->Debugger->appendTimestamp('Before UnitConfigReader');
 		}
 
 		// init config reader and all managers
 		$this->UnitConfigReader = $this->makeClass('kUnitConfigReader');
 		$this->UnitConfigReader->scanModules(MODULES_PATH); // will also set RewriteListeners when existing cache is read
 
 		$this->registerModuleConstants();
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 			$this->Debugger->appendTimestamp('After UnitConfigReader');
 		}
 
 		define('MOD_REWRITE', $this->ConfigValue('UseModRewrite') && !$this->isAdmin ? 1 : 0);
 
 		// start processing request
 		$this->HttpQuery = $this->recallObject('HTTPQuery');
 		$this->HttpQuery->process();
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 			$this->Debugger->appendTimestamp('Processed HTTPQuery initial');
 		}
 
 		$this->Session = $this->recallObject('Session');
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 			$this->Debugger->appendTimestamp('Processed Session');
 		}
 
 		$this->Session->ValidateExpired(); // needs mod_rewrite url already parsed to keep user at proper template after session expiration
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 			$this->Debugger->appendTimestamp('Processed HTTPQuery AfterInit');
 		}
 
 		$this->cacheManager->LoadApplicationCache();
 
 		$site_timezone = $this->ConfigValue('Config_Site_Time');
 
 		if ( $site_timezone ) {
 			date_default_timezone_set($site_timezone);
 		}
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 			$this->Debugger->appendTimestamp('Loaded cache and phrases');
 		}
 
 		$this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there
 
 		$this->UnitConfigReader->AfterConfigRead(); // will set RewriteListeners when missing cache is built first time
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 			$this->Debugger->appendTimestamp('Processed AfterConfigRead');
 		}
 
 		if ( $this->GetVar('m_cat_id') === false ) {
 			$this->SetVar('m_cat_id', 0);
 		}
 
 		if ( !$this->RecallVar('curr_iso') ) {
 			$this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional
 		}
 
 		$visit_id = $this->RecallVar('visit_id');
 
 		if ( $visit_id !== false ) {
 			$this->SetVar('visits_id', $visit_id);
 		}
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 			$this->Debugger->profileFinish('kernel4_startup');
 		}
 
 		$this->InitDone = true;
 
 		$this->HandleEvent(new kEvent('adm:OnStartup'));
 
 		return true;
 	}
 
 	/**
 	 * Performs initialization of manager classes, that can be overridden from unit configs
 	 *
 	 * @return void
 	 * @access public
 	 * @throws Exception
 	 */
 	public function InitManagers()
 	{
 		if ( $this->InitDone ) {
 			throw new Exception('Duplicate call of ' . __METHOD__, E_USER_ERROR);
 			return;
 		}
 
 		$this->UrlManager = $this->makeClass('kUrlManager');
 		$this->EventManager = $this->makeClass('EventManager');
 		$this->Phrases = $this->makeClass('kPhraseCache');
 
 		$this->RegisterDefaultBuildEvents();
 	}
 
 	/**
 	 * Returns module information. Searches module by requested field
 	 *
 	 * @param string $field
 	 * @param mixed $value
 	 * @param string $return_field field value to returns, if not specified, then return all fields
 	 * @return Array
 	 */
 	public function findModule($field, $value, $return_field = null)
 	{
 		$found = $module_info = false;
 
 		foreach ($this->ModuleInfo as $module_info) {
 			if ( strtolower($module_info[$field]) == strtolower($value) ) {
 				$found = true;
 				break;
 			}
 		}
 
 		if ( $found ) {
 			return isset($return_field) ? $module_info[$return_field] : $module_info;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Refreshes information about loaded modules
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function refreshModuleInfo()
 	{
 		if ( defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules', true) ) {
 			$this->registerModuleConstants();
 			return;
 		}
 
 		// use makeClass over recallObject, since used before kApplication initialization during installation
 		$modules_helper = $this->makeClass('ModulesHelper');
 		/* @var $modules_helper kModulesHelper */
 
 		$this->Conn->nextQueryCachable = true;
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'Modules
 				WHERE ' . $modules_helper->getWhereClause() . '
 				ORDER BY LoadOrder';
 		$this->ModuleInfo = $this->Conn->Query($sql, 'Name');
 
 		$this->registerModuleConstants();
 	}
 
 	/**
 	 * Checks if passed language id if valid and sets it to primary otherwise
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function VerifyLanguageId()
 	{
 		$language_id = $this->GetVar('m_lang');
 
 		if ( !$language_id ) {
 			$language_id = 'default';
 		}
 
 		$this->SetVar('lang.current_id', $language_id);
 		$this->SetVar('m_lang', $language_id);
 
 		$lang_mode = $this->GetVar('lang_mode');
 		$this->SetVar('lang_mode', '');
 
 		$lang = $this->recallObject('lang.current');
 		/* @var $lang kDBItem */
 
 		if ( !$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled')) ) {
 			if ( !defined('IS_INSTALL') ) {
 				$this->ApplicationDie('Unknown or disabled language');
 			}
 		}
 
 		$this->SetVar('lang_mode', $lang_mode);
 	}
 
 	/**
 	 * Checks if passed theme id if valid and sets it to primary otherwise
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function VerifyThemeId()
 	{
 		if ( $this->isAdmin ) {
 			kUtil::safeDefine('THEMES_PATH', '/core/admin_templates');
 
 			return;
 		}
 
 		$path = $this->GetFrontThemePath();
 
 		if ( $path === false ) {
 			$this->ApplicationDie('No Primary Theme Selected or Current Theme is Unknown or Disabled');
 		}
 
 		kUtil::safeDefine('THEMES_PATH', $path);
 	}
 
 	/**
 	 * Returns relative path to current front-end theme
 	 *
 	 * @param bool $force
 	 * @return string
 	 * @access public
 	 */
 	public function GetFrontThemePath($force = false)
 	{
 		static $path = null;
 
 		if ( !$force && isset($path) ) {
 			return $path;
 		}
 
 		$theme_id = $this->GetVar('m_theme');
 		if ( !$theme_id ) {
 			$theme_id = 'default'; // $this->GetDefaultThemeId(1); // 1 to force front-end mode!
 		}
 
 		$this->SetVar('m_theme', $theme_id);
 		$this->SetVar('theme.current_id', $theme_id); // KOSTJA: this is to fool theme' getPassedID
 
 		$theme = $this->recallObject('theme.current');
 		/* @var $theme ThemeItem */
 
 		if ( !$theme->isLoaded() || !$theme->GetDBField('Enabled') ) {
 			return false;
 		}
 
 		// assign & then return, since it's static variable
 		$path = '/themes/' . $theme->GetDBField('Name');
 
 		return $path;
 	}
 
 	/**
 	 * Returns primary front/admin language id
 	 *
 	 * @param bool $init
 	 * @return int
 	 * @access public
 	 */
 	public function GetDefaultLanguageId($init = false)
 	{
 		$cache_key = 'primary_language_info[%LangSerial%]';
 		$language_info = $this->getCache($cache_key);
 
 		if ( $language_info === false ) {
 			// cache primary language info first
-			$table = $this->getUnitOption('lang', 'TableName');
-			$id_field = $this->getUnitOption('lang', 'IDField');
+			$language_config = $this->getUnitConfig('lang');
+
+			$table = $language_config->getTableName();
+			$id_field = $language_config->getIDField();
 
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT ' . $id_field . ', IF(AdminInterfaceLang, "Admin", "Front") AS LanguageKey
 					FROM ' . $table . '
 					WHERE (AdminInterfaceLang = 1 OR PrimaryLang = 1) AND (Enabled = 1)';
 			$language_info = $this->Conn->GetCol($sql, 'LanguageKey');
 
 			if ( $language_info !== false ) {
 				$this->setCache($cache_key, $language_info);
 			}
 		}
 
 		$language_key = ($this->isAdmin && $init) || count($language_info) == 1 ? 'Admin' : 'Front';
 
 		if ( array_key_exists($language_key, $language_info) && $language_info[$language_key] > 0 ) {
 			// get from cache
 			return $language_info[$language_key];
 		}
 
 		$language_id = $language_info && array_key_exists($language_key, $language_info) ? $language_info[$language_key] : false;
 
 		if ( !$language_id && defined('IS_INSTALL') && IS_INSTALL ) {
 			$language_id = 1;
 		}
 
 		return $language_id;
 	}
 
 	/**
 	 * Returns front-end primary theme id (even, when called from admin console)
 	 *
 	 * @param bool $force_front
 	 * @return int
 	 * @access public
 	 */
 	public function GetDefaultThemeId($force_front = false)
 	{
 		static $theme_id = 0;
 
 		if ( $theme_id > 0 ) {
 			return $theme_id;
 		}
 
 		if ( kUtil::constOn('DBG_FORCE_THEME') ) {
 			$theme_id = DBG_FORCE_THEME;
 		}
 		elseif ( !$force_front && $this->isAdmin ) {
 			$theme_id = 999;
 		}
 		else {
 			$cache_key = 'primary_theme[%ThemeSerial%]';
 			$theme_id = $this->getCache($cache_key);
 
 			if ( $theme_id === false ) {
 				$this->Conn->nextQueryCachable = true;
-				$sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . '
-						FROM ' . $this->getUnitOption('theme', 'TableName') . '
+				$theme_config = $this->getUnitConfig('theme');
+
+				$sql = 'SELECT ' . $theme_config->getIDField() . '
+						FROM ' . $theme_config->getTableName() . '
 						WHERE (PrimaryTheme = 1) AND (Enabled = 1)';
 				$theme_id = $this->Conn->GetOne($sql);
 
 				if ( $theme_id !== false ) {
 					$this->setCache($cache_key, $theme_id);
 				}
 			}
 		}
 
 		return $theme_id;
 	}
 
 	/**
 	 * Returns site primary currency ISO code
 	 *
 	 * @return string
 	 * @access public
 	 * @todo Move into In-Commerce
 	 */
 	public function GetPrimaryCurrency()
 	{
 		$cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId');
 		$currency_iso = $this->getCache($cache_key);
 
 		if ( $currency_iso === false ) {
 			if ( $this->isModuleEnabled('In-Commerce') ) {
 				$this->Conn->nextQueryCachable = true;
 				$currency_id = $this->siteDomainField('PrimaryCurrencyId');
 
 				$sql = 'SELECT ISO
-						FROM ' . $this->getUnitOption('curr', 'TableName') . '
+						FROM ' . $this->getUnitConfig('curr')->getTableName() . '
 						WHERE ' . ($currency_id > 0 ? 'CurrencyId = ' . $currency_id : 'IsPrimary = 1');
 				$currency_iso = $this->Conn->GetOne($sql);
 			}
 			else {
 				$currency_iso = 'USD';
 			}
 
 			$this->setCache($cache_key, $currency_iso);
 		}
 
 		return $currency_iso;
 	}
 
 	/**
 	 * Returns site domain field. When none of site domains are found false is returned.
 	 *
 	 * @param string $field
 	 * @param bool $formatted
 	 * @param string $format
 	 * @return mixed
 	 * @todo Move into separate module
 	 */
 	public function siteDomainField($field, $formatted = false, $format = null)
 	{
 		if ( $this->isAdmin ) {
 			// don't apply any filtering in administrative console
 			return false;
 		}
 
 		if ( !$this->siteDomain ) {
 			$this->siteDomain = $this->recallObject('site-domain.current');
 			/* @var $site_domain kDBItem */
 		}
 
 		if ( $this->siteDomain->isLoaded() ) {
 			return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field);
 		}
 
 		return false;
 	}
 
 	/**
 	 * Registers default classes such as kDBEventHandler, kUrlManager
 	 *
 	 * Called automatically while initializing kApplication
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function RegisterDefaultClasses()
 	{
 		$this->registerClass('kHelper', KERNEL_PATH . '/kbase.php');
 		$this->registerClass('kMultipleFilter', KERNEL_PATH . '/utility/filters.php');
 		$this->registerClass('kiCacheable', KERNEL_PATH . '/interfaces/cacheable.php');
 
 		$this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php', 'EventManager');
 		$this->registerClass('kHookManager', KERNEL_PATH . '/managers/hook_manager.php');
 		$this->registerClass('kScheduledTaskManager', KERNEL_PATH . '/managers/scheduled_task_manager.php');
 		$this->registerClass('kRequestManager', KERNEL_PATH . '/managers/request_manager.php');
 		$this->registerClass('kSubscriptionManager', KERNEL_PATH . '/managers/subscription_manager.php');
 
 		$this->registerClass('kUrlManager', KERNEL_PATH . '/managers/url_manager.php');
 		$this->registerClass('kUrlProcessor', KERNEL_PATH . '/managers/url_processor.php');
 		$this->registerClass('kPlainUrlProcessor', KERNEL_PATH . '/managers/plain_url_processor.php');
 		$this->registerClass('kRewriteUrlProcessor', KERNEL_PATH . '/managers/rewrite_url_processor.php');
 
 		$this->registerClass('kCacheManager', KERNEL_PATH . '/managers/cache_manager.php');
 		$this->registerClass('PhrasesCache', KERNEL_PATH . '/languages/phrases_cache.php', 'kPhraseCache');
 		$this->registerClass('kTempTablesHandler', KERNEL_PATH . '/utility/temp_handler.php');
 		$this->registerClass('kValidator', KERNEL_PATH . '/utility/validator.php');
 		$this->registerClass('kOpenerStack', KERNEL_PATH . '/utility/opener_stack.php');
 		$this->registerClass('kLogger', KERNEL_PATH . '/utility/logger.php');
 
+		$this->registerClass('kUnitConfig', KERNEL_PATH . '/utility/unit_config.php');
 		$this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php');
 		$this->registerClass('PasswordHash', KERNEL_PATH . '/utility/php_pass.php');
 
 		// Params class descendants
 		$this->registerClass('kArray', KERNEL_PATH . '/utility/params.php');
 		$this->registerClass('Params', KERNEL_PATH . '/utility/params.php');
 		$this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions');
 		$this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php', 'kCache', 'Params');
 		$this->registerClass('kHTTPQuery', KERNEL_PATH . '/utility/http_query.php', 'HTTPQuery');
 
 		// session
 		$this->registerClass('kCookieHasher', KERNEL_PATH . '/utility/cookie_hasher.php');
 		$this->registerClass('Session', KERNEL_PATH . '/session/session.php');
 		$this->registerClass('SessionStorage', KERNEL_PATH . '/session/session_storage.php');
 		$this->registerClass('InpSession', KERNEL_PATH . '/session/inp_session.php', 'Session');
 		$this->registerClass('InpSessionStorage', KERNEL_PATH . '/session/inp_session_storage.php', 'SessionStorage');
 
 		// template parser
 		$this->registerClass('kTagProcessor', KERNEL_PATH . '/processors/tag_processor.php');
 		$this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php', 'm_TagProcessor');
 		$this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php');
 		$this->registerClass('kCatDBTagProcessor', KERNEL_PATH . '/db/cat_tag_processor.php');
 		$this->registerClass('NParser', KERNEL_PATH . '/nparser/nparser.php');
 		$this->registerClass('TemplatesCache', KERNEL_PATH . '/nparser/template_cache.php');
 
 		// database
 		$this->registerClass('kDBConnection', KERNEL_PATH . '/db/db_connection.php');
 		$this->registerClass('kDBConnectionDebug', KERNEL_PATH . '/db/db_connection.php');
 		$this->registerClass('kDBLoadBalancer', KERNEL_PATH . '/db/db_load_balancer.php');
 		$this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php');
 		$this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php');
 		$this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php');
 		$this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php');
 		$this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php');
 		$this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php');
 
 		// email sending
 		$this->registerClass('kEmail', KERNEL_PATH . '/utility/email.php');
 		$this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender');
 		$this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket');
 
 		// do not move to config - this helper is used before configs are read
 		$this->registerClass('kModulesHelper', KERNEL_PATH . self::MODULE_HELPER_PATH, 'ModulesHelper');
 	}
 
 	/**
 	 * Registers default build events
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function RegisterDefaultBuildEvents()
 	{
 		$this->EventManager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild');
 	}
 
 	/**
 	 * Returns cached category information by given cache name. All given category
 	 * information is recached, when at least one of 4 caches is missing.
 	 *
 	 * @param int $category_id
 	 * @param string $name cache name = {filenames, category_designs, category_tree}
 	 * @return string
 	 * @access public
 	 */
 	public function getCategoryCache($category_id, $name)
 	{
 		return $this->cacheManager->getCategoryCache($category_id, $name);
 	}
 
 	/**
 	 * Returns caching type (none, memory, temporary)
 	 *
 	 * @param int $caching_type
 	 * @return bool
 	 * @access public
 	 */
 	public function isCachingType($caching_type)
 	{
 		return $this->cacheManager->isCachingType($caching_type);
 	}
 
 	/**
 	 * Increments serial based on prefix and it's ID (optional)
 	 *
 	 * @param string $prefix
 	 * @param int $id ID (value of IDField) or ForeignKeyField:ID
 	 * @param bool $increment
 	 * @return string
 	 * @access public
 	 */
 	public function incrementCacheSerial($prefix, $id = null, $increment = true)
 	{
 		return $this->cacheManager->incrementCacheSerial($prefix, $id, $increment);
 	}
 
 	/**
 	 * Returns cached $key value from cache named $cache_name
 	 *
 	 * @param int $key key name from cache
 	 * @param bool $store_locally store data locally after retrieved
 	 * @param int $max_rebuild_seconds
 	 * @return mixed
 	 * @access public
 	 */
 	public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0)
 	{
 		return $this->cacheManager->getCache($key, $store_locally, $max_rebuild_seconds);
 	}
 
 	/**
 	 * Stores new $value in cache with $key name
 	 *
 	 * @param int $key key name to add to cache
 	 * @param mixed $value value of cached record
 	 * @param int $expiration when value expires (0 - doesn't expire)
 	 * @return bool
 	 * @access public
 	 */
 	public function setCache($key, $value, $expiration = 0)
 	{
 		return $this->cacheManager->setCache($key, $value, $expiration);
 	}
 
 	/**
 	 * Stores new $value in cache with $key name (only if it's not there)
 	 *
 	 * @param int $key key name to add to cache
 	 * @param mixed $value value of cached record
 	 * @param int $expiration when value expires (0 - doesn't expire)
 	 * @return bool
 	 * @access public
 	 */
 	public function addCache($key, $value, $expiration = 0)
 	{
 		return $this->cacheManager->addCache($key, $value, $expiration);
 	}
 
 	/**
 	 * Sets rebuilding mode for given cache
 	 *
 	 * @param string $name
 	 * @param int $mode
 	 * @param int $max_rebuilding_time
 	 * @return bool
 	 * @access public
 	 */
 	public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0)
 	{
 		return $this->cacheManager->rebuildCache($name, $mode, $max_rebuilding_time);
 	}
 
 	/**
 	 * Deletes key from cache
 	 *
 	 * @param string $key
 	 * @return void
 	 * @access public
 	 */
 	public function deleteCache($key)
 	{
 		$this->cacheManager->deleteCache($key);
 	}
 
 	/**
 	 * Reset's all memory cache at once
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function resetCache()
 	{
 		$this->cacheManager->resetCache();
 	}
 
 	/**
 	 * Returns value from database cache
 	 *
 	 * @param string $name key name
 	 * @param int $max_rebuild_seconds
 	 * @return mixed
 	 * @access public
 	 */
 	public function getDBCache($name, $max_rebuild_seconds = 0)
 	{
 		return $this->cacheManager->getDBCache($name, $max_rebuild_seconds);
 	}
 
 	/**
 	 * Sets value to database cache
 	 *
 	 * @param string $name
 	 * @param mixed $value
 	 * @param int|bool $expiration
 	 * @return void
 	 * @access public
 	 */
 	public function setDBCache($name, $value, $expiration = false)
 	{
 		$this->cacheManager->setDBCache($name, $value, $expiration);
 	}
 
 	/**
 	 * Sets rebuilding mode for given cache
 	 *
 	 * @param string $name
 	 * @param int $mode
 	 * @param int $max_rebuilding_time
 	 * @return bool
 	 * @access public
 	 */
 	public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0)
 	{
 		return $this->cacheManager->rebuildDBCache($name, $mode, $max_rebuilding_time);
 	}
 
 	/**
 	 * Deletes key from database cache
 	 *
 	 * @param string $name
 	 * @return void
 	 * @access public
 	 */
 	public function deleteDBCache($name)
 	{
 		$this->cacheManager->deleteDBCache($name);
 	}
 
 	/**
 	 * Registers each module specific constants if any found
 	 *
 	 * @return bool
 	 * @access protected
 	 */
 	protected function registerModuleConstants()
 	{
 		if ( file_exists(KERNEL_PATH . '/constants.php') ) {
 			kUtil::includeOnce(KERNEL_PATH . '/constants.php');
 		}
 
 		if ( !$this->ModuleInfo ) {
 			return false;
 		}
 
 		foreach ($this->ModuleInfo as $module_info) {
 			$constants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php';
 
 			if ( file_exists($constants_file) ) {
 				kUtil::includeOnce($constants_file);
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * Performs redirect to hard maintenance template
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function redirectToMaintenance()
 	{
 		$maintenance_page = WRITEBALE_BASE . '/maintenance.html';
 		$query_string = ''; // $this->isAdmin ? '' : '?next_template=' . urlencode($_SERVER['REQUEST_URI']);
 
 		if ( file_exists(FULL_PATH . $maintenance_page) ) {
 			header('Location: ' . BASE_PATH . $maintenance_page . $query_string);
 			exit;
 		}
 	}
 
 	/**
 	 * Actually runs the parser against current template and stores parsing result
 	 *
 	 * This method gets 't' variable passed to the script, loads the template given in 't' variable and
 	 * parses it. The result is store in {@link $this->HTML} property.
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function Run()
 	{
 		// process maintenance mode redirect: begin
 		$maintenance_mode = $this->getMaintenanceMode();
 
 		if ( $maintenance_mode == MaintenanceMode::HARD ) {
 			$this->redirectToMaintenance();
 		}
 		elseif ( $maintenance_mode == MaintenanceMode::SOFT ) {
 			$maintenance_template = $this->isAdmin ? 'login' : $this->ConfigValue('SoftMaintenanceTemplate');
 
 			if ( $this->GetVar('t') != $maintenance_template ) {
 				$redirect_params = Array ();
 
 				if ( !$this->isAdmin ) {
 					$redirect_params['next_template'] = urlencode($_SERVER['REQUEST_URI']);
 				}
 
 				$this->Redirect($maintenance_template, $redirect_params);
 			}
 		}
 		// process maintenance mode redirect: end
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
 			$this->Debugger->appendMemoryUsage('Application before Run:');
 		}
 
 		if ( $this->isAdminUser ) {
 			// for permission checking in events & templates
 			$this->LinkVar('module'); // for common configuration templates
 			$this->LinkVar('module_key'); // for common search templates
 			$this->LinkVar('section'); // for common configuration templates
 
 			if ( $this->GetVar('m_opener') == 'p' ) {
 				$this->LinkVar('main_prefix'); // window prefix, that opened selector
 				$this->LinkVar('dst_field'); // field to set value choosed in selector
 			}
 
 			if ( $this->GetVar('ajax') == 'yes' && !$this->GetVar('debug_ajax') ) {
 				// hide debug output from ajax requests automatically
 				kUtil::safeDefine('DBG_SKIP_REPORTING', 1); // safeDefine, because debugger also defines it
 			}
 		}
 		elseif ( $this->GetVar('admin') ) {
 			$admin_session = $this->recallObject('Session.admin');
 			/* @var $admin_session Session */
 
 			// store Admin Console User's ID to Front-End's session for cross-session permission checks
 			$this->StoreVar('admin_user_id', (int)$admin_session->RecallVar('user_id'));
 
 			if ( $this->CheckAdminPermission('CATEGORY.MODIFY', 0, $this->getBaseCategory()) ) {
 				// user can edit cms blocks (when viewing front-end through admin's frame)
 				$editing_mode = $this->GetVar('editing_mode');
 				define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE);
 			}
 		}
 
 		kUtil::safeDefine('EDITING_MODE', ''); // user can't edit anything
 		$this->Phrases->setPhraseEditing();
 
 		$this->EventManager->ProcessRequest();
 
 		$this->InitParser();
 		$t = $this->GetVar('render_template', $this->GetVar('t'));
 
 		if ( !$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin ) {
 			$cms_handler = $this->recallObject('st_EventHandler');
 			/* @var $cms_handler CategoriesEventHandler */
 
 			$t = ltrim($cms_handler->GetDesignTemplate(), '/');
 
 			if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
 				$this->Debugger->appendHTML('<strong>Design Template</strong>: ' . $t . '; <strong>CategoryID</strong>: ' . $this->GetVar('m_cat_id'));
 			}
 		}
 		/*else {
 			$cms_handler->SetCatByTemplate();
 		}*/
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
 			$this->Debugger->appendMemoryUsage('Application before Parsing:');
 		}
 
 		$this->HTML = $this->Parser->Run($t);
 
 		if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
 			$this->Debugger->appendMemoryUsage('Application after Parsing:');
 		}
 	}
 
 	/**
 	 * Only renders template
 	 *
 	 * @see kDBEventHandler::_errorNotFound()
 	 */
 	public function QuickRun()
 	{
 		$this->InitParser();
 		$this->HTML = $this->ParseBlock(Array ('name' => $this->GetVar('t')));
 	}
 
 	/**
 	 * Performs template parser/cache initialization
 	 *
 	 * @param bool|string $theme_name
 	 * @return void
 	 * @access public
 	 */
 	public function InitParser($theme_name = false)
 	{
 		if ( !is_object($this->Parser) ) {
 			$this->Parser = $this->recallObject('NParser');
 			$this->TemplatesCache = $this->recallObject('TemplatesCache');
 		}
 
 		$this->TemplatesCache->forceThemeName = $theme_name;
 	}
 
 	/**
 	 * Send the parser results to browser
 	 *
 	 * Actually send everything stored in {@link $this->HTML}, to the browser by echoing it.
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function Done()
 	{
 		$this->HandleEvent(new kEvent('adm:OnBeforeShutdown'));
 
 		$debug_mode = defined('DEBUG_MODE') && $this->isDebugMode();
 
 		if ( $debug_mode ) {
 			if ( kUtil::constOn('DBG_PROFILE_MEMORY') ) {
 				$this->Debugger->appendMemoryUsage('Application before Done:');
 			}
 
 			$this->Session->SaveData(); // adds session data to debugger report
 			$this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true);
 		}
 		else {
 			// send "Set-Cookie" header before any output is made
 			$this->Session->SetSession();
 			$this->HTML = ob_get_clean() . $this->HTML;
 		}
 
 		$this->_outputPage();
 		$this->cacheManager->UpdateApplicationCache();
 
 		if ( !$debug_mode ) {
 			$this->Session->SaveData();
 		}
 
 		$this->EventManager->runScheduledTasks();
 
 		if ( defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin ) {
 			$this->_storeStatistics();
 		}
 	}
 
 	/**
 	 * Outputs generated page content to end-user
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function _outputPage()
 	{
 		$this->setContentType();
 		ob_start();
 
 		if ( $this->UseOutputCompression() ) {
 			$compression_level = $this->ConfigValue('OutputCompressionLevel');
 
 			if ( !$compression_level || $compression_level < 0 || $compression_level > 9 ) {
 				$compression_level = 7;
 			}
 
 			header('Content-Encoding: gzip');
 			echo gzencode($this->HTML, $compression_level);
 		}
 		else {
 			// when gzip compression not used connection won't be closed early!
 			echo $this->HTML;
 		}
 
 		// send headers to tell the browser to close the connection
 		header('Content-Length: ' . ob_get_length());
 		header('Connection: close');
 
 		// flush all output
 		ob_end_flush();
 
 		if ( ob_get_level() ) {
 			ob_flush();
 		}
 
 		flush();
 
 		// close current session
 		if ( session_id() ) {
 			session_write_close();
 		}
 	}
 
 	/**
 	 * Stores script execution statistics to database
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function _storeStatistics()
 	{
 		global $start;
 
 		$script_time = microtime(true) - $start;
 		$query_statistics = $this->Conn->getQueryStatistics(); // time & count
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'StatisticsCapture
 				WHERE TemplateName = ' . $this->Conn->qstr($this->GetVar('t'));
 		$data = $this->Conn->GetRow($sql);
 
 		if ( $data ) {
 			$this->_updateAverageStatistics($data, 'ScriptTime', $script_time);
 			$this->_updateAverageStatistics($data, 'SqlTime', $query_statistics['time']);
 			$this->_updateAverageStatistics($data, 'SqlCount', $query_statistics['count']);
 
 			$data['Hits']++;
 			$data['LastHit'] = adodb_mktime();
 
 			$this->Conn->doUpdate($data, TABLE_PREFIX . 'StatisticsCapture', 'StatisticsId = ' . $data['StatisticsId']);
 		}
 		else {
 			$data['ScriptTimeMin'] = $data['ScriptTimeAvg'] = $data['ScriptTimeMax'] = $script_time;
 			$data['SqlTimeMin'] = $data['SqlTimeAvg'] = $data['SqlTimeMax'] = $query_statistics['time'];
 			$data['SqlCountMin'] = $data['SqlCountAvg'] = $data['SqlCountMax'] = $query_statistics['count'];
 			$data['TemplateName'] = $this->GetVar('t');
 			$data['Hits'] = 1;
 			$data['LastHit'] = adodb_mktime();
 			$this->Conn->doInsert($data, TABLE_PREFIX . 'StatisticsCapture');
 		}
 	}
 
 	/**
 	 * Calculates average time for statistics
 	 *
 	 * @param Array $data
 	 * @param string $field_prefix
 	 * @param float $current_value
 	 * @return void
 	 * @access protected
 	 */
 	protected function _updateAverageStatistics(&$data, $field_prefix, $current_value)
 	{
 		$data[$field_prefix . 'Avg'] = (($data['Hits'] * $data[$field_prefix . 'Avg']) + $current_value) / ($data['Hits'] + 1);
 
 		if ( $current_value < $data[$field_prefix . 'Min'] ) {
 			$data[$field_prefix . 'Min'] = $current_value;
 		}
 
 		if ( $current_value > $data[$field_prefix . 'Max'] ) {
 			$data[$field_prefix . 'Max'] = $current_value;
 		}
 	}
 
 	/**
 	 * Remembers slow query SQL and execution time into log
 	 *
 	 * @param string $slow_sql
 	 * @param int $time
 	 * @return void
 	 * @access public
 	 */
 	public function logSlowQuery($slow_sql, $time)
 	{
 		$query_crc = kUtil::crc32($slow_sql);
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'SlowSqlCapture
 				WHERE QueryCrc = ' . $query_crc;
 		$data = $this->Conn->Query($sql, null, true);
 
 		if ( $data ) {
 			$this->_updateAverageStatistics($data, 'Time', $time);
 
 			$template_names = explode(',', $data['TemplateNames']);
 			array_push($template_names, $this->GetVar('t'));
 			$data['TemplateNames'] = implode(',', array_unique($template_names));
 
 			$data['Hits']++;
 			$data['LastHit'] = adodb_mktime();
 
 			$this->Conn->doUpdate($data, TABLE_PREFIX . 'SlowSqlCapture', 'CaptureId = ' . $data['CaptureId']);
 		}
 		else {
 			$data['TimeMin'] = $data['TimeAvg'] = $data['TimeMax'] = $time;
 			$data['SqlQuery'] = $slow_sql;
 			$data['QueryCrc'] = $query_crc;
 			$data['TemplateNames'] = $this->GetVar('t');
 			$data['Hits'] = 1;
 			$data['LastHit'] = adodb_mktime();
 
 			$this->Conn->doInsert($data, TABLE_PREFIX . 'SlowSqlCapture');
 		}
 	}
 
 	/**
 	 * Checks if output compression options is available
 	 *
 	 * @return bool
 	 * @access protected
 	 */
 	protected function UseOutputCompression()
 	{
 		if ( kUtil::constOn('IS_INSTALL') || kUtil::constOn('DBG_ZEND_PRESENT') || kUtil::constOn('SKIP_OUT_COMPRESSION') ) {
 			return false;
 		}
 
 		$accept_encoding = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : '';
 
 		return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($accept_encoding, 'gzip');
 	}
 
 	//	Facade
 
 	/**
 	 * Returns current session id (SID)
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function GetSID()
 	{
 		$session = $this->recallObject('Session');
 		/* @var $session Session */
 
 		return $session->GetID();
 	}
 
 	/**
 	 * Destroys current session
 	 *
 	 * @return void
 	 * @access public
 	 * @see UserHelper::logoutUser()
 	 */
 	public function DestroySession()
 	{
 		$session = $this->recallObject('Session');
 		/* @var $session Session */
 
 		$session->Destroy();
 	}
 
 	/**
 	 * Returns variable passed to the script as GET/POST/COOKIE
 	 *
 	 * @param string $name Name of variable to retrieve
 	 * @param mixed $default default value returned in case if variable not present
 	 * @return mixed
 	 * @access public
 	 */
 	public function GetVar($name, $default = false)
 	{
 		return isset($this->HttpQuery->_Params[$name]) ? $this->HttpQuery->_Params[$name] : $default;
 	}
 
 	/**
 	 * Returns variable passed to the script as $type
 	 *
 	 * @param string $name Name of variable to retrieve
 	 * @param string $type Get/Post/Cookie
 	 * @param mixed $default default value returned in case if variable not present
 	 * @return mixed
 	 * @access public
 	 */
 	public function GetVarDirect($name, $type, $default = false)
 	{
 //		$type = ucfirst($type);
 		$array = $this->HttpQuery->$type;
 
 		return isset($array[$name]) ? $array[$name] : $default;
 	}
 
 	/**
 	 * Returns ALL variables passed to the script as GET/POST/COOKIE
 	 *
 	 * @return Array
 	 * @access public
 	 * @deprecated
 	 */
 	public function GetVars()
 	{
 		return $this->HttpQuery->GetParams();
 	}
 
 	/**
 	 * Set the variable 'as it was passed to the script through GET/POST/COOKIE'
 	 *
 	 * This could be useful to set the variable when you know that
 	 * other objects would relay on variable passed from GET/POST/COOKIE
 	 * or you could use SetVar() / GetVar() pairs to pass the values between different objects.<br>
 	 *
 	 * @param string $var Variable name to set
 	 * @param mixed $val Variable value
 	 * @return void
 	 * @access public
 	 */
 	public function SetVar($var,$val)
 	{
 		$this->HttpQuery->Set($var, $val);
 	}
 
 	/**
 	 * Deletes kHTTPQuery variable
 	 *
 	 * @param string $var
 	 * @return void
 	 * @todo Think about method name
 	 */
 	public function DeleteVar($var)
 	{
 		$this->HttpQuery->Remove($var);
 	}
 
 	/**
 	 * Deletes Session variable
 	 *
 	 * @param string $var
 	 * @return void
 	 * @access public
 	 */
 	public function RemoveVar($var)
 	{
 		$this->Session->RemoveVar($var);
 	}
 
 	/**
 	 * Removes variable from persistent session
 	 *
 	 * @param string $var
 	 * @return void
 	 * @access public
 	 */
 	public function RemovePersistentVar($var)
 	{
 		$this->Session->RemovePersistentVar($var);
 	}
 
 	/**
 	 * Restores Session variable to it's db version
 	 *
 	 * @param string $var
 	 * @return void
 	 * @access public
 	 */
 	public function RestoreVar($var)
 	{
 		$this->Session->RestoreVar($var);
 	}
 
 	/**
 	 * Returns session variable value
 	 *
 	 * Return value of $var variable stored in Session. An optional default value could be passed as second parameter.
 	 *
 	 * @param string $var Variable name
 	 * @param mixed $default Default value to return if no $var variable found in session
 	 * @return mixed
 	 * @access public
 	 * @see Session::RecallVar()
 	 */
 	public function RecallVar($var,$default=false)
 	{
 		return $this->Session->RecallVar($var,$default);
 	}
 
 	/**
 	 * Returns variable value from persistent session
 	 *
 	 * @param string $var
 	 * @param mixed $default
 	 * @return mixed
 	 * @access public
 	 * @see Session::RecallPersistentVar()
 	 */
 	public function RecallPersistentVar($var, $default = false)
 	{
 		return $this->Session->RecallPersistentVar($var, $default);
 	}
 
 	/**
 	 * Stores variable $val in session under name $var
 	 *
 	 * Use this method to store variable in session. Later this variable could be recalled.
 	 *
 	 * @param string $var Variable name
 	 * @param mixed $val Variable value
 	 * @param bool $optional
 	 * @return void
 	 * @access public
 	 * @see kApplication::RecallVar()
 	 */
 	public function StoreVar($var, $val, $optional = false)
 	{
 		$session = $this->recallObject('Session');
 		/* @var $session Session */
 
 		$this->Session->StoreVar($var, $val, $optional);
 	}
 
 	/**
 	 * Stores variable to persistent session
 	 *
 	 * @param string $var
 	 * @param mixed $val
 	 * @param bool $optional
 	 * @return void
 	 * @access public
 	 */
 	public function StorePersistentVar($var, $val, $optional = false)
 	{
 		$this->Session->StorePersistentVar($var, $val, $optional);
 	}
 
 	/**
 	 * Stores default value for session variable
 	 *
 	 * @param string $var
 	 * @param string $val
 	 * @param bool $optional
 	 * @return void
 	 * @access public
 	 * @see Session::RecallVar()
 	 * @see Session::StoreVar()
 	 */
 	public function StoreVarDefault($var, $val, $optional = false)
 	{
 		$session = $this->recallObject('Session');
 		/* @var $session Session */
 
 		$this->Session->StoreVarDefault($var, $val, $optional);
 	}
 
 	/**
 	 * Links HTTP Query variable with session variable
 	 *
 	 * If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session.
 	 * This method could be used for making sure that GetVar will return query or session value for given
 	 * variable, when query variable should overwrite session (and be stored there for later use).<br>
 	 * This could be used for passing item's ID into popup with multiple tab -
 	 * in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id').
 	 * After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session
 	 *
 	 * @param string $var HTTP Query (GPC) variable name
 	 * @param mixed $ses_var Session variable name
 	 * @param mixed $default Default variable value
 	 * @param bool $optional
 	 * @return void
 	 * @access public
 	 */
 	public function LinkVar($var, $ses_var = null, $default = '', $optional = false)
 	{
 		if ( !isset($ses_var) ) {
 			$ses_var = $var;
 		}
 
 		if ( $this->GetVar($var) !== false ) {
 			$this->StoreVar($ses_var, $this->GetVar($var), $optional);
 		}
 		else {
 			$this->SetVar($var, $this->RecallVar($ses_var, $default));
 		}
 	}
 
 	/**
 	 * Returns variable from HTTP Query, or from session if not passed in HTTP Query
 	 *
 	 * The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed.
 	 * Returns the default value if variable does not exist in session and was not passed in HTTP Query
 	 *
 	 * @param string $var HTTP Query (GPC) variable name
 	 * @param mixed $ses_var Session variable name
 	 * @param mixed $default Default variable value
 	 * @return mixed
 	 * @access public
 	 * @see LinkVar
 	 */
 	public function GetLinkedVar($var, $ses_var = null, $default = '')
 	{
 		$this->LinkVar($var, $ses_var, $default);
 
 		return $this->GetVar($var);
 	}
 
 	/**
 	 * Renders given tag and returns it's output
 	 *
 	 * @param string $prefix
 	 * @param string $tag
 	 * @param Array $params
 	 * @return mixed
 	 * @access public
 	 * @see kApplication::InitParser()
 	 */
 	public function ProcessParsedTag($prefix, $tag, $params)
 	{
 		$processor = $this->Parser->GetProcessor($prefix);
 		/* @var $processor kDBTagProcessor */
 
 		return $processor->ProcessParsedTag($tag, $params, $prefix);
 	}
 
 	/**
 	 * Return ADODB Connection object
 	 *
 	 * Returns ADODB Connection object already connected to the project database, configurable in config.php
 	 *
 	 * @return kDBConnection
 	 * @access public
 	 */
 	public function &GetADODBConnection()
 	{
 		return $this->Conn;
 	}
 
 	/**
 	 * Allows to parse given block name or include template
 	 *
 	 * @param Array $params Parameters to pass to block. Reserved parameter "name" used to specify block name.
 	 * @param bool $pass_params Forces to pass current parser params to this block/template. Use with caution, because you can accidentally pass "block_no_data" parameter.
 	 * @param bool $as_template
 	 * @return string
 	 * @access public
 	 */
 	public function ParseBlock($params, $pass_params = false, $as_template = false)
 	{
 		if ( substr($params['name'], 0, 5) == 'html:' ) {
 			return substr($params['name'], 5);
 		}
 
 		return $this->Parser->ParseBlock($params, $pass_params, $as_template);
 	}
 
 	/**
 	 * Checks, that we have given block defined
 	 *
 	 * @param string $name
 	 * @return bool
 	 * @access public
 	 */
 	public function ParserBlockFound($name)
 	{
 		return $this->Parser->blockFound($name);
 	}
 
 	/**
 	 * Allows to include template with a given name and given parameters
 	 *
 	 * @param Array $params Parameters to pass to template. Reserved parameter "name" used to specify template name.
 	 * @return string
 	 * @access public
 	 */
 	public function IncludeTemplate($params)
 	{
 		return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0);
 	}
 
 	/**
 	 * Return href for template
 	 *
 	 * @param string $t Template path
 	 * @param string $prefix index.php prefix - could be blank, 'admin'
 	 * @param Array $params
 	 * @param string $index_file
 	 * @return string
 	 */
 	public function HREF($t, $prefix = '', $params = Array (), $index_file = null)
 	{
 		return $this->UrlManager->HREF($t, $prefix, $params, $index_file);
 	}
 
 	/**
 	 * Returns theme template filename and it's corresponding page_id based on given seo template
 	 *
 	 * @param string $seo_template
 	 * @return string
 	 * @access public
 	 */
 	public function getPhysicalTemplate($seo_template)
 	{
 		return $this->UrlManager->getPhysicalTemplate($seo_template);
 	}
 
 	/**
 	 * Returns seo template by physical template
 	 *
 	 * @param string $physical_template
 	 * @return string
 	 * @access public
 	 */
 	public function getSeoTemplate($physical_template)
 	{
 		return $this->UrlManager->getSeoTemplate($physical_template);
 	}
 
 	/**
 	 * Returns template name, that corresponds with given virtual (not physical) page id
 	 *
 	 * @param int $page_id
 	 * @return string|bool
 	 * @access public
 	 */
 	public function getVirtualPageTemplate($page_id)
 	{
 		return $this->UrlManager->getVirtualPageTemplate($page_id);
 	}
 
 	/**
 	 * Returns section template for given physical/virtual template
 	 *
 	 * @param string $template
 	 * @param int $theme_id
 	 * @return string
 	 * @access public
 	 */
 	public function getSectionTemplate($template, $theme_id = null)
 	{
 		return $this->UrlManager->getSectionTemplate($template, $theme_id);
 	}
 
 	/**
 	 * Returns variables with values that should be passed through with this link + variable list
 	 *
 	 * @param Array $params
 	 * @return Array
 	 * @access public
 	 */
 	public function getPassThroughVariables(&$params)
 	{
 		return $this->UrlManager->getPassThroughVariables($params);
 	}
 
 	/**
 	 * Builds url
 	 *
 	 * @param string $t
 	 * @param Array $params
 	 * @param string $pass
 	 * @param bool $pass_events
 	 * @param bool $env_var
 	 * @return string
 	 * @access public
 	 */
 	public function BuildEnv($t, $params, $pass = 'all', $pass_events = false, $env_var = true)
 	{
 		return $this->UrlManager->plain->build($t, $params, $pass, $pass_events, $env_var);
 	}
 
 	/**
 	 * Process QueryString only, create
 	 * events, ids, based on config
 	 * set template name and sid in
 	 * desired application variables.
 	 *
 	 * @param string $env_var environment string value
 	 * @param string $pass_name
 	 * @return Array
 	 * @access public
 	 */
 	public function processQueryString($env_var, $pass_name = 'passed')
 	{
 		return $this->UrlManager->plain->parse($env_var, $pass_name);
 	}
 
 	/**
 	 * Parses rewrite url and returns parsed variables
 	 *
 	 * @param string $url
 	 * @param string $pass_name
 	 * @return Array
 	 * @access public
 	 */
 	public function parseRewriteUrl($url, $pass_name = 'passed')
 	{
 		return $this->UrlManager->rewrite->parse($url, $pass_name);
 	}
 
 	/**
 	 * Returns base part of all urls, build on website
 	 *
 	 * @param string $prefix
 	 * @param bool $ssl
 	 * @param bool $add_port
 	 * @return string
 	 * @access public
 	 */
 	public function BaseURL($prefix = '', $ssl = null, $add_port = true)
 	{
 		if ( $ssl === null ) {
 			// stay on same encryption level
 			return PROTOCOL . SERVER_NAME . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/';
 		}
 
 		if ( $ssl ) {
 			// going from http:// to https://
 			$base_url = $this->isAdmin ? $this->ConfigValue('AdminSSL_URL') : false;
 
 			if ( !$base_url ) {
 				$ssl_url = $this->siteDomainField('SSLUrl');
 				$base_url = $ssl_url !== false ? $ssl_url : $this->ConfigValue('SSL_URL');
 			}
 
 			return rtrim($base_url, '/') . $prefix . '/';
 		}
 
 		// going from https:// to http://
 		$domain = $this->siteDomainField('DomainName');
 
 		if ( $domain === false ) {
 			$domain = DOMAIN;
 		}
 
 		return 'http://' . $domain . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/';
 	}
 
 	/**
 	 * Redirects user to url, that's build based on given parameters
 	 *
 	 * @param string $t
 	 * @param Array $params
 	 * @param string $prefix
 	 * @param string $index_file
 	 * @return void
 	 * @access public
 	 */
 	public function Redirect($t = '', $params = Array(), $prefix = '', $index_file = null)
 	{
 		$js_redirect = getArrayValue($params, 'js_redirect');
 
 		if ( $t == '' || $t === true ) {
 			$t = $this->GetVar('t');
 		}
 
 		// pass prefixes and special from previous url
 		if ( array_key_exists('js_redirect', $params) ) {
 			unset($params['js_redirect']);
 		}
 
 		// allows to send custom responce code along with redirect header
 		if ( array_key_exists('response_code', $params) ) {
 			$response_code = (int)$params['response_code'];
 			unset($params['response_code']);
 		}
 		else {
 			$response_code = 302; // Found
 		}
 
 		if ( !array_key_exists('pass', $params) ) {
 			$params['pass'] = 'all';
 		}
 
 		if ( $this->GetVar('ajax') == 'yes' && $t == $this->GetVar('t') ) {
 			// redirects to the same template as current
 			$params['ajax'] = 'yes';
 		}
 
 		$params['__URLENCODE__'] = 1;
 		$location = $this->HREF($t, $prefix, $params, $index_file);
 
 		if ( $this->isDebugMode() && (kUtil::constOn('DBG_REDIRECT') || (kUtil::constOn('DBG_RAISE_ON_WARNINGS') && $this->Debugger->WarningCount)) ) {
 			$this->Debugger->appendTrace();
 			echo '<strong>Debug output above !!!</strong><br/>' . "\n";
 
 			if ( array_key_exists('HTTP_REFERER', $_SERVER) ) {
 				echo 'Referer: <strong>' . $_SERVER['HTTP_REFERER'] . '</strong><br/>' . "\n";
 			}
 
 			echo "Proceed to redirect: <a href=\"{$location}\">{$location}</a><br/>\n";
 		}
 		else {
 			if ( $js_redirect ) {
 				// show "redirect" template instead of redirecting,
 				// because "Set-Cookie" header won't work, when "Location"
 				// header is used later
 				$this->SetVar('t', 'redirect');
 				$this->SetVar('redirect_to', $location);
 
 				// make all additional parameters available on "redirect" template too
 				foreach ($params as $name => $value) {
 					$this->SetVar($name, $value);
 				}
 
 				return;
 			}
 			else {
 				if ( $this->GetVar('ajax') == 'yes' && $t != $this->GetVar('t') ) {
 					// redirection to other then current template during ajax request
 					kUtil::safeDefine('DBG_SKIP_REPORTING', 1);
 					echo '#redirect#' . $location;
 				}
 				elseif ( headers_sent() != '' ) {
 					// some output occurred -> redirect using javascript
 					echo '<script type="text/javascript">window.location.href = \'' . $location . '\';</script>';
 				}
 				else {
 					// no output before -> redirect using HTTP header
 
 //					header('HTTP/1.1 302 Found');
 					header('Location: ' . $location, true, $response_code);
 				}
 			}
 		}
 
 		// session expiration is called from session initialization,
 		// that's why $this->Session may be not defined here
 		$session = $this->recallObject('Session');
 		/* @var $session Session */
 
 		if ( $this->InitDone ) {
 			// if redirect happened in the middle of application initialization don't call event,
 			// that presumes that application was successfully initialized
 			$this->HandleEvent(new kEvent('adm:OnBeforeShutdown'));
 		}
 
 		$session->SaveData();
 
 		ob_end_flush();
 		exit;
 	}
 
 	/**
 	 * Returns translation of given label
 	 *
 	 * @param string $label
 	 * @param bool $allow_editing return translation link, when translation is missing on current language
 	 * @param bool $use_admin use current Admin Console language to translate phrase
 	 * @return string
 	 * @access public
 	 */
 	public function Phrase($label, $allow_editing = true, $use_admin = false)
 	{
 		return $this->Phrases->GetPhrase($label, $allow_editing, $use_admin);
 	}
 
 	/**
 	 * Replace language tags in exclamation marks found in text
 	 *
 	 * @param string $text
 	 * @param bool $force_escape force escaping, not escaping of resulting string
 	 * @return string
 	 * @access public
 	 */
 	public function ReplaceLanguageTags($text, $force_escape = null)
 	{
 		return $this->Phrases->ReplaceLanguageTags($text, $force_escape);
 	}
 
 	/**
 	 * Checks if user is logged in, and creates
 	 * user object if so. User object can be recalled
 	 * later using "u.current" prefix_special. Also you may
 	 * get user id by getting "u.current_id" variable.
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function ValidateLogin()
 	{
 		$session = $this->recallObject('Session');
 		/* @var $session Session */
 
 		$user_id = $session->GetField('PortalUserId');
 
 		if ( !$user_id && $user_id != USER_ROOT ) {
 			$user_id = USER_GUEST;
 		}
 
 		$this->SetVar('u.current_id', $user_id);
 
 		if ( !$this->isAdmin ) {
 			// needed for "profile edit", "registration" forms ON FRONT ONLY
 			$this->SetVar('u_id', $user_id);
 		}
 
 		$this->StoreVar('user_id', $user_id, $user_id == USER_GUEST); // storing Guest user_id (-2) is optional
 
 		$this->isAdminUser = $this->isAdmin && $this->LoggedIn();
 
 		if ( $this->GetVar('expired') == 1 ) {
 			// this parameter is set only from admin
 			$user = $this->recallObject('u.login-admin', null, Array ('form_name' => 'login'));
 			/* @var $user UsersItem */
 
 			$user->SetError('UserLogin', 'session_expired', 'la_text_sess_expired');
 		}
 
 		if ( ($user_id != USER_GUEST) && defined('DBG_REQUREST_LOG') && DBG_REQUREST_LOG ) {
 			$this->HttpQuery->writeRequestLog(DBG_REQUREST_LOG);
 		}
 
 		if ( $user_id != USER_GUEST ) {
 			// normal users + root
 			$this->LoadPersistentVars();
 		}
 
 		$user_timezone = $this->Session->GetField('TimeZone');
 
 		if ( $user_timezone ) {
 			date_default_timezone_set($user_timezone);
 		}
 	}
 
 	/**
 	 * Loads current user persistent session data
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function LoadPersistentVars()
 	{
 		$this->Session->LoadPersistentVars();
 	}
 
 	/**
 	 * Returns configuration option value by name
 	 *
 	 * @param string $name
 	 * @return string
 	 * @access public
 	 */
 	public function ConfigValue($name)
 	{
 		return $this->cacheManager->ConfigValue($name);
 	}
 
 	/**
 	 * Changes value of individual configuration variable (+resets cache, when needed)
 	 *
 	 * @param string $name
 	 * @param string $value
 	 * @param bool $local_cache_only
 	 * @return string
 	 * @access public
 	 */
 	public function SetConfigValue($name, $value, $local_cache_only = false)
 	{
 		return $this->cacheManager->SetConfigValue($name, $value, $local_cache_only);
 	}
 
 	/**
 	 * Allows to process any type of event
 	 *
 	 * @param kEvent $event
 	 * @param Array $params
 	 * @param Array $specific_params
 	 * @return void
 	 * @access public
 	 */
 	public function HandleEvent($event, $params = null, $specific_params = null)
 	{
 		if ( isset($params) ) {
 			$event = new kEvent($params, $specific_params);
 		}
 
 		$this->EventManager->HandleEvent($event);
 	}
 
 	/**
 	 * Notifies event subscribers, that event has occured
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 */
 	public function notifyEventSubscribers(kEvent $event)
 	{
 		$this->EventManager->notifySubscribers($event);
 	}
 
 	/**
 	 * Allows to process any type of event
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access public
 	 */
 	public function eventImplemented(kEvent $event)
 	{
 		return $this->EventManager->eventImplemented($event);
 	}
 
 	/**
 	 * Registers new class in the factory
 	 *
 	 * @param string $real_class Real name of class as in class declaration
 	 * @param string $file Filename in what $real_class is declared
 	 * @param string $pseudo_class Name under this class object will be accessed using getObject method
 	 * @return void
 	 * @access public
 	 */
 	public function registerClass($real_class, $file, $pseudo_class = null)
 	{
 		$this->Factory->registerClass($real_class, $file, $pseudo_class);
 	}
 
 	/**
 	 * Unregisters existing class from factory
 	 *
 	 * @param string $real_class Real name of class as in class declaration
 	 * @param string $pseudo_class Name under this class object is accessed using getObject method
 	 * @return void
 	 * @access public
 	 */
 	public function unregisterClass($real_class, $pseudo_class = null)
 	{
 		$this->Factory->unregisterClass($real_class, $pseudo_class);
 	}
 
 	/**
 	 * Add new scheduled task
 	 *
 	 * @param string $short_name name to be used to store last maintenance run info
 	 * @param string $event_string
 	 * @param int $run_schedule run schedule like for Cron
 	 * @param int $status
 	 * @access public
 	 */
 	public function registerScheduledTask($short_name, $event_string, $run_schedule, $status = STATUS_ACTIVE)
 	{
 		$this->EventManager->registerScheduledTask($short_name, $event_string, $run_schedule, $status);
 	}
 
 	/**
 	 * Registers Hook from subprefix event to master prefix event
 	 *
 	 * Pattern: Observer
 	 *
 	 * @param string $hook_event
 	 * @param string $do_event
 	 * @param int $mode
 	 * @param bool $conditional
 	 * @access public
 	 */
 	public function registerHook($hook_event, $do_event, $mode = hAFTER, $conditional = false)
 	{
 		$this->EventManager->registerHook($hook_event, $do_event, $mode, $conditional);
 	}
 
 	/**
 	 * Registers build event for given pseudo class
 	 *
 	 * @param string $pseudo_class
 	 * @param string $event_name
 	 * @access public
 	 */
 	public function registerBuildEvent($pseudo_class, $event_name)
 	{
 		$this->EventManager->registerBuildEvent($pseudo_class, $event_name);
 	}
 
 	/**
 	 * Allows one TagProcessor tag act as other TagProcessor tag
 	 *
 	 * @param Array $tag_info
 	 * @return void
 	 * @access public
 	 */
 	public function registerAggregateTag($tag_info)
 	{
 		$aggregator = $this->recallObject('TagsAggregator', 'kArray');
 		/* @var $aggregator kArray */
 
 		$tag_data = Array (
 			$tag_info['LocalPrefix'],
 			$tag_info['LocalTagName'],
 			getArrayValue($tag_info, 'LocalSpecial')
 		);
 
 		$aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], $tag_data);
 	}
 
 	/**
 	 * Returns object using params specified, creates it if is required
 	 *
 	 * @param string $name
 	 * @param string $pseudo_class
 	 * @param Array $event_params
 	 * @param Array $arguments
 	 * @return kBase
 	 */
 	public function recallObject($name, $pseudo_class = null, $event_params = Array(), $arguments = Array ())
 	{
 		/*if ( !$this->hasObject($name) && $this->isDebugMode() && ($name == '_prefix_here_') ) {
 			// first time, when object with "_prefix_here_" prefix is accessed
 			$this->Debugger->appendTrace();
 		}*/
 
 		return $this->Factory->getObject($name, $pseudo_class, $event_params, $arguments);
 	}
 
 	/**
 	 * Returns tag processor for prefix specified
 	 *
 	 * @param string $prefix
 	 * @return kDBTagProcessor
 	 * @access public
 	 */
 	public function recallTagProcessor($prefix)
 	{
 		$this->InitParser(); // because kDBTagProcesor is in NParser dependencies
 
 		return $this->recallObject($prefix . '_TagProcessor');
 	}
 
 	/**
 	 * Checks if object with prefix passes was already created in factory
 	 *
 	 * @param string $name object pseudo_class, prefix
 	 * @return bool
 	 * @access public
 	 */
 	public function hasObject($name)
 	{
 		return $this->Factory->hasObject($name);
 	}
 
 	/**
 	 * Removes object from storage by given name
 	 *
 	 * @param string $name Object's name in the Storage
 	 * @return void
 	 * @access public
 	 */
 	public function removeObject($name)
 	{
 		$this->Factory->DestroyObject($name);
 	}
 
 	/**
 	 * Get's real class name for pseudo class, includes class file and creates class instance
 	 *
 	 * Pattern: Factory Method
 	 *
 	 * @param string $pseudo_class
 	 * @param Array $arguments
 	 * @return kBase
 	 * @access public
 	 */
 	public function makeClass($pseudo_class, $arguments = Array ())
 	{
 		return $this->Factory->makeClass($pseudo_class, $arguments);
 	}
 
 	/**
 	 * Checks if application is in debug mode
 	 *
 	 * @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant
 	 * @return bool
 	 * @author Alex
 	 * @access public
 	 */
 	public function isDebugMode($check_debugger = true)
 	{
 		$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
 		if ($check_debugger) {
 			$debug_mode = $debug_mode && is_object($this->Debugger);
 		}
 		return $debug_mode;
 	}
 
 	/**
 	 * Apply url rewriting used by mod_rewrite or not
 	 *
 	 * @param bool|null $ssl Force ssl link to be build
 	 * @return bool
 	 * @access public
 	 */
 	public function RewriteURLs($ssl = false)
 	{
 		// case #1,#4:
 		//			we want to create https link from http mode
 		//			we want to create https link from https mode
 		//			conditions: ($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')
 
 		// case #2,#3:
 		//			we want to create http link from https mode
 		//			we want to create http link from http mode
 		//			conditions: !$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')
 
 		$allow_rewriting =
 			(!$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')) // always allow mod_rewrite for http
 			|| // or allow rewriting for redirect TO httpS or when already in httpS
 			(($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')); // but only if it's allowed in config!
 
 		return kUtil::constOn('MOD_REWRITE') && $allow_rewriting;
 	}
 
 	/**
-	 * Reads unit (specified by $prefix)
-	 * option specified by $option
+	 * Returns unit config for given prefix
 	 *
 	 * @param string $prefix
-	 * @param string $option
-	 * @param mixed $default
-	 * @return string
+	 * @return kUnitConfig
 	 * @access public
 	 */
-	public function getUnitOption($prefix, $option, $default = false)
+	public function getUnitConfig($prefix)
 	{
-		return $this->UnitConfigReader->getUnitOption($prefix, $option, $default);
+		return $this->UnitConfigReader->getUnitConfig($prefix);
 	}
 
-	/**
-	 * Set's new unit option value
-	 *
-	 * @param string $prefix
-	 * @param string $option
-	 * @param string $value
-	 * @access public
-	 */
-	public function setUnitOption($prefix, $option, $value)
-	{
-		$this->UnitConfigReader->setUnitOption($prefix,$option,$value);
-	}
-
-	/**
-	 * Read all unit with $prefix options
-	 *
-	 * @param string $prefix
-	 * @return Array
-	 * @access public
-	 */
-	public function getUnitOptions($prefix)
-	{
-		return $this->UnitConfigReader->getUnitOptions($prefix);
-	}
 
 	/**
 	 * Returns true if config exists and is allowed for reading
 	 *
 	 * @param string $prefix
 	 * @return bool
 	 */
 	public function prefixRegistred($prefix)
 	{
 		return $this->UnitConfigReader->prefixRegistred($prefix);
 	}
 
 	/**
 	 * Splits any mixing of prefix and
 	 * special into correct ones
 	 *
 	 * @param string $prefix_special
 	 * @return Array
 	 * @access public
 	 */
 	public function processPrefix($prefix_special)
 	{
 		return $this->Factory->processPrefix($prefix_special);
 	}
 
 	/**
 	 * Set's new event for $prefix_special
 	 * passed
 	 *
 	 * @param string $prefix_special
 	 * @param string $event_name
 	 * @return void
 	 * @access public
 	 */
 	public function setEvent($prefix_special, $event_name)
 	{
 		$this->EventManager->setEvent($prefix_special, $event_name);
 	}
 
 	/**
 	 * SQL Error Handler
 	 *
 	 * @param int $code
 	 * @param string $msg
 	 * @param string $sql
 	 * @return bool
 	 * @access public
 	 * @throws Exception
 	 * @deprecated
 	 */
 	public function handleSQLError($code, $msg, $sql)
 	{
 		return $this->_logger->handleSQLError($code, $msg, $sql);
 	}
 
 	/**
 	 * Returns & blocks next ResourceId available in system
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function NextResourceId()
 	{
 		$table_name = TABLE_PREFIX . 'IdGenerator';
 
 		$this->Conn->Query('LOCK TABLES ' . $table_name . ' WRITE');
 		$this->Conn->Query('UPDATE ' . $table_name . ' SET lastid = lastid + 1');
 		$id = $this->Conn->GetOne('SELECT lastid FROM ' . $table_name);
 
 		if ( $id === false ) {
 			$this->Conn->Query('INSERT INTO ' . $table_name . ' (lastid) VALUES (2)');
 			$id = 2;
 		}
 
 		$this->Conn->Query('UNLOCK TABLES');
 
 		return $id - 1;
 	}
 
 	/**
 	 * Returns genealogical main prefix for sub-table prefix passes
 	 * OR prefix, that has been found in REQUEST and some how is parent of passed sub-table prefix
 	 *
 	 * @param string $current_prefix
 	 * @param bool $real_top if set to true will return real topmost prefix, regardless of its id is passed or not
 	 * @return string
 	 * @access public
 	 */
 	public function GetTopmostPrefix($current_prefix, $real_top = false)
 	{
 		// 1. get genealogical tree of $current_prefix
 		$prefixes = Array ($current_prefix);
-		while ($parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix')) {
+		while ($parent_prefix = $this->getUnitConfig($current_prefix)->getParentPrefix()) {
 			if ( !$this->prefixRegistred($parent_prefix) ) {
 				// stop searching, when parent prefix is not registered
 				break;
 			}
 
 			$current_prefix = $parent_prefix;
 			array_unshift($prefixes, $current_prefix);
 		}
 
 		if ( $real_top ) {
 			return $current_prefix;
 		}
 
 		// 2. find what if parent is passed
 		$passed = explode(',', $this->GetVar('all_passed'));
 		foreach ($prefixes as $a_prefix) {
 			if ( in_array($a_prefix, $passed) ) {
 				return $a_prefix;
 			}
 		}
 
 		return $current_prefix;
 	}
 
 	/**
 	 * Triggers email event of type Admin
 	 *
 	 * @param string $email_template_name
 	 * @param int $to_user_id
 	 * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text
 	 * @return kEvent
 	 * @access public
 	 */
 	public function emailAdmin($email_template_name, $to_user_id = null, $send_params = Array ())
 	{
 		return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_ADMIN, $to_user_id, $send_params);
 	}
 
 	/**
 	 * Triggers email event of type User
 	 *
 	 * @param string $email_template_name
 	 * @param int $to_user_id
 	 * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text
 	 * @return kEvent
 	 * @access public
 	 */
 	public function emailUser($email_template_name, $to_user_id = null, $send_params = Array ())
 	{
 		return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_FRONTEND, $to_user_id, $send_params);
 	}
 
 	/**
 	 * Triggers general email event
 	 *
 	 * @param string $email_template_name
 	 * @param int $email_template_type (0 for User, 1 for Admin)
 	 * @param int $to_user_id
 	 * @param array $send_params associative array of direct send params,
 	 *  possible keys: to_email, to_name, from_email, from_name, message, message_text
 	 * @return kEvent
 	 * @access protected
 	 */
 	protected function _email($email_template_name, $email_template_type, $to_user_id = null, $send_params = Array ())
 	{
 		$email = $this->makeClass('kEmail');
 		/* @var $email kEmail */
 
 		if ( !$email->findTemplate($email_template_name, $email_template_type) ) {
 			return false;
 		}
 
 		$email->setParams($send_params);
 
 		return $email->send($to_user_id);
 	}
 
 	/**
 	 * Allows to check if user in this session is logged in or not
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function LoggedIn()
 	{
 		// no session during expiration process
 		return is_null($this->Session) ? false : $this->Session->LoggedIn();
 	}
 
 	/**
 	 * Check current user permissions based on it's group permissions in specified category
 	 *
 	 * @param string $name permission name
 	 * @param int $cat_id category id, current used if not specified
 	 * @param int $type permission type {1 - system, 0 - per category}
 	 * @return int
 	 * @access public
 	 */
 	public function CheckPermission($name, $type = 1, $cat_id = null)
 	{
 		$perm_helper = $this->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		return $perm_helper->CheckPermission($name, $type, $cat_id);
 	}
 
 	/**
 	 * Check current admin permissions based on it's group permissions in specified category
 	 *
 	 * @param string $name permission name
 	 * @param int $cat_id category id, current used if not specified
 	 * @param int $type permission type {1 - system, 0 - per category}
 	 * @return int
 	 * @access public
 	 */
 	public function CheckAdminPermission($name, $type = 1, $cat_id = null)
 	{
 		$perm_helper = $this->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		return $perm_helper->CheckAdminPermission($name, $type, $cat_id);
 	}
 
 	/**
 	 * Set's any field of current visit
 	 *
 	 * @param string $field
 	 * @param mixed $value
 	 * @return void
 	 * @access public
 	 * @todo move to separate module
 	 */
 	public function setVisitField($field, $value)
 	{
 		if ( $this->isAdmin || !$this->ConfigValue('UseVisitorTracking') ) {
 			// admin logins are not registered in visits list
 			return;
 		}
 
 		$visit = $this->recallObject('visits', null, Array ('raise_warnings' => 0));
 		/* @var $visit kDBItem */
 
 		if ( $visit->isLoaded() ) {
 			$visit->SetDBField($field, $value);
 			$visit->Update();
 		}
 	}
 
 	/**
 	 * Allows to check if in-portal is installed
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function isInstalled()
 	{
 		return $this->InitDone && (count($this->ModuleInfo) > 0);
 	}
 
 	/**
 	 * Allows to determine if module is installed & enabled
 	 *
 	 * @param string $module_name
 	 * @return bool
 	 * @access public
 	 */
 	public function isModuleEnabled($module_name)
 	{
 		return $this->findModule('Name', $module_name) !== false;
 	}
 
 	/**
 	 * Returns Window ID of passed prefix main prefix (in edit mode)
 	 *
 	 * @param string $prefix
 	 * @return int
 	 * @access public
 	 */
 	public function GetTopmostWid($prefix)
 	{
 		$top_prefix = $this->GetTopmostPrefix($prefix);
 		$mode = $this->GetVar($top_prefix . '_mode');
 
 		return $mode != '' ? substr($mode, 1) : '';
 	}
 
 	/**
 	 * Get temp table name
 	 *
 	 * @param string $table
 	 * @param mixed $wid
 	 * @return string
 	 * @access public
 	 */
 	public function GetTempName($table, $wid = '')
 	{
 		return $this->GetTempTablePrefix($wid) . $table;
 	}
 
 	/**
 	 * Builds temporary table prefix based on given window id
 	 *
 	 * @param string $wid
 	 * @return string
 	 * @access public
 	 */
 	public function GetTempTablePrefix($wid = '')
 	{
 		if ( preg_match('/prefix:(.*)/', $wid, $regs) ) {
 			$wid = $this->GetTopmostWid($regs[1]);
 		}
 
 		return TABLE_PREFIX . 'ses_' . $this->GetSID() . ($wid ? '_' . $wid : '') . '_edit_';
 	}
 
 	/**
 	 * Checks if given table is a temporary table
 	 *
 	 * @param string $table
 	 * @return bool
 	 * @access public
 	 */
 	public function IsTempTable($table)
 	{
 		static $cache = Array ();
 
 		if ( !array_key_exists($table, $cache) ) {
 			$cache[$table] = preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $table);
 		}
 
 		return (bool)$cache[$table];
 	}
 
 	/**
 	 * Checks, that given prefix is in temp mode
 	 *
 	 * @param string $prefix
 	 * @param string $special
 	 * @return bool
 	 * @access public
 	 */
 	public function IsTempMode($prefix, $special = '')
 	{
 		$top_prefix = $this->GetTopmostPrefix($prefix);
 
 		$var_names = Array (
 			$top_prefix,
 			rtrim($top_prefix . '_' . $special, '_'), // from post
 			rtrim($top_prefix . '.' . $special, '.'), // assembled locally
 		);
 
 		$var_names = array_unique($var_names);
 
 		$temp_mode = false;
 		foreach ($var_names as $var_name) {
 			$value = $this->GetVar($var_name . '_mode');
 
 			if ( $value && (substr($value, 0, 1) == 't') ) {
 				$temp_mode = true;
 				break;
 			}
 		}
 
 		return $temp_mode;
 	}
 
 	/**
 	 * Return live table name based on temp table name
 	 *
 	 * @param string $temp_table
 	 * @return string
 	 */
 	public function GetLiveName($temp_table)
 	{
 		if ( preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $temp_table, $rets) ) {
 			// cut wid from table end if any
 			return $rets[2];
 		}
 		else {
 			return $temp_table;
 		}
 	}
 
 	/**
 	 * Stops processing of user request and displays given message
 	 *
 	 * @param string $message
 	 * @access public
 	 */
 	public function ApplicationDie($message = '')
 	{
 		$message = ob_get_clean() . $message;
 
 		if ( $this->isDebugMode() ) {
 			$message .= $this->Debugger->printReport(true);
 		}
 
 		echo $this->UseOutputCompression() ? gzencode($message, DBG_COMPRESSION_LEVEL) : $message;
 		exit;
 	}
 
 	/**
 	 * Returns comma-separated list of groups from given user
 	 *
 	 * @param int $user_id
 	 * @return string
 	 */
 	public function getUserGroups($user_id)
 	{
 		switch ($user_id) {
 			case USER_ROOT:
 				$user_groups = $this->ConfigValue('User_LoggedInGroup');
 				break;
 
 			case USER_GUEST:
 				$user_groups = $this->ConfigValue('User_LoggedInGroup') . ',' . $this->ConfigValue('User_GuestGroup');
 				break;
 
 			default:
 				$sql = 'SELECT GroupId
 						FROM ' . TABLE_PREFIX . 'UserGroupRelations
 						WHERE PortalUserId = ' . (int)$user_id;
 				$res = $this->Conn->GetCol($sql);
 
 				$user_groups = Array ($this->ConfigValue('User_LoggedInGroup'));
 				if ( $res ) {
 					$user_groups = array_merge($user_groups, $res);
 				}
 
 				$user_groups = implode(',', $user_groups);
 		}
 
 		return $user_groups;
 	}
 
 
 	/**
 	 * Allows to detect if page is browsed by spider (293 scheduled_tasks supported)
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	/*public function IsSpider()
 	{
 		static $is_spider = null;
 
 		if ( !isset($is_spider) ) {
 			$user_agent = trim($_SERVER['HTTP_USER_AGENT']);
 			$robots = file(FULL_PATH . '/core/robots_list.txt');
 			foreach ($robots as $robot_info) {
 				$robot_info = explode("\t", $robot_info, 3);
 				if ( $user_agent == trim($robot_info[2]) ) {
 					$is_spider = true;
 					break;
 				}
 			}
 		}
 
 		return $is_spider;
 	}*/
 
 	/**
 	 * Allows to detect table's presence in database
 	 *
 	 * @param string $table_name
 	 * @param bool $force
 	 * @return bool
 	 * @access public
 	 */
 	public function TableFound($table_name, $force = false)
 	{
 		return $this->Conn->TableFound($table_name, $force);
 	}
 
 	/**
 	 * Returns counter value
 	 *
 	 * @param string $name counter name
 	 * @param Array $params counter parameters
 	 * @param string $query_name specify query name directly (don't generate from parameters)
 	 * @param bool $multiple_results
 	 * @return mixed
 	 * @access public
 	 */
 	public function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false)
 	{
 		$count_helper = $this->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		return $count_helper->getCounter($name, $params, $query_name, $multiple_results);
 	}
 
 	/**
 	 * Resets counter, which are affected by one of specified tables
 	 *
 	 * @param string $tables comma separated tables list used in counting sqls
 	 * @return void
 	 * @access public
 	 */
 	public function resetCounters($tables)
 	{
 		if ( kUtil::constOn('IS_INSTALL') ) {
 			return;
 		}
 
 		$count_helper = $this->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		$count_helper->resetCounters($tables);
 	}
 
 	/**
 	 * Sends XML header + optionally displays xml heading
 	 *
 	 * @param string|bool $xml_version
 	 * @return string
 	 * @access public
 	 * @author Alex
 	 */
 	public function XMLHeader($xml_version = false)
 	{
 		$this->setContentType('text/xml');
 
 		return $xml_version ? '<?xml version="' . $xml_version . '" encoding="' . CHARSET . '"?>' : '';
 	}
 
 	/**
 	 * Returns category tree
 	 *
 	 * @param int $category_id
 	 * @return Array
 	 * @access public
 	 */
 	public function getTreeIndex($category_id)
 	{
 		$tree_index = $this->getCategoryCache($category_id, 'category_tree');
 
 		if ( $tree_index ) {
 			$ret = Array ();
 			list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index);
 
 			return $ret;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Base category of all categories
 	 * Usually replaced category, with ID = 0 in category-related operations.
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function getBaseCategory()
 	{
 		// same, what $this->findModule('Name', 'Core', 'RootCat') does
 		// don't cache while IS_INSTALL, because of kInstallToolkit::createModuleCategory and upgrade
 
 		return $this->ModuleInfo['Core']['RootCat'];
 	}
 
 	/**
 	 * Deletes all data, that was cached during unit config parsing (excluding unit config locations)
 	 *
 	 * @param Array $config_variables
 	 * @access public
 	 */
 	public function DeleteUnitCache($config_variables = null)
 	{
 		$this->cacheManager->DeleteUnitCache($config_variables);
 	}
 
 	/**
 	 * Deletes cached section tree, used during permission checking and admin console tree display
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function DeleteSectionCache()
 	{
 		$this->cacheManager->DeleteSectionCache();
 	}
 
 	/**
 	 * Sets data from cache to object
 	 *
 	 * @param Array $data
 	 * @access public
 	 */
 	public function setFromCache(&$data)
 	{
 		$this->Factory->setFromCache($data);
 		$this->UnitConfigReader->setFromCache($data);
 		$this->EventManager->setFromCache($data);
 
 		$this->ReplacementTemplates = $data['Application.ReplacementTemplates'];
 		$this->RewriteListeners = $data['Application.RewriteListeners'];
 		$this->ModuleInfo = $data['Application.ModuleInfo'];
 	}
 
 	/**
 	 * Gets object data for caching
 	 * The following caches should be reset based on admin interaction (adjusting config, enabling modules etc)
 	 *
 	 * @access public
 	 * @return Array
 	 */
 	public function getToCache()
 	{
 		return array_merge(
 			$this->Factory->getToCache(),
 			$this->UnitConfigReader->getToCache(),
 			$this->EventManager->getToCache(),
 			Array (
 				'Application.ReplacementTemplates' => $this->ReplacementTemplates,
 				'Application.RewriteListeners' => $this->RewriteListeners,
 				'Application.ModuleInfo' => $this->ModuleInfo,
 			)
 		);
 	}
 
 	public function delayUnitProcessing($method, $params)
 	{
 		$this->cacheManager->delayUnitProcessing($method, $params);
 	}
 
 	/**
 	 * Returns current maintenance mode state
 	 *
 	 * @param bool $check_ips
 	 * @return int
 	 * @access public
 	 */
 	public function getMaintenanceMode($check_ips = true)
 	{
 		$exception_ips = defined('MAINTENANCE_MODE_IPS') ? MAINTENANCE_MODE_IPS : '';
 		$setting_name = $this->isAdmin ? 'MAINTENANCE_MODE_ADMIN' : 'MAINTENANCE_MODE_FRONT';
 
 		if ( defined($setting_name) && constant($setting_name) > MaintenanceMode::NONE ) {
 			$exception_ip = $check_ips ? kUtil::ipMatch($exception_ips) : false;
 
 			if ( !$exception_ip ) {
 				return constant($setting_name);
 			}
 		}
 
 		return MaintenanceMode::NONE;
 	}
 
 	/**
 	 * Sets content type of the page
 	 *
 	 * @param string $content_type
 	 * @param bool $include_charset
 	 * @return void
 	 * @access public
 	 */
 	public function setContentType($content_type = 'text/html', $include_charset = null)
 	{
 		static $already_set = false;
 
 		if ( $already_set ) {
 			return;
 		}
 
 		$header = 'Content-type: ' . $content_type;
 
 		if ( !isset($include_charset) ) {
 			$include_charset = $content_type = 'text/html' || $content_type == 'text/plain' || $content_type = 'text/xml';
 		}
 
 		if ( $include_charset ) {
 			$header .= '; charset=' . CHARSET;
 		}
 
 		$already_set = true;
 		header($header);
 	}
 
 	/**
 	 * Posts message to event log
 	 *
 	 * @param string $message
 	 * @param int $code
 	 * @param bool $write_now Allows further customization of log record by returning kLog object
 	 * @return bool|int|kLogger
 	 * @access public
 	 */
 	public function log($message, $code = null, $write_now = false)
 	{
 		$log = $this->_logger->prepare($message, $code)->addSource($this->_logger->createTrace(null, 1));
 
 		if ( $write_now ) {
 			return $log->write();
 		}
 
 		return $log;
 	}
 
 	/**
 	 * Deletes log with given id from database or disk, when database isn't available
 	 *
 	 * @param int $unique_id
 	 * @param int $storage_medium
 	 * @return void
 	 * @access public
 	 * @throws InvalidArgumentException
 	 */
 	public function deleteLog($unique_id, $storage_medium = kLogger::LS_AUTOMATIC)
 	{
 		$this->_logger->delete($unique_id, $storage_medium);
 	}
 
 	/**
 	 * Returns the client IP address.
 	 *
 	 * @return string The client IP address
 	 * @access public
 	 */
 	public function getClientIp()
 	{
 		return $this->HttpQuery->getClientIp();
 	}
 }
Index: branches/5.3.x/core/kernel/managers/scheduled_task_manager.php
===================================================================
--- branches/5.3.x/core/kernel/managers/scheduled_task_manager.php	(revision 15697)
+++ branches/5.3.x/core/kernel/managers/scheduled_task_manager.php	(revision 15698)
@@ -1,230 +1,230 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2010 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kScheduledTaskManager extends kBase implements kiCacheable {
 
 	/**
 	 * Events, that should be run after parser initialization
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $tasks = Array ();
 
 	/**
 	 * Sets data from cache to object
 	 *
 	 * @param Array $data
 	 * @access public
 	 */
 	public function setFromCache(&$data)
 	{
 		$this->tasks = $data['EventManager.scheduledTasks'];
 	}
 
 	/**
 	 * Gets object data for caching
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function getToCache()
 	{
 		return Array (
 			'EventManager.scheduledTasks' => $this->tasks,
 		);
 	}
 
 	/**
 	 * Returns information about registered scheduled tasks
 	 *
 	 * @param bool $from_cache
 	 * @return Array
 	 * @access public
 	 */
 	public function getAll($from_cache = false)
 	{
 		static $scheduled_tasks = null;
 
 		if ( $from_cache ) {
 			return $this->tasks;
 		}
 
 		if ( !isset($scheduled_tasks) ) {
 			$timeout_clause = 'LastRunStatus = ' . ScheduledTask::LAST_RUN_RUNNING . ' AND Timeout > 0 AND ' . adodb_mktime() . ' - LastRunOn > Timeout';
 
 			$sql = 'SELECT *
-					FROM ' . $this->Application->getUnitOption('scheduled-task', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('scheduled-task')->getTableName() . '
 					WHERE (Status = ' . STATUS_ACTIVE . ') AND ((LastRunStatus != ' . ScheduledTask::LAST_RUN_RUNNING . ') OR (' . $timeout_clause . '))';
 			$scheduled_tasks = $this->Conn->Query($sql, 'Name');
 		}
 
 		return $scheduled_tasks;
 	}
 
 	/**
 	 * Add new scheduled task
 	 *
 	 * @param string $short_name name to be used to store last maintenance run info
 	 * @param string $event_string
 	 * @param int $run_schedule run schedule like for Cron
 	 * @param int $status
 	 * @access public
 	 */
 	public function add($short_name, $event_string, $run_schedule, $status = STATUS_ACTIVE)
 	{
 		$this->tasks[$short_name] = Array (
 			'Event' => $event_string, 'RunSchedule' => $run_schedule, 'Status' => $status
 		);
 	}
 
 	/**
 	 * Run registered scheduled tasks with specified event type
 	 *
 	 * @param bool $from_cron
 	 * @access public
 	 */
 	public function runAll($from_cron = false)
 	{
 		if ( defined('IS_INSTALL') ) {
 			return ;
 		}
 
 		$use_cron = $this->Application->ConfigValue('RunScheduledTasksFromCron');
 
 		if ( ($use_cron && !$from_cron) || (!$use_cron && $from_cron) ) {
 			// match execution place with one, set in config
 			return ;
 		}
 
 		ignore_user_abort(true);
 		set_time_limit(0);
 
 		$events_source = $this->getAll();
 
 		$user_id = $this->Application->RecallVar('user_id');
 		$this->Application->StoreVar('user_id', USER_ROOT, true); // to prevent permission checking inside events, true for optional storage
 
 		$site_helper = $this->Application->recallObject('SiteHelper');
 		/* @var $site_helper SiteHelper */
 
 		$site_domain_id = $site_helper->getDomainByName('DomainName', DOMAIN);
 
 		foreach ($events_source as $short_name => $event_data) {
 			if ( $site_domain_id && $event_data['SiteDomainLimitation'] != '' ) {
 				$site_domains = explode('|', substr($event_data['SiteDomainLimitation'], 1, -1));
 
 				if ( !in_array($site_domain_id, $site_domains) ) {
 					// scheduled task isn't allowed on this site domain
 					continue;
 				}
 			}
 
 			// remember LastTimeoutOn only for events that are still running and will be reset
 			if ( $event_data['LastRunStatus'] == ScheduledTask::LAST_RUN_RUNNING ) {
 				$this->update($event_data, Array ('LastTimeoutOn' => adodb_mktime()));
 			}
 
 			$next_run = (int)$event_data['NextRunOn'];
 
 			if ($next_run && ($next_run > adodb_mktime())) {
 				continue;
 			}
 
 			$this->run($event_data);
 		}
 
 		$this->Application->StoreVar('user_id', $user_id, $user_id == USER_GUEST);
 	}
 
 	/**
 	 * Runs scheduled task based on given data
 	 *
 	 * @param Array $scheduled_task_data
 	 * @return bool
 	 * @access public
 	 */
 	public function run($scheduled_task_data)
 	{
 		$event = new kEvent($scheduled_task_data['Event']);
 
 		if ( !$this->Application->prefixRegistred($event->Prefix) ) {
 			// don't process scheduled tasks, left from disabled modules
 			return false;
 		}
 
 		$cron_helper = $this->Application->recallObject('kCronHelper');
 		/* @var $cron_helper kCronHelper */
 
 		$start_time = adodb_mktime();
 
 		// remember, when scheduled task execution started
 		$fields_hash = Array (
 			'LastRunOn' => $start_time,
 			'LastRunStatus' => ScheduledTask::LAST_RUN_RUNNING,
 			'NextRunOn' => $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $start_time),
 		);
 
 		$this->update($scheduled_task_data, $fields_hash);
 
 		$scheduled_task = $this->Application->recallObject('scheduled-task', null, Array ('skip_autoload' => true));
 		/* @var $scheduled_task kDBItem */
 
 		$scheduled_task->LoadFromHash($scheduled_task_data);
 
 		$event->redirect = false;
 		$event->MasterEvent = new kEvent('scheduled-task:OnRun');
 		$this->Application->HandleEvent($event);
 
 		$now = adodb_mktime();
 		$next_run = $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $start_time);
 
 		while ($next_run < $now) {
 			// in case event execution took longer, then RunSchedule (don't use <=, because RunSchedule can be 0)
 			$next_run = $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $next_run);
 		}
 
 		// remember, when scheduled task execution ended
 		$fields_hash = Array (
 			'NextRunOn' => $next_run,
 			'RunTime' => $now - $start_time,
 			'LastRunStatus' => $event->status == kEvent::erSUCCESS ? ScheduledTask::LAST_RUN_SUCCEEDED : ScheduledTask::LAST_RUN_FAILED,
 		);
 
 		$this->update($scheduled_task_data, $fields_hash);
 
 		return true;
 	}
 
 	/**
 	 * Updates scheduled task record with latest changes about it's invocation progress
 	 *
 	 * @param Array $scheduled_task_data
 	 * @param Array $fields_hash
 	 * @return void
 	 * @access protected
 	 */
 	protected function update(&$scheduled_task_data, $fields_hash)
 	{
 		$this->Conn->doUpdate(
 			$fields_hash,
-			$this->Application->getUnitOption('scheduled-task', 'TableName'),
+			$this->Application->getUnitConfig('scheduled-task')->getTableName(),
 			'Name = ' . $this->Conn->qstr($scheduled_task_data['Name'])
 		);
 
 		$scheduled_task_data = array_merge($scheduled_task_data, $fields_hash);
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/managers/subscription_manager.php
===================================================================
--- branches/5.3.x/core/kernel/managers/subscription_manager.php	(revision 15697)
+++ branches/5.3.x/core/kernel/managers/subscription_manager.php	(revision 15698)
@@ -1,204 +1,204 @@
 <?php
 /**
  * @version	$Id$
  * @package	In-Portal
  * @copyright	Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
  * @license	  GNU/GPL
  * In-Portal is Open Source software.
  * This means that this software may have been modified pursuant
  * the GNU General Public License, and as distributed it includes
  * or is derivative of works licensed under the GNU General Public License
  * or other free or open source software licenses.
  * See http://www.in-portal.org/license for copyright notices and details.
  */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kSubscriptionManager extends kBase {
 
 	/**
 	 * List of subscriptions (instances of kSubcriptionItem objects)
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $subscriptions = Array ();
 
 	/**
 	 * Adds set of fields, that uniquely identifies subscription
 	 *
 	 * @param Array $fields
 	 * @return void
 	 */
 	public function add($fields)
 	{
 		$this->subscriptions[] = new kSubscriptionItem($fields);
 	}
 
 	/**
 	 * Detects if current user is subscribed to new posts in given topic
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function subscribed()
 	{
 		foreach ($this->subscriptions as $subscription) {
 			if ( !$subscription->getSubscription()->isLoaded() ) {
 				/* @var $subscription kSubscriptionItem */
 
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * Subscribes current user to new posts in a given topic
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function subscribe()
 	{
 		foreach ($this->subscriptions as $subscription) {
 			if ( !$subscription->subscribe() ) {
 				/* @var $subscription kSubscriptionItem */
 
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * Unsubscribes current user from reciving e-mails about new posts in a gvein topic
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function unsubscribe()
 	{
 		foreach ($this->subscriptions as $subscription) {
 			if ( !$subscription->unsubscribe() ) {
 				/* @var $subscription kSubscriptionItem */
 
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * Returns e-mail event id or throws an exception, when such event not found
 	 *
 	 * @param string $template_name
 	 * @param int $type
 	 * @return string
 	 * @throws Exception
 	 * @access public
 	 */
 	public function getEmailTemplateId($template_name, $type = EmailTemplate::TEMPLATE_TYPE_FRONTEND)
 	{
 		$sql = 'SELECT TemplateId
-				FROM ' . $this->Application->getUnitOption('email-template', 'TableName') . '
+				FROM ' . $this->Application->getUnitConfig('email-template')->getTableName() . '
 				WHERE TemplateName = ' . $this->Conn->qstr($template_name) . ' AND Type = ' . $type;
 		$id = $this->Conn->GetOne($sql);
 
 		if ( !$id ) {
 			throw new Exception('E-mail template "' . $template_name . '" not found');
 		}
 
 		return $id;
 	}
 }
 
 
 class kSubscriptionItem extends kBase {
 
 	/**
 	 * Fields set, that uniquely identifies subscription
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $fields = Array ();
 
 	/**
 	 * Creates new subscription item
 	 *
 	 * @param Array $fields
 	 * @access public
 	 */
 	public function __construct($fields)
 	{
 		parent::__construct();
 
 		$this->fields = $fields;
 	}
 
 	/**
 	 * Returns user subscription
 	 *
 	 * @param bool $reload
 	 * @return kBase|kDBItem
 	 * @access public
 	 */
 	public function getSubscription($reload = false)
 	{
 		$special = kUtil::crc32(serialize($this->fields));
 
 		$subscription = $this->Application->recallObject('system-event-subscription.' . $special, null, Array ('skip_autoload' => true));
 		/* @var $subscription kDBItem */
 
 		if ( !$subscription->isLoaded() || $reload ) {
 			$subscription->Load($this->fields);
 		}
 
 		return $subscription;
 	}
 
 	/**
 	 * Subscribes user
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function subscribe()
 	{
 		$subscription = $this->getSubscription();
 
 		if ( $subscription->isLoaded() ) {
 			return true;
 		}
 
 		$subscription->SetDBFieldsFromHash($this->fields);
 
 		return $subscription->Create();
 	}
 
 	/**
 	 * Unsubscribes user
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function unsubscribe()
 	{
 		$subscription = $this->getSubscription();
 
 		if ( !$subscription->isLoaded() ) {
 			return true;
 		}
 
 		$temp_handler = $this->Application->recallObject($subscription->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 		/* @var $temp_handler kTempTablesHandler */
 
 		$temp_handler->DeleteItems($subscription->Prefix, $subscription->Special, Array ($subscription->GetID()));
 
 		return true;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/managers/url_processor.php
===================================================================
--- branches/5.3.x/core/kernel/managers/url_processor.php	(revision 15697)
+++ branches/5.3.x/core/kernel/managers/url_processor.php	(revision 15698)
@@ -1,127 +1,127 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 abstract class kUrlProcessor extends kBase {
 
 	/**
 	 * Reference to kUrlProcessor class instance
 	 *
 	 * @var kUrlManager
 	 */
 	protected $manager = null;
 
 	public function __construct(&$manager)
 	{
 		parent::__construct();
 
 		$this->setManager($manager);
 	}
 
 	/**
 	 * Sets reference to url manager
 	 *
 	 * @param kUrlManager $manager
 	 * @return void
 	 * @access public
 	 */
 	public function setManager(&$manager)
 	{
 		$this->manager =& $manager;
 	}
 
 	/**
 	 * Returns sorted array of passed prefixes (to build url from)
 	 *
 	 * @param string $pass
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getPassInfo($pass = 'all')
 	{
 		if ( !$pass ) {
 			$pass = 'all';
 		}
 
 		$pass = trim(
 				preg_replace(
 					'/(?<=,|\\A)all(?=,|\\z)/',
 					trim($this->Application->GetVar('passed'), ','),
 					trim($pass, ',')
 				),
 		 ',');
 
 		if ( !$pass ) {
 			return Array ();
 		}
 
 		$pass_info = array_unique(explode(',', $pass)); // array( prefix[.special], prefix[.special] ...
 
 		// we need to keep that sorting despite the sorting below, because this sorts prefixes with same priority by name
 		sort($pass_info, SORT_STRING); // to be prefix1,prefix1.special1,prefix1.special2,prefix3.specialX
 
 		foreach ($pass_info as $prefix) {
 			list ($prefix_only,) = explode('.', $prefix, 2);
-			$sorted[$prefix] = $this->Application->getUnitOption($prefix_only, 'RewritePriority', 0);
+			$sorted[$prefix] = $this->Application->getUnitConfig($prefix_only)->getRewritePriority(0);
 		}
 
 		asort($sorted, SORT_NUMERIC);
 		$pass_info = array_keys($sorted);
 
 		// ensure that "m" prefix is at the beginning
 		$main_index = array_search('m', $pass_info);
 		if ( $main_index !== false ) {
 			unset($pass_info[$main_index]);
 			array_unshift($pass_info, 'm');
 		}
 
 		return $pass_info;
 	}
 
 	/**
 	 * Builds url
 	 *
 	 * @param string $t
 	 * @param Array $params
 	 * @param string $pass
 	 * @param bool $pass_events
 	 * @param bool $env_var
 	 * @return string
 	 * @access public
 	 */
 	abstract public function build($t, $params, $pass='all', $pass_events = false, $env_var = true);
 
 	/**
 	 * Parses given string into a set of variables
 	 *
 	 * @abstract
 	 * @param string $string
 	 * @param string $pass_name
 	 * @return Array
 	 * @access public
 	 */
 	abstract public function parse($string, $pass_name = 'passed');
 
 	/**
 	 * Builds env part that corresponds prefix passed
 	 *
 	 * @param string $prefix_special item's prefix & [special]
 	 * @param Array $params url params
 	 * @param bool $pass_events
 	 * @return string
 	 * @access protected
 	 */
 	abstract protected function BuildModuleEnv($prefix_special, &$params, $pass_events = false);
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/managers/rewrite_url_processor.php
===================================================================
--- branches/5.3.x/core/kernel/managers/rewrite_url_processor.php	(revision 15697)
+++ branches/5.3.x/core/kernel/managers/rewrite_url_processor.php	(revision 15698)
@@ -1,1091 +1,1090 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kRewriteUrlProcessor extends kUrlProcessor {
 
 	/**
 	 * Holds a reference to httpquery
 	 *
 	 * @var kHttpQuery
 	 * @access protected
 	 */
 	protected $HTTPQuery = null;
 
 	/**
 	 * Urls parts, that needs to be matched by rewrite listeners
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_partsToParse = Array ();
 
 	/**
 	 * Category item prefix, that was found
 	 *
 	 * @var string|bool
 	 * @access public
 	 */
 	public $modulePrefix = false;
 
 	/**
 	 * Template aliases for current theme
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_templateAliases = null;
 
 	/**
 	 * Domain-based primary language id
 	 *
 	 * @var int
 	 * @access public
 	 */
 	public $primaryLanguageId = false;
 
 	/**
 	 * Domain-based primary theme id
 	 *
 	 * @var int
 	 * @access public
 	 */
 	public $primaryThemeId = false;
 
 	/**
 	 * Possible url endings from ModRewriteUrlEnding configuration variable
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_urlEndings = Array ('.html', '/', '');
 
 	/**
 	 * Factory storage sub-set, containing mod-rewrite listeners, used during url building and parsing
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $rewriteListeners = Array ();
 
 	/**
 	 * Constructor of kRewriteUrlProcessor class
 	 *
 	 * @param $manager
 	 * @return kRewriteUrlProcessor
 	 */
 	public function __construct(&$manager)
 	{
 		parent::__construct($manager);
 
 		$this->HTTPQuery = $this->Application->recallObject('HTTPQuery');
 
 		// domain based primary language
 		$this->primaryLanguageId = $this->Application->siteDomainField('PrimaryLanguageId');
 
 		if (!$this->primaryLanguageId) {
 			// when domain-based language not found -> use site-wide language
 			$this->primaryLanguageId = $this->Application->GetDefaultLanguageId();
 		}
 
 		// domain based primary theme
 		$this->primaryThemeId = $this->Application->siteDomainField('PrimaryThemeId');
 
 		if (!$this->primaryThemeId) {
 			// when domain-based theme not found -> use site-wide theme
 			$this->primaryThemeId = $this->Application->GetDefaultThemeId(true);
 		}
 
 		$this->_initRewriteListeners();
 	}
 
 	/**
 	 * Parses url
 	 *
 	 * @return void
 	 */
 	public function parseRewriteURL()
 	{
 		$url = $this->Application->GetVar('_mod_rw_url_');
 
 		if ( $url ) {
 			$this->_redirectToDefaultUrlEnding($url);
 			$url = $this->_removeUrlEnding($url);
 		}
 
 		$cached = $this->_getCachedUrl($url);
 
 		if ( $cached !== false ) {
 			$vars = $cached['vars'];
 			$passed = $cached['passed'];
 		}
 		else {
 			$vars = $this->parse($url);
 			$passed = $vars['pass']; // also used in bottom of this method
 			unset($vars['pass']);
 
 			if ( !$this->_partsToParse ) {
 				// don't cache 404 Not Found
 				$this->_setCachedUrl($url, Array ('vars' => $vars, 'passed' => $passed));
 			}
 
 			if ( $this->Application->GetVarDirect('t', 'Post') ) {
 				// template from POST overrides template from URL.
 				$vars['t'] = $this->Application->GetVarDirect('t', 'Post');
 
 				if ( isset($vars['is_virtual']) && $vars['is_virtual'] ) {
 					$vars['m_cat_id'] = 0; // this is virtual template category (for Proj-CMS)
 				}
 			}
 
 			unset($vars['is_virtual']);
 		}
 
 		foreach ($vars as $name => $value) {
 			$this->HTTPQuery->Set($name, $value);
 		}
 
 		$this->_initAll(); // also will use parsed language to load phrases from it
 
 		$this->HTTPQuery->finalizeParsing($passed);
 	}
 
 	/**
 	 * Detects url ending of given url
 	 *
 	 * @param string $url
 	 * @return string
 	 * @access protected
 	 */
 	protected function _findUrlEnding($url)
 	{
 		if ( !$url ) {
 			return '';
 		}
 
 		foreach ($this->_urlEndings as $url_ending) {
 			if ( mb_substr($url, mb_strlen($url) - mb_strlen($url_ending)) == $url_ending ) {
 				return $url_ending;
 			}
 		}
 
 		return '';
 	}
 
 	/**
 	 * Removes url ending from url
 	 *
 	 * @param string $url
 	 * @return string
 	 * @access protected
 	 */
 	protected function _removeUrlEnding($url)
 	{
 		$url_ending = $this->_findUrlEnding($url);
 
 		if ( !$url_ending ) {
 			return $url;
 		}
 
 		return mb_substr($url, 0, mb_strlen($url) - mb_strlen($url_ending));
 	}
 
 	/**
 	 * Redirects user to page with default url ending, where needed
 	 *
 	 * @param string $url
 	 * @return void
 	 * @access protected
 	 */
 	protected function _redirectToDefaultUrlEnding($url)
 	{
 		$default_ending = $this->Application->ConfigValue('ModRewriteUrlEnding');
 
 		if ( $this->_findUrlEnding($url) == $default_ending || !$this->Application->ConfigValue('ForceModRewriteUrlEnding') ) {
 			return;
 		}
 
 		// user manually typed url with different url ending -> redirect to same url with default url ending
 		$target_url = $this->Application->BaseURL() . $this->_removeUrlEnding($url) . $default_ending;
 
 		trigger_error('Mod-rewrite url "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>" without "<strong>' . $default_ending . '</strong>" line ending used', E_USER_NOTICE);
 		$this->Application->Redirect('external:' . $target_url, Array ('response_code' => 301));
 	}
 
 	/**
 	 * Returns url parsing result from cache or false, when not yet parsed
 	 *
 	 * @param $url
 	 * @return Array|bool
 	 * @access protected
 	 */
 	protected function _getCachedUrl($url)
 	{
 		if ( !$url || (defined('DBG_CACHE_URLS') && !DBG_CACHE_URLS) ) {
 			return false;
 		}
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'CachedUrls
 				WHERE Hash = ' . kUtil::crc32($url) . ' AND DomainId = ' . (int)$this->Application->siteDomainField('DomainId');
 		$data = $this->Conn->GetRow($sql);
 
 		if ( $data ) {
 			$lifetime = (int)$data['LifeTime']; // in seconds
 			if ( ($lifetime > 0) && ($data['Cached'] + $lifetime < TIMENOW) ) {
 				// delete expired
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls
 						WHERE UrlId = ' . $data['UrlId'];
 				$this->Conn->Query($sql);
 
 				return false;
 			}
 
 			return unserialize($data['ParsedVars']);
 		}
 
 		return false;
 	}
 
 	/**
 	 * Caches url
 	 *
 	 * @param string $url
 	 * @param Array $data
 	 * @return void
 	 * @access protected
 	 */
 	protected function _setCachedUrl($url, $data)
 	{
 		if ( !$url || (defined('DBG_CACHE_URLS') && !DBG_CACHE_URLS) ) {
 			return;
 		}
 
 		$vars = $data['vars'];
 		$passed = $data['passed'];
 		sort($passed);
 
 		// get expiration
 		if ( $vars['m_cat_id'] > 0 ) {
 			$sql = 'SELECT PageExpiration
 					FROM ' . TABLE_PREFIX . 'Categories
 					WHERE CategoryId = ' . $vars['m_cat_id'];
 			$expiration = $this->Conn->GetOne($sql);
 		}
 
 		// get prefixes
 		$prefixes = Array ();
 		$m_index = array_search('m', $passed);
 
 		if ( $m_index !== false ) {
 			unset($passed[$m_index]);
 
 			if ( $vars['m_cat_id'] > 0 ) {
 				$prefixes[] = 'c:' . $vars['m_cat_id'];
 			}
 
 			$prefixes[] = 'lang:' . $vars['m_lang'];
 			$prefixes[] = 'theme:' . $vars['m_theme'];
 		}
 
 		foreach ($passed as $prefix) {
 			if ( array_key_exists($prefix . '_id', $vars) && is_numeric($vars[$prefix . '_id']) ) {
 				$prefixes[] = $prefix . ':' . $vars[$prefix . '_id'];
 			}
 			else {
 				$prefixes[] = $prefix;
 			}
 		}
 
 		$fields_hash = Array (
 			'Url' => $url,
 			'Hash' => kUtil::crc32($url),
 			'DomainId' => (int)$this->Application->siteDomainField('DomainId'),
 			'Prefixes' => $prefixes ? '|' . implode('|', $prefixes) . '|' : '',
 			'ParsedVars' => serialize($data),
 			'Cached' => adodb_mktime(),
 			'LifeTime' => isset($expiration) && is_numeric($expiration) ? $expiration : -1
 		);
 
 		$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'CachedUrls');
 	}
 
 	/**
 	 * Loads all registered rewrite listeners, so they could be quickly accessed later
 	 *
 	 * @access protected
 	 */
 	protected function _initRewriteListeners()
 	{
 		static $init_done = false;
 
 		if ($init_done || count($this->Application->RewriteListeners) == 0) {
 			// not initialized OR mod-rewrite url with missing config cache
 			return ;
 		}
 
 		foreach ($this->Application->RewriteListeners as $prefix => $listener_data) {
 			foreach ($listener_data['listener'] as $index => $rewrite_listener) {
 				list ($listener_prefix, $listener_method) = explode(':', $rewrite_listener);
 
 				// don't use temp variable, since it will swap objects in Factory in PHP5
 				$this->rewriteListeners[$prefix][$index] = Array ();
 				$this->rewriteListeners[$prefix][$index][0] = $this->Application->recallObject($listener_prefix);
 				$this->rewriteListeners[$prefix][$index][1] = $listener_method;
 			}
 		}
 
 		define('MOD_REWRITE_URL_ENDING', $this->Application->ConfigValue('ModRewriteUrlEnding'));
 
 		$init_done = true;
 	}
 
 	/**
 	 * Parses given string into a set of variables (url in this case)
 	 *
 	 * @param string $string
 	 * @param string $pass_name
 	 * @return Array
 	 * @access public
 	 */
 	public function parse($string, $pass_name = 'pass')
 	{
 		// external url (could be back this website as well)
 		if ( preg_match('/external:(.*)/', $string, $regs) ) {
 			$string = $regs[1];
 		}
 
 		$vars = Array ();
 		$url_components = parse_url($string);
 
 		if ( isset($url_components['query']) ) {
 			parse_str(html_entity_decode($url_components['query']), $url_params);
 
 			if ( isset($url_params[ENV_VAR_NAME]) ) {
 				$url_params = array_merge($url_params, $this->manager->plain->parse($url_params[ENV_VAR_NAME], $pass_name));
 				unset($url_params[ENV_VAR_NAME]);
 			}
 
 			$vars = array_merge($vars, $url_params);
 		}
 
 		$this->_fixPass($vars, $pass_name);
 
 		if ( isset($url_components['path']) ) {
 			if ( BASE_PATH ) {
 				$string = preg_replace('/^' . preg_quote(BASE_PATH, '/') . '/', '', $url_components['path'], 1);
 			}
 			else {
 				$string = $url_components['path'];
 			}
 
 			$string = $this->_removeUrlEnding(trim($string, '/'));
 		}
 		else {
 			$string = '';
 		}
 
 		$url_parts = $string ? explode('/', mb_strtolower($string)) : Array ();
 
 		$this->_partsToParse = $url_parts;
 
 		if ( ($this->HTTPQuery->Get('rewrite') == 'on') || !$url_parts ) {
 			$this->_setDefaultValues($vars);
 		}
 
 		if ( !$url_parts ) {
 			$this->_initAll();
 			$vars['t'] = $this->Application->UrlManager->getTemplateName();
 
 			return $vars;
 		}
 
 		$this->_parseLanguage($url_parts, $vars);
 		$this->_parseTheme($url_parts, $vars);
 
 		// http://site-url/<language>/<theme>/<category>[_<category_page>]/<template>/<module_page>
 		// http://site-url/<language>/<theme>/<category>[_<category_page>]/<module_page> (category-based section template)
 		// http://site-url/<language>/<theme>/<category>[_<category_page>]/<template>/<module_item>
 		// http://site-url/<language>/<theme>/<category>[_<category_page>]/<module_item> (category-based detail template)
 		// http://site-url/<language>/<theme>/<rl_injections>/<category>[_<category_page>]/<rl_part> (customized url)
 
 		if ( $this->_processRewriteListeners($url_parts, $vars) ) {
 			return $vars;
 		}
 
 		$this->_parsePhysicalTemplate($url_parts, $vars);
 
 		if ( ($this->modulePrefix === false) && $vars['m_cat_id'] && !$this->_partsToParse ) {
 			// no category item found, but category found and all url matched -> module index page
 
 			return $vars;
 		}
 
 		if ( $this->_partsToParse ) {
 			$vars = array_merge($vars, $this->manager->prepare404($vars['m_theme']));
 		}
 
 		return $vars;
 	}
 
 	/**
 	 * Ensures, that "m" is always in "pass" variable
 	 *
 	 * @param Array $vars
 	 * @param string $pass_name
 	 * @return void
 	 * @access protected
 	 */
 	protected function _fixPass(&$vars, $pass_name)
 	{
 		if ( isset($vars[$pass_name]) ) {
 			$vars[$pass_name] = array_unique(explode(',', 'm,' . $vars[$pass_name]));
 		}
 		else {
 			$vars[$pass_name] = Array ('m');
 		}
 	}
 
 	/**
 	 * Initializes theme & language based on parse results
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function _initAll()
 	{
 		$this->Application->VerifyThemeId();
 		$this->Application->VerifyLanguageId();
 
 		// no need, since we don't have any cached phrase IDs + nobody will use PhrasesCache::LanguageId soon
 		// $this->Application->Phrases->Init('phrases');
 	}
 
 	/**
 	 * Sets default parsed values before actual url parsing (only, for empty url)
 	 *
 	 * @param Array $vars
 	 * @access protected
 	 */
 	protected function _setDefaultValues(&$vars)
 	{
 		$defaults = Array (
 			'm_cat_id' => 0, // no category
 			'm_cat_page' => 1, // first category page
 			'm_opener' => 's', // stay on same page
 			't' => 'index' // main site page
 		);
 
 		if ($this->primaryLanguageId) {
 			// domain-based primary language
 			$defaults['m_lang'] = $this->primaryLanguageId;
 		}
 
 		if ($this->primaryThemeId) {
 			// domain-based primary theme
 			$defaults['m_theme'] = $this->primaryThemeId;
 		}
 
 		foreach ($defaults as $default_key => $default_value) {
 			if ($this->HTTPQuery->Get($default_key) === false) {
 				$vars[$default_key] = $default_value;
 			}
 		}
 	}
 
 	/**
 	 * Processes url using rewrite listeners
 	 *
 	 * Pattern: Chain of Command
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _processRewriteListeners(&$url_parts, &$vars)
 	{
 		$this->_initRewriteListeners();
 		$page_number = $this->_parsePage($url_parts, $vars);
 
 		foreach ($this->rewriteListeners as $prefix => $listeners) {
 			// set default page
 			// $vars[$prefix . '_Page'] = 1; // will override page in session in case, when none is given in url
 
 			if ($page_number) {
 				// page given in url - use it
 				$vars[$prefix . '_id'] = 0;
 				$vars[$prefix . '_Page'] = $page_number;
 			}
 
 			// $listeners[1] - listener, used for parsing
 			$listener_result = $listeners[1][0]->$listeners[1][1](REWRITE_MODE_PARSE, $prefix, $vars, $url_parts);
 			if ($listener_result === false) {
 				// will not proceed to other methods
 				return true;
 			}
 		}
 
 		// will proceed to other methods
 		return false;
 	}
 
 	/**
 	 * Set's page (when found) to all modules
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return string
 	 * @access protected
 	 *
 	 * @todo Should find a way, how to determine what rewrite listener page is it
 	 */
 	protected function _parsePage(&$url_parts, &$vars)
 	{
 		if (!$url_parts) {
 			return false;
 		}
 
 		$page_number = end($url_parts);
 		if (!is_numeric($page_number)) {
 			return false;
 		}
 
 		array_pop($url_parts);
 		$this->partParsed($page_number, 'rtl');
 
 		return $page_number;
 	}
 
 	/**
 	 * Gets language part from url
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _parseLanguage(&$url_parts, &$vars)
 	{
 		if (!$url_parts) {
 			return false;
 		}
 
 		$url_part = reset($url_parts);
 
 		$sql = 'SELECT LanguageId, IF(LOWER(PackName) = ' . $this->Conn->qstr($url_part) . ', 2, PrimaryLang) AS SortKey
 				FROM ' . TABLE_PREFIX . 'Languages
 				WHERE Enabled = 1
 				ORDER BY SortKey DESC';
 		$language_info = $this->Conn->GetRow($sql);
 
 		if ($language_info && $language_info['LanguageId'] && $language_info['SortKey']) {
 			// primary language will be selected in case, when $url_part doesn't match to other's language pack name
 			// don't use next enabled language, when primary language is disabled
 			$vars['m_lang'] = $language_info['LanguageId'];
 
 			if ($language_info['SortKey'] == 2) {
 				// language was found by pack name
 				array_shift($url_parts);
 				$this->partParsed($url_part);
 			}
 			elseif ($this->primaryLanguageId) {
 				// use domain-based primary language instead of site-wide primary language
 				$vars['m_lang'] = $this->primaryLanguageId;
 			}
 
 			return true;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Gets theme part from url
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return bool
 	 */
 	protected function _parseTheme(&$url_parts, &$vars)
 	{
 		if (!$url_parts) {
 			return false;
 		}
 
 		$url_part = reset($url_parts);
 
 		$sql = 'SELECT ThemeId, IF(LOWER(Name) = ' . $this->Conn->qstr($url_part) . ', 2, PrimaryTheme) AS SortKey, TemplateAliases
 				FROM ' . TABLE_PREFIX . 'Themes
 				WHERE Enabled = 1
 				ORDER BY SortKey DESC';
 		$theme_info = $this->Conn->GetRow($sql);
 
 		if ($theme_info && $theme_info['ThemeId'] && $theme_info['SortKey']) {
 			// primary theme will be selected in case, when $url_part doesn't match to other's theme name
 			// don't use next enabled theme, when primary theme is disabled
 			$vars['m_theme'] = $theme_info['ThemeId'];
 
 			if ($theme_info['TemplateAliases']) {
 				$this->_templateAliases = unserialize($theme_info['TemplateAliases']);
 			}
 			else {
 				$this->_templateAliases = Array ();
 			}
 
 			if ($theme_info['SortKey'] == 2) {
 				// theme was found by name
 				array_shift($url_parts);
 				$this->partParsed($url_part);
 			}
 			elseif ($this->primaryThemeId) {
 				// use domain-based primary theme instead of site-wide primary theme
 				$vars['m_theme'] = $this->primaryThemeId;
 			}
 
 			return true;
 		}
 
 		$vars['m_theme'] = 0; // required, because used later for category/template detection
 
 		return false;
 	}
 
 	/**
 	 * Parses real template name from url
 	 *
 	 * @param Array $url_parts
 	 * @param Array $vars
 	 * @return bool
 	 */
 	protected function _parsePhysicalTemplate($url_parts, &$vars)
 	{
 		if ( !$url_parts ) {
 			return false;
 		}
 
 		$themes_helper = $this->Application->recallObject('ThemesHelper');
 		/* @var $themes_helper kThemesHelper */
 
 		do {
 			$index_added = false;
 			$template_path = implode('/', $url_parts);
 			$template_found = $themes_helper->getTemplateId($template_path, $vars['m_theme']);
 
 			if ( !$template_found ) {
 				$index_added = true;
 				$template_found = $themes_helper->getTemplateId($template_path . '/index', $vars['m_theme']);
 			}
 
 			if ( !$template_found ) {
 				array_shift($url_parts);
 			}
 		} while ( !$template_found && $url_parts );
 
 		if ( $template_found ) {
 			$template_parts = explode('/', $template_path);
 			$vars['t'] = $template_path . ($index_added ? '/index' : '');
 
 			while ( $template_parts ) {
 				$this->partParsed(array_pop($template_parts), 'rtl');
 			}
 
 			// 1. will damage actual category during category item review add process
 			// 2. will use "use_section" parameter of "m_Link" tag to gain same effect
 //			$vars['m_cat_id'] = $themes_helper->getPageByTemplate($template_path, $vars['m_theme']);
 
 			return true;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Returns environment variable values for given prefix (uses directly given params, when available)
 	 *
 	 * @param string $prefix_special
 	 * @param Array $params
 	 * @param bool $keep_events
 	 * @return Array
 	 * @access public
 	 */
 	public function getProcessedParams($prefix_special, &$params, $keep_events)
 	{
 		list ($prefix) = explode('.', $prefix_special);
 
-		$query_vars = $this->Application->getUnitOption($prefix, 'QueryString', Array ());
-		/* @var $query_vars Array */
+		$query_vars = $this->Application->getUnitConfig($prefix)->getQueryString(Array ());
 
 		if ( !$query_vars ) {
 			// given prefix doesn't use "env" variable to pass it's data
 			return false;
 		}
 
 		$event_key = array_search('event', $query_vars);
 		if ( $event_key ) {
 			// pass through event of this prefix
 			unset($query_vars[$event_key]);
 		}
 
 		if ( array_key_exists($prefix_special . '_event', $params) && !$params[$prefix_special . '_event'] ) {
 			// if empty event, then remove it from url
 			unset($params[$prefix_special . '_event']);
 		}
 
 		// if pass events is off and event is not implicity passed
 		if ( !$keep_events && !array_key_exists($prefix_special . '_event', $params) ) {
 			unset($params[$prefix_special . '_event']); // remove event from url if requested
 			//otherwise it will use value from get_var
 		}
 
 		$processed_params = Array ();
 		foreach ($query_vars as $var_name) {
 			// if value passed in params use it, otherwise use current from application
 			$var_name = $prefix_special . '_' . $var_name;
 			$processed_params[$var_name] = array_key_exists($var_name, $params) ? $params[$var_name] : $this->Application->GetVar($var_name);
 
 			if ( array_key_exists($var_name, $params) ) {
 				unset($params[$var_name]);
 			}
 		}
 
 		return $processed_params;
 	}
 
 	/**
 	 * Returns module item details template specified in given category custom field for given module prefix
 	 *
 	 * @param int|Array $category
 	 * @param string $module_prefix
 	 * @param int $theme_id
 	 * @return string
 	 * @access public
 	 * @todo Move to kPlainUrlProcessor
 	 */
 	public function GetItemTemplate($category, $module_prefix, $theme_id = null)
 	{
 		if ( !isset($theme_id) ) {
 			$theme_id = $this->Application->GetVar('m_theme');
 		}
 
 		$category_id = is_array($category) ? $category['CategoryId'] : $category;
 		$cache_key = __CLASS__ . '::' . __FUNCTION__ . '[%CIDSerial:' . $category_id . '%][%ThemeIDSerial:' . $theme_id . '%]' . $module_prefix;
 
 		$cached_value = $this->Application->getCache($cache_key);
 		if ( $cached_value !== false ) {
 			return $cached_value;
 		}
 
 		if ( !is_array($category) ) {
 			if ( $category == 0 ) {
 				$category = $this->Application->findModule('Var', $module_prefix, 'RootCat');
 			}
 			$sql = 'SELECT c.ParentPath, c.CategoryId
 					FROM ' . TABLE_PREFIX . 'Categories AS c
 					WHERE c.CategoryId = ' . $category;
 			$category = $this->Conn->GetRow($sql);
 		}
 		$parent_path = implode(',', explode('|', substr($category['ParentPath'], 1, -1)));
 
 		// item template is stored in module' system custom field - need to get that field Id
 		$primary_lang = $this->Application->GetDefaultLanguageId();
 		$item_template_field_id = $this->getItemTemplateCustomField($module_prefix);
 
 		// looking for item template through cats hierarchy sorted by parent path
 		$query = '	SELECT ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . ',
 								FIND_IN_SET(c.CategoryId, ' . $this->Conn->qstr($parent_path) . ') AS Ord1,
 								c.CategoryId, c.Name, ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . '
 					FROM ' . TABLE_PREFIX . 'Categories AS c
 					LEFT JOIN ' . TABLE_PREFIX . 'CategoryCustomData AS ccd
 					ON ccd.ResourceId = c.ResourceId
 					WHERE c.CategoryId IN (' . $parent_path . ') AND ccd.l' . $primary_lang . '_cust_' . $item_template_field_id . ' != \'\'
 					ORDER BY FIND_IN_SET(c.CategoryId, ' . $this->Conn->qstr($parent_path) . ') DESC';
 		$item_template = $this->Conn->GetOne($query);
 
 		if ( !isset($this->_templateAliases) ) {
 			// when empty url OR mod-rewrite disabled
 
 			$themes_helper = $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$sql = 'SELECT TemplateAliases
 					FROM ' . TABLE_PREFIX . 'Themes
 					WHERE ThemeId = ' . (int)$themes_helper->getCurrentThemeId();
 			$template_aliases = $this->Conn->GetOne($sql);
 
 			$this->_templateAliases = $template_aliases ? unserialize($template_aliases) : Array ();
 		}
 
 		if ( substr($item_template, 0, 1) == '#' ) {
 			// it's template alias + "#" isn't allowed in filenames
 			$item_template = (string)getArrayValue($this->_templateAliases, $item_template);
 		}
 
 		$this->Application->setCache($cache_key, $item_template);
 
 		return $item_template;
 	}
 
 	/**
 	 * Returns category custom field id, where given module prefix item template name is stored
 	 *
 	 * @param string $module_prefix
 	 * @return int
 	 * @access public
 	 * @todo Move to kPlainUrlProcessor; decrease visibility, since used only during upgrade
 	 */
 	public function getItemTemplateCustomField($module_prefix)
 	{
 		$cache_key = __CLASS__ . '::' . __FUNCTION__ . '[%CfSerial%]:' . $module_prefix;
 		$cached_value = $this->Application->getCache($cache_key);
 
 		if ($cached_value !== false) {
 			return $cached_value;
 		}
 
 		$sql = 'SELECT CustomFieldId
 				FROM ' . TABLE_PREFIX . 'CustomFields
 				WHERE FieldName = ' . $this->Conn->qstr($module_prefix . '_ItemTemplate');
 		$item_template_field_id = $this->Conn->GetOne($sql);
 
 		$this->Application->setCache($cache_key, $item_template_field_id);
 
 		return $item_template_field_id;
 	}
 
 	/**
 	 * Marks url part as parsed
 	 *
 	 * @param string $url_part
 	 * @param string $parse_direction
 	 * @access public
 	 */
 	public function partParsed($url_part, $parse_direction = 'ltr')
 	{
 		if ( !$this->_partsToParse ) {
 			return ;
 		}
 
 		if ( $parse_direction == 'ltr' ) {
 			$expected_url_part = reset($this->_partsToParse);
 
 			if ( $url_part == $expected_url_part ) {
 				array_shift($this->_partsToParse);
 			}
 		}
 		else {
 			$expected_url_part = end($this->_partsToParse);
 
 			if ( $url_part == $expected_url_part ) {
 				array_pop($this->_partsToParse);
 			}
 		}
 
 		if ( $url_part != $expected_url_part ) {
 			trigger_error('partParsed: expected URL part "<strong>' . $expected_url_part . '</strong>", received URL part "<strong>' . $url_part . '</strong>"', E_USER_NOTICE);
 		}
 	}
 
 	/**
 	 * Determines if there is more to parse in url
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function moreToParse()
 	{
 		return count($this->_partsToParse) > 0;
 	}
 
 	/**
 	 * Builds url
 	 *
 	 * @param string $t
 	 * @param Array $params
 	 * @param string $pass
 	 * @param bool $pass_events
 	 * @param bool $env_var
 	 * @return string
 	 * @access public
 	 */
 	public function build($t, $params, $pass = 'all', $pass_events = false, $env_var = false)
 	{
 		if ( $this->Application->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin']) ) {
 			$params['admin'] = 1;
 
 			if ( !array_key_exists('editing_mode', $params) ) {
 				$params['editing_mode'] = EDITING_MODE;
 			}
 		}
 
 		$ret = '';
 		$env = '';
 
 		$encode = false;
 
 		if ( isset($params['__URLENCODE__']) ) {
 			$encode = $params['__URLENCODE__'];
 			unset($params['__URLENCODE__']);
 		}
 
 		if ( isset($params['__SSL__']) ) {
 			unset($params['__SSL__']);
 		}
 
 		$catalog_item_found = false;
 		$pass_info = $this->getPassInfo($pass);
 
 		if ( $pass_info ) {
 			if ( $pass_info[0] == 'm' ) {
 				array_shift($pass_info);
 			}
 
 			$inject_parts = Array (); // url parts for beginning of url
 			$params['t'] = $t; // make template available for rewrite listeners
 			$params['pass_template'] = true; // by default we keep given template in resulting url
 
 			if ( !array_key_exists('pass_category', $params) ) {
 				$params['pass_category'] = false; // by default we don't keep categories in url
 			}
 
 			foreach ($pass_info as $pass_index => $pass_element) {
 				list ($prefix) = explode('.', $pass_element);
-				$catalog_item = $this->Application->findModule('Var', $prefix) && $this->Application->getUnitOption($prefix, 'CatalogItem');
+				$catalog_item = $this->Application->findModule('Var', $prefix) && $this->Application->getUnitConfig($prefix)->getCatalogItem();
 
 				if ( array_key_exists($prefix, $this->rewriteListeners) ) {
 					// if next prefix is same as current, but with special => exclude current prefix from url
 					$next_prefix = array_key_exists($pass_index + 1, $pass_info) ? $pass_info[$pass_index + 1] : false;
 					if ( $next_prefix ) {
 						$next_prefix = substr($next_prefix, 0, strlen($prefix) + 1);
 						if ( $prefix . '.' == $next_prefix ) {
 							continue;
 						}
 					}
 
 					// rewritten url part
 					$url_part = $this->BuildModuleEnv($pass_element, $params, $pass_events);
 
 					if ( is_string($url_part) && $url_part ) {
 						$ret .= $url_part . '/';
 
 						if ( $catalog_item ) {
 							// pass category later only for catalog items
 							$catalog_item_found = true;
 						}
 					}
 					elseif ( is_array($url_part) ) {
 						// rewrite listener want to insert something at the beginning of url too
 						if ( $url_part[0] ) {
 							$inject_parts[] = $url_part[0];
 						}
 
 						if ( $url_part[1] ) {
 							$ret .= $url_part[1] . '/';
 						}
 
 						if ( $catalog_item ) {
 							// pass category later only for catalog items
 							$catalog_item_found = true;
 						}
 					}
 					elseif ( $url_part === false ) {
 						// rewrite listener decided not to rewrite given $pass_element
 						$env .= ':' . $this->manager->plain->BuildModuleEnv($pass_element, $params, $pass_events);
 					}
 				}
 				else {
 					$env .= ':' . $this->manager->plain->BuildModuleEnv($pass_element, $params, $pass_events);
 				}
 			}
 
 			if ( $catalog_item_found || preg_match('/c\.[-\d]*/', implode(',', $pass_info)) ) {
 				// "c" prefix is present -> keep category
 				$params['pass_category'] = true;
 			}
 
 			$params['inject_parts'] = $inject_parts;
 
 			$ret = $this->BuildModuleEnv('m', $params, $pass_events) . '/' . $ret;
 			$cat_processed = array_key_exists('category_processed', $params) && $params['category_processed'];
 
 			// remove temporary parameters used by listeners
 			unset($params['t'], $params['inject_parts'], $params['pass_template'], $params['pass_category'], $params['category_processed']);
 
 			$ret = trim($ret, '/');
 
 			if ( isset($params['url_ending']) ) {
 				if ( $ret ) {
 					$ret .= $params['url_ending'];
 				}
 
 				unset($params['url_ending']);
 			}
 			elseif ( $ret ) {
 				$ret .= MOD_REWRITE_URL_ENDING;
 			}
 
 			if ( $env ) {
 				$params[ENV_VAR_NAME] = ltrim($env, ':');
 			}
 		}
 
 		unset($params['pass'], $params['opener'], $params['m_event']);
 
 		if ( array_key_exists('escape', $params) && $params['escape'] ) {
 			$ret = addslashes($ret);
 			unset($params['escape']);
 		}
 
 		$ret = str_replace('%2F', '/', urlencode($ret));
 
 		if ( $params ) {
 			$params_str = '';
 			$join_string = $encode ? '&' : '&amp;';
 
 			foreach ($params as $param => $value) {
 				$params_str .= $join_string . $param . '=' . $value;
 			}
 
 			$ret .= '?' . substr($params_str, strlen($join_string));
 		}
 
 		if ( $encode ) {
 			$ret = str_replace('\\', '%5C', $ret);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Builds env part that corresponds prefix passed
 	 *
 	 * @param string $prefix_special item's prefix & [special]
 	 * @param Array $params url params
 	 * @param bool $pass_events
 	 * @return string
 	 * @access protected
 	 */
 	protected function BuildModuleEnv($prefix_special, &$params, $pass_events = false)
 	{
 		list ($prefix) = explode('.', $prefix_special);
 
 		$url_parts = Array ();
 		$listener = $this->rewriteListeners[$prefix][0];
 
 		$ret = $listener[0]->$listener[1](REWRITE_MODE_BUILD, $prefix_special, $params, $url_parts, $pass_events);
 
 		return $ret;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/managers/plain_url_processor.php
===================================================================
--- branches/5.3.x/core/kernel/managers/plain_url_processor.php	(revision 15697)
+++ branches/5.3.x/core/kernel/managers/plain_url_processor.php	(revision 15698)
@@ -1,292 +1,293 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kPlainUrlProcessor extends kUrlProcessor {
 
 	/**
 	 * Process QueryString only, create events, ids, based on config
 	 * set template name and sid in desired application variables.
 	 *
 	 * @param string $env_var environment string value
 	 * @param string $pass_name
 	 * @return Array
 	 */
 	public function parse($env_var, $pass_name = 'passed')
 	{
 		// env=SID-TEMPLATE:m-1-1-1-1:l0-0-0:n-0-0-0:bb-0-0-1-1-1-0
 		if ( !$env_var ) {
 			return Array ('t' => $this->manager->getTemplateName());
 		}
 
 		$vars = Array ();
 		$more_vars = strpos($env_var, '&');
 
 		if ( $more_vars !== false ) {
 			parse_str(substr($env_var, $more_vars + 1), $vars);
 			$env_var = substr($env_var, 0, $more_vars);
 		}
 
 		// replace escaped ":" symbol not to explode by it
 		$env_var = str_replace('\:', '_&+$$+&_', $env_var); // replace escaped "=" with spec-chars :)
 		$parts = explode(':', $env_var);
 
 		if ( !$this->Application->RewriteURLs() || ($this->Application->RewriteURLs() && $this->Application->GetVar('rewrite') != 'on') ) {
 			$vars = array_merge($vars, $this->extractSIDAndTemplate($parts));
 		}
 
 		if ( $parts ) {
 			$passed = Array ();
 
 			foreach ($parts as $mixed_part) {
 				list ($passed[], $processed_vars) = $this->_parseEnvPart($mixed_part);
 				$vars = array_merge($vars, $processed_vars);
 			}
 
 			$vars[$pass_name] = implode(',', array_unique($passed));
 		}
 
 		return $vars;
 	}
 
 	/**
 	 * Retrieves SessionID and current template from given ENV parts
 	 *
 	 * @param Array $parts
 	 * @return array
 	 * @access protected
 	 */
 	protected function extractSIDAndTemplate(&$parts)
 	{
 		$template = '';
 		$vars = Array ();
 
 		if ( preg_match('/^([\d]+|)-(.*)$/', $parts[0], $regs) ) {
 			// first "env" component matches "sid-template" format
 			// (will be false, when mod-rewrite url to home page is built)
 			$sid = $regs[1];
 			$template = $regs[2];
 			array_shift($parts);
 
 			if ( $sid ) {
 				// Save Session ID
 				$this->Application->SetVar('sid', $sid);
 				$vars['sid'] = $sid;
 			}
 		}
 
 		// Save Template Name
 		$vars['t'] = $this->manager->getTemplateName($template);
 
 		return $vars;
 	}
 
 	/**
 	 * Converts environment part into variable array (based on query map for given prefix)
 	 *
 	 * @param string $mixed_part
 	 * @return Array
 	 * @access protected
 	 */
 	protected function _parseEnvPart($mixed_part)
 	{
 		// In-portal old style env conversion - adds '-' between prefix and first var
 		$mixed_part = str_replace('_&+$$+&_', ':', $mixed_part);
 		$mixed_part = preg_replace("/^([a-zA-Z]+)([0-9]+)-(.*)/", "$1-$2-$3", $mixed_part);
 
 		// replace escaped "-" symbol not to explode by it
 		$escaped_part = str_replace('\-', '_&+$$+&_', $mixed_part);
 		$escaped_part = explode('-', $escaped_part);
 
 		$mixed_part = Array();
 		foreach ($escaped_part as $escaped_val) {
 			$mixed_part[] = str_replace('_&+$$+&_', '-', $escaped_val);
 		}
 
 		$vars = Array ();
 		$prefix_special = array_shift($mixed_part); // l.pick, l
 
 		$http_query = $this->Application->recallObject('HTTPQuery');
 		/* @var $http_query kHTTPQuery */
 
 		$query_map = $http_query->discoverUnit($prefix_special); // from $_GET['env']
 
 		// if config is not defined for prefix in QueryString, then don't process it
 		if ($query_map) {
 			foreach ($query_map as $index => $var_name) {
 				// l_id, l_page, l_bla-bla-bla
 				$val = $mixed_part[$index - 1];
 				if ($val == '') $val = false;
 				$vars[$prefix_special.'_'.$var_name] = $val;
 			}
 		}
 
 		return Array ($prefix_special, $vars);
 	}
 
 	/**
 	 * Builds url
 	 *
 	 * @param string $t
 	 * @param Array $params
 	 * @param string $pass
 	 * @param bool $pass_events
 	 * @param bool $env_var
 	 * @return string
 	 * @access public
 	 */
 	public function build($t, $params, $pass='all', $pass_events = false, $env_var = true)
 	{
 		if ( $this->Application->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin']) ) {
 			$params['admin'] = 1;
 
 			if ( !array_key_exists('editing_mode', $params) ) {
 				$params['editing_mode'] = EDITING_MODE;
 			}
 		}
 
 		$ssl = isset($params['__SSL__']) ? $params['__SSL__'] : 0;
 		$sid = isset($params['sid']) && !$this->Application->RewriteURLs($ssl) ? $params['sid'] : '';
 
 		$ret = '';
 		if ( $env_var ) {
 			$ret = ENV_VAR_NAME . '=';
 		}
 
 		$ret .= $sid . '-'; // SID-TEMPLATE
 
 		$encode = false;
 		if ( isset($params['__URLENCODE__']) ) {
 			$encode = $params['__URLENCODE__'];
 			unset($params['__URLENCODE__']);
 		}
 
 		if ( isset($params['__SSL__']) ) {
 			unset($params['__SSL__']);
 		}
 
 		$env_string = '';
 		$category_id = isset($params['m_cat_id']) ? $params['m_cat_id'] : $this->Application->GetVar('m_cat_id');
 
 		$item_id = false;
 		$pass_info = $this->getPassInfo($pass);
 
 		if ( $pass_info ) {
 			if ( $pass_info[0] == 'm' ) {
 				array_shift($pass_info);
 			}
 
 			foreach ($pass_info as $pass_element) {
 				list($prefix) = explode('.', $pass_element);
 				$require_rewrite = $this->Application->findModule('Var', $prefix);
 
 				if ( $require_rewrite ) {
 					$item_id = isset($params[$pass_element . '_id']) ? $params[$pass_element . '_id'] : $this->Application->GetVar($pass_element . '_id');
 				}
 
 				$env_string .= ':' . $this->BuildModuleEnv($pass_element, $params, $pass_events);
 			}
 		}
 
 		if ( strtolower($t) == '__default__' ) {
 			if ( is_numeric($item_id) ) {
 				$this->manager->initRewrite();
 				$t = $this->manager->rewrite->GetItemTemplate($category_id, $pass_element); // $pass_element should be the last processed element
 				// $t = $this->Application->getCategoryCache($category_id, 'item_templates');
 			}
 			elseif ( $category_id ) {
 				$t = strtolower(preg_replace('/^Content\//i', '', $this->Application->getCategoryCache($category_id, 'filenames')));
 			}
 			else {
 				$t = 'index';
 			}
 		}
 
 		$ret .= $t . ':' . $this->BuildModuleEnv('m', $params, $pass_events) . $env_string;
 
 		unset($params['pass'], $params['opener'], $params['m_event']);
 
 		if ( array_key_exists('escape', $params) && $params['escape'] ) {
 			$ret = addslashes($ret);
 			unset($params['escape']);
 		}
 
 		if ( $params ) {
 			$params_str = '';
 			$join_string = $encode ? '&' : '&amp;';
 
 			foreach ($params as $param => $value) {
 				$params_str .= $join_string . $param . '=' . $value;
 			}
 
 			$ret .= $params_str;
 		}
 
 		if ( $encode ) {
 			$ret = str_replace('\\', '%5C', $ret);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Builds env part that corresponds prefix passed
 	 *
 	 * @param string $prefix_special item's prefix & [special]
 	 * @param Array $params url params
 	 * @param bool $pass_events
 	 * @return string
 	 * @access public
 	 */
 	public function BuildModuleEnv($prefix_special, &$params, $pass_events = false)
 	{
 		list($prefix) = explode('.', $prefix_special);
-		$query_vars = $this->Application->getUnitOption($prefix, 'QueryString', Array ());
-		/* @var $query_vars Array */
+		$config = $this->Application->getUnitConfig($prefix);
+
+		$query_vars = $config->getQueryString(Array ());
 
 		//if pass events is off and event is not implicitly passed
 		if ( !$pass_events && !isset($params[$prefix_special . '_event']) ) {
 			$params[$prefix_special . '_event'] = ''; // remove event from url if requested
 			//otherwise it will use value from get_var
 		}
 
 		if ( !$query_vars ) {
 			return '';
 		}
 
 		$tmp_string = Array (0 => $prefix_special);
 		foreach ($query_vars as $index => $var_name) {
 			//if value passed in params use it, otherwise use current from application
 			$var_name = $prefix_special . '_' . $var_name;
 			$tmp_string[$index] = isset($params[$var_name]) ? $params[$var_name] : $this->Application->GetVar($var_name);
 
 			if ( isset($params[$var_name]) ) {
 				unset($params[$var_name]);
 			}
 		}
 
 		$escaped = array ();
 		foreach ($tmp_string as $tmp_val) {
 			$escaped[] = str_replace(Array ('-', ':'), Array ('\-', '\:'), $tmp_val);
 		}
 
 		$ret = implode('-', $escaped);
-		if ( $this->Application->getUnitOption($prefix, 'PortalStyleEnv') == true ) {
+		if ( $config->getPortalStyleEnv() == true ) {
 			$ret = preg_replace('/^([a-zA-Z]+)-([0-9]+)-(.*)/', '\\1\\2-\\3', $ret);
 		}
 
 		return $ret;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/event_manager.php
===================================================================
--- branches/5.3.x/core/kernel/event_manager.php	(revision 15697)
+++ branches/5.3.x/core/kernel/event_manager.php	(revision 15698)
@@ -1,512 +1,512 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kEventManager extends kBase implements kiCacheable {
 
 	/**
 	 * Instance of hook manager
 	 *
 	 * @var kHookManager
 	 * @access protected
 	 */
 	protected $Hooks = null;
 
 	/**
 	 * Instance of scheduled task manager
 	 *
 	 * @var kScheduledTaskManager
 	 * @access protected
 	 */
 	protected $ScheduledTasks = null;
 
 	/**
 	 * Instance of request manager
 	 *
 	 * @var kRequestManager
 	 * @access protected
 	 */
 	protected $Request = null;
 
 	/**
 	 * Build events registred for pseudo classes.
 	 * key - pseudo class, value - event name
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $buildEvents = Array ();
 
 	/**
 	 * Event recursion tracking stack
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $recursionStack = Array ();
 
 	/**
 	 * Creates new instance of kEventManager class
 	 *
 	 */
 	public function __construct()
 	{
 		parent::__construct();
 
 		$this->Hooks = $this->Application->makeClass('kHookManager');
 		$this->ScheduledTasks = $this->Application->makeClass('kScheduledTaskManager');
 		$this->Request = $this->Application->makeClass('kRequestManager');
 	}
 
 	/**
 	 * Sets data from cache to object
 	 *
 	 * @param Array $data
 	 * @access public
 	 */
 	public function setFromCache(&$data)
 	{
 		$this->Hooks->setFromCache($data);
 		$this->ScheduledTasks->setFromCache($data);
 
 		$this->buildEvents = $data['EventManager.buildEvents'];
 	}
 
 	/**
 	 * Gets object data for caching
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function getToCache()
 	{
 		return array_merge(
 			$this->Hooks->getToCache(),
 			$this->ScheduledTasks->getToCache(),
 			Array (
 				'EventManager.buildEvents' => $this->buildEvents,
 			)
 		);
 	}
 
 	/**
 	 * Returns information about registered scheduled tasks
 	 *
 	 * @param bool $from_cache
 	 * @return Array
 	 * @access public
 	 */
 	public function getScheduledTasks($from_cache = false)
 	{
 		return $this->ScheduledTasks->getAll($from_cache);
 	}
 
 	/**
 	 * Add new scheduled task
 	 *
 	 * @param string $short_name name to be used to store last maintenance run info
 	 * @param string $event_string
 	 * @param int $run_schedule run schedule like for Cron
 	 * @param int $status
 	 * @access public
 	 */
 	public function registerScheduledTask($short_name, $event_string, $run_schedule, $status = STATUS_ACTIVE)
 	{
 		$this->ScheduledTasks->add($short_name, $event_string, $run_schedule, $status);
 	}
 
 	/**
 	 * Run registered scheduled tasks with specified event type
 	 *
 	 * @param bool $from_cron
 	 * @access public
 	 */
 	public function runScheduledTasks($from_cron = false)
 	{
 		$this->ScheduledTasks->runAll($from_cron);
 	}
 
 	/**
 	 * Runs scheduled task based on given data
 	 *
 	 * @param Array $scheduled_task_data
 	 * @return bool
 	 * @access public
 	 */
 	public function runScheduledTask($scheduled_task_data)
 	{
 		return $this->ScheduledTasks->run($scheduled_task_data);
 	}
 
 	/**
 	 * Registers Hook from sub-prefix event to master prefix event
 	 *
 	 * Pattern: Observer
 	 *
 	 * @param string $hook_event
 	 * @param string $do_event
 	 * @param int $mode
 	 * @param bool $conditional
 	 * @access public
 	 */
 	public function registerHook($hook_event, $do_event, $mode = hAFTER, $conditional = false)
 	{
 		$this->Hooks->registerHook($hook_event, $do_event, $mode, $conditional);
 	}
 
 	/**
 	 * Registers build event for given pseudo class
 	 *
 	 * @param string $pseudo_class
 	 * @param string $event_name
 	 * @access public
 	 */
 	public function registerBuildEvent($pseudo_class, $event_name)
 	{
 		$this->buildEvents[$pseudo_class] = $event_name;
 	}
 
 	/**
 	 * Runs build event for given $pseudo_class instance, when defined
 	 *
 	 * @param string $prefix_special
 	 * @param string $pseudo_class
 	 * @param Array $event_params
 	 * @access public
 	 */
 	public function runBuildEvent($prefix_special, $pseudo_class, $event_params)
 	{
 		if ( !isset($this->buildEvents[$pseudo_class]) ) {
 			return ;
 		}
 
 		$event = new kEvent($prefix_special . ':' . $this->buildEvents[$pseudo_class], $event_params);
 		$this->HandleEvent($event);
 	}
 
 	/**
 	 * Check if event is called twice, that causes recursion
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access protected
 	 */
 	protected function isRecursion($event)
 	{
 		$event_key = $event->getPrefixSpecial() . ':' . $event->Name;
 
 		return in_array($event_key, $this->recursionStack);
 	}
 
 	/**
 	 * Adds event to recursion stack
 	 *
 	 * @param kEvent $event
 	 * @access protected
 	 */
 	protected function pushEvent($event)
 	{
 		$event_key = $event->getPrefixSpecial() . ':' . $event->Name;
 
 		array_push($this->recursionStack, $event_key);
 	}
 
 	/**
 	 * Removes event from recursion stack
 	 *
 	 * @access protected
 	 */
 	protected function popEvent()
 	{
 		array_pop($this->recursionStack);
 	}
 
 	/**
 	 * Allows to process any type of event
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access public
 	 */
 	public function HandleEvent($event)
 	{
 		if ( $this->isRecursion($event) || !$this->verifyEventPrefix($event) ) {
 			return;
 		}
 
 		$this->pushEvent($event);
 
 		if ( !$event->SkipBeforeHooks ) {
 			$this->Hooks->runHooks($event, hBEFORE);
 
 			if ( $event->status == kEvent::erFATAL ) {
 				return;
 			}
 		}
 
 		$event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler');
 		/* @var $event_handler kEventHandler */
 
 		$event_handler->processEvent($event);
 
 		if ( $event->status == kEvent::erFATAL ) {
 			return;
 		}
 
 		if ( !$event->SkipAfterHooks ) {
 			$this->Hooks->runHooks($event, hAFTER);
 		}
 
 		$this->popEvent();
 	}
 
 	/**
 	 * Notifies event subscribers, that event has occured
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 */
 	public function notifySubscribers(kEvent $event)
 	{
 		if ( $event->status != kEvent::erSUCCESS ) {
 			return;
 		}
 
 		$cache_key = 'email_to_event_mapping[%EmailTemplateSerial%]';
 		$event_mapping = $this->Application->getCache($cache_key);
 
 		if ( $event_mapping === false ) {
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT TemplateId, TemplateName, Type, BindToSystemEvent
-					FROM ' . $this->Application->getUnitOption('email-template', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('email-template')->getTableName() . '
 					WHERE BindToSystemEvent <> ""';
 			$event_mapping = $this->Conn->Query($sql, 'BindToSystemEvent');
 
 			$this->Application->setCache($cache_key, $event_mapping);
 		}
 
 		$email_template = Array ();
 
 		if ( isset($event_mapping[(string)$event]) ) {
 			$email_template = $event_mapping[(string)$event];
 		}
 		elseif ( isset($event_mapping[$event->Prefix . '.*:' . $event->Name]) ) {
 			$email_template = $event_mapping[$event->Prefix . '.*:' . $event->Name];
 		}
 
 		if ( !$email_template ) {
 			return;
 		}
 
 		$where_clause = Array ();
 		$where_clause['EmailTemplateId'] = 'EmailTemplateId = ' . $email_template['TemplateId'];
 
 		try {
 			$category_ids = Array ();
 
 			$category = $this->Application->recallObject('c');
 			/* @var $category kDBItem */
 
 			if ( $category->isLoaded() ) {
 				$category_ids = explode('|', substr($category->GetDBField('ParentPath'), 1, -1));
 			}
 		}
 		catch (Exception $e) {
 		}
 
 		$where_clause['CategoryId'] = $this->_getSubscriberFilter('CategoryId', $category_ids, true);
 
 		try {
 			$item_id = $parent_item_id = false;
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->isLoaded() ) {
 				$item_id = $object->GetID();
-				$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
+				$parent_prefix = $event->getUnitConfig()->getParentPrefix();
 
 				if ( $parent_prefix ) {
 					$parent_item_id = $object->getParentId($parent_prefix);
 				}
 			}
 		}
 		catch (Exception $e) {
 		}
 
 		$where_clause['ItemId'] = $this->_getSubscriberFilter('ItemId', $item_id);
 		$where_clause['ParentItemId'] = $this->_getSubscriberFilter('ParentItemId', $parent_item_id);
 
 		$event_params = Array (
 			'EmailTemplateId' => $email_template['TemplateId'],
 			'CategoryIds' => $category_ids,
 			'ItemId' => $item_id,
 			'ParentId' => $parent_item_id,
 			'where_clause' => $where_clause,
 		);
 
 		$sql_event = new kEvent($event->getPrefixSpecial() . ':OnGetEventSubscribersQuery', $event_params);
 		$sql_event->MasterEvent = $event;
 		$this->HandleEvent($sql_event);
 
 		$subscribers = $this->Conn->GetIterator($sql_event->getEventParam('sql'));
 
 		if ( !count($subscribers) ) {
 			// mapping exists, but nobody has subscribed
 			return;
 		}
 
 		$send_params = Array (
 			'Prefix' => $event->Prefix,
 			'Special' => $event->Special,
 			'PrefixSpecial' => $event->getPrefixSpecial(),
 		);
 
 		$send_method = $email_template['Type'] == EmailTemplate::TEMPLATE_TYPE_FRONTEND ? 'emailUser' : 'emailAdmin';
 
 		foreach ($subscribers as $subscriber_info) {
 			$send_params['to_name'] = $subscriber_info['SubscriberEmail'];
 			$send_params['to_email'] = $subscriber_info['SubscriberEmail'];
 			$this->Application->$send_method($email_template['TemplateName'], $subscriber_info['UserId'], $send_params);
 		}
 	}
 
 	/**
 	 * Returns filter for searching event subscribers
 	 *
 	 * @param string $field
 	 * @param mixed $value
 	 * @param bool $is_category
 	 * @return string
 	 * @access protected
 	 */
 	protected function _getSubscriberFilter($field, $value, $is_category = false)
 	{
 		if ( $value ) {
 			// send to this item subscribers AND to subscribers to all items
 			if ( $is_category ) {
 				$clause = 'IF(IncludeSublevels = 1, ' . $field . ' IN (' . implode(',', $value) . '), ' . $field . ' = ' . end($value) . ')';
 			}
 			else {
 				$clause = $field . ' = ' . $this->Conn->qstr($value);
 			}
 
 			return $clause . ' OR ' . $field . ' IS NULL';
 		}
 
 		// send to subscribers to all items
 		return $field . ' IS NULL';
 	}
 
 	/**
 	 * Checks, that given event is implemented
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access public
 	 */
 	public function eventImplemented(kEvent $event)
 	{
 		if ( !$this->verifyEventPrefix($event, true) ) {
 			return false;
 		}
 
 		$event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler');
 		/* @var $event_handler kEventHandler */
 
 		return $event_handler->getEventMethod($event) != '';
 	}
 
 	/**
 	 * Checks if event prefix is valid
 	 *
 	 * @param kEvent $event
 	 * @param bool $is_fatal
 	 * @return string
 	 * @access public
 	 */
 	public function verifyEventPrefix($event, $is_fatal = false)
 	{
 		if ( !$this->Application->prefixRegistred($event->Prefix) ) {
 			// when "l-cdata" is requested, then load "l", that will clone "l-cdata" unit config
 			$this->Application->UnitConfigReader->loadConfig($event->Prefix);
 
 			if ( !$this->Application->prefixRegistred($event->Prefix) ) {
 				$error_msg = 'Prefix "<strong>' . $event->Prefix . '</strong>" not registred (requested event "<strong>' . $event->Name . '</strong>")';
 
 				if ($is_fatal) {
 					throw new Exception($error_msg);
 				}
 				else {
 					trigger_error($error_msg, E_USER_WARNING);
 				}
 
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * Processes request
 	 *
 	 * @access public
 	 */
 	public function ProcessRequest()
 	{
 		$this->Request->process();
 	}
 
 	/**
 	 * Allows to add new element to opener stack
 	 *
 	 * @param string $template
 	 * @param Array $params
 	 * @access public
 	 */
 	public function openerStackPush($template = '', $params = Array ())
 	{
 		$this->Request->openerStackPush($template, $params);
 	}
 
 	/**
 	 * Set's new event for $prefix_special
 	 * passed
 	 *
 	 * @param string $prefix_special
 	 * @param string $event_name
 	 * @access public
 	 */
 	public function setEvent($prefix_special,$event_name)
 	{
 		$actions = $this->Application->recallObject('kActions');
 		/* @var $actions Params */
 
 		$actions->Set('events[' . $prefix_special . ']', $event_name);
 	}
 
 	/**
 	 * Allows to determine, that required event is beeing processed right now
 	 *
 	 * @param string $event_key Event name in format prefix[.special]:event_name
 	 * @return bool
 	 * @access public
 	 */
 	public function eventRunning($event_key)
 	{
 		return array_search($event_key, $this->recursionStack) !== false;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/processors/tag_processor.php
===================================================================
--- branches/5.3.x/core/kernel/processors/tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/kernel/processors/tag_processor.php	(revision 15698)
@@ -1,350 +1,351 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kTagProcessor extends kBase {
 
 		/**
 		 * Returns joined prefix and special if any
 		 *
 		 * @param bool $from_submit if true, then joins prefix & special by "_", uses  "." otherwise
 		 * @return string
 		 * @access public
 		 */
 		public function getPrefixSpecial($from_submit = false)
 		{
 			if (!$from_submit) {
 				return parent::getPrefixSpecial();
 			}
 
 			return rtrim($this->Prefix . '_' . $this->Special, '_');
 		}
 
 		/**
 		 * Processes tag
 		 *
 		 * @param _BlockTag $tag
 		 * @return string
 		 * @access public
 		 */
 		function ProcessTag(&$tag)
 		{
 			return $this->ProcessParsedTag($tag->Tag, $tag->NP, $tag->getPrefixSpecial());
 		}
 
 		/**
 		 * Checks, that tag is implemented in this tag processor
 		 *
 		 * @param string $tag
 		 * @param string $prefix
 		 *
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckTag($tag, $prefix)
 		{
 			$method = $tag;
 
 			if ( method_exists($this, $method) ) {
 				return true;
 			}
 
 			if ( $this->Application->hasObject('TagsAggregator') ) {
 				$aggregator = $this->Application->recallObject('TagsAggregator');
 				/* @var $aggregator kArray */
 
 				$tmp = $this->Application->processPrefix($prefix);
 				$tag_mapping = $aggregator->GetArrayValue($tmp['prefix'], $method);
 
 				if ( $tag_mapping ) {
 					return true;
 				}
 			}
 
 			return false;
 		}
 
 		function FormCacheKey($tag, $params, $prefix)
 		{
 			// link tag to it's template
 			$reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/';
 			$template_path = preg_replace($reg_exp, '', $this->Application->Parser->TempalteFullPath, 1);
 			$element = 'file=' . $template_path . ':' . $prefix . '_' . $tag . '_' . crc32( serialize($params) );
 
 			return $this->Application->Parser->FormCacheKey($element);
 		}
 
 		function ProcessParsedTag($tag, $params, $prefix, $file='unknown', $line=0)
 		{
 			$Method = $tag;
 			if ( method_exists($this, $Method) ) {
 				if ( defined('DEBUG_MODE') && defined('DBG_SHOW_TAGS') && DBG_SHOW_TAGS && $this->Application->isDebugMode() ) {
 					$this->Application->Debugger->appendHTML('Processing PreParsed Tag ' . $Method . ' in ' . $this->Prefix);
 				}
 
 				list ($prefix_only,) = explode('.', $prefix);
 				$this->Application->Parser->PrefixesInUse[$prefix_only] = 1;
 
 				$cache_key = '';
 				$backup_prefix = $this->Prefix;
 				$backup_special = $this->Special;
 
 				if ( $this->Application->Parser->CachingEnabled && array_key_exists('cache_timeout', $params) ) {
 					// individual tag caching
 					$cache_key = $this->FormCacheKey($tag, $params, $prefix);
 					$res = $this->Application->Parser->getCache($cache_key);
 
 					if ( $res !== false ) {
 						return $res;
 					}
 				}
 
 				$original_params = $params;
 				$flag_values = $this->PreparePostProcess($params);
 
 				// pass_params for non ParseBlock tags :)
 				if ( $flag_values['pass_params'] ) {
 					$params = array_merge($this->Application->Parser->Params, $params);
 				}
 
 				$ret = $this->$Method($params);
 
 				$this->Init($backup_prefix, $backup_special);
 
 				$ret = $this->PostProcess($ret, $flag_values);
 
 				if ( $this->Application->Parser->CachingEnabled && $flag_values['cache_timeout'] ) {
 					$this->Application->Parser->setCache($cache_key, $ret, (int)$flag_values['cache_timeout']);
 				}
 
 				return $ret;
 			}
 			else {
 				list ($ret, $tag_found) = $this->processAggregatedTag($tag, $params, $prefix, $file, $line);
 
 				if ( $tag_found ) {
 					return $ret;
 				}
 
 				$error_tag = Array ('file' => $file, 'line' => $line);
 				throw new ParserException('Undefined tag: <strong>' . $prefix . ':' . $tag . '</strong>', 0, null, $error_tag);
 
 				return false;
 			}
 		}
 
 		function processAggregatedTag($tag, $params, $prefix, $file = 'unknown', $line = 0)
 		{
 			if ( $this->Application->hasObject('TagsAggregator') ) {
 				$Method = $tag;
 				$aggregator = $this->Application->recallObject('TagsAggregator');
 				/* @var $aggregator kArray */
 
 				$tmp = $this->Application->processPrefix($prefix);
 				$tag_mapping = $aggregator->GetArrayValue($tmp['prefix'], $Method);
 
 				if ( $tag_mapping ) {
 					// aggregated tag defined
 					$tmp = $this->Application->processPrefix($tag_mapping[0]);
 					$__tag_processor = $tmp['prefix'] . '_TagProcessor';
 
 					$processor = $this->Application->recallObject($__tag_processor);
 					/* @var $processor kTagProcessor */
 
 					$processor->Init($tmp['prefix'], getArrayValue($tag_mapping, 2) ? $tag_mapping[2] : $tmp['special']);
 
 					$params['original_tag'] = $Method; // allows to define same method for different aggregated tags in same tag processor
 					$params['PrefixSpecial'] = $this->getPrefixSpecial(); // $prefix;
 					$ret = $processor->ProcessParsedTag($tag_mapping[1], $params, $prefix);
 					if ( isset($params['result_to_var']) ) {
 						$this->Application->Parser->SetParam($params['result_to_var'], $ret);
 						$ret = '';
 					}
 					return Array ($ret, true);
 				}
 				else {
 					// aggregated tag not defined
 					$error_tag = Array ('file' => $file, 'line' => $line);
 					throw new ParserException('Undefined aggregated tag <strong>' . $prefix . ':' . $Method . '</strong> (in ' . get_class($this) . ' tag processor)', 0, null, $error_tag);
 				}
 			}
 
 			return Array ('', false);
 		}
 
 		function PreparePostProcess(&$params)
 		{
 			$flags = Array('js_escape', 'equals_to', 'result_to_var', 'pass_params', 'html_escape', 'strip_nl', 'trim', 'cache_timeout');
 			$flag_values = Array();
 
 			foreach ($flags as $flag_name) {
 				$flag_values[$flag_name] = false;
 				if (isset($params[$flag_name])) {
 					$flag_values[$flag_name] = $params[$flag_name];
 					unset($params[$flag_name]);
 				}
 			}
 			return $flag_values;
 		}
 
 		function PostProcess($ret, $flag_values)
 		{
 			if ($flag_values['html_escape']) {
 				$ret = htmlspecialchars($ret, null, CHARSET);
 			}
 			if ($flag_values['js_escape']) {
 				$ret = addslashes($ret);
 				$ret = str_replace(Array("\r", "\n"), Array('\r', '\n'), $ret);
 				$ret = str_replace('</script>', "</'+'script>", $ret);
 			}
 			if ($flag_values['strip_nl']) {
 				// 1 - strip \r,\n; 2 - strip tabs too
 				$ret = preg_replace($flag_values['strip_nl'] == 2 ? "/[\r\n\t]/" : "/[\r\n]/", '', $ret);
 			}
 			if ($flag_values['trim']) {
 				$ret = trim($ret);
 			}
 			// TODO: in new parser implement this parameter in compiled code (by Alex)
 			if ($flag_values['equals_to'] !== false) {
 				$equals_to = explode('|', $flag_values['equals_to']);
 				$ret = in_array($ret, $equals_to);
 			}
 
 			if ($flag_values['result_to_var']) {
 				$this->Application->Parser->SetParam($flag_values['result_to_var'], $ret);
 				$ret = '';
 			}
 			return $ret;
 		}
 
 
 		/**
 		 * Not tag, method for parameter
 		 * selection from list in this TagProcessor
 		 *
 		 * @param Array $params
 		 * @param string $possible_names
 		 * @return string|bool
 		 * @access protected
 		 */
 		protected function SelectParam($params, $possible_names = null)
 		{
 			if ( !isset($possible_names) ) {
 				// select 1st parameter non-empty parameter value
 				$possible_names = explode(',', $params['possible_names']);
 
 				foreach ($possible_names as $param_name) {
 					$value = $this->Application->Parser->GetParam($param_name);
 					$string_value = (string)$value;
 
 					if ( ($string_value != '') && ($string_value != '0') ) {
 						return $value;
 					}
 				}
 
 				return false;
 			}
 
 			if ( !is_array($possible_names) ) {
 				$possible_names = explode(',', $possible_names);
 			}
 
 			foreach ($possible_names as $name) {
 				if ( isset($params[$name]) ) {
 					return $params[$name];
 				}
 			}
 
 			return false;
 		}
 
 	/**
 	 * Returns templates path for module, which is gathered from prefix module
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @author Alex
 	 */
 	function ModulePath($params)
 	{
 		$force_module = getArrayValue($params, 'module');
 		if ($force_module) {
 			if ($force_module == '#session#') {
 				$force_module = preg_replace('/([^:]*):.*/', '\1', $this->Application->RecallVar('module'));
 				if (!$force_module) $force_module = 'core';
 			}
 			else {
 				$force_module = mb_strtolower($force_module);
 			}
 
 			if ($force_module == 'core') {
 				$module_folder = 'core';
 			}
 			else {
 				$module_folder = trim( $this->Application->findModule('Name', $force_module, 'Path'), '/');
 			}
 		}
 		else {
-			$module_folder = $this->Application->getUnitOption($this->Prefix, 'ModuleFolder');
+			$module_folder = $this->getUnitConfig()->getModuleFolder();
 		}
+
 		return '../../'.$module_folder.'/admin_templates/';
 	}
 }
 
 /*class ProcessorsPool {
 	var $Processors = Array();
 	var $Application;
 	var $Prefixes = Array();
 	var $S;
 
 	function ProcessorsPool()
 	{
 		$this->Application =& KernelApplication::Instance();
 		$this->S =& $this->Application->Session;
 	}
 
 	function RegisterPrefix($prefix, $path, $class)
 	{
 		// echo " RegisterPrefix $prefix, $path, $class <br>";
 		$prefix_item = Array(
 			'path' => $path,
 			'class' => $class
 		);
 		$this->Prefixes[$prefix] = $prefix_item;
 	}
 
 	function CreateProcessor($prefix, &$tag)
 	{
 		// echo " prefix : $prefix <br>";
 		if (!isset($this->Prefixes[$prefix]))
 			$this->Application->ApplicationDie ("<b>Filepath and ClassName for prefix $prefix not defined while processing ".htmlspecialchars($tag->GetFullTag(), null, CHARSET)."!</b>");
 		include_once($this->Prefixes[$prefix]['path']);
 		$ClassName = $this->Prefixes[$prefix]['class'];
 		$a_processor = new $ClassName($prefix);
 		$this->SetProcessor($prefix, $a_processor);
 	}
 
 	function SetProcessor($prefix, &$a_processor)
 	{
 		$this->Processors[$prefix] =& $a_processor;
 	}
 
 	function &GetProcessor($prefix, &$tag)
 	{
 		if (!isset($this->Processors[$prefix]))
 			$this->CreateProcessor($prefix, $tag);
 		return $this->Processors[$prefix];
 	}
 }*/
\ No newline at end of file
Index: branches/5.3.x/core/kernel/utility/formatters/multilang_formatter.php
===================================================================
--- branches/5.3.x/core/kernel/utility/formatters/multilang_formatter.php	(revision 15697)
+++ branches/5.3.x/core/kernel/utility/formatters/multilang_formatter.php	(revision 15698)
@@ -1,315 +1,316 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kMultiLanguage extends kFormatter
 {
 
 	/**
 	 * Multilanguage helper
 	 *
 	 * @var kMultiLanguageHelper
 	 */
 	var $helper = null;
 
 	public function __construct()
 	{
 		parent::__construct();
 
 		$this->helper = $this->Application->recallObject('kMultiLanguageHelper');
 	}
 
 	/**
 	 * Returns ML field equivalent to field name specifed
 	 *
 	 * @param string $field_name
 	 * @param bool $from_primary use primary/current language for name custruction
 	 * @return string
 	 */
 	function LangFieldName($field_name, $from_primary = false)
 	{
 		static $primary_language = null;
 
 		if (preg_match('/^l[0-9]+_/', $field_name)) {
 			return $field_name;
 		}
 
 		if (!isset($primary_language)) {
 			$primary_language = $this->Application->GetDefaultLanguageId();
 		}
 
 		$lang = $from_primary ? $primary_language : $this->Application->GetVar('m_lang');
 
 		if (!$lang || ($lang == 'default')) {
 			$lang = $primary_language;
 		}
 
 		return 'l' . $lang . '_' . $field_name;
 	}
 
 	/**
 	 * The method is supposed to alter config options or cofigure object in some way based on its usage of formatters
 	 * The methods is called for every field with formatter defined when configuring item.
 	 * Could be used for adding additional VirtualFields to an object required by some special Formatter
 	 *
 	 * @param string $field_name
 	 * @param array $field_options
 	 * @param kDBBase $object
 	 */
 	function PrepareOptions($field_name, &$field_options, &$object)
 	{
 		if (getArrayValue($field_options, 'master_field') || getArrayValue($field_options, 'options_processed')) {
 			return ;
 		}
 
+		$config = $object->getUnitConfig();
 		$lang_field_name = $this->LangFieldName($field_name);
 
 		//substitute title field
-		$title_field = $this->Application->getUnitOption($object->Prefix, 'TitleField');
+		$title_field = $config->getTitleField();
 		if ($title_field == $field_name) {
-			$this->Application->setUnitOption($object->Prefix, 'TitleField', $lang_field_name);
+			$object->getUnitConfig()->setTitleField($lang_field_name);
 		}
 
 		$languages = $this->helper->getLanguages();
 		$primary_language_id = $this->Application->GetDefaultLanguageId();
-		$fields = $this->Application->getUnitOption($object->Prefix, 'Fields', Array ());
-		$virtual_fields = $this->Application->getUnitOption($object->Prefix, 'VirtualFields', Array ());
+		$fields = $config->getFields(Array ());
+		$virtual_fields = $config->getVirtualFields(Array ());
 
 		// substitute real field
 		if (array_key_exists($field_name, $fields)) {
 			$tmp_field_options = $fields[$field_name];
 			$tmp_field_options['master_field'] = $field_name;
 			$tmp_field_options['error_field'] = $field_name;
 			$field_required = array_key_exists('required', $tmp_field_options) && $tmp_field_options['required'];
 
 			foreach ($languages as $language_id) {
 				// make all non-primary language fields not required
 				if ($language_id != $primary_language_id) {
 					unset($tmp_field_options['required']);
 				}
 				elseif ($field_required) {
 					$tmp_field_options['required'] = $field_required;
 				}
 
 				$translated_field = 'l' . $language_id . '_' . $field_name;
 				$fields[$translated_field] = $tmp_field_options;
 				$object->SetFieldOptions($translated_field, $tmp_field_options);
 			}
 
 			// makes original field non-required
 			$object_fields = $object->getFields(); // use kDBBase::getFields, since there are no kDBList::setRequired
 			unset($fields[$field_name]['required'], $object_fields[$field_name]['required']);
 			$object->setFields($object_fields);
 
 			// prevents real field with formatter set to be saved in db
 			$virtual_fields[$field_name] = $object_fields[$field_name];
 			$object->SetFieldOptions($field_name, $object_fields[$field_name], true);
 		}
 		elseif (array_key_exists($field_name, $virtual_fields)) {
 			// substitute virtual field
-			$calculated_fields = $this->Application->getUnitOption($object->Prefix, 'CalculatedFields', Array ());
+			$calculated_fields = $config->getSetting('CalculatedFields', Array ());
 			$calculated_field_special = array_key_exists($object->Special, $calculated_fields) ? $object->Special : (array_key_exists('', $calculated_fields) ? '' : false);
 			/* @var $calculated_fields Array */
 
 			$tmp_field_options = $virtual_fields[$field_name];
 			$tmp_field_options['master_field'] = $field_name;
 			$tmp_field_options['error_field'] = $field_name;
 			$field_required = array_key_exists('required', $tmp_field_options) && $tmp_field_options['required'];
 
 			foreach ($languages as $language_id) {
 				// make all non-primary language fields not required
 				if ($language_id != $primary_language_id) {
 					unset($tmp_field_options['required']);
 				}
 				elseif ($field_required) {
 					$tmp_field_options['required'] = $field_required;
 				}
 
 				$translated_field = 'l' . $language_id . '_' . $field_name;
 				$virtual_fields[$translated_field] = $tmp_field_options;
 				$object->SetFieldOptions($translated_field, $tmp_field_options, true);
 
 				// substitute calculated fields associated with given virtual field
 				foreach ($calculated_fields as $special => $special_fields) {
 					if (!array_key_exists($field_name, $special_fields)) {
 						continue;
 					}
 
 					$calculated_fields[$special][$translated_field] = str_replace('%2$s', $language_id, $special_fields[$field_name]);
 
 					if ($special === $calculated_field_special) {
 						$object->addCalculatedField($translated_field, $calculated_fields[$special][$translated_field]);
 					}
 				}
 
 				// manually copy virtual field back to fields (see kDBBase::setVirtualFields about that)
 				$fields[$translated_field] = $tmp_field_options;
 				$object->SetFieldOptions($translated_field, $tmp_field_options);
 			}
 
 			// remove original calculated field
 			foreach ($calculated_fields as $special => $special_fields) {
 				unset($calculated_fields[$special][$field_name]);
 			}
 
 			$object_calculated_fields = $object->getCalculatedFields();
 			unset($object_calculated_fields[$field_name]);
 			$object->setCalculatedFields($object_calculated_fields);
 
 			// save back calculated fields
-			$this->Application->setUnitOption($object->Prefix, 'CalculatedFields', $calculated_fields);
+			$config->setSetting('CalculatedFields', $calculated_fields);
 
 			// makes original field non-required
 			$object_fields = $object->getFields(); // use kDBBase::getFields, since there are no kDBList::setRequired
 			unset($fields[$field_name]['required'], $object_fields[$field_name]['required']);
 			$object->setFields($object_fields);
 
 			$virtual_field_options = $object->GetFieldOptions($field_name, true);
 			unset($virtual_fields[$field_name]['required'], $virtual_field_options['required']);
 			$object->SetFieldOptions($field_name, $virtual_field_options, true);
 		}
 
 		// substitute grid fields
-		$grids = $this->Application->getUnitOption($object->Prefix, 'Grids', Array ());
+		$grids = $config->getGrids(Array ());
 		/* @var $grids Array */
 
 		foreach ($grids as $name => $grid) {
 			if ( getArrayValue($grid, 'Fields', $field_name) ) {
 				// used by column picker to track column position
 				$grids[$name]['Fields'][$field_name]['formatter_renamed'] = true;
 
 				if (!array_key_exists('format', $grids[$name]['Fields'][$field_name])) {
 					// prevent displaying value from primary language
 					// instead of missing value in current language
 					$grids[$name]['Fields'][$field_name]['format'] = 'no_default';
 				}
 
 				if ( !isset($grid['Fields'][$field_name]['title']) ) {
 					$grids[$name]['Fields'][$field_name]['title'] = 'column:la_fld_' . $field_name;
 				}
 
 				kUtil::array_rename_key($grids[$name]['Fields'], $field_name, $lang_field_name);
 			}
 
 			// update sort fields - used for sorting and filtering in SQLs
 			foreach ($grid['Fields'] as $grid_fld_name => $fld_options) {
 				if (isset($fld_options['sort_field']) && $fld_options['sort_field'] == $field_name) {
 					$grids[$name]['Fields'][$grid_fld_name]['sort_field'] = $lang_field_name;
 				}
 			}
 		}
-		$this->Application->setUnitOption($object->Prefix, 'Grids', $grids);
+		$config->setGrids($grids);
 
 		//TODO: substitute possible language-fields sortings after changing language
 		if ( $object->isVirtualField($field_name) ) {
 			$virtual_fields[$field_name]['options_processed'] = true;
 		}
 		else {
 			$fields[$field_name]['options_processed'] = true;
 		}
 
 		$field_options['options_processed'] = true;
-		$this->Application->setUnitOption($object->Prefix, 'Fields', $fields);
-		$this->Application->setUnitOption($object->Prefix, 'VirtualFields', $virtual_fields);
+		$config->setFields($fields);
+		$config->setVirtualFields($virtual_fields);
 	}
 
 	/*function UpdateSubFields($field, $value, &$options, &$object)
 	{
 
 	}
 	*/
 
 	/**
 	 * Checks, that field value on primary language is set
 	 *
 	 * @param string $field
 	 * @param mixed $value
 	 * @param Array $options
 	 * @param kDBItem $object
 	 */
 	function UpdateMasterFields($field, $value, &$options, &$object)
 	{
 		$master_field = array_key_exists('master_field', $options) ? $options['master_field'] : false;
 		if (!$master_field) {
 			return ;
 		}
 
 		// moved here from Parse, because at Parse time not all of the fields may be set - this is extremly actual, when working with PopulateMlFields mode
 		$lang = $this->Application->GetVar('m_lang');
 		$def_lang = $this->Application->GetDefaultLanguageId();
 
 		if ( !$this->Application->GetVar('allow_translation') && ($lang != $def_lang) && $object->isRequired($field) ) {
 			$def_lang_field = 'l' . $def_lang . '_' . $master_field;
 
 			if ( !$object->ValidateRequired($def_lang_field, $options) ) {
 				$object->SetError($master_field, 'primary_lang_required');
 
 				if ( $object->isField($def_lang_field) ) {
 					$object->SetError($def_lang_field, 'primary_lang_required');
 				}
 			}
 		}
 	}
 
 	/**
 	 * Formats value of a given field
 	 *
 	 * @param string $value
 	 * @param string $field_name
 	 * @param kDBItem|kDBList $object
 	 * @param string $format
 	 * @return string
 	 */
 	function Format($value, $field_name, &$object, $format=null)
 	{
 		$master_field = $object->GetFieldOption($field_name, 'master_field');
 
 		if (!$master_field) { // if THIS field is master it does NOT have reference to it's master_field
 			$lang = $this->Application->GetVar('m_lang');
 			$value = $object->GetDBField('l'.$lang.'_'.$field_name); //getting value of current language
 			$master_field = $field_name; // THIS is master_field
 		}
 		$options = $object->GetFieldOptions($field_name);
 		$format = isset($format) ? $format : ( isset($options['format']) ? $options['format'] : null);
 
 		// use strpos, becase 2 comma-separated formats could be specified
 		if ($value == '' && strpos($format, 'no_default') === false) { // try to get default language value
 			$def_lang_value = $object->GetDBField('l'.$this->Application->GetDefaultLanguageId().'_'.$master_field);
 			if ($def_lang_value == '') {
 				return NULL;
 			}
 
 			return $this->_replaceFCKLinks($def_lang_value, $options, $format); //return value from default language
 		}
 
 		return $this->_replaceFCKLinks($value, $options, $format);
 	}
 
 	/**
 	 * Performs required field check on primary language
 	 *
 	 * @param mixed $value
 	 * @param string $field_name
 	 * @param kDBItem $object
 	 * @return mixed
 	 * @access public
 	 */
 	public function Parse($value, $field_name, &$object)
 	{
 		if ($value == '') return NULL;
 
 		return $value;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/utility/temp_handler.php
===================================================================
--- branches/5.3.x/core/kernel/utility/temp_handler.php	(revision 15697)
+++ branches/5.3.x/core/kernel/utility/temp_handler.php	(revision 15698)
@@ -1,1057 +1,1063 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kTempTablesHandler extends kBase {
 
 	var $Tables = Array();
 
 	/**
 	 * Master table name for temp handler
 	 *
 	 * @var string
 	 * @access private
 	 */
 	var $MasterTable = '';
 
 	/**
 	 * IDs from master table
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $MasterIDs = Array();
 
 	var $AlreadyProcessed = Array();
 
 	var $DroppedTables = Array();
 
 	var $FinalRefs = Array();
 
 	var $TableIdCounter = 0;
 
 	var $CopiedTables =  Array();
 
 	/**
 	 * Foreign key cache
 	 *
 	 * @var Array
 	 */
 	var $FKeysCache = Array ();
 
 	/**
 	 * IDs of newly cloned items (key - prefix.special, value - array of ids)
 	 *
 	 * @var Array
 	 */
 	var $savedIDs = Array();
 
 	/**
 	 * Window ID of current window
 	 *
 	 * @var mixed
 	 */
 	var $WindowID = '';
 
 	/**
 	 * Event, that was used to create this object
 	 *
 	 * @var kEvent
 	 * @access protected
 	 */
 	protected $parentEvent = null;
 
 	/**
 	 * Sets new parent event to the object
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access public
 	 */
 	public function setParentEvent($event)
 	{
 		$this->parentEvent = $event;
 	}
 
 	function SetTables($tables)
 	{
 		// set table name as key for tables array
 		$this->Tables = $tables;
 		$this->MasterTable = $tables['TableName'];
 	}
 
 	function saveID($prefix, $special = '', $id = null)
 	{
 		if (!isset($this->savedIDs[$prefix.($special ? '.' : '').$special])) {
 			$this->savedIDs[$prefix.($special ? '.' : '').$special] = array();
 		}
 		if (is_array($id)) {
 			foreach ($id as $tmp_id => $live_id) {
 				$this->savedIDs[$prefix.($special ? '.' : '').$special][$tmp_id] = $live_id;
 			}
 		}
 		else {
 			$this->savedIDs[$prefix.($special ? '.' : '').$special][] = $id;
 		}
 	}
 
 	/**
 	 * Get temp table name
 	 *
 	 * @param string $table
 	 * @return string
 	 */
 	function GetTempName($table)
 	{
 		return $this->Application->GetTempName($table, $this->WindowID);
 	}
 
 	function GetTempTablePrefix()
 	{
 		return $this->Application->GetTempTablePrefix($this->WindowID);
 	}
 
 	/**
 	 * Return live table name based on temp table name
 	 *
 	 * @param string $temp_table
 	 * @return string
 	 */
 	function GetLiveName($temp_table)
 	{
 		return $this->Application->GetLiveName($temp_table);
 	}
 
 	function IsTempTable($table)
 	{
 		return $this->Application->IsTempTable($table);
 	}
 
 	/**
 	 * Return temporary table name for master table
 	 *
 	 * @return string
 	 * @access public
 	 */
 	function GetMasterTempName()
 	{
 		return $this->GetTempName($this->MasterTable);
 	}
 
 	function CreateTempTable($table)
 	{
 		$sql = 'CREATE TABLE ' . $this->GetTempName($table) . '
 				SELECT *
 				FROM ' . $table . '
 				WHERE 0';
 
 		$this->Conn->Query($sql);
 	}
 
 	function BuildTables($prefix, $ids)
 	{
+		$this->TableIdCounter = 0;
 		$this->WindowID = $this->Application->GetVar('m_wid');
 
-		$this->TableIdCounter = 0;
+		$config = $this->Application->getUnitConfig($prefix);
+
 		$tables = Array(
-				'TableName'	=>	$this->Application->getUnitOption($prefix, 'TableName'),
-				'IdField'	=>	$this->Application->getUnitOption($prefix, 'IDField'),
+				'TableName'	=>	$config->getTableName(),
+				'IdField'	=>	$config->getIDField(),
 				'IDs'		=>	$ids,
 				'Prefix'	=>	$prefix,
 				'TableId'	=>	$this->TableIdCounter++,
 		);
 
-		/*$parent_prefix = $this->Application->getUnitOption($prefix, 'ParentPrefix');
-		if ($parent_prefix) {
-			$tables['ForeignKey'] = $this->Application->getUnitOption($prefix, 'ForeignKey');
+		/*$config = $this->Application->getUnitConfig($prefix);
+		$parent_prefix = $config->getParentPrefix();
+
+		if ( $parent_prefix ) {
+			$tables['ForeignKey'] = $config->getForeignKey();
 			$tables['ParentPrefix'] = $parent_prefix;
-			$tables['ParentTableKey'] = $this->Application->getUnitOption($prefix, 'ParentTableKey');
+			$tables['ParentTableKey'] = $config->getParentTableKey();
 		}*/
 
 		$this->FinalRefs[ $tables['TableName'] ] = $tables['TableId']; // don't forget to add main table to FinalRefs too
 
-		$sub_items = $this->Application->getUnitOption($prefix, 'SubItems', Array ());
-		/* @var $sub_items Array */
+		$sub_items = $this->Application->getUnitConfig($prefix)->getSubItems(Array ());
 
 		if ( is_array($sub_items) ) {
 			foreach ($sub_items as $prefix) {
 				$this->AddTables($prefix, $tables);
 			}
 		}
 
 		$this->SetTables($tables);
 	}
 
 	/**
 	 * Searches through TempHandler tables info for required prefix
 	 *
 	 * @param string $prefix
 	 * @param Array $master
 	 * @return mixed
 	 */
 	function SearchTable($prefix, $master = null)
 	{
 		if (is_null($master)) {
 			$master = $this->Tables;
 		}
 
 		if ($master['Prefix'] == $prefix) {
 			return $master;
 		}
 
 		if (isset($master['SubTables'])) {
 			foreach ($master['SubTables'] as $sub_table) {
 				$found = $this->SearchTable($prefix, $sub_table);
 				if ($found !== false) {
 					return $found;
 				}
 			}
 		}
 
 		return false;
 	}
 
 	function AddTables($prefix, &$tables)
 	{
 		if ( !$this->Application->prefixRegistred($prefix) ) {
 			// allows to skip subitem processing if subitem module not enabled/installed
 			return ;
 		}
 
+		$config = $this->Application->getUnitConfig($prefix);
+
 		$tmp = Array(
-				'TableName' => $this->Application->getUnitOption($prefix,'TableName'),
-				'IdField' => $this->Application->getUnitOption($prefix,'IDField'),
-				'ForeignKey' => $this->Application->getUnitOption($prefix,'ForeignKey'),
-				'ParentPrefix' => $this->Application->getUnitOption($prefix, 'ParentPrefix'),
-				'ParentTableKey' => $this->Application->getUnitOption($prefix,'ParentTableKey'),
+				'TableName' => $config->getTableName(),
+				'IdField' => $config->getIDField(),
+				'ForeignKey' => $config->getForeignKey(),
+				'ParentPrefix' => $config->getParentPrefix(),
+				'ParentTableKey' => $config->getParentTableKey(),
 				'Prefix' => $prefix,
-				'AutoClone' => $this->Application->getUnitOption($prefix,'AutoClone'),
-				'AutoDelete' => $this->Application->getUnitOption($prefix,'AutoDelete'),
+				'AutoClone' => $config->getAutoClone(),
+				'AutoDelete' => $config->getAutoDelete(),
 				'TableId' => $this->TableIdCounter++,
 		);
 
 		$this->FinalRefs[ $tmp['TableName'] ] = $tmp['TableId'];
 
-		$constrain = $this->Application->getUnitOption($prefix, 'Constrain');
+		$constrain = $config->getConstrain();
+
 		if ( $constrain ) {
 			$tmp['Constrain'] = $constrain;
 			$this->FinalRefs[ $tmp['TableName'] . $tmp['Constrain'] ] = $tmp['TableId'];
 		}
 
-		$sub_items = $this->Application->getUnitOption($prefix, 'SubItems', Array ());
-		/* @var $sub_items Array */
+		$sub_items = $config->getSubItems(Array ());
 
 		if ( is_array($sub_items) ) {
 			foreach ($sub_items as $prefix) {
 				$this->AddTables($prefix, $tmp);
 			}
 		}
 
 		if ( !is_array(getArrayValue($tables, 'SubTables')) ) {
 			$tables['SubTables'] = Array ();
 		}
 
 		$tables['SubTables'][] = $tmp;
 	}
 
 	function CloneItems($prefix, $special, $ids, $master = null, $foreign_key = null, $parent_prefix = null, $skip_filenames = false)
 	{
 		if (!isset($master)) $master = $this->Tables;
 
 		// recalling by different name, because we may get kDBList, if we recall just by prefix
 		if (!preg_match('/(.*)-item$/', $special)) {
 			$special .= '-item';
 		}
 
 		$object = $this->Application->recallObject($prefix.'.'.$special, $prefix, Array('skip_autoload' => true, 'parent_event' => $this->parentEvent));
 		/* @var $object kCatDBItem */
 
 		$object->PopulateMultiLangFields();
 
 		foreach ($ids as $id) {
 			$mode = 'create';
 			$cloned_ids = getArrayValue($this->AlreadyProcessed, $master['TableName']);
 
 			if ( $cloned_ids ) {
 				// if we have already cloned the id, replace it with cloned id and set mode to update
 				// update mode is needed to update second ForeignKey for items cloned by first ForeignKey
 				if ( getArrayValue($cloned_ids, $id) ) {
 					$id = $cloned_ids[$id];
 					$mode = 'update';
 				}
 			}
 
 			$object->Load($id);
 			$original_values = $object->GetFieldValues();
 
 			if (!$skip_filenames) {
 				$object->NameCopy($master, $foreign_key);
 			}
 			elseif ($master['TableName'] == $this->MasterTable) {
 				// kCatDBItem class only has this attribute
 				$object->useFilenames = false;
 			}
 
 			if (isset($foreign_key)) {
 				$master_foreign_key_field = is_array($master['ForeignKey']) ? $master['ForeignKey'][$parent_prefix] : $master['ForeignKey'];
 				$object->SetDBField($master_foreign_key_field, $foreign_key);
 			}
 
 			if ($mode == 'create') {
 				$this->RaiseEvent('OnBeforeClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key);
 			}
 
 			$object->inCloning = true;
 			$res = $mode == 'update' ? $object->Update() : $object->Create();
 			$object->inCloning = false;
 
 			if ($res)
 			{
 				if ( $mode == 'create' && is_array( getArrayValue($master, 'ForeignKey')) ) {
 					// remember original => clone mapping for dual ForeignKey updating
 					$this->AlreadyProcessed[$master['TableName']][$id] = $object->GetId();
 				}
 
 				if ($mode == 'create') {
 					$this->RaiseEvent('OnAfterClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key, array('original_id' => $id) );
 					$this->saveID($master['Prefix'], $special, $object->GetID());
 				}
 
 				if ( is_array(getArrayValue($master, 'SubTables')) ) {
 					foreach($master['SubTables'] as $sub_table) {
 						if (!getArrayValue($sub_table, 'AutoClone')) continue;
 						$sub_TableName = $object->IsTempTable() ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName'];
 
 						$foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey'];
 						$parent_key_field = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey'];
 
 						if (!$foreign_key_field || !$parent_key_field) continue;
 
 						$query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.'
 											WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field];
 						if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
 
 						$sub_ids = $this->Conn->GetCol($query);
 
 						if ( is_array(getArrayValue($sub_table, 'ForeignKey')) ) {
 							// $sub_ids could containt newly cloned items, we need to remove it here
 							// to escape double cloning
 
 							$cloned_ids = getArrayValue($this->AlreadyProcessed, $sub_table['TableName']);
 							if ( !$cloned_ids ) $cloned_ids = Array();
 							$new_ids = array_values($cloned_ids);
 							$sub_ids = array_diff($sub_ids, $new_ids);
 						}
 
 						$parent_key = $object->GetDBField($parent_key_field);
 
 						$this->CloneItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key, $master['Prefix']);
 					}
 				}
 			}
 		}
 
 		if (!$ids) {
 			$this->savedIDs[$prefix.($special ? '.' : '').$special] = Array();
 		}
 
 		return $this->savedIDs[$prefix.($special ? '.' : '').$special];
 	}
 
 	function DeleteItems($prefix, $special, $ids, $master=null, $foreign_key=null)
 	{
 		if ( !$ids ) {
 			return;
 		}
 
 		if ( !isset($master) ) {
 			$master = $this->Tables;
 		}
 
 		if ( strpos($prefix, '.') !== false ) {
 			list($prefix, $special) = explode('.', $prefix, 2);
 		}
 
 		$prefix_special = rtrim($prefix . '.' . $special, '.');
 
 		//recalling by different name, because we may get kDBList, if we recall just by prefix
 		$recall_prefix = $prefix_special . ($special ? '' : '.') . '-item';
 		$object = $this->Application->recallObject($recall_prefix, $prefix, Array ('skip_autoload' => true, 'parent_event' => $this->parentEvent));
 		/* @var $object kDBItem */
 
 		foreach ($ids as $id) {
 			$object->Load($id);
 			$original_values = $object->GetFieldValues();
 
 			if ( !$object->Delete($id) ) {
 				continue;
 			}
 
 			if ( is_array(getArrayValue($master, 'SubTables')) ) {
 				foreach ($master['SubTables'] as $sub_table) {
 					if ( !getArrayValue($sub_table, 'AutoDelete') ) {
 						continue;
 					}
 
 					$sub_TableName = $object->IsTempTable() ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName'];
 
 					$foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
 					$parent_key_field = is_array($sub_table['ParentTableKey']) ? getArrayValue($sub_table, 'ParentTableKey', $master['Prefix']) : $sub_table['ParentTableKey'];
 
 					if ( !$foreign_key_field || !$parent_key_field ) {
 						continue;
 					}
 
 					$sql = 'SELECT ' . $sub_table['IdField'] . '
 							FROM ' . $sub_TableName . '
 							WHERE ' . $foreign_key_field . ' = ' . $original_values[$parent_key_field];
 					$sub_ids = $this->Conn->GetCol($sql);
 
 					$parent_key = $object->GetDBField(is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$prefix] : $sub_table['ParentTableKey']);
 
 					$this->DeleteItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key);
 				}
 			}
 
 		}
 	}
 
 	function DoCopyLiveToTemp($master, $ids, $parent_prefix=null)
 	{
 		// when two tables refers the same table as sub-sub-table, and ForeignKey and ParentTableKey are arrays
 		// the table will be first copied by first sub-table, then dropped and copied over by last ForeignKey in the array
 		// this should not do any problems :)
 		if ( !preg_match("/.*\.[0-9]+/", $master['Prefix']) ) {
 			if( $this->DropTempTable($master['TableName']) )
 			{
 				$this->CreateTempTable($master['TableName']);
 			}
 		}
 
 		if (is_array($ids)) {
 			$ids = join(',', $ids);
 		}
 
 		$table_sig = $master['TableName'].(isset($master['Constrain']) ? $master['Constrain'] : '');
 
 		if ($ids != '' && !in_array($table_sig, $this->CopiedTables)) {
 			if ( getArrayValue($master, 'ForeignKey') ) {
 				if ( is_array($master['ForeignKey']) ) {
 					$key_field = $master['ForeignKey'][$parent_prefix];
 				}
 				else {
 					$key_field = $master['ForeignKey'];
 				}
 			}
 			else {
 				$key_field = $master['IdField'];
 			}
 
 			$query = 'INSERT INTO '.$this->GetTempName($master['TableName']).'
 									SELECT * FROM '.$master['TableName'].'
 									WHERE '.$key_field.' IN ('.$ids.')';
 			if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain'];
 			$this->Conn->Query($query);
 
 			$this->CopiedTables[] = $table_sig;
 
 			$query = 'SELECT '.$master['IdField'].' FROM '.$master['TableName'].'
 								WHERE '.$key_field.' IN ('.$ids.')';
 			if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain'];
 			$this->RaiseEvent( 'OnAfterCopyToTemp', $master['Prefix'], '', $this->Conn->GetCol($query) );
 		}
 
 		if ( getArrayValue($master, 'SubTables') ) {
 			foreach ($master['SubTables'] as $sub_table) {
 
 				$parent_key = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey'];
 				if (!$parent_key) continue;
 
 				if ( $ids != '' && $parent_key != $key_field ) {
 					$query = 'SELECT '.$parent_key.' FROM '.$master['TableName'].'
 										WHERE '.$key_field.' IN ('.$ids.')';
 					$sub_foreign_keys = join(',', $this->Conn->GetCol($query));
 				}
 				else {
 					$sub_foreign_keys = $ids;
 				}
 				$this->DoCopyLiveToTemp($sub_table, $sub_foreign_keys, $master['Prefix']);
 			}
 		}
 	}
 
 	function GetForeignKeys($master, $sub_table, $live_id, $temp_id=null)
 	{
 		$mode = 1; //multi
 		if (!is_array($live_id)) {
 			$live_id = Array($live_id);
 			$mode = 2; //single
 		}
 		if (isset($temp_id) && !is_array($temp_id)) $temp_id = Array($temp_id);
 
 		if ( isset($sub_table['ParentTableKey']) ) {
 			if ( is_array($sub_table['ParentTableKey']) ) {
 				$parent_key_field = $sub_table['ParentTableKey'][$master['Prefix']];
 			}
 			else {
 				$parent_key_field = $sub_table['ParentTableKey'];
 			}
 		}
 		else {
 			$parent_key_field = $master['IdField'];
 		}
 
 		$cached = getArrayValue($this->FKeysCache, $master['TableName'].'.'.$parent_key_field);
 
 		if ( $cached ) {
 			if ( array_key_exists(serialize($live_id), $cached) ) {
 				list($live_foreign_key, $temp_foreign_key) = $cached[serialize($live_id)];
 				if ($mode == 1) {
 					return $live_foreign_key;
 				}
 				else {
 					return Array($live_foreign_key[0], $temp_foreign_key[0]);
 				}
 			}
 		}
 
 		if ($parent_key_field != $master['IdField']) {
 			$query = 'SELECT '.$parent_key_field.' FROM '.$master['TableName'].'
 								WHERE '.$master['IdField'].' IN ('.join(',', $live_id).')';
 			$live_foreign_key = $this->Conn->GetCol($query);
 
 			if (isset($temp_id)) {
 				// because DoCopyTempToOriginal resets negative IDs to 0 in temp table (one by one) before copying to live
 				$temp_key = $temp_id < 0 ? 0 : $temp_id;
 				$query = 'SELECT '.$parent_key_field.' FROM '.$this->GetTempName($master['TableName']).'
 									WHERE '.$master['IdField'].' IN ('.join(',', $temp_key).')';
 				$temp_foreign_key = $this->Conn->GetCol($query);
 			}
 			else {
 				$temp_foreign_key = Array();
 			}
 		}
 		else {
 			$live_foreign_key = $live_id;
 			$temp_foreign_key = $temp_id;
 		}
 
 		$this->FKeysCache[$master['TableName'].'.'.$parent_key_field][serialize($live_id)] = Array($live_foreign_key, $temp_foreign_key);
 
 		if ($mode == 1) {
 			return $live_foreign_key;
 		}
 		else {
 			return Array($live_foreign_key[0], $temp_foreign_key[0]);
 		}
 	}
 
 	/**
 	 * Copies data from temp to live table and returns IDs of copied records
 	 *
 	 * @param Array $master
 	 * @param string $parent_prefix
 	 * @param Array $current_ids
 	 * @return Array
 	 * @access public
 	 */
 	public function DoCopyTempToOriginal($master, $parent_prefix = null, $current_ids = Array())
 	{
 		if ( !$current_ids ) {
 			$query = 'SELECT ' . $master['IdField'] . ' FROM ' . $this->GetTempName($master['TableName']);
 
 			if ( isset($master['Constrain']) ) {
 				$query .= ' WHERE ' . $master['Constrain'];
 			}
 
 			$current_ids = $this->Conn->GetCol($query);
 		}
 
 		$table_sig = $master['TableName'] . (isset($master['Constrain']) ? $master['Constrain'] : '');
 
 		if ($current_ids) {
 			// delete all ids from live table - for MasterTable ONLY!
 			// because items from Sub Tables get deteleted in CopySubTablesToLive !BY ForeignKey!
 			if ( $master['TableName'] == $this->MasterTable ) {
 				$this->RaiseEvent('OnBeforeDeleteFromLive', $master['Prefix'], '', $current_ids);
 
 				$query = 'DELETE FROM ' . $master['TableName'] . ' WHERE ' . $master['IdField'] . ' IN (' . join(',', $current_ids) . ')';
 				$this->Conn->Query($query);
 			}
 
 			if ( getArrayValue($master, 'SubTables') ) {
 				if ( in_array($table_sig, $this->CopiedTables) || $this->FinalRefs[$table_sig] != $master['TableId'] ) {
 					return Array ();
 				}
 
 				foreach ($current_ids AS $id) {
 					$this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', Array ($id));
 
 					//reset negative ids to 0, so autoincrement in live table works fine
 					if ( $id < 0 ) {
 						$query = '	UPDATE ' . $this->GetTempName($master['TableName']) . '
 									SET ' . $master['IdField'] . ' = 0
 									WHERE ' . $master['IdField'] . ' = ' . $id;
 
 						if ( isset($master['Constrain']) ) {
 							$query .= ' AND ' . $master['Constrain'];
 						}
 
 						$this->Conn->Query($query);
 						$id_to_copy = 0;
 					}
 					else {
 						$id_to_copy = $id;
 					}
 
 					//copy current id_to_copy (0 for new or real id) to live table
 					$query = '	INSERT INTO ' . $master['TableName'] . '
 								SELECT * FROM ' . $this->GetTempName($master['TableName']) . '
 								WHERE ' . $master['IdField'] . ' = ' . $id_to_copy;
 					$this->Conn->Query($query);
 
 					$insert_id = $id_to_copy == 0 ? $this->Conn->getInsertID() : $id_to_copy;
 
 					$this->saveID($master['Prefix'], '', array ($id => $insert_id));
 					$this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', Array ($insert_id), null, Array ('temp_id' => $id));
 
 					$this->UpdateForeignKeys($master, $insert_id, $id);
 
 					//delete already copied record from master temp table
 					$query = '	DELETE FROM ' . $this->GetTempName($master['TableName']) . '
 								WHERE ' . $master['IdField'] . ' = ' . $id_to_copy;
 
 					if ( isset($master['Constrain']) ) {
 						$query .= ' AND ' . $master['Constrain'];
 					}
 
 					$this->Conn->Query($query);
 				}
 
 				$this->CopiedTables[] = $table_sig;
 
 				// when all of ids in current master has been processed, copy all sub-tables data
 				$this->CopySubTablesToLive($master, $current_ids);
 			}
 			elseif ( !in_array($table_sig, $this->CopiedTables) && ($this->FinalRefs[$table_sig] == $master['TableId']) ) { //If current master doesn't have sub-tables - we could use mass operations
 				// We don't need to delete items from live here, as it get deleted in the beginning of the method for MasterTable
 				// or in parent table processing for sub-tables
 				$live_ids = Array ();
 				$this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', $current_ids);
 
 				foreach ($current_ids as $an_id) {
 					if ( $an_id > 0 ) {
 						$live_ids[$an_id] = $an_id;
 						// positive (already live) IDs will be copied in on query all togather below,
 						// so we just store it here
 						continue;
 					}
 					else { // zero or negative ids should be copied one by one to get their InsertId
 						// resetting to 0 so it get inserted into live table with autoincrement
 						$query = '	UPDATE ' . $this->GetTempName($master['TableName']) . '
 									SET ' . $master['IdField'] . ' = 0
 									WHERE ' . $master['IdField'] . ' = ' . $an_id;
 						// constrain is not needed here because ID is already unique
 						$this->Conn->Query($query);
 
 						// copying
 						$query = '	INSERT INTO ' . $master['TableName'] . '
 									SELECT * FROM ' . $this->GetTempName($master['TableName']) . '
 									WHERE ' . $master['IdField'] . ' = 0';
 						$this->Conn->Query($query);
 
 						$live_ids[$an_id] = $this->Conn->getInsertID(); //storing newly created live id
 
 						//delete already copied record from master temp table
 						$query = '	DELETE FROM ' . $this->GetTempName($master['TableName']) . '
 									WHERE ' . $master['IdField'] . ' = 0';
 						$this->Conn->Query($query);
 
 						$this->UpdateChangeLogForeignKeys($master, $live_ids[$an_id], $an_id);
 					}
 				}
 
 				// copy ALL records to live table
 				$query = '	INSERT INTO ' . $master['TableName'] . '
 							SELECT * FROM ' . $this->GetTempName($master['TableName']);
 
 				if ( isset($master['Constrain']) ) {
 					$query .= ' WHERE ' . $master['Constrain'];
 				}
 
 				$this->Conn->Query($query);
 
 				$this->CopiedTables[] = $table_sig;
 				$this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', $live_ids);
 
 				$this->saveID($master['Prefix'], '', $live_ids);
 				// no need to clear temp table - it will be dropped by next statement
 			}
 		}
 
 		if ( $this->FinalRefs[ $master['TableName'] ] != $master['TableId'] ) {
 			return Array ();
 		}
 
 		/*if ( is_array(getArrayValue($master, 'ForeignKey')) )	{ //if multiple ForeignKeys
 			if ( $master['ForeignKey'][$parent_prefix] != end($master['ForeignKey']) ) {
 				return; // Do not delete temp table if not all ForeignKeys have been processed (current is not the last)
 			}
 		}*/
 
 		$this->DropTempTable($master['TableName']);
 		$this->Application->resetCounters($master['TableName']);
 
 		if ( !isset($this->savedIDs[ $master['Prefix'] ]) ) {
 			$this->savedIDs[ $master['Prefix'] ] = Array ();
 		}
 
 		return $this->savedIDs[ $master['Prefix'] ];
 	}
 
 	/**
 	 * Create separate connection for locking purposes
 	 *
 	 * @return kDBConnection
 	 */
 	function &_getSeparateConnection()
 	{
 		static $connection = null;
 
 		if (!isset($connection)) {
 			$connection = $this->Application->makeClass( 'kDBConnection', Array (SQL_TYPE, Array ($this->Application, 'handleSQLError')) );
 			/* @var $connection kDBConnection */
 
 			$connection->debugMode = $this->Application->isDebugMode();
 			$connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB, true);
 		}
 
 		return $connection;
 	}
 
 	function UpdateChangeLogForeignKeys($master, $live_id, $temp_id)
 	{
 		if ($live_id == $temp_id) {
 			return ;
 		}
 
 		$prefix = $master['Prefix'];
 		$main_prefix = $this->Application->GetTopmostPrefix($prefix);
 		$ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
 		$changes = $this->Application->RecallVar($ses_var_name);
 		$changes = $changes ? unserialize($changes) : Array ();
 
 		foreach ($changes as $key => $rec) {
 			if ($rec['Prefix'] == $prefix && $rec['ItemId'] == $temp_id) {
 				// main item change log record
 				$changes[$key]['ItemId'] = $live_id;
 			}
 
 			if ($rec['MasterPrefix'] == $prefix && $rec['MasterId'] == $temp_id) {
 				// sub item change log record
 				$changes[$key]['MasterId'] = $live_id;
 			}
 
 			if (in_array($prefix, $rec['ParentPrefix']) && $rec['ParentId'][$prefix] == $temp_id) {
 				// parent item change log record
 				$changes[$key]['ParentId'][$prefix] = $live_id;
 
 				if (array_key_exists('DependentFields', $rec)) {
 					// these are fields from table of $rec['Prefix'] table!
 					// when one of dependent fields goes into idfield of it's parent item, that was changed
-					$parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
-					$parent_table_key = is_array($parent_table_key) ? $parent_table_key[$prefix] : $parent_table_key;
+					$config = $this->Application->getUnitConfig($rec['Prefix']);
+
+					$parent_table_key = $config->getParentTableKey($prefix);
 
 					if ($parent_table_key == $master['IdField']) {
-						$foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
-						$foreign_key = is_array($foreign_key) ? $foreign_key[$prefix] : $foreign_key;
+						$foreign_key = $config->getForeignKey($prefix);
 
 						$changes[$key]['DependentFields'][$foreign_key] = $live_id;
 					}
 				}
 			}
 		}
 
 		$this->Application->StoreVar($ses_var_name, serialize($changes));
 	}
 
 	function UpdateForeignKeys($master, $live_id, $temp_id)
 	{
 		$this->UpdateChangeLogForeignKeys($master, $live_id, $temp_id);
 
 		foreach ($master['SubTables'] as $sub_table) {
 			$foreign_key_field =  is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
 
 			if (!$foreign_key_field) {
 				continue;
 			}
 
 			list ($live_foreign_key, $temp_foreign_key) = $this->GetForeignKeys($master, $sub_table, $live_id, $temp_id);
 
 			//Update ForeignKey in sub TEMP table
 			if ($live_foreign_key != $temp_foreign_key) {
 				$query = 'UPDATE '.$this->GetTempName($sub_table['TableName']).'
 									SET '.$foreign_key_field.' = '.$live_foreign_key.'
 									WHERE '.$foreign_key_field.' = '.$temp_foreign_key;
 				if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
 				$this->Conn->Query($query);
 			}
 		}
 	}
 
 	function CopySubTablesToLive($master, $current_ids) {
 		foreach ($master['SubTables'] as $sub_table) {
 
 			$table_sig = $sub_table['TableName'].(isset($sub_table['Constrain']) ? $sub_table['Constrain'] : '');
 
 			// delete records from live table by foreign key, so that records deleted from temp table
 			// get deleted from live
 			if (count($current_ids) > 0  && !in_array($table_sig, $this->CopiedTables) ) {
 				$foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
 				if (!$foreign_key_field) continue;
 				$foreign_keys = $this->GetForeignKeys($master, $sub_table, $current_ids);
 				if (count($foreign_keys) > 0) {
 					$query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_table['TableName'].'
 										WHERE '.$foreign_key_field.' IN ('.join(',', $foreign_keys).')';
 					if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
 
 					if ( $this->RaiseEvent( 'OnBeforeDeleteFromLive', $sub_table['Prefix'], '', $this->Conn->GetCol($query), $foreign_keys ) ){
 						$query = 'DELETE FROM '.$sub_table['TableName'].'
 											WHERE '.$foreign_key_field.' IN ('.join(',', $foreign_keys).')';
 						if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
 						$this->Conn->Query($query);
 					}
 				}
 			}
 			//sub_table passed here becomes master in the method, and recursively updated and copy its sub tables
 			$this->DoCopyTempToOriginal($sub_table, $master['Prefix']);
 		}
 	}
 
 	/**
 	 * Raises event using IDs, that are currently being processed in temp handler
 	 *
 	 * @param string $name
 	 * @param string $prefix
 	 * @param string $special
 	 * @param Array $ids
 	 * @param string $foreign_key
 	 * @param Array $add_params
 	 * @return bool
 	 * @access protected
 	 */
 	protected function RaiseEvent($name, $prefix, $special, $ids, $foreign_key = null, $add_params = null)
 	{
 		if ( !is_array($ids) ) {
 			return true;
 		}
 
 		$event_key = $prefix . ($special ? '.' : '') . $special . ':' . $name;
 		$event = new kEvent($event_key);
 		$event->MasterEvent = $this->parentEvent;
-		
+
 		if ( isset($foreign_key) ) {
 			$event->setEventParam('foreign_key', $foreign_key);
 		}
 
 		$set_temp_id = ($name == 'OnAfterCopyToLive') && (!is_array($add_params) || !array_key_exists('temp_id', $add_params));
 
 		foreach ($ids as $index => $id) {
 			$event->setEventParam('id', $id);
 
 			if ( $set_temp_id ) {
 				$event->setEventParam('temp_id', $index);
 			}
 
 			if ( is_array($add_params) ) {
 				foreach ($add_params as $name => $val) {
 					$event->setEventParam($name, $val);
 				}
 			}
 
 			$this->Application->HandleEvent($event);
 		}
 
 		return $event->status == kEvent::erSUCCESS;
 	}
 
 	function DropTempTable($table)
 	{
 		if ( in_array($table, $this->DroppedTables) ) {
 			return false;
 		}
 
 		$query = 'DROP TABLE IF EXISTS ' . $this->GetTempName($table);
 
 		array_push($this->DroppedTables, $table);
 		$this->DroppedTables = array_unique($this->DroppedTables);
 		$this->Conn->Query($query);
 
 		return true;
 	}
 
 	function PrepareEdit()
 	{
 		$this->DoCopyLiveToTemp($this->Tables, $this->Tables['IDs']);
-		if ($this->Application->getUnitOption($this->Tables['Prefix'],'CheckSimulatniousEdit')) {
+
+		if ($this->Application->getUnitConfig($this->Tables['Prefix'])->getCheckSimulatniousEdit()) {
 			$this->CheckSimultaniousEdit();
 		}
 	}
 
 	function SaveEdit($master_ids = Array())
 	{
 		// SessionKey field is required for deleting records from expired sessions
 		$conn =& $this->_getSeparateConnection();
 
 		$sleep_count = 0;
 		do {
 			// acquire lock
 			$conn->ChangeQuery('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
 
 			$sql = 'SELECT SessionKey
 					FROM ' . TABLE_PREFIX . 'Semaphores
 					WHERE (MainPrefix = ' . $conn->qstr($this->Tables['Prefix']) . ')';
 			$another_coping_active = $conn->GetOne($sql);
 
 			if ($another_coping_active) {
 				// another user is coping data from temp table to live -> release lock and try again after 1 second
 				$conn->ChangeQuery('UNLOCK TABLES');
 				$sleep_count++;
 				sleep(1);
 			}
 		} while ($another_coping_active && ($sleep_count <= 30));
 
 		if ($sleep_count > 30) {
 			// another coping process failed to finished in 30 seconds
 			$error_message = $this->Application->Phrase('la_error_TemporaryTableCopyingFailed');
 			$this->Application->SetVar('_temp_table_message', $error_message);
 
 			return false;
 		}
 
 		// mark, that we are coping from temp to live right now, so other similar attempt (from another script) will fail
 		$fields_hash = Array (
 			'SessionKey' => $this->Application->GetSID(),
 			'Timestamp' => adodb_mktime(),
 			'MainPrefix' => $this->Tables['Prefix'],
 		);
 
 		$conn->doInsert($fields_hash, TABLE_PREFIX.'Semaphores');
 		$semaphore_id = $conn->getInsertID();
 
 		// unlock table now to prevent permanent lock in case, when coping will end with SQL error in the middle
 		$conn->ChangeQuery('UNLOCK TABLES');
 
 		$ids = $this->DoCopyTempToOriginal($this->Tables, null, $master_ids);
 
 		// remove mark, that we are coping from temp to live
 		$conn->Query('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
 
 		$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Semaphores
 				WHERE SemaphoreId = ' . $semaphore_id;
 		$conn->ChangeQuery($sql);
 
 		$conn->ChangeQuery('UNLOCK TABLES');
 
 		return $ids;
 	}
 
 	function CancelEdit($master=null)
 	{
 		if (!isset($master)) $master = $this->Tables;
 		$this->DropTempTable($master['TableName']);
 		if ( getArrayValue($master, 'SubTables') ) {
 			foreach ($master['SubTables'] as $sub_table) {
 				$this->CancelEdit($sub_table);
 			}
 		}
 	}
 
 	/**
 	 * Checks, that someone is editing selected records and returns true, when no one.
 	 *
 	 * @param Array $ids
 	 *
 	 * @return bool
 	 */
 	function CheckSimultaniousEdit($ids = null)
 	{
 		$tables = $this->Conn->GetCol('SHOW TABLES');
 		$mask_edit_table = '/' . TABLE_PREFIX . 'ses_(.*)_edit_' . $this->MasterTable . '$/';
 
 		$my_sid = $this->Application->GetSID();
 		$my_wid = $this->Application->GetVar('m_wid');
 		$ids = implode(',', isset($ids) ? $ids : $this->Tables['IDs']);
 		$sids = Array ();
 		if (!$ids) {
 			return true;
 		}
 
 		foreach ($tables as $table) {
 			if ( preg_match($mask_edit_table, $table, $rets) ) {
 				$sid = preg_replace('/(.*)_(.*)/', '\\1', $rets[1]); // remove popup's wid from sid
 				if ($sid == $my_sid) {
 					if ($my_wid) {
 						// using popups for editing
 						if (preg_replace('/(.*)_(.*)/', '\\2', $rets[1]) == $my_wid) {
 							// don't count window, that is being opened right now
 							continue;
 						}
 					}
 					else {
 						// not using popups for editing -> don't count my session tables
 						continue;
 					}
 				}
 
 				$sql = 'SELECT COUNT(' . $this->Tables['IdField'] . ')
 						FROM ' . $table . '
 						WHERE ' . $this->Tables['IdField'] . ' IN (' . $ids . ')';
 				$found = $this->Conn->GetOne($sql);
 
 				if (!$found || in_array($sid, $sids)) {
 					continue;
 				}
 
 				$sids[] = $sid;
 			}
 		}
 
 		if ($sids) {
 			// detect who is it
 			$sql = 'SELECT
 						CONCAT(IF (s.PortalUserId = ' . USER_ROOT . ', \'root\',
 							IF (s.PortalUserId = ' . USER_GUEST . ', \'Guest\',
 								CONCAT(u.FirstName, \' \', u.LastName, \' (\', u.Username, \')\')
 							)
 						), \' IP: \', s.IpAddress, \'\') FROM ' . TABLE_PREFIX . 'UserSessions AS s
 					LEFT JOIN ' . TABLE_PREFIX . 'Users AS u
 					ON u.PortalUserId = s.PortalUserId
 					WHERE s.SessionKey IN (' . implode(',', $sids) . ')';
 			$users = $this->Conn->GetCol($sql);
 
 			if ($users) {
 				$this->Application->SetVar('_simultanious_edit_message',
 					sprintf($this->Application->Phrase('la_record_being_edited_by'), join(",\n", $users))
 				);
 
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/utility/unit_config.php
===================================================================
--- branches/5.3.x/core/kernel/utility/unit_config.php	(nonexistent)
+++ branches/5.3.x/core/kernel/utility/unit_config.php	(revision 15698)
@@ -0,0 +1,1029 @@
+<?php
+/**
+ * @version    $Id$
+ * @package    In-Portal
+ * @copyright    Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
+ * @license      GNU/GPL
+ * In-Portal is Open Source software.
+ * This means that this software may have been modified pursuant
+ * the GNU General Public License, and as distributed it includes
+ * or is derivative of works licensed under the GNU General Public License
+ * or other free or open source software licenses.
+ * See http://www.in-portal.org/license for copyright notices and details.
+ */
+
+defined('FULL_PATH') or die('restricted access!');
+
+/**
+ * TODO: Rename following settings
+ *
+ * ConfigMapping -> ConfigMappings
+ * StatusField -> StatusFields
+ * PermSection -> PermSections
+ */
+
+/**
+ * @method Array getFilterMenu(mixed $default = false)
+ * @method kUnitConfig setFilterMenu(Array $value)
+ *
+ * @method Array getConfigMapping(mixed $default = false)
+ * @method kUnitConfig setConfigMapping(Array $value)
+ *
+ *
+ * @method string getModuleFolder(mixed $default = false)
+ * @method kUnitConfig setModuleFolder(string $value)
+ *
+ * @method string getBasePath(mixed $default = false)
+ * @method kUnitConfig setBasePath(string $value)
+ *
+ * @method Array getEventHandlerClass(mixed $default = false)
+ * @method kUnitConfig setEventHandlerClass(Array $value)
+ *
+ * @method Array getTagProcessorClass(mixed $default = false)
+ * @method kUnitConfig setTagProcessorClass(Array $value)
+ *
+ * @method Array getItemClass(mixed $default = false)
+ * @method kUnitConfig setItemClass(Array $value)
+ *
+ * @method Array getListClass(mixed $default = false)
+ * @method kUnitConfig setListClass(Array $value)
+ *
+ * @method Array getValidatorClass(mixed $default = false)
+ * @method kUnitConfig setValidatorClass(Array $value)
+ *
+ * @method Array getQueryString(mixed $default = false)
+ * @method kUnitConfig setQueryString(Array $value)
+ *
+ * @method string getPermItemPrefix(mixed $default = false)
+ * @method kUnitConfig setPermItemPrefix(string $value)
+ *
+ * @method string getPermTabText(mixed $default = false)
+ * @method kUnitConfig setPermTabText(string $value)
+ *
+ * @method Array getPermSection(mixed $default = false)
+ * @method kUnitConfig setPermSection(Array $value)
+ *
+ * @method bool getAutoLoad(mixed $default = false)
+ * @method kUnitConfig setAutoLoad(bool $value)
+ *
+ * @method string getIDField(mixed $default = false)
+ * @method kUnitConfig setIDField(string $value)
+ *
+ * @method string getTableName(mixed $default = false)
+ * @method kUnitConfig setTableName(string $value)
+ *
+ * @method string getCustomDataTableName(mixed $default = false)
+ * @method kUnitConfig setCustomDataTableName(string $value)
+ *
+ * @method kUnitConfig setStatusField(Array $value)
+ *
+ * @method string getTitleField(mixed $default = false)
+ * @method kUnitConfig setTitleField(string $value)
+ *
+ * @method string getOrderField(mixed $default = false)
+ * @method kUnitConfig setOrderField(string $value)
+ *
+ * @method string getOwnerField(mixed $default = false)
+ * @method kUnitConfig setOwnerField(string $value)
+ *
+ * @method int getConfigPriority(mixed $default = false)
+ * @method kUnitConfig setConfigPriority(int $value)
+ *
+ * @method bool getCatalogItem(mixed $default = false)
+ * @method kUnitConfig setCatalogItem(bool $value)
+ *
+ * @method string getCatalogSelectorName(mixed $default = false)
+ * @method kUnitConfig setCatalogSelectorName(string $value)
+ *
+ * @method string getAdminTemplatePath(mixed $default = false)
+ * @method kUnitConfig setAdminTemplatePath(string $value)
+ *
+ * @method string getAdminTemplatePrefix(mixed $default = false)
+ * @method kUnitConfig setAdminTemplatePrefix(string $value)
+ *
+ * @method string getSearchConfigPostfix(mixed $default = false)
+ * @method kUnitConfig setSearchConfigPostfix(string $value)
+ *
+ * @method string getTitlePhrase(mixed $default = false)
+ * @method kUnitConfig setTitlePhrase(string $value)
+ *
+ * @method int getItemType(mixed $default = false)
+ * @method kUnitConfig setItemType(int $value)
+ *
+ * @method Array getStatisticsInfo(mixed $default = false)
+ * @method kUnitConfig setStatisticsInfo(Array $value)
+ *
+ * @method string getViewMenuPhrase(mixed $default = false)
+ * @method kUnitConfig setViewMenuPhrase(string $value)
+ *
+ * @method string getCatalogTabIcon(mixed $default = false)
+ * @method kUnitConfig setCatalogTabIcon(string $value)
+ *
+ * @method bool getCacheModRewrite(mixed $default = false)
+ * @method kUnitConfig setCacheModRewrite(bool $value)
+ *
+ * @method kUnitConfig setParentTableKey(mixed $value)
+ *
+ * @method kUnitConfig setForeignKey(mixed $value)
+ *
+ * @method string getConstrain(mixed $default = false)
+ * @method kUnitConfig setConstrain(string $value)
+ *
+ * @method bool getAutoDelete(mixed $default = false)
+ * @method kUnitConfig setAutoDelete(bool $value)
+ *
+ * @method bool getAutoClone(mixed $default = false)
+ * @method kUnitConfig setAutoClone(bool $value)
+ *
+ * @method string getParentPrefix(mixed $default = false)
+ * @method kUnitConfig setParentPrefix(string $value)
+ *
+ * @method bool getCheckSimulatniousEdit(mixed $default = false)
+ * @method kUnitConfig setCheckSimulatniousEdit(bool $value)
+ *
+ * @method bool getPortalStyleEnv(mixed $default = false)
+ * @method kUnitConfig setPortalStyleEnv(bool $value)
+ *
+ * @method int getRewritePriority(mixed $default = false)
+ * @method kUnitConfig setRewritePriority(int $value)
+ *
+ * @method Array getRewriteListener(mixed $default = false)
+ * @method kUnitConfig setRewriteListener(Array $value)
+ *
+ * @method bool getForceDontLogChanges(mixed $default = false)
+ * @method kUnitConfig setForceDontLogChanges(bool $value)
+ *
+ * @method Array getUserProfileMapping(mixed $default = false)
+ * @method kUnitConfig setUserProfileMapping(Array $value)
+ *
+ * @method bool getUsePendingEditing(mixed $default = false)
+ * @method kUnitConfig setUsePendingEditing(bool $value)
+ *
+ * @method string getNavigationSelectClause(mixed $default = false)
+ * @method kUnitConfig setNavigationSelectClause(string $value)
+ *
+ * @method bool getPopulateMlFields(bool $default = false)
+ * @method kUnitConfig setPopulateMlFields(bool $value)
+ *
+ * @method bool getLogChanges(bool $default = false)
+ * @method kUnitConfig setLogChanges(bool $value)
+ *
+ * @method int getFileCount(bool $default = false)
+ * @method kUnitConfig setFileCount(int $value)
+ *
+ * @method int getImageCount(bool $default = false)
+ * @method kUnitConfig setImageCount(int $value)
+ *
+ * @method string getDownloadHelperClass(bool $default = false)
+ * @method kUnitConfig setDownloadHelperClass(string $value)
+ *
+ * @method string getSectionPrefix(bool $default = false)
+ * @method kUnitConfig setSectionPrefix(string $value)
+ *
+ * @method bool getSiteConfigProcessed(bool $default = false)
+ * @method kUnitConfig setSiteConfigProcessed(bool $value)
+ *
+ *
+ * @method Array getRegisterClasses(mixed $default = false)
+ * @method kUnitConfig setRegisterClasses(Array $value)
+ * @method kUnitConfig addRegisterClasses(Array $value, string $name = null)
+ * @method kUnitConfig removeRegisterClasses(string $name = null)
+ *
+ * @method Array getScheduledTasks(mixed $default = false)
+ * @method Array getScheduledTaskByName(string $name, mixed $default = false)
+ * @method kUnitConfig setScheduledTasks(Array $value)
+ * @method kUnitConfig addScheduledTasks(Array $value, string $name = null)
+ * @method kUnitConfig removeScheduledTasks(string $name = null)
+ *
+ * @method Array getTitlePresets(mixed $default = false)
+ * @method Array getTitlePresetByName(string $name, mixed $default = false)
+ * @method kUnitConfig setTitlePresets(Array $value)
+ * @method kUnitConfig addTitlePresets(Array $value, string $name = null)
+ * @method kUnitConfig removeTitlePresets(string $name = null)
+ *
+ * @method Array getSections(mixed $default = false)
+ * @method Array getSectionByName(string $name, mixed $default = false)
+ * @method kUnitConfig setSections(Array $value)
+ * @method kUnitConfig addSections(Array $value, string $name = null)
+ * @method kUnitConfig removeSections(string $name = null)
+ *
+ * @method Array getFields(mixed $default = false)
+ * @method Array getFieldByName(string $name, mixed $default = false)
+ * @method kUnitConfig setFields(Array $value)
+ * @method kUnitConfig addFields(Array $value, string $name = null)
+ * @method kUnitConfig removeFields(string $name = null)
+ *
+ * @method Array getVirtualFields(mixed $default = false)
+ * @method Array getVirtualFieldByName(string $name, mixed $default = false)
+ * @method kUnitConfig setVirtualFields(Array $value)
+ * @method kUnitConfig addVirtualFields(Array $value, string $name = null)
+ * @method kUnitConfig removeVirtualFields(string $name = null)
+ *
+ * @method Array getGrids(mixed $default = false)
+ * @method Array getGridByName(string $name, mixed $default = false)
+ * @method kUnitConfig setGrids(Array $value)
+ * @method kUnitConfig addGrids(Array $value, string $name = null)
+ * @method kUnitConfig removeGrids(string $name = null)
+ *
+ * @method Array getHooks(mixed $default = false)
+ * @method kUnitConfig setHooks(Array $value)
+ * @method kUnitConfig addHooks(Array $value, string $name = null)
+ * @method kUnitConfig removeHooks(string $name = null)
+ *
+ * @method Array getAggregateTags(mixed $default = false)
+ * @method kUnitConfig setAggregateTags(Array $value)
+ * @method kUnitConfig addAggregateTags(Array $value, string $name = null)
+ * @method kUnitConfig removeAggregateTags(string $name = null)
+ *
+ * @method Array getEditTabPresets(mixed $default = false)
+ * @method Array getEditTabPresetByName(string $name, mixed $default = false)
+ * @method kUnitConfig setEditTabPresets(Array $value)
+ * @method kUnitConfig addEditTabPresets(Array $value, string $name = null)
+ * @method kUnitConfig removeEditTabPresets(string $name = null)
+ *
+ * @method Array getSubItems(mixed $default = false)
+ * @method kUnitConfig setSubItems(Array $value)
+ * @method kUnitConfig addSubItems(string $value, string $name = null)
+ * @method kUnitConfig removeSubItems(string $name = null)
+ *
+ * @method Array getCustomFields(mixed $default = false)
+ * @method string getCustomFieldByName(string $name, mixed $default = false)
+ * @method kUnitConfig setCustomFields(Array $value)
+ * @method kUnitConfig addCustomFields(Array $value, string $name = null)
+ * @method kUnitConfig removeCustomFields(string $name = null)
+ *
+ * @method Array getClones(mixed $default = false)
+ * @method Array getCloneByName(string $name, mixed $default = false)
+ * @method kUnitConfig setClones(Array $value)
+ * @method kUnitConfig addClones(Array $value, string $name = null)
+ * @method kUnitConfig removeClones(string $name = null)
+ *
+ * @method Array getProcessPrefixes(mixed $default = false)
+ * @method kUnitConfig setProcessPrefixes(Array $value)
+ * @method kUnitConfig addProcessPrefixes(string $value, string $name = null)
+ * @method kUnitConfig removeProcessPrefixes(string $name = null)
+ *
+ * @method Array getForms(mixed $default = false)
+ * @method Array getFormByName(string $name, mixed $default = false)
+ * @method kUnitConfig setForms(Array $value)
+ * @method kUnitConfig addForms(Array $value, string $name = null)
+ * @method kUnitConfig removeForms(string $name = null)
+ *
+ * @method Array getReplacementTemplates(mixed $default = false)
+ * @method string getReplacementTemplateByName(string $name, mixed $default = false)
+ * @method kUnitConfig setReplacementTemplates(Array $value)
+ * @method kUnitConfig addReplacementTemplates(string $value, string $name = null)
+ * @method kUnitConfig removeReplacementTemplates(string $name = null)
+ *
+ * @method Array getItemPropertyMappings(mixed $default = false)
+ * @method string getItemPropertyMappingByName(string $name, mixed $default = false)
+ * @method kUnitConfig setItemPropertyMappings(Array $value)
+ * @method kUnitConfig addItemPropertyMappings(string $value, string $name = null)
+ * @method kUnitConfig removeItemPropertyMappings(string $name = null)
+ *
+ * @method Array getSectionAdjustments(mixed $default = false)
+ * @method mixed getSectionAdjustmentByName(string $name, mixed $default = false)
+ * @method kUnitConfig setSectionAdjustments(Array $value)
+ * @method kUnitConfig addSectionAdjustments(mixed $value, string $name = null)
+ * @method kUnitConfig removeSectionAdjustments(string $name = null)
+ *
+ * @method Array getImportKeys(mixed $default = false)
+ * @method kUnitConfig setImportKeys(Array $value)
+ * @method kUnitConfig addImportKeys(mixed $value, string $name = null)
+ * @method kUnitConfig removeImportKeys(string $name = null)
+ *
+ *
+ * @method Array getCalculatedFieldSpecials(mixed $default = Array ())
+ * @method Array getCalculatedFieldsBySpecial(mixed $special, mixed $default = Array ())
+ * @method kUnitConfig setCalculatedFieldsBySpecial(mixed $special, Array $value)
+ * @method kUnitConfig addCalculatedFieldsBySpecial(mixed $special, mixed $value, string $name = null)
+ * @method kUnitConfig removeCalculatedFieldsBySpecial(mixed $special, string $name = null)
+ *
+ * @method Array getAggregatedCalculatedFieldSpecials(mixed $default = Array ())
+ * @method Array getAggregatedCalculatedFieldsBySpecial(mixed $special, mixed $default = Array ())
+ * @method kUnitConfig setAggregatedCalculatedFieldsBySpecial(mixed $special, Array $value)
+ * @method kUnitConfig addAggregatedCalculatedFieldsBySpecial(mixed $special, mixed $value, string $name = null)
+ * @method kUnitConfig removeAggregatedCalculatedFieldsBySpecial(mixed $special, string $name = null)
+ *
+ * @method Array getListSQLSpecials(mixed $default = Array ())
+ * @method string getListSQLsBySpecial(mixed $special, mixed $default = Array ())
+ * @method kUnitConfig setListSQLsBySpecial(mixed $special, string $value)
+ * @method kUnitConfig removeListSQLsBySpecial(mixed $special, string $name = null)
+ *
+ * @method Array getListSortingSpecials(mixed $default = Array ())
+ * @method Array getListSortingsBySpecial(mixed $special, mixed $default = Array ())
+ * @method kUnitConfig setListSortingsBySpecial(mixed $special, Array $value)
+ * @method kUnitConfig addListSortingsBySpecial(mixed $special, mixed $value, string $name = null)
+ * @method kUnitConfig removeListSortingsBySpecial(mixed $special, string $name = null)
+ *
+ * @method Array getItemSQLSpecials(mixed $default = Array ())
+ * @method string getItemSQLsBySpecial(mixed $special, mixed $default = Array ())
+ * @method kUnitConfig setItemSQLsBySpecial(mixed $special, string $value)
+ * @method kUnitConfig removeItemSQLsBySpecial(mixed $special, string $name = null)
+ */
+class kUnitConfig {
+
+	/**
+	 * Unit config prefix
+	 *
+	 * @var string
+	 * @access protected
+	 */
+	protected $_prefix = '';
+
+	/**
+	 * Default unit config data
+	 *
+	 * @var Array
+	 * @access protected
+	 */
+	protected static $_defaults = Array (
+		'ItemClass' => Array ('class' => 'kDBItem', 'file' => '', 'build_event' => 'OnItemBuild'),
+		'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'),
+		'EventHandlerClass' => Array ('class' => 'kDBEventHandler', 'file' => '', 'build_event' => 'OnBuild'),
+		'TagProcessorClass' => Array ('class' => 'kDBTagProcessor', 'file' => '', 'build_event' => 'OnBuild'),
+
+		'AutoLoad' => true,
+
+		'QueryString' => Array (
+			1 => 'id',
+			2 => 'Page',
+			3 => 'PerPage',
+			4 => 'event',
+			5 => 'mode',
+		),
+
+		'ListSQLs' => Array (
+			'' => '	SELECT %1$s.* %2$s
+					FROM %1$s',
+		),
+
+		'Fields' => Array (),
+	);
+
+	/**
+	 * Word endings, that are suffixed with "es" instead of just "s" during pluralisation
+	 *
+	 * @var string
+	 * @access protected
+	 */
+	protected static $_singularEndingsRegExp = '/(x|s|z|sh|ch)$/';
+
+	/**
+	 * Words, that ends with es/s, but are always singulars
+	 *
+	 * @var string
+	 * @access protected
+	 */
+	protected static $_alwaysSingularRegExp = '/(class|LogChanges|ForceDontLogChanges|PopulateMlFields)$/i';
+
+	/**
+	 * Unit config data
+	 *
+	 * @var Array
+	 * @access protected
+	 */
+	protected $_data = Array ();
+
+	/**
+	 * Unit config settings that can have different values based on special
+	 *
+	 * @var Array
+	 * @access protected
+	 */
+	protected $_specialBased = Array (
+		'CalculatedFields', 'AggregatedCalculatedFields', 'ListSQLs', 'ListSortings', 'ItemSQLs',
+	);
+
+	/**
+	 * Unit config settings, that allow data to be added without a key
+	 *
+	 * @var Array
+	 * @access protected
+	 */
+	protected $_withoutKeys = Array (
+		'RegisterClasses', 'Hooks', 'AggregateTags'
+	);
+
+	/**
+	 * Dynamic method name, that was called
+	 *
+	 * @var string
+	 * @access protected
+	 */
+	protected $_currentMethodName = '';
+
+	/**
+	 * Arguments given to dynamic method name, that was called
+	 *
+	 * @var Array
+	 * @access protected
+	 */
+	protected $_currentArguments = Array ();
+
+	/**
+	 * Creates new instance of kUnitConfig class
+	 *
+	 * @param string $prefix
+	 * @param Array $defaults
+	 * @param bool $use_global_defaults
+	 * @access public
+	 */
+	public function __construct($prefix, $defaults = null, $use_global_defaults = true)
+	{
+		$this->_prefix = $prefix;
+
+		$merge_with = $use_global_defaults ? self::$_defaults : Array ();
+		$this->_data = array_merge($merge_with, isset($defaults) ? $defaults : Array ());
+	}
+
+	/**
+	 * Returns unit config prefix
+	 *
+	 * @return string
+	 * @access public
+	 */
+	public function getPrefix()
+	{
+		return $this->_prefix;
+	}
+
+	/**
+	 * Ensures, that only unit config data is saved when object is serialized
+	 *
+	 * @return Array
+	 * @access public
+	 */
+	public function __sleep()
+	{
+		return Array ('_prefix', '_data');
+	}
+
+	/**
+	 * Dumps unit config into a file
+	 *
+	 * @return void
+	 * @access public
+	 */
+	public function dump()
+	{
+		kUtil::print_r($this->_data, 'Unit Config', true);
+	}
+
+	/**
+	 * Returns unit config data in raw form
+	 *
+	 * @return Array
+	 * @access public
+	 */
+	public function getRaw()
+	{
+		return $this->_data;
+	}
+
+	/**
+	 * Processed dynamically created methods for changing unit config settings
+	 *
+	 * @param string $method_name
+	 * @param Array $arguments
+	 * @return mixed
+	 * @throws InvalidArgumentException
+	 * @throws RuntimeException
+	 * @access public
+	 */
+	public function __call($method_name, $arguments)
+	{
+//		=== regular ===
+//		get{SettingName}()
+//		set{SettingName}($value)
+//		add{SettingName}s(string $value, string $name = null)
+//		remove{SettingName}(string $name = null)
+
+//		=== by special ===
+//		get{SettingName}Specials()
+//		get{SettingName}sBySpecial($special)
+//		set{SettingName}BySpecial($special, $value)
+//		add{SettingName}sBySpecial(string $special, Array $value, string $name = null)
+//		remove{SettingName}sBySpecial(string $special, $name = null)
+
+		if ( !preg_match('/^(get|set|add|remove)(.*?)(BySpecial|Specials|ByName)*$/', $method_name, $regs) ) {
+			throw new RuntimeException('Unknown method <strong>' . __CLASS__ . '::' . $this->_currentMethodName . '</strong>');
+		}
+
+		$this->_currentMethodName = $method_name;
+		$this->_currentArguments = $arguments;
+		$to_call = '_process' . ucfirst($regs[1]);
+
+		return $this->$to_call($regs[2], isset($regs[3]) ? $regs[3] : '');
+	}
+
+	/**
+	 * Processes calls to "get*" methods
+	 *
+	 * @param string $setting_name
+	 * @param string $suffix
+	 * @return mixed
+	 * @access protected
+	 */
+	protected function _processGet($setting_name, $suffix = '')
+	{
+		$is_plural = $this->_isPluralSetting($setting_name);
+
+		if ( $suffix == 'BySpecial' && $is_plural ) {
+			// get{SettingName}BySpecial($special, $default = false)
+			$this->_verifyArguments(Array ('special'));
+
+			return $this->getSetting($setting_name, $this->_getDefaultValue(1, Array ()), $this->_currentArguments[0]);
+		}
+		elseif ( $suffix == 'Specials' && !$is_plural ) {
+			// get{SettingName}Specials($default = Array ())
+			$result = $this->getSetting($this->_getPlural($setting_name), $this->_getDefaultValue(0, Array ()));
+
+			return array_keys($result);
+		}
+		elseif ( $suffix == 'ByName' && !$is_plural ) {
+			$sub_key = $this->_currentArguments[0];
+			$result = $this->getSetting($this->_getPlural($setting_name), Array ());
+
+			return isset($result[$sub_key]) ? $result[$sub_key] : $this->_getDefaultValue(1, false);
+		}
+
+		// get{SettingName}($default = false)
+		return $this->getSetting($setting_name, $this->_getDefaultValue(0, false));
+	}
+
+	/**
+	 * Returns default value from given argument or false, when not passed
+	 *
+	 * @param int $arg_index
+	 * @param mixed $default
+	 * @return bool
+	 * @access protected
+	 */
+	protected function _getDefaultValue($arg_index, $default = false)
+	{
+		return isset($this->_currentArguments[$arg_index]) ? $this->_currentArguments[$arg_index] : $default;
+	}
+
+	/**
+	 * Processes calls to "set*" methods
+	 *
+	 * @param string $setting_name
+	 * @param string $suffix
+	 * @return kUnitConfig
+	 * @access protected
+	 */
+	protected function _processSet($setting_name, $suffix = '')
+	{
+		$is_plural = $this->_isPluralSetting($setting_name);
+
+		if ( $suffix == 'BySpecial' && $is_plural ) {
+			// set{SettingName}BySpecial($special, $value)
+			$this->_verifyArguments(Array ('special', 'value'));
+
+			return $this->setSetting($setting_name, $this->_currentArguments[1], $this->_currentArguments[0]);
+		}
+
+		// set{SettingName}($value)
+		$this->_verifyArguments(Array ('value'));
+
+		return $this->setSetting($setting_name, $this->_currentArguments[0]);
+	}
+
+	/**
+	 * Processes calls to "add*" method
+	 *
+	 * @param string $setting_name
+	 * @param string $suffix
+	 * @return kUnitConfig
+	 * @access protected
+	 * @throws InvalidArgumentException
+	 */
+	protected function _processAdd($setting_name, $suffix = '')
+	{
+		$arguments = $this->_currentArguments;
+
+		if ( !$this->_isPluralSetting($setting_name) ) {
+			throw new InvalidArgumentException('Setting "' . $setting_name . '" isn\'t plural');
+		}
+
+		if ( $suffix == 'BySpecial' ) {
+			// add{SettingName}BySpecial(string $special, string $value, string $name = null)
+			$this->_verifyArguments(Array ('special', 'value'));
+
+			if ( isset($arguments[2]) ) {
+				// add a single value
+				$this->_addToSetting($this->_getSingular($setting_name), $arguments[1], $arguments[2], $arguments[0]);
+			}
+			else {
+				// add multiple values
+				$this->_addToSetting($setting_name, $arguments[1], null, $arguments[0]);
+			}
+		}
+		else {
+			// add{SettingName}(string $value, string $name = null)
+			$this->_verifyArguments(Array ('value'));
+
+			if ( isset($arguments[1]) ) {
+				// add a single value
+				$this->_addToSetting($this->_getSingular($setting_name), $arguments[0], $arguments[1]);
+			}
+			else {
+				// add multiple value
+				$this->_addToSetting($setting_name, $arguments[0], null);
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Adds a value to a setting
+	 *
+	 * @param string $name
+	 * @param mixed $value
+	 * @param string $array_key
+	 * @param string $special
+	 * @return kUnitConfig
+	 * @throws InvalidArgumentException
+	 */
+	protected function _addToSetting($name, $value, $array_key, $special = null)
+	{
+		if ( $this->_isPluralSetting($name) ) {
+			// multiple values given - merge with current values
+			if ( !is_array($value) ) {
+				throw new InvalidArgumentException('Argument $value must be an array');
+			}
+
+			$result = $this->getSetting($name, Array (), $special);
+			$this->setSetting($name, array_merge($result, $value), $special);
+		}
+		else {
+			// single value given
+			$result = $this->getSetting($this->_getPlural($name), Array (), $special);
+
+			if ( $this->_isWithoutKeySetting($name) ) {
+				$result[] = $value;
+			}
+			else {
+				$result[$array_key] = $value;
+			}
+
+			$this->setSetting($this->_getPlural($name), $result, $special);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Processes calls to "remove*" method
+	 *
+	 * @param string $setting_name
+	 * @param string $suffix
+	 * @return kUnitConfig
+	 * @access protected
+	 * @throws InvalidArgumentException
+	 */
+	protected function _processRemove($setting_name, $suffix = '')
+	{
+		$arguments = $this->_currentArguments;
+
+		if ( !$this->_isPluralSetting($setting_name) ) {
+			throw new InvalidArgumentException('Setting "' . $setting_name . '" isn\'t plural');
+		}
+
+		if ( $suffix == 'BySpecial' ) {
+			// remove{SettingName}BySpecial(string $special, string $name = null)
+			$this->_verifyArguments(Array ('special'));
+
+			if ( isset($arguments[1]) ) {
+				// remove single value
+				$this->_removeFromSetting($this->_getSingular($setting_name), $arguments[1], $arguments[0]);
+			}
+			else {
+				// remove multiple value
+				$this->_removeFromSetting($setting_name, null, $arguments[0]);
+			}
+		}
+		else {
+			// remove{SettingName}(string $name = null)
+			if ( isset($arguments[0]) ) {
+				// remove single value
+				$this->_removeFromSetting($this->_getSingular($setting_name), $arguments[0]);
+			}
+			else {
+				// remove multiple values
+				$this->_removeFromSetting($setting_name, null);
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Removes a value from a setting
+	 *
+	 * @param string $name
+	 * @param string $array_key
+	 * @param string $special
+	 * @return kUnitConfig
+	 * @access protected
+	 * @throws RuntimeException
+	 */
+	protected function _removeFromSetting($name, $array_key = null, $special = null)
+	{
+		if ( $this->_isPluralSetting($name) ) {
+			// remove multiple values
+			if ( $this->getSetting($name, false, $special) !== false ) {
+				$this->setSetting($name, null, $special);
+			}
+		}
+		else {
+			// remove single value
+			if ( $this->_isWithoutKeySetting($name) ) {
+				throw new RuntimeException('Unable to change setting without key');
+			}
+
+			$result = $this->getSetting($this->_getPlural($name), false, $special);
+
+			if ( $result !== false ) {
+				unset($result[$array_key]);
+				$this->setSetting($this->_getPlural($name), $result, $special);
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Verifies argument count given
+	 *
+	 * @param Array $argument_names
+	 * @return void
+	 * @access protected
+	 * @throws InvalidArgumentException
+	 */
+	protected function _verifyArguments($argument_names)
+	{
+		if ( count($this->_currentArguments) < count($argument_names) ) {
+			throw new InvalidArgumentException('Method <strong>' . __CLASS__ . '::' . $this->_currentMethodName . '</strong> expects following arguments: <strong>$' . implode('</strong>, <strong>$', $argument_names) . '</strong>');
+		}
+	}
+
+	/**
+	 * Returns setting value by name and filter by special (if passed)
+	 *
+	 * @param string $name
+	 * @param mixed $default
+	 * @param string|kBase $special
+	 * @return mixed
+	 * @access public
+	 */
+	public function getSetting($name, $default = false, $special = null)
+	{
+		if ( in_array($name, $this->_specialBased) && isset($special) ) {
+			$use_special = $this->_guessSpecial($name, $special);
+
+			return isset($this->_data[$name][$use_special]) ? $this->_data[$name][$use_special] : $default;
+		}
+
+		return isset($this->_data[$name]) ? $this->_data[$name] : $default;
+	}
+
+	/**
+	 * Changes value of a setting
+	 *
+	 * @param string $name
+	 * @param mixed $value
+	 * @param string|kBase $special
+	 * @return kUnitConfig
+	 * @access public
+	 */
+	public function setSetting($name, $value, $special = null)
+	{
+		if ( in_array($name, $this->_specialBased) && isset($special) ) {
+			if ( !isset($this->_data[$name]) ) {
+				$this->_data[$name] = Array ();
+			}
+
+			$use_special = $this->_guessSpecial($name, $special);
+
+			if ( !isset($value) ) {
+				unset($this->_data[$name][$use_special]);
+			}
+			else {
+				$this->_data[$name][$use_special] = $value;
+			}
+		}
+		else {
+			if ( !isset($value) ) {
+				unset($this->_data[$name]);
+			}
+			else {
+				$this->_data[$name] = $value;
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Detects settings, that accept arrays
+	 *
+	 * @param string $name
+	 * @return bool
+	 * @access protected
+	 */
+	protected function _isPluralSetting($name)
+	{
+		if ( preg_match(self::$_alwaysSingularRegExp, $name) ) {
+			// simplified exceptions
+			return false;
+		}
+
+		return preg_match('/(es|s)$/', $name);
+	}
+
+	/**
+	 * Detects anonymous settings
+	 *
+	 * @param string $name
+	 * @return bool
+	 * @access protected
+	 */
+	protected function _isWithoutKeySetting($name)
+	{
+		return in_array($this->_getPlural($name), $this->_withoutKeys);
+	}
+
+	/**
+	 * Returns plural form given word
+	 *
+	 * @param string $name
+	 * @return string
+	 * @access protected
+	 */
+	protected function _getPlural($name)
+	{
+		return preg_match(self::$_singularEndingsRegExp, $name) ? $name . 'es' : $name . 's';
+	}
+
+	/**
+	 * Returns singular form of given word
+	 *
+	 * @param $name
+	 * @return mixed
+	 * @throws InvalidArgumentException
+	 */
+	protected function _getSingular($name)
+	{
+		if ( !$this->_isPluralSetting($name) ) {
+			throw new InvalidArgumentException('Setting "' . $name . '" isn\'t plural');
+		}
+
+		return preg_replace('/(es|s)$/', '', $name);
+	}
+
+	/**
+	 * Guesses special to be used by event
+	 *
+	 * @param string $setting_name
+	 * @param string|kBase $special
+	 * @return string
+	 * @access protected
+	 */
+	protected function _guessSpecial($setting_name, $special)
+	{
+		$use_special = $special instanceof kBase ? $special->Special : $special;
+		$value = $this->getSetting($setting_name, Array ());
+
+		return isset($value[$use_special]) ? $use_special : '';
+	}
+
+	/**
+	 * Adds new tab to existing edit tab preset
+	 *
+	 * @param string $preset_name
+	 * @param Array $value
+	 * @param string $name
+	 * @return kUnitConfig
+	 * @throws InvalidArgumentException
+	 * @access public
+	 */
+	public function addEditTabPresetTabs($preset_name, $value, $name = null)
+	{
+		$preset = $this->getEditTabPresetByName($preset_name, false);
+
+		if ( $preset === false ) {
+			throw new InvalidArgumentException('Edit tab preset "' . $preset_name . '" not defined in "' . $this->_prefix . '" unit config');
+		}
+
+		if ( isset($name) ) {
+			$preset[$name] = $value;
+		}
+		else {
+			$preset = array_merge($preset, $value);
+		}
+
+		$this->addEditTabPresets($preset, $preset_name);
+
+		return $this;
+	}
+
+	public function addGridFields($grid_name, $value, $name = null)
+	{
+		$grid = $this->getGridByName($grid_name, false);
+
+		if ( $grid === false ) {
+			throw new InvalidArgumentException('Grid "' . $grid_name . '" not defined in "' . $this->_prefix . '" unit config');
+		}
+
+		if ( isset($name) ) {
+			$grid['Fields'][$name] = $value;
+		}
+		else {
+			$grid['Fields'] = array_merge($grid['Fields'], $value);
+		}
+
+		$this->addGrids($grid, $grid_name);
+
+		return $this;
+	}
+
+	/**
+	 * Returns individual permission section
+	 *
+	 * @param string $name
+	 * @param Array $default
+	 * @return Array
+	 * @access public
+	 * @todo Rename setting to plural form and them remove this method
+	 */
+	public function getPermSectionByName($name, $default = Array ())
+	{
+		$perm_sections = $this->getPermSection(Array ());
+
+		return isset($perm_sections[$name]) ? $perm_sections[$name] : $default;
+	}
+
+	/**
+	 * Returns foreign key by given prefix
+	 *
+	 * @param string $parent_prefix
+	 * @param mixed $default
+	 * @return string|bool
+	 * @access public
+	 */
+	public function getForeignKey($parent_prefix = null, $default = false)
+	{
+		return $this->_getSettingByPrefix('ForeignKey', $parent_prefix, $default);
+	}
+
+	/**
+	 * Returns parent table key by given prefix
+	 *
+	 * @param string $parent_prefix
+	 * @param mixed $default
+	 * @return string|bool
+	 * @access public
+	 */
+	public function getParentTableKey($parent_prefix = null, $default = false)
+	{
+		return $this->_getSettingByPrefix('ParentTableKey', $parent_prefix, $default);
+	}
+
+	/**
+	 * Returns value of a setting by prefix (special workaround for non-special settings)
+	 *
+	 * @param string $name
+	 * @param string $prefix
+	 * @param mixed $default
+	 * @return mixed
+	 * @access protected
+	 */
+	protected function _getSettingByPrefix($name, $prefix = null, $default = false)
+	{
+		$value = $this->getSetting($name, $default);
+
+		if ( !is_array($value) || !isset($prefix) ) {
+			return $value;
+		}
+
+		return isset($value[$prefix]) ? $value[$prefix] : $default;
+	}
+
+	/**
+	 * Returns status field with option to get first field only
+	 *
+	 * @param bool $first_only
+	 * @param mixed $default
+	 * @return mixed
+	 * @access public
+	 */
+	public function getStatusField($first_only = false, $default = false)
+	{
+		$value = $this->getSetting('StatusField', $default);
+
+		return $first_only ? $value[0] : $value;
+	}
+}

Property changes on: branches/5.3.x/core/kernel/utility/unit_config.php
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Index: branches/5.3.x/core/kernel/utility/http_query.php
===================================================================
--- branches/5.3.x/core/kernel/utility/http_query.php	(revision 15697)
+++ branches/5.3.x/core/kernel/utility/http_query.php	(revision 15698)
@@ -1,824 +1,824 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kHTTPQuery extends Params {
 
 	/**
 	 * Cache of QueryString parameters
 	 * from config, that are represented
 	 * in environment variable
 	 *
 	 * @var Array
 	 */
 	protected $discoveredUnits = Array ();
 
 	/**
 	 * $_POST vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Post;
 
 	/**
 	 * $_GET vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Get;
 
 	/**
 	 * $_COOKIE vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Cookie;
 
 	/**
 	 * $_SERVER vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Server;
 
 	/**
 	 * $_ENV vars
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Env;
 
 	/**
 	 * Order in what write
 	 * all vars together in
 	 * the same array
 	 *
 	 * @var string
 	 */
 	var $Order;
 
 	/**
 	 * Uploaded files info
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	var $Files;
 
 	var $specialsToRemove = Array();
 
 	/**
 	 * SessionID is given via "sid" variable in query string
 	 *
 	 * @var bool
 	 */
 	var $_sidInQueryString = false;
 
 	/**
 	 * Trust information, provided by proxy
 	 *
 	 * @var bool
 	 */
 	protected $_trustProxy = false;
 
 	/**
 	 * Loads info from $_POST, $_GET and
 	 * related arrays into common place
 	 *
 	 * @param string $order
 	 * @access public
 	 */
 	public function __construct($order = 'CGPF')
 	{
 		parent::__construct();
 
 		$this->Order = $order;
 
 		if ( isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
 			// when AJAX request is made from jQuery, then create ajax variable,
 			// so any logic based in it (like redirects) will not break down
 			$_GET['ajax'] = 'yes';
 		}
 
 		$vars = kUtil::getConfigVars();
 
 		$this->_trustProxy = isset($vars['TrustProxy']) ? (bool)$vars['TrustProxy'] : false;
 	}
 
 	/**
 	 * Discovers unit form request and returns it's QueryString option on success
 	 *
 	 * @param string $prefix_special
 	 *
 	 * @return Array|bool
 	 * @access public
 	 */
 	public function discoverUnit($prefix_special)
 	{
 		list($prefix) = explode('.', $prefix_special);
 
 		$query_string = $this->getQueryString($prefix);
 
 		if ($query_string) {
 			// only units with QueryString option can be discovered
 			$this->discoveredUnits[$prefix_special] = $query_string;
 
 			return $query_string;
 		}
 
 		unset( $this->discoveredUnits[$prefix] );
 
 		return false;
 	}
 
 	/**
 	 * Returns units, passed in request
 	 *
 	 * @param bool $prefix_special_only
 	 * @return Array
 	 * @access protected
 	 */
 	public function getDiscoveredUnits($prefix_special_only = true)
 	{
 		return $prefix_special_only ? array_keys( $this->discoveredUnits ) : $this->discoveredUnits;
 	}
 
 	/**
 	 * Returns QueryMap for requested unit config.
 	 * In case if unit config is a clone, then get parent item's (from prefix) config to create clone
 	 *
 	 * @param string $prefix
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getQueryString($prefix)
 	{
-		$ret = $this->Application->getUnitOption($prefix, 'QueryString', Array ());
+		$ret = $this->Application->getUnitConfig($prefix)->getQueryString(Array ());
 
 		if ( !$ret && preg_match('/(.*?)-(.*)/', $prefix, $regs) ) {
 			// "#prefix" (new format), "prefix" (old format)
 			return $this->_getQueryString('#' . $regs[2]);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns query string (with safety check against missing prefixes)
 	 *
 	 * @param string $prefix
 	 * @return Array
 	 */
 	private function _getQueryString($prefix)
 	{
 		if ( $this->Application->prefixRegistred($prefix) ) {
-			return $this->Application->getUnitOption($prefix, 'QueryString');
+			return $this->Application->getUnitConfig($prefix)->getQueryString();
 		}
 
 		return substr($prefix, 0, 1) == '#' ? $this->_getQueryString( substr($prefix, 1) ) : Array ();
 	}
 
 	/**
 	 * Removes specials from request
 	 *
 	 * @param Array $array
 	 * @return Array
 	 * @access protected
 	 */
 	protected function _removeSpecials($array)
 	{
 		$ret = Array ();
 		$removed = false;
 
 		foreach ($this->specialsToRemove as $prefix_special => $flag) {
 			if ( $flag ) {
 				$removed = true;
 				list ($prefix, $special) = explode('.', $prefix_special, 2);
 
 				foreach ($array as $key => $val) {
 					$new_key = preg_match("/^" . $prefix . "[._]{1}" . $special . "(.*)/", $key, $regs) ? $prefix . $regs[1] : $key;
 					$ret[$new_key] = is_array($val) ? $this->_removeSpecials($val) : $val;
 				}
 			}
 		}
 
 		return $removed ? $ret : $array;
 	}
 
 	public function process()
 	{
 		$this->AddAllVars();
 		$this->removeSpecials();
 		ini_set('magic_quotes_gpc', 0);
 
 		$this->Application->UrlManager->LoadStructureTemplateMapping();
 
 		$this->AfterInit();
 	}
 
 	/**
 	 * All all requested vars to
 	 * common storage place
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function AddAllVars()
 	{
 		for ($i = 0; $i < strlen($this->Order); $i++) {
 			switch ($this->Order[$i]) {
 				case 'G':
 					$this->Get = $this->AddVars($_GET);
 					if ( array_key_exists('sid', $_GET) ) {
 						$this->_sidInQueryString = true;
 					}
 
 					$vars = $this->Application->processQueryString($this->Get(ENV_VAR_NAME));
 
 					if ( array_key_exists('sid', $vars) ) {
 						// used by Session::GetPassedSIDValue
 						$this->Get['sid'] = $vars['sid'];
 					}
 
 					$this->AddParams($vars);
 					break;
 
 				case 'P':
 					$this->Post = $this->AddVars($_POST);
 					$this->convertPostEvents();
 					$this->_processPostEnvVariables();
 					break;
 
 				case 'C':
 					$cookie_hasher = $this->Application->makeClass('kCookieHasher');
 					/* @var $cookie_hasher kCookieHasher */
 
 					$parsed_cookies = Array ();
 
 					foreach ($_COOKIE as $cookie_name => $encrypted_value) {
 						$parsed_cookies[$cookie_name] = $cookie_hasher->decrypt($cookie_name, $encrypted_value);
 					}
 
 					$this->Cookie = $this->AddVars($parsed_cookies);
 					break;
 
 				/*case 'E';
 					$this->Env = $this->AddVars($_ENV, false); //do not strip slashes!
 					break;
 
 				case 'S';
 					$this->Server = $this->AddVars($_SERVER, false); //do not strip slashes!
 					break;*/
 
 				case 'F';
 					$this->convertFiles();
 					$this->Files = $this->MergeVars($_FILES); // , false); //do not strip slashes!
 					break;
 			}
 		}
 	}
 
 	/**
 	 * Allow POST variables, that names were transformed by PHP ("." replaced with "_") to
 	 * override variables, that were virtually created through environment variable parsing
 	 *
 	 */
 	function _processPostEnvVariables()
 	{
 		$passed = $this->Get('passed');
 		if ( !$passed ) {
 			return;
 		}
 
 		$passed = explode(',', $passed);
 		foreach ($passed as $prefix_special) {
 			if ( strpos($prefix_special, '.') === false ) {
 				continue;
 			}
 
 			list ($prefix, $special) = explode('.', $prefix_special);
 			$query_map = $this->getQueryString($prefix);
 			$post_prefix_special = $prefix . '_' . $special;
 
 			foreach ($query_map as $var_name) {
 				if ( array_key_exists($post_prefix_special . '_' . $var_name, $this->Post) ) {
 					$this->Set($prefix_special . '_' . $var_name, $this->Post[$post_prefix_special . '_' . $var_name]);
 				}
 			}
 		}
 	}
 
 	/**
 	 * Removes requested specials from all request variables
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function removeSpecials()
 	{
 		$this->specialsToRemove = $this->Get('remove_specials');
 
 		if ( $this->specialsToRemove ) {
 			foreach ($this->specialsToRemove as $prefix_special => $flag) {
 				if ( $flag && strpos($prefix_special, '.') === false ) {
 					unset($this->specialsToRemove[$prefix_special]);
 					trigger_error('Incorrect usage of "<strong>remove_specials[' . $prefix_special . ']</strong>" field (no special found)', E_USER_NOTICE);
 				}
 			}
 
 			$this->_Params = $this->_removeSpecials($this->_Params);
 		}
 	}
 
 	/**
 	 * Finishes initialization of kHTTPQuery class
 	 *
 	 * @return void
 	 * @access protected
 	 * @todo: only uses build-in rewrite listeners, when cache is build for the first time
 	 */
 	protected function AfterInit()
 	{
 		$rewrite_url = $this->Get('_mod_rw_url_');
 
 		if ( $this->Application->RewriteURLs() || $rewrite_url ) {
 			// maybe call onafterconfigread here
 
 			$this->Application->UrlManager->initRewrite();
 
 			if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->profileStart('url_parsing', 'Parsing <b>MOD_REWRITE</b> url');
 				$this->Application->UrlManager->rewrite->parseRewriteURL();
 				$description = 'Parsing <b>MOD_REWRITE</b> url (template: <b>' . $this->Get('t') . '</b>)';
 				$this->Application->Debugger->profileFinish('url_parsing', $description);
 			}
 			else {
 				$this->Application->UrlManager->rewrite->parseRewriteURL();
 			}
 
 			if ( !$rewrite_url && $this->rewriteRedirectRequired() ) {
 				// rewrite url is missing (e.g. not a script from tools folder)
 				$url_params = $this->getRedirectParams();
 
 				// no idea about how to check, that given template require category to be passed with it, so pass anyway
 				$url_params['pass_category'] = 1;
 				$url_params['response_code'] = 301; // Moved Permanently
 
 				trigger_error('Non mod-rewrite url "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>" used', E_USER_NOTICE);
 				$this->Application->Redirect('', $url_params);
 			}
 		}
 		else {
 			$this->Application->VerifyThemeId();
 			$this->Application->VerifyLanguageId();
 		}
 
 		if ( !$this->Application->isAdmin && $this->Application->ConfigValue('ForceCanonicalUrls') ) {
 			$template = $this->Application->GetVar('t');
 			$seo_template = $this->Application->getSeoTemplate($template);
 
 			if ( $seo_template && $seo_template != $template ) {
 				$url_params = $this->getRedirectParams();
 				$url_params['response_code'] = 301;
 
 				trigger_error('Request url "<strong>' . $_SERVER['REQUEST_URI'] . '</strong>" points directly to physical template', E_USER_NOTICE);
 				$this->Application->Redirect($seo_template, $url_params);
 			}
 		}
 	}
 
 	/**
 	 * Checks, that non-rewrite url was visited and it's automatic rewrite is required
 	 *
 	 * @return bool
 	 */
 	function rewriteRedirectRequired()
 	{
 		$redirect_conditions = Array (
 			!$this->IsHTTPSRedirect(), // not https <-> http redirect
 			!$this->refererIsOurSite(), // referer doesn't match ssl path or non-ssl domain (same for site domains)
 			!defined('GW_NOTIFY'), // not in payment gateway notification script
 			preg_match('/[\/]{0,1}index.php[\/]{0,1}/', $_SERVER['PHP_SELF']), // "index.php" was visited
 			$this->Get('t') != 'index', // not on index page
 		);
 
 		$perform_redirect = true;
 
 		foreach ($redirect_conditions as $redirect_condition) {
 			$perform_redirect = $perform_redirect && $redirect_condition;
 
 			if (!$perform_redirect) {
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * This is redirect from https to http or via versa
 	 *
 	 * @return bool
 	 */
 	function IsHTTPSRedirect()
 	{
 		$http_referer = array_key_exists('HTTP_REFERER', $_SERVER) ? $_SERVER['HTTP_REFERER'] : false;
 
 		return (
 			( PROTOCOL == 'https://' && preg_match('#http:\/\/#', $http_referer) )
 			||
 			( PROTOCOL == 'http://' && preg_match('#https:\/\/#', $http_referer) )
 		);
 	}
 
 	/**
 	 * Checks, that referer is out site
 	 *
 	 * @return bool
 	 */
 	function refererIsOurSite()
 	{
 		if ( !array_key_exists('HTTP_REFERER', $_SERVER) ) {
 			// no referer -> don't care what happens
 			return false;
 		}
 
 		$site_helper = $this->Application->recallObject('SiteHelper');
 		/* @var $site_helper SiteHelper */
 
 		$found = false;
 		$http_referer = $_SERVER['HTTP_REFERER'];
 		preg_match('/^(.*?):\/\/(.*?)(\/|$)/', $http_referer, $regs); // 1 - protocol, 2 - domain
 
 		if ($regs[1] == 'https') {
 			$found = $site_helper->getDomainByName('SSLUrl', $http_referer) > 0;
 
 			if (!$found) {
 				// check if referer starts with our ssl url
 				$ssl_url = $this->Application->ConfigValue('SSL_URL');
 				$found = $ssl_url && preg_match('/^' . preg_quote($ssl_url, '/') . '/', $http_referer);
 			}
 		}
 		else {
 			$found = $site_helper->getDomainByName('DomainName', $regs[2]) > 0;
 
 			if (!$found) {
 				$found = $regs[2] == DOMAIN;
 			}
 		}
 
 		return $found;
 	}
 
 	function convertFiles()
 	{
 		if ( !$_FILES ) {
 			return ;
 		}
 
 		$tmp = Array ();
 		$file_keys = Array ('error', 'name', 'size', 'tmp_name', 'type');
 
 		foreach ($_FILES as $file_name => $file_info) {
 			if ( is_array($file_info['error']) ) {
 				$tmp[$file_name] = $this->getArrayLevel($file_info['error'], $file_name);
 			}
 			else {
 				$normal_files[$file_name] = $file_info;
 			}
 		}
 
 		if ( !$tmp ) {
 			return ;
 		}
 
 		$files = $_FILES;
 		$_FILES = Array ();
 
 		foreach ($tmp as $prefix => $prefix_files) {
 			$anchor =& $_FILES;
 			foreach ($prefix_files['keys'] as $key) {
 				$anchor =& $anchor[$key];
 			}
 
 			foreach ($prefix_files['value'] as $field_name) {
 				unset($inner_anchor, $copy);
 				$work_copy = $prefix_files['keys'];
 
 				foreach ($file_keys as $file_key) {
 					$inner_anchor =& $files[$prefix][$file_key];
 
 					if ( isset($copy) ) {
 						$work_copy = $copy;
 					}
 					else {
 						$copy = $work_copy;
 					}
 
 					array_shift($work_copy);
 					foreach ($work_copy as $prefix_file_key) {
 						$inner_anchor =& $inner_anchor[$prefix_file_key];
 					}
 
 					$anchor[$field_name][$file_key] = $inner_anchor[$field_name];
 				}
 			}
 		}
 		// keys: img_temp, 0, values: LocalPath, ThumbPath
 	}
 
 	function getArrayLevel(&$level, $prefix='')
 	{
 		$ret['keys'] = $prefix ? Array($prefix) : Array();
 		$ret['value'] = Array();
 
 		foreach($level as $level_key => $level_value)
 		{
 			if( is_array($level_value) )
 			{
 				$ret['keys'][] = $level_key;
 				$tmp = $this->getArrayLevel($level_value);
 
 				$ret['keys'] = array_merge($ret['keys'], $tmp['keys']);
 				$ret['value'] = array_merge($ret['value'], $tmp['value']);
 			}
 			else
 			{
 				$ret['value'][] = $level_key;
 			}
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Overwrites GET events with POST events in case if they are set and not empty
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function convertPostEvents()
 	{
 		$events = $this->Get('events', Array ());
 		/* @var $events Array */
 
 		if ( is_array($events) ) {
 			$events = array_filter($events);
 
 			foreach ($events as $prefix_special => $event_name) {
 				$this->Set($prefix_special . '_event', $event_name);
 			}
 		}
 	}
 
 	function finalizeParsing($passed = Array())
 	{
 		if (!$passed) {
 			return;
 		}
 
 		foreach ($passed as $passed_prefix) {
 			$this->discoverUnit($passed_prefix); // from mod-rewrite url parsing
 		}
 
 		$this->Set('passed', implode(',', $this->getDiscoveredUnits()));
 	}
 
 	/**
 	 * Saves variables from array specified
 	 * into common variable storage place
 	 *
 	 * @param Array $array
 	 * @param bool $strip_slashes
 	 * @return Array
 	 * @access private
 	 */
 	function AddVars($array, $strip_slashes = true)
 	{
 		if ( $strip_slashes ) {
 			$array = $this->StripSlashes($array);
 		}
 
 		foreach ($array as $key => $value) {
 			$this->Set($key, $value);
 		}
 
 		return $array;
 	}
 
 	function MergeVars($array, $strip_slashes = true)
 	{
 		if ( $strip_slashes ) {
 			$array = $this->StripSlashes($array);
 		}
 
 		foreach ($array as $key => $value_array) {
 			// $value_array is an array too
 			$this->_Params = kUtil::array_merge_recursive($this->_Params, Array ($key => $value_array));
 		}
 
 		return $array;
 	}
 
 	function StripSlashes($array)
 	{
 		static $magic_quotes = null;
 
 		if (!isset($magic_quotes)) {
 			$magic_quotes = get_magic_quotes_gpc();
 		}
 
 		foreach ($array as $key => $value) {
 			if (is_array($value)) {
 				$array[$key] = $this->StripSlashes($value);
 			}
 			else {
 				if ($magic_quotes) {
 					$value = stripslashes($value);
 				}
 
 				if (!$this->Application->isAdmin) {
 					$value = htmlspecialchars($value, null, CHARSET);
 				}
 
 				$array[$key] = $value;
 			}
 		}
 
 		return $array;
 	}
 
 	/**
 	 * Returns all $_GET array excluding system parameters, that are not allowed to be passed through generated urls
 	 *
 	 * @param bool $access_error Method is called during no_permission, require login, session expiration link preparation
 	 * @return Array
 	 */
 	function getRedirectParams($access_error = false)
 	{
 		$vars = $this->Get;
 		$unset_vars = Array (ENV_VAR_NAME, 'rewrite', '_mod_rw_url_', 'Action');
 
 		if (!$this->_sidInQueryString) {
 			$unset_vars[] = 'sid';
 		}
 
 		// remove system variables
 		foreach ($unset_vars as $var_name) {
 			if (array_key_exists($var_name, $vars)) {
 				unset($vars[$var_name]);
 			}
 		}
 
 		if ($access_error) {
 			// place 1 of 2 (also in UsersEventHandler::OnSessionExpire)
 			$vars = $this->_removePassThroughVariables($vars);
 		}
 
 		// transform arrays
 		return $this->_transformArrays($vars);
 	}
 
 	/**
 	 * Removes all pass_though variables from redirect params
 	 *
 	 * @param Array $url_params
 	 * @return Array
 	 */
 	function _removePassThroughVariables($url_params)
 	{
 		$pass_through = array_key_exists('pass_through', $url_params) ? $url_params['pass_through'] : '';
 		if (!$pass_through) {
 			return $url_params;
 		}
 
 		$pass_through = explode(',', $pass_through . ',pass_through');
 		foreach ($pass_through as $pass_through_var) {
 			unset($url_params[$pass_through_var]);
 		}
 
 		$url_params['no_pass_through'] = 1; // this way kApplication::HREF won't add them again
 
 		return $url_params;
 	}
 
 	function _transformArrays($array, $level_prefix = '')
 	{
 		$ret = Array ();
 		foreach ($array as $var_name => $var_value) {
 			$new_var_name = $level_prefix ? $level_prefix . '[' . $var_name . ']' : $var_name;
 
 			if (is_array($var_value)) {
 				$ret = array_merge($ret, $this->_transformArrays($var_value, $new_var_name));
 			}
 			else {
 				$ret[$new_var_name] = $var_value;
 			}
 		}
 
 		return $ret;
 	}
 
 	function writeRequestLog($filename)
 	{
 		$log_file = (defined('RESTRICTED') ? RESTRICTED : FULL_PATH) . '/' . $filename;
 
 		if ( is_writable(dirname($log_file)) ) {
 			$fp = fopen($log_file, 'a');
 
 			if ( $fp ) {
 				$session = $this->Application->recallObject('Session');
 				/* @var $session Session */
 
 				$user_id = $session->GetField('PortalUserId');
 				$admin_mark = $this->Application->isAdmin ? 'ADMIN' : 'FRONT';
 
 				$data = '[' . date('D M d H:i:s Y') . '] ' . $admin_mark . '; ip: ' . $this->getClientIp() . '; user_id: ' . $user_id . '; sid: ' . $this->Application->GetSID() . '; request: ' . "\n";
 				if ( $this->Get ) {
 					$data .= "_GET:\n" . print_r($this->Get, true);
 				}
 
 				if ( $this->Post ) {
 					$data .= "_POST:\n" . print_r($this->Post, true);
 				}
 
 				if ( $this->Cookie ) {
 					$data .= "_COOKIE:\n" . print_r($this->Cookie, true);
 				}
 				$data .= str_repeat('=', 100) . "\n";
 
 				fwrite($fp, $data);
 				fclose($fp);
 			}
 			else {
 				trigger_error('Request Log directory not writable', E_USER_WARNING);
 			}
 		}
 		else {
 			trigger_error('Request Log directory not writable', E_USER_WARNING);
 		}
 	}
 
 	/**
 	 * Checks, that url is empty
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function isEmptyUrl()
 	{
 		if ( $this->Application->RewriteURLs() ) {
 			return !$this->Get('_mod_rw_url_');
 		}
 
 		return !count($this->Get);
 	}
 
 	/**
 	 * Returns the client IP address.
 	 *
 	 * @return string The client IP address
 	 * @access public
 	 */
 	public function getClientIp()
 	{
 		if ( $this->_trustProxy ) {
 			if ( array_key_exists('HTTP_CLIENT_IP', $_SERVER) ) {
 				return $_SERVER['HTTP_CLIENT_IP'];
 			}
 
 			if ( array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) ) {
 				$client_ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
 
 				foreach ($client_ip as $ip_address) {
 					$clean_ip_address = trim($ip_address);
 
 					if ( false !== filter_var($clean_ip_address, FILTER_VALIDATE_IP) ) {
 						return $clean_ip_address;
 					}
 				}
 
 				return '';
 			}
 		}
 
 		return $_SERVER['REMOTE_ADDR'];
 	}
 }
Index: branches/5.3.x/core/kernel/utility/event.php
===================================================================
--- branches/5.3.x/core/kernel/utility/event.php	(revision 15697)
+++ branches/5.3.x/core/kernel/utility/event.php	(revision 15698)
@@ -1,444 +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.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	final class kEvent extends kBase {
 
 		/**
 		 * Event finished working succsessfully
 		 *
 		 */
 		const erSUCCESS = 0;
 
 		/**
 		 * Event finished working, but result is unsuccsessfull
 		 *
 		 */
 		const erFAIL = -1;
 
 		/**
 		 * Event experienced FATAL error - no hooks should continue!
 		 *
 		 */
 		const erFATAL = -2;
 
 		/**
 		 * Event failed on internal permission checking (user has no permission)
 		 *
 		 */
 		const erPERM_FAIL = -3;
 
 		/**
 		 * Event requested to stop processing (don't parse templates)
 		 *
 		 */
 		const erSTOP = -4;
 
 		/**
 		 * Reference to event, that created given event
 		 *
 		 * @var kEvent
 		 * @access public
 		 */
 		public $MasterEvent;
 
 		/**
 		 * Event name
 		 *
 		 * @var string
 		 * @access public
 		 */
 		public $Name;
 
 		/**
 		 * Don't execute hooks, before event processing
 		 *
 		 * @var bool
 		 * @access public
 		 */
 		public $SkipBeforeHooks = false;
 
 		/**
 		 * Don't execute hooks, after event processing
 		 *
 		 * @var bool
 		 * @access public
 		 */
 		public $SkipAfterHooks = false;
 
 		/**
 		 * Perform redirect after event processing.
 		 * Redirect after event processing allows to prevent same event being present in resulting url.
 		 * Also could contain template name, that needs to be shown after redirect.
 		 *
 		 * @var mixed
 		 * @access public
 		 */
 		public $redirect = true;
 
 		/**
 		 * Params, used during redirect url building after event successful processing
 		 *
 		 * @var bool
 		 * @access private
 		 */
 		private $redirectParams = Array ();
 
 		/**
 		 * PHP file to redirect to. Defaults to "index.php"
 		 *
 		 * @var string
 		 * @access public
 		 */
 		public $redirectScript = null;
 
 		/**
 		 * Event processing status
 		 *
 		 * @var int
 		 * @access public
 		 */
 		public $status = kEvent::erSUCCESS;
 
 		/**
 		 * Event parameters
 		 * Usually indicate, how particular event should be processed.
 		 *
 		 * @var Array
 		 * @access private
 		 */
 		private $specificParams = Array ();
 
 		/**
 		 * Pseudo class used, to create object, based on event contents
 		 *
 		 * @var string
 		 * @access private
 		 */
 		private $pseudoClass = '';
 
 		/**
 		 * Create event from given prefix, special, name and specific params.
 		 * Parameter $params could be be an an array with following  keys: "prefix", "special" (optional), "name".
 		 * Parameter $params could be a string in format: "prefix:name" or "prefix.special:name".
 		 *
 		 * @param mixed $params
 		 * @param Array $specific_params event specific params (none by default)
 		 * @return kEvent
 		 * @access public
 		 */
 		public function __construct($params = Array(), $specific_params = null)
 		{
 			parent::__construct();
 
 			if ($params) {
 				if ( is_array($params) ) {
 					$prefix = isset($params['prefix']) ? $params['prefix'] : false;
 					$special = isset($params['special']) ? $params['special'] : false;
 
 					if ($prefix) {
 						$this->Init($prefix, $special);
 					}
 
 					$this->Name = isset($params['name']) ? $params['name'] : '';
 				}
 				elseif ( is_string($params) ) {
 					if (preg_match('/([^.:]*)[.]{0,1}([^:]*):(.*)/', $params, $regs)) {
 						$prefix = $regs[1];
 						$special = $regs[2];
 
 						if ($prefix) {
 							$this->Init($prefix, $special);
 						}
 
 						$this->Name = $regs[3];
 					}
 					else {
 						throw new Exception('Invalid event string: "<strong>' . $params . '</strong>". Should be in "prefix[.special]:OnEvent" format');
 					}
 				}
 			}
 
 			if ( isset($specific_params) ) {
 				$this->specificParams = $specific_params;
 			}
 		}
 
 		/**
 		 * Returns joined prefix and special if any
 		 *
 		 * @param bool $from_submit if true, then joins prefix & special by "_", uses  "." otherwise
 		 * @return string
 		 * @access public
 		 */
 		public function getPrefixSpecial($from_submit = false)
 		{
 			if (!$from_submit) {
 				return parent::getPrefixSpecial();
 			}
 
 			return rtrim($this->Prefix . '_' . $this->Special, '_');
 		}
 
 		/**
 		 * Sets event parameter
 		 *
 		 * @param string $name
 		 * @param mixed $value
 		 * @access public
 		 */
 		public function setEventParam($name,$value)
 		{
 			$this->specificParams[$name] = $value;
 		}
 
 		/**
 		 * Returns event parameter by name (supports digging)
 		 *
 		 * @param string $name
 		 * @return mixed
 		 * @access public
 		 */
 		public function getEventParam($name)
 		{
 			$args = func_get_args();
 
 			if (count($args) > 1) {
 				kUtil::array_unshift_ref($args, $this->specificParams);
 
 				return call_user_func_array('getArrayValue', $args); // getArrayValue($this->specificParams, $name);
 			}
 
 			return array_key_exists($name, $this->specificParams) ? $this->specificParams[$name] : false;
 		}
 
 		/**
 		 * Returns all event parameters
 		 *
 		 * @return Array
 		 * @access public
 		 */
 		public function getEventParams()
 		{
 			return $this->specificParams;
 		}
 
 		/**
 		 * Set's pseudo class that differs from
 		 * the one specified in $Prefix
 		 *
 		 * @param string $appendix
 		 * @access public
 		 */
 		public function setPseudoClass($appendix)
 		{
 			$this->pseudoClass = $this->Prefix . $appendix;
 		}
 
 		/**
 		 * Performs event initialization
 		 * Also sets pseudo class same $prefix
 		 *
 		 * @param string $prefix
 		 * @param string $special
 		 * @access public
 		 */
 		public function Init($prefix, $special)
 		{
 			$this->pseudoClass = $prefix;
 
 			parent::Init($prefix, $special);
 		}
 
 		/**
 		 * Returns object used in event
 		 *
 		 * @param Array $params
 		 * @return kDBBase
 		 * @access public
 		 */
 		public function getObject(array $params = Array())
 		{
 			if ( !$this->Application->hasObject($this->prefixSpecial) ) {
 				$top_event = $this;
 
 				// when OnSave calls OnPreSave in first line, then this would make sure OnSave is used
 				while ( is_object($top_event->MasterEvent) ) {
 					$top_event = $top_event->MasterEvent;
 				}
 
 				$params['parent_event'] = $top_event;
 			}
 
 			return $this->Application->recallObject($this->prefixSpecial, $this->pseudoClass, $params);
 		}
 
 		/**
 		 * Executes given event in context of current event
 		 * Sub-event gets this event in "kEvent::MasterEvent" attribute.
 		 * Sub-event execution results (status and redirect* properties) are copied back to current event.
 		 *
 		 * @param string $name name of callable event (optionally could contain prefix_special as well)
 		 * @see kEvent::MasterEvent
 		 * @todo Will overwrite master event data with called event data, which makes 'parent_event' useless in most cases
 		 */
 		public function CallSubEvent($name)
 		{
 			if ( strpos($name, ':') === false ) {
 				// PrefixSpecial not specified -> use from current event
 				$name = $this->getPrefixSpecial() . ':' . $name;
 			}
 
 			$child_event = new kEvent($name);
 			$child_event->copyFrom($this, true);
 
 			$this->Application->HandleEvent($child_event);
 			$this->copyFrom($child_event);
 			$this->specificParams = $child_event->specificParams;
 		}
 
 		/**
 		 * Allows to copy data between events
 		 *
 		 * @param kEvent $source_event
 		 * @param bool $inherit
 		 * @access public
 		 */
 		public function copyFrom($source_event, $inherit = false)
 		{
 			if ( $inherit ) {
 				$this->MasterEvent = $source_event;
 			}
 			else {
 				$this->status = $source_event->status;
 			}
 
 			$this->redirect = $source_event->redirect;
 			$this->redirectParams = $source_event->redirectParams;
 			$this->redirectScript = $source_event->redirectScript;
 			$this->specificParams = $source_event->specificParams;
 		}
 
 		/**
 		 * Returns all redirect parameters
 		 *
 		 * @return Array
 		 * @access public
 		 */
 		public function getRedirectParams()
 		{
 			return $this->redirectParams;
 		}
 
 		/**
 		 * Returns redirect parameter
 		 *
 		 * @param string $name
 		 * @return mixed
 		 * @access public
 		 */
 		public function getRedirectParam($name)
 		{
 			return array_key_exists($name, $this->redirectParams) ? $this->redirectParams[$name] : false;
 		}
 
 		/**
 		 * Set's redirect param for event
 		 *
 		 * @param string $name
 		 * @param string $value
 		 * @access public
 		 */
 		public function SetRedirectParam($name, $value)
 		{
 			$this->redirectParams[$name] = $value;
 		}
 
 		/**
 		 * Allows to merge passed redirect params hash with existing ones
 		 *
 		 * @param Array $params
 		 * @param bool $append
 		 * @access public
 		 */
 		public function setRedirectParams($params, $append = true)
 		{
 			if ( $append ) {
 				// append new parameters to parameters set before
 				$params = kUtil::array_merge_recursive($this->redirectParams, $params);
 			}
 
 			$this->redirectParams = $params;
 		}
 
 		/**
 		 * Allows to tell if this event was called some how (e.g. subevent, hook) from event requested
 		 *
 		 * @param string $event_key event key in format [prefix[.special]:]event_name
 		 * @return bool
 		 * @access public
 		 */
 		public function hasAncestor($event_key)
 		{
 			if ( strpos($event_key, ':') === false ) {
 				$event_key = $this->getPrefixSpecial() . ':' . $event_key;
 			}
 
 			return $this->Application->EventManager->eventRunning($event_key);
 		}
 
 		/**
 		 * Returns permission section associated with event
 		 *
 		 * @return string
 		 * @access public
 		 */
 		public function getSection()
 		{
 			$perm_section = $this->getEventParam('PermSection');
-			if ($perm_section) {
+
+			if ( $perm_section ) {
 				return $perm_section;
 			}
 
 			// 1. get section by current top_prefix
 			$top_prefix = $this->getEventParam('top_prefix');
-			if ($top_prefix == false) {
+
+			if ( $top_prefix == false ) {
 				$top_prefix = $this->Application->GetTopmostPrefix($this->Prefix, true);
 				$this->setEventParam('top_prefix', $top_prefix);
 			}
-			$section = $this->Application->getUnitOption($top_prefix.'.main', 'PermSection');
+
+			$section = $this->Application->getUnitConfig($top_prefix)->getPermSectionByName('main');
 
 			// 2. check if this section has perm_prefix mapping to other prefix
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section);
-			if ($section_data && isset($section_data['perm_prefix']) && $section_data['perm_prefix'] != $top_prefix) {
+
+			if ( $section_data && isset($section_data['perm_prefix']) && $section_data['perm_prefix'] != $top_prefix ) {
 				$this->setEventParam('top_prefix', $section_data['perm_prefix']);
-				$section = $this->Application->getUnitOption($section_data['perm_prefix'].'.main', 'PermSection');
+				$section = $this->Application->getUnitConfig($section_data['perm_prefix'])->getPermSectionByName('main');
 			}
 
-			if (!$section) {
+			if ( !$section ) {
 				throw new Exception('Permission <strong>section</strong> not specified for prefix <strong>' . $top_prefix . '</strong>');
 			}
 
 			return $section;
 		}
 
 		public function __toString()
 		{
 			return $this->getPrefixSpecial() . ':' . $this->Name;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/kernel/utility/unit_config_reader.php
===================================================================
--- branches/5.3.x/core/kernel/utility/unit_config_reader.php	(revision 15697)
+++ branches/5.3.x/core/kernel/utility/unit_config_reader.php	(revision 15698)
@@ -1,1019 +1,1009 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kUnitConfigReader extends kBase implements kiCacheable {
 
 	/**
 	 * Configs reader
 	 *
-	 * @var Array
+	 * @var Array|kUnitConfig[]
 	 * @access private
 	 */
 	var $configData = Array();
+
 	var $configFiles = Array();
 
 	var $CacheExpired = false;
 
 	var $prefixFiles = array();
 
 	var $ProcessAllConfigs = false;
 	var $FinalStage = false;
 	var $StoreCache = false;
 	var $AfterConfigProcessed = array();
 
 	/**
 	 * Escaped directory separator for using in regular expressions
 	 *
 	 * @var string
 	 */
 	var $_directorySeparator = '';
 
 	/**
 	 * Regular expression for detecting module folder
 	 *
 	 * @var string
 	 */
 	var $_moduleFolderRegExp = '';
 
 	/**
 	 * Folders to skip during unit config search
 	 *
 	 * @var Array
 	 */
 	var $_skipFolders = Array ('CVS', '.svn', 'admin_templates', 'libchart');
 
 	/**
 	 * Creates instance of unit config reader
 	 *
 	 */
 	public function __construct()
 	{
 		parent::__construct();
 
 		$this->_directorySeparator = preg_quote(DIRECTORY_SEPARATOR);
 
 		$editor_path = explode('/', trim(EDITOR_PATH, '/'));
 		$this->_skipFolders[] = array_pop($editor_path); // last of cmseditor folders
 
 		$this->_moduleFolderRegExp = '#' . $this->_directorySeparator . '(core|modules' . $this->_directorySeparator . '.*?)' . $this->_directorySeparator . '#';
 	}
 
 	/**
 	 * Sets data from cache to object
 	 *
 	 * @param Array $data
 	 * @access public
 	 */
 	public function setFromCache(&$data)
 	{
 		$this->prefixFiles = $data['ConfigReader.prefixFiles'];
 	}
 
 	/**
 	 * Gets object data for caching
 	 *
 	 * @access public
 	 * @return Array
 	 */
 	public function getToCache()
 	{
 		return Array (
 			'ConfigReader.prefixFiles' => $this->prefixFiles,
 		);
 	}
 
 	function scanModules($folderPath, $cache = true)
 	{
 		if (defined('IS_INSTALL') && IS_INSTALL && !defined('FORCE_CONFIG_CACHE')) {
 			// disable config caching during installation
 			$cache = false;
 		}
 
 		if ($cache) {
 			$restored = $this->Application->cacheManager->LoadUnitCache();
 
 			if ($restored) {
 				if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) {
 					$this->Application->Debugger->appendHTML('UnitConfigReader: Restoring Cache');
 				}
 
 				return;
 			}
 		}
 
 		if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) {
 			$this->Application->Debugger->appendHTML('UnitConfigReader: Generating Cache');
 		}
 
 		$this->ProcessAllConfigs = true;
 
 		$this->includeConfigFiles($folderPath, $cache);
 		$this->ParseConfigs();
 
 		// tell AfterConfigRead to store cache if needed
 		// can't store it here because AfterConfigRead needs ability to change config data
 		$this->StoreCache = $cache;
 
 		if ( !$this->Application->InitDone ) {
 			// scanModules is called multiple times during installation process
 			$this->Application->InitManagers();
 
 			// get build-in rewrite listeners ONLY to be able to parse mod-rewrite url when unit config cache is missing
 			$this->retrieveCollections();
 			$this->_sortRewriteListeners();
 		}
 
 		$this->Application->cacheManager->applyDelayedUnitProcessing();
 	}
 
 	function findConfigFiles($folderPath, $level = 0)
 	{
 		// if FULL_PATH = "/" ensure, that all "/" in $folderPath are not deleted
 		$reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/';
 		$folderPath = preg_replace($reg_exp, '', $folderPath, 1); // this make sense, since $folderPath may NOT contain FULL_PATH
 
 		$base_folder = FULL_PATH . $folderPath . DIRECTORY_SEPARATOR;
 		$sub_folders = glob($base_folder . '*', GLOB_ONLYDIR);
 		if (!$sub_folders) {
 			return ;
 		}
 
 		if ($level == 0) {
 			// don't scan Front-End themes because of extensive directory structure
 			$sub_folders = array_diff($sub_folders, Array ($base_folder . 'themes', $base_folder . 'tools'));
 		}
 
 		foreach ($sub_folders as $full_path) {
 			$sub_folder = substr($full_path, strlen($base_folder));
 
 			if (in_array($sub_folder, $this->_skipFolders)) {
 				continue;
 			}
 
 			if (preg_match('/^\./', $sub_folder)) {
 				// don't scan ".folders"
 				continue;
 			}
 
 			$config_name = $this->getConfigName($folderPath . DIRECTORY_SEPARATOR . $sub_folder);
 
 			if (file_exists(FULL_PATH . $config_name)) {
 				$this->configFiles[] = $config_name;
 			}
 
 			$this->findConfigFiles($full_path, $level + 1);
 		}
 	}
 
 	function includeConfigFiles($folderPath, $cache = true)
 	{
 		$this->Application->refreshModuleInfo();
 
 		if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 			$data = $this->Application->getCache('master:config_files', false, $cache ? CacheSettings::$unitCacheRebuildTime : 0);
 		}
 		else {
 			$data = $this->Application->getDBCache('config_files', $cache ? CacheSettings::$unitCacheRebuildTime : 0);
 		}
 
 		if ( $data ) {
 			$this->configFiles = unserialize($data);
 
 			if ( !defined('DBG_VALIDATE_CONFIGS') && !DBG_VALIDATE_CONFIGS ) {
 				shuffle($this->configFiles);
 			}
 		}
 		else {
 			$this->findConfigFiles(FULL_PATH . DIRECTORY_SEPARATOR . 'core'); // search from core directory
 			$this->findConfigFiles($folderPath); // search from modules directory
 
 			if ( $cache ) {
 				if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 					$this->Application->setCache('master:config_files', serialize($this->configFiles));
 				}
 				else {
 					$this->Application->setDBCache('config_files', serialize($this->configFiles));
 				}
 			}
 		}
 
 		foreach ($this->configFiles as $filename) {
 			$prefix = $this->PreloadConfigFile($filename);
 
 			if (!$prefix) {
 				throw new Exception('Prefix not defined in config file <strong>' . $filename . '</strong>');
 			}
 		}
 
 		if ($cache) {
 			unset($this->configFiles);
 		}
 	}
 
 	/**
 	 * Process all read config files - called ONLY when there is no cache!
 	 *
 	 */
 	function ParseConfigs()
 	{
 		// 1. process normal configs
-		$prioritized_configs = array();
+		$prioritized_configs = Array ();
+
 		foreach ($this->configData as $prefix => $config) {
-			if (isset($config['ConfigPriority'])) {
-				$prioritized_configs[$prefix] = $config['ConfigPriority'];
+			if ( $config->getConfigPriority() !== false ) {
+				$prioritized_configs[$prefix] = $config->getConfigPriority();
 				continue;
 			}
+
 			$this->parseConfig($prefix);
 		}
 
 		foreach ($this->configData as $prefix => $config) {
 			$this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix');
 			$clones = $this->postProcessConfig($prefix, 'Clones', 'prefix');
 		}
 
 		// 2. process prioritized configs
 		asort($prioritized_configs);
 		foreach ($prioritized_configs as $prefix => $priority) {
 			$this->parseConfig($prefix);
 		}
 	}
 
 	function AfterConfigRead($store_cache = null)
 	{
 //		if (!$this->ProcessAllConfigs) return ;
 		$this->FinalStage = true;
 		foreach ($this->configData as $prefix => $config) {
 			$this->runAfterConfigRead($prefix);
 		}
 
 		if ( !isset($store_cache) ) {
 			// $store_cache not overridden -> use global setting
 			$store_cache = $this->StoreCache;
 		}
 
-		if ($store_cache || (defined('IS_INSTALL') && IS_INSTALL)) {
+		if ( $store_cache || (defined('IS_INSTALL') && IS_INSTALL) ) {
 			// cache is not stored during install, but dynamic clones should be processed in any case
 			$this->processDynamicClones();
 			$this->retrieveCollections();
 		}
 
-		if ($store_cache) {
+		if ( $store_cache ) {
 			$this->_sortRewriteListeners();
 
 			$this->Application->HandleEvent(new kEvent('adm:OnAfterCacheRebuild'));
 
 			$this->Application->cacheManager->UpdateUnitCache();
 
-			if (defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_VALIDATE_CONFIGS') && DBG_VALIDATE_CONFIGS) {
+			if ( defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_VALIDATE_CONFIGS') && DBG_VALIDATE_CONFIGS ) {
 				// validate configs here to have changes from OnAfterConfigRead hooks to prefixes
 				foreach ($this->configData as $prefix => $config) {
-					if (!isset($config['TableName'])) continue;
+					if ( !$config->getTableName() ) {
+						continue;
+					}
+
 					$this->ValidateConfig($prefix);
 				}
 			}
 		}
 	}
 
 	/**
 	 * Sort rewrite listeners according to RewritePriority (non-prioritized listeners goes first)
 	 *
 	 */
 	function _sortRewriteListeners()
 	{
 		$listeners = Array ();
 		$prioritized_listeners = Array ();
 
 		// process non-prioritized listeners
 		foreach ($this->Application->RewriteListeners as $prefix => $listener_data) {
 			if ($listener_data['priority'] === false) {
 				$listeners[$prefix] = $listener_data;
 			}
 			else {
 				$prioritized_listeners[$prefix] = $listener_data['priority'];
 			}
 		}
 
 		// process prioritized listeners
 		asort($prioritized_listeners, SORT_NUMERIC);
 		foreach ($prioritized_listeners as $prefix => $priority) {
 			$listeners[$prefix] = $this->Application->RewriteListeners[$prefix];
 		}
 
 		$this->Application->RewriteListeners = $listeners;
 	}
 
 	/**
 	 * Re-reads all configs
 	 *
 	 */
 	function ReReadConfigs()
 	{
 		// don't reset prefix file, since file scanning could slow down the process
 		$prefix_files_backup = $this->prefixFiles;
 		$this->Application->cacheManager->EmptyUnitCache();
 		$this->prefixFiles = $prefix_files_backup;
 
 		// parse all configs
 		$this->ProcessAllConfigs = true;
 		$this->AfterConfigProcessed = Array ();
 		$this->includeConfigFiles(MODULES_PATH, false);
 		$this->ParseConfigs();
 		$this->AfterConfigRead(false);
 		$this->processDynamicClones();
 
 		// don't call kUnitConfigReader::retrieveCollections since it
 		// will overwrite what we already have in kApplication class instance
 	}
 
 	/**
 	 * Process clones, that were defined via OnAfterConfigRead event
 	 *
 	 */
 	function processDynamicClones()
 	{
 		$new_clones = Array();
 		foreach ($this->configData as $prefix => $config) {
 			$clones = $this->postProcessConfig($prefix, 'Clones', 'prefix');
 
 			if ($clones) {
 				$new_clones = array_merge($new_clones, $clones);
 			}
 		}
 
 		// execute delayed methods for cloned unit configs
 		$this->Application->cacheManager->applyDelayedUnitProcessing();
 
 		// call OnAfterConfigRead for cloned configs
 		$new_clones = array_unique($new_clones);
 		foreach ($new_clones as $prefix) {
 			$this->runAfterConfigRead($prefix);
 		}
 	}
 
 	/**
 	 * Process all collectible unit config options here to also catch ones, defined from OnAfterConfigRead events
 	 *
 	 */
 	function retrieveCollections()
 	{
 		foreach ($this->configData as $prefix => $config) {
 			// collect replacement templates
-			if (array_key_exists('ReplacementTemplates', $config) && $config['ReplacementTemplates']) {
-				$this->Application->ReplacementTemplates = array_merge($this->Application->ReplacementTemplates, $config['ReplacementTemplates']);
+			if ( $config->getReplacementTemplates() ) {
+				$this->Application->ReplacementTemplates = array_merge($this->Application->ReplacementTemplates, $config->getReplacementTemplates());
 			}
 
 			// collect rewrite listeners
-			if (array_key_exists('RewriteListener', $config) && $config['RewriteListener']) {
-				$rewrite_listeners = $config['RewriteListener'];
+			if ( $config->getRewriteListener() ) {
+				$rewrite_listeners = $config->getRewriteListener();
 
-				if (!is_array($rewrite_listeners)) {
+				if ( !is_array($rewrite_listeners) ) {
 					// when one method is used to build and parse url
 					$rewrite_listeners = Array ($rewrite_listeners, $rewrite_listeners);
 				}
 
 				foreach ($rewrite_listeners as $index => $rewrite_listener) {
-					if (strpos($rewrite_listener, ':') === false) {
+					if ( strpos($rewrite_listener, ':') === false ) {
 						$rewrite_listeners[$index] = $prefix . '_EventHandler:' . $rewrite_listener;
 					}
 				}
 
-				$rewrite_priority = array_key_exists('RewritePriority', $config) ? $config['RewritePriority'] : false;
+				$rewrite_priority = $config->getRewritePriority();
 
 				$this->Application->RewriteListeners[$prefix] = Array ('listener' => $rewrite_listeners, 'priority' => $rewrite_priority);
 			}
 		}
 	}
 
 	/**
 	 * Register nessasary classes
 	 * This method should only process the data which is cached!
 	 *
 	 * @param string $prefix
 	 * @access private
 	 */
 	function parseConfig($prefix)
 	{
 		$this->parseClasses($prefix);
 		$this->parseScheduledTasks($prefix);
 		$this->parseHooks($prefix);
 		$this->parseAggregatedTags($prefix);
 	}
 
 	protected function parseClasses($prefix)
 	{
-		$config =& $this->configData[$prefix];
+		$config = $this->configData[$prefix];
 		$register_classes = $this->getClasses($prefix);
 
 		foreach ($register_classes as $class_info) {
 			$this->Application->registerClass(
 				$class_info['class'],
-				$config['BasePath'] . DIRECTORY_SEPARATOR . $class_info['file'],
+				$config->getBasePath() . DIRECTORY_SEPARATOR . $class_info['file'],
 				$class_info['pseudo']
 			);
 
 			if ( isset($class_info['build_event']) && $class_info['build_event'] ) {
 				$this->Application->delayUnitProcessing('registerBuildEvent', Array ($class_info['pseudo'], $class_info['build_event']));
 			}
 		}
 	}
 
 	protected function parseScheduledTasks($prefix)
 	{
-		$config =& $this->configData[$prefix];
+		$config = $this->configData[$prefix];
 
-		if ( !isset($config['ScheduledTasks']) || !$config['ScheduledTasks'] ) {
+		if ( !$config->getScheduledTasks() ) {
 			return ;
 		}
 
-		$scheduled_tasks = $config['ScheduledTasks'];
+		$scheduled_tasks = $config->getScheduledTasks();
 
 		foreach ($scheduled_tasks as $short_name => $scheduled_task_info) {
 			$event_status = array_key_exists('Status', $scheduled_task_info) ? $scheduled_task_info['Status'] : STATUS_ACTIVE;
-			$this->Application->delayUnitProcessing('registerScheduledTask', Array ( $short_name, $config['Prefix'] . ':' . $scheduled_task_info['EventName'], $scheduled_task_info['RunSchedule'], $event_status ));
+			$this->Application->delayUnitProcessing('registerScheduledTask', Array ( $short_name, $config->getPrefix() . ':' . $scheduled_task_info['EventName'], $scheduled_task_info['RunSchedule'], $event_status ));
 		}
 	}
 
 	protected function parseHooks($prefix)
 	{
-		$config =& $this->configData[$prefix];
+		$config = $this->configData[$prefix];
 
-		if ( !isset($config['Hooks']) || !$config['Hooks'] ) {
-			return ;
+		if ( !$config->getHooks() ) {
+			return;
 		}
 
-		$hooks = $config['Hooks'];
+		$hooks = $config->getHooks();
 
 		foreach ($hooks as $hook) {
-			if ( isset($config['ParentPrefix']) && ($hook['HookToPrefix'] == $config['ParentPrefix']) ) {
-				trigger_error('Depricated Hook Usage [prefix: <strong>' . $config['Prefix'] . '</strong>; do_prefix: <strong>' . $hook['DoPrefix'] . '</strong>] use <strong>#PARENT#</strong> as <strong>HookToPrefix</strong> value, where HookToPrefix is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE);
+			if ( $config->getParentPrefix() && ($hook['HookToPrefix'] == $config->getParentPrefix()) ) {
+				trigger_error('Deprecated Hook Usage [prefix: <strong>' . $config->getPrefix() . '</strong>; do_prefix: <strong>' . $hook['DoPrefix'] . '</strong>] use <strong>#PARENT#</strong> as <strong>HookToPrefix</strong> value, where HookToPrefix is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE);
 			}
 
-			if ($hook['HookToPrefix'] == '') {
+			if ( $hook['HookToPrefix'] == '' ) {
 				// new: set hooktoprefix to current prefix if not set
-				$hook['HookToPrefix'] = $config['Prefix'];
+				$hook['HookToPrefix'] = $config->getPrefix();
 			}
 
-			if ( isset($config['ParentPrefix']) ) {
+			if ( $config->getParentPrefix() ) {
 				// new: allow to set hook to parent prefix what ever it is
-				if ($hook['HookToPrefix'] == '#PARENT#') {
-					$hook['HookToPrefix'] = $config['ParentPrefix'];
+				if ( $hook['HookToPrefix'] == '#PARENT#' ) {
+					$hook['HookToPrefix'] = $config->getParentPrefix();
 				}
 
-				if ($hook['DoPrefix'] == '#PARENT#') {
-					$hook['DoPrefix'] = $config['ParentPrefix'];
+				if ( $hook['DoPrefix'] == '#PARENT#' ) {
+					$hook['DoPrefix'] = $config->getParentPrefix();
 				}
 			}
-			elseif ($hook['HookToPrefix'] == '#PARENT#' || $hook['DoPrefix'] == '#PARENT#') {
+			elseif ( $hook['HookToPrefix'] == '#PARENT#' || $hook['DoPrefix'] == '#PARENT#' ) {
 				// we need parent prefix but it's not set !
 				continue;
 			}
 
 			$hook_events = (array)$hook['HookToEvent'];
-			$do_prefix = $hook['DoPrefix'] == '' ? $config['Prefix'] : $hook['DoPrefix'];
+			$do_prefix = $hook['DoPrefix'] == '' ? $config->getPrefix() : $hook['DoPrefix'];
 
 			foreach ($hook_events as $hook_event) {
 				$hook_event = $hook['HookToPrefix'] . '.' . $hook['HookToSpecial'] . ':' . $hook_event;
 				$do_event = $do_prefix . '.' . $hook['DoSpecial'] . ':' . $hook['DoEvent'];
 
 				$this->Application->delayUnitProcessing('registerHook', Array ($hook_event, $do_event, $hook['Mode'], $hook['Conditional']));
 			}
 		}
 	}
 
 	protected function parseAggregatedTags($prefix)
 	{
-		$config =& $this->configData[$prefix];
-		$aggregated_tags = isset($config['AggregateTags']) ? $config['AggregateTags'] : Array ();
+		$config = $this->configData[$prefix];
+		$aggregated_tags = $config->getAggregateTags();
+
+		if ( !$aggregated_tags ) {
+			return;
+		}
 
 		foreach ($aggregated_tags as $aggregate_tag) {
-			if ( isset($config['ParentPrefix']) ) {
-				if ($aggregate_tag['AggregateTo'] == $config['ParentPrefix']) {
-					trigger_error('Depricated Aggregate Tag Usage [prefix: <b>'.$config['Prefix'].'</b>; AggregateTo: <b>'.$aggregate_tag['AggregateTo'].'</b>] use <b>#PARENT#</b> as <b>AggregateTo</b> value, where AggregateTo is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE);
+			if ( $config->getParentPrefix() ) {
+				if ( $aggregate_tag['AggregateTo'] == $config->getParentPrefix() ) {
+					trigger_error('Deprecated Aggregate Tag Usage [prefix: <b>' . $config->getPrefix() . '</b>; AggregateTo: <b>' . $aggregate_tag['AggregateTo'] . '</b>] use <b>#PARENT#</b> as <b>AggregateTo</b> value, where AggregateTo is same as ParentPrefix', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE);
 				}
 
-				if ($aggregate_tag['AggregateTo'] == '#PARENT#') {
-					$aggregate_tag['AggregateTo'] = $config['ParentPrefix'];
+				if ( $aggregate_tag['AggregateTo'] == '#PARENT#' ) {
+					$aggregate_tag['AggregateTo'] = $config->getParentPrefix();
 				}
 			}
 
-			$aggregate_tag['LocalPrefix'] = $config['Prefix'];
+			$aggregate_tag['LocalPrefix'] = $config->getPrefix();
 			$this->Application->delayUnitProcessing('registerAggregateTag', Array ($aggregate_tag));
 		}
 	}
 
 	function ValidateConfig($prefix)
 	{
 		global $debugger;
 
-		$config =& $this->configData[$prefix];
+		$config = $this->configData[$prefix];
 
-		$tablename = $config['TableName'];
+		$table_name = $config->getTableName();
 		$float_types = Array ('float', 'double', 'numeric');
 
-		$table_found = $this->Conn->Query('SHOW TABLES LIKE "'.$tablename.'"');
-		if (!$table_found) {
+		$table_found = $this->Conn->Query('SHOW TABLES LIKE "' . $table_name . '"');
+		if ( !$table_found ) {
 			// config present, but table missing, strange
 			kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1);
-			$debugger->appendHTML("<b class='debug_error'>Config Warning: </b>Table <strong>$tablename</strong> missing, but prefix <b>".$config['Prefix']."</b> requires it!");
+			$debugger->appendHTML("<b class='debug_error'>Config Warning: </b>Table <strong>$table_name</strong> missing, but prefix <b>" . $prefix . "</b> requires it!");
 			$debugger->WarningCount++;
 
-			return ;
+			return;
 		}
 
-		$res = $this->Conn->Query('DESCRIBE '.$tablename);
-		$config_link = $debugger->getFileLink(FULL_PATH.$this->prefixFiles[$config['Prefix']], 1, $config['Prefix']);
+		$res = $this->Conn->Query('DESCRIBE ' . $table_name);
+		$config_link = $debugger->getFileLink(FULL_PATH . $this->prefixFiles[$prefix], 1, $prefix);
 
 		$error_messages = Array (
 			'field_not_found' => 'Field <strong>%s</strong> exists in the database, but <strong>is not defined</strong> in config',
 			'default_missing' => 'Default value for field <strong>%s</strong> not set in config',
 			'not_null_error1' => 'Field <strong>%s</strong> is NOT NULL in the database, but is not configured as not_null', // or required',
 			'not_null_error2' => 'Field <strong>%s</strong> is described as NOT NULL in config, but <strong>does not have DEFAULT value</strong>',
 			'not_null_error3' => 'Field <strong>%s</strong> is described as <strong>NOT NULL in config</strong>, but is <strong>NULL in db</strong>',
 			'invalid_default' => '<strong>Default value</strong> for field %s<strong>%s</strong> not sync. to db (in config = %s, in db = %s)',
 			'date_column_not_null_error' => 'Field <strong>%s</strong> must be NULL in config and database, since it contains date',
 			'user_column_default_error' => 'Field <strong>%s</strong> must be have NULL as default value, since it holds user id',
 			'type_missing' => '<strong>Type definition</strong> for field <strong>%s</strong> missing in config',
 			'virtual_type_missing' => '<strong>Type definition</strong> for virtual field <strong>%s</strong> missing in config',
 			'virtual_default_missing' => 'Default value for virtual field <strong>%s</strong> not set in config',
 			'virtual_not_null_error' => 'Virtual field <strong>%s</strong> cannot be not null, since it doesn\'t exist in database',
 			'invalid_calculated_field' => 'Calculated field <strong>%s</strong> is missing corresponding virtual field',
 		);
 
 		$config_errors = Array ();
-		$tablename = preg_replace('/^'.preg_quote(TABLE_PREFIX, '/').'(.*)/', '\\1', $tablename); // remove table prefix
+		$fields = $config->getFields();
+		$table_name = preg_replace('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', '\\1', $table_name); // remove table prefix
 
-		// validate unit config field declaration in relation to database table structure
-		foreach ($res as $field) {
-			$f_name = $field['Field'];
+		if ( $fields ) {
+			// validate unit config field declaration in relation to database table structure
+			foreach ($res as $field) {
+				$f_name = $field['Field'];
 
-			if (getArrayValue($config, 'Fields')) {
-				if (preg_match('/l[\d]+_[\w]/', $f_name)) {
+				if ( preg_match('/l[\d]+_[\w]/', $f_name) ) {
 					// skip multilingual fields
 					continue;
 				}
 
-				if (!array_key_exists ($f_name, $config['Fields'])) {
+				if ( !array_key_exists($f_name, $fields) ) {
 					$config_errors[] = sprintf($error_messages['field_not_found'], $f_name);
 				}
 				else {
 					$db_default = $field['Default'];
 
-					if (is_numeric($db_default)) {
+					if ( is_numeric($db_default) ) {
 						$db_default = preg_match('/[\.,]/', $db_default) ? (float)$db_default : (int)$db_default;
 					}
 
 					$default_missing = false;
-					$options = $config['Fields'][$f_name];
+					$options = $fields[$f_name];
 					$not_null = isset($options['not_null']) && $options['not_null'];
 					$formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false;
 
-					if (!array_key_exists('default', $options)) {
+					if ( !array_key_exists('default', $options) ) {
 						$config_errors[] = sprintf($error_messages['default_missing'], $f_name);
 						$default_missing = true;
 					}
 
-					if ($field['Null'] != 'YES') {
+					if ( $field['Null'] != 'YES' ) {
 						// field is NOT NULL in database (MySQL5 for null returns "NO", but MySQL4 returns "")
-						if ( $f_name != $config['IDField'] && !isset($options['not_null']) /*&& !isset($options['required'])*/ ) {
+						if ( $f_name != $config->getIDField() && !isset($options['not_null']) /*&& !isset($options['required'])*/ ) {
 							$config_errors[] = sprintf($error_messages['not_null_error1'], $f_name);
 						}
-						if ($not_null && !isset($options['default']) ) {
+						if ( $not_null && !isset($options['default']) ) {
 							$config_errors[] = sprintf($error_messages['not_null_error2'], $f_name);
 						}
 					}
-					elseif ($not_null) {
+					elseif ( $not_null ) {
 						$config_errors[] = sprintf($error_messages['not_null_error3'], $f_name);
 					}
 
-					if (($formatter == 'kDateFormatter') && $not_null) {
+					if ( ($formatter == 'kDateFormatter') && $not_null ) {
 						$config_errors[] = sprintf($error_messages['date_column_not_null_error'], $f_name);
 					}
 
 					// columns, holding userid should have NULL as default value
-					if (array_key_exists('type', $options) && !$default_missing) {
+					if ( array_key_exists('type', $options) && !$default_missing ) {
 						// both type and default value set
 
-						if (preg_match('/ById$/', $f_name) && $options['default'] !== null) {
+						if ( preg_match('/ById$/', $f_name) && $options['default'] !== null ) {
 							$config_errors[] = sprintf($error_messages['user_column_default_error'], $f_name);
 						}
 					}
 
-					if (!array_key_exists('type', $options)) {
+					if ( !array_key_exists('type', $options) ) {
 						$config_errors[] = sprintf($error_messages['type_missing'], $f_name);
 					}
 
-					if (!$default_missing && ($field['Type'] != 'text')) {
+					if ( !$default_missing && ($field['Type'] != 'text') ) {
 						if ( is_null($db_default) && $not_null ) {
 							$db_default = $options['type'] == 'string' ? '' : 0;
 						}
 
-						if ($f_name == $config['IDField'] && $options['type'] != 'string' && $options['default'] !== 0) {
+						if ( $f_name == $config->getIDField() && $options['type'] != 'string' && $options['default'] !== 0 ) {
 							$config_errors[] = sprintf($error_messages['invalid_default'], '<span class="debug_error">IDField</span> ', $f_name, $this->varDump($options['default']), $this->varDump($field['Default']));
 						}
-						else if (((string)$options['default'] != '#NOW#') && ($db_default !== $options['default']) && !in_array($options['type'], $float_types)) {
+						else if ( ((string)$options['default'] != '#NOW#') && ($db_default !== $options['default']) && !in_array($options['type'], $float_types) ) {
 							$config_errors[] = sprintf($error_messages['invalid_default'], '', $f_name, $this->varDump($options['default']), $this->varDump($db_default));
 						}
 					}
 				}
 			}
 		}
 
 		// validate virtual fields
-		if ( array_key_exists('VirtualFields', $config) ) {
-			foreach ($config['VirtualFields'] as $f_name => $options) {
-				if (!array_key_exists('type', $options)) {
+		if ( $config->getVirtualFields()  ) {
+			foreach ($config->getVirtualFields() as $f_name => $options) {
+				if ( !array_key_exists('type', $options) ) {
 					$config_errors[] = sprintf($error_messages['virtual_type_missing'], $f_name);
 				}
 
-				if (array_key_exists('not_null', $options)) {
+				if ( array_key_exists('not_null', $options) ) {
 					$config_errors[] = sprintf($error_messages['virtual_not_null_error'], $f_name);
 				}
 
-				if (!array_key_exists('default', $options)) {
+				if ( !array_key_exists('default', $options) ) {
 					$config_errors[] = sprintf($error_messages['virtual_default_missing'], $f_name);
 				}
 			}
 		}
 
 		// validate calculated fields
-		if ( array_key_exists('CalculatedFields', $config) ) {
-			foreach ($config['CalculatedFields'] as $special => $calculated_fields) {
-				foreach ($calculated_fields as $calculated_field => $calculated_field_expr) {
-					if ( !isset($config['VirtualFields'][$calculated_field]) ) {
+		if ( $config->getCalculatedFieldSpecials() ) {
+			$virtual_fields = $config->getVirtualFields();
+
+			foreach ($config->getCalculatedFieldSpecials() as $special) {
+				foreach ($config->getCalculatedFieldsBySpecial($special) as $calculated_field => $calculated_field_expr) {
+					if ( !isset($virtual_fields[$calculated_field]) ) {
 						$config_errors[] = sprintf($error_messages['invalid_calculated_field'], $calculated_field);
 					}
 				}
 			}
 
 			$config_errors = array_unique($config_errors);
 		}
 
-		if ($config_errors) {
-			$error_prefix = '<strong class="debug_error">Config Error'.(count($config_errors) > 1 ? 's' : '').': </strong> for prefix <strong>'.$config_link.'</strong> ('.$tablename.') in unit config:<br />';
-			$config_errors = $error_prefix.'&nbsp;&nbsp;&nbsp;'.implode('<br />&nbsp;&nbsp;&nbsp;', $config_errors);
+		if ( $config_errors ) {
+			$error_prefix = '<strong class="debug_error">Config Error' . (count($config_errors) > 1 ? 's' : '') . ': </strong> for prefix <strong>' . $config_link . '</strong> (' . $table_name . ') in unit config:<br />';
+			$config_errors = $error_prefix . '&nbsp;&nbsp;&nbsp;' . implode('<br />&nbsp;&nbsp;&nbsp;', $config_errors);
 
 			kUtil::safeDefine('DBG_RAISE_ON_WARNINGS', 1);
 			$debugger->appendHTML($config_errors);
 			$debugger->WarningCount++;
 		}
 	}
 
 	function varDump($value)
 	{
 		return '<strong>'.var_export($value, true).'</strong> of '.gettype($value);
 	}
 
 	function postProcessConfig($prefix, $config_key, $dst_prefix_var)
 	{
-		$main_config =& $this->configData[$prefix];
-		$sub_configs = isset($main_config[$config_key]) && $main_config[$config_key] ? $main_config[$config_key] : Array ();
+		$main_config = $this->configData[$prefix];
+		$sub_configs = $main_config->getSetting($config_key);
+
 		if ( !$sub_configs ) {
 			return Array ();
 		}
-		unset($main_config[$config_key]);
 
-		$processed = array();
-		foreach ($sub_configs as $sub_prefix => $sub_config) {
-			if ($config_key == 'AggregateConfigs' && !isset($this->configData[$sub_prefix])) {
+		$processed = Array ();
+		$main_config->setSetting($config_key, null);
+
+		foreach ($sub_configs as $sub_prefix => $sub_config_data) {
+			if ( $config_key == 'AggregateConfigs' && !isset($this->configData[$sub_prefix]) ) {
 				$this->loadConfig($sub_prefix);
 			}
-			$sub_config['Prefix'] = $sub_prefix;
-			$this->configData[$sub_prefix] = kUtil::array_merge_recursive($this->configData[$$dst_prefix_var], $sub_config);
+
+			$sub_config_data['Prefix'] = $sub_prefix;
+			$sub_config_base = $this->configData[$$dst_prefix_var]->getRaw();
+			$sub_config = new kUnitConfig($sub_prefix, kUtil::array_merge_recursive($sub_config_base, $sub_config_data));
+			$this->configData[$sub_prefix] = $sub_config;
 
 			// when merging empty array to non-empty results non-empty array, but empty is required
-			foreach ($sub_config as $sub_key => $sub_value) {
-				if (!$sub_value) {
-					unset($this->configData[$sub_prefix][$sub_key]);
+			foreach ($sub_config_data as $sub_key => $sub_value) {
+				if ( !$sub_value && is_array($sub_value) ) {
+					$sub_config->setSetting($sub_key, null);
 				}
 			}
-			if ($config_key == 'Clones') {
+
+			if ( $config_key == 'Clones' ) {
 				$this->prefixFiles[$sub_prefix] = $this->prefixFiles[$prefix];
 			}
 
 			$this->postProcessConfig($sub_prefix, $config_key, $dst_prefix_var);
-			if ($config_key == 'AggregateConfigs') {
+
+			if ( $config_key == 'AggregateConfigs' ) {
 				$processed = array_merge($this->postProcessConfig($sub_prefix, 'Clones', 'prefix'), $processed);
 			}
-			elseif ($this->ProcessAllConfigs) {
+			elseif ( $this->ProcessAllConfigs ) {
 				$this->parseConfig($sub_prefix);
 			}
+
 			array_push($processed, $sub_prefix);
 		}
 
-		if (!$prefix) {
-			// configs, that used only for cloning & not used ifself
+		if ( !$prefix ) {
+			// configs, that used only for cloning & not used itself
 			unset($this->configData[$prefix]);
 		}
+
 		return array_unique($processed);
 	}
 
 	function PreloadConfigFile($filename)
 	{
 		$config_found = file_exists(FULL_PATH . $filename) && $this->configAllowed($filename);
 
-		if (defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_PROFILE_INCLUDES') && DBG_PROFILE_INCLUDES) {
+		if ( defined('DEBUG_MODE') && DEBUG_MODE && defined('DBG_PROFILE_INCLUDES') && DBG_PROFILE_INCLUDES ) {
 			if ( in_array($filename, get_included_files()) ) {
 				return '';
 			}
 
 			global $debugger;
 
-			if ($config_found) {
+			if ( $config_found ) {
 				$file = FULL_PATH . $filename;
 				$file_crc = crc32($file);
 
 				$debugger->ProfileStart('inc_' . $file_crc, $file);
 				include_once($file);
 				$debugger->ProfileFinish('inc_' . $file_crc);
 				$debugger->profilerAddTotal('includes', 'inc_' . $file_crc);
 			}
 		}
-		elseif ($config_found) {
+		elseif ( $config_found ) {
 			include_once(FULL_PATH . $filename);
 		}
 
-		if ($config_found) {
-			if (isset($config) && $config) {
+		if ( $config_found ) {
+			/* @var $config kUnitConfig|Array */
+
+			if ( isset($config) && $config ) {
 				// config file is included for 1st time -> save it's content for future processing
-				$prefix = array_key_exists('Prefix', $config) ? $config['Prefix'] : '';
+				if ( !is_object($config) ) {
+					$prefix = array_key_exists('Prefix', $config) ? $config['Prefix'] : '';
+					$config = new kUnitConfig($prefix, $config);
+				}
+				else {
+					$prefix = $config->getPrefix();
+				}
 
-				preg_match($this->_moduleFolderRegExp, $filename, $rets);
-				$config['ModuleFolder'] = str_replace(DIRECTORY_SEPARATOR, '/', $rets[1]);
-				$config['BasePath'] = dirname(FULL_PATH . $filename);
+				preg_match($this->_moduleFolderRegExp, $filename, $regs);
+				$config->setModuleFolder(str_replace(DIRECTORY_SEPARATOR, '/', $regs[1]));
+				$config->setBasePath(dirname(FULL_PATH . $filename));
 
-				if (array_key_exists('AdminTemplatePath', $config)) {
+				if ( $config->getAdminTemplatePath() !== false ) {
 					// append template base folder for admin templates path of this prefix
-					$module_templates = $rets[1] == 'core' ? '' : substr($rets[1], 8) . '/';
-					$config['AdminTemplatePath'] = $module_templates . $config['AdminTemplatePath'];
+					$module_templates = $regs[1] == 'core' ? '' : substr($regs[1], 8) . '/';
+					$config->setAdminTemplatePath($module_templates . $config->getAdminTemplatePath());
 				}
 
-				if (array_key_exists($prefix, $this->prefixFiles) && ($this->prefixFiles[$prefix] != $filename)) {
+				if ( array_key_exists($prefix, $this->prefixFiles) && ($this->prefixFiles[$prefix] != $filename) ) {
 					trigger_error(
 						'Single unit config prefix "<strong>' . $prefix . '</strong>" ' .
 						'is used in multiple unit config files: ' .
 						'"<strong>' . $this->prefixFiles[$prefix] . '</strong>", "<strong>' . $filename . '</strong>"',
 						E_USER_WARNING
 					);
 				}
 
 				$this->configData[$prefix] = $config;
 				$this->prefixFiles[$prefix] = $filename;
 
 				return $prefix;
 			}
 			else {
 				$prefix = array_search($filename, $this->prefixFiles);
 
 				if ( $prefix ) {
 					// attempt is made to include config file twice or more, but include_once prevents that,
 					// but file exists on hdd, then it is already saved to all required arrays, just return it's prefix
 					return $prefix;
 				}
 			}
 		}
 
 		return 'dummy';
 	}
 
 	function loadConfig($prefix)
 	{
 		if ( !isset($this->prefixFiles[$prefix]) ) {
 			throw new Exception('Configuration file for prefix "<strong>' . $prefix . '</strong>" is unknown');
 
 			return ;
 		}
 
 		$file = $this->prefixFiles[$prefix];
 		$prefix = $this->PreloadConfigFile($file);
 
 		if ($this->FinalStage) {
 			// run prefix OnAfterConfigRead so all
 			// hooks to it can define their clonses
 			$this->runAfterConfigRead($prefix);
 		}
 
 		$clones = $this->postProcessConfig($prefix, 'AggregateConfigs', 'sub_prefix');
 		$clones = array_merge($this->postProcessConfig($prefix, 'Clones', 'prefix'), $clones);
 
 		if ($this->FinalStage) {
 			$clones = array_unique($clones);
 
 			foreach ($clones as $a_prefix) {
 				$this->runAfterConfigRead($a_prefix);
 			}
 		}
 	}
 
 	function runAfterConfigRead($prefix)
 	{
 		if (in_array($prefix, $this->AfterConfigProcessed)) {
 			return ;
 		}
 
 		$this->Application->HandleEvent( new kEvent($prefix . ':OnAfterConfigRead') );
 
 		if (!(defined('IS_INSTALL') && IS_INSTALL)) {
 			// allow to call OnAfterConfigRead multiple times during install
 			array_push($this->AfterConfigProcessed, $prefix);
 		}
 	}
 
 	/**
-	 * Reads unit (specified by $prefix)
-	 * option specified by $option
+	 * Returns unit config for given prefix
 	 *
 	 * @param string $prefix
-	 * @param string $name
-	 * @param mixed $default
-	 * @return string
+	 * @return kUnitConfig
 	 * @access public
 	 */
-	function getUnitOption($prefix, $name, $default = false)
+	public function getUnitConfig($prefix = null)
 	{
-		if (preg_match('/(.*)\.(.*)/', $prefix, $rets)) {
-			if (!isset($this->configData[$rets[1]])) {
-				$this->loadConfig($rets[1]);
-			}
-			$ret = isset($this->configData[$rets[1]][$name][$rets[2]]) ? $this->configData[$rets[1]][$name][$rets[2]] : false;
-//			$ret = getArrayValue($this->configData, $rets[1], $name, $rets[2]);
-		}
-		else {
-			if (!isset($this->configData[$prefix])) {
-				$this->loadConfig($prefix);
-			}
-			$ret = isset($this->configData[$prefix][$name]) ? $this->configData[$prefix][$name] : false;
-//			$ret = getArrayValue($this->configData, $prefix, $name);
-		}
-		return $ret === false ? $default : $ret;
-	}
-
-	/**
-	 * Read all unit with $prefix options
-	 *
-	 * @param string $prefix
-	 * @return Array
-	 * @access public
-	 */
-	function getUnitOptions($prefix)
-	{
-		if (!isset($this->configData[$prefix])) {
+		if ( !isset($this->configData[$prefix]) ) {
 			$this->loadConfig($prefix);
 		}
 
 		return $this->configData[$prefix];
 	}
 
 	/**
-	 * Set's new unit option value
+	 * Returns prefixes of unit configs, that were registered
 	 *
-	 * @param string $prefix
-	 * @param string $name
-	 * @param string $value
+	 * @return Array
 	 * @access public
 	 */
-	function setUnitOption($prefix, $name, $value)
+	public function getPrefixes()
 	{
-		if ( preg_match('/(.*)\.(.*)/', $prefix, $rets) ) {
-			if ( !isset($this->configData[$rets[1]]) ) {
-				$this->loadConfig($rets[1]);
-			}
-
-			$this->configData[$rets[1]][$name][$rets[2]] = $value;
-		}
-		else {
-			if ( !isset($this->configData[$prefix]) ) {
-				$this->loadConfig($prefix);
-			}
-
-			$this->configData[$prefix][$name] = $value;
-		}
+		return array_keys($this->configData);
 	}
 
 	protected function getClasses($prefix)
 	{
-		$config =& $this->configData[$prefix];
+		$config = $this->configData[$prefix];
 		$class_params = Array ('ItemClass', 'ListClass', 'EventHandlerClass', 'TagProcessorClass');
-		$register_classes = isset($config['RegisterClasses']) ? $config['RegisterClasses'] : Array ();
+		$register_classes = $config->getRegisterClasses();
 
 		foreach ($class_params as $param_name) {
-			if ( !isset($config[$param_name]) ) {
+			$value = $config->getSetting($param_name);
+
+			if ( !$value ) {
 				continue;
 			}
 
-			$config[$param_name]['pseudo'] = $this->getPseudoByOptionName($param_name, $prefix);
-			$register_classes[] = $config[$param_name];
+			$value['pseudo'] = $this->getPseudoByOptionName($param_name, $prefix);
+			$config->setSetting($param_name, $value);
+
+			$register_classes[] = $value;
 		}
 
 		return $register_classes;
 	}
 
 	protected function getPseudoByOptionName($option_name, $prefix)
 	{
 		$pseudo_class_map = Array (
 			'ItemClass' => '%s',
 			'ListClass' => '%s_List',
 			'EventHandlerClass' => '%s_EventHandler',
 			'TagProcessorClass' => '%s_TagProcessor'
 		);
 
 		return sprintf($pseudo_class_map[$option_name], $prefix);
 	}
 
 	/**
 	 * Get's config file name based
 	 * on folder name supplied
 	 *
 	 * @param string $folderPath
 	 * @return string
 	 * @access private
 	 */
 	function getConfigName($folderPath)
 	{
 		return $folderPath . DIRECTORY_SEPARATOR . basename($folderPath) . '_config.php';
 	}
 
 	/**
 	 * Checks if config file is allowed for includion (if module of config is installed)
 	 *
 	 * @param string $config_path relative path from in-portal directory
 	 */
 	function configAllowed($config_path)
 	{
 		static $module_paths = null;
 
 		if (defined('IS_INSTALL') && IS_INSTALL) {
 			// at installation start no modules in db and kernel configs could not be read
 			return true;
 		}
 
 		if (preg_match('#^' . $this->_directorySeparator . 'core#', $config_path)) {
 			// always allow to include configs from "core" module's folder
 			return true;
 		}
 
 		if (!$this->Application->ModuleInfo) {
 			return false;
 		}
 
 		if (!isset($module_paths)) {
 			$module_paths = Array ();
 
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				$module_paths[] = str_replace('/', DIRECTORY_SEPARATOR, rtrim($module_info['Path'], '/'));
 			}
 
 			$module_paths = array_unique($module_paths);
 		}
 
 		preg_match($this->_moduleFolderRegExp, $config_path, $rets);
 
 		// config file path starts with module folder path
 		return in_array($rets[1], $module_paths);
 	}
 
 	/**
 	 * Returns true if config exists and is allowed for reading
 	 *
 	 * @param string $prefix
 	 * @return bool
 	 */
 	function prefixRegistred($prefix)
 	{
 		return isset($this->prefixFiles[$prefix]) ? true : false;
 	}
 
 	/**
 	 * Returns config file for given prefix
 	 *
 	 * @param string $prefix
 	 * @return string
 	 */
 	function getPrefixFile($prefix)
 	{
 		return array_key_exists($prefix, $this->prefixFiles) ? $this->prefixFiles[$prefix] : false;
 	}
 
 	function iterateConfigs($callback_function, $params)
 	{
 		$this->includeConfigFiles(MODULES_PATH); //make sure to re-read all configs
 		$this->AfterConfigRead();
 
-		foreach ($this->configData as $prefix => $config_data) {
-			$callback_function[0]->$callback_function[1]($prefix, $config_data, $params);
+		foreach ($this->configData as $prefix => $config) {
+			$callback_function[0]->$callback_function[1]($prefix, $config, $params);
 		}
 	}
 
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/event_handler.php
===================================================================
--- branches/5.3.x/core/kernel/event_handler.php	(revision 15697)
+++ branches/5.3.x/core/kernel/event_handler.php	(revision 15698)
@@ -1,227 +1,227 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	/**
 	 * Note:
 	 *   1. When addressing variables from submit containing
 	 *	 	Prefix_Special as part of their name use
 	 *	 	$event->getPrefixSpecial(true) instead of
 	 *	 	$event->getPrefixSpecial() as usual. This is due PHP
 	 *	 	is converting "." symbols in variable names during
 	 *	 	submit info "_". $event->getPrefixSpecial optional
 	 *	 	1st parameter returns correct current Prefix_Special
 	 *	 	for variables being submitted such way (e.g. variable
 	 *	 	name that will be converted by PHP: "users.read_only_id"
 	 *	 	will be submitted as "users_read_only_id".
 	 *
 	 *	 2.	When using $this->Application->LinkVar on variables submitted
 	 *		from the form which contains $Prefix_Special then note 1st item.
 	 * 		Example: LinkVar($event->getPrefixSpecial(true).'_varname', $event->getPrefixSpecial().'_varname')
 	 *
 	 */
 
 	/**
 	 * Default event handler. Mostly abstract class
 	 *
 	 */
 	class kEventHandler extends kBase {
 
 		/**
 		 * In case if event should be handled with method, which name differs from
 		 * event name, then it should be specified here.
 		 * key - event name, value - event method
 		 *
 		 * @var Array
 		 * @access protected
 		 */
 		protected $eventMethods = Array ();
 
 		/**
 		 * Defines mapping vs event names and permission names
 		 *
 		 * @var Array
 		 * @access protected
 		 */
 		protected $permMapping = Array ();
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			$this->mapEvents();
 			$this->mapPermissions();
 		}
 
 		/**
 		 * Define alternative event processing method names
 		 *
 		 * @return void
 		 * @see kEventHandler::$eventMethods
 		 * @access protected
 		 */
 		protected function mapEvents()
 		{
 
 		}
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 
 		}
 
 		/**
 		 * Returns prefix and special (when present) joined by a "."
 		 *
 		 * @return string
 		 * @access private
 		 */
 		public function getPrefixSpecial()
 		{
 			throw new Exception('Usage of getPrefixSpecial() method is forbidden in kEventHandler class children. Use $event->getPrefixSpecial(true); instead');
 		}
 
 		/**
 		 * Executes event, specified in $event variable
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access public
 		 */
 		public function processEvent(kEvent $event)
 		{
 			$event_name = $this->getEventMethod($event);
 
 			$this->$event_name($event);
 		}
 
 		/**
 		 * Returns method name, that should called to process given event.
 		 * When no such method exists and exception is thrown.
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @throws Exception
 		 */
 		public function getEventMethod(kEvent $event)
 		{
 			$event_name = $event->Name;
 
 			if ( array_key_exists($event_name, $this->eventMethods) ) {
 				$event_name = $this->eventMethods[$event_name];
 			}
 
 			if ( method_exists($this, $event_name) ) {
 				return $event_name;
 			}
 
 			throw new Exception('Event "<strong>' . $event->Name . '</strong>" not implemented in class "<strong>' . get_class($this) . '</strong>"');
 		}
 
 		/**
 		 * Sample dummy event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBuild(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Returns to previous template in opener stack
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnGoBack(kEvent $event)
 		{
 			$url = $this->Application->RecallVar('export_finish_url');
 
 			if ( $url ) {
 				$this->Application->Redirect('external:' . $url);
 			}
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Apply some special processing to object being
 		 * recalled before using it in other events that
 		 * call prepareObject
 		 *
 		 * @param kDBItem|kDBList $object
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function prepareObject(&$object, kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			return $perm_helper->CheckEventPermission($event, $this->permMapping);
 		}
 
 		/**
 		 * Occurs, when config was parsed, allows to change config data dynamically
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 
 		}
 
 		/**
 		 * Returns sql query to be used for event subscriber list selection
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function OnGetEventSubscribersQuery(kEvent $event)
 		{
 			$sql = 'SELECT SubscriberEmail, UserId
-					FROM ' . $this->Application->getUnitOption('system-event-subscription', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('system-event-subscription')->getTableName() . '
 					WHERE (' . implode(') AND (', $event->getEventParam('where_clause')) . ')';
 			$event->setEventParam('sql', $sql);
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/kernel/nparser/compiler.php
===================================================================
--- branches/5.3.x/core/kernel/nparser/compiler.php	(revision 15697)
+++ branches/5.3.x/core/kernel/nparser/compiler.php	(revision 15698)
@@ -1,142 +1,142 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class NParserCompiler extends kHelper {
 
 	var $Errors = array();
 	var $Templates = array();
 
 	function CompileTemplatesStep()
 	{
 		$templates = $this->Application->RecallVar('templates_to_compile');
 		if ( !$templates ) {
 			// build $templates
 			$templates = $this->FindTemplates();
 		}
 		else {
 			$templates = unserialize($templates);
 		}
 
 		$total = count($templates);
 		$current = $this->Application->RecallVar('current_template_to_compile');
 		if ( !$current ) {
 			$current = 0;
 		}
 
 		$errors = $this->Application->RecallVar('compile_errors');
 		if ( $errors ) {
 			$this->Errors = unserialize($errors);
 		}
 
 		kUtil::safeDefine('DBG_NPARSER_FORCE_COMPILE', 1);
 
 		$i = $current;
 		$this->Application->InitParser(true);
 
 		while ( $i < $total && $i < ($current + 20) ) {
 			$a_template = $templates[$i];
 
 			try {
 				$this->Application->Parser->CheckTemplate($a_template['module'] . '/' . $a_template['path']);
 			} catch ( Exception $e ) {
 				$this->Errors[] = Array ('message' => $e->getMessage(), 'exception_class' => get_class($e), 'file' => $e->getFile(), 'line' => $e->getLine());
 			}
 
 			$i++;
 		}
 
 		$this->Application->StoreVar('current_template_to_compile', $i);
 		$this->Application->StoreVar('templates_to_compile', serialize($templates));
 		$this->Application->StoreVar('compile_errors', serialize($this->Errors));
 		$res = floor(($current / $total) * 100);
 
 		if ( $res == 100 || $current >= $total ) {
 			$this->Application->RemoveVar('templates_to_compile');
 			$this->Application->RemoveVar('current_template_to_compile');
 			$this->Application->Redirect($this->Application->GetVar('finish_template'));
 		}
 
 		echo $res;
 	}
 
 	function FindTemplates()
 	{
 		$this->Templates = Array ();
 
 		// find admin templates
 		foreach ($this->Application->ModuleInfo as $module => $options) {
 			if ($module == 'In-Portal') {
 				// don't check In-Portal admin templates, because it doesn't have them
 				continue;
 			}
 
 			$template_path = '/' . $options['Path'] . 'admin_templates';
 			$options['Path'] = $template_path;
 
 			$this->FindTemplateFiles($template_path, $options);
 		}
 
 		// find Front-End templates (from enabled themes only)
 		$sql = 'SELECT Name
-				FROM ' . $this->Application->getUnitOption('theme', 'TableName') . '
+				FROM ' . $this->Application->getUnitConfig('theme')->getTableName() . '
 				WHERE Enabled = 1';
 		$themes = $this->Conn->GetCol($sql);
 
 		$options = Array ();
 		foreach ($themes as $theme_name) {
 			$template_path = '/themes/' . $theme_name;
 			$options['Name'] = 'theme:' . $theme_name;
 			$options['Path'] = $template_path;
 
 			$this->FindTemplateFiles($template_path, $options);
 		}
 
 		return $this->Templates;
 	}
 
 	/**
 	 * Recursively collects all TPL file across whole installation
 	 *
 	 * @param string $folder_path
 	 * @param Array $options
 	 * @return void
 	 */
 	function FindTemplateFiles($folder_path, $options)
 	{
 		// if FULL_PATH = "/" ensure, that all "/" in $folderPath are not deleted
 		$reg_exp = '/^' . preg_quote(FULL_PATH, '/') . '/';
 		$folder_path = preg_replace($reg_exp, '', $folder_path, 1); // this make sense, since $folderPath may NOT contain FULL_PATH
 
 		$iterator = new DirectoryIterator(FULL_PATH . $folder_path);
 		/* @var $file_info DirectoryIterator */
 
 		foreach ($iterator as $file_info) {
 			$filename = $file_info->getFilename();
 			$full_path = $file_info->getPathname();
 
 			if ( $file_info->isDir() && !$file_info->isDot() && $filename != '.svn' && $filename != 'CVS' ) {
 				$this->FindTemplateFiles($full_path, $options);
 			}
 			elseif ( pathinfo($full_path, PATHINFO_EXTENSION) == 'tpl' ) {
 				$this->Templates[] = Array (
 					'module' => mb_strtolower( $options['Name'] ),
 					'path' => str_replace(FULL_PATH . $options['Path'] . '/', '', preg_replace('/\.tpl$/', '', $full_path))
 				);
 			}
 		}
 	}
 
 }
\ No newline at end of file
Index: branches/5.3.x/core/kernel/kbase.php
===================================================================
--- branches/5.3.x/core/kernel/kbase.php	(revision 15697)
+++ branches/5.3.x/core/kernel/kbase.php	(revision 15698)
@@ -1,1176 +1,1187 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 //defined('FULL_PATH') or die('restricted access!');
 
 /**
 * Base
 *
 */
 class kBase {
 
 	/**
 	* Reference to global kApplication instance
 	*
 	* @var kApplication
 	* @access protected
 	*/
 	protected $Application = null;
 
 	/**
 	* Connection to database
 	*
 	* @var kDBConnection
 	* @access protected
 	*/
 	protected $Conn = null;
 
 	/**
 	 * Prefix, used during object creation
 	 *
 	 * @var string
 	 * @access public
 	 */
 	public $Prefix = '';
 
 	/**
 	 * Special, used during object creation
 	 *
 	 * @var string
 	 * @access public
 	 */
 	public $Special = '';
 
 	/**
 	 * Joined prefix and special, usually taken directly from
 	 * tag beeing processed, to use in kApplication::recallObject method
 	 *
 	 * @var string
 	 * @access protected
 	 * @see kApplication::recallObject
 	 */
 	protected $prefixSpecial = '';
 
 	/**
 	 * Set's references to kApplication and kDBConnection class instances
 	 *
 	 * @access public
 	 * @see kApplication
 	 * @see kDBConnection
 	 */
 	public function __construct()
 	{
 		$this->Application =& kApplication::Instance();
 		$this->Conn =& $this->Application->GetADODBConnection();
 	}
 
 	/**
 	 * Set's prefix and special
 	 *
 	 * @param string $prefix
 	 * @param string $special
 	 * @access public
 	 */
 	public function Init($prefix, $special)
 	{
 		$prefix = explode('_', $prefix, 2);
 		$this->Prefix = $prefix[0];
 		$this->Special = $special;
 
 		$this->prefixSpecial = rtrim($this->Prefix . '.' . $this->Special, '.');
 	}
 
 	/**
 	 * Returns prefix and special (when present) joined by a "."
 	 *
 	 * @return string
 	 * @access public
 	 */
 	public function getPrefixSpecial()
 	{
 		return $this->prefixSpecial;
 	}
 
 	/**
+	 * Returns unit config, used in tag
+	 *
+	 * @return kUnitConfig
+	 * @access public
+	 */
+	public function getUnitConfig()
+	{
+		return $this->Application->getUnitConfig($this->Prefix);
+	}
+
+	/**
 	 * Creates string representation of a class (for logging)
 	 *
 	 * @return string
 	 * @access public
 	 */
 	public function __toString()
 	{
 		return 'ClassName: ' . get_class($this) . '; PrefixSpecial: ' . $this->getPrefixSpecial();
 	}
 }
 
 
 class kHelper extends kBase {
 
 	/**
 	 * Performs helper initialization
 	 *
 	 * @access public
 	 */
 	public function InitHelper()
 	{
 
 	}
 
 	/**
 	 * Append prefix and special to tag
 	 * params (get them from tagname) like
 	 * they were really passed as params
 	 *
 	 * @param string $prefix_special
 	 * @param Array $tag_params
 	 * @return Array
 	 * @access protected
 	 */
 	protected function prepareTagParams($prefix_special, $tag_params = Array())
 	{
 		$parts = explode('.', $prefix_special);
 
 		$ret = $tag_params;
 		$ret['Prefix'] = $parts[0];
 		$ret['Special'] = count($parts) > 1 ? $parts[1] : '';
 		$ret['PrefixSpecial'] = $prefix_special;
 
 		return $ret;
 	}
 }
 
 
 abstract class kDBBase extends kBase {
 
 	/**
 	* Name of primary key field for the unit
 	*
 	* @var string
 	* @access public
 	* @see kDBBase::TableName
 	*/
 	public $IDField = '';
 
 	/**
 	* Unit's database table name
 	*
 	* @var string
 	* @access public
 	*/
 	public $TableName = '';
 
 	/**
 	 * Form name, used for validation
 	 *
 	 * @var string
 	 */
 	protected $formName = '';
 
 	/**
 	 * Final form configuration
 	 *
 	 * @var Array
 	 */
 	protected $formConfig = Array ();
 
 	/**
 	* SELECT, FROM, JOIN parts of SELECT query (no filters, no limit, no ordering)
 	*
 	* @var string
 	* @access protected
 	*/
 	protected $SelectClause = '';
 
 	/**
 	* Unit fields definitions (fields from database plus virtual fields)
 	*
 	* @var Array
 	* @access protected
 	*/
 	protected $Fields = Array ();
 
 	/**
 	 * Mapping between unit custom field IDs and their names
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $customFields = Array ();
 
 	/**
 	 * Unit virtual field definitions
 	 *
 	 * @var Array
 	 * @access protected
 	 * @see kDBBase::getVirtualFields()
 	 * @see kDBBase::setVirtualFields()
 	 */
 	protected $VirtualFields = Array ();
 
 	/**
 	 * Fields that need to be queried using custom expression, e.g. IF(...) AS value
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $CalculatedFields = Array ();
 
 
 	/**
 	 * Fields that contain aggregated functions, e.g. COUNT, SUM, etc.
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $AggregatedCalculatedFields = Array ();
 
 	/**
 	 * Tells, that multilingual fields sould not be populated by default.
 	 * Can be overriden from kDBBase::Configure method
 	 *
 	 * @var bool
 	 * @access protected
 	 */
 	protected $populateMultiLangFields = false;
 
 	/**
 	 * Event, that was used to create this object
 	 *
 	 * @var kEvent
 	 * @access protected
 	 */
 	protected $parentEvent = null;
 
 	/**
 	 * Sets new parent event to the object
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access public
 	 */
 	public function setParentEvent($event)
 	{
 		$this->parentEvent = $event;
 	}
 
 	/**
 	 * Set object' TableName to LIVE table, defined in unit config
 	 *
 	 * @access public
 	 */
 	public function SwitchToLive()
 	{
-		$this->TableName = $this->Application->getUnitOption($this->Prefix, 'TableName');
+		$this->TableName = $this->getUnitConfig()->getTableName();
 	}
 
 	/**
 	 * Set object' TableName to TEMP table created based on table, defined in unit config
 	 *
 	 * @access public
 	 */
 	public function SwitchToTemp()
 	{
-		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
+		$table_name = $this->getUnitConfig()->getTableName();
 		$this->TableName = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix);
 	}
 
 	/**
 	 * Checks if object uses temp table
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function IsTempTable()
 	{
 		return $this->Application->IsTempTable($this->TableName);
 	}
 
 	/**
 	* Sets SELECT part of list' query
 	*
 	* @param string $sql SELECT and FROM [JOIN] part of the query up to WHERE
 	* @access public
 	*/
 	public function SetSelectSQL($sql)
 	{
 		$this->SelectClause = $sql;
 	}
 
 	/**
 	 * Returns object select clause without any transformations
 	 *
 	 * @return string
 	 * @access public
 	 */
 	public function GetPlainSelectSQL()
 	{
 		return $this->SelectClause;
 	}
 
 	/**
 	 * Returns SELECT part of list' query.
 	 * 1. Occurrences of "%1$s" and "%s" are replaced to kDBBase::TableName
 	 * 2. Occurrences of "%3$s" are replaced to temp table prefix (only for table, using TABLE_PREFIX)
 	 *
 	 * @param string $base_query given base query will override unit default select query
 	 * @param bool $replace_table replace all possible occurrences
 	 * @return string
 	 * @access public
 	 * @see kDBBase::replaceModePrefix
 	 */
 	public function GetSelectSQL($base_query = null, $replace_table = true)
 	{
 		if (!isset($base_query)) {
 			$base_query = $this->SelectClause;
 		}
 
 		if (!$replace_table) {
 			return $base_query;
 		}
 
 		$query = str_replace(Array('%1$s', '%s'), $this->TableName, $base_query);
 
 		return $this->replaceModePrefix($query);
 	}
 
 	/**
 	 * Allows sub-stables to be in same mode as main item (e.g. LEFT JOINED ones)
 	 *
 	 * @param string $query
 	 * @return string
 	 * @access protected
 	 */
 	protected function replaceModePrefix($query)
 	{
 		$live_table = substr($this->Application->GetLiveName($this->TableName), strlen(TABLE_PREFIX));
 
 		if (preg_match('/'.preg_quote(TABLE_PREFIX, '/').'(.*)'.preg_quote($live_table, '/').'/', $this->TableName, $rets)) {
 			// will only happen, when table has a prefix (like in K4)
 			return str_replace('%3$s', $rets[1], $query);
 		}
 
 		// will happen, when K3 table without prefix is used
 		return $query;
 	}
 
 	/**
 	 * Sets calculated fields
 	 *
 	 * @param Array $fields
 	 * @access public
 	 */
 	public function setCalculatedFields($fields)
 	{
 		$this->CalculatedFields = $fields;
 	}
 
 	/**
 	 * Adds calculated field declaration to object.
 	 *
 	 * @param string $name
 	 * @param string $sql_clause
 	 * @access public
 	 */
 	public function addCalculatedField($name, $sql_clause)
 	{
 		$this->CalculatedFields[$name] = $sql_clause;
 	}
 
 	/**
 	 * Returns required mixing of aggregated & non-aggregated calculated fields
 	 *
 	 * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only
 	 * @return Array
 	 * @access public
 	 */
 	public function getCalculatedFields($aggregated = 1)
 	{
 		switch ($aggregated) {
 			case 0:
 				$fields = array_merge($this->CalculatedFields, $this->AggregatedCalculatedFields);
 				break;
 
 			case 1:
 				$fields = $this->CalculatedFields;
 				break;
 
 			case 2:
 				$fields = $this->AggregatedCalculatedFields; // TODO: never used
 				break;
 
 			default:
 				$fields = Array();
 				break;
 		}
 
 		return $fields;
 	}
 
 	/**
 	 * Checks, that given field is a calculated field
 	 *
 	 * @param string $field
 	 * @return bool
 	 * @access public
 	 */
 	public function isCalculatedField($field)
 	{
 		return array_key_exists($field, $this->CalculatedFields);
 	}
 
 	/**
 	 * Insert calculated fields sql into query in place of %2$s,
 	 * return processed query.
 	 *
 	 * @param string $query
 	 * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only
 	 * @return string
 	 * @access protected
 	 */
 	protected function addCalculatedFields($query, $aggregated = 1)
 	{
 		$fields = $this->getCalculatedFields($aggregated);
 		if ( $fields ) {
 			$sql = Array ();
 			// inside calculated field "%2$s" is current language
 			$fields = str_replace('%2$s', $this->Application->GetVar('m_lang'), $fields);
 
 			// can't use "%3$s" as usual, because it's already populated in kDBBase::replaceModePrefix() across whole query
 			$fields = str_replace('%4$s', $this->Application->GetDefaultLanguageId(), $fields);
 
 			foreach ($fields as $field_name => $field_expression) {
 				$sql[] = '(' . $field_expression . ') AS `' . $field_name . '`';
 			}
 			$sql = implode(',', $sql);
 
 			// inside sql "%2$s" is placeholder for calculated fields
 			return $this->Application->ReplaceLanguageTags(str_replace('%2$s', ',' . $sql, $query));
 		}
 
 		return str_replace('%2$s', '', $query);
 	}
 
 	/**
 	 * Performs initial object configuration, which includes setting the following:
 	 * - primary key and table name
 	 * - field definitions (including field modifiers, formatters, default values)
 	 *
 	 * @param bool $populate_ml_fields create all ml fields from db in config or not
 	 * @param string $form_name form name for validation
 	 * @access public
 	 */
 	public function Configure($populate_ml_fields = null, $form_name = null)
 	{
 		if ( isset($populate_ml_fields) ) {
 			$this->populateMultiLangFields = $populate_ml_fields;
 		}
 
-		$this->IDField = $this->Application->getUnitOption($this->Prefix, 'IDField');
-		$this->TableName = $this->Application->getUnitOption($this->Prefix, 'TableName');
+		$config = $this->getUnitConfig();
+		$this->IDField = $config->getIDField();
+		$this->TableName = $config->getTableName();
 
 		$this->initForm($form_name);
 		$this->defineFields();
 
 		$this->ApplyFieldModifiers(null, true); // should be called only after all fields definitions been set
 		$this->prepareConfigOptions(); // this should go last, but before setDefaultValues, order is significant!
 
 		// only set on first call of method
 		if ( isset($populate_ml_fields) ) {
 			$this->SetDefaultValues();
 		}
 	}
 
 	/**
 	 * Adjusts object according to given form name
 	 *
 	 * @param string $form_name
 	 * @return void
 	 * @access protected
 	 */
 	protected function initForm($form_name = null)
 	{
-		$forms = $this->Application->getUnitOption($this->Prefix, 'Forms', Array ());
+		$config = $this->getUnitConfig();
 
 		$this->formName = $form_name;
-		$this->formConfig = isset($forms['default']) ? $forms['default'] : Array ();
+		$this->formConfig = $config->getFormByName('default', Array ());
 
 		if ( !$this->formName ) {
 			return ;
 		}
 
-		if ( !isset($forms[$this->formName]) ) {
+		$form_data = $config->getFormByName($this->formName);
+
+		if ( !$form_data ) {
 			trigger_error('Form "<strong>' . $this->formName . '</strong>" isn\'t declared in "<strong>' . $this->Prefix . '</strong>" unit config.', E_USER_NOTICE);
 		}
 		else {
-			$this->formConfig = kUtil::array_merge_recursive($this->formConfig, $forms[$this->formName]);
+			$this->formConfig = kUtil::array_merge_recursive($this->formConfig, $form_data);
 		}
 	}
 
 	/**
 	 * Add field definitions from all possible sources
 	 * Used field sources: database fields, custom fields, virtual fields, calculated fields, aggregated calculated fields
 	 *
 	 * @access protected
 	 */
 	protected function defineFields()
 	{
 		$this->Fields = $this->getFormOption('Fields', Array ());
 		$this->customFields = $this->getFormOption('CustomFields', Array());
 
 		$this->setVirtualFields( $this->getFormOption('VirtualFields', Array ()) );
 
 		$calculated_fields = $this->getFormOption('CalculatedFields', Array());
 		$this->CalculatedFields = $this->getFieldsBySpecial($calculated_fields);
 
 		$aggregated_calculated_fields = $this->getFormOption('AggregatedCalculatedFields', Array());
 		$this->AggregatedCalculatedFields = $this->getFieldsBySpecial($aggregated_calculated_fields);
 	}
 
 	/**
 	 * Returns form name, used for validation
 	 *
 	 * @return string
 	 */
 	public function getFormName()
 	{
 		return $this->formName;
 	}
 
 	/**
 	 * Reads unit (specified by $prefix) option specified by $option and applies form change to it
 	 *
 	 * @param string $option
 	 * @param mixed $default
 	 * @return string
 	 * @access public
 	 */
 	public function getFormOption($option, $default = false)
 	{
-		$ret = $this->Application->getUnitOption($this->Prefix, $option, $default);
+		$ret = $this->getUnitConfig()->getSetting($option, $default);
 
 		if ( isset($this->formConfig[$option]) ) {
 			$ret = kUtil::array_merge_recursive($ret, $this->formConfig[$option]);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Only exteracts fields, that match current object Special
 	 *
 	 * @param Array $fields
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getFieldsBySpecial($fields)
 	{
 		if ( array_key_exists($this->Special, $fields) ) {
 			return $fields[$this->Special];
 		}
 
 		return array_key_exists('', $fields) ? $fields[''] : Array();
 	}
 
 	/**
 	 * Sets aggeregated calculated fields
 	 *
 	 * @param Array $fields
 	 * @access public
 	 */
 	public function setAggregatedCalculatedFields($fields)
 	{
 		$this->AggregatedCalculatedFields = $fields;
 	}
 
 	/**
 	 * Set's field names from table from config
 	 *
 	 * @param Array $fields
 	 * @access public
 	 */
 	public function setCustomFields($fields)
 	{
 		$this->customFields = $fields;
 	}
 
 	/**
 	 * Returns custom fields information from table from config
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function getCustomFields()
 	{
 		return $this->customFields;
 	}
 
 	/**
 	 * Set's fields information from table from config
 	 *
 	 * @param Array $fields
 	 * @access public
 	 */
 	public function setFields($fields)
 	{
 		$this->Fields = $fields;
 	}
 
 	/**
 	 * Returns fields information from table from config
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function getFields()
 	{
 		return $this->Fields;
 	}
 
 	/**
 	 * Checks, that given field exists
 	 *
 	 * @param string $field
 	 * @return bool
 	 * @access public
 	 */
 	public function isField($field)
 	{
 		return array_key_exists($field, $this->Fields);
 	}
 
 	/**
 	 * Override field options with ones defined in submit via "field_modfiers" array (common for all prefixes)
 	 *
 	 * @param Array $field_modifiers
 	 * @param bool $from_submit
 	 * @return void
 	 * @access public
 	 * @author Alex
 	 */
 	public function ApplyFieldModifiers($field_modifiers = null, $from_submit = false)
 	{
 		$allowed_modifiers = Array ('required', 'multiple');
 
 		if ( $this->Application->isAdminUser ) {
 			// can change upload dir on the fly (admin only!)
 			$allowed_modifiers[] = 'upload_dir';
 		}
 
 		if ( !isset($field_modifiers) ) {
 			$field_modifiers = $this->Application->GetVar('field_modifiers');
 
 			if ( !$field_modifiers ) {
 				// no field modifiers
 				return;
 			}
 
 			$field_modifiers = getArrayValue($field_modifiers, $this->getPrefixSpecial());
 		}
 
 		if ( !$field_modifiers ) {
 			// no field modifiers for current prefix_special
 			return;
 		}
 
-		$fields = $this->Application->getUnitOption($this->Prefix, 'Fields', Array ());
-		$virtual_fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields', Array ());
+		$config = $this->getUnitConfig();
+		$fields = $config->getFields(Array ());
+		$virtual_fields = $config->getVirtualFields(Array ());
 
 		foreach ($field_modifiers as $field => $field_options) {
 			foreach ($field_options as $option_name => $option_value) {
 				if ( !in_array(strtolower($option_name), $allowed_modifiers) ) {
 					continue;
 				}
 
 				if ( $from_submit ) {
 					// there are no "lN_FieldName" fields, since ApplyFieldModifiers is
 					// called before PrepareOptions method, which creates them
 					$field = preg_replace('/^l[\d]+_(.*)/', '\\1', $field);
 				}
 
 				if ( $this->isVirtualField($field) ) {
 					$virtual_fields[$field][$option_name] = $option_value;
 					$this->SetFieldOption($field, $option_name, $option_value, true);
 				}
 
 				$fields[$field][$option_name] = $option_value;
 				$this->SetFieldOption($field, $option_name, $option_value);
 			}
 		}
 
-		$this->Application->setUnitOption($this->Prefix, 'Fields', $fields);
-		$this->Application->setUnitOption($this->Prefix, 'VirtualFields', $virtual_fields);
+		$config->setFields($fields);
+		$config->setVirtualFields($virtual_fields);
 	}
 
 	/**
 	 * Set fields (+options) for fields that physically doesn't exist in database
 	 *
 	 * @param Array $fields
 	 * @access public
 	 */
 	public function setVirtualFields($fields)
 	{
 		if ($fields) {
 			$this->VirtualFields = $fields;
 			$this->Fields = array_merge($this->VirtualFields, $this->Fields);
 		}
 	}
 
 	/**
 	 * Returns virtual fields
 	 *
 	 * @return Array
 	 * @access public
 	 */
 	public function getVirtualFields()
 	{
 		return $this->VirtualFields;
 	}
 
 	/**
 	 * Checks, that given field is a virtual field
 	 *
 	 * @param string $field
 	 * @return bool
 	 * @access public
 	 */
 	public function isVirtualField($field)
 	{
 		return array_key_exists($field, $this->VirtualFields);
 	}
 
 	/**
 	 * Performs additional initialization for field default values
 	 *
 	 * @access protected
 	 */
 	protected function SetDefaultValues()
 	{
 		foreach ($this->Fields as $field => $options) {
 			if ( array_key_exists('default', $options) && $options['default'] === '#NOW#' ) {
 				$this->Fields[$field]['default'] = adodb_mktime();
 			}
 		}
 	}
 
 	/**
 	 * Overwrites field definition in unit config
 	 *
 	 * @param string $field
 	 * @param Array $options
 	 * @param bool $is_virtual
 	 * @access public
 	 */
 	public function SetFieldOptions($field, $options, $is_virtual = false)
 	{
 		if ($is_virtual) {
 			$this->VirtualFields[$field] = $options;
 			$this->Fields = array_merge($this->VirtualFields, $this->Fields);
 		}
 		else {
 			$this->Fields[$field] = $options;
 		}
 	}
 
 	/**
 	 * Changes/sets given option's value in given field definiton
 	 *
 	 * @param string $field
 	 * @param string $option_name
 	 * @param mixed $option_value
 	 * @param bool $is_virtual
 	 * @access public
 	 */
 	public function SetFieldOption($field, $option_name, $option_value, $is_virtual = false)
 	{
 		if ($is_virtual) {
 			$this->VirtualFields[$field][$option_name] = $option_value;
 		}
 
 		$this->Fields[$field][$option_name] = $option_value;
 	}
 
 	/**
 	 * Returns field definition from unit config.
 	 * Also executes sql from "options_sql" field option to form "options" field option
 	 *
 	 * @param string $field
 	 * @param bool $is_virtual
 	 * @return Array
 	 * @access public
 	 */
 	public function GetFieldOptions($field, $is_virtual = false)
 	{
 		$property_name = $is_virtual ? 'VirtualFields' : 'Fields';
 
 		if ( !array_key_exists($field, $this->$property_name) ) {
 			return Array ();
 		}
 
 		if (!$is_virtual) {
 			if (!array_key_exists('options_prepared', $this->Fields[$field]) || !$this->Fields[$field]['options_prepared']) {
 				// executes "options_sql" from field definition, only when field options are accessed (late binding)
 				$this->PrepareFieldOptions($field);
 				$this->Fields[$field]['options_prepared'] = true;
 			}
 		}
 
 		return $this->{$property_name}[$field];
 	}
 
 	/**
 	 * Returns field option
 	 *
 	 * @param string $field
 	 * @param string $option_name
 	 * @param bool $is_virtual
 	 * @param mixed $default
 	 * @return mixed
 	 * @access public
 	 */
 	public function GetFieldOption($field, $option_name, $is_virtual = false, $default = false)
 	{
 		$field_options = $this->GetFieldOptions($field, $is_virtual);
 
 		if ( !$field_options && strpos($field, '#') === false ) {
 			// we use "#FIELD_NAME#" as field for InputName tag in JavaScript, so ignore it
 			$form_name = $this->getFormName();
 			trigger_error('Field "<strong>' . $field . '</strong>" is not defined' . ($form_name ? ' on "<strong>' . $this->getFormName() . '</strong>" form' : '') . ' in "<strong>' . $this->Prefix . '</strong>" unit config', E_USER_WARNING);
 
 			return false;
 		}
 
 		return array_key_exists($option_name, $field_options) ? $field_options[$option_name] : $default;
 	}
 
 	/**
 	 * Returns formatted field value
 	 *
 	 * @param string $name
 	 * @param string $format
 	 *
 	 * @return string
 	 * @access protected
 	 */
 	public function GetField($name, $format = null)
 	{
 		$formatter_class = $this->GetFieldOption($name, 'formatter');
 
 		if ( $formatter_class ) {
 			$value = ($formatter_class == 'kMultiLanguage') && !preg_match('/^l[0-9]+_/', $name) ? '' : $this->GetDBField($name);
 
 			$formatter = $this->Application->recallObject($formatter_class);
 			/* @var $formatter kFormatter */
 
 			return $formatter->Format($value, $name, $this, $format);
 		}
 
 		return $this->GetDBField($name);
 	}
 
 	/**
 	 * Returns unformatted field value
 	 *
 	 * @param string $field
 	 * @return string
 	 * @access protected
 	 */
 	abstract protected function GetDBField($field);
 
 	/**
 	 * Checks of object has given field
 	 *
 	 * @param string $name
 	 * @return bool
 	 * @access protected
 	 */
 	abstract protected function HasField($name);
 
 	/**
 	 * Returns field values
 	 *
 	 * @return Array
 	 * @access protected
 	 */
 	abstract protected function GetFieldValues();
 
 	/**
 	 * Populates values of sub-fields, based on formatters, set to mater fields
 	 *
 	 * @param Array $fields
 	 * @access public
 	 * @todo Maybe should not be publicly accessible
 	 */
 	public function UpdateFormattersSubFields($fields = null)
 	{
 		if ( !is_array($fields) ) {
 			$fields = array_keys($this->Fields);
 		}
 
 		foreach ($fields as $field) {
 			if ( isset($this->Fields[$field]['formatter']) ) {
 				$formatter = $this->Application->recallObject($this->Fields[$field]['formatter']);
 				/* @var $formatter kFormatter */
 
 				$formatter->UpdateSubFields($field, $this->GetDBField($field), $this->Fields[$field], $this);
 			}
 		}
 	}
 
 	/**
 	 * Use formatters, specified in field declarations to perform additional field initialization in unit config
 	 *
 	 * @access protected
 	 */
 	protected function prepareConfigOptions()
 	{
 		$field_names = array_keys($this->Fields);
 
 		foreach ($field_names as $field_name) {
 			if ( !array_key_exists('formatter', $this->Fields[$field_name]) ) {
 				continue;
 			}
 
 			$formatter = $this->Application->recallObject( $this->Fields[$field_name]['formatter'] );
 			/* @var $formatter kFormatter */
 
 			$formatter->PrepareOptions($field_name, $this->Fields[$field_name], $this);
 		}
 	}
 
 	/**
 	 * Escapes fields only, not expressions
 	 *
 	 * @param string $field_expr
 	 * @return string
 	 * @access protected
 	 */
 	protected function escapeField($field_expr)
 	{
 		return preg_match('/[.(]/', $field_expr) ? $field_expr : '`' . $field_expr . '`';
 	}
 
 	/**
 	 * Replaces current language id in given field options
 	 *
 	 * @param string $field_name
 	 * @param Array $field_option_names
 	 * @access protected
 	 */
 	protected function _replaceLanguageId($field_name, $field_option_names)
 	{
 		// don't use GetVar('m_lang') since it's always equals to default language on editing form in admin
 		$current_language_id = $this->Application->Phrases->LanguageId;
 		$primary_language_id = $this->Application->GetDefaultLanguageId();
 
 		$field_options =& $this->Fields[$field_name];
 		foreach ($field_option_names as $option_name) {
 			$field_options[$option_name] = str_replace('%2$s', $current_language_id, $field_options[$option_name]);
 			$field_options[$option_name] = str_replace('%3$s', $primary_language_id, $field_options[$option_name]);
 		}
 	}
 
 	/**
 	 * Transforms "options_sql" field option into valid "options" array for given field
 	 *
 	 * @param string $field_name
 	 * @access protected
 	 */
 	protected function PrepareFieldOptions($field_name)
 	{
 		$field_options =& $this->Fields[$field_name];
 		if (array_key_exists('options_sql', $field_options) ) {
 			// get options based on given sql
 			$replace_options = Array ('option_title_field', 'option_key_field', 'options_sql');
 			$this->_replaceLanguageId($field_name, $replace_options);
 
 			$select_clause = $this->escapeField($field_options['option_title_field']) . ',' . $this->escapeField($field_options['option_key_field']);
 			$sql = sprintf($field_options['options_sql'], $select_clause);
 
 			if (array_key_exists('serial_name', $field_options)) {
 				// try to cache option sql on serial basis
 				$cache_key = 'sql_' . crc32($sql) . '[%' . $field_options['serial_name'] . '%]';
 				$dynamic_options = $this->Application->getCache($cache_key);
 
 				if ($dynamic_options === false) {
 					$this->Conn->nextQueryCachable = true;
 					$dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field']));
 					$this->Application->setCache($cache_key, $dynamic_options);
 				}
 			}
 			else {
 				// don't cache options sql
 				$dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field']));
 			}
 
 			$options_hash = array_key_exists('options', $field_options) ? $field_options['options'] : Array ();
 			$field_options['options'] = kUtil::array_merge_recursive($options_hash, $dynamic_options); // because of numeric keys
 		}
 	}
 
 	/**
 	 * Returns ID of currently processed record
 	 *
 	 * @return int
 	 * @access public
 	 */
 	public function GetID()
 	{
 		return $this->GetDBField($this->IDField);
 	}
 
 	/**
 	 * Allows kDBTagProcessor.SectionTitle to detect if it's editing or new item creation
 	 *
 	 * @return bool
 	 * @access public
 	 */
 	public function IsNewItem()
 	{
 		return $this->GetID() ? false : true;
 	}
 
 	/**
 	 * Returns parent table information
 	 *
 	 * @param string $special special of main item
 	 * @param bool $guess_special if object retrieved with specified special is not loaded, then try not to use special
 	 * @return Array
 	 * @access public
 	 */
 	public function getLinkedInfo($special = '', $guess_special = false)
 	{
-		$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
-		if ($parent_prefix) {
+		$config = $this->getUnitConfig();
+		$parent_prefix = $config->getParentPrefix();
+
+		if ( $parent_prefix ) {
 			// if this is linked table, then set id from main table
 			$table_info = Array (
-				'TableName' => $this->Application->getUnitOption($this->Prefix,'TableName'),
-				'IdField' => $this->Application->getUnitOption($this->Prefix,'IDField'),
-				'ForeignKey' => $this->Application->getUnitOption($this->Prefix,'ForeignKey'),
-				'ParentTableKey' => $this->Application->getUnitOption($this->Prefix,'ParentTableKey'),
+				'TableName' => $config->getTableName(),
+				'IdField' => $config->getIDField(),
+				'ForeignKey' => $config->getForeignKey($parent_prefix),
+				'ParentTableKey' => $config->getParentTableKey($parent_prefix),
 				'ParentPrefix' => $parent_prefix
 			);
 
-			if (is_array($table_info['ForeignKey'])) {
-					$table_info['ForeignKey'] = getArrayValue($table_info, 'ForeignKey', $parent_prefix);
-			}
-
-			if (is_array($table_info['ParentTableKey'])) {
-				$table_info['ParentTableKey'] = getArrayValue($table_info, 'ParentTableKey', $parent_prefix);
-			}
-
-			$main_object = $this->Application->recallObject($parent_prefix.'.'.$special, null, Array ('raise_warnings' => 0));
+			$main_object = $this->Application->recallObject($parent_prefix . '.' . $special, null, Array ('raise_warnings' => 0));
 			/* @var $main_object kDBItem */
 
-			if (!$main_object->isLoaded() && $guess_special) {
+			if ( !$main_object->isLoaded() && $guess_special ) {
 				$main_object = $this->Application->recallObject($parent_prefix);
 			}
 
-			return array_merge($table_info, Array('ParentId'=> $main_object->GetDBField( $table_info['ParentTableKey'] ) ) );
+			$table_info['ParentId'] = $main_object->GetDBField($table_info['ParentTableKey']);
+
+			return $table_info;
 		}
 
 		return false;
 	}
 
 	/**
 	 * Returns true, when list/item was queried/loaded
 	 *
 	 * @return bool
 	 * @access protected
 	 */
 	abstract protected function isLoaded();
 
 	/**
 	 * Returns specified field value from all selected rows.
 	 * Don't affect current record index
 	 *
 	 * @param string $field
 	 * @return Array
 	 * @access protected
 	 */
 	abstract protected function GetCol($field);
 }
 
 
 /**
  * Base class for exceptions, that trigger redirect action once thrown
  */
 class kRedirectException extends Exception {
 
 	/**
 	 * Redirect template
 	 *
 	 * @var string
 	 * @access protected
 	 */
 	protected $template = '';
 
 	/**
 	 * Redirect params
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $params = Array ();
 
 	/**
 	 * Creates redirect exception
 	 *
 	 * @param string $message
 	 * @param int $code
 	 * @param Exception $previous
 	 */
 	public function __construct($message = '', $code = 0, $previous = NULL)
 	{
 		parent::__construct($message, $code, $previous);
 	}
 
 	/**
 	 * Initializes exception
 	 *
 	 * @param string $template
 	 * @param Array $params
 	 * @return void
 	 * @access public
 	 */
 	public function setup($template, $params = Array ())
 	{
 		$this->template = $template;
 		$this->params = $params;
 	}
 
 	/**
 	 * Display exception details in debugger (only useful, when DBG_REDIRECT is enabled) and performs redirect
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function run()
 	{
 		$application =& kApplication::Instance();
 
 		if ( $application->isDebugMode() ) {
 			$application->Debugger->appendException($this);
 		}
 
 		$application->Redirect($this->template, $this->params);
 	}
 }
 
 
 /**
  * Exception, that is thrown when user don't have permission to perform requested action
  */
 class kNoPermissionException extends kRedirectException {
 
 }
Index: branches/5.3.x/core/units/priorites/priority_eh.php
===================================================================
--- branches/5.3.x/core/units/priorites/priority_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/priorites/priority_eh.php	(revision 15698)
@@ -1,399 +1,400 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class PriorityEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnRecalculatePriorities' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Define alternative event processing method names
 	 *
 	 * @return void
 	 * @see kEventHandler::$eventMethods
 	 * @access protected
 	 */
 	protected function mapEvents()
 	{
 		parent::mapEvents();
 
 		$events_map = Array (
 			'OnMassMoveUp' => 'OnChangePriority',
 			'OnMassMoveDown' => 'OnChangePriority',
 		);
 
 		$this->eventMethods = array_merge($this->eventMethods, $events_map);
 	}
 
 	/**
 	 * Occurs, when config was parsed, allows to change config data dynamically
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterConfigRead(kEvent $event)
 	{
 		parent::OnAfterConfigRead($event);
 
 		$hooks = Array(
 			Array(
 				'Mode' => hAFTER,
 				'Conditional' => false,
 				'HookToPrefix' => '',
 				'HookToSpecial' => '*',
 				'HookToEvent' => Array('OnAfterItemLoad', 'OnPreCreate', 'OnListBuild'),
 				'DoPrefix' => 'priority',
 				'DoSpecial' => '*',
 				'DoEvent' => 'OnPreparePriorities',
 				'Conditional' => false,
 			),
 			Array(
 				'Mode' => hBEFORE,
 				'Conditional' => false,
 				'HookToPrefix' => '',
 				'HookToSpecial' => '*',
 				'HookToEvent' => Array('OnPreSaveCreated'),
 				'DoPrefix' => 'priority',
 				'DoSpecial' => '*',
 				'DoEvent' => 'OnPreparePriorities',
 				'Conditional' => false,
 			),
 			Array(
 				'Mode' => hAFTER,
 				'Conditional' => false,
 				'HookToPrefix' => '',
 				'HookToSpecial' => '*',
 				'HookToEvent' => Array('OnPreSave', 'OnPreSaveCreated', 'OnSave', 'OnUpdate'),
 				'DoPrefix' => 'priority',
 				'DoSpecial' => '*',
 				'DoEvent' => 'OnSavePriorityChanges',
 				'Conditional' => false,
 			),
 			Array(
 				'Mode' => hAFTER,
 				'Conditional' => false,
 				'HookToPrefix' => '',
 				'HookToSpecial' => '*',
 				'HookToEvent' => Array('OnSave'),
 				'DoPrefix' => 'priority',
 				'DoSpecial' => '*',
 				'DoEvent' => 'OnSaveItems',
 				'Conditional' => false,
 			),
 			Array(
 				'Mode' => hBEFORE,
 				'Conditional' => false,
 				'HookToPrefix' => '',
 				'HookToSpecial' => '*',
 				'HookToEvent' => Array('OnBeforeItemCreate'),
 				'DoPrefix' => 'priority',
 				'DoSpecial' => '*',
 				'DoEvent' => 'OnItemCreate',
 				'Conditional' => false,
 			),
 			Array(
 				'Mode' => hBEFORE,
 				'Conditional' => false,
 				'HookToPrefix' => '',
 				'HookToSpecial' => '*',
 				'HookToEvent' => Array('OnAfterItemDelete'),
 				'DoPrefix' => 'priority',
 				'DoSpecial' => '*',
 				'DoEvent' => 'OnItemDelete',
 				'Conditional' => false,
 			)
 		);
 
-		$prefixes = $this->Application->getUnitOption($event->Prefix, 'ProcessPrefixes', Array ());
-		/* @var $prefixes Array */
+		$prefixes = $event->getUnitConfig()->getProcessPrefixes(Array ());
 
 		foreach ($prefixes as $prefix) {
 			foreach ($hooks as $hook) {
 				if ( !is_array($hook['HookToEvent']) ) {
 					$hook['HookToEvent'] = Array($hook['HookToEvent']);
 				}
 
 				foreach ($hook['HookToEvent'] as $hook_event) {
 					$this->Application->registerHook(
 						$prefix . '.' . $hook['HookToSpecial'] . ':' . $hook_event,
 						$event->Prefix . '.' . $hook['DoSpecial'] . ':' . $hook['DoEvent'],
 						$hook['Mode'],
 						$hook['Conditional']
 					);
 				}
 			}
 		}
 	}
 
 	/**
 	 * Should be hooked to OnAfterItemLoad, OnPreSaveCreated (why latter?)
 	 *
 	 * @param kEvent $event
 	 */
 	function OnPreparePriorities($event)
 	{
 		if ( !$this->Application->isAdminUser ) {
 			return ;
 		}
 
 		$priority_helper = $this->Application->recallObject('PriorityHelper');
 		/* @var $priority_helper kPriorityHelper */
 
 		list ($constrain, $joins) = $this->getConstrainInfo($event);
 		$is_new = $event->MasterEvent->Name == 'OnPreCreate' || $event->MasterEvent->Name == 'OnPreSaveCreated';
 		$priority_helper->preparePriorities($event->MasterEvent, $is_new, $constrain, $joins);
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSavePriorityChanges($event)
 	{
 		if ($event->MasterEvent->status != kEvent::erSUCCESS) {
 			// don't update priorities, when OnSave validation failed
 			return ;
 		}
 
 		$object = $event->MasterEvent->getObject();
+		/* @var $object kDBItem */
 
 		$tmp = $this->Application->RecallVar('priority_changes'.$this->Application->GetVar('m_wid'));
 		$changes = $tmp ? unserialize($tmp) : array();
 
 		if (!isset($changes[$object->GetID()])) {
 			$changes[$object->GetId()]['old'] = $object->GetID() == 0 ? 'new' : $object->GetDBField('OldPriority');
 		}
 
 		if ($changes[$object->GetId()]['old'] == $object->GetDBField('Priority')) return ;
 		$changes[$object->GetId()]['new'] = $object->GetDBField('Priority');
 
 		list ($constrain, $joins) = $this->getConstrainInfo($event);
 
 		if ($constrain) {
 			$changes[$object->GetId()]['constrain'] = $constrain;
 		}
 
 		$this->Application->StoreVar('priority_changes'.$this->Application->GetVar('m_wid'), serialize($changes));
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 */
 	function OnItemDelete($event)
 	{
 		// just store the prefix in which the items were deleted
 		$del = $this->Application->RecallVar('priority_deleted' . $this->Application->GetVar('m_wid'));
 		$del = $del ? unserialize($del) : array();
 
 		list ($constrain, $joins) = $this->getConstrainInfo($event);
 		$cache_key = crc32($event->MasterEvent->Prefix . ':' . $constrain . ':' . $joins);
 
 		if ( !isset($del[$cache_key]) ) {
 			$del[$cache_key] = Array (
 				'prefix' => $event->MasterEvent->Prefix,
 				'constrain' => $constrain,
 				'joins' => $joins,
 			);
 
 			$this->Application->StoreVar('priority_deleted' . $this->Application->GetVar('m_wid'), serialize($del));
 		}
 	}
 
 	/**
 	 * Called before script shut-down and recalculate all deleted prefixes, to avoid recalculation on each deleted item
 	 *
 	 * @param kEvent $event
 	 */
 	function OnBeforeShutDown($event)
 	{
 		$del = $this->Application->RecallVar('priority_deleted'.$this->Application->GetVar('m_wid'));
 		$del = $del ? unserialize($del) : array();
 
 		$priority_helper = $this->Application->recallObject('PriorityHelper');
 		/* @var $priority_helper kPriorityHelper */
 
 		foreach ($del as $del_info) {
 			$dummy_event = new kEvent( array('prefix'=>$del_info['prefix'], 'name'=>'Dummy' ) );
 			$ids = $priority_helper->recalculatePriorities($dummy_event, $del_info['constrain'], $del_info['joins']);
 
 			if ($ids) {
 				$priority_helper->massUpdateChanged($del_info['prefix'], $ids);
 			}
 		}
 
 		$this->Application->RemoveVar('priority_deleted'.$this->Application->GetVar('m_wid'));
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 */
 	function OnSaveItems($event)
 	{
 		$tmp = $this->Application->RecallVar('priority_changes'.$this->Application->GetVar('m_wid'));
 		$changes = $tmp ? unserialize($tmp) : array();
 
 		$priority_helper = $this->Application->recallObject('PriorityHelper');
 		/* @var $priority_helper kPriorityHelper */
 
 		list ($constrain, $joins) = $this->getConstrainInfo($event);
 		$ids = $priority_helper->updatePriorities($event->MasterEvent, $changes, Array (0 => $event->MasterEvent->getEventParam('ids')), $constrain, $joins);
 
 		if ($ids) {
 			$priority_helper->massUpdateChanged($event->MasterEvent->Prefix, $ids);
 		}
 	}
 
 	function OnItemCreate($event)
 	{
 		$obj = $event->MasterEvent->getObject();
 		if ($obj->GetDBField('Priority') == 0) {
 			$priority_helper = $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			list ($constrain, $joins) = $this->getConstrainInfo($event);
 			$priority_helper->preparePriorities($event->MasterEvent, true, $constrain, $joins);
 		}
 	}
 
 	/**
 	 * Processes OnMassMoveUp, OnMassMoveDown events
 	 *
 	 * @param kEvent $event
 	 */
 	function OnChangePriority($event)
 	{
 		$prefix = $this->Application->GetVar('priority_prefix');
 		$dummy_event = new kEvent( array('prefix'=>$prefix, 'name'=>'Dummy' ) );
 
 		$ids = $this->StoreSelectedIDs($dummy_event);
 
 		if ($ids) {
-			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+			$config = $this->Application->getUnitConfig($prefix);
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			if ( $this->Application->IsTempMode($prefix) ) {
 				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $prefix);
 			}
 
 			$sql = 'SELECT Priority, '.$id_field.'
 					FROM '.$table_name.'
 					WHERE '.$id_field.' IN ('.implode(',', $ids).') ORDER BY Priority DESC';
 			$priorities = $this->Conn->GetCol($sql, $id_field);
 
 			$priority_helper = $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			list ($constrain, $joins) = $this->getConstrainInfo($event);
 
 			$sql = 'SELECT IFNULL(MIN(item_table.Priority), -1)
 					FROM '.$table_name . ' item_table
 					' . $joins;
 
 			if ( $constrain ) {
 				$sql .= ' WHERE ' . $priority_helper->normalizeConstrain($constrain);
 			}
 
 			$min_priority = $this->Conn->GetOne($sql);
 
 			foreach ($ids as $id) {
 				$new_priority = $priorities[$id] + ($event->Name == 'OnMassMoveUp' ? +1 : -1);
 				if ($new_priority > -1 || $new_priority < $min_priority) {
 					continue;
 				}
 
 				$changes = Array (
 					$id	=>	Array ('old' => $priorities[$id], 'new' => $new_priority),
 				);
 
 				if ($constrain) {
 					$changes[$id]['constrain'] = $constrain;
 				}
 
 				$sql = 'UPDATE '.$table_name.'
 						SET Priority = '.$new_priority.'
 						WHERE '.$id_field.' = '.$id;
 				$this->Conn->Query($sql);
 
 				$ids = $priority_helper->updatePriorities($dummy_event, $changes, Array ($id => $id), $constrain, $joins);
 
 				if ($ids) {
 					$priority_helper->massUpdateChanged($prefix, $ids);
 				}
 			}
 		}
 
 		$this->clearSelectedIDs($dummy_event);
 	}
 
 	/**
 	 * Completely recalculates priorities in current category
 	 *
 	 * @param kEvent $event
 	 */
 	function OnRecalculatePriorities($event)
 	{
 		$priority_helper = $this->Application->recallObject('PriorityHelper');
 		/* @var $priority_helper kPriorityHelper */
 
 		$prefix = $this->Application->GetVar('priority_prefix');
 		$dummy_event = new kEvent($prefix . ':Dummy');
 
 		list ($constrain, $joins) = $this->getConstrainInfo($event);
 		$ids = $priority_helper->recalculatePriorities($dummy_event, $constrain, $joins);
 
 		if ($ids) {
 			$priority_helper->massUpdateChanged($prefix, $ids);
 		}
 	}
 
 	/**
 	 * Returns constrain for current priority calculations
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function getConstrainInfo($event)
 	{
 		$constrain_event = new kEvent($event->MasterEvent->getPrefixSpecial() . ':OnGetConstrainInfo');
 		$constrain_event->setEventParam('actual_event', $event->Name);
 		$constrain_event->setEventParam('original_special', $event->MasterEvent->Special);
 		$constrain_event->setEventParam('original_event', $event->MasterEvent->Name);
 		$this->Application->HandleEvent($constrain_event);
 
 		return $constrain_event->getEventParam('constrain_info');
 	}
 }
Index: branches/5.3.x/core/units/visits/visits_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/visits/visits_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/visits/visits_tag_processor.php	(revision 15698)
@@ -1,182 +1,184 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class VisitsTagProcessor extends kDBTagProcessor {
 
 		function UserFound($params)
 		{
 			$virtual_users = Array(USER_ROOT, USER_GUEST, 0);
 
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			return !in_array( $object->GetDBField( $params['user_field'] ) , $virtual_users );
 		}
 
 		/**
 		 * Returns link for user editing
 		 *
 		 * @param Array $params
 		 *
 		 * @return string
 		 * @access protected
 		 */
 		protected function UserLink($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$user_id = $object->GetDBField( $params['user_field'] );
 
 			if (!$user_id) {
 				return '';
 			}
 
 			$url_params =  Array (
 				'm_opener' => 'd',
 				'u_mode' => 't',
 				'u_event' => 'OnEdit',
 				'u_id' => $user_id,
 				'pass' => 'all,u'
 			);
 
 			return $this->Application->HREF($params['edit_template'], '', $url_params);
 		}
 
 		function getDateLimitClause($field)
 		{
 			$search_filter = $this->Application->RecallVar( $this->getPrefixSpecial().'_search_filter');
 			if($search_filter)
 			{
 				$search_filter = unserialize($search_filter);
 				return $search_filter[$field]['value'];
 			}
 			return '';
 		}
 
 		function AffiliateOrderInfo($params)
 		{
 			$list =& $this->GetList($params);
 
 			$date_limit = str_replace($list->TableName, 'vis', $this->getDateLimitClause('VisitDate') );
 
-			$affil_table = $this->Application->getUnitOption('affil', 'TableName');
-			$affil_idfield = $this->Application->getUnitOption('affil', 'IDField');
-			$sql = 'SELECT '.$affil_idfield.' FROM '.$affil_table.' WHERE PortalUserId = '.$this->Application->RecallVar('user_id');
+			$affiliates_config = $this->Application->getUnitConfig('affil');
+
+			$sql = 'SELECT '. $affiliates_config->getIDField() .'
+					FROM '. $affiliates_config->getTableName() .'
+					WHERE PortalUserId = '.$this->Application->RecallVar('user_id');
 			$affiliate_id = $this->Conn->GetOne($sql);
 
 			$sql = 'SELECT COUNT(ord.OrderId) AS OrderCount
 					FROM '.$list->TableName.' vis
 					LEFT JOIN '.TABLE_PREFIX.'Orders ord ON ord.VisitId = vis.VisitId
 					WHERE (vis.AffiliateId = '.$affiliate_id.') AND (ord.Status = '.ORDER_STATUS_PROCESSED.')'.($date_limit ? ' AND '.$date_limit : '');
 
 			$result = $this->Conn->GetRow($sql);
 
 			$sql = 'SELECT COUNT(*) FROM '.$list->TableName.' vis
 					WHERE AffiliateId = '.$affiliate_id.($date_limit ? ' AND '.$date_limit : '');
 
 			$result['TotalVisitors'] = $this->Conn->GetOne($sql);
 			$result['OrderTotalAmount'] = $list->getTotal('OrderTotalAmount', 'SUM');
 			$result['OrderAffiliateCommission'] = $list->getTotal('OrderAffiliateCommission', 'SUM');
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 
 			$format_fields = Array('OrderTotalAmount', 'OrderAffiliateCommission');
 
 			if (array_key_exists('currency', $params) && $params['currency']) {
 				$iso = $this->GetISO($params['currency']);
 				foreach($format_fields as $format_field)
 				{
 					$format = $list->GetFieldOption($format_field, 'format');
 					$value = sprintf($format, $result[$format_field]);
 					$value = $this->ConvertCurrency($value, $iso);
 					$value = $this->AddCurrencySymbol($value, $iso);
 					$result[$format_field] = $value;
 				}
 			}
 
 			$block_params = array_merge($block_params, $result);
 
 			return $this->Application->ParseBlock($block_params);
 		}
 
 		function ListVisitors($params)
 		{
 			$o = '';
 
 			$params['render_as'] = $params['item_render_as'];
 
 			$o_visitors = $this->PrintList2($params);
 
 			if($o_visitors)
 			{
 				$header = '';
 				$footer = '';
 
 				$block_params = $this->prepareTagParams($params);
 
 				$header_block = getArrayValue($params, 'header_render_as');
 				if($header_block)
 				{
 					$block_params['name'] = $header_block;
 					$header = $this->Application->ParseBlock($block_params);
 				}
 
 				$footer_block = getArrayValue($params, 'footer_render_as');
 				if($footer_block)
 				{
 					$block_params['name'] = $footer_block;
 					$footer = $this->Application->ParseBlock($block_params);
 				}
 
 				$o = $header.$o_visitors.$footer;
 			}
 			else
 			{
 				$visitors_params = array('name' => $params['empty_myvisitors_render_as']);
 				$o = $this->Application->ParseBlock($visitors_params);
 			}
 
 			return $o;
 
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param string $params
 		 * @return kDBList
 		 */
 		function &GetList($params)
 		{
 			$list_name = $this->SelectParam($params, 'list_name,name');
 			if ( !$list_name ) {
 				$list_name = $this->Application->Parser->GetParam('list_name');
 			}
 
 			$types = $this->SelectParam($params, 'types');
 
 			if ( $types == 'myvisitororders' || $types == 'myvisitors' ) {
 				$special = 'incommerce';
 				$names_mapping = $this->Application->GetVar('NamesToSpecialMapping', Array ());
 				$names_mapping[$this->Prefix][$list_name] = $special;
 				$this->Application->SetVar('NamesToSpecialMapping', $names_mapping);
 			}
 
 			return parent::GetList($params);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/thesaurus/thesaurus_tp.php
===================================================================
--- branches/5.3.x/core/units/thesaurus/thesaurus_tp.php	(revision 15697)
+++ branches/5.3.x/core/units/thesaurus/thesaurus_tp.php	(revision 15698)
@@ -1,96 +1,96 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ThesaurusTagProcessor extends kDBTagProcessor {
 
 		function SubSearchLink($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$params['search_type'] = 'subsearch';
 			$params['keywords'] = $object->GetDBField('ThesaurusTerm');
 			$params['narrow_search'] = 1;
 
 			return $this->Application->ProcessParsedTag('m', 'Link', $params);
 		}
 
 		function _getThesaurusRecords()
 		{
 			$keywords = htmlspecialchars_decode( trim($this->Application->GetVar('keywords')) );
-			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
+			$table_name = $this->getUnitConfig()->getTableName();
 
 			$sql = 'SELECT *
 					FROM ' . $table_name . '
 					WHERE SearchTerm LIKE ' . $this->Conn->qstr($keywords).' OR SearchTerm LIKE ' . $this->Conn->qstr($keywords . '_');
 			$records = $this->Conn->Query($sql);
 
 			$new_records = Array ();
 			foreach ($records as $record) {
 				if ($record['ThesaurusType'] == THESAURUS_TYPE_USE) {
 					// expand in global scope
 					$sql = 'SELECT *
 							FROM ' . $table_name . '
 							WHERE SearchTerm = ' . $this->Conn->qstr($record['ThesaurusTerm']);
 					$use_records = $this->Conn->Query($sql);
 					if ($use_records) {
 						$new_records = array_merge($new_records, $use_records);
 					}
 				}
 
 				$new_records[] = $record;
 			}
 
 			usort($new_records, Array (&$this, '_sortThesaurus'));
 			ksort($new_records);
 
 			return $new_records;
 		}
 
 		function HasThesaurus($params)
 		{
 			$new_records = $this->_getThesaurusRecords();
 			return count($new_records) > 0;
 		}
 
 		function PrintThesaurus($params)
 		{
 			$new_records = $this->_getThesaurusRecords();
 
 			$ret = '';
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 			foreach ($new_records as $record) {
 				$block_params['term'] = $record['ThesaurusTerm'];
 
 				$url_params = Array (
 					'search_type' => 'subsearch',
 					'keywords' => $record['ThesaurusTerm'],
 					'narrow_search' => 1,
 				);
 				$block_params['url'] = $this->Application->ProcessParsedTag('m', 'Link', $url_params);
 
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 
 			return $ret;
 		}
 
 		function _sortThesaurus($record_a, $record_b)
 		{
 			return strcmp(strtolower($record_a['ThesaurusTerm']), strtolower($record_b['ThesaurusTerm']));
 		}
 	}
Index: branches/5.3.x/core/units/categories/categories_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/categories/categories_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/categories/categories_tag_processor.php	(revision 15698)
@@ -1,2239 +1,2243 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class CategoriesTagProcessor extends kDBTagProcessor {
 
 	function SubCatCount($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		if ( isset($params['today']) && $params['today'] ) {
 			$sql = 'SELECT COUNT(*)
 					FROM ' . $object->TableName . '
 					WHERE (ParentPath LIKE "' . $object->GetDBField('ParentPath') . '%") AND (CreatedOn > ' . (adodb_mktime() - 86400) . ')';
 			return $this->Conn->GetOne($sql) - 1;
 		}
 
 		return $object->GetDBField('CachedDescendantCatsQty');
 	}
 
 	/**
 	 * Returns category count in system
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function CategoryCount($params)
 	{
 		$count_helper = $this->Application->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		$today_only = isset($params['today']) && $params['today'];
 		return $count_helper->CategoryCount($today_only);
 	}
 
 	function IsNew($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return $object->GetDBField('IsNew') ? 1 : 0;
 	}
 
 	function IsPick($params)
 	{
 		return $this->IsEditorsPick($params);
 	}
 
 	/**
 	 * Returns item's editors pick status (using not formatted value)
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsEditorsPick($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return $object->GetDBField('EditorsPick') == 1;
 	}
 
 	function ItemIcon($params)
 	{
-		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-		$grid = $grids[ $params['grid'] ];
+		$grid = $this->getUnitConfig()->getGridByName($params['grid']);
 
-		if (!array_key_exists('Icons', $grid)) {
+		if ( !array_key_exists('Icons', $grid) ) {
 			return '';
 		}
 
 		$icons = $grid['Icons'];
-		$icon_prefix = array_key_exists('icon_prefix', $params)? $params['icon_prefix'] : 'icon16_';
+		$icon_prefix = array_key_exists('icon_prefix', $params) ? $params['icon_prefix'] : 'icon16_';
 
-		if (array_key_exists('name', $params)) {
+		if ( array_key_exists('name', $params) ) {
 			$icon_name = $params['name'];
+
 			return array_key_exists($icon_name, $icons) ? $icons[$icon_name] : '';
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
-		if ($object->GetDBField('ThemeId') > 0) {
-			if (!$object->GetDBField('IsMenu')) {
+		if ( $object->GetDBField('ThemeId') > 0 ) {
+			if ( !$object->GetDBField('IsMenu') ) {
 				return $icon_prefix . 'section_menuhidden_system.png';
 			}
+
 			return $icon_prefix . 'section_system.png';
 		}
 
 		$status = $object->GetDBField('Status');
 
-		if ($status == STATUS_DISABLED) {
+		if ( $status == STATUS_DISABLED ) {
 			return $icon_prefix . 'section_disabled.png';
 		}
 
-		if (!$object->GetDBField('IsMenu')) {
+		if ( !$object->GetDBField('IsMenu') ) {
 			return $icon_prefix . 'section_menuhidden.png';
 		}
 
-		if ($status == STATUS_PENDING) {
+		if ( $status == STATUS_PENDING ) {
 			return $icon_prefix . 'section_pending.png';
 		}
 
-		if ($object->GetDBField('IsNew') && ($icon_prefix == 'icon16_')) {
+		if ( $object->GetDBField('IsNew') && ($icon_prefix == 'icon16_') ) {
 			return $icon_prefix . 'section_new.png'; // show gris icon only in grids
 		}
 
 		return $icon_prefix . 'section.png';
 	}
 
 	function ItemCount($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
-		$ci_table = $this->Application->getUnitOption('ci', 'TableName');
+		$ci_table = $this->Application->getUnitConfig('ci')->getTableName();
 
 		$module_prefixes = implode(',', $this->Conn->qstrArray($this->_getModulePrefixes()));
 
 		$sql = 'SELECT COUNT(*)
 				FROM ' . $object->TableName . ' c
 				JOIN ' . $ci_table . ' ci ON c.CategoryId = ci.CategoryId
 				WHERE (c.TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight') . ') AND (ci.ItemPrefix IN (' . $module_prefixes . '))';
 
 		return $this->Conn->GetOne($sql);
 	}
 
 	function _getModulePrefixes()
 	{
 		$ret = Array ();
 
 		foreach ($this->Application->ModuleInfo as $module_info) {
 			$ret[] = $module_info['Var'];
 		}
 
 		return array_unique($ret);
 	}
 
 	function ListCategories($params)
 	{
 		return $this->PrintList2($params);
 	}
 
 	function RootCategoryName($params)
 	{
 		return $this->Application->ProcessParsedTag('m', 'RootCategoryName', $params);
 	}
 
 	function CheckModuleRoot($params)
 	{
 		$module_name = getArrayValue($params, 'module') ? $params['module'] : 'In-Commerce';
 		$module_root_cat = $this->Application->findModule('Name', $module_name, 'RootCat');
 
 		$additional_cats = $this->SelectParam($params, 'add_cats');
 		if ($additional_cats) {
 			$additional_cats = explode(',', $additional_cats);
 		}
 		else {
 			$additional_cats = array();
 		}
 
 		if ($this->Application->GetVar('m_cat_id') == $module_root_cat || in_array($this->Application->GetVar('m_cat_id'), $additional_cats)) {
 			$home_template = getArrayValue($params, 'home_template');
 
 			if ( !$home_template ) {
 				return;
 			}
 
 			$this->Application->Redirect($home_template, Array('pass'=>'all'));
 		};
 	}
 
 	function CategoryPath($params)
 	{
 		$navigation_bar = $this->Application->recallObject('kNavigationBar');
 		/* @var $navigation_bar kNavigationBar */
 
 		return $navigation_bar->build($params);
 	}
 
 	/**
 	 * Shows category path to specified category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function FieldCategoryPath($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$field = $this->SelectParam($params, 'name,field');
 		$category_id = $object->GetDBField($field);
 
 		if ($category_id) {
 			$params['cat_id'] = $category_id;
 
 			$navigation_bar = $this->Application->recallObject('kNavigationBar');
 			/* @var $navigation_bar kNavigationBar */
 
 			return $navigation_bar->build($params);
 		}
 
 		return '';
 	}
 
 	function CurrentCategoryName($params)
 	{
 		$cat_object = $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List');
 		/* @var $cat_object kDBList */
 
 		$sql = 'SELECT '.$this->getTitleField().'
 				FROM '.$cat_object->TableName.'
 				WHERE CategoryId = '.(int)$this->Application->GetVar('m_cat_id');
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Returns current category name
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Find where it's used
 	 */
 	function CurrentCategory($params)
 	{
 		return $this->CurrentCategoryName($params);
 	}
 
 	function getTitleField()
 	{
 		$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 		/* @var $ml_formatter kMultiLanguage */
 
 		return $ml_formatter->LangFieldName('Name');
 	}
 
 	/**
 	 * Returns symlinked category for given category
 	 *
 	 * @param int $category_id
 	 * @return int
 	 */
 	function getCategorySymLink($category_id)
 	{
 		if (!$category_id) {
 			// don't bother to get symlink for "Home" category
 			return $category_id;
 		}
 
 		$cache_key = 'category_symlinks[%CSerial%]';
 		$cache = $this->Application->getCache($cache_key);
 
 		if ($cache === false) {
-			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
+			$config = $this->getUnitConfig();
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			// get symlinked categories, that are not yet deleted
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT c1.SymLinkCategoryId, c1.' . $id_field . '
 					FROM ' . $table_name . ' c1
 					JOIN ' . $table_name . ' c2 ON c1.SymLinkCategoryId = c2.' . $id_field;
 			$cache = $this->Conn->GetCol($sql, $id_field);
 
 			$this->Application->setCache($cache_key, $cache);
 		}
 
 		return array_key_exists($category_id, $cache) ? $cache[$category_id] : $category_id;
 	}
 
 	function CategoryLink($params)
 	{
 		$category_id = getArrayValue($params, 'cat_id');
 
 		if ( $category_id === false ) {
 			$category_id = $this->Application->GetVar($this->getPrefixSpecial() . '_id');
 		}
 
 		if ( "$category_id" == 'Root' ) {
 			$category_id = $this->Application->findModule('Name', $params['module'], 'RootCat');
 		}
 		elseif ( "$category_id" == 'current' ) {
 			$category_id = $this->Application->GetVar('m_cat_id');
 		}
 
 		if ( !array_key_exists('direct_link', $params) || !$params['direct_link'] ) {
 			$category_id = $this->getCategorySymLink((int)$category_id);
 		}
 		else {
 			unset($params['direct_link']);
 		}
 
 		$virtual_template = $this->Application->getVirtualPageTemplate($category_id);
 
 		if ( ($virtual_template !== false) && preg_match('/external:(.*)/', $virtual_template, $rets) ) {
 			// external url (return here, instead of always replacing $params['t'] for kApplication::HREF to find it)
 			return $rets[1];
 		}
 
 		unset($params['cat_id'], $params['module']);
 		$new_params = Array ('pass' => 'm', 'm_cat_id' => $category_id, 'pass_category' => 1);
 		$params = array_merge($params, $new_params);
 
 		return $this->Application->ProcessParsedTag('m', 't', $params);
 	}
 
 	function CategoryList($params)
 	{
 		//$object = $this->Application->recallObject( $this->getPrefixSpecial() , $this->Prefix.'_List', $params );
 		$object =& $this->GetList($params);
 
 		if ($object->GetRecordsCount() == 0)
 		{
 			if (isset($params['block_no_cats'])) {
 				$params['name'] = $params['block_no_cats'];
 				return $this->Application->ParseBlock($params);
 			}
 			else {
 				return '';
 			}
 		}
 
 		if (isset($params['block'])) {
 			return $this->PrintList($params);
 		}
 		else {
 			$params['block'] = $params['block_main'];
 			if (isset($params['block_row_start'])) {
 				$params['row_start_block'] = $params['block_row_start'];
 			}
 
 			if (isset($params['block_row_end'])) {
 				$params['row_end_block'] = $params['block_row_end'];
 			}
 			return $this->PrintList2($params);
 		}
 	}
 
 	function Meta($params)
 	{
 		$object = $this->Application->recallObject($this->Prefix); // .'.-item'
 		/* @var $object CategoriesItem */
 
 		$meta_type = $params['name'];
 		if ($object->isLoaded()) {
 			// 1. get module prefix by current category
 			$category_helper = $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$category_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 			$module_info = $category_helper->getCategoryModule($params,  $category_path);
 
 			// In-Edit & Proj-CMS module prefixes doesn't have custom field with item template
 			if ($module_info && $module_info['Var'] != 'adm' && $module_info['Var'] != 'st') {
 
 				// 2. get item template by current category & module prefix
 				$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 				/* @var $rewrite_processor kRewriteUrlProcessor */
 
 				$category_params = Array (
 					'CategoryId' => $object->GetID(),
 					'ParentPath' => $object->GetDBField('ParentPath'),
 				);
 
 				$item_template = $rewrite_processor->GetItemTemplate($category_params, $module_info['Var']);
 
 				if ($this->Application->GetVar('t') == $item_template) {
 					// we are located on item's details page
 					$item = $this->Application->recallObject($module_info['Var']);
 					/* @var $item kCatDBItem */
 
 					// 3. get item's meta data
 					$value = $item->GetField('Meta'.$meta_type);
 					if ($value) {
 						return $value;
 					}
 				}
 
 				// 4. get category meta data
 				$value = $object->GetField('Meta'.$meta_type);
 				if ($value) {
 					return $value;
 				}
 			}
 		}
 
 		// 5. get default meta data
 		switch ($meta_type) {
 			case 'Description':
 				$config_name = 'Category_MetaDesc';
 				break;
 			case 'Keywords':
 				$config_name = 'Category_MetaKey';
 				break;
 		}
 
 		return $this->Application->ConfigValue($config_name);
 	}
 
 	function BuildListSpecial($params)
 	{
 		if (($this->Special != '') && !is_numeric($this->Special)) {
 			// When recursive category list is printed (like in sitemap), then special
 			// should be generated even if it's already present. Without it list on this
 			// level will erase list on previous level, because it will be stored in same object.
 			return $this->Special;
 		}
 
 		if ( isset($params['parent_cat_id']) ) {
 			$parent_cat_id = $params['parent_cat_id'];
 		}
 		else {
 			$parent_cat_id = $this->Application->GetVar($this->Prefix.'_id');
 			if (!$parent_cat_id) {
 				$parent_cat_id = $this->Application->GetVar('m_cat_id');
 			}
 			if (!$parent_cat_id) {
 				$parent_cat_id = 0;
 			}
 		}
 
 		$list_unique_key = $this->getUniqueListKey($params);
 		// check for "admin" variable, because we are parsing front-end template from admin when using template editor feature
 		if ($this->Application->GetVar('admin') || !$this->Application->isAdmin) {
 			// add parent category to special, when on Front-End,
 			// because there can be many category lists on same page
 			$list_unique_key .= $parent_cat_id;
 		}
 
 		if ($list_unique_key == '') {
 			return parent::BuildListSpecial($params);
 		}
 
 		return crc32($list_unique_key);
 	}
 
 	function IsCurrent($params)
 	{
 		$object = $this->getObject($params);
 		if ($object->GetID() == $this->Application->GetVar('m_cat_id')) {
 			return true;
 		}
 		else {
 			return false;
 		}
 	}
 
 	/**
 	 * Substitutes category in last template base on current category
 	 * This is required becasue when you navigate catalog using AJAX, last_template is not updated
 	 * but when you open item edit from catalog last_template is used to build opener_stack
 	 * So, if we don't substitute m_cat_id in last_template, after saving item we'll get redirected
 	 * to the first category we've opened, not the one we navigated to using AJAX
 	 *
 	 * @param Array $params
 	 */
 	function UpdateLastTemplate($params)
 	{
 		$category_id = $this->Application->GetVar('m_cat_id');
 
 		$wid = $this->Application->GetVar('m_wid');
 		list($index_file, $env) = explode('|', $this->Application->RecallVar(rtrim('last_template_'.$wid, '_')), 2);
 
 		$vars_backup = Array ();
 		$vars = $this->Application->processQueryString( str_replace('%5C', '\\', $env) );
 
 		foreach ($vars as $var_name => $var_value) {
 			$vars_backup[$var_name] = $this->Application->GetVar($var_name);
 			$this->Application->SetVar($var_name, $var_value);
 		}
 
 		// update required fields
 		$this->Application->SetVar('m_cat_id', $category_id);
 		$this->Application->Session->SaveLastTemplate($params['template']);
 
 		foreach ($vars_backup as $var_name => $var_value) {
 			$this->Application->SetVar($var_name, $var_value);
 		}
 	}
 
 	function GetParentCategory($params)
 	{
 		$parent_id = $this->Application->getBaseCategory();
 		$category_id = $this->Application->GetVar('m_cat_id');
 
 		if ($category_id != $parent_id) {
+			$config = $this->getUnitConfig();
+
 			$sql = 'SELECT ParentId
-					FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . '
-					WHERE ' . $this->Application->getUnitOption($this->Prefix, 'IDField') . ' = ' . $category_id;
+					FROM ' . $config->getTableName() . '
+					WHERE ' . $config->getIDField() . ' = ' . $category_id;
 			$parent_id = $this->Conn->GetOne($sql);
 		}
 
 		return $parent_id;
 	}
 
 	function InitCacheUpdater($params)
 	{
 		kUtil::safeDefine('CACHE_PERM_CHUNK_SIZE', 30);
 
 		$continue = $this->Application->GetVar('continue');
 		$total_cats = (int)$this->Conn->GetOne('SELECT COUNT(*) FROM ' . TABLE_PREFIX . 'Categories');
 
 		if ( $continue === false ) {
 			$rebuild_mode = $this->Application->ConfigValue('CategoryPermissionRebuildMode');
 
 			if ( $rebuild_mode == CategoryPermissionRebuild::AUTOMATIC && $total_cats > CACHE_PERM_CHUNK_SIZE ) {
 				// first step, if category count > CACHE_PERM_CHUNK_SIZE, then ask for cache update
 				return true;
 			}
 
 			// if we don't have to ask, then assume user selected "Yes" in permcache update dialog
 			$continue = 1;
 		}
 
 		$updater = $this->Application->makeClass('kPermCacheUpdater', Array ($continue));
 		/* @var $updater kPermCacheUpdater */
 
 		if ( $continue === '0' ) { // No in dialog
 			$updater->clearData();
 			$this->Application->Redirect($params['destination_template']);
 		}
 
 		$ret = false; // don't ask for update
 
 		if ( $continue == 1 ) { // Initial run
 			$updater->setData();
 		}
 
 		if ( $continue == 2 ) { // Continuing
 			// called from AJAX request => returns percent
 			$needs_more = true;
 
 			while ( $needs_more && $updater->iteration <= CACHE_PERM_CHUNK_SIZE ) {
 				// until proceeded in this step category count exceeds category per step limit
 				$needs_more = $updater->DoTheJob();
 			}
 
 			if ( $needs_more ) {
 				// still some categories are left for next step
 				$updater->setData();
 			}
 			else {
 				// all done, update left tree and redirect
 				$updater->SaveData();
 
 				$this->Application->HandleEvent(new kEvent('c:OnResetCMSMenuCache'));
 
 				$this->Application->RemoveVar('PermCache_UpdateRequired');
 				$this->Application->StoreVar('RefreshStructureTree', 1);
 				$this->Application->Redirect($params['destination_template']);
 			}
 
 			$ret = $updater->getDonePercent();
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Parses warning block, but with style="display: none;". Used during permissions saving from AJAX
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function SaveWarning($params)
 	{
 		if ( $this->Prefix == 'st' ) {
 			// don't use this method for other prefixes then Categories, that use this tag processor
 			return parent::SaveWarning($params);
 		}
 
 		$main_prefix = getArrayValue($params, 'main_prefix');
 		if ( $main_prefix && $main_prefix != '$main_prefix' ) {
 			$top_prefix = $main_prefix;
 		}
 		else {
 			$top_prefix = $this->Application->GetTopmostPrefix($this->Prefix);
 		}
 
 		$temp_tables = substr($this->Application->GetVar($top_prefix . '_mode'), 0, 1) == 't';
 		$modified = $this->Application->RecallVar($top_prefix . '_modified');
 
 		if ( !$temp_tables ) {
 			$this->Application->RemoveVar($top_prefix . '_modified');
 			return '';
 		}
 
 		$block_name = $this->SelectParam($params, 'render_as,name');
 		if ( $block_name ) {
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $block_name;
 			$block_params['edit_mode'] = $temp_tables ? 1 : 0;
 			$block_params['display'] = $temp_tables && $modified ? 1 : 0;
 			return $this->Application->ParseBlock($block_params);
 		}
 
 		return $temp_tables && $modified ? 1 : 0;
 	}
 
 	/**
 	 * Allows to detect if this prefix has something in clipboard
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function HasClipboard($params)
 	{
 		$clipboard = $this->Application->RecallVar('clipboard');
 		if ($clipboard) {
 			$clipboard = unserialize($clipboard);
 			foreach ($clipboard as $prefix => $clipboard_data) {
 				foreach ($clipboard_data as $mode => $ids) {
 					if ( count($ids) ) {
 						return 1;
 					}
 				}
 			}
 		}
 		return 0;
 	}
 
 	/**
 	 * Allows to detect if root category being edited
 	 *
 	 * @param Array $params
 	 */
 	function IsRootCategory($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object CategoriesItem */
 
 		return $object->IsRoot();
 	}
 
 	/**
 	 * Returns home category id
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function HomeCategory($params)
 	{
 		return $this->Application->getBaseCategory();
 	}
 
 	/**
 	 * Used for disabling "Home" and "Up" buttons in category list
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function ModuleRootCategory($params)
 	{
 		return $this->Application->GetVar('m_cat_id') == $this->Application->getBaseCategory();
 	}
 
 	function CatalogItemCount($params)
 	{
 		$params['skip_quering'] = true;
 		$object =& $this->GetList($params);
 
 		return $object->GetRecordsCount(false) != $object->GetRecordsCount() ? $object->GetRecordsCount().' / '.$object->GetRecordsCount(false) : $object->GetRecordsCount();
 	}
 
 	function InitCatalog($params)
 	{
 		$tab_prefixes = $this->Application->GetVar('tp'); // {all, <prefixes_list>, none}
 		if ( $tab_prefixes === false ) {
 			$tab_prefixes = 'all';
 		}
 
 		$skip_prefixes = isset($params['skip_prefixes']) && $params['skip_prefixes'] ? explode(',', $params['skip_prefixes']) : Array();
 		$replace_main = isset($params['replace_m']) && $params['replace_m'];
 
 		// get all prefixes available
 		$prefixes = Array();
 		foreach ($this->Application->ModuleInfo as $module_name => $module_data) {
 			$prefix = $module_data['Var'];
 
 			if ( $prefix == 'adm' /* || $prefix == 'm'*/ ) {
 				continue;
 			}
 
 			if ($prefix == 'm' && $replace_main) {
 				$prefix = 'c';
 			}
 
 			$prefixes[] = $prefix;
 		}
 
 		if ($tab_prefixes == 'none') {
 			$skip_prefixes = array_unique(array_merge($skip_prefixes, $prefixes));
 			unset($skip_prefixes[ array_search($replace_main ? 'c' : 'm', $skip_prefixes) ]);
 		}
 		elseif ($tab_prefixes != 'all') {
 			// prefix list here
 			$tab_prefixes = explode(',', $tab_prefixes); // list of prefixes that should stay
 			$skip_prefixes = array_unique(array_merge($skip_prefixes, array_diff($prefixes, $tab_prefixes)));
 		}
 
 		$params['name'] = $params['render_as'];
 		$params['skip_prefixes'] = implode(',', $skip_prefixes);
 		return $this->Application->ParseBlock($params);
 	}
 
 	/**
 	 * Determines, that printed category/menu item is currently active (will also match parent category)
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsActive($params)
 	{
 		static $current_path = null;
 
 		if ( !isset($current_path) ) {
 			$sql = 'SELECT ParentPath
 					FROM ' . TABLE_PREFIX . 'Categories
 					WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id');
 			$current_path = $this->Conn->GetOne($sql);
 		}
 
 		if ( array_key_exists('parent_path', $params) ) {
 			$test_path = $params['parent_path'];
 		}
 		else {
 			$template = isset($params['template']) ? $params['template'] : '';
 
 			if ( $template ) {
 				// when using from "c:CachedMenu" tag
 				$sql = 'SELECT ParentPath
 						FROM ' . TABLE_PREFIX . 'Categories
 						WHERE NamedParentPath = ' . $this->Conn->qstr('Content/' . $template);
 				$test_path = $this->Conn->GetOne($sql);
 			}
 			else {
 				// when using from "c:PrintList" tag
 				$cat_id = array_key_exists('cat_id', $params) && $params['cat_id'] ? $params['cat_id'] : false;
 				if ( $cat_id === false ) {
 					// category not supplied -> get current from PrintList
 					$category = $this->getObject($params);
 				}
 				else {
 					if ( "$cat_id" == 'Root' ) {
 						$cat_id = $this->Application->findModule('Name', $params['module'], 'RootCat');
 					}
 
 					$category = $this->Application->recallObject($this->Prefix . '.-c' . $cat_id, $this->Prefix, Array ('skip_autoload' => true));
 					/* @var $category CategoriesItem */
 
 					$category->Load($cat_id);
 				}
 
 				$test_path = $category->GetDBField('ParentPath');
 			}
 		}
 
 		return strpos($current_path, $test_path) !== false;
 	}
 
 	/**
 	 * Checks if user have one of required permissions
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function HasPermission($params)
 	{
 		$perm_helper = $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		$params['raise_warnings'] = 0;
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$params['cat_id'] = $object->isLoaded() ? $object->GetDBField('ParentPath') : $this->Application->GetVar('m_cat_id');
 		return $perm_helper->TagPermissionCheck($params);
 	}
 
 	/**
 	 * Prepares name for field with event in it (used only on front-end)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SubmitName($params)
 	{
 		return 'events[' . $this->Prefix . '][' . $params['event'] . ']';
 	}
 
 	/**
 	 * Returns last modification date of items in category / system
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function LastUpdated($params)
 	{
 		$category_id = (int)$this->Application->GetVar('m_cat_id');
 		$local = array_key_exists('local', $params) && ($category_id > 0) ? $params['local'] : false;
 
 		$serial_name = $this->Application->incrementCacheSerial('c', $local ? $category_id : null, false);
 		$cache_key = 'category_last_updated[%' . $serial_name . '%]';
 
 		$row_data = $this->Application->getCache($cache_key);
 
 		if ( $row_data === false ) {
 			if ( $local && ($category_id > 0) ) {
 				// scan only current category & it's children
 				list ($tree_left, $tree_right) = $this->Application->getTreeIndex($category_id);
 
 				$sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate
 		        		FROM ' . TABLE_PREFIX . 'Categories
 		        		WHERE TreeLeft BETWEEN ' . $tree_left . ' AND ' . $tree_right;
 			}
 			else {
 				// scan all categories in system
 				$sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate
 		       			FROM ' . TABLE_PREFIX . 'Categories';
 			}
 
 			$this->Conn->nextQueryCachable = true;
 			$row_data = $this->Conn->GetRow($sql);
 			$this->Application->setCache($cache_key, $row_data);
 		}
 
 		if ( !$row_data ) {
 			return '';
 		}
 
 		$date = $row_data[$row_data['NewDate'] > $row_data['ModDate'] ? 'NewDate' : 'ModDate'];
 
 		// format date
 		$format = isset($params['format']) ? $params['format'] : '_regional_DateTimeFormat';
 
 		if ( preg_match("/_regional_(.*)/", $format, $regs) ) {
 			$lang = $this->Application->recallObject('lang.current');
 			/* @var $lang LanguagesItem */
 
 			if ( $regs[1] == 'DateTimeFormat' ) {
 				// combined format
 				$format = $lang->GetDBField('DateFormat') . ' ' . $lang->GetDBField('TimeFormat');
 			}
 			else {
 				// simple format
 				$format = $lang->GetDBField($regs[1]);
 			}
 		}
 
 		return adodb_date($format, $date);
 	}
 
 	function CategoryItemCount($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
 		$params['cat_id'] = $object->GetID();
 
 		$count_helper = $this->Application->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		return $count_helper->CategoryItemCount($params['prefix'], $params);
 	}
 
 	/**
 	 * Returns prefix + any word (used for shared between categories per page settings)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function VarName($params)
 	{
 		return $this->Prefix.'_'.$params['type'];
 	}
 
 	/**
 	 * Checks if current category is valid symbolic link to another category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function IsCategorySymLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
 		$sym_category_id = $object->GetDBField('SymLinkCategoryId');
 
-		if (is_null($sym_category_id))
-		{
+		if ( is_null($sym_category_id) ) {
 			return false;
 		}
 
-		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
+		$config = $this->getUnitConfig();
+		$id_field = $config->getIDField();
+		$table_name = $config->getTableName();
 
-		$sql = 'SELECT '.$id_field.'
-				FROM '.$table_name.'
-				WHERE '.$id_field.' = '.$sym_category_id;
+		$sql = 'SELECT ' . $id_field . '
+				FROM ' . $table_name . '
+				WHERE ' . $id_field . ' = ' . $sym_category_id;
 
-		return $this->Conn->GetOne($sql)? true : false;
+		return $this->Conn->GetOne($sql) ? true : false;
 	}
 
 	/**
 	 * Returns module prefix based on root category for given
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function GetModulePrefix($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$parent_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 
 		$category_helper = $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		$module_info = $category_helper->getCategoryModule($params, $parent_path);
 		return $module_info['Var'];
 	}
 
 	function ImageSrc($params)
 	{
 		list ($ret, $tag_processed) = $this->processAggregatedTag('ImageSrc', $params, $this->getPrefixSpecial());
 		return $tag_processed ? $ret : false;
 	}
 
 	function PageLink($params)
 	{
 		$params['m_cat_page'] = $this->Application->GetVar($this->getPrefixSpecial() . '_Page');
 
 		return parent::PageLink($params);
 	}
 
 	/**
 	 * Returns spelling suggestions against search keyword
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function SpellingSuggestions($params)
 	{
-		$keywords = htmlspecialchars_decode( trim($this->Application->GetVar('keywords')) );
+		$keywords = htmlspecialchars_decode(trim($this->Application->GetVar('keywords')));
+
 		if ( !$keywords ) {
 			return '';
 		}
 
 		// 1. try to get already cached suggestion
 		$cache_key = 'search.suggestion[%SpellingDictionarySerial%]:' . $keywords;
 		$suggestion = $this->Application->getCache($cache_key);
 
 		if ( $suggestion !== false ) {
 			return $suggestion;
 		}
 
-		$table_name = $this->Application->getUnitOption('spelling-dictionary', 'TableName');
+		$table_name = $this->Application->getUnitConfig('spelling-dictionary')->getTableName();
 
 		// 2. search suggestion in database
 		$this->Conn->nextQueryCachable = true;
 		$sql = 'SELECT SuggestedCorrection
 				FROM ' . $table_name . '
 				WHERE MisspelledWord = ' . $this->Conn->qstr($keywords);
 		$suggestion = $this->Conn->GetOne($sql);
 
 		if ( $suggestion !== false ) {
 			$this->Application->setCache($cache_key, $suggestion);
 
 			return $suggestion;
 		}
 
 		// 3. suggestion not found in database, ask webservice
 		$app_id = $this->Application->ConfigValue('YahooApplicationId');
 		$url = 'http://search.yahooapis.com/WebSearchService/V1/spellingSuggestion?appid=' . $app_id . '&query=';
 
 		$curl_helper = $this->Application->recallObject('CurlHelper');
 		/* @var $curl_helper kCurlHelper */
 
-		$xml_data = $curl_helper->Send( $url . urlencode($keywords) );
+		$xml_data = $curl_helper->Send($url . urlencode($keywords));
 
 		$xml_helper = $this->Application->recallObject('kXMLHelper');
 		/* @var $xml_helper kXMLHelper */
 
 		$root_node =& $xml_helper->Parse($xml_data);
 		/* @var $root_node kXMLNode */
 
 		$result = $root_node->FindChild('RESULT');
 		/* @var $result kXMLNode */
 
 		if ( is_object($result) ) {
 			// webservice responded -> save in local database
 			$fields_hash = Array ('MisspelledWord' => $keywords, 'SuggestedCorrection' => $result->Data);
 
 			$this->Conn->doInsert($fields_hash, $table_name);
 			$this->Application->setCache($cache_key, $result->Data);
 
 			return $result->Data;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Shows link for searching by suggested word
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SuggestionLink($params)
 	{
 		$params['keywords'] = $this->SpellingSuggestions($params);
 
 		return $this->Application->ProcessParsedTag('m', 'Link', $params);
 	}
 
 	function InitCatalogTab($params)
 	{
 		$tab_params['mode'] = $this->Application->GetVar('tm'); // single/multi selection possible
 		$tab_params['special'] = $this->Application->GetVar('ts'); // use special for this tab
 		$tab_params['dependant'] = $this->Application->GetVar('td'); // is grid dependant on categories grid
 
 		// set default params (same as in catalog)
 		if ( $tab_params['mode'] === false ) {
 			$tab_params['mode'] = 'multi';
 		}
 
 		if ( $tab_params['special'] === false ) {
 			$tab_params['special'] = '';
 		}
 
 		if ( $tab_params['dependant'] === false ) {
 			$tab_params['dependant'] = 'yes';
 		}
 
 		// pass params to block with tab content
 		$params['name'] = $params['render_as'];
 		$special = $tab_params['special'] ? $tab_params['special'] : $this->Special;
 		$params['prefix'] = trim($this->Prefix.'.'.$special, '.');
 
 		$prefix_append = $this->Application->GetVar('prefix_append');
 		if ($prefix_append) {
 			$params['prefix'] .= $prefix_append;
 		}
 
 		$default_grid = array_key_exists('default_grid', $params) ? $params['default_grid'] : 'Default';
 		$radio_grid = array_key_exists('radio_grid', $params) ? $params['radio_grid'] : 'Radio';
 
 		$params['cat_prefix'] = trim('c.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.');
 		$params['tab_mode'] = $tab_params['mode'];
 		$params['grid_name'] = ($tab_params['mode'] == 'multi') ? $default_grid : $radio_grid;
 		$params['tab_dependant'] = $tab_params['dependant'];
 		$params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name
 
 		if ($special == 'showall' || $special == 'user') {
 			$params['grid_name'] .= 'ShowAll';
 		}
 
 		// use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag
 		return $this->Application->ParseBlock($params, 1);
 	}
 
 	/**
 	 * Show CachedNavbar of current item primary category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CategoryName($params)
 	{
 		// show category cachednavbar of
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId');
 
 		$cache_key = 'category_paths[%CIDSerial:' . $category_id . '%][%PhrasesSerial%][Adm:' . (int)$this->Application->isAdmin . ']';
 		$category_path = $this->Application->getCache($cache_key);
 
 		if ($category_path === false) {
 			// not chached
 			if ($category_id > 0) {
 				$cached_navbar = $object->GetField('CachedNavbar');
 
 				if ($category_id == $object->GetDBField('ParentId')) {
 					// parent category cached navbar is one element smaller, then current ones
 					$cached_navbar = explode('&|&', $cached_navbar);
 					array_pop($cached_navbar);
 					$cached_navbar = implode('&|&', $cached_navbar);
 				}
 				else {
 					// no relation with current category object -> query from db
 					$language_id = (int)$this->Application->GetVar('m_lang');
 					if (!$language_id) {
 						$language_id = 1;
 					}
 
 					$sql = 'SELECT l' . $language_id . '_CachedNavbar
 							FROM ' . $object->TableName . '
 							WHERE ' . $object->IDField . ' = ' . $category_id;
 					$cached_navbar = $this->Conn->GetOne($sql);
 				}
 
 				$cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $cached_navbar);
 
 				$category_path = trim($this->CategoryName( Array('cat_id' => 0) ).' > '.str_replace('&|&', ' > ', $cached_navbar), ' > ');
 			}
 			else {
 				$category_path = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 			}
 
 			$this->Application->setCache($cache_key, $category_path);
 		}
 
 		return $category_path;
 	}
 
 	// structure related
 
 	/**
 	 * Returns page object based on requested params
 	 *
 	 * @param Array $params
 	 * @return CategoriesItem
 	 */
 	function &_getPage($params)
 	{
 		$page = $this->Application->recallObject($this->Prefix . '.-virtual', null, $params);
 		/* @var $page kDBItem */
 
 		// 1. load by given id
 		$page_id = array_key_exists('page_id', $params) ? $params['page_id'] : false;
 		if ($page_id) {
 			if ($page_id != $page->GetID()) {
 				// load if different
 				$page->Load($page_id);
 			}
 
 			return $page;
 		}
 
 		// 2. load by template
 		$template = array_key_exists('page', $params) ? $params['page'] : '';
 		if (!$template) {
 			$template = $this->Application->GetVar('t');
 		}
 
 		// different path in structure AND design template differes from requested template
 		$structure_path_match = mb_strtolower( $page->GetDBField('NamedParentPath') ) == mb_strtolower('Content/' . $template);
 		$design_match = $page->GetDBField('CachedTemplate') == $template;
 
 		if (!$structure_path_match && !$design_match) {
 			// Same sql like in "c:getPassedID". Load, when current page object doesn't match requested page object
 			$themes_helper = $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$page_id = $themes_helper->getPageByTemplate($template);
 
 			$page->Load($page_id);
 		}
 
 		return $page;
 	}
 
 	/**
 	 * Returns requested content block content of current or specified page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ContentBlock($params)
 	{
 		$num = getArrayValue($params, 'num');
 
 		if ( !$num ) {
 			$name = getArrayValue($params, 'name');
 
 			if ( $name ) {
 				$num = kUtil::crc32($name);
 			}
 		}
 
 		if ( !$num ) {
 			return 'NO CONTENT NUM SPECIFIED';
 		}
 
 		$page =& $this->_getPage($params);
 		/* @var $page kDBItem */
 
 		if ( !$page->isLoaded() ) {
 			// page is not created yet => all blocks are empty
 			return '';
 		}
 
 		$page_helper = $this->Application->recallObject('PageHelper');
 		/* @var $page_helper PageHelper */
 
 		$content = $this->Application->recallObject('content.-block', null, Array ('skip_autoload' => true));
 		/* @var $content kDBItem */
 
 		if ( !$page_helper->loadContentBlock($content, $page, $num) && EDITING_MODE ) {
 			$page_helper->createNewContentBlock($page->GetID(), $num);
 			$page_helper->loadContentBlock($content, $page, $num);
 		}
 
 		$edit_code_before = $edit_code_after = '';
 
 		if ( EDITING_MODE == EDITING_MODE_CONTENT ) {
 			$button_code = $this->Application->ProcessParsedTag($content->getPrefixSpecial(), 'AdminEditButton', $params);
 
 			$edit_code_before = '
 				<div class="cms-edit-btn-container">
 					' . $button_code . '
 					<div class="cms-btn-content">';
 
 			$edit_code_after = '</div></div>';
 		}
 
 		if ( $this->Application->GetVar('_editor_preview_') == 1 ) {
 			$data = $this->Application->RecallVar('_editor_preview_content_');
 		}
 		else {
 			$data = $content->GetField('Content');
 		}
 
 		$data = $edit_code_before . $this->_transformContentBlockData($data, $params) . $edit_code_after;
 
 		if ( $data != '' ) {
 			$this->Application->Parser->DataExists = true;
 		}
 
 		return $data;
 	}
 
 	/**
 	 * Apply all kinds of content block data transformations without rewriting ContentBlock tag
 	 *
 	 * @param string $data
 	 * @param Array $params
 	 * @return string
 	 */
 	function _transformContentBlockData(&$data, $params)
 	{
 		return $data;
 	}
 
 	/**
 	 * Returns current page name or page based on page/page_id parameters
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used?
 	 */
 	function PageName($params)
 	{
 		$page =& $this->_getPage($params);
 
 		return $page->GetDBField('Name');
 	}
 
 	/**
 	 * Returns current/given page information
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PageInfo($params)
 	{
 		$page =& $this->_getPage($params);
 
 		switch ($params['type']) {
 			case 'title':
 				// TODO: rename column to SectionTitle
 				$db_field = 'Name'; // "Section Title" - title to show on page (e.g. in <h1> tag)
 				break;
 
 			case 'htmlhead_title':
 				// TODO: rename column to HtmlTitle
 				$db_field = 'Title'; // "Title (on Page)" - in <title> html tag
 				break;
 
 			case 'meta_title':
 				$db_field = 'MetaTitle';
 				break;
 
 			case 'menu_title':
 				$db_field = 'MenuTitle'; // "Title (Menu Item)" - in menu and navigation bar
 				break;
 
 			case 'meta_keywords':
 				$db_field = 'MetaKeywords';
 				$cat_field = 'Keywords';
 				break;
 
 			case 'meta_description':
 				$db_field = 'MetaDescription';
 				$cat_field = 'Description';
 				break;
 
 			case 'tracking':
 			case 'index_tools':
 				if (!EDITING_MODE) {
 					$tracking = $page->GetDBField('IndexTools');
 					return $tracking ? $tracking : $this->Application->ConfigValue('cms_DefaultTrackingCode');
 				}
 				// no break here on purpose
 
 			default:
 				return '';
 		}
 
 		$default = isset($params['default']) ? $params['default'] : '';
 		$val = $page->GetField($db_field);
 		if (!$default) {
 			if ($this->Application->isModuleEnabled('In-Portal')) {
 				if (!$val && ($params['type'] == 'meta_keywords' || $params['type'] == 'meta_description')) {
 					// take category meta if it's not set for the page
 					return $this->Application->ProcessParsedTag('c', 'Meta', Array('name' => $cat_field));
 				}
 			}
 		}
 
 		if (isset($params['force_default']) && $params['force_default']) {
 			return $default;
 		}
 
 		if (preg_match('/^_Auto:/', $val)) {
 			$val = $default;
 
 			/*if ($db_field == 'Title') {
 				$page->SetDBField($db_field, $default);
 				$page->Update();
 			}*/
 		}
 		elseif ($page->GetID() == false) {
 			return $default;
 		}
 
 		return $val;
 	}
 
 	/**
 	 * Includes admin css and js, that are required for cms usage on Front-Edn
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function EditingScripts($params)
 	{
 		if ( $this->Application->GetVar('admin_scripts_included') || !EDITING_MODE ) {
 			return '';
 		}
 
 		$this->Application->SetVar('admin_scripts_included', 1);
 		$js_url = $this->Application->BaseURL() . 'core/admin_templates/js';
 
 		$minify_helper = $this->Application->recallObject('MinifyHelper');
 		/* @var $minify_helper MinifyHelper */
 
 		$to_compress = Array (
 			$js_url . '/jquery/thickbox/thickbox.css',
 			$js_url . '/../incs/cms.css',
 			$js_url . '/../img/toolbar/toolbar-sprite.css',
 		);
 
 		$css_compressed = $minify_helper->CompressScriptTag(Array ('files' => implode('|', $to_compress), 'templates_base' => $js_url . '/../'));
 
 		$ret = '<link rel="stylesheet" href="' . $css_compressed . '" type="text/css" media="screen"/>' . "\n";
 
 		$ret .= '	<!--[if IE]>
 					<link rel="stylesheet" href="' . $js_url . '/../incs/cms_ie.css' . '" type="text/css" media="screen"/>
 					<![endif]-->';
 
 		if ( EDITING_MODE == EDITING_MODE_DESIGN ) {
 			$ret .= '	<style type="text/css" media="all">
 							div.movable-element .movable-header { cursor: move; }
 						</style>';
 		}
 
 		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery.pack.js"></script>' . "\n";
 		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery-ui.custom.min.js"></script>' . "\n";
 
 		$to_compress = Array (
 			$js_url . '/is.js',
 			$js_url . '/application.js',
 			$js_url . '/script.js',
 			$js_url . '/toolbar.js',
 			$js_url . '/jquery/thickbox/thickbox.js',
 			$js_url . '/template_manager.js',
 		);
 
 		$js_compressed = $minify_helper->CompressScriptTag( Array ('files' => implode('|', $to_compress)) );
 
 		$ret .= '<script type="text/javascript" src="' . $js_compressed . '"></script>' . "\n";
 		$ret .= '<script language="javascript">' . "\n";
 		$ret .= "TB.pathToImage = '" . $js_url . "/jquery/thickbox/loadingAnimation.gif';" . "\n";
 
 		$template = $this->Application->GetVar('t');
 		$theme_id = $this->Application->GetVar('m_theme');
 
 		$url_params = Array ('block' => '#BLOCK#', 'theme-file_event' => '#EVENT#', 'theme_id' => $theme_id, 'source' => $template, 'pass' => 'all,theme-file', 'front' => 1, 'm_opener' => 'd', '__NO_REWRITE__' => 1, 'no_amp' => 1);
 		$edit_template_url = $this->Application->HREF('themes/template_edit', ADMIN_DIRECTORY, $url_params, 'index.php');
 
 		$url_params = Array ('theme-file_event' => 'OnSaveLayout', 'source' => $template, 'pass' => 'all,theme-file', '__NO_REWRITE__' => 1, 'no_amp' => 1);
 		$save_layout_url = $this->Application->HREF('index', '', $url_params);
 
 		$page =& $this->_getPage($params);
 
 		$url_params = Array(
 			'pass'			=>	'm,c',
 			'c_id'			=>	$page->GetID(),
 			'c_event'		=>	'OnGetPageInfo',
 			'__URLENCODE__'	=>	1,
 			'__NO_REWRITE__'=>	1,
 			'index_file' => 'index.php',
 		);
 
 		$page_helper = $this->Application->recallObject('PageHelper');
 		/* @var $page_helper PageHelper */
 
 		$class_params = Array (
 			'pageId' => $page->GetID(),
 			'pageInfo' => $page_helper->getPageInfo( $page->GetID() ),
 			'editUrl' => $edit_template_url,
 			'browseUrl' => $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', '__NO_REWRITE__' => 1, 'no_amp' => 1)),
 			'saveLayoutUrl' => $save_layout_url,
 			'editingMode' => (int)EDITING_MODE,
 		);
 
 		$ret .= "var aTemplateManager = new TemplateManager(" . json_encode($class_params) . ");\n";
 		$ret .= "var main_title = '" . addslashes( $this->Application->ConfigValue('Site_Name') ) . "';" . "\n";
 
 		$use_popups = (int)$this->Application->ConfigValue('UsePopups');
 		$ret .= "var \$use_popups = " . ($use_popups > 0 ? 'true' : 'false') . ";\n";
 		$ret .= "var \$modal_windows = " . ($use_popups == 2 ? 'true' : 'false') . ";\n";
 
 		if ( EDITING_MODE != EDITING_MODE_BROWSE ) {
 			$ret .= 'var $visible_toolbar_buttons = true' . ";\n";
 			$ret .= 'var $use_toolbarlabels = ' . ($this->Application->ConfigValue('UseToolbarLabels') ? 'true' : 'false') . ";\n";;
 
 			$ret .= "var base_url = '" . $this->Application->BaseURL() . "';" . "\n";
 			$ret .= 'TB.closeHtml = \'<img src="' . $js_url . '/../img/close_window15.gif" width="15" height="15" style="border-width: 0px;" alt="close"/><br/>\';' . "\n";
 
 			$url_params = Array ('m_theme' => '', 'pass' => 'm', 'm_opener' => 'r', '__NO_REWRITE__' => 1, 'no_amp' => 1);
 			$browse_url = $this->Application->HREF('catalog/catalog', ADMIN_DIRECTORY, $url_params, 'index.php');
 			$browse_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $browse_url);
 
 			$ret .= '
 				set_window_title(document.title + \' - ' . addslashes($this->Application->Phrase('la_AdministrativeConsole', false)) . '\');
 
 				t = \'' . $this->Application->GetVar('t') . '\';
 
 				if (window.parent.frames["menu"] != undefined) {
 					if ( $.isFunction(window.parent.frames["menu"].SyncActive) ) {
 						window.parent.frames["menu"].SyncActive("' . $browse_url . '");
 					}
 				}
 			';
 		}
 
 		$ret .= '</script>' . "\n";
 
 		if ( EDITING_MODE != EDITING_MODE_BROWSE ) {
 			// add form, so admin scripts could work
 			$ret .= '<form id="kernel_form" name="kernel_form" enctype="multipart/form-data" method="post" action="' . $browse_url . '">
 						<input type="hidden" name="MAX_FILE_SIZE" id="MAX_FILE_SIZE" value="' . MAX_UPLOAD_SIZE . '" />
 						<input type="hidden" name="sid" id="sid" value="' . $this->Application->GetSID() . '" />
 					</form>';
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Prints "Edit Page" button on cms page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function EditPage($params)
 	{
 		if ( $this->Application->GetVar('preview') ) {
 			// prevents draft preview function to replace last template in session and break page/content block editing process
 			$this->Application->SetVar('skip_last_template', 1);
 		}
 
 		if (!EDITING_MODE) {
 			return '';
 		}
 
 		$display_mode = array_key_exists('mode', $params) ? $params['mode'] : false;
 		unset($params['mode']);
 		$edit_code = '';
 
 		$page =& $this->_getPage($params);
 
 		if (!$page->isLoaded() || (($display_mode != 'end') && (EDITING_MODE == EDITING_MODE_BROWSE))) {
 			// when "EditingScripts" tag is not used, make sure, that scripts are also included
 			return $this->EditingScripts($params);
 		}
 
 		// show "EditPage" button only for pages, that exists in structure
 		if ($display_mode != 'end') {
 			$edit_btn = $edit_url = '';
 
 			if ( EDITING_MODE == EDITING_MODE_CONTENT ) {
 				$item_prefix = isset($params['item_prefix']) ? $params['item_prefix'] : '';
 				unset($params['item_prefix']);
 
 				if ( $item_prefix ) {
 					$params['button_class'] = 'cms-section-properties-btn';
 					$edit_btn = $this->Application->ProcessParsedTag($item_prefix, 'AdminEditButton', $params) . "\n";
 				}
 				else {
 					$edit_btn = $this->AdminEditButton($params) . "\n"; // "st" object must be loaded before this
 				}
 			}
 			elseif ( EDITING_MODE == EDITING_MODE_DESIGN ) {
 				$url_params = Array(
 					'pass'			=>	'm,theme,theme-file',
 					'm_opener'		=>	'd',
 					'theme_id'		=>	$this->Application->GetVar('m_theme'),
 					'theme_mode'	=>	't',
 					'theme_event'	=>	'OnEdit',
 					'theme-file_id' =>	$this->_getThemeFileId(),
 					'front'			=>	1,
 					'__URLENCODE__'	=>	1,
 					'__NO_REWRITE__'=>	1,
 					'index_file' => 'index.php',
 				);
 
 				$edit_url = $this->Application->HREF('themes/file_edit', ADMIN_DIRECTORY, $url_params);
 
 				$button1_icon = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/save_button.gif';
 				$button1_title = $this->Application->Phrase('la_btn_SaveChanges', false, true);
 				$button1_code = '<button style="background-image: url(' . $button1_icon . '); onclick="aTemplateManager.saveLayout(); return false;" class="cms-btn-new cms-save-layout-btn">' . $button1_title . '</button>';
 
 				$button2_icon = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/cancel_button.gif';
 				$button2_title = $this->Application->Phrase('la_btn_Cancel', false, true);
 				$button2_code = '<button style="background-image: url(' . $button2_icon . '); onclick="aTemplateManager.cancelLayout(); return false;" class="cms-btn-new cms-cancel-layout-btn">' . $button2_title . '</button>';
 
 				$button3_icon = $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png';
 				$button3_title = $this->Application->Phrase('la_btn_SectionTemplate', false, true);
 				$button3_code = '<button style="background-image: url(' . $button3_icon . ');' . ($display_mode === false ? ' margin: 0px;' : '') . '" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'theme\', \'themes/file_edit\');" class="cms-btn-new cms-section-properties-btn">' . $button3_title . '</button>';
 
 				$edit_btn .= '<div class="cms-layout-btn-container"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . '>' . $button1_code . $button2_code . '</div>' . $button3_code . "\n";
 			}
 
 			if ( $display_mode == 'start' ) {
 				// button with border around the page
 				if ( EDITING_MODE == EDITING_MODE_CONTENT ) {
 					$tabs = "\n" . str_repeat("\t", 9);
 					$base_url = $this->Application->BaseURL();
 					$toolbar_hidden = $this->Application->GetVar('toolbar_hidden');
 
 					$edit_code .= '
 						<div>
 							<div id="cms-editing-notice">
 								<div class="top">
 									<a href="#" id="cms-close-editing-notice"></a>
 									<span prev_editors=""></span>
 								</div>
 								<div class="bottom"></div>
 							</div>
 
 							<div id="cms-revision-dropdown">
 								<div class="top"></div>
 								<div class="bottom"></div>
 							</div>
 						</div>';
 
 					if ( $this->Application->ConfigValue('EnablePageContentRevisionControl') ) {
 						$edit_code .= '<div id="cms-revision-toolbar-layer"' . ($toolbar_hidden ? ' style="top: -56px;"' : '') . '>
 											<div id="cms-revision-toolbar">
 												<script type="text/javascript">
 													var a_toolbar = new ToolBar(undefined, undefined, "' . $base_url . '#MODULE#/admin_templates/img/");
 													' . $this->toolbarButton('select', 'la_ToolTip_Save', $tabs) . $this->toolbarButton('delete', 'la_ToolTip_Discard', $tabs) . $tabs . 'a_toolbar.AddButton( new ToolBarSeparator("sep1") );';
 
 						if ( $this->Application->CheckAdminPermission('CATEGORY.REVISION.MODERATE', 0) ) {
 							$edit_code .= $this->toolbarButton('approve', 'la_ToolTip_Publish', $tabs) . $this->toolbarButton('decline', 'la_ToolTip_Decline', $tabs) . $tabs . 'a_toolbar.AddButton( new ToolBarSeparator("sep2") );';
 						}
 
 						$edit_code .= $this->toolbarButton('preview', 'la_ToolTip_Preview', $tabs);
 
 						if ( $this->Application->CheckAdminPermission('CATEGORY.REVISION.HISTORY.VIEW', 0) ) {
 							$edit_code .= $this->toolbarButton('history', 'la_ToolTip_History', $tabs);
 						}
 
 						$edit_code .= $tabs . 'a_toolbar.Render();' . "\n";
 
 						$revision = $this->Application->recallObject('page-revision.current');
 						/* @var $revision kDBItem */
 
 						if ( !$revision->GetDBField('IsDraft') ) {
 							$edit_code .= $tabs . 'a_toolbar.DisableButton("select");' . $tabs . 'a_toolbar.DisableButton("delete");' . $tabs . 'a_toolbar.DisableButton("preview");';
 						}
 
 						if ( $revision->GetDBField('Status') == STATUS_ACTIVE || $revision->GetDBField('IsDraft') ) {
 							$edit_code .= $tabs . 'a_toolbar.DisableButton("approve");';
 						}
 
 						if ( $revision->GetDBField('Status') == STATUS_DISABLED || $revision->GetDBField('IsLive') || $revision->GetDBField('IsDraft') ) {
 							$edit_code .= $tabs . 'a_toolbar.DisableButton("decline");';
 						}
 
 						$publishing_tools = $this->Application->Phrase('la_btn_PublishingTools', false, true);
 
 						$edit_code .= substr($tabs, 0, -1) . '</script>
 
 									<div id="cms-current-revision-info">
 										<span class="revision-title"></span>
 										<div class="draft-saved"></div>
 									</div>
 
 									<a href="#" id="cms-close-toolbar"></a>
 									<div class="cms-clear"></div>
 								</div>
 
 								<a href="#" id="cms-toggle-revision-toolbar"' . ($toolbar_hidden ? '' : ' class="opened"') . '><span>' . $publishing_tools . '</span></a>
 							</div>' . "\n";
 					}
 				}
 
 				$edit_code .= '<div class="cms-section-properties-btn-container">' . $edit_btn . '<div class="cms-btn-content">';
 
 			}
 			else {
 				// button without border around the page
 				$edit_code .= $edit_btn;
 			}
 		}
 
 		if ($display_mode == 'end') {
 			// draw border around the page
 			$edit_code .= '</div></div>';
 		}
 
 		if ($display_mode != 'end') {
 			if ( EDITING_MODE == EDITING_MODE_CONTENT ) {
 				$url_params = Array(
 					'pass'			=>	'm',
 					'm_opener'		=>	'd',
 					'm_cat_id'		=>	$page->GetID(),
 					'__URLENCODE__'	=>	1,
 					'__NO_REWRITE__'=>	1,
 					'front'			=>	1,
 					'index_file' => 'index.php',
 				);
 
 				$revision = $this->Application->GetVar('revision');
 
 				if ( $revision ) {
 					$url_params['revision'] = $revision;
 				}
 
 				$page_admin_url = $this->Application->HREF('', ADMIN_DIRECTORY, $url_params);
 				$edit_code .= '<form method="POST" style="display: inline; margin: 0px" name="kf_revisions_'.$page->GetID().'" id="kf_revisions_'.$page->GetID().'" action="' . $page_admin_url . '">
 					<input type="hidden" name="revision" value="' . $this->Application->GetVar('revision', 0) . '"/>
 				</form>';
 			}
 
 			if ( $edit_url ) {
 				$edit_code .= '<form method="POST" style="display: inline; margin: 0px" name="kf_' . $page->GetID() . '" id="kf_' . $page->GetID() . '" action="' . $edit_url . '"></form>';
 			}
 
 			// when "EditingScripts" tag is not used, make sure, that scripts are also included
 			$edit_code .= $this->EditingScripts($params);
 		}
 
 		return $edit_code;
 	}
 
 	function toolbarButton($name, $title, $tabs)
 	{
 		$phrase = $this->Application->Phrase($title, false, true);
 
 		return $tabs . 'a_toolbar.AddButton( new ToolBarButton("' . $name . '", "' . htmlspecialchars($phrase, null, CHARSET) . '") );';
 	}
 
 	function _getThemeFileId()
 	{
 		$template = $this->Application->GetVar('t');
 
 		if (!$this->Application->TemplatesCache->TemplateExists($template) && !$this->Application->isAdmin) {
 			$cms_handler = $this->Application->recallObject($this->Prefix . '_EventHandler');
 			/* @var $cms_handler CategoriesEventHandler */
 
 			$template = ltrim($cms_handler->GetDesignTemplate(), '/');
 		}
 
 		$file_path = dirname($template) == '.' ? '' : '/' . dirname($template);
 		$file_name = basename($template);
 
 		$sql = 'SELECT FileId
 				FROM ' . TABLE_PREFIX . 'ThemeFiles
 				WHERE (ThemeId = ' . (int)$this->Application->GetVar('m_theme') . ') AND (FilePath = ' . $this->Conn->qstr($file_path) . ') AND (FileName = ' . $this->Conn->qstr($file_name . '.tpl') . ')';
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Creates a button for editing item in Admin Console
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function AdminEditButton($params)
 	{
 		if ( EDITING_MODE != EDITING_MODE_CONTENT ) {
 			return '';
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$params['item_prefix'] = 'c';
 
 		if ( $this->Prefix == 'st' ) {
 			$params['button_icon'] = 'section_properties.png';
 			$params['button_class'] = 'cms-section-properties-btn';
 			$params['button_title'] = 'la_btn_SectionProperties';
 		}
 
 		return parent::AdminEditButton($params);
 	}
 
 	/**
 	 * Builds site menu
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CachedMenu($params)
 	{
 		$menu_helper = $this->Application->recallObject('MenuHelper');
 		/* @var $menu_helper MenuHelper */
 
 		return $menu_helper->menuTag($this->getPrefixSpecial(), $params);
 	}
 
 	/**
 	 * Trick to allow some kind of output formatting when using CachedMenu tag
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function SplitColumn($params)
 	{
 		return $this->Application->GetVar($params['i']) > ceil($params['total'] / $params['columns']);
 	}
 
 	/**
 	 * Returns direct children count of given category
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function HasSubCats($params)
 	{
 		$sql = 'SELECT COUNT(*)
 				FROM ' . TABLE_PREFIX . 'Categories
 				WHERE ParentId = ' . $params['cat_id'];
 
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Prints sub-pages of given/current page.
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo This could be reached by using "parent_cat_id" parameter. Only difference here is new block parameter "path". Need to rewrite.
 	 */
 	function PrintSubPages($params)
 	{
 		$list = $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List', $params);
 		/* @var $list kDBList */
 
 		$category_id = array_key_exists('category_id', $params) ? $params['category_id'] : $this->Application->GetVar('m_cat_id');
 
 		$list->addFilter('current_pages', TABLE_PREFIX . 'CategoryItems.CategoryId = ' . $category_id);
 		$list->Query();
 		$list->GoFirst();
 
 		$o = '';
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		while (!$list->EOL()) {
 			$block_params['path'] = $list->GetDBField('Path');
 			$o .= $this->Application->ParseBlock($block_params);
 
 			$list->GoNext();
 		}
 
 		return $o;
 	}
 
 	/**
 	 * Builds link for browsing current page on Front-End
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PageBrowseLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$themes_helper = $this->Application->recallObject('ThemesHelper');
 		/* @var $themes_helper kThemesHelper */
 
 		$site_config_helper = $this->Application->recallObject('SiteConfigHelper');
 		/* @var $site_config_helper SiteConfigHelper */
 
 		$settings = $site_config_helper->getSettings();
 
 		$url_params = Array (
 			'm_cat_id' => $object->GetID(),
 			'm_theme' => $themes_helper->getCurrentThemeId(),
 			'editing_mode' => $settings['default_editing_mode'],
 			'pass' => 'm',
 			'admin' => 1,
 		);
 
 		if ($this->Application->ConfigValue('UseModRewrite')) {
 			$url_params['__MOD_REWRITE__'] = 1;
 		}
 		else {
 			$url_params['index_file'] = 'index.php';
 		}
 
 		return $this->Application->HREF($object->GetDBField('NamedParentPath'), '_FRONT_END_', $url_params);
 	}
 
 	/**
 	 * Builds a link for securely accessing a page later (even if it will not be publicly accessible)
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function DirectLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$themes_helper = $this->Application->recallObject('ThemesHelper');
 		/* @var $themes_helper kThemesHelper */
 
 		$url_params = Array (
 			'm_cat_id' => $object->GetID(),
 			'm_theme' => $themes_helper->getCurrentThemeId(),
 			'pass' => 'm',
 			'authkey' => $object->GetDBField('DirectLinkAuthKey'),
 			'__SSL__' => 0,
 			'__NO_SID__' => 0,
 		);
 
 		if ($this->Application->ConfigValue('UseModRewrite')) {
 			$url_params['__MOD_REWRITE__'] = 1;
 		}
 		else {
 			$url_params['index_file'] = 'index.php';
 		}
 
 		return $this->Application->HREF($object->GetDBField('NamedParentPath'), '_FRONT_END_', $url_params);
 	}
 
 	/**
 	 * Builds link to category as a cms page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ContentPageLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$params['t'] = mb_strtolower($object->GetDBField('NamedParentPath'));
 		$params['m_cat_id'] = 0;
 
 		return $this->Application->ProcessParsedTag('m', 'Link', $params);
 	}
 
 	/**
 	 * Prepares cms page description for search result page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SearchDescription($params)
 	{
 		$object = $this->getObject($params);
 		$desc =  $object->GetField('MetaDescription');
 		if (!$desc) {
 			$sql = 'SELECT *
 					FROM ' . TABLE_PREFIX . 'PageContent
 					WHERE PageId = ' . $object->GetID() . ' AND ContentNum = 1';
 			$content = $this->Conn->GetRow($sql);
 
 			if ($content['l'.$this->Application->GetVar('m_lang').'_Content']) {
 				$desc = $content['l'.$this->Application->GetVar('m_lang').'_Content'];
 			}
 			else {
 				$desc = $content['l'.$this->Application->GetDefaultLanguageId().'_Content'];
 			}
 		}
 
 		return mb_substr($desc, 0, 300).(mb_strlen($desc) > 300 ? '...' : '');
 	}
 
 	/**
 	 * Simplified version of "c:CategoryLink" for "c:PrintList"
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used? Needs refactoring.
 	 */
 	function EnterCatLink($params)
 	{
 		$object = $this->getObject($params);
 
 		$url_params = Array ('pass' => 'm', 'm_cat_id' => $object->GetID());
 		return $this->Application->HREF($params['template'], '', $url_params);
 	}
 
 	/**
 	 * Simplified version of "c:CategoryPath", that do not use blocks for rendering
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used? Maybe needs to be removed.
 	 */
 	function PagePath($params)
 	{
 		$object = $this->getObject($params);
 		$path = $object->GetField('CachedNavbar');
 		if ($path) {
 			$items = explode('&|&', $path);
 			array_shift($items);
 			return implode(' -&gt; ', $items);
 		}
 
 		return '';
 	}
 
 	/**
 	 * Returns configuration variable value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Needs to be replaced with "m:GetConfig" tag; Not used now (were used on structure_edit.tpl).
 	 */
 	function AllowManualFilenames($params)
 	{
 		return $this->Application->ConfigValue('ProjCMSAllowManualFilenames');
 	}
 
 	/**
 	 * Draws path to current page (each page can be link to it)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CurrentPath($params)
 	{
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $block_params['render_as'];
 
 		$object = $this->Application->recallObject($this->Prefix);
 		/* @var $object kDBItem */
 
 		$category_ids = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 
-		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
-
+		$config = $this->getUnitConfig();
 		$language = (int)$this->Application->GetVar('m_lang');
 
-		if (!$language) {
+		if ( !$language ) {
 			$language = 1;
 		}
 
-		$sql = 'SELECT l'.$language.'_Name AS Name, NamedParentPath
-				FROM '.$table_name.'
-				WHERE '.$id_field.' IN ('.implode(',', $category_ids).')';
+		$sql = 'SELECT l' . $language . '_Name AS Name, NamedParentPath
+				FROM ' . $config->getTableName() . '
+				WHERE ' . $config->getIDField() . ' IN (' . implode(',', $category_ids) . ')';
 		$categories_data = $this->Conn->Query($sql);
 
 		$ret = '';
 		foreach ($categories_data as $index => $category_data) {
-			if ($category_data['Name'] == 'Content') {
+			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 ;
+
+		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);
+		$config = $this->getUnitConfig();
+		$edit_tab_preset = $config->getEditTabPresetByName('Default');
+
+		$config->addEditTabPresets(Array (
+			'Default' => Array ('permissions' => $edit_tab_preset['permissions'])
+		));
 	}
 
 	/**
 	 * Prints catalog export templates
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PrintCatalogExportTemplates($params)
 	{
+		$ret = Array ();
 		$prefixes = explode(',', $params['prefixes']);
 
-		$ret = Array ();
 		foreach ($prefixes as $prefix) {
-			if ($this->Application->prefixRegistred($prefix)) {
-				$module_path = $this->Application->getUnitOption($prefix, 'ModuleFolder') . '/';
+			if ( $this->Application->prefixRegistred($prefix) ) {
+				$module_path = $this->Application->getUnitConfig($prefix)->getModuleFolder() . '/';
 				$module_name = $this->Application->findModule('Path', $module_path, 'Name');
 
 				$ret[$prefix] = mb_strtolower($module_name) . '/export';
 			}
 		}
 
-		$json_helper = $this->Application->recallObject('JSONHelper');
-		/* @var $json_helper JSONHelper */
-
-		return $json_helper->encode($ret);
+		return json_encode($ret);
 	}
 
 	/**
 	 * Checks, that "view in browse mode" functionality available
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function BrowseModeAvailable($params)
 	{
 		$valid_special = $params['Special'] != 'user';
 		$not_selector = $this->Application->GetVar('type') != 'item_selector';
 
 		return $valid_special && $not_selector;
 	}
 
 	/**
 	 * Returns a link for editing product
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ItemEditLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
-		$edit_template = $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePath') . '/' . $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePrefix') . 'edit';
+		$config = $this->getUnitConfig();
+		$edit_template = $config->getAdminTemplatePath() . '/' . $config->getAdminTemplatePrefix() . 'edit';
 
 		$url_params = Array (
 			'm_opener'				=>	'd',
 			$this->Prefix.'_mode'	=>	't',
 			$this->Prefix.'_event'	=>	'OnEdit',
 			$this->Prefix.'_id'		=>	$object->GetID(),
 			'm_cat_id'				=>	$object->GetDBField('ParentId'),
 			'pass'					=>	'all,'.$this->Prefix,
 			'no_pass_through'		=>	1,
 		);
 
 		return $this->Application->HREF($edit_template,'', $url_params);
 	}
 
 	function RelevanceIndicator($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 		$sql = 'SELECT Relevance
 				FROM '.$search_results_table.'
 				WHERE ResourceId = '.$object->GetDBField('ResourceId');
 
     	$percents_off = (int)(100 - (100 * $this->Conn->GetOne($sql)));
     	$percents_off = ($percents_off < 0) ? 0 : $percents_off;
     	if ($percents_off) {
         	$params['percent_off'] = $percents_off;
     		$params['percent_on'] = 100 - $percents_off;
         	$params['name'] = $this->SelectParam($params, 'relevance_normal_render_as,block_relevance_normal');
     	}
     	else {
     		$params['name'] = $this->SelectParam($params, 'relevance_full_render_as,block_relevance_full');
     	}
     	return $this->Application->ParseBlock($params);
 	}
 
 	/**
 	 * Returns list of categories, that have category add/edit permission
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function AllowedCategoriesJSON($params)
 	{
 		if ($this->Application->RecallVar('user_id') == USER_ROOT) {
 			$categories = true;
 		}
 		else {
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
-			$perm_prefix = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix');
+			$perm_prefix = $this->getUnitConfig()->getPermItemPrefix();
 			$categories = $perm_helper->getPermissionCategories($perm_prefix . '.' . ($object->IsNewItem() ? 'ADD' : 'MODIFY'));
 		}
 
 		$json_helper = $this->Application->recallObject('JSONHelper');
 		/* @var $json_helper JSONHelper */
 
 		return $json_helper->encode($categories);
 	}
 
 	function PageEditable($params)
 	{
 		if ($this->Application->isDebugMode()) {
 			return true;
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return !$object->GetDBField('Protected');
 	}
 
 	/**
 	 * Returns element for "__item__" navigation bar part
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function CategoryItemElement($params)
 	{
 		$category_helper = $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		$navigation_bar = $this->Application->recallObject('kNavigationBar');
 		/* @var $navigation_bar kNavigationBar */
 
 		$category_id = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id');
 		$parent_path = explode('|', substr($navigation_bar->getParentPath($category_id), 1, -1));
 		array_shift($parent_path); // remove "Content" category
 		$module_info = $category_helper->getCategoryModule($params, $parent_path);
 
 		if ( !$module_info ) {
 			return '';
 		}
 
 		$module_prefix = $module_info['Var'];
 
 		$object = $this->Application->recallObject($module_prefix);
 		/* @var $object kCatDBItem */
 
-		$title_field = $this->Application->getUnitOption($module_prefix, 'TitleField');
+		$title_field = $this->Application->getUnitConfig($module_prefix)->getTitleField();
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		$block_params['title'] = $object->GetField($title_field);
 		$block_params['prefix'] = $module_prefix;
 
 		return $this->Application->ParseBlock($block_params);
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/categories/categories_event_handler.php
===================================================================
--- branches/5.3.x/core/units/categories/categories_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/categories/categories_event_handler.php	(revision 15698)
@@ -1,3060 +1,3077 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CategoriesEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnRebuildCache' => Array ('self' => 'add|edit'),
 				'OnCopy' => Array ('self' => true),
 				'OnCut' => Array ('self' => 'edit'),
 				'OnPasteClipboard' => Array ('self' => true),
 				'OnPaste' => Array ('self' => 'add|edit', 'subitem' => 'edit'),
 
 				'OnRecalculatePriorities' => Array ('self' => 'add|edit'), // category ordering
 				'OnItemBuild' => Array ('self' => true), // always allow to view individual categories (regardless of CATEGORY.VIEW right)
 				'OnUpdatePreviewBlock' => Array ('self' => true), // for FCKEditor integration
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Categories are sorted using special sorting event
 		 *
 		 */
 		function mapEvents()
 		{
 			parent::mapEvents();
 
 			$events_map = Array (
 				'OnMassMoveUp' => 'OnChangePriority',
 				'OnMassMoveDown' => 'OnChangePriority',
 			);
 
 			$this->eventMethods = array_merge($this->eventMethods, $events_map);
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( $event->Name == 'OnResetCMSMenuCache' ) {
 				// events from "Tools -> System Tools" section are controlled via that section "edit" permission
 
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				$perm_value = $this->Application->CheckPermission('in-portal:service.edit');
 
 				return $perm_helper->finalizePermissionCheck($event, $perm_value);
 			}
 
 			if ( !$this->Application->isAdmin ) {
 				if ( $event->Name == 'OnSetSortingDirect' ) {
 					// allow sorting on front event without view permission
 					return true;
 				}
 
 				if ( $event->Name == 'OnItemBuild' ) {
 					$category_id = $this->getPassedID($event);
 					if ( $category_id == 0 ) {
 						return true;
 					}
 				}
 			}
 
 			if ( in_array($event->Name, $this->_getMassPermissionEvents()) ) {
 				$items = $this->_getPermissionCheckInfo($event);
 
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				if ( ($event->Name == 'OnSave') && array_key_exists(0, $items) ) {
 					// adding new item (ID = 0)
 					$perm_value = $perm_helper->AddCheckPermission($items[0]['ParentId'], $event->Prefix) > 0;
 				}
 				else {
 					// leave only items, that can be edited
 					$ids = Array ();
 					$check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission';
 					foreach ($items as $item_id => $item_data) {
 						if ( $perm_helper->$check_method($item_data['CreatedById'], $item_data['ParentId'], $event->Prefix) > 0 ) {
 							$ids[] = $item_id;
 						}
 					}
 
 					if ( !$ids ) {
 						// no items left for editing -> no permission
 						return $perm_helper->finalizePermissionCheck($event, false);
 					}
 
 					$perm_value = true;
 					$event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method
 				}
 
 				return $perm_helper->finalizePermissionCheck($event, $perm_value);
 			}
 
 			if ( $event->Name == 'OnRecalculatePriorities' ) {
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				$category_id = $this->Application->GetVar('m_cat_id');
 
 				return $perm_helper->AddCheckPermission($category_id, $event->Prefix) || $perm_helper->ModifyCheckPermission(0, $category_id, $event->Prefix);
 			}
 
 			if ( $event->Name == 'OnPasteClipboard' ) {
 				// forces permission check to work by current category for "Paste In Category" operation
 				$category_id = $this->Application->GetVar('m_cat_id');
 				$this->Application->SetVar('c_id', $category_id);
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Returns events, that require item-based (not just event-name based) permission check
 		 *
 		 * @return Array
 		 */
 		function _getMassPermissionEvents()
 		{
 			return Array (
 				'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove',
 				'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown',
 				'OnCut',
 			);
 		}
 
 		/**
 		 * Returns category item IDs, that require permission checking
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
 		function _getPermissionCheckIDs($event)
 		{
 			if ($event->Name == 'OnSave') {
 				$selected_ids = implode(',', $this->getSelectedIDs($event, true));
 				if (!$selected_ids) {
 					$selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave)
 				}
 			}
 			else {
 				// OnEdit, OnMassDelete events, when items are checked in grid
 				$selected_ids = implode(',', $this->StoreSelectedIDs($event));
 			}
 
 			return $selected_ids;
 		}
 
 		/**
 		 * Returns information used in permission checking
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 */
 		function _getPermissionCheckInfo($event)
 		{
 			// when saving data from temp table to live table check by data from temp table
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$config = $event->getUnitConfig();
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			if ($event->Name == 'OnSave') {
 				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $event->Prefix);
 			}
 
 			$sql = 'SELECT ' . $id_field . ', CreatedById, ParentId
 					FROM ' . $table_name . '
 					WHERE ' . $id_field . ' IN (' . $this->_getPermissionCheckIDs($event) . ')';
 			$items = $this->Conn->Query($sql, $id_field);
 
 			if (!$items) {
 				// when creating new category, then no IDs are stored in session
 				$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 				list ($id, $fields_hash) = each($items_info);
 
 				if (array_key_exists('ParentId', $fields_hash)) {
 					$item_category = $fields_hash['ParentId'];
 				}
 				else {
 					$item_category = $this->Application->RecallVar('m_cat_id'); // saved in c:OnPreCreate event permission checking
 				}
 
 				$items[$id] = Array (
 					'CreatedById' => $this->Application->RecallVar('user_id'),
 					'ParentId' => $item_category,
 				);
 			}
 
 			return $items;
 		}
 
 		/**
 		 * Set's mark, that root category is edited
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnEdit(kEvent $event)
 		{
 			$category_id = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
 			$home_category = $this->Application->getBaseCategory();
 
 			$this->Application->StoreVar('IsRootCategory_' . $this->Application->GetVar('m_wid'), ($category_id === '0') || ($category_id == $home_category));
 
 			parent::OnEdit($event);
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				// keep "Section Properties" link (in browse modes) clean
 				$this->Application->DeleteVar('admin');
 			}
 		}
 
 		/**
 		 * Adds selected link to listing
 		 *
 		 * @param kEvent $event
 		 */
 		function OnProcessSelected($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$selected_ids = $this->Application->GetVar('selected_ids');
 
 			$this->RemoveRequiredFields($object);
 			$object->SetDBField($this->Application->RecallVar('dst_field'), $selected_ids['c']);
 			$object->Update();
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Apply system filter to categories list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			// don't show "Content" category in advanced view
 			$object->addFilter('system_categories', '%1$s.Status <> 4');
 
 			// show system templates from current theme only + all virtual templates
 			$object->addFilter('theme_filter', '%1$s.ThemeId = ' . $this->_getCurrentThemeId() . ' OR %1$s.ThemeId = 0');
 
 			if ($event->Special == 'showall') {
 				// if using recycle bin don't show categories from there
 				$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 				if ($recycle_bin) {
 					$sql = 'SELECT TreeLeft, TreeRight
 							FROM '.TABLE_PREFIX.'Categories
 							WHERE CategoryId = '.$recycle_bin;
 					$tree_indexes = $this->Conn->GetRow($sql);
 
 					$object->addFilter('recyclebin_filter', '%1$s.TreeLeft < '.$tree_indexes['TreeLeft'].' OR %1$s.TreeLeft > '.$tree_indexes['TreeRight']);
 				}
 			}
 
 			if ( (string)$event->getEventParam('parent_cat_id') !== '' ) {
 				$parent_cat_id = $event->getEventParam('parent_cat_id');
 
 				if ("$parent_cat_id" == 'Root') {
 					$module_name = $event->getEventParam('module') ? $event->getEventParam('module') : 'In-Commerce';
 					$parent_cat_id = $this->Application->findModule('Name', $module_name, 'RootCat');
 				}
 			}
 			else {
 				$parent_cat_id = $this->Application->GetVar('c_id');
 				if (!$parent_cat_id) {
 					$parent_cat_id = $this->Application->GetVar('m_cat_id');
 				}
 				if (!$parent_cat_id) {
 					$parent_cat_id = 0;
 				}
 			}
 
 			if ("$parent_cat_id" == '0') {
 				// replace "0" category with "Content" category id (this way template
 				$parent_cat_id = $this->Application->getBaseCategory();
 			}
 
 			if ("$parent_cat_id" != 'any') {
 				if ($event->getEventParam('recursive')) {
 					if ($parent_cat_id > 0) {
 						// not "Home" category
 						$tree_indexes = $this->Application->getTreeIndex($parent_cat_id);
 
 						$object->addFilter('parent_filter', '%1$s.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']);
 					}
 				}
 				else {
 					$object->addFilter('parent_filter', '%1$s.ParentId = '.$parent_cat_id);
 				}
 			}
 
 			$object->addFilter('perm_filter', TABLE_PREFIX . 'CategoryPermissionsCache.PermId = 1'); // check for CATEGORY.VIEW permission
 			if ($this->Application->RecallVar('user_id') != USER_ROOT) {
 				// apply permission filters to all users except "root"
 				$view_filters = Array ();
 				$groups = explode(',',$this->Application->RecallVar('UserGroups'));
 
 				foreach ($groups as $group) {
 					$view_filters[] = 'FIND_IN_SET('.$group.', ' . TABLE_PREFIX . 'CategoryPermissionsCache.ACL)';
 				}
 
 				$view_filter = implode(' OR ', $view_filters);
 				$object->addFilter('perm_filter2', $view_filter);
 			}
 
 			if (!$this->Application->isAdminUser)	{
 				// apply status filter only on front
 				$object->addFilter('status_filter', $object->TableName.'.Status = 1');
 			}
 
 			// process "types" and "except" parameters
 			$type_clauses = Array();
 
 			$types = $event->getEventParam('types');
 			$types = $types ? explode(',', $types) : Array ();
 
 			$except_types = $event->getEventParam('except');
 			$except_types = $except_types ? explode(',', $except_types) : Array ();
 
+			$config = $event->getUnitConfig();
+
 			if (in_array('related', $types) || in_array('related', $except_types)) {
 				$related_to = $event->getEventParam('related_to');
 				if (!$related_to) {
 					$related_prefix = $event->Prefix;
 				}
 				else {
 					$sql = 'SELECT Prefix
 							FROM '.TABLE_PREFIX.'ItemTypes
 							WHERE ItemName = '.$this->Conn->qstr($related_to);
 					$related_prefix = $this->Conn->GetOne($sql);
 				}
 
-				$rel_table = $this->Application->getUnitOption('rel', 'TableName');
-				$item_type = (int)$this->Application->getUnitOption($event->Prefix, 'ItemType');
+				$rel_table = $this->Application->getUnitConfig('rel')->getTableName();
+				$item_type = (int)$config->getItemType();
 
 				if ($item_type == 0) {
 					trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING);
 				}
 
 				// process case, then this list is called inside another list
 				$prefix_special = $event->getEventParam('PrefixSpecial');
 				if (!$prefix_special) {
 					$prefix_special = $this->Application->Parser->GetParam('PrefixSpecial');
 				}
 
 				$id = false;
 				if ($prefix_special !== false) {
 					$processed_prefix = $this->Application->processPrefix($prefix_special);
 					if ($processed_prefix['prefix'] == $related_prefix) {
 						// printing related categories within list of items (not on details page)
 						$list = $this->Application->recallObject($prefix_special);
 						/* @var $list kDBList */
 
 						$id = $list->GetID();
 					}
 				}
 
 				if ($id === false) {
 					// printing related categories for single item (possibly on details page)
 					if ($related_prefix == 'c') {
 						$id = $this->Application->GetVar('m_cat_id');
 					}
 					else {
 						$id = $this->Application->GetVar($related_prefix . '_id');
 					}
 				}
 
 				$p_item = $this->Application->recallObject($related_prefix . '.current', null, Array('skip_autoload' => true));
 				/* @var $p_item kCatDBItem */
 
 				$p_item->Load( (int)$id );
 
 				$p_resource_id = $p_item->GetDBField('ResourceId');
 
 				$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
 						WHERE
 							(Enabled = 1)
 							AND (
 									(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 									OR
 									(Type = 1
 										AND (
 												(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
 												OR
 												(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
 											)
 									)
 							)';
 
 				$related_ids_array = $this->Conn->Query($sql);
 				$related_ids = Array();
 
 				foreach ($related_ids_array as $key => $record) {
 					$related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ];
 				}
 
 				if (count($related_ids) > 0) {
 					$type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).')';
 					$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).')';
 				}
 				else {
 					$type_clauses['related']['include'] = '0';
 					$type_clauses['related']['except'] = '1';
 				}
 
 				$type_clauses['related']['having_filter'] = false;
 			}
 
 			if (in_array('category_related', $type_clauses)) {
 				$object->removeFilter('parent_filter');
 				$resource_id = $this->Conn->GetOne('
-								SELECT ResourceId FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
+								SELECT ResourceId FROM '.$config->getTableName().'
 								WHERE CategoryId = '.$parent_cat_id
 							);
 
 				$sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'CatalogRelationships
 						WHERE SourceId = '.$resource_id.' AND SourceType = 1';
 				$related_cats = $this->Conn->GetCol($sql);
 				$related_cats = is_array($related_cats) ? $related_cats : Array();
 
 				$sql = 'SELECT DISTINCT(SourceId) FROM '.TABLE_PREFIX.'CatalogRelationships
 						WHERE TargetId = '.$resource_id.' AND TargetType = 1 AND Type = 1';
 				$related_cats2 = $this->Conn->GetCol($sql);
 				$related_cats2 = is_array($related_cats2) ? $related_cats2 : Array();
 				$related_cats = array_unique( array_merge( $related_cats2, $related_cats ) );
 
 				if ($related_cats) {
 					$type_clauses['category_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')';
 					$type_clauses['category_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')';
 				}
 				else
 				{
 					$type_clauses['category_related']['include'] = '0';
 					$type_clauses['category_related']['except'] = '1';
 				}
 				$type_clauses['category_related']['having_filter'] = false;
 			}
 
 			if (in_array('product_related', $types)) {
 				$object->removeFilter('parent_filter');
 
 				$product_id = $event->getEventParam('product_id') ? $event->getEventParam('product_id') : $this->Application->GetVar('p_id');
-				$resource_id = $this->Conn->GetOne('
-								SELECT ResourceId FROM '.$this->Application->getUnitOption('p', 'TableName').'
-								WHERE ProductId = '.$product_id
-							);
 
-				$sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'CatalogRelationships
+				$sql = 'SELECT ResourceId
+						FROM ' . $this->Application->getUnitConfig('p')->getTableName() . '
+						WHERE ProductId = ' . $product_id;
+				$resource_id = $this->Conn->GetOne($sql);
+
+				$sql = 'SELECT DISTINCT(TargetId)
+						FROM ' . TABLE_PREFIX . 'CatalogRelationships
 						WHERE SourceId = '.$resource_id.' AND TargetType = 1';
 				$related_cats = $this->Conn->GetCol($sql);
+
 				$related_cats = is_array($related_cats) ? $related_cats : Array();
-				$sql = 'SELECT DISTINCT(SourceId) FROM '.TABLE_PREFIX.'CatalogRelationships
+
+				$sql = 'SELECT DISTINCT(SourceId)
+						FROM ' . TABLE_PREFIX . 'CatalogRelationships
 						WHERE TargetId = '.$resource_id.' AND SourceType = 1 AND Type = 1';
 				$related_cats2 = $this->Conn->GetCol($sql);
+
 				$related_cats2 = is_array($related_cats2) ? $related_cats2 : Array();
 				$related_cats = array_unique( array_merge( $related_cats2, $related_cats ) );
 
 				if ($related_cats) {
 					$type_clauses['product_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')';
 					$type_clauses['product_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')';
 				}
 				else {
 					$type_clauses['product_related']['include'] = '0';
 					$type_clauses['product_related']['except'] = '1';
 				}
 
 				$type_clauses['product_related']['having_filter'] = false;
 			}
 
 			$type_clauses['menu']['include'] = '%1$s.IsMenu = 1';
 			$type_clauses['menu']['except'] = '%1$s.IsMenu = 0';
 			$type_clauses['menu']['having_filter'] = false;
 
 			if (in_array('search', $types) || in_array('search', $except_types)) {
 				$event_mapping = Array (
 					'simple'		=>	'OnSimpleSearch',
 					'subsearch'		=>	'OnSubSearch',
 					'advanced'		=>	'OnAdvancedSearch'
 				);
 
 				$keywords = $event->getEventParam('keyword_string');
 				$type = $this->Application->GetVar('search_type', 'simple');
 
 				if ( $keywords ) {
 					// processing keyword_string param of ListProducts tag
 					$this->Application->SetVar('keywords', $keywords);
 					$type = 'simple';
 				}
 
 				$search_event = $event_mapping[$type];
 				$this->$search_event($event);
 
 				$object = $event->getObject();
 				/* @var $object kDBList */
 
 				$search_sql = '	FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search
 								search_result JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
 				$sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL());
 
 				$object->SetSelectSQL($sql);
 
 				$object->addCalculatedField('Relevance', 'search_result.Relevance');
 
 				$type_clauses['search']['include'] = '1';
 				$type_clauses['search']['except'] = '0';
 				$type_clauses['search']['having_filter'] = false;
 			}
 
 			$search_helper = $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$search_helper->SetComplexFilter($event, $type_clauses, implode(',', $types), implode(',', $except_types));
 		}
 
 		/**
 		 * Returns current theme id
 		 *
 		 * @return int
 		 */
 		function _getCurrentThemeId()
 		{
 			$themes_helper = $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			return (int)$themes_helper->getCurrentThemeId();
 		}
 
 		/**
 		 * Returns ID of current item to be edited
 		 * by checking ID passed in get/post as prefix_id
 		 * or by looking at first from selected ids, stored.
 		 * Returned id is also stored in Session in case
 		 * it was explicitly passed as get/post
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access public
 		 */
 		public function getPassedID(kEvent $event)
 		{
 			if ( ($event->Special == 'page') || ($event->Special == '-virtual') || ($event->Prefix == 'st') ) {
 				return $this->_getPassedStructureID($event);
 			}
 
 			if ( $this->Application->isAdmin ) {
 				return parent::getPassedID($event);
 			}
 
 			return $this->Application->GetVar('m_cat_id');
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function _getPassedStructureID($event)
 		{
 			static $page_by_template = Array ();
 
 			if ( $event->Special == 'current' ) {
 				return $this->Application->GetVar('m_cat_id');
 			}
 
 			$event->setEventParam('raise_warnings', 0);
 
 			$page_id = parent::getPassedID($event);
 
 			if ( $page_id === false ) {
 				$template = $event->getEventParam('page');
 				if ( !$template ) {
 					$template = $this->Application->GetVar('t');
 				}
 
 				// bug: when template contains "-" symbols (or others, that stripDisallowed will replace) it's not found
 				if ( !array_key_exists($template, $page_by_template) ) {
+					$config = $event->getUnitConfig();
 					$template_crc = kUtil::crc32(mb_strtolower($template));
 
-					$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
-							FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+					$sql = 'SELECT ' . $config->getIDField() . '
+							FROM ' . $config->getTableName() . '
 							WHERE
 								(
 									(NamedParentPathHash = ' . $template_crc . ') OR
 									(`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplateHash = ' . $template_crc . ')
 								) AND (ThemeId = ' . $this->_getCurrentThemeId() . ' OR ThemeId = 0)';
 
 					$page_id = $this->Conn->GetOne($sql);
 				}
 				else {
 					$page_id = $page_by_template[$template];
 				}
 
 				if ( $page_id === false && EDITING_MODE ) {
 					// create missing pages, when in editing mode
 					$object = $this->Application->recallObject($this->Prefix . '.rebuild', NULL, Array ('skip_autoload' => true));
 					/* @var $object CategoriesItem */
 
 					$created = $this->_prepareAutoPage($object, $template, NULL, SMS_MODE_AUTO); // create virtual (not system!) page
 					if ( $created ) {
 						$rebuild_mode = $this->Application->ConfigValue('CategoryPermissionRebuildMode');
 
 						if ( $rebuild_mode == CategoryPermissionRebuild::SILENT || !$this->Application->isAdmin ) {
 							$updater = $this->Application->makeClass('kPermCacheUpdater');
 							/* @var $updater kPermCacheUpdater */
 
 							$updater->OneStepRun();
 						}
 
 						$this->_resetMenuCache();
 
 						$this->Application->RemoveVar('PermCache_UpdateRequired');
 
 						$page_id = $object->GetID();
 						$this->Application->SetVar('m_cat_id', $page_id);
 					}
 				}
 
 				if ( $page_id ) {
 					$page_by_template[$template] = $page_id;
 				}
 			}
 
 			if ( !$page_id && !$this->Application->isAdmin ) {
 				$page_id = $this->Application->GetVar('m_cat_id');
 			}
 
 			return $page_id;
 		}
 
 		function ParentGetPassedID($event)
 		{
 			return parent::getPassedID($event);
 		}
 
 		/**
 		 * Adds calculates fields for item statuses
 		 *
 		 * @param kCatDBItem $object
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function prepareObject(&$object, kEvent $event)
 		{
 			if ( $event->Special == '-virtual' ) {
 				return;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$object->addCalculatedField(
 				'IsNew',
 				'	IF(%1$s.NewItem = 2,
 						IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '.
 							$this->Application->ConfigValue('Category_DaysNew').
 							'*3600*24), 1, 0),
 						%1$s.NewItem
 				)');
 		}
 
 		/**
 		 * Set correct parent path for newly created categories
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterCopyToLive(kEvent $event)
 		{
 			parent::OnAfterCopyToLive($event);
 
 			$object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true, 'live_table' => true));
 			/* @var $object CategoriesItem */
 
 			$parent_path = false;
 			$object->Load($event->getEventParam('id'));
 
 			if ( $event->getEventParam('temp_id') == 0 ) {
 				if ( $object->isLoaded() ) {
 					// update path only for real categories (not including "Home" root category)
 					$fields_hash = $object->buildParentBasedFields();
 					$this->Conn->doUpdate($fields_hash, $object->TableName, 'CategoryId = ' . $object->GetID());
 					$parent_path = $fields_hash['ParentPath'];
 				}
 			}
 			else {
 				$parent_path = $object->GetDBField('ParentPath');
 			}
 
 			if ( $parent_path ) {
 				$cache_updater = $this->Application->makeClass('kPermCacheUpdater', Array (null, $parent_path));
 				/* @var $cache_updater kPermCacheUpdater */
 
 				$cache_updater->OneStepRun();
 			}
 		}
 
 		/**
 		 * Set cache modification mark if needed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeDeleteFromLive(kEvent $event)
 		{
 			parent::OnBeforeDeleteFromLive($event);
 
 			$id = $event->getEventParam('id');
 
 			// loading anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event
 			$temp_object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $temp_object CategoriesItem */
 
 			$temp_object->Load($id);
 
 			if ( $id == 0 ) {
 				if ( $temp_object->isLoaded() ) {
 					// new category -> update cache (not loaded when "Home" category)
 					$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 				}
 
 				return ;
 			}
 
 			// existing category was edited, check if in-cache fields are modified
 			$live_object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('live_table' => true, 'skip_autoload' => true));
 			/* @var $live_object CategoriesItem */
 
 			$live_object->Load($id);
 			$cached_fields = Array ('l' . $this->Application->GetDefaultLanguageId() . '_Name', 'Filename', 'Template', 'ParentId', 'Priority');
 
 			foreach ($cached_fields as $cached_field) {
 				if ( $live_object->GetDBField($cached_field) != $temp_object->GetDBField($cached_field) ) {
 					// use session instead of REQUEST because of permission editing in category can contain
 					// multiple submits, that changes data before OnSave event occurs
 					$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 					break;
 				}
 			}
 
 			// remember category filename change between temp and live records
 			if ( $temp_object->GetDBField('Filename') != $live_object->GetDBField('Filename') ) {
 				$filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ());
 
 				$filename_changes[ $live_object->GetID() ] = Array (
 					'from' => $live_object->GetDBField('Filename'),
 					'to' => $temp_object->GetDBField('Filename')
 				);
 
 				$this->Application->SetVar($event->Prefix . '_filename_changes', $filename_changes);
 			}
 		}
 
 		/**
 		 * Calls kDBEventHandler::OnSave original event
 		 * Used in proj-cms:StructureEventHandler->OnSave
 		 *
 		 * @param kEvent $event
 		 */
 		function parentOnSave($event)
 		{
 			parent::OnSave($event);
 		}
 
 		/**
 		 * Reset root-category flag when new category is created
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			// 1. for permission editing of Home category
 			$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 
 			parent::OnPreCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// 2. preset template
 			$category_id = $this->Application->GetVar('m_cat_id');
 			$root_category = $this->Application->getBaseCategory();
 
 			if ( $category_id == $root_category ) {
 				$object->SetDBField('Template', $this->_getDefaultDesign());
 			}
 
 			// 3. set default owner
 			$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 		}
 
 		/**
 		 * Checks cache update mark and redirect to cache if needed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSave(kEvent $event)
 		{
 			// get data from live table before it is overwritten by parent OnSave method call
 			$ids = $this->getSelectedIDs($event, true);
 			$is_editing = implode('', $ids);
 			$old_statuses = $is_editing ? $this->_getCategoryStatus($ids) : Array ();
 
 			$object = $event->getObject();
 			/* @var $object CategoriesItem */
 
 			parent::OnSave($event);
 
 			if ( $event->status != kEvent::erSUCCESS ) {
 				return;
 			}
 
 			if ( $this->Application->RecallVar('PermCache_UpdateRequired') ) {
 				$this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 			}
 
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 			$this->_resetMenuCache();
 
 			if ( $is_editing ) {
 				// send email event to category owner, when it's status is changed (from admin)
 				$object->SwitchToLive();
 				$new_statuses = $this->_getCategoryStatus($ids);
 				$process_statuses = Array (STATUS_ACTIVE, STATUS_DISABLED);
 
 				foreach ($new_statuses as $category_id => $new_status) {
 					if ( $new_status != $old_statuses[$category_id] && in_array($new_status, $process_statuses) ) {
 						$object->Load($category_id);
 						$email_event = $new_status == STATUS_ACTIVE ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
 						$this->Application->emailUser($email_event, $object->GetDBField('CreatedById'));
 					}
 				}
 			}
 
 			// change opener stack in case if edited category filename was changed
 			$filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ());
 
 			if ( $filename_changes ) {
 				$opener_stack = $this->Application->makeClass('kOpenerStack');
 				/* @var $opener_stack kOpenerStack */
 
 				list ($template, $params, $index_file) = $opener_stack->pop();
 
 				foreach ($filename_changes as $change_info) {
 					$template = str_ireplace($change_info['from'], $change_info['to'], $template);
 				}
 
 				$opener_stack->push($template, $params, $index_file);
 				$opener_stack->save();
 			}
 		}
 
 		/**
 		 * Returns statuses of given categories
 		 *
 		 * @param Array $category_ids
 		 * @return Array
 		 */
 		function _getCategoryStatus($category_ids)
 		{
-			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
+			$config = $this->getUnitConfig();
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			$sql = 'SELECT Status, ' . $id_field . '
 					FROM ' . $table_name . '
 					WHERE ' . $id_field . ' IN (' . implode(',', $category_ids) . ')';
+
 			return $this->Conn->GetCol($sql, $id_field);
 		}
 
 		/**
 		 * Creates a new item in temp table and
 		 * stores item id in App vars and Session on success
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveCreated(kEvent $event)
 		{
 			$object = $event->getObject( Array ('skip_autoload' => true) );
 			/* @var $object CategoriesItem */
 
 			if ( $object->IsRoot() ) {
 				// don't create root category while saving permissions
 				return;
 			}
 
 			parent::OnPreSaveCreated($event);
 		}
 
 		/**
 		 * Deletes sym link to other category
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sql = 'UPDATE ' . $object->TableName . '
 					SET SymLinkCategoryId = NULL
 					WHERE SymLinkCategoryId = ' . $object->GetID();
 			$this->Conn->Query($sql);
 
 			// delete direct subscriptions to category, that was deleted
 			$sql = 'SELECT SubscriptionId
 					FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions
 					WHERE CategoryId = ' . $object->GetID();
 			$ids = $this->Conn->GetCol($sql);
 
 			if ( $ids ) {
 				$temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler');
 				/* @var $temp_handler kTempTablesHandler */
 
 				$temp_handler->DeleteItems('system-event-subscription', '', $ids);
 			}
 		}
 
 		/**
 		 * Exclude root categories from deleting
 		 *
 		 * @param kEvent $event
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(kEvent $event, $type)
 		{
 			if ( $event->Name == 'OnMassDelete' && $type == 'before' ) {
 				$ids = $event->getEventParam('ids');
 				if ( !$ids || $this->Application->ConfigValue('AllowDeleteRootCats') ) {
 					return;
 				}
 
 				$root_categories = Array ();
 
 				// get module root categories and exclude them
 				foreach ($this->Application->ModuleInfo as $module_info) {
 					$root_categories[] = $module_info['RootCat'];
 				}
 
 				$root_categories = array_unique($root_categories);
 
 				if ( $root_categories && array_intersect($ids, $root_categories) ) {
 					$event->setEventParam('ids', array_diff($ids, $root_categories));
 					$this->Application->StoreVar('root_delete_error', 1);
 				}
 			}
 		}
 
 		/**
 		 * Checks, that given template exists (physically) in given theme
 		 *
 		 * @param string $template
 		 * @param int $theme_id
 		 * @return bool
 		 */
 		function _templateFound($template, $theme_id = null)
 		{
 			static $init_made = false;
 
 			if (!$init_made) {
 				$this->Application->InitParser(true);
 				$init_made = true;
 			}
 
 			if (!isset($theme_id)) {
 				$theme_id = $this->_getCurrentThemeId();
 			}
 
 			$theme_name = $this->_getThemeName($theme_id);
 
 			return $this->Application->TemplatesCache->TemplateExists('theme:' . $theme_name . '/' . $template);
 		}
 
 		/**
 		 * Removes ".tpl" in template path
 		 *
 		 * @param string $template
 		 * @return string
 		 */
 		function _stripTemplateExtension($template)
 		{
 	//		return preg_replace('/\.[^.\\\\\\/]*$/', '', $template);
 
 			return preg_replace('/^[\\/]{0,1}(.*)\.tpl$/', "$1", $template);
 		}
 
 		/**
 		 * Deletes all selected items.
 		 * Automatically recourse into sub-items using temp handler, and deletes sub-items
 		 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnMassDelete(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$to_delete = Array ();
 			$ids = $this->StoreSelectedIDs($event);
 			$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
 
 			if ( $recycle_bin ) {
 				$rb = $this->Application->recallObject('c.recycle', null, Array ('skip_autoload' => true));
 				/* @var $rb CategoriesItem */
 
 				$rb->Load($recycle_bin);
 
 				$cat = $event->getObject(Array ('skip_autoload' => true));
 				/* @var $cat CategoriesItem */
 
 				foreach ($ids as $id) {
 					$cat->Load($id);
 
 					if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $cat->GetDBField('ParentPath')) ) {
 						// already in "Recycle Bin" -> delete for real
 						$to_delete[] = $id;
 						continue;
 					}
 
 					// just move into "Recycle Bin" category
 					$cat->SetDBField('ParentId', $recycle_bin);
 					$cat->Update();
 				}
 
 				$ids = $to_delete;
 			}
 
 			$event->setEventParam('ids', $ids);
 			$this->customProcessing($event, 'before');
 			$ids = $event->getEventParam('ids');
 
 			if ( $ids ) {
 				$recursive_helper = $this->Application->recallObject('RecursiveHelper');
 				/* @var $recursive_helper kRecursiveHelper */
 
 				foreach ($ids as $id) {
 					$recursive_helper->DeleteCategory($id, $event->Prefix);
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 
 			$this->_ensurePermCacheRebuild($event);
 		}
 
 		/**
 		 * Add selected items to clipboard with mode = COPY (CLONE)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCopy($event)
 		{
 			$this->Application->RemoveVar('clipboard');
 
 			$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
 			/* @var $clipboard_helper kClipboardHelper */
 
 			$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Add selected items to clipboard with mode = CUT
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCut($event)
 		{
 			$this->Application->RemoveVar('clipboard');
 
 			$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
 			/* @var $clipboard_helper kClipboardHelper */
 
 			$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Controls all item paste operations. Can occur only with filled clipboard.
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPasteClipboard($event)
 		{
 			$clipboard = unserialize( $this->Application->RecallVar('clipboard') );
 			foreach ($clipboard as $prefix => $clipboard_data) {
 				$paste_event = new kEvent($prefix.':OnPaste', Array('clipboard_data' => $clipboard_data));
 				$this->Application->HandleEvent($paste_event);
 
 				$event->copyFrom($paste_event);
 			}
 		}
 
 		/**
 		 * Checks permission for OnPaste event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 */
 		function _checkPastePermission($event)
 		{
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$category_id = $this->Application->GetVar('m_cat_id');
 			if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) {
 				// no items left for editing -> no permission
 				return $perm_helper->finalizePermissionCheck($event, false);
 			}
 
 			return true;
 		}
 
 		/**
 		 * Paste categories with sub-items from clipboard
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPaste($event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$clipboard_data = $event->getEventParam('clipboard_data');
 
 			if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) {
 				return;
 			}
 
 			// 1. get ParentId of moved category(-es) before it gets updated!!!)
 			$source_category_id = 0;
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$config = $event->getUnitConfig();
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			if ( $clipboard_data['cut'] ) {
 				$sql = 'SELECT ParentId
 						FROM ' . $table_name . '
 						WHERE ' . $id_field . ' = ' . $clipboard_data['cut'][0];
 				$source_category_id = $this->Conn->GetOne($sql);
 			}
 
 			$recursive_helper = $this->Application->recallObject('RecursiveHelper');
 			/* @var $recursive_helper kRecursiveHelper */
 
 			if ( $clipboard_data['cut'] ) {
 				$recursive_helper->MoveCategories($clipboard_data['cut'], $this->Application->GetVar('m_cat_id'));
 			}
 
 			if ( $clipboard_data['copy'] ) {
 				// don't allow to copy/paste system OR theme-linked virtual pages
 
 				$sql = 'SELECT ' . $id_field . '
 						FROM ' . $table_name . '
 						WHERE ' . $id_field . ' IN (' . implode(',', $clipboard_data['copy']) . ') AND (`Type` = ' . PAGE_TYPE_VIRTUAL . ') AND (ThemeId = 0)';
 				$allowed_ids = $this->Conn->GetCol($sql);
 
 				if ( !$allowed_ids ) {
 					return;
 				}
 
 				foreach ($allowed_ids as $id) {
 					$recursive_helper->PasteCategory($id, $event->Prefix);
 				}
 			}
 
 			$priority_helper = $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			if ( $clipboard_data['cut'] ) {
 				$ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $source_category_id);
 
 				if ( $ids ) {
 					$priority_helper->massUpdateChanged($event->Prefix, $ids);
 				}
 			}
 
 			// recalculate priorities of newly pasted categories in destination category
 			$parent_id = $this->Application->GetVar('m_cat_id');
 			$ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id);
 
 			if ( $ids ) {
 				$priority_helper->massUpdateChanged($event->Prefix, $ids);
 			}
 
 			if ( $clipboard_data['cut'] || $clipboard_data['copy'] ) {
 				$this->_ensurePermCacheRebuild($event);
 			}
 		}
 
 		/**
 		 * Ensures, that category permission cache is rebuild when category is added/edited/deleted
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function _ensurePermCacheRebuild($event)
 		{
 			$rebild_mode = $this->Application->ConfigValue('CategoryPermissionRebuildMode');
 
 			if ( $rebild_mode == CategoryPermissionRebuild::SILENT ) {
 				$updater = $this->Application->makeClass('kPermCacheUpdater');
 				/* @var $updater kPermCacheUpdater */
 
 				$updater->OneStepRun();
 			}
 			elseif ( $rebild_mode == CategoryPermissionRebuild::AUTOMATIC ) {
 				// rebuild with progress bar
 				$event->redirect = 'categories/cache_updater';
 			}
 
 			$this->_resetMenuCache();
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 		}
 
 		/**
 		 * Occurs when pasting category
 		 *
 		 * @param kEvent $event
 		 */
 		/*function OnCatPaste($event)
 		{
 			$inp_clipboard = $this->Application->RecallVar('ClipBoard');
 			$inp_clipboard = explode('-', $inp_clipboard, 2);
 
 			if($inp_clipboard[0] == 'COPY')
 			{
-				$saved_cat_id = $this->Application->GetVar('m_cat_id');
+				$config = $event->getUnitConfig();
 				$cat_ids = $event->getEventParam('cat_ids');
+				$saved_cat_id = $this->Application->GetVar('m_cat_id');
+
+				$ids_sql = 'SELECT ' . $config->getIDField() . '
+							FROM ' . $config->getTableName() . '
+							WHERE ResourceId IN (%s)';
 
-				$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-				$table = $this->Application->getUnitOption($event->Prefix, 'TableName');
-				$ids_sql = 'SELECT '.$id_field.' FROM '.$table.' WHERE ResourceId IN (%s)';
 				$resource_ids_sql = 'SELECT ItemResourceId FROM '.TABLE_PREFIX.'CategoryItems WHERE CategoryId = %s AND PrimaryCat = 1';
 
 				$object = $this->Application->recallObject($event->Prefix.'.item', $event->Prefix, Array('skip_autoload' => true));
 
 				foreach($cat_ids as $source_cat => $dest_cat)
 				{
 					$item_resource_ids = $this->Conn->GetCol( sprintf($resource_ids_sql, $source_cat) );
 					if(!$item_resource_ids) continue;
 
 					$this->Application->SetVar('m_cat_id', $dest_cat);
 					$item_ids = $this->Conn->GetCol( sprintf($ids_sql, implode(',', $item_resource_ids) ) );
 
 					$temp = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 					if($item_ids) $temp->CloneItems($event->Prefix, $event->Special, $item_ids);
 				}
 
 				$this->Application->SetVar('m_cat_id', $saved_cat_id);
 			}
 		}*/
 
 		/**
 		 * Clears clipboard content
 		 *
 		 * @param kEvent $event
 		 */
 		function OnClearClipboard($event)
 		{
 			$this->Application->RemoveVar('clipboard');
 		}
 
 		/**
 		 * Sets correct status for new categories created on front-end
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object CategoriesItem */
 
 			if ( $object->GetDBField('ParentId') <= 0 ) {
 				// no parent category - use current (happens during import)
 				$object->SetDBField('ParentId', $this->Application->GetVar('m_cat_id'));
 			}
 
 			$this->_beforeItemChange($event);
 
 			if ( $this->Application->isAdmin || $event->Prefix == 'st' ) {
 				// don't check category permissions when auto-creating structure pages
 				return ;
 			}
 
 			$perm_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$new_status = false;
 			$category_id = $this->Application->GetVar('m_cat_id');
 
 			if ( $perm_helper->CheckPermission('CATEGORY.ADD', 0, $category_id) ) {
 				$new_status = STATUS_ACTIVE;
 			}
 			else {
 				if ( $perm_helper->CheckPermission('CATEGORY.ADD.PENDING', 0, $category_id) ) {
 					$new_status = STATUS_PENDING;
 				}
 			}
 
 			if ( $new_status ) {
 				$object->SetDBField('Status', $new_status);
 
 				// don't forget to set Priority for suggested from Front-End categories
 				$min_priority = $this->_getNextPriority($object->GetDBField('ParentId'), $object->TableName);
 				$object->SetDBField('Priority', $min_priority);
 			}
 			else {
 				$event->status = kEvent::erPERM_FAIL;
 				return ;
 			}
 		}
 
 		/**
 		 * Returns next available priority for given category from given table
 		 *
 		 * @param int $category_id
 		 * @param string $table_name
 		 * @return int
 		 */
 		function _getNextPriority($category_id, $table_name)
 		{
 			$sql = 'SELECT MIN(Priority)
 					FROM ' . $table_name . '
 					WHERE ParentId = ' . $category_id;
 			return (int)$this->Conn->GetOne($sql) - 1;
 		}
 
 		/**
 		 * Sets correct status for new categories created on front-end
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->_beforeItemChange($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->GetChangedFields() ) {
 				$object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
 			}
 		}
 
 		/**
 		 * Performs redirect to correct suggest confirmation template
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCreate(kEvent $event)
 		{
 			parent::OnCreate($event);
 
 			if ( $this->Application->isAdmin || $event->status != kEvent::erSUCCESS ) {
 				// don't sent email or rebuild cache directly after category is created by admin
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$cache_updater = $this->Application->makeClass('kPermCacheUpdater', Array (null, $object->GetDBField('ParentPath')));
 			/* @var $cache_updater kPermCacheUpdater */
 
 			$cache_updater->OneStepRun();
 
 			$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
 
 			$next_template = $is_active ? 'suggest_confirm_template' : 'suggest_pending_confirm_template';
 			$event->redirect = $this->Application->GetVar($next_template);
 			$event->SetRedirectParam('opener', 's');
 
 			// send email events
-			$perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix');
+			$perm_prefix = $event->getUnitConfig()->getPermItemPrefix();
 
 			$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
 			$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix);
 			$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $object->GetDBField('CreatedById'));
 		}
 
 		/**
 		 * Returns current per-page setting for list
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access protected
 		 */
 		protected function getPerPage(kEvent $event)
 		{
 			if ( !$this->Application->isAdmin ) {
 				$same_special = $event->getEventParam('same_special');
 				$event->setEventParam('same_special', true);
 
 				$per_page = parent::getPerPage($event);
 
 				$event->setEventParam('same_special', $same_special);
 			}
 
 			return parent::getPerPage($event);
 		}
 
 		/**
 		 * Set's correct page for list based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetPagination(kEvent $event)
 		{
 			parent::SetPagination($event);
 
 			if ( !$this->Application->isAdmin ) {
 				$page_var = $event->getEventParam('page_var');
 
 				if ( $page_var !== false ) {
 					$page = $this->Application->GetVar($page_var);
 
 					if ( is_numeric($page) ) {
 						$object = $event->getObject();
 						/* @var $object kDBList */
 
 						$object->SetPage($page);
 					}
 				}
 			}
 		}
 
 		/**
 		 * Apply same processing to each item being selected in grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function iterateItems(kEvent $event)
 		{
 			if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) {
 				parent::iterateItems($event);
 			}
 
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object CategoriesItem */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ( $ids ) {
-				$status_field = $object->getStatusField();
 				$propagate_category_status = $this->Application->GetVar('propagate_category_status');
+				$status_field = $event->getUnitConfig()->getStatusField(true);
 
 				foreach ($ids as $id) {
 					$object->Load($id);
 					$object->SetDBField($status_field, $event->Name == 'OnMassApprove' ? 1 : 0);
 
 					if ( $object->Update() ) {
 						if ( $propagate_category_status ) {
 							$sql = 'UPDATE ' . $object->TableName . '
 									SET ' . $status_field . ' = ' . $object->GetDBField($status_field) . '
 									WHERE TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight');
 							$this->Conn->Query($sql);
 						}
 
 						$event->status = kEvent::erSUCCESS;
 
 						$email_event = $event->Name == 'OnMassApprove' ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY';
 						$this->Application->emailUser($email_event, $object->GetDBField('CreatedById'));
 					}
 					else {
 						$event->status = kEvent::erFAIL;
 						$event->redirect = false;
 						break;
 					}
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 		}
 
 		/**
 		 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access protected
 		 */
 		protected function checkItemStatus(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->isLoaded() ) {
 				return true;
 			}
 
 			if ( $object->GetDBField('Status') != STATUS_ACTIVE && $object->GetDBField('Status') != 4 ) {
 				if ( !$object->GetDBField('DirectLinkEnabled') || !$object->GetDBField('DirectLinkAuthKey') ) {
 					return false;
 				}
 
 				return $this->Application->GetVar('authkey') == $object->GetDBField('DirectLinkAuthKey');
 			}
 
 			return true;
 		}
 
 		/**
 		 * Set's correct sorting for list based on data provided with event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetSorting(kEvent $event)
 		{
 			$types = $event->getEventParam('types');
 			$types = $types ? explode(',', $types) : Array ();
 
 			if ( in_array('search', $types) ) {
 				$event->setPseudoClass('_List');
 
 				$object = $event->getObject();
 				/* @var $object kDBList */
 
 				// 1. no user sorting - sort by relevance
 				$default_sortings = parent::_getDefaultSorting($event);
 				$default_sorting = key($default_sortings['Sorting']) . ',' . current($default_sortings['Sorting']);
 
 				if ( $object->isMainList() ) {
 					$sort_by = $this->Application->GetVar('sort_by', '');
 
 					if ( !$sort_by ) {
 						$this->Application->SetVar('sort_by', 'Relevance,desc|' . $default_sorting);
 					}
 					elseif ( strpos($sort_by, 'Relevance,') !== false ) {
 						$this->Application->SetVar('sort_by', $sort_by . '|' . $default_sorting);
 					}
 				}
 				else {
 					$sorting_settings = $this->getListSetting($event, 'Sortings');
 					$sort_by = trim(getArrayValue($sorting_settings, 'Sort1') . ',' . getArrayValue($sorting_settings, 'Sort1_Dir'), ',');
 
 					if ( !$sort_by ) {
 						$event->setEventParam('sort_by', 'Relevance,desc|' . $default_sorting);
 					}
 					elseif ( strpos($sort_by, 'Relevance,') !== false ) {
 						$event->setEventParam('sort_by', $sort_by . '|' . $default_sorting);
 					}
 				}
 
 				$this->_removeForcedSortings($event);
 			}
 
 			parent::SetSorting($event);
 		}
 
 		/**
 		 * Removes forced sortings
 		 *
 		 * @param kEvent $event
 		 */
 		protected function _removeForcedSortings(kEvent $event)
 		{
-			$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
-			/* @var $list_sortings Array */
+			$config = $event->getUnitConfig();
 
-			foreach ($list_sortings as $special => $sortings) {
-				unset($list_sortings[$special]['ForcedSorting']);
+			foreach ($config->getListSortingSpecials() as $special) {
+				$list_sortings = $config->getListSortingsBySpecial($special);
+				unset($list_sortings['ForcedSorting']);
+				$config->setListSortingsBySpecial('', $list_sortings);
 			}
-
-			$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
 		}
 
 		/**
 		 * Default sorting in search results only comes from relevance field
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getDefaultSorting(kEvent $event)
 		{
 			$types = $event->getEventParam('types');
 			$types = $types ? explode(',', $types) : Array ();
 
 			return in_array('search', $types) ? Array () : parent::_getDefaultSorting($event);
 		}
 
 		// ============= for cms page processing =======================
 
 		/**
 		 * Returns default design template
 		 *
 		 * @return string
 		 */
 		function _getDefaultDesign()
 		{
 			$default_design = trim($this->Application->ConfigValue('cms_DefaultDesign'), '/');
 
 			if (!$default_design) {
 				// theme-based alias for default design
 				return '#default_design#';
 			}
 
 			if (strpos($default_design, '#') === false) {
 				// real template, not alias, so prefix with "/"
 				return '/' . $default_design;
 			}
 
 			// alias
 			return $default_design;
 		}
 
 		/**
 		 * Returns default design based on given virtual template (used from kApplication::Run)
 		 *
 		 * @param string $t
 		 * @return string
 		 * @access public
 		 */
 		public function GetDesignTemplate($t = null)
 		{
 			if ( !isset($t) ) {
 				$t = $this->Application->GetVar('t');
 			}
 
 			$page = $this->Application->recallObject($this->Prefix . '.-virtual', null, Array ('page' => $t));
 			/* @var $page CategoriesItem */
 
 			if ( $page->isLoaded() ) {
 				$real_t = $page->GetDBField('CachedTemplate');
 				$this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId'));
 
 				if ( $page->GetDBField('FormId') ) {
 					$this->Application->SetVar('form_id', $page->GetDBField('FormId'));
 				}
 			}
 			else {
 				$not_found = $this->Application->ConfigValue('ErrorTemplate');
 				$real_t = $not_found ? $not_found : 'error_notfound';
 
 				$themes_helper = $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				$theme_id = $this->Application->GetVar('m_theme');
 				$category_id = $themes_helper->getPageByTemplate($real_t, $theme_id);
 				$this->Application->SetVar('m_cat_id', $category_id);
 
 				header('HTTP/1.0 404 Not Found');
 			}
 
 			// replace alias in form #alias_name# to actual template used in this theme
 			if ( $this->Application->isAdmin ) {
 				$themes_helper = $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				// only, used when in "Design Mode"
 				$this->Application->SetVar('theme.current_id', $themes_helper->getCurrentThemeId());
 			}
 
 			$theme = $this->Application->recallObject('theme.current');
 			/* @var $theme kDBItem */
 
 			$template = $theme->GetField('TemplateAliases', $real_t);
 
 			if ( $template ) {
 				return $template;
 			}
 
 			return $real_t;
 		}
 
 		/**
 		 * Sets category id based on found template (used from kApplication::Run)
 		 *
 		 * @deprecated
 		 */
 		/*function SetCatByTemplate()
 		{
 			$t = $this->Application->GetVar('t');
 			$page = $this->Application->recallObject($this->Prefix . '.-virtual');
 
 			if ($page->isLoaded()) {
 				$this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId') );
 			}
 		}*/
 
 		/**
 		 * Prepares template paths
 		 *
 		 * @param kEvent $event
 		 */
 		function _beforeItemChange($event)
 		{
 			$object = $event->getObject();
 			/* @var $object CategoriesItem */
 
 			$object->checkFilename();
 			$object->generateFilename();
 
 			$now = adodb_mktime();
 
 			if ( !$this->Application->isDebugMode() && strpos($event->Special, 'rebuild') === false ) {
 				$object->SetDBField('Type', $object->GetOriginalField('Type'));
 				$object->SetDBField('Protected', $object->GetOriginalField('Protected'));
 
 				if ( $object->GetDBField('Protected') ) {
 					// some fields are read-only for protected pages, when debug mode is off
 					$object->SetDBField('AutomaticFilename', $object->GetOriginalField('AutomaticFilename'));
 					$object->SetDBField('Filename', $object->GetOriginalField('Filename'));
 					$object->SetDBField('Status', $object->GetOriginalField('Status'));
 				}
 			}
 
 			$is_admin = $this->Application->isAdminUser;
 
 			if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField('CreatedById')) ) {
 				$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 			}
 
 			if ($object->GetChangedFields()) {
 				$object->SetDBField('Modified_date', $now);
 				$object->SetDBField('Modified_time', $now);
 			}
 
 			$object->setRequired('PageCacheKey', $object->GetDBField('OverridePageCacheKey'));
 			$object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') ));
 
 			if ($object->GetDBField('Type') == PAGE_TYPE_TEMPLATE) {
 				if (!$this->_templateFound($object->GetDBField('Template'), $object->GetDBField('ThemeId'))) {
 					$object->SetError('Template', 'template_file_missing', 'la_error_TemplateFileMissing');
 				}
 			}
 
 			$this->_saveTitleField($object, 'Title');
 			$this->_saveTitleField($object, 'MenuTitle');
 
 			$root_category = $this->Application->getBaseCategory();
 
 			if ( file_exists(FULL_PATH . '/themes') && ($object->GetDBField('ParentId') == $root_category) && ($object->GetDBField('Template') == CATEGORY_TEMPLATE_INHERIT) ) {
 				// there are themes + creating top level category
 				$object->SetError('Template', 'no_inherit');
 			}
 
 			if ( !$this->Application->isAdminUser && $object->isVirtualField('cust_RssSource') ) {
 				// only administrator can set/change "cust_RssSource" field
 
 				if ($object->GetDBField('cust_RssSource') != $object->GetOriginalField('cust_RssSource')) {
 					$object->SetError('cust_RssSource', 'not_allowed', 'la_error_OperationNotAllowed');
 				}
 			}
 
 			if ( !$object->GetDBField('DirectLinkAuthKey') ) {
 				$key_parts = Array (
 					$object->GetID(),
 					$object->GetDBField('ParentId'),
 					$object->GetField('Name'),
 					'b38'
 				);
 
 				$object->SetDBField('DirectLinkAuthKey', substr( md5( implode(':', $key_parts) ), 0, 20 ));
 			}
 		}
 
 		/**
 		 * Sets page name to requested field in case when:
 		 * 1. page was auto created (through theme file rebuild)
 		 * 2. requested field is empty
 		 *
 		 * @param kDBItem $object
 		 * @param string $field
 		 * @author Alex
 		 */
 		function _saveTitleField(&$object, $field)
 		{
 			$value = $object->GetField($field, 'no_default'); // current value of target field
 
 			$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 			/* @var $ml_formatter kMultiLanguage */
 
 			$src_field = $ml_formatter->LangFieldName('Name');
 			$dst_field = $ml_formatter->LangFieldName($field);
 
 			$dst_field_not_changed = $object->GetOriginalField($dst_field) == $value;
 
 			if ($value == '' || preg_match('/^_Auto: (.*)/', $value) || (($object->GetOriginalField($src_field) == $value) && $dst_field_not_changed)) {
 				// target field is empty OR target field value starts with "_Auto: " OR (source field value
 				// before change was equals to current target field value AND target field value wasn't changed)
 				$object->SetField($dst_field, $object->GetField($src_field));
 			}
 		}
 
 		/**
 		 * Don't allow to delete system pages, when not in debug mode
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemDelete(kEvent $event)
 		{
 			parent::OnBeforeItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->GetDBField('Protected') && !$this->Application->isDebugMode(false) ) {
 				$event->status = kEvent::erFAIL;
 			}
 		}
 
 		/**
 		 * Creates category based on given TPL file
 		 *
 		 * @param CategoriesItem $object
 		 * @param string $template
 		 * @param int $theme_id
 		 * @param int $system_mode
 		 * @param array $template_info
 		 * @return bool
 		 */
 		function _prepareAutoPage(&$object, $template, $theme_id = null, $system_mode = SMS_MODE_AUTO, $template_info = Array ())
 		{
 			$template = $this->_stripTemplateExtension($template);
 
 			if ($system_mode == SMS_MODE_AUTO) {
 				$page_type = $this->_templateFound($template, $theme_id) ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL;
 			}
 			else {
 				$page_type = $system_mode == SMS_MODE_FORCE ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL;
 			}
 
 			if (($page_type == PAGE_TYPE_TEMPLATE) && ($template_info === false)) {
 				// do not auto-create system pages, when browsing through site
 				return false;
 			}
 
 			if (!isset($theme_id)) {
 				$theme_id = $this->_getCurrentThemeId();
 			}
 
 			$root_category = $this->Application->getBaseCategory();
 			$page_category = $this->Application->GetVar('m_cat_id');
 			if (!$page_category) {
 				$page_category = $root_category;
 				$this->Application->SetVar('m_cat_id', $page_category);
 			}
 
 			if (($page_type == PAGE_TYPE_VIRTUAL) && (strpos($template, '/') !== false)) {
 				// virtual page, but have "/" in template path -> create it's path
 				$category_path = explode('/', $template);
 				$template = array_pop($category_path);
 
 				$page_category = $this->_getParentCategoryFromPath($category_path, $root_category, $theme_id);
 			}
 
 			$page_name = ($page_type == PAGE_TYPE_TEMPLATE) ? '_Auto: ' . $template : $template;
 			$page_description = '';
 
 			if ($page_type == PAGE_TYPE_TEMPLATE) {
 				$design_template = strtolower($template); // leading "/" not added !
 				if ($template_info) {
 					if (array_key_exists('name', $template_info) && $template_info['name']) {
 						$page_name = $template_info['name'];
 					}
 
 					if (array_key_exists('desc', $template_info) && $template_info['desc']) {
 						$page_description = $template_info['desc'];
 					}
 
 					if (array_key_exists('section', $template_info) && $template_info['section']) {
 						// this will override any global "m_cat_id"
 						$page_category = $this->_getParentCategoryFromPath(explode('||', $template_info['section']), $root_category, $theme_id);
 					}
 				}
 			}
 			else {
 				$design_template = $this->_getDefaultDesign(); // leading "/" added !
 			}
 
 			$object->Clear();
 			$object->SetDBField('ParentId', $page_category);
 			$object->SetDBField('Type', $page_type);
 			$object->SetDBField('Protected', 1); // $page_type == PAGE_TYPE_TEMPLATE
 
 			$object->SetDBField('IsMenu', 0);
 			$object->SetDBField('ThemeId', $theme_id);
 
 			// put all templates to then end of list (in their category)
 			$min_priority = $this->_getNextPriority($page_category, $object->TableName);
 			$object->SetDBField('Priority', $min_priority);
 
 			$object->SetDBField('Template', $design_template);
 			$object->SetDBField('CachedTemplate', $design_template);
 
 			$primary_language = $this->Application->GetDefaultLanguageId();
 			$current_language = $this->Application->GetVar('m_lang');
 			$object->SetDBField('l' . $primary_language . '_Name', $page_name);
 			$object->SetDBField('l' . $current_language . '_Name', $page_name);
 			$object->SetDBField('l' . $primary_language . '_Description', $page_description);
 			$object->SetDBField('l' . $current_language . '_Description', $page_description);
 
 			return $object->Create();
 		}
 
 		function _getParentCategoryFromPath($category_path, $base_category, $theme_id = null)
 		{
 			static $category_ids = Array ();
 
 			if (!$category_path) {
 				return $base_category;
 			}
 
 			if (array_key_exists(implode('||', $category_path), $category_ids)) {
 				return $category_ids[ implode('||', $category_path) ];
 			}
 
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			$object = $this->Application->recallObject($this->Prefix . '.rebuild-path', null, Array ('skip_autoload' => true));
 			/* @var $object CategoriesItem */
 
 			$parent_id = $base_category;
 
 			$filenames_helper = $this->Application->recallObject('FilenamesHelper');
 			/* @var $filenames_helper kFilenamesHelper */
 
 			$safe_category_path = array_map(Array (&$filenames_helper, 'replaceSequences'), $category_path);
 
 			foreach ($category_path as $category_order => $category_name) {
 				$this->Application->SetVar('m_cat_id', $parent_id);
 
 				// get virtual category first, when possible
 				$sql = 'SELECT ' . $object->IDField . '
 						FROM ' . $object->TableName . '
 						WHERE
 							(
 								Filename = ' . $this->Conn->qstr($safe_category_path[$category_order]) . ' OR
 								Filename = ' . $this->Conn->qstr( $filenames_helper->replaceSequences('_Auto: ' . $category_name) ) . '
 							) AND
 							(ParentId = ' . $parent_id . ') AND
 							(ThemeId = 0 OR ThemeId = ' . $theme_id . ')
 						ORDER BY ThemeId ASC';
 				$parent_id = $this->Conn->GetOne($sql);
 
 				if ($parent_id === false) {
 					// page not found
 					$template = implode('/', array_slice($safe_category_path, 0, $category_order + 1));
 
 					// don't process system templates in sub-categories
 					$system = $this->_templateFound($template, $theme_id) && (strpos($template, '/') === false);
 
 					if (!$this->_prepareAutoPage($object, $category_name, $theme_id, $system ? SMS_MODE_FORCE : false)) {
 						// page was not created
 						break;
 					}
 
 					$parent_id = $object->GetID();
 				}
 			}
 
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 			$category_ids[ implode('||', $category_path) ] = $parent_id;
 
 			return $parent_id;
 		}
 
 		/**
 		 * Returns theme name by it's id. Used in structure page creation.
 		 *
 		 * @param int $theme_id
 		 * @return string
 		 */
 		function _getThemeName($theme_id)
 		{
 			static $themes = null;
 
 			if (!isset($themes)) {
-				$id_field = $this->Application->getUnitOption('theme', 'IDField');
-				$table_name = $this->Application->getUnitOption('theme', 'TableName');
+				$theme_config = $this->Application->getUnitConfig('theme');
+				$id_field = $theme_config->getIDField();
+				$table_name = $theme_config->getTableName();
 
 				$sql = 'SELECT Name, ' . $id_field . '
 						FROM ' . $table_name . '
 						WHERE Enabled = 1';
 				$themes = $this->Conn->GetCol($sql, $id_field);
 			}
 
 			return array_key_exists($theme_id, $themes) ? $themes[$theme_id] : false;
 		}
 
 		/**
 		 * Resets SMS-menu cache
 		 *
 		 * @param kEvent $event
 		 */
 		function OnResetCMSMenuCache($event)
 		{
 			if ($this->Application->GetVar('ajax') == 'yes') {
 				$event->status = kEvent::erSTOP;
 			}
 
 			$this->_resetMenuCache();
 			$event->SetRedirectParam('action_completed', 1);
 		}
 
 		/**
 		 * Performs reset of category-related caches (menu, structure dropdown, template mapping)
 		 *
 		 * @return void
 		 * @access protected
 		 */
 		protected function _resetMenuCache()
 		{
 			// reset cms menu cache (all variables are automatically rebuild, when missing)
 			if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 				$this->Application->rebuildCache('master:cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime);
 				$this->Application->rebuildCache('master:StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime);
 				$this->Application->rebuildCache('master:template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime);
 			}
 			else {
 				$this->Application->rebuildDBCache('cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime);
 				$this->Application->rebuildDBCache('StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime);
 				$this->Application->rebuildDBCache('template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime);
 			}
 		}
 
 		/**
 		 * Updates structure config
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			if (defined('IS_INSTALL') && IS_INSTALL) {
 				// skip any processing, because Categories table doesn't exists until install is finished
 				return ;
 			}
 
 			$site_config_helper = $this->Application->recallObject('SiteConfigHelper');
 			/* @var $site_config_helper SiteConfigHelper */
 
 			$settings = $site_config_helper->getSettings();
 
 			$root_category = $this->Application->getBaseCategory();
 
-			// set root category
-			$section_adjustments = $this->Application->getUnitOption($event->Prefix, 'SectionAdjustments');
+			$config = $event->getUnitConfig();
 
-			$section_adjustments['in-portal:browse'] = Array (
+			// set root category
+			$config->addSectionAdjustments(Array (
+				'in-portal:browse' => Array (
 				'url' => Array ('m_cat_id' => $root_category),
 				'late_load' => Array ('m_cat_id' => $root_category),
 				'onclick' => 'checkCatalog(' . $root_category . ')',
-			);
-
-			$section_adjustments['in-portal:browse_site'] = Array (
+				),
+				'in-portal:browse_site' => Array (
 				'url' => Array ('editing_mode' => $settings['default_editing_mode']),
-			);
-
-			$this->Application->setUnitOption($event->Prefix, 'SectionAdjustments', $section_adjustments);
+				)
+			));
 
 			// prepare structure dropdown
 			$category_helper = $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
-			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
+			$fields = $config->getFields();
 
 			$fields['ParentId']['default'] = (int)$this->Application->GetVar('m_cat_id');
 			$fields['ParentId']['options'] = $category_helper->getStructureTreeAsOptions();
 
 			// limit design list by theme
 			$theme_id = $this->_getCurrentThemeId();
 			$design_sql = $fields['Template']['options_sql'];
 			$design_sql = str_replace('(tf.FilePath = "/designs")', '(' . implode(' OR ', $this->getDesignFolders()) . ')' . ' AND (t.ThemeId = ' . $theme_id . ')', $design_sql);
 			$fields['Template']['options_sql'] = $design_sql;
 
 			// adds "Inherit From Parent" option to "Template" field
 			$fields['Template']['options'] = Array (CATEGORY_TEMPLATE_INHERIT => $this->Application->Phrase('la_opt_InheritFromParent'));
 
-			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
+			$config->setFields($fields);
 
 			if ($this->Application->isAdmin) {
 				// don't sort by Front-End sorting fields
-				$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
+				$config_mapping = $config->getConfigMapping();
 				$remove_keys = Array ('DefaultSorting1Field', 'DefaultSorting2Field', 'DefaultSorting1Dir', 'DefaultSorting2Dir');
+
 				foreach ($remove_keys as $remove_key) {
 					unset($config_mapping[$remove_key]);
 				}
-				$this->Application->setUnitOption($event->Prefix, 'ConfigMapping', $config_mapping);
+
+				$config->setConfigMapping($config_mapping);
 			}
 			else {
 				// sort by parent path on Front-End only
-				$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
-				$list_sortings['']['ForcedSorting'] = Array ("CurrentSort" => 'asc');
-				$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
+				$config->setListSortingsBySpecial('', Array (
+					'ForcedSorting' => Array ('CurrentSort' => 'asc'),
+				));
 			}
 
 			// add grids for advanced view (with primary category column)
-			$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
-			$process_grids = Array ('Default', 'Radio');
-			foreach ($process_grids as $process_grid) {
-				$grid_data = $grids[$process_grid];
+			foreach (Array ('Default', 'Radio') as $process_grid) {
+				$grid_data = $config->getGridByName($process_grid);
 				$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_parent_category_td', 'filter_block' => 'grid_like_filter');
-				$grids[$process_grid . 'ShowAll'] = $grid_data;
+				$config->addGrids($grid_data, $process_grid . 'ShowAll');
 			}
-			$this->Application->setUnitOption($this->Prefix, 'Grids', $grids);
 		}
 
 		/**
 		 * Returns folders, that can contain design templates
 		 *
 		 * @return array
 		 * @access protected
 		 */
 		protected function getDesignFolders()
 		{
 			$ret = Array ('tf.FilePath = "/designs"', 'tf.FilePath = "/platform/designs"');
 
 			foreach ($this->Application->ModuleInfo as $module_info) {
 				$ret[] = 'tf.FilePath = "/' . $module_info['TemplatePath'] . 'designs"';
 			}
 
 			return array_unique($ret);
 		}
 
 		/**
 		 * Removes this item and it's children (recursive) from structure dropdown
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			if ( !$this->Application->isAdmin ) {
 				// calculate priorities dropdown only for admin
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// remove this category & it's children from dropdown
 			$sql = 'SELECT ' . $object->IDField . '
-					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+					FROM ' . $event->getUnitConfig()->getTableName() . '
 					WHERE ParentPath LIKE "' . $object->GetDBField('ParentPath') . '%"';
 			$remove_categories = $this->Conn->GetCol($sql);
 
 			$options = $object->GetFieldOption('ParentId', 'options');
+
 			foreach ($remove_categories as $remove_category) {
 				unset($options[$remove_category]);
 			}
+
 			$object->SetFieldOption('ParentId', 'options', $options);
 		}
 
 		/**
 		 * Occurs after creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(kEvent $event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object CategoriesItem */
 
 			// need to update path after category is created, so category is included in that path
 			$fields_hash = $object->buildParentBasedFields();
 			$this->Conn->doUpdate($fields_hash, $object->TableName, $object->IDField . ' = ' . $object->GetID());
 			$object->SetDBFieldsFromHash($fields_hash);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterRebuildThemes($event)
 		{
 			$sql = 'SELECT t.ThemeId, CONCAT( tf.FilePath, \'/\', tf.FileName ) AS Path, tf.FileMetaInfo
 					FROM ' . TABLE_PREFIX . 'ThemeFiles AS tf
 					LEFT JOIN ' . TABLE_PREFIX . 'Themes AS t ON t.ThemeId = tf.ThemeId
 					WHERE t.Enabled = 1 AND tf.FileType = 1
 					AND (
 						SELECT COUNT(CategoryId)
 						FROM ' . TABLE_PREFIX . 'Categories c
 						WHERE CONCAT(\'/\', c.Template, \'.tpl\') = CONCAT( tf.FilePath, \'/\', tf.FileName ) AND (c.ThemeId = t.ThemeId)
 					) = 0 ';
 			$files = $this->Conn->Query($sql, 'Path');
 			if ( !$files ) {
 				// all possible pages are already created
 				return;
 			}
 
 			kUtil::setResourceLimit();
 
 			$dummy = $this->Application->recallObject($event->Prefix . '.rebuild', NULL, Array ('skip_autoload' => true));
 			/* @var $dummy CategoriesItem */
 
 			$error_count = 0;
 			foreach ($files as $a_file => $file_info) {
 				$status = $this->_prepareAutoPage($dummy, $a_file, $file_info['ThemeId'], SMS_MODE_FORCE, unserialize($file_info['FileMetaInfo'])); // create system page
 				if ( !$status ) {
 					$error_count++;
 				}
 			}
 
 			if ( $this->Application->ConfigValue('CategoryPermissionRebuildMode') == CategoryPermissionRebuild::SILENT ) {
 				$updater = $this->Application->makeClass('kPermCacheUpdater');
 				/* @var $updater kPermCacheUpdater */
 
 				$updater->OneStepRun();
 			}
 
 			$this->_resetMenuCache();
 
 			if ( $error_count ) {
 				// allow user to review error after structure page creation
 				$event->MasterEvent->redirect = false;
 			}
 		}
 
 		/**
 		 * Processes OnMassMoveUp, OnMassMoveDown events
 		 *
 		 * @param kEvent $event
 		 */
 		function OnChangePriority($event)
 		{
 			$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
 			$event->CallSubEvent('priority:' . $event->Name);
 
 			$this->Application->StoreVar('RefreshStructureTree', 1);
 			$this->_resetMenuCache();
 		}
 
 		/**
 		 * Completely recalculates priorities in current category
 		 *
 		 * @param kEvent $event
 		 */
 		function OnRecalculatePriorities($event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
 			$event->CallSubEvent('priority:' . $event->Name);
 
 			$this->_resetMenuCache();
 		}
 
 		/**
 		 * Update Preview Block for FCKEditor
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdatePreviewBlock($event)
 		{
 			$event->status = kEvent::erSTOP;
 			$string = htmlspecialchars_decode($this->Application->GetVar('preview_content'));
 
 			$category_helper = $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$string = $category_helper->replacePageIds($string);
 
 			$this->Application->StoreVar('_editor_preview_content_', $string);
 		}
 
 		/**
 		 * Makes simple search for categories
 		 * based on keywords string
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSimpleSearch($event)
 		{
 			$event->redirect = false;
 			$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 
 			$keywords = htmlspecialchars_decode( trim($this->Application->GetVar('keywords')) );
 
 			$query_object = $this->Application->recallObject('HTTPQuery');
 			/* @var $query_object kHTTPQuery */
 
 			$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
 
 			if ( !isset($query_object->Get['keywords']) && !isset($query_object->Post['keywords']) && $this->Conn->Query($sql) ) {
 				// used when navigating by pages or changing sorting in search results
 				return;
 			}
 
 			if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length'))
 			{
 				$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
 				$this->Application->SetVar('keywords_too_short', 1);
 				return; // if no or too short keyword entered, doing nothing
 			}
 
 			$this->Application->StoreVar('keywords', $keywords);
 
 			$this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search
 
 			$keywords = strtr($keywords, Array('%' => '\\%', '_' => '\\_'));
 
 			$event->setPseudoClass('_List');
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
+			$config = $event->getUnitConfig();
+
 			$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
 			$lang = $this->Application->GetVar('m_lang');
-			$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$items_table = $config->getTableName();
 			$module_name = 'In-Portal';
 
 			$sql = 'SELECT *
-					FROM ' . $this->Application->getUnitOption('confs', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('confs')->getTableName() . '
 					WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1';
 			$search_config = $this->Conn->Query($sql, 'FieldName');
 
 			$field_list = array_keys($search_config);
 
 			$join_clauses = Array();
 
 			// field processing
 			$weight_sum = 0;
 
 			$alias_counter = 0;
 
-			$custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
+			$custom_fields = $config->getCustomFields();
 			if ($custom_fields) {
-				$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
+				$custom_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName();
 				$join_clauses[] = '	LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
 			}
 
 			// what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table))
 			$search_config_map = Array();
 
 			foreach ($field_list as $key => $field) {
 				$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
 				$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
 
 				// processing multilingual fields
 				if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
 					$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 					$field_list[$key] = 'l'.$lang.'_'.$field;
 
 					if (!isset($search_config[$field]['ForeignField'])) {
 						$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 				}
 
 				// processing fields from other tables
 				$foreign_field = $search_config[$field]['ForeignField'];
 
 				if ( $foreign_field ) {
 					$exploded = explode(':', $foreign_field, 2);
 					if ($exploded[0] == 'CALC') {
 						// ignoring having type clauses in simple search
 						unset($field_list[$key]);
 						continue;
 					}
 					else {
 						$multi_lingual = false;
 						if ($exploded[0] == 'MULTI') {
 							$multi_lingual = true;
 							$foreign_field = $exploded[1];
 						}
 
 						$exploded = explode('.', $foreign_field);	// format: table.field_name
 						$foreign_table = TABLE_PREFIX.$exploded[0];
 
 						$alias_counter++;
 						$alias = 't'.$alias_counter;
 
 						if ($multi_lingual) {
 							$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
 							$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
 							$search_config_map[ $field_list[$key] ] = $field;
 							$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 						}
 						else {
 							$field_list[$key] = $alias.'.'.$exploded[1];
 							$search_config_map[ $field_list[$key] ] = $field;
 						}
 
 						$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
 						$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
 
 						$join_clauses[] = '	LEFT JOIN '.$foreign_table.' '.$alias.'
 											ON '.$join_clause;
 					}
 				}
 				else {
 					// processing fields from local table
 					if ($search_config[$field]['CustomFieldId']) {
 						$local_table = 'custom_data';
 
 						// search by custom field value on current language
 						$custom_field_id = array_search($field_list[$key], $custom_fields);
 						$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
 
 						// search by custom field value on primary language
 						$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
 						$search_config_map[ $field_list[$key.'_primary'] ] = $field;
 					}
 
 					$field_list[$key] = $local_table.'.'.$field_list[$key];
 					$search_config_map[ $field_list[$key] ] = $field;
 				}
 			}
 
 			// keyword string processing
 			$search_helper = $this->Application->recallObject('SearchHelper');
 			/* @var $search_helper kSearchHelper */
 
 			$where_clause = Array ();
 			foreach ($field_list as $field) {
 				if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
 					// local real field
 					$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
 					if ($filter_data) {
 						$where_clause[] = $filter_data['value'];
 					}
 				}
 				elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
 					$custom_field_name = 'cust_' . $search_config_map[$field];
 					$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
 					if ($filter_data) {
 						$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
 					}
 				}
 				else {
 					$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
 				}
 			}
 
 			$where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below!
 
 			$where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')';
 
 			if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
 				$sub_search_ids = $event->MasterEvent->getEventParam('ResultIds');
 
 				if ( $sub_search_ids !== false ) {
 					if ( $sub_search_ids ) {
 						$where_clause .= 'AND (' . $items_table . '.ResourceId IN (' . implode(',', $sub_search_ids) . '))';
 					}
 					else {
 						$where_clause .= 'AND FALSE';
 					}
 				}
 			}
 
 			// exclude template based sections from search results (ie. registration)
 			if ( $this->Application->ConfigValue('ExcludeTemplateSectionsFromSearch') ) {
 				$where_clause .= ' AND ' . $items_table . '.ThemeId = 0';
 			}
 
 			// making relevance clause
 			$positive_words = $search_helper->getPositiveKeywords($keywords);
 			$this->Application->StoreVar('highlight_keywords', serialize($positive_words));
 			$revelance_parts = Array();
 			reset($search_config);
 
 			foreach ($positive_words as $keyword_index => $positive_word) {
 				$positive_word = $search_helper->transformWildcards($positive_word);
 				$positive_words[$keyword_index] = $this->Conn->escape($positive_word);
 			}
 
 			foreach ($field_list as $field) {
 
 				if (!array_key_exists($field, $search_config_map)) {
 					$map_key = $search_config_map[$items_table . '.' . $field];
 				}
 				else {
 					$map_key = $search_config_map[$field];
 				}
 
 				$config_elem = $search_config[ $map_key ];
 				$weight = $config_elem['Priority'];
 
 				// search by whole words only ([[:<:]] - word boundary)
 				/*$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
 				foreach ($positive_words as $keyword) {
 					$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
 				}*/
 
 				// search by partial word matches too
 				$revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
 				foreach ($positive_words as $keyword) {
 					$revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
 				}
 			}
 
 			$revelance_parts = array_unique($revelance_parts);
 
-			$conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
+			$conf_postfix = $config->getSearchConfigPostfix();
 			$rel_keywords	= $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix)	/ 100;
 			$rel_pop		= $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix)		/ 100;
 			$rel_rating		= $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix)	/ 100;
 			$relevance_clause = '('.implode(' + ', $revelance_parts).') / '.$weight_sum.' * '.$rel_keywords;
 			if ($rel_pop && $object->isField('Hits')) {
 				$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
 			}
 			if ($rel_rating && $object->isField('CachedRating')) {
 				$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
 			}
 
 			// building final search query
 			if (!$this->Application->GetVar('do_not_drop_search_table')) {
 				$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
 				$this->Application->SetVar('do_not_drop_search_table', true);
 			}
 
 			$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
 			if ($search_table_exists) {
 				$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
 			}
 			else {
 				$select_intro = 'CREATE TABLE '.$search_table.' AS ';
 			}
 
-			$edpick_clause = $this->Application->getUnitOption($event->Prefix.'.EditorsPick', 'Fields') ? $items_table.'.EditorsPick' : '0';
+			$edpick_clause = $config->getFieldByName('EditorsPick') ? $items_table.'.EditorsPick' : '0';
 
 			$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
-								'.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' AS ItemId,
+								'.$items_table.'.'.$config->getIDField().' AS ItemId,
 								'.$items_table.'.ResourceId,
-								'.$this->Application->getUnitOption($event->Prefix, 'ItemType').' AS ItemType,
+								'.$config->getItemType().' AS ItemType,
 								 '.$edpick_clause.' AS EdPick
 						FROM '.$object->TableName.'
 						'.implode(' ', $join_clauses).'
 						WHERE '.$where_clause.'
-						GROUP BY '.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' ORDER BY Relevance DESC';
+						GROUP BY '.$items_table.'.'.$config->getIDField().' ORDER BY Relevance DESC';
 
 			$this->Conn->Query($sql);
 
 			if ( !$search_table_exists ) {
 				$sql = 'ALTER TABLE ' . $search_table . '
 						ADD INDEX (ResourceId),
 						ADD INDEX (Relevance)';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSubSearch($event)
 		{
 			// keep search results from other items after doing a sub-search on current item type
 			$this->Application->SetVar('do_not_drop_search_table', true);
 
 			$ids = Array ();
 			$search_table = TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search';
 			$sql = 'SHOW TABLES LIKE "' . $search_table . '"';
 
 			if ( $this->Conn->Query($sql) ) {
-				$item_type = $this->Application->getUnitOption($event->Prefix, 'ItemType');
+				$item_type = $event->getUnitConfig()->getItemType();
 
 				// 1. get ids to be used as search bounds
 				$sql = 'SELECT DISTINCT ResourceId
 						FROM ' . $search_table . '
 						WHERE ItemType = ' . $item_type;
 				$ids = $this->Conn->GetCol($sql);
 
 				// 2. delete previously found ids
 				$sql = 'DELETE FROM ' . $search_table . '
 						WHERE ItemType = ' . $item_type;
 				$this->Conn->Query($sql);
 			}
 
 			$event->setEventParam('ResultIds', $ids);
 			$event->CallSubEvent('OnSimpleSearch');
 		}
 
 		/**
 		 * Make record to search log
 		 *
 		 * @param string $keywords
 		 * @param int $search_type 0 - simple search, 1 - advanced search
 		 */
 		function saveToSearchLog($keywords, $search_type = 0)
 		{
 			// don't save keywords for each module separately, just one time
 			// static variable can't help here, because each module uses it's own class instance !
 			if (!$this->Application->GetVar('search_logged')) {
 				$sql = 'UPDATE '.TABLE_PREFIX.'SearchLogs
 						SET Indices = Indices + 1
 						WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search
 		        $this->Conn->Query($sql);
 		        if ($this->Conn->getAffectedRows() == 0) {
 		            $fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type);
 		        	$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLogs');
 		        }
 
 		        $this->Application->SetVar('search_logged', 1);
 			}
 		}
 
 		/**
 		 * Load item if id is available
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function LoadItem(kEvent $event)
 		{
 			if ( $event->Special != '-virtual' ) {
 				parent::LoadItem($event);
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$id = $this->getPassedID($event);
 
 			if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) {
 				// object is already loaded by same id
 				return;
 			}
 
 			if ( $object->Load($id, null, true) ) {
 				$actions = $this->Application->recallObject('kActions');
 				/* @var $actions Params */
 
 				$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
 			}
 			else {
 				$object->setID($id);
 			}
 		}
 
 		/**
 		 * Returns constrain for priority calculations
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @see PriorityEventHandler
 		 * @access protected
 		 */
 		protected function OnGetConstrainInfo(kEvent $event)
 		{
 			$constrain = ''; // for OnSave
 
 			$event_name = $event->getEventParam('original_event');
 			$actual_event_name = $event->getEventParam('actual_event');
 
 			if ( $actual_event_name == 'OnSavePriorityChanges' || $event_name == 'OnAfterItemLoad' || $event_name == 'OnAfterItemDelete' ) {
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$constrain = 'ParentId = ' . $object->GetDBField('ParentId');
 			}
 			elseif ( $actual_event_name == 'OnPreparePriorities' ) {
 				$constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id');
 			}
 			elseif ( $event_name == 'OnSave' ) {
 				$constrain = '';
 			}
 			else {
 				$constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id');
 			}
 
 			$event->setEventParam('constrain_info', Array ($constrain, ''));
 		}
 
 		/**
 		 * Parses category part of url, build main part of url
 		 *
 		 * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE.
 		 * @param string $prefix Prefix, that listener uses for system integration
 		 * @param Array $params Params, that are used for url building or created during url parsing.
 		 * @param Array $url_parts Url parts to parse (only for parsing).
 		 * @param bool $keep_events Keep event names in resulting url (only for building).
 		 * @return bool|string|Array Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener.
 		 */
 		public function CategoryRewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix, &$params, &$url_parts, $keep_events = false)
 		{
 			if ($rewrite_mode == REWRITE_MODE_BUILD) {
 				return $this->_buildMainUrl($prefix, $params, $keep_events);
 			}
 
 			if ( $this->_parseFriendlyUrl($url_parts, $params) ) {
 				// friendly urls work like exact match only!
 				return false;
 			}
 
 			$this->_parseCategory($url_parts, $params);
 
 			return true;
 		}
 
 		/**
 		 * Build main part of every url
 		 *
 		 * @param string $prefix_special
 		 * @param Array $params
 		 * @param bool $keep_events
 		 * @return string
 		 */
 		protected function _buildMainUrl($prefix_special, &$params, $keep_events)
 		{
 			$ret = '';
 			list ($prefix) = explode('.', $prefix_special);
 
 			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			$processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events);
 			if ($processed_params === false) {
 				return '';
 			}
 
 			// add language
 			if ($processed_params['m_lang'] && ($processed_params['m_lang'] != $rewrite_processor->primaryLanguageId)) {
 				$language_name = $this->Application->getCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]');
 				if ($language_name === false) {
 					$sql = 'SELECT PackName
 							FROM ' . TABLE_PREFIX . 'Languages
 							WHERE LanguageId = ' . $processed_params['m_lang'];
 					$language_name = $this->Conn->GetOne($sql);
 
 					$this->Application->setCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]', $language_name);
 				}
 
 				$ret .= $language_name . '/';
 			}
 
 			// add theme
 			if ($processed_params['m_theme'] && ($processed_params['m_theme'] != $rewrite_processor->primaryThemeId)) {
 				$theme_name = $this->Application->getCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]');
 				if ($theme_name === false) {
 					$sql = 'SELECT Name
 							FROM ' . TABLE_PREFIX . 'Themes
 							WHERE ThemeId = ' . $processed_params['m_theme'];
 					$theme_name = $this->Conn->GetOne($sql);
 
 					$this->Application->setCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]', $theme_name);
 
 				}
 
 				$ret .= $theme_name . '/';
 			}
 
 			// inject custom url parts made by other rewrite listeners just after language/theme url parts
 			if ($params['inject_parts']) {
 				$ret .= implode('/', $params['inject_parts']) . '/';
 			}
 
 			// add category
 			if ($processed_params['m_cat_id'] > 0 && $params['pass_category']) {
 				$category_filename = $this->Application->getCategoryCache($processed_params['m_cat_id'], 'filenames');
 
 				preg_match('/^Content\/(.*)/i', $category_filename, $regs);
 
 				if ($regs) {
 					$template = array_key_exists('t', $params) ? $params['t'] : false;
 
 					if (strtolower($regs[1]) == strtolower($template)) {
 						// we could have category path like "Content/<template_path>" in this case remove template
 						$params['pass_template'] = false;
 					}
 
 					$ret .= $regs[1] . '/';
 				}
 
 				$params['category_processed'] = true;
 			}
 
 			// reset category page
 			$force_page_adding = false;
 			if (array_key_exists('reset', $params) && $params['reset']) {
 				unset($params['reset']);
 
 				if ($processed_params['m_cat_id']) {
 					$processed_params['m_cat_page'] = 1;
 					$force_page_adding = true;
 				}
 			}
 
 			if ((array_key_exists('category_processed', $params) && $params['category_processed'] && ($processed_params['m_cat_page'] > 1)) || $force_page_adding) {
 				// category name was added before AND category page number found
 				$ret = rtrim($ret, '/') . '_' . $processed_params['m_cat_page'] . '/';
 			}
 
 			$template = array_key_exists('t', $params) ? $params['t'] : false;
 			$category_template = ($processed_params['m_cat_id'] > 0) && $params['pass_category'] ? $this->Application->getCategoryCache($processed_params['m_cat_id'], 'category_designs') : '';
 
 			if ((strtolower($template) == '__default__') && ($processed_params['m_cat_id'] == 0)) {
 				// for "Home" category set template to index when not set
 				$template = 'index';
 			}
 
 			// remove template from url if it is category index cached template
 			if ( ($template == $category_template) || (mb_strtolower($template) == '__default__') ) {
 				// given template is also default template for this category OR '__default__' given
 				$params['pass_template'] = false;
 			}
 
 			// remove template from url if it is site homepage on primary language & theme
 			if ( ($template == 'index') && $processed_params['m_lang'] == $rewrite_processor->primaryLanguageId && $processed_params['m_theme'] == $rewrite_processor->primaryThemeId ) {
 				// given template is site homepage on primary language & theme
 				$params['pass_template'] = false;
 			}
 
 			if ($template && $params['pass_template']) {
 				$ret .= $template . '/';
 			}
 
 			return mb_strtolower( rtrim($ret, '/') );
 		}
 
 		/**
 		 * Checks if whole url_parts matches a whole In-CMS page
 		 *
 		 * @param Array $url_parts
 		 * @param Array $vars
 		 * @return bool
 		 */
 		protected function _parseFriendlyUrl($url_parts, &$vars)
 		{
 			if (!$url_parts) {
 				return false;
 			}
 
 			$sql = 'SELECT CategoryId, NamedParentPath
 					FROM ' . TABLE_PREFIX . 'Categories
 					WHERE FriendlyURL = ' . $this->Conn->qstr(implode('/', $url_parts));
 			$friendly = $this->Conn->GetRow($sql);
 
 			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			if ($friendly) {
 				$vars['m_cat_id'] = $friendly['CategoryId'];
 				$vars['t'] = preg_replace('/^Content\//i', '', $friendly['NamedParentPath']);
 
 				while ($url_parts) {
 					$rewrite_processor->partParsed( array_shift($url_parts) );
 				}
 
 				return true;
 			}
 
 			return false;
 		}
 
 		/**
 		 * Extracts category part from url
 		 *
 		 * @param Array $url_parts
 		 * @param Array $vars
 		 * @return bool
 		 */
 		protected function _parseCategory($url_parts, &$vars)
 		{
 			if (!$url_parts) {
 				return false;
 			}
 
 			$res = false;
 			$url_part = array_shift($url_parts);
 
 			$category_id = 0;
 			$last_category_info = false;
 			$category_path = $url_part == 'content' ? '' : 'content';
 
 			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			do {
 				$category_path = trim($category_path . '/' . $url_part, '/');
 				// bb_<topic_id> -> forums/bb_2
 				if ( !preg_match('/^bb_[\d]+$/', $url_part) && preg_match('/(.*)_([\d]+)$/', $category_path, $rets) ) {
 					$category_path = $rets[1];
 					$vars['m_cat_page'] = $rets[2];
 				}
 
 				$sql = 'SELECT CategoryId, SymLinkCategoryId, NamedParentPath
 						FROM ' . TABLE_PREFIX . 'Categories
 						WHERE (LOWER(NamedParentPath) = ' . $this->Conn->qstr($category_path) . ') AND (ThemeId = ' . $vars['m_theme'] . ' OR ThemeId = 0)';
 				$category_info = $this->Conn->GetRow($sql);
 
 				if ($category_info !== false) {
 					$last_category_info = $category_info;
 					$rewrite_processor->partParsed($url_part);
 
 					$url_part = array_shift($url_parts);
 					$res = true;
 				}
 			} while ($category_info !== false && $url_part);
 
 			if ($last_category_info) {
 				// this category is symlink to other category, so use it's url instead
 				// (used in case if url prior to symlink adding was indexed by spider or was bookmarked)
 				if ($last_category_info['SymLinkCategoryId']) {
 					$sql = 'SELECT CategoryId, NamedParentPath
 							FROM ' . TABLE_PREFIX . 'Categories
 							WHERE (CategoryId = ' . $last_category_info['SymLinkCategoryId'] . ')';
 					$category_info = $this->Conn->GetRow($sql);
 
 					if ($category_info) {
 						// web symlinked category was found use it
 						// TODO: maybe 302 redirect should be made to symlinked category url (all other url parts should stay)
 						$last_category_info = $category_info;
 					}
 				}
 
 				// 1. Set virtual page as template, this will be replaced to physical template later in kApplication::Run.
 				// 2. Don't set CachedTemplate field as template here, because we will loose original page associated with it's cms blocks!
 				$vars['t'] = mb_strtolower( preg_replace('/^Content\//i', '', $last_category_info['NamedParentPath']));
 
 				$vars['m_cat_id'] = $last_category_info['CategoryId'];
 				$vars['is_virtual'] = true; // for template from POST, strange code there!
 			}
 			/*else {
 				$vars['m_cat_id'] = 0;
 			}*/
 
 			return $res;
 		}
 
 		/**
 		 * Set's new unique resource id to user
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemValidate(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$resource_id = $object->GetDBField('ResourceId');
 
 			if ( !$resource_id ) {
 				$object->SetDBField('ResourceId', $this->Application->NextResourceId());
 			}
 		}
 
 		/**
 		 * Occurs before an item has been cloned
 		 * Id of newly created item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeClone(kEvent $event)
 		{
 			parent::OnBeforeClone($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('ResourceId', 0); // this will reset it
 
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/categories/categories_item.php
===================================================================
--- branches/5.3.x/core/units/categories/categories_item.php	(revision 15697)
+++ branches/5.3.x/core/units/categories/categories_item.php	(revision 15698)
@@ -1,289 +1,289 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CategoriesItem extends kDBItem
 	{
 		/**
 		 * Builds parent path for this category
 		 *
 		 * @return Array
 		 * @access public
 		 */
 		public function buildParentBasedFields()
 		{
 			static $parent_cache = Array (
 				0 => Array ('ParentPath' => '|', 'NamedParentPath' => '', 'CachedTemplate' => ''),
 			);
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$languages = $ml_helper->getLanguages();
 			$parent_id = $this->GetDBField('ParentId');
 
 			if ( !isset($parent_cache[$parent_id]) ) {
 				$select_fields = Array ('ParentPath', 'NamedParentPath', 'Template', 'CachedTemplate');
 
 				foreach ($languages as $language_id) {
 					$select_fields[] = 'l' . $language_id . '_Name';
 					$select_fields[] = 'l' . $language_id . '_CachedNavbar';
 				}
 
 				$sql = 'SELECT ' . implode(', ', $select_fields) . '
-						FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . '
+						FROM ' . $this->getUnitConfig()->getTableName() . '
 						WHERE CategoryId = ' . $parent_id;
 				$parent_cache[$parent_id] = $this->Conn->GetRow($sql);
 			}
 
 			$named_parent_path = trim($parent_cache[$parent_id]['NamedParentPath'] . '/' . $this->GetDBField('Filename'), '/');
 			$cached_template = $this->GetDBField('Template') == CATEGORY_TEMPLATE_INHERIT ? $parent_cache[$parent_id]['CachedTemplate'] : $this->GetDBField('Template');
 
 			// maybe also build CachedNavbar field here
 			$ret = Array (
 				'ParentPath' => $parent_cache[$parent_id]['ParentPath'] . $this->GetID() . '|',
 				'NamedParentPath' => $named_parent_path,
 				'NamedParentPathHash' => kUtil::crc32(mb_strtolower(preg_replace('/^Content\//i', '', $named_parent_path))),
 				'CachedTemplate' => $cached_template,
 				'CachedTemplateHash' => kUtil::crc32(mb_strtolower($cached_template)),
 			);
 
 			$primary_language = $this->Application->GetDefaultLanguageId();
 
 			foreach ($languages as $language_id) {
 				$language_prefix = 'l' . $language_id . '_';
 				$cached_navbar = $parent_cache[$parent_id][$language_prefix . 'CachedNavbar'] . '&|&';
 				$cached_navbar .= $this->GetDBField($language_prefix . 'Name') ? $this->GetDBField($language_prefix . 'Name') : $this->GetDBField('l' . $primary_language . '_Name');
 
 				$ret['l' . $language_id . '_CachedNavbar'] = $cached_navbar;
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result
 		 *
 		 * @param string $string
 		 * @return string
 		 * @access protected
 		 */
 		protected function stripDisallowed($string)
 		{
 			$filenames_helper = $this->Application->recallObject('FilenamesHelper');
 			/* @var $filenames_helper kFilenamesHelper */
 
 			$string = $filenames_helper->replaceSequences($string);
 
 			return $this->checkAutoFilename($string);
 		}
 
 		public function checkFilename()
 		{
 			if ( $this->GetDBField('AutomaticFilename') ) {
 				// filename will be generated from scratch, don't check anything here
 				return;
 			}
 			elseif ( $this->GetDBField('Type') == PAGE_TYPE_TEMPLATE ) {
 				// system page with AutomaticFilename checkbox unchecked -> compatibility with Proj-CMS <= 4.3.9 (when "/" were allowed in Filename)
 				return;
 			}
 
 			$filename = $this->GetDBField('Filename');
 			$this->SetDBField('Filename', $this->stripDisallowed($filename));
 		}
 
 		protected function checkAutoFilename($filename)
 		{
 			static $current_theme = null;
 
 			if (!$filename) {
 				return $filename;
 			}
 
 			if (!isset($current_theme)) {
 				$themes_helper = $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				$current_theme = (int)$themes_helper->getCurrentThemeId();
 			}
 
 			$escape_char = $this->Application->ConfigValue('FilenameSpecialCharReplacement');
 
 			$item_id = !$this->GetID() ? 0 : $this->GetID();
 			$item_theme = $this->GetDBField('ThemeId');
 
 			if (!$item_theme) {
 				// user creates category manually, that's why ThemeId = 0 -> use current admin theme instead
 				$item_theme = $current_theme;
 			}
 
 			$unique_clause = '(Filename = %s) AND (ThemeId = ' . $item_theme . ' OR ThemeId = 0) AND (ParentId = ' . $this->GetDBField('ParentId') . ')';
 
 			$sql_mask = '	SELECT ' . $this->IDField . '
 							FROM %s
 							WHERE ' . sprintf($unique_clause, $this->Conn->qstr($filename));
 
 			// check temp table
 			$sql_temp = sprintf($sql_mask, $this->TableName);
 			$found_temp_ids = $this->Conn->GetCol($sql_temp);
 
 			// check live table
 			$sql_live = sprintf($sql_mask, $this->Application->GetLiveName($this->TableName));
 			$found_live_ids = $this->Conn->GetCol($sql_live);
 
 			$found_item_ids = array_unique( array_merge($found_temp_ids, $found_live_ids) );
 
 			$has_page = preg_match('/(.*)_([\d]+)([a-z]*)$/', $filename, $rets);
 			$duplicates_found = (count($found_item_ids) > 1) || ($found_item_ids && $found_item_ids[0] != $item_id);
 
 			if ($duplicates_found || $has_page) {// other category has same filename as ours OR we have filename, that ends with _number
 				$append = $duplicates_found ? $escape_char . 'a' : '';
 				if ($has_page) {
 					$filename = $rets[1].'_'.$rets[2];
 					$append = $rets[3] ? $rets[3] : $escape_char . 'a';
 				}
 
 				// check live & temp table
 				$sql_temp = '	SELECT ' . $this->IDField . '
 								FROM ' . $this->TableName . '
 								WHERE ' . $unique_clause . ' AND (' . $this->IDField . ' != ' . $item_id . ')';
 
 				$sql_live = '	SELECT ' . $this->IDField . '
 								FROM ' . $this->Application->GetLiveName($this->TableName) . '
 								WHERE ' . $unique_clause . ' AND (' . $this->IDField . ' != ' . $item_id . ')';
 
 				while ( $this->Conn->GetOne( sprintf($sql_temp, $this->Conn->qstr($filename.$append)) ) > 0 ||
 				$this->Conn->GetOne( sprintf($sql_live, $this->Conn->qstr($filename.$append)) ) > 0 )
 				{
 					if (mb_substr($append, -1) == 'z') $append .= 'a';
 					$append = mb_substr($append, 0, mb_strlen($append) - 1) . chr( ord( mb_substr($append, -1) ) + 1 );
 				}
 
 				return $filename . $append;
 			}
 
 			return $filename;
 		}
 
 		/**
 		 * Generate item's filename based on it's title field value
 		 *
 		 * @return void
 		 * @access public
 		 */
 		public function generateFilename()
 		{
 			if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) {
 				return ;
 			}
 
 			$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 			/* @var $ml_formatter kMultiLanguage */
 
 			$name = $this->stripDisallowed( $this->GetDBField($ml_formatter->LangFieldName('Name', true)) );
 
 			if ( $name != $this->GetDBField('Filename') ) {
 				$this->SetDBField('Filename', $name);
 			}
 		}
 
 		/**
 		 * Allows to detect if root category being edited
 		 *
 		 * @return int
 		 * @access public
 		 */
 		public function IsRoot()
 		{
 			return $this->Application->RecallVar('IsRootCategory_'.$this->Application->GetVar('m_wid'));
 		}
 
 
 		/**
 		 * Sets correct name to Home category while editing it
 		 *
 		 * @return bool
 		 * @access public
 		 */
 		public function IsNewItem()
 		{
 			if ( $this->IsRoot() && $this->Prefix == 'c' ) {
-				$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
+				$title_field = $this->getUnitConfig()->getTitleField();
 				$category_name = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 				$this->SetDBField($title_field, $category_name);
 
 				return false;
 			}
 
 			return parent::IsNewItem();
 		}
 
 		/**
 		 * Sets new name for item in case if it is being copied in same table
 		 *
 		 * @param array $master Table data from TempHandler
 		 * @param int $foreign_key ForeignKey value to filter name check query by
 		 * @param string $title_field FieldName to alter, by default - TitleField of the prefix
 		 * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name
 		 * @access public
 		 */
 		public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s')
 		{
 			if (!isset($title_field)) {
-				$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
+				$title_field = $this->getUnitConfig()->getTitleField();
 				if (!$title_field || isset($this->CalculatedFields[$title_field]) ) return;
 			}
 
 			$new_name = $this->GetDBField($title_field);
 			$cat_id = $this->Application->GetVar('m_cat_id');
 			$this->SetDBField('ParentId', $cat_id);
 			$original_checked = false;
 			do {
 				if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) {
 					$new_name = 'Copy '.($regs[1]+1).' of '.$regs[2];
 				}
 				elseif ($original_checked) {
 					$new_name = 'Copy of '.$new_name;
 				}
 
 				// if we are cloning in temp table this will look for names in temp table,
 				// since object' TableName contains correct TableName (for temp also!)
 				// if we are cloning live - look in live
 				$query = '	SELECT ' . $title_field . '
 							FROM ' . $this->TableName . '
 							WHERE ParentId = ' . (int)$cat_id . ' AND ' . $title_field . ' = ' . $this->Conn->qstr($new_name);
 
 				$foreign_key_field = getArrayValue($master, 'ForeignKey');
 				$foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[ $master['ParentPrefix'] ] : $foreign_key_field;
 
 				if ($foreign_key_field && isset($foreign_key)) {
 					$query .= ' AND '.$foreign_key_field.' = '.$foreign_key;
 				}
 
 				$res = $this->Conn->GetOne($query);
 
 				/*// if not found in live table, check in temp table if applicable
 				if ($res === false && $object->Special == 'temp') {
 					$query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).'
 										WHERE '.$name_field.' = '.$this->Conn->qstr($new_name);
 					$res = $this->Conn->GetOne($query);
 				}*/
 
 				$original_checked = true;
 			} while ($res !== false);
 			$this->SetDBField($title_field, $new_name);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/themes/themes_eh.php
===================================================================
--- branches/5.3.x/core/units/themes/themes_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/themes/themes_eh.php	(revision 15698)
@@ -1,201 +1,199 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ThemesEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array(
 				'OnChangeTheme' => Array('self' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( $event->Name == 'OnItemBuild' ) {
 				// check permission without using $event->getSection(),
 				// so first cache rebuild won't lead to "ldefault_Name" field being used
 				return true;
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Allows to set selected theme as primary
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetPrimary($event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$ids = $this->StoreSelectedIDs($event);
 			if ($ids) {
 				$id = array_shift($ids);
 				$this->setPrimary($id);
 
 				$this->Application->HandleEvent(new kEvent('adm:OnRebuildThemes'));
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		function setPrimary($id)
 		{
-			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
+			$config = $this->getUnitConfig();
+			$table_name = $config->getTableName();
 
 			$sql = 'UPDATE '.$table_name.'
 					SET PrimaryTheme = 0';
 			$this->Conn->Query($sql);
 
 
 			$sql = 'UPDATE '.$table_name.'
 					SET PrimaryTheme = 1, Enabled = 1
-					WHERE '.$id_field.' = '.$id;
+					WHERE '. $config->getIDField() .' = '.$id;
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Set's primary theme (when checkbox used on editing form)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterCopyToLive(kEvent $event)
 		{
 			parent::OnAfterCopyToLive($event);
 
 			$object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true, 'live_table' => true));
 			/* @var $object kDBItem */
 
 			$object->Load($event->getEventParam('id'));
 
 			if ( $object->GetDBField('PrimaryTheme') ) {
 				$this->setPrimary($event->getEventParam('id'));
 			}
 		}
 
 		/**
 		 * Also rebuilds theme files, when enabled theme is saved
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSave(kEvent $event)
 		{
 			parent::OnSave($event);
 
 			if ( ($event->status != kEvent::erSUCCESS) || !$event->getEventParam('ids') ) {
 				return ;
 			}
 
+			$config = $event->getUnitConfig();
 			$ids = $event->getEventParam('ids');
 
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
-
 			$sql = 'SELECT COUNT(*)
-					FROM ' . $table_name . '
-					WHERE ' . $id_field . ' IN (' . $ids . ') AND (Enabled = 1)';
+					FROM ' . $config->getTableName() . '
+					WHERE ' . $config->getIDField() . ' IN (' . $ids . ') AND (Enabled = 1)';
 			$enabled_themes = $this->Conn->GetOne($sql);
 
 			if ( $enabled_themes ) {
 				$this->Application->HandleEvent(new kEvent('adm:OnRebuildThemes'));
 			}
 		}
 
 		/**
 		 * Allows to change the theme
 		 *
 		 * @param kEvent $event
 		 */
 		function OnChangeTheme($event)
 		{
 			if ($this->Application->isAdminUser) {
 				// for structure theme dropdown
 				$this->Application->StoreVar('theme_id', $this->Application->GetVar('theme'));
 				$this->Application->StoreVar('RefreshStructureTree', 1);
 				return ;
 			}
 
 			$this->Application->SetVar('t', 'index');
 			$this->Application->SetVar('m_cat_id', 0);
 
 			$this->Application->SetVar('m_theme', $this->Application->GetVar('theme'));
 		}
 
 		/**
 		 * Apply system filter to themes list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			if ( in_array($event->Special, Array ('enabled', 'selected', 'available')) || !$this->Application->isAdminUser ) {
 				// "enabled" special or Front-End
 				$object->addFilter('enabled_filter', '%1$s.Enabled = ' . STATUS_ACTIVE);
 			}
 
 			// site domain theme picker
 			if ( $event->Special == 'selected' || $event->Special == 'available' ) {
 				$edit_picker_helper = $this->Application->recallObject('EditPickerHelper');
 				/* @var $edit_picker_helper EditPickerHelper */
 
 				$edit_picker_helper->applyFilter($event, 'Themes');
 			}
 
 			// apply domain-based theme filtering
 			$themes = $this->Application->siteDomainField('Themes');
 
 			if ( strlen($themes) ) {
 				$themes = explode('|', substr($themes, 1, -1));
 				$object->addFilter('domain_filter', '%1$s.ThemeId IN (' . implode(',', $themes) . ')');
 			}
 		}
 	}
Index: branches/5.3.x/core/units/sections/site_config_eh.php
===================================================================
--- branches/5.3.x/core/units/sections/site_config_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/sections/site_config_eh.php	(revision 15698)
@@ -1,75 +1,77 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class SiteConfigEventHandler extends kEventHandler {
 
 		/**
 		 * SiteConfigHelper instance
 		 *
 		 * @var SiteConfigHelper
 		 */
 		var $_helper = null;
 
 		/**
 		 * [HOOK] Universal hook to modify all configs
 		 *
 		 * @param kEvent $event
 		 */
 		function OnApplySiteConfigChanges($event)
 		{
 			if ( $event->MasterEvent->getEventParam('skip_site_config') ) {
 				return ;
 			}
 
 			if (!isset($this->_helper)) {
 				$this->_helper = $this->Application->recallObject('SiteConfigHelper');
 			}
 
 			$prefix_file = basename( $this->Application->UnitConfigReader->getPrefixFile($event->MasterEvent->Prefix) );
 
 			$cut_pos = strrpos($prefix_file, '_config.php');
 			$prefix_file = substr($prefix_file, 0, $cut_pos) . '_' . $event->MasterEvent->Prefix . '.php';
 
+			$master_config = $event->MasterEvent->getUnitConfig();
+
 			if (file_exists(SYSTEM_PRESET_PATH . DIRECTORY_SEPARATOR . $prefix_file)) {
-				if ( $this->Application->getUnitOption($event->MasterEvent->Prefix, 'SiteConfigProcessed') ) {
+				if ( $master_config->getSiteConfigProcessed() ) {
 					// don't apply same site config twice during installation
 					return ;
 				}
 
 				require SYSTEM_PRESET_PATH . DIRECTORY_SEPARATOR . $prefix_file;
 			}
 			else {
 				return ;
 			}
 
 			$change_names = Array (
 				'remove_sections', 'debug_only_sections', 'remove_buttons',
 				'hidden_fields', 'virtual_hidden_fields', 'debug_only_fields', 'debug_only_virtual_fields',
 				'required_fields', 'virtual_required_fields', 'hide_edit_tabs', 'hide_columns'
 			);
 
 			$changes = Array ();
 			foreach ($change_names as $change_name) {
 				if (isset($$change_name)) {
 					$changes[$change_name] = $$change_name;
 				}
 			}
 
 			// apply changes
 			$this->_helper->processConfigChanges($event->MasterEvent->Prefix, $changes);
-			$this->Application->setUnitOption($event->MasterEvent->Prefix, 'SiteConfigProcessed', true);
+			$master_config->setSiteConfigProcessed(true);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/skins/skin_eh.php
===================================================================
--- branches/5.3.x/core/units/skins/skin_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/skins/skin_eh.php	(revision 15698)
@@ -1,160 +1,160 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class SkinEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnItemBuild' => Array ('self' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * With "primary" special loads primary skin
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access public
 		 */
 		public function getPassedID(kEvent $event)
 		{
 			if ( $event->Special == 'primary' ) {
 				return Array ('IsPrimary' => 1);
 			}
 
 			return parent::getPassedID($event);
 		}
 
 		/**
 		 * Allows to set selected theme as primary
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetPrimary($event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$ids = $this->StoreSelectedIDs($event);
 			if ($ids) {
 				$id = array_shift($ids);
 				$this->setPrimary($id);
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		function setPrimary($id)
 		{
-			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
+			$config = $this->getUnitConfig();
+			$table_name = $config->getTableName();
 
 			$sql = 'UPDATE '.$table_name.'
 					SET IsPrimary = 0';
 			$this->Conn->Query($sql);
 
 
 			$sql = 'UPDATE '.$table_name.'
 					SET IsPrimary = 1
-					WHERE '.$id_field.' = '.$id;
+					WHERE '. $config->getIDField() .' = '.$id;
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Don't make cloned skin primary
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeClone(kEvent $event)
 		{
 			parent::OnBeforeClone($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('IsPrimary', 0);
 		}
 
 		/**
 		 * Re-compile skin, after it's changed (live table only)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemUpdate(kEvent $event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->IsTempTable() ) {
 				$skin_helper = $this->Application->recallObject('SkinHelper');
 				/* @var $skin_helper SkinHelper */
 
 				$skin_helper->compile($object);
 			}
 		}
 
 		/**
 		 * [HOOK] Compile stylesheet file based on theme definitions
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCompileStylesheet($event)
 		{
 			$object = $event->getObject( Array ('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$object->SwitchToLive();
 
 			$ids = $event->MasterEvent->getEventParam('ids');
 			if ( !is_array($ids) ) {
 				$ids = explode(',', $ids);
 			}
 
 			if ( !$ids ) {
 				return ;
 			}
 
 			$skin_helper = $this->Application->recallObject('SkinHelper');
 			/* @var $skin_helper SkinHelper */
 
 			foreach ($ids as $id) {
 				$object->Load($id);
 				$skin_helper->compile($object);
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/promo_blocks/promo_block_eh.php
===================================================================
--- branches/5.3.x/core/units/promo_blocks/promo_block_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/promo_blocks/promo_block_eh.php	(revision 15698)
@@ -1,351 +1,352 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class PromoBlockEventHandler extends kDBEventHandler {
 
 	/**
 	 * Define alternative event processing method names
 	 *
 	 * @return void
 	 * @see kEventHandler::$eventMethods
 	 * @access protected
 	 */
 	protected function mapEvents()
 	{
 		parent::mapEvents();
 
 		$events_map = Array (
 			'OnMassMoveUp' => 'OnChangePriority',
 			'OnMassMoveDown' => 'OnChangePriority',
 		);
 
 		$this->eventMethods = array_merge($this->eventMethods, $events_map);
 	}
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnItemBuild' => Array ('self' => true),
 			'OnSetSticky' => Array ('self' => 'view'),
 			'OnRegisterView' => Array ('self' => true),
 			'OnFollowLink' => Array ('self' => true),
 			'OnResetCounters' => Array ('self' => 'add|edit'),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Sets default value for promo block group
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnPreCreate(kEvent $event)
 	{
 		parent::OnPreCreate($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$promo_block_group = $this->Application->recallObject('promo-block-group');
 		/* @var $promo_block_group kDBItem */
 
 		$object->SetDBField('PromoBlockGroupId', $promo_block_group->GetID());
 	}
 
 	/**
 	 * Processes OnMassMoveUp, OnMassMoveDown events
 	 *
 	 * @param kEvent $event
 	 */
 	function OnChangePriority($event)
 	{
 		$this->Application->SetVar('priority_prefix', $event->getPrefixSpecial());
 		$event->CallSubEvent('priority:' . $event->Name);
 	}
 
 	/**
 	 * Apply any custom changes to list's sql query
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 * @see kDBEventHandler::OnListBuild()
 	 */
 	protected function SetCustomQuery(kEvent $event)
 	{
 		parent::SetCustomQuery($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBList */
 
 		if ( $this->Application->isAdmin ) {
 			$promo_block_group = $this->Application->recallObject('promo-block-group');
 			/* @var $promo_block_group kDBItem */
 
 			$object->addFilter('promo_group_filter', '%1$s.PromoBlockGroupId = ' . $promo_block_group->GetID());
 			return;
 		}
 
 		$group_id = $event->getEventParam('group_id');
 
 		if ( !$group_id ) {
 			$page = $this->Application->recallObject('st');
 			/* @var $page CategoriesItem */
 
 			$group_id = $page->GetDBField('PromoBlockGroupId');
 		}
 
 		$object->addFilter('status_filter', '%1$s.Status = ' . STATUS_ACTIVE);
 		$object->addFilter('scheduled_from_filter', '%1$s.ScheduleFromDate IS NULL OR %1$s.ScheduleFromDate <= ' . TIMENOW);
 		$object->addFilter('scheduled_to_filter', '%1$s.ScheduleToDate IS NULL OR %1$s.ScheduleToDate >= ' . TIMENOW);
 		$object->addFilter('promo_group_filter', $group_id ? '%1$s.PromoBlockGroupId = ' . $group_id : 'FALSE');
 	}
 
 	/**
 	 * Set's block as sticky
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSetSticky(kEvent $event)
 	{
 		$object = $event->getObject( Array('skip_autoload' => true) );
 		/* @var $object kDBItem */
 
 		$ids = $this->StoreSelectedIDs($event);
 
 		if ( $ids ) {
 			$id = array_shift($ids);
 
 			$sql = 'UPDATE ' . $object->TableName . '
 					SET Sticky = 0';
 			$this->Conn->Query($sql);
 
 			$sql = 'UPDATE ' . $object->TableName . '
 					SET Sticky = 1, Status = ' . STATUS_ACTIVE . '
 					WHERE BlockId = ' . $id;
 			$this->Conn->Query($sql);
 		}
 
 		$this->clearSelectedIDs($event);
 	}
 
 
 	/**
 	 * Set Required fields
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemCreate(kEvent $event)
 	{
 		parent::OnBeforeItemCreate($event);
 
 		$this->_itemChanged($event);
 	}
 
 	/**
 	 * Set Required fields
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemUpdate(kEvent $event)
 	{
 		parent::OnBeforeItemUpdate($event);
 
 		$this->_itemChanged($event);
 	}
 
 	/**
 	 * Schedule dates
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function _itemChanged(kEvent $event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$date_from = $object->GetDBField('ScheduleFromDate_date');
 		$date_to = $object->GetDBField('ScheduleToDate_date');
 
 		if ( $date_from && $date_to && $date_from >= $date_to ) {
 			$object->SetError('ScheduleFromDate_date', 'wrong_date_interval');
 		}
 
 		$object->setRequired('CategoryId', $object->GetDBField('LinkType') == PromoBlockType::INTERNAL);
 		$object->setRequired('ExternalLink', $object->GetDBField('LinkType') == PromoBlockType::EXTERNAL);
 	}
 
 	/**
 	 * Registers view of the promo block
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRegisterView(kEvent $event)
 	{
 		$this->_incrementField($event, 'NumberOfViews');
 	}
 
 	/**
 	 * Registers click on the promo block
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnFollowLink(kEvent $event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$this->_incrementField($event, 'NumberOfClicks', false);
 
 		if ( $object->GetDBField('LinkType') == 1 ) { // Internal
 			$sql = 'SELECT NamedParentPath
 					FROM ' . TABLE_PREFIX . 'Categories
 					WHERE CategoryId = ' . $object->GetDBField('CategoryId');
 			$event->redirect = $this->Conn->GetOne($sql);
 
 			$event->SetRedirectParam('pass', 'm');
 		}
 		else {
 			$ext_url = $object->GetDBField('ExternalLink');
 			$event->redirect = 'external:' . (preg_match('/^(http|ftp):\\/\\/.*/', $ext_url) ? $ext_url : $this->Application->BaseURL() . $ext_url);
 		}
 	}
 
 	/**
 	 * Increment given promo block counters
 	 *
 	 * @param kEvent $event
 	 * @param string $field
 	 * @param bool $is_ajax
 	 * @return void
 	 * @access protected
 	 */
 	protected function _incrementField(kEvent $event, $field, $is_ajax = true)
 	{
 		if ( $is_ajax ) {
 			$event->status = kEvent::erSTOP;
 
 			if ( $this->Application->GetVar('ajax') != 'yes' ) {
 				return ;
 			}
 		}
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ( !$object->isLoaded() ) {
 			echo 'FAILED';
 			return ;
 		}
 
 		// don't use kDBItem::Update to support concurrent view updates from different visitors
 		$sql = 'UPDATE ' . $object->TableName . '
 				SET ' . $field . ' = ' . $field . ' + 1
 				WHERE ' . $object->IDField . ' = ' . $object->GetID();
 		$this->Conn->Query($sql);
 
 		echo 'OK';
 	}
 
 	/**
 	 * Resets promo block counters
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetCounters(kEvent $event)
 	{
 		$object = $event->getObject( Array ('skip_autoload' => true) );
 		/* @var $object kDBItem */
 
 		$ids = $this->StoreSelectedIDs($event);
 
 		foreach ($ids as $id) {
 			$object->Load($id);
 			$object->SetDBField('NumberOfViews', 0);
 			$object->SetDBField('NumberOfClicks', 0);
 			$object->Update();
 		}
 
 		$this->clearSelectedIDs($event);
 	}
 
 	/**
 	 * Occurs, when config was parsed, allows to change config data dynamically
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterConfigRead(kEvent $event)
 	{
 		parent::OnAfterConfigRead($event);
 
 		$category_helper = $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
-		$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
+		$config = $event->getUnitConfig();
+		$fields = $config->getFields();
 
 		$fields['CategoryId']['options'] = $category_helper->getStructureTreeAsOptions();
 
 		// images multilang fields
 		$a_image = Array(
 			'type' => 'string', 'max_len' => 255,
 			'formatter' => 'kUploadFormatter', 'upload_dir' => IMAGES_PATH . 'promo_blocks/',
 			'multiple' => 1, 'thumb_format' => 'resize:100x100',
 			'file_types' => '*.jpg;*.jpeg;*.gif;*.png;*.bmp', 'files_description' => '!la_hint_ImageFiles!',
 			'not_null' => 1, 'default' => '',
 		);
 
 		// get active languages
 		$sql = 'SELECT LanguageId
 				FROM ' . TABLE_PREFIX . 'Languages';
 		$languages = $this->Conn->GetCol($sql);
 
 		foreach ($languages as $lang_id) {
 			$fields['l' . $lang_id . '_Image'] = $a_image;
 		}
 
 		$fields['l' . $this->Application->GetDefaultLanguageId() . '_Image']['required'] = 1;
 
-		$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
+		$config->setFields($fields);
 	}
 }
Index: branches/5.3.x/core/units/statistics/statistics_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/statistics/statistics_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/statistics/statistics_tag_processor.php	(revision 15698)
@@ -1,335 +1,350 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class StatisticsTagProcessor extends kDBTagProcessor {
 
 		var $TagCache = Array(); // parsed tag (in sql queries only) values are cached
 
 		var $CurrentSQL = ''; // sql query being currently processed
 		var $PostFormatting = false; // apply formatting to sql query results
 		var $PostFormattingParams = Array(); // post formatting params if any
 
 		function CalculateValue($params)
 	    {
 	    	$object = $this->getObject($params);
 	        $this->CurrentSQL = $object->GetDBField($params['field']);
 
 	        // 1. replace prefix to actual one
 			$this->CurrentSQL = str_replace("<%prefix%>", TABLE_PREFIX, $this->CurrentSQL);
 
 			// 2. replace all pseudo-tags found in sql with their values
 			while ( ($tag = $this->FindTag()) != false ) {
 				$this->CurrentSQL = str_replace('<%'.$tag.'%>', $this->ProcessStatisticTag($tag), $this->CurrentSQL);
 			}
 
 			// 3. query sql and process gathered data
 	        $values = $this->Conn->GetCol($this->CurrentSQL);
 	        if (!$values) return '';
 	        if (!$this->PostFormatting) return array_shift($values);
 
 
     		switch ($this->PostFormatting) {
     			case 'number':
     				// simple-specific postformatting
     				$lang = $this->Application->recallObject('lang.current');
 					/* @var $lang LanguagesItem */
 
     				$value = $lang->formatNumber($value, $this->PostFormattingParams['precision']);
     				break;
 
     			case 'COUNT':
     				// extended postformatting
     				$value = count($values);
     				break;
 
     			case 'SUM':
     				$value = 0;
     				foreach ($values as $cur_value) {
     					$value += $cur_value;
     				}
 
     				if ($this->PostFormattingParams['format_as'] == 'file') {
     					$value = size($value);
     				}
     				break;
 
     			// other type of information (not from db)
     			case 'SysFileSize':
     				$value = size( dir_size(FULL_PATH.'/') );
     				break;
 
     			default: // simple-default postformatting
     				$value = adodb_date($this->PostFormatting, array_shift($values));
     				break;
     		}
     		$this->PostFormatting = false;
     		$this->PostFormattingParams = Array();
 
 
 	        return $value;
 
 	    }
 
 	    function FindTag()
 		{
 			// finds tag in current sql & returns it if found, false otherwise
 			$tagOpen = '<%'; $tagClose = '%>';	$tagOpenLen = strlen($tagOpen);
 			$startPos = strpos($this->CurrentSQL, $tagOpen);
 			if( $startPos !== false )
 			{
 				$endPos = strpos($this->CurrentSQL, $tagClose, $startPos);
 				return ($endPos > $startPos) ? substr($this->CurrentSQL, $startPos + $tagOpenLen, $endPos - $startPos - $tagOpenLen) : false;
 			}
 			return false;
 		}
 
 		function ProcessStatisticTag($tag)
 		{
 			$tag = trim($tag);
 			if (isset($this->TagCache[$tag])) {
 				return $this->TagCache[$tag];
 			}
 
 			$object = $this->getObject();
 			/* @var $object kDBItem */
 
 			list($tag_name, $tag_params) = explode(' ', $tag, 2); // 1st - function, 2nd .. nth - params
 			preg_match_all('/([\${}a-zA-Z0-9_.-]+)=(["\']{1,1})(.*?)(?<!\\\)\\2/s', $tag_params, $rets, PREG_SET_ORDER);
 
 			$tag_params = Array();
 			foreach ($rets AS $key => $val){
 				$tag_params[$val[1]] = str_replace(Array('\\' . $val[2], '+'), Array($val[2], ' '), $val[3]);
 			}
 
 			switch ($tag_name) {
 				case 'm:config':
 					// m:config name="<variable_name>"
 					return $this->Application->ConfigValue($tag_params['name']);
 					break;
 
 				case 'm:post_format':
 					// m:post_format field="<field_name>" type="<formatting_type>" precision="2"
 					$lang = $this->Application->recallObject('lang.current');
 					/* @var $lang LanguagesItem */
 
 					switch ($tag_params['type']) {
 						case 'date':
 							$this->PostFormatting = $lang->GetDBField('DateFormat');
 							break;
 
 						case 'time':
 							$this->PostFormatting = $lang->GetDBField('TimeFormat');
 							break;
 
 						case 'currency':
 							$this->PostFormatting = 'number';
 							$this->PostFormattingParams['precision'] = $tag_params['precision'];
 							break;
 					}
 					return $tag_params['field'];
 					break;
 
 				case 'm:custom_action':
 					// m:custom_action sql="empty" action="SysFileSize"
 					$this->PostFormatting = $tag_params['action'];
 					return ($tag_params['sql'] == 'empty') ? 'SELECT 1' : $tag_params['sql'];
 					break;
 
 				case 'modules:get_current':
 					return $object->GetDBField('Module');
 					break;
 
 				case 'm:sql_action':
 					//m:sql_action sql="SHOW TABLES" action="COUNT" field="*"
 					$this->PostFormatting = $tag_params['action'];
 					$this->PostFormattingParams = $tag_params;
 					return $tag_params['sql'];
 					break;
 
 				case 'link:hit_count':
 					if ($tag_params['type'] == 'top') {// by now only top is supported
 						$top_links_count = $this->Application->ConfigValue('Link_TopCount');
 
 						$sql = 'SELECT Hits
 								FROM '.TABLE_PREFIX.'Link
 								ORDER BY Hits DESC LIMIT 0, '.$top_links_count;
 						return $this->getLastRecord($sql, 'Hits');
 					}
 					break;
 
 				case 'article:hit_count':
 					if ($tag_params['type'] == 'top') {// by now only top is supported
 						$top_articles_count = $this->Application->ConfigValue('News_VotesToHot');
 						$min_votes = $this->Application->ConfigValue('News_MinVotes');
 
 						$sql = 'SELECT CachedRating
 								FROM '.TABLE_PREFIX.'News
 								WHERE CachedVotesQty > '.$min_votes.'
 								ORDER BY CachedRating DESC LIMIT 0, '.$top_articles_count;
 						return $this->getLastRecord($sql, 'CachedRating');
 					}
 					break;
 
 				case 'topic:hit_count':
 					if ($tag_params['type'] == 'top') {// by now only top is supported
 						$top_posts_count = $this->Application->ConfigValue('Topic_PostsToPop');
 
 						$sql = 'SELECT Views
 								FROM '.TABLE_PREFIX.'Topic
 								ORDER BY Views DESC LIMIT 0, '.$top_posts_count;
 						return $this->getLastRecord($sql, 'Views');
 					}
 					break;
 
 
 			}
 
 			return '';
 		}
 
 		function getLastRecord($sql, $field)
 		{
 			$records = $this->Conn->GetCol($sql);
 			return count($records) ? array_pop($records) : 0;
 		}
 
 		/**
 		 * Allows to get pending item count for prefix
 		 *
 		 * @param Array $params
 		 * @return int
 		 */
 		function CountPending($params)
 		{
 			$prefix = $params['prefix'];
 			$cache_key = 'statistics.pending[%' . $this->Application->incrementCacheSerial($prefix, null, false) . '%]';
 			$value = $this->Application->getCache($cache_key);
 
 			if ($value === false) {
-				$statistics_info = $this->Application->getUnitOption($prefix.'.pending', 'StatisticsInfo');
+				$config = $this->Application->getUnitConfig($prefix);
+				$statistics_info = $this->_getPendingStatisticsInfo($prefix);
+
 				if (!$statistics_info) {
 					return 0;
 				}
 
-				$table = $this->Application->getUnitOption($prefix, 'TableName');
-				$status_field = array_shift( $this->Application->getUnitOption($prefix, 'StatusField') );
+				$status_field = $config->getStatusField(true);
 				$this->Conn->nextQueryCachable = true;
 				$sql = 'SELECT COUNT(*)
-						FROM '.$table.'
+						FROM '. $config->getTableName() .'
 						WHERE '.$status_field.' = '.$statistics_info['status'];
 				$value = $this->Conn->GetOne($sql);
 				$this->Application->setCache($cache_key, $value);
 			}
 
 			return $value;
 		}
 
+		/**
+		 * Returns pending statistics info
+		 *
+		 * @param string $prefix
+		 * @return string
+		 * @access protected
+		 */
+		protected function _getPendingStatisticsInfo($prefix)
+		{
+			$config = $this->Application->getUnitConfig($prefix);
+
+			return getArrayValue($config->getStatisticsInfo(), 'pending');
+		}
+
 		function GetTotalPending()
 		{
 			$prefixes = $this->getPendingPrefixes();
 
 			$sum = 0;
 			foreach ($prefixes as $prefix) {
 				$sum += $this->CountPending( Array('prefix' => $prefix) );
 			}
 			return $sum;
 		}
 
 		/**
 		 * Get prefixes, that are allowed to have pending items (check module licenses too)
 		 *
 		 * @return Array
 		 */
 		function getPendingPrefixes()
 		{
 			$modules_helper = $this->Application->recallObject('ModulesHelper');
 			/* @var $modules_helper kModulesHelper */
 
 			$licensed_modules = array_map('strtolower', $modules_helper->_GetModules());
 
 			$sql = 'SELECT LOWER(Module), Prefix
 					FROM '.TABLE_PREFIX.'ItemTypes it
 					LEFT JOIN '.TABLE_PREFIX.'Modules m ON m.Name = it.Module
 					WHERE (m.Loaded = 1) AND (LENGTH(it.ClassName) > 0)';
 			$prefixes = $this->Conn->GetCol($sql, 'Prefix');
 
 			$module_names = array_map('strtolower', array_unique(array_values($prefixes)));
 			$module_names = array_intersect($module_names, $licensed_modules);
 
 			$ret = Array ();
 			foreach ($prefixes as $prefix => $module_name) {
 				if (in_array($module_name, $module_names)) {
 					$ret[] = $prefix;
 				}
 			}
 
 			return $ret;
 
 		}
 
 		function PrintPendingStatistics($params)
 		{
 			$check_prefixes = $this->getPendingPrefixes();
 			if (!$check_prefixes) {
 				return '';
 			}
 
 			$ret = '';
 			$columns = $params['columns'];
 			$block_params = $this->prepareTagParams( Array('name' => $this->SelectParam($params, 'render_as,block') ) );
 
 			$prefixes = Array();
 			foreach ($check_prefixes as $prefix) {
-				$statistics_info = $this->Application->getUnitOption($prefix.'.pending', 'StatisticsInfo');
+				$statistics_info = $this->_getPendingStatisticsInfo($prefix);
 				if ($statistics_info) {
 					$prefixes[] = $prefix;
 				}
 			}
 
 			$row_number = 0;
 
 			foreach ($prefixes as $i => $prefix) {
 				$block_params['prefix'] = $prefix;
-				$statistics_info = $this->Application->getUnitOption($prefix.'.pending', 'StatisticsInfo');
+				$statistics_info = $this->_getPendingStatisticsInfo($prefix);
 
 				if ($i % $columns == 0) {
 					$column_number = 1;
 					$ret .= '<tr>';
 				}
 
 				$block_params['column_number'] = $column_number;
 				$block_params['is_first'] = $i < $columns ? 1 : 0;
 				$template = $statistics_info['url']['t'];
 				unset($statistics_info['url']['t']);
 				$url = $this->Application->HREF($template, '', $statistics_info['url']);
 				if ($statistics_info['js_url'] != '#url#') {
 					$statistics_info['js_url'] = 'javascript:'.$statistics_info['js_url'];
 				}
 
 				$block_params['url'] = str_replace('#url#', $url, $statistics_info['js_url']);
 				$block_params['icon'] = $statistics_info['icon'];
 				$block_params['label'] = $statistics_info['label'];
 				$ret .= $this->Application->ParseBlock($block_params);
 				$column_number++;
 
 				if (($i+1) % $columns == 0) {
 					$ret .= '</tr>';
 				}
 			}
 			return $ret;
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/related_searches/related_searches_event_handler.php
===================================================================
--- branches/5.3.x/core/units/related_searches/related_searches_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/related_searches/related_searches_event_handler.php	(revision 15698)
@@ -1,39 +1,39 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class RelatedSearchEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Prepares new kDBItem object
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnNew(kEvent $event)
 		{
 			parent::OnNew($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$table_info = $object->getLinkedInfo();
 
-			$source_itemtype = $this->Application->getUnitOption($table_info['ParentPrefix'], 'ItemType');
-			$object->SetDBField('ItemType', $source_itemtype);
+			$source_item_type = $this->Application->getUnitConfig($table_info['ParentPrefix'])->getItemType();
+			$object->SetDBField('ItemType', $source_item_type);
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/mailing_lists/mailing_list_eh.php
===================================================================
--- branches/5.3.x/core/units/mailing_lists/mailing_list_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/mailing_lists/mailing_list_eh.php	(revision 15698)
@@ -1,406 +1,407 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class MailingListEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnCancelMailing' => Array ('self' => 'edit'),
 				'OnGenerateEmailQueue' => Array ('self' => true),
 				'OnProcessEmailQueue' => Array ('self' => true),
 				'OnGetHtmlBody' => Array ('self' => 'edit'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Prepare recipient list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnNew(kEvent $event)
 		{
 			parent::OnNew($event);
 
 			$recipient_type = $this->Application->GetVar('mailing_recipient_type');
 			if ( !$recipient_type ) {
 				return;
 			}
 
 			$recipients = $this->Application->GetVar($recipient_type);
 			if ( $recipients ) {
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$to = $recipient_type . '_' . implode(';' . $recipient_type . '_', array_keys($recipients));
 
 				$object->SetDBField('To', $to);
 			}
 
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Prepare recipient list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			parent::OnPreCreate($event);
 
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Don't allow to delete mailings in progress
 		 *
 		 * @param kEvent $event
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(kEvent $event, $type)
 		{
 			if ( $event->Name == 'OnMassDelete' && $type == 'before' ) {
 				$ids = $event->getEventParam('ids');
 
 				if ( $ids ) {
-					$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-					$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+					$config = $event->getUnitConfig();
+					$id_field = $config->getIDField();
 
 					$sql = 'SELECT ' . $id_field . '
-							FROM ' . $table_name . '
+							FROM ' . $config->getTableName() . '
 							WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ') AND Status <> ' . MailingList::PARTIALLY_PROCESSED;
 					$allowed_ids = $this->Conn->GetCol($sql);
 
 					$event->setEventParam('ids', $allowed_ids);
 				}
 			}
 		}
 
 		/**
 		 * Delete all related mails in email queue
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$this->_deleteQueue($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// delete mailing attachments after mailing is deleted
 			$attachments = $object->GetField('Attachments', 'file_paths');
 			if ( $attachments ) {
 				$attachments = explode('|', $attachments);
 
 				foreach ($attachments as $attachment_file) {
 					if ( file_exists($attachment_file) ) {
 						unlink($attachment_file);
 					}
 				}
 			}
 		}
 
 		/**
 		 * Cancels given mailing and deletes all it's email queue
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCancelMailing($event)
 		{
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ($ids) {
 				foreach ($ids as $id) {
 					$object->Load($id);
 					$object->SetDBField('Status', MailingList::CANCELLED);
 					$object->Update();
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Checks, that at least one message text field is filled
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$this->Application->GetVar('mailing_recipient_type') ) {
 				// user manually typed email addresses -> normalize
 				$recipients = str_replace(',', ';', $object->GetDBField('To'));
 				$recipients = array_map('trim', explode(';', $recipients));
 
 				$object->SetDBField('To', implode(';', $recipients));
 			}
 
 			// remember user, who created mailing, because of his name
 			// is needed for "From" field, but mailing occurs from cron
 			$user_id = $this->Application->RecallVar('user_id');
 			$object->SetDBField('PortalUserId', $user_id);
 
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Checks, that at least one message text field is filled
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Dynamically changes required fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function setRequired(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->setRequired('MessageHtml', !$object->GetDBField('MessageText'));
 			$object->setRequired('MessageText', !$object->GetDBField('MessageHtml'));
 		}
 
 		/**
 		 * Deletes mailing list email queue, when it becomes cancelled
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemUpdate(kEvent $event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$status = $object->GetDBField('Status');
 			if ( ($status != $object->GetOriginalField('Status')) && ($status == MailingList::CANCELLED) ) {
 				$this->_deleteQueue($event);
 			}
 		}
 
 		/**
 		 * Deletes email queue records related with given mailing list
 		 *
 		 * @param kEvent $event
 		 */
 		function _deleteQueue($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
-			$sql = 'DELETE FROM ' . $this->Application->getUnitOption('email-queue', 'TableName') . '
+			$sql = 'DELETE FROM ' . $this->Application->getUnitConfig('email-queue')->getTableName() . '
 					WHERE MailingId = ' . $object->GetID();
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Allows to safely get mailing configuration variables
 		 *
 		 * @param string $variable_name
 		 * @return int
 		 */
 		function _ensureDefault($variable_name)
 		{
 			$value = $this->Application->ConfigValue($variable_name);
 			if ( $value === false ) {
 				// ensure default value, when configuration variable is missing
 				return 10;
 			}
 
 			if ( !$value ) {
 				// configuration variable found, but it's value is empty or zero
 				return false;
 			}
 
 			return $value;
 		}
 
 		/**
 		 * Generates email queue for active mailing lists
 		 *
 		 * @param kEvent $event
 		 */
 		function OnGenerateEmailQueue($event)
 		{
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$config = $event->getUnitConfig();
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			$where_clause = Array (
 				'Status NOT IN (' . MailingList::CANCELLED . ',' . MailingList::PROCESSED . ')',
 				'(EmailsQueuedTotal < EmailsTotal) OR (EmailsTotal = 0)',
 				'`To` <> ""',
 			);
 
 			$sql = 'SELECT *
 					FROM ' . $table_name . '
 					WHERE (' . implode(') AND (', $where_clause) . ')
 					ORDER BY ' . $id_field . ' ASC';
 			$mailing_lists = $this->Conn->Query($sql, $id_field);
 
 			if ( !$mailing_lists ) {
 				return;
 			}
 
 			// queue 10 emails per step summary from all mailing lists (FIFO logic)
 			$to_queue = $this->_ensureDefault('MailingListQueuePerStep');
 
 			if ( $to_queue === false ) {
 				return;
 			}
 
 			$mailing_list_helper = $this->Application->recallObject('MailingListHelper');
 			/* @var $mailing_list_helper MailingListHelper */
 
 			foreach ($mailing_lists as $mailing_id => $mailing_data) {
 				if ( $mailing_data['EmailsTotal'] == 0 ) {
 					// no work performed on this mailing list -> calculate totals
 					$updated_fields = $mailing_list_helper->generateRecipients($mailing_id, $mailing_data);
 					$updated_fields['Status'] = MailingList::PARTIALLY_PROCESSED;
 					$mailing_data = array_merge($mailing_data, $updated_fields);
 
 					$this->Conn->doUpdate($updated_fields, $table_name, $id_field . ' = ' . $mailing_id);
 				}
 
 				$emails = unserialize($mailing_data['ToParsed']);
 
 				if ( !$emails ) {
 					continue;
 				}
 
 				// queue allowed count of emails
 				$i = 0;
 				$process_count = count($emails) >= $to_queue ? $to_queue : count($emails);
 
 				while ($i < $process_count) {
 					$mailing_list_helper->queueEmail($emails[$i], $mailing_id, $mailing_data);
 					$i++;
 				}
 
 				// remove processed emails from array
 				$to_queue -= $process_count; // decrement available for processing email count
 				array_splice($emails, 0, $process_count);
 
 				$updated_fields = Array (
 					'ToParsed' => serialize($emails),
 					'EmailsQueuedTotal' => $mailing_data['EmailsQueuedTotal'] + $process_count,
 				);
 
 				$this->Conn->doUpdate($updated_fields, $table_name, $id_field . ' = ' . $mailing_id);
 
 				if ( !$to_queue ) {
 					// emails to be queued per step reached -> leave
 					break;
 				}
 			}
 		}
 
 		/**
 		 * Process email queue from cron
 		 *
 		 * @param kEvent $event
 		 */
 		function OnProcessEmailQueue($event)
 		{
 			$deliver_count = $this->_ensureDefault('MailingListSendPerStep');
 			if ($deliver_count === false) {
 				return ;
 			}
 
-			$queue_table = $this->Application->getUnitOption('email-queue', 'TableName');
+			$queue_table = $this->Application->getUnitConfig('email-queue')->getTableName();
 
 			// get queue part to send
 			$sql = 'SELECT *
 					FROM ' . $queue_table . '
 					WHERE (SendRetries < 5) AND (LastSendRetry < ' . strtotime('-2 hours') . ')
 					LIMIT 0,' . $deliver_count;
 			$messages = $this->Conn->Query($sql);
 
 			if (!$messages) {
 				// no more messages left in queue
 				return ;
 			}
 
 			$mailing_list_helper = $this->Application->recallObject('MailingListHelper');
 			/* @var $mailing_list_helper MailingListHelper */
 
 			$mailing_list_helper->processQueue($messages);
 		}
 
 		/**
 		 * Returns HTML of sent e-mail for iframe
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnGetHtmlBody(kEvent $event)
 		{
 			$event->status = kEvent::erSTOP;
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			echo '<html><body style="font-size: 14px;">' . $object->GetDBField('MessageHtml') . '</body></html>';
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/custom_fields/custom_fields_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/custom_fields/custom_fields_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/custom_fields/custom_fields_tag_processor.php	(revision 15698)
@@ -1,164 +1,164 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CustomFieldsTagProcessor extends kDBTagProcessor {
 
 		/**
 		 * Return LEFT JOINed custom field name from main item config
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function GetMainField($params)
 		{
 			$object = $this->getObject($params);
 			$append = isset($params['append']) && $params['append'] ? $params['append'] : '';
 			return 'cust_'.$object->GetDBField('FieldName').$append;
 		}
 
 		function CustomField($params)
 		{
 			$params['name'] = $this->GetMainField($params);
 			$source_prefix = $this->Application->Parser->GetParam('SourcePrefix');
 			return $this->Application->ProcessParsedTag($source_prefix, 'Field', $params);
 		}
 
 		function CustomFormat($params)
 		{
 			$params['name'] = $this->GetMainField($params);
 			$source_prefix = $this->Application->Parser->GetParam('SourcePrefix');
 			return $this->Application->ProcessParsedTag($source_prefix, 'Format', $params);
 		}
 
 		function CustomInputName($params)
 		{
 			$params['name'] = $this->GetMainField($params);
 			$source_prefix = $this->Application->Parser->GetParam('SourcePrefix');
 			return $this->Application->ProcessParsedTag($source_prefix, 'InputName', $params);
 		}
 
 		function setParamValue(&$params, $param_name)
 		{
 			// $deep_level if GetParam = 1 used in case if PrintList is called during parsing "grid" block (=> +1 to deep_level)
 			if (!isset($params[$param_name])) {
 				$params[$param_name] = $this->Application->Parser->GetParam($param_name, 1);
 			}
 
 			return $params[$param_name];
 		}
 
 		/**
 		 * Prints list content using block specified
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access public
 		 */
 		function PrintList($params)
 		{
 			$this->setParamValue($params, 'SourcePrefix');
 			$this->setParamValue($params, 'value_field');
 
 			$list =& $this->GetList($params);
-			$id_field = $this->Application->getUnitOption($this->Prefix,'IDField');
+			$id_field = $this->getUnitConfig()->getIDField();
 
 			$list->Query();
 			$o = '';
 			$list->GoFirst();
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 			$block_params['pass_params'] = 'true';
 
 			$prev_heading = '';
 			$display_original = false;
 			$source_prefix = getArrayValue($params, 'SourcePrefix');
 			$source_object = $original_object = null;
 
 			if ( $source_prefix ) {
 				$source_object = $this->Application->recallObject($source_prefix, null, Array ('raise_warnings' => 0)); // it's possible, that in some cases object will not be loaded
 				/* @var $source_object kCatDBItem */
 
 				$display_original = $this->Application->ProcessParsedTag($source_prefix, 'DisplayOriginal', Array ('display_original' => $this->setParamValue($params, 'display_original')));
 			}
 
 			if ( $display_original ) {
 				$block_params['display_original'] = $display_original;
 				$block_params['original_title'] = $this->setParamValue($params, 'original_title');
 
 				$original_object = $this->Application->recallObject($source_prefix . '.original', null, Array ('raise_warnings' => 0)); // it's possible, that in some cases object will not be loaded
 				/* @var $original_object kCatDBItem */
 			}
 
 			if ($this->Special == 'general') {
 				$list->groupRecords('Heading');
 			}
 
 			$i = 0;
 			while ( !$list->EOL() ) {
 				$block_params['is_last'] = ($i == $list->GetSelectedCount() - 1);
 				$block_params['not_last'] = !$block_params['is_last']; // for front-end
 
 				$this->Application->SetVar($this->getPrefixSpecial() . '_id', $list->GetDBField($id_field)); // for edit/delete links using GET
 
 				if ( $source_prefix ) {
 					$field_name = 'cust_' . $list->GetDBField('FieldName');
 
 					$formatter = $source_object->GetFieldOption($field_name, 'formatter');
 					$language_prefix = $formatter == 'kMultiLanguage' ? 'l' . $this->Application->GetVar('m_lang') . '_' : '';
 
 					$list->SetDBField($params['value_field'], $source_object->GetDBField($language_prefix . 'cust_' . $list->GetDBField('FieldName')));
 
 					if ( $display_original ) {
 						$list->SetDBField('OriginalValue', $original_object->GetField('cust_' . $list->GetDBField('FieldName')));
 					}
 
 					$block_params['field'] = $block_params['virtual_field'] = 'cust_' . $list->GetDBField('FieldName');
 					$block_params['show_heading'] = ($prev_heading != $list->GetDBField('Heading')) ? 1 : 0;
 
 					$list->SetDBField('DirectOptions', $source_object->GetFieldOption($field_name, 'options'));
 				}
 
 				$o .= $this->Application->ParseBlock($block_params);
 				$prev_heading = $list->GetDBField('Heading');
 				$list->GoNext();
 				$i++;
 			}
 
 			$this->Application->SetVar($this->getPrefixSpecial() . '_id', '');
 
 			return $o;
 		}
 
 		/**
 		 * If data was modfied & is in TempTables mode, then parse block with name passed;
 		 * remove modification mark if not in TempTables mode
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access public
 		 * @author Alexey
 		 */
 		function SaveWarning($params)
 		{
 			$source_prefix = array_key_exists('SourcePrefix', $params) ? $params['SourcePrefix'] : false;
 			if ($source_prefix && $source_prefix == 'c') {
 				return $this->Application->ProcessParsedTag('c', 'SaveWarning', $params);
 			}
 
 			return parent::SaveWarning($params);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/custom_fields/custom_fields_event_handler.php
===================================================================
--- branches/5.3.x/core/units/custom_fields/custom_fields_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/custom_fields/custom_fields_event_handler.php	(revision 15698)
@@ -1,352 +1,352 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CustomFieldsEventHandler extends kDBEventHandler {
 
 		/**
 		 * Changes permission section to one from REQUEST, not from config
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			$sql = 'SELECT Prefix
-					FROM '.TABLE_PREFIX.'ItemTypes
-					WHERE ItemType = '.$this->Conn->qstr( $this->Application->GetVar('cf_type') );
+					FROM ' . TABLE_PREFIX . 'ItemTypes
+					WHERE ItemType = ' . $this->Conn->qstr($this->Application->GetVar('cf_type'));
 			$main_prefix = $this->Conn->GetOne($sql);
 
-			$section = $this->Application->getUnitOption($main_prefix.'.custom', 'PermSection');
+			$section = $this->Application->getUnitConfig($main_prefix)->getPermSectionByName('custom');
 			$event->setEventParam('PermSection', $section);
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$item_type = $this->Application->GetVar('cf_type');
+
 			if ( !$item_type ) {
 				$prefix = $event->getEventParam('SourcePrefix');
-				$item_type = $this->Application->getUnitOption($prefix, 'ItemType');
+				$item_type = $this->Application->getUnitConfig($prefix)->getItemType();
 			}
 
 			if ( $event->Special == 'general' ) {
 				$object->addFilter('generaltab_filter', '%1$s.OnGeneralTab = 1');
 			}
 
 			if ( $item_type ) {
 				$hidden_fields = $this->Conn->qstrArray($this->_getHiddenFields($event));
 
 				if ( $hidden_fields ) {
 					$object->addFilter('hidden_filter', '%1$s.FieldName NOT IN (' . implode(',', $hidden_fields) . ')');
 				}
 
 				$object->addFilter('itemtype_filter', '%1$s.Type = ' . $item_type);
 			}
 
 			if ( !($this->Application->isDebugMode() && $this->Application->isAdminUser) ) {
 				$object->addFilter('user_filter', '%1$s.IsSystem = 0');
 			}
 		}
 
 		/**
 		 * Returns prefix, that custom fields are printed for
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
 		function _getSourcePrefix($event)
 		{
 			$prefix = $event->getEventParam('SourcePrefix');
-			if (!$prefix) {
+
+			if ( !$prefix ) {
 				$sql = 'SELECT Prefix
 						FROM ' . TABLE_PREFIX . 'ItemTypes
-						WHERE ItemType = ' . $this->Conn->qstr( $this->Application->GetVar('cf_type') );
+						WHERE ItemType = ' . $this->Conn->qstr($this->Application->GetVar('cf_type'));
 				$prefix = $this->Conn->GetOne($sql);
 			}
 
 			return $prefix;
 		}
 
 		/**
 		 * Get custom fields, that should no be shown anywhere
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getHiddenFields($event)
 		{
-			$prefix = $this->_getSourcePrefix($event);
-
 			$hidden_fields = Array ();
-			$virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields', Array ());
-			$custom_fields = $this->Application->getUnitOption($prefix, 'CustomFields', Array ());
-			/* @var $custom_fields Array */
+			$config = $this->Application->getUnitConfig($this->_getSourcePrefix($event));
 
-			foreach ($custom_fields as $custom_field) {
+			foreach ($config->getCustomFields(Array ()) as $custom_field) {
 				$check_field = 'cust_' . $custom_field;
-				$show_mode = array_key_exists('show_mode', $virtual_fields[$check_field]) ? $virtual_fields[$check_field]['show_mode'] : true;
+
+				$field_options = $config->getVirtualFieldByName($check_field);
+				$show_mode = array_key_exists('show_mode', $field_options) ? $field_options['show_mode'] : true;
 
 				if ( ($show_mode === false) || (($show_mode === smDEBUG) && !(defined('DEBUG_MODE') && DEBUG_MODE)) ) {
 					$hidden_fields[] = $custom_field;
 				}
 			}
 
 			return $hidden_fields;
 		}
 
 		/**
 		 * Prevents from duplicate item creation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sql = 'SELECT COUNT(*)
-					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+					FROM ' . $event->getUnitConfig()->getTableName() . '
 					WHERE FieldName = ' . $this->Conn->qstr($object->GetDBField('FieldName')) . ' AND Type = ' . $object->GetDBField('Type');
 			$found = $this->Conn->GetOne($sql);
 
 			if ( $found ) {
 				$event->status = kEvent::erFAIL;
 				$object->SetError('FieldName', 'duplicate', 'la_error_CustomExists');
 			}
 		}
 
 		/**
 		 * Occurs after deleting item, id of deleted item
 		 * is stored as 'id' param of event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$main_prefix = $this->getPrefixByItemType($object->GetDBField('Type'));
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			// call main item config to clone cdata table
-			$this->Application->getUnitOption($main_prefix, 'TableName');
+			$this->Application->getUnitConfig($main_prefix)->getTableName();
 			$ml_helper->deleteField($main_prefix . '-cdata', $event->getEventParam('id'));
 		}
 
 		/**
 		 * Get config prefix based on item type
 		 *
 		 * @param int $item_type
 		 * @return string
 		 * @access protected
 		 */
 		protected function getPrefixByItemType($item_type)
 		{
 			$sql = 'SELECT Prefix
 					FROM ' . TABLE_PREFIX . 'ItemTypes
 					WHERE ItemType = ' . $item_type;
 
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Creates new database columns, once custom field is successfully created
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSaveCustomField($event)
 		{
 			if ( $event->MasterEvent->status != kEvent::erSUCCESS ) {
 				return ;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$main_prefix = $this->getPrefixByItemType($object->GetDBField('Type'));
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			// call main item config to clone cdata table
 			define('CUSTOM_FIELD_ADDED', 1); // used in cdata::scanCustomFields method
-			$this->Application->getUnitOption($main_prefix, 'TableName');
+			$this->Application->getUnitConfig($main_prefix)->getTableName();
 			$ml_helper->createFields($main_prefix . '-cdata');
 		}
 
 		/**
 		 * Deletes all selected items.
 		 * Automatically recurse into sub-items using temp handler, and deletes sub-items
 		 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnMassDelete(kEvent $event)
 		{
 			parent::OnMassDelete($event);
 
 			$event->SetRedirectParam('opener', 's');
 		}
 
 		/**
 		 * Prepare temp tables for creating new item
 		 * but does not create it. Actual create is
 		 * done in OnPreSaveCreated
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			parent::OnPreCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('Type', $this->Application->GetVar('cf_type'));
 		}
 
 		/**
 		 * Prepares ValueList field's value as xml for editing
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !in_array($object->GetDBField('ElementType'), $this->_getMultiElementTypes()) ) {
 				return ;
 			}
 
 			$custom_field_helper = $this->Application->recallObject('InpCustomFieldsHelper');
 			/* @var $custom_field_helper InpCustomFieldsHelper */
 
 			$options = $custom_field_helper->GetValuesHash($object->GetDBField('ValueList'), VALUE_LIST_SEPARATOR, false);
 
 			$records = Array ();
 
 			$option_key = key($options);
 			if ( $option_key === '' || $option_key == 0 ) {
 				// remove 1st empty option, and add it later, when options will be saved, but allow string option keys
 				unset($options[$option_key]); // keep index, don't use array_unshift!
 			}
 
 			foreach ($options as $option_key => $option_title) {
 				$records[] = Array ('OptionKey' => $option_key, 'OptionTitle' => $option_title);
 			}
 
 			$minput_helper = $this->Application->recallObject('MInputHelper');
 			/* @var $minput_helper MInputHelper */
 
 			$xml = $minput_helper->prepareMInputXML($records, Array ('OptionKey', 'OptionTitle'));
 			$object->SetDBField('Options', $xml);
 		}
 
 		/**
 		 * Returns custom field element types, that will use minput control
 		 *
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getMultiElementTypes()
 		{
 			return Array ('select', 'multiselect', 'radio');
 		}
 
 		/**
 		 * Saves minput content to ValueList field
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !in_array($object->GetDBField('ElementType'), $this->_getMultiElementTypes()) ) {
 				return ;
 			}
 
 			$minput_helper = $this->Application->recallObject('MInputHelper');
 			/* @var $minput_helper MInputHelper */
 
 			$ret = $object->GetDBField('ElementType') == 'select' ? Array ('' => '=+') : Array ();
 			$records = $minput_helper->parseMInputXML($object->GetDBField('Options'));
 
 			if ( $object->GetDBField('SortValues') ) {
 				usort($records, Array (&$this, '_sortValues'));
 				ksort($records);
 			}
 
 			foreach ($records as $record) {
 				if ( substr($record['OptionKey'], 0, 3) == 'SQL' ) {
 					$ret[] = $record['OptionTitle'];
 				}
 				else {
 					$ret[] = $record['OptionKey'] . '=' . $record['OptionTitle'];
 				}
 			}
 
 			$object->SetDBField('ValueList', implode(VALUE_LIST_SEPARATOR, $ret));
 		}
 
 		function _sortValues($record_a, $record_b)
 		{
 			return strcasecmp($record_a['OptionTitle'], $record_b['OptionTitle']);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/users/users_event_handler.php
===================================================================
--- branches/5.3.x/core/units/users/users_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/users/users_event_handler.php	(revision 15698)
@@ -1,1935 +1,1936 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class UsersEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				// admin
 				'OnSetPersistantVariable'	=>	Array('self' => 'view'), // because setting to logged in user only
 				'OnUpdatePassword'			=>	Array('self' => true),
 				'OnSaveSelected'			=>	Array ('self' => 'view'),
 				'OnGeneratePassword'			=>	Array ('self' => 'view'),
 
 				// front
 				'OnRefreshForm'				=>	Array('self' => true),
 
 				'OnForgotPassword'			=>	Array('self' => true),
 
 				'OnSubscribeQuery'			=>	Array('self' => true),
 				'OnSubscribeUser'			=>	Array('self' => true),
 
 				'OnRecommend'				=>	Array('self' => true),
 
 				'OnItemBuild'				=>	Array('self' => true),
 				'OnMassResetSettings'	=> Array('self' => 'edit'),
 				'OnMassCloneUsers'	=> Array('self' => 'add'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Returns fields, that are not allowed to be changed from request
 		 *
 		 * @param Array $hash
 		 * @return Array
 		 * @access protected
 		 */
 		protected function getRequestProtectedFields($hash)
 		{
 			$fields = parent::getRequestProtectedFields($hash);
 
 			$fields = array_merge($fields, Array ('PrevEmails', 'ResourceId', 'IPAddress', 'IsBanned', 'PwResetConfirm', 'PwRequestTime', 'OldStyleLogin'));
 
 			if ( !$this->Application->isAdmin ) {
 				$fields = array_merge($fields, Array ('UserType', 'Status', 'EmailVerified', 'IsBanned'));
 			}
 
 			return $fields;
 		}
 
 		/**
 		 * Builds item (loads if needed)
 		 *
 		 * Pattern: Prototype Manager
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function OnItemBuild(kEvent $event)
 		{
 			parent::OnItemBuild($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $event->Special == 'forgot' || $object->getFormName() == 'registration' ) {
 				$this->_makePasswordRequired($event);
 			}
 		}
 
 		/**
 		 * Shows only admins when required
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			if ( $event->Special == 'regular' ) {
 				$object->addFilter('primary_filter', '%1$s.UserType = ' . UserType::USER);
 			}
 
 			if ( $event->Special == 'admins' ) {
 				$object->addFilter('primary_filter', '%1$s.UserType = ' . UserType::ADMIN);
 			}
 
 			if ( !$this->Application->isAdminUser ) {
 				$object->addFilter('status_filter', '%1$s.Status = ' . STATUS_ACTIVE);
 			}
 
 			if ( $event->Special == 'online' ) {
 				$object->addFilter('online_users_filter', 's.PortalUserId IS NOT NULL');
 			}
 
 			if ( $event->Special == 'group' ) {
 				$group_id = $this->Application->GetVar('g_id');
 
 				if ( $group_id !== false ) {
 					// show only users, that user doesn't belong to current group
 					$sql = 'SELECT PortalUserId
 							FROM ' . $this->Application->GetTempName(TABLE_PREFIX . 'UserGroupRelations', 'prefix:g') . '
 							WHERE GroupId = ' . (int)$group_id;
 					$user_ids = $this->Conn->GetCol($sql);
 
 					if ( $user_ids ) {
 						$object->addFilter('already_member_filter', '%1$s.PortalUserId NOT IN (' . implode(',', $user_ids) . ')');
 					}
 				}
 			}
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( $event->Name == 'OnLogin' || $event->Name == 'OnLoginAjax' || $event->Name == 'OnLogout' ) {
 				// permission is checked in OnLogin event directly
 				return true;
 			}
 
 			if ( $event->Name == 'OnResetRootPassword' ) {
 				return defined('DBG_RESET_ROOT') && DBG_RESET_ROOT;
 			}
 
 			if ( $event->Name == 'OnLoginAs' ) {
 				$admin_session = $this->Application->recallObject('Session.admin');
 				/* @var $admin_session Session */
 
 				return $admin_session->LoggedIn();
 			}
 
 			if ( !$this->Application->isAdminUser ) {
 				$user_id = $this->Application->RecallVar('user_id');
 				$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 				if ( ($event->Name == 'OnCreate' || $event->Name == 'OnRegisterAjax') && $user_id == USER_GUEST ) {
 					// "Guest" can create new users
 					return true;
 				}
 
 				if ( $event->Name == 'OnUpdate' && $user_id > 0 ) {
 					$user_dummy = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
 					/* @var $user_dummy UsersItem */
 
 					foreach ($items_info as $id => $field_values) {
 						if ( $id != $user_id ) {
 							// registered users can update their record only
 							return false;
 						}
 
 						$user_dummy->Load($id);
-						$status_field = $user_dummy->getStatusField();
+						$status_field = $event->getUnitConfig()->getStatusField(true);
 
 						if ( $user_dummy->GetDBField($status_field) != STATUS_ACTIVE ) {
 							// not active user is not allowed to update his record (he could not activate himself manually)
 							return false;
 						}
 
 						if ( isset($field_values[$status_field]) && $user_dummy->GetDBField($status_field) != $field_values[$status_field] ) {
 							// user can't change status by himself
 							return false;
 						}
 					}
 
 					return true;
 				}
 
 				if ( $event->Name == 'OnResetLostPassword' && $event->Special == 'forgot' && $user_id == USER_GUEST ) {
 					// non-logged in users can reset their password, when reset code is valid
 					return is_numeric($this->getPassedID($event));
 				}
 
 				if ( $event->Name == 'OnUpdate' && $user_id <= 0 ) {
 					// guests are not allowed to update their record, because they don't have it :)
 					return false;
 				}
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Handles session expiration (redirects to valid template)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSessionExpire($event)
 		{
 			$this->Application->resetCounters('UserSessions');
 
 			// place 2 of 2 (also in kHTTPQuery::getRedirectParams)
 			$admin_url_params = Array (
 				'm_cat_id' => 0, // category means nothing on admin login screen
 				'm_wid' => '', // remove wid, otherwise parent window may add wid to its name breaking all the frameset (for <a> targets)
 				'pass' => 'm', // don't pass any other (except "m") prefixes to admin session expiration template
 				'expired' => 1, // expiration mark to show special error on login screen
 				'no_pass_through' => 1, // this way kApplication::HREF won't add them again
 			);
 
 			if ($this->Application->isAdmin) {
 				$this->Application->Redirect('index', $admin_url_params, '', 'index.php');
 			}
 
 			if ($this->Application->GetVar('admin') == 1) {
 				// Front-End showed in admin's right frame
 				$session_admin = $this->Application->recallObject('Session.admin');
 				/* @var $session_admin Session */
 
 				if (!$session_admin->LoggedIn()) {
 					// front-end session created from admin session & both expired
 					$this->Application->DeleteVar('admin');
 					$this->Application->Redirect('index', $admin_url_params, '', 'admin/index.php');
 				}
 			}
 
 			// Front-End session expiration
 			$get = $this->Application->HttpQuery->getRedirectParams();
 			$t = $this->Application->GetVar('t');
 			$get['js_redirect'] = $this->Application->ConfigValue('UseJSRedirect');
 			$this->Application->Redirect($t ? $t : 'index', $get);
 		}
 
 		/**
 		 * [SCHEDULED TASK] Deletes expired sessions
 		 *
 		 * @param kEvent $event
 		 */
 		function OnDeleteExpiredSessions($event)
 		{
 			if (defined('IS_INSTALL') && IS_INSTALL) {
 				return ;
 			}
 
 			$this->Application->Session->DeleteExpired();
 		}
 
 		/**
 		 * Checks user data and logs it in if allowed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnLogin($event)
 		{
 			$object = $event->getObject( Array ('form_name' => 'login') );
 			/* @var $object kDBItem */
 
 			$field_values = $this->getSubmittedFields($event);
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 			$username = $object->GetDBField('UserLogin');
 			$password = $object->GetDBField('UserPassword');
 			$remember_login = $object->GetDBField('UserRememberLogin') == 1;
 
 			/* @var $user_helper UserHelper */
 			$user_helper = $this->Application->recallObject('UserHelper');
 
 			$user_helper->event =& $event;
 			$result = $user_helper->loginUser($username, $password, false, $remember_login);
 
 			if ($result != LoginResult::OK) {
 				$event->status = kEvent::erFAIL;
 				$object->SetError('UserLogin', $result == LoginResult::NO_PERMISSION ? 'no_permission' : 'invalid_password');
 			}
 
 			if ( is_object($event->MasterEvent) && ($event->MasterEvent->Name == 'OnLoginAjax') ) {
 				// used to insert just logged-in user e-mail on "One Step Checkout" form in "Modern Store" theme
 				$user =& $user_helper->getUserObject();
 				$event->SetRedirectParam('user_email', $user->GetDBField('Email'));
 			}
 		}
 
 		/**
 		 * Performs user login from ajax request
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnLoginAjax($event)
 		{
 			$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
 			/* @var $ajax_form_helper AjaxFormHelper */
 
 			$ajax_form_helper->transitEvent($event, 'OnLogin'); //, Array ('do_refresh' => 1));
 		}
 
 		/**
 		 * [HOOK] Auto-Logins Front-End user when "Remember Login" cookie is found
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAutoLoginUser($event)
 		{
 			$remember_login_cookie = $this->Application->GetVar('remember_login');
 
 			if (!$remember_login_cookie || $this->Application->isAdmin || $this->Application->LoggedIn()) {
 				return ;
 			}
 
 			/* @var $user_helper UserHelper */
 			$user_helper = $this->Application->recallObject('UserHelper');
 
 			$user_helper->loginUser('', '', false, false, $remember_login_cookie);
 		}
 
 		/**
 		 * Called when user logs in using old in-portal
 		 *
 		 * @param kEvent $event
 		 */
 		function OnInpLogin($event)
 		{
 			$sync_manager = $this->Application->recallObject('UsersSyncronizeManager', null, Array(), Array ('InPortalSyncronize'));
 			/* @var $sync_manager UsersSyncronizeManager */
 
 			$sync_manager->performAction('LoginUser', $event->getEventParam('user'), $event->getEventParam('pass') );
 
 			if ($event->redirect && is_string($event->redirect)) {
 				// some real template specified instead of true
 				$this->Application->Redirect($event->redirect, $event->getRedirectParams());
 			}
 		}
 
 		/**
 		 * Called when user logs in using old in-portal
 		 *
 		 * @param kEvent $event
 		 */
 		function OnInpLogout($event)
 		{
 			$sync_manager = $this->Application->recallObject('UsersSyncronizeManager', null, Array(), Array ('InPortalSyncronize'));
 			/* @var $sync_manager UsersSyncronizeManager */
 
 			$sync_manager->performAction('LogoutUser');
 		}
 
 		/**
 		 * Performs user logout
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnLogout($event)
 		{
 			/* @var $user_helper UserHelper */
 			$user_helper = $this->Application->recallObject('UserHelper');
 
 			$user_helper->event =& $event;
 			$user_helper->logoutUser();
 		}
 
 		/**
 		 * Redirects user after successful registration to confirmation template (on Front only)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(kEvent $event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$this->afterItemChanged($event);
 
 			$this->assignToPrimaryGroup($event);
 		}
 
 		/**
 		 * Performs user registration
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCreate(kEvent $event)
 		{
 			if ( $this->Application->isAdmin ) {
 				parent::OnCreate($event);
 
 				return ;
 			}
 
 			$object = $event->getObject( Array('form_name' => 'registration') );
 			/* @var $object UsersItem */
 
 			$field_values = $this->getSubmittedFields($event);
 			$user_email = getArrayValue($field_values, 'Email');
 			$subscriber_id = $user_email ? $this->getSubscriberByEmail($user_email) : false;
 
 			if ( $subscriber_id ) {
 				// update existing subscriber
 				$object->Load($subscriber_id);
 				$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_NewGroup'));
 				$this->Application->SetVar($event->getPrefixSpecial(true), Array ($object->GetID() => $field_values));
 			}
 
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 			$status = $object->isLoaded() ? $object->Update() : $object->Create();
 
 			if ( !$status ) {
 				$event->status = kEvent::erFAIL;
 				$event->redirect = false;
 				$object->setID( (int)$object->GetID() );
 			}
 
 			$this->setNextTemplate($event, true);
 
 			if ( ($event->status == kEvent::erSUCCESS) && $event->redirect ) {
 				$this->assignToPrimaryGroup($event);
 
 				$object->sendEmails();
 				$this->autoLoginUser($event);
 			}
 		}
 
 		/**
 		 * Processes user registration from ajax request
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnRegisterAjax(kEvent $event)
 		{
 			$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
 			/* @var $ajax_form_helper AjaxFormHelper */
 
 			$ajax_form_helper->transitEvent($event, 'OnCreate', Array ('do_refresh' => 1));
 		}
 
 		/**
 		 * Returns subscribed user ID by given e-mail address
 		 *
 		 * @param string $email
 		 * @return int|bool
 		 * @access protected
 		 */
 		protected function getSubscriberByEmail($email)
 		{
 			$verify_user = $this->Application->recallObject('u.verify', null, Array ('skip_autoload' => true));
 			/* @var $verify_user UsersItem */
 
 			$verify_user->Load($email, 'Email');
 
 			return $verify_user->isLoaded() && $verify_user->isSubscriberOnly() ? $verify_user->GetID() : false;
 		}
 
 		/**
 		 * Login user if possible, if not then redirect to corresponding template
 		 *
 		 * @param kEvent $event
 		 */
 		function autoLoginUser($event)
 		{
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			if ( $object->GetDBField('Status') == STATUS_ACTIVE ) {
 				/* @var $user_helper UserHelper */
 				$user_helper = $this->Application->recallObject('UserHelper');
 
 				$user =& $user_helper->getUserObject();
 				$user->Load($object->GetID());
 
 				if ( $user_helper->checkLoginPermission() ) {
 					$user_helper->loginUserById( $user->GetID() );
 				}
 			}
 		}
 
 		/**
 		 * Set's new unique resource id to user
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$this->beforeItemChanged($event);
 
 			$cs_helper = $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			if ( !$object->isSubscriberOnly() ) {
 				// don't check state-to-country relations for subscribers
 				$cs_helper->CheckStateField($event, 'State', 'Country');
 			}
 
 			if ( $object->getFormName() != 'login' ) {
 				$this->_makePasswordRequired($event);
 			}
 
 			$cs_helper->PopulateStates($event, 'State', 'Country');
 
 			$this->setUserGroup($object);
 
 			/* @var $user_helper UserHelper */
 			$user_helper = $this->Application->recallObject('UserHelper');
 
 			if ( !$user_helper->checkBanRules($object) ) {
 				$object->SetError('Username', 'banned');
 			}
 
 			$object->SetDBField('IPAddress', $this->Application->getClientIp());
 
 			if ( !$this->Application->isAdmin ) {
 				$object->SetDBField('FrontLanguage', $this->Application->GetVar('m_lang'));
 			}
 		}
 
 		/**
 		 * Sets primary group of the user
 		 *
 		 * @param kDBItem $object
 		 */
 		protected function setUserGroup(&$object)
 		{
 			if ($object->Special == 'subscriber') {
 				$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_SubscriberGroup'));
 
 				return ;
 			}
 
 			// set primary group to user
 			if ( !$this->Application->isAdminUser ) {
 				$group_id = $object->GetDBField('PrimaryGroupId');
 
 				if ($group_id) {
 					// check, that group is allowed for Front-End
 					$sql = 'SELECT GroupId
 							FROM ' . TABLE_PREFIX . 'UserGroups
 							WHERE GroupId = ' . (int)$group_id . ' AND FrontRegistration = 1';
 					$group_id = $this->Conn->GetOne($sql);
 				}
 
 				if (!$group_id) {
 					// when group not selected OR not allowed -> use default group
 					$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_NewGroup'));
 				}
 			}
 		}
 
 		/**
 		 * Assigns a user to it's primary group
 		 *
 		 * @param kEvent $event
 		 */
 		protected function assignToPrimaryGroup($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$primary_group_id = $object->GetDBField('PrimaryGroupId');
 
 			if ($primary_group_id) {
 				$ug_table = TABLE_PREFIX . 'UserGroupRelations';
 
 				if ( $object->IsTempTable() ) {
 					$ug_table = $this->Application->GetTempName($ug_table, 'prefix:' . $event->Prefix);
 				}
 
 				$fields_hash = Array (
 					'PortalUserId' => $object->GetID(),
 					'GroupId' => $primary_group_id,
 				);
 
 				$this->Conn->doInsert($fields_hash, $ug_table, 'REPLACE');
 			}
 		}
 
 		/**
 		 * Set's new unique resource id to user
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemValidate(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$resource_id = $object->GetDBField('ResourceId');
 
 			if ( !$resource_id ) {
 				$object->SetDBField('ResourceId', $this->Application->NextResourceId());
 			}
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnRecommend($event)
 		{
 			$object = $event->getObject( Array ('form_name' => 'recommend') );
 			/* @var $object kDBItem */
 
 			$field_values = $this->getSubmittedFields($event);
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 			if ( !$object->ValidateField('RecommendEmail') ) {
 				$event->status = kEvent::erFAIL;
 
 				return ;
 			}
 
 	    	$send_params = Array (
 		    	'to_email' => $object->GetDBField('RecommendEmail'),
 				'to_name' => $object->GetDBField('RecommendEmail'),
 	    	);
 
 			$user_id = $this->Application->RecallVar('user_id');
 			$email_sent = $this->Application->emailUser('USER.SUGGEST', $user_id, $send_params);
 			$this->Application->emailAdmin('USER.SUGGEST');
 
 			if ( $email_sent ) {
 				$event->SetRedirectParam('pass', 'all');
 				$event->redirect = $this->Application->GetVar('template_success');
 			}
 			else {
 				$event->status = kEvent::erFAIL;
 				$object->SetError('RecommendEmail', 'send_error');
 			}
 		}
 
 		/**
 		 * Saves address changes and mades no redirect
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdateAddress($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);
 
 				if ( $id > 0 ) {
 					$object->Load($id);
 				}
 
 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 				$object->setID($id);
 				$object->Validate();
 			}
 
 			$cs_helper = $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$cs_helper->PopulateStates($event, 'State', 'Country');
 
 			$event->redirect = false;
 		}
 
 		/**
 		 * Validate subscriber's email & store it to session -> redirect to confirmation template
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSubscribeQuery($event)
 		{
 			$object = $event->getObject( Array ('form_name' => 'subscription') );
 			/* @var $object UsersItem */
 
 			$field_values = $this->getSubmittedFields($event);
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 			if ( !$object->ValidateField('SubscriberEmail') ) {
 				$event->status = kEvent::erFAIL;
 
 				return ;
 			}
 
 			$user_email = $object->GetDBField('SubscriberEmail');
 			$object->Load($user_email, 'Email');
 			$event->SetRedirectParam('subscriber_email', $user_email);
 
 			if ( $object->isLoaded() && $object->isSubscribed() ) {
 				$event->redirect = $this->Application->GetVar('unsubscribe_template');
 			}
 			else {
 				$event->redirect = $this->Application->GetVar('subscribe_template');
 			}
 
 			$event->SetRedirectParam('pass', 'm');
 		}
 
 		/**
 		 * Subscribe/Unsubscribe user based on email stored in previous step
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSubscribeUser($event)
 		{
 			$object = $event->getObject( Array ('form_name' => 'subscription') );
 			/* @var $object UsersItem */
 
 			$user_email = $this->Application->GetVar('subscriber_email');
 			$object->SetDBField('SubscriberEmail', $user_email);
 
 			if ( !$object->ValidateField('SubscriberEmail') ) {
 				$event->status = kEvent::erFAIL;
 
 				return ;
 			}
 
 			$username_required = $object->isRequired('Username');
 			$this->RemoveRequiredFields($object);
 			$object->Load($user_email, 'Email');
 
 			if ( $object->isLoaded() ) {
 				if ( $object->isSubscribed() ) {
 					if ( $event->getEventParam('no_unsubscribe') ) {
 						// for customization code from FormsEventHandler
 						return ;
 					}
 
 					if ( $object->isSubscriberOnly() ) {
 						$temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler');
 						/* @var $temp_handler kTempTablesHandler */
 
 						$temp_handler->DeleteItems($event->Prefix, '', Array($object->GetID()));
 					}
 					else {
 						$this->RemoveSubscriberGroup( $object->GetID() );
 					}
 
 					$event->redirect = $this->Application->GetVar('unsubscribe_ok_template');
 				}
 				else {
 					$this->AddSubscriberGroup($object);
 					$event->redirect = $this->Application->GetVar('subscribe_ok_template');
 				}
 			}
 			else {
 				$object->generatePassword();
 				$object->SetDBField('Email', $user_email);
 
 				if ( $username_required )	{
 					$object->SetDBField('Username', str_replace('@', '_at_', $user_email));
 				}
 
 				$object->SetDBField('Status', STATUS_ACTIVE); // make user subscriber Active by default
 
 				if ( $object->Create() ) {
 					$this->AddSubscriberGroup($object);
 					$event->redirect = $this->Application->GetVar('subscribe_ok_template');
 				}
 			}
 		}
 
 		/**
 		 * Adding user to subscribers group
 		 *
 		 * @param UsersItem $object
 		 */
 		function AddSubscriberGroup(&$object)
 		{
 			if ( !$object->isSubscriberOnly() ) {
 				$fields_hash = Array (
 					'PortalUserId' => $object->GetID(),
 					'GroupId' => $this->Application->ConfigValue('User_SubscriberGroup'),
 				);
 
 				$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'UserGroupRelations');
 			}
 
 			$this->Application->emailAdmin('USER.SUBSCRIBE');
 			$this->Application->emailUser('USER.SUBSCRIBE', $object->GetID());
 		}
 
 		/**
 		 * Removing user from subscribers group
 		 *
 		 * @param int $user_id
 		 */
 		function RemoveSubscriberGroup($user_id)
 		{
 			$group_id = $this->Application->ConfigValue('User_SubscriberGroup');
 
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserGroupRelations
 					WHERE PortalUserId = ' . $user_id . ' AND GroupId = ' . $group_id;
 			$this->Conn->Query($sql);
 
 			$this->Application->emailAdmin('USER.UNSUBSCRIBE');
 			$this->Application->emailUser('USER.UNSUBSCRIBE', $user_id);
 		}
 
 		/**
 		 * Validates forgot password form and sends password reset confirmation e-mail
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 */
 		function OnForgotPassword($event)
 		{
 			$object = $event->getObject( Array ('form_name' => 'forgot_password') );
 			/* @var $object kDBItem */
 
 			$field_values = $this->getSubmittedFields($event);
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 			$user = $this->Application->recallObject('u.tmp', null, Array ('skip_autoload' => true));
 			/* @var $user UsersItem */
 
 			$found = $allow_reset = false;
 			$email_or_username = $object->GetDBField('ForgotLogin');
 			$is_email = strpos($email_or_username, '@') !== false;
 
 			if ( strlen($email_or_username) ) {
 				$user->Load($email_or_username, $is_email ? 'Email' : 'Username');
 			}
 
 			if ( $user->isLoaded() ) {
 				$min_pwd_reset_delay = $this->Application->ConfigValue('Users_AllowReset');
 				$found = ($user->GetDBField('Status') == STATUS_ACTIVE) && strlen($user->GetDBField('Password'));
 
 				if ( !$user->GetDBField('PwResetConfirm') ) {
 					// no reset made -> allow
 					$allow_reset = true;
 				}
 				else {
 					// reset made -> wait N minutes, then allow
 					$allow_reset = TIMENOW > $user->GetDBField('PwRequestTime') + $min_pwd_reset_delay;
 				}
 			}
 
-			if ( $found && $allow_reset ) {
+			if ($found && $allow_reset) {
 				$this->Application->emailUser('USER.PSWDC', $user->GetID());
 				$event->redirect = $this->Application->GetVar('template_success');
 
-				return;
+				return ;
 			}
 
 			if ( strlen($email_or_username) ) {
 				$object->SetError('ForgotLogin', $found ? 'reset_denied' : ($is_email ? 'unknown_email' : 'unknown_username'));
-			}
+				}
 
 			if ( !$object->ValidateField('ForgotLogin') ) {
 				$event->status = kEvent::erFAIL;
 			}
 		}
 
 		/**
 		 * Updates kDBItem
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdate(kEvent $event)
 		{
 			parent::OnUpdate($event);
 
 			if ( !$this->Application->isAdmin ) {
 				$this->setNextTemplate($event);
 			}
 		}
 
 		/**
 		 * Checks state against country
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->beforeItemChanged($event);
 
 			$cs_helper = $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$cs_helper->CheckStateField($event, 'State', 'Country');
 			$cs_helper->PopulateStates($event, 'State', 'Country');
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $event->Special == 'forgot' ) {
 				$object->SetDBField('PwResetConfirm', '');
 				$object->SetDBField('PwRequestTime_date', NULL);
 				$object->SetDBField('PwRequestTime_time', NULL);
 			}
 
 			$changed_fields = array_keys($object->GetChangedFields());
 
 			if ( $changed_fields && !in_array('Modified', $changed_fields) ) {
 				$object->SetDBField('Modified_date', adodb_mktime());
 				$object->SetDBField('Modified_time', adodb_mktime());
 			}
 
 			if ( !$this->Application->isAdmin && in_array('Email', $changed_fields) && ($event->Special != 'email-restore') ) {
 				$object->SetDBField('EmailVerified', 0);
 			}
 		}
 
 		/**
 		 * Occurs before item is changed
 		 *
 		 * @param kEvent $event
 		 */
 		function beforeItemChanged($event)
 		{
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			if ( !$this->Application->isAdmin && $object->getFormName() == 'registration' ) {
 				// sets new user's status based on config options
 				$status_map = Array (1 => STATUS_ACTIVE, 2 => STATUS_DISABLED, 3 => STATUS_PENDING, 4 => STATUS_PENDING);
 				$object->SetDBField('Status', $status_map[ $this->Application->ConfigValue('User_Allow_New') ]);
 
 				if ( $this->Application->ConfigValue('User_Password_Auto') ) {
 					$object->generatePassword( rand(5, 8) );
 				}
 
 				if ( $this->Application->ConfigValue('RegistrationCaptcha') ) {
 					$captcha_helper = $this->Application->recallObject('CaptchaHelper');
 					/* @var $captcha_helper kCaptchaHelper */
 
 					$captcha_helper->validateCode($event, false);
 				}
 
 				if ( $event->Name == 'OnBeforeItemUpdate' ) {
 					// when a subscriber-only users performs normal registration, then assign him to Member group
 					$this->setUserGroup($object);
 				}
 			}
 		}
 
 		/**
 		 * Sets redirect template based on user status & user request contents
 		 *
 		 * @param kEvent $event
 		 * @param bool $for_registration
 		 */
 		function setNextTemplate($event, $for_registration = false)
 		{
 			$event->SetRedirectParam('opener', 's');
 
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			$next_template = false;
 
 			if ( $object->GetDBField('Status') == STATUS_ACTIVE && $this->Application->GetVar('next_template') ) {
 				$next_template = $this->Application->GetVar('next_template');
 			}
 			elseif ( $for_registration ) {
 				switch ( $this->Application->ConfigValue('User_Allow_New') ) {
 					case 1: // Immediate
 						$next_template = $this->Application->GetVar('registration_confirm_template');
 						break;
 
 					case 3: // Upon Approval
 					case 4: // Email Activation
 						$next_template = $this->Application->GetVar('registration_confirm_pending_template');
 						break;
 				}
 			}
 
 			if ($next_template) {
 				$event->redirect = $next_template;
 			}
 		}
 
 		/**
 		 * Delete users from groups if their membership is expired
 		 *
 		 * @param kEvent $event
 		 */
 		function OnCheckExpiredMembership($event)
 		{
 			// send pre-expiration reminders: begin
 			$pre_expiration = adodb_mktime() + $this->Application->ConfigValue('User_MembershipExpirationReminder') * 3600 * 24;
 			$sql = 'SELECT PortalUserId, GroupId
 					FROM '.TABLE_PREFIX.'UserGroupRelations
 					WHERE (MembershipExpires IS NOT NULL) AND (ExpirationReminderSent = 0) AND (MembershipExpires < '.$pre_expiration.')';
 
 			$skip_clause = $event->getEventParam('skip_clause');
 			if ($skip_clause) {
 				$sql .= ' AND !('.implode(') AND !(', $skip_clause).')';
 			}
 
 			$records = $this->Conn->Query($sql);
 			if ($records) {
 				$conditions = Array();
 				foreach ($records as $record) {
 					$this->Application->emailUser('USER.MEMBERSHIP.EXPIRATION.NOTICE', $record['PortalUserId']);
 					$this->Application->emailAdmin('USER.MEMBERSHIP.EXPIRATION.NOTICE');
 					$conditions[] = '(PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'].')';
 				}
 				$sql = 'UPDATE '.TABLE_PREFIX.'UserGroupRelations
 						SET ExpirationReminderSent = 1
 						WHERE '.implode(' OR ', $conditions);
 				$this->Conn->Query($sql);
 			}
 			// send pre-expiration reminders: end
 
 			// remove users from groups with expired membership: begin
 			$sql = 'SELECT PortalUserId
 					FROM '.TABLE_PREFIX.'UserGroupRelations
 					WHERE (MembershipExpires IS NOT NULL) AND (MembershipExpires < '.adodb_mktime().')';
 			$user_ids = $this->Conn->GetCol($sql);
 			if ($user_ids) {
 				foreach ($user_ids as $id) {
 					$this->Application->emailUser('USER.MEMBERSHIP.EXPIRED', $id);
 					$this->Application->emailAdmin('USER.MEMBERSHIP.EXPIRED');
 				}
 			}
 			$sql = 'DELETE FROM '.TABLE_PREFIX.'UserGroupRelations
 					WHERE (MembershipExpires IS NOT NULL) AND (MembershipExpires < '.adodb_mktime().')';
 			$this->Conn->Query($sql);
 			// remove users from groups with expired membership: end
 		}
 
 		/**
 		 * Used to keep user registration form data, while showing affiliate registration form fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnRefreshForm($event)
 		{
 			$event->redirect = false;
 			$item_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			list($id, $field_values) = each($item_info);
 
 			$object = $event->getObject( Array ('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$object->setID($id);
 			$object->IgnoreValidation = true;
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 		}
 
 		/**
 		 * Sets persistant variable
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetPersistantVariable($event)
 		{
 			$field =  $this->Application->GetVar('field');
 			$value = $this->Application->GetVar('value');
 			$this->Application->StorePersistentVar($field, $value);
 
 			$force_tab = $this->Application->GetVar('SetTab');
 			if ($force_tab) {
 				$this->Application->StoreVar('force_tab', $force_tab);
 			}
 		}
 
 		/**
 		 * Return user from order by special .ord
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access public
 		 */
 		public function getPassedID(kEvent $event)
 		{
 			switch ($event->Special) {
 				case 'ord':
 					$order = $this->Application->recallObject('ord');
 					/* @var $order OrdersItem */
 
 					return $order->GetDBField('PortalUserId');
 					break;
 
 				case 'profile':
 					$id = $this->Application->GetVar('user_id');
 
 					if ( !$id ) {
 						// if none user_id given use current user id
 						$id = $this->Application->RecallVar('user_id');
 					}
 
 					return $id;
 					break;
 
 				case 'forgot':
 					/* @var $user_helper UserHelper */
 					$user_helper = $this->Application->recallObject('UserHelper');
 
 					$id = $user_helper->validateUserCode($this->Application->GetVar('user_key'), 'forgot_password');
 
 					if ( is_numeric($id) ) {
 						return $id;
 					}
 					break;
 			}
 
 			if ( preg_match('/^(login|register|recommend|subscribe|forgot)/', $event->Special) ) {
 				// this way we can have 2+ objects stating with same special, e.g. "u.login-sidebox" and "u.login-main"
 				return USER_GUEST;
 			}
 
 			return parent::getPassedID($event);
 		}
 
 		/**
 		 * Allows to change root password
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdatePassword($event)
 		{
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 			if ( !$items_info ) {
 				return;
 			}
 
 			list ($id, $field_values) = each($items_info);
 			$user_id = $this->Application->RecallVar('user_id');
 
 			if ( $id == $user_id && ($user_id > 0 || $user_id == USER_ROOT) ) {
 				$user_dummy = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
 				/* @var $user_dummy kDBItem */
 
 				$user_dummy->Load($id);
-				$status_field = $user_dummy->getStatusField();
+				$status_field = $event->getUnitConfig()->getStatusField(true);
 
 				if ( $user_dummy->GetDBField($status_field) != STATUS_ACTIVE ) {
 					// not active user is not allowed to update his record (he could not activate himself manually)
 					return ;
 				}
 			}
 
 			if ( $user_id == USER_ROOT ) {
 				$object = $event->getObject(Array ('skip_autoload' => true));
 				/* @var $object UsersItem */
 
 				// this is internal hack to allow root/root passwords for dev
 				if ( $this->Application->isDebugMode() && $field_values['RootPassword'] == 'root' ) {
 					$object->SetFieldOption('RootPassword', 'min_length', 4);
 				}
 
 				$this->RemoveRequiredFields($object);
 				$object->SetDBField('RootPassword', $this->Application->ConfigValue('RootPass'));
 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 				$object->setID(-1);
 
 				if ( $object->Validate() ) {
 					// validation on, password match too
 					$fields_hash = Array ('VariableValue' => $object->GetDBField('RootPassword'));
-					$conf_table = $this->Application->getUnitOption('conf', 'TableName');
+					$conf_table = $this->Application->getUnitConfig('conf')->getTableName();
 					$this->Conn->doUpdate($fields_hash, $conf_table, 'VariableName = "RootPass"');
 					$event->SetRedirectParam('opener', 'u');
 				}
 				else {
 					$event->status = kEvent::erFAIL;
 					$event->redirect = false;
 					return ;
 				}
 			}
 			else {
 				$object = $event->getObject();
 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 				if ( !$object->Update() ) {
 					$event->status = kEvent::erFAIL;
 					$event->redirect = false;
 				}
 			}
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Resets grid settings, remembered in each user record
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnMassResetSettings($event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			$default_user_id = $this->Application->ConfigValue('DefaultSettingsUserId');
 			if ( in_array($default_user_id, $ids) ) {
 				array_splice($ids, array_search($default_user_id, $ids), 1);
 			}
 
 			if ( $ids ) {
 				$q = 'DELETE FROM ' . TABLE_PREFIX . 'UserPersistentSessionData WHERE PortalUserId IN (' . join(',', $ids) . ') AND
 							 (VariableName LIKE "%_columns_%"
 							 OR
 							 VariableName LIKE "%_filter%"
 							 OR
 							 VariableName LIKE "%_PerPage%")';
 				$this->Conn->Query($q);
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access protected
 		 */
 		protected function checkItemStatus(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->isLoaded() ) {
 				return true;
 			}
 
 			$virtual_users = Array (USER_ROOT, USER_GUEST);
 
 			return ($object->GetDBField('Status') == STATUS_ACTIVE) || in_array($object->GetID(), $virtual_users);
 		}
 
 		/**
 		 * Sends approved/declined email event on user status change
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemUpdate(kEvent $event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$this->afterItemChanged($event);
 
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			if ( !$this->Application->isAdmin && ($event->Special != 'email-restore') ) {
 				$this->sendEmailChangeEvent($event);
 			}
 
 			if ( !$this->Application->isAdmin || $object->IsTempTable() ) {
 				return;
 			}
 
 			$this->sendStatusChangeEvent($object->GetID(), $object->GetOriginalField('Status'), $object->GetDBField('Status'));
 		}
 
 		/**
 		 * Occurs, after item is changed
 		 *
 		 * @param kEvent $event
 		 */
 		protected function afterItemChanged($event)
 		{
 			$this->saveUserImages($event);
 
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			if ( $object->GetDBField('EmailPassword') && $object->GetDBField('Password_plain') ) {
 				$email_passwords = $this->Application->RecallVar('email_passwords');
 				$email_passwords = $email_passwords ? unserialize($email_passwords) : Array ();
 
 				$email_passwords[ $object->GetID() ] = $object->GetDBField('Password_plain');
 				$this->Application->StoreVar('email_passwords', serialize($email_passwords));
 			}
 
 			// update user subscription status (via my profile or new user registration)
 			if ( !$this->Application->isAdmin && !$object->isSubscriberOnly() ) {
 				if ( $object->GetDBField('SubscribeToMailing') && !$object->isSubscribed() ) {
 					$this->AddSubscriberGroup($object);
 				}
 				elseif ( !$object->GetDBField('SubscribeToMailing') && $object->isSubscribed() ) {
 					$this->RemoveSubscriberGroup( $object->GetID() );
 				}
 			}
 		}
 
 		/**
 		 * Stores user's original Status before overwriting with data from temp table
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeDeleteFromLive(kEvent $event)
 		{
 			parent::OnBeforeDeleteFromLive($event);
 
 			$user_id = $event->getEventParam('id');
 			$user_status = $this->Application->GetVar('user_status', Array ());
 
 			if ( $user_id > 0 ) {
 				$user_status[$user_id] = $this->getUserStatus($user_id);
 				$this->Application->SetVar('user_status', $user_status);
 			}
 		}
 
 		/**
 		 * Sends approved/declined email event on user status change (in temp tables during editing)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterCopyToLive(kEvent $event)
 		{
 			parent::OnAfterCopyToLive($event);
 
 			$temp_id = $event->getEventParam('temp_id');
 			$email_passwords = $this->Application->RecallVar('email_passwords');
 
 			if ( $email_passwords ) {
 				$email_passwords = unserialize($email_passwords);
 
 				if ( isset($email_passwords[$temp_id]) ) {
 					$object = $event->getObject();
 					/* @var $object kDBItem */
 
 					$object->SwitchToLive();
 					$object->Load( $event->getEventParam('id') );
 					$object->SetField('Password', $email_passwords[$temp_id]);
 					$object->SetField('VerifyPassword', $email_passwords[$temp_id]);
 
 					$this->Application->emailUser($temp_id > 0 ? 'USER.NEW.PASSWORD': 'USER.ADD.BYADMIN', $object->GetID());
 
 					unset($email_passwords[$temp_id]);
 					$this->Application->StoreVar('email_passwords', serialize($email_passwords));
 				}
 			}
 
 			if ( $temp_id > 0 ) {
 				// only send status change e-mail on user update
 				$new_status = $this->getUserStatus($temp_id);
 				$user_status = $this->Application->GetVar('user_status');
 
 				$this->sendStatusChangeEvent($temp_id, $user_status[$temp_id], $new_status);
 			}
 		}
 
 		/**
 		 * Returns user status (active, pending, disabled) based on ID and temp mode setting
 		 *
 		 * @param int $user_id
 		 * @return int
 		 */
 		function getUserStatus($user_id)
 		{
-			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
+			$config = $this->getUnitConfig();
 
 			$sql = 'SELECT Status
-					FROM '.$table_name.'
-					WHERE '.$id_field.' = '.$user_id;
+					FROM '. $config->getTableName() .'
+					WHERE '. $config->getIDField() .' = '.$user_id;
+
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Sends approved/declined email event on user status change
 		 *
 		 * @param int $user_id
 		 * @param int $prev_status
 		 * @param int $new_status
 		 */
 		function sendStatusChangeEvent($user_id, $prev_status, $new_status)
 		{
 			$status_events = Array (
 				STATUS_ACTIVE	=>	'USER.APPROVE',
 				STATUS_DISABLED	=>	'USER.DENY',
 			);
 			$email_event = isset($status_events[$new_status]) ? $status_events[$new_status] : false;
 
 			if (($prev_status != $new_status) && $email_event) {
 				$this->Application->emailUser($email_event, $user_id);
 				$this->Application->emailAdmin($email_event);
 			}
 
 			// deletes sessions from users, that are no longer active
 			if (($prev_status != $new_status) && ($new_status != STATUS_ACTIVE)) {
 				$sql = 'SELECT SessionKey
 						FROM ' . TABLE_PREFIX . 'UserSessions
 						WHERE PortalUserId = ' . $user_id;
 				$session_ids = $this->Conn->GetCol($sql);
 
 				$this->Application->Session->DeleteSessions($session_ids);
 			}
 		}
 
 		/**
 		 * Sends restore/validation email event on user email change
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function sendEmailChangeEvent(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			$new_email = $object->GetDBField('Email');
 			$prev_email = $object->GetOriginalField('Email');
 
 			if ( !$new_email || ($prev_email == $new_email) ) {
 				return;
 			}
 
 			$prev_emails = $object->GetDBField('PrevEmails');
 			$prev_emails = $prev_emails ? unserialize($prev_emails) : Array ();
 
 			$fields_hash = Array (
 				'PrevEmails' => serialize($prev_emails),
 				'EmailVerified' => 0,
 			);
 
 			$user_id = $object->GetID();
 
 			if ( $prev_email ) {
 				$hash = md5(TIMENOW + $user_id);
 				$prev_emails[$hash] = $prev_email;
 				$fields_hash['PrevEmails'] = serialize($prev_emails);
 
 				$send_params = Array (
 					'hash' => $hash,
 					'to_email' => $prev_email,
 					'to_name' => trim($object->GetDBField('FirstName') . ' ' . $object->GetDBField('LastName')),
 				);
 
 				$this->Application->emailUser('USER.EMAIL.CHANGE.UNDO', null, $send_params);
 			}
 
 			if ( $new_email ) {
 				$this->Application->emailUser('USER.EMAIL.CHANGE.VERIFY', $user_id);
 			}
 
 			// direct DB update, since USER.EMAIL.CHANGE.VERIFY puts verification code in user record, that we don't want to loose
 			$this->Conn->doUpdate($fields_hash, $object->TableName, 'PortalUserId = ' . $user_id);
 		}
 
 		/**
 		 * OnAfterConfigRead for users
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
-			$forms = $this->Application->getUnitOption($event->Prefix, 'Forms');
-			$form_fields =& $forms['default']['Fields'];
+			$config = $event->getUnitConfig();
+			$default_form = $config->getFormByName('default');
+			$form_fields =& $default_form['Fields'];
 
 			// 1. arrange user registration countries
 			$site_helper = $this->Application->recallObject('SiteHelper');
 			/* @var $site_helper SiteHelper */
 
 			$first_country = $site_helper->getDefaultCountry('', false);
 
 			if ($first_country === false) {
 				$first_country = $this->Application->ConfigValue('User_Default_Registration_Country');
 			}
 
 			if ($first_country) {
 				// update user country dropdown sql
 				$form_fields['Country']['options_sql'] = preg_replace('/ORDER BY (.*)/', 'ORDER BY IF (CountryStateId = '.$first_country.', 1, 0) DESC, \\1', $form_fields['Country']['options_sql']);
 			}
 
 			// 2. set default user registration group
 			$form_fields['PrimaryGroupId']['default'] = $this->Application->ConfigValue('User_NewGroup');
 
 			// 3. allow avatar upload on Front-End
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->createItemFiles($event->Prefix, true); // create image fields
 
 			if ($this->Application->isAdminUser) {
 				// 4. when in administrative console, then create all users with Active status
 				$form_fields['Status']['default'] = STATUS_ACTIVE;
 
 				// 5. remove groups tab on editing forms when AdvancedUserManagement config variable not set
 				if (!$this->Application->ConfigValue('AdvancedUserManagement')) {
-					$edit_tab_presets = $this->Application->getUnitOption($event->Prefix, 'EditTabPresets');
+					$edit_tab_presets = $config->getEditTabPresets();
 
 					foreach ($edit_tab_presets as $preset_name => $preset_tabs) {
 						if (array_key_exists('groups', $preset_tabs)) {
 							unset($edit_tab_presets[$preset_name]['groups']);
 
 							if (count($edit_tab_presets[$preset_name]) == 1) {
 								// only 1 tab left -> remove it too
 								$edit_tab_presets[$preset_name] = Array ();
 							}
 						}
 					}
 
-					$this->Application->setUnitOption($event->Prefix, 'EditTabPresets', $edit_tab_presets);
+					$config->setEditTabPresets($edit_tab_presets);
 				}
 			}
 
 			if ( $this->Application->ConfigValue('RegistrationUsernameRequired') ) {
 				// Username becomes required only, when it's used in registration process
 				$max_username = $this->Application->ConfigValue('MaxUserName');
 
 				$form_fields['Username']['required'] = 1;
 				$form_fields['Username']['min_len'] = $this->Application->ConfigValue('Min_UserName');
 				$form_fields['Username']['max_len'] = $max_username ? $max_username : 255;
 			}
 
-			$this->Application->setUnitOption($event->Prefix, 'Forms', $forms);
+			$config->addForms($default_form, 'default');
 		}
 
 		/**
 		 * OnMassCloneUsers
 		 *
 		 * @param kEvent $event
 		 */
 		function OnMassCloneUsers($event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$temp_handler = $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$ids = $this->StoreSelectedIDs($event);
 			$temp_handler->CloneItems($event->Prefix, '', $ids);
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * When cloning users, reset password (set random)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeClone(kEvent $event)
 		{
 			parent::OnBeforeClone($event);
 
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			$object->generatePassword();
 			$object->SetDBField('ResourceId', 0); // this will reset it
 
 			// change email because it should be unique
 			$object->NameCopy(Array (), $object->GetID(), 'Email', 'copy%1$s.%2$s');
 		}
 
 		/**
 		 * Saves selected ids to session
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveSelected($event)
 		{
 			$this->StoreSelectedIDs($event);
 
 			// remove current ID, otherwise group selector will use it in filters
 			$this->Application->DeleteVar($event->getPrefixSpecial(true) . '_id');
 		}
 
 		/**
 		 * Sets primary group of selected users
 		 *
 		 * @param kEvent $event
 		 */
 		function OnProcessSelected($event)
 		{
 			$event->SetRedirectParam('opener', 'u');
 			$user_ids = $this->getSelectedIDs($event, true);
 			$this->clearSelectedIDs($event);
 
 			$dst_field = $this->Application->RecallVar('dst_field');
-			if ( $dst_field != 'PrimaryGroupId' ) {
-				return;
+			if ($dst_field != 'PrimaryGroupId') {
+				return ;
 			}
 
 			$group_ids = array_keys($this->Application->GetVar('g'));
 			$primary_group_id = $group_ids ? array_shift($group_ids) : false;
 
-			if ( !$user_ids || !$primary_group_id ) {
-				return;
+			if (!$user_ids || !$primary_group_id) {
+				return ;
 			}
 
-			$table_name = $this->Application->getUnitOption('ug', 'TableName');
+			$table_name = $this->Application->getUnitConfig('ug')->getTableName();
 
 			// 1. mark group as primary
 			$sql = 'UPDATE ' . TABLE_PREFIX . 'Users
 					SET PrimaryGroupId = ' . $primary_group_id . '
 					WHERE PortalUserId IN (' . implode(',', $user_ids) . ')';
 			$this->Conn->Query($sql);
 
 			$sql = 'SELECT PortalUserId
 					FROM ' . $table_name . '
 					WHERE (GroupId = ' . $primary_group_id . ') AND (PortalUserId IN (' . implode(',', $user_ids) . '))';
 			$existing_members = $this->Conn->GetCol($sql);
 
 			// 2. add new members to a group
 			$new_members = array_diff($user_ids, $existing_members);
 
 			foreach ($new_members as $user_id) {
 				$fields_hash = Array (
 					'GroupId' => $primary_group_id,
 					'PortalUserId' => $user_id,
 				);
 
 				$this->Conn->doInsert($fields_hash, $table_name);
 			}
 		}
 
 		/**
 		 * Loads user images
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			// linking existing images for item with virtual fields
 			$image_helper = $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			$object = $event->getObject();
 			/* @var $object UsersItem */
 
 			$image_helper->LoadItemImages($object);
 
 			$cs_helper = $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$cs_helper->PopulateStates($event, 'State', 'Country');
 
 			// get user subscription status
 			$object->SetDBField('SubscribeToMailing', $object->isSubscribed() ? 1 : 0);
 
 			if ( !$this->Application->isAdmin ) {
 				$object->SetFieldOption('FrontLanguage', 'options', $this->getEnabledLanguages());
 			}
 		}
 
 		/**
 		 * Returns list of enabled languages with their names
 		 *
 		 * @return Array
 		 * @access protected
 		 */
 		protected function getEnabledLanguages()
 		{
 			$cache_key = 'user_languages[%LangSerial%]';
 
 			$ret = $this->Application->getCache($cache_key);
 
 			if ( $ret === false ) {
 				$languages = $this->Application->recallObject('lang.enabled', 'lang_List');
 				/* @var $languages kDBList */
 
 				$ret = Array ();
 
 				foreach ($languages as $language_info) {
 					$ret[$languages->GetID()] = $language_info['LocalName'];
 				}
 
 				$this->Application->setCache($cache_key, $ret);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Save user images
 		 *
 		 * @param kEvent $event
 		 */
 		function saveUserImages($event)
 		{
 			if (!$this->Application->isAdmin) {
 				$image_helper = $this->Application->recallObject('ImageHelper');
 				/* @var $image_helper ImageHelper */
 
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				// process image upload in virtual fields
 				$image_helper->SaveItemImages($object);
 			}
 		}
 
 		/**
 		 * Makes password required for new users
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			parent::OnPreCreate($event);
 
 			if ( $event->status != kEvent::erSUCCESS ) {
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$user_type = $this->Application->GetVar('user_type');
 
 			if ( $user_type ) {
 				$object->SetDBField('UserType', $user_type);
 
 				if ( $user_type == UserType::ADMIN ) {
 					$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_AdminGroup'));
 				}
 			}
 
 			if ( $this->Application->ConfigValue('User_Password_Auto') ) {
 				$object->SetDBField('EmailPassword', 1);
 			}
 
 			$this->_makePasswordRequired($event);
 		}
 
 		/**
 		 * Makes password required for new users
 		 *
 		 * @param kEvent $event
 		 */
 		function _makePasswordRequired($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$required_fields = Array ('Password', 'Password_plain', 'VerifyPassword', 'VerifyPassword_plain');
 			$object->setRequired($required_fields);
 		}
 
 		/**
 		 * Load item if id is available
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function LoadItem(kEvent $event)
 		{
 			$id = $this->getPassedID($event);
 
 			if ( $id < 0 ) {
 				// when root, guest and so on
 
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$object->Clear($id);
 				return;
 			}
 
 			parent::LoadItem($event);
 		}
 
 		/**
 		 * Occurs just after login (for hooking)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterLogin($event)
 		{
 			if ( is_object($event->MasterEvent) && !$this->Application->isAdmin ) {
 				$event->MasterEvent->SetRedirectParam('login', 1);
 			}
 		}
 
 		/**
 		 * Occurs just before logout (for hooking)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnBeforeLogout($event)
 		{
 			if ( is_object($event->MasterEvent) && !$this->Application->isAdmin ) {
 				$event->MasterEvent->SetRedirectParam('logout', 1);
 			}
 		}
 
 		/**
 		 * Generates password
 		 *
 		 * @param kEvent $event
 		 */
 		function OnGeneratePassword($event)
 		{
 			$event->status = kEvent::erSTOP;
 
 			if ( $this->Application->isAdminUser ) {
 				echo kUtil::generatePassword();
 			}
 		}
 
 		/**
 		 * Changes user's password and logges him in
 		 *
 		 * @param kEvent $event
 		 */
 		function OnResetLostPassword($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$event->CallSubEvent('OnUpdate');
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				/* @var $user_helper UserHelper */
 				$user_helper = $this->Application->recallObject('UserHelper');
 
 			    $user =& $user_helper->getUserObject();
 				$user->Load( $object->GetID() );
 
 				if ( $user_helper->checkLoginPermission() ) {
 		    		$user_helper->loginUserById( $user->GetID() );
 				}
 			}
 		}
 
 		/**
 		 * Generates new Root password and email it
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnResetRootPassword($event)
 		{
 			$password_formatter = $this->Application->recallObject('kPasswordFormatter');
 			/* @var $password_formatter kPasswordFormatter */
 
 			$new_root_password = kUtil::generatePassword();
 
 			$this->Application->SetConfigValue('RootPass', $password_formatter->hashPassword($new_root_password));
 			$this->Application->emailAdmin('ROOT.RESET.PASSWORD', null, Array ('password' => $new_root_password));
 
 			$event->SetRedirectParam('reset', 1);
 			$event->SetRedirectParam('pass', 'm');
 		}
 
 		/**
 		 * Perform login of user, selected in Admin Console, on Front-End in a separate window
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnLoginAs(kEvent $event)
 		{
 			/* @var $user_helper UserHelper */
 			$user_helper = $this->Application->recallObject('UserHelper');
 
 			$user =& $user_helper->getUserObject();
 			$user->Load( $this->Application->GetVar('user_id') );
 
 			if ( !$user->isLoaded() ) {
 				return ;
 			}
 
 			if ( $user_helper->checkLoginPermission() ) {
 				$user_helper->loginUserById( $user->GetID() );
 			}
 		}
 	}
Index: branches/5.3.x/core/units/favorites/favorites_eh.php
===================================================================
--- branches/5.3.x/core/units/favorites/favorites_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/favorites/favorites_eh.php	(revision 15698)
@@ -1,111 +1,116 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class FavoritesEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnFavoriteToggle' => Array ('subitem' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Adds/removes item from favorites
 	 *
 	 * @param kEvent $event
 	 */
 	function OnFavoriteToggle($event)
 	{
-		$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
+		$config = $event->getUnitConfig();
+		$parent_prefix = $config->getParentPrefix();
+
 		$parent_object = $this->Application->recallObject($parent_prefix);
 		/* @var $parent_object kDBItem */
 
-		if (!$parent_object->isLoaded() || !$this->Application->CheckPermission('FAVORITES', 0, $parent_object->GetDBField('ParentPath'))) {
+		if ( !$parent_object->isLoaded() || !$this->Application->CheckPermission('FAVORITES', 0, $parent_object->GetDBField('ParentPath')) ) {
 			$event->status = kEvent::erPERM_FAIL;
-			return ;
+
+			return;
 		}
 
 		$user_id = $this->Application->RecallVar('user_id');
 		$sql = 'SELECT FavoriteId
-				FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
-				WHERE (PortalUserId = '.$user_id.') AND (ResourceId = '.$parent_object->GetDBField('ResourceId').')';
+				FROM ' . $config->getTableName() . '
+				WHERE (PortalUserId = ' . $user_id . ') AND (ResourceId = ' . $parent_object->GetDBField('ResourceId') . ')';
 		$favorite_id = $this->Conn->GetOne($sql);
 
-		$object = $event->getObject(Array('skip_autoload' => true));
+		$object = $event->getObject(Array ('skip_autoload' => true));
 		/* @var $object kDBItem */
 
-		if ($favorite_id) {
+		if ( $favorite_id ) {
 			$object->Delete($favorite_id);
 		}
 		else {
 			$object->Create();
 		}
 
-		$event->SetRedirectParam('pass', 'm,'.$parent_prefix);
+		$event->SetRedirectParam('pass', 'm,' . $parent_prefix);
 	}
 
 	/**
 	 * Prepares Favorite record fields
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemCreate(kEvent $event)
 	{
 		parent::OnBeforeItemCreate($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$user_id = $this->Application->RecallVar('user_id');
 		$object->SetDBField('PortalUserId', $user_id);
 
-		$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
+		$parent_prefix = $event->getUnitConfig()->getParentPrefix();
+
 		$parent_object = $this->Application->recallObject($parent_prefix);
 		/* @var $parent_object kDBItem */
 
 		$object->SetDBField('ResourceId', $parent_object->GetDBField('ResourceId'));
-		$object->SetDBField('ItemTypeId', $this->Application->getUnitOption($parent_prefix, 'ItemType'));
+		$object->SetDBField('ItemTypeId', $this->Application->getUnitConfig($parent_prefix)->getItemType());
 	}
 
 	/**
-	 * [HOOK] Deletes favorite record to item, that is beeing deleted
+	 * [HOOK] Deletes favorite record to item, that is being deleted
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDeleteFavoriteItem($event)
 	{
 		$main_object = $event->MasterEvent->getObject();
+		/* @var $main_object kDBItem */
 
-		$sql = 'DELETE FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
-				WHERE ResourceId = '.$main_object->GetDBField('ResourceId');
+		$sql = 'DELETE FROM ' . $event->getUnitConfig()->getTableName() . '
+				WHERE ResourceId = ' . $main_object->GetDBField('ResourceId');
 		$this->Conn->Query($sql);
 	}
 
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/files/file_tp.php
===================================================================
--- branches/5.3.x/core/units/files/file_tp.php	(revision 15697)
+++ branches/5.3.x/core/units/files/file_tp.php	(revision 15698)
@@ -1,37 +1,37 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class FileTagProcessor extends kDBTagProcessor {
 
 		function DownloadFileLink($params)
 		{
-			$params[$this->getPrefixSpecial().'_event'] = 'OnDownloadFile';
-			$params['pass'] = 'm,'.$this->Application->getUnitOption($this->Prefix, 'ParentPrefix').','.$this->getPrefixSpecial();
+			$params[$this->getPrefixSpecial() . '_event'] = 'OnDownloadFile';
+			$params['pass'] = 'm,' . $this->getUnitConfig()->getParentPrefix() . ',' . $this->getPrefixSpecial();
 
 			return $this->ItemLink($params);
 		}
 
 		function FileIcon($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$last_dot = mb_strrpos($object->GetDBField('FilePath'), '.');
 			$ext = ($last_dot !== false) ? mb_substr($object->GetDBField('FilePath'), $last_dot + 1).'.gif' : '';
 
 			return $ext ? $ext : $params['default'];
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/search_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/search_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/search_helper.php	(revision 15698)
@@ -1,805 +1,806 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kSearchHelper extends kHelper {
 
 		/**
 		 * Perform Exact Search flag
 		 *
 		 * @var bool
 		 * @access protected
 		 */
 		protected $_performExactSearch = true;
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			$this->_performExactSearch = $this->Application->ConfigValue('PerformExactSearch');
 		}
 
 		/**
 		 * Splits search phrase into keyword using quotes,plus and minus sings and spaces as split criteria
 		 *
 		 * @param string $keyword
 		 * @return Array
 		 * @access public
 		 */
 		public function splitKeyword($keyword)
 		{
 			if ( $this->Application->ConfigValue('CheckStopWords') ) {
 				$keyword_after_remove = $this->_removeStopWords($keyword);
 				if ( $keyword_after_remove ) {
 					// allow to search through stop word grid
 					$keyword = $keyword_after_remove;
 				}
 			}
 
 			$final = Array ();
 			$quotes_re = '/([+\-]?)"(.*?)"/';
 			$no_quotes_re = '/([+\-]?)([^ ]+)/';
 
 			preg_match_all($quotes_re, $keyword, $res);
 			foreach ($res[2] as $index => $kw) {
 				$final[$kw] = $res[1][$index];
 			}
 
 			$keyword = preg_replace($quotes_re, '', $keyword);
 			preg_match_all($no_quotes_re, $keyword, $res);
 
 			foreach ($res[2] as $index => $kw) {
 				$final[$kw] = $res[1][$index];
 			}
 
 			if ( $this->_performExactSearch ) {
 				foreach ($final AS $kw => $plus_minus) {
 					if ( !$plus_minus ) {
 						$final[$kw] = '+';
 					}
 				}
 			}
 
 			return $final;
 		}
 
 		function getPositiveKeywords($keyword)
 		{
 			$keywords = $this->splitKeyword($keyword);
 
 			$ret = Array();
 			foreach ($keywords as $keyword => $sign) {
 				if ($sign == '+' || $sign == '') {
 					$ret[] = $keyword;
 				}
 			}
 			return $ret;
 		}
 
 		/**
 		 * Replace wildcards to match MySQL
 		 *
 		 * @param string $keyword
 		 * @return string
 		 */
 		function transformWildcards($keyword)
 		{
 			return str_replace(Array ('%', '_', '*', '?') , Array ('\%', '\_', '%', '_'), $keyword);
 		}
 
 		function buildWhereClause($keyword, $fields)
 		{
 			$keywords = $this->splitKeyword( $this->transformWildcards($keyword) );
 
 			$normal_conditions = $plus_conditions = $minus_conditions = Array();
 
 			foreach ($keywords as $keyword => $sign) {
 				$keyword = $this->Conn->escape($keyword);
 
 				switch ($sign) {
 					case '+':
 						$plus_conditions[] = implode(" LIKE '%" . $keyword . "%' OR ", $fields) . " LIKE '%" . $keyword . "%'";
 						break;
 
 					case '-':
 						$condition = Array ();
 
 						foreach ($fields as $field) {
 							$condition[] = $field . " NOT LIKE '%" . $keyword . "%' OR " . $field . ' IS NULL';
 						}
 						$minus_conditions[] = '(' . implode(') AND (', $condition) . ')';
 						break;
 
 					case '':
 						$normal_conditions[] = implode(" LIKE '%" . $keyword . "%' OR ", $fields) . " LIKE '%" . $keyword . "%'";
 						break;
 				}
 			}
 
 			// building where clause
 			if ($normal_conditions) {
 				$where_clause = '(' . implode(') OR (', $normal_conditions) . ')';
 			}
 			else {
 				$where_clause = '1';
 			}
 
 			if ($plus_conditions) {
 				$where_clause = '(' . $where_clause . ') AND (' . implode(') AND (', $plus_conditions) . ')';
 			}
 
 			if ($minus_conditions) {
 				$where_clause = '(' . $where_clause . ') AND (' . implode(') AND (', $minus_conditions) . ')';
 			}
 
 			return $where_clause;
 		}
 
 		/**
 		 * Returns additional information about search field
 		 *
 		 * @param kDBList $object
 		 * @param string $field_name
 		 * @return Array
 		 */
 		function _getFieldInformation(&$object, $field_name)
 		{
 			$sql_filter_type = $object->isVirtualField($field_name) ? 'having' : 'where';
 			$field_options = $object->GetFieldOptions($field_name);
 
 			$table_name = '';
 			$field_type = isset($field_options['type']) ? $field_options['type'] : 'string';
 
 			if (preg_match('/(.*)\.(.*)/', $field_name, $regs)) {
 				$table_name = '`'.$regs[1].'`.'; // field from external table
 				$field_name = $regs[2];
 			}
 			elseif ($sql_filter_type == 'where') {
 				$table_name = '`'.$object->TableName.'`.'; // field from local table
 			}
 
 			$table_name = ($sql_filter_type == 'where') ? $table_name : '';
 
 			// replace wid inside table name to WID_MARK constant value
 			$is_temp_table = preg_match('/(.*)'.TABLE_PREFIX.'ses_'.$this->Application->GetSID().'(_[\d]+){0,1}_edit_(.*)/', $table_name, $regs);
 			if ($is_temp_table) {
 				$table_name = $regs[1].TABLE_PREFIX.'ses_'.EDIT_MARK.'_edit_'.$regs[3]; // edit_mark will be replaced with sid[_main_wid] in AddFilters
 			}
 
 			return Array ($field_name, $field_type, $table_name, $sql_filter_type);
 		}
 
 		/**
 		 * Removes stop words from keyword
 		 *
 		 * @param string $keyword
 		 * @return string
 		 */
 		function _removeStopWords($keyword)
 		{
 			static $stop_words = Array ();
 
 			if (!$stop_words) {
 				$sql = 'SELECT StopWord
-						FROM ' . $this->Application->getUnitOption('stop-word', 'TableName') . '
+						FROM ' . $this->Application->getUnitConfig('stop-word')->getTableName() . '
 						ORDER BY LENGTH(StopWord) DESC, StopWord ASC';
 				$stop_words = $this->Conn->GetCol($sql);
 
 				foreach ($stop_words as $index => $stop_word) {
 					$stop_words[$index] = '/(^| )' . preg_quote($stop_word, '/') . '( |$)/';
 				}
 			}
 
 			$keyword = preg_replace($stop_words, ' ', $keyword);
 
 			return trim( preg_replace('/[ ]+/', ' ', $keyword) );
 		}
 
 		/**
 		 * Performs new search on a given grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access public
 		 */
 		public function performSearch($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// process search keyword
 			$search_keyword = $this->Application->GetVar($event->getPrefixSpecial(true) . '_search_keyword');
 			$this->Application->StoreVar($event->getPrefixSpecial() . '_search_keyword', $search_keyword);
 
 			$custom_filter = $this->processCustomFilters($event);
 
 			if ( !$search_keyword && $custom_filter === false ) {
 				$this->resetSearch($event);
 				return ;
 			}
 
 			if ( $search_keyword ) {
 				$this->processAutomaticFilters($event, $search_keyword, $custom_filter);
 			}
 		}
 
 		/**
 		 * Creates filtering sql clauses based on given search restrictions
 		 *
 		 * @param kEvent $event
 		 * @param string $search_keyword
 		 * @param Array $custom_filter
 		 * @return void
 		 */
 		function processAutomaticFilters($event, $search_keyword, $custom_filter)
 		{
 			$grid_name = $this->Application->GetVar('grid_name');
-			$grids = $this->Application->getUnitOption($event->Prefix, 'Grids');
-			$search_fields = array_keys($grids[$grid_name]['Fields']);
+			$grid = $event->getUnitConfig()->getGridByName($grid_name);
+			$search_fields = array_keys($grid['Fields']);
 
 			$search_filter = Array();
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			foreach ($search_fields as $search_field) {
 				$custom_search = isset($custom_filter[$search_field]);
 
 				$filter_data = $this->getSearchClause($object, $search_field, $search_keyword, $custom_search);
 
 				if ($filter_data) {
 					$search_filter[$search_field] = $filter_data;
 				}
 				else {
 					unset($search_filter[$search_field]);
 				}
 			}
 			$this->Application->StoreVar($event->getPrefixSpecial().'_search_filter', serialize($search_filter) );
 		}
 
 		/**
 		 * Returns search clause for any particular field
 		 *
 		 * @param kDBList $object
 		 * @param string $field_name
 		 * @param string $search_keyword what we are searching (false, when building custom filter clause)
 		 * @param string $custom_search already found using custom filter
 		 * @return Array
 		 */
 		function getSearchClause(&$object, $field_name, $search_keyword, $custom_search)
 		{
 			if ($object->isVirtualField($field_name) && !$object->isCalculatedField($field_name)) {
 				// Virtual field, that is shown in grid, but it doesn't have corresponding calculated field.
 				// Happens, when field value is calculated on the fly (during grid display) and it is not searchable.
 				return '';
 			}
 
 			$search_keywords = $this->splitKeyword($search_keyword);
 
 			list ($field_name, $field_type, $table_name, $sql_filter_type) = $this->_getFieldInformation($object, $field_name);
 
 			$filter_value = '';
 
 			// get field clause by formatter name and/or parameters
 			$field_options = $object->GetFieldOptions($field_name);
 			$formatter = getArrayValue($field_options, 'formatter');
 
 			switch ($formatter) {
 				case 'kOptionsFormatter':
 					$search_keys = Array();
 
 					if ($custom_search === false) {
 						// if keywords passed through simple search filter (on each grid)
 						$use_phrases = getArrayValue($field_options, 'use_phrases');
 						$multiple = array_key_exists('multiple', $field_options) && $field_options['multiple'];
 
 						foreach ($field_options['options'] as $key => $val) {
 							$match_to = mb_strtolower($use_phrases ? $this->Application->Phrase($val) : $val);
 
 							foreach ($search_keywords as $keyword => $sign) {
 								// doesn't support wildcards
 								if (strpos($match_to, mb_strtolower($keyword)) === false) {
 									if ($sign == '+') {
 										$filter_value = $table_name.'`'.$field_name.'` = NULL';
 										break;
 									}
 									else {
 										continue;
 									}
 								}
 
 								if ($sign == '+' || $sign == '') {
 									// don't add single quotes to found option ids when multiselect (but escape string anyway)
 									$search_keys[$key] = $multiple ? $this->Conn->escape($key) : $this->Conn->qstr($key);
 								}
 								elseif($sign == '-') {
 									// if same value if found as exclusive too, then remove from search result
 									unset($search_keys[$key]);
 								}
 							}
 						}
 					}
 
 					if ($search_keys) {
 						if ($multiple) {
 							$filter_value = $table_name.'`'.$field_name.'` LIKE "%|' . implode('|%" OR ' . $table_name.'`'.$field_name.'` LIKE "%|', $search_keys) . '|%"';
 						}
 						else {
 							$filter_value = $table_name.'`'.$field_name.'` IN ('.implode(',', $search_keys).')';
 						}
 					}
 
 					$field_processed = true;
 					break;
 
 				case 'kDateFormatter':
 					// if date is searched using direct filter, then do nothing here, otherwise search using LIKE clause
 					$field_processed = ($custom_search !== false) ? true : false;
 					break;
 
 				default:
 					$field_processed = false;
 					break;
 			}
 
 			// if not already processed by formatter, then get clause by field type
 			if (!$field_processed && $search_keywords) {
 				switch($field_type)
 				{
 					case 'int':
 					case 'integer':
 					case 'numeric':
 						$search_keys = Array();
 						foreach ($search_keywords as $keyword => $sign) {
 							if (!is_numeric($keyword) || ($sign == '-')) {
 								continue;
 							}
 							$search_keys[] = $this->Conn->qstr($keyword);
 						}
 
 						if ($search_keys) {
 							$filter_value = $table_name.'`'.$field_name.'` IN ('.implode(',', $search_keys).')';
 						}
 						break;
 
 					case 'double':
 					case 'float':
 					case 'real':
 						$search_keys = Array();
 						foreach ($search_keywords as $keyword => $sign) {
 							$keyword = str_replace(',', '.', $keyword);
 							if (!is_numeric($keyword) || ($sign == '-')) continue;
 							$search_keys[] = 'ABS('.$table_name.'`'.$field_name.'` - '.$this->Conn->qstr($keyword).') <= 0.0001';
 						}
 
 						if ($search_keys) {
 							$filter_value = '('.implode(') OR (', $search_keys).')';
 						}
 						break;
 
 					case 'string':
 						$filter_value = $this->buildWhereClause($search_keyword, Array($table_name.'`'.$field_name.'`'));
 						break;
 				}
 			}
 
 			if ($filter_value) {
 				return Array('type' => $sql_filter_type, 'value' => $filter_value);
 			}
 
 			return false;
 		}
 
 		/**
 		 * Processes custom filters from submit
 		 *
 		 * @param kEvent $event
 		 * @return Array|bool
 		 */
 		function processCustomFilters($event)
 		{
 			$grid_name = $this->Application->GetVar('grid_name');
 
 			// update "custom filter" with values from submit: begin
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view');
 			$custom_filters = $this->Application->RecallPersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name/*, ALLOW_DEFAULT_SETTINGS*/);
 			if ($custom_filters) {
 				$custom_filters = unserialize($custom_filters);
 				$custom_filter = isset($custom_filters[$grid_name]) ? $custom_filters[$grid_name] : Array ();
 			}
 			else {
 				$custom_filter = Array ();
 			}
 
 			// submit format custom_filters[prefix_special][field]
 			$submit_filters = $this->Application->GetVar('custom_filters');
 			if ($submit_filters) {
 				$submit_filters = getArrayValue($submit_filters, $event->getPrefixSpecial(), $grid_name);
 				if ($submit_filters) {
 					foreach ($submit_filters as $field_name => $field_options) {
 						list ($filter_type, $field_value) = each($field_options);
 						$is_empty = strlen(is_array($field_value) ? implode('', $field_value) : $field_value) == 0;
 						if ($is_empty) {
 							if (isset($custom_filter[$field_name])) {
 								// use isset, because non-existing key will cause "php notice"!
 								unset($custom_filter[$field_name][$filter_type]); // remove filter
 
 								if (!$custom_filter[$field_name]) {
 									// if no filters left for field, then delete record at all
 									unset($custom_filter[$field_name]);
 								}
 							}
 						}
 						else {
 							$custom_filter[$field_name][$filter_type]['submit_value'] = $field_value;
 						}
 					}
 				}
 			}
 
 			if ($custom_filter) {
 				$custom_filters[$grid_name] = $custom_filter;
 			}
 			else {
 				unset($custom_filters[$grid_name]);
 			}
 			// update "custom filter" with values from submit: end
 
 			if (!$custom_filter) {
 				// in case when no filters specified, there are nothing to process
 				$this->Application->StorePersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name, serialize($custom_filters) );
 				return false;
 			}
 
 			$object = $event->getObject(); // don't recall it each time in getCustomFilterSearchClause
-			$grid_info = $this->Application->getUnitOption($event->Prefix.'.'.$grid_name, 'Grids');
+			$grid_info = $event->getUnitConfig()->getGridByName($grid_name);
+
 			foreach ($custom_filter as $field_name => $field_options) {
 				list ($filter_type, $field_options) = each($field_options);
 				$field_options['grid_options'] = $grid_info['Fields'][$field_name];
 				$field_options = $this->getCustomFilterSearchClause($object, $field_name, $filter_type, $field_options);
 				if ($field_options['value']) {
 					unset($field_options['grid_options']);
 					$custom_filter[$field_name][$filter_type] = $field_options;
 				}
 			}
 
 			$custom_filters[$grid_name] = $custom_filter;
 			$this->Application->StorePersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name, serialize($custom_filters) );
 			return $custom_filter;
 		}
 
 		/**
 		 * Checks, that range filters "To" part is defined for given grid
 		 *
 		 * @param string $prefix_special
 		 * @param string $grid_name
 		 * @return bool
 		 */
 		function rangeFiltersUsed($prefix_special, $grid_name)
 		{
 			static $cache = Array ();
 			$cache_key = $prefix_special . $grid_name;
 
 			if (array_key_exists($cache_key, $cache)) {
 				return $cache[$cache_key];
 			}
 
 			$view_name = $this->Application->RecallVar($prefix_special . '_current_view');
 			$custom_filters = $this->Application->RecallPersistentVar($prefix_special . '_custom_filter.' . $view_name/*, ALLOW_DEFAULT_SETTINGS*/);
 			if (!$custom_filters) {
 				// filters not defined for given prefix
 				$cache[$cache_key] = false;
 				return false;
 			}
 
 			$custom_filters = unserialize($custom_filters);
 			if (!is_array($custom_filters) || !array_key_exists($grid_name, $custom_filters)) {
 				// filters not defined for given grid
 				$cache[$cache_key] = false;
 				return false;
 			}
 
 			$range_filter_defined = false;
 
 			$custom_filter = $custom_filters[$grid_name];
 			foreach ($custom_filter as $field_name => $field_options) {
 				list ($filter_type, $field_options) = each($field_options);
 
 				if (strpos($filter_type, 'range') === false) {
 					continue;
 				}
 
 				$to_value = (string)$field_options['submit_value']['to'];
 				if ($to_value !== '') {
 					$range_filter_defined = true;
 					break;
 				}
 			}
 
 			$cache[$cache_key] = $range_filter_defined;
 			return $range_filter_defined;
 		}
 
 		/**
 		 * Return numeric range filter value + checking that it's number
 		 *
 		 * @param Array $value array containing range filter value
 		 * @return unknown
 		 */
 		function getRangeValue($value)
 		{
 			// fix user typing error, since MySQL only sees "." as decimal separator
 			$value = str_replace(',', '.', $value);
 
 			return strlen($value) && is_numeric($value) ? $this->Conn->qstr($value) : false;
 		}
 
 		/**
 		 * Returns filter clause
 		 *
 		 * @param kDBItem $object
 		 * @param string $field_name
 		 * @param string $filter_type
 		 * @param Array $field_options
 		 * @return Array
 		 */
 		function getCustomFilterSearchClause(&$object, $field_name, $filter_type, $field_options)
 		{
 			// this is usually used for mutlilingual fields and date fields
 			if (isset($field_options['grid_options']['sort_field'])) {
 				$field_name = $field_options['grid_options']['sort_field'];
 			}
 			list ($field_name, $field_type, $table_name, $sql_filter_type) = $this->_getFieldInformation($object, $field_name);
 
 			$filter_value = '';
 
 			switch ($filter_type) {
 				case 'range':
 					$from = $this->getRangeValue($field_options['submit_value']['from']);
 					$to = $this->getRangeValue($field_options['submit_value']['to']);
 
 					if ( $from !== false && $to !== false ) {
 						// add range filter
 						$filter_value = $table_name . '`' . $field_name . '` >= ' . $from . ' AND ' . $table_name . '`' . $field_name . '` <= ' . $to;
 					}
 					elseif ( $field_type == 'int' || $field_type == 'integer' ) {
 						if ( $from !== false ) {
 							// add equals filter on $from
 							$filter_value = $table_name . '`' . $field_name . '` = ' . $from;
 						}
 						elseif ( $to !== false ) {
 							// add equals filter on $to
 							$filter_value = $table_name . '`' . $field_name . '` = ' . $to;
 						}
 					}
 					else {
 						// MySQL can't compare values in "float" type columns using "=" operator
 						if ( $from !== false ) {
 							// add equals filter on $from
 							$filter_value = 'ABS(' . $table_name . '`' . $field_name . '` - ' . $from . ') <= 0.0001';
 						}
 						elseif ( $to !== false ) {
 							// add equals filter on $to
 							$filter_value = 'ABS(' . $table_name . '`' . $field_name . '` - ' . $to . ') <= 0.0001';
 						}
 					}
 					break;
 
 				case 'date_range':
 					$from = $this->processRangeField($object, $field_name, $field_options['submit_value'], 'from');
 					$to = $this->processRangeField($object, $field_name, $field_options['submit_value'], 'to');
 
 					$day_seconds = 23 * 60 * 60 + 59 * 60 + 59;
 					if ($from !== false && $to === false) {
 						$from = strtotime(date('Y-m-d', $from) . ' 00:00:00', $from); // reset to morning
 						$to = $from + $day_seconds;
 					}
 					elseif ($from === false && $to !== false) {
 						$to = strtotime(date('Y-m-d', $to) . ' 23:59:59', $to); // reset to evening
 						$from = $to - $day_seconds;
 					}
 
 					if ($from !== false && $to !== false) {
 						$filter_value = $table_name.'`'.$field_name.'` >= '.$from.' AND '.$table_name.'`'.$field_name.'` <= '.$to;
 					}
 					break;
 
 				case 'equals':
 				case 'options':
 					$field_value = strlen($field_options['submit_value']) ? $this->Conn->qstr($field_options['submit_value']) : false;
 					if ($field_value) {
 						$filter_value = $table_name.'`'.$field_name.'` = '.$field_value;
 					}
 					break;
 
 				case 'picker':
 					$field_value = strlen($field_options['submit_value']) ? $this->Conn->escape($field_options['submit_value']) : false;
 					if ($field_value) {
 						$filter_value = $table_name.'`'.$field_name.'` LIKE "%|'.$field_value.'|%"';
 					}
 					break;
 
 				case 'multioptions':
 					$field_value = $field_options['submit_value'];
 
 					if ( $field_value ) {
 						$field_value = explode('|', substr($field_value, 1, -1));
 						$multiple = $object->GetFieldOption($field_name, 'multiple');
 						$field_value = $this->Conn->qstrArray($field_value, $multiple ? 'escape' : 'qstr');
 
 						if ( $multiple ) {
 							$filter_value = $table_name . '`' . $field_name . '` LIKE "%|' . implode('|%" OR ' . $table_name . '`' . $field_name . '` LIKE "%|', $field_value) . '|%"';
 						}
 						else {
 							$filter_value = $table_name . '`' . $field_name . '` IN (' . implode(',', $field_value) . ')';
 						}
 					}
 					break;
 
 				case 'like':
 					$filter_value = $this->buildWhereClause($field_options['submit_value'], Array($table_name.'`'.$field_name.'`'));
 					break;
 
 				default:
 					break;
 			}
 
 			$field_options['sql_filter_type'] = $sql_filter_type;
 			$field_options['value'] = $filter_value;
 
 			return $field_options;
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kdbItem $object
 		 * @param string $search_field
 		 * @param string $value
 		 * @param string $type
 		 */
 		function processRangeField(&$object, $search_field, $value, $type)
 		{
 			if ( !strlen($value[$type]) ) {
 				return false;
 			}
 
 			$options = $object->GetFieldOptions($search_field);
 			$dt_separator = array_key_exists('date_time_separator', $options) ? $options['date_time_separator'] : ' ';
 			$value[$type] = trim($value[$type], $dt_separator); // trim any
 
 			$tmp_value = explode($dt_separator, $value[$type], 2);
 			if ( count($tmp_value) == 1 ) {
 				$time_format = $this->_getInputTimeFormat($options);
 				if ( $time_format ) {
 					// time is missing, but time format available -> guess time and add to date
 					$time = ($type == 'from') ? adodb_mktime(0, 0, 0) : adodb_mktime(23, 59, 59);
 					$time = adodb_date($time_format, $time);
 					$value[$type] .= $dt_separator . $time;
 				}
 			}
 
 			$formatter = $this->Application->recallObject($options['formatter']);
 			/* @var $formatter kFormatter */
 
 			$value_ts = $formatter->Parse($value[$type], $search_field, $object);
 
 			if ( $object->GetErrorPseudo($search_field) ) {
 				// invalid format -> ignore this date in search
 				$object->RemoveError($search_field);
 
 				return false;
 			}
 
 			return $value_ts;
 		}
 
 		/**
 		 * Returns InputTimeFormat using given field options
 		 *
 		 * @param Array $field_options
 		 * @return string
 		 */
 		function _getInputTimeFormat($field_options)
 		{
 			if ( array_key_exists('input_time_format', $field_options) ) {
 				return $field_options['input_time_format'];
 			}
 
 			$lang_current = $this->Application->recallObject('lang.current');
 			/* @var $lang_current LanguagesItem */
 
 			return $lang_current->GetDBField('InputTimeFormat');
 		}
 
 		/**
 		 * Resets current search
 		 *
 		 * @param kEvent $event
 		 */
 		function resetSearch($event)
 		{
 			$this->Application->RemoveVar($event->getPrefixSpecial().'_search_filter');
 			$this->Application->RemoveVar($event->getPrefixSpecial().'_search_keyword');
 
 			$view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view');
 			$this->Application->RemovePersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name);
 		}
 
 		/**
 		 * Creates filters based on "types" & "except" parameters from PrintList
 		 *
 		 * @param kEvent $event
 		 * @param Array $type_clauses
 		 * @param string $types
 		 * @param string $except_types
 		 */
 		function SetComplexFilter($event, &$type_clauses, $types, $except_types)
 		{
 			$includes_or_filter = $this->Application->makeClass('kMultipleFilter', Array (kDBList::FLT_TYPE_OR));
 			/* @var $includes_or_filter kMultipleFilter */
 
 			$excepts_and_filter = $this->Application->makeClass('kMultipleFilter', Array (kDBList::FLT_TYPE_AND));
 			/* @var $excepts_and_filter kMultipleFilter */
 
 			$includes_or_filter_h = $this->Application->makeClass('kMultipleFilter', Array (kDBList::FLT_TYPE_OR));
 			/* @var $includes_or_filter_h kMultipleFilter */
 
 			$excepts_and_filter_h = $this->Application->makeClass('kMultipleFilter', Array (kDBList::FLT_TYPE_AND));
 			/* @var $excepts_and_filter_h kMultipleFilter */
 
 			if ( $types ) {
 				$types = explode(',', $types);
 
 				foreach ($types as $type) {
 					$type = trim($type);
 
 					if ( isset($type_clauses[$type]) ) {
 						if ( $type_clauses[$type]['having_filter'] ) {
 							$includes_or_filter_h->addFilter('filter_' . $type, $type_clauses[$type]['include']);
 						}
 						else {
 							$includes_or_filter->addFilter('filter_' . $type, $type_clauses[$type]['include']);
 						}
 					}
 				}
 			}
 
 			if ( $except_types ) {
 				$except_types = explode(',', $except_types);
 				foreach ($except_types as $type) {
 					$type = trim($type);
 
 					if ( isset($type_clauses[$type]) ) {
 						if ( $type_clauses[$type]['having_filter'] ) {
 							$excepts_and_filter_h->addFilter('filter_' . $type, $type_clauses[$type]['except']);
 						}
 						else {
 							$excepts_and_filter->addFilter('filter_' . $type, $type_clauses[$type]['except']);
 						}
 					}
 				}
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$object->addFilter('includes_filter', $includes_or_filter);
 			$object->addFilter('excepts_filter', $excepts_and_filter);
 
 			$object->addFilter('includes_filter_h', $includes_or_filter_h, kDBList::HAVING_FILTER);
 			$object->addFilter('excepts_filter_h', $excepts_and_filter_h, kDBList::HAVING_FILTER);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/col_picker_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/col_picker_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/col_picker_helper.php	(revision 15698)
@@ -1,298 +1,301 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kColumnPickerHelper extends kHelper {
 
 	var $PickerData;
 	var $GridName;
 	var $UseFreezer;
 
 	/**
 	 * Default column width in pixels
 	 *
 	 * @var int
 	 */
 	var $defaultColumnWidth = 100;
 
 	/**
 	 * Columns renamed by formatter in last used grid
 	 *
 	 * @var Array
 	 */
 	var $formatterRenamed = Array ();
 
 	public function __construct()
 	{
 		parent::__construct();
 
 		$this->UseFreezer = $this->Application->ConfigValue('UseColumnFreezer');
 	}
 
 	function LoadColumns($prefix)
 	{
 		$default_value = $this->Application->isAdmin ? ALLOW_DEFAULT_SETTINGS : false;
 		$val = $this->Application->RecallPersistentVar($this->_getVarName($prefix, 'get'), $default_value);
 
 		if (!$val) {
 			$cols = $this->RebuildColumns($prefix);
 		}
 		else {
 			$cols = unserialize($val);
 			$current_cols = $this->GetColumns($prefix);
 
 			if ($cols === false || $cols['crc'] != $current_cols['crc'])
 			{
 				$cols = $this->RebuildColumns($prefix, $cols);
 			}
 		}
 		return $cols;
 	}
 
 	function PreparePicker($prefix, $grid_name)
 	{
 		$this->SetGridName($grid_name);
 		$this->PickerData = $this->LoadColumns($prefix);
 	}
 
 	function ApplyPicker($prefix, &$fields, $grid_name)
 	{
 		$this->PreparePicker($prefix, $grid_name);
 		uksort($fields, array($this, 'CmpElems'));
 		$this->RemoveHiddenColumns($fields);
 	}
 
 	function SetGridName($grid_name)
 	{
 		$this->GridName = $grid_name;
 	}
 
 	function CmpElems($a, $b)
 	{
 		// remove language prefix from field, because formatter renamed column
 		if (in_array($a, $this->formatterRenamed)) {
 			$a = preg_replace('/^l[\d]+_/', '', $a);
 		}
 
 		if (in_array($b, $this->formatterRenamed)) {
 			$b = preg_replace('/^l[\d]+_/', '', $b);
 		}
 
 		$a_index = array_search($a, $this->PickerData['order']);
 		$b_index = array_search($b, $this->PickerData['order']);
 
 		if ($a_index == $b_index) {
 	        return 0;
 	    }
 
 	    return ($a_index < $b_index) ? -1 : 1;
 	}
 
 	function RebuildColumns($prefix, $current=null)
 	{
 		// get default columns from unit config
 		$cols = $this->GetColumns($prefix);
 
 		if (is_array($current)) {
 			// 1. prepare visible columns
 
 			// keep user column order (common columns between user and default grid)
 			$common = array_intersect($current['order'], $cols['order']);
 
 			// get new columns (found in default grid, but not found in user's grid)
 			$added = array_diff($cols['order'], $current['order']);
 
 			if (in_array('__FREEZER__', $added)) {
 				// in case if freezer was added, then make it first column
 				array_unshift($common, '__FREEZER__');
 				unset($added[array_search('__FREEZER__', $added)]);
 			}
 
 			$cols['order'] = array_merge($common, $added);
 
 			// 2. prepare hidden columns
 			if ($added) {
 				$hidden_added = array_intersect($added, $cols['hidden_fields']);
 				$cols['hidden_fields'] = array_intersect($current['order'], $current['hidden_fields']);
 
 				// when some of new columns are hidden, then keep them hidden
 				foreach ($hidden_added as $position => $field) {
 					$cols['hidden_fields'][$position] = $field;
 				}
 			}
 			else {
 				$cols['hidden_fields'] = array_intersect($current['order'], $current['hidden_fields']);
 			}
 
 			foreach($common as $col) {
 				$cols['widths'][$col] = isset($current['widths'][$col]) ? $current['widths'][$col] : $this->defaultColumnWidth;
 			}
 			$this->SetCRC($cols);
 		}
 		$this->StoreCols($prefix, $cols);
 
 		return $cols;
 	}
 
 	function StoreCols($prefix, $cols)
 	{
 		$this->Application->StorePersistentVar($this->_getVarName($prefix, 'set'), serialize($cols));
 	}
 
 	/**
 	* Gets variable name in persistent session to store column positions in
 	*
 	* @param string $prefix
 	* @param string $mode
 	* @return string
 	*/
 	function _getVarName($prefix, $mode = 'get')
 	{
 		$view_name = $this->Application->RecallVar($prefix . '_current_view');
 
 		$ret = $prefix . '[' . $this->GridName . ']columns_.' . $view_name;
 		if ($mode == 'get') {
 			if ($this->Application->RecallPersistentVar($ret) === false) {
 				$ret = $prefix . '_columns_.' . $view_name;
 			}
 		}
 
 		return $ret;
 	}
 
 	function GetColumns($prefix)
 	{
 		$splited = $this->Application->processPrefix($prefix);
-		$grids = $this->Application->getUnitOption($splited['prefix'], 'Grids');
-		$conf_fields = $this->UseFreezer ? array_merge_recursive(
-			array('__FREEZER__' => array('title' => '__FREEZER__')),
-			$grids[$this->GridName]['Fields']
-		) : $grids[$this->GridName]['Fields'];
-//		$conf_fields = $grids[$this->GridName]['Fields'];
+		$grid = $this->Application->getUnitConfig($splited['prefix'])->getGridByName($this->GridName);
+
+		if ( $this->UseFreezer ) {
+			$freezer_column = Array ('__FREEZER__' => Array ('title' => '__FREEZER__'));
+			$conf_fields = array_merge_recursive($freezer_column, $grid['Fields']);
+		}
+		else {
+			$conf_fields = $grid['Fields'];
+		}
 
 		// we NEED to recall dummy here to apply fields changes imposed by formatters,
 		// such as replacing multilingual field titles etc.
 		$dummy = $this->Application->recallObject($prefix, null, Array ('skip_autoload' => 1));
 
 		$counter = 0;
 		$hidden = array();
 		$fields = array();
 		$titles = array();
 		$widths = array();
 		foreach ($conf_fields as $name => $options) {
 			if (array_key_exists('formatter_renamed', $options) && $options['formatter_renamed']) {
 				// remove language prefix from field, because formatter renamed column
 				$this->formatterRenamed[] = $name;
 				$name = preg_replace('/^l[\d]+_/', '', $name);
 			}
 
 			$fields[$counter] = $name;
 			$titles[$name] = isset($options['title']) ? $options['title'] : 'column:la_fld_' . $name;
 			$widths[$name] = isset($options['width']) ? $options['width'] : $this->defaultColumnWidth; // only once per grid !
 
 			if (isset($options['hidden']) && $options['hidden']) {
 				$hidden[$counter] = $name;
 			}
 
 			$counter++;
 		}
 		$sorted_fields = $fields;
 		sort($sorted_fields);
 		$cols = array(
 			'order' => $fields,
 			'titles' => $titles,
 			'hidden_fields' => $hidden,
 			'widths' => $widths,
 		);
 		$this->SetCRC($cols);
 		return $cols;
 	}
 
 	function SetCRC(&$cols)
 	{
 		$sorted_fields = $cols['order'];
 		$sorted_titles = $cols['titles'];
 		asort($sorted_fields);
 		asort($sorted_titles);
 		$cols['crc'] = crc32(implode(',', $sorted_fields).implode(',', $sorted_titles));
 	}
 
 	function RemoveHiddenColumns(&$fields)
 	{
 		$to_remove = array();
 		foreach ($fields as $name => $options) {
 			if (array_key_exists('formatter_renamed', $options) && $options['formatter_renamed']) {
 				// remove language prefix from field, because formatter renamed column
 				$name_renamed = preg_replace('/^l[\d]+_/', '', $name);
 			}
 			else {
 				$name_renamed = $name;
 			}
 
 			if (array_search($name_renamed, $this->PickerData['hidden_fields']) !== false) {
 				$to_remove[] = $name;
 			}
 		}
 
 		foreach ($to_remove as $name) {
 			unset($fields[$name]);
 		}
 	}
 
 	function SaveColumns($prefix, $picked, $hidden)
 	{
 		$order = $picked ? explode('|', $picked) : array();
 		$hidden = $hidden ? explode('|', $hidden) : array();
 		$order = array_merge($order, $hidden);
 
 		$cols = $this->LoadColumns($prefix);
 		$cols['order'] = $order;
 		$cols['hidden_fields'] = $hidden;
 
 		$this->SetCRC($cols);
 		$this->StoreCols($prefix, $cols);
 	}
 
 	function SaveWidths($prefix, $widths)
 	{
 		if (!is_array($widths)) {
 			$widths = explode(':', $widths);
 		}
 
 		$i = 0;
 		array_shift($widths); // removing first col (checkbox col) width
 
 		foreach ($this->PickerData['order'] as $ord => $field) {
 			if ($field == '__FREEZER__') {
 				continue;
 			}
 
 			$this->PickerData['widths'][$field] = isset($widths[$i]) ? $widths[$i] : $this->defaultColumnWidth;
 			$i++;
 		}
 
 		$this->StoreCols($prefix, $this->PickerData);
 	}
 
 	function GetWidth($field)
 	{
 		if (in_array($field, $this->formatterRenamed)) {
 			// remove language prefix from field, because formatter renamed column
 			$field = preg_replace('/^l[\d]+_/', '', $field);
 		}
 
 		return isset($this->PickerData['widths'][$field]) ? $this->PickerData['widths'][$field] : false;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/mailing_list_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/mailing_list_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/mailing_list_helper.php	(revision 15698)
@@ -1,315 +1,316 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class MailingListHelper extends kHelper {
 
 		var $_mailingId = false;
 
 		/**
 		 * Adds new email from given mailing to emails queue
 		 *
 		 * @param string $email
 		 * @param int $mailing_id
 		 * @param Array $mailing_data
 		 */
 		function queueEmail($email, $mailing_id, &$mailing_data)
 		{
 			$esender = $this->Application->recallObject('EmailSender');
 			/* @var $esender kEmailSendingHelper */
 
 			if ($this->_mailingId != $mailing_id) {
 				if (is_numeric($this->_mailingId)) {
 					// clear fields after previous mailing processing
 					$esender->Clear();
 				}
 
 				// 1. set headers same for all emails
 				list ($mailing_data['FromName'], $mailing_data['FromEmail']) = $this->_getSenderData($mailing_data);
 				$esender->SetFrom($mailing_data['FromEmail'], $mailing_data['FromName']);
 
 				$esender->SetSubject($mailing_data['Subject']);
 				if ( !$mailing_data['MessageText'] ) {
 					$mailing_data['MessageText'] = $esender->ConvertToText($mailing_data['MessageHtml']);
 				}
 
 				$esender->SetBody($mailing_data['MessageHtml'], $mailing_data['MessageText']);
 
 		    	// 2. add attachment if any
 		    	$attachments = $mailing_data['Attachments'] ? explode('|', $mailing_data['Attachments']) : Array ();
 
 				foreach ($attachments as $attachment) {
 					$esender->AddAttachment(FULL_PATH . ITEM_FILES_PATH . $attachment);
 				}
 
 				$this->_mailingId = $mailing_id;
 			}
 
 			// 3. set recipient specific fields
 			$esender->SetTo($email, $email);
 
 			if ( $this->Application->ConfigValue('EnableEmailLog') ) {
 				// 4. write to log
 				$log_fields_hash = Array (
 					'From' => $mailing_data['FromName'] . '(' . $mailing_data['FromEmail'] . ')',
 					'To' => $email,
 					'Subject' => $mailing_data['Subject'],
 					'HtmlBody' => $mailing_data['MessageHtml'],
 					'TextBody' => $mailing_data['MessageText'],
 					'SentOn' => TIMENOW,
 					'EventParams' => serialize( Array ('MailingId' => $mailing_id) ),
 				);
 
 				$esender->setLogData($log_fields_hash);
 			}
 
 			$esender->Deliver(null, $mailing_id, false);
 		}
 
 		/**
 		 * Returns mass mail sender name & email
 		 *
 		 * @param Array $mailing_data
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getSenderData(&$mailing_data)
 		{
 			$is_root = true;
 			$email_address = $name = '';
 
 			if ( $mailing_data['PortalUserId'] > 0 ) {
 				$sender = $this->Application->recallObject('u.-item', null, Array ('skip_autoload' => true));
 				/* @var $sender UsersItem */
 
 				$sender->Load($mailing_data['PortalUserId']);
 
 				$email_address = $sender->GetDBField('Email');
 				$name = trim($sender->GetDBField('FirstName') . ' ' . $sender->GetDBField('LastName'));
 				$is_root = false;
 			}
 
 			if ( $is_root || !$email_address ) {
 				$email_address = $this->Application->ConfigValue('DefaultEmailSender');
 			}
 
 			if ( $is_root || !$name ) {
 				$name = strip_tags($this->Application->ConfigValue('Site_Name'));
 			}
 
 			return Array ($name, $email_address);
 		}
 
 		/**
 		 * Generates recipients emails based on "To" field value
 		 *
 		 * @param int $id
 		 * @param Array $fields_hash
 		 */
 		function generateRecipients($id, $fields_hash)
 		{
 			// for each group convert ids to names
 			$recipient_emails = Array ();
 			$recipients_grouped = $this->groupRecipientsByType(explode(';', $fields_hash['To']));
 
 			foreach ($recipients_grouped as $recipient_type => $group_recipients) {
 				$recipient_emails = array_merge($recipient_emails, $this->_getRecipientEmails($recipient_type, $group_recipients));
 			}
 
 			$recipient_emails = array_unique($recipient_emails);
 
 			return Array (
 				'ToParsed' => serialize($recipient_emails),
 				'EmailsTotal' => count($recipient_emails),
 			);
 		}
 
 		/**
 		 * Groups recipients by type
 		 *
 		 * @param Array $recipients
 		 * @return Array
 		 * @access public
 		 */
 		public function groupRecipientsByType($recipients)
 		{
 			$recipients_grouped = Array ();
 
 			foreach ($recipients as $recipient) {
 				if ( strpos($recipient, '_') !== false ) {
 					list ($recipient_type, $recipient_id) = explode('_', $recipient);
 				}
 				else {
 					$recipient_type = 'direct';
 					$recipient_id = $recipient;
 				}
 
 				if ( !array_key_exists($recipient_type, $recipients_grouped) ) {
 					$recipients_grouped[$recipient_type] = Array ();
 				}
 
 				$recipients_grouped[$recipient_type][] = $recipient_id;
 			}
 
 			return $recipients_grouped;
 		}
 
 		function _getRecipientEmails($recipient_type, $recipient_ids)
 		{
 			if (strpos($recipient_type, '.') !== false) {
 				// remove special
 				list ($recipient_type, ) = explode('.', $recipient_type);
 			}
 
 			if ($recipient_type != 'u' && $recipient_type != 'g') {
 				// these are already emails
 				return $recipient_ids;
 			}
 
 			switch ($recipient_type) {
 				case 'u':
 					$sql = 'SELECT Email
 							FROM ' . TABLE_PREFIX . 'Users
 							WHERE (PortalUserId IN (' . implode(',', $recipient_ids) . ')) AND (Email <> "")';
 					break;
 
 				case 'g':
 					$sql = 'SELECT u.Email
 							FROM ' . TABLE_PREFIX . 'UserGroupRelations ug
 							LEFT JOIN ' . TABLE_PREFIX . 'Users u ON u.PortalUserId = ug.PortalUserId
 							WHERE (ug.GroupId IN (' . implode(',', $recipient_ids) . ')) AND (u.Email <> "")';
 					break;
 
 				default:
 					$sql = '';
 					break;
 			}
 
 			return $this->Conn->GetCol($sql);
 		}
 
 		function getRecipientNames($recipient_type, $recipient_ids)
 		{
 			if (strpos($recipient_type, '.') !== false) {
 				// remove special
 				list ($recipient_type, ) = explode('.', $recipient_type);
 			}
 
 			switch ($recipient_type) {
 				case 'u':
 					$title_field = 'Email';
 					break;
 
 				case 'g':
 					$title_field = 'Name';
 					break;
 
 				default:
 					$title_field = false;
 					break;
 			}
 
 			if ($title_field === false || !$recipient_ids) {
 				return $recipient_ids;
 			}
 
-			$id_field = $this->Application->getUnitOption($recipient_type, 'IDField');
-			$table_name = $this->Application->getUnitOption($recipient_type, 'TableName');
+			$config = $this->Application->getUnitConfig($recipient_type);
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			$sql = 'SELECT ' . $title_field . '
 					FROM ' . $table_name . '
 					WHERE ' . $id_field . ' IN (' . implode(',', $recipient_ids) . ')';
 			return $this->Conn->GetCol($sql);
 		}
 
 		/**
 		 * Updates information about sent email count based on given totals by mailings
 		 *
 		 * @param Array $mailing_totals
 		 */
 		function _updateSentTotals($mailing_totals)
 		{
 			if (array_key_exists(0, $mailing_totals)) {
 				// don't update sent email count for mails queued directly (not via mailing lists)
 				unset($mailing_totals[0]);
 			}
 
-			$id_field = $this->Application->getUnitOption('mailing-list', 'IDField');
-			$table_name = $this->Application->getUnitOption('mailing-list', 'TableName');
+			$config = $this->Application->getUnitConfig('mailing-list');
+			$table_name = $config->getTableName();
 
 			// update sent email count for each processed mailing
 			foreach ($mailing_totals as $mailing_id => $mailing_total) {
 				$sql = 'UPDATE ' . $table_name . '
 						SET EmailsSent = EmailsSent + ' . $mailing_total . '
-						WHERE ' . $id_field . ' = ' . $mailing_id;
+						WHERE ' . $config->getIDField() . ' = ' . $mailing_id;
 				$this->Conn->Query($sql);
 			}
 
 			// mark mailings, that were processed completely
 			$sql = 'UPDATE ' . $table_name . '
 					SET Status = ' . MailingList::PROCESSED . '
 					WHERE (Status = ' . MailingList::PARTIALLY_PROCESSED . ') AND (EmailsSent = EmailsTotal)';
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Sent given messages from email queue
 		 *
 		 * @param Array $messages
 		 */
 		function processQueue(&$messages)
 		{
 			$esender = $this->Application->recallObject('EmailSender');
 			/* @var $esender kEmailSendingHelper */
 
-			$queue_table = $this->Application->getUnitOption('email-queue', 'TableName');
+			$queue_table = $this->Application->getUnitConfig('email-queue')->getTableName();
 
 			$i = 0;
 			$message = Array ();
 			$mailing_totals = Array ();
 			$message_count = count($messages);
 
 			while ($i < $message_count) {
 				$message[0] = unserialize($messages[$i]['MessageHeaders']);
 				$message[1] =& $messages[$i]['MessageBody'];
 
 				$esender->setLogData( unserialize($messages[$i]['LogData']) );
 				$delivered = $esender->Deliver($message, true); // immediate send!
 
 				if ($delivered) {
 					// send succeeded, delete from queue
 					$sql = 'DELETE FROM ' . $queue_table . '
 							WHERE EmailQueueId = ' . $messages[$i]['EmailQueueId'];
 					$this->Conn->Query($sql);
 
 					$mailing_id = $messages[$i]['MailingId'];
 					if (!array_key_exists($mailing_id, $mailing_totals)) {
 						$mailing_totals[$mailing_id] = 0;
 					}
 					$mailing_totals[$mailing_id]++;
 				}
 				else {
 					// send failed, increment retries counter
 					$sql = 'UPDATE ' . $queue_table . '
 							SET SendRetries = SendRetries + 1, LastSendRetry = ' . adodb_mktime() . '
 							WHERE EmailQueueId = ' . $messages[$i]['EmailQueueId'];
 					$this->Conn->Query($sql);
 				}
 				$i++;
 			}
 
 			$this->_updateSentTotals($mailing_totals);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/list_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/list_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/list_helper.php	(revision 15698)
@@ -1,219 +1,221 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 	class ListHelper extends kHelper {
 
 		/**
 		 * Detects, that current sorting of the list is not default
 		 *
 		 * @param kDBList $list
 		 * @return bool
 		 */
 		function hasUserSorting(&$list)
 		{
 			static $cache = Array ();
 
-			if (array_key_exists($list->getPrefixSpecial(), $cache)) {
-				return $cache[ $list->getPrefixSpecial() ];
+			if ( array_key_exists($list->getPrefixSpecial(), $cache) ) {
+				return $cache[$list->getPrefixSpecial()];
 			}
 
 			$user_sorting_start = $this->getUserSortIndex($list);
 
-			$sorting_configs = $this->Application->getUnitOption($list->Prefix, 'ConfigMapping', Array ());
-			$list_sortings = $this->Application->getUnitOption($list->Prefix, 'ListSortings', Array ());
-			$sorting_prefix = getArrayValue($list_sortings, $list->Special) ? $list->Special : '';
+			$config = $list->getUnitConfig();
+			$sorting_configs = $config->getConfigMapping(Array ());
+			$list_sortings = $config->getListSortingsBySpecial($list, Array ());
 
-			if (array_key_exists('DefaultSorting1Field', $sorting_configs)) {
-				$list_sortings[$sorting_prefix]['Sorting'] = Array (
+			if ( array_key_exists('DefaultSorting1Field', $sorting_configs) ) {
+				$list_sortings['Sorting'] = Array (
 					$this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']),
 					$this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']),
 				);
 			}
 
-			$sorting = getArrayValue($list_sortings, $sorting_prefix, 'Sorting');
+			$sorting = getArrayValue($list_sortings, 'Sorting');
 			$sort_fields = is_array($sorting) ? array_keys($sorting) : Array ();
 
 			for ($order_number = 0; $order_number < 2; $order_number++) {
 				// current sorting in list
 				$sorting_pos = $user_sorting_start + $order_number;
 				$current_order_field = $list->GetOrderField($sorting_pos, true);
 				$current_order_direction = $list->GetOrderDirection($sorting_pos, true);
 
-				if (!$current_order_field || !$current_order_direction) {
+				if ( !$current_order_field || !$current_order_direction ) {
 					// no sorting defined for this sorting position
 					continue;
 				}
 
 				// remove language prefix from field
 				$field_options = $list->GetFieldOptions($current_order_field);
-				if (array_key_exists('formatter', $field_options) && $field_options['formatter'] == 'kMultiLanguage') {
+				if ( array_key_exists('formatter', $field_options) && $field_options['formatter'] == 'kMultiLanguage' ) {
 					// remove language prefix
 					$current_order_field = preg_replace('/^l[\d]+_(.*)/', '\\1', $current_order_field);
 				}
 
 				// user sorting found
-				if (array_key_exists($order_number, $sort_fields)) {
+				if ( array_key_exists($order_number, $sort_fields) ) {
 					// default sorting found
 					$default_order_field = $sort_fields[$order_number];
 					$default_order_direction = $sorting[$default_order_field]; // because people can write
 
-					if ($current_order_field != $default_order_field || strcasecmp($current_order_direction, $default_order_direction) != 0) {
+					if ( $current_order_field != $default_order_field || strcasecmp($current_order_direction, $default_order_direction) != 0 ) {
 						// #1. user sorting differs from default sorting -> changed
-						$cache[ $list->getPrefixSpecial() ] = true;
+						$cache[$list->getPrefixSpecial()] = true;
+
 						return true;
 					}
 				}
 				else {
 					// #2. user sorting + no default sorting -> changed
-					$cache[ $list->getPrefixSpecial() ] = true;
+					$cache[$list->getPrefixSpecial()] = true;
+
 					return true;
 				}
 			}
 
 			// #3. user sorting match default or not defined -> not changed
-			$cache[ $list->getPrefixSpecial() ] = false;
+			$cache[$list->getPrefixSpecial()] = false;
+
 			return false;
 		}
 
 		/**
 		 * Returns default per-page value for given prefix
 		 *
 		 * @param string $prefix
 		 * @param int $default
 		 * @return int
 		 */
 		function getDefaultPerPage($prefix, $default = 10)
 		{
 			$ret = false;
-			$config_mapping = $this->Application->getUnitOption($prefix, 'ConfigMapping');
+			$config_mapping = $this->Application->getUnitConfig($prefix)->getConfigMapping();
 
-			if ($config_mapping) {
-				if (!array_key_exists('PerPage', $config_mapping)) {
+			if ( $config_mapping ) {
+				if ( !array_key_exists('PerPage', $config_mapping) ) {
 					trigger_error('Incorrect mapping of <span class="debug_error">PerPage</span> key in config for prefix <strong>' . $prefix . '</strong>', E_USER_WARNING);
 				}
 
 				$per_page = $this->Application->ConfigValue($config_mapping['PerPage']);
 
-				if ($per_page) {
+				if ( $per_page ) {
 					return $per_page;
 				}
 			}
 
 			// none of checked above per-page locations are useful, then try default value
 			return $default;
 		}
 
 		/**
 		 * Returns index where 1st changable sorting field begins
 		 *
 		 * @param kDBList $list
 		 * @return int
 		 * @todo 	This is copy of kDBTagProcessor::getUserSortIndex method.
 		 * 			Can't call helper there, because it will slow down grid output
 		 * 			when we have a lot of columns
 		 */
 		function getUserSortIndex(&$list)
 		{
-			$list_sortings = $this->Application->getUnitOption($list->Prefix, 'ListSortings', Array ());
-			$sorting_prefix = getArrayValue($list_sortings, $list->Special) ? $list->Special : '';
+			$list_sortings = $list->getUnitConfig()->getListSortingsBySpecial($list, Array ());
 
 			$user_sorting_start = 0;
-			$forced_sorting = getArrayValue($list_sortings, $sorting_prefix, 'ForcedSorting');
+			$forced_sorting = getArrayValue($list_sortings, 'ForcedSorting');
 
 			if ( $forced_sorting ) {
 				$user_sorting_start = count($forced_sorting);
 			}
 
 			return $user_sorting_start;
 		}
 
 		/**
 		 * Returns ID of previous/next record related to current record
 		 *
 		 * @param kDBItem $object
 		 * @param string $list_prefix
 		 * @param bool $next
 		 * @param string $select_fields
 		 * @return int
 		 */
 		function getNavigationResource(&$object, $list_prefix, $next = true, $select_fields = null)
 		{
 			$list = $this->Application->recallObject($list_prefix);
 			/* @var $list kDBList */
 
 			if ( !isset($select_fields) ) {
 				$select_fields = '%1$s.' . $object->IDField;
 			}
 
 			if ( is_array($select_fields) ) {
 				$select_fields = implode(', ', $select_fields);
 			}
 
 			$list->SetSelectSQL( str_replace(Array ('%1$s.*', '%2$s'), Array ($select_fields, ''), $list->GetPlainSelectSQL()) );
 
 			$operators = Array (
 				'asc' => $next ? '>' : '<',
 				'desc' => $next ? '<' : '>',
 			);
 
 			$where_clause = Array ();
 			$lang = $this->Application->GetVar('m_lang');
 			$order_fields = $order_fields_backup = $list->getOrderFields();
 
 			foreach ($order_fields as $index => $order) {
 				$where_clause[$index] = Array ();
 
 				if ( !$next ) {
 					$list->changeOrderDirection($index, $order_fields_backup[$index][1] == 'asc' ? 'desc' : 'asc');
 				}
 
 				for ($i = 0; $i <= $index; $i++) {
 					$order_field = $order_fields_backup[$i][0];
 					$is_expression = $order_fields_backup[$i][2];
 
 					if ( preg_match('/^IF\(COALESCE\(.*?\.(l' . $lang . '_.*?), ""\),/', $order_field, $regs) ) {
 						// undo result of kDBList::getMLSortField method
 						$order_field = $regs[1];
 						$is_expression = false;
 					}
 
 					$order_direction = $order_fields_backup[$i][1];
 
 					$field_prefix = $list->isVirtualField($order_field) || $is_expression ? '' : '%1$s.';
 
 					$actual_operator = $i == $index ? $operators[$order_direction] : '=';
 					$where_clause[$index][] = $field_prefix . $order_field . ' ' . $actual_operator . ' ' . $this->Conn->qstr($object->GetDBField($order_field));
 				}
 
 				$where_clause[$index] = '(' . implode(') AND (', $where_clause[$index]) . ')';
 			}
 
 			$where_clause = '(%1$s.' . $object->IDField . ' != ' . $object->GetID() . ') AND ((' . implode(') OR (', $where_clause) . '))';
 			$list->addFilter('navigation_filter', $where_clause);
 
 			// do extractCalculatedFields (transforms having into where), since we don't select fields from JOINed tables
 			$sql = $list->extractCalculatedFields($list->GetSelectSQL(), 1, true);
 
 			$list->removeFilter('navigation_filter');
 			$list->setOrderFields($order_fields_backup);
 
 			if ( $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->appendHTML('Quering <strong>' . ($next ? 'next' : 'previous') . '</strong> item for "<strong>' . $list_prefix . '</strong>" list:');
 			}
 
 			return $this->Conn->GetOne($sql);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/page_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/page_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/page_helper.php	(revision 15698)
@@ -1,275 +1,275 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class PageHelper extends kHelper {
 
 	/**
 	 * Returns page info
 	 *
 	 * @param int $page_id
 	 * @return Array
 	 */
 	function getPageInfo($page_id)
 	{
 		list ($user_id, $history_permission) = $this->getHistoryPermissionAndUser($page_id);
 
 		$where_clause = Array (
 			'pr.PageId = ' . $page_id,
 			'pr.CreatedById <> ' . $user_id,
 			'pr.IsDraft = 1',
 		);
 
 		$sql = 'SELECT CASE pr.CreatedById WHEN ' . USER_ROOT . ' THEN "root" WHEN ' . USER_GUEST . ' THEN "Guest" ELSE IF(u.Username = "", u.Email, u.Username) END
-				FROM ' . $this->Application->getUnitOption('page-revision', 'TableName') . ' pr
+				FROM ' . $this->Application->getUnitConfig('page-revision')->getTableName() . ' pr
 				LEFT JOIN ' . TABLE_PREFIX . 'Users u ON u.PortalUserId = pr.CreatedById
 				WHERE (' . implode(') AND (', $where_clause) . ')';
 		$users = $this->Conn->GetCol($sql);
 
 		$page_revisions = Array ();
 
 		if ( $history_permission ) {
 			$tag_params = Array ('per_page' => -1, 'skip_parent_filter' => 1, 'requery' => 1, 'page_id' => $page_id);
 
 			$revisions = $this->Application->recallObject('page-revision.list', 'page-revision_List', $tag_params);
 			/* @var $revisions kDBList */
 
 			$revisions->Query();
 			$revisions->GoFirst();
 
 			$status_options = $revisions->GetFieldOptions('Status');
 			$draft_label = $this->Application->Phrase('la_Draft', false, true);
 			$title_label = $this->Application->Phrase('la_RevisionNumber', false, true);
 			$by_label = $this->Application->Phrase('la_By', false, true);
 
 			while ( !$revisions->EOL() ) {
 				$status = $revisions->GetDBField('Status');
 				$status_label = $this->Application->Phrase($status_options['options'][$status], false, true);
 
 				$page_revisions[ 'r' . $revisions->GetDBField('RevisionNumber') ] = Array (
 					'title' => $revisions->GetDBField('IsDraft') ? $draft_label : sprintf($title_label, $revisions->GetDBField('RevisionNumber')),
 					'status' => $status,
 					'status_label' => mb_strtolower($status_label),
 					'datetime' => $revisions->GetField('CreatedOn'),
 					'author' => $by_label . ': ' . $revisions->GetField('CreatedById'),
 					'draft' => (int)$revisions->GetDBField('IsDraft'),
 				);
 
 				$revisions->GoNext();
 			}
 		}
 
 		$current_revision = $this->Application->recallObject('page-revision.current');
 		/* @var $current_revision kDBItem */
 
 		$revision_status = $current_revision->GetDBField('Status');
 		$status_options = $current_revision->GetFieldOptions('Status');
 		$status_label = $this->Application->Phrase($status_options['options'][$revision_status], false, true);
 
 		$revision_phase = $current_revision->GetDBField('IsDraft') ? 'la_title_EditingDraft' : 'la_title_ViewingRevision';
 		$revision_title = sprintf($this->Application->Phrase($revision_phase, false, true), $current_revision->GetDBField('RevisionNumber'), mb_strtolower($status_label));
 		$current_revision_info = Array ('title' => $revision_title, 'status' => $revision_status, 'saved' => '');
 
 		$autosave_time = $current_revision->GetDBField('AutoSavedOn');
 
 		if ( $autosave_time ) {
 			$phrase = $this->Application->Phrase($current_revision->GetDBField('IsDraft') ? 'la_DraftSavedAt' : 'la_SavedAt', false, true);
 			$current_revision_info['saved'] = sprintf($phrase, $current_revision->GetField('AutoSavedOn_time') . ' (' . $this->getAgoTime($autosave_time) . ')');
 		}
 
 		$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 		/* @var $ml_helper kMultiLanguageHelper */
 
 		$currently_editing = $ml_helper->getPluralPhrase(
 			count($users),
 			Array (
 				'phrase1' => 'la_PageCurrentlyEditing1',
 				'phrase2' => 'la_PageCurrentlyEditing2',
 				'phrase5' => 'la_PageCurrentlyEditing5',
 			),
 			false, true
 		);
 
 		$currently_editing = sprintf($currently_editing, implode(', ', $users));
 
 		return Array ('current_revision' => $current_revision_info, 'editors' => $users, 'editors_warning' => $currently_editing, 'revisions' => $page_revisions);
 	}
 
 	/**
 	 * Returns time passed between 2 given dates in "X minutes Y seconds ago" format
 	 *
 	 * @param int $from_date
 	 * @param int $to_date
 	 * @return string
 	 */
 	function getAgoTime($from_date, $to_date = null, $max_levels = 1)
 	{
 		$blocks = Array (
 			Array ('name' => 'year', 'amount' => 60*60*24*365),
 			Array ('name' => 'month' ,'amount' => 60*60*24*31),
 			Array ('name' => 'week', 'amount' => 60*60*24*7),
 			Array ('name' => 'day', 'amount' => 60*60*24),
 			Array ('name' => 'hour', 'amount' => 60*60),
 			Array ('name' => 'minute', 'amount' => 60),
 			Array ('name' => 'second', 'amount' => 1),
 		);
 
 		if ( !isset($to_date) ) {
 			$to_date = adodb_mktime();
 		}
 
 		$diff = abs($to_date - $from_date);
 
 		if ( $diff == 0 ) {
 			return 'now';
 		}
 
 		$current_level = 1;
 		$result = Array ();
 
 		foreach ($blocks as $block) {
 			if ($current_level > $max_levels) {
 				break;
 			}
 
 			if ( $diff / $block['amount'] >= 1 ) {
 				$amount = floor($diff / $block['amount']);
 				$plural = $amount > 1 ? 's' : '';
 
 				$result[] = $amount . ' ' . $block['name'] . $plural;
 				$diff -= $amount * $block['amount'];
 				$current_level++;
 			}
 		}
 
 		return implode(' ', $result) . ' ago';
 	}
 
 	/**
 	 * Returns where clause for loading correct revision for a given page
 	 *
 	 * @param int $page_id
 	 * @param int $live_revision_number
 	 * @param string $table_name
 	 * @return string
 	 */
 	function getRevsionWhereClause($page_id, $live_revision_number, $table_name = '')
 	{
 		$revision = (int)$this->Application->GetVar('revision');
 		list ($user_id, $has_permission) = $this->getHistoryPermissionAndUser($page_id);
 
 		if ( $has_permission && $revision ) {
 			$revision_clause = $table_name . 'RevisionNumber = ' . $revision . ' AND ' . $table_name . 'IsDraft = 0';
 		}
 		else {
 			$editing_mode = $this->Application->GetVar('editing_mode'); // not in a EDITING_MODE constant, while in admin console
 			$revision_clause = $table_name . 'RevisionNumber = ' . $live_revision_number . ' AND ' . $table_name . 'IsDraft = 0';
 
 			if ( $this->Application->GetVar('preview') || $editing_mode == EDITING_MODE_CONTENT ) {
 				$revision_clause = '(' . $table_name . 'CreatedById = ' . $user_id . ' AND ' . $table_name . 'IsDraft = 1) OR (' . $revision_clause . ')';
 			}
 		}
 
 		return $revision_clause;
 	}
 
 	/**
 	 * Returns current admin user id (even, when called from front-end) and it's revision history view permission
 	 *
 	 * @param int $page_id
 	 * @return Array
 	 */
 	function getHistoryPermissionAndUser($page_id)
 	{
 		$user_id = (int)$this->Application->RecallVar($this->Application->isAdmin ? 'user_id' : 'admin_user_id');
 		$history_permission = $this->Application->CheckAdminPermission('CATEGORY.REVISION.HISTORY.VIEW', 0, $page_id);
 
 		return Array ($user_id, $history_permission);
 	}
 
 	/**
 	 * Creates new content block in every revision that misses it. Plus creates first page revision
 	 *
 	 * @param int $page_id
 	 * @param int $num
 	 */
 	function createNewContentBlock($page_id, $num)
 	{
 		$sql = 'SELECT pc.PageContentId, pr.RevisionId
 				FROM ' . TABLE_PREFIX . 'PageRevisions pr
 				LEFT JOIN ' . TABLE_PREFIX . 'PageContent pc ON pc.RevisionId = pr.RevisionId AND pc.ContentNum = ' . $num . '
 				WHERE pr.PageId = ' . $page_id;
 		$revisions = $this->Conn->GetCol($sql, 'RevisionId');
 
 		if ( !$revisions ) {
 			// no revisions for a page -> create a live revision
 			$revision = $this->Application->recallObject('page-revision.live', null, Array ('skip_autoload' => true));
 			/* @var $revision kDBItem */
 
 			$revision->SetDBField('PageId', $page_id);
 			$revision->SetDBField('RevisionNumber', 1);
 			$revision->SetDBField('Status', STATUS_ACTIVE);
 			$revision->Create();
 
 			$revisions[ $revision->GetID() ] = NULL;
 		}
 
 		$content_block = $this->Application->recallObject('content.new', null, Array ('skip_autoload' => true));
 		/* @var $content_block kDBItem */
 
 		$content_block->SetDBField('PageId', $page_id);
 		$content_block->SetDBField('ContentNum', $num);
 
 		foreach ($revisions as $revision_id => $content_block_id) {
 			if ( is_numeric($content_block_id) ) {
 				continue;
 			}
 
 			$content_block->SetDBField('RevisionId', $revision_id);
 			$content_block->Create();
 		}
 	}
 
 	/**
 	 * Loads content block by it's number
 	 *
 	 * @param kDBItem $content_block
 	 * @param CategoriesItem $page
 	 * @param int $num
 	 *
 	 * @return bool
 	 */
 	function loadContentBlock(&$content_block, &$page, $num)
 	{
 		$page_id = $page->GetID();
 
 		if ( !EDITING_MODE && !$this->Application->GetVar('preview') ) {
 			$revision_clause = 'pr.RevisionNumber = ' . $page->GetDBField('LiveRevisionNumber') . ' AND pr.IsDraft = 0';
 		}
 		else {
 			$revision_clause = $this->getRevsionWhereClause($page_id, $page->GetDBField('LiveRevisionNumber'), 'pr.');
 		}
 
 
 		$sql = 	$content_block->GetSelectSQL() . '
 				WHERE (' . $content_block->TableName . '.PageId = ' . $page_id . ') AND (' . $content_block->TableName . '.ContentNum = ' . $num . ') AND (' . $revision_clause . ')
 				ORDER BY pr.IsDraft DESC, pr.RevisionNumber DESC';
 		$content_data = $this->Conn->GetRow($sql);
 
 		$content_block->LoadFromHash($content_data);
 
 		return $content_block->isLoaded();
 	}
 }
Index: branches/5.3.x/core/units/helpers/language_import_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/language_import_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/language_import_helper.php	(revision 15698)
@@ -1,1302 +1,1307 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 /**
  * Language pack format version description
  *
  * v1
  * ==========
  * All language properties are separate nodes inside <LANGUAGE> node. There are
  * two more nodes PHRASES and EVENTS for phrase and email event translations.
  *
  * v2
  * ==========
  * All data, that will end up in Language table is now attributes of LANGUAGE node
  * and is name exactly as field name, that will be used to store that data.
  *
  * v4
  * ==========
  * Hint & Column translation added to each phrase translation
  *
  * v5
  * ==========
  * Use separate xml nodes for subject, headers, html & plain translations
  *
  * v6
  * ==========
  * Added e-mail design templates
  *
  */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class LanguageImportHelper extends kHelper {
 
 		/**
 		 * Overwrite existing translations during import
 		 */
 		const OVERWRITE_EXISTING = 1;
 
 		/**
 		 * New translations are added as synced with other languages
 		 */
 		const SYNC_ADDED = 2;
 
 		/**
 		 * Current Language in import
 		 *
 		 * @var LanguagesItem
 		 */
 		var $lang_object = null;
 
 		/**
 		 * Current user's IP address
 		 *
 		 * @var string
 		 */
 		var $ip_address = '';
 
 		/**
 		 * Event type + name mapping to id (from system)
 		 *
 		 * @var Array
 		 */
 		var $events_hash = Array ();
 
 		/**
 		 * Language pack import mode
 		 *
 		 * @var int
 		 */
 		var $_importOptions = 0;
 
 		/**
 		 * Language IDs, that were imported
 		 *
 		 * @var Array
 		 */
 		var $_languages = Array ();
 
 		/**
 		 * Temporary table names to perform import on
 		 *
 		 * @var Array
 		 */
 		var $_tables = Array ();
 
 		/**
 		 * Phrase types allowed for import/export operations
 		 *
 		 * @var Array
 		 */
 		var $phrase_types_allowed = Array ();
 
 		/**
 		 * Encoding, used for language pack exporting
 		 *
 		 * @var string
 		 */
 		var $_exportEncoding = 'base64';
 
 		/**
 		 * Exported data limits (all or only specified ones)
 		 *
 		 * @var Array
 		 */
 		var $_exportLimits = Array (
 			'phrases' => false,
 			'email-template' => false,
 			'country-state' => false,
 		);
 
 		/**
 		 * Debug language pack import process
 		 *
 		 * @var bool
 		 */
 		var $_debugMode = false;
 
 		/**
 		 * Latest version of language pack format. Versions are not backwards compatible!
 		 *
 		 * @var int
 		 */
 		var $_latestVersion = 6;
 
 		/**
 		 * Prefix-based serial numbers, that should be changed after import is finished
 		 *
 		 * @var Array
 		 */
 		var $changedPrefixes = Array ();
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			// "core/install/english.lang", phrase count: 3318, xml parse time on windows: 10s, insert time: 0.058s
 			kUtil::setResourceLimit();
 
 			$this->lang_object = $this->Application->recallObject('lang.import', null, Array ('skip_autoload' => true));
 
 			if (!(defined('IS_INSTALL') && IS_INSTALL)) {
 				// perform only, when not in installation mode
 				$this->_updateEventsCache();
 			}
 
 			$this->ip_address = $this->Application->getClientIp();
 
 //			$this->_debugMode = $this->Application->isDebugMode();
 		}
 
 		/**
 		 * Tells if given option is enabled
 		 *
 		 * @param int $option_bit
 		 * @return bool
 		 * @access protected
 		 */
 		protected function hasOption($option_bit)
 		{
 			return ($this->_importOptions & $option_bit) == $option_bit;
 		}
 
 		/**
 		 * Sets import option
 		 *
 		 * @param int $option_bit
 		 * @param bool $enabled
 		 * @return void
 		 * @access public
 		 */
 		public function setOption($option_bit, $enabled = true)
 		{
 			if ( $enabled ) {
 				$this->_importOptions |= $option_bit;
 			}
 			else {
 				$this->_importOptions = $this->_importOptions & ~$option_bit;
 			}
 		}
 
 		/**
 		 * Performs import of given language pack (former Parse method)
 		 *
 		 * @param string $filename
 		 * @param string $phrase_types
 		 * @param Array $module_ids
 		 * @return bool
 		 */
 		function performImport($filename, $phrase_types, $module_ids)
 		{
 			// define the XML parsing routines/functions to call based on the handler path
 			if (!file_exists($filename) || !$phrase_types /*|| !$module_ids*/) {
 				return false;
 			}
 
 			if ($this->_debugMode) {
 				$start_time = microtime(true);
 				$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '")');
 			}
 
 			if (defined('IS_INSTALL') && IS_INSTALL) {
 				// new events could be added during module upgrade
 				$this->_updateEventsCache();
 			}
 
 			$phrase_types = explode('|', substr($phrase_types, 1, -1) );
 //			$module_ids = explode('|', substr($module_ids, 1, -1) );
 
 			$this->phrase_types_allowed = array_flip($phrase_types);
 
 			$this->_parseXML($filename);
 
 			// copy data from temp tables to live
 			foreach ($this->_languages as $language_id) {
 				$this->_performUpgrade($language_id, 'phrases', 'PhraseKey', Array ('l%s_Translation', 'l%s_HintTranslation', 'l%s_ColumnTranslation', 'l%s_TranslateFrom', 'PhraseType'));
 				$this->_performUpgrade($language_id, 'email-template', 'TemplateId', Array ('l%s_Subject', 'Headers', 'l%s_HtmlBody', 'l%s_PlainTextBody', 'l%s_TranslateFrom'));
 				$this->_performUpgrade($language_id, 'country-state', 'CountryStateId', Array ('l%s_Name'));
 			}
 
 			$this->_initImportTables(true);
 			$this->changedPrefixes = array_unique($this->changedPrefixes);
 
 			foreach ($this->changedPrefixes as $prefix) {
 				$this->Application->incrementCacheSerial($prefix);
 			}
 
 			if ($this->_debugMode) {
 				$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (microtime(true) - $start_time));
 			}
 
 			return true;
 		}
 
 		/**
 		 * Creates XML file with exported language data (former Create method)
 		 *
 		 * @param string $filename filename to export into
 		 * @param Array $phrase_types phrases types to export from modules passed in $module_ids
 		 * @param Array $language_ids IDs of languages to export
 		 * @param Array $module_ids IDs of modules to export phrases from
 		 */
 		function performExport($filename, $phrase_types, $language_ids, $module_ids)
 		{
 			$fp = fopen($filename,'w');
 			if (!$fp || !$phrase_types || !$module_ids || !$language_ids) {
 				return false;
 			}
 
 			$phrase_types = explode('|', substr($phrase_types, 1, -1) );
 			$module_ids = explode('|', substr($module_ids, 1, -1) );
 
 			$ret = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
 			$ret .= '<LANGUAGES Version="' . $this->_latestVersion . '">' . "\n";
 
 			$export_fields = $this->_getExportFields();
 
 			// get languages
 			$sql = 'SELECT *
-					FROM ' . $this->Application->getUnitOption('lang','TableName') . '
+					FROM ' . $this->Application->getUnitConfig('lang')->getTableName() . '
 					WHERE LanguageId IN (' . implode(',', $language_ids) . ')';
 			$languages = $this->Conn->Query($sql, 'LanguageId');
 
 			// get phrases
 			$phrase_modules = $module_ids;
 			array_push($phrase_modules, ''); // for old language packs without module
 
 			$phrase_modules = $this->Conn->qstrArray($phrase_modules);
 
 			// apply phrase selection limit
 			if ($this->_exportLimits['phrases']) {
 				$escaped_phrases = $this->Conn->qstrArray($this->_exportLimits['phrases']);
 				$limit_where = 'Phrase IN (' . implode(',', $escaped_phrases) . ')';
 			}
 			else {
 				$limit_where = 'TRUE';
 			}
 
 			$sql = 'SELECT *
-					FROM ' . $this->Application->getUnitOption('phrases','TableName') . '
+					FROM ' . $this->Application->getUnitConfig('phrases')->getTableName() . '
 					WHERE PhraseType IN (' . implode(',', $phrase_types) . ') AND Module IN (' . implode(',', $phrase_modules) . ') AND ' . $limit_where . '
 					ORDER BY Phrase';
 			$phrases = $this->Conn->Query($sql, 'PhraseId');
 
 			// email events
 			$module_sql = preg_replace('/(.*),/U', 'INSTR(Module,\'\\1\') OR ', implode(',', $module_ids) . ',');
 
 			// apply event selection limit
 			if ($this->_exportLimits['email-template']) {
 				$escaped_email_templates = $this->Conn->qstrArray($this->_exportLimits['email-template']);
 				$limit_where = 'TemplateName IN (' . implode(',', $escaped_email_templates) . ')';
 			}
 			else {
 				$limit_where = 'TRUE';
 			}
 
 			$sql = 'SELECT *
-					FROM ' . $this->Application->getUnitOption('email-template', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('email-template')->getTableName() . '
 					WHERE `Type` IN (' . implode(',', $phrase_types) . ') AND (' . substr($module_sql, 0, -4) . ') AND ' . $limit_where . '
 					ORDER BY TemplateName, `Type`';
 			$email_templates = $this->Conn->Query($sql, 'TemplateId');
 
 			if ( in_array('Core', $module_ids) ) {
 				if ($this->_exportLimits['country-state']) {
 					$escaped_countries = $this->Conn->qstrArray($this->_exportLimits['country-state']);
 					$limit_where = '`IsoCode` IN (' . implode(',', $escaped_countries) . ')';
 				}
 				else {
 					$limit_where = 'TRUE';
 				}
 
-				$country_table = $this->Application->getUnitOption('country-state', 'TableName');
+				$country_table = $this->Application->getUnitConfig('country-state')->getTableName();
 
 				// countries
 				$sql = 'SELECT *
 						FROM ' . $country_table . '
 						WHERE Type = ' . DESTINATION_TYPE_COUNTRY . ' AND ' . $limit_where . '
 						ORDER BY `IsoCode`';
 				$countries = $this->Conn->Query($sql, 'CountryStateId');
 
 				// states
 				$sql = 'SELECT state.*
 						FROM ' . $country_table . ' state
 						JOIN ' . $country_table . ' country ON country.CountryStateId = state.StateCountryId
 						WHERE state.Type = ' . DESTINATION_TYPE_STATE . ' AND ' . str_replace('`IsoCode`', 'country.`IsoCode`', $limit_where) . '
 						ORDER BY state.`IsoCode`';
 				$states = $this->Conn->Query($sql, 'CountryStateId');
 
 				foreach ($states as $state_id => $state_data) {
 					$country_id = $state_data['StateCountryId'];
 
 					if ( !array_key_exists('States', $countries[$country_id]) ) {
 						$countries[$country_id]['States'] = Array ();
 					}
 
 					$countries[$country_id]['States'][] = $state_id;
 				}
 			}
 
 			foreach ($languages as $language_id => $language_info) {
 				// language
 				$ret .= "\t" . '<LANGUAGE Encoding="' . $this->_exportEncoding . '"';
 
 				foreach ($export_fields	as $export_field) {
 					$ret .= ' ' . $export_field . '="' . htmlspecialchars($language_info[$export_field], NULL, CHARSET) . '"';
 				}
 
 				$ret .= '>' . "\n";
 
 				// filename replacements
 				$replacements = $language_info['FilenameReplacements'];
 
 				if ( $replacements ) {
 					$ret .= "\t\t" . '<REPLACEMENTS>' . $this->_exportConvert($replacements) . '</REPLACEMENTS>' . "\n";
 				}
 
 				// e-mail design templates
 				if ( $language_info['HtmlEmailTemplate'] || $language_info['TextEmailTemplate'] ) {
 					$ret .= "\t\t" . '<EMAILDESIGNS>' . "\n";
 
 					if ( $language_info['HtmlEmailTemplate'] ) {
 						$ret .= "\t\t\t" . '<HTML>' . $this->_exportConvert($language_info['HtmlEmailTemplate']) . '</HTML>' . "\n";
 					}
 
 					if ( $language_info['TextEmailTemplate'] ) {
 						$ret .= "\t\t\t" . '<TEXT>' . $this->_exportConvert($language_info['TextEmailTemplate']) . '</TEXT>' . "\n";
 					}
 
 					$ret .= "\t\t" . '</EMAILDESIGNS>' . "\n";
 				}
 
 				// phrases
 				if ($phrases) {
 					$ret .= "\t\t" . '<PHRASES>' . "\n";
 					foreach ($phrases as $phrase_id => $phrase) {
 						$translation = $phrase['l' . $language_id . '_Translation'];
 						$hint_translation = $phrase['l' . $language_id . '_HintTranslation'];
 						$column_translation = $phrase['l' . $language_id . '_ColumnTranslation'];
 
 						if (!$translation) {
 							// phrase is not translated on given language
 							continue;
 						}
 
 						if ( $this->_exportEncoding == 'base64' ) {
 							$hint_translation = base64_encode($hint_translation);
 							$column_translation = base64_encode($column_translation);
 						}
 						else {
 							$hint_translation = htmlspecialchars($hint_translation, NULL, CHARSET);
 							$column_translation = htmlspecialchars($column_translation, NULL, CHARSET);
 						}
 
 						$attributes = Array (
 							'Label="' . $phrase['Phrase'] . '"',
 							'Module="' . $phrase['Module'] . '"',
 							'Type="' . $phrase['PhraseType'] . '"'
 						);
 
 						if ( $phrase['l' . $language_id . '_HintTranslation'] ) {
 							$attributes[] = 'Hint="' . $hint_translation . '"';
 						}
 
 						if ( $phrase['l' . $language_id . '_ColumnTranslation'] ) {
 							$attributes[] = 'Column="' . $column_translation . '"';
 						}
 
 						$ret .= "\t\t\t" . '<PHRASE ' . implode(' ', $attributes) . '>' . $this->_exportConvert($translation) . '</PHRASE>' . "\n";
 					}
 
 					$ret .= "\t\t" . '</PHRASES>' . "\n";
 				}
 
 				// email events
 				if ($email_templates) {
 					$ret .= "\t\t" . '<EVENTS>' . "\n";
 
 					foreach ($email_templates as $template_data) {
 						$fields_hash = Array (
 							'HEADERS' => $template_data['Headers'],
 							'SUBJECT' => $template_data['l' . $language_id . '_Subject'],
 							'HTMLBODY' => $template_data['l' . $language_id . '_HtmlBody'],
 							'PLAINTEXTBODY' => $template_data['l' . $language_id . '_PlainTextBody'],
 						);
 
 						$data = '';
 
 						foreach ($fields_hash as $xml_node => $xml_content) {
 							if ( $xml_content ) {
 								$data .= "\t\t\t\t" . '<' . $xml_node . '>' . $this->_exportConvert($xml_content) . '</' . $xml_node . '>' . "\n";
 							}
 						}
 
 						if ( $data ) {
 							$ret .= "\t\t\t" . '<EVENT Event="' . $template_data['TemplateName'] . '" Type="' . $template_data['Type'] . '">' . "\n" . $data . "\t\t\t" . '</EVENT>' . "\n";
 						}
 					}
 
 					$ret .= "\t\t" . '</EVENTS>' . "\n";
 				}
 
 				if (in_array('Core', $module_ids) && $countries) {
 					$ret .= "\t\t" . '<COUNTRIES>' . "\n";
 					foreach ($countries as $country_data) {
 						$translation = $country_data['l' . $language_id . '_Name'];
 
 						if (!$translation) {
 							// country is not translated on given language
 							continue;
 						}
 
 						$data = $this->_exportEncoding == 'base64' ? base64_encode($translation) : $translation;
 
 						if (array_key_exists('States', $country_data)) {
 							$ret .= "\t\t\t" . '<COUNTRY Iso="' . $country_data['IsoCode'] . '" Translation="' . $data . '">' . "\n";
 
 							foreach ($country_data['States'] as $state_id) {
 								$translation = $states[$state_id]['l' . $language_id . '_Name'];
 
 								if (!$translation) {
 									// state is not translated on given language
 									continue;
 								}
 
 								$data = $this->_exportEncoding == 'base64' ? base64_encode($translation) : $translation;
 								$ret .= "\t\t\t\t" . '<STATE Iso="' . $states[$state_id]['IsoCode'] . '" Translation="' . $data . '"/>' . "\n";
 							}
 
 							$ret  .= "\t\t\t" . '</COUNTRY>' . "\n";
 						}
 						else {
 							$ret .= "\t\t\t" . '<COUNTRY Iso="' . $country_data['IsoCode'] . '" Translation="' . $data . '"/>' . "\n";
 						}
 					}
 
 					$ret .= "\t\t" . '</COUNTRIES>' . "\n";
 				}
 
 				$ret .= "\t" . '</LANGUAGE>' . "\n";
 			}
 
 			$ret .= '</LANGUAGES>';
 			fwrite($fp, $ret);
 			fclose($fp);
 
 			return true;
 		}
 
 		/**
 		 * Converts string before placing into export file
 		 *
 		 * @param string $string
 		 * @return string
 		 * @access protected
 		 */
 		protected function _exportConvert($string)
 		{
 			return $this->_exportEncoding == 'base64' ? base64_encode($string) : '<![CDATA[' . $string . ']]>';
 		}
 
 		/**
 		 * Sets language pack encoding (not charset) used during export
 		 *
 		 * @param string $encoding
 		 */
 		function setExportEncoding($encoding)
 		{
 			$this->_exportEncoding = $encoding;
 		}
 
 		/**
 		 * Sets language pack data limit for export
 		 *
 		 * @param string $prefix
 		 * @param string $data
 		 */
 		function setExportLimit($prefix, $data = null)
 		{
 			if ( !isset($data) ) {
 				$key_field = $prefix == 'phrases' ? 'Phrase' : 'TemplateName';
 				$ids = $this->getExportIDs($prefix);
 
+				$config = $this->Application->getUnitConfig($prefix);
+
 				$sql = 'SELECT ' . $key_field . '
-						FROM ' . $this->Application->getUnitOption($prefix, 'TableName') . '
-						WHERE ' . $this->Application->getUnitOption($prefix, 'IDField') . ' IN (' . $ids . ')';
+						FROM ' . $config->getTableName() . '
+						WHERE ' . $config->getIDField() . ' IN (' . $ids . ')';
 				$rows = $this->Conn->GetIterator($sql);
 
 				if ( count($rows) ) {
 					$data = '';
 
 					foreach ($rows as $row) {
 						$data .= ',' . $row[$key_field];
 					}
 
 					$data = substr($data, 1);
 				}
 			}
 
 			if ( !is_array($data) ) {
 				$data = str_replace(',', "\n", $data);
 				$data = preg_replace("/\n+/", "\n", str_replace("\r", '', trim($data)));
 				$data = $data ? array_map('trim', explode("\n", $data)) : Array ();
 			}
 
 			$this->_exportLimits[$prefix] = $data;
 		}
 
 		/**
 		 * Performs upgrade of given language pack part
 		 *
 		 * @param int $language_id
 		 * @param string $prefix
 		 * @param string $unique_field
 		 * @param Array $data_fields
 		 */
 		function _performUpgrade($language_id, $prefix, $unique_field, $data_fields)
 		{
 			$live_records = $this->_getTableData($language_id, $prefix, $unique_field, $data_fields[0], false);
 			$temp_records = $this->_getTableData($language_id, $prefix, $unique_field, $data_fields[0], true);
 
 			if ( !$temp_records ) {
 				// no data for given language
 				return;
 			}
 
 			// perform insert for records, that are missing in live table
+			$config = $this->Application->getUnitConfig($prefix);
 			$to_insert = array_diff($temp_records, $live_records);
 
 			if ( $to_insert ) {
 				$to_insert = $this->Conn->qstrArray($to_insert);
 
-				$sql = 'INSERT INTO ' . $this->Application->getUnitOption($prefix, 'TableName') . '
+				$sql = 'INSERT INTO ' . $config->getTableName() . '
 						SELECT *
 						FROM ' . $this->_tables[$prefix] . '
 						WHERE ' . $unique_field . ' IN (' . implode(',', $to_insert) . ')';
 				$this->Conn->Query($sql);
 
 				// new records were added
 				$this->changedPrefixes[] = $prefix;
 			}
 
 			// perform update for records, that are present in live table
 			$to_update = array_diff($temp_records, $to_insert);
 
 			if ( $to_update ) {
 				$to_update = $this->Conn->qstrArray($to_update);
 
-				$sql = 'UPDATE ' . $this->Application->getUnitOption($prefix, 'TableName') . ' live
+				$sql = 'UPDATE ' . $config->getTableName() . ' live
 						SET ';
 
 				foreach ($data_fields as $index => $data_field) {
 					$data_field = sprintf($data_field, $language_id);
 
 					$sql .= '	live.' . $data_field . ' = (
 									SELECT temp' . $index . '.' . $data_field . '
 									FROM ' . $this->_tables[$prefix] . ' temp' . $index . '
 									WHERE temp' . $index . '.' . $unique_field . ' = live.' . $unique_field . '
 								),';
 				}
 
 				$sql = substr($sql, 0, -1); // cut last comma
 
 				$where_clause = Array (
 					// this won't make any difference, but just in case
 					$unique_field . ' IN (' . implode(',', $to_update) . ')',
 				);
 
 				if ( !$this->hasOption(self::OVERWRITE_EXISTING) ) {
 					// empty OR not set
 					$data_field = sprintf($data_fields[0], $language_id);
 					$where_clause[] = '(' . $data_field . ' = "") OR (' . $data_field . ' IS NULL)';
 				}
 
 				if ( $where_clause ) {
 					$sql .= "\n" . 'WHERE (' . implode(') AND (', $where_clause) . ')';
 				}
 
 				$this->Conn->Query($sql);
 
 				if ( $this->Conn->getAffectedRows() > 0 ) {
 					// existing records were updated
 					$this->changedPrefixes[] = $prefix;
 				}
 			}
 		}
 
 		/**
 		 * Returns data from given table used for language pack upgrade
 		 *
 		 * @param int $language_id
 		 * @param string $prefix
 		 * @param string $unique_field
 		 * @param string $data_field
 		 * @param bool $temp_mode
 		 * @return Array
 		 */
 		function _getTableData($language_id, $prefix, $unique_field, $data_field, $temp_mode = false)
 		{
 			$data_field = sprintf($data_field, $language_id);
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+			$table_name = $this->Application->getUnitConfig($prefix)->getTableName();
 
 			if ($temp_mode) {
 				// for temp table get only records, that have contents on given language (not empty and isset)
 				$sql = 'SELECT ' . $unique_field . '
 						FROM ' . $this->Application->GetTempName($table_name, 'prefix:' . $prefix) . '
 						WHERE (' . $data_field . ' <> "") AND (' . $data_field . ' IS NOT NULL)';
 			}
 			else {
 				// for live table get all records, no matter on what language
 				$sql = 'SELECT ' . $unique_field . '
 						FROM ' . $table_name;
 			}
 
 			return $this->Conn->GetCol($sql);
 		}
 
 		function _parseXML($filename)
 		{
 			if ( $this->_debugMode ) {
 				$start_time = microtime(true);
 				$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '")');
 			}
 
 			$languages = simplexml_load_file($filename);
 
 			if ( $languages === false) {
 				// invalid language pack contents
 				return false;
 			}
 
 			// PHP 5.3 version would be: $languages->count()
 			if ( count($languages->children()) ) {
 				$this->_processLanguages($languages);
 				$this->_processLanguageData($languages);
 			}
 
 			if ( $this->_debugMode ) {
 				$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (microtime(true) - $start_time));
 			}
 
 			return true;
 		}
 
 		/**
 		 * Creates temporary tables, used during language import
 		 *
 		 * @param bool $drop_only
 		 */
 		function _initImportTables($drop_only = false)
 		{
 			$this->_tables['phrases'] = $this->_prepareTempTable('phrases', $drop_only);
 			$this->_tables['email-template'] = $this->_prepareTempTable('email-template', $drop_only);
 			$this->_tables['country-state'] = $this->_prepareTempTable('country-state', $drop_only);
 		}
 
 		/**
 		 * Create temp table for prefix, if table already exists, then delete it and create again
 		 *
 		 * @param string $prefix
 		 * @param bool $drop_only
 		 * @return string Name of created temp table
 		 * @access protected
 		 */
 		protected function _prepareTempTable($prefix, $drop_only = false)
 		{
-			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
-			$table = $this->Application->getUnitOption($prefix,'TableName');
+			$config = $this->Application->getUnitConfig($prefix);
+			$id_field = $config->getIDField();
+			$table = $config->getTableName();
 			$temp_table = $this->Application->GetTempName($table);
 
 			$sql = 'DROP TABLE IF EXISTS %s';
 			$this->Conn->Query( sprintf($sql, $temp_table) );
 
 			if (!$drop_only) {
 				$sql = 'CREATE TABLE ' . $temp_table . ' SELECT * FROM ' . $table . ' WHERE 0';
 				$this->Conn->Query($sql);
 
 				$sql = 'ALTER TABLE %1$s CHANGE %2$s %2$s INT(11) NOT NULL DEFAULT "0"';
 				$this->Conn->Query( sprintf($sql, $temp_table, $id_field) );
 
 				switch ($prefix) {
 					case 'phrases':
 						$unique_field = 'PhraseKey';
 						break;
 
 					case 'email-template':
 						$unique_field = 'TemplateId';
 						break;
 
 					case 'country-state':
 						$unique_field = 'CountryStateId';
 						break;
 
 					default:
 						throw new Exception('Unknown prefix "<strong>' . $prefix . '</strong>" during language pack import');
 						break;
 				}
 
 				$sql = 'ALTER TABLE ' . $temp_table . ' ADD UNIQUE (' . $unique_field . ')';
 				$this->Conn->Query($sql);
 			}
 
 			return $temp_table;
 		}
 
 		/**
 		 * Prepares mapping between event name+type and their ids in database
 		 *
 		 */
 		function _updateEventsCache()
 		{
 			$sql = 'SELECT TemplateId, CONCAT(TemplateName,"_",Type) AS EventMix
-					FROM ' . $this->Application->getUnitOption('email-template', 'TableName');
+					FROM ' . $this->Application->getUnitConfig('email-template')->getTableName();
+
 			$this->events_hash = $this->Conn->GetCol($sql, 'EventMix');
 		}
 
 		/**
 		 * Returns language fields to be exported
 		 *
 		 * @return Array
 		 */
 		function _getExportFields()
 		{
 			return Array (
 				'PackName', 'LocalName', 'DateFormat', 'ShortDateFormat', 'TimeFormat', 'ShortTimeFormat',
 				'InputDateFormat', 'InputTimeFormat', 'DecimalPoint', 'ThousandSep', 'UnitSystem', 'Locale',
 				'UserDocsUrl'
 			);
 		}
 
 		/**
 		 * Processes parsed XML
 		 *
 		 * @param SimpleXMLElement $languages
 		 */
 		function _processLanguages($languages)
 		{
 			$version = (int)$languages['Version'];
 
 			if ( !$version ) {
 				// version missing -> guess it
 				if ( $languages->DATEFORMAT->getName() ) {
 					$version = 1;
 				}
 				elseif ( (string)$languages->LANGUAGE['Charset'] != '' ) {
 					$version = 2;
 				}
 			}
 
 			if ( $version == 1 ) {
 				$field_mapping = Array (
 					'DATEFORMAT' => 'DateFormat',
 					'TIMEFORMAT' => 'TimeFormat',
 					'INPUTDATEFORMAT' => 'InputDateFormat',
 					'INPUTTIMEFORMAT' => 'InputTimeFormat',
 					'DECIMAL' => 'DecimalPoint',
 					'THOUSANDS' => 'ThousandSep',
 					'CHARSET' => 'Charset',
 					'UNITSYSTEM' => 'UnitSystem',
 					'DOCS_URL' => 'UserDocsUrl',
 				);
 			}
 			else {
 				$export_fields = $this->_getExportFields();
 			}
 
 			foreach ($languages as $language_node) {
 				$fields_hash = Array (
 					'PackName' => (string)$language_node['PackName'],
 					'LocalName' => (string)$language_node['PackName'],
 					'Encoding' => (string)$language_node['Encoding'],
 					'SynchronizationModes' => Language::SYNCHRONIZE_DEFAULT,
 				);
 
 				if ( $version > 1 ) {
 					foreach ($export_fields as $export_field) {
 						if ( (string)$language_node[$export_field] ) {
 							$fields_hash[$export_field] = (string)$language_node[$export_field];
 						}
 					}
 				}
 
 				$container_nodes = Array ('PHRASES', 'EVENTS', 'COUNTRIES');
 
 				foreach ($language_node as $sub_node) {
 					/* @var $sub_node SimpleXMLElement */
 
 					if ( in_array($sub_node->getName(), $container_nodes) ) {
 							continue;
 					}
 
 					switch ($sub_node->getName()) {
 						case 'REPLACEMENTS':
 							// added since v2
 							$replacements = (string)$sub_node;
 
 							if ( $fields_hash['Encoding'] != 'plain' ) {
 								$replacements = base64_decode($replacements);
 							}
 
 							$fields_hash['FilenameReplacements'] = $replacements;
 							break;
 
 						case 'EMAILDESIGNS':
 							// added since v6
 							$this->_decodeEmailDesignTemplate($fields_hash, 'HtmlEmailTemplate', (string)$sub_node->HTML);
 							$this->_decodeEmailDesignTemplate($fields_hash, 'TextEmailTemplate', (string)$sub_node->TEXT);
 							break;
 
 						default:
 							if ( $version == 1 ) {
 								$fields_hash[$field_mapping[$sub_node->Name]] = (string)$sub_node;
 							}
 							break;
 					}
 				}
 
 				$this->_processLanguage($fields_hash);
 			}
 
 			if ( !defined('IS_INSTALL') || !IS_INSTALL ) {
 				$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 				/* @var $ml_helper kMultiLanguageHelper */
 
 				// create ML columns for new languages
 				$ml_helper->resetState();
 				$ml_helper->massCreateFields();
 			}
 
 			// create temp tables after new language columns were added
 			$this->_initImportTables();
 		}
 
 		/**
 		 * Processes parsed XML
 		 *
 		 * @param SimpleXMLElement $languages
 		 */
 		function _processLanguageData($languages)
 		{
 			foreach ($languages as $language_node) {
 				$encoding = (string)$language_node['Encoding'];
 				$language_id = $this->_languages[kUtil::crc32((string)$language_node['PackName'])];
 
 				$container_nodes = Array ('PHRASES', 'EVENTS', 'COUNTRIES');
 
 				foreach ($language_node as $sub_node) {
 					/* @var $sub_node SimpleXMLElement */
 
 					if ( !in_array($sub_node->getName(), $container_nodes) || !count($sub_node->children()) ) {
 						// PHP 5.3 version would be: !$sub_node->count()
 						continue;
 					}
 
 					switch ($sub_node->getName()) {
 						case 'PHRASES':
 							$this->_processPhrases($sub_node, $language_id, $encoding);
 							break;
 
 						case 'EVENTS':
 							$this->_processEvents($sub_node, $language_id, $encoding);
 							break;
 
 						case 'COUNTRIES':
 							$this->_processCountries($sub_node, $language_id, $encoding);
 							break;
 					}
 				}
 			}
 		}
 
 		/**
 		 * Decodes e-mail template design from language pack
 		 *
 		 * @param Array $fields_hash
 		 * @param string $field
 		 * @param string $design_template
 		 */
 		protected function _decodeEmailDesignTemplate(&$fields_hash, $field, $design_template)
 		{
 			if ( $fields_hash['Encoding'] != 'plain' ) {
 				$design_template = base64_decode($design_template);
 			}
 
 			if ( $design_template ) {
 				$fields_hash[$field] = $design_template;
 			}
 		}
 
 		/**
 		 * Performs phases import
 		 *
 		 * @param SimpleXMLElement $phrases
 		 * @param int $language_id
 		 * @param string $language_encoding
 		 */
 		function _processPhrases($phrases, $language_id, $language_encoding)
 		{
 			static $other_translations = Array ();
 
 			$primary_language = $this->Application->GetDefaultLanguageId();
 			$translate_from = $this->hasOption(self::SYNC_ADDED) || $primary_language == $language_id ? 0 : $primary_language;
 
 			if ( $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->profileStart('L[' . $language_id . ']P', 'Language: ' . $language_id . '; Phrases Import');
 			}
 
 			foreach ($phrases as $phrase_node) {
 				/* @var $phrase_node SimpleXMLElement */
 
 				$phrase_key = mb_strtoupper($phrase_node['Label']);
 
 				$fields_hash = Array (
 					'Phrase' => (string)$phrase_node['Label'],
 					'PhraseKey' => $phrase_key,
 					'PhraseType' => (int)$phrase_node['Type'],
 					'Module' => (string)$phrase_node['Module'] ? (string)$phrase_node['Module'] : 'Core',
 					'LastChanged' => TIMENOW,
 					'LastChangeIP' => $this->ip_address,
 				);
 
 				$translation = (string)$phrase_node;
 				$hint_translation = (string)$phrase_node['Hint'];
 				$column_translation = (string)$phrase_node['Column'];
 
 				if ( array_key_exists($fields_hash['PhraseType'], $this->phrase_types_allowed) ) {
 					if ( $language_encoding != 'plain' ) {
 						$translation = base64_decode($translation);
 						$hint_translation = base64_decode($hint_translation);
 						$column_translation = base64_decode($column_translation);
 					}
 
 					if ( !array_key_exists($phrase_key, $other_translations) ) {
 						// ensure translation in every language to make same column count in every insert
 						$other_translations[$phrase_key] = Array ();
 
 						foreach ($this->_languages as $other_language_id) {
 							$other_translations[$phrase_key]['l' . $other_language_id . '_Translation'] = '';
 							$other_translations[$phrase_key]['l' . $other_language_id . '_HintTranslation'] = '';
 							$other_translations[$phrase_key]['l' . $other_language_id . '_ColumnTranslation'] = '';
 							$other_translations[$phrase_key]['l' . $other_language_id . '_TranslateFrom'] = 0;
 						}
 					}
 
 					$other_translations[$phrase_key]['l' . $language_id . '_Translation'] = $translation;
 					$other_translations[$phrase_key]['l' . $language_id . '_HintTranslation'] = $hint_translation;
 					$other_translations[$phrase_key]['l' . $language_id . '_ColumnTranslation'] = $column_translation;
 					$other_translations[$phrase_key]['l' . $language_id . '_TranslateFrom'] = $translate_from;
 
 					$fields_hash = array_merge($fields_hash, $other_translations[$phrase_key]);
 					$this->Conn->doInsert($fields_hash, $this->_tables['phrases'], 'REPLACE', false);
 				}
 			}
 
 			if ( $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->profileFinish('L[' . $language_id . ']P', 'Language: ' . $language_id . '; Phrases Import');
 			}
 
 			$this->Conn->doInsert($fields_hash, $this->_tables['phrases'], 'REPLACE');
 		}
 
 		/**
 		 * Performs email event import
 		 *
 		 * @param SimpleXMLElement $events
 		 * @param int $language_id
 		 * @param string $language_encoding
 		 */
 		function _processEvents($events, $language_id, $language_encoding)
 		{
 			static $other_translations = Array ();
 
 			$primary_language = $this->Application->GetDefaultLanguageId();
 			$translate_from = $this->hasOption(self::SYNC_ADDED) || $primary_language == $language_id ? 0 : $primary_language;
 
 			if ( $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->profileStart('L[' . $language_id . ']E', 'Language: ' . $language_id . '; Events Import');
 			}
 
 			$email_template_helper = $this->Application->recallObject('kEmailTemplateHelper');
 			/* @var $email_template_helper kEmailTemplateHelper */
 
 			foreach ($events as $event_node) {
 				/* @var $event_node SimpleXMLElement */
 
 				$message_type = (string)$event_node['MessageType'];
 				$email_template_id = $this->_getEmailTemplateId((string)$event_node['Event'], (int)$event_node['Type']);
 
 				if ( !$email_template_id ) {
 					continue;
 				}
 
 				$fields_hash = Array (
 					'TemplateId' => $email_template_id,
 					'TemplateName' => (string)$event_node['Event'],
 					'Type' => (int)$event_node['Type'],
 				);
 
 				if ( $message_type == '' ) {
 					$parsed = $email_template_helper->parseTemplate($event_node, '');
 					$parsed = array_map($language_encoding == 'plain' ? 'rtrim' : 'base64_decode', $parsed);
 
 				}
 				else {
 					$template = $language_encoding == 'plain' ? rtrim($event_node) : base64_decode($event_node);
 					$parsed = $email_template_helper->parseTemplate($template, $message_type);
 				}
 
 				if ( !array_key_exists($email_template_id, $other_translations) ) {
 					// ensure translation in every language to make same column count in every insert
 					$other_translations[$email_template_id] = Array ();
 
 					foreach ($this->_languages as $other_language_id) {
 						$other_translations[$email_template_id]['l' . $other_language_id . '_Subject'] = '';
 						$other_translations[$email_template_id]['l' . $other_language_id . '_HtmlBody'] = '';
 						$other_translations[$email_template_id]['l' . $other_language_id . '_PlainTextBody'] = '';
 						$other_translations[$email_template_id]['l' . $other_language_id . '_TranslateFrom'] = 0;
 					}
 				}
 
 				$other_translations[$email_template_id]['l' . $language_id . '_Subject'] = $parsed['Subject'];
 				$other_translations[$email_template_id]['l' . $language_id . '_HtmlBody'] = $parsed['HtmlBody'];
 				$other_translations[$email_template_id]['l' . $language_id . '_PlainTextBody'] = $parsed['PlainTextBody'];
 				$other_translations[$email_template_id]['l' . $language_id . '_TranslateFrom'] = $translate_from;
 
 				if ( $parsed['Headers'] ) {
 					$other_translations[$email_template_id]['Headers'] = $parsed['Headers'];
 				}
 				elseif ( !$parsed['Headers'] && !array_key_exists('Headers', $other_translations[$email_template_id]) ) {
 					$other_translations[$email_template_id]['Headers'] = $parsed['Headers'];
 				}
 
 				$fields_hash = array_merge($fields_hash, $other_translations[$email_template_id]);
 				$this->Conn->doInsert($fields_hash, $this->_tables['email-template'], 'REPLACE', false);
 			}
 
 			if ( $this->Application->isDebugMode() ) {
 				$this->Application->Debugger->profileFinish('L[' . $language_id . ']E', 'Language: ' . $language_id . '; Events Import');
 			}
 
 			if ( isset($fields_hash) ) {
 				// at least one email event in language pack was found in database
 				$this->Conn->doInsert($fields_hash, $this->_tables['email-template'], 'REPLACE');
 			}
 		}
 
 		/**
 		 * Performs country_state translation import
 		 *
 		 * @param SimpleXMLElement $country_states
 		 * @param int $language_id
 		 * @param string $language_encoding
 		 * @param bool $process_states
 		 * @return void
 		 */
 		function _processCountries($country_states, $language_id, $language_encoding, $process_states = false)
 		{
 			static $other_translations = Array ();
 
 			foreach ($country_states as $country_state_node) {
 				/* @var $country_state_node SimpleXMLElement */
 
 				if ( $process_states ) {
 					$country_state_id = $this->_getStateId((string)$country_states['Iso'], (string)$country_state_node['Iso']);
 				}
 				else {
 					$country_state_id = $this->_getCountryId((string)$country_state_node['Iso']);
 				}
 
 				if ( !$country_state_id ) {
 					continue;
 				}
 
 				if ( $language_encoding == 'plain' ) {
 					$translation = rtrim($country_state_node['Translation']);
 				}
 				else {
 					$translation = base64_decode($country_state_node['Translation']);
 				}
 
 				$fields_hash = Array ('CountryStateId' => $country_state_id);
 
 
 				if ( !array_key_exists($country_state_id, $other_translations) ) {
 					// ensure translation in every language to make same column count in every insert
 					$other_translations[$country_state_id] = Array ();
 
 					foreach ($this->_languages as $other_language_id) {
 						$other_translations[$country_state_id]['l' . $other_language_id . '_Name'] = '';
 					}
 				}
 
 				$other_translations[$country_state_id]['l' . $language_id . '_Name'] = $translation;
 
 				$fields_hash = array_merge($fields_hash, $other_translations[$country_state_id]);
 				$this->Conn->doInsert($fields_hash, $this->_tables['country-state'], 'REPLACE', false);
 
 				// PHP 5.3 version would be: $country_state_node->count()
 				if ( !$process_states && count($country_state_node->children()) ) {
 					$this->_processCountries($country_state_node, $language_id, $language_encoding, true);
 				}
 			}
 
 			$this->Conn->doInsert($fields_hash, $this->_tables['country-state'], 'REPLACE');
 		}
 
 		/**
 		 * Creates/updates language based on given fields and returns it's id
 		 *
 		 * @param Array $fields_hash
 		 * @return int
 		 */
 		function _processLanguage($fields_hash)
 		{
 			// 1. get language from database
 			$sql = 'SELECT ' . $this->lang_object->IDField . '
 					FROM ' . $this->lang_object->TableName . '
 					WHERE PackName = ' . $this->Conn->qstr($fields_hash['PackName']);
 			$language_id = $this->Conn->GetOne($sql);
 
 			if ( $language_id ) {
 				// 2. language found -> update, when allowed
 				$this->lang_object->Load($language_id);
 
 				if ( $this->hasOption(self::OVERWRITE_EXISTING) ) {
 					// update live language record based on data from xml
 					$this->lang_object->SetFieldsFromHash($fields_hash);
 					$this->lang_object->Update();
 				}
 			}
 			else {
 				// 3. language not found -> create
 				$this->lang_object->SetFieldsFromHash($fields_hash);
 				$this->lang_object->SetDBField('Enabled', STATUS_ACTIVE);
 
 				if ( $this->lang_object->Create() ) {
 					$language_id = $this->lang_object->GetID();
 
 					if ( defined('IS_INSTALL') && IS_INSTALL ) {
 						// language created during install becomes admin interface language
 						$this->lang_object->setPrimary(true, true);
 					}
 				}
 			}
 
 			// 4. collect ID of every processed language
 			if ( !in_array($language_id, $this->_languages) ) {
 				$this->_languages[kUtil::crc32($fields_hash['PackName'])] = $language_id;
 			}
 
 			return $language_id;
 		}
 
 		/**
 		 * Returns e-mail template id based on it's name and type
 		 *
 		 * @param string $template_name
 		 * @param string $template_type
 		 * @return int
 		 */
 		function _getEmailTemplateId($template_name, $template_type)
 		{
 			$cache_key = $template_name . '_' . $template_type;
 
 			return array_key_exists($cache_key, $this->events_hash) ? $this->events_hash[$cache_key] : 0;
 		}
 
 		/**
 		 * Returns country id based on it's 3letter ISO code
 		 *
 		 * @param string $iso
 		 * @return int
 		 */
 		function _getCountryId($iso)
 		{
 			static $cache = null;
 
 			if (!isset($cache)) {
 				$sql = 'SELECT CountryStateId, IsoCode
 						FROM ' . TABLE_PREFIX . 'CountryStates
 						WHERE Type = ' . DESTINATION_TYPE_COUNTRY;
 				$cache = $this->Conn->GetCol($sql, 'IsoCode');
 			}
 
 			return array_key_exists($iso, $cache) ? $cache[$iso] : false;
 		}
 
 		/**
 		 * Returns state id based on 3letter country ISO code and 2letter state ISO code
 		 *
 		 * @param string $country_iso
 		 * @param string $state_iso
 		 * @return int
 		 */
 		function _getStateId($country_iso, $state_iso)
 		{
 			static $cache = null;
 
 			if (!isset($cache)) {
 				$sql = 'SELECT CountryStateId, CONCAT(StateCountryId, "-", IsoCode) AS IsoCode
 						FROM ' . TABLE_PREFIX . 'CountryStates
 						WHERE Type = ' . DESTINATION_TYPE_STATE;
 				$cache = $this->Conn->GetCol($sql, 'IsoCode');
 			}
 
 			$country_id = $this->_getCountryId($country_iso);
 
 			return array_key_exists($country_id . '-' . $state_iso, $cache) ? $cache[$country_id . '-' . $state_iso] : false;
 		}
 
 		/**
 		 * Returns comma-separated list of IDs, that will be exported
 		 *
 		 * @param string $prefix
 		 * @return string
 		 * @access public
 		 */
 		public function getExportIDs($prefix)
 		{
 			$ids = $this->Application->RecallVar($prefix . '_selected_ids');
 
 			if ( $ids ) {
 				// some records were selected in grid
 				return $ids;
 			}
 
 			$tag_params = Array (
 				'grid' => $prefix == 'phrases' ? 'Phrases' : 'Emails',
 				'skip_counting' => 1,
 				'per_page' => -1
 			);
 
 			$list = $this->Application->recallObject($prefix, $prefix . '_List', $tag_params);
 			/* @var $list kDBList */
 
 			$sql = $list->getCountSQL($list->GetSelectSQL());
 			$sql = str_replace('COUNT(*) AS count', $list->TableName . '.' . $list->IDField, $sql);
 
 			$ids = '';
 			$rows = $this->Conn->GetIterator($sql);
 
 			if ( count($rows) ) {
 				foreach ($rows as $row) {
 					$ids .= ',' . $row[$list->IDField];
 				}
 
 				$ids = substr($ids, 1);
 			}
 
 			return $ids;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/user_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/user_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/user_helper.php	(revision 15698)
@@ -1,763 +1,763 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class UserHelper extends kHelper {
 
 		/**
 		 * Event to be used during login processings
 		 *
 		 * @var kEvent
 		 */
 		var $event = null;
 
 		/**
 		 * Performs user login and returns the result
 		 *
 		 * @param string $username
 		 * @param string $password
 		 * @param bool $dry_run
 		 * @param bool $remember_login
 		 * @param string $remember_login_cookie
 		 * @return int
 		 */
 		function loginUser($username, $password, $dry_run = false, $remember_login = false, $remember_login_cookie = '')
 		{
 			if ( !isset($this->event) ) {
 				$this->event = new kEvent('u:OnLogin');
 			}
 
 			if ( !$password && !$remember_login_cookie ) {
 				return LoginResult::INVALID_PASSWORD;
 			}
 
 			$object =& $this->getUserObject();
 
 			// process "Save Username" checkbox
 			if ( $this->Application->isAdmin ) {
 				$save_username = $this->Application->GetVar('cb_save_username') ? $username : '';
 				$this->Application->Session->SetCookie('save_username', $save_username, strtotime('+1 year'));
 
 				// cookie will be set on next refresh, but refresh won't occur if
 				// login error present, so duplicate cookie in kHTTPQuery
 				$this->Application->SetVar('save_username', $save_username);
 			}
 
 			// logging in "root" (admin only)
 			$super_admin = ($username == 'super-root') && $this->verifySuperAdmin();
 
 			if ( $this->Application->isAdmin && ($username == 'root') || ($super_admin && $username == 'super-root') ) {
 				$password_formatter = $this->Application->recallObject('kPasswordFormatter');
 				/* @var $password_formatter kPasswordFormatter */
 
 				if ( !$password_formatter->checkPasswordFromSetting('RootPass', $password) ) {
 					return LoginResult::INVALID_PASSWORD;
 				}
 
 				$user_id = USER_ROOT;
 				$object->Clear($user_id);
 				$object->SetDBField('Username', 'root');
 
 				if ( !$dry_run ) {
 					$this->loginUserById($user_id, $remember_login_cookie);
 
 					if ( $super_admin ) {
 						$this->Application->StoreVar('super_admin', 1);
 					}
 
 					// reset counters
 					$this->Application->resetCounters('UserSessions');
 
 					$this->_processLoginRedirect('root', $password);
 					$this->_processInterfaceLanguage();
 					$this->_fixNextTemplate();
 				}
 
 				return LoginResult::OK;
 			}
 
 			$user_id = $this->getUserId($username, $password, $remember_login_cookie);
 
 			if ( $user_id ) {
 				$object->Load($user_id);
 
 				if ( !$this->checkBanRules($object) ) {
 					return LoginResult::BANNED;
 				}
 
 				if ( $object->GetDBField('Status') == STATUS_ACTIVE ) {
 					if ( !$this->checkLoginPermission() ) {
 						return LoginResult::NO_PERMISSION;
 					}
 
 					if ( !$dry_run ) {
 						$this->loginUserById($user_id, $remember_login_cookie);
 
 						if ( $remember_login ) {
 							// remember username & password when "Remember Login" checkbox us checked (when user is using login form on Front-End)
 							$sql = 'SELECT MD5(Password)
 									FROM ' . TABLE_PREFIX . 'Users
 									WHERE PortalUserId = ' . $user_id;
 							$remember_login_hash = $this->Conn->GetOne($sql);
 
 							$this->Application->Session->SetCookie('remember_login', $username . '|' . $remember_login_hash, strtotime('+1 month'));
 						}
 
 						if ( !$remember_login_cookie ) {
 							// reset counters
 							$this->Application->resetCounters('UserSessions');
 
 							$this->_processLoginRedirect($username, $password);
 							$this->_processInterfaceLanguage();
 							$this->_fixNextTemplate();
 						}
 					}
 
 					return LoginResult::OK;
 				}
 				else {
 					$pending_template = $this->Application->GetVar('pending_disabled_template');
 
 					if ( $pending_template !== false && !$dry_run ) {
 						// when user found, but it's not yet approved redirect hit to notification template
 						$this->event->redirect = $pending_template;
 
 						return LoginResult::OK;
 					}
 					else {
 						// when no notification template given return an error
 						return LoginResult::INVALID_PASSWORD;
 					}
 				}
 			}
 
 			if ( !$dry_run ) {
 				$this->event->SetRedirectParam('pass', 'all');
 //				$this->event->SetRedirectParam('pass_category', 1); // to test
 			}
 
 			return LoginResult::INVALID_PASSWORD;
 		}
 
 		/**
 		 * Login user by it's id
 		 *
 		 * @param int $user_id
 		 * @param bool $remember_login_cookie
 		 */
 		function loginUserById($user_id, $remember_login_cookie = false)
 		{
 			$object =& $this->getUserObject();
 
 			$this->Application->StoreVar('user_id', $user_id);
 			$this->Application->SetVar('u.current_id', $user_id);
 			$this->Application->Session->SetField('PortalUserId', $user_id);
 
 			if ($user_id != USER_ROOT) {
 				$groups = $this->Application->RecallVar('UserGroups');
 				list ($first_group, ) = explode(',', $groups);
 
 				$this->Application->Session->SetField('GroupId', $first_group);
 				$this->Application->Session->SetField('GroupList', $groups);
 				$this->Application->Session->SetField('TimeZone', $object->GetDBField('TimeZone'));
 			}
 
 			$this->Application->LoadPersistentVars();
 
 			if (!$remember_login_cookie) {
 				// don't change last login time when auto-login is used
 				$this_login = (int)$this->Application->RecallPersistentVar('ThisLogin');
 				$this->Application->StorePersistentVar('LastLogin', $this_login);
 				$this->Application->StorePersistentVar('ThisLogin', adodb_mktime());
 			}
 
 			$hook_event = new kEvent('u:OnAfterLogin');
 			$hook_event->MasterEvent = $this->event;
 			$this->Application->HandleEvent($hook_event);
 		}
 
 		/**
 		 * Checks login permission
 		 *
 		 * @return bool
 		 */
 		function checkLoginPermission()
 		{
 			$object =& $this->getUserObject();
 			$ip_restrictions = $object->GetDBField('IPRestrictions');
 
 			if ( $ip_restrictions && !$this->Application->isDebugMode() && !kUtil::ipMatch($ip_restrictions, "\n") ) {
 				return false;
 			}
 
 			$groups = $object->getMembershipGroups(true);
 			if ( !$groups ) {
 				$groups = Array ();
 			}
 
 			$default_group = $this->getUserTypeGroup();
 			if ( $default_group !== false ) {
 				array_push($groups, $default_group);
 			}
 
 			// store groups, because kApplication::CheckPermission will use them!
 			array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup'));
 			$groups = array_unique($groups);
 			$this->Application->StoreVar('UserGroups', implode(',', $groups), true); // true for optional
 
 			return $this->Application->CheckPermission($this->Application->isAdmin ? 'ADMIN' : 'LOGIN', 1);
 		}
 
 		/**
 		 * Returns default user group for it's type
 		 *
 		 * @return bool|string
 		 * @access protected
 		 */
 		protected function getUserTypeGroup()
 		{
 			$group_id = false;
 			$object =& $this->getUserObject();
 
 			if ( $object->GetDBField('UserType') == UserType::USER ) {
 				$group_id = $this->Application->ConfigValue('User_NewGroup');
 			}
 			elseif ( $object->GetDBField('UserType') == UserType::ADMIN ) {
 				$group_id = $this->Application->ConfigValue('User_AdminGroup');
 			}
 
 			$ip_restrictions = $this->getGroupsWithIPRestrictions();
 
 			if ( !isset($ip_restrictions[$group_id]) || kUtil::ipMatch($ip_restrictions[$group_id], "\n") ) {
 				return $group_id;
 			}
 
 			return false;
 		}
 
 		/**
 		 * Returns groups with IP restrictions
 		 *
 		 * @return Array
 		 * @access public
 		 */
 		public function getGroupsWithIPRestrictions()
 		{
 			static $cache = null;
 
 			if ( $this->Application->isDebugMode() ) {
 				return Array ();
 			}
 
 			if ( !isset($cache) ) {
 				$sql = 'SELECT IPRestrictions, GroupId
 						FROM ' . TABLE_PREFIX . 'UserGroups
 						WHERE IPRestrictions IS NOT NULL';
 				$cache = $this->Conn->GetCol($sql, 'GroupId');
 			}
 
 			return $cache;
 		}
 
 		/**
 		 * Performs user logout
 		 *
 		 */
 		function logoutUser()
 		{
 			if (!isset($this->event)) {
 				$this->event = new kEvent('u:OnLogout');
 			}
 
 			$hook_event = new kEvent('u:OnBeforeLogout');
 			$hook_event->MasterEvent = $this->event;
 			$this->Application->HandleEvent($hook_event);
 
 			$this->_processLoginRedirect();
 
 			$user_id = USER_GUEST;
 			$this->Application->SetVar('u.current_id', $user_id);
 
 			$object = $this->Application->recallObject('u.current', null, Array('skip_autoload' => true));
 			/* @var $object UsersItem */
 
 			$object->Load($user_id);
 
 			$this->Application->DestroySession();
 
 			$this->Application->StoreVar('user_id', $user_id, true);
 			$this->Application->Session->SetField('PortalUserId', $user_id);
 
 			$group_list = $this->Application->ConfigValue('User_GuestGroup') . ',' . $this->Application->ConfigValue('User_LoggedInGroup');
 			$this->Application->StoreVar('UserGroups', $group_list, true);
 			$this->Application->Session->SetField('GroupList', $group_list);
 
 			if ($this->Application->ConfigValue('UseJSRedirect')) {
 				$this->event->SetRedirectParam('js_redirect', 1);
 			}
 
 			$this->Application->resetCounters('UserSessions');
 			$this->Application->Session->SetCookie('remember_login', '', strtotime('-1 hour'));
 
 			// don't pass user prefix on logout, since resulting url will have broken "env"
 			$this->event->SetRedirectParam('pass', MOD_REWRITE ? 'm' : 'all');
 
 			$this->_fixNextTemplate();
 		}
 
 		/**
 		 * Returns user id based on given criteria
 		 *
 		 * @param string $username
 		 * @param string $password
 		 * @param string $remember_login_cookie
 		 * @return int
 		 */
 		function getUserId($username, $password, $remember_login_cookie)
 		{
 			if ( $remember_login_cookie ) {
 				list ($username, $password) = explode('|', $remember_login_cookie); // 0 - username, 1 - md5(password_hash)
 			}
 
 			$sql = 'SELECT PortalUserId, Password, PasswordHashingMethod
 					FROM ' . TABLE_PREFIX . 'Users
 					WHERE Email = %1$s OR Username = %1$s';
 			$user_info = $this->Conn->GetRow(sprintf($sql, $this->Conn->qstr($username)));
 
 			if ( $user_info ) {
 				if ( $remember_login_cookie ) {
 					return md5($user_info['Password']) == $password;
 				}
 				else {
 					$password_formatter = $this->Application->recallObject('kPasswordFormatter');
 					/* @var $password_formatter kPasswordFormatter */
 
 					$hashing_method = $user_info['PasswordHashingMethod'];
 
 					if ( $password_formatter->checkPassword($password, $user_info['Password'], $hashing_method) ) {
 						if ( $hashing_method != PasswordHashingMethod::PHPPASS ) {
 							$this->_fixUserPassword($user_info['PortalUserId'], $password);
 						}
 
 						return $user_info['PortalUserId'];
 					}
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Apply new password hashing to given user's password
 		 *
 		 * @param int $user_id
 		 * @param string $password
 		 * @return void
 		 * @access protected
 		 */
 		protected function _fixUserPassword($user_id, $password)
 		{
 			$password_formatter = $this->Application->recallObject('kPasswordFormatter');
 			/* @var $password_formatter kPasswordFormatter */
 
 			$fields_hash = Array (
 				'Password' => $password_formatter->hashPassword($password),
 				'PasswordHashingMethod' => PasswordHashingMethod::PHPPASS,
 			);
 
 			$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Users', 'PortalUserId = ' . $user_id);
 		}
 
 		/**
 		 * Process all required data and redirect logged-in user
 		 *
 		 * @param string $username
 		 * @param string $password
 		 * @return void
 		 */
 		protected function _processLoginRedirect($username = null, $password = null)
 		{
 			// set next template
 			$next_template = $this->Application->GetVar('next_template');
 
 			if ( $next_template ) {
 				$this->event->redirect = $next_template;
 			}
 
 			// process IIS redirect
 			if ( $this->Application->ConfigValue('UseJSRedirect') ) {
 				$this->event->SetRedirectParam('js_redirect', 1);
 			}
 
 			// synchronize login
 			$sync_manager = $this->Application->recallObject('UsersSyncronizeManager', null, Array (), Array ('InPortalSyncronize'));
 			/* @var $sync_manager UsersSyncronizeManager */
 
 			if ( isset($username) && isset($password) ) {
 				$sync_manager->performAction('LoginUser', $username, $password);
 			}
 			else {
 				$sync_manager->performAction('LogoutUser');
 			}
 		}
 
 		/**
 		 * Sets correct interface language after successful login, based on user settings
 		 *
 		 * @return void
 		 * @access protected
 		 */
 		protected function _processInterfaceLanguage()
 		{
 			if ( defined('IS_INSTALL') && IS_INSTALL ) {
 				$this->event->SetRedirectParam('m_lang', 1); // data
 				$this->Application->Session->SetField('Language', 1); // interface
 
 				return;
 			}
 
 			$language_field = $this->Application->isAdmin ? 'AdminLanguage' : 'FrontLanguage';
 			$primary_language_field = $this->Application->isAdmin ? 'AdminInterfaceLang' : 'PrimaryLang';
 			$is_root = $this->Application->RecallVar('user_id') == USER_ROOT;
 
 			$object =& $this->getUserObject();
 
 			$user_language_id = $is_root ? $this->Application->RecallPersistentVar($language_field) : $object->GetDBField($language_field);
 
 			$sql = 'SELECT LanguageId, IF(LanguageId = ' . (int)$user_language_id . ', 2, ' . $primary_language_field . ') AS SortKey
 					FROM ' . TABLE_PREFIX . 'Languages
 					WHERE Enabled = 1
 					HAVING SortKey <> 0
 					ORDER BY SortKey DESC';
 			$language_info = $this->Conn->GetRow($sql);
 			$language_id = $language_info && $language_info['LanguageId'] ? $language_info['LanguageId'] : $user_language_id;
 
 			if ( $user_language_id != $language_id ) {
 				// first login OR language was deleted or disabled
 				if ( $is_root ) {
 					$this->Application->StorePersistentVar($language_field, $language_id);
 				}
 				else {
 					$object->SetDBField($language_field, $language_id);
 					$object->Update();
 				}
 			}
 
 			// set language for Admin Console & Front-End with disabled Mod-Rewrite
 			$this->event->SetRedirectParam('m_lang', $language_id); // data
 			$this->Application->Session->SetField('Language', $language_id); // interface
 		}
 
 		/**
 		 * Injects redirect params into next template, which doesn't happen if next template starts with "external:"
 		 *
 		 * @return void
 		 * @access protected
 		 */
 		protected function _fixNextTemplate()
 		{
 			if ( !MOD_REWRITE || !is_object($this->event) ) {
 				return;
 			}
 
 			// solve problem, when template is "true" instead of actual template name
 			$template = is_string($this->event->redirect) ? $this->event->redirect : '';
 			$url = $this->Application->HREF($template, '', $this->event->getRedirectParams(), $this->event->redirectScript);
 			$vars = $this->Application->parseRewriteUrl($url, 'pass');
 			unset($vars['login'], $vars['logout']);
 
 			// merge back url params, because they were ignored if this was "external:" url
 			$vars['pass'] = implode(',', $vars['pass']);
 			$vars = array_merge($vars, $this->event->getRedirectParams());
 
 			$template = $vars['t'];
 			unset($vars['is_virtual'], $vars['t']);
 
 			$this->event->redirect = $template;
 			$this->event->setRedirectParams($vars, false);
 		}
 
 		/**
 		 * Checks that user is allowed to use super admin mode
 		 *
 		 * @return bool
 		 */
 		function verifySuperAdmin()
 		{
 			$sa_mode = kUtil::ipMatch(defined('SA_IP') ? SA_IP : '');
 			return $sa_mode || $this->Application->isDebugMode();
 		}
 
 		/**
 		 * Returns user object, used during login processing
 		 *
 		 * @return UsersItem
 		 * @access public
 		 */
 		public function &getUserObject()
 		{
 			$prefix_special = $this->Application->isAdmin ? 'u.current' : 'u'; // "u" used on front not to change theme
 
 			$object = $this->Application->recallObject($prefix_special, null, Array('skip_autoload' => true));
 			/* @var $object UsersItem */
 
 			return $object;
 		}
 
 		/**
 		 * Checks, if given user fields matches at least one of defined ban rules
 		 *
 		 * @param kDBItem $object
 		 * @return bool
 		 */
 		function checkBanRules(&$object)
 		{
-			$table = $this->Application->getUnitOption('ban-rule', 'TableName');
+			$table = $this->Application->getUnitConfig('ban-rule')->getTableName();
 
 			if (!$this->Conn->TableFound($table)) {
 				// when ban table not found -> assume user is ok by default
 				return true;
 			}
 
 			$sql = 'SELECT *
 					FROM ' . $table . '
 					WHERE ItemType = 6 AND Status = ' . STATUS_ACTIVE . '
 					ORDER BY Priority DESC';
 			$rules = $this->Conn->Query($sql);
 
 			$found = false;
 
 			foreach ($rules as $rule) {
 				$field = $rule['ItemField'];
 				$this_value = mb_strtolower( $object->GetDBField($field) );
 				$test_value = mb_strtolower( $rule['ItemValue'] );
 
 				switch ( $rule['ItemVerb'] ) {
 					case 1: // is
 						if ($this_value == $test_value) {
 							$found = true;
 						}
 						break;
 
 					case 2: // is not
 						if ($this_value != $test_value) {
 							$found = true;
 						}
 						break;
 
 					case 3: // contains
 						if ( strstr($this_value, $test_value) ) {
 							$found = true;
 						}
 						break;
 
 					case 4: // not contains
 						if ( !strstr($this_value, $test_value) ) {
 							$found = true;
 						}
 						break;
 
 					case 7: // exists
 						if ( strlen($this_value) > 0 ) {
 							$found = true;
 						}
 						break;
 
 					case 8: // unique
 						if ( $this->_checkValueExist($field, $this_value) ) {
 							$found = true;
 						}
 						break;
 				}
 
 				if ( $found ) {
 					// check ban rules, until one of them matches
 
 					if ( $rule['RuleType'] ) {
 						// invert rule type
 						$found = false;
 					}
 
 					break;
 				}
 			}
 
 			return !$found;
 		}
 
 		/**
 		 * Checks if value is unique in Users table against the specified field
 		 *
 		 * @param string $field
 		 * @param string $value
 		 * @return string
 		 */
 		function _checkValueExist($field, $value)
 	    {
 	    	$sql = 'SELECT *
-	    			FROM ' . $this->Application->getUnitOption('u', 'TableName') . '
+	    			FROM ' . $this->Application->getUnitConfig('u')->getTableName() . '
 					WHERE '. $field .' = ' . $this->Conn->qstr($value);
 
 			return $this->Conn->GetOne($sql);
 	    }
 
 		public function validateUserCode($user_code, $code_type, $expiration_timeout = null)
 		{
 			$expiration_timeouts = Array (
 				'forgot_password' => 'config:Users_AllowReset',
 				'activation' => 'config:UserEmailActivationTimeout',
 				'verify_email' => 'config:Users_AllowReset',
 				'custom' => '',
 			);
 
 		    if ( !$user_code ) {
 		    	return 'code_is_not_valid';
 		    }
 
 		    $sql = 'SELECT PwRequestTime, PortalUserId
 		    		FROM ' . TABLE_PREFIX . 'Users
 		    		WHERE PwResetConfirm = ' . $this->Conn->qstr( trim($user_code) );
 		    $user_info = $this->Conn->GetRow($sql);
 
 		    if ( $user_info === false ) {
 		    	return 'code_is_not_valid';
 		    }
 
 	    	$expiration_timeout = isset($expiration_timeout) ? $expiration_timeout : $expiration_timeouts[$code_type];
 
 	    	if ( preg_match('/^config:(.*)$/', $expiration_timeout, $regs) ) {
 	    		$expiration_timeout = $this->Application->ConfigValue( $regs[1] );
 	    	}
 
 	    	if ( $expiration_timeout && $user_info['PwRequestTime'] < strtotime('-' . $expiration_timeout . ' minutes') ) {
 				return 'code_expired';
 	    	}
 
 			return $user_info['PortalUserId'];
 		}
 
 		/**
 		 * Restores user's email, returns error label, if error occurred
 		 *
 		 * @param string $hash
 		 * @return string
 		 * @access public
 		 */
 		public function restoreEmail($hash)
 		{
 			if ( !preg_match('/^[a-f0-9]{32}$/', $hash) ) {
 				return 'invalid_hash';
 			}
 
 			$sql = 'SELECT PortalUserId, PrevEmails
 					FROM ' . TABLE_PREFIX . 'Users
 					WHERE PrevEmails LIKE ' . $this->Conn->qstr('%' . $hash . '%');
 			$user_info = $this->Conn->GetRow($sql);
 
 			if ( $user_info === false ) {
 				return 'invalid_hash';
 			}
 
 			$prev_emails = $user_info['PrevEmails'];
 			$prev_emails = $prev_emails ? unserialize($prev_emails) : Array ();
 
 			if ( !isset($prev_emails[$hash]) ) {
 				return 'invalid_hash';
 			}
 
 			$email_to_restore = $prev_emails[$hash];
 			unset($prev_emails[$hash]);
 
 			$object = $this->Application->recallObject('u.email-restore', null, Array ('skip_autoload' => true));
 			/* @var $object UsersItem */
 
 			$object->Load($user_info['PortalUserId']);
 			$object->SetDBField('PrevEmails', serialize($prev_emails));
 			$object->SetDBField('Email', $email_to_restore);
 			$object->SetDBField('EmailVerified', 1);
 
 			return $object->Update() ? '' : 'restore_impossible';
 		}
 
 		/**
 		 * Generates random string
 		 *
 		 * @param int $length
 		 * @param bool $special_chars
 		 * @param bool $extra_special_chars
 		 * @return string
 		 * @access public
 		 */
 		public function generateRandomString($length = 12, $special_chars = true, $extra_special_chars = false)
 		{
 			$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
 
 			if ( $special_chars ) {
 				$chars .= '!@#$%^&*()';
 			}
 
 			if ( $extra_special_chars ) {
 				$chars .= '-_ []{}<>~`+=,.;:/?|';
 			}
 
 			$password = '';
 
 			for ($i = 0; $i < $length; $i++) {
 				$password .= substr($chars, $this->_generateRandomNumber(0, strlen($chars) - 1), 1);
 			}
 
 			return $password;
 		}
 
 		/**
 		 * Generates a random number
 		 *
 		 * @param int $min Lower limit for the generated number (optional, default is 0)
 		 * @param int $max Upper limit for the generated number (optional, default is 4294967295)
 		 * @return int A random number between min and max
 		 * @access protected
 		 */
 		protected function _generateRandomNumber($min = 0, $max = 0)
 		{
 			static $rnd_value = '';
 
 			// Reset $rnd_value after 14 uses
 			// 32(md5) + 40(sha1) + 40(sha1) / 8 = 14 random numbers from $rnd_value
 			if ( strlen($rnd_value) < 8 ) {
 				$random_seed = $this->Application->getDBCache('random_seed');
 				$rnd_value = md5(uniqid(microtime() . mt_rand(), true) . $random_seed);
 				$rnd_value .= sha1($rnd_value);
 				$rnd_value .= sha1($rnd_value . $random_seed);
 				$random_seed = md5($random_seed . $rnd_value);
 				$this->Application->setDBCache('random_seed', $random_seed);
 			}
 
 			// Take the first 8 digits for our value
 			$value = substr($rnd_value, 0, 8);
 
 			// Strip the first eight, leaving the remainder for the next call to wp_rand().
 			$rnd_value = substr($rnd_value, 8);
 
 			$value = abs(hexdec($value));
 
 			// Reduce the value to be within the min - max range
 			// 4294967295 = 0xffffffff = max random number
 			if ( $max != 0 ) {
 				$value = $min + (($max - $min + 1) * ($value / (4294967295 + 1)));
 			}
 
 			return abs(intval($value));
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/controls/minput_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/controls/minput_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/controls/minput_helper.php	(revision 15698)
@@ -1,218 +1,218 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class MInputHelper extends kHelper {
 
 		/**
 		 * Returns table for given prefix
 		 *
 		 * @param string $prefix
 		 * @param bool $temp
 		 * @return string
 		 * @access protected
 		 */
 		protected function getTable($prefix, $temp = false)
 		{
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+			$table_name = $this->Application->getUnitConfig($prefix)->getTableName();
 
 			return $temp ? $this->Application->GetTempName($table_name, 'prefix:' . $prefix) : $table_name;
 		}
 
 		function prepareMInputXML($records, $use_fields)
 		{
 			$xml = '';
 			foreach ($records as $record) {
 				$xml .= '<record>';
 				foreach ($record as $field_name => $field_value) {
 					if (!in_array($field_name, $use_fields)) {
 						continue;
 					}
 					$xml .= '<field name="' . $field_name . '">' . htmlspecialchars($field_value, null, CHARSET) . '</field>';
 				}
 				$xml .= '</record>';
 			}
 
 			return $xml ? '<records>'.$xml.'</records>' : '';
 		}
 
 		/**
 		 * Returns validation errors in XML format
 		 *
 		 * @param kDBItem $object
 		 * @param Array $fields_hash
 		 * @return string
 		 */
 		function prepareErrorsXML(&$object, $fields_hash)
 		{
 			$xml = '';
 			$errors = Array ();
 
 			foreach ($fields_hash as $field_name => $field_value) {
 				if (!$object->ValidateField($field_name)) {
 					$field_options = $object->GetFieldOptions($field_name);
 					$error_field = array_key_exists('error_field', $field_options) ? $field_options['error_field'] : $field_name;
 
 					$errors[$error_field] = '<field name="'.$error_field.'">'.$object->GetErrorMsg($error_field, false).'</field>';
 				}
 			}
 
 			return '<errors>'.implode('', $errors).'</errors>';
 		}
 
 		/**
 		 * Validates MInput control fields
 		 *
 		 * @param kEvent $event
 		 */
 		function OnValidateMInputFields($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 			if ($items_info) {
 				list ($id, $field_values) = each($items_info);
 
 				foreach ($field_values as $field_name => $field_value) {
 					$object->SetField($field_name, $field_value);
 				}
 
 				$event_mapping = Array (
 					'AddRecord' => 'OnBeforeItemCreate',
 					'SaveRecord' => 'OnBeforeItemUpdate',
 				);
 
 				$request_type = $this->Application->GetVar('request_type');
 
 				if (array_key_exists($request_type, $event_mapping)) {
 					$event->CallSubEvent($event_mapping[$request_type]);
 				}
 
 				echo $this->prepareErrorsXML($object, $field_values);
 			}
 
 			$event->status = kEvent::erSTOP;
 		}
 
 		function parseMInputXML($xml)
 		{
 			$records = Array ();
 			$records_node = simplexml_load_string($xml);
 
 			if ( $records_node === false ) {
 				return false;
 			}
 
 			foreach ($records_node as $record_node) {
 				$record = Array ();
 
 				foreach ($record_node as $field_node) {
 					$record[(string)$field_node['name']] = (string)$field_node;
 				}
 
 				$records[] = $record;
 			}
 
 			return $records;
 		}
 
 		/**
 		 * Loads selected values from sub_prefix to main item virtual field.
 		 * Called from OnAfterItemLoad of main prefix.
 		 *
 		 * @param kEvent $event
 		 * @param string $store_field main item's field name, to store values into
 		 * @param string $sub_prefix prefix used to store info about selected items
 		 * @param Array $use_fields fields, used in value string building
 		 */
 		function LoadValues($event, $store_field, $sub_prefix, $use_fields)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sub_item = $this->Application->recallObject($sub_prefix, null, Array('skip_autoload' => true));
 			/* @var $sub_item kDBItem */
 
-			$foreign_key = $this->Application->getUnitOption($sub_prefix, 'ForeignKey');
+			$foreign_key = $this->Application->getUnitConfig($sub_prefix)->getForeignKey();
 
 			$sql = 'SELECT *
 					FROM '.$this->getTable($sub_prefix, $object->IsTempTable()).'
 					WHERE '.$foreign_key.' = '.$object->GetID();
 
 			$selected_items = $this->Conn->Query($sql);
 
 			$field_names = array_keys( $sub_item->GetFieldValues() );
 
 			foreach ($selected_items as $key => $fields_hash) {
 				$sub_item->Clear();
 				$sub_item->SetDBFieldsFromHash($fields_hash);
 
 				// to fill *_date and *_time fields from main date fields
 				$sub_item->UpdateFormattersSubFields();
 
 				foreach ($field_names as $field) {
 					$field_options = $sub_item->GetFieldOptions($field);
 					$formatter = array_key_exists('formatter', $field_options) ? $field_options['formatter'] : false;
 
 					if ($formatter == 'kDateFormatter') {
 						$selected_items[$key][$field] = $sub_item->GetField($field, $field_options['input_format']);
 					}
 					else {
 						$selected_items[$key][$field] = $sub_item->GetDBField($field);
 					}
 				}
 			}
 
 			$object->SetDBField($store_field, $this->prepareMInputXML($selected_items, $use_fields));
 		}
 
 		/**
 		 * Saves data from minput control to subitem table (used from subitem hook)
 		 *
 		 * @param kEvent $sub_event
 		 * @param string $store_field
 		 */
 		function SaveValues(&$sub_event, $store_field)
 		{
 			$main_object = $sub_event->MasterEvent->getObject();
 			/* @var $main_object kDBItem */
 
 			$affected_field = $main_object->GetDBField($store_field);
 
 			$object = $this->Application->recallObject($sub_event->getPrefixSpecial(), null, Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$sub_table = $object->TableName;
-			$foreign_key = $this->Application->getUnitOption($sub_event->Prefix, 'ForeignKey');
+			$foreign_key = $sub_event->getUnitConfig()->getForeignKey();
 
 			$sql = 'DELETE FROM '.$sub_table.'
 					WHERE '.$foreign_key.' = '.$main_object->GetID();
 
 			$this->Conn->Query($sql);
 
 			if ($affected_field) {
 				$records = $this->parseMInputXML($affected_field);
 				$main_id = $main_object->GetID();
 
 				foreach ($records as $fields_hash) {
 					$object->Clear();
 					$fields_hash[$foreign_key] = $main_id;
 					$object->SetDBFieldsFromHash($fields_hash);
 					$object->Create();
 				}
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/controls/edit_picker_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/controls/edit_picker_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/controls/edit_picker_helper.php	(revision 15698)
@@ -1,186 +1,186 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class EditPickerHelper extends kHelper {
 
-
-		function getTable($prefix, $temp=false)
+		function getTable($prefix, $temp = false)
 		{
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
-			return $temp ? $this->Application->GetTempName($table_name, 'prefix:'.$prefix) : $table_name;
+			$table_name = $this->Application->getUnitConfig($prefix)->getTableName();
+
+			return $temp ? $this->Application->GetTempName($table_name, 'prefix:' . $prefix) : $table_name;
 		}
 
 		/**
 		 * Applies filter for multiple lists in inp_edit_picker control.
 		 * Called from SetCustomQuery of prefix, that contains all available items.
 		 *
 		 * @param kEvent $event
 		 * @param string $storage_field main item's field name, where values are located
 		 */
 		function applyFilter($event, $storage_field)
 		{
 			if ($event->Special != 'selected' && $event->Special != 'available') {
 				return ;
 			}
 
 			if ($storage_field != $event->getEventParam('link_to_field')) {
 				return ;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$main_object = $this->Application->recallObject($event->getEventParam('link_to_prefix'));
 			/* @var $main_object kDBItem */
 
 			$selected_items = $main_object->GetDBField($storage_field);
 			if ($selected_items) {
 				$filter_type = $event->Special == 'selected' ? 'IN' : 'NOT IN';
 				$selected_items = explode('|', substr($selected_items, 1, -1));
 				$filter_clause = '%1$s.' . $object->IDField.' '.$filter_type.' ('.implode(',', $selected_items).')';
 			}
 			else {
 				$filter_clause = ($event->Special == 'selected') ? 'FALSE' : 'TRUE';
 			}
 
 			$constrain = $this->_getConstrain($main_object, $storage_field, 'filter');
 			if ($constrain) {
 				$filter_clause .= ' AND (' . $constrain . ')';
 			}
 
 			$object->addFilter('edit_picker_filter', $filter_clause);
 		}
 
 		/**
 		 * Loads selected values from sub_prefix to main item virtual field.
 		 * Called from OnAfterItemLoad of main prefix.
 		 *
 		 * @param kEvent $event
 		 * @param string $store_field main item's field name, to store values into
 		 * @param string $source_field prefix and it's field used to store info about selected items (format: prefix.field)
 		 */
 		function LoadValues($event, $store_field, $source_field)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			list ($sub_prefix, $sub_prefix_field) = explode('.', $source_field);
-			$foreign_key = $this->Application->getUnitOption($sub_prefix, 'ForeignKey');
+			$foreign_key = $this->Application->getUnitConfig($sub_prefix)->getForeignKey();
 
 			$sql = 'SELECT '.$sub_prefix_field.'
 					FROM '.$this->getTable($sub_prefix, $object->IsTempTable()).'
 					WHERE '.$foreign_key.' = '.$object->GetID();
 
 			$constrain = $this->_getConstrain($object, $store_field, 'load');
 			if ($constrain) {
 				$sql .= ' AND (' . $sub_prefix_field . ' IN (' . $constrain . '))';
 			}
 
 			$selected_items = array_unique($this->Conn->GetCol($sql));
 
 			$object->SetDBField($store_field, $selected_items ? '|'.implode('|', $selected_items).'|' : '');
 		}
 
 		/**
 		 * Saves value to sub-item's table
 		 *
 		 * @param kEvent $sub_event
 		 * @param string $store_field main item's field name, to get values from
 		 * @param string $sub_prefix_field check already existing records by this field
 		 */
 		function SaveValues(&$sub_event, $store_field, $sub_prefix_field)
 		{
 			$main_object = $sub_event->MasterEvent->getObject();
 			/* @var $main_object kDBItem */
 
 			$affected_field = $main_object->GetDBField($store_field);
 
 			$object = $this->Application->recallObject($sub_event->getPrefixSpecial(), null, Array('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$sub_table = $object->TableName;
-			$foreign_key = $this->Application->getUnitOption($sub_event->Prefix, 'ForeignKey');
+			$foreign_key = $sub_event->getUnitConfig()->getForeignKey();
 
 			// 1. get previous values from db
 			$sql = 'SELECT ' . $sub_prefix_field . '
 					FROM ' . $sub_table . '
 					WHERE '.$foreign_key.' = '.$main_object->GetID();
 
 			$constrain = $this->_getConstrain($main_object, $store_field, 'save');
 			if ($constrain) {
 				$sql .= ' AND (' . $sub_prefix_field . ' IN (' . $constrain . '))';
 			}
 
 			$old_values = $this->Conn->GetCol($sql);
 
 			// 2. get new values from form
 			$new_values = $affected_field ? explode('|', substr($affected_field, 1, -1)) : Array ();
 
 			$records_to_add = array_diff($new_values, $old_values);
 			$records_to_delete = array_diff($old_values, $new_values);
 
 			if ($records_to_delete && $main_object->isLoaded()) {
 				$where_clause = Array (
 					$foreign_key . ' = ' . $main_object->GetID(),
 					$sub_prefix_field . ' IN (' . implode(',', $records_to_delete) . ')',
 				);
 				$sql = 'SELECT ' . $object->IDField . '
 						FROM ' . $sub_table . '
 						WHERE (' . implode(') AND (', $where_clause) . ')';
 				$delete_ids = $this->Conn->GetCol($sql);
 
 				foreach ($delete_ids as $delete_id) {
 					$object->Delete($delete_id);
 				}
 			}
 
 			if ($records_to_add) {
 				$main_id = $main_object->GetID();
 				foreach ($records_to_add as $add_id) {
 					$object->Clear();
 					$object->SetDBField($foreign_key, $main_id);
 					$object->SetDBField($sub_prefix_field, $add_id);
 					$object->Create();
 				}
 			}
 		}
 
 		/**
 		 * Returns constrain for picker options query
 		 *
 		 * @param kDBItem $object
 		 * @param string $store_field
 		 * @param string $mode
 		 * @return bool|string
 		 */
 		function _getConstrain(&$object, $store_field, $mode = 'filter')
 		{
 			$field_options = $object->GetFieldOptions($store_field);
 			$constrain = array_key_exists('option_constrain', $field_options) ? $field_options['option_constrain']
 					: false;
 
 			if ( $mode == 'filter' ) {
 				// filter on edit form
 				return $constrain;
 			}
 			elseif ( $constrain ) {
 				// load or save
 				return sprintf($field_options['options_sql'], $field_options['option_key_field']);
 			}
 
 			return false;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/file_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/file_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/file_helper.php	(revision 15698)
@@ -1,461 +1,455 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class FileHelper extends kHelper {
 
 		/**
 		 * Puts existing item images (from sub-item) to virtual fields (in main item)
 		 *
 		 * @param kCatDBItem $object
 		 * @return void
 		 * @access public
 		 */
 		public function LoadItemFiles(&$object)
 		{
 			$max_file_count = $this->Application->ConfigValue($object->Prefix.'_MaxImageCount'); // file count equals to image count (temporary measure)
 
 			$sql = 'SELECT *
 					FROM '.TABLE_PREFIX.'CatalogFiles
 					WHERE ResourceId = '.$object->GetDBField('ResourceId').'
 					ORDER BY FileId ASC
 					LIMIT 0, '.(int)$max_file_count;
 			$item_files = $this->Conn->Query($sql);
 
 			$file_counter = 1;
 			foreach ($item_files as $item_file) {
 				$file_path = $item_file['FilePath'];
 				$object->SetDBField('File'.$file_counter, $file_path);
 				$object->SetOriginalField('File'.$file_counter, $file_path);
 				$object->SetFieldOption('File'.$file_counter, 'original_field', $item_file['FileName']);
 				$file_counter++;
 			}
 		}
 
 		/**
 		 * Saves newly uploaded images to external image table
 		 *
 		 * @param kCatDBItem $object
 		 * @return void
 		 * @access public
 		 */
 		public function SaveItemFiles(&$object)
 		{
-			$table_name = $this->Application->getUnitOption('#file', 'TableName');
-			$max_file_count = $this->Application->getUnitOption($object->Prefix, 'FileCount'); // $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
+			$table_name = $this->Application->getUnitConfig('#file')->getTableName();
+			$max_file_count = $object->getUnitConfig()->getFileCount();
+//			$max_file_count = $this->Application->ConfigValue($object->Prefix . '_MaxImageCount');
 
 			$this->CheckFolder(FULL_PATH . ITEM_FILES_PATH);
 
 			$i = 0;
 			while ($i < $max_file_count) {
 				$field = 'File'.($i + 1);
 				$field_options = $object->GetFieldOptions($field);
 
 				$file_path = $object->GetDBField($field);
 				if ($file_path) {
 					if (isset($field_options['original_field'])) {
 						$key_clause = 'FileName = '.$this->Conn->qstr($field_options['original_field']).' AND ResourceId = '.$object->GetDBField('ResourceId');
 
 						if ($object->GetDBField('Delete'.$field)) {
 							// if item was cloned, then new filename is in db (not in $image_src)
 							$sql = 'SELECT FilePath
 									FROM '.$table_name.'
 									WHERE '.$key_clause;
 							$file_path = $this->Conn->GetOne($sql);
 							if (@unlink(FULL_PATH.ITEM_FILES_PATH.$file_path)) {
 								$sql = 'DELETE FROM '.$table_name.'
 										WHERE '.$key_clause;
 								$this->Conn->Query($sql);
 							}
 						}
 						else {
 							// image record found -> update
 							$fields_hash = Array (
 								'FilePath' => $file_path,
 							);
 
 							$this->Conn->doUpdate($fields_hash, $table_name, $key_clause);
 						}
 					}
 					else {
 						// record not found -> create
 						$fields_hash = Array (
 							'ResourceId' => $object->GetDBField('ResourceId'),
 							'FileName' => $field,
 							'Status' => STATUS_ACTIVE,
 							'FilePath' => $file_path,
 						);
 
 						$this->Conn->doInsert($fields_hash, $table_name);
 						$field_options['original_field'] = $field;
 						$object->SetFieldOptions($field, $field_options);
 					}
 				}
 				$i++;
 			}
 		}
 
 		/**
 		 * Preserves cloned item images/files to be rewritten with original item images/files
 		 *
 		 * @param Array $field_values
 		 * @return void
 		 * @access public
 		 */
 		public function PreserveItemFiles(&$field_values)
 		{
 			foreach ($field_values as $field_name => $field_value) {
 				if ( !is_array($field_value) ) {
 					continue;
 				}
 
 				if ( isset($field_value['upload']) && ($field_value['error'] == UPLOAD_ERR_NO_FILE) ) {
 					// this is upload field, but nothing was uploaded this time
 					unset($field_values[$field_name]);
 				}
 			}
 		}
 
 		/**
 		 * Determines what image/file fields should be created (from post or just dummy fields for 1st upload)
 		 *
 		 * @param string $prefix
 		 * @param bool $is_image
 		 * @return void
 		 * @access public
 		 */
 		public function createItemFiles($prefix, $is_image = false)
 		{
 			$items_info = $this->Application->GetVar($prefix);
 			if ($items_info) {
 				list (, $fields_values) = each($items_info);
 				$this->createUploadFields($prefix, $fields_values, $is_image);
 			}
 			else {
 				$this->createUploadFields($prefix, Array(), $is_image);
 			}
 		}
 
 		/**
 		 * Dynamically creates virtual fields for item for each image/file field in submit
 		 *
 		 * @param string $prefix
 		 * @param Array $fields_values
 		 * @param bool $is_image
 		 * @return void
 		 * @access public
 		 */
 		public function createUploadFields($prefix, $fields_values, $is_image = false)
 		{
-			$field_options = Array (
-				'type'			=>	'string',
-				'max_len'		=>	240,
-				'default'		=>	'',
-			);
+			$field_options = Array ('type' => 'string', 'max_len' => 240, 'default' => '',);
 
-			if ($is_image) {
+			if ( $is_image ) {
 				$field_options['formatter'] = 'kPictureFormatter';
 				$field_options['include_path'] = 1;
 				$field_options['allowed_types'] = Array ('image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png', 'image/gif', 'image/bmp');
 				$field_prefix = 'Image';
 			}
 			else {
 				$field_options['formatter'] = 'kUploadFormatter';
 				$field_options['upload_dir'] = ITEM_FILES_PATH;
 				$field_options['allowed_types'] = Array ('application/pdf', 'application/msexcel', 'application/msword', 'application/mspowerpoint');
 				$field_prefix = 'File';
 			}
 
-			$fields = $this->Application->getUnitOption($prefix, 'Fields');
-			$virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields');
-
 			$image_count = 0;
+			$config = $this->Application->getUnitConfig($prefix);
+
 			foreach ($fields_values as $field_name => $field_value) {
-				if (preg_match('/^('.$field_prefix.'[\d]+|Primary'.$field_prefix.')$/', $field_name)) {
-					$fields[$field_name] = $field_options;
-					$virtual_fields[$field_name] = $field_options;
-					$this->_createCustomFields($prefix, $field_name, $virtual_fields, $is_image);
+				if ( preg_match('/^(' . $field_prefix . '[\d]+|Primary' . $field_prefix . ')$/', $field_name) ) {
+					$config->addFields($field_options, $field_name);
+					$config->addVirtualFields($field_options, $field_name);
+					$this->_createCustomFields($prefix, $field_name, $config, $is_image);
 
 					$image_count++;
 				}
 			}
 
-			if (!$image_count) {
+			if ( !$image_count ) {
 				// no images found in POST -> create default image fields
-				$image_count = $this->Application->ConfigValue($prefix.'_MaxImageCount');
+				$image_count = $this->Application->ConfigValue($prefix . '_MaxImageCount');
 
-				if ($is_image) {
+				if ( $is_image ) {
 					$created_count = 1;
 					$image_names = Array ('Primary' . $field_prefix => '');
 
-					while ($created_count < $image_count) {
+					while ( $created_count < $image_count ) {
 						$image_names[$field_prefix . $created_count] = '';
 						$created_count++;
 					}
 				}
 				else {
 					$created_count = 0;
 					$image_names = Array ();
 
-					while ($created_count < $image_count) {
+					while ( $created_count < $image_count ) {
 						$image_names[$field_prefix . ($created_count + 1)] = '';
 						$created_count++;
 					}
 				}
 
-				if ($created_count) {
+				if ( $created_count ) {
 					$this->createUploadFields($prefix, $image_names, $is_image);
 				}
 
-				return ;
+				return;
 			}
 
-			$this->Application->setUnitOption($prefix, $field_prefix.'Count', $image_count);
-			$this->Application->setUnitOption($prefix, 'Fields', $fields);
-			$this->Application->setUnitOption($prefix, 'VirtualFields', $virtual_fields);
+			$config->setSetting($field_prefix . 'Count', $image_count);
 		}
 
 		/**
 		 * Adds ability to create more virtual fields associated with main image/file
 		 *
 		 * @param string $prefix
 		 * @param string $field_name
-		 * @param Array $virtual_fields
+		 * @param kUnitConfig $config
 		 * @param bool $is_image
 		 * @return void
 		 * @access protected
 		 */
-		protected function _createCustomFields($prefix, $field_name, &$virtual_fields, $is_image = false)
+		protected function _createCustomFields($prefix, $field_name, kUnitConfig $config, $is_image = false)
 		{
-			$virtual_fields['Delete' . $field_name] = Array ('type' => 'int', 'default' => 0);
+			$config->addVirtualFields(Array ('type' => 'int', 'default' => 0), 'Delete' . $field_name);
 
 			if ( $is_image ) {
-				$virtual_fields[$field_name . 'Alt'] = Array ('type' => 'string', 'default' => '');
+				$config->addVirtualFields(Array ('type' => 'string', 'default' => ''), $field_name . 'Alt');
 			}
 		}
 
 		/**
 		 * Downloads file to user
 		 *
 		 * @param string $filename
 		 * @return void
 		 * @access public
 		 */
 		public function DownloadFile($filename)
 		{
 			$this->Application->setContentType(kUtil::mimeContentType($filename), false);
 			header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
 			header('Content-Length: ' . filesize($filename));
 			readfile($filename);
 			flush();
 		}
 
 		/**
 		 * Creates folder with given $path
 		 *
 		 * @param string $path
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckFolder($path)
 		{
 			$result = true;
 
 			if (!file_exists($path) || !is_dir($path)) {
 				$parent_path = preg_replace('#(/|\\\)[^/\\\]+(/|\\\)?$#', '', $path);
 				$result = $this->CheckFolder($parent_path);
 
 				if ($result) {
 					$result = mkdir($path);
 
 					if ($result) {
 						chmod($path, 0777);
 
 						// don't commit any files from created folder
 						if (file_exists(FULL_PATH . '/CVS')) {
 							$cvsignore = fopen($path . '/.cvsignore', 'w');
 							fwrite($cvsignore, '*.*');
 							fclose($cvsignore);
 							chmod($path . '/.cvsignore', 0777);
 						}
 					}
 					else {
 						trigger_error('Cannot create directory "<strong>' . $path . '</strong>"', E_USER_WARNING);
 						return false;
 					}
 				}
 			}
 
 			return $result;
 		}
 
 		/**
 		 * Copies all files and directories from $source to $destination directory. Create destination directory, when missing.
 		 *
 		 * @param string $source
 		 * @param string $destination
 		 * @return bool
 		 * @access public
 		 */
 		public function copyFolderRecursive($source, $destination)
 		{
 			if ( substr($source, -1) == DIRECTORY_SEPARATOR ) {
 				$source = substr($source, 0, -1);
 				$destination .= DIRECTORY_SEPARATOR . basename($source);
 			}
 
 			$iterator = new DirectoryIterator($source);
 			/* @var $file_info DirectoryIterator */
 
 			$result = $this->CheckFolder($destination);
 
 			foreach ($iterator as $file_info) {
 				if ( $file_info->isDot() ) {
 					continue;
 				}
 
 				$file = $file_info->getFilename();
 
 				if ( $file_info->isDir() ) {
 					$result = $this->copyFolderRecursive($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
 				}
 				else {
 					$result = copy($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
 				}
 
 				if (!$result) {
 					trigger_error('Cannot create file/directory "<strong>' . $destination . DIRECTORY_SEPARATOR . $file . '</strong>"', E_USER_WARNING);
 					break;
 				}
 			}
 
 			return $result;
 		}
 
 		/**
 		 * Copies all files from $source to $destination directory. Create destination directory, when missing.
 		 *
 		 * @param string $source
 		 * @param string $destination
 		 * @return bool
 		 * @access public
 		 */
 		public function copyFolder($source, $destination)
 		{
 			if ( substr($source, -1) == DIRECTORY_SEPARATOR ) {
 				$source = substr($source, 0, -1);
 				$destination .= DIRECTORY_SEPARATOR . basename($source);
 			}
 
 			$iterator = new DirectoryIterator($source);
 			/* @var $file_info DirectoryIterator */
 
 			$result = $this->CheckFolder($destination);
 
 			foreach ($iterator as $file_info) {
 				if ( $file_info->isDot() || !$file_info->isFile() ) {
 					continue;
 				}
 
 				$file = $file_info->getFilename();
 
 				$result = copy($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
 
 				if ( !$result ) {
 					trigger_error('Cannot create file "<strong>' . $destination . DIRECTORY_SEPARATOR . $file . '</strong>"', E_USER_WARNING);
 					break;
 				}
 			}
 
 			return $result;
 		}
 
 		/**
 		 * Transforms given path to file into it's url, where each each component is encoded (excluding domain and protocol)
 		 *
 		 * @param string $url
 		 * @return string
 		 * @access public
 		 */
 		public function pathToUrl($url)
 		{
 			$url = str_replace(DIRECTORY_SEPARATOR, '/', preg_replace('/^' . preg_quote(FULL_PATH, '/') . '(.*)/', '\\1', $url, 1));
 			$url = implode('/', array_map('rawurlencode', explode('/', $url)));
 
 			return rtrim($this->Application->BaseURL(), '/') . $url;
 		}
 
 		/**
 		 * Transforms given url to path to it
 		 *
 		 * @param string $url
 		 * @return string
 		 * @access public
 		 */
 		public function urlToPath($url)
 		{
 			$base_url = rtrim($this->Application->BaseURL(), '/');
 
 			// escape replacement patterns, like "\<number>"
 			$full_path = preg_replace('/(\\\[\d]+)/', '\\\\\1', FULL_PATH);
 			$path = preg_replace('/^' . preg_quote($base_url, '/') . '(.*)/', $full_path . '\\1', $url, 1);
 
 			return str_replace('/', DIRECTORY_SEPARATOR, rawurldecode($path));
 		}
 
 		/**
 		 * Ensures, that new file will not overwrite any of previously created files with same name
 		 *
 		 * @param string $path
 		 * @param string $name
 		 * @param Array $forbidden_names
 		 * @return string
 		 */
 		public function ensureUniqueFilename($path, $name, $forbidden_names = Array ())
 		{
 			$parts = pathinfo($name);
 			$ext = '.' . $parts['extension'];
 			$filename = $parts['filename'];
 			$path = rtrim($path, '/');
 
 			$original_checked = false;
 			$new_name = $filename . $ext;
 
 			if ( $parts['dirname'] != '.' ) {
 				$path .= '/' . ltrim($parts['dirname'], '/');
 			}
 
 			// make sure target folder always exists, especially for cases,
 			// when storage engine folder is supplied as a part of $name
 			$this->CheckFolder($path);
 
 			while (file_exists($path . '/' . $new_name) || in_array($path . '/' . $new_name, $forbidden_names)) {
 				if ( preg_match('/(.*)_([0-9]*)(' . preg_quote($ext, '/') . ')/', $new_name, $regs) ) {
 					$new_name = $regs[1] . '_' . ((int)$regs[2] + 1) . $regs[3];
 				}
 				elseif ( $original_checked ) {
 					$new_name = $filename . '_1' . $ext;
 				}
 
 				$original_checked = true;
 			}
 
 			if ( $parts['dirname'] != '.' ) {
 				$new_name = $parts['dirname'] . '/' . $new_name;
 			}
 
 			return $new_name;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/mod_rewrite_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/mod_rewrite_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/mod_rewrite_helper.php	(revision 15698)
@@ -1,332 +1,334 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CategoryItemRewrite extends kHelper {
 
 		/**
 		 * Builds/parses category item part of url
 		 *
 		 * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE.
 		 * @param string $prefix Prefix, that listener uses for system integration
 		 * @param Array $params Params, that are used for url building or created during url parsing.
 		 * @param Array $url_parts Url parts to parse (only for parsing).
 		 * @param bool $keep_events Keep event names in resulting url (only for building).
 		 * @return bool Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener.
 		 * @access public
 		 */
 		public function RewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix, &$params, &$url_parts, $keep_events = false)
 		{
 			static $parsed = false;
 
 			if ($rewrite_mode == REWRITE_MODE_BUILD) {
 				return $this->_buildCategoryItemUrl($prefix, $params, $keep_events);
 			}
 
 			if (!$parsed) {
 				$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 				/* @var $rewrite_processor kRewriteUrlProcessor */
 
 				$module_prefix = $this->_parseCategoryItemUrl($url_parts, $params);
 
 				if ($module_prefix !== false) {
 					$params['pass'][] = $module_prefix;
 					$rewrite_processor->modulePrefix = $module_prefix;
 				}
 
 				$parsed = true;
 			}
 
 			return true;
 		}
 
 		/**
 		 * Build category item part of url
 		 *
 		 * @param string $prefix_special
 		 * @param Array $params
 		 * @param bool $keep_events
 		 * @return string
 		 * @access protected
 		 */
 		protected function _buildCategoryItemUrl($prefix_special, &$params, $keep_events)
 		{
 			static $default_per_page = Array ();
 
 			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			$ret = '';
 			list ($prefix) = explode('.', $prefix_special);
 			$processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events);
 
 			if ($processed_params === false) {
 				return '';
 			}
 
 			if (!array_key_exists($prefix, $default_per_page)) {
 				$list_helper = $this->Application->recallObject('ListHelper');
 				/* @var $list_helper ListHelper */
 
 				$default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix);
 			}
 
 			if ($processed_params[$prefix_special . '_id']) {
 				$category_id = array_key_exists('m_cat_id', $params) ? $params['m_cat_id'] : $this->Application->GetVar('m_cat_id');
 
 				// if template is also item template of category, then remove template
 				$template = array_key_exists('t', $params) ? $params['t'] : false;
 				$item_template = $rewrite_processor->GetItemTemplate($category_id, $prefix);
 
 				if ($template == $item_template || strtolower($template) == '__default__') {
 					// given template is also default template for this category item or '__default__' given
 					$params['pass_template'] = false;
 				}
 
 				// get item's filename
 				if ($prefix == 'bb') {
 					$ret .= 'bb_' . $processed_params[$prefix_special . '_id'] . '/';
 				}
 				else {
 					$filename = $this->getFilename($prefix, $processed_params[$prefix_special . '_id'], $category_id);
 					if ($filename !== false) {
 						$ret .= $filename . '/';
 					}
 				}
 			} else {
 				if ($processed_params[$prefix_special . '_Page'] == 1) {
 					// when printing category items and we are on the 1st page -> there is no information about
 					// category item prefix and $params['pass_category'] will not be added automatically
 					$params['pass_category'] = true;
 				}
 				elseif ($processed_params[$prefix_special . '_Page'] > 1) {
 					// $ret .= $processed_params[$prefix_special . '_Page'] . '/';
 					$params['page'] = $processed_params[$prefix_special . '_Page'];
 				}
 
 				$per_page = $processed_params[$prefix_special . '_PerPage'];
 
 				if ($per_page && ($per_page != $default_per_page[$prefix])) {
 					$params['per_page'] = $processed_params[$prefix_special . '_PerPage'];
 				}
 			}
 
 			return mb_strtolower( rtrim($ret, '/') );
 		}
 
 		/**
 		 * Builds/parses review part of url
 		 *
 		 * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE.
 		 * @param string $prefix_special Prefix, that listener uses for system integration
 		 * @param Array $params Params, that are used for url building or created during url parsing.
 		 * @param Array $url_parts Url parts to parse (only for parsing).
 		 * @param bool $keep_events Keep event names in resulting url (only for building).
 		 * @return bool Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener.
 		 * @access public
 		 */
 		public function ReviewRewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix_special, &$params, &$url_parts, $keep_events = false)
 		{
 			static $default_per_page = Array ();
 
 			if ( $rewrite_mode != REWRITE_MODE_BUILD ) {
 				// don't parse anything
 				return true;
 			}
 
 			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			$ret = '';
 			list ($prefix) = explode('.', $prefix_special);
 			$processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events);
 
 			if ($processed_params === false) {
 				return '';
 			}
 
 			if (!array_key_exists($prefix, $default_per_page)) {
 				$list_helper = $this->Application->recallObject('ListHelper');
 				/* @var $list_helper ListHelper */
 
 				$default_per_page[$prefix] = $list_helper->getDefaultPerPage($prefix);
 			}
 
 			if ($processed_params[$prefix_special . '_id']) {
 				return false;
 			}
 			else {
 				if ($processed_params[$prefix_special . '_Page'] == 1) {
 					// when printing category items and we are on the 1st page -> there is no information about
 					// category item prefix and $params['pass_category'] will not be added automatically
 					$params['pass_category'] = true;
 				}
 				elseif ($processed_params[$prefix_special . '_Page'] > 1) {
 					// $ret .= $processed_params[$prefix_special . '_Page'] . '/';
 					$params['page'] = $processed_params[$prefix_special . '_Page'];
 				}
 
 				$per_page = $processed_params[$prefix_special . '_PerPage'];
 
 				if ($per_page && ($per_page != $default_per_page[$prefix])) {
 					$params['per_page'] = $processed_params[$prefix_special . '_PerPage'];
 				}
 			}
 
 			return mb_strtolower( rtrim($ret, '/') );
 		}
 
 		/**
 		 * Returns item's filename that corresponds id passed. If possible, then get it from cache
 		 *
 		 * @param string $prefix
 		 * @param int $id
 		 * @param int $category_id
 		 * @return string
 		 * @access protected
 		 */
 		protected function getFilename($prefix, $id, $category_id = null)
 		{
 			if ($prefix == 'c') {
 				throw new Exception('Method "<strong>' . __FUNCTION__ . '</strong>" no longer work with "<strong>c</strong>" prefix. Please use "<strong>getCategoryCache</strong>" method instead');
 
 				return false;
 			}
 
 			$category_id = isset($category_id) ? $category_id : $this->Application->GetVar('m_cat_id');
 
 			$cache_key = 'filenames[%' . $this->Application->incrementCacheSerial($prefix, $id, false) . '%]:' . (int)$category_id;
 			$filename = $this->Application->getCache($cache_key);
 
 			if ($filename === false) {
 				$this->Conn->nextQueryCachable = true;
+				$config = $this->Application->getUnitConfig($prefix);
+
 				$sql = 'SELECT ResourceId
-						FROM ' . $this->Application->getUnitOption($prefix, 'TableName') . '
-						WHERE ' . $this->Application->getUnitOption($prefix, 'IDField') . ' = ' . $this->Conn->qstr($id);
+						FROM ' . $config->getTableName() . '
+						WHERE ' . $config->getIDField() . ' = ' . $this->Conn->qstr($id);
 				$resource_id = $this->Conn->GetOne($sql);
 
 				$this->Conn->nextQueryCachable = true;
 				$sql = 'SELECT Filename
 						FROM ' . TABLE_PREFIX . 'CategoryItems
 						WHERE (ItemResourceId = ' . $resource_id . ') AND (CategoryId = ' . (int)$category_id . ')';
 				$filename = $this->Conn->GetOne($sql);
 
 				if ($filename !== false) {
 					$this->Application->setCache($cache_key, $filename);
 				}
 			}
 
 			return $filename;
 		}
 
 		/**
 		 * Sets template and id, corresponding to category item given in url
 		 *
 		 * @param Array $url_parts
 		 * @param Array $vars
 		 * @return bool|string
 		 * @access protected
 		 */
 		protected function _parseCategoryItemUrl(&$url_parts, &$vars)
 		{
 			if ( !$url_parts ) {
 				return false;
 			}
 
 			$item_filename = end($url_parts);
 			if ( is_numeric($item_filename) ) {
 				// this page, don't process here
 				return false;
 			}
 
 			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			if ( preg_match('/^bb_([\d]+)/', $item_filename, $regs) ) {
 				// process topics separately, because they don't use item filenames
 				array_pop($url_parts);
 				$rewrite_processor->partParsed($item_filename, 'rtl');
 
 				return $this->_parseTopicUrl($regs[1], $vars);
 			}
 
 			// locating the item in CategoryItems by filename to detect its ItemPrefix and its category ParentPath
 			$sql = 'SELECT ci.ItemResourceId, ci.ItemPrefix, c.ParentPath, ci.CategoryId
 					FROM ' . TABLE_PREFIX . 'CategoryItems AS ci
 					LEFT JOIN ' . TABLE_PREFIX . 'Categories AS c ON c.CategoryId = ci.CategoryId
 					WHERE (ci.CategoryId = ' . (int)$vars['m_cat_id'] . ') AND (ci.Filename = ' . $this->Conn->qstr($item_filename) . ')';
 			$cat_item = $this->Conn->GetRow($sql);
 
 			if ( $cat_item !== false ) {
 				// item found
 				$module_prefix = $cat_item['ItemPrefix'];
 				$item_template = $rewrite_processor->GetItemTemplate($cat_item, $module_prefix, $vars['m_theme']);
 
 				// converting ResourceId to corresponding Item id
-				$module_config = $this->Application->getUnitOptions($module_prefix);
+				$module_config = $this->Application->getUnitConfig($module_prefix);
 
-				$sql = 'SELECT ' . $module_config['IDField'] . '
-						FROM ' . $module_config['TableName'] . '
+				$sql = 'SELECT ' . $module_config->getIDField() . '
+						FROM ' . $module_config->getTableName() . '
 					 	WHERE ResourceId = ' . $cat_item['ItemResourceId'];
 				$item_id = $this->Conn->GetOne($sql);
 
 				if ( $item_id ) {
 					array_pop($url_parts);
 					$rewrite_processor->partParsed($item_filename, 'rtl');
 
 					if ( $item_template ) {
 						// when template is found in category -> set it
 						$vars['t'] = $item_template;
 					}
 
 					// we have category item id
 					$vars[$module_prefix . '_id'] = $item_id;
 
 					return $module_prefix;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Set's template and topic id corresponding to topic given in url
 		 *
 		 * @param int $topic_id
 		 * @param Array $vars
 		 * @return string
 		 * @access protected
 		 */
 		protected function _parseTopicUrl($topic_id, &$vars)
 		{
 			$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 			/* @var $rewrite_processor kRewriteUrlProcessor */
 
 			$sql = 'SELECT c.ParentPath, c.CategoryId
 					FROM ' . TABLE_PREFIX . 'Categories AS c
 					WHERE c.CategoryId = ' . (int)$vars['m_cat_id'];
 			$cat_item = $this->Conn->GetRow($sql);
 
 			$item_template = $rewrite_processor->GetItemTemplate($cat_item, 'bb', $vars['m_theme']);
 
 			if ($item_template) {
 				$vars['t'] = $item_template;
 			}
 
 			$vars['bb_id'] = $topic_id;
 
 			return 'bb';
 		}
 	}
Index: branches/5.3.x/core/units/helpers/priority_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/priority_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/priority_helper.php	(revision 15698)
@@ -1,248 +1,250 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kPriorityHelper extends kHelper {
 
 		/**
 		 * Prepares options for priority dropdown
 		 *
 		 * @param kEvent $event
 		 * @param bool $is_new for newly created items add new priority to the end
 		 * @param string $constrain constrain for priority selection (if any)
 		 * @param string $joins left joins, used by constrain (if any)
 		 *
 		 */
 		function preparePriorities($event, $is_new = false, $constrain = '', $joins = '')
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$field_options = $object->GetFieldOptions('Priority');
-			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$table_name = $event->getUnitConfig()->getTableName();
 
 			$sql = 'SELECT COUNT(*)
 					FROM ' . $table_name . ' item_table
 					' . $joins;
 
 			if ( $constrain ) {
 				$sql .= ' WHERE ' . $this->normalizeConstrain($constrain);
 			}
 
 			if ( !$object->isField('OldPriority') ) {
 				// make sure, then OldPriority field is defined
 				$virtual_fields = $object->getVirtualFields();
 				$virtual_fields['OldPriority'] = Array ('type' => 'int', 'default' => 0);
 				$object->setVirtualFields($virtual_fields);
 			}
 
 			$items_count = $this->Conn->GetOne($sql);
 			$current_priority = $object instanceof kDBList ? 0 : $object->GetDBField('Priority');
 
 			if ( $is_new || $current_priority == -($items_count + 1) ) {
 				$items_count++;
 			}
 
 			if ( $is_new ) {
 				// add new item to the end of list
 				$object->SetDBField('Priority', -$items_count);
 				$object->SetDBField('OldPriority', -$items_count);
 			}
 			else {
 				// storing priority right after load for comparing when updating
 				$object->SetDBField('OldPriority', $current_priority);
 			}
 
 			for ($i = 1; $i <= $items_count; $i++) {
 				$field_options['options'][-$i] = $i;
 			}
 
 			$object->SetFieldOptions('Priority', $field_options);
 		}
 
 		/**
 		 * Updates priorities for changed items
 		 *
 		 * @param kEvent $event
 		 * @param Array $changes = Array (ID => Array ('constrain' => ..., 'new' => ..., 'old' => ...), ...)
 		 * @param Array $new_ids = Array (temp_id => live_id)
 		 * @param string $constrain
 		 * @param string $joins
 		 * @return Array
 		 */
 		function updatePriorities($event, $changes, $new_ids, $constrain = '', $joins = '')
 		{
 			// TODO: no need pass external $constrain, since the one from $pair is used
 
 			if ( !$changes ) {
 				// no changes to process
 				return Array ();
 			}
 
 			list ($id, $pair) = each($changes);
 
 			if ( !$id && !isset($pair['constrain']) ) {
 				// adding new item without constrain -> priority stays the same
 				return Array ($id);
 			}
 
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$config = $event->getUnitConfig();
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			if ( $this->Application->IsTempMode($event->Prefix, $event->Special) ) {
 				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $event->Prefix);
 			}
 
 			$ids = Array ();
 			$not_processed = array_keys($changes);
 
 			foreach ($changes as $id => $pair) {
 				array_push($ids, $id);
 				$constrain = isset($pair['constrain']) ? $this->normalizeConstrain($pair['constrain']) . ' AND ' : '';
 
 				if ( $pair['old'] == 'new' ) {
 					// replace 0 with newly created item id (from $new_ids mapping)
 					$not_processed[array_search($id, $not_processed)] = $new_ids[$id];
 					$id = $new_ids[$id];
 
 					$sql = 'SELECT MIN(item_table.Priority)
 							FROM ' . $table_name . ' item_table
 							' . $joins . '
 							WHERE ' . $constrain . ' item_table.' . $id_field . ' NOT IN (' . implode(',', $not_processed) . ')';
 					$min_priority = (int)$this->Conn->GetOne($sql) - 1;
 
 					if ( $pair['new'] < $min_priority ) {
 						$pair['new'] = $min_priority;
 					}
 
 					$pair['old'] = $min_priority;
 				}
 
 				if ( $pair['new'] < $pair['old'] ) {
 					$set = '	SET item_table.Priority = item_table.Priority + 1';
 					$where = '	WHERE ' . $constrain . '
 								item_table.Priority >= ' . $pair['new'] . '
 								AND
 								item_table.Priority < ' . $pair['old'] . '
 								AND
 								' . $id_field . ' NOT IN (' . implode(',', $not_processed) . ')';
 				}
 				elseif ( $pair['new'] > $pair['old'] ) {
 					$set = '	SET item_table.Priority = item_table.Priority - 1';
 					$where = '	WHERE ' . $constrain . '
 								item_table.Priority > ' . $pair['old'] . '
 								AND
 								item_table.Priority <= ' . $pair['new'] . '
 								AND
 								' . $id_field . ' NOT IN (' . implode(',', $not_processed) . ')';
 				}
 				else {
 					$set = '	SET item_table.Priority = ' . $pair['new'];
 					$where = '	WHERE ' . $id_field . ' = ' . $id;
 				}
 
 				$sql = 'SELECT item_table.' . $id_field . '
 						FROM ' . $table_name . ' item_table
 						' . $joins . '
 						' . $where;
 				$ids = array_merge($ids, $this->Conn->GetCol($sql));
 
 				$q = 'UPDATE ' . $table_name . ' item_table
 						' . $joins . '
 						' . $set . $where;
 				$this->Conn->Query($q);
 
 				unset($not_processed[array_search($id, $not_processed)]);
 			}
 
 			return $ids;
 		}
 
 		/**
 		 * Recalculates priorities
 		 *
 		 * @param kEvent $event
 		 * @param string $constrain
 		 * @param string $joins
 		 * @return Array
 		 */
 		function recalculatePriorities($event, $constrain = '', $joins = '')
 		{
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$config = $event->getUnitConfig();
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			if ( $constrain ) {
 				$constrain = $this->normalizeConstrain($constrain);
 			}
 
 			if ( $this->Application->IsTempMode($event->Prefix, $event->Special) ) {
 				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $event->Prefix);
 			}
 
 			$sql = 'SELECT ' . $id_field . '
 					FROM ' . $table_name . ' item_table ' .
 					$joins . ' ' .
 					($constrain ? ' WHERE ' . $constrain : '') . '
 					ORDER BY item_table.Priority DESC';
 			$items = $this->Conn->GetCol($sql);
 
 			foreach ($items as $item_number => $item_id) {
 				$sql = 'UPDATE ' . $table_name . '
 						SET Priority = ' . -($item_number + 1) . '
 						WHERE ' . $id_field . ' = ' . $item_id;
 				$this->Conn->Query($sql);
 			}
 
 			return $items;
 		}
 
 		/**
 		 * Adds current table name into constrain if doesn't have it already (to prevent ambiguous columns during joins)
 		 *
 		 * @param string $constrain
 		 * @return string
 		 */
 		function normalizeConstrain($constrain)
 		{
 			if ( strpos($constrain, '.') === false ) {
 				return 'item_table.' . $constrain;
 			}
 
 			return $constrain;
 		}
 
 		/**
 		 * Performs fake kDBItem::Update call, so any OnBefore/OnAfter events would be notified of priority change
 		 *
 		 * @param string $prefix
 		 * @param Array $ids
 		 */
 		function massUpdateChanged($prefix, $ids)
 		{
 			$ids = array_unique($ids);
 
 			$dummy = $this->Application->recallObject($prefix . '.-dummy', null, Array ('skip_autoload' => true));
 			/* @var $dummy kDBItem */
 
 			$sql = 	$dummy->GetSelectSQL() . '
 					WHERE ' . $dummy->TableName . '.' . $dummy->IDField . ' IN (' . implode(',', $ids) . ')';
 			$records = $this->Conn->Query($sql);
 
 			foreach ($records as $record) {
 				$dummy->LoadFromHash($record);
 				$dummy->Update();
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/image_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/image_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/image_helper.php	(revision 15698)
@@ -1,709 +1,709 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ImageHelper extends kHelper {
 
 		/**
 		 * File helper reference
 		 *
 		 * @var FileHelper
 		 */
 		var $fileHelper = null;
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			ini_set('gd.jpeg_ignore_warning', 1);
 			$this->fileHelper = $this->Application->recallObject('FileHelper');
 		}
 
 		/**
 		 * Parses format string into array
 		 *
 		 * @param string $format sample format: "resize:300x500;wm:inc/wm.png|c|-20"
 		 * @return Array sample result: Array('max_width' => 300, 'max_height' => 500, 'wm_filename' => 'inc/wm.png', 'h_margin' => 'c', 'v_margin' => -20)
 		 */
 		function parseFormat($format)
 		{
 			$res = Array ();
 
 			$format_parts = explode(';', $format);
 			foreach ($format_parts as $format_part) {
 				if (preg_match('/resize:(\d*)x(\d*)/', $format_part, $regs)) {
 					$res['max_width'] = $regs[1];
 					$res['max_height'] = $regs[2];
 				}
 				elseif (preg_match('/wm:([^\|]*)\|([^\|]*)\|([^\|]*)/', $format_part, $regs)) {
 					$res['wm_filename'] = FULL_PATH.THEMES_PATH.'/'.$regs[1];
 					$res['h_margin'] = strtolower($regs[2]);
 					$res['v_margin'] = strtolower($regs[3]);
 				}
 				elseif (preg_match('/crop:([^\|]*)\|([^\|]*)/', $format_part, $regs)) {
 					$res['crop_x'] = strtolower($regs[1]);
 					$res['crop_y'] = strtolower($regs[2]);
 				}
 				elseif ($format_part == 'img_size' || $format_part == 'img_sizes') {
 					$res['image_size'] = true;
 				}
 				elseif (preg_match('/fill:(.*)/', $format_part, $regs)) {
 					$res['fill'] = $regs[1];
 				} elseif (preg_match('/default:(.*)/', $format_part, $regs)) {
 					$res['default'] = FULL_PATH.THEMES_PATH.'/'.$regs[1];
 				}
 			}
 
 			return $res;
 		}
 
 		/**
 		 * Resized given image to required dimensions & saves resized image to "resized" subfolder in source image folder
 		 *
 		 * @param string $src_image full path to image (on server)
 		 * @param mixed $max_width maximal allowed resized image width or false if no limit
 		 * @param mixed $max_height maximal allowed resized image height or false if no limit
 		 * @return string direct url to resized image
 		 */
 		function ResizeImage($src_image, $max_width, $max_height = false)
 		{
 			$image_size = false;
 
 			if (is_numeric($max_width)) {
 				$params['max_width'] = $max_width;
 				$params['max_height'] = $max_height;
 			}
 			else {
 				$params = $this->parseFormat($max_width);
 
 				if (array_key_exists('image_size', $params)) {
 					// image_size param shouldn't affect resized file name (crc part)
 					$image_size = $params['image_size'];
 					unset($params['image_size']);
 				}
 			}
 
 			if ((!$src_image || !file_exists($src_image)) && array_key_exists('default', $params) && !(defined('DBG_IMAGE_RECOVERY') && DBG_IMAGE_RECOVERY)) {
 				$src_image = $params['default'];
 			}
 
 			if ($params['max_width'] > 0 || $params['max_height'] > 0) {
 				list ($params['target_width'], $params['target_height'], $needs_resize) = $this->GetImageDimensions($src_image, $params['max_width'], $params['max_height'], $params);
 
 				if (!is_numeric($params['max_width'])) {
 					$params['max_width'] = $params['target_width'];
 				}
 
 				if (!is_numeric($params['max_height'])) {
 					$params['max_height'] = $params['target_height'];
 				}
 
 				$src_path = dirname($src_image);
 				$transform_keys = Array ('crop_x', 'crop_y', 'fill', 'wm_filename');
 
 				if ($needs_resize || array_intersect(array_keys($params), $transform_keys)) {
 					// resize required OR watermarking required -> change resulting image name !
 					ksort($params);
 					$src_path_escaped = preg_replace('/(\\\[\d]+)/', '\\\\\1', $src_path); // escape replacement patterns, like "\<number>"
 					$dst_image = preg_replace('/^'.preg_quote($src_path, '/').'(.*)\.(.*)$/', $src_path_escaped . DIRECTORY_SEPARATOR . 'resized\\1_' . crc32(serialize($params)) . '.\\2', $src_image);
 
 					$this->fileHelper->CheckFolder( dirname($dst_image) );
 
 					if (!file_exists($dst_image) || filemtime($src_image) > filemtime($dst_image)) {
 						// resized image not available OR should be recreated due source image change
 						$params['dst_image'] = $dst_image;
 						$image_resized = $this->ScaleImage($src_image, $params);
 						if (!$image_resized) {
 							// resize failed, because of server error
 							$dst_image = $src_image;
 						}
 					}
 
 					// resize/watermarking ok
 					$src_image = $dst_image;
 				}
 			}
 
 			if ($image_size) {
 				// return only image size (resized or not)
 				$image_info = $this->getImageInfo($src_image);
 				return $image_info ? $image_info[3] : '';
 			}
 
 			return $this->fileHelper->pathToUrl($src_image);
 		}
 
 		/**
 		 * Proportionally resizes given image to destination dimensions
 		 *
 		 * @param string $src_image full path to source image (already existing)
 		 * @param Array $params
 		 * @return bool
 		 */
 		function ScaleImage($src_image, $params)
 		{
 			$image_info = $this->getImageInfo($src_image);
 			if (!$image_info) {
 				return false;
 			}
 
 			/*list ($params['max_width'], $params['max_height'], $resized) = $this->GetImageDimensions($src_image, $params['max_width'], $params['max_height'], $params);
 			if (!$resized) {
 				// image dimensions are smaller or equals to required dimensions
 				return false;
 			}*/
 
 			if (!$this->Application->ConfigValue('ForceImageMagickResize') && function_exists('imagecreatefromjpeg')) {
 				// try to resize using GD
 				$resize_map = Array (
 					'image/jpeg' => 'imagecreatefromjpeg:imagejpeg:jpg',
 					'image/gif' => 'imagecreatefromgif:imagegif:gif',
 					'image/png' => 'imagecreatefrompng:imagepng:png',
 					'image/bmp' => 'imagecreatefrombmp:imagejpeg:bmp',
 					'image/x-ms-bmp' => 'imagecreatefrombmp:imagejpeg:bmp',
 				);
 
 				$mime_type = $image_info['mime'];
 				if (!isset($resize_map[$mime_type])) {
 					return false;
 				}
 
 				list ($read_function, $write_function, $file_extension) = explode(':', $resize_map[$mime_type]);
 
 				// when source image has large dimensions (over 1MB filesize), then 16M is not enough
 				kUtil::setResourceLimit();
 
 				$src_image_rs = @$read_function($src_image);
 				if ($src_image_rs) {
 					$dst_image_rs = imagecreatetruecolor($params['target_width'], $params['target_height']); // resize target size
 
 					$preserve_transparency = ($file_extension == 'gif') || ($file_extension == 'png');
 
 					if ($preserve_transparency) {
 						// preserve transparency of PNG and GIF images
 						$dst_image_rs = $this->_preserveTransparency($src_image_rs, $dst_image_rs, $image_info[2]);
 					}
 
 					// 1. resize
 					imagecopyresampled($dst_image_rs, $src_image_rs, 0, 0, 0, 0, $params['target_width'], $params['target_height'], $image_info[0], $image_info[1]);
 
 					$watermark_size = 'target';
 
 					if (array_key_exists('crop_x', $params) || array_key_exists('crop_y', $params)) {
 						// 2.1. crop image to given size
 						$dst_image_rs =& $this->_cropImage($dst_image_rs, $params, $preserve_transparency ? $image_info[2] : false);
 						$watermark_size = 'max';
 					} elseif (array_key_exists('fill', $params)) {
 						// 2.2. fill image margins from resize with given color
 						$dst_image_rs =& $this->_applyFill($dst_image_rs, $params, $preserve_transparency ? $image_info[2] : false);
 						$watermark_size = 'max';
 					}
 
 					// 3. apply watermark
 					$dst_image_rs =& $this->_applyWatermark($dst_image_rs, $params[$watermark_size . '_width'], $params[$watermark_size . '_height'], $params);
 
 					if ($write_function == 'imagegif') {
 						return @$write_function($dst_image_rs, $params['dst_image']);
 					}
 
 					return @$write_function($dst_image_rs, $params['dst_image'], $write_function == 'imagepng' ? 0 : 100);
 				}
 			}
 			else {
 				// try to resize using ImageMagick
 				// TODO: implement crop and watermarking using imagemagick
 				exec('/usr/bin/convert '.$src_image.' -resize '.$params['target_width'].'x'.$params['target_height'].' '.$params['dst_image'], $shell_output, $exec_status);
 				return $exec_status == 0;
 			}
 
 			return false;
 		}
 
 		/**
 		 * Preserve transparency for GIF and PNG images
 		 *
 		 * @param resource $src_image_rs
 		 * @param resource $dst_image_rs
 		 * @param int $image_type
 		 * @return resource
 		 */
 		function _preserveTransparency($src_image_rs, $dst_image_rs, $image_type)
 		{
 			$transparent_index = imagecolortransparent($src_image_rs);
 
 			// if we have a specific transparent color
 			if ( $transparent_index >= 0 && $transparent_index < imagecolorstotal($src_image_rs) ) {
 				// get the original image's transparent color's RGB values
 				$transparent_color = imagecolorsforindex($src_image_rs, $transparent_index);
 
 				// allocate the same color in the new image resource
 				$transparent_index = imagecolorallocate($dst_image_rs, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
 
 				// completely fill the background of the new image with allocated color
 				imagefill($dst_image_rs, 0, 0, $transparent_index);
 
 				// set the background color for new image to transparent
 				imagecolortransparent($dst_image_rs, $transparent_index);
 
 				return $dst_image_rs;
 			}
 
 			// always make a transparent background color for PNGs that don't have one allocated already
 			if ( $image_type == IMAGETYPE_PNG ) {
 				// turn off transparency blending (temporarily)
 				imagealphablending($dst_image_rs, false);
 
 				// create a new transparent color for image
 				$transparent_color = imagecolorallocatealpha($dst_image_rs, 0, 0, 0, 127);
 
 				// completely fill the background of the new image with allocated color
 				imagefill($dst_image_rs, 0, 0, $transparent_color);
 
 				// restore transparency blending
 				imagesavealpha($dst_image_rs, true);
 			}
 
 			return $dst_image_rs;
 		}
 
 		/**
 		 * Fills margins (if any) of resized are with given color
 		 *
 		 * @param resource $src_image_rs resized image resource
 		 * @param Array $params crop parameters
 		 * @param int|bool $image_type
 		 * @return resource
 		 */
 		function &_applyFill(&$src_image_rs, $params, $image_type = false)
 		{
 			$x_position = round(($params['max_width'] - $params['target_width']) / 2); // center
 			$y_position = round(($params['max_height'] - $params['target_height']) / 2); // center
 
 			// crop resized image
 			$fill_image_rs = imagecreatetruecolor($params['max_width'], $params['max_height']);
 
 			if ($image_type !== false) {
 				$fill_image_rs = $this->_preserveTransparency($src_image_rs, $fill_image_rs, $image_type);
 			}
 
 			$fill = $params['fill'];
 
 			if (substr($fill, 0, 1) == '#') {
 				// hexdecimal color
 				$color = imagecolorallocate($fill_image_rs, hexdec( substr($fill, 1, 2) ), hexdec( substr($fill, 3, 2) ), hexdec( substr($fill, 5, 2) ));
 			}
 			else {
 				// for now we don't support color names, but we will in future
 				return $src_image_rs;
 			}
 
 			imagefill($fill_image_rs, 0, 0, $color);
 			imagecopy($fill_image_rs, $src_image_rs, $x_position, $y_position, 0, 0, $params['target_width'], $params['target_height']);
 
 			return $fill_image_rs;
 		}
 
 		/**
 		 * Crop given image resource using given params and return resulting image resource
 		 *
 		 * @param resource $src_image_rs resized image resource
 		 * @param Array $params crop parameters
 		 * @param int|bool $image_type
 		 * @return resource
 		 */
 		function &_cropImage(&$src_image_rs, $params, $image_type = false)
 		{
 			if ($params['crop_x'] == 'c') {
 				$x_position = round(($params['max_width'] - $params['target_width']) / 2); // center
 			}
 			elseif ($params['crop_x'] >= 0) {
 				$x_position = $params['crop_x']; // margin from left
 			}
 			else {
 				$x_position = $params['target_width'] - ($params['max_width'] - $params['crop_x']); // margin from right
 			}
 
 			if ($params['crop_y'] == 'c') {
 				$y_position = round(($params['max_height'] - $params['target_height']) / 2); // center
 			}
 			elseif ($params['crop_y'] >= 0) {
 				$y_position = $params['crop_y']; // margin from top
 			}
 			else {
 				$y_position = $params['target_height'] - ($params['max_height'] - $params['crop_y']); // margin from bottom
 			}
 
 			// crop resized image
 			$crop_image_rs = imagecreatetruecolor($params['max_width'], $params['max_height']);
 
 			if ($image_type !== false) {
 				$crop_image_rs = $this->_preserveTransparency($src_image_rs, $crop_image_rs, $image_type);
 			}
 
 			if (array_key_exists('fill', $params)) {
 				// fill image margins from resize with given color
 				$crop_image_rs =& $this->_applyFill($crop_image_rs, $params, $image_type);
 			}
 
 			imagecopy($crop_image_rs, $src_image_rs, $x_position, $y_position, 0, 0, $params['target_width'], $params['target_height']);
 
 			return $crop_image_rs;
 		}
 
 		/**
 		 * Apply watermark (transparent PNG image) to given resized image resource
 		 *
 		 * @param resource $src_image_rs
 		 * @param int $max_width
 		 * @param int $max_height
 		 * @param Array $params
 		 * @return resource
 		 */
 		function &_applyWatermark(&$src_image_rs, $max_width, $max_height, $params)
 		{
 			$watermark_file = array_key_exists('wm_filename', $params) ? $params['wm_filename'] : false;
 
 			if (!$watermark_file || !file_exists($watermark_file)) {
 				// no watermark required, or provided watermark image is missing
 				return $src_image_rs;
 			}
 
 			$watermark_img_rs = imagecreatefrompng($watermark_file);
 			list ($watermark_width, $watermark_height) = $this->getImageInfo($watermark_file);
 
 			imagealphablending($src_image_rs, true);
 
 			if ($params['h_margin'] == 'c') {
 				$x_position = round($max_width / 2 - $watermark_width / 2); // center
 			}
 			elseif ($params['h_margin'] >= 0) {
 				$x_position = $params['h_margin']; // margin from left
 			}
 			else {
 				$x_position = $max_width - ($watermark_width - $params['h_margin']); // margin from right
 			}
 
 			if ($params['v_margin'] == 'c') {
 				$y_position = round($max_height / 2 - $watermark_height / 2); // center
 			}
 			elseif ($params['v_margin'] >= 0) {
 				$y_position = $params['v_margin']; // margin from top
 			}
 			else {
 				$y_position = $max_height - ($watermark_height - $params['v_margin']); // margin from bottom
 			}
 
 			imagecopy($src_image_rs, $watermark_img_rs, $x_position, $y_position, 0, 0, $watermark_width, $watermark_height);
 
 			return $src_image_rs;
 		}
 
 		/**
 		 * Returns destination image size without actual resizing (useful for <img .../> HTML tag)
 		 *
 		 * @param string $src_image full path to source image (already existing)
 		 * @param int $dst_width destination image width (in pixels)
 		 * @param int $dst_height destination image height (in pixels)
 		 * @param Array $params
 		 * @return Array resized image dimensions (0 - width, 1 - height)
 		 */
 		function GetImageDimensions($src_image, $dst_width, $dst_height, $params)
 		{
 			$image_info = $this->getImageInfo($src_image);
 			if (!$image_info) {
 				return false;
 			}
 
 			$orig_width = $image_info[0];
 			$orig_height = $image_info[1];
 
 			$too_large = is_numeric($dst_width) ? ($orig_width > $dst_width) : false;
 			$too_large = $too_large || (is_numeric($dst_height) ? ($orig_height > $dst_height) : false);
 
 			if ($too_large) {
 				$width_ratio = $dst_width ? $dst_width / $orig_width : 1;
 				$height_ratio = $dst_height ? $dst_height / $orig_height : 1;
 
 				if (array_key_exists('crop_x', $params) || array_key_exists('crop_y', $params)) {
 					// resize by smallest inverted radio
 					$resize_by = $this->_getCropImageMinRatio($image_info, $dst_width, $dst_height);
 
 					if ($resize_by === false) {
 						return Array ($orig_width, $orig_height, false);
 					}
 
 					$ratio = $resize_by == 'width' ? $width_ratio : $height_ratio;
 				}
 				else {
 					$ratio = min($width_ratio, $height_ratio);
 				}
 
 				$width = ceil($orig_width * $ratio);
 				$height = ceil($orig_height * $ratio);
 			}
 			else {
 				$width = $orig_width;
 				$height = $orig_height;
 			}
 
 			return Array ($width, $height, $too_large);
 		}
 
 		/**
 		 * Returns ratio type with smaller relation of original size to target size
 		 *
 		 * @param Array $image_info image information from "ImageHelper::getImageInfo"
 		 * @param int $dst_width destination image width (in pixels)
 		 * @param int $dst_height destination image height (in pixels)
 		 * @return Array
 		 */
 		function _getCropImageMinRatio($image_info, $dst_width, $dst_height)
 		{
 			$width_ratio = $dst_width ? $image_info[0] / $dst_width : 1;
 			$height_ratio = $dst_height ? $image_info[1] / $dst_height : 1;
 			$minimal_ratio = min($width_ratio, $height_ratio);
 
 			if ($minimal_ratio < 1) {
 				// ratio is less then 1, image will be enlarged -> don't allow that
 				return false;
 			}
 
 			return $width_ratio < $height_ratio ? 'width' : 'height';
 		}
 
 		/**
 		 * Returns image dimensions + checks if given file is existing image
 		 *
 		 * @param string $src_image full path to source image (already existing)
 		 * @return mixed
 		 */
 		function getImageInfo($src_image)
 		{
 			if (!file_exists($src_image)) {
 				return false;
 			}
 
 			$image_info = @getimagesize($src_image);
 			if (!$image_info) {
 				trigger_error('Image <b>'.$src_image.'</b> <span class="debug_error">missing or invalid</span>', E_USER_WARNING);
 				return false;
 			}
 
 			return $image_info;
 		}
 
 		/**
 		 * Returns maximal image size (width & height) among fields specified
 		 *
 		 * @param kDBItem $object
 		 * @param string $fields
 		 * @param string $format any format, that returns full url (e.g. files_resized:WxH, resize:WxH, full_url, full_urls)
 		 * @return string
 		 */
 		function MaxImageSize(&$object, $fields, $format = null)
 		{
 			static $cached_sizes = Array ();
 
 			$cache_key = $object->getPrefixSpecial().'_'.$object->GetID();
 			if (!isset($cached_sizes[$cache_key])) {
 				$images = Array ();
 
 				$fields = explode(',', $fields);
 				foreach ($fields as $field) {
 					$image_data = $object->GetField($field, $format);
 					if (!$image_data) {
 						continue;
 					}
 
 					$images = array_merge($images, explode('|', $image_data));
 				}
 
 				$max_width = 0;
 				$max_height = 0;
 				$base_url = rtrim($this->Application->BaseURL(), '/');
 
 				foreach ($images as $image_url) {
 					$image_path = preg_replace('/^'.preg_quote($base_url, '/').'(.*)/', FULL_PATH.'\\1', $image_url);
 					$image_info = $this->getImageInfo($image_path);
 					$max_width = max($max_width, $image_info[0]);
 					$max_height = max($max_height, $image_info[1]);
 				}
 
 				$cached_sizes[$cache_key] = Array ($max_width, $max_height);
 			}
 
 			return $cached_sizes[$cache_key];
 		}
 
 		/**
 		 * Puts existing item images (from sub-item) to virtual fields (in main item)
 		 *
 		 * @param kCatDBItem|kDBItem $object
 		 */
 		function LoadItemImages(&$object)
 		{
 			if (!$this->_canUseImages($object)) {
 				return ;
 			}
 
 			$max_image_count = $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
 
 			$sql = 'SELECT *
 					FROM '.TABLE_PREFIX.'CatalogImages
 					WHERE ResourceId = '.$object->GetDBField('ResourceId').'
 					ORDER BY Priority DESC
 					LIMIT 0, ' . (int)$max_image_count;
 			$item_images = $this->Conn->Query($sql);
 
 			$image_counter = 1;
 			foreach ($item_images as $item_image) {
 				$image_path = $item_image['ThumbPath'];
 				if ($item_image['DefaultImg'] == 1 || $item_image['Name'] == 'main') {
 					// process primary image separately
 					if ( $object->isField('PrimaryImage') ) {
 						$object->SetDBField('PrimaryImage', $image_path);
 						$object->SetOriginalField('PrimaryImage', $image_path);
 						$object->SetFieldOption('PrimaryImage', 'original_field', $item_image['Name']);
 
 						$this->_loadCustomFields($object, $item_image, 0);
 					}
 					continue;
 				}
 
 				if (abs($item_image['Priority'])) {
 					// use Priority as image counter, when specified
 					$image_counter = abs($item_image['Priority']);
 				}
 
 				if ( $object->isField('Image'.$image_counter) ) {
 					$object->SetDBField('Image'.$image_counter, $image_path);
 					$object->SetOriginalField('Image'.$image_counter, $image_path);
 					$object->SetFieldOption('Image'.$image_counter, 'original_field', $item_image['Name']);
 
 					$this->_loadCustomFields($object, $item_image, $image_counter);
 				}
 				$image_counter++;
 			}
 		}
 
 		/**
 		 * Saves newly uploaded images to external image table
 		 *
 		 * @param kCatDBItem|kDBItem $object
 		 */
 		function SaveItemImages(&$object)
 		{
 			if (!$this->_canUseImages($object)) {
 				return ;
 			}
 
-			$table_name = $this->Application->getUnitOption('img', 'TableName');
-			$max_image_count = $this->Application->getUnitOption($object->Prefix, 'ImageCount'); // $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
+			$table_name = $this->Application->getUnitConfig('img')->getTableName();
+			$max_image_count = $object->getUnitConfig()->getImageCount(); // $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');
 
 			$i = 0;
 			while ($i < $max_image_count) {
 				$field = $i ? 'Image'.$i : 'PrimaryImage';
 				$field_options = $object->GetFieldOptions($field);
 
 				$image_src = $object->GetDBField($field);
 				if ($image_src) {
 					if (isset($field_options['original_field'])) {
 						$key_clause = 'Name = '.$this->Conn->qstr($field_options['original_field']).' AND ResourceId = '.$object->GetDBField('ResourceId');
 
 						if ($object->GetDBField('Delete'.$field)) {
 							// if item was cloned, then new filename is in db (not in $image_src)
 							$sql = 'SELECT ThumbPath
 									FROM '.$table_name.'
 									WHERE '.$key_clause;
 							$image_src = $this->Conn->GetOne($sql);
 							if (@unlink(FULL_PATH.$image_src)) {
 								$sql = 'DELETE FROM '.$table_name.'
 										WHERE '.$key_clause;
 								$this->Conn->Query($sql);
 							}
 						}
 						else {
 							// image record found -> update
 							$fields_hash = Array (
 								'ThumbPath' => $image_src,
 							);
 							$this->_saveCustomFields($object, $fields_hash, $i);
 
 							$this->Conn->doUpdate($fields_hash, $table_name, $key_clause);
 						}
 					}
 					else {
 						// image record not found -> create
 						$fields_hash = Array (
 							'ResourceId' => $object->GetDBField('ResourceId'),
 							'Name' => $field,
 							'AltName' => $field,
 							'Enabled' => STATUS_ACTIVE,
 							'DefaultImg' => $i ? 0 : 1, // first image is primary, others not primary
 							'ThumbPath' => $image_src,
 							'Priority' => ($i == 0)? 0 : $i * (-1),
 						);
 						$this->_saveCustomFields($object, $fields_hash, $i);
 
 						$this->Conn->doInsert($fields_hash, $table_name);
 						$field_options['original_field'] = $field;
 						$object->SetFieldOptions($field, $field_options);
 					}
 				}
 				$i++;
 			}
 		}
 
 		/**
 		 * Adds ability to load custom fields along with main image field
 		 *
 		 * @param kCatDBItem|kDBItem $object
 		 * @param Array $fields_hash
 		 * @param int $counter 0 - primary image, other number - additional image number
 		 */
 		function _loadCustomFields(&$object, $fields_hash, $counter)
 		{
 			$field_name = $counter ? 'Image' . $counter . 'Alt' : 'PrimaryImageAlt';
 
 			$object->SetDBField($field_name, (string)$fields_hash['AltName']);
 		}
 
 		/**
 		 * Adds ability to save custom field along with main image save
 		 *
 		 * @param kCatDBItem|kDBItem $object
 		 * @param Array $fields_hash
 		 * @param int $counter 0 - primary image, other number - additional image number
 		 */
 		function _saveCustomFields(&$object, &$fields_hash, $counter)
 		{
 			$field_name = $counter ? 'Image' . $counter . 'Alt' : 'PrimaryImageAlt';
 
 			$fields_hash['AltName'] = (string)$object->GetDBField($field_name);
 		}
 
 		/**
 		 * Checks, that item can use image upload capabilities
 		 *
 		 * @param kCatDBItem|kDBItem $object
 		 * @return bool
 		 */
 		function _canUseImages(&$object)
 		{
 			$prefix = $object->Prefix == 'p' ? 'img' : $object->Prefix . '-img';
 
 			return $this->Application->prefixRegistred($prefix);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/rating_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/rating_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/rating_helper.php	(revision 15698)
@@ -1,266 +1,266 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class RatingHelper extends kHelper {
 
 		/**
 		 * One star width/height in pixels
 		 *
 		 * @var int
 		 */
 		var $ratingUnitWidth = 25;
 		var $ratingSmallUnitWidth = 10; //20;
 
 		/**
 		 * Maximal star count
 		 *
 		 * @var int
 		 */
 		var $ratingMaximal = 5;
 
 		var $_phrases = Array (
 			'current_rating' => 'lu_CurrentRating',
 			'vote_title' => 'lu_VoteTitle',
 			'vote_count' => 'lu_VoteCount',
 			'invalid_rating' => 'lu_InvalidRating',
 			'already_voted' => 'lu_AlreadyVoted',
 			'thanks_for_voting' => 'lu_ThanksForVoting',
 		);
 
 		/**
 		 * Draws rating bar for a given category item
 		 *
 		 * @param kDBItem $object
 		 * @param bool $show_div
 		 * @param string $additional_msg
 		 * @param string $additional_style
 		 * @return string
 		 * @access public
 		 */
 		public function ratingBar(&$object, $show_div = true, $additional_msg = '', $additional_style = '')
 		{
 			// 1. user is allowed to vote by permissions
-			$perm_prefix = $this->Application->getUnitOption($object->Prefix, 'PermItemPrefix');
+			$perm_prefix = $object->getUnitConfig()->getPermItemPrefix();
 			$static = !$this->Application->CheckPermission($perm_prefix . '.RATE', 0, $object->GetDBField('CategoryId'));
 
 			// 2. user isn't voting too frequently
 			$spam_helper =& $this->_getSpamHelper($object);
 			$user_voted = $spam_helper->InSpamControl();
 
 			if ( !$static && !$user_voted ) {
 				// allow to set rating when not static and user not voted before
 				$voting_js = $this->getVotingControl($object, $additional_style);
 			}
 			else {
 				$voting_js = '';
 			}
 
 			$msg_info = Array ('text' => $additional_msg, 'class' => Array ());
 
 			if ( $static ) {
 				$msg_info['class'][] = 'static';
 			}
 
 			if ( $user_voted ) {
 				$msg_info['class'][] = 'voted';
 			}
 
 			$rater = $this->ratingBarSimple($this->getAverageRating($object), $voting_js, $msg_info, $additional_style);
 
 			if ( $show_div ) {
 				// adds div around rating stars (when drawing rating first time)
 				$rater = '<div class="inline-rating" id="page_rating_' . $object->GetID() . '">' . $rater . '</div>';
 			}
 
 			return $rater;
 		}
 
 		/**
 		 * Returns average rating
 		 *
 		 * @param kDBItem $object
 		 * @return float|int
 		 */
 		function getAverageRating(&$object)
 		{
 			$total_votes = $object->GetDBField('CachedVotesQty');
 			$total_rating = $object->GetDBField('CachedRating') * $total_votes;
 
 			return $total_votes ? $total_rating / $total_votes : 0;
 		}
 
 		/**
 		 * Draws rating bar for a given category item
 		 *
 		 * @param float $average_rating
 		 * @param string $voting_js
 		 * @param Array $msg_info
 		 * @param string $additional_style
 		 * @return string
 		 * @access public
 		 */
 		public function ratingBarSimple($average_rating, $voting_js = '', $msg_info = null, $additional_style = '')
 		{
 			if ( !isset($msg_info) || !is_array($msg_info) ) {
 				$msg_info = Array ('text' => '', 'class' => Array ());
 			}
 
 			$unit_selected_width = $additional_style ? $this->ratingSmallUnitWidth : $this->ratingUnitWidth;
 			$rating_width = $average_rating ? @number_format($average_rating, 2) * $unit_selected_width : 0;
 
 			$rating2 = $average_rating ? @number_format($average_rating, 2) : 0;
 			$current_rating_text = $this->_replaceInPhrase('current_rating', Array ('<strong>' . $rating2 . '</strong>', $this->ratingMaximal));
 
 			$rater = '	<span class="inline-rating">
 							<ul class="star-rating ' . $additional_style . '" style="width: ' . $unit_selected_width * $this->ratingMaximal . 'px;">
 								<li class="current-rating" style="width: ' . $rating_width . 'px;">' . $current_rating_text . '</li>' . "\n" .
 					 			$voting_js . '
 					 		</ul>
 					 	</span>';
 
 			// this part is disabled for now, will be addressed once properly review
 			/*$rating1 = $average_rating ? @number_format($average_rating, 1) : 0;
 			$rater .= '	<p class="' . implode(' ', $msg_info['class']) . '">' . $this->_replaceInPhrase('vote_title', Array ('<strong>' . $rating1 . '</strong>', $this->ratingMaximal)) . ' (' . $this->_replaceInPhrase('vote_count', Array ($total_votes)) . ') </p>';*/
 
 			if ( $voting_js ) {
 				$rater .= '&nbsp;<span class="' . implode(' ', $msg_info['class']) . '">' . $msg_info['text'] . '</span>';
 			}
 			else {
 				// adds div around rating stars (when drawing rating first time)
 				$rater = '<div class="inline-rating">' . $rater . '</div>';
 			}
 
 			return $rater;
 		}
 
 		/**
 		 * Returns control, used to vote on a given $object
 		 *
 		 * @param kDBItem $object
 		 * @param string $additional_style
 		 * @return string
 		 */
 		function getVotingControl(&$object, $additional_style = '')
 		{
 			$ret = '';
 
 			for ($i = 1; $i <= $this->ratingMaximal; $i++) {
 				$ret .= '<li><a href="#vote-' . $i . '" onclick="aRatingManager.makeVote(' . $i . ', \'' . $object->Prefix . '\', ' . $object->GetID() . ', \'' . $additional_style . '\'); return false;" title="' . $this->_replaceInPhrase('vote_title', Array ($i, $this->ratingMaximal)) . '" class="r' . $i . '-unit rater" rel="nofollow">' . $i . '</a></li>' . "\n";
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Saves user's vote, when allowed
 		 *
 		 * @param kDBItem $object
 		 * @return string
 		 */
 		function makeVote(&$object)
 		{
 			$spam_helper =& $this->_getSpamHelper($object);
 
 			if (!$object->isLoaded() || $spam_helper->InSpamControl()) {
 				return '@err:' . $this->_replaceInPhrase('already_voted');
 			}
 
-			$perm_prefix = $this->Application->getUnitOption($object->Prefix, 'PermItemPrefix');
+			$perm_prefix = $object->getUnitConfig()->getPermItemPrefix();
 			$can_rate = $this->Application->CheckPermission($perm_prefix . '.RATE', 0, $object->GetDBField('CategoryId'));
 			$rating = (int)$this->Application->GetVar('rating'); // not numeric rating is from GoogleBot :(
 			$additional_style = $this->Application->GetVar('size');
 
 			if (($rating <= 0) || ($rating > $this->ratingMaximal) || !$can_rate) {
 				return '@err:' . $this->_replaceInPhrase('invalid_rating');
 			}
 
 			// save current rating
 			$fields_hash = Array (
 				'ItemId'		=>	$object->GetID(),
 				'RatingValue'	=>	$rating,
 				'IPAddress'		=>	$this->Application->getClientIp(),
 				'CreatedOn'		=>	adodb_mktime(),
 			);
 			$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'CatalogRatings');
 
 			// recalculate average rating
 			$votes_count = $object->GetDBField('CachedVotesQty');
 			$avg_rating = $object->GetDBField('CachedRating');
 
 			$avg_rating = round((($votes_count * $avg_rating) + $rating) / ($votes_count + 1), 2);
 			$object->SetDBField('CachedRating', "$avg_rating");
 			$object->Update();
 
 			$sql = 'UPDATE '.$object->TableName.'
 					SET CachedVotesQty = CachedVotesQty + 1
 					WHERE '.$object->IDField.' = '.$object->GetID();
 			$this->Conn->Query($sql);
 
 			$object->SetDBField('CachedVotesQty', $object->GetDBField('CachedVotesQty') + 1); // for using in template
 
 			// prevent user from voting too quickly
 			$spam_helper->AddToSpamControl();
 
 			return $this->ratingBar($object, false, '<span class="thanks">' . $this->_replaceInPhrase('thanks_for_voting') . '</span>', $additional_style);
 		}
 
 		/*function purgeVotes()
 		{
 			$expired = adodb_mktime() - 86400 * $this->Application->ConfigValue('Timeout_Rating'); // 3600
 
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'CatalogRatings
 					WHERE CreatedOn < ' . $expired;
 			$this->Conn->Query($sql);
 		}*/
 
 		/**
 		 * Performs sprintf on phrase translation using given variables
 		 *
 		 * @param string $phrase
 		 * @param Array $arguments
 		 * @return string
 		 */
 		function _replaceInPhrase($phrase, $arguments = Array ())
 		{
 			$value = $this->Application->Phrase($this->_phrases[$phrase], false);
 
 			if ($arguments) {
 				return vsprintf($value, $arguments);
 			}
 
 			return $value;
 		}
 
 		/**
 		 * Returns SpamHelper object linked to given object
 		 *
 		 * @param kDBItem $object
 		 * @return SpamHelper
 		 * @access protected
 		 */
 		protected function &_getSpamHelper(&$object)
 		{
 			$spam_helper = $this->Application->recallObject('SpamHelper');
 			/* @var $spam_helper SpamHelper */
 
 			// 2. user isn't voting too frequently
-			$config_mapping = $this->Application->getUnitOption($object->Prefix, 'ConfigMapping');
+			$config_mapping = $object->getUnitConfig()->getConfigMapping();
 			$review_settings = $config_mapping['RatingDelayValue'] . ':' . $config_mapping['RatingDelayInterval'];
 			$spam_helper->InitHelper($object->GetDBField('ResourceId'), 'Rating', $review_settings, $object->GetCol('ResourceId'));
 
 			return $spam_helper;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/country_states_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/country_states_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/country_states_helper.php	(revision 15698)
@@ -1,223 +1,225 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kCountryStatesHelper extends kHelper
 	{
 		/**
 		 * Returns countries, that have states
 		 *
 		 * @return Array
 		 */
 		function getCountriesWithStates()
 		{
 			static $cache = null;
 
-			if (!isset($cache)) {
-				$table_name = $this->Application->getUnitOption('country-state', 'TableName');
+			if ( !isset($cache) ) {
+				$table_name = $this->Application->getUnitConfig('country-state')->getTableName();
 
 				$sql = 'SELECT DISTINCT cname.IsoCode, cid.StateCountryId
 						FROM ' . $table_name . ' cid
 						JOIN ' . $table_name . ' cname ON cname.CountryStateId = cid.StateCountryId
 						WHERE cid.StateCountryId IS NOT NULL';
 				$cache = $this->Conn->GetCol($sql, 'StateCountryId');
 			}
 
 			return $cache;
 		}
 
 		/**
 		 * Checks, that country with given 3symbol ISO code has states
 		 *
 		 * @param string $country_code
 		 * @return bool
 		 */
 		function CountryHasStates($country_code)
 		{
 			return $country_code ? in_array($country_code, $this->getCountriesWithStates()) : false;
 		}
 
 		/**
 		 * Prepares states dropdown based on country selected
 		 *
 		 * @param kEvent $event
 		 * @param string $state_field
 		 * @param string $country_field
 		 */
 		function PopulateStates($event, $state_field, $country_field)
 		{
 			static $cache = Array ();
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$country_iso = $object->GetDBField($country_field);
 
 			if (!$country_iso) {
 				return ;
 			}
 
 			if (!array_key_exists($country_iso, $cache)) {
 				$states = $this->getStates($country_iso);
 
 				if ( !$states ) {
 					return;
 				}
 
 				$cache[$country_iso] = $states;
 			}
 
 			$field_options = $object->GetFieldOptions($state_field);
 
 			$field_options['options'] = $cache[$country_iso];
 			$field_options['options'][''] = '';
 
 			$object->SetFieldOptions($state_field, $field_options);
 		}
 
 		/**
 		 * Returns list of given country states
 		 *
 		 * @param string $country_iso
 		 * @return Array
 		 */
 		public function getStates($country_iso)
 		{
 			$country_id = $this->getCountryStateId($country_iso, DESTINATION_TYPE_COUNTRY);
 
 			if ( !$country_id ) {
 				return Array ();
 			}
 
 			// don't use GetVar('m_lang') since it's always equals to default language on editing form in admin
 			$current_language = $this->Application->Phrases->LanguageId;
 			$primary_language = $this->Application->GetDefaultLanguageId();
 
 			$sql = 'SELECT IF(l' . $current_language . '_Name = "", l' . $primary_language . '_Name, l' . $current_language . '_Name) AS Name, IsoCode
-					FROM ' . $this->Application->getUnitOption('country-state', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('country-state')->getTableName() . '
 					WHERE (Type = ' . DESTINATION_TYPE_STATE . ') AND (StateCountryId = ' . $country_id . ')
 					ORDER BY Name ASC';
 
 			return $this->Conn->GetCol($sql, 'IsoCode');
 		}
 
 		/**
 		 * Returns valid state ISO code for state name and country code passed
 		 *
 		 * @param string $state_name
 		 * @param string $country_iso
 		 * @return string
 		 */
 		function getStateIso($state_name, $country_iso)
 		{
-			if (!$this->CountryHasStates($country_iso)) {
+			if ( !$this->CountryHasStates($country_iso) ) {
 				return $state_name;
 			}
 
-			$table_name = $this->Application->getUnitOption('country-state', 'TableName');
+			$table_name = $this->Application->getUnitConfig('country-state')->getTableName();
 			$country_id = $this->getCountryStateId($country_iso, DESTINATION_TYPE_COUNTRY);
 
 			// don't use GetVar('m_lang') since it's always equals to default language on editing form in admin
 			$current_language = $this->Application->Phrases->LanguageId;
 			$primary_language = $this->Application->GetDefaultLanguageId();
 
 			$sql = 'SELECT IsoCode
 					FROM ' . $table_name . '
 					WHERE (Type = ' . DESTINATION_TYPE_STATE . ') AND (StateCountryId = %1$s) AND
 						(
 							(IsoCode = %2$s) OR (UPPER(l%3$s_Name) = %2$s) OR (UPPER(l%4$s_Name) = %2$s)
 						)';
 
-			$state_name = trim( mb_strtoupper($state_name) );
+			$state_name = trim(mb_strtoupper($state_name));
 			$sql = sprintf($sql, $country_id, $this->Conn->qstr($state_name), $current_language, $primary_language);
 
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Checks, that entered state matches entered country
 		 *
 		 * @param kEvent $event
 		 * @param string $state_field
 		 * @param string $country_field
 		 * @param bool $auto_required
 		 * @return void
 		 */
 		function CheckStateField($event, $state_field, $country_field, $auto_required = true)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$country_iso = $object->GetDBField($country_field);
 
 			if ( $auto_required ) {
 				$object->setRequired($state_field, $this->CountryHasStates($country_iso));
 			}
 
 			$state = $object->GetDBField($state_field);
 
 			if ( $country_iso && $state ) {
 				$state_iso = $this->getStateIso($state, $country_iso);
 
 				if ( $state_iso !== false ) {
 					// replace state name with it's ISO code
 					$object->SetDBField($state_field, $state_iso);
 				}
 				else {
 					// state not found by name -> report error
 					$object->SetError($state_field, 'invalid_state', 'la_invalid_state');
 				}
 			}
 		}
 
 		/**
 		 * Returns country/state id based on given iso code and it's type
 		 *
 		 * @param string $iso_code
 		 * @param int $type
 		 * @return int
 		 */
 		function getCountryStateId($iso_code, $type)
 		{
-			$sql = 'SELECT ' . $this->Application->getUnitOption('country-state', 'IDField') . '
-					FROM ' . $this->Application->getUnitOption('country-state', 'TableName') . '
+			$config = $this->Application->getUnitConfig('country-state');
+
+			$sql = 'SELECT ' . $config->getIDField() . '
+					FROM ' . $config->getTableName() . '
 					WHERE (Type = ' . $type . ') AND (IsoCode = ' . $this->Conn->qstr($iso_code) . ')';
 
 			return (int)$this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Returns 3 symbols ISO code from 2 symbols ISO code or otherwise, when $from_short parameter is used
 		 *
 		 * @param string $iso_code
 		 * @param bool $from_short
 		 * @return string
 		 */
 		function getCountryIso($iso_code, $from_short = false)
 		{
 			if ($from_short) {
 				$sql = 'SELECT IsoCode
 						FROM ' . TABLE_PREFIX . 'CountryStates
 						WHERE ShortIsoCode = ' . $this->Conn->qstr($iso_code) . ' AND `Type` = ' . DESTINATION_TYPE_COUNTRY;
 			}
 			else {
 				$sql = 'SELECT ShortIsoCode
 						FROM ' . TABLE_PREFIX . 'CountryStates
 						WHERE IsoCode = ' . $this->Conn->qstr($iso_code) . ' AND `Type` = ' . DESTINATION_TYPE_COUNTRY;
 			}
 
 			return $this->Conn->GetOne($sql);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/count_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/count_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/count_helper.php	(revision 15698)
@@ -1,291 +1,294 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kCountHelper extends kHelper {
 
 		/**
 		 * Returns counter value
 		 *
 		 * @param string $name counter name
 		 * @param Array $params counter parameters
 		 * @param string $query_name specify query name directly (don't generate from parmeters)
 		 * @param bool $multiple_results
 		 * @return mixed
 		 */
 		function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false)
 		{
 			$this->resetExpiredCounters();
 
 			$clone_counter = false;
 			$query_name = isset($query_name) ? $query_name : rtrim($name.'-'.implode('-', array_values($params)), '-');
 			$sql = 'SELECT *
 					FROM '.TABLE_PREFIX.'Counters
 					WHERE Name = '.$this->Conn->qstr($query_name);
 			$count_data = $this->Conn->GetRow($sql);
 
 			if (!$count_data && ($query_name != $name)) {
 				// cloned record not found -> search for origial one
 				$sql = 'SELECT *
 						FROM '.TABLE_PREFIX.'Counters
 						WHERE Name = '.$this->Conn->qstr($name);
 				$count_data = $this->Conn->GetRow($sql);
 				$clone_counter = true;
 			}
 
 			if (is_null($count_data['CountValue'])) {
 				$params['PREFIX'] = TABLE_PREFIX;
 				$count_sql = $count_data['CountQuery'];
 
 				foreach ($params as $replace_from => $replace_to) {
 					$count_sql = str_replace('<%'.$replace_from.'%>', $replace_to, $count_sql);
 				}
 
 				if ($multiple_results) {
 					$count_data['CountValue'] = serialize($this->Conn->GetCol($count_sql));
 				}
 				else {
 					$count_data['CountValue'] = $this->Conn->GetOne($count_sql);
 				}
 
 				if ($clone_counter && !$count_data['IsClone']) {
 					// don't clone clones
 					$count_data['CounterId'] = 0;
 					$count_data['Name'] = $query_name;
 					$count_data['IsClone'] = 1;
 				}
 
 				$count_data['LastCounted'] = adodb_mktime();
 				$this->Conn->doInsert($count_data, TABLE_PREFIX.'Counters', 'REPLACE');
 			}
 
 			return $multiple_results ? unserialize($count_data['CountValue']) : $count_data['CountValue'];
 		}
 
 		/**
 		 * Resets counter, which are affected by one of specified tables
 		 *
 		 * @param string $tables comma separated tables list used in counting sqls
 		 * @return void
 		 * @access public
 		 */
 		public function resetCounters($tables)
 		{
 			$tables = explode(',', $tables);
 			$prefix_length = strlen(TABLE_PREFIX);
 			foreach ($tables as $index => $table) {
 				// remove prefixes
 				if (substr($table, 0, $prefix_length) == TABLE_PREFIX) {
 					$table = substr($table, $prefix_length);
 				}
 				$tables[$index] = '(TablesAffected LIKE "%|'.$table.'|%")';
 			}
 
 			$sql = 'UPDATE '.TABLE_PREFIX.'Counters
 					SET CountValue = NULL
 					WHERE '.implode(' OR ', $tables);
 
 			$this->Conn->Query($sql);
 		}
 
 		function resetExpiredCounters()
 		{
 			static $reset = false;
 
 			if (!$reset) {
 				// reset expired counts
 				$sql = 'UPDATE '.TABLE_PREFIX.'Counters
 						SET CountValue = NULL
 						WHERE (LifeTime > 0) AND (LastCounted < '.adodb_mktime().' - LifeTime)';
 				$this->Conn->Query($sql);
 				$reset = true;
 			}
 		}
 
 		/**
 		 * Counts items (of specific type) in category & subcategories
 		 *
 		 * @param string $prefix
 		 * @param Array $params
 		 * @param mixed $count_sql
 		 * @return int
 		 */
 		function CategoryItemCount($prefix, $params, $count_sql = null)
 		{
-			if (!$this->Application->findModule('Var', $prefix)) {
+			if ( !$this->Application->findModule('Var', $prefix) ) {
 				// this is not module item
 				return $this->Application->ProcessParsedTag($prefix, 'CategoryItemCount', $params);
 			}
 
 			// 1. get root category for counting
 			$category_id = isset($params['cat_id']) ? $params['cat_id'] : false;
-			if (!is_numeric($category_id)) {
+			if ( !is_numeric($category_id) ) {
 				$category_id = $this->Application->GetVar('m_cat_id');
 			}
 
-			if (!isset($count_sql)) {
+			if ( !isset($count_sql) ) {
 				$count_sql = 'COUNT(*)';
 			}
 
 			$where_clauses = Array ();
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+			$table_name = $this->Application->getUnitConfig($prefix)->getTableName();
 
 			// 1. count category items
-			$sql = 'SELECT '.$count_sql.'
-					FROM '.$table_name.' item_table
-					INNER JOIN '.TABLE_PREFIX.'CategoryItems ci ON (ci.ItemResourceId = item_table.ResourceId)
-					INNER JOIN '.TABLE_PREFIX.'Categories c ON (ci.CategoryId = c.CategoryId)';
+			$sql = 'SELECT ' . $count_sql . '
+					FROM ' . $table_name . ' item_table
+					INNER JOIN ' . TABLE_PREFIX . 'CategoryItems ci ON (ci.ItemResourceId = item_table.ResourceId)
+					INNER JOIN ' . TABLE_PREFIX . 'Categories c ON (ci.CategoryId = c.CategoryId)';
 
 			// 2. count items from subcategories
-			if ($category_id > 0) {
+			if ( $category_id > 0 ) {
 				// get subcategories of required category
 				$tmp_sql = 'SELECT TreeLeft, TreeRight
-							FROM '.TABLE_PREFIX.'Categories
-							WHERE CategoryId = '.$category_id;
+							FROM ' . TABLE_PREFIX . 'Categories
+							WHERE CategoryId = ' . $category_id;
 				$tree_info = $this->Conn->GetRow($tmp_sql);
-				$where_clauses[] = 'c.TreeLeft BETWEEN '.$tree_info['TreeLeft'].' AND '.$tree_info['TreeRight'];
+				$where_clauses[] = 'c.TreeLeft BETWEEN ' . $tree_info['TreeLeft'] . ' AND ' . $tree_info['TreeRight'];
 			}
-			$where_clauses[] = 'item_table.Status = '.STATUS_ACTIVE;
+			$where_clauses[] = 'item_table.Status = ' . STATUS_ACTIVE;
 
-			if (isset($params['today']) && $params['today']) {
-				$today = adodb_mktime(0,0,0, adodb_date('m'), adodb_date('d'), adodb_date('Y'));
-				$where_clauses[] = 'item_table.CreatedOn >= '.$today;
+			if ( isset($params['today']) && $params['today'] ) {
+				$today = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y'));
+				$where_clauses[] = 'item_table.CreatedOn >= ' . $today;
 			}
 
-			$sql .= ' WHERE ('.implode(') AND (', $where_clauses).')';
+			$sql .= ' WHERE (' . implode(') AND (', $where_clauses) . ')';
 
 			return (int)$this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Counts items (of specific type) from all categories
 		 *
 		 * @param string $prefix
 		 * @param bool $today
 		 * @param string $count_sql
 		 * @return int
 		 */
 		function ItemCount($prefix, $today = false, $count_sql = null)
 		{
-		  	$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+			$table_name = $this->Application->getUnitConfig($prefix)->getTableName();
 
-		  	if (!isset($count_sql)) {
-		  		$count_sql = 'COUNT(*)';
-		  	}
-
-		  	$sql = 'SELECT '.$count_sql.'
-		  			FROM '.$table_name.' item_table
-		  			INNER JOIN '.TABLE_PREFIX.'CategoryItems ci ON ci.ItemResourceId = item_table.ResourceId
-		    		INNER JOIN '.TABLE_PREFIX.'Categories c ON c.CategoryId = ci.CategoryId
-		    		INNER JOIN '.TABLE_PREFIX.'CategoryPermissionsCache perm_cache ON ci.CategoryId = perm_cache.CategoryId';
-
-		  	list ($view_perm, $view_filter) = $this->GetPermissionClause($prefix, 'perm_cache');
-		  	$where_clauses = Array (
-		  		$view_filter, 'perm_cache.PermId = '.$view_perm, 'ci.PrimaryCat = 1', 'c.Status = '.STATUS_ACTIVE,
+			if ( !isset($count_sql) ) {
+				$count_sql = 'COUNT(*)';
+			}
+
+			$sql = 'SELECT ' . $count_sql . '
+		  			FROM ' . $table_name . ' item_table
+		  			INNER JOIN ' . TABLE_PREFIX . 'CategoryItems ci ON ci.ItemResourceId = item_table.ResourceId
+		    		INNER JOIN ' . TABLE_PREFIX . 'Categories c ON c.CategoryId = ci.CategoryId
+		    		INNER JOIN ' . TABLE_PREFIX . 'CategoryPermissionsCache perm_cache ON ci.CategoryId = perm_cache.CategoryId';
+
+			list ($view_perm, $view_filter) = $this->GetPermissionClause($prefix, 'perm_cache');
+
+			$where_clauses = Array (
+				$view_filter, 'perm_cache.PermId = ' . $view_perm,
+				'ci.PrimaryCat = 1',
+				'c.Status = ' . STATUS_ACTIVE,
 			);
 
-		  	if ($today) {
-		  		$today_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y'));
-		  		$where_clauses[] = 'item_table.CreatedOn >= '.$today_date;
-		  	}
+			if ( $today ) {
+				$today_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y'));
+				$where_clauses[] = 'item_table.CreatedOn >= ' . $today_date;
+			}
 
-		  	$sql .= ' WHERE ('.implode(') AND (', $where_clauses).')';
+			$sql .= ' WHERE (' . implode(') AND (', $where_clauses) . ')';
 
 			return (int)$this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Returns categories count in system
 		 *
 		 * @param bool $today
 		 * @return int
 		 */
 		function CategoryCount($today = false)
 		{
-		  	$cache_key = 'category_count[%CSerial%]';
+			$cache_key = 'category_count[%CSerial%]';
 
-			if ($today) {
-		  		$today_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y'));
-		  		$cache_key .= ':date=' . $today_date;
-		  	}
+			if ( $today ) {
+				$today_date = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), adodb_date('Y'));
+				$cache_key .= ':date=' . $today_date;
+			}
 
-		  	$count = $this->Application->getCache($cache_key);
+			$count = $this->Application->getCache($cache_key);
 
-		  	if ($count === false) {
+			if ( $count === false ) {
 				$sql = 'SELECT COUNT(*)
-			  			FROM ' . $this->Application->getUnitOption('c', 'TableName') . ' c
+			  			FROM ' . $this->Application->getUnitConfig('c')->getTableName() . ' c
 			    		INNER JOIN ' . TABLE_PREFIX . 'CategoryPermissionsCache perm_cache ON c.CategoryId = perm_cache.CategoryId';
 
-			  	list ($view_perm, $view_filter) = $this->GetPermissionClause('c', 'perm_cache');
+				list ($view_perm, $view_filter) = $this->GetPermissionClause('c', 'perm_cache');
 
-			  	$where_clauses = Array (
-			  		$view_filter,
-			  		'perm_cache.PermId = ' . $view_perm,
-			  		'c.Status = ' . STATUS_ACTIVE,
+				$where_clauses = Array (
+					$view_filter,
+					'perm_cache.PermId = ' . $view_perm,
+					'c.Status = ' . STATUS_ACTIVE,
 				);
 
-			  	if ($today) {
-			  		$where_clauses[] = 'c.CreatedOn >= ' . $today_date;
-			  	}
+				if ( $today ) {
+					$where_clauses[] = 'c.CreatedOn >= ' . $today_date;
+				}
 
-			    $sql .= ' WHERE ('.implode(') AND (', $where_clauses).')';
+				$sql .= ' WHERE (' . implode(') AND (', $where_clauses) . ')';
 
 				$count = $this->Conn->GetOne($sql);
 
-				if ($count !== false) {
+				if ( $count !== false ) {
 					$this->Application->setCache($cache_key, $count);
 				}
-		  	}
+			}
 
-		  	return $count;
+			return $count;
 		}
 
 		/**
 		 * Returns permission limitation clause for category item lists
 		 *
 		 * @param string $prefix
 		 * @param string $table_alias
 		 * @return Array
 		 */
 		function GetPermissionClause($prefix, $table_alias)
 		{
 			$permissions_ids = $this->Application->getCache(__CLASS__ . '::' . __FUNCTION__);
 
-			if ($permissions_ids === false) {
+			if ( $permissions_ids === false ) {
 				$this->Conn->nextQueryCachable = true;
 
 				$sql = 'SELECT PermissionConfigId, PermissionName
-						FROM '.TABLE_PREFIX.'CategoryPermissionsConfig
+						FROM ' . TABLE_PREFIX . 'CategoryPermissionsConfig
 						WHERE PermissionName LIKE "%.VIEW"';
 				$permissions_ids = $this->Conn->GetCol($sql, 'PermissionName');
 
 				$this->Application->setCache(__CLASS__ . '::' . __FUNCTION__, $permissions_ids);
 			}
 
-			$permission_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix');
+			$permission_prefix = $this->Application->getUnitConfig($prefix)->getPermItemPrefix();
 			$view_perm = $permissions_ids[$permission_prefix . '.VIEW'];
 
 			$groups = explode(',', $this->Application->RecallVar('UserGroups'));
 
 			foreach ($groups as $group) {
-				$view_filters[] = 'FIND_IN_SET('.$group.', '.$table_alias.'.acl)';
+				$view_filters[] = 'FIND_IN_SET(' . $group . ', ' . $table_alias . '.acl)';
 			}
 
 			$view_filter = implode(' OR ', $view_filters);
 
 			return Array ($view_perm, $view_filter);
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/csv_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/csv_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/csv_helper.php	(revision 15698)
@@ -1,380 +1,410 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	kUtil::safeDefine('EXPORT_STEP', 100); // export by 100 items
 	kUtil::safeDefine('IMPORT_STEP', 10);
 
 	class kCSVHelper extends kHelper {
 
 		var $PrefixSpecial;
 		var $grid;
 
 		var $delimiter_mapping = Array(0 => "\t", 1 => ',', 2 => ';', 3 => ' ', 4 => ':');
 		var $enclosure_mapping = Array(0 => '"', 1 => "'");
 		var $separator_mapping = Array(0 => "\n", 1 => "\r\n");
 
 		function ExportStep()
 		{
 			$export_data = $this->Application->RecallVar('export_data');
 			$export_rand = $this->Application->RecallVar('export_rand');
 			$get_rand = $this->Application->GetVar('export_rand');
 
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			if ( $export_data && $export_rand == $get_rand ) {
 				$export_data = unserialize($export_data);
 				$first_step = false;
 			}
 			else {
 				// first step
 				$export_data = Array ();
 				$export_data['prefix'] = $this->PrefixSpecial;
 				$export_data['grid'] = $this->grid;
 				$export_data['file_name'] = EXPORT_PATH . '/' . $file_helper->ensureUniqueFilename(EXPORT_PATH, 'export_' . $export_data['prefix'] . '.csv');
 				$export_data['step'] = EXPORT_STEP;
 				$export_data['delimiter'] = $this->delimiter_mapping[(int)$this->Application->ConfigValue('CSVExportDelimiter')];
 				$export_data['enclosure'] = $this->enclosure_mapping[(int)$this->Application->ConfigValue('CSVExportEnclosure')];
 				$export_data['record_separator'] = $this->separator_mapping[(int)$this->Application->ConfigValue('CSVExportSeparator')];
 				$export_data['page'] = 1;
 				$export_data['source_encoding'] = strtoupper(CHARSET);
 				$export_data['encoding'] = $this->Application->ConfigValue('CSVExportEncoding') ? false : 'UTF-16LE';
 
 				$this->Application->StoreVar('export_rand', $get_rand);
 
 				$first_step = true;
 			}
 
 			$file = fopen($export_data['file_name'], $first_step ? 'w' : 'a');
 
-			$prefix_elems = preg_split('/\.|_/', $export_data['prefix'], 2);
-			$grids = $this->Application->getUnitOption($prefix_elems[0], 'Grids');
-			$grid_config = $grids[$export_data['grid']]['Fields'];
+			$prefix_elements = preg_split('/\.|_/', $export_data['prefix'], 2);
+			$grid_config = $this->_getGridColumns($prefix_elements[0], $export_data['grid']);
 
 			$list_params = Array ('per_page' => $export_data['step'], 'grid' => $export_data['grid']);
-			$list = $this->Application->recallObject(rtrim(implode('.', $prefix_elems), '.'), $prefix_elems[0] . '_List', $list_params);
+			$list = $this->Application->recallObject(rtrim(implode('.', $prefix_elements), '.'), $prefix_elements[0] . '_List', $list_params);
 			/* @var $list kDBList */
 
 			$list->SetPage($export_data['page']);
 			$list->Query();
 			$list->GoFirst();
 
 			$picker_helper = $this->Application->recallObject('ColumnPickerHelper');
 			/* @var $picker_helper kColumnPickerHelper */
 
-			$picker_helper->ApplyPicker(rtrim(implode('.', $prefix_elems), '.'), $grid_config, $export_data['grid']);
+			$picker_helper->ApplyPicker(rtrim(implode('.', $prefix_elements), '.'), $grid_config, $export_data['grid']);
 
 			if ( $first_step ) {
 				// if UTF-16, write Unicode marker
 				if ( $export_data['encoding'] == 'UTF-16LE' ) {
 					fwrite($file, chr(0xFF) . chr(0xFE));
 				}
 
 				// inserting header line
 				$headers = Array ();
 				foreach ($grid_config as $field_name => $field_data) {
 					$use_phrases = array_key_exists('use_phrases', $field_data) ? $field_data['use_phrases'] : true;
 					$field_title = isset($field_data['title']) ? $field_data['title'] : 'column:la_fld_' . $field_name;
 					$header = $use_phrases ? $this->Application->Phrase($field_title) : $field_title;
 					array_push($headers, $header);
 				}
 
 				$csv_line = kUtil::getcsvline($headers, $export_data['delimiter'], $export_data['enclosure'], $export_data['record_separator']);
 				if ( $export_data['encoding'] ) {
 					$csv_line = mb_convert_encoding($csv_line, $export_data['encoding'], $export_data['source_encoding']);
 				}
 				fwrite($file, $csv_line);
 			}
 
 			while (!$list->EOL()) {
 				$data = Array ();
 				foreach ($grid_config as $field_name => $field_data) {
 					if ( isset($field_data['export_field']) ) {
 						$field_name = $field_data['export_field'];
 					}
 					$value = $list->GetField($field_name, isset($field_data['format']) ? $field_data['format'] : null);
 					$value = str_replace("\r\n", "\n", $value);
 					$value = str_replace("\r", "\n", $value);
 					array_push($data, $value);
 				}
 
 				if ( $export_data['encoding'] == 'UTF-16LE' ) {
 					fwrite($file, chr(0xFF) . chr(0xFE));
 				}
 
 				$csv_line = kUtil::getcsvline($data, $export_data['delimiter'], $export_data['enclosure'], $export_data['record_separator']);
 				if ( $export_data['encoding'] ) {
 					$csv_line = mb_convert_encoding($csv_line, $export_data['encoding'], $export_data['source_encoding']);
 				}
 				fwrite($file, $csv_line);
 
 				$list->GoNext();
 			}
 
 			$records_processed = $export_data['page'] * $export_data['step'];
 			$percent_complete = min($records_processed / $list->GetRecordsCount() * 100, 100);
 
 			fclose($file);
 
 			if ( $records_processed >= $list->GetRecordsCount() ) {
 				$this->Application->StoreVar('export_data', serialize($export_data));
 				$this->Application->Redirect($this->Application->GetVar('finish_template'));
 			}
 
 			echo $percent_complete;
 
 			$export_data['page']++;
 			$this->Application->StoreVar('export_data', serialize($export_data));
 		}
 
 		function ExportData($name)
 		{
 			$export_data = unserialize($this->Application->RecallVar('export_data'));
 			return isset($export_data[$name]) ? $export_data[$name] : false;
 		}
 
 		/**
 		 * Returns prefix from request or from stored import/export data
 		 *
 		 * @param bool $is_import
 		 * @return string
 		 */
 		public function getPrefix($is_import = false)
 		{
 			$prefix = $this->Application->GetVar('PrefixSpecial');
 
 			if ( !$prefix ) {
 				return $is_import ? $this->ImportData('prefix') : $this->ExportData('prefix');
 			}
 
 			return $prefix;
 		}
 
 		function GetCSV()
 		{
 			kUtil::safeDefine('DBG_SKIP_REPORTING', 1);
 
 			$export_data = unserialize($this->Application->RecallVar('export_data'));
 			$filename = preg_replace('/(.*)\.csv$/', '\1', basename($export_data['file_name'])) . '.csv';
 
 			$this->Application->setContentType('text/csv');
 			header('Content-Disposition: attachment; filename="' . $filename . '"');
 			readfile($export_data['file_name']);
 			die();
 		}
 
 		function ImportStart($filename)
 		{
-			if(!file_exists($filename) || !is_file($filename)) return 'cant_open_file';
+			if ( !file_exists($filename) || !is_file($filename) ) {
+				return 'cant_open_file';
+			}
 
-			$import_data = Array();
+			$import_data = Array ();
 			$import_data['source_encoding'] = strtoupper(CHARSET);
 			$import_data['encoding'] = $this->Application->ConfigValue('CSVExportEncoding') ? false : 'UTF-16LE';
 			$import_data['errors'] = '';
 
 			// convert file in case of UTF-16LE
-			if($import_data['source_encoding'] != $import_data['encoding']) {
-				copy($filename, $filename.'.orginal');
+			if ( $import_data['source_encoding'] != $import_data['encoding'] ) {
+				copy($filename, $filename . '.orginal');
 				$file_content = file_get_contents($filename);
 				$file = fopen($filename, 'w');
-				fwrite($file, mb_convert_encoding(str_replace(chr(0xFF).chr(0xFE), '', $file_content), $import_data['source_encoding'], $import_data['encoding']));
+				fwrite($file, mb_convert_encoding(str_replace(chr(0xFF) . chr(0xFE), '', $file_content), $import_data['source_encoding'], $import_data['encoding']));
 				fclose($file);
-
 			}
 
 			$import_data['prefix'] = $this->PrefixSpecial;
 			$import_data['grid'] = $this->grid;
 			$import_data['file'] = $filename;
 			$import_data['total_lines'] = count(file($filename));
-			if(!$import_data['total_lines']) $import_data['total_lines'] = 1;
+
+			if ( !$import_data['total_lines'] ) {
+				$import_data['total_lines'] = 1;
+			}
+
 			unset($file_content);
 			$import_data['lines_processed'] = 0;
 			$import_data['delimiter'] = $this->delimiter_mapping[(int)$this->Application->ConfigValue('CSVExportDelimiter')];
 			$import_data['enclosure'] = $this->enclosure_mapping[(int)$this->Application->ConfigValue('CSVExportEnclosure')];
 			$import_data['step'] = IMPORT_STEP;
 			$import_data['not_imported_lines'] = '';
 			$import_data['added'] = 0;
 			$import_data['updated'] = 0;
 
 			$file = fopen($filename, 'r');
 			// getting first line for headers
 			$headers = fgetcsv($file, 8192, $import_data['delimiter'], $import_data['enclosure']);
 			fclose($file);
 
-			$prefix_elems = preg_split('/\.|_/', $import_data['prefix'], 2);
-			$grids = $this->Application->getUnitOption($prefix_elems[0], 'Grids');
-			$grid_config = $grids[ $import_data['grid'] ]['Fields'];
+			$prefix_elements = preg_split('/\.|_/', $import_data['prefix'], 2);
+			$grid_config = $this->_getGridColumns($prefix_elements[0], $import_data['grid']);
 
 			$field_list = Array();
-			foreach($grid_config as $field_name => $field_data) {
-				if(isset($field_data['export_field'])) {
+
+			foreach ($grid_config as $field_name => $field_data) {
+				if ( isset($field_data['export_field']) ) {
 					$field_name = $field_data['export_field'];
 				}
+
 				$field_title = isset($field_data['title']) ? $field_data['title'] : 'column:la_fld_' . $field_name;
 				$field_label = $this->Application->Phrase($field_title);
 				$field_pos = array_search($field_label, $headers);
-				if($field_pos !== false) {
+
+				if ( $field_pos !== false ) {
 					$field_list[$field_pos] = $field_name;
 				}
 			}
 
-			if(!count($field_list)) return 'no_matching_columns';
+			if ( !count($field_list) ) {
+				return 'no_matching_columns';
+			}
+
 			$import_data['field_list'] = $field_list;
 
 			// getting key list
-			$field_positions = Array();
-			$config_key_list = $this->Application->getUnitOption($prefix_elems[0], 'ImportKeys');
-			if(!$config_key_list) $config_key_list = Array();
-			array_unshift($config_key_list, Array($this->Application->getUnitOption($prefix_elems[0], 'IDField')));
-
-			$key_list = Array();
-			foreach($config_key_list as $arr_key => $import_key) {
-				$key_list[$arr_key] = is_array($import_key) ? $import_key : Array($import_key);
+			$field_positions = Array ();
+			$config = $this->Application->getUnitConfig($prefix_elements[0]);
+			$config_key_list = $config->getImportKeys();
 
-				foreach($key_list[$arr_key] as $key_field) {
+			if ( !$config_key_list ) {
+				$config_key_list = Array ();
+			}
+
+			$key_list = Array ();
+			array_unshift($config_key_list, Array ($config->getIDField()));
+
+			foreach ($config_key_list as $arr_key => $import_key) {
+				$key_list[$arr_key] = is_array($import_key) ? $import_key : Array ($import_key);
+
+				foreach ($key_list[$arr_key] as $key_field) {
 					$field_positions[$key_field] = array_search($key_field, $import_data['field_list']);
-					if($field_positions[$key_field] === false) {
+					if ( $field_positions[$key_field] === false ) {
 						// no such key field combination in imported file
 						unset($key_list[$arr_key]);
 						break;
 					}
 				}
 			}
+
 			$import_data['key_list'] = $key_list;
 			$import_data['field_positions'] = $field_positions;
 
 			$this->Application->StoreVar('import_data', serialize($import_data));
 			return true;
 		}
 
+		/**
+		 * Returns columns of given grid
+		 *
+		 * @param string $prefix
+		 * @param string $grid_name
+		 * @return Array
+		 * @access protected
+		 */
+		protected function _getGridColumns($prefix, $grid_name)
+		{
+			$grid = $this->Application->getUnitConfig($prefix)->getGridByName($grid_name);
+
+			return $grid['Fields'];
+		}
+
 		function ImportStep()
 		{
 			$import_data = unserialize($this->Application->RecallVar('import_data'));
 			$prefix_elems = preg_split('/\.|_/', $import_data['prefix'], 2);
 
 			$object = $this->Application->recallObject($prefix_elems[0].'.-csvimport', $prefix_elems[0], Array('skip_autoload' => true, 'populate_ml_fields' => true));
 			/* @var $object kDBItem */
 
 			$file = fopen($import_data['file'], 'r');
 			$eof = false;
 			// skipping lines that has been already imported
 			for($i = 0; $i < $import_data['lines_processed'] + 1; $i++) {
 				if(feof($file)) break;
 				fgets($file, 8192);
 			}
 
 			$import_event = new kEvent($prefix_elems[0].'.-csvimport:OnBeforeCSVLineImport');
 
 			for($i = 0; $i < $import_data['step']; $i++) {
 				if(feof($file)) break;
 				$data = fgetcsv($file, 8192, $import_data['delimiter'], $import_data['enclosure']);
 				if(!$data) continue;
 
 				$object->Clear();
 				$action = 'Create';
 
 				// 1. trying to load object by keys
 				foreach($import_data['key_list'] as $key) {
 					$fail = false;
 					$key_array = Array();
 					foreach($key as $key_field) {
 						if(!isset($data[ $import_data['field_positions'][$key_field] ])) {
 							$fail = true;
 							break;
 						}
 						$key_array[$key_field] = $data[ $import_data['field_positions'][$key_field] ];
 					}
 					if($fail) continue;
 					if($object->Load($key_array)) {
 						$action = 'Update';
 						break;
 					}
 				}
 
 				// 2. set object fields
 				foreach($import_data['field_list'] as $position => $field_name) {
 					if(isset($data[$position])) {
 						$object->SetField($field_name, $data[$position]);
 					}
 				}
 
 				// 3. validate item and run event
 				$status = $object->Validate();
 				$import_event->status = $status ? kEvent::erSUCCESS : kEvent::erFAIL;
 				$this->Application->HandleEvent($import_event);
 
 				if($import_event->status == kEvent::erSUCCESS && $object->$action()) {
 					$import_data[ ($action == 'Create') ? 'added' : 'updated' ]++;
 				}
 				else {
 					$msg = '';
 					$errors = $object->GetFieldErrors();
 
 					foreach ($errors as $field => $info) {
 						if (!$info['pseudo']) continue;
 						$msg .= "$field: {$info['pseudo']} ";
 					}
 
 					$import_data['errors'] .= ($i + $import_data['lines_processed'] + 1).": $msg\n";
 					$import_data['not_imported_lines'] .= ','.($i + $import_data['lines_processed'] + 1);
 				}
 			}
 
 			$import_data['lines_processed'] += $import_data['step'];
 
 			$import_data['not_imported_lines'] = ltrim($import_data['not_imported_lines'], ',');
 			$this->Application->StoreVar('import_data', serialize($import_data));
 
 			$feof = feof($file);
 			fclose($file);
 
 			if($feof) {
 				$this->Application->Redirect($this->Application->GetVar('finish_template'));
 			}
 			else {
 				$percent_complete = floor($import_data['lines_processed'] / $import_data['total_lines'] * 100);
 				if($percent_complete > 99) $percent_complete = 99;
 				echo $percent_complete;
 			}
 		}
 
 		function ImportData($name)
 		{
 			$import_data = unserialize($this->Application->RecallVar('import_data'));
 			return isset($import_data[$name]) ? $import_data[$name] : false;
 		}
 
 		function GetNotImportedLines()
 		{
 			$import_data = unserialize($this->Application->RecallVar('import_data'));
 
 			if(!$import_data['not_imported_lines']) return false;
 			$line_numbers = explode(',', $import_data['not_imported_lines']);
 			$line_numbers[] = 0; // include header row in output
 
 			$file = fopen($import_data['file'], 'r');
 			$eof = false;
 			$result = '';
 			for($i = 0; $i <= max($line_numbers); $i++) {
 				if(feof($file)) break;
 				$line = fgets($file, 8192);
 				if(in_array($i, $line_numbers)) {
 					$result .= $i.':'.$line;
 				}
 			}
 			return $result."\n\n".$import_data['errors'];
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/cat_dbitem_export_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/cat_dbitem_export_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/cat_dbitem_export_helper.php	(revision 15698)
@@ -1,1573 +1,1577 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	define('EXPORT_STEP', 100); // export by 200 items (e.g. links)
 	define('IMPORT_STEP', 20); // export by 200 items (e.g. links)
 	define('IMPORT_CHUNK', 10240); // 10240); //30720); //50120); // 5 KB
 
 	define('IMPORT_TEMP', 1);
 	define('IMPORT_LIVE', 2);
 
 	class kCatDBItemExportHelper extends kHelper {
 
 		var $false = false;
 
 		var $cache = Array();
 
 		/**
 		 * Allows to find out what items are new in cache
 		 *
 		 * @var Array
 		 */
 		var $cacheStatus = Array();
 
 		var $cacheTable = '';
 
 		var $exportFields = Array();
 
 		/**
 		 * Export options
 		 *
 		 * @var Array
 		 */
 		var $exportOptions = Array();
 
 		/**
-		 * Item beeing currenly exported
+		 * Item being currently exported
 		 *
 		 * @var kCatDBItem
 		 */
 		var $curItem = null;
 
 		/**
 		 * Dummy category object
 		 *
 		 * @var CategoriesItem
 		 */
 		var $dummyCategory = null;
 
 		/**
 		 * Pointer to opened file
 		 *
 		 * @var resource
 		 */
 		var $filePointer = null;
 
 		/**
 		 * Custom fields definition of current item
 		 *
 		 * @var Array
 		 */
 		var $customFields = Array();
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			$this->cacheTable = TABLE_PREFIX.'ImportCache';
 		}
 
 		/**
 		 * Returns value from cache if found or false otherwise
 		 *
 		 * @param string $type
 		 * @param int $key
 		 * @return mixed
 		 */
 		function getFromCache($type, $key)
 		{
 			return getArrayValue($this->cache, $type, $key);
 		}
 
 		/**
 		 * Adds value to be cached
 		 *
 		 * @param string $type
 		 * @param int $key
 		 * @param mixed $value
 		 * @param bool $is_new
 		 */
 		function addToCache($type, $key, $value, $is_new = true)
 		{
 			/*if ( !isset($this->cache[$type]) ) {
 				$this->cache[$type] = Array ();
 			}*/
 
 			$this->cache[$type][$key] = $value;
 
 			if ( $is_new ) {
 				$this->cacheStatus[$type][$key] = true;
 			}
 		}
 
 		function storeCache($cache_types)
 		{
 			$fields_hash = Array ();
 			$cache_types = explode(',', $cache_types);
 
 			foreach ($cache_types as $cache_type) {
 				$fields_hash = Array ('CacheName' => $cache_type);
 				$cache = getArrayValue($this->cacheStatus, $cache_type);
 
 				if ( !$cache ) {
 					$cache = Array ();
 				}
 
 				foreach ($cache as $var_name => $cache_status) {
 					$fields_hash['VarName'] = $var_name;
 					$fields_hash['VarValue'] = $this->cache[$cache_type][$var_name];
 
 					$this->Conn->doInsert($fields_hash, $this->cacheTable, 'INSERT', false);
 				}
 			}
 
 			if ( isset($fields_hash['VarName']) ) {
 				$this->Conn->doInsert($fields_hash, $this->cacheTable, 'INSERT');
 			}
 		}
 
 		function loadCache()
 		{
 			$this->cache = Array ();
 
 			$sql = 'SELECT *
 					FROM ' . $this->cacheTable;
 			$records = $this->Conn->GetIterator($sql);
 
 			foreach ($records as $record) {
 				$this->addToCache($record['CacheName'], $record['VarName'], $record['VarValue'], false);
 			}
 		}
 
 		/**
 		 * Fill required fields with dummy values
 		 *
 		 * @param kEvent|bool $event
 		 * @param kCatDBItem|bool $object
 		 * @param bool $set_status
 		 */
 		function fillRequiredFields($event, &$object, $set_status = false)
 		{
 			if ( $object == $this->false ) {
 				$object = $event->getObject();
 				/* @var $object kCatDBItem */
 			}
 
 			$has_empty = false;
 			$fields = $object->getFields();
 
 			if ( $object->isField('CreatedById') ) {
 				// CSV file was created without required CreatedById column
 				if ( $object->isRequired('CreatedById') ) {
 					$object->setRequired('CreatedById', false);
 				}
 
 				if ( !is_numeric( $object->GetDBField('CreatedById') ) ) {
 					$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 				}
 			}
 
 			foreach ($fields as $field_name => $field_options) {
 				if ( $object->isVirtualField($field_name) || !$object->isRequired($field_name) ) {
 					continue;
 				}
 
 				if ( $object->GetDBField($field_name) ) {
 					continue;
 				}
 
 				$formatter_class = getArrayValue($field_options, 'formatter');
 
 				if ( $formatter_class ) {
 					// not tested
 					$formatter = $this->Application->recallObject($formatter_class);
 					/* @var $formatter kFormatter */
 
 					$sample_value = $formatter->GetSample($field_name, $field_options, $object);
 				}
 
 				$has_empty = true;
 				$object->SetField($field_name, isset($sample_value) && $sample_value ? $sample_value : 'no value');
 			}
 			$object->UpdateFormattersSubFields();
 
 			if ( $set_status && $has_empty ) {
 				$object->SetDBField('Status', 0);
 			}
 		}
 
 		/**
 		 * Verifies that all user entered export params are correct
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access protected
 		 */
 		protected function verifyOptions($event)
 		{
 			if ($this->Application->RecallVar($event->getPrefixSpecial().'_ForceNotValid'))
 			{
 				$this->Application->StoreVar($event->getPrefixSpecial().'_ForceNotValid', 0);
 				return false;
 			}
 
 			$this->fillRequiredFields($event, $this->false);
 
 			$object = $event->getObject();
 			/* @var $object kCatDBItem */
 
 			$cross_unique_fields = Array('FieldsSeparatedBy', 'FieldsEnclosedBy');
 			if (($object->GetDBField('CategoryFormat') == 1) || ($event->Special == 'import')) // in one field
 			{
 				$object->setRequired('CategorySeparator');
 				$cross_unique_fields[] = 'CategorySeparator';
 			}
 
 			$ret = $object->Validate();
 
 			// check if cross unique fields has no same values
 			foreach ($cross_unique_fields as $field_index => $field_name)
 			{
 				if ($object->GetErrorPseudo($field_name) == 'required') {
 					continue;
 				}
 
 				$check_fields = $cross_unique_fields;
 				unset($check_fields[$field_index]);
 
 				foreach ($check_fields as $check_field)
 				{
 					if ($object->GetDBField($field_name) == $object->GetDBField($check_field))
 					{
 						$object->SetError($check_field, 'unique');
 					}
 				}
 			}
 
 			if ($event->Special == 'import')
 			{
 				$this->exportOptions = $this->loadOptions($event);
 
 				$automatic_fields = ($object->GetDBField('FieldTitles') == 1);
 				$object->setRequired('ExportColumns', !$automatic_fields);
 				$category_prefix = '__CATEGORY__';
 				if ( $automatic_fields && ($this->exportOptions['SkipFirstRow']) ) {
 					$this->openFile($event);
 					$this->exportOptions['ExportColumns'] = $this->readRecord();
 
 					if (!$this->exportOptions['ExportColumns']) {
 						$this->exportOptions['ExportColumns'] = Array ();
 					}
 
 					$this->closeFile();
 
 					// remove additional (non-parseble columns)
 					foreach ($this->exportOptions['ExportColumns'] as $field_index => $field_name) {
 						if (!$this->validateField($field_name, $object)) {
 							unset($this->exportOptions['ExportColumns'][$field_index]);
 						}
 					}
 					$category_prefix = '';
 				}
 
 				// 1. check, that we have column definitions
 				if (!$this->exportOptions['ExportColumns']) {
 					$object->setError('ExportColumns', 'required');
 					$ret = false;
 				}
 				else {
 					// 1.1. check that all required fields are present in imported file
 					$missing_columns = Array();
 					$fields = $object->getFields();
 
 					foreach ($fields as $field_name => $field_options) {
 						if ($object->skipField($field_name)) continue;
 						if ( $object->isRequired($field_name) && !in_array($field_name, $this->exportOptions['ExportColumns']) ) {
 							$missing_columns[] = $field_name;
 							$object->setError('ExportColumns', 'required_fields_missing', 'la_error_RequiredColumnsMissing');
 							$ret = false;
 						}
 					}
 
 					if (!$ret && $this->Application->isDebugMode()) {
 						$this->Application->Debugger->appendHTML('Missing required for import/export:');
 						$this->Application->Debugger->dumpVars($missing_columns);
 					}
 				}
 
 
 				// 2. check, that we have only mixed category field or only separated category fields
 				$category_found['mixed'] = false;
 				$category_found['separated'] = false;
 
 				foreach ($this->exportOptions['ExportColumns'] as $import_field) {
 					if (preg_match('/^'.$category_prefix.'Category(Path|[0-9]+)/', $import_field, $rets)) {
 						$category_found[$rets[1] == 'Path' ? 'mixed' : 'separated'] = true;
 					}
 				}
 				if ($category_found['mixed'] && $category_found['separated']) {
 					$object->SetError('ExportColumns', 'unique_category', 'la_error_unique_category_field');
 					$ret = false;
 				}
 
 				// 3. check, that duplicates check fields are selected & present in imported fields
 				if ($this->exportOptions['ReplaceDuplicates']) {
 					if ($this->exportOptions['CheckDuplicatesMethod'] == 1) {
 						$check_fields = Array($object->IDField);
 					}
 					else {
 						$check_fields = $this->exportOptions['DuplicateCheckFields'] ? explode('|', substr($this->exportOptions['DuplicateCheckFields'], 1, -1)) : Array();
 						$object = $event->getObject();
 
 						$fields = $object->getFields();
 						$language_id = $this->Application->GetDefaultLanguageId();
 
 						foreach ($check_fields as $index => $check_field) {
 							foreach ($fields as $field_name => $field_options) {
 								if ($field_name == 'l'.$language_id.'_'.$check_field) {
 									$check_fields[$index] = 'l'.$language_id.'_'.$check_field;
 									break;
 								}
 							}
 						}
 					}
 					$this->exportOptions['DuplicateCheckFields'] = $check_fields;
 
 					if (!$check_fields) {
 						$object->setError('CheckDuplicatesMethod', 'required');
 						$ret = false;
 					}
 					else {
 						foreach ($check_fields as $check_field) {
 							$check_field = preg_replace('/^cust_(.*)/', 'Custom_\\1', $check_field);
 							if (!in_array($check_field, $this->exportOptions['ExportColumns'])) {
 								$object->setError('ExportColumns', 'required');
 								$ret = false;
 								break;
 							}
 						}
 					}
 				}
 				$this->saveOptions($event);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns filename to read import data from
 		 *
 		 * @return string
 		 */
 		function getImportFilename()
 		{
 			if ($this->exportOptions['ImportSource'] == 1)
 			{
 				$ret = $this->exportOptions['ImportFilename']; // ['name']; commented by Kostja
 			}
 			else {
 				$ret = $this->exportOptions['ImportLocalFilename'];
 			}
 			return EXPORT_PATH.'/'.$ret;
 		}
 
 		/**
 		 * Returns filename to write export data to
 		 *
 		 * @return string
 		 */
 		function getExportFilename()
 		{
 			$extension = $this->getFileExtension();
 			$filename = preg_replace('/(.*)\.' . $extension . '$/', '\1', $this->exportOptions['ExportFilename']) . '.' . $extension;
 
 			return EXPORT_PATH . DIRECTORY_SEPARATOR . $filename;
 		}
 
 		/**
 		 * Opens file required for export/import operations
 		 *
 		 * @param kEvent $event
 		 */
 		function openFile($event)
 		{
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->CheckFolder(EXPORT_PATH);
 
 			if ($event->Special == 'export') {
 				$write_mode = ($this->exportOptions['start_from'] == 0) ? 'w' : 'a';
 				$this->filePointer = fopen($this->getExportFilename(), $write_mode);
 			}
 			else {
 				$this->filePointer = fopen($this->getImportFilename(), 'r');
 			}
 
 			// skip UTF-8 BOM Modifier
 			$first_chars = fread($this->filePointer, 3);
 			if (bin2hex($first_chars) != 'efbbbf') {
 				fseek($this->filePointer, 0);
 			}
 		}
 
 		/**
 		 * Closes opened file
 		 *
 		 */
 		function closeFile()
 		{
 			fclose($this->filePointer);
 		}
 
 		function getCustomSQL()
 		{
 			$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 			/* @var $ml_formatter kMultiLanguage */
 
 			$custom_sql = '';
 
 			foreach ($this->customFields as $custom_id => $custom_name) {
 				$custom_sql .= 'custom_data.' . $ml_formatter->LangFieldName('cust_' . $custom_id) . ' AS cust_' . $custom_name . ', ';
 			}
 
 			return substr($custom_sql, 0, -2);
 		}
 
 		function getPlainExportSQL($count_only = false)
 		{
 			if ( $count_only && isset($this->exportOptions['ForceCountSQL']) ) {
 				$sql = $this->exportOptions['ForceCountSQL'];
 			}
 			elseif ( !$count_only && isset($this->exportOptions['ForceSelectSQL']) ) {
 				$sql = $this->exportOptions['ForceSelectSQL'];
 			}
 			else {
 				$items_list = $this->Application->recallObject($this->curItem->Prefix . '.export-items-list', $this->curItem->Prefix . '_List');
 				/* @var $items_list kDBList */
 
 				$items_list->SetPerPage(-1);
 
 				if ( $this->exportOptions['export_ids'] != '' ) {
 					$items_list->addFilter('export_ids', $items_list->TableName . '.' . $items_list->IDField . ' IN (' . implode(',', $this->exportOptions['export_ids']) . ')');
 				}
 
 				if ( $count_only ) {
 					$sql = $items_list->getCountSQL($items_list->GetSelectSQL(true, false));
 				}
 				else {
 					$sql = $items_list->GetSelectSQL();
 				}
 			}
 
 			if ( !$count_only ) {
 				$sql .= ' LIMIT ' . $this->exportOptions['start_from'] . ',' . EXPORT_STEP;
 			}
 			/*else {
 				$sql = preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
 			}*/
 
 			return $sql;
 		}
 
 		function getExportSQL($count_only = false)
 		{
-			if ( !$this->Application->getUnitOption($this->curItem->Prefix, 'CatalogItem') ) {
+			if ( !$this->curItem->getUnitConfig()->getCatalogItem() ) {
 				return $this->GetPlainExportSQL($count_only); // in case this is not a CategoryItem
 			}
 
 			if ( $this->exportOptions['export_ids'] === false ) {
 				// get links from current category & all it's subcategories
 				$join_clauses = Array ();
 
 				$custom_sql = $this->getCustomSQL();
 
 				if ( $custom_sql ) {
-					$custom_table = $this->Application->getUnitOption($this->curItem->Prefix . '-cdata', 'TableName');
+					$custom_table = $this->Application->getUnitConfig($this->curItem->Prefix . '-cdata')->getTableName();
 					$join_clauses[$custom_table . ' custom_data'] = 'custom_data.ResourceId = item_table.ResourceId';
 				}
 
 				$join_clauses[TABLE_PREFIX . 'CategoryItems ci'] = 'ci.ItemResourceId = item_table.ResourceId';
 				$join_clauses[TABLE_PREFIX . 'Categories c'] = 'c.CategoryId = ci.CategoryId';
 
 				$sql = 'SELECT item_table.*, ci.CategoryId' . ($custom_sql ? ', ' . $custom_sql : '') . '
 						FROM ' . $this->curItem->TableName . ' item_table';
 
 				foreach ($join_clauses as $table_name => $join_expression) {
 					$sql .= ' LEFT JOIN ' . $table_name . ' ON ' . $join_expression;
 				}
 
 				$sql .= ' WHERE ';
 
 				if ( $this->exportOptions['export_cats_ids'][0] == 0 ) {
 					$sql .= '1';
 				}
 				else {
 					foreach ($this->exportOptions['export_cats_ids'] as $category_id) {
 						$sql .= '(c.ParentPath LIKE "%|' . $category_id . '|%") OR ';
 					}
 
 					$sql = substr($sql, 0, -4);
 				}
 
 				$sql .= ' ORDER BY ci.PrimaryCat DESC'; // NEW
 			}
 			else {
 				// get only selected links
 				$sql = 'SELECT item_table.*, ' . $this->exportOptions['export_cats_ids'][0] . ' AS CategoryId
 						FROM ' . $this->curItem->TableName . ' item_table
 						WHERE ' . $this->curItem->IDField . ' IN (' . implode(',', $this->exportOptions['export_ids']) . ')';
 			}
 
 			if ( !$count_only ) {
 				$sql .= ' LIMIT ' . $this->exportOptions['start_from'] . ',' . EXPORT_STEP;
 			}
 			else {
 				$sql = preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
 			}
 
 			return $sql;
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function performExport($event)
 		{
 			$this->exportOptions = $this->loadOptions($event);
 			$this->exportFields = $this->exportOptions['ExportColumns'];
-			$this->curItem = $event->getObject( Array('skip_autoload' => true) );
-			$this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
+			$this->curItem = $event->getObject(Array ('skip_autoload' => true));
+			$this->customFields = $event->getUnitConfig()->getCustomFields();
 			$this->openFile($event);
 
-			if ($this->exportOptions['start_from'] == 0) // first export step
-			{
-				if (!getArrayValue($this->exportOptions, 'IsBaseCategory')) {
+			if ( $this->exportOptions['start_from'] == 0 ) {
+				// first export step
+				if ( !getArrayValue($this->exportOptions, 'IsBaseCategory') ) {
 					$this->exportOptions['IsBaseCategory'] = 0;
 				}
 
-				if ($this->exportOptions['IsBaseCategory'] ) {
+				if ( $this->exportOptions['IsBaseCategory'] ) {
 					$sql = 'SELECT ParentPath
-							FROM '.TABLE_PREFIX.'Categories
+							FROM ' . TABLE_PREFIX . 'Categories
 							WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id');
 					$parent_path = $this->Conn->GetOne($sql);
 					$parent_path = explode('|', substr($parent_path, 1, -1));
-					if ($parent_path && $parent_path[0] == $this->Application->getBaseCategory()) {
+
+					if ( $parent_path && $parent_path[0] == $this->Application->getBaseCategory() ) {
 						array_shift($parent_path);
 					}
 
 					$this->exportOptions['BaseLevel'] = count($parent_path); // level to cut from other categories
 				}
 
 				// 1. export field titles if required
-				if ($this->exportOptions['IncludeFieldTitles'])
-				{
-					$data_array = Array();
-					foreach ($this->exportFields as $export_field)
-					{
+				if ( $this->exportOptions['IncludeFieldTitles'] ) {
+					$data_array = Array ();
+					foreach ($this->exportFields as $export_field) {
 						$data_array = array_merge($data_array, $this->getFieldCaption($export_field));
 					}
 					$this->writeRecord($data_array);
 				}
-				$this->exportOptions['total_records'] = $this->Conn->GetOne( $this->getExportSQL(true) );
+
+				$this->exportOptions['total_records'] = $this->Conn->GetOne($this->getExportSQL(true));
 			}
 
 			// 2. export data
 			$records = $this->Conn->Query( $this->getExportSQL() );
 			$records_exported = 0;
 			foreach ($records as $record_info) {
 				$this->curItem->LoadFromHash($record_info);
 
 				$data_array = Array();
 				foreach ($this->exportFields as $export_field)
 				{
 					$data_array = array_merge($data_array, $this->getFieldValue($export_field) );
 				}
 				$this->writeRecord($data_array);
 				$records_exported++;
 			}
 			$this->closeFile();
 
 			$this->exportOptions['start_from'] += $records_exported;
 			$this->saveOptions($event);
 
 			return $this->exportOptions;
 		}
 
 		function getItemFields()
 		{
 			// just in case dummy user selected automtic mode & moved columns too :(
 			$src_options = $this->curItem->GetFieldOption('ExportColumns', 'options');
 			$dst_options = $this->curItem->GetFieldOption('AvailableColumns', 'options');
 
 			return array_merge($dst_options, $src_options);
 		}
 
 		/**
 		 * Checks if field really belongs to importable field list
 		 *
 		 * @param string $field_name
 		 * @param kCatDBItem $object
 		 * @return bool
 		 */
 		function validateField($field_name, &$object)
 		{
 			// 1. convert custom field
 			$field_name = preg_replace('/^Custom_(.*)/', '__CUSTOM__\\1', $field_name);
 
 			// 2. convert category field (mixed version & separated version)
 			$field_name = preg_replace('/^Category(Path|[0-9]+)/', '__CATEGORY__Category\\1', $field_name);
 
 			$valid_fields = $object->getPossibleExportColumns();
 			return isset($valid_fields[$field_name]) || isset($valid_fields['__VIRTUAL__'.$field_name]);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function performImport($event)
 		{
 			if (!$this->exportOptions) {
 				// load import options in case if not previously loaded in verification function
 				$this->exportOptions = $this->loadOptions($event);
 			}
 
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 			$this->Application->SetVar('m_cat_id', (int)$this->Application->RecallVar('ImportCategory') );
 
 			$this->openFile($event);
 
 			$bytes_imported = 0;
 			if ($this->exportOptions['start_from'] == 0) // first export step
 			{
 				// 1st time run
 				if ($this->exportOptions['SkipFirstRow']) {
 					$this->readRecord();
 					$this->exportOptions['start_from'] = ftell($this->filePointer);
 					$bytes_imported = ftell($this->filePointer);
 				}
 
 				$current_category_id = $this->Application->GetVar('m_cat_id');
 				if ($current_category_id > 0) {
 					$sql = 'SELECT ParentPath FROM '.TABLE_PREFIX.'Categories WHERE CategoryId = '.$current_category_id;
 					$this->exportOptions['ImportCategoryPath'] = $this->Conn->GetOne($sql);
 				}
 				else {
 					$this->exportOptions['ImportCategoryPath'] = '';
 				}
 				$this->exportOptions['total_records'] = filesize($this->getImportFilename());
 			}
 			else {
 				$this->loadCache();
 			}
 
 			$this->exportFields = $this->exportOptions['ExportColumns'];
 			$this->addToCache('category_parent_path', $this->Application->GetVar('m_cat_id'), $this->exportOptions['ImportCategoryPath']);
 
 			// 2. import data
 			$this->dummyCategory = $this->Application->recallObject('c.-tmpitem', 'c', Array('skip_autoload' => true));
 			fseek($this->filePointer, $this->exportOptions['start_from']);
 
 			$items_processed = 0;
 			while (($bytes_imported < IMPORT_CHUNK && $items_processed < IMPORT_STEP) && !feof($this->filePointer)) {
 				$data = $this->readRecord();
 				if ($data) {
 					if ($this->exportOptions['ReplaceDuplicates']) {
 						// set fields used as keys for replace duplicates code
 						$this->resetImportObject($event, IMPORT_TEMP, $data);
 					}
 
 					$this->processCurrentItem($event, $data);
 				}
 				$bytes_imported = ftell($this->filePointer) - $this->exportOptions['start_from'];
 				$items_processed++;
 			}
 
 			$this->closeFile();
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 
 			$this->exportOptions['start_from'] += $bytes_imported;
 			$this->storeCache('new_ids');
 
 			$this->saveOptions($event);
 
 			if ($this->exportOptions['start_from'] == $this->exportOptions['total_records']) {
 				$this->Conn->Query('TRUNCATE TABLE '.$this->cacheTable);
 			}
 
 			return $this->exportOptions;
 		}
 
 		function setCurrentID()
 		{
 			$this->curItem->setID( $this->curItem->GetDBField($this->curItem->IDField) );
 		}
 
 		/**
 		 * Sets value of import/export object
 		 * @param int $field_index
 		 * @param mixed $value
 		 * @return void
 		 * @access protected
 		 */
 		protected function setFieldValue($field_index, $value)
 		{
 			if ( empty($value) ) {
 				$value = null;
 			}
 
 			$field_name = getArrayValue($this->exportFields, $field_index);
 			if ( $field_name == 'ResourceId' ) {
 				return ;
 			}
 
 			if ( substr($field_name, 0, 7) == 'Custom_' ) {
 				$field_name = 'cust_' . substr($field_name, 7);
 				$this->curItem->SetField($field_name, $value);
 			}
 			elseif ( $field_name == 'CategoryPath' || $field_name == '__CATEGORY__CategoryPath' ) {
 				$this->curItem->CategoryPath = $value ? explode($this->exportOptions['CategorySeparator'], $value) : Array ();
 			}
 			elseif ( substr($field_name, 0, 8) == 'Category' ) {
 				$this->curItem->CategoryPath[(int)substr($field_name, 8) - 1] = $value;
 			}
 			elseif ( substr($field_name, 0, 20) == '__CATEGORY__Category' ) {
 				$this->curItem->CategoryPath[(int)substr($field_name, 20) - 1] = $value;
 			}
 			elseif ( substr($field_name, 0, 11) == '__VIRTUAL__' ) {
 				$field_name = substr($field_name, 11);
 				$this->curItem->SetField($field_name, $value);
 			}
 			else {
 				$this->curItem->SetField($field_name, $value);
 			}
 
 			if ( $this->curItem->GetErrorPseudo($field_name) ) {
 				$this->curItem->SetDBField($field_name, null);
 				$this->curItem->RemoveError($field_name);
 			}
 		}
 
 		/**
 		 * Resets import object
 		 *
 		 * @param kEvent $event
 		 * @param int $object_type
 		 * @param Array $record_data
 		 * @return void
 		 */
 		function resetImportObject($event, $object_type, $record_data = null)
 		{
 			switch ($object_type) {
 				case IMPORT_TEMP:
 					$this->curItem = $event->getObject( Array('skip_autoload' => true) );
 					break;
 
 				case IMPORT_LIVE:
 					$this->curItem = $this->Application->recallObject($event->Prefix.'.-tmpitem'.$event->Special, $event->Prefix, Array('skip_autoload' => true));
 					break;
 			}
 			$this->curItem->Clear();
 			$this->curItem->SetDBField('CategoryId', NULL); // since default value is import root category
-			$this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
+			$this->customFields = $event->getUnitConfig()->getCustomFields();
 
 			if (isset($record_data)) {
 				$this->setImportData($record_data);
 			}
 		}
 
 		function setImportData($record_data)
 		{
 			foreach ($record_data as $field_index => $field_value) {
 				$this->setFieldValue($field_index, $field_value);
 			}
 			$this->setCurrentID();
 		}
 
 
 		function getItemCategory()
 		{
 			static $lang_prefix = null;
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			$category_id = $this->getFromCache('category_names', implode(':', $this->curItem->CategoryPath));
 			if ($category_id) {
 				$this->Application->SetVar('m_cat_id', $category_id);
 				return $category_id;
 			}
 
 			if (is_null($lang_prefix)) {
 				$lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_';
 			}
 
 			foreach ($this->curItem->CategoryPath as $category_index => $category_name) {
 				if (!$category_name) continue;
 				$category_key = kUtil::crc32( implode(':', array_slice($this->curItem->CategoryPath, 0, $category_index + 1) ) );
 
 				$category_id = $this->getFromCache('category_names', $category_key);
 				if ($category_id === false) {
 					// get parent category path to search only in it
 					$current_category_id = $this->Application->GetVar('m_cat_id');
 //					$parent_path = $this->getParentPath($current_category_id);
 
 					// get category id from database by name
 					$sql = 'SELECT CategoryId
 							FROM '.TABLE_PREFIX.'Categories
 							WHERE ('.$lang_prefix.'Name = '.$this->Conn->qstr($category_name).') AND (ParentId = '.(int)$current_category_id.')';
 					$category_id = $this->Conn->GetOne($sql);
 
 					if ( $category_id === false ) {
 						// category not in db -> create
 						$category_fields = Array (
 							$lang_prefix.'Name' => $category_name, $lang_prefix.'Description' => $category_name,
 							'Status' => STATUS_ACTIVE, 'ParentId' => $current_category_id, 'AutomaticFilename' => 1
 						);
 
 						$this->dummyCategory->Clear();
 						$this->dummyCategory->SetDBFieldsFromHash($category_fields);
 
 						if ( $this->dummyCategory->Create() ) {
 							$category_id = $this->dummyCategory->GetID();
 							$this->addToCache('category_parent_path', $category_id, $this->dummyCategory->GetDBField('ParentPath'));
 							$this->addToCache('category_names', $category_key, $category_id);
 						}
 					}
 					else {
 						$this->addToCache('category_names', $category_key, $category_id);
 					}
 				}
 
 				if ($category_id) {
 					$this->Application->SetVar('m_cat_id', $category_id);
 				}
 			}
 			if (!$this->curItem->CategoryPath) {
 				$category_id = $backup_category_id;
 			}
 
 			return $category_id;
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 * @param Array $record_data
 		 * @return bool
 		 */
 		function processCurrentItem($event, $record_data)
 		{
 			$save_method = 'Create';
-			$load_keys = Array();
+			$load_keys = Array ();
 
 			// create/update categories
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			// perform replace duplicates code
-			if ($this->exportOptions['ReplaceDuplicates']) {
+			if ( $this->exportOptions['ReplaceDuplicates'] ) {
 				// get replace keys first, then reset current item to empty one
 				$category_id = $this->getItemCategory();
-				if ($this->exportOptions['CheckDuplicatesMethod'] == 1) {
-					if ($this->curItem->GetID()) {
-						$load_keys = Array($this->curItem->IDField => $this->curItem->GetID());
+
+				if ( $this->exportOptions['CheckDuplicatesMethod'] == 1 ) {
+					if ( $this->curItem->GetID() ) {
+						$load_keys = Array ($this->curItem->IDField => $this->curItem->GetID());
 					}
 				}
 				else {
 					$key_fields = $this->exportOptions['DuplicateCheckFields'];
+
 					foreach ($key_fields as $key_field) {
 						$load_keys[$key_field] = $this->curItem->GetDBField($key_field);
 					}
 				}
 
 				$this->resetImportObject($event, IMPORT_LIVE);
 
-				if (count($load_keys)) {
+				if ( count($load_keys) ) {
 					$where_clause = '';
 					$language_id = (int)$this->Application->GetVar('m_lang');
 
-					if (!$language_id) {
+					if ( !$language_id ) {
 						$language_id = 1;
 					}
 
 					foreach ($load_keys as $field_name => $field_value) {
-						if (preg_match('/^cust_(.*)/', $field_name, $regs)) {
+						if ( preg_match('/^cust_(.*)/', $field_name, $regs) ) {
 							$custom_id = array_search($regs[1], $this->customFields);
-							$field_name = 'l'.$language_id.'_cust_'.$custom_id;
-							$where_clause .= '(custom_data.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND ';
+							$field_name = 'l' . $language_id . '_cust_' . $custom_id;
+							$where_clause .= '(custom_data.`' . $field_name . '` = ' . $this->Conn->qstr($field_value) . ') AND ';
 						}
 						else {
-							$where_clause .= '(item_table.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND ';
+							$where_clause .= '(item_table.`' . $field_name . '` = ' . $this->Conn->qstr($field_value) . ') AND ';
 						}
-
 					}
 					$where_clause = substr($where_clause, 0, -5);
 
 					$item_id = $this->getFromCache('new_ids', kUtil::crc32($where_clause));
-					if (!$item_id) {
-						if ($this->exportOptions['CheckDuplicatesMethod'] == 2) {
+
+					if ( !$item_id ) {
+						if ( $this->exportOptions['CheckDuplicatesMethod'] == 2 ) {
 							// by other fields
 							$parent_path = $this->getParentPath($category_id);
-							$where_clause = '(c.ParentPath LIKE "'.$parent_path.'%") AND '.$where_clause;
+							$where_clause = '(c.ParentPath LIKE "' . $parent_path . '%") AND ' . $where_clause;
 						}
 
-						$cdata_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
-						$sql = 'SELECT '.$this->curItem->IDField.'
-								FROM '.$this->curItem->TableName.' item_table
-								LEFT JOIN '.$cdata_table.' custom_data ON custom_data.ResourceId = item_table.ResourceId
-								LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON ci.ItemResourceId = item_table.ResourceId
-								LEFT JOIN '.TABLE_PREFIX.'Categories c ON c.CategoryId = ci.CategoryId
-								WHERE '.$where_clause;
+						$cdata_table = $this->Application->getUnitConfig($event->Prefix . '-cdata')->getTableName();
+
+						$sql = 'SELECT ' . $this->curItem->IDField . '
+								FROM ' . $this->curItem->TableName . ' item_table
+								LEFT JOIN ' . $cdata_table . ' custom_data ON custom_data.ResourceId = item_table.ResourceId
+								LEFT JOIN ' . TABLE_PREFIX . 'CategoryItems ci ON ci.ItemResourceId = item_table.ResourceId
+								LEFT JOIN ' . TABLE_PREFIX . 'Categories c ON c.CategoryId = ci.CategoryId
+								WHERE ' . $where_clause;
 						$item_id = $this->Conn->GetOne($sql);
 					}
+
 					$save_method = $item_id && $this->curItem->Load($item_id) ? 'Update' : 'Create';
-					if ($save_method == 'Update') {
+
+					if ( $save_method == 'Update' ) {
 						// replace id from csv file with found id (only when ID is found in cvs file)
-						if (in_array($this->curItem->IDField, $this->exportFields)) {
-							$record_data[ array_search($this->curItem->IDField, $this->exportFields) ] = $item_id;
+						if ( in_array($this->curItem->IDField, $this->exportFields) ) {
+							$record_data[array_search($this->curItem->IDField, $this->exportFields)] = $item_id;
 						}
 					}
 				}
 
 				$this->setImportData($record_data);
 			}
 			else {
 				$this->resetImportObject($event, IMPORT_LIVE, $record_data);
 				$category_id = $this->getItemCategory();
 			}
 
 			// create main record
-			if ($save_method == 'Create') {
+			if ( $save_method == 'Create' ) {
 				$this->fillRequiredFields($this->false, $this->curItem, true);
 			}
 
 //			$sql_start = microtime(true);
-			if (!$this->curItem->$save_method()) {
+			if ( !$this->curItem->$save_method() ) {
 				$this->Application->SetVar('m_cat_id', $backup_category_id);
+
 				return false;
 			}
 //			$sql_end = microtime(true);
 //			$this->saveLog('SQL ['.$save_method.'] Time: '.($sql_end - $sql_start).'s');
 
-			if ($load_keys && ($save_method == 'Create') && $this->exportOptions['ReplaceDuplicates']) {
+			if ( $load_keys && ($save_method == 'Create') && $this->exportOptions['ReplaceDuplicates'] ) {
 				// map new id to old id
-				$this->addToCache('new_ids', kUtil::crc32($where_clause), $this->curItem->GetID() );
+				$this->addToCache('new_ids', kUtil::crc32($where_clause), $this->curItem->GetID());
 			}
 
 			// assign item to categories
 			$this->curItem->assignToCategory($category_id, false);
 
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
+
 			return true;
 		}
 
 		/*function saveLog($msg)
 		{
 			static $first_time = true;
 
 			$fp = fopen((defined('RESTRICTED') ? RESTRICTED : FULL_PATH) . '/sqls.log', $first_time ? 'w' : 'a');
 			fwrite($fp, $msg."\n");
 			fclose($fp);
 
 			$first_time = false;
 		}*/
 
 		/**
 		 * Returns category parent path, if possible, then from cache
 		 *
 		 * @param int $category_id
 		 * @return string
 		 */
 		function getParentPath($category_id)
 		{
 			$parent_path = $this->getFromCache('category_parent_path', $category_id);
 			if ($parent_path === false) {
 				$sql = 'SELECT ParentPath
 						FROM '.TABLE_PREFIX.'Categories
 						WHERE CategoryId = '.$category_id;
 				$parent_path = $this->Conn->GetOne($sql);
 				$this->addToCache('category_parent_path', $category_id, $parent_path);
 			}
 			return $parent_path;
 		}
 
 		function getFileExtension()
 		{
 			return $this->exportOptions['ExportFormat'] == 1 ? 'csv' : 'xml';
 		}
 
 		function getLineSeparator($option = 'LineEndings')
 		{
 			return $this->exportOptions[$option] == 1 ? "\r\n" : "\n";
 		}
 
 		/**
 		 * Returns field caption for any exported field
 		 *
 		 * @param string $field
 		 * @return string
 		 */
 		function getFieldCaption($field)
 		{
 			if (substr($field, 0, 10) == '__CUSTOM__')
 			{
 				$ret = 'Custom_'.substr($field, 10, strlen($field) );
 			}
 			elseif (substr($field, 0, 12) == '__CATEGORY__')
 			{
 				return $this->getCategoryTitle();
 			}
 			elseif (substr($field, 0, 11) == '__VIRTUAL__') {
 				$ret = substr($field, 11);
 			}
 			else
 			{
 				$ret = $field;
 			}
 
 			return Array($ret);
 		}
 
 		/**
 		 * Returns requested field value (including custom fields and category fields)
 		 *
 		 * @param string $field
 		 * @return string
 		 */
 		function getFieldValue($field)
 		{
 			if (substr($field, 0, 10) == '__CUSTOM__') {
 				$field = 'cust_'.substr($field, 10, strlen($field));
 				$ret = $this->curItem->GetField($field);
 			}
 			elseif (substr($field, 0, 12) == '__CATEGORY__') {
 				return $this->getCategoryPath();
 			}
 			elseif (substr($field, 0, 11) == '__VIRTUAL__') {
 				$field = substr($field, 11);
 				$ret = $this->curItem->GetField($field);
 			}
 			else
 			{
 				$ret = $this->curItem->GetField($field);
 			}
 
 			$ret = str_replace("\r\n", $this->getLineSeparator('LineEndingsInside'), $ret);
 			return Array($ret);
 		}
 
 		/**
 		 * Returns category field(-s) caption based on export mode
 		 *
 		 * @return string
 		 */
 		function getCategoryTitle()
 		{
 			// category path in separated fields
 			$category_count = $this->getMaxCategoryLevel();
 			if ($this->exportOptions['CategoryFormat'] == 1)
 			{
 				// category path in one field
 				return $category_count ? Array('CategoryPath') : Array();
 			}
 			else
 			{
 				$i = 0;
 				$ret = Array();
 				while ($i < $category_count) {
 					$ret[] = 'Category'.($i + 1);
 					$i++;
 				}
 				return $ret;
 			}
 		}
 
 		/**
 		 * Returns category path in required format for current link
 		 *
 		 * @return string
 		 */
 		function getCategoryPath()
 		{
 			$category_id = $this->curItem->GetDBField('CategoryId');
 			$category_path = $this->getFromCache('category_path', $category_id);
 
 			if ( !$category_path ) {
 				$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 				/* @var $ml_formatter kMultiLanguage */
 
 				$sql = 'SELECT ' . $ml_formatter->LangFieldName('CachedNavbar') . '
 						FROM ' . TABLE_PREFIX . 'Categories
 						WHERE CategoryId = ' . $category_id;
 				$category_path = $this->Conn->GetOne($sql);
 
 				$category_path = $category_path ? explode('&|&', $category_path) : Array ();
 
 				if ( $category_path && strtolower($category_path[0]) == 'content' ) {
 					array_shift($category_path);
 				}
 
 				if ( $this->exportOptions['IsBaseCategory'] ) {
 					$i = $this->exportOptions['BaseLevel'];
 					while ( $i > 0 ) {
 						array_shift($category_path);
 						$i--;
 					}
 				}
 
 				$category_count = $this->getMaxCategoryLevel();
 
 				if ( $this->exportOptions['CategoryFormat'] == 1 ) {
 					// category path in single field
 					$category_path = $category_count ? Array (implode($this->exportOptions['CategorySeparator'], $category_path)) : Array ();
 				}
 				else {
 					// category path in separated fields
 					$levels_used = count($category_path);
 
 					if ( $levels_used < $category_count ) {
 						$i = 0;
 						while ( $i < $category_count - $levels_used ) {
 							$category_path[] = '';
 							$i++;
 						}
 					}
 				}
 				$this->addToCache('category_path', $category_id, $category_path);
 			}
 
 			return $category_path;
 		}
 
 		/**
 		 * Get maximal category deep level from links beeing exported
 		 *
 		 * @return int
 		 */
 		function getMaxCategoryLevel()
 		{
 			static $max_level = -1;
 
 			if ($max_level != -1)
 			{
 				return $max_level;
 			}
 
 			$sql = 'SELECT IF(c.CategoryId IS NULL, 0, MAX( LENGTH(c.ParentPath) - LENGTH( REPLACE(c.ParentPath, "|", "") ) - 1 ))
 					FROM '.$this->curItem->TableName.' item_table
 					LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON item_table.ResourceId = ci.ItemResourceId
 					LEFT JOIN '.TABLE_PREFIX.'Categories c ON c.CategoryId = ci.CategoryId
 					WHERE (ci.PrimaryCat = 1) AND ';
 
 			$where_clause = '';
 			if ($this->exportOptions['export_ids'] === false) {
 				// get links from current category & all it's subcategories
 				if ($this->exportOptions['export_cats_ids'][0] == 0) {
 					$where_clause = 1;
 				}
 				else {
 					foreach ($this->exportOptions['export_cats_ids'] as $category_id) {
 						$where_clause .= '(c.ParentPath LIKE "%|'.$category_id.'|%") OR ';
 					}
 					$where_clause = substr($where_clause, 0, -4);
 				}
 			}
 			else {
 				// get only selected links
 				$where_clause = $this->curItem->IDField.' IN ('.implode(',', $this->exportOptions['export_ids']).')';
 			}
 
 			$max_level = $this->Conn->GetOne($sql.'('.$where_clause.')');
 
 			if ($this->exportOptions['IsBaseCategory'] ) {
 				$max_level -= $this->exportOptions['BaseLevel'];
 			}
 
 			return $max_level;
 		}
 
 		/**
 		 * Saves one record to export file
 		 *
 		 * @param Array $fields_hash
 		 */
 		function writeRecord($fields_hash)
 		{
 			kUtil::fputcsv($this->filePointer, $fields_hash, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy'], $this->getLineSeparator() );
 		}
 
 		function readRecord()
 		{
 			return fgetcsv($this->filePointer, 10000, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy']);
 		}
 
 		/**
 		 * Saves import/export options
 		 *
 		 * @param kEvent $event
 		 * @param Array $options
 		 * @return void
 		 */
 		function saveOptions($event, $options = null)
 		{
 			if ( !isset($options) ) {
 				$options = $this->exportOptions;
 			}
 
 			$this->Application->StoreVar($event->getPrefixSpecial() . '_options', serialize($options));
 		}
 
 		/**
 		 * Loads import/export options
 		 *
 		 * @param kEvent $event
 		 * @return Array
 		 */
 		function loadOptions($event)
 		{
 			return unserialize( $this->Application->RecallVar($event->getPrefixSpecial() . '_options') );
 		}
 
 		/**
 		 * Sets correct available & export fields
 		 *
 		 * @param kEvent $event
 		 */
 		function prepareExportColumns($event)
 		{
-			$object = $event->getObject( Array('skip_autoload' => true) );
+			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kCatDBItem */
 
 			if ( !$object->isField('ExportColumns') ) {
 				// import/export prefix was used (see kDBEventHandler::prepareObject) but object don't plan to be imported/exported
-				return ;
+				return;
 			}
 
-			$available_columns = Array();
+			$available_columns = Array ();
 
-			if ($this->Application->getUnitOption($event->Prefix, 'CatalogItem')) {
+			if ( $event->getUnitConfig()->getCatalogItem() ) {
 				// category field (mixed)
 				$available_columns['__CATEGORY__CategoryPath'] = 'CategoryPath';
 
-				if ($event->Special == 'import') {
+				if ( $event->Special == 'import' ) {
 					// category field (separated fields)
 					$max_level = $this->Application->ConfigValue('MaxImportCategoryLevels');
 					$i = 0;
-					while ($i < $max_level) {
-						$available_columns['__CATEGORY__Category'.($i + 1)] = 'Category'.($i + 1);
+					while ( $i < $max_level ) {
+						$available_columns['__CATEGORY__Category' . ($i + 1)] = 'Category' . ($i + 1);
 						$i++;
 					}
 				}
 			}
 
 			// db fields
 			$fields = $object->getFields();
 
 			foreach ($fields as $field_name => $field_options) {
 				if ( !$object->skipField($field_name) ) {
-					$available_columns[$field_name] = $field_name.( $object->isRequired($field_name) ? '*' : '');
+					$available_columns[$field_name] = $field_name . ($object->isRequired($field_name) ? '*' : '');
 				}
 			}
 
-			$handler = $this->Application->recallObject($event->Prefix.'_EventHandler');
+			$handler = $this->Application->recallObject($event->Prefix . '_EventHandler');
 			/* @var $handler kDBEventHandler */
 
 			$available_columns = array_merge($available_columns, $handler->getCustomExportColumns($event));
 
 			// custom fields
 			$custom_fields = $object->getCustomFields();
 
-			foreach ($custom_fields as $custom_id => $custom_name)
-			{
-				$available_columns['__CUSTOM__'.$custom_name] = $custom_name;
+			foreach ($custom_fields as $custom_id => $custom_name) {
+				$available_columns['__CUSTOM__' . $custom_name] = $custom_name;
 			}
 
 			// columns already in use
-			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
-			if ($items_info)
-			{
+			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
+			if ( $items_info ) {
 				list($item_id, $field_values) = each($items_info);
 				$export_keys = $field_values['ExportColumns'];
-				$export_keys = $export_keys ? explode('|', substr($export_keys, 1, -1) ) : Array();
+				$export_keys = $export_keys ? explode('|', substr($export_keys, 1, -1)) : Array ();
 			}
 			else {
-				$export_keys = Array();
+				$export_keys = Array ();
 			}
 
-			$export_columns = Array();
-			foreach ($export_keys as $field_key)
-			{
+			$export_columns = Array ();
+			foreach ($export_keys as $field_key) {
 				$field_name = $this->getExportField($field_key);
 				$export_columns[$field_key] = $field_name;
 				unset($available_columns[$field_key]);
 			}
 
 			$options = $object->GetFieldOptions('ExportColumns');
 			$options['options'] = $export_columns;
 			$object->SetFieldOptions('ExportColumns', $options);
 
 			$options = $object->GetFieldOptions('AvailableColumns');
 			$options['options'] = $available_columns;
 			$object->SetFieldOptions('AvailableColumns', $options);
 
 			$this->updateImportFiles($event);
 			$this->PrepareExportPresets($event);
 		}
 
 		/**
 		 * Prepares export presets
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 */
 		function PrepareExportPresets($event)
 		{
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$options = $object->GetFieldOptions('ExportPresets');
 			$export_settings = $this->Application->RecallPersistentVar('export_settings');
 
 			if ( !$export_settings ) {
 				return;
 			}
 
 			$export_settings = unserialize($export_settings);
 
 			if ( !isset($export_settings[$event->Prefix]) ) {
 				return;
 			}
 
 			$export_presets = array ('' => '');
 
 			foreach ($export_settings[$event->Prefix] as $key => $val) {
 				$export_presets[implode('|', $val['ExportColumns'])] = $key;
 			}
 
 			$options['options'] = $export_presets;
 			$object->SetFieldOptions('ExportPresets', $options);
 		}
 
 		function getExportField($field_key)
 		{
 			$prepends = Array('__CUSTOM__', '__CATEGORY__');
 			foreach ($prepends as $prepend)
 			{
 				if (substr($field_key, 0, strlen($prepend) ) == $prepend)
 				{
 					$field_key = substr($field_key, strlen($prepend), strlen($field_key) );
 					break;
 				}
 			}
 			return $field_key;
 		}
 
 		/**
 		 * Updates uploaded files list
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function updateImportFiles($event)
 		{
 			if ( $event->Special != 'import' ) {
 				return ;
 			}
 
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$import_filenames = Array ();
 			$file_helper->CheckFolder(EXPORT_PATH);
 
 			$iterator = new DirectoryIterator(EXPORT_PATH);
 			/* @var $file_info DirectoryIterator */
 
 			foreach ($iterator as $file_info) {
 				$file = $file_info->getFilename();
 
 				if ( $file_info->isDir() || $file == 'dummy' || $file_info->getSize() == 0 ) {
 					continue;
 				}
 
 				$import_filenames[$file] = $file . ' (' . kUtil::formatSize( $file_info->getSize() ) . ')';
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetFieldOption('ImportLocalFilename', 'options', $import_filenames);
 		}
 
 		/**
 		 * Returns module folder
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
 		function getModuleName($event)
 		{
-			$module_path = $this->Application->getUnitOption($event->Prefix, 'ModuleFolder') . '/';
+			$module_path = $event->getUnitConfig()->getModuleFolder() . '/';
 			$module_name = $this->Application->findModule('Path', $module_path, 'Name');
 
 			return mb_strtolower($module_name);
 		}
 
 		/**
 		 * Export form validation & processing
 		 *
 		 * @param kEvent $event
 		 */
 		function OnExportBegin($event)
 		{
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 			if ( !$items_info ) {
 				$items_info = unserialize($this->Application->RecallVar($event->getPrefixSpecial() . '_ItemsInfo'));
 				$this->Application->SetVar($event->getPrefixSpecial(true), $items_info);
 			}
 
 			list($item_id, $field_values) = each($items_info);
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$object->SetFieldsFromHash($field_values);
 			$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
 
 			$object->setID($item_id);
 			$this->setRequiredFields($event);
 
 			// save export/import options
 			if ( $event->Special == 'export' ) {
 				$export_ids = $this->Application->RecallVar($event->Prefix . '_export_ids');
 				$export_cats_ids = $this->Application->RecallVar($event->Prefix . '_export_cats_ids');
 
 				// used for multistep export
 				$field_values['export_ids'] = $export_ids ? explode(',', $export_ids) : false;
 				$field_values['export_cats_ids'] = $export_cats_ids ? explode(',', $export_cats_ids) : Array ($this->Application->GetVar('m_cat_id'));
 			}
 
 			$field_values['ExportColumns'] = $field_values['ExportColumns'] ? explode('|', substr($field_values['ExportColumns'], 1, -1) ) : Array();
 			$field_values['start_from'] = 0;
 
 			$nevent = new kEvent($event->Prefix . ':OnBeforeExportBegin');
 			$nevent->setEventParam('options', $field_values);
 			$this->Application->HandleEvent($nevent);
 			$field_values = $nevent->getEventParam('options');
 
 			$this->saveOptions($event, $field_values);
 
 			if ( $this->verifyOptions($event) ) {
 				if ( $this->_getExportSavePreset($object) ) {
 					$name = $object->GetDBField('ExportPresetName');
 
 					$export_settings = $this->Application->RecallPersistentVar('export_settings');
 					$export_settings = $export_settings ? unserialize($export_settings) : array ();
 					$export_settings[$event->Prefix][$name] = $field_values;
 					$this->Application->StorePersistentVar('export_settings', serialize($export_settings));
 				}
 
 				$progress_t = $this->Application->RecallVar('export_progress_t');
 				if ( $progress_t ) {
 					$this->Application->RemoveVar('export_progress_t');
 				}
 				else {
 					$progress_t = $this->getModuleName($event) . '/' . $event->Special . '_progress';
 				}
 				$event->redirect = $progress_t;
 
 				if ( $event->Special == 'import' ) {
 					$import_category = (int)$this->Application->RecallVar('ImportCategory');
 
 					// in future could use module root category if import category will be unavailable :)
 					$event->SetRedirectParam('m_cat_id', $import_category); // for template permission checking
 					$this->Application->StoreVar('m_cat_id', $import_category); // for event permission checking
 				}
 			}
 			else {
 				// make uploaded file local & change source selection
 				$filename = getArrayValue($field_values, 'ImportFilename');
 
 				if ( $filename ) {
 					$this->updateImportFiles($event);
 					$object->SetDBField('ImportSource', 2);
 					$field_values['ImportSource'] = 2;
 					$object->SetDBField('ImportLocalFilename', $filename);
 					$field_values['ImportLocalFilename'] = $filename;
 					$this->saveOptions($event, $field_values);
 				}
 
 				$event->status = kEvent::erFAIL;
 				$event->redirect = false;
 			}
 		}
 
 		/**
 		 * Returns export save preset name, when used at all
 		 *
 		 * @param kDBItem $object
 		 * @return string
 		 */
 		function _getExportSavePreset(&$object)
 		{
 			if ( !$object->isField('ExportSavePreset') ) {
 				return '';
 			}
 
 			return $object->GetDBField('ExportSavePreset');
 		}
 
 		/**
 		 * set required fields based on import or export params
 		 *
 		 * @param kEvent $event
 		 */
 		function setRequiredFields($event)
 		{
 			$required_fields['common'] = Array('FieldsSeparatedBy', 'LineEndings', 'CategoryFormat');
 
 			$required_fields['export'] = Array('ExportFormat', 'ExportFilename','ExportColumns');
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ($this->_getExportSavePreset($object)) {
 				$required_fields['export'][] = 'ExportPresetName';
 			}
 
 			$required_fields['import'] = Array('FieldTitles', 'ImportSource', 'CheckDuplicatesMethod'); // ImportFilename, ImportLocalFilename
 
 			if ($event->Special == 'import')
 			{
 				$import_source = Array(1 => 'ImportFilename', 2 => 'ImportLocalFilename');
 				$used_field = $import_source[ $object->GetDBField('ImportSource') ];
 
 				$required_fields[$event->Special][] = $used_field;
 				$object->SetFieldOption($used_field, 'error_field', 'ImportSource');
 
 				if ($object->GetDBField('FieldTitles') == 2) $required_fields[$event->Special][] = 'ExportColumns'; // manual field titles
 			}
 
 			$required_fields = array_merge($required_fields['common'], $required_fields[$event->Special]);
 			$object->setRequired($required_fields);
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/multilanguage_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/multilanguage_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/multilanguage_helper.php	(revision 15698)
@@ -1,590 +1,595 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	/**
 	 * Performs action on multilingual fields
 	 *
 	 */
 	class kMultiLanguageHelper extends kHelper {
 
 		/**
 		 * Determines, that language info should be requeried
 		 *
 		 * @var bool
 		 * @access protected
 		 */
 		protected $initMade = false;
 
 		/**
 		 * Maximal language id
 		 *
 		 * @var int
 		 */
 		protected $languageCount = 0;
 		/**
 		 * Languages created in system
 		 *
 		 * @var Array
 		 */
 		protected $languagesIDs = Array ();
 
 		/**
 		 * Structure of table, that is currently processed
 		 *
 		 * @var Array
 		 */
 		var $curStructure = Array();
 
 		/**
 		 * Field, to get structure information from
 		 *
 		 * @var string
 		 */
 		var $curSourceField = false;
 
 		/**
 		 * Indexes used in table of 32
 		 *
 		 * @var int
 		 */
 		var $curIndexCount = 0;
 
 		/**
 		 * Fields from config, that are currently used
 		 *
 		 * @var Array
 		 */
 		var $curFields = Array();
 
 		public function resetState()
 		{
 			$this->initMade = false;
 		}
 
 		/**
 		 * Updates language count in system (always is divisible by 5)
 		 *
 		 */
 		protected function _queryLanguages()
 		{
 			if ( !$this->initMade ) {
 				$this->languagesIDs = $this->getActualLanguages();
 				$this->languageCount = max(max($this->languagesIDs), 5);
 				$this->initMade = true;
 			}
 		}
 
 		/**
 		 * Returns language ids, that can be used
 		 *
 		 * @return Array
 		 */
 		protected function getActualLanguages()
 		{
 			$cache_key = 'actual_language_ids[%LangSerial%]';
 			$ret = $this->Application->getCache($cache_key);
 
 			if ( $ret === false ) {
 				$this->Conn->nextQueryCachable = true;
-				$sql = 'SELECT ' . $this->Application->getUnitOption('lang', 'IDField') . '
-						FROM ' . $this->Application->getUnitOption('lang', 'TableName');
+				$config = $this->Application->getUnitConfig('lang');
+
+				$sql = 'SELECT ' . $config->getIDField() . '
+						FROM ' . $config->getTableName();
 				$ret = $this->Conn->GetCol($sql);
 
 				$this->Application->setCache($cache_key, $ret);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Checks if language with specified id is created
 		 *
 		 * @param int $language_id
 		 * @return bool
 		 */
 		protected function LanguageFound($language_id)
 		{
 			return in_array($language_id, $this->languagesIDs) || $language_id <= 5;
 		}
 
 		/**
 		 * Returns list of processable languages
 		 *
 		 * @return Array
 		 */
 		public function getLanguages()
 		{
 			$cache_key = 'processable_language_ids[%LangSerial%]';
 			$ret = $this->Application->getCache($cache_key);
 
 			if ( $ret === false ) {
 				$ret = Array ();
 				$this->_queryLanguages();
 
 				for ($language_id = 1; $language_id <= $this->languageCount; $language_id++) {
 					if ( $this->LanguageFound($language_id) ) {
 						$ret[] = $language_id;
 					}
 				}
 
 				$this->Application->setCache($cache_key, $ret);
 			}
 
 			return $ret;
 		}
 
 		function scanTable($mask)
 		{
 			$i = 0;
 			$fields_found = 0;
 			$fields = array_keys($this->curStructure);
 
 			foreach ($fields as $field_name) {
 				if (preg_match($mask, $field_name)) {
 					$fields_found++;
 				}
 			}
 			return $fields_found;
 		}
 
 		function readTableStructure($table_name, $refresh = false)
 		{
 //			if ($refresh || !getArrayValue($structure_status, $prefix.'.'.$table_name)) {
 				$this->curStructure = $this->Conn->Query('DESCRIBE '.$table_name, 'Field');
 				$this->curIndexCount = count($this->Conn->Query('SHOW INDEXES FROM '.$table_name));
 //			}
 		}
 
 		/**
 		 * Creates missing multilingual fields for all unit configs, registered in system
 		 *
 		 * @param bool $reread_configs
 		 * @return void
 		 * @access public
 		 */
 		public function massCreateFields($reread_configs = true)
 		{
 			if ( $reread_configs ) {
 				$this->Application->UnitConfigReader->ReReadConfigs();
 			}
 
-			foreach ($this->Application->UnitConfigReader->configData as $prefix => $config_data) {
+			foreach ($this->Application->UnitConfigReader->getPrefixes() as $prefix) {
 				$this->createFields($prefix);
 			}
 		}
 
 		/**
 		 * Creates missing multilanguage fields in table by specified prefix
 		 *
 		 * @param string $prefix
 		 * @param bool $refresh Forces config field structure to be re-read from database
 		 * @return void
 		 */
 		function createFields($prefix, $refresh = false)
 		{
 			if ( $refresh && preg_match('/(.*)-cdata$/', $prefix, $regs) ) {
 				// call main item config to clone cdata table
 				$this->Application->UnitConfigReader->loadConfig($regs[1]);
 				$this->Application->UnitConfigReader->runAfterConfigRead($prefix);
 			}
 
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
-			$this->curFields = $this->Application->getUnitOption($prefix, 'Fields');
+			$config = $this->Application->getUnitConfig($prefix);
+			$table_name = $config->getTableName();
+			$this->curFields = $config->getFields();
 
 			if ( !($table_name && $this->curFields) || ($table_name && !$this->Conn->TableFound($table_name, kUtil::constOn('IS_INSTALL'))) ) {
 				// invalid config found or prefix not found
 				return ;
 			}
 
 			$this->_queryLanguages();
 
 			$sqls = Array ();
 			$this->readTableStructure($table_name, $refresh);
 
 			foreach ($this->curFields as $field_name => $field_options) {
 				if ( getArrayValue($field_options, 'formatter') == 'kMultiLanguage' ) {
 					if ( isset($field_options['master_field']) ) {
 						unset($this->curFields[$field_name]);
 						continue;
 					}
 
 					$this->setSourceField($field_name);
 					if ( $this->languageCount > 0 ) {
 						// `l77_Name` VARCHAR( 255 ) NULL DEFAULT '0';
 						$field_mask = Array ();
 						$field_mask['name'] = 'l%s_' . $field_name;
 						$field_mask['null'] = getArrayValue($field_options, 'not_null') ? 'NOT NULL' : 'NULL';
 
 						if ( $this->curSourceField ) {
 							$default_value = $this->getFieldParam('Default') != 'NULL'
 									? $this->Conn->qstr($this->getFieldParam('Default'))
 									: $this->getFieldParam('Default');
 							$field_mask['type'] = $this->getFieldParam('Type');
 						}
 						else {
 							$default_value = is_null($field_options['default']) ? 'NULL'
 									: $this->Conn->qstr($field_options['default']);
 							$field_mask['type'] = $field_options['db_type'];
 						}
 
 						$field_mask['default'] = ($field_mask['null'] == 'NOT NULL' && $default_value == 'NULL') ? '' : 'DEFAULT ' . $default_value;
 
 						if ( strtoupper($field_mask['type']) == 'TEXT' ) {
 							// text fields in mysql doesn't have default value
 							$field_mask = $field_mask['name'] . ' ' . $field_mask['type'] . ' ' . $field_mask['null'];
 						}
 						else {
 							$field_mask = $field_mask['name'] . ' ' . $field_mask['type'] . ' ' . $field_mask['null'] . ' ' . $field_mask['default'];
 						}
 
 						$alter_sqls = $this->generateAlterSQL($field_mask, 1, $this->languageCount);
 						if ( $alter_sqls ) {
 							$sqls[] = 'ALTER TABLE ' . $table_name . ' ' . $alter_sqls;
 						}
 					}
 				}
 			}
 
 			foreach ($sqls as $sql_query) {
 				$this->Conn->Query($sql_query);
 			}
 		}
 
 		/**
 		 * Creates missing multilanguage fields in table by specified prefix
 		 *
 		 * @param string $prefix
 		 * @param int $src_language
 		 * @param int $dst_language
 		 * @return void
 		 * @access public
 		 */
 		public function copyMissingData($prefix, $src_language, $dst_language)
 		{
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
-			$this->curFields = $this->Application->getUnitOption($prefix, 'Fields');
+			$config = $this->Application->getUnitConfig($prefix);
+			$table_name = $config->getTableName();
+			$this->curFields = $config->getFields();
 
 			if ( !($table_name && $this->curFields) || ($table_name && !$this->Conn->TableFound($table_name, kUtil::constOn('IS_INSTALL'))) ) {
 				// invalid config found or prefix not found
 				return ;
 			}
 
 			foreach ($this->curFields as $field_name => $field_options) {
 				$formatter = isset($field_options['formatter']) ? $field_options['formatter'] : '';
 
 				if ( ($formatter == 'kMultiLanguage') && !isset($field_options['master_field']) ) {
 					$sql = 'UPDATE ' . $table_name . '
 							SET l' . $dst_language . '_' . $field_name . ' = l' . $src_language . '_' . $field_name . '
 							WHERE l' . $dst_language . '_' . $field_name . ' = "" OR l' . $dst_language . '_' . $field_name . ' IS NULL';
 					$this->Conn->Query($sql);
 				}
 			}
 		}
 
 		function deleteField($prefix, $custom_id)
 		{
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+			$table_name = $this->Application->getUnitConfig($prefix)->getTableName();
 			$sql = 'DESCRIBE '.$table_name.' "l%_cust_'.$custom_id.'"';
 			$fields = $this->Conn->GetCol($sql);
 
 			$sql = 'ALTER TABLE '.$table_name.' ';
 			$sql_template = 'DROP COLUMN %s, ';
 
 			foreach ($fields as $field_name) {
 				$sql .= sprintf($sql_template, $field_name);
 			}
 
 			$this->Conn->Query( substr($sql, 0, -2) );
 		}
 
 		/**
 		 * Returns parameter requested of current source field
 		 *
 		 * @param string $param_name
 		 * @return string
 		 */
 		function getFieldParam($param_name)
 		{
 			return $this->curStructure[$this->curSourceField][$param_name];
 		}
 
 		/**
 		 * Detects field name to create other fields from
 		 *
 		 * @param string $field_name
 		 */
 		function setSourceField($field_name)
 		{
 			$ret = $this->scanTable('/^l[\d]+_'.preg_quote($field_name, '/').'$/');
 			if (!$ret) {
 				// no multilingual fields at all (but we have such field without language prefix)
 				$original_found = $this->scanTable('/^'.preg_quote($field_name, '$/').'/');
 				$this->curSourceField = $original_found ? $field_name : false;
 			}
 			else {
 				$this->curSourceField = 'l1_'.$field_name;
 			}
 		}
 
 		/**
 		 * Returns ALTER statement part for adding required fields to table
 		 *
 		 * @param string $field_mask sql mask for creating field with correct definition (type & size)
 		 * @param int $start_index add new fields starting from this index
 		 * @param int $create_count create this much new multilingual field translations
 		 * @return string
 		 */
 		function generateAlterSQL($field_mask, $start_index, $create_count)
 		{
 			static $single_lang = null;
 			if (!isset($single_lang)) {
 				// if single language mode, then create indexes only on primary columns
-				$table_name = $this->Application->getUnitOption('lang', 'TableName');
+				$table_name = $this->Application->getUnitConfig('lang')->getTableName();
 				$sql = 'SELECT COUNT(*)
 						FROM '.$table_name.'
 						WHERE Enabled = 1';
 				// if language count = 0, then assume it's multi language mode
 				$single_lang = $this->Conn->GetOne($sql) == 1;
 			}
 
 			$ret = '';
 			$ml_field = preg_replace('/l(.*?)_(.*?) (.*)/', '\\2', $field_mask);
 
 			$i_count = $start_index + $create_count;
 			while ($start_index < $i_count) {
 
 				if (isset($this->curStructure['l'.$start_index.'_'.$ml_field]) || (!$this->LanguageFound($start_index)) ) {
 					$start_index++;
 					continue;
 				}
 
 				$prev_index = $start_index - 1;
 				do {
 					list($prev_field,$type) = explode(' ', sprintf($field_mask, $prev_index) );
 				} while ($prev_index > 0 && !$this->LanguageFound($prev_index--));
 
 				if (substr($prev_field, 0, 3) == 'l0_') {
 					$prev_field = substr($prev_field, 3, strlen($prev_field));
 					if (!$this->curSourceField) {
 						// get field name before this one
 						$fields = array_keys($this->curFields);
 //						$prev_field = key(end($this->curStructure));
 						$prev_field = $fields[array_search($prev_field, $fields) - 1];
 						if (getArrayValue($this->curFields[$prev_field], 'formatter') == 'kMultiLanguage') {
 							$prev_field = 'l'.$this->languageCount.'_'.$prev_field;
 						}
 					}
 				}
 
 				$field_expression = sprintf($field_mask, $start_index);
 				$ret .= 'ADD COLUMN '.$field_expression.' AFTER `'.$prev_field.'`, ';
 
 				if ($this->curIndexCount < 32 && ($start_index == $this->Application->GetDefaultLanguageId() || !$single_lang)) {
 					// create index for primary language column + for all others (if multiple languages installed)
 					list($field_name, $field_params) = explode(' ', $field_expression, 2);
 
 					$index_type = isset($this->curFields[$ml_field]['index_type']) ? $this->curFields[$prev_field]['index_type'] : 'string';
 
 					$ret .= $index_type == 'string' ? 'ADD INDEX (`'.$field_name.'` (5) ), ' : 'ADD INDEX (`'.$field_name.'`), ';
 					$this->curIndexCount++;
 				}
 
 				$start_index++;
 			}
 			return preg_replace('/, $/', ';', $ret);
 		}
 
 		/**
 		 * Returns phrase based on given number
 		 *
 		 * @param int $number
 		 * @param Array $forms
 		 * @param bool $allow_editing
 		 * @param bool $use_admin
 		 * @return string
 		 * @access public
 		 */
 		public function getPluralPhrase($number, $forms, $allow_editing = true, $use_admin = false)
 		{
 			// normalize given forms
 			if ( !array_key_exists('phrase3', $forms) ) {
 				$forms['phrase3'] = $forms['phrase2'];
 			}
 
 			if ( !array_key_exists('phrase4', $forms) ) {
 				$forms['phrase4'] = $forms['phrase2'];
 			}
 
 			if ( !array_key_exists('phrase5', $forms) ) {
 				$forms['phrase5'] = $forms['phrase2'];
 			}
 
 			$phrase_type = $this->getPluralPhraseType($number);
 
 			return $this->Application->Phrase($forms['phrase' . $phrase_type], $allow_editing, $use_admin);
 		}
 
 		/**
 		 * Returns phrase type based on given number
 		 *
 		 * @param int $number
 		 * @return int
 		 * @access protected
 		 */
 		protected function getPluralPhraseType($number)
 		{
 			$last_digit = substr($number, -1);
 			$last_but_one_digit = strlen($number) > 1 ? substr($number, -2, 1) : false;
 			$phrase_type = 5;
 
 			if ( $last_but_one_digit != 1 ) {
 				if ( $last_digit >= 1 && $last_digit <= 4 ) {
 					$phrase_type = $last_digit;
 				}
 			}
 
 			return (string)$phrase_type;
 		}
 
 		/**
 		 * Allows usage of
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access public
 		 */
 		public function replaceMLCalculatedFields(kEvent $event)
 		{
+			$config = $event->getUnitConfig();
 			$editing_language = $this->getEditingLanguage();
 
-			$calculated_fields = $this->Application->getUnitOption($event->Prefix, 'CalculatedFields', Array ());
+			$calculated_fields = $config->getSetting('CalculatedFields', Array ());
 			/* @var $calculated_fields Array */
 
 			foreach ($calculated_fields as $special => $fields) {
 				foreach ($fields as $field_name => $field_expression) {
 					$calculated_fields[$special][$field_name] = str_replace('%5$s', $editing_language, $field_expression);
 				}
 			}
 
-			$this->Application->setUnitOption($event->Prefix, 'CalculatedFields', $calculated_fields);
+			$config->setSetting('CalculatedFields', $calculated_fields);
 		}
 
 		/**
 		 * Returns language, that is being edited or current language
 		 *
 		 * @return int
 		 * @access public
 		 */
 		public function getEditingLanguage()
 		{
 			$language_id = $this->Application->GetVar('lang_id');
 
 			if ( !$language_id ) {
 				$language_id = $this->Application->GetVar('m_lang');
 			}
 
 			return $language_id;
 		}
 
 		/**
 		 * Determines if we're editing phrase/e-mail event on it's source language
 		 *
 		 * @param int $source_language
 		 * @return bool
 		 * @access public
 		 */
 		public function editingInSourceLanguage($source_language)
 		{
 			return $this->getSourceLanguage($source_language) == $this->getEditingLanguage();
 		}
 
 		/**
 		 * Replaces source language in given label translation
 		 *
 		 * @param kDBItem $object
 		 * @param string $label
 		 * @return string
 		 * @access public
 		 */
 		public function replaceSourceLanguage(kDBItem $object, $label)
 		{
 			$ret = $this->Application->Phrase($label);
 			$options = $object->GetFieldOption('TranslateFromLanguage', 'options');
 			$source_language = $this->getSourceLanguage($object->GetDBField('TranslateFromLanguage'));
 
 			return sprintf($ret, $options[$source_language]);
 		}
 
 		/**
 		 * Ensures, that primary language is used, when no translation is needed
 		 *
 		 * @param int $source_language
 		 * @return bool
 		 * @access public
 		 */
 		public function getSourceLanguage($source_language)
 		{
 			if ( !$source_language ) {
 				$source_language = $this->Application->GetDefaultLanguageId();
 			}
 
 			return $source_language;
 		}
 
 		/**
 		 * Translation synchronization state management
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access public
 		 * @throws InvalidArgumentException
 		 */
 		public function updateTranslationState(kEvent $event)
 		{
 			if ( $event->Name != 'OnBeforeCopyToLive' ) {
 				throw new InvalidArgumentException('Unsupported "' . (string)$event . '" event');
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$object->SwitchToTemp();
 			$object->Load($event->getEventParam('id'));
 
 			$save_mode = $this->Application->GetVar('translation_save_mode');
 
 			if ( $save_mode === false ) {
 				return;
 			}
 
 			$editing_language = $this->getEditingLanguage();
 
 			if ( $save_mode == TranslationSaveMode::SYNC_WITH_PRIMARY ) {
 				$object->SetDBField('l' . $editing_language . '_TranslateFrom', 0);
 			}
 			else {
 				$languages = $this->getLanguages();
 
 				foreach ($languages as $language_id) {
 					$object->SetDBField('l' . $language_id . '_TranslateFrom', $language_id == $editing_language ? 0 : $editing_language);
 				}
 			}
 
 			if ( $object->GetChangedFields() ) {
 				$object->Update();
 			}
 		}
 	}
Index: branches/5.3.x/core/units/helpers/cron_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/cron_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/cron_helper.php	(revision 15698)
@@ -1,680 +1,682 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kCronHelper extends kHelper {
 
 	const COMMON = 0;
 	const MINUTE = 1;
 	const HOUR = 2;
 	const DAY = 3;
 	const MONTH = 4;
 	const WEEKDAY = 5;
 
 	/**
 	 * Defines possible cron fields and their matching priority
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $fieldTypes = Array (self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE);
 
 	protected $commonSettings = Array (
 		'* * * * *' => 'la_opt_CronEveryMinute',
 		'*/5 * * * *' => 'la_opt_CronEveryFiveMinutes',
 		'0,30 * * * *' => 'la_opt_CronTwiceAnHour',
 		'0 * * * *' => 'la_opt_CronOnceAnHour',
 		'0 0,12 * * *' => 'la_opt_CronTwiceADay',
 		'0 0 * * *' => 'la_opt_CronOnceADay',
 		'0 0 * * 0' => 'la_opt_CronOnceAWeek',
 		'0 0 1,15 * *' => 'la_opt_CronTwiceAMonth',
 		'0 0 1 * *' => 'la_opt_CronOnceAMonth',
 		'0 0 1 1 *' => 'la_opt_CronOnceAYear',
 	);
 
 	protected $minuteSettings = Array (
 		'*' => 'la_opt_CronEveryMinute',
 		'*/2' => 'la_opt_CronEveryOtherMinute',
 		'*/5' => 'la_opt_CronEveryFiveMinutes',
 		'*/10' => 'la_opt_CronEveryTenMinutes',
 		'*/15' => 'la_opt_CronEveryFifteenMinutes',
 		'0,30' => 'la_opt_CronEveryThirtyMinutes',
 		'--' => 'la_opt_CronMinutes',
 		// minutes added dynamically later
 	);
 
 	protected $hourSettings = Array (
 		'*' => 'la_opt_CronEveryHour',
 		'*/2' => 'la_opt_CronEveryOtherHour',
 		'*/3' => 'la_opt_CronEveryThreeHours',
 		'*/4' => 'la_opt_CronEveryFourHours',
 		'*/6' => 'la_opt_CronEverySixHours',
 		'0,12' => 'la_opt_CronEveryTwelveHours',
 		'--' => 'la_opt_CronHours',
 		// hours added dynamically later
 	);
 
 	protected $daySettings = Array (
 		'*' => 'la_opt_CronEveryDay',
 		'*/2' => 'la_opt_CronEveryOtherDay',
 		'1,15' => 'la_opt_CronTwiceAMonth',
 		'--' => 'la_opt_CronDays',
 		// days added dynamically later
 	);
 
 	protected $monthSettings = Array (
 		'*' => 'la_opt_CronEveryMonth',
 		'*/2' => 'la_opt_CronEveryOtherMonth',
 		'*/4' => 'la_opt_CronEveryThreeMonths',
 		'1,7' => 'la_opt_CronEverySixMonths',
 		'--' => 'la_opt_CronMonths',
 		'1' => 'la_opt_January',
 		'2' => 'la_opt_February',
 		'3' => 'la_opt_March',
 		'4' => 'la_opt_April',
 		'5' => 'la_opt_May',
 		'6' => 'la_opt_June',
 		'7' => 'la_opt_July',
 		'8' => 'la_opt_August',
 		'9' => 'la_opt_September',
 		'10' => 'la_opt_October',
 		'11' => 'la_opt_November',
 		'12' => 'la_opt_December',
 	);
 
 	protected $weekdaySettings = Array (
 		'*' => 'la_opt_CronEveryWeekday',
 		'1-5' => 'la_opt_CronMondayThroughFriday',
 		'0,6' => 'la_opt_CronSaturdayAndSunday',
 		'1,3,5' => 'la_opt_CronMondayWednesdayAndFriday',
 		'2,4' => 'la_opt_CronTuesdayAndThursday',
 		'--' => 'la_opt_CronWeekdays',
 		'0' => 'la_opt_Sunday',
 		'1' => 'la_opt_Monday',
 		'2' => 'la_opt_Tuesday',
 		'3' => 'la_opt_Wednesday',
 		'4' => 'la_opt_Thursday',
 		'5' => 'la_opt_Friday',
 		'6' => 'la_opt_Saturday',
 	);
 
 	/**
 	 * Returns possible field options by type
 	 *
 	 * @param int $field_type
 	 * @return Array
 	 */
 	public function getOptions($field_type)
 	{
 		$mapping = Array (
 			self::COMMON => $this->commonSettings,
 			self::MINUTE => $this->minuteSettings,
 			self::HOUR => $this->hourSettings,
 			self::DAY => $this->daySettings,
 			self::MONTH => $this->monthSettings,
 			self::WEEKDAY => $this->weekdaySettings,
 		);
 
 		$ret = $mapping[$field_type];
 		/* @var $ret Array */
 
 		foreach ($ret as $option_key => $option_title) {
 			$option_title = substr($option_title, 0, 1) == '+' ? substr($option_title, 1) : $this->Application->Phrase($option_title);
 			$ret[$option_key] = $option_title;
 
 			if ( "$option_key" !== '--' ) {
 				$ret[$option_key] .= ' (' . $option_key . ')';
 			}
 		}
 
 		if ( $field_type == self::MINUTE ) {
 			for ($i = 0; $i <= 59; $i++) {
 				$ret[$i] = ':' . str_pad($i, 2, '0', STR_PAD_LEFT) . ' (' . $i . ')';
 			}
 		}
 		elseif ( $field_type == self::HOUR ) {
 			$language = $this->Application->recallObject('lang.current');
 			/* @var $language LanguagesItem */
 
 			$short_time_format = str_replace(':s', '', $language->GetDBField('TimeFormat'));
 
 			for ($i = 0; $i <= 23; $i++) {
 				$ret[$i] = adodb_date($short_time_format, adodb_mktime($i, 0, 0)) . ' (' . $i . ')';
 			}
 		}
 		elseif ( $field_type == self::DAY ) {
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$forms = Array (
 				'phrase1' => 'la_NumberSuffixSt', 'phrase2' => 'la_NumberSuffixNd', 'phrase3' => 'la_NumberSuffixRd',
 				'phrase4' => 'la_NumberSuffixTh', 'phrase5' => 'la_NumberSuffixTh'
 			);
 
 			for ($i = 1; $i <= 31; $i++) {
 				$ret[$i] = $i . $ml_helper->getPluralPhrase($i, $forms) . ' (' . $i . ')';
 			}
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns field name by type
 	 *
 	 * @param int $field_type
 	 * @param string $field_prefix
 	 * @return string
 	 * @access protected
 	 */
 	protected function _getFieldNameByType($field_type, $field_prefix)
 	{
 		$field_mapping = Array (
 			self::MINUTE => 'Minute',
 			self::HOUR => 'Hour',
 			self::DAY => 'Day',
 			self::MONTH => 'Month',
 			self::WEEKDAY => 'Weekday',
 		);
 
 		return $field_prefix . $field_mapping[$field_type];
 	}
 
 	/**
 	 * Creates virtual fields for given unit
 	 *
 	 * @param string $prefix
 	 * @param string $field_prefix
 	 * @return void
 	 * @access public
 	 */
 	public function initUnit($prefix, $field_prefix = '')
 	{
-		$virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields', Array ());
+		$config = $this->Application->getUnitConfig($prefix);
 
-		$virtual_fields[$field_prefix . 'CommonHints'] = Array (
-			'type' => 'string',
-			'formatter' => 'kOptionsFormatter', 'options' => $this->getOptions(self::COMMON),
-			'default' => ''
-		);
+		$config->addVirtualFields(Array (
+			$field_prefix . 'CommonHints' => Array (
+				'type' => 'string',
+				'formatter' => 'kOptionsFormatter', 'options' => $this->getOptions(self::COMMON),
+				'default' => ''
+			),
+		));
 
 		foreach ($this->fieldTypes as $field_type) {
 			$field_name = $this->_getFieldNameByType($field_type, $field_prefix);
-			$virtual_fields[$field_name] = Array ('type' => 'string', 'max_len' => 30, 'default' => '*');
 
-			$virtual_fields[$field_name . 'Hints'] = Array (
-				'type' => 'string',
-				'formatter' => 'kOptionsFormatter', 'options' => $this->getOptions($field_type),
-				'default' => ''
-			);
+			$config->addVirtualFields(Array (
+				$field_name => Array ('type' => 'string', 'max_len' => 30, 'default' => '*'),
+				$field_name . 'Hints' => Array (
+					'type' => 'string',
+					'formatter' => 'kOptionsFormatter', 'options' => $this->getOptions($field_type),
+					'default' => ''
+				),
+			));
 		}
-
-		$this->Application->setUnitOption($prefix, 'VirtualFields', $virtual_fields);
 	}
 
 	/**
 	 * Loads schedule values from database into virtual fields
 	 *
 	 * @param kDBItem $object
 	 * @param string $field_prefix
 	 */
 	public function load(kDBItem $object, $field_prefix = '')
 	{
 		$combined_value = explode(' ', $object->GetDBField($field_prefix));
 
 		foreach ($this->fieldTypes as $field_type) {
 			$field_name = $this->_getFieldNameByType($field_type, $field_prefix);
 			$object->SetDBField($field_name, $combined_value[$field_type - 1]);
 		}
 	}
 
 	/**
 	 * Validates schedule values and saves them to database
 	 *
 	 * @param kDBItem $object
 	 * @param string $field_prefix
 	 * @return bool
 	 * @access public
 	 */
 	public function validateAndSave(kDBItem $object, $field_prefix = '')
 	{
 		$validated = true;
 		$combined_value = Array ();
 		$cron_field = new kCronField();
 
 		foreach ($this->fieldTypes as $field_type) {
 			$field_name = $this->_getFieldNameByType($field_type, $field_prefix);
 			$value = preg_replace('/\s+/s', '', mb_strtoupper($object->GetDBField($field_name)));
 
 			if ( $cron_field->validate($field_type, $value) ) {
 				$object->SetDBField($field_name, $value);
 			}
 			else {
 				$validated = false;
 				$object->SetError($field_name, 'invalid_format');
 			}
 
 			$combined_value[$field_type] = $value;
 		}
 
 		ksort($combined_value);
 		$object->SetDBField($field_prefix, implode(' ', $combined_value));
 
 		return $validated;
 	}
 
 	/**
 	 * Replaces aliases in the field
 	 *
 	 * @param int $field_type
 	 * @param string $value
 	 * @return string
 	 * @access public
 	 */
 	public static function replaceAliases($field_type, $value)
 	{
 		$replacements = Array ();
 		$value = mb_strtolower($value);
 
 		if ( $field_type == self::MONTH ) {
 			$replacements = Array (
 				'jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6,
 				'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12,
 			);
 		}
 		elseif ( $field_type == self::WEEKDAY ) {
 			$replacements = Array ('sun' => 0, 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6);
 		}
 
 		if ( $replacements ) {
 			$value = str_replace(array_keys($replacements), array_values($replacements), $value);
 		}
 
 		return $value;
 	}
 
 	/**
 	 * Returns next (after given one or now) timestamp matching given cron expression
 	 *
 	 * @param string $expression
 	 * @param int $date
 	 * @param bool $inverse
 	 * @param bool $allow_current_date
 	 * @return int
 	 * @access public
 	 * @throws RuntimeException
 	 */
 	public function getMatch($expression, $date = NULL, $inverse = false, $allow_current_date = false)
 	{
 		if ( !isset($date) ) {
 			$date = TIMENOW;
 		}
 
 		$next_run = strtotime('-' . (int)adodb_date('s', $date) . ' seconds', $date);
 		$expression_parts = explode(' ', $expression);
 
 		$cron_field = new kCronField();
 
 		// set a hard limit to bail on an impossible date
 		for ($i = 0; $i < 1000; $i++) {
 			foreach ($this->fieldTypes as $field_type) {
 				$matched = false;
 				$part = $expression_parts[$field_type - 1];
 
 				// check if this is singular or a list
 				if ( strpos($part, ',') === false ) {
 					$matched = $cron_field->match($field_type, $next_run, $part);
 				}
 				else {
 					$rules = explode(',', $part);
 
 					foreach ($rules as $rule) {
 						if ( $cron_field->match($field_type, $next_run, $rule) ) {
 							$matched = true;
 							break;
 						}
 					}
 				}
 
 				// if the field is not matched, then start over
 				if ( !$matched ) {
 					$next_run = $cron_field->increment($field_type, $next_run, $inverse);
 					continue 2;
 				}
 			}
 
 			// Skip this match if needed
 			if ( (!$allow_current_date && $next_run == $date) ) {
 				$next_run = $cron_field->increment(self::MINUTE, $next_run, $inverse);
 				continue;
 			}
 
 			return $next_run;
 		}
 
 		throw new RuntimeException('Impossible CRON expression');
 	}
 }
 
 
 class kCronField extends kBase {
 
 	/**
 	 * Validates field value
 	 *
 	 * @param int $field_type
 	 * @param string $value
 	 * @param bool $asterisk_allowed
 	 * @return bool
 	 * @access public
 	 */
 	public function validate($field_type, $value, $asterisk_allowed = true)
 	{
 		$rules = explode(',', kCronHelper::replaceAliases($field_type, $value));
 
 		foreach ($rules as $rule) {
 			if ( $this->_isIncrementRule($rule) ) {
 				if ( !$this->_validateIncrementRule($field_type, $rule) ) {
 					return false;
 				}
 			}
 			elseif ( $this->_isRangeRule($rule) ) {
 				if ( !$this->_validateRangeRule($field_type, $rule) ) {
 					return false;
 				}
 			}
 			elseif ( !$this->_validateNumberRule($field_type, $rule, $asterisk_allowed) ) {
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 	/**
 	 * Determines if expression is range
 	 *
 	 * @param string $rule
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _isRangeRule($rule)
 	{
 		return strpos($rule, '-') !== false;
 	}
 
 	/**
 	 * Validates range rule
 	 *
 	 * @param int $field_type
 	 * @param string $rule
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _validateRangeRule($field_type, $rule)
 	{
 		$parts = explode('-', $rule);
 
 		if ( count($parts) != 2 ) {
 			return false;
 		}
 
 		$min_value = $parts[0];
 		$max_value = $parts[1];
 
 		if ( !$this->_validateNumberRule($field_type, $min_value) || !$this->_validateNumberRule($field_type, $max_value) || $min_value >= $max_value ) {
 			return false;
 		}
 
 		return true;
 	}
 
 	/**
 	 * Determines if expression is increment
 	 *
 	 * @param string $rule
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _isIncrementRule($rule)
 	{
 		return strpos($rule, '/') !== false;
 	}
 
 	/**
 	 * Validates increment rule
 	 *
 	 * @param int $field_type
 	 * @param string $rule
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _validateIncrementRule($field_type, $rule)
 	{
 		$parts = explode('/', $rule);
 
 		if ( count($parts) != 2 ) {
 			return false;
 		}
 
 		$interval = $parts[0];
 		$increment = $parts[1];
 
 		if ( $this->_isRangeRule($interval) ) {
 			if ( !$this->_validateRangeRule($field_type, $interval) ) {
 				return false;
 			}
 		}
 		elseif ( !$this->_validateNumberRule($field_type, $interval, true) ) {
 			return false;
 		}
 
 		if ( !$this->_validateNumberRule($field_type, $increment) ) {
 			return false;
 		}
 
 		return true;
 	}
 
 	/**
 	 * Validates, that number within range OR an asterisk is given
 	 *
 	 * @param int $field_type
 	 * @param string $rule
 	 * @param bool $asterisk_allowed
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _validateNumberRule($field_type, $rule, $asterisk_allowed = false)
 	{
 		if ( "$rule" === '*' ) {
 			return $asterisk_allowed;
 		}
 
 		$int_rule = (int)$rule;
 
 		if ( !is_numeric($rule) || "$int_rule" !== "$rule" ) {
 			// not integer
 			return false;
 		}
 
 		$range_mapping = Array (
 			kCronHelper::MINUTE => Array ('from' => 0, 'to' => 59),
 			kCronHelper::HOUR => Array ('from' => 0, 'to' => 23),
 			kCronHelper::DAY => Array ('from' => 1, 'to' => 31),
 			kCronHelper::MONTH => Array ('from' => 1, 'to' => 12),
 			kCronHelper::WEEKDAY => Array ('from' => 0, 'to' => 7),
 		);
 
 		return $int_rule >= $range_mapping[$field_type]['from'] && $int_rule <= $range_mapping[$field_type]['to'];
 	}
 
 	/**
 	 * Tries to match given date to given expression
 	 *
 	 * @param int $field_type
 	 * @param int $date
 	 * @param string $rule
 	 * @return bool
 	 * @access public
 	 */
 	public function match($field_type, $date, $rule)
 	{
 		$date_part = $this->_getDatePart($field_type, $date, $rule);
 
 		if ( $this->_isIncrementRule($rule) ) {
 			return $this->_isInIncrement($date_part, $rule);
 		}
 		elseif ( $this->_isRangeRule($rule) ) {
 			return $this->_isInRange($date_part, $rule);
 		}
 
 		return $rule == '*' || $date_part == $rule;
 	}
 
 	/**
 	 * Returns only part, needed based on field type of date in timestamp
 	 *
 	 * @param int $field_type
 	 * @param int $date
 	 * @param string $rule
 	 * @return int
 	 * @access protected
 	 */
 	protected function _getDatePart($field_type, $date, $rule)
 	{
 		$mapping = Array (
 			kCronHelper::MINUTE => 'i',
 			kCronHelper::HOUR => 'G',
 			kCronHelper::DAY => 'j',
 			kCronHelper::MONTH => 'n',
 			kCronHelper::WEEKDAY => 'N',
 		);
 
 		if ( $field_type == kCronHelper::WEEKDAY ) {
 			// Test to see which Sunday to use -- 0 == 7 == Sunday
 			$mapping[$field_type] = in_array(7, str_split($rule)) ? 'N' : 'w';
 		}
 
 		return (int)adodb_date($mapping[$field_type], $date);
 	}
 
 	/**
 	 * Test if a value is within a range
 	 *
 	 * @param string $date_value Set date value
 	 * @param string $rule Value to test
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _isInRange($date_value, $rule)
 	{
 		$parts = array_map('trim', explode('-', $rule, 2));
 
 		return $date_value >= $parts[0] && $date_value <= $parts[1];
 	}
 
 	/**
 	 * Test if a value is within an increments of ranges (offset[-to]/step size)
 	 *
 	 * @param string $date_value Set date value
 	 * @param string $rule Value to test
 	 * @return bool
 	 * @access protected
 	 */
 	protected function _isInIncrement($date_value, $rule)
 	{
 		$parts = array_map('trim', explode('/', $rule, 2));
 		$stepSize = isset($parts[1]) ? $parts[1] : 0;
 
 		if ( $parts[0] == '*' || $parts[0] == 0 ) {
 			return (int)$date_value % $stepSize == 0;
 		}
 
 		$range = explode('-', $parts[0], 2);
 		$offset = $range[0];
 		$to = isset($range[1]) ? $range[1] : $date_value;
 
 		// Ensure that the date value is within the range
 		if ( $date_value < $offset || $date_value > $to ) {
 			return false;
 		}
 
 		for ($i = $offset; $i <= $to; $i += $stepSize) {
 			if ( $i == $date_value ) {
 				return true;
 			}
 		}
 
 		return false;
 	}
 
 	/**
 	 * Increments/decrements given date for 1 unit based on field type
 	 *
 	 * @param int $field_type
 	 * @param int $date
 	 * @param bool $inverse
 	 * @return int
 	 * @access public
 	 */
 	public function increment($field_type, $date, $inverse = false)
 	{
 		$mapping = Array (
 			kCronHelper::MINUTE => '1 minute',
 			kCronHelper::HOUR => '1 hour',
 			kCronHelper::DAY => '1 day',
 			kCronHelper::MONTH => '1 month',
 			kCronHelper::WEEKDAY => '1 day',
 		);
 
 		return $this->_resetTime($field_type, strtotime(($inverse ? '-' : '+') . $mapping[$field_type], $date), $inverse);
 	}
 
 	/**
 	 * Resets time based on field type
 	 *
 	 * @param int $field_type
 	 * @param int $date
 	 * @param bool $inverse
 	 * @return int
 	 * @access public
 	 */
 	protected function _resetTime($field_type, $date, $inverse = false)
 	{
 		if ( $field_type == kCronHelper::MONTH || $field_type == kCronHelper::WEEKDAY || $field_type == kCronHelper::DAY ) {
 			if ( $inverse ) {
 				$date = strtotime(adodb_date('Y-m-d 23:59:59', $date));
 				// set time 23:59:00
 			}
 			else {
 				// set time 00:00:00
 				$date = strtotime(adodb_date('Y-m-d 00:00:00', $date));
 			}
 		}
 		elseif ( $field_type == kCronHelper::HOUR ) {
 			if ( $inverse ) {
 				// set time <current_hour>:59:00
 				$date = strtotime(adodb_date('Y-m-d H:59:59', $date));
 			}
 			else {
 				// set time <current_hour>:00:00
 				$date = strtotime(adodb_date('Y-m-d H:00:00', $date));
 			}
 		}
 
 		return $date;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/menu_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/menu_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/menu_helper.php	(revision 15698)
@@ -1,412 +1,412 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class MenuHelper extends kHelper {
 
 		/**
 		 * Cached version of site menu
 		 *
 		 * @var Array
 		 * @access protected
 		 */
 		protected $Menu = NULL;
 
 		/**
 		 * Parent path mapping used in CachedMenu tag
 		 *
 		 * @var Array
 		 * @access protected
 		 */
 		protected $parentPaths = Array ();
 
 		/**
 		 * Builds site menu
 		 *
 		 * @param string $prefix_special
 		 * @param Array $params
 		 *
 		 * @return string
 		 * @access public
 		 */
 		public function menuTag($prefix_special, $params)
 		{
 			list ($menu, $root_path) = $this->_prepareMenu();
 			$cat = $this->_getCategoryId($params);
 
 			$parent_path = array_key_exists($cat, $this->parentPaths) ? $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 ( array_key_exists('level', $params) && $params['level'] > count($levels) ) {
 				// current level is deeper, then requested level
 				return '';
 			}
 
 			$level = max(array_key_exists('level', $params) ? $params['level'] - 1 : count($levels) - 1, 0);
 			$parent = array_key_exists($level, $levels) ? $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'];
 			}
 
 			$block_params = $this->prepareTagParams($prefix_special, $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 ) {
 				// no menus on this level
 				return '';
 			}
 
 			$ret = '';
 			$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($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;
 
 				$ret .= $this->Application->ParseBlock($block_params);
 				$cur_item++;
 			}
 
 			$this->Application->SetVar('m_cat_id', $real_cat_id);
 
 			return $ret;
 		}
 
 		/**
 		 * Builds cached menu version
 		 *
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _prepareMenu()
 		{
 			static $root_cat = NULL, $root_path = NULL;
 
 			if ( !$root_cat ) {
 				$root_cat = $this->Application->getBaseCategory();
 				$cache_key = 'parent_paths[%CIDSerial:' . $root_cat . '%]';
 				$root_path = $this->Application->getCache($cache_key);
 
 				if ( $root_path === false ) {
 					$this->Conn->nextQueryCachable = true;
 					$sql = 'SELECT ParentPath
 							FROM ' . TABLE_PREFIX . 'Categories
 							WHERE CategoryId = ' . $root_cat;
 					$root_path = $this->Conn->GetOne($sql);
 					$this->Application->setCache($cache_key, $root_path);
 				}
 			}
 
 			if ( !$this->Menu ) {
 				if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 					$menu = $this->Application->getCache('master:cms_menu', false, CacheSettings::$cmsMenuRebuildTime);
 				}
 				else {
 					$menu = $this->Application->getDBCache('cms_menu', CacheSettings::$cmsMenuRebuildTime);
 				}
 
 				if ( $menu ) {
 					$menu = unserialize($menu);
 					$this->parentPaths = $menu['parentPaths'];
 				}
 				else {
 					$menu = $this->_buildMenuStructure($root_cat);
 					$menu['parentPaths'] = $this->parentPaths;
 
 					if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 						$this->Application->setCache('master:cms_menu', serialize($menu));
 					}
 					else {
 						$this->Application->setDBCache('cms_menu', serialize($menu));
 					}
 				}
 
 				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');
 			}
 			elseif ( $cat == 0 ) {
 				$cat = $this->Application->getBaseCategory();
 			}
 
 			return $cat;
 		}
 
 		/**
 		 * Prepares cms menu item block parameters
 		 *
 		 * @param Array $page
 		 * @param int $real_cat_id
 		 * @param string $root_path
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _prepareMenuItem($page, $real_cat_id, $root_path)
 		{
 			static $language_id = NULL, $primary_language_id = NULL, $template = NULL;
 
 			if ( !isset($language_id) ) {
 				$language_id = $this->Application->GetVar('m_lang');
 				$primary_language_id = $this->Application->GetDefaultLanguageId();
 				$template = $this->Application->GetVar('t');
 			}
 
 			$active = $category_active = false;
 			$title = $page['l' . $language_id . '_ItemName'] ? $page['l' . $language_id . '_ItemName'] : $page['l' . $primary_language_id . '_ItemName'];
 
 			if ( $page['ItemType'] == 'cat' ) {
 				if ( array_key_exists($real_cat_id, $this->parentPaths) ) {
 					$active = strpos($this->parentPaths[$real_cat_id], $page['ParentPath']) !== false;
 				}
 				elseif ( $page['ItemPath'] == $template ) {
 					// physical template in menu
 					$active = true;
 				}
 
 				$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'));
 			}*/
 
 			if ( substr($page['ItemPath'], 0, 3) == 'id:' ) {
 				// resolve ID path here, since it can be used directly without m_Link tag (that usually resolves it)
 				$page['ItemPath'] = $this->Application->getVirtualPageTemplate(substr($page['ItemPath'], 3));
 			}
 
 			$block_params = Array (
 				'title' => $title,
 				'template' => $page['ItemPath'],
 				'active' => $active,
 				'category_active' => $category_active, // new
 				'parent_path' => $page['ParentPath'],
 				'parent_id' => $page['ParentId'],
 				'cat_id' => $page['CategoryId'],
 				'item_type' => $page['ItemType'],
 				'page_id' => $page['ItemId'],
 				'use_section' => ($page['Type'] == PAGE_TYPE_TEMPLATE) && ($page['ItemPath'] != 'index'),
 				'has_sub_menu' => isset($page['sub_items']) && count($this->_removeNonMenuItems($page['sub_items'])) > 0,
 				'external_url' => $page['UseExternalUrl'] ? $page['ExternalUrl'] : false, // for backward compatibility
 				'menu_icon' => $page['UseMenuIconUrl'] ? $page['MenuIconUrl'] : false,
 			);
 
 			return $block_params;
 		}
 
 		/**
 		 * Returns only items, that are visible in menu
 		 *
 		 * @param Array $menu
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _removeNonMenuItems($menu)
 		{
 			$theme_id = $this->Application->GetVar('m_theme');
 
 			foreach ($menu as $menu_index => $menu_item) {
 				// $menu_index is in "cN" format, where N is category id
 				if ( !$menu_item['IsMenu'] || $menu_item['Status'] != STATUS_ACTIVE || ($menu_item['ThemeId'] != $theme_id && $menu_item['ThemeId'] != 0) ) {
 					// don't show sections, that are not from menu OR system templates from other themes
 					unset($menu[$menu_index]);
 				}
 			}
 
 			return $menu;
 		}
 
 		/**
 		 * Builds cache of all menu items and their parent categories
 		 *
 		 * @param int $top_category_id
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _buildMenuStructure($top_category_id)
 		{
 			// 1. get parent paths of leaf categories, that are in menu (across all themes)
 			$sql = 'SELECT ParentPath, CategoryId
-					FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('c')->getTableName() . '
 					WHERE IsMenu = 1 AND Status = ' . STATUS_ACTIVE;
 			$this->parentPaths = $this->Conn->GetCol($sql ,'CategoryId');
 
 			// 2. figure out parent paths of all categories in path to leaf categories
 			foreach ($this->parentPaths as $leaf_parent_path) {
 				$parent_categories = explode('|', substr($leaf_parent_path, 1, -1));
 
 				foreach ($parent_categories as $index => $parent_category_id) {
 					if ( !isset($this->parentPaths[$parent_category_id]) ) {
 						$parent_path = array_slice($parent_categories, 0, $index + 1);
 						$this->parentPaths[$parent_category_id] = '|' . implode('|', $parent_path) . '|';
 					}
 				}
 			}
 
 			return $this->_altBuildMenuStructure($top_category_id, implode(',', array_keys($this->parentPaths)));
 		}
 
 		/**
 		 * Builds cache for children of given category (no matter, what menu status is)
 		 *
 		 * @param int $parent_category_id
 		 * @param string $category_limit
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _altBuildMenuStructure($parent_category_id, $category_limit = NULL)
 		{
 			// Sub-categories from current category
 			$items = $this->_getSubCategories($parent_category_id, $category_limit);
 
 			// sort menu items
 			uasort($items, Array (&$this, '_menuSort'));
 
 			// process sub-menus of each menu
 			foreach ($items as $key => $menu_item) {
 				if ( $menu_item['CategoryId'] == $parent_category_id ) {
 					// don't process myself - prevents recursion
 					continue;
 				}
 
 				$sub_items = $this->_altBuildMenuStructure($menu_item['CategoryId'], $category_limit);
 
 				if ( $sub_items ) {
 					$items[$key]['sub_items'] = $sub_items;
 				}
 			}
 
 			return $items;
 		}
 
 		/**
 		 * Returns given category sub-categories
 		 *
 		 * @param int $parent_id
 		 * @param string $category_limit
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getSubCategories($parent_id, $category_limit = NULL)
 		{
 			static $items_by_parent = NULL, $lang_part = NULL;
 
 			if ( !isset($lang_part) ) {
 				$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 				/* @var $ml_helper kMultiLanguageHelper */
 
 				$lang_part = '';
 				$languages = $ml_helper->getLanguages();
 
 				foreach ($languages as $language_id) {
 					$lang_part .= 'c.l' . $language_id . '_MenuTitle AS l' . $language_id . '_ItemName,' . "\n";
 				}
 			}
 
 			if ( !isset($items_by_parent) ) {
 				$items_by_parent = Array ();
 
 				// Sub-categories from current category
 				$sql = 'SELECT
 							c.CategoryId AS CategoryId,
 							CONCAT(\'c\', c.CategoryId) AS ItemId,
 							c.Priority AS ItemPriority,
 							' . $lang_part . '
 
 							IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', c.Template, CONCAT("id:", c.CategoryId)) AS ItemPath,
 							c.ParentPath AS ParentPath,
 							c.ParentId As ParentId,
 							\'cat\' AS ItemType,
 							c.IsMenu, c.Type, c.ThemeId, c.UseExternalUrl, c.ExternalUrl, c.UseMenuIconUrl, c.MenuIconUrl,
 							c.Status
 						FROM ' . TABLE_PREFIX . 'Categories AS c';
 
 				if ( isset($category_limit) && $category_limit ) {
 					$sql .= ' WHERE c.CategoryId IN (' . $category_limit . ')';
 				}
 
 				$items = $this->Conn->Query($sql, 'ItemId');
 
 				foreach ($items as $item_id => $item_data) {
 					$item_parent_id = $item_data['ParentId'];
 
 					if ( !array_key_exists($item_parent_id, $items_by_parent) ) {
 						$items_by_parent[$item_parent_id] = Array ();
 					}
 
 					$items_by_parent[$item_parent_id][$item_id] = $item_data;
 				}
 			}
 
 			return array_key_exists($parent_id, $items_by_parent) ? $items_by_parent[$parent_id] : Array ();
 		}
 
 		/**
 		 * Method for sorting pages by priority in descending 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
 		}
 	}
Index: branches/5.3.x/core/units/helpers/site_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/site_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/site_helper.php	(revision 15698)
@@ -1,147 +1,147 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2010 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class SiteHelper extends kHelper {
 
 		/**
 		 * Returns default country
 		 *
 		 * @param string $country_prefix
 		 * @param bool $iso_format
 		 * @return int
 		 */
 		function getDefaultCountry($country_prefix = '', $iso_format = true)
 		{
 			$country = $this->Application->siteDomainField($country_prefix . 'Country');
 
 			if ($iso_format && !$country) {
 				$country = 'USA';
 			}
 
 			if (!$iso_format && strlen($country)) {
 				return $this->getCountryId($country);
 			}
 
 			return $country;
 		}
 
 		/**
 		 * Returns country id based on it's ISO code
 		 *
 		 * @param string $iso_code
 		 * @return int
 		 */
 		function getCountryId($iso_code)
 		{
 			static $cache = null;
 
 			if (!isset($cache)) {
 				$sql = 'SELECT CountryStateId, IsoCode
-						FROM ' . $this->Application->getUnitOption('country-state', 'TableName') . '
+						FROM ' . $this->Application->getUnitConfig('country-state')->getTableName() . '
 						WHERE Type = ' . DESTINATION_TYPE_COUNTRY;
 				$cache = $this->Conn->GetCol($sql, 'IsoCode');
 			}
 
 			return $cache[$iso_code];
 		}
 
 		/**
 		 * Gets site domains from cache
 		 *
 		 * @return Array
 		 */
 		function getSiteDomains()
 		{
 			static $cache = null;
 
 			if ( !isset($cache) ) {
 				if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 					$cache = $this->Application->getCache('master:domains_parsed', false, CacheSettings::$domainsParsedRebuildTime);
 				}
 				else {
 					$cache = $this->Application->getDBCache('domains_parsed', CacheSettings::$domainsParsedRebuildTime);
 				}
 
 				if ($cache) {
 					$cache = unserialize($cache);
 				}
 				else {
 					$sql = 'SELECT *
 							FROM ' . TABLE_PREFIX . 'SiteDomains
 							ORDER BY Priority DESC';
 					$cache = $this->Conn->Query($sql, 'DomainId');
 
 					if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 						$this->Application->setCache('master:domains_parsed', serialize($cache));
 					}
 					else {
 						$this->Application->setDBCache('domains_parsed', serialize($cache));
 					}
 				}
 			}
 
 			return $cache;
 		}
 
 		/**
 		 * Try to match visited domain to any of existing
 		 *
 		 * @param string $field
 		 * @param string $value
 		 * @return int
 		 */
 		function getDomainByName($field, $value)
 		{
 			$site_domains = $this->getSiteDomains();
 			$name_fields = Array ('DomainName', 'SSLUrl');
 
 			foreach ($site_domains as $id => $site_domain) {
 				if ( in_array($field, $name_fields) ) {
 					if ( !$site_domain[$field . 'UsesRegExp'] ) {
 						// not regular expression -> escape manually
 						$site_domain[$field] = preg_quote($site_domain[$field], '/');
 					}
 
 					if ( $site_domain[$field] && preg_match('/^' . $site_domain[$field] . ($field == 'DomainName' ? '$' : '') . '/', $value) ) {
 						return $id;
 					}
 				}
 				elseif ( $site_domain[$field] == $value ) {
 					return $id;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Try to match domain settings based on visitor's IP address
 		 *
 		 * @return int
 		 */
 		function getDomainByIP()
 		{
 			$site_domains = $this->getSiteDomains();
 
 			foreach ($site_domains as $id => $site_domain) {
 				if (kUtil::ipMatch($site_domain['DomainIPRange'], "\n")) {
 					return $id;
 				}
 			}
 
 			return false;
 		}
 	}
Index: branches/5.3.x/core/units/helpers/recursive_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/recursive_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/recursive_helper.php	(revision 15698)
@@ -1,227 +1,227 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kRecursiveHelper extends kHelper {
 
 		function DeleteCategory($category_id, $prefix='c')
 		{
-			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+			$config = $this->Application->getUnitConfig($prefix);
+			$id_field = $config->getIDField();
 
 			$sql = 'SELECT '.$id_field.'
-					FROM '.$table_name.'
+					FROM '. $config->getTableName() .'
 					WHERE ParentId = '.$category_id;
 
 			$sub_categories = $this->Conn->GetCol($sql);
 			if ($sub_categories) {
 				foreach ($sub_categories as $sub_category_id) {
 					$this->DeleteCategory($sub_category_id);
 				}
 			}
 
-			$ci_table = $this->Application->getUnitOption('ci', 'TableName');
+			$ci_table = $this->Application->getUnitConfig('ci')->getTableName();
+
 			// 1. remove category items from this category if it is supplemental (non-primary) category to them
 			$sql = 'DELETE FROM '.$ci_table.'
 					WHERE ('.$id_field.' = '.$category_id.') AND (PrimaryCat = 0)';
 			$this->Conn->Query($sql);
 
 			$temp_handler = $this->Application->recallObject($prefix.'_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			// 2. delete items this have this category as primary
 			$delete_ids = $this->getCategoryItems($category_id, true);
 
 			foreach ($delete_ids as $item_prefix => $resource_ids) {
 				if (!$item_prefix) {
 					// not ItemPrefix filled -> old categoryitem linking
 					continue;
 				}
 				$item_ids = $this->GetItemIDs($item_prefix, $resource_ids);
 				$temp_handler->BuildTables($item_prefix, $item_ids);
 				$temp_handler->DeleteItems($item_prefix, '', $item_ids);
 			}
 
 			// 3. delete this category
 			$temp_handler->BuildTables($prefix, Array($category_id));
 			$temp_handler->DeleteItems($prefix, '', Array($category_id));
 		}
 
 		/**
 		 * Converts resource ids list to id field list for given prefix
 		 *
 		 * @param string $prefix
 		 * @param Array $resource_ids
 		 * @return Array
 		 */
 		function GetItemIDs($prefix, $resource_ids)
 		{
 			if (!$resource_ids) {
 				return Array();
 			}
 
-			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+			$config = $this->Application->getUnitConfig($prefix);
 
-			$sql = 'SELECT '.$id_field.'
-					FROM '.$table_name.'
+			$sql = 'SELECT '. $config->getIDField() .'
+					FROM '. $config->getTableName() .'
 					WHERE ResourceId IN ('.implode(',', $resource_ids).')';
 			return $this->Conn->GetCol($sql);
 		}
 
 		// moves selected categories to destination category
-		function MoveCategories($category_ids, $dest_category_id)
+		function MoveCategories($category_ids, $dst_category_id)
 		{
 			if (!$category_ids) return ;
 
-			$id_field = $this->Application->getUnitOption('c', 'IDField');
-			$table_name = $this->Application->getUnitOption('c', 'TableName');
+			$categories_config = $this->Application->getUnitConfig('c');
+			$id_field = $categories_config->getIDField();
+			$table_name = $categories_config->getTableName();
 
 			// do not move categories into their children
 			$sql = 'SELECT ParentPath
 					FROM '.$table_name.'
-					WHERE '.$id_field.' = '.$dest_category_id;
-			$dest_parent_path = explode('|', substr($this->Conn->GetOne($sql), 1, -1));
+					WHERE '.$id_field.' = '.$dst_category_id;
+			$dst_parent_path = explode('|', substr($this->Conn->GetOne($sql), 1, -1));
 
-			$child_categories = array_intersect($dest_parent_path, $category_ids); // get categories, then can't be moved
+			$child_categories = array_intersect($dst_parent_path, $category_ids); // get categories, then can't be moved
 			$category_ids = array_diff($category_ids, $child_categories); // remove them from movable categories list
 
 			if ($category_ids) {
 				$sql = 'UPDATE '.$table_name.'
-						SET ParentId = '.$dest_category_id.'
+						SET ParentId = '.$dst_category_id.'
 						WHERE '.$id_field.' IN ('.implode(',', $category_ids).')';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Complete cloning or category with subcategories and sub-items
 		 *
 		 * @param int $category_id
 		 * @param string $prefix
 		 */
 		function PasteCategory($category_id, $prefix = 'c')
 		{
 			$backup_category_id = $this->Application->GetVar('m_cat_id');
 
 			$src_parent_path = $this->_getParentPath($category_id);
 			$dst_parent_path = $this->_getParentPath($backup_category_id);
 
 			if ( substr($dst_parent_path, 0, strlen($src_parent_path)) == $src_parent_path ) {
 				// target path contains source path -> recursion
 				return;
 			}
 
 			// 1. clone category
 			$temp_handler = $this->Application->recallObject($prefix . '_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler*/
 			$temp_handler->BuildTables($prefix, Array ($category_id));
 			$new_category_id = array_pop($temp_handler->CloneItems($prefix, '', Array ($category_id)));
 			$this->Application->SetVar('m_cat_id', $new_category_id);
 
-			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
-
 			// 2. assign supplemental items to current category to new category
 			$paste_ids = $this->getCategoryItems($category_id, false);
 
 			foreach ($paste_ids as $item_prefix => $resource_ids) {
 				if ( !$item_prefix ) {
 					// not ItemPrefix filled -> old categoryitem linking
 					continue;
 				}
 
 				$item_object = $this->Application->recallObject($item_prefix . '.-item', null, Array ('skip_autoload' => true));
 				/* @var $item_object kCatDBItem */
 
 				foreach ($resource_ids as $item_resource_id) {
 					$item_object->Load($item_resource_id, 'ResourceId');
 					$item_object->assignToCategory($new_category_id, false);
 				}
 			}
 
 			// 3. clone items that have current category as primary
 			$paste_ids = $this->getCategoryItems($category_id, true);
 
 			foreach ($paste_ids as $item_prefix => $resource_ids) {
 				if ( !$item_prefix ) {
 					// not ItemPrefix filled -> old categoryitem linking
 					continue;
 				}
 
 				// 2. clone items from current category (for each prefix separately)
 				$item_ids = $this->GetItemIDs($item_prefix, $resource_ids);
 				$temp_handler->BuildTables($item_prefix, $item_ids);
 				$temp_handler->CloneItems($item_prefix, '', $item_ids);
 			}
 
+			$config = $this->Application->getUnitConfig($prefix);
+
 			// 4. do same stuff for each subcategory
-			$sql = 'SELECT ' . $id_field . '
-					FROM ' . $table_name . '
+			$sql = 'SELECT ' . $config->getIDField() . '
+					FROM ' . $config->getTableName() . '
 					WHERE ParentId = ' . $category_id;
 
 			$sub_categories = $this->Conn->GetCol($sql);
 			if ( $sub_categories ) {
 				foreach ($sub_categories as $sub_category_id) {
 					$this->PasteCategory($sub_category_id, $prefix);
 				}
 			}
 
 			$this->Application->SetVar('m_cat_id', $backup_category_id);
 		}
 
 		/**
 		 * Returns grouped category items
 		 *
 		 * @param int $category_id
 		 * @param bool $item_primary_category
 		 * @return Array
 		 */
 		function getCategoryItems($category_id, $item_primary_category = true)
 		{
-			$ci_table = $this->Application->getUnitOption('ci', 'TableName');
+			$ci_table = $this->Application->getUnitConfig('ci')->getTableName();
 
 			$sql = 'SELECT ItemPrefix, ItemResourceId
 					FROM '.$ci_table.'
 					WHERE (CategoryId = '.$category_id.') AND (PrimaryCat = '.($item_primary_category ? 1 : 0).')';
 			$category_items = $this->Conn->GetCol($sql, 'ItemResourceId');
 
 			$item_ids = Array();
 			foreach ($category_items as $resource_id => $item_prefix) {
 				$item_ids[$item_prefix][] = $resource_id;
 			}
 			return $item_ids;
 		}
 
 		/**
 		 * Returns parent path for given category
 		 *
 		 * @param int $category_id
 		 * @return Array
 		 */
 		function _getParentPath($category_id)
 		{
 			static $cache = Array ();
 
 			if (!array_key_exists($category_id, $cache)) {
 				$sql = 'SELECT ParentPath
 						FROM ' . TABLE_PREFIX . 'Categories
 						WHERE CategoryId = ' . $category_id;
 				$cache[$category_id] = $this->Conn->GetOne($sql);
 			}
 
 			return $cache[$category_id];
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/modules_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/modules_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/modules_helper.php	(revision 15698)
@@ -1,503 +1,503 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kModulesHelper extends kHelper {
 
 		/**
 		 * Identifies new module, that isn't installed yet
 		 */
 		const NOT_INSTALLED = 1;
 
 		/**
 		 * Identifies installed module
 		 */
 		const INSTALLED = 2;
 
 		/**
 		 * Identifies both installed & new modules
 		 */
 		const ANY = 3;
 
 		function getWhereClause()
 		{
 			$where_clause = Array('Loaded = 1');
 
 			if (!$this->Application->isAdmin) {
 				// no license checks on front-end
 				return implode(' AND ', $where_clause);
 			}
 
 			$modules = $this->_GetModules();
 			if ($modules) {
 				foreach ($modules as $module_index => $module) {
 					$modules[$module_index] = $this->Conn->qstr($module);
 				}
 				$where_clause[] = 'Name IN ('.implode(',', $modules).')';
 			}
 
 			return implode(' AND ', $where_clause);
 		}
 
 		function _EnableCookieSID()
 		{
 			$session = $this->Application->recallObject('Session');
 			/* @var $session Session */
 
 			return $session->CookiesEnabled;
 		}
 
 		function _IsSpider($UserAgent)
 		{
 			global $robots;
 			$lines = file(FULL_PATH.'/robots_list.txt');
 
 			if (!is_array($robots)) {
 				$robots = Array();
 				for($i = 0; $i < count($lines); $i++) {
 					$l = $lines[$i];
 					$p = explode("\t", $l, 3);
 					$robots[] = $p[2];
 		    	}
 		  	}
 			return in_array($UserAgent, $robots);
 		}
 
 		function _MatchIp($ip1, $ip2)
 		{
 			$matched = TRUE;
 
 			$ip = explode('.', $ip1);
 			$MatchIp = explode('.', $ip2);
 			for ($i = 0; $i < count($ip); $i++) {
 				if($i == count($MatchIp)) break;
 				if (trim($ip[$i]) != trim($MatchIp[$i]) || trim($ip[$i]) == '*') {
 					$matched = FALSE;
 					break;
 				}
 			}
 			return $matched;
 		}
 
 		function _IpAccess($IpAddress, $AllowList, $DenyList)
 		{
 			$allowed = explode(',', $AllowList);
 			$denied = explode(',', $DenyList);
 
 			$MatchAllowed = FALSE;
 			for ($x = 0; $x < count($allowed); $x++) {
 				$ip = explode('.', $allowed[$x]);
 
 				$MatchAllowed = $this->_MatchIp($IpAddress, $allowed[$x]);
 				if ($MatchAllowed)
 				break;
 			}
 			$MatchDenied = FALSE;
 			for ($x = 0; $x < count($denied); $x++) {
 				$ip = explode('.', $denied[$x]);
 
 				$MatchDenied = $this->_MatchIp($IpAddress, $denied[$x]);
 				if ($MatchDenied)
 				break;
 			}
 
 			$Result = (($MatchAllowed && !$MatchDenied) || (!$MatchAllowed && !$MatchDenied) ||
 		  			  ($MatchAllowed && $MatchDenied));
 			return $Result;
 		}
 
 		/**
 		 * Leaves only domain part from hostname (e.g. extract "intechnic.lv" from "test.intechnic.lv")
 		 * Used for admin login license check
 		 *
 		 * @param string $d
 		 * @return string
 		 */
 		function _StripDomainHost($d)
 		{
 			$IsIp = false;
 			$dotcount = substr_count($d, '.');
 			if ($dotcount == 3) {
 				$IsIp = true;
 				for ($x = 0; $x < strlen($d); $x++) {
 					if (!is_numeric(substr($d, $x, 1)) && substr($d, $x, 1) != '.')
 					{
 						$IsIp = false;
 						break;
 					}
 				}
 			}
 
 			if ($dotcount > 1 && !$IsIp) {
 				$p = explode('.', $d);
 				$ret = $p[count($p) - 2].'.'.$p[count($p) - 1];
 			}
 			else {
 				$ret = $d;
 			}
 			return $ret;
 		}
 
 		/**
 		 * When logging into admin then check only last 2 parts of host name VS domain in license
 		 *
 		 * @param string $user_domain
 		 * @param string $license_domain
 		 * @return int
 		 */
 		function _CheckDomain($user_domain, $license_domain)
 		{
 			if ($this->Application->isAdmin) {
 				$user_domain = $this->_StripDomainHost($user_domain);
 				return preg_match('/(.*)'.preg_quote($user_domain, '/').'$/', $license_domain);
 			}
 			else {
 				return preg_match('/(.*)'.preg_quote($license_domain, '/').'$/', $user_domain);
 			}
 		}
 
 		/**
 		 * Returns modules list, that are in license
 		 *
 		 * @return Array
 		 */
 		function _GetModules()
 		{
 		    static $modules = null;
 
 		    if (isset($modules)) {
 		    	return $modules;
 		    }
 
 		    $modules = Array();
 		    $vars = kUtil::getConfigVars();
 		    $license = array_key_exists('License', $vars) ? base64_decode($vars['License']) : false;
 		    if ($license) {
 			    list ( , , $i_Keys) = $this->_ParseLicense($license);
 			    $domain = $this->_GetDomain($vars);
 			    if (!$this->_IsLocalSite($domain)) {
 			        for ($x = 0; $x < count($i_Keys); $x++) {
 			            $key = $i_Keys[$x];
 			            if ($this->_CheckDomain($domain, $key['domain'])) {
 			            	// used hostname is subdomain or matches domain from license
 			            	$modules = explode(',', $key['mod']);
 			            }
 			        }
 			    }
 			    else {
 			    	// all already installed modules are licensed for localhost
 			        $modules = array_keys($this->Application->ModuleInfo);
 			    }
 		    }
 
 		    // all modules starting from "in-" doesn't require license
 		    $base_modules = Array ('Core', 'In-Portal', 'Custom');
 		    $modules = array_merge($modules, $base_modules, $this->_getFreeModules($vars));
 		    $modules = array_unique( array_map('strtolower', $modules) );
 
 		    return $modules;
 		}
 
 		/**
 		 * Get all modules, that don't require licensing
 		 *
 		 * @param Array $vars
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getFreeModules($vars)
 		{
 			$domain = $this->_GetDomain($vars);
 			$modules = array_map('strtolower', $this->getModules());
 
 			if ( !$this->_IsLocalSite($domain) ) {
 				return array_diff($modules, Array ('in-commerce', 'in-auction'));
 			}
 
 			return $modules;
 		}
 
 		/**
 		 * Allows to determine if module is licensed
 		 *
 		 * @param string $name
 		 * @return bool
 		 */
 		function _ModuleLicensed($name)
 		{
 		    $modules = $this->_GetModules();
 			return in_array($name, $modules);
 		}
 
 		/**
 		 * Returns domain from licences (and direct in case of install script)
 		 *
 		 * @param Array $vars
 		 * @return string
 		 */
 		function _GetDomain($vars)
 		{
 			return isset($vars['Domain']) ? $vars['Domain'] : SERVER_NAME;
 		}
 
 		function _keyED($txt, $encrypt_key)
 		{
 			$encrypt_key = md5($encrypt_key);
 			$ctr = 0;
 			$tmp = '';
 			for ($i = 0; $i < strlen($txt); $i++) {
 				if ($ctr == strlen($encrypt_key)) $ctr = 0;
 				$tmp .= substr($txt, $i, 1) ^ substr($encrypt_key, $ctr, 1);
 				$ctr++;
 			}
 			return $tmp;
 		}
 
 
 		function _decrypt($txt, $key)
 		{
 			$txt = $this->_keyED($txt,$key);
 			$tmp = '';
 			for ($i = 0; $i < strlen($txt); $i++) {
 				$md5 = substr($txt, $i, 1);
 				$i++;
 				$tmp .= (substr($txt, $i, 1) ^ $md5);
 			}
 			return $tmp;
 		}
 
 		function LoadFromRemote()
 		{
 		    return '';
 		}
 
 		function DLid()
 		{
 		    die($GLOBALS['lid']."\n");
 		}
 
 		function _LoadLicense($LoadRemote = false)
 		{
 		    $f = FULL_PATH.'/intechnic.php';
 		    if ($this->_falseIsLocalSite($f)) $ret = true;
 		    if (file_exists($f)) {
 		        $contents = file($f);
 		        $data = base64_decode($contents[1]);
 		    }
 		    else {
 		        if ($LoadRemote) return $LoadFromRemote;
 		    }
 		   return $data;
 		}
 
 		function _VerifyKey($domain, $k)
 		{
 		    $key = md5($domain);
 		    $lkey = substr($key, 0, strlen($key) / 2);
 		    $rkey = substr($key, strlen($key) / 2);
 		    $r = $rkey.$lkey;
 		    if ($k == $r) return true;
 		    return false;
 		}
 
 		function _ParseLicense($txt)
 		{
 //			global $i_User, $i_Pswd, $i_Keys;
 
 			if (!$this->_falseIsLocalSite($txt)) {
 				$nah = false;
 			}
 
 			$data = $this->_decrypt($txt, 'beagle');
 			$i_User = $i_Pswd = '';
 			$i_Keys = Array();
 			$lines = explode("\n", $data);
 			for ($x = 0; $x < count($lines); $x++) {
 				$l = $lines[$x];
 				$p = explode('=', $l, 2);
 				switch($p[0]) {
 					case 'Username':
 						$i_User = $p[1];
 						break;
 
 					case 'UserPass':
 						$i_Pswd = $p[1];
 						break;
 
 					default:
 						if (substr($p[0], 0, 3) == 'key') {
 							$parts = explode('|', $p[1]);
 							if ($this->_VerifyKey($parts[0], $parts[1])) {
 								unset($K);
 								$k['domain'] = $parts[0];
 								$k['key'] = $parts[1];
 								$k['desc'] = $parts[2];
 								$k['mod'] = $parts[3];
 								$i_Keys[] = $k;
 							}
 						}
 						break;
 				}
 			}
 
 			return Array ($i_User, $i_Pswd, $i_Keys);
 		}
 
 		function _GetObscureValue($i)
 		{
 			if ($i == 'x') return 0254; $z = '';
 			if ($i == 'z') return 0x7F.'.';
 			if ($i == 'c') return '--code--';
 			if ($i >= 5 && $i < 7) return $this->_GetObscureValue($z)*$this->_GetObscureValue('e');
 			if ($i > 30) return Array(0x6c,0x6f,0x63,0x61,0x6c,0x68,0x6f,0x73,0x74);
 			if ($i > 20) return 99;
 			if ($i > 10) return '.'.($this->_GetObscureValue(6.5)+1);
 			if ($i == 'a') return 0xa;
 
 			return 0;
 		}
 
 		function _Chr($val)
 		{
 			$x = $this->_GetObscureValue(25);
 			$f = chr($x).chr($x+5).chr($x+15);
 			return $f($val);
 		}
 
 		function _IsLocalSite($domain)
 		{
 		    $ee = $this->_GetObscureValue(35); $yy = '';
 			foreach ($ee as $e) $yy .= $this->_Chr($e);
 		    $localb = FALSE;
 		    if(substr($domain,0,3)==$this->_GetObscureValue('x'))
 		    {
 		        $b = substr($domain,0,6);
 		        $p = explode(".",$domain);
 		        $subnet = $p[1];
 		        if($p[1]>15 && $p[1]<32)
 		            $localb=TRUE;
 		    }
 		    $zz = $this->_GetObscureValue('z').$this->_GetObscureValue(5).'.'.(int)$this->_GetObscureValue(7).$this->_GetObscureValue(12);
 		    $ff = $this->_GetObscureValue('z')+65;
 		    $hh = $ff-0x18;
 		    if($domain==$yy || $domain==$zz || substr($domain,0,7)==$ff.$this->_Chr(46).$hh ||
 		       substr($domain,0,3)==$this->_GetObscureValue('a').$this->_Chr(46) || $localb || strpos($domain,".")==0)
 		    {
 		        return TRUE;
 		    }
 		    return FALSE;
 		}
 
 		function _falseIsLocalSite($domain)
 		{
 		    $localb = FALSE;
 		    if(substr($domain,0,3)=="172")
 		    {
 		        $b = substr($domain,0,6);
 		        $p = explode(".",$domain);
 		        $subnet = $p[1];
 		        if($p[1]>15 && $p[1]<32)
 		            $localb=TRUE;
 		    }
 		    if($domain=="localhost" || $domain=="127.0.0.1" || substr($domain,0,7)=="192.168" ||
 		       substr($domain,0,3)=="10." || $localb || strpos($domain,".")==0)
 		    {
 		        return TRUE;
 		    }
 		    return FALSE;
 		}
 
 		function verifyLicense($license_hash)
 		{
 			$license_hash = base64_decode($license_hash);
 			list ($license_user, $license_password, ) = $this->_ParseLicense($license_hash);
 			return strlen($license_user) && strlen($license_password);
 		}
 
 		function moduleInstalled($module_name)
 		{
 			static $modules = null;
 
 			if ( is_null($modules) ) {
 				$sql = 'SELECT LOWER(Name)
-						FROM ' . $this->Application->getUnitOption('mod', 'TableName');
+						FROM ' . $this->Application->getUnitConfig('mod')->getTableName();
 				$modules = $this->Conn->GetCol($sql);
 			}
 
 			if ( $module_name == 'kernel' ) {
 				$module_name = 'in-portal';
 			}
 
 			return in_array(strtolower($module_name), $modules);
 		}
 
 		/**
 		 * Returns list of matching modules
 		 *
 		 * @param int $module_type
 		 * @return Array
 		 * @access public
 		 */
 		public function getModules($module_type = self::ANY)
 		{
 			$modules = Array ();
 
 			try {
 				$iterator = new DirectoryIterator(MODULES_PATH);
 				/* @var $file_info DirectoryIterator */
 			}
 			catch (UnexpectedValueException $e) {
 				return $modules;
 			}
 
 			foreach ($iterator as $file_info) {
 				$file_path = $file_info->getPathname();
 
 				if ( $file_info->isDir() && !$file_info->isDot() && $this->isInPortalModule($file_path) ) {
 					$install_order = trim( file_get_contents($file_path . '/install/install_order.txt') );
 					$modules[$install_order] = $file_info->getFilename();
 				}
 			}
 
 			// allows to control module install order
 			ksort($modules, SORT_NUMERIC);
 
 			if ( $module_type == self::ANY ) {
 				return $modules;
 			}
 
 			foreach ($modules as $install_order => $module_name) {
 				$installed = $this->moduleInstalled($module_name);
 
 				if ( ($module_type == self::INSTALLED && !$installed) || ($module_type == self::NOT_INSTALLED && $installed) ) {
 					unset($modules[$install_order]);
 				}
 			}
 
 			return $modules;
 		}
 
 		/**
 		 * Checks, that given folder is In-Portal module's root folder
 		 *
 		 * @param string $folder_path
 		 * @return bool
 		 * @access public
 		 */
 		public static function isInPortalModule($folder_path)
 		{
 			return file_exists($folder_path . '/install.php') && file_exists($folder_path . '/install/install_schema.sql');
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/permissions_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/permissions_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/permissions_helper.php	(revision 15698)
@@ -1,834 +1,834 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kPermissionsHelper extends kHelper {
 
 		/**
 		 * Current set of permissions for group being edited
 		 *
 		 * @var Array
 		 */
 		var $Permissions = Array();
 
 		function LoadPermissions($group_id, $cat_id, $type = 1, $prefix = '')
 		{
-			$perm_table = $this->Application->getUnitOption('perm', 'TableName');
+			$perm_table = $this->Application->getUnitConfig('perm')->getTableName();
 			$perm_table = $this->Application->GetTempName($perm_table, 'prefix:'.$prefix);
 			$sql = 'SELECT *
 					FROM '.$perm_table.'
 					WHERE (GroupId = '.$group_id.') AND (CatId = '.$cat_id.') AND (Type = '.$type.')';
 			$permissions = $this->Conn->Query($sql, 'Permission');
 
 			$this->Permissions = Array();
 			foreach ($permissions as $perm_name => $perm_options) {
 				$perm_record['value'] = $perm_options['PermissionValue'];
 				$perm_record['id'] = $perm_options['PermissionId'];
 				$this->Permissions[$perm_name] = $perm_record;
 			}
 		}
 
 		function getPermissionValue($perm_name)
 		{
 			 return isset($this->Permissions[$perm_name]) ? $this->Permissions[$perm_name]['value'] : 0;
 		}
 
 		function getPermissionID($perm_name)
 		{
 			return isset($this->Permissions[$perm_name]) ? $this->Permissions[$perm_name]['id'] : 0;
 		}
 
 		/**
 		 * This is old permission like ADMIN or LOGIN
 		 *
 		 * @param string $section_name
 		 * @param string $perm_name
 		 * @return bool
 		 */
 		function isOldPermission($section_name, $perm_name)
 		{
 			return $section_name == 'in-portal:root' && $perm_name != 'view';
 		}
 
 		/**
 		 * Returns permission names to check based on event name and item prefix (main item or subitem)
 		 *
 		 * @param kEvent $event
 		 * @param Array $perm_mapping
 		 * @return Array
 		 */
 		function getPermissionByEvent($event, $perm_mapping)
 		{
 			$top_prefix = $event->getEventParam('top_prefix');
 
 			$prefix_type = ($top_prefix == $event->Prefix) ? 'self' : 'subitem';
 			$perm_mapping = getArrayValue($perm_mapping, $event->Name);
 
 			if (!$perm_mapping[$prefix_type]) {
 				throw new Exception('Permission mappings not defined for event <strong>' . $top_prefix . ' <- ' . $event->Prefix . ':' . $event->Name . '</strong>');
 			}
 
 			if ($perm_mapping[$prefix_type] === true) {
 				// event is defined in mapping but is not checked by permissions
 				return true;
 			}
 
 			return explode('|', $perm_mapping[$prefix_type]);
 		}
 
 		/**
 		 * Common event permission checking method
 		 *
 		 * @param kEvent $event
 		 * @param Array $perm_mapping
 		 * @return bool
 		 */
 		function CheckEventPermission($event, $perm_mapping)
 		{
 			$section = $event->getSection();
 			if (preg_match('/^CATEGORY:(.*)/', $section)) {
 				return $this->CheckEventCategoryPermission($event, $perm_mapping);
 			}
 
 			$top_prefix = $event->getEventParam('top_prefix');
 			$check_perms = $this->getPermissionByEvent($event, $perm_mapping);
 
 			if ($check_perms === true) {
 				// event is defined in mapping but is not checked by permissions
 				return true;
 			}
 
 			$perm_status = false;
 			foreach ($check_perms as $perm_name) {
 				// check if at least one of required permissions is set
 				if ($perm_name == 'debug' && $this->Application->isDebugMode(false)) {
 					// universal "debug" permission
 					return true;
 				}
 
 				$perm_name = $section.'.'.$perm_name;
 				$perm_status = $this->CheckPermission($perm_name, 1);
 				if (($perm_name == $section.'.add') && $perm_status && ($top_prefix == $event->Prefix)) {
 					// main item, add permission allowed, but ID is > 0, then deny permission
 					// how to get id here
 				}
 
 				if ($perm_status) {
 					return $perm_status;
 				}
 			}
 
 			return $this->finalizePermissionCheck($event, $perm_status);
 		}
 
 		/**
 		 * Returns owner + primary category for each item (used for permission checking)
 		 *
 		 * @param string $prefix
 		 * @param string $ids
 		 * @param bool $temp_mode
 		 * @return Array
 		 * @author Alex
 		 */
 		function GetCategoryItemData($prefix, $ids, $temp_mode = false)
 		{
 			if (is_array($ids)) {
 				$ids = implode(',', $ids);
 			}
-			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
-			$ci_table = $this->Application->getUnitOption('ci', 'TableName');
+
+			$config = $this->Application->getUnitConfig($prefix);
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
+			$ci_table = $this->Application->getUnitConfig('ci')->getTableName();
 
 			if ($temp_mode) {
 				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $prefix);
 				$ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $prefix);
 			}
 
-			$owner_field = $this->Application->getUnitOption($prefix, 'OwnerField');
-			if (!$owner_field) {
-				$owner_field = 'CreatedById';
-			}
+			$owner_field = $config->getOwnerField('CreatedById');
 
 			$sql = 'SELECT item_table.'.$id_field.', item_table.'.$owner_field.' AS CreatedById, ci.CategoryId
 					FROM '.$table_name.' item_table
 					LEFT JOIN '.$ci_table.' ci ON ci.ItemResourceId = item_table.ResourceId
 					WHERE item_table.'.$id_field.' IN ('.$ids.') AND (ci.PrimaryCat = 1)';
 			return $this->Conn->Query($sql, $id_field);
 		}
 
 		/**
 		 * Check category-based permissions for category items
 		 *
 		 * @param kEvent $event
 		 * @param Array $event_perm_mapping
 		 * @return bool
 		 */
 		function _frontCheckEventCategoryPermission($event, $event_perm_mapping)
 		{
 			// mapping between specific permissions and common permissions
 			static $perm_mapping = Array(
 				'add' => 'ADD', 'add.pending' => 'ADD.PENDING', 'edit' => 'MODIFY',
 				'edit.pending' => 'MODIFY.PENDING', 'delete' => 'DELETE', 'view' => 'VIEW',
 				'debug' => 'DEBUG'
 			);
 
 			$top_prefix = $event->getEventParam('top_prefix');
 			$event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler');
 			/* @var $event_handler kCatDBEventHandler */
 
 			$raise_warnings = $event->getEventParam('raise_warnings');
 			$event->setEventParam('raise_warnings', 0);
 			if ( $event->Prefix != $top_prefix ) {
 				$top_event = new kEvent($top_prefix . ':' . $event->Name);
 				$id = $event_handler->getPassedID($top_event);
 			}
 			else {
 				$id = $event_handler->getPassedID($event);
 			}
 			$event->setEventParam('raise_warnings', $raise_warnings);
 
 			$owner_id = USER_ROOT; // owner is root if not detected
 			if ( !$id ) {
 				// item being created -> check by current (before editing started, saved in OnPreCreate event) category permissions
 				// note: category in session is placed on catalog data import start
 				$category_id = $this->Application->isAdmin ? $this->Application->RecallVar('m_cat_id') : $this->Application->GetVar('m_cat_id');
 			}
 			elseif ( $top_prefix == 'c' || $top_prefix == 'st' ) {
 				$category_id = $id;
 			}
 			else {
 				// item being edited -> check by it's primary category permissions
 				$items_info = $this->GetCategoryItemData($top_prefix, $id);
 
 				if ( $items_info ) {
 					$category_id = $items_info[$id]['CategoryId'];
 					$owner_id = $items_info[$id]['CreatedById'];
 				}
 				else {
 					// item wasn't found in database
 					$category_id = $this->Application->GetVar('m_cat_id');
 				}
 			}
 
 			// specific permission check for pending & owner permissions: begin
 			$uploader_events = Array ('OnUploadFile', 'OnDeleteFile', 'OnViewFile');
 			if ( in_array($event->Name, $uploader_events) ) {
 				// don't recall target object during uploader-related, because OnItemLoad will use incorrect
 				// $user_id in Firefox (during Flash problems session will be used from Internet Exploere)
 				$new_item = false;
 			}
 			else {
 				$new_item = $this->Application->isAdminUser && $event_handler->isNewItemCreate($event) ? true : false;
 				$check_status = $this->checkCombinedPermissions($event, $owner_id, (int)$category_id, $new_item);
 			}
 
 			if ( isset($check_status) ) {
 				return $this->finalizePermissionCheck($event, $check_status);
 			}
 			// specific permission check for pending & owner permissions: end
 
 			$perm_status = false;
 			$check_perms = $this->getPermissionByEvent($event, $event_perm_mapping);
 
 			if ( $check_perms === true ) {
 				// event is defined in mapping but is not checked by permissions
 				return true;
 			}
 
-			$item_prefix = $this->Application->getUnitOption($top_prefix, 'PermItemPrefix');
+			$item_prefix = $this->Application->getUnitConfig($top_prefix)->getPermItemPrefix();
+
 			foreach ($check_perms as $perm_name) {
 				// check if at least one of required permissions is set
 				if ( !isset($perm_mapping[$perm_name]) ) {
 					// not mapped permission (e.g. advanced:approve) -> skip
 					continue;
 				}
 
 				if ( $perm_name == 'debug' && $this->Application->isDebugMode(false) ) {
 					// universal "debug" permission
 					return true;
 				}
 
 				$perm_name = $item_prefix . '.' . $perm_mapping[$perm_name];
 				$perm_status = $this->CheckPermission($perm_name, 0, (int)$category_id);
 
 				if ( $perm_status ) {
 					return $perm_status;
 				}
 			}
 
 			return $this->finalizePermissionCheck($event, $perm_status);
 		}
 
 		/**
 		 * Finalizes permission checking (with additional debug output, when in debug mode)
 		 *
 		 * @param kEvent $event
 		 * @param bool $perm_status
 		 * @return bool
 		 */
 		function finalizePermissionCheck($event, $perm_status)
 		{
 			if (!$perm_status) {
 				if (MOD_REWRITE) {
 //					$event->SetRedirectParam('m_cat_id', 0); // category means nothing on admin login screen
 					$event->SetRedirectParam('next_template', urlencode('external:' . $_SERVER['REQUEST_URI']));
 				}
 				else {
 					$event->SetRedirectParam('next_template', $this->Application->GetVar('t'));
 				}
 
 				if ($this->Application->isDebugMode()) {
 					// for debugging purposes
 					$event->SetRedirectParam('section', $event->getSection());
 					$event->SetRedirectParam('main_prefix', $event->getEventParam('top_prefix'));
 					$event->SetRedirectParam('event_name', $event->Name);
 				}
 
 				$event->status = kEvent::erPERM_FAIL;
 			}
 
 			return $perm_status;
 		}
 
 		/**
 		 * Allows to check combined permissions (*.owner, *.pending) for add/modify/delete operations from admin & front-end
 		 *
 		 * @param kEvent $event
 		 * @param int $owner_id
 		 * @param int $category_id
 		 * @param bool $new_item
 		 * @return mixed
 		 */
 		function checkCombinedPermissions($event, $owner_id, $category_id, $new_item = false)
 		{
 			$ret = null; // true/false when used, null when not used
 			$top_prefix = $event->getEventParam('top_prefix');
 
 			// check admin permission
 			if (substr($event->Name, 0, 9) == 'OnPreSave') {
 				if ($new_item) {
 					$ret = $this->AddCheckPermission($category_id, $top_prefix);
 				}
 				else {
 					// add & modify because $new_item is false, when item is aready created & then saved in temp table (even with 0 id)
 					$ret =	$this->AddCheckPermission($category_id, $top_prefix) ||
 							$this->ModifyCheckPermission($owner_id, $category_id, $top_prefix);
 				}
 			}
 
 			// check front-end permissions
 			switch ($event->Name) {
 				case 'OnCreate':
 					$ret = $this->AddCheckPermission($category_id, $top_prefix);
 					break;
 
 				case 'OnUpdate':
 					$ret = $this->ModifyCheckPermission($owner_id, $category_id, $top_prefix);
 					break;
 
 				case 'OnDelete':
 				case 'OnMassDelete':
 					$ret = $this->DeleteCheckPermission($owner_id, $category_id, $top_prefix);
 					break;
 			}
 
 			if ($ret === 0) {
 				// permission check failed (user has no permission)
 				$event->status = kEvent::erPERM_FAIL;
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Simplified permission check for category items, when adding/editing them from advanced view.
 		 *
 		 * @param kEvent $event
 		 * @param Array $event_perm_mapping
 		 * @return mixed
 		 */
 		function CheckEventCategoryPermission($event, $event_perm_mapping)
 		{
 			if (!$this->Application->isAdmin) {
 				// check front-end permission by old scheme
 				return $this->_frontCheckEventCategoryPermission($event, $event_perm_mapping);
 			}
 
 			if (substr($event->Name, 0, 9) == 'OnPreSave') {
 				// check separately, because permission mapping is not defined for OnPreSave* events
 				$check_perms = Array ('add', 'edit');
 			}
 			else {
 				$check_perms = $this->getPermissionByEvent($event, $event_perm_mapping);
 			}
 
 			if ($check_perms === true) {
 				// event is defined in mapping but is not checked by permissions
 				return true;
 			}
 
 			// 1. most of events does require admin login only
 			$perm_status = $this->Application->isAdminUser;
 
 			// 2. in case, when event require more, then "view" right, then restrict it to temporary tables only
 			if (!in_array('view', $check_perms)) {
 				$perm_status = $perm_status && $this->Application->IsTempMode($event->Prefix, $event->Special);
 			}
 
 			return $this->finalizePermissionCheck($event, $perm_status);
 		}
 
 		function TagPermissionCheck($params, $is_owner = false)
 		{
 			$perm_prefix = getArrayValue($params, 'perm_prefix');
 			$perm_event = getArrayValue($params, 'perm_event');
 			$permission_groups = getArrayValue($params, 'permissions');
 			$check_admin = isset($params['admin']) && $params['admin'];
 
 			if ($permission_groups && !$perm_event) {
 				// check permissions by permission names in current category
 				$permission_groups = explode('|', $permission_groups);
 				$group_has_permission = false;
 
 				$perm_category = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id');
 
 				if ($perm_prefix) {
 					// use primary category of item with id from {perm_prefix}_id as base for permission checking
 					$perm_category = $this->getPrimaryCategory($perm_prefix);
 				}
 
 				$is_system = isset($params['system']) && $params['system'] ? 1 : 0;
 				foreach ($permission_groups as $permission_group) {
 					$has_permission = true;
 					$permissions = explode(',', $permission_group);
 
 					if ( $check_admin ) {
 						foreach ($permissions as $permission) {
 							$owner_checked = (strpos($permission, '.OWNER.') !== false) ? $is_owner : true;
 							$has_permission = $has_permission && $this->CheckAdminPermission($permission, $is_system, $perm_category) && $owner_checked;
 						}
 					}
 					else {
 						foreach ($permissions as $permission) {
 							$owner_checked = (strpos($permission, '.OWNER.') !== false) ? $is_owner : true;
 							$has_permission = $has_permission && $this->CheckPermission($permission, $is_system, $perm_category) && $owner_checked;
 						}
 					}
 
 					$group_has_permission = $group_has_permission || $has_permission;
 
 					if ($group_has_permission) {
 						return true;
 					}
 				}
 				return false;
 			}
 			elseif ($perm_event) {
 				// check permission by event name
 				list ($prefix, ) = explode(':', $perm_event);
 
 				$event_handler = $this->Application->recallObject($prefix . '_EventHandler');
 				/* @var $event_handler kEventHandler */
 
 				return $event_handler->CheckPermission( new kEvent($perm_event) );
 			}
 
 			return true;
 		}
 
 		/**
 		 * Returns item's primary category (get item_id from request)
 		 *
 		 * @param string $prefix
 		 * @return int
 		 */
 		function getPrimaryCategory($prefix)
 		{
-			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
 			$id = $this->Application->GetVar($prefix.'_id');
 
 			if (!$id) {
 				return $this->Application->GetVar('m_cat_id');
 			}
 
+			$config = $this->Application->getUnitConfig($prefix);
+
 			$sql = 'SELECT ResourceId
-					FROM '.$table_name.'
-					WHERE '.$id_field.' = '.(int)$id;
+					FROM '. $config->getTableName() .'
+					WHERE '. $config->getIDField() .' = '.(int)$id;
 			$resource_id = $this->Conn->GetOne($sql);
 
 			$sql = 'SELECT CategoryId
-					FROM '.$this->Application->getUnitOption('ci', 'TableName').'
+					FROM '.$this->Application->getUnitConfig('ci')->getTableName().'
 					WHERE ItemResourceId = '.$resource_id.' AND PrimaryCat = 1';
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Returns no permission template to redirect to
 		 *
 		 * @param Array $params
 		 * @return Array
 		 */
 		function getPermissionTemplate($params)
 		{
 			$t = $this->Application->GetVar('t');
 			$next_t = getArrayValue($params, 'next_template');
 
 			if ( $next_t ) {
 				$t = $next_t;
 			}
 
 			$redirect_params = $this->Application->HttpQuery->getRedirectParams(true);
 
 			if (array_key_exists('pass_category', $params)) {
 				$redirect_params['pass_category'] = $params['pass_cateogry'];
 			}
 
 			if (MOD_REWRITE) {
 				// TODO: $next_t variable is ignored !!! (is anyone using m_RequireLogin tag with "next_template" parameter?)
 				$redirect_params = Array (
 					'm_cat_id' => 0, // category means nothing on admin login screen
 					'next_template' => urlencode('external:' . $_SERVER['REQUEST_URI']),
 				);
 			}
 			else {
 				$redirect_params['next_template'] = $t;
 			}
 
 			if ($this->Application->isAdmin) {
 				$redirect_params['m_wid'] = ''; // remove wid, otherwise parent window may add wid to its name breaking all the frameset (for <a> targets)
 				$redirect_params['pass'] = 'm'; // don't pass any other (except "m") prefixes to admin login template
 			}
 
 			if (!$this->Application->LoggedIn()) {
 				$redirect_template = array_key_exists('login_template', $params) ? $params['login_template'] : '';
 
 				if (!$redirect_template && $this->Application->isAdmin) {
 					$redirect_template = 'login';
 				}
 			}
 			else {
 				if (array_key_exists('no_permissions_template', $params)) {
 					$redirect_template = $params['no_permissions_template'];
 				}
 				else {
 					$redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate');
 				}
 
 				if ($this->Application->isDebugMode()) {
 					$redirect_params['from_template'] = 1;
 					$redirect_params['perms'] = $params[ isset($params['permissions']) ? 'permissions' : 'perm_event' ];
 				}
 			}
 
 			if (isset($params['index_file']) && $params['index_file']) {
 				$redirect_params['index_file'] = $params['index_file'];
 			}
 
 			return Array ($redirect_template, $redirect_params);
 		}
 
 		/**
 		 * Check current user permissions based on it's group permissions in specified category (for non-system permissions) or just checks if system permission is set
 		 *
 		 * @param string $name permission name
 		 * @param int $cat_id category id, current used if not specified
 		 * @param int $type permission type {1 - system, 0 - per category}
 		 * @return int
 		 */
 		function CheckPermission($name, $type = 1, $cat_id = null)
 		{
 			$user_id = $this->Application->RecallVar('user_id');
 			return $this->CheckUserPermission($user_id, $name, $type, $cat_id);
 		}
 
 		/**
 		 * Check current admin permissions (when called from Front-End) based on it's group permissions in specified category (for non-system permissions) or just checks if system permission is set
 		 *
 		 * @param string $name permission name
 		 * @param int $cat_id category id, current used if not specified
 		 * @param int $type permission type {1 - system, 0 - per category}
 		 * @return int
 		 */
 		function CheckAdminPermission($name, $type = 1, $cat_id = null)
 		{
 			if ( $this->Application->isAdmin ) {
 				return $this->CheckPermission($name, $type, $cat_id);
 			}
 
 			$user_id = $this->Application->RecallVar('admin_user_id');
 			return $this->CheckUserPermission($user_id, $name, $type, $cat_id);
 		}
 
 		function CheckUserPermission($user_id, $name, $type = 1, $cat_id = null)
 		{
 			$user_id = (int)$user_id;
 
 			if ( $user_id == USER_ROOT ) {
 				// "root" is allowed anywhere
 				return substr($name, -5) == '.deny' || $name == 'SYSTEM_ACCESS.READONLY' ? 0 : 1;
 			}
 
 			if ( !isset($cat_id) ) {
 				$cat_id = $this->Application->GetVar('m_cat_id');
 			}
 
 			if ( $type == 1 ) {
 				// "system" permission are always checked per "Home" category (ID = 0)
 				$cat_id = 0;
 			}
 			elseif ( "$cat_id" === "0" ) {
 				$cat_id = $this->Application->getBaseCategory();
 			}
 
 			// perm cache is build only based on records in db, that's why if permission is not explicitly denied, then
 			// that (perm cache creator) code thinks that it is allowed & adds corresponding record and code below will
 			// return incorrect results
 			if ( $user_id == $this->Application->RecallVar('user_id') ) {
 				$groups = $this->Application->RecallVar('UserGroups');
 			}
 			else {
 				// checking not current user
 				$groups = $this->Application->RecallVar('UserGroups:' . $user_id);
 
 				if ( $groups === false ) {
 //					die('me');
 					$sql = 'SELECT GroupId
 							FROM '.TABLE_PREFIX.'UserGroupRelations
 							WHERE (PortalUserId = '.$user_id.') AND ( (MembershipExpires IS NULL) OR ( MembershipExpires >= UNIX_TIMESTAMP() ) )';
 					$groups = $this->Conn->GetCol($sql);
 
 					array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup') );
 					$groups = implode(',', $groups);
 
 					$this->Application->StoreVar('UserGroups:' . $user_id, $groups);
 				}
 			}
 
 			$groups = explode(',', $groups);
 			$cache_key = $name . '|' . $type . '|' . $cat_id . '|' . implode(',', $groups);
 			$perm_value = $this->Application->getCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key);
 
 			if ( $perm_value !== false ) {
 				return $perm_value;
 			}
 
 			if ( preg_match('/(.*)\.VIEW$/', $name) && ($type == 0) ) {
 				// cached view permission of category: begin
 				if ( strpos($cat_id, '|') !== false ) {
 					$category_path = explode('|', substr($cat_id, 1, -1));
 					$cat_id = end($category_path);
 				}
 
 				$sql = 'SELECT PermissionConfigId
 						FROM ' . TABLE_PREFIX . 'CategoryPermissionsConfig
 						WHERE PermissionName = ' . $this->Conn->qstr($name);
 				$perm_id = $this->Conn->GetOne($sql);
 
 				$sql = 'SELECT PermId
 						FROM ' . TABLE_PREFIX . 'CategoryPermissionsCache
 						WHERE (PermId = ' . $perm_id . ') AND (CategoryId = ' . (int)$cat_id . ')';
 
 				$view_filters = Array ();
 				foreach ($groups as $group) {
 					$view_filters[] = 'FIND_IN_SET(' . $group . ', ACL)';
 				}
 				$sql .= ' AND (' . implode(' OR ', $view_filters) . ')';
 				$perm_value = $this->Conn->GetOne($sql) ? 1 : 0;
 
 				$this->Application->setCache('permissions[%CPermSerial%]:' . $cache_key, $perm_value);
 				return $perm_value;
 				// cached view permission of category: end
 			}
 
 			if ( is_numeric($cat_id) && $cat_id == 0 ) {
 				$cat_hierarchy = Array (0);
 			}
 			else {
 				if ( strpos($cat_id, '|') !== false ) {
 					$cat_hierarchy = $cat_id;
 				}
 				else {
 					$sql = 'SELECT ParentPath
-							FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
+							FROM ' . $this->Application->getUnitConfig('c')->getTableName() . '
 							WHERE CategoryId = ' . $cat_id;
 					$cat_hierarchy = $this->Conn->GetOne($sql);
 					if ( $cat_hierarchy === false ) {
 						// category was deleted, but reference to it stays in other tables -> data integrity is broken
 						$cat_hierarchy = '|' . $this->Application->getBaseCategory() . '|';
 					}
 				}
 
 				$cat_hierarchy = explode('|', substr($cat_hierarchy, 1, -1));
 				$cat_hierarchy = array_reverse($cat_hierarchy);
 				array_push($cat_hierarchy, 0);
 			}
 
 			$perm_value = 0;
 			$groups = implode(',', $groups);
 			foreach ($cat_hierarchy as $category_id) {
 				$sql = 'SELECT SUM(PermissionValue)
 						FROM ' . TABLE_PREFIX . 'Permissions
 						WHERE Permission = "' . $name . '" AND CatId = ' . $category_id . ' AND GroupId IN (' . $groups . ') AND Type = ' . $type;
 				$res = $this->Conn->GetOne($sql);
 
 				if ( $res !== false && !is_null($res) ) {
 					$perm_value = $res ? 1 : 0;
 					break;
 				}
 			}
 
 			$this->Application->setCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key, $perm_value);
 
 			return $perm_value;
 		}
 
 		/**
 		 * Returns categories, where given permission is set to "1"
 		 *
 		 * @param string $permission_name
 		 * @return Array
 		 */
 		function getPermissionCategories($permission_name)
 		{
 			$groups = $this->Application->RecallVar('UserGroups');
 
 			// get categories, where given permission is explicitely defined
 			$sql = 'SELECT SUM(PermissionValue), CatId
 					FROM ' . TABLE_PREFIX . 'Permissions
 					WHERE Permission = "' . $permission_name . '" AND GroupId IN (' . $groups . ') AND Type = 0
 					GROUP BY CatId';
 			$permissions = $this->Conn->GetCol($sql, 'CatId');
 
 			// get all categories along with their parent path
 			$sql = 'SELECT ParentPath, CategoryId
 					FROM ' . TABLE_PREFIX . 'Categories';
 			$parent_paths = $this->Conn->GetCol($sql, 'CategoryId');
 
 			foreach ($parent_paths as $category_id => $parent_path) {
 				if (array_key_exists($category_id, $permissions)) {
 					// permission for given category is set explicitly
 					continue;
 				}
 
 				$perm_value = 0;
 				$parent_path = explode('|', substr($parent_path, 1, -1));
 				$parent_path = array_reverse($parent_path);
 				array_push($parent_path, 0);
 
 				foreach ($parent_path as $parent_category_id) {
 					if (array_key_exists($parent_category_id, $permissions)) {
 						$perm_value = $permissions[$parent_category_id] ? 1 : 0;
 						break;
 					}
 				}
 
 				$permissions[$category_id] = $perm_value;
 			}
 
 			// remove categories, where given permissions is denied
 			foreach ($permissions as $category_id => $perm_value) {
 				if (!$perm_value) {
 					unset($permissions[$category_id]);
 				}
 			}
 
 			return array_keys($permissions);
 		}
 
 		/**
 		 * Allows to check MODIFY & OWNER.MODFY +/- PENDING permission combinations on item
 		 *
 		 * @param int $owner_id user_id, that is owner of the item
 		 * @param int $category_id primary category of item
 		 * @param string $prefix prefix of item
 		 * @return int {0 - no MODIFY permission, 1 - has MODIFY permission, 2 - has MODIFY.PENDING permission}
 		 */
 		function ModifyCheckPermission($owner_id, $category_id, $prefix)
 		{
-			$perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix');
+			$perm_prefix = $this->Application->getUnitConfig($prefix)->getPermItemPrefix();
 
 			$live_modify = $this->CheckPermission($perm_prefix.'.MODIFY', ptCATEGORY, $category_id);
 			if ($live_modify) {
 				return 1;
 			}
 			else if ($this->CheckPermission($perm_prefix.'.MODIFY.PENDING', ptCATEGORY, $category_id)) {
 				return 2;
 			}
 
 			if ($owner_id == $this->Application->RecallVar('user_id')) {
 				// user is item's OWNER -> check this permissions first
 				$live_modify = $this->CheckPermission($perm_prefix.'.OWNER.MODIFY', ptCATEGORY, $category_id);
 				if ($live_modify) {
 					return 1;
 				}
 				else if ($this->CheckPermission($perm_prefix.'.OWNER.MODIFY.PENDING', ptCATEGORY, $category_id)) {
 					return 2;
 				}
 			}
 
 			return 0;
 		}
 
 		/**
 		 * Allows to check DELETE & OWNER.DELETE permission combinations on item
 		 *
 		 * @param int $owner_id user_id, that is owner of the item
 		 * @param int $category_id primary category of item
 		 * @param string $prefix prefix of item
 		 * @return int {0 - no DELETE permission, 1 - has DELETE/OWNER.DELETE permission}
 		 */
 		function DeleteCheckPermission($owner_id, $category_id, $prefix)
 		{
-			$perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix');
+			$perm_prefix = $this->Application->getUnitConfig($prefix)->getPermItemPrefix();
 
 			$live_delete = $this->CheckPermission($perm_prefix.'.DELETE', ptCATEGORY, $category_id);
 			if ($live_delete) {
 				return 1;
 			}
 
 			if ($owner_id == $this->Application->RecallVar('user_id')) {
 				// user is item's OWNER -> check this permissions first
 				$live_delete = $this->CheckPermission($perm_prefix.'.OWNER.DELETE', ptCATEGORY, $category_id);
 				if ($live_delete) {
 					return 1;
 				}
 			}
 
 			return 0;
 		}
 
 		/**
 		 * Allows to check ADD +/- PENDING permission combinations on item
 		 *
 		 * @param int $category_id primary category of item
 		 * @param string $prefix prefix of item
 		 * @return int {0 - no ADD permission, 1 - has ADD permission, 2 - has ADD.PENDING permission}
 		 */
 		function AddCheckPermission($category_id, $prefix)
 		{
-			$perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix');
+			$perm_prefix = $this->Application->getUnitConfig($prefix)->getPermItemPrefix();
 
 			$live_add = $this->CheckPermission($perm_prefix.'.ADD', ptCATEGORY, $category_id);
 			if ($live_add) {
 				return 1;
 			}
 			else if ($this->CheckPermission($perm_prefix.'.ADD.PENDING', ptCATEGORY, $category_id)) {
 				return 2;
 			}
 
 			return 0;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/template_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/template_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/template_helper.php	(revision 15698)
@@ -1,442 +1,444 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class 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 ;
 			}
 
 			// 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 CategoriesEventHandler */
 
 				$t = ltrim($cms_handler->GetDesignTemplate($t), '/');
 			}
 
 			$this->_sourceTemplate = $t;
 		}
 
 		function _getThemeName()
 		{
+			$config = $this->Application->getUnitConfig('theme');
 			$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;
+					FROM ' . $config->getTableName() . '
+					WHERE ' . $config->getIDField() . ' = ' . $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 = '')
 		{
 			try {
 				// 1. parse template
 				$this->Application->InitParser( $this->_getThemeName() ); // we have no parser when saving block content
 
 				$this->_getSourceTemplate();
 
 				// 2. design templates have leading "/" in the beginning
 				$this->Application->Parser->Run($this->_sourceTemplate . $append);
 			}
 			catch (ParserException $e) {
 				$this->_parseErrors[] = Array ('msg' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine());
 			}
 
 			if ($this->_parseErrors) {
 				if ($this->_isMainTemplate()) {
 					// 2.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 {
 					// 2.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 = $area = 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);
 
 			$deep_level = 0;
 
 			$pairs = Array ();
 			$skip_count = Array (); // by deep level!
 
 			foreach ($divs as $div) {
 				if (strpos($div[0][0], '/') === false) {
 					// opening div
 					$skip_count[$deep_level] = 0;
 
 					if (strpos($div[0][0], $class) !== false) {
 						// ours opening (this deep level) -> save
 						$pair = Array ('open_pos' => $div[0][1], 'open_len' => strlen($div[0][0]));
 					}
 					else {
 						// not ours opening -> skip next closing (this deep level)
 						$skip_count[$deep_level]++;
 					}
 
 					$deep_level++;
 				}
 				else {
 					// closing div
 					$deep_level--;
 
 					if ($skip_count[$deep_level] == 0) {
 						// nothing to skip (this deep level) -> save
 						$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 closing div as requested
 						$skip_count[$deep_level]--;
 					}
 				}
 			}
 
 			return $pairs;
 		}
 
 		/**
 		 * Returns information about parser element locations in template
 		 *
 		 * @param string $info_type
 		 * @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
 		 *
 		 * @param bool $relative
 		 * @param string $extension
 		 * @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;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/form_submission_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/form_submission_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/form_submission_helper.php	(revision 15698)
@@ -1,124 +1,138 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class FormSubmissionHelper extends kHelper {
 
 		/**
 		 * Role names for easy usage via FormField tag
 		 *
 		 * @var Array
 		 */
 		var $roleNames = Array (
 			'name' => SubmissionFormField::COMMUNICATION_ROLE_NAME,
 			'email' => SubmissionFormField::COMMUNICATION_ROLE_EMAIL,
 			'subject' => SubmissionFormField::COMMUNICATION_ROLE_SUBJECT,
 			'body' => SubmissionFormField::COMMUNICATION_ROLE_BODY,
 		);
 
 		/**
 		 * Returns submission field based on given role
 		 *
 		 * @param kDBItem $form_submission
 		 * @param string $role
 		 * @param bool $formatted
 		 * @param string $format
 		 * @return string
+		 * @access public
 		 */
-		function getFieldByRole(&$form_submission, $role, $formatted = false, $format = null)
+		public function getFieldByRole(&$form_submission, $role, $formatted = false, $format = null)
 		{
-			static $cache = Array ();
-
 			$form_id = $form_submission->GetDBField('FormId');
+			$field_name = $this->getFieldNameByRole($form_id, $role);
+
+			if ( $field_name ) {
+				return $formatted ? $form_submission->GetField($field_name, $format) : $form_submission->GetDBField($field_name);
+			}
 
-			if (!array_key_exists($form_id, $cache)) {
-				$id_field = $this->Application->getUnitOption('formflds', 'IDField');
-				$table_name = $this->Application->getUnitOption('formflds', 'TableName');
+			return false;
+		}
 
-				$sql = 'SELECT ' . $id_field . ', EmailCommunicationRole
-						FROM ' . $table_name . '
+		/**
+		 * Returns submission field name based on given role
+		 *
+		 * @param int $form_id
+		 * @param string $role
+		 * @return string
+		 * @access public
+		 */
+		public function getFieldNameByRole($form_id, $role)
+		{
+			static $cache = Array ();
+
+			if ( !array_key_exists($form_id, $cache) ) {
+				$config = $this->Application->getUnitConfig('formflds');
+
+				$sql = 'SELECT ' . $config->getIDField() . ', EmailCommunicationRole
+						FROM ' . $config->getTableName() . '
 						WHERE FormId = ' . $form_id . ' AND EmailCommunicationRole <> 0';
 				$cache[$form_id] = $this->Conn->GetCol($sql, 'EmailCommunicationRole');
 			}
 
 			// convert string representation of role to numeric
-			if (!is_numeric($role)) {
+			if ( !is_numeric($role) ) {
 				$role = strtolower($role);
 				$role = array_key_exists($role, $this->roleNames) ? $this->roleNames[$role] : false;
 			}
 
-			// get field by role
-			$field_id = array_key_exists($role, $cache[$form_id]) ? $cache[$form_id][$role] : false;
-
-			if ($field_id) {
-				return $formatted ? $form_submission->GetField('fld_' . $field_id, $format) : $form_submission->GetDBField('fld_' . $field_id);
-			}
-
-			return false;
+			// get field name by role
+			return array_key_exists($role, $cache[$form_id]) ? 'fld_' . $cache[$form_id][$role] : false;
 		}
 
 		/**
 		 * Returns submission field based on given name
 		 *
 		 * @param kDBItem $form_submission
 		 * @param string $name
 		 * @param bool $formatted
 		 * @param string $format
 		 * @return string
 		 * @todo Might not work correctly!
 		 */
 		function getFieldByName(&$form_submission, $name, $formatted = false, $format = null)
 		{
 			static $cache = Array ();
 
 			$form_id = $form_submission->GetDBField('FormId');
 
-			if (!array_key_exists($form_id, $cache)) {
-				$id_field = $this->Application->getUnitOption('formflds', 'IDField');
-				$table_name = $this->Application->getUnitOption('formflds', 'TableName');
+			if ( !array_key_exists($form_id, $cache) ) {
+				$config = $this->Application->getUnitConfig('formflds');
 
-				$sql = 'SELECT ' . $id_field . ', FieldName
-						FROM ' . $table_name . '
+				$sql = 'SELECT ' . $config->getIDField() . ', FieldName
+						FROM ' . $config->getTableName() . '
 						WHERE FormId = ' . $form_id;
 				$cache[$form_id] = $this->Conn->GetCol($sql, 'FieldName');
 			}
 
-			if ($field_id) {
+			// get field by name
+			$field_id = array_key_exists($name, $cache[$form_id]) ? $cache[$form_id][$name] : false;
+
+			if ( $field_id ) {
 				return $formatted ? $form_submission->GetField('fld_' . $field_id, $format) : $form_submission->GetDBField('fld_' . $field_id);
 			}
 
 			return false;
 		}
 
 		/**
 		 * Returns form object field based on form submission
 		 *
 		 * @param $form_submission kDBItem
 		 * @return kDBItem
 		 */
 		function &getForm(&$form_submission)
 		{
 			$form_id = $form_submission->GetDBField('FormId');
 
 			$form = $this->Application->recallObject('form', null, Array ('skip_autoload' => true));
 			/* @var $form kDBItem */
 
 			if ( !$form->isLoaded() || ($form->GetID() != $form_id) ) {
 				$form->Load($form_id);
 			}
 
 			return $form;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/navigation_bar.php
===================================================================
--- branches/5.3.x/core/units/helpers/navigation_bar.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/navigation_bar.php	(revision 15698)
@@ -1,367 +1,369 @@
 <?php
 /**
  * @version	$Id$
  * @package	In-Portal
  * @copyright	Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
  * @license	  GNU/GPL
  * In-Portal is Open Source software.
  * This means that this software may have been modified pursuant
  * the GNU General Public License, and as distributed it includes
  * or is derivative of works licensed under the GNU General Public License
  * or other free or open source software licenses.
  * See http://www.in-portal.org/license for copyright notices and details.
  */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class kNavigationBar extends kBase {
 
 	/**
 	 * Parameters to indicate how exactly navigation bar should look like
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_params = Array ();
 
 	/**
 	 * Prints category path using given blocks. Also supports used defined path elements at the end.
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	public function build($params)
 	{
 		// elements:
 		// - current_render_as - currently selected element (automatic detection)
 		// - render_as - link to a regular template
 		// - category_render_as - link to category path element
 		// - custom_render_as - link to custom element (that have "__" in front of them)
 		// - root_cat_render_as - link to Home page
 
 		$this->_params = $params;
 		$this->_params['is_first'] = 1;
 
 		$home_element = $this->_getHomeElement();
 
 		if ( !getArrayValue($this->_params, 'titles') && !getArrayValue($this->_params, 'templates') ) {
 			// no static templates given, show only category path
 			return $home_element . $this->getCategoryPath();
 		}
 
 		$ret = '';
 		$block_params = $this->_getBaseParams();
 		$current_template = $this->_getCurrentTemplate();
 		$navigation_parts = $this->getNavigationParts();
 
 		foreach ($navigation_parts as $template => $title) {
 			$block_params['template'] = $template;
 
 			if ( $title == '__categorypath__' ) {
 				$ret .= $this->getCategoryPath();
 			}
 			else {
 				$is_current = $template == $current_template;
 				$block_params['current'] = $is_current;
 
 				if ( substr($title, 0, 2) == '__' ) {
 					$block_params['title'] = $title;
 					$block_params['name'] = $this->SelectParam($this->_params, 'custom_render_as,render_as');
 				}
 				else {
 					$block_params['title'] = $this->Application->Phrase($title);
 					$block_params['name'] = $this->_params[$is_current ? 'current_render_as' : 'render_as'];
 				}
 
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 		}
 
 		return $home_element . $ret;
 	}
 
 	/**
 	 * Returns base params for rendering each navigation bar element
 	 *
 	 * @return Array
 	 * @access protected
 	 */
 	protected function _getBaseParams()
 	{
 		$block_params = Array (
 			'no_editing' => 1,
 			'category' => 0,
 			'separator' => $this->_params['separator'],
 			'current' => 0,
 		);
 
 		return $block_params;
 	}
 
 	/**
 	 * Returns the name of current physical template
 	 *
 	 * @return string
 	 * @access protected
 	 */
 	protected function _getCurrentTemplate()
 	{
 		return $this->Application->getPhysicalTemplate($this->Application->GetVar('t'));
 	}
 
 	/**
 	 * Returns element for "Home" category
 	 *
 	 * @return string
 	 * @access protected
 	 */
 	protected function _getHomeElement()
 	{
 		if ( isset($this->_params['shift']) && $this->_params['shift'] ) {
 			$home_element = '';
 			$this->_params['shift']--;
 		}
 		else {
 			$home_element = $this->_getHomeCategoryPath();
 			unset($this->_params['is_first']);
 		}
 
 		return $home_element;
 	}
 
 	/**
 	 * Renders path to top catalog category
 	 *
 	 * @return string
 	 * @access protected
 	 */
 	protected function _getHomeCategoryPath()
 	{
 		$block_params = $this->_getBaseParams();
 		$block_params['cat_id'] = $this->Application->getBaseCategory();
 		$block_params['current'] = $this->_getCurrentCategoryId() == $block_params['cat_id'] ? 1 : 0;
 		$block_params['is_first'] = $this->_params['is_first'];
 		$block_params['template'] = ''; // to prevent warning when category element is rendered using general "render_as" block
 
 		$category_name = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 		$block_params['cat_name'] = $block_params['title'] = $category_name;
 
 		$block_params['name'] = $this->SelectParam($this->_params, 'root_cat_render_as,category_render_as,render_as');
 
 		if ( $block_params['current'] ) {
 			$block_params['name'] = $this->SelectParam($this->_params, 'current_render_as,render_as');
 		}
 
 		return $this->Application->ParseBlock($block_params);
 	}
 
 	/**
 	 * Returns currently selected category
 	 *
 	 * @return mixed
 	 */
 	protected function _getCurrentCategoryId()
 	{
 		return isset($this->_params['cat_id']) ? $this->_params['cat_id'] : $this->Application->GetVar('m_cat_id');
 	}
 
 	/**
 	 * Get navigation parts
 	 *
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getNavigationParts()
 	{
 		$titles = explode(',', $this->_params['titles']);
 		$templates = explode(',', $this->_params['templates']);
 
 		if ( getArrayValue($this->_params, 'show_category') && !in_array('__categorypath__', $titles) ) {
 			// insert before __item__ or first element, when __item__ isn't specified
 			$item_index = (int)array_search('__item__', $titles);
 			array_splice($titles, $item_index, 0, '__categorypath__');
 			array_splice($templates, $item_index, 0, '__categorypath__');
 		}
 
 		return array_combine($templates, $titles);
 	}
 
 	/**
 	 * Renders path to given category using given blocks.
 	 *
 	 * @return string
 	 * @access protected
 	 */
 	protected function getCategoryPath()
 	{
 		$category_path = $this->getCategoryParentPath();
 
 		if ( !$category_path ) {
 			// in "Home" category
 			return '';
 		}
 
 		$main_category_id = $this->_getCurrentCategoryId();
 
 		if ( isset($this->_params['shift']) && $this->_params['shift'] ) {
 			array_splice($category_path, 0, $this->_params['shift']);
 		}
 
 		$category_helper = $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		$module_info = $category_helper->getCategoryModule($this->_params, array_keys($category_path));
 		$module_item_id = $this->Application->GetVar($module_info['Var'] . '_id');
 
 		$ret = '';
 		$block_params = $this->_getBaseParams();
 		$block_params['category'] = 1;
 		$block_params['template'] = ''; // to prevent warning when category element is rendered using general "render_as" block
 
 		if ( isset($this->_params['is_first']) ) {
 			$block_params['is_first'] = $this->_params['is_first'];
 		}
 
 		$block_params['separator'] = $this->_params['separator'];
 		$no_current = isset($this->_params['no_current']) && $this->_params['no_current'];
 		$backup_category_id = $this->Application->GetVar('c_id');
 
 		foreach ($category_path as $category_id => $category_name) {
 			$block_params['cat_id'] = $category_id;
 			$block_params['cat_name'] = $block_params['title'] = $category_name;
 
 			if ( $no_current ) {
 				$block_params['current'] = 0;
 			}
 			else {
 				$block_params['current'] = ($main_category_id == $category_id) && !$module_item_id ? 1 : 0;
 			}
 
 			$block_params['name'] = $this->SelectParam($this->_params, 'category_render_as,render_as');
 
 			if ( $block_params['current'] ) {
 				$block_params['name'] = $this->SelectParam($this->_params, 'current_render_as,render_as');
 			}
 
 			$this->Application->SetVar('c_id', $category_id);
 			$ret .= $this->Application->ParseBlock($block_params);
 
 			if ( array_key_exists('is_first', $block_params) ) {
 				unset($block_params['is_first']);
 			}
 		}
 
 		$this->Application->SetVar('c_id', $backup_category_id);
 
 		return $ret;
 	}
 
 	/**
 	 * Returns given category's parent path as array of id=>name elements
 	 *
 	 * @return Array
 	 * @access protected
 	 */
 	protected function getCategoryParentPath()
 	{
 		$main_category_id = $this->_getCurrentCategoryId();
 
 		if ( $main_category_id == 0 ) {
 			// don't query path for "Home" category
 			return Array ();
 		}
 
 		$category_title = isset($this->_params['category_title']) ? $this->_params['category_title'] : 'Name';
 		$cache_key = 'parent_paths_named[%CIDSerial:' . $main_category_id . '%]:' . $category_title;
 		$cached_path = $this->Application->getCache($cache_key);
 
 		if ( $cached_path === false ) {
 			$parent_path = explode('|', substr($this->getParentPath($main_category_id), 1, -1));
 
 			$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 			/* @var $ml_formatter kMultiLanguage */
 
 			$navbar_field = $ml_formatter->LangFieldName($category_title);
 
-			$id_field = $this->Application->getUnitOption('c', 'IDField');
-			$table_name = $this->Application->getUnitOption('c', 'TableName');
+			$config = $this->Application->getUnitConfig('c');
+			$id_field = $config->getIDField();
 
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT ' . $navbar_field . ', ' . $id_field . '
-					FROM ' . $table_name . '
+					FROM ' . $config->getTableName() . '
 					WHERE ' . $id_field . ' IN (' . implode(',', $parent_path) . ')';
 			$category_names = $this->Conn->GetCol($sql, $id_field);
 
 			$cached_path = Array ();
 			$skip_category = $this->Application->getBaseCategory();
 
 			if ( $category_names ) {
 				foreach ($parent_path as $category_id) {
 					if ( $category_id == $skip_category ) {
 						continue;
 					}
 
 					$cached_path[$category_id] = $category_names[$category_id];
 				}
 			}
 
 			$this->Application->setCache($cache_key, $cached_path);
 		}
 
 		return $cached_path;
 	}
 
 	/**
 	 * Returns parent path from a given category
 	 *
 	 * @param int $category_id
 	 * @return string
 	 * @access public
 	 */
 	public function getParentPath($category_id)
 	{
 		$cache_key = 'parent_paths[%CIDSerial:' . $category_id . '%]';
 		$parent_path = $this->Application->getCache($cache_key);
 
 		if ( $parent_path !== false ) {
 			return $parent_path;
 		}
 
+		$config = $this->Application->getUnitConfig('c');
+
 		$this->Conn->nextQueryCachable = true;
 		$sql = 'SELECT ParentPath
-				FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
-				WHERE ' . $this->Application->getUnitOption('c', 'IDField') . ' = ' . $category_id;
+				FROM ' . $config->getTableName() . '
+				WHERE ' . $config->getIDField() . ' = ' . $category_id;
 		$parent_path = $this->Conn->GetOne($sql);
 
 		$this->Application->setCache($cache_key, $parent_path);
 
 		return $parent_path;
 	}
 
 	/**
 	 * Not tag. Method for parameter selection from list in this TagProcessor
 	 *
 	 * @param Array $params
 	 * @param Array $possible_names
 	 *
 	 * @return string
 	 * @access protected
 	 */
 	protected function SelectParam($params, $possible_names)
 	{
 		if ( !is_array($params) ) {
 			return '';
 		}
 		if ( !is_array($possible_names) ) {
 			$possible_names = explode(',', $possible_names);
 		}
 
 		foreach ($possible_names as $name) {
 			if ( isset($params[$name]) ) {
 				return $params[$name];
 			}
 		}
 
 		return '';
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/themes_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/themes_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/themes_helper.php	(revision 15698)
@@ -1,638 +1,642 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class kThemesHelper extends kHelper {
 
 		/**
 		 * Where all themes are located
 		 *
 		 * @var string
 		 */
 		var $themesFolder = '';
 
 		/**
 		 * List of theme names, found on system
 		 *
 		 * @var Array
 		 */
 		var $_themeNames = Array ();
 
 		/**
 		 * Temporary array when all theme files from db are stored
 		 *
 		 * @var Array
 		 */
 		var $themeFiles = Array ();
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			$this->themesFolder = FULL_PATH.'/themes';
 		}
 
 		/**
 		 * Updates file system changes to database for selected theme
 		 *
 		 * @param string $theme_name
 		 *
 		 * @return mixed returns ID of created/used theme or false, if none created
 		 */
 		function refreshTheme($theme_name)
 		{
 			if (!file_exists($this->themesFolder . '/' . $theme_name)) {
 				// requested theme was not found on hdd
 				return false;
 			}
 
-			$id_field = $this->Application->getUnitOption('theme', 'IDField');
-			$table_name = $this->Application->getUnitOption('theme', 'TableName');
+			$config = $this->Application->getUnitConfig('theme');
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			$sql = 'SELECT *
 					FROM ' . $table_name . '
 					WHERE Name = ' . $this->Conn->qstr($theme_name);
 			$theme_info = $this->Conn->GetRow($sql);
 
 			if ($theme_info) {
 				$theme_id = $theme_info[$id_field];
 				$theme_enabled = $theme_info['Enabled'];
 			}
 			else {
 				$theme_id = $theme_enabled = false;
 			}
 
 			$this->themeFiles = Array ();
 			if ($theme_id) {
 				if (!$theme_enabled) {
 					// don't process existing theme files, that are disabled
 					return $theme_id;
 				}
 
 				// reset found mark for every themes file (if theme is not new)
 				$sql = 'UPDATE '.TABLE_PREFIX.'ThemeFiles
 						SET FileFound = 0
 						WHERE ThemeId = '.$theme_id;
 				$this->Conn->Query($sql);
 
 				// get all theme files from db
 				$sql = 'SELECT FileId, CONCAT(FilePath, "/", FileName) AS FullPath
 						FROM '.TABLE_PREFIX.'ThemeFiles
 						WHERE ThemeId = '.$theme_id;
 				$this->themeFiles = $this->Conn->GetCol($sql, 'FullPath');
 			}
 			else {
 				// theme was not found in db, but found on hdd -> create new
 				$theme_info = Array (
 					 'Name' => $theme_name,
 					 'Enabled' => 0,
 					 'Description' => $theme_name,
 					 'PrimaryTheme' => 0,
 					 'CacheTimeout' => 3600, // not in use right now
 					 'StylesheetId' => 0,	  // not in use right now
 					 'LanguagePackInstalled' => 0
 				);
 
 				$this->Conn->doInsert($theme_info, $table_name);
 				$theme_id = $this->Conn->getInsertID();
 
 				if (!$theme_enabled) {
 					// don't process newly created theme files, because they are disabled
 					return $theme_id;
 				}
 			}
 
 			$this->_themeNames[$theme_id] = $theme_name;
 			$theme_path = $this->themesFolder.'/'.$theme_name;
 			$this->FindThemeFiles('', $theme_path, $theme_id); // search from base theme directory
 
 			// delete file records from db, that were not found on hdd
 			$sql = 'DELETE FROM '.TABLE_PREFIX.'ThemeFiles
 					WHERE ThemeId = '.$theme_id.' AND FileFound = 0';
 			$this->Conn->Query($sql);
 
 			// install language packs, associated with theme (if any found and wasn't aready installed)
 			if (!$theme_info['LanguagePackInstalled']) {
 				$this->installThemeLanguagePack($theme_path);
 
 				$fields_hash = Array (
 					'LanguagePackInstalled' => 1,
 				);
 
 				$this->Conn->doUpdate($fields_hash, $table_name, $id_field . ' = ' . $theme_id);
 			}
 
 			$this->_saveThemeSettings($theme_id, $theme_path);
 
 			return $theme_id;
 		}
 
 		/**
 		 * Saves information from "/_install/theme.xml" files in theme
 		 *
 		 * @param int $theme_id
 		 * @param string $theme_path
 		 * @return void
 		 * @access protected
 		 */
 		protected function _saveThemeSettings($theme_id, $theme_path)
 		{
-			$id_field = $this->Application->getUnitOption('theme', 'IDField');
-			$table_name = $this->Application->getUnitOption('theme', 'TableName');
+			$config = $this->Application->getUnitConfig('theme');
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
+
 			$this->Conn->doUpdate($this->_getThemeSettings($theme_id, $theme_path), $table_name, $id_field . ' = ' . $theme_id);
 		}
 
 		/**
 		 * Installs module(-s) language pack for given theme
 		 *
 		 * @param string $theme_path
 		 * @param string|bool $module_name
 		 * @return void
 		 */
 		function installThemeLanguagePack($theme_path, $module_name = false)
 		{
 			if ( $module_name === false ) {
 				$modules = $this->Application->ModuleInfo;
 			}
 			else {
 				$modules = Array ($module_name => $this->Application->ModuleInfo[$module_name]);
 			}
 
 			$language_import_helper = $this->Application->recallObject('LanguageImportHelper');
 			/* @var $language_import_helper LanguageImportHelper */
 
 			foreach ($modules as $module_name => $module_info) {
 				if ( $module_name == 'In-Portal' ) {
 					continue;
 				}
 
 				$lang_file = $theme_path . '/' . $module_info['TemplatePath'] . '_install/english.lang';
 
 				if ( file_exists($lang_file) ) {
 					$language_import_helper->performImport($lang_file, '|0|', '');
 				}
 			}
 		}
 
 		/**
 		 * Parses information, discovered from "/_install/theme.xml" files in theme
 		 *
 		 * @param int $theme_id
 		 * @param string $theme_path
 		 * @return Array
 		 * @access protected
 		 */
 		protected function _getThemeSettings($theme_id, $theme_path)
 		{
 			$setting_mapping = Array ('image_resize_rules' => 'ImageResizeRules');
 			$ret = Array ('TemplateAliases' => Array (), 'ImageResizeRules' => Array ());
 
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				$xml_file = $theme_path . '/' . $module_info['TemplatePath'] . '_install/theme.xml';
 
 				if ( $module_name == 'In-Portal' || !file_exists($xml_file) ) {
 					continue;
 				}
 
 				$theme = simplexml_load_file($xml_file);
 
 				if ( $theme === false ) {
 					// broken xml OR no aliases defined
 					continue;
 				}
 
 				foreach ($theme as $setting) {
 					/* @var $setting SimpleXMLElement */
 
 					$setting_name = $setting->getName();
 					$setting_value = trim($setting);
 
 					if ( isset($setting_mapping[$setting_name]) ) {
 						$ret[$setting_mapping[$setting_name]][] = $setting_value;
 						continue;
 					}
 
 					// this is template alias
 					$module_override = (string)$setting['module'];
 
 					if ( $module_override ) {
 						// allow to put template mappings form all modules into single theme.xml file
 						$module_folder = $this->Application->findModule('Name', $module_override, 'TemplatePath');
 					}
 					else {
 						// no module specified -> use module based on theme.xml file location
 						$module_folder = $module_info['TemplatePath'];
 					}
 
 					// only store alias, when template exists on disk
 					if ( $this->getTemplateId($setting_value, $theme_id) ) {
 						$alias = '#' . $module_folder . strtolower($setting->getName()) . '#';
 
 						// remember alias in global theme mapping
 						$ret['TemplateAliases'][$alias] = $setting_value;
 
 						// store alias in theme file record to use later in design dropdown
 						$this->updateTemplate($setting_value, $theme_id, Array ('TemplateAlias' => $alias));
 					}
 				}
 			}
 
 			$ret['TemplateAliases'] = serialize($ret['TemplateAliases']);
 			$ret['ImageResizeRules'] = implode("\n", array_filter($ret['ImageResizeRules']));
 
 			return $ret;
 		}
 
 		/**
 		 * Returns ID of given physical template (relative to theme) given from ThemeFiles table
 		 * @param string $template_path
 		 * @param int $theme_id
 		 * @return int
 		 * @access public
 		 */
 		public function getTemplateId($template_path, $theme_id)
 		{
 			$template_path = $this->Application->getPhysicalTemplate($template_path);
 
 			$sql = 'SELECT FileId
 					FROM ' . TABLE_PREFIX . 'ThemeFiles
 					WHERE ' . $this->getTemplateWhereClause($template_path, $theme_id);
 
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Updates template record with a given data
 		 *
 		 * @param string $template_path
 		 * @param int $theme_id
 		 * @param Array $fields_hash
 		 * @return void
 		 * @access public
 		 */
 		public function updateTemplate($template_path, $theme_id, $fields_hash)
 		{
 			$where_clause = $this->getTemplateWhereClause($template_path, $theme_id);
 			$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'ThemeFiles', $where_clause);
 		}
 
 		/**
 		 * Returns where clause to get associated record from ThemeFiles table for given template path
 		 * @param string $template_path
 		 * @param int $theme_id
 		 * @return string
 		 * @access protected
 		 */
 		protected function getTemplateWhereClause($template_path, $theme_id)
 		{
 			$folder = dirname($template_path);
 
 			$where_clause = Array (
 				'ThemeId = ' . $theme_id,
 				'FilePath = ' . $this->Conn->qstr($folder == '.' ? '' : '/' . $folder),
 				'FileName = ' . $this->Conn->qstr(basename($template_path) . '.tpl'),
 			);
 
 			return '(' . implode(') AND (', $where_clause) . ')';
 		}
 
 		/**
 		 * Installs given module language pack and refreshed it from all themes
 		 *
 		 * @param string $module_name
 		 */
 		function synchronizeModule($module_name)
 		{
 			$sql = 'SELECT `Name`, ThemeId
 					FROM ' . TABLE_PREFIX . 'Themes';
 			$themes = $this->Conn->GetCol($sql, 'ThemeId');
 
 			if (!$themes) {
 				return ;
 			}
 
 			foreach ($themes as $theme_id => $theme_name) {
 				$theme_path = $this->themesFolder . '/' . $theme_name;
 
 				// install language pack
 				$this->installThemeLanguagePack($theme_path, $module_name);
 
 				// update TemplateAliases mapping
 				$this->_saveThemeSettings($theme_id, $theme_path);
 			}
 		}
 
 		/**
 		 * Searches for new templates (missing in db) in specified folder
 		 *
 		 * @param string $folder_path subfolder of searchable theme
 		 * @param string $theme_path theme path from web server root
 		 * @param int $theme_id id of theme we are scanning
 		 * @param int $auto_structure_mode
 		 */
 		function FindThemeFiles($folder_path, $theme_path, $theme_id, $auto_structure_mode = 1)
 		{
 			$ignore_regexp = $this->getIgnoreRegexp($theme_path . $folder_path);
 
 			$iterator = new DirectoryIterator($theme_path . $folder_path . '/');
 			/* @var $file_info DirectoryIterator */
 
 			foreach ($iterator as $file_info) {
 				$filename = $file_info->getFilename();
 				$auto_structure = preg_match($ignore_regexp, $filename) ? 2 : $auto_structure_mode;
 				$file_path = $folder_path . '/' . $filename; // don't pass path to theme top folder!
 
 				if ( $file_info->isDir() && !$file_info->isDot() && $filename != 'CVS' && $filename != '.svn' ) {
 					$this->FindThemeFiles($file_path, $theme_path, $theme_id, $auto_structure);
 				}
 				elseif ( pathinfo($filename, PATHINFO_EXTENSION) == 'tpl' ) {
 					$meta_info = $this->_getTemplateMetaInfo(trim($file_path, '/'), $theme_id);
 					$file_id = isset($this->themeFiles[$file_path]) ? $this->themeFiles[$file_path] : false;
 					$file_description = array_key_exists('desc', $meta_info) ? $meta_info['desc'] : '';
 
 					if ($file_id) {
 						// file was found in db & on hdd -> mark as existing
 						$fields_hash = Array (
 							'FileFound' => 1,
 							'Description' => $file_description,
 							'FileType' => $auto_structure,
 							'FileMetaInfo' => serialize($meta_info),
 						);
 
 						$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'ThemeFiles', 'FileId = ' . $file_id);
 					}
 					else {
 						// file was found on hdd, but missing in db -> create new file record
 						$fields_hash = Array (
 							'ThemeId' => $theme_id,
 							'FileName' => $filename,
 							'FilePath' => $folder_path,
 							'Description' => $file_description,
 							'FileType' => $auto_structure, // 1 - built-in, 0 - custom (not in use right now), 2 - skipped in structure
 							'FileMetaInfo' => serialize($meta_info),
 							'FileFound' => 1,
 						);
 
 						$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'ThemeFiles');
 						$this->themeFiles[$file_path] = $this->Conn->getInsertID();
 					}
 //					echo 'FilePath: [<strong>'.$folder_path.'</strong>]; FileName: [<strong>'.$filename.'</strong>]; IsNew: [<strong>'.($file_id > 0 ? 'NO' : 'YES').'</strong>]<br />';
 				}
 			}
 		}
 
 		/**
 		 * Returns single regular expression to match all ignore patters, that are valid for given folder
 		 *
 		 * @param string $folder_path
 		 * @return string
 		 */
 		protected function getIgnoreRegexp($folder_path)
 		{
 			// always ignore design and element templates
 			$ignore = '\.des\.tpl$|\.elm\.tpl$';
 			$sms_ignore_file = $folder_path . '/.smsignore';
 
 			if ( file_exists($sms_ignore_file) ) {
 				$manual_patterns = array_map('trim', file($sms_ignore_file));
 
 				foreach ($manual_patterns as $manual_pattern) {
 					$ignore .= '|' . str_replace('/', '\\/', $manual_pattern);
 				}
 			}
 
 			return '/' . $ignore . '/';
 		}
 
 		/**
 		 * Returns template information (name, description, path) from it's header comment
 		 *
 		 * @param string $template
 		 * @param int $theme_id
 		 * @return Array
 		 */
 		function _getTemplateMetaInfo($template, $theme_id)
 		{
 			static $init_made = false;
 			if (!$init_made) {
 				$this->Application->InitParser(true);
 				$init_made = true;
 			}
 
 			$template = 'theme:' . $this->_themeNames[$theme_id] . '/' . $template;
 			$template_file = $this->Application->TemplatesCache->GetRealFilename($template); // ".tpl" was added before
 
 			return $this->parseTemplateMetaInfo($template_file);
 		}
 
 		function parseTemplateMetaInfo($template_file)
 		{
 			if (!file_exists($template_file)) {
 				// when template without info it's placed in top category
 				return Array ();
 			}
 
 			$template_data = file_get_contents($template_file);
 
 			if (substr($template_data, 0, 6) == '<!--##') {
 				// template starts with comment in such format
 				/*<!--##
 				<NAME></NAME>
 				<DESC></DESC>
 				<SECTION>||</SECTION>
 				##-->*/
 
 				$comment_end = strpos($template_data, '##-->');
 				if ($comment_end === false) {
 					// badly formatted comment
 					return Array ();
 				}
 
 				$comment = trim( substr($template_data, 6, $comment_end - 6) );
 				if (preg_match_all('/<(NAME|DESC|SECTION)>(.*?)<\/(NAME|DESC|SECTION)>/is', $comment, $regs)) {
 					$ret = Array ();
 					foreach ($regs[1] as $param_order => $param_name) {
 						$ret[ strtolower($param_name) ] = trim($regs[2][$param_order]);
 					}
 
 					if (array_key_exists('section', $ret) && $ret['section']) {
 						$category_path = explode('||', $ret['section']);
 						$category_path = array_map('trim', $category_path);
 						$ret['section'] = implode('||', $category_path);
 					}
 
 					return $ret;
 				}
 			}
 
 			return Array ();
 		}
 
 		/**
 		 * Updates file system changes to database for all themes (including new ones)
 		 *
 		 */
 		function refreshThemes()
 		{
 			$themes_found = Array ();
 
 			try {
 				$iterator = new DirectoryIterator($this->themesFolder . '/');
 				/* @var $file_info DirectoryIterator */
 
 				foreach ($iterator as $file_info) {
 					$filename = $file_info->getFilename();
 
 					if ( $file_info->isDir() && !$file_info->isDot() && $filename != '.svn' && $filename != 'CVS' ) {
 						$theme_id = $this->refreshTheme($filename);
 
 						if ( $theme_id ) {
 							$themes_found[] = $theme_id;
 							// increment serial of updated themes
 							$this->Application->incrementCacheSerial('theme', $theme_id);
 						}
 					}
 				}
 			}
 			catch ( UnexpectedValueException $e ) {
 			}
 
-			$id_field = $this->Application->getUnitOption('theme', 'IDField');
-			$table_name = $this->Application->getUnitOption('theme', 'TableName');
+			$config = $this->Application->getUnitConfig('theme');
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			// 1. only one theme found -> enable it and make primary
 			/*if (count($themes_found) == 1) {
 				$sql = 'UPDATE ' . $table_name . '
 						SET Enabled = 1, PrimaryTheme = 1
 						WHERE ' . $id_field . ' = ' . current($themes_found);
 				$this->Conn->Query($sql);
 			}*/
 
 			// 2. if none themes found -> delete all from db OR delete all except of found themes
 			$sql = 'SELECT ' . $id_field . '
 					FROM ' . $table_name;
 			if ( $themes_found ) {
 				$sql .= ' WHERE ' . $id_field . ' NOT IN (' . implode(',', $themes_found) . ')';
 			}
 			$theme_ids = $this->Conn->GetCol($sql);
 			$this->deleteThemes($theme_ids);
 
 			foreach ($theme_ids as $theme_id) {
 				// increment serial of deleted themes
 				$this->Application->incrementCacheSerial('theme', $theme_id);
 			}
 
 			$this->Application->incrementCacheSerial('theme');
 			$this->Application->incrementCacheSerial('theme-file');
 
 			$minify_helper = $this->Application->recallObject('MinifyHelper');
 			/* @var $minify_helper MinifyHelper */
 
 			$minify_helper->delete();
 		}
 
 		/**
 		 * Deletes themes with ids passed from db
 		 *
 		 * @param Array $theme_ids
 		 */
 		function deleteThemes($theme_ids)
 		{
 			if (!$theme_ids) {
 				return ;
 			}
 
-			$id_field = $this->Application->getUnitOption('theme', 'IDField');
-			$table_name = $this->Application->getUnitOption('theme', 'TableName');
+			$config = $this->Application->getUnitConfig('theme');
+			$id_field = $config->getIDField();
 
-			$sql = 'DELETE FROM '.$table_name.'
+			$sql = 'DELETE FROM '. $config->getTableName() .'
 					WHERE '.$id_field.' IN ('.implode(',', $theme_ids).')';
 			$this->Conn->Query($sql);
 
 			$sql = 'DELETE FROM '.TABLE_PREFIX.'ThemeFiles
 					WHERE '.$id_field.' IN ('.implode(',', $theme_ids).')';
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Returns current theme (also works in admin)
 		 *
 		 * @return int
 		 */
 		function getCurrentThemeId()
 		{
 			static $theme_id = null;
 
 			if (isset($theme_id)) {
 				return $theme_id;
 			}
 
 			if ($this->Application->isAdmin) {
 				// get theme, that user selected in catalog
 				$theme_id = $this->Application->RecallVar('theme_id');
 
 				if ($theme_id === false) {
 					// query, because "m_theme" is always empty in admin
-					$id_field = $this->Application->getUnitOption('theme', 'IDField');
-					$table_name = $this->Application->getUnitOption('theme', 'TableName');
+					$config = $this->Application->getUnitConfig('theme');
 
-					$sql = 'SELECT ' . $id_field . '
-							FROM ' . $table_name . '
+					$sql = 'SELECT ' . $config->getIDField() . '
+							FROM ' . $config->getTableName() . '
 							WHERE (PrimaryTheme = 1) AND (Enabled = 1)';
 					$theme_id = $this->Conn->GetOne($sql);
 				}
 
 				return $theme_id;
 			}
 
 			// use current theme, because it's available on Front-End
 			$theme_id = $this->Application->GetVar('m_theme');
 			if (!$theme_id) {
 				// happens in mod-rewrite mode, then requested template is not found
 				$theme_id = $this->Application->GetDefaultThemeId();
 			}
 
 			return $theme_id;
 		}
 
 		/**
 		 * Returns page id based on given template
 		 *
 		 * @param string $template
 		 * @param int $theme_id
 		 * @return int
 		 */
 		function getPageByTemplate($template, $theme_id = null)
 		{
 			if (!isset($theme_id)) {
 				// during mod-rewrite url parsing current theme
 				// is not available to kHTTPQuery class, so don't use it
 				$theme_id = (int)$this->getCurrentThemeId();
 			}
 
 			$template_crc = kUtil::crc32(mb_strtolower($template));
+			$categories_config = $this->Application->getUnitConfig('c');
 
-			$sql = 'SELECT ' . $this->Application->getUnitOption('c', 'IDField') . '
-					FROM ' . $this->Application->getUnitOption('c', 'TableName') . '
+			$sql = 'SELECT ' . $categories_config->getIDField() . '
+					FROM ' . $categories_config->getTableName() . '
 					WHERE
 						(
 							(NamedParentPathHash = ' . $template_crc . ') OR
 							(`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplateHash = ' . $template_crc . ')
 						)
 						AND (ThemeId = ' . $theme_id . ($theme_id > 0 ? ' OR ThemeId = 0' : '') . ')';
 
 			return $this->Conn->GetOne($sql);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/sections_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/sections_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/sections_helper.php	(revision 15698)
@@ -1,378 +1,379 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	/**
 	 * Processes sections info from the configs
 	 *
 	 */
 	class kSectionsHelper extends kHelper {
 
 		/**
 		 * Holds information about all sections
 		 *
 		 * @var Array
 		 */
 		var $Tree = Array();
 
 		/**
 		 * Checks, that we are in debug mode
 		 *
 		 * @var bool
 		 */
 		var $debugMode = false;
 
 		/**
 		 * Checks, that we are in super admin mode
 		 *
 		 * @var bool
 		 */
 		var $superAdminMode = false;
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			$this->debugMode = $this->Application->isDebugMode();
 			$this->superAdminMode = $this->Application->RecallVar('super_admin');
 		}
 
 		/**
 		 * Set's prefix and special
 		 *
 		 * @param string $prefix
 		 * @param string $special
 		 * @access public
 		 */
 		public function Init($prefix, $special)
 		{
 			parent::Init($prefix, $special);
 
 			$this->BuildTree();
 		}
 
 		/**
 		 * Builds xml for tree in left frame in admin
 		 *
 		 * @return void
 		 * @access public
 		 */
 		public function BuildTree()
 		{
 			if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 				$data = $this->Application->getCache('master:sections_parsed', false, CacheSettings::$sectionsParsedRebuildTime);
 			}
 			else {
 				$data = $this->Application->getDBCache('sections_parsed', CacheSettings::$sectionsParsedRebuildTime);
 			}
 
 			if ( $data ) {
 				$this->Tree = unserialize($data);
 				return ;
 			}
 
 			if ( !defined('IS_INSTALL') || !IS_INSTALL ) {
 				// don't reread all configs during install, because they are reread on every install step
 				$this->Application->UnitConfigReader->ReReadConfigs();
 			}
 
 			$this->Tree = Array ();
 
 			// 1. build base tree (don't update parent with children list yet)
 
 			// 1.1. process prefixes without priority
 			$prioritized_prefixes = Array ();
-			$prefixes = array_keys($this->Application->UnitConfigReader->configData);
+			$prefixes = $this->Application->UnitConfigReader->getPrefixes();
 
 			foreach ($prefixes as $prefix) {
-				$config =& $this->Application->UnitConfigReader->configData[$prefix];
+				$config = $this->Application->getUnitConfig($prefix);
 
-				if ( array_key_exists('ConfigPriority', $config) ) {
-					$prioritized_prefixes[$prefix] = $config['ConfigPriority'];
+				if ( $config->getConfigPriority() !== false ) {
+					$prioritized_prefixes[$prefix] = $config->getConfigPriority();
 					continue;
 				}
 
 				$this->_processPrefixSections($prefix);
 			}
 
 			// 2. process prefixes with priority
 			asort($prioritized_prefixes);
 			foreach ($prioritized_prefixes as $prefix => $priority) {
 				$this->_processPrefixSections($prefix);
 			}
 
 			// 2. apply section adjustments
 			foreach ($prefixes as $prefix) {
-				$config =& $this->Application->UnitConfigReader->configData[$prefix];
-				$section_adjustments = getArrayValue($config, 'SectionAdjustments');
-				/* @var $section_adjustments Array */
+				$config = $this->Application->getUnitConfig($prefix);
+
+				$section_adjustments = $config->getSectionAdjustments();
 
 				if ( !$section_adjustments ) {
 					continue;
 				}
 
 				foreach ($section_adjustments as $section_name => $adjustment_params) {
 					if ( is_array($adjustment_params) ) {
 						if ( !array_key_exists($section_name, $this->Tree) ) {
 							// don't process adjustments for non-existing sections
 							continue;
 						}
 
 						$this->Tree[$section_name] = kUtil::array_merge_recursive($this->Tree[$section_name], $adjustment_params);
 					}
 					else {
 						// then remove section
 						unset($this->Tree[$section_name]);
 					}
 				}
 			}
 
 			// 3.
 			foreach ($this->Tree as $section_name => $section_params) {
 				// 3.1. update parent -> children references
 				$parent_section = $section_params['parent'];
 				$section_order = "{$section_params['priority']}";
 
 				if ( !isset($parent_section) ) {
 					// don't process parent section of "in-portal:root" section
 					continue;
 				}
 
 				if ( !array_key_exists('children', $this->Tree[$parent_section]) ) {
 					$this->Tree[$parent_section]['children'] = Array ();
 				}
 
 				if ( array_key_exists($section_order, $this->Tree[$parent_section]['children']) ) {
 					trigger_error('Section "<strong>' . $section_name . '</strong>" has replaced section "<strong>' . $this->Tree[$parent_section]['children'][$section_order] . '</strong>" (parent section: "<strong>' . $parent_section . '</strong>"; duplicate priority: <strong>' . $section_order . '</strong>)', E_USER_WARNING);
 				}
 
 				$this->Tree[$parent_section]['children'][$section_order] = $section_name;
 
 				if ( $section_params['type'] == stTAB ) {
 					// if this is tab, then mark parent section as TabOnly
 					$this->Tree[$parent_section]['tabs_only'] = true;
 				}
 
 				// 3.2. process icons here, because they also can be adjusted
 				if ( isset($section_params['icon']) && preg_match('/([^:]+):(.*)/', $section_params['icon'], $regs) ) {
 					$this->Tree[$section_name]['icon'] = $regs[2];
 					$this->Tree[$section_name]['icon_module'] = $regs[1]; // set "icon_module" used in "combined_header" block
 					$module_folder = trim($this->Application->findModule('Name', $regs[1], 'Path'), '/');
 
 					if ( $module_folder == '' ) {
 						$module_folder = 'core';
 					}
 				}
 				else {
-					$module_folder = $this->Application->getUnitOption($section_params['SectionPrefix'], 'ModuleFolder');
+					$module_folder = $this->Application->getUnitConfig($section_params['SectionPrefix'])->getModuleFolder();
+
 					if ( !array_key_exists('icon_module', $section_params) ) {
 						// set "icon_module" used in "combined_header" block
 						$this->Tree[$section_name]['icon_module'] = $this->Application->findModule('Path', $module_folder . '/', 'Name');
 					}
 				}
 
 				// this is to display HELP icon instead of missing one.. can be replaced with some other icon to draw attention
 				$icon_file = $module_folder . '/admin_templates/img/icons/icon24_' . $this->Tree[$section_name]['icon'];
 
 				/*$core_file = FULL_PATH.'/core/admin_templates/img/icons/icon24_' . $this->Tree[$section_name]['icon'].'.png';
 				if ($module_folder != 'core' && file_exists($core_file) && file_exists(FULL_PATH.'/'.$icon_file.'.png')) {
 					if (crc32(file_get_contents($core_file)) == crc32(file_get_contents(FULL_PATH.'/'.$icon_file.'.png'))) {
 						trigger_error('Section "<strong>' . $section_name . '</strong>" uses icon copy from "Core" module', E_USER_NOTICE);
 					}
 				}*/
 
 				if ( !file_exists(FULL_PATH . '/' . $icon_file . '.png') ) {
 					$this->Tree[$section_name]['icon'] = 'help';
 					$this->Tree[$section_name]['icon_module'] = 'core';
 				}
 			}
 
 			$this->Application->HandleEvent(new kEvent('adm:OnAfterBuildTree'));
 
 			if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 				$this->Application->setCache('master:sections_parsed', serialize($this->Tree));
 			}
 			else {
 				$this->Application->setDBCache('sections_parsed', serialize($this->Tree));
 			}
 		}
 
 		function _processPrefixSections($prefix)
 		{
-			$config =& $this->Application->UnitConfigReader->configData[$prefix];
-			$sections = getArrayValue($config, 'Sections');
-			/* @var $sections Array */
+			$config = $this->Application->getUnitConfig($prefix);
+			$sections = $config->getSections();
 
 			if ( !$sections ) {
 				return ;
 			}
 
 			foreach ($sections as $section_name => $section_params) {
 				// we could also skip not allowed sections here in future
 				if ( isset($section_params['SectionPrefix']) ) {
 					$section_prefix = $section_params['SectionPrefix'];
 				}
-				elseif ( $this->Application->getUnitOption($prefix, 'SectionPrefix') ) {
-					$section_prefix = $this->Application->getUnitOption($prefix, 'SectionPrefix');
+				elseif ( $this->Application->getUnitConfig($prefix)->getSectionPrefix() ) {
+					$section_prefix = $this->Application->getUnitConfig($prefix)->getSectionPrefix();
 				}
 				else {
 					$section_prefix = $prefix;
 				}
 
 				if ( is_float($section_params['priority']) ) {
 					$section_params['priority'] = (string)$section_params['priority'];
 				}
 
 				$section_params['SectionPrefix'] = $section_prefix;
 				$section_params['url']['m_opener'] = 'r';
 				$section_params['url']['no_pass_through'] = 1;
 				$pass_section = getArrayValue($section_params, 'url', 'pass_section');
 
 				if ( $pass_section ) {
 					unset($section_params['url']['pass_section']);
 					$section_params['url']['section'] = $section_name;
 
 					if ( !isset($section_params['url']['module']) ) {
-						$module_name = $this->Application->findModule('Path', $config['ModuleFolder'] . '/', 'Name');
+						$module_name = $this->Application->findModule('Path', $config->getModuleFolder() . '/', 'Name');
 						$section_params['url']['module'] = $module_name;
 					}
 				}
 
 				if ( !isset($section_params['url']['t']) ) {
 					$section_params['url']['t'] = 'index';
 				}
 
 				if ( !isset($section_params['onclick']) ) {
 					$section_params['onclick'] = 'checkEditMode()';
 				}
 
 				if ( !isset($section_params['container']) ) {
 					$section_params['container'] = 0; // for js tree printing to xml
 				}
 
 				$current_data = isset($this->Tree[$section_name]) ? $this->Tree[$section_name] : Array ();
 
 				if ( $current_data ) {
 					trigger_error('Section "<strong>' . $section_name . '</strong>" declaration (originally defined in "<strong>' . $current_data['SectionPrefix'] . '</strong>") was overwriten from "<strong>' . $prefix . '</strong>"', E_USER_WARNING);
 				}
 
 				$this->Tree[$section_name] = kUtil::array_merge_recursive($current_data, $section_params);
 			}
 		}
 
 		/**
 		 * Returns details information about section
 		 *
 		 * @param string $section_name
 		 * @return Array
 		 */
 		function &getSectionData($section_name)
 		{
 			if (isset($this->Tree[$section_name])) {
 				$ret =& $this->Tree[$section_name];
 			}
 			else {
 				$ret = Array();
 			}
 			return $ret;
 		}
 
 		/**
 		 * Returns first child, that is not a folder
 		 *
 		 * @param string $section_name
 		 * @param bool $check_permission
 		 * @return string
 		 * @access public
 		 */
 		public function getFirstChild($section_name, $check_permission = false)
 		{
 			$section_data =& $this->getSectionData($section_name);
 			$children = isset($section_data['children']) && $section_data['children'] ? $section_data['children'] : false;
 			/* @var $children Array */
 
 			if ( $children ) {
 				// get 1st child
 				ksort($children, SORT_NUMERIC);
 				foreach ($children as $child_section) {
 					if ( !$this->sectionVisible($child_section, $check_permission) ) {
 						continue;
 					}
 
 					break;
 				}
 
 				return $this->getFirstChild($child_section, $check_permission);
 			}
 
 			return $section_name;
 		}
 
 		/**
 		 * Checks if given section is visible by it's definition and optionally by user permission
 		 *
 		 * @param string $section_name
 		 * @param bool $check_permission
 		 * @return bool
 		 */
 		function sectionVisible($section_name, $check_permission = false)
 		{
 			$section_data =& $this->getSectionData($section_name);
 
 			if (isset($section_data['show_mode']) && is_numeric($section_data['show_mode'])) {
 				$show_mode = $section_data['show_mode'];
 
 				// if super admin section -> show in super admin mode & debug mode
 				$show_section = $show_mode == smNORMAL || ((($show_mode & smSUPER_ADMIN) == smSUPER_ADMIN) && ($this->superAdminMode || $this->debugMode));
 
 				if (!$show_section) {
 					// if section is in debug mode only && debug mode -> show
 					$show_section = (($show_mode & smDEBUG) == smDEBUG) && $this->debugMode;
 				}
 
 				if (!$show_section) {
 					// visibility by section definition
 					return false;
 				}
 			}
 
 			// visibility by section permission
 			if ($check_permission) {
 				$perm_section = $this->getPermSection($section_name);
 
 				return $this->Application->CheckPermission($perm_section.'.view');
 			}
 
 			return true;
 		}
 
 		/**
 		 * Returns section for permission checking based on given section
 		 *
 		 * @param string $section_name
 		 * @return string
 		 */
 		function getPermSection($section_name)
 		{
 			$ret = $section_name;
 			$section_data =& $this->getSectionData($section_name);
 
 			if ($section_data && isset($section_data['perm_prefix'])) {
 				// this section uses other section permissions
-				$ret = $this->Application->getUnitOption($section_data['perm_prefix'].'.main', 'PermSection');
+				$ret = $this->Application->getUnitConfig($section_data['perm_prefix'])->getPermSectionByName('main');
 			}
+
 			return $ret;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/helpers/site_config_helper.php
===================================================================
--- branches/5.3.x/core/units/helpers/site_config_helper.php	(revision 15697)
+++ branches/5.3.x/core/units/helpers/site_config_helper.php	(revision 15698)
@@ -1,212 +1,215 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class SiteConfigHelper extends kHelper {
 
 		public function __construct()
 		{
 			parent::__construct();
 
 			$preset_name = $this->Application->ConfigValue('AdminConsoleInterface');
 			define('SYSTEM_PRESET_PATH', FULL_PATH . ADMIN_PRESETS_DIRECTORY . DIRECTORY_SEPARATOR . 'system_presets' . DIRECTORY_SEPARATOR . $preset_name);
 		}
 
 		/**
 		 * Returns settings for current admin console preset
 		 *
 		 * @return Array
 		 */
 		function getSettings()
 		{
 			static $settings = null;
 
 			if (isset($settings)) {
 				return $settings;
 			}
 
 			$default_settings = $this->_getDefaultSettings();
 
 			$preset_name = $this->Application->ConfigValue('AdminConsoleInterface');
 			$base_path = FULL_PATH . ADMIN_DIRECTORY . '/system_presets/' . $preset_name;
 
 			if (file_exists($base_path . DIRECTORY_SEPARATOR . 'settings.php')) {
 				include_once $base_path . DIRECTORY_SEPARATOR . 'settings.php'; // will get $settings array
 
 				foreach ($default_settings as $setting_name => $setting_value) {
 					if (!array_key_exists($setting_name, $settings)) {
 						$settings[$setting_name] = $setting_value;
 					}
 				}
 
 				return $settings;
 			}
 			else {
 				$settings = $default_settings;
 			}
 
 			return $settings;
 		}
 
 		/**
 		 * Returns default settings for admin console presets
 		 *
 		 * @return Array
 		 */
 		function _getDefaultSettings()
 		{
 			$settings = Array (
 				'default_editing_mode' => EDITING_MODE_BROWSE,
 				'visible_editing_modes' => Array (
 					EDITING_MODE_BROWSE,
 					EDITING_MODE_CONTENT,
 					EDITING_MODE_DESIGN,
 				),
 			);
 
 			return $settings;
 		}
 
 		/**
 		 * Applies given changes to given prefix unit config
 		 *
 		 * @param string $prefix
 		 * @param Array $changes
 		 */
 		function processConfigChanges($prefix, $changes)
 		{
 			extract($changes);
 
-			$section_adjustments = $this->Application->getUnitOption('core-sections', 'SectionAdjustments', Array ());
-			$title_presets = $this->Application->getUnitOption($prefix, 'TitlePresets', Array ());
-			$fields = $this->Application->getUnitOption($prefix, 'Fields', Array ());
-			$virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields', Array ());
-			$edit_tab_presets = $this->Application->getUnitOption($prefix, 'EditTabPresets', Array ());
-			$grids = $this->Application->getUnitOption($prefix, 'Grids', Array ());
+			$config = $this->Application->getUnitConfig($prefix);
+			$core_config = $this->Application->getUnitConfig('core-sections');
+
+			$section_adjustments = $core_config->getSectionAdjustments(Array ());
+			$title_presets = $config->getTitlePresets(Array ());
+			$fields = $config->getFields(Array ());
+			$virtual_fields = $config->getVirtualFields(Array ());
+			$edit_tab_presets = $config->getEditTabPresets(Array ());
+			$grids = $config->getGrids(Array ());
 
 			$field_names = array_keys($fields);
 			$virtual_field_names = array_keys($virtual_fields);
 
 			if (isset($remove_sections)) {
 				// process sections
 				foreach ($remove_sections as $remove_section) {
 					$section_adjustments[$remove_section]['show_mode'] = smHIDE;
 				}
 			}
 
 			if (isset($debug_only_sections)) {
 				// process sections
 				foreach ($debug_only_sections as $debug_only_section) {
 					$section_adjustments[$debug_only_section]['show_mode'] = smDEBUG;
 				}
 			}
 
 			if (isset($remove_buttons)) {
 				// process toolbar buttons
 				foreach ($remove_buttons as $title_preset => $toolbar_buttons) {
 					$title_presets[$title_preset]['toolbar_buttons'] = array_diff($title_presets[$title_preset]['toolbar_buttons'], $toolbar_buttons);
 				}
 			}
 
 			$reset_fields = true;
 			$reset_virtual_fields = true;
 
 			// process hidden fields
 			if (isset($hidden_fields)) {
 				$fields = $this->_setFieldOption($fields, $hidden_fields, 'show_mode', false, $reset_fields);
 				$reset_fields = false;
 			}
 
 			if (isset($virtual_hidden_fields)) {
 				$virtual_fields = $this->_setFieldOption($virtual_fields, $virtual_hidden_fields, 'show_mode', false, $reset_virtual_fields);
 				$reset_virtual_fields = false;
 			}
 
 			// process debug only fields
 			if (isset($debug_only_fields)) {
 				$fields = $this->_setFieldOption($fields, $debug_only_fields, 'show_mode', smDEBUG, $reset_fields);
 				$reset_fields = false;
 			}
 
 			if (isset($debug_only_virtual_fields)) {
 				$virtual_fields = $this->_setFieldOption($virtual_fields, $debug_only_virtual_fields, 'show_mode', smDEBUG, $reset_virtual_fields);
 				$reset_virtual_fields = false;
 			}
 
 			// process required fields
 			if (isset($required_fields)) {
 				$fields = $this->_setFieldOption($fields, $required_fields, 'required', 1);
 			}
 
 			if (isset($virtual_required_fields)) {
 				$virtual_fields = $this->_setFieldOption($virtual_fields, $virtual_required_fields, 'required', 1);
 			}
 
 			if (isset($hide_edit_tabs)) {
 				// hide edit tabs
 				foreach ($hide_edit_tabs as $preset_name => $edit_tabs) {
 					foreach ($edit_tabs as $edit_tab) {
 						unset($edit_tab_presets[$preset_name][$edit_tab]);
 					}
 				}
 			}
 
 			if (isset($hide_columns)) {
 				// hide columns in grids
 				foreach ($hide_columns as $grid_name => $columns) {
 					foreach ($columns as $column) {
 						unset($grids[$grid_name]['Fields'][$column]);
 					}
 				}
 			}
 
 			// save changes
-			$this->Application->setUnitOption('core-sections', 'SectionAdjustments', $section_adjustments);
-			$this->Application->setUnitOption($prefix, 'TitlePresets', $title_presets);
-			$this->Application->setUnitOption($prefix, 'Fields', $fields);
-			$this->Application->setUnitOption($prefix, 'VirtualFields', $virtual_fields);
-			$this->Application->setUnitOption($prefix, 'EditTabPresets', $edit_tab_presets);
-			$this->Application->setUnitOption($prefix, 'Grids', $grids);
+			$core_config->setSectionAdjustments($section_adjustments);
+			$config->setTitlePresets($title_presets);
+			$config->setFields($fields);
+			$config->setVirtualFields($virtual_fields);
+			$config->setEditTabPresets($edit_tab_presets);
+			$config->setGrids($grids);
 		}
 
 		/**
 		 * Sets given option for given fields and unsets it for all other fields
 		 *
 		 * @param Array $fields
 		 * @param Array $set_fields
 		 * @param string $option_name
 		 * @param mixed $option_value
 		 * @param bool $reset
 		 */
 		function _setFieldOption($fields, $set_fields, $option_name, $option_value, $reset = true)
 		{
 			if ($reset) {
 				// unset given option for rest of fields (all except $set_fields)
 				$unset_fields = array_diff(array_keys($fields), $set_fields);
 				foreach ($unset_fields as $unset_field) {
 					if (array_key_exists($option_name, $fields[$unset_field])) {
 						unset($fields[$unset_field][$option_name]);
 					}
 				}
 			}
 
 			// set given option to given fields
 			foreach ($set_fields as $set_field) {
 				$fields[$set_field][$option_name] = $option_value;
 			}
 
 			return $fields;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/selectors/selectors_event_handler.php
===================================================================
--- branches/5.3.x/core/units/selectors/selectors_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/selectors/selectors_event_handler.php	(revision 15698)
@@ -1,453 +1,457 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 	class SelectorsEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array(
 				'OnResetToBase'		=>	Array('subitem' => 'add|edit'),
 				'OnMassResetToBase'	=>	Array('subitem' => 'add|edit'),
 
 				'OnOpenStyleEditor'	=>	Array('subitem' => 'add|edit'),
 				'OnSaveStyle'		=>	Array('subitem' => 'add|edit'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Occurs before an item has been cloned
 		 * Id of newly created item is passed as event' 'id' param
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeClone(kEvent $event)
 		{
 			parent::OnBeforeClone($event);
 
 			$event->Init($event->Prefix, '-item');
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$title_field = 'SelectorName';
 			$new_name = $object->GetDBField($title_field);
 			$original_checked = false;
 
 			$foreign_key = $event->getEventParam('foreign_key'); // in case if whole stylesheet is cloned
 			if ( $foreign_key === false ) {
 				$foreign_key = $object->GetDBField('StylesheetId');
 			} // in case if selector is copied ifself
 
 			do {
 				if ( preg_match('/(.*)-([\d]+)/', $new_name, $regs) ) {
 					$new_name = $regs[1] . '-' . ($regs[2] + 1);
 				}
 				elseif ( $original_checked ) {
 					$new_name = $new_name . '-1';
 				}
 
 				// if we are cloning in temp table this will look for names in temp table,
 				// since object' TableName contains correct TableName (for temp also!)
 				// if we are cloning live - look in live
 				$query = '	SELECT ' . $title_field . '
 							FROM ' . $object->TableName . '
 							WHERE ' . $title_field . ' = ' . $this->Conn->qstr($new_name) . ' AND StylesheetId = ' . $foreign_key;
 
 				$res = $this->Conn->GetOne($query);
 
 				/*// if not found in live table, check in temp table if applicable
 				if ($res === false && $object->Special == 'temp') {
 					$query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).'
 										WHERE '.$name_field.' = '.$this->Conn->qstr($new_name);
 					$res = $this->Conn->GetOne($query);
 				}*/
 
 				$original_checked = true;
 			} while ( $res !== false );
 			$object->SetDBField($title_field, $new_name);
 		}
 
 		/**
 		 * Show base styles or block styles
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			switch ($event->Special) {
 				case 'base':
 					$object->addFilter('type_filter', '%1$s.Type = 1');
 					break;
 
 				case 'block':
 					$object->addFilter('type_filter', '%1$s.Type = 2');
 					break;
 			}
 		}
 
 		/**
 		 * Occurs before updating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->SerializeSelectorData($event);
 		}
 
 		/**
 		 * Occurs before creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$this->SerializeSelectorData($event);
 		}
 
 		/**
 		 * Occurs after updating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemUpdate(kEvent $event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$this->UnserializeSelectorData($event);
 		}
 
 		/**
 		 * Occurs after creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(kEvent $event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$this->UnserializeSelectorData($event);
 		}
 
 		/**
 		 * Returns special of main item for linking with sub-item
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function getMainSpecial(kEvent $event)
 		{
 			return '';
 		}
 
 		/**
 		 * Save css-style name & description before opening css editor
 		 *
 		 * @param kEvent $event
 		 */
 		function OnOpenStyleEditor($event)
 		{
 			$this->SaveChanges($event);
 			$event->redirect = false;
 		}
 
 		/**
 		 * Saves Changes to Item
 		 *
 		 * @param kEvent $event
 		 */
 		function SaveChanges($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);
 
 				if ( $id == 0 ) {
 					$parent_id = getArrayValue($field_values, 'ParentId');
 					if ( $parent_id ) {
 						$object->Load($parent_id);
 					}
 
 					$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 					$object->Create();
 
 					$this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID());
 				}
 				else {
 					$object->Load($id);
 					$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 					$object->Update();
 				}
 			}
 		}
 
 		/**
 		 * Save style changes from style editor
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveStyle($event)
 		{
 			$this->SaveChanges($event);
 
 			$object = $event->getObject();
 			$this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetId() );
 
 			$this->finalizePopup($event);
 		}
 
 		/**
 		 * Extract styles
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$selector_data = $object->GetDBField('SelectorData');
 
 			if ( $selector_data ) {
 				$selector_data = unserialize($selector_data);
 				$object->SetDBField('SelectorData', $selector_data);
 			}
 			else {
 				$selector_data = Array ();
 			}
 
 			$this->AddParentProperties($event, $selector_data);
 		}
 
 		/**
 		 * Serialize item before saving to db
 		 *
 		 * @param kEvent $event
 		 */
 		function SerializeSelectorData($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$selector_data = $object->GetDBField('SelectorData');
 			if ( !$selector_data ) {
 				$selector_data = Array ();
 			}
 
 			$selector_data = $this->RemoveParentProperties($event, $selector_data);
 
 			if ( !kUtil::IsSerialized($selector_data) ) {
 				$selector_data = serialize($selector_data);
 			}
 
 			$object->SetDBField('SelectorData', $selector_data);
 		}
 
 		/**
 		 * Unserialize data back when update was made
 		 *
 		 * @param kEvent $event
 		 */
 		function UnserializeSelectorData($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$selector_data = $object->GetDBField('SelectorData');
 
 			if ( !$selector_data ) {
 				$selector_data = Array ();
 			}
 
 			if ( kUtil::IsSerialized($selector_data) ) {
 				$selector_data = unserialize($selector_data);
 			}
 
 			$selector_data = $this->AddParentProperties($event, $selector_data);
 
 			$object->SetDBField('SelectorData', $selector_data);
 		}
 
 		/**
 		 * Populate options based on temporary table :)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPrepareBaseStyles($event)
 		{
 			$object = $event->getObject();
 
 			$parent_info = $object->getLinkedInfo();
-			$title_field = $this->Application->getUnitOption($event->Prefix,'TitleField');
-			$sql = 'SELECT '.$title_field.', '.$object->IDField.' FROM '.$object->TableName.' WHERE Type = 1 AND StylesheetId = '.$parent_info['ParentId'].' ORDER BY '.$title_field;
+			$title_field = $event->getUnitConfig()->getTitleField();
 
+			$sql = 'SELECT '.$title_field.', '.$object->IDField.'
+					FROM '.$object->TableName.'
+					WHERE Type = 1 AND StylesheetId = '.$parent_info['ParentId'].'
+					ORDER BY '.$title_field;
 			$options = $this->Conn->GetCol($sql,$object->IDField);
+
 			$object->SetFieldOption('ParentId', 'options', $options);
 		}
 
 		/**
 		 * Remove properties of parent style that match by value from style
 		 *
 		 * @param kEvent $event
 		 * @param Array $selector_data
 		 * @return Array
 		 */
 		function RemoveParentProperties($event, $selector_data)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$parent_id = $object->GetDBField('ParentId');
 
 			if ( $parent_id ) {
 				$sql = 'SELECT SelectorData
 						FROM ' . $object->TableName . '
 						WHERE ' . $object->IDField . ' = ' . $parent_id;
 				$base_selector_data = $this->Conn->GetOne($sql);
 
 				if ( kUtil::IsSerialized($base_selector_data) ) {
 					$base_selector_data = unserialize($base_selector_data);
 				}
 
 				foreach ($selector_data as $prop_name => $prop_value) {
 					if ( !$prop_value || getArrayValue($base_selector_data, $prop_name) == $prop_value ) {
 						unset($selector_data[$prop_name]);
 					}
 				}
 			}
 			else {
 				foreach ($selector_data as $prop_name => $prop_value) {
 					if ( !$prop_value ) {
 						unset($selector_data[$prop_name]);
 					}
 				}
 			}
 
 			$object->SetDBField('SelectorData', $selector_data);
 
 			return $selector_data;
 		}
 
 		/**
 		 * Add back properties from parent style, that match this style property values
 		 *
 		 * @param kEvent $event
 		 * @param Array $selector_data
 		 * @return Array
 		 */
 		function AddParentProperties($event, $selector_data)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$parent_id = $object->GetDBField('ParentId');
 			if ( $parent_id ) {
 				$sql = 'SELECT SelectorData
 						FROM ' . $object->TableName . '
 						WHERE ' . $object->IDField . ' = ' . $parent_id;
 				$base_selector_data = $this->Conn->GetOne($sql);
 
 				if ( kUtil::IsSerialized($base_selector_data) ) {
 					$base_selector_data = unserialize($base_selector_data);
 				}
 
 				$selector_data = kUtil::array_merge_recursive($base_selector_data, $selector_data);
 				$object->SetDBField('SelectorData', $selector_data);
 
 				return $selector_data;
 			}
 
 			return Array ();
 		}
 
 		/**
 		 * Reset Style definition to base style -> no customizations
 		 *
 		 * @param kEvent $event
 		 */
 		function OnResetToBase($event)
 		{
 			$object = $event->getObject();
 			/* @var $object SelectorsItem */
 
 			$field_values = $this->getSubmittedFields($event);
 			$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 			$object->ResetStyle();
 
 			$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
 		}
 
 		/**
 		 * Resets selected styles properties to values of their base classes
 		 *
 		 * @param kEvent $event
 		 */
 		function OnMassResetToBase($event)
 		{
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object SelectorsItem */
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 
 			if ( $items_info ) {
 				foreach ($items_info as $id => $field_values) {
 					$object->Load($id);
 					$object->ResetStyle();
 				}
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/custom_data/custom_data_event_handler.php
===================================================================
--- branches/5.3.x/core/units/custom_data/custom_data_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/custom_data/custom_data_event_handler.php	(revision 15698)
@@ -1,242 +1,257 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CustomDataEventHandler extends kDBEventHandler {
 
 		/**
 		 * [HOOK] Allows to apply custom fields functionality to specific config
 		 * When main item is created, then cdata config is cloned
 		 *
 		 * @param kEvent $event
 		 */
 		function OnDefineCustomFields($event)
 		{
-			// 1. clone customdata table
-			$clones = $this->Application->getUnitOption('cdata', 'Clones');
-
-			$data_table = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'CustomDataTableName');
-
-			if ( !$data_table ) {
-				$data_table = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'TableName') . 'CustomData';
-			}
-
-			$clones[$event->MasterEvent->Prefix.'-cdata'] = Array (
-				'ParentPrefix' => $event->MasterEvent->Prefix,
-				'TableName' => $data_table,
-			);
-			$this->Application->setUnitOption('cdata', 'Clones', $clones);
+			// 1. clone custom data table
+			$this->Application->getUnitConfig('cdata')->addClones(Array (
+				$event->MasterEvent->Prefix . '-cdata' => Array (
+					'ParentPrefix' => $event->MasterEvent->Prefix,
+					'TableName' => $this->_getDataTable($event->MasterEvent),
+				)
+			));
 
 			// 2. add custom field information to main item
 			$this->createCustomFields($event->MasterEvent->Prefix);
 		}
 
 		/**
+		 * Returns custom data table name for given event
+		 *
+		 * @param kEvent $event
+		 * @return string
+		 * @access protected
+		 */
+		protected function _getDataTable(kEvent $event)
+		{
+			$config = $event->getUnitConfig();
+			$ret = $config->getCustomDataTableName();
+
+			return $ret ? $ret : $config->getTableName() . 'CustomData';
+		}
+
+		/**
 		 * Returns list of custom fields for a given $prefix
 		 *
 		 * @param $prefix
 		 *
 		 * @return Array|bool
 		 * @access protected
 		 */
 		protected function scanCustomFields($prefix)
 		{
 			static $custom_fields = Array ();
 
-			if (defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound('CustomFields', true)) {
+			if ( defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound('CustomFields', true) ) {
 				return false;
 			}
 
-			if (!$prefix) {
+			if ( !$prefix ) {
 				// prefix not specified
 				return false;
 			}
 
-			$item_type = $this->Application->getUnitOption($prefix, 'ItemType');
-			if (!$item_type) {
+			$item_type = $this->Application->getUnitConfig($prefix)->getItemType();
+			if ( !$item_type ) {
 				// no main config of such type
 				return false;
 			}
 
 			$no_caching = (defined('IS_INSTALL') && IS_INSTALL) || (defined('CUSTOM_FIELD_ADDED') && CUSTOM_FIELD_ADDED);
 
-			if (!$custom_fields || $no_caching) {
+			if ( !$custom_fields || $no_caching ) {
 				// query all custom fields at once -> saves 4 sqls queries
 
-				if ($no_caching) {
+				if ( $no_caching ) {
 					$all_custom_fields = $this->getCustomFields();
 				}
 				else {
 					$cache_key = 'all_custom_fields[%CfSerial%][%ModSerial%]';
 					$all_custom_fields = $this->Application->getCache($cache_key, false);
 
-					if ($all_custom_fields === false) {
+					if ( $all_custom_fields === false ) {
 						$this->Conn->nextQueryCachable = true;
 						$all_custom_fields = $this->getCustomFields();
 						$this->Application->setCache($cache_key, $all_custom_fields);
 					}
 				}
 
 				foreach ($all_custom_fields as $custom_field_id => $custom_field_data) {
 					$cf_type = $custom_field_data['Type'];
-					if (!array_key_exists($cf_type, $custom_fields)) {
+					if ( !array_key_exists($cf_type, $custom_fields) ) {
 						$custom_fields[$cf_type] = Array ();
 					}
 
 					$custom_fields[$cf_type][$custom_field_id] = $custom_field_data;
 				}
 			}
 
 			return array_key_exists($item_type, $custom_fields) ? $custom_fields[$item_type] : false;
 		}
 
 		/**
 		 * Returns sorted list of all custom fields
 		 *
 		 * @return Array
 		 */
 		function getCustomFields()
 		{
 			$sql = 'SELECT *
 					FROM '.TABLE_PREFIX.'CustomFields';
 			$ret = $this->Conn->Query($sql, 'CustomFieldId');
 
 			ksort($ret);
 
 			return $ret;
 		}
 
 		/**
 		 * Fills cloned cdata config with data from it's parent
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
-			$main_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
+			$config = $event->getUnitConfig();
+			$main_prefix = $config->getParentPrefix();
+
 			if ( !$main_prefix ) {
-				return ;
+				return;
 			}
 
 			$custom_fields = $this->scanCustomFields($main_prefix);
+
 			if ( !$custom_fields ) {
-				return ;
+				return;
 			}
 
-			// 2. create fields (for customdata item)
-			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields', Array ());
-			$field_options = Array ('type' => 'string', 'formatter' => 'kMultiLanguage', 'db_type' => 'text', 'default' => '');
+			// 2. create fields (for custom data item)
+			$field_options = Array (
+				'type' => 'string',
+				'formatter' => 'kMultiLanguage', 'db_type' => 'text',
+				'default' => ''
+			);
 
 			foreach ($custom_fields as $custom_id => $custom_params) {
-				if ( isset($fields['cust_' . $custom_id]) ) {
+				$field_name = 'cust_' . $custom_id;
+
+				if ( $config->getFieldByName($field_name) ) {
 					continue;
 				}
-				$fields['cust_' . $custom_id] = $field_options;
-				$fields['cust_' . $custom_id]['force_primary'] = !$custom_params['MultiLingual'];
-			}
 
-			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
+				$field_options['force_primary'] = !$custom_params['MultiLingual'];
+				$config->addFields($field_options, $field_name);
+			}
 		}
 
 		/**
 		 * Creates "cust_<custom_name>" virtual fields for main item
 		 *
 		 * @param string $prefix
 		 * @return void
 		 * @access protected
 		 */
 		protected function createCustomFields($prefix)
 		{
 			$custom_fields = $this->scanCustomFields($prefix);
 			if ( !$custom_fields ) {
 				return;
 			}
 
 			$calculated_fields = Array ();
-			$virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields', Array ());
+			$config = $this->Application->getUnitConfig($prefix);
 
 			$cf_helper = $this->Application->recallObject('InpCustomFieldsHelper');
 			/* @var $cf_helper InpCustomFieldsHelper */
 
 			$is_install = defined('IS_INSTALL') && IS_INSTALL;
 
 			foreach ($custom_fields as $custom_id => $custom_params) {
 				$custom_name = $custom_params['FieldName'];
 				$field_options = Array ('type' => 'string', 'default' => $custom_params['DefaultValue']);
 
 				// raises warnings during 4.3.9 -> 5.0.0 upgrade, no fatal sqls though
 				if ( $custom_params['IsRequired'] ) {
 					$field_options['required'] = 1;
 				}
 
 				$calculated_fields['cust_' . $custom_name] = 'cust.l' . $this->Application->GetDefaultLanguageId() . '_cust_' . $custom_id;
 
 				switch ( $custom_params['ElementType'] ) {
 					case 'date':
 						unset($field_options['options']);
 						$field_options['formatter'] = 'kDateFormatter';
 						$field_options['input_time_format'] = '';
 						$field_options['time_format'] = '';
 						break;
 
 					case 'datetime':
 						unset($field_options['options']);
 						$field_options['formatter'] = 'kDateFormatter';
 						break;
 
 					case 'select':
 					case 'multiselect':
 					case 'radio':
 						if ( $custom_params['ValueList'] ) {
 							// $is_install check prevents 335 bad phrase sql errors on upgrade to 5.1.0
 							$field_options['options'] = $is_install ? Array () : $cf_helper->GetValuesHash($custom_params['ValueList']);
 							$field_options['formatter'] = 'kOptionsFormatter';
 							$field_options['multiple'] = $custom_params['ElementType'] == 'multiselect';
 						}
 						break;
 
 					default:
 						if ( $custom_params['MultiLingual'] ) {
 							$field_options['formatter'] = 'kMultiLanguage';
 							$calculated_fields['cust_' . $custom_name] = 'cust.l%2$s_cust_' . $custom_id;
 						}
 						break;
 				}
 
-				if ( !isset($virtual_fields['cust_' . $custom_name]) ) {
-					$virtual_fields['cust_' . $custom_name] = Array ();
-				}
-				$virtual_fields['cust_' . $custom_name] = kUtil::array_merge_recursive($field_options, $virtual_fields['cust_' . $custom_name]);
+				$field_name = 'cust_' . $custom_name;
+				$old_field_options = $config->getVirtualFieldByName($field_name, Array ());
+
+				$config->addVirtualFields(Array (
+					$field_name => kUtil::array_merge_recursive($field_options, $old_field_options)
+				));
+
 				$custom_fields[$custom_id] = $custom_name;
 			}
 
-			$config_calculated_fields = $this->Application->getUnitOption($prefix, 'CalculatedFields', Array ());
-			foreach ($config_calculated_fields as $special => $special_fields) {
+			foreach ($config->getCalculatedFieldSpecials() as $special) {
 				if ( $special == '-virtual' ) {
 					continue;
 				}
 
-				$config_calculated_fields[$special] = array_merge($config_calculated_fields[$special], $calculated_fields);
+				$config->addCalculatedFieldsBySpecial($special, $calculated_fields);
 			}
-			$this->Application->setUnitOption($prefix, 'CalculatedFields', $config_calculated_fields);
 
-			$this->Application->setUnitOption($prefix, 'CustomFields', $custom_fields);
-			$this->Application->setUnitOption($prefix, 'VirtualFields', $virtual_fields);
+			$config->setCustomFields($custom_fields);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/logs/session_logs/session_log_eh.php
===================================================================
--- branches/5.3.x/core/units/logs/session_logs/session_log_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/logs/session_logs/session_log_eh.php	(revision 15698)
@@ -1,130 +1,132 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class SessionLogEventHandler extends kDBEventHandler {
 
 		/**
 		 * Opens log for new session
 		 *
 		 * @param kEvent $event
 		 */
 		function OnStartSession($event)
 		{
 			if (!$this->Application->ConfigValue('UseChangeLog')) {
 				// don't use session log when change log is disabled
 				return ;
 			}
 
 			$object = $this->Application->recallObject($event->Prefix, null, Array ('skip_autoload' => 1));
 			/* @var $object kDBItem */
 
 			$fields_hash = Array (
 				'SessionStart' => adodb_mktime(),
 				'IP' => $this->Application->getClientIp(),
 				'PortalUserId' => $this->Application->RecallVar('user_id'),
 				'SessionId' => $this->Application->GetSID(),
 				'Status' => SESSION_LOG_ACTIVE,
 			);
 
 			$object->SetDBFieldsFromHash($fields_hash);
 
 			$object->UpdateFormattersSubFields();
 
 			if ($object->Create()) {
 				$this->Application->StoreVar('_SessionLogId_', $object->GetID());
 			}
 		}
 
 		/**
 		 * Closes log for current session
 		 *
 		 * @param kEvent $event
 		 */
 		function OnEndSession($event)
 		{
 			$object = $this->Application->recallObject($event->Prefix, null, Array ('skip_autoload' => 1));
 			/* @var $object kDBItem */
 
 			$object->Load($this->Application->RecallVar('_SessionLogId_'));
 			if (!$object->isLoaded()) {
 				return ;
 			}
 
 			$fields_hash = Array (
 				'SessionEnd' => adodb_mktime(),
 				'Status' => SESSION_LOG_LOGGED_OUT,
 			);
 
 			$object->SetDBFieldsFromHash($fields_hash);
 
 			$object->UpdateFormattersSubFields();
 			$object->Update();
 		}
 
 		/**
 		 * Apply custom processing to item
 		 *
 		 * @param kEvent $event
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(kEvent $event, $type)
 		{
 			if ( $event->Name == 'OnMassDelete' && $type == 'before' ) {
 				$ids = $event->getEventParam('ids');
 				if ( $ids ) {
-					$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-					$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+					$config = $event->getUnitConfig();
+					$id_field = $config->getIDField();
 
 					$sql = 'SELECT ' . $id_field . '
-							FROM ' . $table_name . '
+							FROM ' . $config->getTableName() . '
 							WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ') AND Status <> ' . SESSION_LOG_ACTIVE;
 					$allowed_ids = $this->Conn->GetCol($sql);
 
 					$event->setEventParam('ids', $allowed_ids);
 				}
 			}
 		}
 
 		/**
 		 * Delete changes, related to deleted session
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
-			$sql = 'SELECT ' . $this->Application->getUnitOption('change-log', 'IDField') . '
-					FROM ' . $this->Application->getUnitOption('change-log', 'TableName') . '
+			$change_logs_config = $this->Application->getUnitConfig('change-log');
+
+			$sql = 'SELECT ' . $change_logs_config->getIDField() . '
+					FROM ' . $change_logs_config->getTableName() . '
 					WHERE SessionLogId = ' . $object->GetID();
 			$related_ids = $this->Conn->GetCol($sql);
 
 			if ( $related_ids ) {
 				$temp_handler = $this->Application->recallObject('change-log_TempHandler', 'kTempTablesHandler');
 				/* @var $temp_handler kTempTablesHandler */
 
 				$temp_handler->DeleteItems('change-log', '', $related_ids);
 			}
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/logs/system_logs/system_log_eh.php
===================================================================
--- branches/5.3.x/core/units/logs/system_logs/system_log_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/logs/system_logs/system_log_eh.php	(revision 15698)
@@ -1,120 +1,122 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class SystemLogEventHandler extends kDBEventHandler {
 
 	/**
 	 * Filters messages, that require e-mail notification to be sent
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function SetCustomQuery(kEvent $event)
 	{
 		parent::SetCustomQuery($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBList */
 
 		if ( $event->Special == 'email' ) {
 			$unique_id = $event->getEventParam('unique_id');
 
 			if ( $unique_id !== false ) {
 				$object->addFilter('notification_filter', '%1$s.LogNotificationStatus = ' . kLogger::LNS_SENT);
 				$object->addFilter('unique_filter', '%1$s.LogUniqueId = ' . $unique_id);
 			}
 			else {
 				$object->addFilter('notification_filter', '%1$s.LogNotificationStatus = ' . kLogger::LNS_PENDING);
 			}
 		}
 	}
 
 	/**
 	 * [SCHEDULED TASK] Sends delayed notification of system log messages
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSendNotifications(kEvent $event)
 	{
 		// initialize list outside of e-mail event with right settings
 		$list = $this->Application->recallObject($event->Prefix . '.email', $event->Prefix . '_List', Array ('per_page' => 20));
 		/* @var $list kDBList */
 
 		if ( !$list->GetRecordsCount() ) {
 			// no messages, that needs to be sent
 			return;
 		}
 
 		$notification_email = $this->Application->ConfigValue('SystemLogNotificationEmail');
 
 		if ( !$notification_email ) {
 			$this->Application->removeObject($event->Prefix . '.email');
 			trigger_error('System Log notification E-mail not specified', E_USER_NOTICE);
 
 			return;
 		}
 
 		$send_params = Array (
 			'to_name' => $notification_email,
 			'to_email' => $notification_email,
 		);
 
 		$this->Application->EmailEventAdmin('SYSTEM.LOG.NOTIFY', null, $send_params);
 		$this->Application->removeObject($event->Prefix . '.email');
 
 		$object = $event->getObject(Array ('skip_autoload' => true));
 		/* @var $object kDBItem */
 
 		foreach ($list as $fields_hash) {
 			$object->LoadFromHash($fields_hash);
 			$object->SetDBField('LogNotificationStatus', kLogger::LNS_SENT);
 			$object->Update();
 		}
 	}
 
 	/**
 	 * [SCHEDULED TASK] Will remove old system logs
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRotate(kEvent $event)
 	{
 		$rotation_interval = (int)$this->Application->ConfigValue('SystemLogRotationInterval');
 
 		if ( $rotation_interval === -1 ) {
 			// forever
 			return;
 		}
 
-		$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
-				FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+		$config = $event->getUnitConfig();
+
+		$sql = 'SELECT ' . $config->getIDField() . '
+				FROM ' . $config->getTableName() . '
 				WHERE ' . TIMENOW . ' - LogTimestamp > ' . $rotation_interval . '
 				LIMIT 0,50';
 		$ids = $this->Conn->GetCol($sql);
 
 		if ( $ids ) {
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
 		}
 	}
 }
Index: branches/5.3.x/core/units/logs/change_logs/change_log_eh.php
===================================================================
--- branches/5.3.x/core/units/logs/change_logs/change_log_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/logs/change_logs/change_log_eh.php	(revision 15698)
@@ -1,66 +1,66 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class ChangeLogEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnEnableLog' => Array ('self' => 'view'),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Updates affected record count in session, when change log record is deleted
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemDelete(kEvent $event)
 	{
 		parent::OnAfterItemDelete($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
-		$sql = 'UPDATE ' . $this->Application->getUnitOption('session-log', 'TableName') . '
+		$sql = 'UPDATE ' . $this->Application->getUnitConfig('session-log')->getTableName() . '
 				SET AffectedItems = AffectedItems - 1
 				WHERE SessionLogId = ' . $object->GetDBField('SessionLogId');
 		$this->Conn->Query($sql);
 	}
 
 	/**
 	 * Changes configuration value to enable log writing
 	 *
 	 * @param kEvent $event
 	 */
 	function OnEnableLog($event)
 	{
 		$this->Application->SetConfigValue('UseChangeLog', 1);
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/logs/email_logs/email_log_eh.php
===================================================================
--- branches/5.3.x/core/units/logs/email_logs/email_log_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/logs/email_logs/email_log_eh.php	(revision 15698)
@@ -1,126 +1,128 @@
 <?php
 /**
  * @version	$Id$
  * @package	In-Portal
  * @copyright	Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
  * @license	  GNU/GPL
  * In-Portal is Open Source software.
  * This means that this software may have been modified pursuant
  * the GNU General Public License, and as distributed it includes
  * or is derivative of works licensed under the GNU General Public License
  * or other free or open source software licenses.
  * See http://www.in-portal.org/license for copyright notices and details.
  */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class EmailLogEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnItemBuild' => Array ('self' => true),
 			'OnGetHtmlBody' => Array ('self' => 'edit'),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Returns ID of current item to be edited
 	 * by checking ID passed in get/post as prefix_id
 	 * or by looking at first from selected ids, stored.
 	 * Returned id is also stored in Session in case
 	 * it was explicitly passed as get/post
 	 *
 	 * @param kEvent $event
 	 * @return int
 	 * @access public
 	 */
 	public function getPassedID(kEvent $event)
 	{
 		if ( $this->Application->isAdmin ) {
 			return parent::getPassedID($event);
 		}
 
 		$authkey = $this->Application->GetVar('authkey');
 
 		return $authkey ? Array ('AccessKey' => $authkey) : false;
 	}
 
 	/**
 	 * [SCHEDULED TASK] Will remove old e-mail logs
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRotate(kEvent $event)
 	{
 		$rotation_interval = (int)$this->Application->ConfigValue('EmailLogRotationInterval');
 
 		if ( $rotation_interval === -1 ) {
 			// forever
 			return;
 		}
 
-		$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
-				FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+		$config = $event->getUnitConfig();
+
+		$sql = 'SELECT ' . $config->getIDField() . '
+				FROM ' . $config->getTableName() . '
 				WHERE ' . TIMENOW . ' - SentOn > ' . $rotation_interval;
 		$ids = $this->Conn->GetCol($sql);
 
 		if ( $ids ) {
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
 		}
 	}
 
 	/**
 	 * Returns HTML of sent e-mail for iframe
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnGetHtmlBody(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		echo '<html><body style="font-size: 14px;">' . $object->GetDBField('HtmlBody') . '</body></html>';
 	}
 
 	/**
 	 * Checks, that currently loaded item is allowed for viewing (non permission-based)
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access protected
 	 */
 	protected function checkItemStatus(kEvent $event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ( !$object->isLoaded() ) {
 			return true;
 		}
 
 		$access_key = $object->GetDBField('AccessKey');
 
 		return $access_key && $this->Application->GetVar('authkey') == $access_key;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/scheduled_tasks/scheduled_task_eh.php
===================================================================
--- branches/5.3.x/core/units/scheduled_tasks/scheduled_task_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/scheduled_tasks/scheduled_task_eh.php	(revision 15698)
@@ -1,297 +1,297 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ScheduledTaskEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnMassCancel' => Array ('self' => 'add|edit'),
 				'OnRun' => Array ('self' => 'add|edit'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Does custom validation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemValidate(kEvent $event)
 		{
 			parent::OnBeforeItemValidate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$event_string = $object->GetDBField('Event');
 
 			if ( !$event_string ) {
 				return;
 			}
 
 			try {
 				$this->Application->eventImplemented(new kEvent($event_string));
 			}
 			catch (Exception $e) {
 				$object->SetError('Event', 'invalid_event', '+' . $e->getMessage());
 			}
 		}
 
 		/**
 		 * [HOOK] Refreshes scheduled task list in database based on cached data from unit configs
 		 *
 		 * @param kEvent $event
 		 */
 		function OnRefresh($event)
 		{
 			$scheduled_tasks_from_cache = $this->Application->EventManager->getScheduledTasks(true);
 
 			$object = $event->getObject( Array ('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$processed_ids = Array ();
 			$scheduled_tasks_from_db = $this->Conn->Query($object->GetSelectSQL(), 'Name');
 
 			$cron_helper = $this->Application->recallObject('kCronHelper');
 			/* @var $cron_helper kCronHelper */
 
 			foreach ($scheduled_tasks_from_cache as $scheduled_task_name => $scheduled_task_params) {
 				if ( !isset($scheduled_tasks_from_db[$scheduled_task_name]) ) {
 					$fields_hash = Array (
 						'Event' => $scheduled_task_params['Event'],
 						'Name' => $scheduled_task_name,
 						'Type' => ScheduledTask::TYPE_SYSTEM,
 						'Status' => isset($scheduled_task_params['Status']) ? $scheduled_task_params['Status'] : STATUS_ACTIVE,
 						'RunSchedule' => $scheduled_task_params['RunSchedule'],
 					);
 
 					$object->Clear();
 					$object->SetDBFieldsFromHash($fields_hash);
 					$cron_helper->load($object, 'RunSchedule');
 					$object->Create();
 				}
 				else {
 					$object->LoadFromHash( $scheduled_tasks_from_db[$scheduled_task_name] );
 				}
 
 				$processed_ids[] = $object->GetID();
 			}
 
 			// delete all non-processed scheduled tasks (ones, that were deleted from unit configs)
 			$sql = 'SELECT ' . $object->IDField . '
 					FROM ' . $object->TableName . '
 					WHERE (Type = ' . ScheduledTask::TYPE_SYSTEM . ') AND (' . $object->IDField . ' NOT IN (' . implode(',', $processed_ids) . '))';
 			$delete_ids = $this->Conn->GetCol($sql);
 
 			if ($delete_ids) {
 				$temp_handler = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
 				/* @var $temp_handler kTempTablesHandler */
 
 				$temp_handler->DeleteItems($event->Prefix, $event->Special, $delete_ids);
 			}
 
 			$this->Application->removeObject($event->getPrefixSpecial());
 		}
 
 		/**
 		 * Don't allow to delete other user's messages
 		 *
 		 * @param kEvent $event
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(kEvent $event, $type)
 		{
 			if ( $event->Name == 'OnMassDelete' && $type == 'before' ) {
 				if ( $this->Application->isDebugMode() ) {
 					// allow to delete system scheduled tasks in debug mode
 					return;
 				}
 
 				$ids = $event->getEventParam('ids');
 
 				if ( $ids ) {
-					$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-					$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+					$config = $event->getUnitConfig();
+					$id_field = $config->getIDField();
 
 					$sql = 'SELECT ' . $id_field . '
-							FROM ' . $table_name . '
+							FROM ' . $config->getTableName() . '
 							WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ') AND Type <> ' . ScheduledTask::TYPE_SYSTEM;
 					$event->setEventParam('ids', $this->Conn->GetCol($sql));
 				}
 			}
 		}
 
 		/**
 		 * Cancels scheduled tasks, that are currently running
 		 *
 		 * @param kEvent $event
 		 */
 		function OnMassCancel($event)
 		{
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ($ids) {
 				$object = $event->getObject( Array ('skip_autoload' => true) );
 				/* @var $object kDBItem */
 
 				foreach ($ids as $id) {
 					$object->Load($id);
 
 					if ($object->GetDBField('LastRunStatus') == ScheduledTask::LAST_RUN_RUNNING) {
 						// only changes status, doesn't affect currency running scheduled tasks
 						$object->SetDBField('LastRunStatus', ScheduledTask::LAST_RUN_FAILED);
 						$object->Update();
 					}
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Runs selected scheduled tasks
 		 *
 		 * @param kEvent $event
 		 */
 		function OnRun($event)
 		{
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ($ids) {
 				$object = $event->getObject( Array ('skip_autoload' => true) );
 				/* @var $object kDBItem */
 
 				$where_clause = Array (
 					$object->TableName . '.' . $object->IDField . ' IN (' . implode(',', $ids) . ')',
 					$object->TableName . '.Status = ' . STATUS_ACTIVE,
 					$object->TableName . '.LastRunStatus <> ' . ScheduledTask::LAST_RUN_RUNNING,
 				);
 
 				$sql =	$object->GetSelectSQL() . '
 						WHERE (' . implode(') AND (', $where_clause) . ')';
 				$scheduled_tasks = $this->Conn->Query($sql);
 
 				foreach ($scheduled_tasks as $scheduled_task_data) {
 					$this->Application->EventManager->runScheduledTask($scheduled_task_data);
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Loads schedule from database to virtual fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$cron_helper = $this->Application->recallObject('kCronHelper');
 			/* @var $cron_helper kCronHelper */
 
 			$cron_helper->load($object, 'RunSchedule');
 		}
 
 		/**
 		 * Validates schedule
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$this->_itemChanged($event);
 		}
 
 		/**
 		 * Validates schedule
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->_itemChanged($event);
 		}
 
 		/**
 		 * Validates schedule
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function _itemChanged(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$cron_helper = $this->Application->recallObject('kCronHelper');
 			/* @var $cron_helper kCronHelper */
 
 			if ( $cron_helper->validateAndSave($object, 'RunSchedule') && !$object->GetDBField('NextRunOn_date') ) {
 				$next_run = $cron_helper->getMatch($object->GetDBField('RunSchedule'));
 				$object->SetDBField('NextRunOn_date', $next_run);
 				$object->SetDBField('NextRunOn_time', $next_run);
 			}
 		}
 
 		/**
 		 * Creates schedule fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			$cron_helper = $this->Application->recallObject('kCronHelper');
 			/* @var $cron_helper kCronHelper */
 
 			$cron_helper->initUnit($event->Prefix, 'RunSchedule');
 
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/images/image_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/images/image_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/images/image_tag_processor.php	(revision 15698)
@@ -1,497 +1,498 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class ImageTagProcessor extends kDBTagProcessor {
 
 	/**
 	 * Prepares all image parameters as list block parameters (for easy usage)
 	 *
 	 * @param kDBList $object
 	 * @param Array $block_params
 	 * @return void
 	 * @access protected
 	 * @author Alex
 	 */
 	protected function PrepareListElementParams(&$object, &$block_params)
 	{
 		$image_url = $this->ImageSrc($block_params);
 		if ( !$image_url ) {
 			return ;
 		}
 
-		$parent_prefix = $this->Application->getUnitOption($object->Prefix, 'ParentPrefix');
+		$parent_prefix = $object->getUnitConfig()->getParentPrefix();
 
 		$parent_item = $this->Application->recallObject($parent_prefix);
 		/* @var $parent_item kDBItem */
 
 		$block_params['img_path'] = $image_url;
 		$image_dimensions = $this->ImageSize($block_params);
 		$block_params['img_size'] = $image_dimensions ? $image_dimensions : ' width="' . $block_params['DefaultWidth'] . '"';
 		$block_params['alt'] = $object->GetField('AltName') ? $object->GetField('AltName') : htmlspecialchars($this->getItemTitle($parent_item), null, CHARSET);
 		$block_params['align'] = array_key_exists('align', $block_params) ? $block_params['align'] : 'left';
 	}
 
 	/**
 	 * Returns value of object's title field
 	 *
 	 * @param kDBItem $object
 	 * @return string
 	 * @access protected
 	 */
 	protected function getItemTitle(&$object)
 	{
-		$title_field = $this->Application->getUnitOption($object->Prefix, 'TitleField');
+		$title_field = $object->getUnitConfig()->getTitleField();
 
 		return $object->GetField($title_field);
 	}
 
 	/**
 	 * [AGGREGATED TAGS] works as <inp2:CatalogItemPrefix_Image, ImageSize, ImageSrc ..../>
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ItemImageTag($params)
 	{
 		$this->LoadItemImage($params);
 		return $this->$params['original_tag']($params);
 	}
 
 	function LargeImageExists($params)
 	{
 		$object = $this->getObject($params);
 		if ($object->GetDBField('SameImages') == null || $object->GetDBField('SameImages') == 1) {
 			return false;
 		}
 		else {
 			return true;
 		}
 	}
 
 	function LoadItemImage($params)
 	{
 		$parent_item = $this->Application->recallObject($params['PrefixSpecial']);
 		/* @var $parent_item kCatDBItem */
 
 		$object = $this->Application->recallObject($this->getPrefixSpecial(), null, Array('skip_autoload' => true));
 		/* @var $object kDBItem */
 
 		$object->Clear();
 
 		// if we need primary thumbnail which is preloaded with category item's list
 		$is_primary = $this->SelectParam($params, 'primary,Primary');
 		$image_name = $this->SelectParam($params, 'name,Name');
 		$image_field = $this->SelectParam($params, 'field,Field'); // ie. virtual names PrimaryImage, Image1, Image2
 		$image_id = $this->Application->GetVar($this->Prefix.'_id');
 
 		if (
 			// is primary, when primary mark set OR name & field not given
 			($is_primary || !($image_name || $image_field)) &&
 
 			// primary image is preloaded AND direct id not given
 			$parent_item->isField('ThumbPath') && !$image_id
 		) {
 			if (is_null($parent_item->GetDBField('SameImages'))) {
 				// JOIN definetly failed, because it's not-null column
 				$object->setLoaded(false);
 			}
 			else {
 				$object->SetDBField('Url', $parent_item->GetDBField('FullUrl'));
 
 				$object->SetDBFieldsFromHash($parent_item->GetFieldValues(), null, Array('AltName', 'SameImages', 'LocalThumb', 'ThumbPath', 'ThumbUrl', 'LocalImage', 'LocalPath'));
 
 				if (!$object->GetDBField('AltName')) {
 					$object->SetDBField('AltName', $this->getItemTitle($parent_item));
 				}
 
 				$object->setLoaded();
 			}
 		}
 		else { // if requested image is not primary thumbnail - load it directly
-			$id_field = $this->Application->getUnitOption($this->Prefix, 'ForeignKey');
-			$parent_table_key = $this->Application->getUnitOption($this->Prefix, 'ParentTableKey');
+			$config = $this->getUnitConfig();
+			$id_field = $config->getForeignKey();
+			$parent_table_key = $config->getParentTableKey();
 
 			$keys[$id_field] = $parent_item->GetDBField($parent_table_key);
 
 			// which image to load?
 			if ($is_primary) {
 				// by PrimaryImage mark
 				$keys['DefaultImg'] = 1;
 			}
 			elseif ($image_name) {
 				// by ImageName
 				$keys['Name'] = $image_name;
 			}
 			elseif ($image_field) {
 				// by virtual field name in main object
 				$field_options = $parent_item->GetFieldOptions( $image_field );
 				$keys['Name'] = isset($field_options['original_field']) ? $field_options['original_field'] : $image_field;
 			}
 			elseif ($image_id) {
 				// by ID
 				$keys['ImageId'] = $image_id;
 			}
 			else {
 				// by PrimaryImage if no other criteria given
 				$keys['DefaultImg'] = 1;
 			}
 
 			$object->Load($keys);
 
 			if ( $image_field ) {
 				$image_src = $parent_item->GetDBField( $image_field );
 
 				// when image is uploaded to virtual field in main item, but not saved to db
 				$object->SetDBField('ThumbPath', $image_src);
 
 				if (!$object->isLoaded() && $image_src) {
 					// set fields for displaying new image during main item suggestion with errors
 					$fields_hash = Array (
 						'Url' => '',
 						'ThumbUrl' => '',
 						'LocalPath' => '',
 
 						'SameImages' => 1,
 						'LocalThumb' => 1,
 						'LocalImage' => 1,
 					);
 
 					$object->SetDBFieldsFromHash($fields_hash);
 					$object->setLoaded();
 				}
 			}
 		}
 	}
 
 	function getImageDimension($type, $params)
 	{
 		$ret = isset($params['Max'.$type]) ? $params['Max'.$type] : false;
 		if (!$ret) {
 			return $ret;
 		}
-		$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
+		$parent_prefix = $this->getUnitConfig()->getParentPrefix();
 
 		if ($ret == 'thumbnail') {
 			$ret = $this->Application->ConfigValue($parent_prefix.'_ThumbnailImage'.$type);
 		}
 		if ($ret == 'fullsize') {
 			$ret = $this->Application->ConfigValue($parent_prefix.'_FullImage'.$type);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Appends "/" to beginning of image path (in case when missing)
 	 *
 	 * @param kDBItem $object
 	 * @todo old in-portal doesn't append first slash, but we do => append first slash for him :)
 	 */
 	function makeRelativePaths(&$object)
 	{
 		$thumb_path = $object->GetDBField('ThumbPath');
 		if ($thumb_path && substr($thumb_path, 0, 1) != DIRECTORY_SEPARATOR) {
 			$object->SetDBField('ThumbPath', DIRECTORY_SEPARATOR . $thumb_path);
 		}
 
 		$local_path = $object->GetDBField('LocalPath');
 		if ($local_path && substr($local_path, 0, 1) != DIRECTORY_SEPARATOR) {
 			$object->SetDBField('LocalPath', DIRECTORY_SEPARATOR . $local_path);
 		}
 	}
 
 	function ImageSrc($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$this->makeRelativePaths($object);
 
 		// show "noimage.gif" when requested image is missing OR was not uploaded
 		$use_default_image = !(defined('DBG_IMAGE_RECOVERY') && DBG_IMAGE_RECOVERY);
 
 		$src_image_url = $this->_getImageUrl($params);
 		$src_image = $this->_getImagePath($src_image_url);
 
 		if (!$object->isLoaded() || ($src_image_url && $src_image)) {
 			// we can auto-resize image, when it is stored locally
 			$max_width = $this->getImageDimension('Width', $params);
 			$max_height = $this->getImageDimension('Height', $params);
 			$format = array_key_exists('format', $params) ? $params['format'] : false;
 
 			if (!$max_width && $format) {
 				// user watermarks from format param
 				$max_width = $format;
 			}
 
 			if ($max_width > 0 || $max_height > 0 || $format) {
 				list ($max_width, $max_height) = $this->_transformParams($params, $max_width, $max_height);
 
 				if ($object->isLoaded() && file_exists($src_image)) {
 					$image_helper = $this->Application->recallObject('ImageHelper');
 					/* @var $image_helper ImageHelper */
 
 					return $image_helper->ResizeImage($src_image, $max_width, $max_height);
 				}
 				elseif ($use_default_image) {
 					return $this->_getDefaultImage($params, $max_width, $max_height);
 				}
 
 				return $src_image_url;
 			}
 		}
 
 		if ($src_image_url) {
 			// convert full url to full path!
 			$dst_image = $this->_getImagePath($src_image_url);
 			$image_found = $dst_image ? file_exists($dst_image) : true;
 
 			if ($image_found) {
 				// image isn't deleted OR is stored on remote location
 				return $src_image_url;
 			}
 		}
 
 		// return Default Image or false if NOT specified (only for case, when SameImages = 0)
 		return $use_default_image ? $this->_getDefaultImage($params) : $src_image_url;
 	}
 
 	/**
 	 * Get location on disk for images, stored locally and false for remote images
 	 *
 	 * @param string $src_image
 	 * @return string
 	 */
 	function _getImagePath($src_image)
 	{
 		if (!$src_image) {
 			return false;
 		}
 
 		$file_helper = $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$dst_image = $file_helper->urlToPath($src_image);
 
 		return $dst_image != $src_image ? $dst_image : false;
 	}
 
 	function _getImageUrl($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$base_url = rtrim($this->Application->BaseURL(), '/');
 
 		// if we need thumbnail, or full image is same as thumbnail
 		$show_thumbnail =	$this->SelectParam($params, 'thumbnail,Thumbnail') || // old style
 							(isset($params['MaxWidth']) && $params['MaxWidth'] == 'thumbnail') || // new style
 							(isset($params['MaxHeight']) && $params['MaxHeight'] == 'thumbnail');
 
 		if ($show_thumbnail || $object->GetDBField('SameImages')) {
 			// return local image or url
 			$ret = $object->GetDBField('LocalThumb') ? $base_url . $object->GetDBField('ThumbPath') : $object->GetDBField('ThumbUrl');
 		}
 		else { // if we need full which is not the same as thumb
 			$ret = $object->GetDBField('LocalImage') ? $base_url . $object->GetDBField('LocalPath') : $object->GetDBField('Url');
 		}
 
 		return $ret == $base_url ? '' : $ret;
 	}
 
 	/**
 	 * Transforms Image/ImageSrc aggregated tag parameters into ones, that ResizeImage method understands
 	 *
 	 * @param Array $params
 	 * @param int|bool $max_width
 	 * @param int|bool $max_height
 	 * @return Array
 	 */
 	function _transformParams($params, $max_width = false, $max_height = false)
 	{
 		$resize_format = 'resize:' . $max_width . 'x' . $max_height;
 
 		$crop = $this->SelectParam($params, 'Crop,crop');
 		if ($crop) {
 			if (strpos($crop, ';') === false) {
 				$crop = 'c|c';
 			}
 
 			$max_width = (is_null($max_height) ? $max_width : $resize_format) . ';crop:' . $crop;
 			$max_height = null;
 		}
 
 		$fill = $this->SelectParam($params, 'Fill,fill');
 		if ($fill) {
 			$max_width = (is_null($max_height) ? $max_width : $resize_format) . ';fill:' . $fill;
 			$max_height = null;
 		}
 
 		$watermark = $this->SelectParam($params, 'Watermark,watermark');
 		if ($watermark) {
 			$max_width = (is_null($max_height) ? $max_width : $resize_format) . ';wm:' . $watermark;
 			$max_height = null;
 		}
 
 		return Array ($max_width, $max_height);
 	}
 
 	/**
 	 * Returns default full url to default images
 	 *
 	 * @param Array $params
 	 * @param int|bool $max_width
 	 * @param int|bool $max_height
 	 * @return string
 	 */
 	function _getDefaultImage($params, $max_width = false, $max_height = false)
 	{
 		$default_image = $this->SelectParam($params, 'default_image,DefaultImage');
 		if (!$default_image) {
 			return '';
 		}
 
 		// show default image, use different base urls for admin and front-end
 		$base_url = rtrim($this->Application->BaseURL(), '/');
 		$sub_folder = $this->Application->isAdmin ? rtrim(IMAGES_PATH, '/') : THEMES_PATH;
 
 		if (($max_width !== false) || ($max_height !== false)) {
 			$image_helper = $this->Application->recallObject('ImageHelper');
 			/* @var $image_helper ImageHelper */
 
 			$src_image = FULL_PATH . $sub_folder . '/' . $default_image;
 
 			return $image_helper->ResizeImage($src_image, $max_width, $max_height);
 		}
 
 		return $base_url . $sub_folder . '/' . $default_image;
 	}
 
 	function getFullPath($path)
 	{
 		if (!$path) {
 			return $path;
 		}
 
 		// absolute url
 		if (preg_match('/^(.*):\/\/(.*)$/U', $path)) {
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			return $file_helper->urlToPath($path);
 		}
 
 		// TODO: change to urlToPath usage later
 		// relative url (we add sort of <inp2:m_TemplatesBase/> does
 
 		return FULL_PATH . '/' . mb_substr(THEMES_PATH, 1) . '/' . rawurldecode($path);
 	}
 
 	/**
 	 * Makes size clause for img tag, such as
 	 * ' width="80" height="100"' according to max_width
 	 * and max_heght limits.
 	 *
 	 * @param array $params
 	 * @return string
 	 */
 	function ImageSize($params)
 	{
 		$img_path = $this->getFullPath($params['img_path']);
 
 		$image_helper = $this->Application->recallObject('ImageHelper');
 		/* @var $image_helper ImageHelper */
 
 		$max_width = $this->getImageDimension('Width', $params);
 		$max_height = $this->getImageDimension('Height', $params);
 
 		$image_dimensions = $image_helper->GetImageDimensions($img_path, $max_width, $max_height, $params);
 		if (!$image_dimensions) {
 			return false;
 		}
 
 		return ' width="'.$image_dimensions[0].'" height="'.$image_dimensions[1].'"';
 	}
 
 	/**
 	 * Prepares image parameters & parses block with them (for admin)
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function Image($params)
 	{
 		$image_url = $this->ImageSrc($params);
 
 		if ( !$image_url ) {
 			return '';
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$params['img_path'] = $image_url;
 		$image_dimensions = $this->ImageSize($params);
 		$params['img_size'] = $image_dimensions ? $image_dimensions : ' width="' . $params['DefaultWidth'] . '"';
 		$params['alt'] = htmlspecialchars($object->GetField('AltName'), null, CHARSET); // really used ?
 		$params['name'] = $this->SelectParam($params, 'block,render_as');
 		$params['align'] = array_key_exists('align', $params) ? $params['align'] : 'left';
 		$params['no_editing'] = 1;
 
 		if ( !$object->isLoaded() && !$this->SelectParam($params, 'default_image,DefaultImage') ) {
 			return '';
 		}
 
 		return $this->Application->ParseBlock($params);
 	}
 
 	/**
 	 * Returns url for image in case when image source is url (for admin)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ImageUrl($params)
 	{
 		$object = $this->getObject($params);
 		if ($object->GetDBField('SameImages') ? $object->GetDBField('LocalThumb') : $object->GetDBField('LocalImage') ) {
 			$ret = $this->Application->Phrase(getArrayValue($params,'local_phrase'));
 		}
 		else {
 			$ret = $object->GetDBField('SameImages') ? $object->GetDBField('ThumbUrl') : $object->GetDBField('Url');
 		}
 		return $ret;
 	}
 
 	/**
 	 * If data was modfied & is in TempTables mode, then parse block with name passed;
 	 * remove modification mark if not in TempTables mode
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 * @author Alexey
 	 */
 	function SaveWarning($params)
 	{
 		if ($this->Prefix == 'c-img') {
 			return $this->Application->ProcessParsedTag('c', 'SaveWarning', $params);
 		}
 
 		return parent::SaveWarning($params);
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/images/image_event_handler.php
===================================================================
--- branches/5.3.x/core/units/images/image_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/images/image_event_handler.php	(revision 15698)
@@ -1,496 +1,496 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class ImageEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnCleanImages' => Array ('subitem' => true),
 			'OnCleanResizedImages' => Array ('subitem' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 
 	/**
 	 * Define alternative event processing method names
 	 *
 	 * @return void
 	 * @see kEventHandler::$eventMethods
 	 * @access protected
 	 */
 	protected function mapEvents()
 	{
 		parent::mapEvents();	// ensure auto-adding of approve/decline and so on events
 
 		$image_events = Array (
 			'OnAfterCopyToTemp'=>'ImageAction',
 			'OnBeforeDeleteFromLive'=>'ImageAction',
 			'OnBeforeCopyToLive'=>'ImageAction',
 			'OnBeforeItemDelete'=>'ImageAction',
 			'OnAfterClone'=>'ImageAction',
 		);
 
 		$this->eventMethods = array_merge($this->eventMethods, $image_events);
 	}
 
 	/**
 	 * Returns special of main item for linking with sub-item
 	 *
 	 * @param kEvent $event
 	 * @return string
 	 * @access protected
 	 */
 	protected function getMainSpecial(kEvent $event)
 	{
 		if ( $event->Special == 'list' && !$this->Application->isAdmin ) {
 			// ListImages aggregated tag uses this special
 			return '';
 		}
 
 		return parent::getMainSpecial($event);
 	}
 
 	/**
 	 * Don't allow to delete primary category item image, when there are no more images
 	 *
 	 * @param kEvent $event
 	 * @param string $type
 	 * @return void
 	 * @access protected
 	 */
 	protected function customProcessing(kEvent $event, $type)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ( $event->Name == 'OnMassDelete' && $type == 'before' ) {
 			$ids = $event->getEventParam('ids');
 
 			$parent_info = $object->getLinkedInfo($event->Special);
 
 			$sql = 'SELECT ImageId
 					FROM ' . $object->TableName . '
 					WHERE DefaultImg = 1 AND ' . $parent_info['ForeignKey'] . ' = ' . $parent_info['ParentId'];
 			$primary_file_id = $this->Conn->GetOne($sql);
 
 			if ( $primary_file_id ) {
 				$file_id_index = array_search($primary_file_id, $ids);
 
 				if ( $file_id_index ) {
 					// allow deleting of primary product file, when there is another file to make primary
 					$sql = 'SELECT COUNT(*)
 							FROM ' . $object->TableName . '
 							WHERE DefaultImg = 0 AND ' . $parent_info['ForeignKey'] . ' = ' . $parent_info['ParentId'];
 					$non_primary_file_count = $this->Conn->GetOne($sql);
 
 					if ( $non_primary_file_count ) {
 						unset($ids[$file_id_index]);
 					}
 				}
 			}
 
 			$event->setEventParam('ids', $ids);
 		}
 
 		switch ($type) {
 			case 'before' :
 				// empty unused fields
 				$object->SetDBField($object->GetDBField('LocalImage') ? 'Url' : 'LocalPath', '');
 				$object->SetDBField($object->GetDBField('LocalThumb') ? 'ThumbUrl' : 'ThumbPath', '');
 
 				if ( $object->GetDBField('SameImages') ) {
 					$object->SetDBField('LocalImage', 1);
 					$object->SetDBField('LocalPath', '');
 					$object->SetDBField('Url', '');
 				}
 				break;
 
 			case 'after':
 				// make sure, that there is only one primary image for the item
 				if ( $object->GetDBField('DefaultImg') ) {
 					$sql = 'UPDATE ' . $object->TableName . '
 							SET DefaultImg = 0
 							WHERE ResourceId = ' . $object->GetDBField('ResourceId') . ' AND ImageId <> ' . $object->GetID();
 					$this->Conn->Query($sql);
 				}
 				break;
 		}
 	}
 
 	/**
 	 * Performs temp-table related action on current image record
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function ImageAction($event)
 	{
 		$id = $event->getEventParam('id');
 
 		$object = $this->Application->recallObject($event->Prefix . '.-item', $event->Prefix, Array ('skip_autoload' => true));
 		/* @var $object kDBItem */
 
 		if ( in_array($event->Name, Array ('OnBeforeDeleteFromLive', 'OnAfterClone')) ) {
 			$object->SwitchToLive();
 		}
 		elseif ( $event->Name == 'OnBeforeItemDelete' ) {
 			// keep current table
 		}
 		else {
 			$object->SwitchToTemp();
 		}
 
 		$object->Load($id);
 
 		$file_helper = $this->Application->recallObject('FileHelper');
 		/* @var $file_helper FileHelper */
 
 		$fields = Array ('LocalPath' => 'LocalImage', 'ThumbPath' => 'LocalThumb');
 
 		foreach ($fields as $a_field => $mode_field) {
 			$file = $object->GetDBField($a_field);
 
 			if ( !$file ) {
 				continue;
 			}
 
 			$source_file = FULL_PATH . $file;
 
 			switch ($event->Name) {
 				// Copy image files to pending dir and update corresponding fields in temp record
 				// Checking for existing files and renaming if necessary - two users may upload same pending files at the same time!
 				case 'OnAfterCopyToTemp':
 					$file = preg_replace('/^' . preg_quote(IMAGES_PATH, '/') . '/', IMAGES_PENDING_PATH, $file, 1);
 					$new_file = $file_helper->ensureUniqueFilename(FULL_PATH, $file);
 
 					$dst_file = FULL_PATH . $new_file;
 					copy($source_file, $dst_file);
 
 					$object->SetFieldOption($a_field, 'skip_empty', false);
 					$object->SetDBField($a_field, $new_file);
 					break;
 
 				// Copy image files to live dir (checking if file exists and renaming if necessary)
 				// and update corresponding fields in temp record (which gets copied to live automatically)
 				case 'OnBeforeCopyToLive':
 					if ( $object->GetDBField($mode_field) ) {
 						// if image is local -> rename file if it exists in live folder
 						$file = preg_replace('/^' . preg_quote(IMAGES_PENDING_PATH, '/') . '/', IMAGES_PATH, $file, 1);
 						$new_file = $file_helper->ensureUniqueFilename(FULL_PATH, $file);
 
 						$dst_file = FULL_PATH . $new_file;
 						rename($source_file, $dst_file);
 					}
 					else {
 						// if image is remote url - remove local file (if any), update local file field with empty value
 						if ( file_exists($source_file) ) {
 							@unlink($source_file);
 						}
 
 						$new_file = '';
 					}
 
 					$object->SetFieldOption($a_field, 'skip_empty', false);
 					$object->SetDBField($a_field, $new_file);
 					break;
 
 				case 'OnBeforeDeleteFromLive': // Delete image files from live folder before copying over from temp
 				case 'OnBeforeItemDelete': // Delete image files when deleting Image object
 					@unlink(FULL_PATH . $file);
 					break;
 
 				case 'OnAfterClone':
 					// Copy files when cloning objects, renaming it on the fly
 					$new_file = $file_helper->ensureUniqueFilename(FULL_PATH, $file);
 					$dst_file = FULL_PATH . $new_file;
 					copy($source_file, $dst_file);
 
 					$object->SetFieldOption($a_field, 'skip_empty', false);
 					$object->SetDBField($a_field, $new_file);
 					break;
 			}
 		}
 
 		if ( in_array($event->Name, Array ('OnAfterClone', 'OnBeforeCopyToLive', 'OnAfterCopyToTemp')) ) {
 			$object->Update(null, null, true);
 		}
 	}
 
 	/**
 	 * Sets primary image of user/category/category item
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSetPrimary($event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$object->SetDBField('DefaultImg', 1);
 		$object->Update();
 	}
 
 	/**
 	 * Occurs before updating item
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemUpdate(kEvent $event)
 	{
 		parent::OnBeforeItemUpdate($event);
 
 		$this->processImageStatus($event);
 	}
 
 	/**
 	 * Occurs after creating item
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemCreate(kEvent $event)
 	{
 		parent::OnAfterItemCreate($event);
 
 		$this->processImageStatus($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$object->Update();
 	}
 
 	/**
 	 * Occurs before item changed
 	 *
 	 * @param kEvent $event
 	 */
 	function processImageStatus($event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$id = $object->GetDBField('ResourceId');
 
 		$sql = 'SELECT ImageId
 				FROM ' . $object->TableName . '
 				WHERE ResourceId = ' . $id . ' AND DefaultImg = 1';
 		$primary_image_id = $this->Conn->GetOne($sql);
 
 		if ( !$primary_image_id ) {
 			$object->SetDBField('DefaultImg', 1);
 		}
 
 		if ( $object->GetDBField('DefaultImg') && $object->Validate() ) {
 			$sql = 'UPDATE ' . $object->TableName . '
 					SET DefaultImg = 0
 					WHERE ResourceId = ' . $id . ' AND ImageId <> ' . $object->GetDBField('ImageId');
 			$this->Conn->Query($sql);
 
 			$object->SetDBField('Enabled', 1);
 		}
 	}
 
 	/**
 	 * Apply any custom changes to list's sql query
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 * @see kDBEventHandler::OnListBuild()
 	 */
 	protected function SetCustomQuery(kEvent $event)
 	{
 		parent::SetCustomQuery($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBList */
 
 		if ( !$this->Application->isAdminUser ) {
 			$object->addFilter('active', '%1$s.Enabled = 1');
 		}
 
 		$product_id = $event->getEventParam('product_id');
 
 		if ( $product_id ) {
 			$object->removeFilter('parent_filter');
 
 			$sql = 'SELECT ResourceId
-					FROM ' . $this->Application->getUnitOption('p', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('p')->getTableName() . '
 					WHERE ProductId = ' . $product_id;
 			$resource_id = (int)$this->Conn->GetOne($sql);
 
 			$object->addFilter('product_images', '%1$s.ResourceId = ' . $resource_id);
 		}
 
 		$search_helper = $this->Application->recallObject('SearchHelper');
 		/* @var $search_helper kSearchHelper */
 
 		$types = $event->getEventParam('types');
 		$except_types = $event->getEventParam('except');
 		$type_clauses = $this->getTypeClauses($event);
 
 		$search_helper->SetComplexFilter($event, $type_clauses, $types, $except_types);
 	}
 
 	/**
 	 * Return type clauses for list bulding on front
 	 *
 	 * @param kEvent $event
 	 * @return Array
 	 */
 	function getTypeClauses($event)
 	{
 		$type_clauses = Array ();
 
 		$type_clauses['additional']['include'] = '%1$s.DefaultImg != 1';
 		$type_clauses['additional']['except'] = '%1$s.DefaultImg = 1';
 		$type_clauses['additional']['having_filter'] = false;
 
 		return $type_clauses;
 	}
 
 	/**
 	 * [SCHEDULED TASK] Remove unused images from "/system/images" and "/system/images/pending" folders
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCleanImages($event)
 	{
 		// 1. get images, that are currently in use
-		$active_images = $this->_getActiveImages( $this->Application->getUnitOption('img', 'TableName') );
+		$active_images = $this->_getActiveImages( $this->Application->getUnitConfig('img')->getTableName() );
 		$active_images[] = 'noimage.gif';
 
 		// 2. get images on disk
 		$this->_deleteUnusedImages(FULL_PATH . IMAGES_PATH, $active_images);
 
 		// 3. get images in use from "images/pending" folder
 		$active_images = $this->_getPendingImages();
 
 		// 4. get image on disk
 		$this->_deleteUnusedImages(FULL_PATH . IMAGES_PENDING_PATH, $active_images);
 	}
 
 	/**
 	 * Gets image filenames (no path) from given table
 	 *
 	 * @param string $image_table
 	 * @return Array
 	 */
 	function _getActiveImages($image_table)
 	{
 		$sql = 'SELECT LocalPath, ThumbPath
 				FROM ' . $image_table . '
 				WHERE COALESCE(LocalPath, "") <> "" OR COALESCE(ThumbPath) <> ""';
 		$images = $this->Conn->Query($sql);
 
 		$active_images = Array ();
 		foreach ($images as $image) {
 			if ($image['LocalPath']) {
 				$active_images[] = basename($image['LocalPath']);
 			}
 
 			if ($image['ThumbPath']) {
 				$active_images[] = basename($image['ThumbPath']);
 			}
 		}
 
 		return $active_images;
 	}
 
 	/**
 	 * Gets active images, that are currently beeing edited inside temporary tables
 	 *
 	 * @return Array
 	 */
 	function _getPendingImages()
 	{
 		$tables = $this->Conn->GetCol('SHOW TABLES');
 		$mask_edit_table = '/'.TABLE_PREFIX.'ses_(.*)_edit_' . TABLE_PREFIX . 'CatalogImages/';
 
 		$active_images = Array ();
 
 		foreach ($tables as $table) {
 			if (!preg_match($mask_edit_table, $table)) {
 				continue;
 			}
 
 			$active_images = array_unique( array_merge($active_images, $this->_getActiveImages($table)) );
 		}
 
 		return $active_images;
 	}
 
 	/**
 	 * Deletes all files in given path, except of given $active_images
 	 *
 	 * @param string $path
 	 * @param Array $active_images
 	 */
 	function _deleteUnusedImages($path, &$active_images)
 	{
 		$images = glob($path . '*.*');
 		if ($images) {
 			$images = array_map('basename', $images);
 
 			// delete images, that are on disk, but are not mentioned in CatalogImages table
 			$delete_images = array_diff($images, $active_images);
 			foreach ($delete_images as $delete_image) {
 				unlink($path . $delete_image);
 			}
 		}
 	}
 
 	/**
 	 * [SCHEDULED TASK] Remove all images from "/system/images/resized" and "/system/images/pending/resized" folders
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCleanResizedImages($event)
 	{
 		$images = glob(FULL_PATH . IMAGES_PATH . 'resized/*.*');
 		if ($images) {
 			foreach ($images as $image) {
 				unlink($image);
 			}
 		}
 
 		$images = glob(FULL_PATH . IMAGES_PENDING_PATH . 'resized/*.*');
 		if ($images) {
 			foreach ($images as $image) {
 				unlink($image);
 			}
 		}
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/configuration/configuration_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/configuration/configuration_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/configuration/configuration_tag_processor.php	(revision 15698)
@@ -1,297 +1,294 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class ConfigurationTagProcessor extends kDBTagProcessor {
 
 	public function __construct()
 	{
 		parent::__construct();
 
 		$this->Application->LinkVar('module_key');
 	}
 
 	/**
 	 * Prints list content using block specified
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PrintList($params)
 	{
 		$list =& $this->GetList($params);
-		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
+		$id_field = $this->getUnitConfig()->getIDField();
 
 		$list->Query();
 		$o = '';
 		$list->GoFirst();
 
 		$block_params = $this->prepareTagParams($params);
 		$block_params['pass_params'] = 'true';
 		$block_params['IdField'] = $list->IDField;
 
 		$prev_heading = '';
 		$next_block = $params['full_block'];
 		$list->groupRecords('Heading');
 		$field_values = $this->Application->GetVar($this->getPrefixSpecial(true));
 
 		while (!$list->EOL()) {
 			$this->Application->SetVar($this->getPrefixSpecial() . '_id', $list->GetDBField($id_field)); // for edit/delete links using GET
 
 			// using 2 blocks for drawing o row in case if current & next record titles match
 			$next_record =& $list->getCurrentRecord(1);
 			$this_key = $list->GetDBField('Prompt') . ':' . $list->GetDBField('DisplayOrder');
 			$next_key = $next_record !== false ? $next_record['Prompt'] . ':' . $next_record['DisplayOrder'] : '';
 
 			if ( $next_key == $this_key ) {
 				$curr_block = $params['half_block1'];
 				$next_block = $params['half_block2'];
 			}
 			else {
 				$curr_block = $next_block;
 				$next_block = $params['full_block'];
 			}
 
 			$variable_name = $list->GetDBField('VariableName');
 
 			// allows to override value part of block
 			if ( $this->Application->ParserBlockFound('cf_' . $variable_name . '_value') ) {
 				$block_params['value_render_as'] = 'cf_' . $variable_name . '_value';
 			}
 			else {
 				$block_params['value_render_as'] = $params['value_render_as'];
 			}
 
 			// allow to completely override whole block
 			if ( $this->Application->ParserBlockFound('cf_' . $variable_name . '_element') ) {
 				$block_params['name'] = 'cf_' . $variable_name . '_element';
 				$block_params['original_render_as'] = $curr_block;
 			}
 			else {
 				$block_params['name'] = $curr_block;
 				$block_params['original_render_as'] = $curr_block;
 			}
 
 			$block_params['show_heading'] = ($prev_heading != $list->GetDBField('Heading')) ? 1 : 0;
 
 			// set values from submit if any
 			if ( $field_values ) {
 				$list->SetDBField('VariableValue', $field_values[$list->GetID()]['VariableValue']);
 			}
 			$list->SetDBField('DirectOptions', '');
 
 			$o .= $this->Application->ParseBlock($block_params);
 			$prev_heading = $list->GetDBField('Heading');
 			$list->GoNext();
 		}
 
 		$this->Application->RemoveVar('ModuleRootCategory');
 		$this->Application->SetVar($this->getPrefixSpecial() . '_id', '');
 
 		return $o;
 	}
 
 	function getModuleItemName()
 	{
 		$module = $this->Application->GetVar('module');
-		$table = $this->Application->getUnitOption('confs', 'TableName');
+		$table = $this->Application->getUnitConfig('confs')->getTableName();
 
 		$sql = 'SELECT ConfigHeader
-				FROM '.$table.'
-				WHERE ModuleName = '.$this->Conn->qstr($module);
+				FROM ' . $table . '
+				WHERE ModuleName = ' . $this->Conn->qstr($module);
+
 		return $this->Conn->GetOne($sql);
 	}
 
 	function PrintConfList($params)
 	{
 		$list =& $this->GetList($params);
-		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 
 		$list->Query();
-		$o = '';
 		$list->GoFirst();
 
-		$tmp_row = Array();
+		$tmp_row = Array ();
 
-		while (!$list->EOL()) {
+		while ( !$list->EOL() ) {
 			$rec = $list->getCurrentRecord();
 			$tmp_row[0][$rec['VariableName']] = $rec['VariableValue'];
-			$tmp_row[0][$rec['VariableName'].'_prompt'] = $rec['Prompt'];
+			$tmp_row[0][$rec['VariableName'] . '_prompt'] = $rec['Prompt'];
 			$list->GoNext();
 		}
 
 		$list->Records = $tmp_row;
 
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 		$block_params['module_key'] = $this->Application->GetVar('module_key');
 		$block_params['module_item'] = $this->getModuleItemName();
 		$list->GoFirst();
 
 		return $this->Application->ParseBlock($block_params);
 
 	}
 
 	function ShowRelevance($params)
 	{
 		return $this->Application->GetVar('module_key') != '_';
 	}
 
 	function ConfigValue($params)
 	{
 		return $this->Application->ConfigValue($params['name']);
 	}
 
 	function IsRequired($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$field = $params['field'];
 		$field_options = $object->GetFieldOptions($field);
 
 		if ( $field == 'VariableValue' ) {
 			$custom_options = $object->GetDBField('Validation');
 
 			if ( $custom_options ) {
 				$field_options = array_merge($field_options, unserialize($custom_options));
 			}
 		}
 
 		return isset($field_options['required']) && $field_options['required'];
 	}
 
 	function Error($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
 		if ( !($object instanceof kDBList) ) {
 			return parent::Error($params);
 		}
 
 		$field = $object->GetDBField($params['id_field']);
 		$errors = $this->Application->GetVar('errors_' . $this->getPrefixSpecial(), Array ());
 
 		return array_key_exists($field, $errors) ? $errors[$field] : '';
 	}
 
 	/**
 	 * Allows to show category path of selected module
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CategoryPath($params)
 	{
 		if (!isset($params['cat_id'])) {
 			$params['cat_id'] = $this->ModuleRootCategory( Array() );
 		}
 
 		$navigation_bar = $this->Application->recallObject('kNavigationBar');
 		/* @var $navigation_bar kNavigationBar */
 
 		return $navigation_bar->build($params);
 	}
 
 	/**
 	 * Shows edit warning in case if module root category changed but not saved
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SaveWarning($params)
 	{
 		$temp_category_id = $this->Application->RecallVar('ModuleRootCategory');
 		if ($temp_category_id !== false) {
 			return $this->Application->ParseBlock($params);
 		}
 		return '';
 	}
 
 	function ModuleRootCategory($params)
 	{
 		$category_id = $this->Application->RecallVar('ModuleRootCategory');
 		if ($category_id === false) {
 			$category_id = $this->Application->findModule('Name', $this->Application->GetVar('module'), 'RootCat');
 		}
 		return $category_id;
 	}
 
 	/**
 	 * Returns variable ID by it's name (used on search relevance configuration screen)
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function GetVariableID($params)
 	{
 		static $cached_ids = Array ();
 
 		$var_name = $params['name'];
-		if (!isset($cached_ids[$var_name])) {
-			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
-
-			$sql = 'SELECT '.$id_field.'
-					FROM '.$table_name.'
-					WHERE VariableName = '.$this->Conn->qstr($params['name']);
+
+		if ( !isset($cached_ids[$var_name]) ) {
+			$config = $this->getUnitConfig();
+
+			$sql = 'SELECT ' . $config->getIDField() . '
+					FROM ' . $config->getTableName() . '
+					WHERE VariableName = ' . $this->Conn->qstr($params['name']);
 			$cached_ids[$var_name] = $this->Conn->GetOne($sql);
 		}
 
 		return $cached_ids[$var_name];
 	}
 
 	function GetVariableSection($params)
 	{
 		static $cached_sections = Array ();
 
 		$var_name = $params['name'];
-		if (!isset($cached_sections[$var_name])) {
-			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
+		if ( !isset($cached_sections[$var_name]) ) {
 			$sql = 'SELECT Section
-					FROM '.$table_name.'
-					WHERE VariableName = '.$this->Conn->qstr($params['name']);
+					FROM ' . $this->getUnitConfig()->getTableName() . '
+					WHERE VariableName = ' . $this->Conn->qstr($params['name']);
 			$cached_sections[$var_name] = $this->Conn->GetOne($sql);
 		}
 
 		return $cached_sections[$var_name];
 	}
 
 	/**
 	 * Returns system setting editing link
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function ItemEditLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$params['m_opener'] = 'd';
 		$params[$object->Prefix . '_mode'] = 't';
 		$params[$object->Prefix . '_event'] = 'OnEdit';
 		$params[$object->Prefix . '_id'] = $object->GetID();
 		$params['pass'] = 'all,' . $object->Prefix;
 
 		return $this->Application->ProcessParsedTag('m', 'Link', $params);
 
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/configuration/configuration_event_handler.php
===================================================================
--- branches/5.3.x/core/units/configuration/configuration_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/configuration/configuration_event_handler.php	(revision 15698)
@@ -1,567 +1,569 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ConfigurationEventHandler extends kDBEventHandler  {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnGenerateMaintenancePage' => Array ('self' => 'add|edit'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Changes permission section to one from REQUEST, not from config
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			$event->setEventParam('PermSection', $this->Application->GetVar('section'));
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$module = $this->Application->GetVar('module');
 			$section = $this->Application->GetVar('section');
 
 			$object->addFilter('module_filter', '%1$s.ModuleOwner = ' . $this->Conn->qstr($module));
 			$object->addFilter('section_filter', '%1$s.Section = ' . $this->Conn->qstr($section));
 
 			$can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange');
 
 			if ( !$can_change && !$this->Application->isDebugMode() ) {
 				$object->addFilter('interface_change_filter', '%1$s.VariableName NOT IN ("AdminConsoleInterface", "AllowAdminConsoleInterfaceChange")');
 			}
 
 			if ( defined('IS_INSTALL') && IS_INSTALL ) {
 				$object->addFilter('install_filter', '%1$s.Install = 1');
 			}
 
 			$object->addFilter('visible_filter', '%1$s.Heading <> ""');
 		}
 
 		/**
 		 * Presets new system setting fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			parent::OnPreCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('Section', $this->Application->GetVar('section'));
 			$object->SetDBField('ModuleOwner', $this->Application->GetVar('module'));
 		}
 
 		/**
 		 * Sets custom validation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			static $default_field_options = null;
 
 			parent::OnAfterItemLoad($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// ability to validate each configuration variable separately
 			if ( !isset($default_field_options) ) {
 				$default_field_options = $object->GetFieldOptions('VariableValue');
 			}
 
 			$new_field_options = $default_field_options;
 			$validation = $object->GetDBField('Validation');
 
 			if ( $validation ) {
 				$new_field_options = array_merge($new_field_options, unserialize($validation));
 			}
 
 			$object->SetFieldOptions('VariableValue', $new_field_options);
 		}
 
 		/**
 		 * Performs custom validation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemValidate(kEvent $event)
 		{
 			parent::OnBeforeItemValidate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// if password field is empty, then don't update
 			if ( $object->GetDBField('ElementType') == 'password' ) {
 				if ( trim($object->GetDBField('VariableValue')) != '' ) {
 					$password_formatter = $this->Application->recallObject('kPasswordFormatter');
 					/* @var $password_formatter kPasswordFormatter */
 
 					$object->SetDBField('VariableValue', $password_formatter->hashPassword($object->GetDBField('VariableValue')));
 				}
 			}
 
 			$this->_processCountryState($event);
 
 			$variable_name = $object->GetDBField('VariableName');
 			$new_value = $object->GetDBField('VariableValue');
 
 			if ( $variable_name == 'AdminConsoleInterface' ) {
 				$can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange');
 
 				if ( ($new_value != $object->GetOriginalField('VariableValue')) && !$can_change ) {
 					$object->SetError('VariableValue', 'not_allowed', 'la_error_OperationNotAllowed');
 				}
 			}
 			elseif ( $variable_name == 'HardMaintenanceTemplate' ) {
 				$compile = $event->MasterEvent->getEventParam('compile_maintenance_template');
 				$compile = $compile || $new_value != $object->GetOriginalField('VariableValue');
 
 				if ( $compile && !$this->_generateMaintenancePage($new_value) ) {
 					$object->SetError('VariableValue', 'template_file_missing', 'la_error_TemplateFileMissing');
 				}
 			}
 			elseif ( $variable_name == 'DefaultEmailRecipients' ) {
 				$email_event_data = $this->Application->GetVar('email-template_' . $event->Prefix);
 				$object->SetDBField('VariableValue', $email_event_data[0]['Recipients']);
 			}
 
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section = $object->GetDBField('Section');
 
 			if ( $section && !$sections_helper->getSectionData($section) ) {
 				$object->SetError('Section', 'unknown_section');
 			}
 		}
 
 		/**
 		 * Checks, that state belongs to selected country
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function _processCountryState(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$country_iso = $this->_getCountryByState($event);
 			$state_name = $object->GetDBField('VariableValue');
 
 			if ( !$country_iso || !$state_name ) {
 				return;
 			}
 
 			$cs_helper = $this->Application->recallObject('CountryStatesHelper');
 			/* @var $cs_helper kCountryStatesHelper */
 
 			$state_iso = $cs_helper->getStateIso($state_name, $country_iso);
 
 			if ( $state_iso !== false ) {
 				$object->SetDBField('VariableValue', $state_iso);
 			}
 			else {
 				// selected state doesn't belong to selected country
 				$object->SetError('VariableValue', 'invalid_state', 'la_InvalidState');
 			}
 		}
 
 		/**
 		 * Returns country iso code, that matches current state variable name
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access protected
 		 */
 		protected function _getCountryByState(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$variable_name = $object->GetDBField('VariableName');
 
 			$state_country_hash = Array (
 				'Comm_State' => 'Comm_Country',
 				'Comm_Shipping_State' => 'Comm_Shipping_Country'
 			);
 
 			if ( !array_key_exists($variable_name, $state_country_hash) ) {
 				return false;
 			}
 
 			$field_values = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 			$sql = 'SELECT VariableId
-					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+					FROM ' . $event->getUnitConfig()->getTableName() . '
 					WHERE VariableName = ' . $this->Conn->qstr($state_country_hash[$variable_name]);
 			$country_variable_id = $this->Conn->GetOne($sql);
 
 			return $field_values[$country_variable_id]['VariableValue'];
 		}
 
 		/**
-		 * Does custom password setting processong
+		 * Does custom password setting processing
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// if password field is empty, then don't update
 			if ( $object->GetDBField('ElementType') == 'password' && trim($object->GetDBField('VariableValue')) == '' ) {
 				$object->SetFieldOption('VariableValue', 'skip_empty', 1);
 			}
 		}
 
 		/**
 		 * Occurs after updating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemUpdate(kEvent $event)
 		{
 			static $skin_deleted = false;
 
 			parent::OnAfterItemUpdate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->GetDBField('ElementType') == 'password' && trim($object->GetDBField('VariableValue')) == '' ) {
 				$object->SetFieldOption('VariableValue', 'skip_empty', 0);
 			}
 
 			// allows to check if variable's value was changed now
 			$variable_name = $object->GetDBField('VariableName');
 			$changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ());
 
 			if ( $object->GetDBField('VariableValue') != $object->GetOriginalField('VariableValue') ) {
 				$changed[] = $variable_name;
 				$this->Application->SetVar($event->getPrefixSpecial() . '_changed', $changed);
 
 				// update value in cache, so other code (during this script run) would use new value
 				$this->Application->SetConfigValue($variable_name, $object->GetDBField('VariableValue'), true);
 			}
 
 			if ( $variable_name == 'Require_AdminSSL' || $variable_name == 'AdminSSL_URL' ) {
 				// when administrative console is moved to SSL mode, then delete skin
 				if ( in_array($variable_name, $changed) && !$skin_deleted ) {
 					$skin_helper = $this->Application->recallObject('SkinHelper');
 					/* @var $skin_helper SkinHelper */
 
 					$skin_file = $skin_helper->getSkinPath();
 					if ( file_exists($skin_file) ) {
 						unlink($skin_file);
 					}
 
 					$skin_deleted = true;
 				}
 			}
 		}
 
 		/**
 		 * Updates kDBItem
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdate(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 			// 1. save user selected module root category
 			$new_category_id = getArrayValue($items_info, 'ModuleRootCategory', 'VariableValue');
 			if ( $new_category_id !== false ) {
 				unset($items_info['ModuleRootCategory']);
 			}
 
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			if ( $items_info ) {
 				$has_error = false;
 
 				foreach ($items_info as $id => $field_values) {
 					$object->Clear(); // clear validation errors from previous variable
 					$object->Load($id);
 					$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 					if ( !$object->Update($id) ) {
 						// don't stop when error found !
 						$has_error = true;
 					}
 				}
 
 				$event->status = $has_error ? kEvent::erFAIL : kEvent::erSUCCESS;
 			}
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$event->SetRedirectParam('action_completed', 1);
 
 				if ( $new_category_id !== false ) {
 					// root category was submitted
 					$module = $this->Application->GetVar('module');
 					$root_category_id = $this->Application->findModule('Name', $module, 'RootCat');
 
 					if ( $root_category_id != $new_category_id ) {
 						// root category differs from one in db
 						$fields_hash = Array ('RootCat' => $new_category_id);
 						$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Modules', 'Name = ' . $this->Conn->qstr($module));
 					}
 				}
 
 				// reset cache
 				$changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ());
 				$require_refresh = Array ('AdvancedUserManagement', 'Site_Name', 'AdminConsoleInterface', 'UsePopups');
 
 				$refresh_sections = array_intersect($require_refresh, $changed);
 				$require_full_refresh = Array ('Site_Name', 'AdminConsoleInterface');
 
 				if ( array_intersect($require_full_refresh, $changed) ) {
 					$event->SetRedirectParam('refresh_all', 1);
 				}
 				elseif ( $refresh_sections ) {
 					$event->SetRedirectParam('refresh_tree', 1);
 				}
 
 				if ( $refresh_sections ) {
 					// reset sections too, because of AdvancedUserManagement
 					$this->Application->DeleteSectionCache();
 				}
 
 				$this->Application->DeleteUnitCache($changed);
 			}
 			else{
 				$errors = $this->Application->GetVar('errors_' . $event->getPrefixSpecial());
 
 				if ( $errors ) {
 					// because we have list out there, and this is item
 					$this->Application->SetVar('first_error', key($errors));
 					$this->Application->removeObject($event->getPrefixSpecial());
 				}
 			}
 
 			// keeps module and section in REQUEST to ensure, that last admin template will work
 			$event->SetRedirectParam('module', $this->Application->GetVar('module'));
 			$event->SetRedirectParam('section', $this->Application->GetVar('section'));
 		}
 
 		/**
 		 * Process items from selector (selected_ids var, key - prefix, value - comma separated ids)
 		 *
 		 * @param kEvent $event
 		 */
 		function OnProcessSelected($event)
 		{
 			$selected_ids = $this->Application->GetVar('selected_ids');
 			$this->Application->StoreVar('ModuleRootCategory', $selected_ids['c']);
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Generates maintenance page
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnGenerateMaintenancePage(kEvent $event)
 		{
 			$event->setEventParam('compile_maintenance_template', 1);
 
 			$event->CallSubEvent('OnUpdate');
 		}
 
 		/**
 		 * Generates HTML version of hard maintenance template
 		 *
 		 * @param string $template
 		 * @return bool
 		 * @access protected
 		 */
 		protected function _generateMaintenancePage($template = null)
 		{
 			if ( !isset($template) ) {
 				$template = $this->Application->ConfigValue('HardMaintenanceTemplate');
 			}
 
 			$curl_helper = $this->Application->recallObject('CurlHelper');
 			/* @var $curl_helper kCurlHelper */
 
 			$html = $curl_helper->Send($this->Application->BaseURL() . '?t=' . $template);
 
 			if ( $curl_helper->isGoodResponseCode() ) {
 				file_put_contents(WRITEABLE . DIRECTORY_SEPARATOR . 'maintenance.html', $html);
 
 				return true;
 			}
 
 			return false;
 		}
 
 		/**
 		 * Returns auto-complete values for ajax-dropdown
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSuggestValues(kEvent $event)
 		{
 			if ( !$this->Application->isAdminUser ) {
 				// very careful here, because this event allows to
 				// view every object field -> limit only to logged-in admins
 				return;
 			}
 
 			$event->status = kEvent::erSTOP;
 
 			$field = $this->Application->GetVar('field');
 			$cur_value = $this->Application->GetVar('cur_value');
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$field || !$cur_value || !$object->isField($field) ) {
 				return;
 			}
 
 			$limit = $this->Application->GetVar('limit');
 			if ( !$limit ) {
 				$limit = 20;
 			}
 
 			$sql = 'SELECT DISTINCT ' . $field . ', ModuleOwner
-					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+					FROM ' . $event->getUnitConfig()->getTableName() . '
 					WHERE ' . $field . ' LIKE ' . $this->Conn->qstr('%' . $cur_value . '%') . '
 					ORDER BY ' . $field . ' ASC';
 			$raw_suggestions = $this->Conn->Query($sql);
 
 			$suggestions = Array ();
 			$this->Application->XMLHeader();
 
 			foreach ($raw_suggestions as $raw_suggestion) {
 				$suggestion = $raw_suggestion[$field];
 
 				if ( !isset($suggestions[$suggestion]) ) {
 					$suggestions[$suggestion] = Array ();
 				}
 
 				$suggestions[$suggestion][] = $raw_suggestion['ModuleOwner'];
 			}
 
 			array_splice($suggestions, $limit);
 
 			echo '<suggestions>';
 			$of_label = $this->Application->Phrase('la_From', false);
 
 			foreach ($suggestions as $suggestion_value => $suggestion_modules) {
 				$suggestion_module = in_array('In-Portal', $suggestion_modules) ? 'In-Portal' : implode(', ', $suggestion_modules);
 				$suggestion_title = $suggestion_value . ' <em style="color: grey;">' . $of_label . ' ' . $suggestion_module . '</em>';
 
 				echo '<item value="' . htmlspecialchars($suggestion_value, null, CHARSET) . '">' . htmlspecialchars($suggestion_title, null, CHARSET) . '</item>';
 			}
 
 			echo '</suggestions>';
 		}
 
 		/**
 		 * Prefills module dropdown
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			$options = Array ();
 
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				if ( $module_name == 'Core' ) {
 					continue;
 				}
 
 				$options[$module_name] = $module_name;
 
 				if ( $module_name == 'In-Portal' ) {
 					$options['In-Portal:Users'] = 'In-Portal:Users';
 				}
 			}
 
-			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
+			$config = $event->getUnitConfig();
+
+			$fields = $config->getFields();
 			$fields['ModuleOwner']['options'] = $options;
-			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
+			$config->setFields($fields);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/filters/item_filter_eh.php
===================================================================
--- branches/5.3.x/core/units/filters/item_filter_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/filters/item_filter_eh.php	(revision 15698)
@@ -1,159 +1,158 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class ItemFilterEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnItemBuild' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Apply any custom changes to list's sql query
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 * @see kDBEventHandler::OnListBuild()
 	 */
 	protected function SetCustomQuery(kEvent $event)
 	{
 		parent::SetCustomQuery($event);
 
 		if ( !$this->Application->isAdmin ) {
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$prefix_info = $this->Application->processPrefix($event->getEventParam('prefix'));
 
 			$object->addFilter('prefix_filter', '%1$s.ItemPrefix = ' . $this->Conn->qstr($prefix_info['prefix']));
 			$object->addFilter('status_filter', '%1$s.Enabled = 1');
 
 			if ( $event->Special == 'used' ) {
 				$filters = array_keys($this->Application->GetVar('filters', Array ()));
 
 				if ( $filters ) {
 					$filters = $this->Conn->qstrArray($filters);
 					$object->addFilter('field_filter', '%1$s.FilterField IN (' . implode(',', $filters) . ')');
 				}
 				else {
 					$object->addFilter('field_filter', 'FALSE');
 				}
 			}
 
 			$exclude_filters = $this->Application->GetVar('exclude_filters');
 
 			if ( $exclude_filters ) {
 				$exclude_filters = $this->Conn->qstrArray(explode(',', $exclude_filters));
 				$object->addFilter('field_filter', '%1$s.FilterField NOT IN (' . implode(',', $exclude_filters) . ')');
 			}
 
 			if ( $event->getEventParam('per_page') === false ) {
 				$event->setEventParam('per_page', -1);
 			}
 		}
 	}
 
 	/**
 	 * Validates filter settings
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemValidate(kEvent $event)
 	{
 		parent::OnBeforeItemValidate($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$prefix = $object->GetDBField('ItemPrefix');
 
 		if ( $prefix ) {
 			if ( !$this->Application->prefixRegistred($prefix) ) {
 				$object->SetError('ItemPrefix', 'not_registered');
 			}
 
 			$field = $object->GetDBField('FilterField');
 
 			if ( $field ) {
-				$fields = $this->Application->getUnitOption($prefix, 'Fields');
-				$virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields');
+				$config = $this->Application->getUnitConfig($prefix);
 
-				if ( !isset($fields[$field]) && !isset($virtual_fields[$field]) ) {
+				if ( !$config->getFieldByName($field) && !$config->getVirtualFieldByName($field) ) {
 					$object->SetError('FilterField', 'non_existing', null, Array ($prefix));
 				}
 			}
 		}
 
 		$object->setRequired('RangeCount', $object->GetDBField('FilterType') == 'range');
 	}
 
 	/**
 	 * Load item if id is available
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function LoadItem(kEvent $event)
 	{
 		static $cache = null;
 
 		if ( $this->Application->isAdmin ) {
 			parent::LoadItem($event);
 
 			return;
 		}
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ( !isset($cache) ) {
 			$cache = $this->Conn->Query($object->GetSelectSQL(), 'FilterKey');
 		}
 
 		$filter_key = $event->getEventParam('prefix') . '_' . $event->getEventParam('field');
 
 		if ( isset($cache[$filter_key]) ) {
 			$object->LoadFromHash($cache[$filter_key]);
 		}
 
 		if ( $object->isLoaded() ) {
 			$actions = $this->Application->recallObject('kActions');
 			/* @var $actions Params */
 
 			$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
 		}
 		else {
 			$object->setID(false);
 		}
 	}
 }
Index: branches/5.3.x/core/units/modules/modules_event_handler.php
===================================================================
--- branches/5.3.x/core/units/modules/modules_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/modules/modules_event_handler.php	(revision 15698)
@@ -1,178 +1,177 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ModulesEventHandler extends kDBEventHandler {
 
 		/**
 		 * Builds item (loads if needed)
 		 *
 		 * Pattern: Prototype Manager
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function OnItemBuild(kEvent $event)
 		{
 			$this->Application->SetVar($event->getPrefixSpecial(true) . '_id', $event->Special);
 
 			parent::OnItemBuild($event);
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			if ( $event->Special ) {
 				$object->addFilter('current_module', '%1$s.Name = ' . $event->Special);
 			}
 
 			$object->addFilter('not_core', '%1$s.Name <> "Core"');
 		}
 
 		/**
 		 * Define alternative event processing method names
 		 *
 		 * @return void
 		 * @see kEventHandler::$eventMethods
 		 * @access protected
 		 */
 		protected function mapEvents()
 		{
 			parent::mapEvents();
 
 			$this->eventMethods['OnMassApprove'] = 'moduleAction';
 			$this->eventMethods['OnMassDecline'] = 'moduleAction';
 		}
 
 		/**
 		 * Disabled modules, but not In-Portal
 		 *
 		 * @param kEvent $event
 		 */
 		function moduleAction($event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return ;
 			}
 
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if (!$ids) {
 				return ;
 			}
 
 			$updated = 0;
-			$status_field = $this->Application->getUnitOption($event->Prefix, 'StatusField');
-			$status_field = array_shift($status_field);
+			$status_field = $event->getUnitConfig()->getStatusField(true);
 
 			foreach ($ids as $id) {
 				$object->Load($id);
 
 				if (in_array($id, Array ('In-Portal', 'Core')) || !$object->isLoaded()) {
 					// don't allow any kind of manupulations with kernel
 					// approve/decline on not installed module
 					continue;
 				}
 
 				$enabled = $event->Name == 'OnMassApprove' ? 1 : 0;
 				$object->SetDBField($status_field, $enabled);
 
 				if (!$object->GetChangedFields()) {
 					// no changes -> skip
 					continue;
 				}
 
 				if ($object->Update()) {
 					$updated++;
 
 					$sql = 'UPDATE ' . TABLE_PREFIX . 'ImportScripts
 							SET Status = ' . $enabled . '
  							WHERE Module = "' . $object->GetDBField('Name') . '"';
  					$this->Conn->Query($sql);
 				}
 				else {
 					$event->status = kEvent::erFAIL;
 					$event->redirect = false;
 					break;
 				}
 			}
 
 			if ( $updated ) {
 				$event->status = kEvent::erSUCCESS;
 				$event->SetRedirectParam('opener', 's');
 
 				$this->Application->DeleteUnitCache();
 				$this->Application->DeleteSectionCache();
 
 				$event->SetRedirectParam('RefreshTree', 1);
 			}
 		}
 
 		/**
 		 * Occurs after list is queried
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterListQuery(kEvent $event)
 		{
 			parent::OnAfterListQuery($event);
 
 			$modules_helper = $this->Application->recallObject('ModulesHelper');
 			/* @var $modules_helper kModulesHelper */
 
 			$new_modules = $modules_helper->getModules(kModulesHelper::NOT_INSTALLED);
 
 			if ( !$new_modules || $this->Application->RecallVar('user_id') != USER_ROOT ) {
 				return;
 			}
 
 			require_once FULL_PATH . '/core/install/install_toolkit.php';
 
 			$toolkit = new kInstallToolkit();
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			foreach ($new_modules as $module) {
 				$module_record = Array (
 					'Name' => $toolkit->getModuleName($module),
 					'Path' => 'modules/' . $module . '/',
 					'Version' => $toolkit->GetMaxModuleVersion('modules/' . $module . '/'),
 					'Loaded' => 0,
 					'BuildDate' => null,
 				);
 
 				$object->addRecord($module_record);
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/system_event_subscriptions/system_event_subscription_tp.php
===================================================================
--- branches/5.3.x/core/units/system_event_subscriptions/system_event_subscription_tp.php	(revision 15697)
+++ branches/5.3.x/core/units/system_event_subscriptions/system_event_subscription_tp.php	(revision 15698)
@@ -1,260 +1,262 @@
 <?php
 /**
  * @version	$Id$
  * @package	In-Portal
  * @copyright	Copyright (C) 1997 - 2012 Intechnic. All rights reserved.
  * @license	  GNU/GPL
  * In-Portal is Open Source software.
  * This means that this software may have been modified pursuant
  * the GNU General Public License, and as distributed it includes
  * or is derivative of works licensed under the GNU General Public License
  * or other free or open source software licenses.
  * See http://www.in-portal.org/license for copyright notices and details.
  */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class SystemEventSubscriptionTagProcessor extends kDBTagProcessor {
 
 	/**
 	 * Holds reference to subscription analyzer
 	 *
 	 * @var kSubscriptionAnalyzer
 	 */
 	protected $_analyzer = null;
 
 	/**
 	 * Allows to show category path of selected module
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CategoryPath($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$category_id = $object->GetDBField('CategoryId');
 
 		if ( !is_numeric($category_id) ) {
 			return '';
 		}
 
 		$params['cat_id'] = $category_id;
 
 		$navigation_bar = $this->Application->recallObject('kNavigationBar');
 		/* @var $navigation_bar kNavigationBar */
 
 		return $navigation_bar->build($params);
 	}
 
 	/**
 	 * Prints item name
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function ItemName($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
 		if ( !isset($this->_analyzer) ) {
 			$this->_analyzer = new kSubscriptionAnalyzer($object);
 			$this->_analyzer->run();
 		}
 
 		return $this->_analyzer->getTitle($this->SelectParam($params, 'name,field'));
 	}
 }
 
 
 class kSubscriptionAnalyzer extends kBase {
 
 	/**
 	 * Reference to a list object
 	 *
 	 * @var kDBList
 	 * @access protected
 	 */
 	protected $_subscriptions = null;
 
 	/**
 	 * Remember what to what ids subscription exists for each of subscribed prefixes
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_prefixToIdsMap = Array ();
 
 	/**
 	 * Reverse index that remembers what prefix is used in what row (fields: ItemId, ParentItemId)
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_prefixToRowMap = Array ();
 
 	/**
 	 * Holds title of each item in list in format [prefix][id] = title
 	 *
 	 * @var Array
 	 * @access protected
 	 */
 	protected $_itemTitles = Array ();
 
 	/**
 	 * Set's references to kApplication and kDBConnection class instances
 	 *
 	 * @param kDBList $subscriptions
 	 * @access public
 	 * @see kApplication
 	 * @see kDBConnection
 	 */
 	public function __construct($subscriptions)
 	{
 		parent::__construct();
 
 		$this->_subscriptions = $subscriptions;
 	}
 
 	/**
 	 * Analyzes list
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function run()
 	{
 		foreach ($this->_subscriptions as $subscription) {
 			$prefix = $this->_getPrefix();
-			$parent_prefix = $this->Application->getUnitOption($prefix, 'ParentPrefix');
+			$parent_prefix = $this->Application->getUnitConfig($prefix)->getParentPrefix();
 
 			$this->_addIdToPrefix($prefix, 'ItemId');
 			$this->_addIdToPrefix($parent_prefix, 'ParentItemId');
 		}
 
 		$this->_queryItemTitles();
 		$this->_subscriptions->GoFirst();
 	}
 
 	/**
 	 * Returns item title, associated with item's ID in given field
 	 *
 	 * @param string $field
 	 * @return string
 	 */
 	public function getTitle($field)
 	{
 		$row_index = $this->_subscriptions->key();
 
 		if ( !isset($this->_prefixToRowMap[$row_index][$field]) ) {
 			return '';
 		}
 
 		$prefix = $this->_prefixToRowMap[$row_index][$field];
 		$value = $this->_subscriptions->GetDBField($field);
 
 		return $this->_itemTitles[$prefix][$value];
 	}
 
 	/**
 	 * Queries titles for each of subscribed items
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function _queryItemTitles()
 	{
 		foreach ($this->_prefixToIdsMap as $prefix => $ids) {
-			$id_field = $this->Application->getUnitOption($prefix, 'IDField');
+			$config = $this->Application->getUnitConfig($prefix);
+			$id_field = $config->getIDField();
 
 			$sql = 'SELECT ' . $this->_getTitleField($prefix) . ', ' . $id_field . '
-					FROM ' . $this->Application->getUnitOption($prefix, 'TableName') . '
+					FROM ' . $config->getTableName() . '
 					WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ')';
 			$this->_itemTitles[$prefix] = $this->Conn->GetCol($sql, $id_field);
 		}
 	}
 
 	/**
 	 * Adds ID from a gvein field (when it isn't NULL) to prefix ids
 	 *
 	 * @param string $prefix
 	 * @param string $field
 	 * @return void
 	 * @access protected
 	 */
 	protected function _addIdToPrefix($prefix, $field)
 	{
 		$id = $this->_subscriptions->GetDBField($field);
 
 		if ( !$id || !$prefix ) {
 			return;
 		}
 
 		// add id to prefix ids list
 		if ( !isset($this->_prefixToIdsMap[$prefix]) ) {
 			$this->_prefixToIdsMap[$prefix] = Array ();
 		}
 
 		if ( !in_array($id, $this->_prefixToIdsMap[$prefix]) ) {
 			$this->_prefixToIdsMap[$prefix][] = $id;
 		}
 
 		// remeber prefix associated with this field
 		$row_index = $this->_subscriptions->key();
 
 		if ( !isset($this->_prefixToRowMap[$row_index]) ) {
 			$this->_prefixToRowMap[$row_index] = Array ();
 		}
 
 		$this->_prefixToRowMap[$row_index][$field] = $prefix;
 	}
 
 	/**
 	 * Returns prefix of main item in current row
 	 *
 	 * @return string
 	 * @access protected
 	 * @throws Exception
 	 */
 	protected function _getPrefix()
 	{
 		$event = new kEvent($this->_subscriptions->GetDBField('BindToSystemEvent'));
 
 		if ( !$event->Prefix ) {
 			throw new Exception('Subscription "<strong>#' . $this->_subscriptions->GetID() . '</strong>" is connected to invalid or missing e-mail template "<strong>#' . $this->_subscriptions->GetDBField('EmailTemplateId') . '</strong>"');
 		}
 
 		return $event->Prefix;
 	}
 
 	/**
 	 * Returns title field of given prefix
 	 *
 	 * @param string $prefix
 	 * @return array
 	 */
 	protected function _getTitleField($prefix)
 	{
 		$lang_prefix = '';
-		$title_field = $this->Application->getUnitOption($prefix, 'TitleField');
+		$config = $this->Application->getUnitConfig($prefix);
+		$title_field = $config->getTitleField();
 
 		if ( preg_match('/^(l[\d]+_)(.*)/', $title_field, $regs) ) {
 			// object was initialized and we have lang prefix in unit config
 			$lang_prefix = $regs[1];
 			$title_field = $regs[2];
 		}
 		else {
 			// object wasn't initialized -> check other way OR not ml title field
-			$fields = $this->Application->getUnitOption($prefix, 'Fields');
+			$field_options = $config->getFieldByName($title_field);
 
-			if ( isset($fields[$title_field]['formatter']) && $fields[$title_field]['formatter'] == 'kMultiLanguage' ) {
+			if ( isset($field_options['formatter']) && $field_options['formatter'] == 'kMultiLanguage' ) {
 				$lang_prefix = 'l' . $this->Application->GetVar('m_lang') . '_';
 			}
 		}
 
 		return $lang_prefix . $title_field;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/category_items/category_items_event_handler.php
===================================================================
--- branches/5.3.x/core/units/category_items/category_items_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/category_items/category_items_event_handler.php	(revision 15698)
@@ -1,174 +1,172 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CategoryItemsEventHander extends kDBEventHandler
 	{
 		/**
 		 * Setting language dependant navigation bar as calculated field
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 			/* @var $ml_formatter kMultiLanguage */
 
 			$object->addCalculatedField('CategoryName', 'c.' . $ml_formatter->LangFieldName('CachedNavbar'));
 		}
 
 		/**
 		 * Set's new category as primary for product
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetPrimary($event)
 		{
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			if ( $ids ) {
 				$id = array_shift($ids);
 				$table_info = $object->getLinkedInfo();
 
 				$this->Conn->Query('UPDATE ' . $object->TableName . ' SET PrimaryCat = 0 WHERE ' . $table_info['ForeignKey'] . ' = ' . $table_info['ParentId']);
 				$this->Conn->Query('UPDATE ' . $object->TableName . ' SET PrimaryCat = 1 WHERE (' . $table_info['ForeignKey'] . ' = ' . $table_info['ParentId'] . ') AND (CategoryId = ' . $id . ')');
 			}
 
 			$event->SetRedirectParam('opener', 's');
 		}
 
 		/**
 		 * Apply custom processing to item
 		 *
 		 * @param kEvent $event
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(kEvent $event, $type)
 		{
 			if ( $event->Name == 'OnMassDelete' ) {
 				$object = $event->getObject();
 				$table_info = $object->getLinkedInfo();
 
 				switch ($type) {
 					case 'before':
 						$ids = $event->getEventParam('ids');
 
 						if ( $ids ) {
 							$sql = 'SELECT CategoryId
 									FROM ' . $object->TableName . '
 									WHERE (PrimaryCat = 0) AND (' . $table_info['ForeignKey'] . '=' . $table_info['ParentId'] . ') AND CategoryId IN (' . implode(',', $ids) . ')';
 							$event->setEventParam('ids', $this->Conn->GetCol($sql));
 						}
 						break;
 
 					// not needed because 'before' does not allow to delete primary cat!
 					/*case 'after':
 						// set 1st not deleted category as primary
 						$sql = 'SELECT COUNT(*)
 								FROM ' . $object->TableName . '
 								WHERE (PrimaryCat = 1) AND (' . $table_info['ForeignKey'] . ' = ' . $table_info['ParentId'] . ')';
 						$has_primary = $this->Conn->GetOne($sql);
 
 						if ( !$has_primary ) {
 							$sql = 'SELECT CategoryId
 									FROM ' . $object->TableName . '
 									WHERE ' . $table_info['ForeignKey'] . ' = ' . $table_info['ParentId'];
 							$cat_id = $this->Conn->GetOne($sql);
 
 							$sql = 'UPDATE ' . $object->TableName . '
 									SET PrimaryCat = 1
 									WHERE (' . $table_info['ForeignKey'] . ' = ' . $table_info['ParentId'] . ') AND (CategoryId = ' . $cat_id . ')';
 							$this->Conn->Query($sql);
 						}
 						break;*/
 				}
 			}
 		}
 
 		/**
 		 * Removes primary mark from cloned category items record
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterClone(kEvent $event)
 		{
 			parent::OnAfterClone($event);
 
-			$id = $event->getEventParam('id');
-			$table = $this->Application->getUnitOption($event->Prefix, 'TableName');
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
+			$config = $event->getUnitConfig();
 
-			$sql = 'UPDATE %s
+			$sql = 'UPDATE ' . $config->getTableName() . '
 					SET PrimaryCat = 0
-					WHERE %s = %s';
-			$this->Conn->Query(sprintf($sql, $table, $id_field, $id));
+					WHERE ' . $config->getIDField() . ' = ' . $event->getEventParam('id');
+			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Deletes items of requested type from requested categories.
 		 * In case if item is deleted from it's last category, then delete item too.
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnDeleteFromCategory($event)
 		{
 			$category_ids = $event->getEventParam('category_ids');
 
 			if ( !$category_ids ) {
 				return ;
 			}
 
 			$item_prefix = $event->getEventParam('item_prefix');
 			$item = $this->Application->recallObject($item_prefix . '.-item', null, Array ('skip_autoload' => true));
 			/* @var $item kCatDBItem */
 
-			$ci_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
-			$item_table = $this->Application->getUnitOption($item_prefix, 'TableName');
+			$ci_table = $event->getUnitConfig()->getTableName();
+			$item_table = $this->Application->getUnitConfig($item_prefix)->getTableName();
 
 			$sql = 'SELECT ItemResourceId, CategoryId
 					FROM %1$s
 					INNER JOIN %2$s ON (%1$s.ResourceId = %2$s.ItemResourceId)
 					WHERE CategoryId IN (%3$s)';
 			$category_items = $this->Conn->Query( sprintf($sql, $item_table, $ci_table, implode(',', $category_ids)) );
 
 			$item_hash = Array ();
 			foreach ($category_items as $ci_row) {
 				$item_hash[ $ci_row['ItemResourceId'] ][] = $ci_row['CategoryId'];
 			}
 
 			foreach ($item_hash as $item_resource_id => $delete_category_ids) {
 				$item->Load($item_resource_id, 'ResourceId');
 				$item->DeleteFromCategories($delete_category_ids);
 			}
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/groups/group_tp.php
===================================================================
--- branches/5.3.x/core/units/groups/group_tp.php	(revision 15697)
+++ branches/5.3.x/core/units/groups/group_tp.php	(revision 15698)
@@ -1,36 +1,37 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class GroupTagProcessor extends kDBTagProcessor {
 
 		/**
 		 * Hides permission tab, when it's not allowed by configuration settings
 		 *
 		 * @param Array $params
 		 */
 		function ModifyUnitConfig($params)
 		{
-			$edit_tab_presets = $this->Application->getUnitOption($this->Prefix, 'EditTabPresets');
-
 			$event = new kEvent($this->getPrefixSpecial() . ':OnItemBuild');
 			$permission = $event->getSection() . '.advanced:manage_permissions';
-			if (!$this->Application->CheckPermission($permission)) {
-				unset($edit_tab_presets['Default']['permissions']);
-			}
 
-			$this->Application->setUnitOption($this->Prefix, 'EditTabPresets', $edit_tab_presets);
+			if ( !$this->Application->CheckPermission($permission) ) {
+				$config = $this->getUnitConfig();
+
+				$edit_tab_preset = $config->getEditTabPresetByName('Default');
+				unset($edit_tab_preset['permissions']);
+				$config->addEditTabPresets($edit_tab_preset, 'Default');
+			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/reviews/reviews_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/reviews/reviews_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/reviews/reviews_tag_processor.php	(revision 15698)
@@ -1,238 +1,242 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class ReviewsTagProcessor extends kDBTagProcessor
 {
 	/**
 	 * Returns a link for editing product
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ItemEditLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBList */
 
 		$item_prefix = $this->Application->findModule('Name', $object->GetDBField('Module'), 'Var');
-		$edit_template = $this->Application->getUnitOption($item_prefix, 'AdminTemplatePath') . '/' . $this->Application->getUnitOption($item_prefix, 'AdminTemplatePrefix') . 'edit';
+		$item_config = $this->Application->getUnitConfig($item_prefix);
+
+		$edit_template = $item_config->getAdminTemplatePath() . '/' . $item_config->getAdminTemplatePrefix() . 'edit';
 
 		$url_params = Array (
 			'm_opener'				=>	'd',
 			$item_prefix.'_mode'	=>	't',
 			$item_prefix.'_event'	=>	'OnEdit',
 			$item_prefix.'_id'		=>	$object->GetDBField('CatalogItemId'),
 			'm_cat_id'				=>	$object->GetDBField('CatalogItemCategory'),
 			'pass'					=>	'all,'.$item_prefix,
 			'no_pass_through'		=>	1,
 		);
 
 		return $this->Application->HREF($edit_template,'', $url_params);
 	}
 
 	function HelpfulLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
-		$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
+		$parent_prefix = $this->getUnitConfig()->getParentPrefix();
+
 		$params['events[' . $parent_prefix . ']'] = 'OnReviewHelpful';
 		$params['review_id'] = $object->GetID();
 
 		return $this->Application->ProcessParsedTag($parent_prefix, 'ItemLink', $params);
 	}
 
 	/**
 	 * Prints overall rating statistics
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	protected function PrintRatingPercents($params)
 	{
 		static $cache = null;
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
-		$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
+		$parent_prefix = $this->getUnitConfig()->getParentPrefix();
 
 		$main_object = $this->Application->recallObject($parent_prefix);
 		/* @var $main_object kCatDBItem */
 
 		if ( !isset($cache) ) {
 			$sql = 'SELECT COUNT(*), Rating
 					FROM ' . $object->TableName . '
 					WHERE ItemId = ' . $main_object->GetDBField('ResourceId') . '
 					GROUP BY Rating';
 			$cache = $this->Conn->GetCol($sql, 'Rating');
 		}
 
 		$ratings = array_reverse( array_keys( $object->GetFieldOption('Rating', 'options') ) );
 
 		if ( !isset($params['show_none']) || !$params['show_none'] ) {
 			$none_index = array_search(0, $ratings);
 
 			if ( $none_index !== false ) {
 				unset($ratings[$none_index]);
 			}
 		}
 
 		$ret = '';
 		$total = array_sum($cache);
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 		$block_params['strip_nl'] = 2;
 
 		foreach ($ratings as $rating) {
 			$block_params['rating'] = $rating;
 			$block_params['count'] = isset($cache[$rating]) ? $cache[$rating] : 0;
 			$block_params['percent'] = $total ? round(($block_params['count'] / $total) * 100) : 0;
 
 			$ret .= $this->Application->ParseBlock($block_params);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Returns requested field value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function Field($params)
 	{
 		$field = $this->SelectParam($params, 'name,field');
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		if ($field == 'ReviewText') {
 			if ($object->GetDBField('TextFormat') == 1) {
 				$params['no_special'] = 'no_special';
 			}
 			else {
 				unset($params['no_special']);
 			}
 		}
 
 		return parent::Field($params);
 	}
 
 	function AlreadyReviewed($params)
 	{
-		$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
+		$parent_prefix = $this->getUnitConfig()->getParentPrefix();
+
 		$main_object = $this->Application->recallObject($parent_prefix);
 		/* @var $main_object kCatDBItem */
 
 		$spam_helper = $this->Application->recallObject('SpamHelper');
 		/* @var $spam_helper SpamHelper */
 
 		$spam_helper->InitHelper($main_object->GetDBField('ResourceId'), 'Review', 0, $main_object->GetCol('ResourceId'));
 
 		return $spam_helper->InSpamControl();
 	}
 
 	function HasError($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return method_exists($object, 'GetErrorMsg') ? parent::HasError($params) : 0;
 	}
 
 	/**
 	 * Preserve main item id in subitem pagination url
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PageLink($params)
 	{
 		$object = $this->getObject($params);
 		/* @var kDBList */
 
 		$parent_info = $object->getLinkedInfo();
 
 		if ($parent_info['ParentId'] > 0) {
 			$params['pass'] = 'm,'.$this->getPrefixSpecial().','.$parent_info['ParentPrefix'];
 		}
 
 		return parent::PageLink($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'];
 		$params['prefix'] = trim($this->Prefix.'.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.');
 
 		$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') ? $params['default_grid'] : $params['radio_grid'];
 		$params['tab_dependant'] = $tab_params['dependant'];
 		$params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name
 		$params['tab_name'] = $this->Application->GetVar('tab_name');
 
 		return $this->Application->ParseBlock($params, 1);
 	}
 
 	/**
 	 * Returns reviews count for each item type (in "Reviews" section)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CatalogItemCount($params)
 	{
 		$params['skip_quering'] = true;
 		$object =& $this->GetList($params);
 
 		return $object->GetRecordsCount(false) != $object->GetRecordsCount() ? $object->GetRecordsCount().' / '.$object->GetRecordsCount(false) : $object->GetRecordsCount();
 	}
 
 	/**
 	 * Dynamic votes indicator
 	 *
 	 * @param Array $params
 	 *
 	 * @return string
 	 */
 	function VotesIndicator($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$rating_helper = $this->Application->recallObject('RatingHelper');
 		/* @var $rating_helper RatingHelper */
 
 		$rating = isset($params['rating']) ? $params['rating'] : $object->GetDBField('Rating');
 		$small_style = array_key_exists('small_style', $params) ? $params['small_style'] : false;
 
 		return $rating_helper->ratingBarSimple($rating, '', null, $small_style);
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/reviews/reviews_event_handler.php
===================================================================
--- branches/5.3.x/core/units/reviews/reviews_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/reviews/reviews_event_handler.php	(revision 15698)
@@ -1,634 +1,643 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ReviewsEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Returns special of main item for linking with sub-item
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function getMainSpecial(kEvent $event)
 		{
 			if ( $event->Special == 'product' && !$this->Application->isAdmin ) {
 				// rev.product should auto-link
 				return '';
 			}
 
 			return parent::getMainSpecial($event);
 		}
 
 		/**
 		 * Checks REVIEW/REVIEW.PENDING permission by main object primary category (not current category)
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( $event->Name == 'OnAddReview' || $event->Name == 'OnCreate' ) {
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
-				$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
+				$parent_prefix = $event->getUnitConfig()->getParentPrefix();
+
 				$main_object = $this->Application->recallObject($parent_prefix);
 				/* @var $main_object kCatDBItem */
 
 				$perm_name = $this->getPermPrefix($event).'.REVIEW';
 				$res = 	$this->Application->CheckPermission($perm_name, 0, $main_object->GetDBField('CategoryId')) ||
 						$this->Application->CheckPermission($perm_name.'.PENDING', 0, $main_object->GetDBField('CategoryId'));
 
 				if ( !$res ) {
 					$event->status = kEvent::erPERM_FAIL;
 				}
 
 				return $res;
 			}
 
 			$check_events = Array (
 				'OnItemBuild', 'OnUpdate', /*'OnMassApprove', 'OnMassDecline'*/
 			);
 
 			$perm_category = $this->_getReviewCategory($event);
 
 			if ( in_array($event->Name, $check_events) ) {
 				// check for PRODUCT.VIEW permission
 
 				$perm_helper = $this->Application->recallObject('PermissionsHelper');
 				/* @var $perm_helper kPermissionsHelper */
 
 				$perm_prefix = $this->getPermPrefix($event);
 
 				if ( $perm_category === false ) {
 					// no item id present -> allow
 					return true;
 				}
 
 				switch ($event->Name) {
 					case 'OnItemBuild':
 						$res = $this->Application->CheckPermission($perm_prefix . '.VIEW', 0, $perm_category);
 						break;
 
 					case 'OnUpdate':
 					case 'OnMassApprove':
 					case 'OnMassDecline':
 						$res = 	$this->Application->CheckPermission($perm_prefix . '.ADD', 0, $perm_category) ||
 								$this->Application->CheckPermission($perm_prefix . '.MODIFY', 0, $perm_category);
 						break;
 
 					default:
 						$res = false;
 						break;
 				}
 
 				if ( !$res ) {
 					$event->status = kEvent::erPERM_FAIL;
 				}
 
 				return $res;
 
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Returns primary category of review's main item
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function _getReviewCategory($event)
 		{
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial());
 
 			if ($items_info) {
 				// rev:PresetFormFields is used to initialize new review creation
 				list ($review_id, ) = each($items_info);
 			}
 			else {
 				// when adding new review in admin
 				$review_id = false;
 
 			}
 
 			if (!$review_id) {
 				return false;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// 1. get main item resource id (use object, because of temp tables in admin)
 			$sql = 'SELECT ItemId
 					FROM ' . $object->TableName . '
 					WHERE ' . $object->IDField . ' = ' . $review_id;
 			$resource_id = $this->Conn->GetOne($sql);
 
 			// 2. set main item id (for permission checks)
-			$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
-			$sql = 'SELECT ' . $this->Application->getUnitOption($parent_prefix, 'IDField') .'
-					FROM ' . $this->Application->getUnitOption($parent_prefix, 'TableName') .'
+			$parent_prefix = $event->getUnitConfig()->getParentPrefix();
+			$parent_config = $this->Application->getUnitConfig($parent_prefix);
+
+			$sql = 'SELECT ' . $parent_config->getIDField() .'
+					FROM ' . $parent_config->getTableName() .'
 					WHERE ResourceId = ' . $resource_id;
 			$this->Application->SetVar($parent_prefix . '_id', $this->Conn->GetOne($sql));
 
 			// 3. get main item category
 			$sql = 'SELECT CategoryId
-					FROM ' . $this->Application->getUnitOption('ci', 'TableName') .'
+					FROM ' . $this->Application->getUnitConfig('ci')->getTableName() .'
 					WHERE ItemResourceId = ' . $resource_id .' AND PrimaryCat = 1';
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Returns prefix for permissions
 		 *
 		 * @param kEvent $event
 		 */
 		function getPermPrefix($event)
 		{
 			$main_prefix = $this->Application->GetTopmostPrefix($event->Prefix, true);
 			// this will return LINK for l, ARTICLE for n, TOPIC for bb, PRODUCT for p
 
-			return $this->Application->getUnitOption($main_prefix, 'PermItemPrefix');
+			return $this->Application->getUnitConfig($main_prefix)->getPermItemPrefix();
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 * @see OnListBuild
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			if ( !$this->Application->isAdminUser ) {
 				$object->addFilter('active', '%1$s.Status = ' . STATUS_ACTIVE);
 			}
 
 			switch ($event->Special) {
 				case 'showall':
 					$object->clearFilters();
 					break;
 
 				case 'item': // used ?
 					$object->clearFilters();
 					$parent_info = $object->getLinkedInfo();
 
 					$parent = $this->Application->recallObject($parent_info['ParentPrefix']);
 					/* @var $parent kDBItem */
 
 					$object->addFilter('item_reviews', '%1$s.ItemId = ' . $parent->GetDBField('ResourceId'));
 					break;
 
 				case 'products': // used in In-Portal (Structure & Data -> Reviews section)
 					$object->removeFilter('parent_filter'); // this is important
 					$object->addFilter('product_reviews', 'pr.ResourceId IS NOT NULL');
 					break;
 			}
 
 			if ( preg_match('/(.*)-rev/', $event->Prefix, $regs) ) {
 				// "Structure & Data" -> "Reviews" (section in K4)
-				$item_type = $this->Application->getUnitOption($regs[1], 'ItemType');
+				$item_type = $this->Application->getUnitConfig($regs[1])->getItemType();
 				$object->addFilter('itemtype_filter', '%1$s.ItemType = ' . $item_type);
 
 				if ( $this->Application->isAdmin ) {
 					// temporarily solution so we can see sub-items on separate grid in Admin
 					$object->removeFilter('parent_filter');
 				}
 			}
 
 			if ( $event->getEventParam('type') == 'current_user' ) {
 				$object->addFilter('current_user', '%1$s.CreatedById = ' . $this->Application->RecallVar('user_id'));
 				$object->addFilter('current_ip', '%1$s.IPAddress = "' . $this->Application->getClientIp() . '"');
 			}
 		}
 
 		/**
 		 * Adds review from front in case if user is logged in
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAddReview($event)
 		{
 			$event->CallSubEvent('OnCreate');
 		}
 
 		/**
 		 * Get new review status on user review permission
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 */
 		function getReviewStatus($event)
 		{
-			$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
+			$parent_prefix = $event->getUnitConfig()->getParentPrefix();
+
 			$main_object = $this->Application->recallObject($parent_prefix);
 			/* @var $main_object kCatDBItem */
 
 			$ret = STATUS_DISABLED;
 			$perm_name = $this->getPermPrefix($event).'.REVIEW';
 			if ($this->Application->CheckPermission($perm_name, 0, $main_object->GetDBField('CategoryId'))) {
 				$ret = STATUS_ACTIVE;
 			}
 			else if ($this->Application->CheckPermission($perm_name.'.PENDING', 0, $main_object->GetDBField('CategoryId'))) {
 				$ret = STATUS_PENDING;
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Prefills all fields on front-end
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$parent_info = $object->getLinkedInfo();
-			$item_type = $this->Application->getUnitOption($parent_info['ParentPrefix'], 'ItemType');
+			$item_type = $this->Application->getUnitConfig($parent_info['ParentPrefix'])->getItemType();
 
 			$object->SetDBField('IPAddress', $this->Application->getClientIp());
 			$object->SetDBField('ItemType', $item_type);
 			$object->SetDBField('Module', $this->Application->findModule('Var', $parent_info['ParentPrefix'], 'Name'));
 
 			if ( $this->Application->isAdminUser ) {
 				// don't perform spam control on admin
 				return ;
 			}
 
 			$spam_helper = $this->Application->recallObject('SpamHelper');
 			/* @var $spam_helper SpamHelper */
 
 			$spam_helper->InitHelper($parent_info['ParentId'], 'Review', 0);
 
 			if ( $spam_helper->InSpamControl() ) {
 				$event->status = kEvent::erFAIL;
 				$object->SetError('ReviewText', 'too_frequent', 'lu_ferror_review_duplicate');
 				return;
 			}
 
 			$rating = $object->GetDBField('Rating');
 			if ( $rating < 1 || $rating > 5 ) {
 				$object->SetDBField('Rating', null);
 			}
 
 			$object->SetDBField('ItemId', $parent_info['ParentId']); // ResourceId
 			$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 
 			$object->SetDBField('Status', $this->getReviewStatus($event));
 			$object->SetDBField('TextFormat', 0); // set plain text format directly
 		}
 
 		/**
 		 * Sets correct rating value
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$rating = $object->GetDBField('Rating');
 
 			if ( !$rating ) {
 				$object->SetDBField('Rating', null);
 			}
 		}
 
 		/**
 		 * Updates item review counter
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(kEvent $event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$this->updateSubitemCounters($event);
 
 			if ( !$this->Application->isAdminUser ) {
 				$spam_helper = $this->Application->recallObject('SpamHelper');
 				/* @var $spam_helper SpamHelper */
 
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$parent_info = $object->getLinkedInfo();
 
-				$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
+				$config_mapping = $event->getUnitConfig()->getConfigMapping();
 				$review_settings = $config_mapping['ReviewDelayValue'] . ':' . $config_mapping['ReviewDelayInterval'];
 				$spam_helper->InitHelper($parent_info['ParentId'], 'Review', $review_settings);
 
 				$spam_helper->AddToSpamControl();
 
 				$review_status = $object->GetDBField('Status');
 
 				if ( $review_status == STATUS_ACTIVE || $review_status == STATUS_PENDING ) {
 					$email_event = $this->getPermPrefix($event) . '.REVIEW.' . ($review_status == STATUS_ACTIVE ? 'ADD' : 'ADD.PENDING');
 					$this->Application->emailUser($email_event, $object->GetDBField('CreatedById'));
 					$this->Application->emailAdmin($email_event);
 				}
 			}
 		}
 
 		/**
 		 * Updates item review counter
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemUpdate(kEvent $event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$this->updateSubitemCounters($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $this->Application->isAdminUser && !$object->IsTempTable() ) {
 				// send email on review status change from reviews grid in admin
 				$review_status = $object->GetDBField('Status');
 				$process_status = Array (STATUS_ACTIVE, STATUS_DISABLED);
 
 				if ( ($review_status != $object->GetOriginalField('Status')) && in_array($review_status, $process_status) ) {
 					$this->_loadMainObject($event);
 
 					$email_event = $this->getPermPrefix($event) . '.REVIEW.' . ($review_status == STATUS_ACTIVE ? 'APPROVE' : 'DENY');
 					$this->Application->emailUser($email_event, $object->GetDBField('CreatedById'));
 				}
 			}
 		}
 
 		/**
 		 * Loads main object of review (link, article, etc.)
 		 *
 		 * @param kEvent $event
 		 * @return kCatDBItem
 		 */
 		function _loadMainObject($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
-			$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
-			$parent_table_key = $this->Application->getUnitOption($event->Prefix, 'ParentTableKey');
-			$foreign_key = $this->Application->getUnitOption($event->Prefix, 'ForeignKey');
+			$config = $event->getUnitConfig();
+			$parent_prefix = $config->getParentPrefix();
 
 			$main_object = $this->Application->recallObject($parent_prefix, null, Array ('skip_autoload' => true));
 			/* @var $main_object kDBItem */
 
-			$main_object->Load($object->GetDBField($foreign_key), $parent_table_key);
+			$main_object->Load($object->GetDBField($config->getForeignKey()), $config->getParentTableKey());
 		}
 
 		/**
 		 * Updates total review counter, cached rating, votes count
 		 *
 		 * @param kEvent $event
 		 */
 		function updateSubitemCounters($event)
 		{
 			if ( $event->Special == '-item' ) {
 				// ignore Main Item Copy/Pasting and Deleting
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
-			$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
-			$parent_table = $this->Application->getUnitOption($parent_prefix, 'TableName');
+			$parent_prefix = $event->getUnitConfig()->getParentPrefix();
+			$parent_table = $this->Application->getUnitConfig($parent_prefix)->getTableName();
 
 			if ( $object->IsTempTable() ) {
 				$parent_table = $this->Application->GetTempName($parent_table, 'prefix:' . $object->Prefix);
 			}
 
 			$fields_hash = Array ('CachedReviewsQty' => 0, 'CachedRating' => 0, 'CachedVotesQty' => 0);
 
 			// 1. update review counter
 			$sql = 'SELECT COUNT(ReviewId)
 					FROM ' . $object->TableName . '
 					WHERE ItemId = ' . $object->GetDBField('ItemId');
 			$fields_hash['CachedReviewsQty'] = $this->Conn->GetOne($sql);
 
 			// 2. update votes counter + rating
 			$rating = $object->GetDBField('Rating');
 
 			$sql = 'SELECT CachedRating, CachedVotesQty
 					FROM ' . $parent_table . '
 					WHERE ResourceId = ' . $object->GetDBField('ItemId');
 			$parent_data = $this->Conn->GetRow($sql);
 
 			$avg_rating = $parent_data['CachedRating'];
 			$votes_count = $parent_data['CachedVotesQty'];
 
 			switch ($event->Name) {
 				case 'OnAfterItemCreate': // adding new review with rating
 					$this->changeRating($avg_rating, $votes_count, $rating, '+');
 					break;
 
 				case 'OnAfterItemDelete':
 					$this->changeRating($avg_rating, $votes_count, $rating, '-');
 					break;
 
 				case 'OnAfterItemUpdate':
 					$this->changeRating($avg_rating, $votes_count, $object->GetOriginalField('Rating'), '-');
 					$this->changeRating($avg_rating, $votes_count, $rating, '+');
 					break;
 			}
 
 			$fields_hash['CachedRating'] = "$avg_rating";
 			$fields_hash['CachedVotesQty'] = $votes_count;
 
 			$this->Conn->doUpdate($fields_hash, $parent_table, 'ResourceId = ' . $object->GetDBField('ItemId'));
 		}
 
 		/**
 		 * Changes average rating and votes count based on requested operation
 		 *
 		 * @param float $avg_rating average rating before new vote
 		 * @param int $votes_count votes count before new vote
 		 * @param int $rating new vote (from 1 to 5)
 		 * @param string $operation requested operation (+ / -)
 		 */
 		function changeRating(&$avg_rating, &$votes_count, $rating, $operation)
 		{
 			if ( $rating < 1 || $rating > 5 ) {
 				return;
 			}
 
 			if ( $operation == '+' ) {
 				$avg_rating = (($avg_rating * $votes_count) + $rating) / ($votes_count + 1);
 				++$votes_count;
 			}
 			else {
 				if ( $votes_count > 1 ) { // escape division by 0
 					$avg_rating = (($avg_rating * $votes_count) - $rating) / ($votes_count - 1);
 				}
 				else {
 					$avg_rating = (($avg_rating * $votes_count) - $rating) / 1;
 				}
 				--$votes_count;
 			}
 		}
 
 		/**
 		 * Updates main item cached review counter
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$this->updateSubitemCounters($event);
 		}
 
 		/**
 		 * Creates review & redirect to confirmation template
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCreate(kEvent $event)
 		{
 			parent::OnCreate($event);
 
 			if ( $event->status != kEvent::erSUCCESS || $this->Application->isAdmin ) {
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $this->Application->GetVar('ajax') == 'yes' ) {
 				$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
 				/* @var $ajax_form_helper AjaxFormHelper */
 
 				$params = Array ('status' => 'OK');
 
 				if ( $event->status != kEvent::erSUCCESS ) {
 					$ajax_form_helper->prepareJSONErrors($event, $params);
 				}
 
 				// let FormManager decide what template to show
 				$params['review_status'] = $object->GetDBField('Status');
 
 				$ajax_form_helper->sendResponse($event, $params);
 			}
 			else {
 				$event->SetRedirectParam('opener', 's');
 				$next_template = $object->GetDBField('Status') == STATUS_ACTIVE ? 'success_template' : 'success_pending_template';
 				$event->redirect = $this->Application->GetVar($next_template);
 
-				$parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
+				$parent_prefix = $event->getUnitConfig()->getParentPrefix();
 				$event->SetRedirectParam('pass', 'm,'.$parent_prefix);
 			}
 		}
 
 		/**
 		 * Makes left join to item's table, when in separate grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			if (preg_match('/(.*)-rev/', $event->Prefix, $regs) && $this->Application->prefixRegistred($regs[1])) {
 				// "Structure & Data" -> "Reviews" (section in K4)
 
+				$config = $event->getUnitConfig();
+				$item_config = $this->Application->getUnitConfig($regs[1]);
+
 				// 1. add join to items table (for "Structure & Data" -> "Reviews" section)
-				$item_table = $this->Application->getUnitOption($regs[1], 'TableName');
-				$ci_table = $this->Application->getUnitOption('ci', 'TableName');
+				$item_table = $item_config->getTableName();
+				$ci_table = $this->Application->getUnitConfig('ci')->getTableName();
 
-				$list_sqls = $this->Application->getUnitOption($event->Prefix, 'ListSQLs');
-				$list_sqls[''] .= ' LEFT JOIN '.$item_table.' item_table ON item_table.ResourceId = %1$s.ItemId';
-				$list_sqls[''] .= ' LEFT JOIN '.$ci_table.' ci ON item_table.ResourceId = ci.ItemResourceId AND ci.PrimaryCat = 1';
-				$this->Application->setUnitOption($event->Prefix, 'ListSQLs', $list_sqls);
+				$list_sql = $config->getListSQLsBySpecial('');
+				$list_sql .= ' LEFT JOIN '.$item_table.' item_table ON item_table.ResourceId = %1$s.ItemId';
+				$list_sql .= ' LEFT JOIN '.$ci_table.' ci ON item_table.ResourceId = ci.ItemResourceId AND ci.PrimaryCat = 1';
+				$config->setListSQLsBySpecial('', $list_sql);
 
 				// 2. add calculated field
-				$calculated_fields = $this->Application->getUnitOption($event->Prefix, 'CalculatedFields');
-				$calculated_fields['']['CatalogItemName'] = 'item_table.' . $this->getTitleField($regs[1]);
-				$calculated_fields['']['CatalogItemId'] = 'item_table.' . $this->Application->getUnitOption($regs[1], 'IDField');
-				$calculated_fields['']['CatalogItemCategory'] = 'ci.CategoryId';
-				$this->Application->setUnitOption($event->Prefix, 'CalculatedFields', $calculated_fields);
+				$config->addCalculatedFieldsBySpecial('', Array (
+					'CatalogItemName' => 'item_table.' . $this->getTitleField($regs[1]),
+					'CatalogItemId' => 'item_table.' . $item_config->getIDField(),
+					'CatalogItemCategory' => 'ci.CategoryId',
+				));
 			}
 		}
 
 		/**
 		 * Convert TitleField field of kMultiLanguage formatter used for it
 		 *
 		 * @param string $prefix
 		 * @return string
 		 */
 		function getTitleField($prefix)
 		{
+			$config = $this->Application->getUnitConfig($prefix);
 			$lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_';
 
-			$title_field = $this->Application->getUnitOption($prefix, 'TitleField');
-			$field_options = $this->Application->getUnitOption($prefix.'.'.$title_field, 'Fields');
+			$title_field = $config->getTitleField();
+			$field_options = $config->getFieldByName($title_field);
 
 			$formatter_class = isset($field_options['formatter']) ? $field_options['formatter'] : '';
+
 			if ($formatter_class == 'kMultiLanguage' && !isset($field_options['master_field'])) {
 				$title_field = $lang_prefix.$title_field;
 			}
+
 			return $title_field;
 		}
 
 		/**
 		 * Set's new perpage for Category Item Reviews (used on Front-end)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetPerPage(kEvent $event)
 		{
 			parent::OnSetPerPage($event);
 
-			$parent_prefix = $event->Application->getUnitOption($event->Prefix, 'ParentPrefix');
+			$parent_prefix = $event->getUnitConfig()->getParentPrefix();
 			$event->SetRedirectParam('pass', 'm,' . $event->getPrefixSpecial() . ',' . $parent_prefix);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/forms/form_submissions/form_submissions_eh.php
===================================================================
--- branches/5.3.x/core/units/forms/form_submissions/form_submissions_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/forms/form_submissions/form_submissions_eh.php	(revision 15698)
@@ -1,552 +1,544 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class FormSubmissionsEventHandler extends kDBEventHandler {
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( !$this->Application->isAdmin ) {
 				if ( $event->Name == 'OnCreate' ) {
 					// anybody can submit forms on front
 					return true;
 				}
 			}
 
 			$section = $event->getSection();
 			$form_id = $this->Application->GetVar('form_id');
 
 			$event->setEventParam('PermSection', $section . ':' . $form_id);
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Always allow to view feedback form
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnItemBuild' => Array ('self' => true),
 				'OnEdit' => Array ('self' => 'view', 'subitem' => 'view'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Returns filter block based on field element type
 		 *
 		 * @param string $element_type
 		 * @return string
 		 */
 		function _getFilterBlock($element_type)
 		{
 			$mapping = Array (
 				'text' => 'grid_like_filter',
 				'select' => 'grid_options_filter',
 				'radio' => 'grid_options_filter',
 				'checkbox' => 'grid_options_filter',
 				'password' => 'grid_like_filter',
 				'textarea' => 'grid_like_filter',
 				'label' => 'grid_like_filter',
 				'upload' => 'grid_empty_filter',
 			);
 
 			return $mapping[$element_type];
 		}
 
-		function OnBuildFormFields($event)
+		protected function OnBuildFormFields(kEvent $event)
 		{
 			$form_id = $this->Application->GetVar('form_id');
-			if (!$form_id) return ;
 
-			$conf_fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
-			$conf_grids = $this->Application->getUnitOption($event->Prefix, 'Grids');
+			if ( !$form_id ) {
+				return;
+			}
+
+			$config = $event->getUnitConfig();
 
 			$helper = $this->Application->recallObject('InpCustomFieldsHelper');
 			/* @var $helper InpCustomFieldsHelper */
 
 			$sql = 'SELECT *
 					FROM ' . TABLE_PREFIX . 'FormFields
 					WHERE FormId = ' . (int)$form_id . '
 					ORDER BY Priority DESC';
 			$fields = $this->Conn->Query($sql, 'FormFieldId');
 
 			$use_options = Array ('radio', 'select', 'checkbox');
 			$check_visibility = $this->Application->LoggedIn() && !$this->Application->isAdminUser;
 
 			foreach ($fields as $field_id => $options) {
 				$field_visible = $check_visibility ? $options['Visibility'] == SubmissionFormField::VISIBILITY_EVERYONE : true;
-				$field_options = Array('type' => 'string', 'default' => $options['DefaultValue']);
+				$field_options = Array ('type' => 'string', 'default' => $options['DefaultValue']);
 
-				if ($options['Required'] && $field_visible) {
+				if ( $options['Required'] && $field_visible ) {
 					$field_options['required'] = 1;
 				}
 
-				if ($options['Validation'] == 1) {
+				if ( $options['Validation'] == 1 ) {
 					$field_options['formatter'] = 'kFormatter';
 					$field_options['regexp'] = '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i';
 				}
 
-				if ($options['DisplayInGrid']) {
+				if ( $options['DisplayInGrid'] ) {
 					$title = $options['Prompt'];
 
-					if (substr($title, 0, 1) == '+') {
+					if ( substr($title, 0, 1) == '+' ) {
 						$this->Application->Phrases->AddCachedPhrase('form_col_title' . $field_id, substr($title, 1));
 						$title = 'form_col_title' . $field_id;
 					}
 
-					$conf_grids['Default']['Fields']['fld_' . $field_id] = Array (
+					$grid_field_options = Array (
 						'title' => $title, 'no_special' => 1, 'nl2br' => 1, 'first_chars' => 200,
 						'filter_block' => $this->_getFilterBlock($options['ElementType'])
 					);
 
-					if ($options['ElementType'] == 'upload') {
-						$conf_grids['Default']['Fields']['fld_' . $field_id]['data_block'] = 'grid_upload_td';
+					if ( $options['ElementType'] == 'upload' ) {
+						$grid_field_options['data_block'] = 'grid_upload_td';
 					}
 
-					if ($options['Validation'] == 1) {
-						$conf_grids['Default']['Fields']['fld_' . $field_id]['data_block'] = 'grid_email_td';
+					if ( $options['Validation'] == 1 ) {
+						$grid_field_options['data_block'] = 'grid_email_td';
 					}
+
+					$config->addGridFields('Default', $grid_field_options, 'fld_' . $field_id);
 				}
 
-				if ($options['ElementType'] == 'checkbox' && !$options['ValueList']) {
+				if ( $options['ElementType'] == 'checkbox' && !$options['ValueList'] ) {
 					// fix case, when user haven't defined any options for checkbox
 					$options['ValueList'] = '1=la_Yes||0=la_No';
 				}
 
-				if (in_array($options['ElementType'], $use_options) && $options['ValueList']) {
+				if ( in_array($options['ElementType'], $use_options) && $options['ValueList'] ) {
 					// field type can have options and user have defined them too
-					$field_options['options'] = $helper->GetValuesHash( $options['ValueList'] );
+					$field_options['options'] = $helper->GetValuesHash($options['ValueList']);
 					$field_options['formatter'] = 'kOptionsFormatter';
 				}
 
-				if ($options['ElementType'] == 'password') {
+				if ( $options['ElementType'] == 'password' ) {
 					$field_options['formatter'] = 'kPasswordFormatter';
 					$field_options['hashing_method'] = PasswordHashingMethod::NONE;
 					$field_options['verify_field'] = 'fld_' . $field_id . '_verify';
 				}
 
-				if ($options['ElementType'] == 'upload') {
+				if ( $options['ElementType'] == 'upload' ) {
 					$field_options['formatter'] = 'kUploadFormatter';
 					$field_options['upload_dir'] = WRITEBALE_BASE . DIRECTORY_SEPARATOR . 'user_files' . DIRECTORY_SEPARATOR . 'form_submissions';
 
 					if ( $options['UploadMaxSize'] ) {
 						$field_options['max_size'] = $options['UploadMaxSize'] * 1024; // convert Kbytes to bytes
 					}
 
 					if ( $options['UploadExtensions'] ) {
 						$field_options['file_types'] = '*.' . implode(';*.', explode(',', $options['UploadExtensions']));
 					}
 				}
 
-				$conf_fields['fld_' . $field_id] = $field_options;
+				$config->addFields($field_options, 'fld_' . $field_id);
 			}
-
-			$this->Application->setUnitOption($event->Prefix, 'Fields', $conf_fields);
-			$this->Application->setUnitOption($event->Prefix, 'Grids', $conf_grids);
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$object->addFilter('form_filter', '%1$s.FormId = ' . (int)$this->Application->GetVar('form_id'));
 		}
 
 		/**
 		 * Allows user to see it's last feedback form data
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access public
 		 */
 		public function getPassedID(kEvent $event)
 		{
 			if ( $event->Special == 'last' ) {
 				// allow user to see his last submitted form
 				return $this->Application->RecallVar('last_submission_id');
 			}
 
 			if ( $this->Application->isAdminUser ) {
 				// don't check ids in admin
 				return parent::getPassedID($event);
 			}
 
 			// no way to see other user's form submission by giving it's ID directly in url
 			return 0;
 		}
 
 		/**
 		 * Creates new form submission from Front-End
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCreate(kEvent $event)
 		{
 			parent::OnCreate($event);
 
 			if ( $event->status != kEvent::erSUCCESS ) {
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// allows user to view only it's last submission
 			$this->Application->StoreVar('last_submission_id', $object->GetID());
 
 			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
 			/* @var $form_submission_helper FormSubmissionHelper */
 
 			$form =& $form_submission_helper->getForm($object);
 
 			$notify_email = $form->GetDBField('SubmitNotifyEmail');
 
 			if ( $notify_email ) {
 				$send_params = Array (
 					'to_name' => $notify_email,
 					'to_email' => $notify_email,
 				);
 
 				$this->Application->emailAdmin('FORM.SUBMITTED', null, $send_params);
 			}
 			else {
 				$this->Application->emailAdmin('FORM.SUBMITTED');
 			}
 
 //			$this->Application->emailUser('FORM.SUBMITTED', null, Array ('to_email' => ''));
 
 			$event->SetRedirectParam('opener', 's');
 			$event->SetRedirectParam('m_cat_id', 0);
 
 			$theme = $this->Application->recallObject('theme.current');
 			/* @var $theme kDBItem */
 
 			$template = htmlspecialchars_decode($this->Application->GetVar('success_template')); // kHTTPQuery do htmlspecialchars on everything
 			$alias_template = $theme->GetField('TemplateAliases', $template);
 
 			$event->redirect = $alias_template ? $alias_template : $template;
 		}
 
 		/**
 		 * Processes Captcha code
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('IPAddress', $this->Application->getClientIp());
 
 			if ( !$object->GetDBField('ReferrerURL') ) {
 				$referrer = $this->Application->GetVar('original_referrer');
 
 				if ( !$referrer ) {
 					$base_url = preg_quote($this->Application->BaseURL(), '/');
 					$referrer = preg_replace('/^' . $base_url . '/', '/', $_SERVER['HTTP_REFERER'], 1);
 				}
 
 				$object->SetDBField('ReferrerURL', $referrer);
 			}
 
 			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
 			/* @var $form_submission_helper FormSubmissionHelper */
 
 			$form =& $form_submission_helper->getForm($object);
 
 			// validate captcha code
 			if ( $form->GetDBField('UseSecurityImage') && !$this->Application->LoggedIn() ) {
 				$captcha_helper = $this->Application->recallObject('CaptchaHelper');
 				/* @var $captcha_helper kCaptchaHelper */
 
 				$captcha_helper->validateCode($event, false);
 			}
 		}
 
 		/**
 		 * Checks, that target submission was selected for merging
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->setRequired('MergeToSubmission', $object->GetDBField('IsMergeToSubmission'));
 		}
 
 		/**
 		 * Passes form_id, when using "Prev"/"Next" toolbar buttons
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveAndGo(kEvent $event)
 		{
 			parent::OnPreSaveAndGo($event);
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$event->SetRedirectParam('pass', 'm,form,formsubs');
 			}
 		}
 
 		/**
 		 * Saves edited item in temp table and goes
 		 * to passed tabs, by redirecting to it with OnPreSave event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveAndGoToTab(kEvent $event)
 		{
 			parent::OnPreSaveAndGoToTab($event);
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$event->SetRedirectParam('pass', 'm,form,formsubs');
 			}
 		}
 
 		/**
 		 * Set's new per-page for grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetPerPage(kEvent $event)
 		{
 			parent::OnSetPerPage($event);
 
 			$event->SetRedirectParam('pass', 'm,form,' . $event->getPrefixSpecial());
 		}
 
 		/**
 		 * Occurs when page is changed (only for hooking)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSetPage(kEvent $event)
 		{
 			parent::OnSetPage($event);
 
 			$event->SetRedirectParam('pass', 'm,form,' . $event->getPrefixSpecial());
 		}
 
 		/**
 		 * Fills merge-to dropdown
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			if ($event->Special == 'merge-to') {
 				return ;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$form_id = $object->GetDBField('FormId');
-			$email_field = $this->getFieldByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_EMAIL);
+			$email_field = $this->getFieldNameByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_EMAIL);
 
 			if (!$email_field) {
 				return ;
 			}
 
 			$merge_to = $this->Application->recallObject($event->Prefix . '.merge-to', null, Array ('skip_autoload' => true));
 			/* @var $merge_to kDBItem */
 
 			$sql = $merge_to->GetSelectSQL() . ' WHERE (FormId = ' . $form_id . ') AND (' . $email_field . ' = ' . $this->Conn->qstr( $object->GetDBField($email_field) ) . ')';
 			$submissions = $this->Conn->Query($sql, $object->IDField);
 
 			// remove this submission
 			unset($submissions[ $object->GetID() ]);
 
 			if (!$submissions) {
 				return ;
 			}
 
 			$options = Array ();
 
-			$name_field = $this->getFieldByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_NAME);
-			$subject_field = $this->getFieldByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_SUBJECT);
+			$name_field = $this->getFieldNameByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_NAME);
+			$subject_field = $this->getFieldNameByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_SUBJECT);
 
 			$language = $this->Application->recallObject('lang.current');
 			/* @var $language kDBItem */
 
 			$date_format = $language->GetDBField('DateFormat');
 
 			foreach ($submissions as $submission_id => $submission_data) {
 				$option_title = ''; // SenderName (email@address.com) - Subject (06/29/2010)
 				$merge_to->LoadFromHash($submission_data);
 
 				if ($name_field) {
 					$option_title = $merge_to->GetDBField($name_field) . ' (' . $merge_to->GetDBField($email_field) . ') - ';
 				}
 				else {
 					$option_title = $merge_to->GetDBField($email_field) . ' - ';
 				}
 
 				if ($subject_field) {
 					$option_title .= $merge_to->GetField($subject_field) . ' (' . $merge_to->GetField('SubmissionTime', $date_format) . ')';
 				}
 				else {
 					$option_title .= $merge_to->GetField('SubmissionTime', $date_format);
 				}
 
 				$options[$submission_id] = $option_title;
 			}
 
 			$object->SetFieldOption('MergeToSubmission', 'options', $options);
 		}
 
 		/**
 		 * Returns submission field name based on given role
 		 *
 		 * @param int $form_id
 		 * @param string $role
 		 * @return string
+		 * @see FormSubmissionHelper::getFieldByRole()
 		 */
-		function getFieldByRole($form_id, $role)
+		function getFieldNameByRole($form_id, $role)
 		{
-			static $cache = Array ();
-
-			if (!array_key_exists($form_id, $cache)) {
-				$id_field = $this->Application->getUnitOption('formflds', 'IDField');
-				$table_name = $this->Application->getUnitOption('formflds', 'TableName');
-
-				$sql = 'SELECT ' . $id_field . ', EmailCommunicationRole
-						FROM ' . $table_name . '
-						WHERE FormId = ' . $form_id . ' AND EmailCommunicationRole <> 0';
-				$cache[$form_id] = $this->Conn->GetCol($sql, 'EmailCommunicationRole');
-			}
+			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
+			/* @var $form_submission_helper FormSubmissionHelper */
 
-			// get field name by role
-			return array_key_exists($role, $cache[$form_id]) ? 'fld_' . $cache[$form_id][$role] : false;
+			return $form_submission_helper->getFieldNameByRole($form_id, $role);
 		}
 
 		/**
 		 * Performs submission merge
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdate(kEvent $event)
 		{
 			parent::OnUpdate($event);
 
 			if ($event->status == kEvent::erSUCCESS) {
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$merge_to = $object->GetDBField('MergeToSubmission');
 
 				if (!$merge_to) {
 					return ;
 				}
 
 				$form_id = $object->GetDBField('FormId');
 
 				$sql = 'SELECT *
 						FROM ' . TABLE_PREFIX . 'Forms
 						WHERE FormId = ' . $form_id;
 				$form_info = $this->Conn->GetRow($sql);
 
 				$reply = $this->Application->recallObject('submission-log.merge', null, Array ('skip_autoload' => true));
 				/* @var $reply kDBItem */
 
-				$email_field = $this->getFieldByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_EMAIL);
-				$subject_field = $this->getFieldByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_SUBJECT);
-				$body_field = $this->getFieldByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_BODY);
+				$email_field = $this->getFieldNameByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_EMAIL);
+				$subject_field = $this->getFieldNameByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_SUBJECT);
+				$body_field = $this->getFieldNameByRole($form_id, SubmissionFormField::COMMUNICATION_ROLE_BODY);
 
 				$reply->SetDBField('FormSubmissionId', $merge_to);
 
 				if ($email_field) {
 					$reply->SetDBField('FromEmail', $object->GetDBField($email_field));
 				}
 
 				$reply->SetDBField('ToEmail', $form_info['ReplyFromEmail']);
 
 				if ($subject_field) {
 					$reply->SetDBField('Subject', $object->GetDBField($subject_field));
 				}
 
 				if ($body_field) {
 					$reply->SetDBField('Message', $object->GetDBField($body_field));
 				}
 
 				$reply->SetDBField('SentOn_date', $object->GetDBField('SubmissionTime'));
 				$reply->SetDBField('SentOn_time', $object->GetDBField('SubmissionTime'));
 				$reply->SetDBField('MessageId', $object->GetDBField('MessageId'));
 				$reply->SetDBField('SentStatus', SUBMISSION_LOG_SENT);
 
 				// as if emails was really received via mailbox
 				$this->Application->SetVar('client_mode', 1);
 
 				if ($reply->Create()) {
 					// delete submission, since it was merged
 					$object->Delete();
 				}
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/forms/form_submissions/form_submission_tp.php
===================================================================
--- branches/5.3.x/core/units/forms/form_submissions/form_submission_tp.php	(revision 15697)
+++ branches/5.3.x/core/units/forms/form_submissions/form_submission_tp.php	(revision 15698)
@@ -1,60 +1,60 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class FormSubmissionTagProcessor extends kDBTagProcessor {
 
 	/**
 	 * Returns phrase translation by name
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access public
 	 */
 	function PhraseFromRequest($params)
 	{
 		$phrase_name = $this->Application->GetVar($params['name']);
 
 		if (array_key_exists('default', $params) && !$phrase_name) {
 			$phrase_name = $params['default'];
 		}
 
 		return $this->Application->Phrase($phrase_name);
 	}
 
 	/**
 	 * Allows to retrieve for submission field by it's name or role in email communications
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function FormField($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
 		/* @var $form_submission_helper FormSubmissionHelper */
 
 		$formatted = !(array_key_exists('db', $params) && $params['db']);
 		$format = $formatted ? (array_key_exists('format', $params) ? $params['format'] : null) : null;
 
-		if (array_key_exists('role', $params)) {
+		if ( array_key_exists('role', $params) ) {
 			return $form_submission_helper->getFieldByRole($object, $params['role'], $formatted, $format);
 		}
 
-		return $form_submission_helper->getFieldByName($params['name'], $formatted, $format);
+		return $form_submission_helper->getFieldByName($object, $params['name'], $formatted, $format);
 	}
 }
Index: branches/5.3.x/core/units/forms/forms/forms_eh.php
===================================================================
--- branches/5.3.x/core/units/forms/forms/forms_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/forms/forms/forms_eh.php	(revision 15698)
@@ -1,626 +1,630 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class FormsEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			// user can view any form on front-end
 			'OnItemBuild' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
-	function OnCreateSubmissionNodes($event)
+	protected function OnCreateSubmissionNodes(kEvent $event)
 	{
-		if (defined('IS_INSTALL') && IS_INSTALL) {
+		if ( defined('IS_INSTALL') && IS_INSTALL ) {
 			// skip any processing, because Forms table doesn't exists until install is finished
-			return ;
+			return;
 		}
 
 		$forms = $this->getForms();
 
-		if (!$forms) {
-			return ;
+		if ( !$forms ) {
+			return;
 		}
 
-		$form_subsection = Array(
-			'parent'		=>	'in-portal:forms',
-			'icon'			=>	'form_submission',
-			'label'			=>	'',
-			'url'			=>	Array('t' => 'submissions/submissions_list', 'pass' => 'm,form'),
-			'permissions'	=>	Array('view', 'add', 'edit', 'delete'),
-			'priority'		=>	1,
-			'type'			=>	stTREE,
+		$form_subsection = Array (
+			'parent' => 'in-portal:forms',
+			'icon' => 'form_submission',
+			'label' => '',
+			'url' => Array ('t' => 'submissions/submissions_list', 'pass' => 'm,form'),
+			'permissions' => Array ('view', 'add', 'edit', 'delete'),
+			'priority' => 1,
+			'type' => stTREE,
 		);
 
 		$priority = 1;
-		$sections = $this->Application->getUnitOption($event->Prefix, 'Sections');
+		$config = $event->getUnitConfig();
 
 		foreach ($forms as $form_id => $form_name) {
 			$this->Application->Phrases->AddCachedPhrase('form_sub_label_'.$form_id, $form_name);
 			$this->Application->Phrases->AddCachedPhrase('la_description_in-portal:submissions:'.$form_id, $form_name.' Submissions');
+
 			$form_subsection['label'] = 'form_sub_label_'.$form_id;
 			$form_subsection['url']['form_id'] = $form_id;
 			$form_subsection['priority'] = $priority++;
-			$sections['in-portal:submissions:'.$form_id] = $form_subsection;
-		}
 
-		$this->Application->setUnitOption($event->Prefix, 'Sections', $sections);
+			$config->addSections($form_subsection, 'in-portal:submissions:' . $form_id);
+		}
 	}
 
-	function getForms()
+	protected function getForms()
 	{
 		$cache_key = 'forms[%FormSerial%]';
 		$forms = $this->Application->getCache($cache_key);
 
-		if ($forms === false) {
+		if ( $forms === false ) {
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT Title, FormId
 					FROM ' . TABLE_PREFIX . 'Forms
 					ORDER BY Title ASC';
 			$forms = $this->Conn->GetCol($sql, 'FormId');
 
 			$this->Application->setCache($cache_key, $forms);
 		}
 
 		return $forms;
 	}
 
 	/**
 	 * Saves content of temp table into live and
 	 * redirects to event' default redirect (normally grid template)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSave(kEvent $event)
 	{
 		parent::OnSave($event);
 
 		if ( $event->status == kEvent::erSUCCESS ) {
 			$this->OnCreateFormFields($event);
 			$this->_deleteSectionCache();
 		}
 	}
 
 	/**
 	 * Deletes all selected items.
 	 * Automatically recurse into sub-items using temp handler, and deletes sub-items
 	 * by calling its Delete method if sub-item has AutoDelete set to true in its config file
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnMassDelete(kEvent $event)
 	{
 		parent::OnMassDelete($event);
 
 		if ( $event->status == kEvent::erSUCCESS ) {
 			$this->_deleteSectionCache();
 		}
 	}
 
 	function _deleteSectionCache()
 	{
 		$this->Application->HandleEvent(new kEvent('adm:OnResetSections'));
 
 		$this->Application->StoreVar('RefreshStructureTree', 1);
 	}
 
 	/**
 	 * Dynamically fills custom data config
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCreateFormFields($event)
 	{
 		$cur_fields = $this->Conn->Query('DESCRIBE '.TABLE_PREFIX.'FormSubmissions', 'Field');
 		$cur_fields = array_keys($cur_fields);
 
 		// keep all fields, that are not created on the fly (includes ones, that are added during customizations)
 		foreach ($cur_fields as $field_index => $field_name) {
 			if (!preg_match('/^fld_[\d]+/', $field_name)) {
 				unset($cur_fields[$field_index]);
 			}
 		}
 
 		$desired_fields = $this->Conn->GetCol('SELECT CONCAT(\'fld_\', FormFieldId) FROM '.TABLE_PREFIX.'FormFields ORDER BY FormFieldId');
 
 		$sql = array();
 
 		$fields_to_add = array_diff($desired_fields, $cur_fields);
 		foreach ($fields_to_add as $field) {
 			$field_expression = $field.' Text NULL';
 			$sql[] = 'ADD COLUMN '.$field_expression;
 		}
 
 		$fields_to_drop = array_diff($cur_fields, $desired_fields);
 		foreach ($fields_to_drop as $field) {
 			$sql[] = 'DROP COLUMN '.$field;
 		}
 
 		if ($sql) {
 			$query = 'ALTER TABLE '.TABLE_PREFIX.'FormSubmissions '.implode(', ', $sql);
 			$this->Conn->Query($query);
 		}
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnFormSubmit($event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
-		$fields = explode(',',$this->Application->GetVar('fields'));
+		$fields = explode(',', $this->Application->GetVar('fields'));
 		$required_fields = explode(',', $this->Application->GetVar('required_fields'));
 		$fields_params = $this->Application->GetVar('fields_params');
 
-		$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
+		$virtual_fields = $event->getUnitConfig()->getVirtualFields();
 
 		foreach ($fields as $field) {
 			$virtual_fields[$field] = Array ();
 
 			if ( in_array($field, $required_fields) ) {
 				$virtual_fields[$field]['required'] = 1;
 			}
 
 			$params = getArrayValue($fields_params, $field);
 
 			if ( $params !== false ) {
 				if ( getArrayValue($params, 'Type') == 'email' ) {
 					$virtual_fields[$field]['formatter'] = 'kFormatter';
 					$virtual_fields[$field]['regexp'] = '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i';
 					$virtual_fields[$field]['error_msgs'] = Array ('invalid_format' => '!la_invalid_email!');
 				}
 
 				if ( getArrayValue($params, 'Type') == 'file' ) {
 					$virtual_fields[$field]['formatter'] = 'kUploadFormatter';
 					$virtual_fields[$field]['upload_dir'] = '/uploads/sketches/';
 				}
 			}
 		}
 
 		$object->SetVirtualFields($virtual_fields);
 		$field_values = $this->getSubmittedFields($event);
 		$checkboxes = explode(',', $this->Application->GetVar('checkbox_fields')); // MailingList,In-Link,In-Newz,In-Bulletin
 
 		foreach ($checkboxes as $checkbox) {
-			if (isset($field_values[$checkbox])) {
+			if ( isset($field_values[$checkbox]) ) {
 				$field_values[$checkbox] = 1;
 			}
 			else {
 				$field_values[$checkbox] = '0';
 			}
 		}
 
 		$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 		if ( $object->Validate() ) {
 			$event->redirect = $this->Application->GetVar('success_template');
 			$this->Application->emailAdmin($this->Application->GetVar('email_event'));
 
 			$send_params = Array (
 				'to_email' => $field_values[$this->Application->GetVar('email_field')],
 				'to_name' => $field_values[$this->Application->GetVar('name_field')]
 			);
 
 			$this->Application->emailUser($this->Application->GetVar('email_event'), null, $send_params);
 
 			if ( $field_values['MailingList'] ) {
 				$this->Application->StoreVar('SubscriberEmail', $field_values['Email']);
 				$this->Application->HandleEvent(new kEvent('u:OnSubscribeUser', Array ('no_unsubscribe' => 1)));
 			}
 		}
 		else {
 			$event->status = kEvent::erFAIL;
 		}
 	}
 
 	/**
 	 * Don't use security image, when form requires login
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemCreate(kEvent $event)
 	{
 		parent::OnBeforeItemCreate($event);
 
 		$this->itemChanged($event);
 	}
 
 	/**
 	 * Don't use security image, when form requires login
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemUpdate(kEvent $event)
 	{
 		parent::OnBeforeItemUpdate($event);
 
 		$this->itemChanged($event);
 	}
 
 	/**
 	 * Occurs before item is changed
 	 *
 	 * @param kEvent $event
 	 */
 	function itemChanged($event)
 	{
 		$this->_validatePopSettings($event);
 		$this->_disableSecurityImage($event);
 		$this->_setRequired($event);
 	}
 
 	/**
 	 * Validates POP3 settings (performs test connect)
 	 *
 	 * @param kEvent $event
 	 */
 	function _validatePopSettings($event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$modes = Array ('Reply', 'Bounce');
 		$fields = Array ('Server', 'Port', 'Username', 'Password');
 		$changed_fields = array_keys( $object->GetChangedFields() );
 
 		foreach ($modes as $mode) {
 			$set = true;
 			$changed = false;
 
 			foreach ($fields as $field) {
 				$value = $object->GetDBField($mode . $field);
 
 				if (strlen( trim($value) ) == 0) {
 					$set = false;
 					break;
 				}
 
 				if (!$changed && in_array($mode . $field, $changed_fields)) {
 					$changed = true;
 				}
 			}
 
 			if ($set && $changed) {
 				// fields are set and at least on of them is changed
 				$connection_info = Array ();
 
 				foreach ($fields as $field) {
 					$connection_info[ strtolower($field) ] = $object->GetDBField($mode . $field);
 				}
 
 				$pop3_helper = $this->Application->makeClass('POP3Helper', Array ($connection_info, 10));
 				/* @var $pop3_helper POP3Helper */
 
 				switch ( $pop3_helper->initMailbox(true) ) {
 					case 'socket':
 						$object->SetError($mode . 'Server', 'connection_failed');
 						break;
 
 					case 'login':
 						$object->SetError($mode . 'Username', 'login_failed');
 						break;
 
 					case 'list':
 						$object->SetError($mode . 'Server', 'message_listing_failed');
 						break;
 				}
 			}
 		}
 
 	}
 
 	/**
 	 * Makes email communication fields required, when form uses email communication
 	 *
 	 * @param kEvent $event
 	 */
 	function _setRequired($event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$required = $object->GetDBField('EnableEmailCommunication');
 		$fields = Array (
 			'ReplyFromName', 'ReplyFromEmail', 'ReplyServer', 'ReplyPort', 'ReplyUsername', 'ReplyPassword',
 		);
 
 		if ($required && $object->GetDBField('BounceEmail')) {
 			$bounce_fields = Array ('BounceEmail', 'BounceServer', 'BouncePort', 'BounceUsername', 'BouncePassword');
 			$fields = array_merge($fields, $bounce_fields);
 		}
 
 		$object->setRequired($fields, $required);
 	}
 
 	/**
 	 * Don't use security image, when form requires login
 	 *
 	 * @param kEvent $event
 	 */
 	function _disableSecurityImage($event)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ($object->GetDBField('RequireLogin')) {
 			$object->SetDBField('UseSecurityImage', 0);
 		}
 	}
 
 	/**
 	 * Queries pop3 server about new incoming mail
 	 *
 	 * @param kEvent $event
 	 */
 	function OnProcessReplies($event)
 	{
 		$this->_processMailbox($event, false);
 	}
 
 	/**
 	 * Queries pop3 server about new incoming mail
 	 *
 	 * @param kEvent $event
 	 */
 	function OnProcessBouncedReplies($event)
 	{
 		$this->_processMailbox($event, true);
 	}
 
 	/**
 	 * Queries pop3 server about new incoming mail
 	 *
 	 * @param kEvent $event
 	 * @param bool $bounce_mode
 	 */
 	function _processMailbox($event, $bounce_mode = false)
 	{
+		$config = $event->getUnitConfig();
 		$this->Application->SetVar('client_mode', 1);
 
-		$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-		$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
-
 		$sql = 'SELECT *
-				FROM ' . $table_name . '
+				FROM ' . $config->getTableName() . '
 				WHERE EnableEmailCommunication = 1';
-		$forms = $this->Conn->Query($sql, $id_field);
+		$forms = $this->Conn->Query($sql, $config->getIDField());
 
 		$mailbox_helper = $this->Application->recallObject('MailboxHelper');
 		/* @var $mailbox_helper MailboxHelper */
 
 		$field_prefix = $bounce_mode ? 'Bounce' : 'Reply';
 
 		foreach ($forms as $form_id => $form_info) {
 			$recipient_email = $bounce_mode ? $form_info['BounceEmail'] : $form_info['ReplyFromEmail'];
 
-			if (!$recipient_email) {
+			if ( !$recipient_email ) {
 				continue;
 			}
 
 			$mailbox_helper->process(
 				Array (
 					'server' => $form_info[$field_prefix . 'Server'],
 					'port' => $form_info[$field_prefix . 'Port'],
 					'username' => $form_info[$field_prefix . 'Username'],
 					'password' => $form_info[$field_prefix . 'Password']
 				),
 				Array (&$this, 'isValidRecipient'),
 				Array (&$this, 'processEmail'),
 				Array (
 					'recipient_email' => $recipient_email,
 					'bounce_mode' => $bounce_mode,
 					'form_info' => $form_info,
 				)
 			);
 		}
 	}
 
 	function isValidRecipient($params)
 	{
 		$mailbox_helper = $this->Application->recallObject('MailboxHelper');
 		/* @var $mailbox_helper MailboxHelper */
 
 		$recipients = $mailbox_helper->getRecipients();
 		$recipient_email = $params['recipient_email'];
 
 		$emails_found = preg_match_all('/((' . REGEX_EMAIL_USER . ')(@' . REGEX_EMAIL_DOMAIN . '))/i', $recipients, $all_emails);
 
 		if (is_array($all_emails)) {
 			for ($i = 0; $i < $emails_found; $i++) {
 				if ($all_emails[1][$i] == $recipient_email) {
 					// only read messages, that are addresses to submission reply email
 					return true;
 				}
 			}
 		}
 
 		// If this is a forwarded message - we drop all the other aliases and deliver only to the x-forward to address;
 		if (preg_match('/((' . REGEX_EMAIL_USER . ')(@' . REGEX_EMAIL_DOMAIN . '))/i', $mailbox_helper->headers['x-forward-to'], $get_to_email)) {
 			if ($get_to_email[1] == $recipient_email) {
 				// only read messages, that are addresses to submission reply email
 				return true;
 			}
 		}
 
 		return false;
 	}
 
 	function processEmail($params, &$fields_hash)
 	{
-		if ($params['bounce_mode']) {
+		if ( $params['bounce_mode'] ) {
 			// mark original message as bounced
 
 			$mailbox_helper = $this->Application->recallObject('MailboxHelper');
 			/* @var $mailbox_helper MailboxHelper */
 
-			if (!array_key_exists('attachments', $mailbox_helper->parsedMessage)) {
+			if ( !array_key_exists('attachments', $mailbox_helper->parsedMessage) ) {
 				// for now only parse bounces based on attachments, skip other bounce types
 				return false;
 			}
 
 			for ($i = 0; $i < count($mailbox_helper->parsedMessage['attachments']); $i++) {
 				$attachment =& $mailbox_helper->parsedMessage['attachments'][$i];
 
-				switch ($attachment['headers']['content-type']) {
+				switch ( $attachment['headers']['content-type'] ) {
 					case 'message/delivery-status':
 						// save as BounceInfo
 						$mime_decode_helper = $this->Application->recallObject('MimeDecodeHelper');
 						/* @var $mime_decode_helper MimeDecodeHelper */
 
-						$charset = $mailbox_helper->parsedMessage[ $fields_hash['MessageType'] ][0]['charset'];
+						$charset = $mailbox_helper->parsedMessage[$fields_hash['MessageType']][0]['charset'];
 						$fields_hash['Message'] = $mime_decode_helper->convertEncoding($charset, $attachment['data']);
 						break;
 
 					case 'message/rfc822':
 						// undelivered message
 						$fields_hash['Subject'] = $attachment['filename2'] ? $attachment['filename2'] : $attachment['filename'];
 						break;
 				}
 			}
 		}
 
-		if (!preg_match('/^(.*) #verify(.*)$/', $fields_hash['Subject'], $regs)) {
+		if ( !preg_match('/^(.*) #verify(.*)$/', $fields_hash['Subject'], $regs) ) {
 			// incorrect subject, no verification code
 			$form_info = $params['form_info'];
 
-			if ($form_info['ProcessUnmatchedEmails'] && ($fields_hash['FromEmail'] != $params['recipient_email'])) {
+			if ( $form_info['ProcessUnmatchedEmails'] && ($fields_hash['FromEmail'] != $params['recipient_email']) ) {
 				// it's requested to convert unmatched emails to new submissions
 				$form_id = $form_info['FormId'];
 				$this->Application->SetVar('form_id', $form_id);
 
-				$sql = 'SELECT ' . $this->Application->getUnitOption('formsubs', 'IDField') . '
-						FROM ' . $this->Application->getUnitOption('formsubs', 'TableName') . '
+				$form_submission_config = $this->Application->getUnitConfig('formsubs');
+
+				$sql = 'SELECT ' . $form_submission_config->getIDField() . '
+						FROM ' . $form_submission_config->getTableName() . '
 						WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']);
 				$found = $this->Conn->GetOne($sql);
 
-				if ($found) {
+				if ( $found ) {
 					// don't process same message twice
 					return false;
 				}
 
 				$sql = 'SELECT *
 						FROM ' . TABLE_PREFIX . 'FormFields
 						WHERE (FormId = ' . $form_info['FormId'] . ') AND (EmailCommunicationRole > 0)';
 				$form_fields = $this->Conn->Query($sql, 'EmailCommunicationRole');
 
 				// what roles are filled from what fields
 				$role_mapping = Array (
 					SubmissionFormField::COMMUNICATION_ROLE_EMAIL => 'FromEmail',
 					SubmissionFormField::COMMUNICATION_ROLE_NAME => 'FromName',
 					SubmissionFormField::COMMUNICATION_ROLE_SUBJECT => 'Subject',
 					SubmissionFormField::COMMUNICATION_ROLE_BODY => 'Message',
 				);
 
 				$submission_fields = Array ();
 
 				foreach ($role_mapping as $role => $email_field) {
-					if (array_key_exists($role, $form_fields)) {
-						$submission_fields[ 'fld_' . $form_fields[$role]['FormFieldId'] ] = $fields_hash[$email_field];
+					if ( array_key_exists($role, $form_fields) ) {
+						$submission_fields['fld_' . $form_fields[$role]['FormFieldId']] = $fields_hash[$email_field];
 					}
 				}
 
-				if ($submission_fields) {
+				if ( $submission_fields ) {
 					// remove object, because it's linked to single form upon creation forever
 					$this->Application->removeObject('formsubs.-item');
 
 					$form_submission = $this->Application->recallObject('formsubs.-item', null, Array ('skip_autoload' => true));
 					/* @var $form_submission kDBItem */
 
 					// in case that other non-role mapped fields are required
 					$form_submission->IgnoreValidation = true;
 					$form_submission->SetDBFieldsFromHash($submission_fields);
 					$form_submission->SetDBField('FormId', $form_id);
 					$form_submission->SetDBField('MessageId', $fields_hash['MessageId']);
 					$form_submission->SetDBField('SubmissionTime_date', adodb_mktime());
 					$form_submission->SetDBField('SubmissionTime_time', adodb_mktime());
 					$form_submission->SetDBField('ReferrerURL', $this->Application->Phrase('la_Text_Email'));
+
 					return $form_submission->Create();
 				}
 			}
 
 			return false;
 		}
 
-		$sql = 'SELECT ' . $this->Application->getUnitOption('submission-log', 'IDField') . '
-				FROM ' . $this->Application->getUnitOption('submission-log', 'TableName') . '
+		$submission_log_config = $this->Application->getUnitConfig('submission-log');
+
+		$sql = 'SELECT ' . $submission_log_config->getIDField() . '
+				FROM ' . $submission_log_config->getTableName() . '
 				WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']);
 		$found = $this->Conn->GetOne($sql);
 
-		if ($found) {
+		if ( $found ) {
 			// don't process same message twice
 			return false;
 		}
 
 		$reply_to = $this->Application->recallObject('submission-log.-reply-to', null, Array ('skip_autoload' => true));
 		/* @var $reply_to kDBItem */
 
 		$reply_to->Load($regs[2], 'VerifyCode');
-		if (!$reply_to->isLoaded()) {
+
+		if ( !$reply_to->isLoaded() ) {
 			// fake verification code OR feedback, containing submission log was deleted
 			return false;
 		}
 
-		if ($params['bounce_mode']) {
+		if ( $params['bounce_mode'] ) {
 			// mark original message as bounced
 			$reply_to->SetDBField('BounceInfo', $fields_hash['Message']);
 			$reply_to->SetDBField('BounceDate_date', TIMENOW);
 			$reply_to->SetDBField('BounceDate_time', TIMENOW);
 			$reply_to->SetDBField('SentStatus', SUBMISSION_LOG_BOUNCE);
 			$reply_to->Update();
 
 			return true;
 		}
 
 		$reply = $this->Application->recallObject('submission-log.-reply', null, Array ('skip_autoload' => true));
 		/* @var $reply kDBItem */
 
 		$reply->SetDBFieldsFromHash($fields_hash);
 		$reply->SetDBField('ReplyTo', $reply_to->GetID());
 		$reply->SetDBField('FormSubmissionId', $reply_to->GetDBField('FormSubmissionId'));
 		$reply->SetDBField('ToEmail', $params['recipient_email']);
 		$reply->SetDBField('Subject', $regs[1]); // save subject without verification code
 		$reply->SetDBField('SentStatus', SUBMISSION_LOG_SENT);
 
 		return $reply->Create();
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/forms/submission_log/submission_log_eh.php
===================================================================
--- branches/5.3.x/core/units/forms/submission_log/submission_log_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/forms/submission_log/submission_log_eh.php	(revision 15698)
@@ -1,710 +1,710 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class SubmissionLogEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnResendReply' => Array ('subitem' => 'add|edit'),
 				'OnSaveDraft' => Array ('subitem' => 'add|edit'),
 				'OnUseDraft' => Array ('subitem' => 'add|edit'),
 				'OnDeleteDraft' => Array ('subitem' => 'add|edit'),
 
 				'OnProcessBounceMail' => Array ('subitem' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			$section = $event->getSection();
 			$form_id = $this->Application->GetVar('form_id');
 
 			if ( $form_id ) {
 				// copy form_id to env to be passed info upload links
 				$this->Application->SetVar($event->getPrefixSpecial() . '_form_id', $form_id);
 			}
 			else {
 				$form_id = $this->Application->GetVar($event->getPrefixSpecial() . '_form_id');
 			}
 
 			$event->setEventParam('PermSection', $section . ':' . $form_id);
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Prepares new kDBItem object
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnNew(kEvent $event)
 		{
 			parent::OnNew($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$form_submission = $this->Application->recallObject('formsubs');
 			/* @var $form_submission kDBItem */
 
 			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
 			/* @var $form_submission_helper FormSubmissionHelper */
 
 			$form =& $form_submission_helper->getForm($form_submission);
 			/* @var $form kDBItem */
 
 			$from_email = $form->GetDBField('ReplyFromEmail');
 			$to_email = $form_submission_helper->getFieldByRole($form_submission, SubmissionFormField::COMMUNICATION_ROLE_EMAIL);
 
 			if ( $this->Application->GetVar('client_mode') ) {
 				// debug code for sending email from client
 				$object->SetDBField('FromEmail', $to_email);
 				$object->SetDBField('ToEmail', $from_email);
 
 			}
 			else {
 				$object->SetDBField('FromEmail', $from_email);
 				$object->SetDBField('ToEmail', $to_email);
 			}
 
 			$object->SetDBField('Cc', $form->GetDBField('ReplyCc'));
 			$object->SetDBField('Bcc', $form->GetDBField('ReplyBcc'));
 
 			$ids = $this->StoreSelectedIDs($event);
 			if ( $ids ) {
 				$org_message = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
 				/* @var $org_message kDBItem */
 
 				$org_message->Load(array_shift($ids));
 				// client could reply from different email, so compare to admin email!
 				if ( $org_message->GetDBField('ToEmail') == $from_email ) {
 					// can reply only to client email, not own :)
 
 					// transform subject
 					$message_subject = $org_message->GetDBField('Subject');
 
 					if ( $message_subject ) {
 						$object->SetDBField('Subject', $this->_transformSubject($message_subject, 'Re'));
 					}
 
 					// add signature
 					$message_body = $form->GetDBField('ReplyMessageSignature');
 
 					if ( $org_message->GetDBField('Message') ) {
 						// add replied marks
 						$message_body .= '> ' . preg_replace('/([\r]*\n)/', '\\1> ', $org_message->GetDBField('Message'));
 					}
 
 					$object->SetDBField('ToEmail', $org_message->GetDBField('FromEmail')); // user client's email from reply
 					$object->SetDBField('Message', $message_body);
 					$object->SetDBField('ReplyTo', $org_message->GetID());
 				}
 			}
 			else {
 				$sql = 'SELECT COUNT(*)
 						FROM ' . $object->TableName . '
 						WHERE FormSubmissionId = ' . $form_submission->GetID();
 				$replies_found = $this->Conn->GetOne($sql);
 
 				if ( !$replies_found ) {
 					// 1st message from admin -> quote subject & text from feedback
 					$message_subject = $form_submission_helper->getFieldByRole($form_submission, SubmissionFormField::COMMUNICATION_ROLE_SUBJECT, true);
 
 					if ( $message_subject ) {
 						$object->SetDBField('Subject', $this->_transformSubject($message_subject, 'Re'));
 					}
 
 					// add signature
 					$message_body = $form->GetDBField('ReplyMessageSignature');
 
 					// add replied marks
 					$original_message_body = $form_submission_helper->getFieldByRole($form_submission, SubmissionFormField::COMMUNICATION_ROLE_BODY);
 
 					if ( $original_message_body ) {
 						$message_body .= '> ' . preg_replace('/([\r]*\n)/', '\\1> ', $original_message_body);
 					}
 
 					$object->SetDBField('Message', $message_body);
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Parses $search string in subject and reformats it
 		 * Used for replying and forwarding
 		 *
 		 * @param string $subject
 		 * @param string $search
 		 * @return string
 		 */
 		function _transformSubject($subject, $search = 'Re')
 		{
 			$regex = '/'.$search.'(\[([\d]+)\]){0,1}:/i';
 			preg_match_all($regex, $subject, $regs);
 
 			if ($regs[2]) {
 				$reply_count = 0; // reply count without numbers (equals to "re[1]")
 				$max_reply_number = 0; // maximal reply number
 				sort($regs[2], SORT_NUMERIC); // sort ascending (non-numeric replies first)
 				foreach ($regs[2] as $match) {
 					if (!$match) {
 						// found "re:"
 						$reply_count++;
 					}
 					elseif ($match > $max_reply) {
 						// found "re:[number]"
 						$max_reply_number = $match;
 					}
 				}
 
 				return $search.'['.($reply_count + $max_reply_number + 1).']: '.trim(preg_replace($regex, '', $subject));
 			}
 
 			return $search.': '.$subject;
 		}
 
 		/**
 		 * Resends reply, that was not sent last time
 		 *
 		 * @param kEvent $event
 		 */
 		function OnResendReply($event)
 		{
 			$ids = $this->StoreSelectedIDs($event);
 
 			if (!$ids) {
 				return ;
 			}
 
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$sql = 'SELECT f.ReplyFromEmail, sl.' . $object->IDField . '
 					FROM ' . $object->TableName . ' sl
-					JOIN ' . $this->Application->getUnitOption('formsubs', 'TableName') . ' fs ON fs.FormSubmissionId = sl.FormSubmissionId
-					JOIN ' . $this->Application->getUnitOption('form', 'TableName') . ' f ON f.FormId = fs.FormId
+					JOIN ' . $this->Application->getUnitConfig('formsubs')->getTableName() . ' fs ON fs.FormSubmissionId = sl.FormSubmissionId
+					JOIN ' . $this->Application->getUnitConfig('form')->getTableName() . ' f ON f.FormId = fs.FormId
 					WHERE sl.' . $object->IDField . ' IN (' . implode(',', $ids) . ')';
 			$reply_emails = $this->Conn->GetCol($sql, $object->IDField);
 
 			foreach ($ids as $id) {
 				$object->Load($id);
 
 				// allow to send messages, that were successfully sended before :(
 				if (($object->GetDBField('ToEmail') != $reply_emails[$id]) && ($object->GetDBField('SentStatus') != SUBMISSION_LOG_SENT)) {
 					$object->SetOriginalField('SentStatus', 0); // reset sent status to update sent date automatically
 
 					$this->_sendEmail($object); // resend email here
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 
 			if (!$this->Application->GetVar('from_list')) {
 				$event->SetRedirectParam('opener', 'u');
 			}
 		}
 
 		/**
 		 * Updates last operation dates for log record
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$this->_validateRecipients($event);
 			$this->_updateStatusDates($event);
 		}
 
 		/**
 		 * Updates last operation dates for log record
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->_validateRecipients($event);
 			$this->_updateStatusDates($event);
 		}
 
 		/**
 		 * Validates email recipients
 		 *
 		 * @param kEvent $event
 		 */
 		function _validateRecipients($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$esender = $this->Application->recallObject('EmailSender');
 			/* @var $esender kEmailSendingHelper */
 
 			$cc = $object->GetDBField('Cc');
 
 			if ($cc && ($esender->GetRecipients($cc) === false)) {
 				$object->SetError('Cc', 'invalid_format');
 			}
 
 			$bcc = $object->GetDBField('Bcc');
 
 			if ($bcc && ($esender->GetRecipients($bcc) === false)) {
 				$object->SetError('Bcc', 'invalid_format');
 			}
 		}
 
 		/**
 		 * Generates verification code and sets it inside sent message
 		 *
 		 * @param kDBItem $object
 		 * @return string
 		 */
 		function _generateVerificationCode(&$object)
 		{
 			$code = Array (
 				$object->GetDBField('FromEmail'),
 				$object->GetDBField('ToEmail'),
 				$object->GetID(),
 				microtime(true)
 			);
 
 			$object->SetDBField('VerifyCode', md5( implode('-', $code) ));
 		}
 
 		/**
 		 * Sends email based on fields from given submission-log record
 		 *
 		 * @param kDBItem $object
 		 */
 		function _sendEmail(&$object)
 		{
 			if ($this->Application->GetVar('client_mode')) {
 				return ;
 			}
 
 			if (!$object->GetDBField('VerifyCode')) {
 				$this->_generateVerificationCode($object);
 			}
 
 			$form_submission =& $this->_getFormSubmission($object);
 
 			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
 			/* @var $form_submission_helper FormSubmissionHelper */
 
 			$form =& $form_submission_helper->getForm($form_submission);
 
 			$send_params = Array (
 				'from_name' => $form->GetDBField('ReplyFromName'),
 				'from_email' => $object->GetDBField('FromEmail'),
 
 				'to_email' => $object->GetDBField('ToEmail'),
 
 				'subject' => $object->GetDBField('Subject'),
 				'message' => $object->GetDBField('Message'),
 			);
 
 			$to_name = $form_submission_helper->getFieldByRole($form_submission, SubmissionFormField::COMMUNICATION_ROLE_NAME);
 
 			if ($to_name) {
 				$send_params['to_name'] = $to_name;
 			}
 
 			$esender = $this->Application->recallObject('EmailSender');
 			/* @var $esender kEmailSendingHelper */
 
 			$esender->SetReturnPath( $form->GetDBField('BounceEmail') );
 
 			if ($object->GetDBField('Cc')) {
 				$recipients = $esender->GetRecipients( $object->GetDBField('Cc') );
 
 				foreach ($recipients as $recipient_info) {
 					$esender->AddCc($recipient_info['Email'], $recipient_info['Name']);
 				}
 			}
 
 			if ($object->GetDBField('Bcc')) {
 				$recipients = $esender->GetRecipients( $object->GetDBField('Bcc') );
 
 				foreach ($recipients as $recipient_info) {
 					$esender->AddBcc($recipient_info['Email'], $recipient_info['Name']);
 				}
 			}
 
 			if ($object->GetDBField('Attachment')) {
 				$attachments = explode('|', $object->GetField('Attachment', 'file_paths'));
 
 				foreach ($attachments as $attachment) {
 					$esender->AddAttachment($attachment);
 				}
 			}
 
 			$this->Application->emailAdmin('FORM.SUBMISSION.REPLY.TO.USER', null, $send_params);
 
 			// mark as sent after sending is finished
 			$object->SetDBField('SentStatus', SUBMISSION_LOG_SENT);
 
 			// reset bounce status before (re-)sending
 			$object->SetDBField('BounceInfo', NULL);
 			$object->SetDBField('BounceDate_date', NULL);
 			$object->SetDBField('BounceDate_time', NULL);
 
 			if ($object->GetDBField('DraftId')) {
 				$temp_handler = $this->Application->recallObject('draft_TempHandler', 'kTempTablesHandler');
 				/* @var $temp_handler kTempTablesHandler */
 
 				$temp_handler->DeleteItems('draft', '', Array ($object->GetDBField('DraftId')));
 				$object->SetDBField('DraftId', 0);
 			}
 
 			$object->Update();
 		}
 
 		/**
 		 * Sends new email after log record was created
 		 * Updates last update time for submission
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(kEvent $event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$this->_sendEmail($object); // send email
 
 			$this->_updateSubmission($event);
 
 			$reply_to = $object->GetDBField('ReplyTo');
 			if ( !$reply_to ) {
 				$reply_to = $this->_getLastMessageId($event, !$this->Application->GetVar('client_mode'));
 			}
 
 			if ( $reply_to ) {
 				// this is reply to other message -> mark it as replied
 				$org_message = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
 				/* @var $org_message kDBItem */
 
 				$org_message->Load($reply_to);
 				$org_message->SetDBField('ReplyStatus', SUBMISSION_LOG_REPLIED);
 				$org_message->Update();
 			}
 
 			if ( $this->Application->GetVar('client_mode') ) {
 				// new reply from client received -> send notification about it
 				$this->Application->emailAdmin('FORM.SUBMISSION.REPLY.FROM.USER');
 			}
 		}
 
 		/**
 		 * Returns last message id (client OR admin)
 		 *
 		 * @param kEvent $event
 		 * @param bool $from_client
 		 * @return int
 		 */
 		function _getLastMessageId($event, $from_client = false)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$form_submission =& $this->_getFormSubmission($object);
 
 			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
 			/* @var $form_submission_helper FormSubmissionHelper */
 
 			$form =& $form_submission_helper->getForm($form_submission);
 			$reply_email = $form->GetDBField('ReplyFromEmail');
 
 			$sql = 'SELECT MAX(' . $object->IDField . ')
 					FROM ' . $object->TableName . '
 					WHERE (FormSubmissionId = ' . $form_submission->GetID() . ') AND (ToEmail' . ($from_client ? ' = ' : ' <> ') . $this->Conn->qstr($reply_email) . ')';
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Updates last update time for submission
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemUpdate(kEvent $event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$this->_updateSubmission($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// send out email event to admin for bouncing
 			$sent_status = $object->GetDBField('SentStatus');
 
 			if ( $object->GetOriginalField('SentStatus') != $sent_status && $sent_status == SUBMISSION_LOG_BOUNCE ) {
 				$this->Application->emailAdmin('FORM.SUBMISSION.REPLY.FROM.USER.BOUNCED');
 			}
 		}
 
 		/**
 		 * Sets last sent/reply dates based on field changes in log record
 		 *
 		 * @param kEvent $event
 		 */
 		function _updateStatusDates($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$now = adodb_mktime();
 
 			$sent_status = $object->GetDBField('SentStatus');
 			if (($event->Special != 'merge') && ($sent_status == SUBMISSION_LOG_SENT) && ($sent_status != $object->GetOriginalField('SentStatus'))) {
 				// sent status was set
 				$object->SetDBField('SentOn_date', $now);
 				$object->SetDBField('SentOn_time', $now);
 			}
 
 			$reply_status = $object->GetDBField('ReplyStatus');
 			if (($reply_status == SUBMISSION_LOG_REPLIED) && ($reply_status != $object->GetOriginalField('ReplyStatus'))) {
 				// sent status was set
 				$object->SetDBField('RepliedOn_date', $now);
 				$object->SetDBField('RepliedOn_time', $now);
 			}
 		}
 
 		/**
 		 * Returns form submission by given event of submission log
 		 *
 		 * @param kDBItem $object
 		 * @return kDBItem
 		 */
 		function &_getFormSubmission(&$object)
 		{
 			$submission_id = $object->GetDBField('FormSubmissionId');
 
 			$form_submission = $this->Application->recallObject('formsubs.-item', null, Array ('skip_autoload' => true));
 			/* @var $form_submission kDBItem */
 
 			if ($form_submission->isLoaded() && ($form_submission->GetID() == $submission_id)) {
 				// already loaded AND has needed id
 				return $form_submission;
 			}
 
 			$form_submission->Load($submission_id);
 
 			return $form_submission;
 		}
 
 		/**
 		 * Sets last updated field for form submission
 		 *
 		 * @param kEvent $event
 		 */
 		function _updateSubmission($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$form_submission =& $this->_getFormSubmission($object);
 
 			// 1. set last updated
 			$last_updated = max ($object->GetDBField('SentOn'), $object->GetDBField('RepliedOn'));
 
 			if ($form_submission->GetDBField('LastUpdatedOn') < $last_updated) {
 				// don't set smaller last update, that currenly set
 				$form_submission->SetDBField('LastUpdatedOn_date', $last_updated);
 				$form_submission->SetDBField('LastUpdatedOn_time', $last_updated);
 			}
 
 			// 2. update submission status
 			$form_submission_helper = $this->Application->recallObject('FormSubmissionHelper');
 			/* @var $form_submission_helper FormSubmissionHelper */
 
 			$form =& $form_submission_helper->getForm($form_submission);
 			$client_responce = $form->GetDBField('ReplyFromEmail') == $object->GetDBField('ToEmail');
 			$replied = $object->GetDBField('ReplyStatus') == SUBMISSION_LOG_REPLIED;
 
 			if (!$client_responce && !$replied) {
 				 // admin sends new email to client
 				 $form_submission->SetDBField('LogStatus', SUBMISSION_REPLIED);
 			}
 			elseif ($client_responce) {
 				// client email becomes replied OR receiving new unreplied email from client
 				$form_submission->SetDBField('LogStatus', $replied ?  SUBMISSION_REPLIED : SUBMISSION_NEW_EMAIL);
 			}
 
 			if ($object->GetDBField('SentStatus') == SUBMISSION_LOG_BOUNCE) {
 				// propagate bounce status from reply
 				$form_submission->SetDBField('LogStatus', SUBMISSION_BOUNCE);
 			}
 
 			$form_submission->Update();
 		}
 
 		/**
 		 * Saves current unsent message as draft
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveDraft($event)
 		{
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$draft = $this->Application->recallObject('draft', null, Array('skip_autoload' => true));
 			/* @var $draft kDBItem */
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info) {
 				foreach ($items_info as $id => $field_values) {
 					$object->setID($id);
 	 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 	 				$load_keys = Array (
 	 					'FormSubmissionId' => $object->GetDBField('FormSubmissionId'),
 	 					'CreatedById' => $this->Application->RecallVar('user_id'),
 	 				);
 
 	 				// get existing draft for given submission and user
 	 				$draft->Load($load_keys);
 
 	 				$draft->SetDBField('Message', $object->GetDBField('Message'));
 
 	 				if ($draft->isLoaded()) {
 	 					$draft->Update();
 	 				}
 	 				else {
 	 					$draft->SetDBFieldsFromHash($load_keys);
 	 					$draft->Create();
 	 				}
 				}
 			}
 
 			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * Uses found draft instead of submission reply body
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUseDraft($event)
 		{
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$draft = $this->Application->recallObject('draft', null, Array('skip_autoload' => true));
 			/* @var $draft kDBItem */
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info) {
 				foreach ($items_info as $id => $field_values) {
 					$object->setID($id);
 	 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 	 				$load_keys = Array (
 	 					'FormSubmissionId' => $object->GetDBField('FormSubmissionId'),
 	 					'CreatedById' => $this->Application->RecallVar('user_id'),
 	 				);
 
 	 				// get existing draft for given submission and user
 	 				$draft->Load($load_keys);
 					if ($draft->isLoaded()) {
 						$object->SetDBField('Message', $draft->GetDBField('Message'));
 						$object->SetDBField('DraftId', $draft->GetID());
 					}
 				}
 			}
 
 			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
 			$event->redirect = false;
 		}
 
 		/**
 		 * Deletes draft, that matches given user and form submission
 		 *
 		 * @param kEvent $event
 		 */
 		function OnDeleteDraft($event)
 		{
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$draft = $this->Application->recallObject('draft', null, Array('skip_autoload' => true));
 			/* @var $draft kDBItem */
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info) {
 				foreach ($items_info as $id => $field_values) {
 					$object->setID($id);
 	 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 					$object->SetDBField('DraftId', 0);
 
 	 				$load_keys = Array (
 	 					'FormSubmissionId' => $object->GetDBField('FormSubmissionId'),
 	 					'CreatedById' => $this->Application->RecallVar('user_id'),
 	 				);
 
 	 				// get existing draft for given submission and user
 	 				$draft->Load($load_keys);
 					if ($draft->isLoaded()) {
 						$temp_handler = $this->Application->recallObject('draft_TempHandler', 'kTempTablesHandler');
 						/* @var $temp_handler kTempTablesHandler */
 
 						$temp_handler->DeleteItems('draft', '', Array ($draft->GetID()));
 					}
 				}
 			}
 
 			$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
 			$event->redirect = false;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/admin/admin_events_handler.php
===================================================================
--- branches/5.3.x/core/units/admin/admin_events_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/admin/admin_events_handler.php	(revision 15698)
@@ -1,1386 +1,1384 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class AdminEventsHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnSaveColumns' => Array ('self' => true),
 			'OnClosePopup' => Array ('self' => true),
 			'OnSaveSetting' => Array ('self' => true),
 			'OnDropTempTablesByWID' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Checks user permission to execute given $event
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access public
 	 */
 	public function CheckPermission(kEvent $event)
 	{
 		$perm_value = null;
 
 		$system_events = Array (
 			'OnResetModRwCache', 'OnResetSections', 'OnResetConfigsCache', 'OnResetParsedData', 'OnResetMemcache',
 			'OnDeleteCompiledTemplates', 'OnCompileTemplates', 'OnGenerateTableStructure', 'OnSynchronizeDBRevisions',
 			'OnDeploy', 'OnRebuildThemes', 'OnCheckPrefixConfig', 'OnMemoryCacheGet', 'OnMemoryCacheSet'
 		);
 
 		if ( in_array($event->Name, $system_events) ) {
 			// events from "Tools -> System Tools" section are controlled via that section "edit" permission
 			$perm_value = /*$this->Application->isDebugMode() ||*/ $this->Application->CheckPermission($event->getSection() . '.edit');
 		}
 
 		$tools_events = Array (
 			'OnBackup' => 'in-portal:backup.view',
 			'OnBackupProgress' => 'in-portal:backup.view',
 			'OnDeleteBackup' => 'in-portal:backup.view',
 			'OnBackupCancel' => 'in-portal:backup.view',
 
 			'OnRestore' => 'in-portal:restore.view',
 			'OnRestoreProgress' => 'in-portal:restore.view',
 			'OnRestoreCancel' => 'in-portal:backup.view',
 
 			'OnSqlQuery' => 'in-portal:sql_query.view',
 		);
 
 		if ( array_key_exists($event->Name, $tools_events) ) {
 			$perm_value = $this->Application->CheckPermission($tools_events[$event->Name]);
 		}
 
 		if ( $event->Name == 'OnSaveMenuFrameWidth' ) {
 			$perm_value = $this->Application->isAdminUser;
 		}
 
 		$perm_helper = $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		$csv_events = Array ('OnCSVImportBegin', 'OnCSVImportStep', 'OnExportCSV', 'OnGetCSV');
 
 		if ( in_array($event->Name, $csv_events) ) {
 			$csv_helper = $this->Application->recallObject('CSVHelper');
 			/* @var $csv_helper kCSVHelper */
 
 			$prefix = $csv_helper->getPrefix(stripos($event->Name, 'import') !== false);
 
 			$perm_mapping = Array (
 				'OnCSVImportBegin' => 'OnProcessSelected',
 				'OnCSVImportStep' => 'OnProcessSelected',
 				'OnExportCSV' => 'OnLoad',
 				'OnGetCSV' => 'OnLoad',
 			);
 
 			$tmp_event = new kEvent($prefix . ':' . $perm_mapping[$event->Name] );
 			$perm_value = $perm_helper->CheckEventPermission($tmp_event, $this->permMapping);
 		}
 
 		if ( isset($perm_value) ) {
 			return $perm_helper->finalizePermissionCheck($event, $perm_value);
 		}
 
 		return parent::CheckPermission($event);
 	}
 
 	/**
 	 * Reset mod-rewrite url cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetModRwCache(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls');
 
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Resets tree section cache and refreshes admin section tree
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetSections(kEvent $event)
 	{
 		if ($this->Application->GetVar('ajax') == 'yes') {
 			$event->status = kEvent::erSTOP;
 		}
 
 		if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
 			$this->Application->rebuildCache('master:sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime);
 		}
 		else {
 			$this->Application->rebuildDBCache('sections_parsed', kCache::REBUILD_LATER, CacheSettings::$sectionsParsedRebuildTime);
 		}
 
 		$event->SetRedirectParam('refresh_tree', 1);
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Resets unit config cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetConfigsCache(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 			$this->Application->rebuildCache('master:config_files', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime);
 		}
 		else {
 			$this->Application->rebuildDBCache('config_files', kCache::REBUILD_LATER, CacheSettings::$unitCacheRebuildTime);
 		}
 
 		$this->OnResetParsedData($event);
 
 		$skin_helper = $this->Application->recallObject('SkinHelper');
 		/* @var $skin_helper SkinHelper */
 
 		$skin_helper->deleteCompiled();
 	}
 
 	/**
 	 * Resets parsed data from unit configs
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetParsedData(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$this->Application->DeleteUnitCache();
 
 		if ( $this->Application->GetVar('validate_configs') ) {
 			$event->SetRedirectParam('validate_configs', 1);
 		}
 
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Resets memory cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnResetMemcache(kEvent $event)
 	{
 		if ($this->Application->GetVar('ajax') == 'yes') {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$this->Application->resetCache();
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Compiles all templates (with a progress bar)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCompileTemplates(kEvent $event)
 	{
 		$compiler = $this->Application->recallObject('NParserCompiler');
 		/* @var $compiler NParserCompiler */
 
 		$compiler->CompileTemplatesStep();
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Deletes all compiled templates
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDeleteCompiledTemplates(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$base_path = WRITEABLE . DIRECTORY_SEPARATOR . 'cache';
 
 		// delete debugger reports
 		$debugger_reports = glob(RESTRICTED . '/debug_@*@.txt');
 
 		if ( $debugger_reports ) {
 			foreach ($debugger_reports as $debugger_report) {
 				unlink($debugger_report);
 			}
 		}
 
 		$this->_deleteCompiledTemplates($base_path);
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Deletes compiled templates in a given folder
 	 *
 	 * @param string $folder
 	 * @param bool $unlink_folder
 	 * @return void
 	 * @access protected
 	 */
 	protected function _deleteCompiledTemplates($folder, $unlink_folder = false)
 	{
 		$sub_folders = glob($folder . '/*', GLOB_ONLYDIR);
 
 		if ( is_array($sub_folders) ) {
 			foreach ($sub_folders as $sub_folder) {
 				$this->_deleteCompiledTemplates($sub_folder, true);
 			}
 		}
 
 		$files = glob($folder . '/*.php');
 
 		if ( is_array($files) ) {
 			foreach ($files as $file) {
 				unlink($file);
 			}
 		}
 
 		if ( $unlink_folder ) {
 			rmdir($folder);
 		}
 	}
 
 	/**
 	 * Generates structure for specified table
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnGenerateTableStructure(kEvent $event)
 	{
 		$types_hash = Array (
 			'string'	=>	'varchar|text|mediumtext|longtext|date|datetime|time|timestamp|char|year|enum|set',
 			'int'		=>	'smallint|mediumint|int|bigint|tinyint',
 			'float'		=>	'float|double|decimal',
 		);
 
 		$table_name = $this->Application->GetVar('table_name');
 		if ( !$table_name ) {
 			echo 'error: no table name specified';
 			return;
 		}
 
 		if ( TABLE_PREFIX && !preg_match('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', $table_name) && (strtolower($table_name) != $table_name) ) {
 			// table name without prefix, then add it (don't affect K3 tables named in lowercase)
 			$table_name = TABLE_PREFIX . $table_name;
 		}
 
 		if ( !$this->Conn->TableFound($table_name) ) {
 			// table with prefix doesn't exist, assume that just config prefix passed -> resolve table name from it
 			$prefix = preg_replace('/^' . preg_quote(TABLE_PREFIX, '/') . '/', '', $table_name);
 			if ( $this->Application->prefixRegistred($prefix) ) {
 				// when prefix is found -> use it's table (don't affect K3 tables named in lowecase)
-				$table_name = $this->Application->getUnitOption($prefix, 'TableName');
+				$table_name = $this->Application->getUnitConfig($prefix)->getTableName();
 			}
 		}
 
 		$table_info = $this->Conn->Query('DESCRIBE '.$table_name);
 
 		// 1. prepare config keys
 		$grids = Array (
 			'Default' => Array (
 				'Icons' => Array ('default' => 'icon16_item.png'),
 				'Fields' => Array (),
 			)
 		);
 
 		$grid_fields = Array();
 
 		$id_field = '';
 		$fields = Array ();
 		$float_types = Array ('float', 'double', 'numeric');
 		foreach ($table_info as $field_info) {
 			if ( preg_match('/l[\d]+_.*/', $field_info['Field']) ) {
 				// don't put multilingual fields in config
 				continue;
 			}
 
 			$field_options = Array ();
 
 			if ( $field_info['Key'] == 'PRI' ) {
 				$grid_col_options = Array ('title' => 'column:la_fld_Id', 'filter_block' => 'grid_range_filter', 'width' => 80);
 			}
 			else {
 				$grid_col_options = Array ('filter_block' => 'grid_like_filter');
 			}
 
 			// 1. get php field type by mysql field type
 			foreach ($types_hash as $php_type => $db_types) {
 				if ( preg_match('/' . $db_types . '/', $field_info['Type']) ) {
 					$field_options['type'] = $php_type;
 					break;
 				}
 			}
 
 			// 2. get field default value
 			$default_value = $field_info['Default'];
 			$not_null = $field_info['Null'] != 'YES';
 
 			if ( is_numeric($default_value) ) {
 				$default_value = preg_match('/[\.,]/', $default_value) ? (float)$default_value : (int)$default_value;
 			}
 
 			if ( is_null($default_value) && $not_null ) {
 				$default_value = $field_options['type'] == 'string' ? '' : 0;
 			}
 
 			if ( in_array($php_type, $float_types) ) {
 				// this is float number
 				if ( preg_match('/' . $db_types . '\([\d]+,([\d]+)\)/i', $field_info['Type'], $regs) ) {
 					// size is described in structure -> add formatter
 					$field_options['formatter'] = 'kFormatter';
 					$field_options['format'] = '%01.' . $regs[1] . 'f';
 
 					if ( $not_null ) {
 						// null fields, will most likely have NULL as default value
 						$default_value = 0;
 					}
 				}
 				elseif ( $not_null ) {
 					// no size information, just convert to float
 					// null fields, will most likely have NULL as default value
 					$default_value = (float)$default_value;
 				}
 			}
 
 			if ( preg_match('/varchar\(([\d]+)\)/i', $field_info['Type'], $regs) ) {
 				$field_options['max_len'] = (int)$regs[1];
 			}
 
 			if ( preg_match('/tinyint\([\d]+\)/i', $field_info['Type']) ) {
 				$field_options['formatter'] = 'kOptionsFormatter';
 				$field_options['options'] = Array (1 => 'la_Yes', 0 => 'la_No');
 				$field_options['use_phrases'] = 1;
 				$grid_col_options['filter_block'] = 'grid_options_filter';
 			}
 
 			if ( $not_null ) {
 				$field_options['not_null'] = 1;
 			}
 
 			if ( $field_info['Key'] == 'PRI' ) {
 				$default_value = 0;
 				$id_field = $field_info['Field'];
 			}
 
 			if ( $php_type == 'int' && !$not_null ) {
 				// numeric null field
 				if ( preg_match('/(On|Date)$/', $field_info['Field']) || $field_info['Field'] == 'Modified' ) {
 					$field_options['formatter'] = 'kDateFormatter';
 					$grid_col_options['filter_block'] = 'grid_date_range_filter';
 					$grid_col_options['width'] = 120;
 				}
 				else {
 					$grid_col_options['filter_block'] = 'grid_range_filter';
 					$grid_col_options['width'] = 80;
 				}
 			}
 
 			if ( $php_type == 'int' && ($not_null || is_numeric($default_value)) ) {
 				// is integer field AND not null
 				$field_options['default'] = (int)$default_value;
 			}
 			else {
 				$field_options['default'] = $default_value;
 			}
 
 			$fields[$field_info['Field']] = $field_options;
 			$grids_fields[$field_info['Field']] = $grid_col_options;
 		}
 
 		$grids['Default']['Fields'] = $grids_fields;
 
 		$ret = Array (
 			'IDField' => $id_field,
 			'Fields' => $fields,
 			'Grids' => $grids,
 		);
 
 		$decorator = new UnitConfigDecorator();
 		$ret = $decorator->decorate($ret);
 
 		$this->Application->InitParser();
 		ob_start();
 		echo $this->Application->ParseBlock(Array('name' => 'incs/header', 'body_properties' => 'style="background-color: #E7E7E7; margin: 8px;"'));
 	?>
 		<script type="text/javascript">
 			set_window_title('Table "<?php echo $table_name; ?>" Structure');
 		</script>
 
 		<a href="javascript:window_close();">Close Window</a><br /><br />
 		<?php echo $GLOBALS['debugger']->highlightString($ret); ?>
 		<br /><br /><a href="javascript:window_close();">Close Window</a><br />
 	<?php
 		echo $this->Application->ParseBlock(Array('name' => 'incs/footer'));
 		echo ob_get_clean();
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Refreshes ThemeFiles & Themes tables by actual content on HDD
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRebuildThemes(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') == 'yes' ) {
 			$event->status = kEvent::erSTOP;
 		}
 
 		$themes_helper = $this->Application->recallObject('ThemesHelper');
 		/* @var $themes_helper kThemesHelper */
 
 		$themes_helper->refreshThemes();
 
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Saves grid column widths after their resize by user
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSaveColumns(kEvent $event)
 	{
 		$picker_helper = $this->Application->recallObject('ColumnPickerHelper');
 		/* @var $picker_helper kColumnPickerHelper */
 
 		$picker_helper->SetGridName($this->Application->GetLinkedVar('grid_name'));
 
 		$picked = trim($this->Application->GetVar('picked_str'), '|');
 		$hidden = trim($this->Application->GetVar('hidden_str'), '|');
 
 		$main_prefix = $this->Application->GetVar('main_prefix');
 
 		$picker_helper->SaveColumns($main_prefix, $picked, $hidden);
 		$this->finalizePopup($event);
 	}
 
 	/**
 	 * Saves various admin settings via ajax
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSaveSetting(kEvent $event)
 	{
 		if ( $this->Application->GetVar('ajax') != 'yes' ) {
 			return;
 		}
 
 		$var_name = $this->Application->GetVar('var_name');
 		$var_value = $this->Application->GetVar('var_value');
 
 		$this->Application->StorePersistentVar($var_name, $var_value);
 
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Just closes popup & deletes last_template & opener_stack if popup, that is closing
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnClosePopup(kEvent $event)
 	{
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Occurs right after initialization of the kernel, used mainly as hook-to event
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnStartup(kEvent $event)
 	{
 		if ( $this->Application->isAdmin ) {
 			return;
 		}
 
 		$base_url = preg_quote($this->Application->BaseURL(), '/');
 		$referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
 
 		if ( $referrer && !preg_match('/^' . $base_url . '/', $referrer) ) {
 			$this->Application->Session->SetCookie('original_referrer', $referrer);
 			$this->Application->SetVar('original_referrer', $referrer);
 		}
 	}
 
 	/**
 	 * Occurs right before echoing the output, in Done method of application, used mainly as hook-to event
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeShutdown(kEvent $event)
 	{
 
 	}
 
 	/**
 	 * Is called after tree was build (when not from cache)
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterBuildTree(kEvent $event)
 	{
 
 	}
 
 	/**
 	 * Called by AJAX to perform CSV export
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnExportCSV(kEvent $event)
 	{
 		$csv_helper = $this->Application->recallObject('CSVHelper');
 		/* @var $csv_helper kCSVHelper */
 
 		$csv_helper->PrefixSpecial = $csv_helper->getPrefix(false);
 		$csv_helper->grid = $this->Application->GetVar('grid');
 		$csv_helper->ExportStep();
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Returning created by AJAX CSV file
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnGetCSV(kEvent $event)
 	{
 		$csv_helper = $this->Application->recallObject('CSVHelper');
 		/* @var $csv_helper kCSVHelper */
 
 		$csv_helper->GetCSV();
 	}
 
 	/**
 	 * Start CSV import
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCSVImportBegin(kEvent $event)
 	{
 		$object = $event->getObject(Array ('skip_autoload' => true));
 		/* @var $object kDBItem */
 
 		$field_values = $this->getSubmittedFields($event);
 		$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 		$event->redirect = false;
 		$result = 'required';
 
 		if ( $object->GetDBField('ImportFile') ) {
 			$csv_helper = $this->Application->recallObject('CSVHelper');
 			/* @var $csv_helper kCSVHelper */
 
 			$csv_helper->PrefixSpecial = $csv_helper->getPrefix(true);
 			$csv_helper->grid = $this->Application->GetVar('grid');
 			$result = $csv_helper->ImportStart($object->GetField('ImportFile', 'file_paths'));
 
 			if ( $result === true ) {
 				$event->redirect = $this->Application->GetVar('next_template');
 				$event->SetRedirectParam('PrefixSpecial', $this->Application->GetVar('PrefixSpecial'));
 				$event->SetRedirectParam('grid', $this->Application->GetVar('grid'));
 			}
 		}
 
 		if ( $event->redirect === false ) {
 			$object->SetError('ImportFile', $result);
 			$event->status = kEvent::erFAIL;
 		}
 	}
 
 	/**
 	 * Performs one CSV import step
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCSVImportStep(kEvent $event)
 	{
 		$import_helper = $this->Application->recallObject('CSVHelper');
 		/* @var $import_helper kCSVHelper */
 
 		$import_helper->ImportStep();
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Shows unit config filename, where requested prefix is defined
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnCheckPrefixConfig(kEvent $event)
 	{
 		$prefix = $this->Application->GetVar('config_prefix');
 		$config_file = $this->Application->UnitConfigReader->prefixFiles[$prefix];
 
 		$this->Application->InitParser();
 
 		ob_start();
 		echo $this->Application->ParseBlock(Array('name' => 'incs/header', 'body_properties' => 'style="background-color: #E7E7E7; margin: 8px;"'));
 		?>
 		<script type="text/javascript">
 			set_window_title('Unit Config of "<?php echo $prefix; ?>" prefix');
 		</script>
 
 		<a href="javascript:window_close();">Close Window</a><br /><br />
 		<strong>Prefix:</strong> <?php echo $prefix; ?><br />
 		<strong>Unit Config:</strong> <?php echo $GLOBALS['debugger']->highlightString($config_file); ?><br />
 		<br /><a href="javascript:window_close();">Close Window</a><br />
 
 		<?php
 		echo $this->Application->ParseBlock(Array ('name' => 'incs/footer'));
 		echo ob_get_clean();
 
 		$event->status = kEvent::erSTOP;
 	}
 
 	/**
 	 * Deletes temp tables, when user closes window using "x" button in top right corner
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDropTempTablesByWID(kEvent $event)
 	{
 		$sid = $this->Application->GetSID();
 		$wid = $this->Application->GetVar('m_wid');
 		$tables = $this->Conn->GetCol('SHOW TABLES');
 		$mask_edit_table = '/' . TABLE_PREFIX . 'ses_' . $sid . '_' . $wid . '_edit_(.*)$/';
 
 		foreach ($tables as $table) {
 			if ( preg_match($mask_edit_table, $table, $rets) ) {
 				$this->Conn->Query('DROP TABLE IF EXISTS ' . $table);
 			}
 		}
 
 		echo 'OK';
 		$event->status = kEvent::erSTOP;
 	}
 
 
 	/**
 	 * Backup all data
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBackup(kEvent $event)
 	{
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 		/* @var $backup_helper BackupHelper */
 
 		if ( !$backup_helper->initBackup() ) {
 			$event->status = kEvent::erFAIL;
 		}
 
 		$event->redirect = 'tools/backup2';
 	}
 
 	/**
 	 * Perform next backup step
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBackupProgress(kEvent $event)
 	{
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 		/* @var $backup_helper BackupHelper */
 
 		$done_percent = $backup_helper->performBackup();
 
 		if ( $done_percent == 100 ) {
 			$event->redirect = 'tools/backup3';
 			return;
 		}
 
 		$event->status = kEvent::erSTOP;
 		echo $done_percent;
 	}
 
 	/**
 	 * Stops Backup & redirect to Backup template
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBackupCancel(kEvent $event)
 	{
 		$event->redirect = 'tools/backup1';
 	}
 
 	/**
 	 * Starts restore process
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRestore(kEvent $event)
 	{
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 		/* @var $backup_helper BackupHelper */
 
 		$backup_helper->initRestore();
 		$event->redirect = 'tools/restore3';
 	}
 
 	/**
 	 * Performs next restore step
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRestoreProgress(kEvent $event)
 	{
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 		/* @var $backup_helper BackupHelper */
 
 		$done_percent = $backup_helper->performRestore();
 
 		if ( $done_percent == BackupHelper::SQL_ERROR_DURING_RESTORE ) {
 			$event->redirect = 'tools/restore4';
 		}
 		elseif ( $done_percent == BackupHelper::FAILED_READING_BACKUP_FILE ) {
 			$this->Application->StoreVar('adm.restore_error', 'File read error');
 			$event->redirect = 'tools/restore4';
 		}
 		elseif ( $done_percent == 100 ) {
 			$backup_helper->replaceRestoredFiles();
 			$this->Application->StoreVar('adm.restore_success', 1);
 			$event->redirect = 'tools/restore4';
 		}
 		else {
 			$event->status = kEvent::erSTOP;
 			echo $done_percent;
 		}
 	}
 
 	/**
 	 * Stops Restore & redirect to Restore template
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnRestoreCancel(kEvent $event)
 	{
 		$event->redirect = 'tools/restore1';
 	}
 
 	/**
 	 * Deletes one backup file
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDeleteBackup(kEvent $event)
 	{
 		$backup_helper = $this->Application->recallObject('BackupHelper');
 		/* @var $backup_helper BackupHelper */
 
 		$backup_helper->delete();
 	}
 
 	/**
 	 * Starts restore process
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSqlQuery(kEvent $event)
 	{
 		$sql = $this->Application->GetVar('sql');
 
 		if ( $sql ) {
 			$start = microtime(true);
 			$result = $this->Conn->Query($sql);
 			$this->Application->SetVar('sql_time', round(microtime(true) - $start, 7));
 
 			if ( $result && is_array($result) ) {
 				$this->Application->SetVar('sql_has_rows', 1);
 				$this->Application->SetVar('sql_rows', serialize($result));
 			}
 
 			$check_sql = trim(strtolower($sql));
 
 			if ( preg_match('/^(insert|update|replace|delete)/', $check_sql) ) {
 				$this->Application->SetVar('sql_has_affected', 1);
 				$this->Application->SetVar('sql_affected', $this->Conn->getAffectedRows());
 			}
 		}
 
 		$this->Application->SetVar('query_status', 1);
 		$event->status = kEvent::erFAIL;
 	}
 
 	/**
 	 * Occurs after unit config cache was successfully rebuilt
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterCacheRebuild(kEvent $event)
 	{
 
 	}
 
 	/**
 	 * Removes "Community -> Groups" section when it is not allowed
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterConfigRead(kEvent $event)
 	{
 		parent::OnAfterConfigRead($event);
 
-		$section_adjustments = $this->Application->getUnitOption($event->Prefix, 'SectionAdjustments', Array());
+		$config = $event->getUnitConfig();
 
 		if ( !$this->Application->ConfigValue('AdvancedUserManagement') ) {
-			$section_adjustments['in-portal:user_groups'] = 'remove';
+			$config->addSectionAdjustments('remove', 'in-portal:user_groups');
 		}
 
-		$section_adjustments['in-portal:root'] = Array (
+		$config->addSectionAdjustments(Array (
 			'label' => $this->Application->ConfigValue('Site_Name')
-		);
-
-		$this->Application->setUnitOption($event->Prefix, 'SectionAdjustments', $section_adjustments);
+		), 'in-portal:root');
 	}
 
 	/**
 	 * Saves menu (tree) frame width
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSaveMenuFrameWidth(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		if ( !$this->Application->ConfigValue('ResizableFrames') ) {
 			return;
 		}
 
 		$this->Application->SetConfigValue('MenuFrameWidth', (int)$this->Application->GetVar('width'));
 	}
 
 	/**
 	 * Retrieves data from memory cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnMemoryCacheGet(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		$ret = Array ('message' => '', 'code' => 0); // 0 - ok, > 0 - error
 		$key = $this->Application->GetVar('key');
 
 		if ( !$key ) {
 			$ret['code'] = 1;
 			$ret['message'] = 'Key name missing';
 		}
 		else {
 			$value = $this->Application->getCache($key);
 
 			$ret['value'] =& $value;
 			$ret['size'] = is_string($value) ? kUtil::formatSize(strlen($value)) : '?';
 			$ret['type'] = gettype($value);
 
 			if ( kUtil::IsSerialized($value) ) {
 				$value = unserialize($value);
 			}
 
 			if ( is_array($value) ) {
 				$ret['value'] = print_r($value, true);
 			}
 
 			if ( $ret['value'] === false ) {
 				$ret['code'] = 2;
 				$ret['message'] = 'Key "' . $key . '" doesn\'t exist';
 			}
 		}
 
 		$json_helper = $this->Application->recallObject('JSONHelper');
 		/* @var $json_helper JSONHelper */
 
 		echo $json_helper->encode($ret);
 	}
 
 	/**
 	 * Retrieves data from memory cache
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnMemoryCacheSet(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		$ret = Array ('message' => '', 'code' => 0); // 0 - ok, > 0 - error
 		$key = $this->Application->GetVar('key');
 
 		if ( !$key ) {
 			$ret['code'] = 1;
 			$ret['message'] = 'Key name missing';
 		}
 		else {
 			$value = $this->Application->GetVar('value');
 			$res = $this->Application->setCache($key, $value);
 
 			$ret['result'] = $res ? 'OK' : 'FAILED';
 		}
 
 		$json_helper = $this->Application->recallObject('JSONHelper');
 		/* @var $json_helper JSONHelper */
 
 		echo $json_helper->encode($ret);
 	}
 
 	/**
 	 * Deploy changes
 	 *
 	 * Usage: "php tools/run_event.php adm:OnDeploy b674006f3edb1d9cd4d838c150b0567d"
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnDeploy(kEvent $event)
 	{
 		$deployment_helper = $this->Application->recallObject('DeploymentHelper');
 		/* @var $deployment_helper DeploymentHelper */
 
 		$deployment_helper->deployAll();
 
 		if ( $deployment_helper->isCommandLine ) {
 			// command line invocation -> don't render template
 			$event->status = kEvent::erSTOP;
 		}
 		else {
 			// browser invocation -> don't perform redirect
 			$event->redirect = false;
 		}
 
 		$event->SetRedirectParam('action_completed', 1);
 	}
 
 	/**
 	 * Synchronizes database revisions from "project_upgrades.sql" file
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSynchronizeDBRevisions(kEvent $event)
 	{
 		$deployment_helper = $this->Application->recallObject('DeploymentHelper');
 		/* @var $deployment_helper DeploymentHelper */
 
 		if ( $deployment_helper->deployAll(true) ) {
 			$this->Application->SetVar('action_completed', 1);
 		}
 
 		if ( $deployment_helper->isCommandLine ) {
 			// command line invocation -> don't render template
 			$event->status = kEvent::erSTOP;
 		}
 		else {
 			// browser invocation -> don't perform redirect
 			$event->redirect = false;
 		}
 	}
 
 	/**
 	 * [SCHEDULED TASK]
 	 * 1. Delete all Debug files from system/.restricted folder	(format debug_@977827436@.txt)
 	 * 2. Run MySQL OPTIMIZE SQL one by one on all In-Portal tables (found by prefix).
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnOptimizePerformance(kEvent $event)
 	{
 		$start_time = adodb_mktime();
 
 		$sql = 'SELECT SessionKey
 				FROM ' . TABLE_PREFIX . 'UserSessions
 				WHERE LastAccessed > ' . $start_time;
 		$active_sessions = array_flip($this->Conn->GetCol($sql));
 
 		$files = scandir(RESTRICTED);
 		$file_path = RESTRICTED . '/';
 
 		foreach ($files AS $file_name) {
 			if ( !preg_match('#^debug_@([0-9]{9})@.txt$#', $file_name, $matches) ) {
 				// not debug file
 				continue;
 			}
 
 			$sid = $matches[1];
 
 			if ( isset($active_sessions[$sid]) || (filemtime($file_path . $file_name) > $start_time) ) {
 				// debug file belongs to an active session
 				// debug file is recently created (after sessions snapshot)
 				continue;
 			}
 
 			unlink($file_path . $file_name);
 		}
 
 		$system_tables = $this->Conn->GetCol('SHOW TABLES LIKE "' . TABLE_PREFIX . '%"');
 
 		foreach ($system_tables AS $table_name) {
 			$this->Conn->Query('OPTIMIZE TABLE ' . $table_name);
 		}
 	}
 
 	/**
 	 * [SCHEDULED TASK] Pre-resizes images, used in templates
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnPreResizeImages(kEvent $event)
 	{
 		$scheduled_task = $event->MasterEvent->getObject();
 		/* @var $scheduled_task kDBItem */
 
 		$mass_resizer = new MassImageResizer();
 
 		// rules from scheduled task itself
 		$mass_resizer->addRules($scheduled_task->GetDBField('Settings'));
 
 		// rules from all enabled themes
 		$sql = 'SELECT ImageResizeRules
-				FROM ' . $this->Application->getUnitOption('theme', 'TableName') . '
+				FROM ' . $this->Application->getUnitConfig('theme')->getTableName() . '
 				WHERE Enabled = 1';
 		$mass_resizer->addRules($this->Conn->GetCol($sql));
 
 		$mass_resizer->run();
 	}
-	
+
 	/**
 	 * Returns popup size (by template), if not cached, then parse template to get value
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnGetPopupSize(kEvent $event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		if ( $this->Application->GetVar('ajax') != 'yes' ) {
 			return;
 		}
 
 		$t = $this->Application->GetVar('template_name');
 
 		$sql = 'SELECT *
 				FROM ' . TABLE_PREFIX . 'PopupSizes
 				WHERE TemplateName = ' . $this->Conn->qstr($t);
 		$popup_info = $this->Conn->GetRow($sql);
 
 		$this->Application->setContentType('text/plain');
 
 		if ( !$popup_info ) {
 			// dies when SetPopupSize tag found & in ajax request
 			$this->Application->InitParser();
 			$this->Application->ParseBlock(Array ('name' => $t));
 
 			// tag SetPopupSize not found in template -> use default size
 			echo '750x400';
 		}
 		else {
 			echo $popup_info['PopupWidth'] . 'x' . $popup_info['PopupHeight'];
 		}
 	}
 }
 
 /**
  * Resizes multiple images according to given rules
  */
 class MassImageResizer extends kBase {
 
 	/**
 	 * Rules, that tell how images must be resized
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	private $_rules = Array ();
 
 	/**
 	 * Remembers, which fields of which unit are used
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	private $_unitFields = Array ();
 
 	/**
 	 * Remembers which fields of which units require which format
 	 *
 	 * @var Array
 	 * @access private
 	 */
 	private $_unitFieldFormats = Array ();
 
 	/**
 	 * Adds more resize rules
 	 *
 	 * @param string|Array $rules
 	 * @return void
 	 * @access public
 	 */
 	public function addRules($rules)
 	{
 		$rules = (array)$rules;
 
 		foreach ($rules as $rule) {
 			$rule = $this->_cleanup($rule);
 
 			if ( $rule ) {
 				$this->_rules = array_merge($this->_rules, $rule);
 			}
 		}
 	}
 
 	/**
 	 * Normalizes given set of rules
 	 *
 	 * @param string $rules
 	 * @return Array
 	 * @access private
 	 */
 	private function _cleanup($rules)
 	{
 		$ret = explode("\n", str_replace(Array ("\r\n", "\r"), "\n", $rules));
 
 		return array_filter(array_map('trim', $ret));
 	}
 
 	/**
 	 * Transforms rules given in raw format into 3D array of easily manageable rules
 	 *
 	 * @return void
 	 * @access private
 	 */
 	private function _preProcessRules()
 	{
 		foreach ($this->_rules as $raw_rule) {
 			list ($prefix, $field, $format) = explode(':', $raw_rule, 3);
 
 			if ( !isset($this->_unitFields[$prefix]) ) {
 				$this->_unitFields[$prefix] = Array ();
 				$this->_unitFieldFormats[$prefix] = Array ();
 			}
 
 			$this->_unitFields[$prefix][] = $field;
 
 			if ( !isset($this->_unitFieldFormats[$prefix][$field]) ) {
 				$this->_unitFieldFormats[$prefix][$field] = Array ();
 			}
 
 			$this->_unitFieldFormats[$prefix][$field][] = $format;
 		}
 	}
 
 	/**
 	 * Performs resize operation
 	 *
 	 * @return void
 	 * @access public
 	 */
 	public function run()
 	{
 		$this->_preProcessRules();
 
 		foreach ($this->_unitFields as $prefix => $fields) {
 			$sql = 'SELECT ' . implode(',', array_unique($fields)) . '
-					FROM ' . $this->Application->getUnitOption($prefix, 'TableName');
+					FROM ' . $this->Application->getUnitConfig($prefix)->getTableName();
 			$unit_data = $this->Conn->GetIterator($sql);
 
 			if ( !count($unit_data) ) {
 				continue;
 			}
 
 			$object = $this->Application->recallObject($prefix . '.resize', null, Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			foreach ($unit_data as $field_values) {
 				foreach ($field_values as $field => $value) {
 					if ( !$value ) {
 						continue;
 					}
 
 					$object->SetDBField($field, $value);
 
 					foreach ($this->_unitFieldFormats[$prefix][$field] as $format) {
 						// will trigger image resize if needed
 						$object->GetField($field, $format);
 					}
 				}
 			}
 		}
 	}
 }
 
 
 class UnitConfigDecorator {
 
 	var $parentPath = Array ();
 
 	/**
 	 * Decorates given array
 	 *
 	 * @param Array $var
 	 * @param int $level
 	 * @return string
 	 */
 	public function decorate($var, $level = 0)
 	{
 		$ret = '';
 
 		$deep_level = count($this->parentPath);
 
 		if ( $deep_level && ($this->parentPath[0] == 'Fields') ) {
 			$expand = $level < 2;
 		}
 		elseif ( $deep_level && ($this->parentPath[0] == 'Grids') ) {
 			if ( $deep_level == 3 && $this->parentPath[2] == 'Icons' ) {
 				$expand = false;
 			}
 			else {
 				$expand = $level < 4;
 			}
 		}
 		else {
 			$expand = $level == 0;
 		}
 
 		if ( is_array($var) ) {
 			$ret .= 'Array (';
 			$prepend = $expand ? "\n" . str_repeat("\t", $level + 1) : '';
 
 			foreach ($var as $key => $value) {
 				array_push($this->parentPath, $key);
 				$ret .= $prepend . (is_string($key) ? "'" . $key . "'" : $key) . ' => ' . $this->decorate($value, $level + 1) . ', ';
 				array_pop($this->parentPath);
 			}
 
 			$prepend = $expand ? "\n" . str_repeat("\t", $level) : '';
 			$ret = rtrim($ret, ', ') . $prepend . ')';
 		}
 		else {
 			if ( is_null($var) ) {
 				$ret = 'NULL';
 			}
 			elseif ( is_string($var) ) {
 				$ret = "'" . $var . "'";
 			}
 			else {
 				$ret = $var;
 			}
 		}
 
 		return $ret;
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/admin/admin_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/admin/admin_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/admin/admin_tag_processor.php	(revision 15698)
@@ -1,1192 +1,1204 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class AdminTagProcessor extends kDBTagProcessor {
 
 		/**
 		 * Allows to execute js script after the page is fully loaded
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function AfterScript($params)
 		{
 			$after_script = $this->Application->GetVar('after_script');
 			if ($after_script) {
 				return '<script type="text/javascript">'.$after_script.'</script>';
 			}
 			return '';
 		}
 
 		/**
 		 * Returns section title with #section# keyword replaced with current section
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function GetSectionTitle($params)
 		{
 			if (array_key_exists('default', $params)) {
 				return $params['default'];
 			}
 
 			return $this->Application->Phrase( kUtil::replaceModuleSection($params['phrase']) );
 		}
 
 		/**
 		 * Returns section icon with #section# keyword replaced with current section
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function GetSectionIcon($params)
 		{
 			return kUtil::replaceModuleSection($params['icon']);
 		}
 
 		/**
 		 * Returns version of module by name
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function ModuleVersion($params)
 		{
 			return $this->Application->findModule('Name', $params['module'], 'Version');
 		}
 
 		/**
 		 * Used in table form section drawing
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function DrawTree($params)
 		{
 			static $deep_level = 0;
 
 			// when processings, then sort children by priority (key of children array)
 			$ret = '';
 			$section_name = $params['section_name'];
 			$params['name'] = $this->SelectParam($params, 'name,render_as,block');
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$params['children_count'] = isset($section_data['children']) ? count($section_data['children']) : 0;
 			$params['deep_level'] = $deep_level++;
 			$template = $section_data['url']['t'];
 			unset($section_data['url']['t']);
 			$section_data['section_url'] = $this->Application->HREF($template, '', $section_data['url']);
 			$ret .= $this->Application->ParseBlock( array_merge($params, $section_data) );
 			if (!isset($section_data['children'])) {
 				return $ret;
 			}
 
 			ksort($section_data['children'], SORT_NUMERIC);
 			foreach ($section_data['children'] as $section_name) {
 				if (!$sections_helper->sectionVisible($section_name)) {
 					continue;
 				}
 
 				$params['section_name'] = $section_name;
 				$ret .= $this->DrawTree($params);
 				$deep_level--;
 			}
 
 
 			return $ret;
 		}
 
 
 		function SectionInfo($params)
 		{
 			$section = $params['section'];
 			if ($section == '#session#') {
 				$section = $this->Application->RecallVar('section');
 			}
 
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section);
 			if (!$section_data) {
 				throw new Exception('Use of undefined section "<strong>' . $section . '</strong>" in "<strong>' . __METHOD__ . '</strong>"');
 				return '';
 			}
 
 			if (array_key_exists('parent', $params) && $params['parent']) {
 				do {
 					$section = $section_data['parent'];
 					$section_data =& $sections_helper->getSectionData($section);
 				} while (array_key_exists('use_parent_header', $section_data) && $section_data['use_parent_header']);
 			}
 
 			$info = $params['info'];
 			switch ($info) {
 				case 'module_path':
 					if (isset($params['module']) && $params['module']) {
 						$module = $params['module'];
 					}
 					elseif (isset($section_data['icon_module'])) {
 						$module = $section_data['icon_module'];
 					}
 					else {
 						$module = '#session#';
 					}
 					$res = $this->ModulePath(array('module' => $module));
 					break;
 
 				case 'perm_section':
 					$res = $sections_helper->getPermSection($section);
 					break;
 
 				case 'label':
 					$res = '';
 
 					if ( $section ) {
 						if ( $section == 'in-portal:root' ) {
 							// don't translate label for top section, because it's already translated
 							$res = $section_data['label'];
 						}
 						else {
 							$no_editing = array_key_exists('no_editing', $params) ? $params['no_editing'] : false;
 
 							$res = $this->Application->Phrase($section_data['label'], !$no_editing);
 						}
 					}
 					break;
 
 				default:
 					$res = $section_data[$info];
 					break;
 			}
 
 			if (array_key_exists('as_label', $params) && $params['as_label']) {
 				$res = $this->Application->Phrase($res);
 			}
 
 			return $res;
 		}
 
 
 		function PrintSection($params)
 		{
 			$section_name = $params['section_name'];
 			if ($section_name == '#session#') {
 				$section_name = $this->Application->RecallVar('section');
 			}
 
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			if (isset($params['use_first_child']) && $params['use_first_child']) {
 				$section_name = $sections_helper->getFirstChild($section_name, true);
 			}
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$params['name'] = $this->SelectParam($params, 'name,render_as,block');
 			$params['section_name'] = $section_name;
 
 			$url_params = $section_data['url'];
 			unset($url_params['t']);
 
 			$section_data['section_url'] = $this->Application->HREF($section_data['url']['t'], '', $url_params);
 			$ret = $this->Application->ParseBlock( array_merge($params, $section_data) );
 
 			return $ret;
 		}
 
 		/**
 		 * Used in XML drawing for tree
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PrintSections($params)
 		{
 			// when processings, then sort children by priority (key of children array)
 			$ret = '';
 			$section_name = $params['section_name'];
 			if ($section_name == '#session#') {
 				$section_name = $this->Application->RecallVar('section');
 			}
 
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$params['name'] = $this->SelectParam($params, 'name,render_as,block');
 			if (!isset($section_data['children'])) {
 				return '';
 			}
 
 			ksort($section_data['children'], SORT_NUMERIC);
 			foreach ($section_data['children'] as $section_name) {
 				$params['section_name'] = $section_name;
 				$section_data =& $sections_helper->getSectionData($section_name);
 
 				if (!$sections_helper->sectionVisible($section_name)) {
 					continue;
 				}
 				else {
 					$show_mode = isset($section_data['show_mode']) ? $section_data['show_mode'] : smNORMAL;
 					$section_data['debug_only'] = ($show_mode == smDEBUG) || ($show_mode == smSUPER_ADMIN) ? 1 : 0;
 				}
 
 				if (isset($section_data['tabs_only']) && $section_data['tabs_only']) {
 					$perm_status = false;
 					$folder_label = $section_data['label'];
 					ksort($section_data['children'], SORT_NUMERIC);
 					foreach ($section_data['children'] as $priority => $section_name) {
 						// if only tabs in this section & none of them have permission, then skip section too
 
 						$section_name = $sections_helper->getPermSection($section_name);
 						$perm_status = $this->Application->CheckPermission($section_name.'.view', 1);
 						if ($perm_status) {
 							break;
 						}
 					}
 					if (!$perm_status) {
 						// no permission for all tabs -> don't display tree node either
 						continue;
 					}
 
 					$params['section_name'] = $section_name;
 					$section_data =& $sections_helper->getSectionData($section_name);
 					$section_data['label'] = $folder_label; // use folder label in tree
 					$section_data['is_tab'] = 1;
 				}
 				else  {
 					$section_name = $sections_helper->getPermSection($section_name);
 					if (!$this->Application->CheckPermission($section_name.'.view', 1)) continue;
 				}
 
 				$params['children_count'] = isset($section_data['children']) ? count($section_data['children']) : 0;
 
 				// remove template, so it doesn't appear as additional parameter in url
 				$template = $section_data['url']['t'];
 				unset($section_data['url']['t']);
 
 				$section_data['section_url'] = $this->Application->HREF($template, '', $section_data['url']);
 
 				$late_load = getArrayValue($section_data, 'late_load');
 				if ($late_load) {
 					$t = $late_load['t'];
 					unset($late_load['t']);
 					$section_data['late_load'] = $this->Application->HREF($t, '', $late_load);
 					$params['children_count'] = 99;
 				}
 				else {
 					$section_data['late_load'] = '';
 				}
 
 				// restore template
 				$section_data['url']['t'] = $template;
 
 				$ret .= $this->Application->ParseBlock( array_merge($params, $section_data) );
 				$params['section_name'] = $section_name;
 			}
 
 			return preg_replace("/\r\n|\n/", '', $ret);
 		}
 
 		function ListSectionPermissions($params)
 		{
 			$section_name = isset($params['section_name']) ? $params['section_name'] : $this->Application->GetVar('section_name');
 
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$block_params = array_merge($section_data, Array('name' => $params['render_as'], 'section_name' => $section_name));
 
 			$ret = '';
 			foreach ($section_data['permissions'] as $perm_name) {
 				if (preg_match('/^advanced:(.*)/', $perm_name) != $params['type']) continue;
 				$block_params['perm_name'] = $perm_name;
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 			return $ret;
 		}
 
 		function ModuleInclude($params)
 		{
 			foreach ($params as $param_name => $param_value) {
 				$params[$param_name] = kUtil::replaceModuleSection($param_value);
 			}
 
 			return $this->Application->ProcessParsedTag('m', 'ModuleInclude', $params);
 		}
 
 		function TodayDate($params)
 		{
 			return date($params['format']);
 		}
 
 		function TreeEditWarrning($params)
 		{
 			$ret = $this->Application->Phrase($params['label']);
 			$ret = str_replace(Array('&lt;', '&gt;', 'br/', 'br /', "\n", "\r"), Array('<', '>', 'br', 'br', '', ''), $ret);
 			if (getArrayValue($params, 'escape')) {
 				$ret = addslashes($ret);
 			}
 			$ret = str_replace('<br>', '\n', $ret);
 			return $ret;
 		}
 
 		/**
 		 * Draws section tabs using block name passed
 		 *
 		 * @param Array $params
 		 */
 		function ListTabs($params)
 		{
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($params['section_name']);
 
 			$ret = '';
 			$block_params = Array('name' => $params['render_as']);
 			ksort($section_data['children'], SORT_NUMERIC);
 			foreach ($section_data['children'] as $priority => $section_name) {
 				$perm_section = $sections_helper->getPermSection($section_name);
 
 				if ( !$this->Application->CheckPermission($perm_section.'.view') ) {
 					continue;
 				}
 
 				$tab_data =& $sections_helper->getSectionData($section_name);
 				$block_params['t'] = $tab_data['url']['t'];
 				$block_params['pass'] = $tab_data['url']['pass'];
 				$block_params['title'] = $tab_data['label'];
 				$block_params['main_prefix'] = $section_data['SectionPrefix'];
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 
 
 			return $ret;
 		}
 
 		/**
 		 * Returns list of module item tabs that have view permission in current category
 		 *
 		 * @param Array $params
 		 */
 		function ListCatalogTabs($params)
 		{
 			$ret = '';
 			$special = isset($params['special']) ? $params['special'] : '';
 			$replace_main = isset($params['replace_m']) && $params['replace_m'];
-			$skip_prefixes = isset($params['skip_prefixes']) ? explode(',', $params['skip_prefixes']) : Array();
+			$skip_prefixes = isset($params['skip_prefixes']) ? explode(',', $params['skip_prefixes']) : Array ();
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				$prefix = $module_info['Var'];
 
-				if ($prefix == 'm' && $replace_main) {
+				if ( $prefix == 'm' && $replace_main ) {
 					$prefix = 'c';
 				}
 
-				if (in_array($prefix, $skip_prefixes) || !$this->Application->prefixRegistred($prefix) || !$this->Application->getUnitOption($prefix, 'CatalogItem')) {
+				if ( $this->Application->prefixRegistred($prefix) ) {
+					$config = $this->Application->getUnitConfig($prefix);
+				}
+				else {
+					$config = null;
+				}
+
+				if ( in_array($prefix, $skip_prefixes) || !is_object($config) || !$config->getCatalogItem() ) {
 					continue;
 				}
 
-				$icon = $this->Application->getUnitOption($prefix, 'CatalogTabIcon');
-				if (strpos($icon, ':') !== false) {
+				$icon = $config->getCatalogTabIcon();
+
+				if ( strpos($icon, ':') !== false ) {
 					list ($icon_module, $icon) = explode(':', $icon, 2);
 				}
 				else {
 					$icon_module = 'core';
 				}
 
-				$label = $this->Application->getUnitOption($prefix, $params['title_property']);
+				$label = $config->getSetting($params['title_property']);
 				$block_params['title'] = $label;
 				$block_params['prefix'] = $prefix;
 				$block_params['icon_module'] = $icon_module;
 				$block_params['icon'] = $icon;
+
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
+
 			return $ret;
 		}
 
 		/**
 		 * Renders inividual catalog tab based on prefix and title_property given
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function CatalogTab($params)
 		{
-			$icon = $this->Application->getUnitOption($params['prefix'], 'CatalogTabIcon');
-			if (strpos($icon, ':') !== false) {
+			$config = $this->Application->getUnitConfig($params['prefix']);
+			$icon = $config->getCatalogTabIcon();
+
+			if ( strpos($icon, ':') !== false ) {
 				list ($icon_module, $icon) = explode(':', $icon, 2);
 			}
 			else {
 				$icon_module = 'core';
 			}
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 			$block_params['icon_module'] = $icon_module;
 			$block_params['icon'] = $icon;
-			$block_params['title'] = $this->Application->getUnitOption($params['prefix'], $params['title_property']);
+			$block_params['title'] = $config->getSetting($params['title_property']);
 
 			return $this->Application->ParseBlock($block_params);
 		}
 
 		/**
 		 * Allows to construct link for opening any type of catalog item selector
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function SelectorLink($params)
 		{
 			$mode = 'catalog';
 			if (isset($params['mode'])) { // {catalog, advanced_view}
 				$mode = $params['mode'];
 				unset($params['mode']);
 			}
 
 			$params['t'] = 'catalog/item_selector/item_selector_'.$mode;
 			$params['m_cat_id'] = $this->Application->getBaseCategory();
 
 			$default_params = Array('no_amp' => 1, 'pass' => 'all,'.$params['prefix']);
 			unset($params['prefix']);
 
 			$pass_through = Array();
 			if (isset($params['tabs_dependant'])) { // {yes, no}
 				$pass_through['td'] = $params['tabs_dependant'];
 				unset($params['tabs_dependant']);
 			}
 
 			if (isset($params['selection_mode'])) { // {single, multi}
 				$pass_through['tm'] = $params['selection_mode'];
 				unset($params['selection_mode']);
 			}
 
 			if (isset($params['tab_prefixes'])) { // {all, none, <comma separated prefix list>}
 				$pass_through['tp'] = $params['tab_prefixes'];
 				unset($params['tab_prefixes']);
 			}
 
 			if ($pass_through) {
 				// add pass_through to selector url if any
 				$params['pass_through'] = implode(',', array_keys($pass_through));
 				$params = array_merge($params, $pass_through);
 			}
 
 			// user can override default parameters (except pass_through of course)
 			$params = array_merge($default_params, $params);
 
 	    	return $this->Application->ProcessParsedTag('m', 'T', $params);
 		}
 
 		function TimeFrame($params)
 		{
 			$w = adodb_date('w');
 			$m = adodb_date('m');
 			$y = adodb_date('Y');
 
 			//FirstDayOfWeek is 0 for Sunday and 1 for Monday
 			$fdow = $this->Application->ConfigValue('FirstDayOfWeek');
 
 			if ( $fdow && $w == 0 ) {
 				$w = 7;
 			}
 			$today_start = adodb_mktime(0, 0, 0, adodb_date('m'), adodb_date('d'), $y);
 			$first_day_of_this_week = $today_start - ($w - $fdow) * 86400;
 			$first_day_of_this_month = adodb_mktime(0, 0, 0, $m, 1, $y);
 			$this_quater = ceil($m / 3);
 			$this_quater_start = adodb_mktime(0, 0, 0, $this_quater * 3 - 2, 1, $y);
 
 			switch ( $params['type'] ) {
 				case 'last_week_start':
 					$timestamp = $first_day_of_this_week - 86400 * 7;
 					break;
 
 				case 'last_week_end':
 					$timestamp = $first_day_of_this_week - 1;
 					break;
 
 				case 'last_month_start':
 					$timestamp = $m == 1 ? adodb_mktime(0, 0, 0, 12, 1, $y - 1) : adodb_mktime(0, 0, 0, $m - 1, 1, $y);
 					break;
 
 				case 'last_month_end':
 					$timestamp = $first_day_of_this_month = adodb_mktime(0, 0, 0, $m, 1, $y) - 1;
 					break;
 
 				case 'last_quater_start':
 					$timestamp = $this_quater == 1 ? adodb_mktime(0, 0, 0, 10, 1, $y - 1) : adodb_mktime(0, 0, 0, ($this_quater - 1) * 3 - 2, 1, $y);
 					break;
 
 				case 'last_quater_end':
 					$timestamp = $this_quater_start - 1;
 					break;
 
 				case 'last_6_months_start':
 					$timestamp = $m <= 6 ? adodb_mktime(0, 0, 0, $m + 6, 1, $y - 1) : adodb_mktime(0, 0, 0, $m - 6, 1, $y);
 					break;
 
 				case 'last_year_start':
 					$timestamp = adodb_mktime(0, 0, 0, 1, 1, $y - 1);
 					break;
 
 				case 'last_year_end':
 					$timestamp = adodb_mktime(23, 59, 59, 12, 31, $y - 1);
 					break;
 
 				default:
 					$timestamp = 0;
 					break;
 			}
 
 			if ( isset($params['format']) ) {
 				$format = $params['format'];
 
 				if ( preg_match("/_regional_(.*)/", $format, $regs) ) {
 					$lang = $this->Application->recallObject('lang.current');
 					/* @var $lang LanguagesItem */
 
 					$format = $lang->GetDBField($regs[1]);
 				}
 
 				return adodb_date($format, $timestamp);
 			}
 
 			return $timestamp;
 		}
 
 		/**
 		 * Redirect to cache rebuild template, when required by installator
 		 *
 		 * @param Array $params
 		 */
 		function CheckPermCache($params)
 		{
 			// we have separate session between install wizard and admin console, so store in cache
 			$global_mark = $this->Application->getDBCache('ForcePermCacheUpdate');
 			$local_mark = $this->Application->RecallVar('PermCache_UpdateRequired');
 
 			if ( $global_mark || $local_mark ) {
 				$this->Application->RemoveVar('PermCache_UpdateRequired');
 				$rebuild_mode = $this->Application->ConfigValue('CategoryPermissionRebuildMode');
 
 				if ( $rebuild_mode == CategoryPermissionRebuild::SILENT ) {
 					$updater = $this->Application->makeClass('kPermCacheUpdater');
 					/* @var $updater kPermCacheUpdater */
 
 					$updater->OneStepRun();
 
 					$this->Application->HandleEvent(new kEvent('c:OnResetCMSMenuCache'));
 				}
 				elseif ( $rebuild_mode == CategoryPermissionRebuild::AUTOMATIC ) {
 					// update with progress bar
 					return true;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Checks if current protocol is SSL
 		 *
 		 * @param Array $params
 		 * @return int
 		 */
 		function IsSSL($params)
 		{
 			return (PROTOCOL == 'https://')? 1 : 0;
 		}
 
 		function PrintColumns($params)
 		{
 			$picker_helper = $this->Application->recallObject('ColumnPickerHelper');
 			$picker_helper->SetGridName($this->Application->GetLinkedVar('grid_name'));
 			/* @var $picker_helper kColumnPickerHelper */
 
 			$main_prefix = $this->Application->RecallVar('main_prefix');
 			$cols = $picker_helper->LoadColumns($main_prefix);
 
 			$this->Application->Phrases->AddCachedPhrase('__FREEZER__', '-------------');
 
 			$o = '';
 			if (isset($params['hidden']) && $params['hidden']) {
 				foreach ($cols['hidden_fields'] as $col) {
 					$title = $this->Application->Phrase($cols['titles'][$col]);
 					$o .= "<option value='$col'>".$title;
 				}
 			}
 			else {
 				foreach ($cols['order'] as $col) {
 					if (in_array($col, $cols['hidden_fields'])) continue;
 					$title = $this->Application->Phrase($cols['titles'][$col]);
 					$o .= "<option value='$col'>".$title;
 				}
 			}
 			return $o;
 		}
 
 		/**
 		 * Allows to set popup size (key - current template name)
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access protected
 		 */
 		protected function SetPopupSize($params)
 		{
 			$width = $params['width'];
 			$height = $params['height'];
 
 			if ( $this->Application->GetVar('ajax') == 'yes' ) {
 				// during AJAX request just output size
 				die($width . 'x' . $height);
 			}
 
 			if ( !$this->UsePopups($params) ) {
 				return;
 			}
 
 			$t = $this->Application->GetVar('t');
 
 			$sql = 'SELECT *
 					FROM ' . TABLE_PREFIX . 'PopupSizes
 					WHERE TemplateName = ' . $this->Conn->qstr($t);
 			$popup_info = $this->Conn->GetRow($sql);
 
 			if ( !$popup_info ) {
 				// create new popup size record
 				$fields_hash = Array (
 					'TemplateName' => $t,
 					'PopupWidth' => $width,
 					'PopupHeight' => $height,
 				);
 
 				$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'PopupSizes');
 			}
 			elseif ( $popup_info['PopupWidth'] != $width || $popup_info['PopupHeight'] != $height ) {
 				// popup found and size in tag differs from one in db -> update in db
 				$fields_hash = Array (
 					'PopupWidth' => $width,
 					'PopupHeight' => $height,
 				);
 
 				$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'PopupSizes', 'PopupId = ' . $popup_info['PopupId']);
 			}
 		}
 
 		/**
 		 * Allows to check if popups are generally enabled OR to check for "popup" or "modal" mode is enabled
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function UsePopups($params)
 		{
 			if ($this->Application->GetVar('_force_popup')) {
 				return true;
 			}
 
 			$use_popups = (int)$this->Application->ConfigValue('UsePopups');
 
 			if (array_key_exists('mode', $params)) {
 				$mode_mapping = Array ('popup' => 1, 'modal' => 2);
 				return $use_popups == $mode_mapping[ $params['mode'] ];
 			}
 
 			return $use_popups;
 		}
 
 		function UseToolbarLabels($params)
 		{
 			return (int)$this->Application->ConfigValue('UseToolbarLabels');
 		}
 
 		/**
 		 * Checks if debug mode enabled (optionally) and specified constant is on
 		 *
 		 * @param Array $params
 		 * @return bool
 		 * @todo Could be a duplicate of kMainTagProcessor::ConstOn
 		 */
 		function ConstOn($params)
 		{
 			$constant_name = $this->SelectParam($params, 'name,const');
 			$debug_mode = isset($params['debug_mode']) && $params['debug_mode'] ? $this->Application->isDebugMode() : true;
 
 			return $debug_mode && kUtil::constOn($constant_name);
 		}
 
 		/**
 		 * Builds link to last template in main frame of admin
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function MainFrameLink($params)
 		{
 			$persistent = isset($params['persistent']) && $params['persistent'];
 			if ($persistent && $this->Application->ConfigValue('RememberLastAdminTemplate')) {
 				// check last_template in persistent session
 				$last_template = $this->Application->RecallPersistentVar('last_template_popup');
 			}
 			else {
 				// check last_template in session
 				$last_template = $this->Application->RecallVar('last_template_popup'); // because of m_opener=s there
 			}
 
 			if (!$last_template) {
 				$params['persistent'] = 1;
 				return $persistent ? false : $this->MainFrameLink($params);
 			}
 
 			list($index_file, $env) = explode('|', $last_template);
 			$vars = $this->Application->processQueryString($env, 'pass');
 			$recursion_templates = Array ('login', 'index', 'no_permission');
 
 			if (isset($vars['admin']) && $vars['admin'] == 1) {
 				// index template doesn't begin recursion on front-end (in admin frame)
 				$vars['m_theme'] = '';
 
 				if (isset($params['m_opener']) && $params['m_opener'] == 'r') {
 					// front-end link for highlighting purposes
 					$vars['t'] = 'index';
 					$vars['m_cat_id'] = $this->Application->getBaseCategory();
 				}
 
 				unset($recursion_templates[ array_search('index', $recursion_templates)]);
 			}
 
 			if (in_array($vars['t'], $recursion_templates)) {
 				// prevents redirect recursion OR old in-portal pages
 				$params['persistent'] = 1;
 				return $persistent ? false : $this->MainFrameLink($params);
 			}
 
 			$vars = array_merge($vars, $params);
 			$t = $vars['t'];
 			unset($vars['t'], $vars['persistent']);
 
 			// substitute language in link to current (link will work, even when language will be changed)
 			$vars['m_lang'] = $this->Application->GetVar('m_lang');
 
 			return $this->Application->HREF($t, '', $vars, $index_file);
 		}
 
 		/**
 		 * Returns menu frame width or 200 in case, when invalid width specified in config
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function MenuFrameWidth($params)
 		{
 			$width = (int)$this->Application->ConfigValue('MenuFrameWidth');
 
 			return $width > 0 ? $width : 200;
 		}
 
 		function AdminSkin($params)
 		{
 			$skin_helper = $this->Application->recallObject('SkinHelper');
 			/* @var $skin_helper SkinHelper */
 
 			return $skin_helper->AdminSkinTag($params);
 		}
 
 		/**
 		 * Prints errors, discovered during mass template compilation
 		 *
 		 * @param $params
 		 * @return string
 		 * @access protected
 		 */
 		protected function PrintCompileErrors($params)
 		{
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 
 			$errors = $this->Application->RecallVar('compile_errors');
 			if ( !$errors ) {
 				return '';
 			}
 
 			$ret = '';
 			$errors = unserialize($errors);
 			$path_regexp = '/^' . preg_quote(FULL_PATH, '/') . '/';
 
 			foreach ($errors as $an_error) {
 				$block_params = array_merge($block_params, $an_error);
 				$block_params['file'] = preg_replace($path_regexp, '', $an_error['file'], 1);
 
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 
 			$this->Application->RemoveVar('compile_errors');
 
 			return $ret;
 		}
 
 		function CompileErrorCount($params)
 		{
 			$errors = $this->Application->RecallVar('compile_errors');
 			if (!$errors) {
 				return 0;
 			}
 
 			return count( unserialize($errors) );
 		}
 
 		/**
 		 * Detects if given exception isn't one caused by tag error
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access protected
 		 */
 		protected function IsParserException($params)
 		{
 			return mb_strtolower($params['class']) == 'parserexception';
 		}
 
 		function ExportData($params)
 		{
 			$export_helper = $this->Application->recallObject('CSVHelper');
 			/* @var $export_helper kCSVHelper */
 			$result = $export_helper->ExportData( $this->SelectParam($params, 'var,name,field') );
 			return ($result === false) ? '' : $result;
 		}
 
 		function ImportData($params)
 		{
 			$import_helper = $this->Application->recallObject('CSVHelper');
 			/* @var $import_helper kCSVHelper */
 			$result = $import_helper->ImportData( $this->SelectParam($params, 'var,name,field') );
 			return ($result === false) ? '' : $result;
 		}
 
 		function PrintCSVNotImportedLines($params)
 		{
 			$import_helper = $this->Application->recallObject('CSVHelper');
 			/* @var $import_helper kCSVHelper */
 			return  $import_helper->GetNotImportedLines();
 		}
 
 		/**
 		 * Returns input field name to
 		 * be placed on form (for correct
 		 * event processing)
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access public
 		 */
 		function InputName($params)
 		{
 			list($id, $field) = $this->prepareInputName($params);
 
 			$ret = $this->getPrefixSpecial().'[0]['.$field.']'; // 0 always, as has no idfield
 			if( getArrayValue($params, 'as_preg') ) $ret = preg_quote($ret, '/');
 			return $ret;
 		}
 
 		/**
 		 * Returns list of all backup file dates formatted
 		 * in passed block
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access public
 		 */
 		function PrintBackupDates($params)
 		{
 	 		$backup_helper = $this->Application->recallObject('BackupHelper');
 			/* @var $backup_helper BackupHelper */
 
 			$ret = '';
 			$dates = $backup_helper->getBackupFiles();
 
 			foreach ($dates as $date) {
 				$params['backuptimestamp'] = $date['filedate'];
 				$params['backuptime'] = date('F j, Y, g:i a', $date['filedate']);
 				$params['backupsize'] = round($date['filesize'] / 1024 / 1024, 2); // MBytes
 
 				$ret .= $this->Application->ParseBlock($params);
 			}
 
 	        return $ret;
 		}
 
 		/**
 		 * Returns phpinfo() output
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PrintPHPinfo($params)
 		{
 			ob_start();
 			phpinfo();
 
 			return ob_get_clean();
 		}
 
 		function PrintSqlCols($params)
 		{
 			$ret = '';
 			$block = $params['render_as'];
 			$a_data = unserialize($this->Application->GetVar('sql_rows'));
 
 			$a_row = current($a_data);
 
 			foreach ($a_row AS $col => $value) {
 				$ret .= $this->Application->ParseBlock(Array ('name' => $block, 'value' => $col));
 			}
 
 			return $ret;
 		}
 
 		function PrintSqlRows($params)
 		{
 			$ret = '';
 			$block = $params['render_as'];
 			$a_data = unserialize($this->Application->GetVar('sql_rows'));
 
 			foreach ($a_data as $a_row) {
 				$cells = '';
 				$a_row = array_map('htmlspecialchars', $a_row);
 
 				foreach ($a_row as $value) {
 					$cells .= '<td>' . $value . '</td>';
 				}
 
 				$ret .= $this->Application->ParseBlock(Array ('name' => $block, 'cells' => $cells));
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Prints available and enabled import sources using given block
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PrintImportSources($params)
  		{
  			$sql = 'SELECT *
  					FROM ' . TABLE_PREFIX . 'ImportScripts
  					WHERE (Status = ' . STATUS_ACTIVE . ') AND (Type = "CSV")';
  			$import_sources = $this->Conn->Query($sql);
 
  			$block_params = $this->prepareTagParams($params);
  			$block_params['name'] = $params['render_as'];
 
  			$ret = '';
  			foreach ($import_sources as $import_source) {
  				$block_params['script_id'] = $import_source['ImportId'];
  				$block_params['script_module'] = mb_strtolower($import_source['Module']);
  				$block_params['script_name'] = $import_source['Name'];
  				$block_params['script_prefix'] = $import_source['Prefix'];
  				$block_params['module_path'] = $this->Application->findModule('Name', $import_source['Module'], 'Path');
 
  				$ret .= $this->Application->ParseBlock($block_params);
  			}
 
  			return $ret;
  		}
 
  		/**
  		 * Checks, that new window should be opened in "incs/close_popup" template instead of refreshing parent window
  		 *
  		 * @param Array $params
  		 * @return bool
  		 */
  		function OpenNewWindow($params)
  		{
 			if (!$this->UsePopups($params)) {
 				return false;
 			}
 
  			$diff = array_key_exists('diff', $params) ? $params['diff'] : 0;
  			$wid = $this->Application->GetVar('m_wid');
 
 			$stack_name = rtrim('opener_stack_' . $wid, '_');
 			$opener_stack = $this->Application->RecallVar($stack_name);
 			$opener_stack = $opener_stack ? unserialize($opener_stack) : Array ();
 
 			return count($opener_stack) >= 2 - $diff;
  		}
 
 		/**
 		 * Allows to dynamically change current language in template
 		 *
 		 * @param Array $params
 		 */
 		function SetLanguage($params)
 		{
 			$this->Application->SetVar('m_lang', $params['language_id']);
 			$this->Application->Phrases->Init('phrases', '', $params['language_id']);
 		}
 
 		/**
 		 * Performs HTTP Authentification for administrative console
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function HTTPAuth($params)
 		{
 			if ( !$this->Application->ConfigValue('UseHTTPAuth') ) {
 				// http authentification not required
 				return true;
 			}
 
 			$super_admin_ips = defined('SA_IP') ? SA_IP : false;
 			$auth_bypass_ips = $this->Application->ConfigValue('HTTPAuthBypassIPs');
 
 			if ( ($auth_bypass_ips && kUtil::ipMatch($auth_bypass_ips)) || ($super_admin_ips && kUtil::ipMatch($super_admin_ips)) ) {
 				// user ip is in ip bypass list
 				return true;
 			}
 
 			if ( !array_key_exists('PHP_AUTH_USER', $_SERVER) ) {
 				// ask user to authentificate, when not authentificated before
 				return $this->_httpAuthentificate();
 			}
 			else {
 				// validate user credentials (browsers remembers user/password
 				// and sends them each time page is visited, so no need to save
 				// authentification result in session)
 				if ( $this->Application->ConfigValue('HTTPAuthUsername') != $_SERVER['PHP_AUTH_USER'] ) {
 					// incorrect username
 					return $this->_httpAuthentificate();
 				}
 
 				$password_formatter = $this->Application->recallObject('kPasswordFormatter');
 				/* @var $password_formatter kPasswordFormatter */
 
 				if ( !$password_formatter->checkPasswordFromSetting('HTTPAuthPassword', $_SERVER['PHP_AUTH_PW']) ) {
 					// incorrect password
 					return $this->_httpAuthentificate();
 				}
 			}
 
 			return true;
 		}
 
 		/**
 		 * Ask user to authentificate
 		 *
 		 * @return bool
 		 */
 		function _httpAuthentificate()
 		{
 			$realm = strip_tags( $this->Application->ConfigValue('Site_Name') );
 			header('WWW-Authenticate: Basic realm="' . $realm . '"');
 			header('HTTP/1.0 401 Unauthorized');
 
 			return false;
 		}
 
 		/**
 		 * Checks, that we are using memory cache
 		 *
 		 * @param Array $params
 		 * @return bool
 		 */
 		function MemoryCacheEnabled($params)
 		{
 			return $this->Application->isCachingType(CACHING_TYPE_MEMORY);
 		}
 
 		/**
 		 * Generates HTML for additional js and css files inclusion in accordance to selected editor language
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access protected
 		 */
 		protected function IncludeCodeMirrorFilesByLanguage($params)
 		{
 			$ret = '';
 			$language = $params['language'];
 
 			$language_map = Array (
 				'application/x-httpd-php' => Array (
 					'htmlmixed.js', 'xml.js', 'javascript.js', 'css.js', 'clike.js', 'php.js'
 				),
 				'text/html' => Array (
 					'xml.js', 'javascript.js', 'css.js', 'htmlmixed.js'
 				)
 			);
 
 			if ( !isset($language_map[$language]) ) {
 				$language_map[$language] = Array ($language . '.js');
 			}
 
 			foreach ($language_map[$language] as $filename) {
 				$ret .= $this->_includeCodeMirrorFile($filename, $params);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Generates code for one CodeMirror additional file inclusion
 		 *
 		 * @param string $filename
 		 * @param Array $params
 		 * @return string
 		 * @access protected
 		 * @throws InvalidArgumentException
 		 */
 		protected function _includeCodeMirrorFile($filename, $params)
 		{
 			static $included = Array ();
 
 			$name = pathinfo($filename, PATHINFO_FILENAME);
 			$extension = pathinfo($filename, PATHINFO_EXTENSION);
 
 			if ( $extension != 'js' && $extension != 'css' ) {
 				throw new InvalidArgumentException('Extension "' . $extension . '" not supported');
 			}
 
 			if ( isset($included[$filename]) ) {
 				return '';
 			}
 
 			$included[$filename] = 1;
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 			$block_params['resource_extension'] = $extension;
 			$block_params['resource_file'] = $name . '/' . $filename;
 
 			return $this->Application->ParseBlock($block_params);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/config_search/config_search_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/config_search/config_search_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/config_search/config_search_tag_processor.php	(revision 15698)
@@ -1,55 +1,55 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ConfigSearchTagProcessor extends kDBTagProcessor {
 
 		/**
 		 * Prints list content using block specified
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access public
 		 */
 		function PrintList($params)
 		{
 			$list =& $this->GetList($params);
-			$id_field = $this->Application->getUnitOption($this->Prefix,'IDField');
+			$id_field = $this->getUnitConfig()->getIDField();
 
 			$list->Query();
 			$o = '';
 			$list->GoFirst();
 
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $this->SelectParam($params, 'render_as,block');
 			$block_params['pass_params'] = 'true';
 
 			$list->groupRecords('ConfigHeader');
 			$prev_heading = '';
 
 			while (!$list->EOL())
 			{
 				$this->Application->SetVar( $this->getPrefixSpecial().'_id', $list->GetDBField($id_field) ); // for edit/delete links using GET
 				$block_params['show_heading'] = ($prev_heading != $list->GetDBField('ConfigHeader') ) ? 1 : 0;
 
 				$o.= $this->Application->ParseBlock($block_params);
 				$prev_heading = $list->GetDBField('ConfigHeader');
 				$list->GoNext();
 			}
 
 			$this->Application->SetVar( $this->getPrefixSpecial().'_id', '');
 			return $o;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/config_search/config_search_event_handler.php
===================================================================
--- branches/5.3.x/core/units/config_search/config_search_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/config_search/config_search_event_handler.php	(revision 15698)
@@ -1,154 +1,155 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class ConfigSearchEventHandler extends kDBEventHandler  {
 
 		/**
 		 * Changes permission section to one from REQUEST, not from config
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			$module = $this->Application->GetVar('module');
 			$main_prefix = $this->Application->findModule('Name', $module, 'Var');
-			$section = $this->Application->getUnitOption($main_prefix.'.search', 'PermSection');
+
+			$section = $this->Application->getUnitConfig($main_prefix)->getPermSectionByName('search');
 			$event->setEventParam('PermSection', $section);
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			// show only items that belong to selected module
 			$module = $this->Application->GetVar('module');
 			$object->addFilter('module_filter', '%1$s.ModuleName = ' . $this->Conn->qstr($module));
 
 			// don't show disabled search items
 			$object->addFilter('active_filter', '%1$s.SimpleSearch <> -1');
 		}
 
 		/**
 		 * Updates kDBItem
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdate(kEvent $event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return ;
 			}
 
 			parent::OnUpdate($event);
 
 			$conf_update = new kEvent('conf:OnUpdate');
 			$conf_update->redirect = false;
 			$this->Application->HandleEvent($conf_update);
 
 			$event->SetRedirectParam('opener', 's');
 
 			// keeps module and section in REQUEST to ensure, that last admin template will work
 			$event->SetRedirectParam('module', $this->Application->GetVar('module'));
 			$event->SetRedirectParam('module_key', $this->Application->GetVar('module_key'));
 			$event->SetRedirectParam('section', $this->Application->GetVar('section'));
 		}
 
 		/**
 		 * Cancels kDBItem Editing/Creation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCancel(kEvent $event)
 		{
 			parent::OnCancel($event);
 
 			$event->SetRedirectParam('opener', 's');
 		}
 
 		/**
 		 * [HOOK] Creates search config record corresponding to custom field, that was just created
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCreateCustomField($event)
 		{
 			$custom_field = $event->MasterEvent->getObject();
 			/* @var $custom_field kDBItem */
 
 			if ( $custom_field->GetDBField('Type') == 6 || $custom_field->GetDBField('IsSystem') == 1 ) {
 				// user & system custom fields are not searchable
 				return ;
 			}
 
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$custom_id = $custom_field->GetID();
 			if ( !$object->isLoaded() || ($object->GetDBField('CustomFieldId') != $custom_id) ) {
 				$object->Load($custom_id, 'CustomFieldId');
 			}
 
 			$cf_search = Array ();
 			$element_type = $custom_field->GetDBField('ElementType');
 
 			$cf_search['DisplayOrder'] = $custom_field->GetDBField('DisplayOrder');
 			$cf_search['FieldType'] = $element_type;
 			$cf_search['DisplayName'] = $custom_field->GetDBField('FieldLabel');
 			$cf_search['FieldName'] = $custom_field->GetDBField('FieldName');
 			$cf_search['Description'] = $custom_field->GetDBField('Prompt');
 			$cf_search['ConfigHeader'] = $custom_field->GetDBField('Heading'); //  'la_Text_CustomFields';
 			$cf_search['SimpleSearch'] = in_array($element_type, Array ('text', 'range', 'select', 'multiselect')) ? 1 : 0;
 			$cf_search['TableName'] = 'CustomFields';
 
 			$sql = 'SELECT Module
 					FROM ' . TABLE_PREFIX . 'ItemTypes
 					WHERE ItemType = ' . $custom_field->GetDBField('Type');
 
 			$cf_search['ModuleName'] = $this->Conn->GetOne($sql);
 
 			$object->SetFieldsFromHash($cf_search);
 			$object->SetDBField('CustomFieldId', $custom_id);
 
 			if ( $object->isLoaded() ) {
 				$object->Update();
 			}
 			else {
 				$object->Create();
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/user_profile/user_profile_eh.php
===================================================================
--- branches/5.3.x/core/units/user_profile/user_profile_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/user_profile/user_profile_eh.php	(revision 15698)
@@ -1,93 +1,93 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class UserProfileEventHandler extends kDBEventHandler {
 
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnItemBuild'	=>	Array ('subitem' => true),
 				'OnUpdate'	=>	Array ('subitem' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Saves user profile to database
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdate(kEvent $event)
 		{
 			$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
 			list ($user_id, $field_values) = each($items_info);
 
 			if ($user_id != $this->Application->RecallVar('user_id')) {
 				// we are not updating own profile
 				return ;
 			}
 
 			$public_profile_add = Array ();
 			$public_profile_remove = Array ();
-			$profile_mapping = $this->Application->getUnitOption('u', 'UserProfileMapping');
+			$profile_mapping = $this->Application->getUnitConfig('u')->getUserProfileMapping();
 
 			foreach ($field_values as $variable_name => $variable_value) {
 				if (array_key_exists($variable_name, $profile_mapping)) {
 					// old style variable for displaying fields in public profile (named "pp_*")
 					if ($variable_value) {
 						$public_profile_add[] = $profile_mapping[$variable_name];
 					}
 					else {
 						$public_profile_remove[] = $profile_mapping[$variable_name];
 					}
 				}
 				else {
 					$this->Application->StorePersistentVar($variable_name, htmlspecialchars_decode($variable_value));
 				}
 			}
 
 			if ($public_profile_add || $public_profile_remove) {
 				$user = $this->Application->recallObject('u.current');
 				/* @var $user kDBItem */
 
 				// get current value
 				$display_to_public_old = $user->GetDBField('DisplayToPublic');
 				$display_to_public_new = $display_to_public_old ? explode('|', substr($display_to_public_old, 1, -1)) : Array ();
 
 				// update value
 				$display_to_public_new = array_diff(array_merge($display_to_public_new, $public_profile_add), $public_profile_remove);
 				$display_to_public_new = array_unique($display_to_public_new);
 				$display_to_public_new = $display_to_public_new ? '|' . implode('|', $display_to_public_new) . '|' : '';
 
 				if ($display_to_public_new != $display_to_public_old) {
 					$user->SetDBField('DisplayToPublic', $display_to_public_new);
 					$user->Update();
 				}
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/user_profile/user_profile_tp.php
===================================================================
--- branches/5.3.x/core/units/user_profile/user_profile_tp.php	(revision 15697)
+++ branches/5.3.x/core/units/user_profile/user_profile_tp.php	(revision 15698)
@@ -1,145 +1,145 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class UserProfileTagProcessor extends kDBTagProcessor {
 
 		function Field($params)
 		{
 			$field = $this->SelectParam($params, 'name,field');
-			$profile_mapping = $this->Application->getUnitOption('u', 'UserProfileMapping');
+			$profile_mapping = $this->Application->getUnitConfig('u')->getUserProfileMapping();
 			$user_field = array_key_exists($field, $profile_mapping) ? $profile_mapping[$field] : false;
 
 			if (array_key_exists('profile_field', $params) && $params['profile_field']) {
 				// get variable from mapping
 				$params['name'] = $user_field;
 				$value = $this->Application->ProcessParsedTag('u.profile', 'Field', $params);
 			}
 			elseif ($user_field) {
 				// old style variable for displaying fields in public profile (named "pp_*")
 				$block_params = Array ('name' => 'DisplayToPublic',	'value' => $user_field);
 				$value = $this->Application->ProcessParsedTag($this->getUserPrefixSpecial(), 'Selected', $block_params);
 			}
 			else {
 				// get variable by name
 				$value = $this->recallUserProfileVar($field);
 			}
 
 			if (isset($params['checked']) && $params['checked']) {
 				$checked_value = isset($params['value']) ? $params['value'] : 1;
 				$value = ($value == $checked_value) ? 'checked' : '';
 			}
 
 			return $value;
 		}
 
 		/**
 		 * Returns prefix and special of user to operate with
 		 *
 		 * @return string
 		 */
 		function getUserPrefixSpecial()
 		{
 			return $this->Application->GetVar('user_id') ? 'u.profile' : 'u.current';
 		}
 
 		/**
 		 * Allows to get persistent var from other user
 		 *
 		 * @param string $var_name
 		 * @return mixed
 		 */
 		function recallUserProfileVar($var_name)
 		{
 			static $cache = null;
 
 			if (!isset($cache)) {
 				$user = $this->Application->recallObject( $this->getUserPrefixSpecial() );
 				/* @var $user kDBItem */
 
 				$sql = 'SELECT VariableValue, VariableName
 						FROM ' . TABLE_PREFIX . 'UserPersistentSessionData
 						WHERE (PortalUserId = ' . $user->GetID() . ')';
 				$cache = $this->Conn->GetCol($sql, 'VariableName');
 			}
 
 			if (array_key_exists($var_name, $cache)) {
 				// get variable value from persistent session
 				return $cache[$var_name];
 			}
 			else {
 				// not found in persistent session -> get default value from config variable with same name
 				$config_value = $this->Application->ConfigValue($var_name);
 
 				if ($config_value !== false) {
 					return $config_value;
 				}
 			}
 
 			return false;
 		}
 
 
 
 		/**
 		 * Returns visible field count in user profile
 		 *
 		 * @param Array $params
 		 * @return int
 		 */
 		function ProfileFieldCount($params)
 		{
 			static $field_count = null;
 
 			if (!isset($field_count)) {
 				$user = $this->Application->recallObject( $this->getUserPrefixSpecial() );
 				/* @var $user kDBItem */
 
 				$display_to_public = $user->GetDBField('DisplayToPublic');
 				$field_count = $display_to_public ? substr_count($display_to_public, '|') - 1 : 0;
 			}
 
 			return $field_count;
 		}
 
 		/**
 		 * Allows to detect that not all fields were shown
 		 *
 		 * @param Array $params
 		 * @return bool
 		 * @access protected
 		 */
 		protected function NotLastField($params)
 		{
 			$counter = (int)$this->Application->GetVar( $params['counter'] );
 
 			return $counter < $this->ProfileFieldCount($params);
 		}
 
 		/**
 		 * Because of persistent session table doesn't have ids, we use user id as id for each record
 		 *
 		 * @param Array $params
 		 * @return Array (id,field)
 		 * @access private
 		 */
 		function prepareInputName($params)
 		{
 			$params['force_id'] = $this->Application->RecallVar('user_id');
 
 			return parent::prepareInputName($params);
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/relationship/relationship_event_handler.php
===================================================================
--- branches/5.3.x/core/units/relationship/relationship_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/relationship/relationship_event_handler.php	(revision 15698)
@@ -1,300 +1,310 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class RelationshipEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnProcessSelected' => Array ('subitem' => 'add|edit')
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Initializes new relation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnNew(kEvent $event)
 		{
 			parent::OnNew($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$table_info = $object->getLinkedInfo();
 
 			$object->SetDBField('SourceId', $table_info['ParentId']);
-			$source_itemtype = $this->Application->getUnitOption($table_info['ParentPrefix'], 'ItemType');
-			$object->SetDBField('SourceType', $source_itemtype);
+			$source_item_type = $this->Application->getUnitConfig($table_info['ParentPrefix'])->getItemType();
+			$object->SetDBField('SourceType', $source_item_type);
 
 			$object->SetDBField('TargetId', $this->Application->GetVar('target_id'));
 			$object->SetDBField('TargetType', $this->Application->GetVar('target_type'));
 
 			$this->OnAfterItemLoad($event);
 		}
 
 		/**
 		 * Add new relation
 		 *
 		 * @param kEvent $event
 		 */
 		function OnProcessSelected($event)
 		{
 			$dst_field = $this->Application->RecallVar('dst_field');
 
 			if ( $dst_field == 'TargetId' ) {
 				// prepare target_id & target_type
 				$object = $event->getObject(Array ('skip_autoload' => true));
 
 				$target_id = 0;
 				$target_prefix = false;
 				$selected_ids = $this->Application->GetVar('selected_ids');
 
 				foreach ($selected_ids as $selected_prefix => $target_id) {
 					if ( $target_id > 0 ) {
 						$target_prefix = $selected_prefix;
 						break;
 					}
 				}
 
 				if ( !$target_prefix ) {
 					$event->SetRedirectParam('opener', 'u');
 					return;
 				}
 
+				$target_config = $this->Application->getUnitConfig($target_prefix);
+
 				$sql = 'SELECT ResourceId
-						FROM ' . $this->Application->getUnitOption($target_prefix, 'TableName') . '
-						WHERE ' . $this->Application->getUnitOption($target_prefix, 'IDField') . ' IN (' . $target_id . ')';
+						FROM ' . $target_config->getTableName() . '
+						WHERE ' . $target_config->getIDField() . ' IN (' . $target_id . ')';
 				$target_id = $this->Conn->GetOne($sql);
-				$target_type = $this->Application->getUnitOption($target_prefix, 'ItemType');
+				$target_type = $target_config->getItemType();
 
 				// don't add same relation twice
 				$table_info = $object->getLinkedInfo();
 				$sql = 'SELECT TargetId
 						FROM ' . $object->TableName . '
 						WHERE (SourceId = ' . $table_info['ParentId'] . ') AND (TargetId = ' . $target_id . ')';
 				$duplicate_relation = $this->Conn->GetOne($sql) == $target_id;
 
 				$event->SetRedirectParam('opener', 'u');
 
 				if ( !$duplicate_relation ) {
 					// place correct template in opener stack
-					$source_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix');
-					$template = $this->Application->getUnitOption($source_prefix, 'AdminTemplatePath') . '/relations_edit';
+					$source_prefix = $event->getUnitConfig()->getParentPrefix();
+					$template = $this->Application->getUnitConfig($source_prefix)->getAdminTemplatePath() . '/relations_edit';
 
 					$redirect_params = Array (
 						$event->Prefix . '_event' => 'OnNew',
 						'target_id' => $target_id,
 						'm_opener' => 's',
 						'target_type' => $target_type,
 						'pass' => 'all,' . $event->Prefix,
 					);
 
 					$this->Application->EventManager->openerStackPush($template, $redirect_params);
 				}
 			}
 			else {
 				$event->SetRedirectParam('opener', 'u');
 			}
 		}
 
 		/**
 		 * Set ItemName & ItemType virtual fields based on loaded item data
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sql = 'SELECT Prefix
 					FROM ' . TABLE_PREFIX . 'ItemTypes
 					WHERE ItemType = ' . $object->GetDBField('TargetType');
-			$target_prefix = $this->Conn->GetOne($sql);
+			$target_config = $this->Application->getUnitConfig($this->Conn->GetOne($sql));
 
-			$title_field = $this->getTitleField($target_prefix);
-			$title_phrase = $this->Application->getUnitOption($target_prefix, 'TitlePhrase');
+			$title_field = $this->getTitleField($target_config);
+			$title_phrase = $target_config->getTitlePhrase();
 
 			$sql = 'SELECT ' . $title_field . '
-					FROM ' . $this->Application->getUnitOption($target_prefix, 'TableName') . '
+					FROM ' . $target_config->getTableName() . '
 					WHERE ResourceId = ' . $object->GetDBField('TargetId');
 
 			$object->SetDBField('ItemName', $this->Conn->GetOne($sql));
 			$object->SetDBField('ItemType', $this->Application->Phrase($title_phrase));
 		}
 
 		/**
 		 * Creates needed sql query to load list,
 		 * if no query is defined in config for
 		 * special requested, then use default
 		 * query
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function ListPrepareQuery(kEvent $event)
 		{
 			return $this->BaseQuery($event, 'ListSQLs');
 		}
 
 		/**
 		 * Creates needed sql query to load item,
 		 * if no query is defined in config for
 		 * special requested, then use list query
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 * @access protected
 		 */
 		protected function ItemPrepareQuery(kEvent $event)
 		{
 			return $this->BaseQuery($event, 'ItemSQLs');
 		}
 
 
 		/**
 		 * Get item name & type based on relation type & modules installed
 		 *
 		 * @param kEvent $event
 		 * @param string $sql_field
 		 */
 		function BaseQuery($event, $sql_field)
 		{
-			$sqls = $this->Application->getUnitOption($event->Prefix,$sql_field);
+			$sqls = $event->getUnitConfig()->getSetting($sql_field);
 			$sql = isset($sqls[$event->Special]) ? $sqls[$event->Special] : $sqls[''];
 
 			$configs = $this->extractModulesInfo();
 
 			// 2. build sql based on information queried
 			$sql_templates['ItemName'] = 'IFNULL(%s.%s,\' \')';
 			$sql_templates['TableJoin'] = 'LEFT JOIN %1$s ON %1$s.ResourceId = rel.TargetId';
 			$sql_templates['TargetName'] = 'IF(rel.TargetType = %s, \'%s\', %s)';
 
-			$sql_parts = Array();
+			$sql_parts = Array ();
 			$sql_parts['TargetName'] = "''";
-			foreach ($configs as $prefix => $config_data) {
-				$title_field = $this->getTitleField($prefix);
 
-				$sql_parts['ItemName'][] = sprintf($sql_templates['ItemName'], $config_data['TableName'], $title_field);
-				$sql_parts['TableJoin'][] = sprintf($sql_templates['TableJoin'], $config_data['TableName']);
+			foreach ($configs as $config) {
+				$title_field = $this->getTitleField($config);
+
+				$sql_parts['ItemName'][] = sprintf($sql_templates['ItemName'], $config->getTableName(), $title_field);
+				$sql_parts['TableJoin'][] = sprintf($sql_templates['TableJoin'], $config->getTableName());
 
-				$sql_parts['TargetName'] = sprintf(	$sql_templates['TargetName'],
-					$config_data['ItemType'],
-					'!'.$config_data['TitlePhrase'].'!',
-					$sql_parts['TargetName']);
-				$sql_parts['TargetName'] = str_replace('rel','%1$s',$sql_parts['TargetName']);
+				$sql_parts['TargetName'] = sprintf($sql_templates['TargetName'], $config->getItemType(), '!' . $config->getTitlePhrase() . '!', $sql_parts['TargetName']);
+				$sql_parts['TargetName'] = str_replace('rel', '%1$s', $sql_parts['TargetName']);
 			}
 
 			$object = $event->getObject();
 
-			$vars = Array('#ITEM_NAMES#', '#ITEM_TYPES#');
-			$replacements = Array( implode(', ',$sql_parts['ItemName']), $sql_parts['TargetName'] );
+			$vars = Array ('#ITEM_NAMES#', '#ITEM_TYPES#');
+			$replacements = Array (implode(', ', $sql_parts['ItemName']), $sql_parts['TargetName']);
 
 			$calculated_fields = $object->getCalculatedFields();
 
 			foreach ($calculated_fields as $field_name => $field_expression) {
 				$calculated_fields[$field_name] = str_replace($vars, $replacements, $field_expression);
 			}
 
 			$object->setCalculatedFields($calculated_fields);
 
-			$sql = str_replace('#ITEM_JOIN#', implode(' ',$sql_parts['TableJoin']), $sql);
-			$sql = str_replace('rel.','%1$s.',$sql);
+			$sql = str_replace('#ITEM_JOIN#', implode(' ', $sql_parts['TableJoin']), $sql);
+			$sql = str_replace('rel.', '%1$s.', $sql);
 
 			return $sql;
 		}
 
 		/**
 		 * Convert TitleField field of kMultiLanguage formatter used for it
 		 *
-		 * @param string $prefix
+		 * @param kUnitConfig $config
 		 * @return string
 		 */
-		function getTitleField($prefix)
+		function getTitleField(kUnitConfig $config)
 		{
-			$lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_';
+			$lang_prefix = 'l' . $this->Application->GetVar('m_lang') . '_';
 
-			$title_field = $this->Application->getUnitOption($prefix, 'TitleField');
-			$field_options = $this->Application->getUnitOption($prefix.'.'.$title_field, 'Fields');
+			$title_field = $config->getTitleField();
+			$field_options = $config->getFieldByName($title_field);
 
 			$formatter_class = isset($field_options['formatter']) ? $field_options['formatter'] : '';
-			if ($formatter_class == 'kMultiLanguage' && !isset($field_options['master_field'])) {
-				$title_field = $lang_prefix.$title_field;
+
+			if ( $formatter_class == 'kMultiLanguage' && !isset($field_options['master_field']) ) {
+				$title_field = $lang_prefix . $title_field;
 			}
+
 			return $title_field;
 		}
 
 		/**
 		 * Get configs from modules installed
 		 *
-		 * @return Array
+		 * @return Array|kUnitConfig[]
 		 * @access private
 		 */
 		function extractModulesInfo()
 		{
 			// get installed modules & their config info
 			// maybe we should leave only prefixes, that have "view" permission
-			$configs = Array();
-			foreach ($this->Application->ModuleInfo as $module_name => $module_data) {
+			$ret = Array ();
+
+			foreach ($this->Application->ModuleInfo as $module_data) {
 				$prefix = $module_data['Var'];
-				if ($prefix == 'm') {
+
+				if ( $prefix == 'm' ) {
 					$prefix = 'c';
 				}
-				if (!$this->Application->prefixRegistred($prefix)) continue;
 
-				$configs[$prefix] = $this->Application->getUnitOptions($prefix);
-				if($configs[$prefix] === false) unset($configs[$prefix]);
-				if(!isset($configs[$prefix]['CatalogItem']) || !$configs[$prefix]['CatalogItem']) unset($configs[$prefix]);
+				if ( !$this->Application->prefixRegistred($prefix) ) {
+					continue;
+				}
+
+				$config = $this->Application->getUnitConfig($prefix);
+
+				if ( $config->getCatalogItem() ) {
+					$ret[$prefix] = $config;
+				}
 			}
-			return $configs;
+
+			return $ret;
 		}
 
 		/**
 		 * Deletes relations to hooked item from other items
 		 *
 		 * @param kEvent $event
 		 */
 		function OnDeleteForeignRelations($event)
 		{
 			$main_object = $event->MasterEvent->getObject();
 			/* @var $main_object kDBItem */
 
-			$sql = 'DELETE FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+			$sql = 'DELETE FROM ' . $event->getUnitConfig()->getTableName() . '
 					WHERE TargetId = ' . $main_object->GetDBField('ResourceId');
 			$this->Conn->Query($sql);
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/captcha/captcha_eh.php
===================================================================
--- branches/5.3.x/core/units/captcha/captcha_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/captcha/captcha_eh.php	(revision 15698)
@@ -1,47 +1,47 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class CaptchaEventHandler extends kEventHandler {
 
 		/**
 		 * [HOOK] Validates captcha code in item
 		 *
 		 * @param kEvent $event
 		 */
 		function OnValidateCode($event)
 		{
 			$captcha_helper = $this->Application->recallObject('CaptchaHelper');
 			/* @var $captcha_helper kCaptchaHelper */
 
 			$captcha_helper->validateCode($event->MasterEvent);
 		}
 
 		/**
 		 * [HOOK] Initializes captcha code processing routine
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPrepareCaptcha($event)
 		{
 			$captcha_helper = $this->Application->recallObject('CaptchaHelper');
 			/* @var $captcha_helper kCaptchaHelper */
 
 			// create field for captcha code storage
-			$virtual_fields = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'VirtualFields');
-			$virtual_fields['Captcha'] = Array ('type' => 'string', 'default' => '');
-			$this->Application->setUnitOption($event->MasterEvent->Prefix, 'VirtualFields', $virtual_fields);
+			$event->MasterEvent->getUnitConfig()->addVirtualFields(Array (
+				'Captcha' => Array ('type' => 'string', 'default' => ''),
+			));
 		}
 	}
Index: branches/5.3.x/core/units/phrases/phrases_event_handler.php
===================================================================
--- branches/5.3.x/core/units/phrases/phrases_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/phrases/phrases_event_handler.php	(revision 15698)
@@ -1,559 +1,563 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class PhrasesEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnItemBuild' => Array ('self' => true, 'subitem' => true),
 				'OnPreparePhrase' => Array ('self' => true, 'subitem' => true),
 				'OnExportPhrases' => Array ('self' => 'view'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Hides phrases from disabled modules
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			$object->addFilter('module_filter', '%1$s.Module IN (SELECT Name FROM ' . TABLE_PREFIX . 'Modules WHERE Loaded = 1)');
 		}
 
 		/**
 		 * Apply some special processing to object being
 		 * recalled before using it in other events that
 		 * call prepareObject
 		 *
 		 * @param kDBItem|kDBList $object
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function prepareObject(&$object, kEvent $event)
 		{
 			// don't call parent
 			if ( $event->Special == 'import' || $event->Special == 'export' ) {
 				$this->RemoveRequiredFields($object);
 				$object->setRequired(Array ('ExportDataTypes', 'LangFile', 'PhraseType', 'Module'));
 
 				// allow multiple phrase types to be selected during import/export
 				$object->SetFieldOption('PhraseType', 'type', 'string');
 			}
 		}
 
 		/**
 		 * Allow to create phrases from front end in debug mode with DBG_PHRASES constant set
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( !$this->Application->isAdmin && $this->Application->isDebugMode(false) && kUtil::constOn('DBG_PHRASES') ) {
 				$allow_events = Array ('OnCreate', 'OnUpdate');
 
 				if ( in_array($event->Name, $allow_events) ) {
 					return true;
 				}
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Prepares phrase for translation
 		 *
 		 * @param kEvent $event
 		 */
 		function OnPreparePhrase($event)
 		{
 			$label = $this->Application->GetVar($event->getPrefixSpecial() . '_label');
 
 			if (!$label) {
 				return ;
 			}
 
 			// we got label, try to get it's ID then if any
 			$phrase_id = $this->_getPhraseId($label);
 
 			if ($phrase_id) {
 				$event->SetRedirectParam($event->getPrefixSpecial(true) . '_id', $phrase_id);
 				$event->SetRedirectParam('pass', 'm,' . $event->getPrefixSpecial());
 
 				$next_template = $this->Application->GetVar('next_template');
 
 				if ($next_template) {
 					$event->SetRedirectParam('next_template', $next_template);
 				}
 			}
 			else {
 				$event->CallSubEvent('OnNew');
 			}
 
 			if ($this->Application->GetVar('simple_mode')) {
 				$event->SetRedirectParam('simple_mode', 1);
 			}
 		}
 
 		function _getPhraseId($phrase)
 		{
-			$sql = 'SELECT ' . $this->Application->getUnitOption($this->Prefix, 'IDField') . '
-					FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . '
+			$config = $this->getUnitConfig();
+
+			$sql = 'SELECT ' . $config->getIDField() . '
+					FROM ' . $config->getTableName() . '
 					WHERE PhraseKey = ' . $this->Conn->qstr( mb_strtoupper($phrase) );
 
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Sets phrase type based on place where it's created (to display on form only)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			parent::OnPreCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$this->_setPhraseModule($object);
 		}
 
 		/**
 		 * Forces new label in case if issued from get link
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnNew(kEvent $event)
 		{
 			parent::OnNew($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$label = $this->Application->GetVar($event->getPrefixSpecial() . '_label');
 
 			if ( $label ) {
 				// phrase is created in language, used to display phrases
 				$object->SetDBField('Phrase', $label);
 				$object->SetDBField('PhraseType', $this->_getPhraseType($label)); // to show on form
 				$object->SetDBField('PrimaryTranslation', $this->_getPrimaryTranslation($label));
 			}
 
 			$this->_setPhraseModule($object);
 
 			if ( $event->Special == 'export' || $event->Special == 'import' ) {
 				$object->SetDBField('PhraseType', '|0|1|2|');
 				$object->SetDBField('Module', '|' . implode('|', array_keys($this->Application->ModuleInfo)) . '|');
 
 				$export_mode = $this->Application->GetVar('export_mode');
 
 				if ( $export_mode != 'lang' ) {
 					$object->SetDBField('ExportDataTypes', '|' . $export_mode . '|');
 				}
 			}
 		}
 
 		/**
 		 * Returns given phrase translation on primary language
 		 *
 		 * @param string $phrase
 		 * @return string
 		 * @access protected
 		 */
 		protected function _getPrimaryTranslation($phrase)
 		{
 			$sql = 'SELECT l' . $this->Application->GetDefaultLanguageId() . '_Translation
-					FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . '
+					FROM ' . $this->getUnitConfig()->getTableName() . '
 					WHERE PhraseKey = ' . $this->Conn->qstr( mb_strtoupper($phrase) );
 
 			return $this->Conn->GetOne($sql);
 		}
 
 		/**
 		 * Sets new phrase module
 		 *
 		 * @param kDBItem $object
 		 * @return void
 		 * @access protected
 		 */
 		protected function _setPhraseModule(&$object)
 		{
 			$last_module = $this->Application->GetVar('last_module');
 
 			if ( $last_module ) {
 				$object->SetDBField('Module', $last_module);
 			}
 		}
 
 		/**
 		 * Forces create to use live table
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnCreate(kEvent $event)
 		{
 			if ( $this->Application->GetVar($event->Prefix . '_label') ) {
 				$object = $event->getObject(Array ('skip_autoload' => true));
 				/* @var $object kDBItem */
 
 				if ( $this->Application->GetVar('m_lang') != $this->Application->GetVar('lang_id') ) {
 					$object->SwitchToLive();
 				}
 
 				$this->returnToOriginalTemplate($event);
 			}
 
 			parent::OnCreate($event);
 		}
 
 		/**
 		 * Redirects to original template after phrase is being update
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnUpdate(kEvent $event)
 		{
 			if ( $this->Application->GetVar($event->Prefix . '_label') ) {
 				$this->returnToOriginalTemplate($event);
 			}
 
 			parent::OnUpdate($event);
 		}
 
 		/**
 		 * Returns to original template after phrase adding/editing
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function returnToOriginalTemplate(kEvent $event)
 		{
 			$next_template = $this->Application->GetVar('next_template');
 
 			if ( $next_template ) {
 				$event->redirect = $next_template;
 				$event->SetRedirectParam('opener', 's');
 			}
 		}
 
 		/**
 		 * Set last change info, when phrase is created
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$primary_language_id = $this->Application->GetDefaultLanguageId();
 
 			if ( !$object->GetDBField('l' . $primary_language_id . '_Translation') ) {
 				// no translation on primary language -> try to copy from other language
 				$src_languages = Array ('lang_id', 'm_lang'); // editable language, theme language
 
 				foreach ($src_languages as $src_language) {
 					$src_language = $this->Application->GetVar($src_language);
 					$src_value = $src_language ? $object->GetDBField('l' . $src_language . '_Translation') : false;
 
 					if ( $src_value ) {
 						$object->SetDBField('l' . $primary_language_id . '_Translation', $src_value);
 						break;
 					}
 				}
 			}
 
 			$this->_phraseChanged($event);
 		}
 
 		/**
 		 * Update last change info, when phrase is updated
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->_phraseChanged($event);
 		}
 
 		/**
 		 * Set's phrase key and last change info, used for phrase updating and loading
 		 *
 		 * @param kEvent $event
 		 */
 		function _phraseChanged($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$label = $object->GetDBField('Phrase');
 			$object->SetDBField('PhraseKey', mb_strtoupper($label));
 			$object->SetDBField('PhraseType', $this->_getPhraseType($label));
 
 			if ( $this->translationChanged($object) ) {
 				$object->SetDBField('LastChanged_date', adodb_mktime() );
 				$object->SetDBField('LastChanged_time', adodb_mktime() );
 				$object->SetDBField('LastChangeIP', $this->Application->getClientIp());
 			}
 
 			$this->Application->Session->SetCookie('last_module', $object->GetDBField('Module'));
 		}
 
 		/**
 		 * Returns phrase type, that corresponds given phrase label
 		 *
 		 * @param string $label
 		 * @return int
 		 * @access protected
 		 */
 		protected function _getPhraseType($label)
 		{
 			$phrase_type_map = Array (
 				'LU' => Language::PHRASE_TYPE_FRONT,
 				'LA' => Language::PHRASE_TYPE_ADMIN,
 				'LC' => Language::PHRASE_TYPE_COMMON
 			);
 
 			$label = mb_strtoupper($label);
 			$label_prefix = substr($label, 0, 2);
 
 			return isset($phrase_type_map[$label_prefix]) ? $phrase_type_map[$label_prefix] : Language::PHRASE_TYPE_COMMON;
 		}
 
 		/**
 		 * Checks, that at least one of phrase's translations was changed
 		 *
 		 * @param kDBItem $object
 		 * @return bool
 		 */
 		function translationChanged(&$object)
 		{
 			$translation_fields = $this->getTranslationFields();
 			$changed_fields = array_keys( $object->GetChangedFields() );
 
 			foreach ($changed_fields as $changed_field) {
 				$changed_field = preg_replace('/^l[\d]+_/', '', $changed_field);
 
 				if ( in_array($changed_field, $translation_fields) ) {
 					return true;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Returns fields, that can be translated
 		 *
 		 * @return Array
 		 * @access protected
 		 */
 		protected function getTranslationFields()
 		{
 			return Array ('Translation', 'HintTranslation', 'ColumnTranslation');
 		}
 
 		/**
 		 * Put correct translation to source language fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$translation_fields = $this->getTranslationFields();
 			$source_language = $ml_helper->getSourceLanguage($object->GetDBField('TranslateFromLanguage'));
 
 			foreach ($translation_fields as $translation_field) {
 				$object->SetDBField('Source' . $translation_field, $object->GetDBField('l' . $source_language . '_' . $translation_field));
 			}
 		}
 
 		/**
 		 * Changes default module to custom (when available)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			if ( $this->Application->findModule('Name', 'Custom') ) {
-				$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
+				$config = $event->getUnitConfig();
+
+				$fields = $config->getFields();
 				$fields['Module']['default'] = 'Custom';
-				$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
+				$config->setFields($fields);
 			}
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$ml_helper->replaceMLCalculatedFields($event);
 
 			if ( $this->Application->GetVar('regional') ) {
-				$this->Application->setUnitOption($event->Prefix, 'PopulateMlFields', true);
+				$event->getUnitConfig()->setPopulateMlFields(true);
 			}
 		}
 
 		/**
 		 * Saves changes & changes language
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreSaveAndChangeLanguage(kEvent $event)
 		{
 			$label = $this->Application->GetVar($event->getPrefixSpecial() . '_label');
 
 			if ( $label && !$this->UseTempTables($event) ) {
 				$phrase_id = $this->_getPhraseId($label);
 
 				if ( $phrase_id ) {
 					$event->CallSubEvent('OnUpdate');
 					$event->SetRedirectParam('opener', 's');
 				}
 				else {
 					$event->CallSubEvent('OnCreate');
 					$event->SetRedirectParam('opener', 's');
 				}
 
 				if ( $event->status != kEvent::erSUCCESS ) {
 					return;
 				}
 
 				$event->SetRedirectParam($event->getPrefixSpecial() . '_event', 'OnPreparePhrase');
 				$event->SetRedirectParam('pass_events', true);
 			}
 
 			if ( $this->Application->GetVar('simple_mode') ) {
 				$event->SetRedirectParam('simple_mode', 1);
 			}
 
 			parent::OnPreSaveAndChangeLanguage($event);
 		}
 
 		/**
 		 * Prepare temp tables and populate it
 		 * with items selected in the grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnEdit(kEvent $event)
 		{
 			parent::OnEdit($event);
 
 			// use language from grid, instead of primary language used by default
 			$event->SetRedirectParam('m_lang', $this->Application->GetVar('m_lang'));
 		}
 
 		/**
 		 * Stores ids of selected phrases and redirects to export language step 1
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnExportPhrases(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
-			$this->Application->setUnitOption('phrases', 'AutoLoad', false);
+			$this->Application->getUnitConfig('phrases')->setAutoLoad(false);
 
 			$this->StoreSelectedIDs($event);
 			$this->Application->StoreVar('export_language_ids', $this->Application->GetVar('m_lang'));
 
 			$event->setRedirectParams(
 				Array (
 					'phrases.export_event' => 'OnNew',
 					'pass' => 'all,phrases.export',
 					'export_mode' => $event->Prefix,
 				)
 			);
 		}
 
 		/**
 		 * Updates translation state for all saved phrases
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeCopyToLive(kEvent $event)
 		{
 			parent::OnBeforeCopyToLive($event);
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$ml_helper->updateTranslationState($event);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/phrases/phrase_tp.php
===================================================================
--- branches/5.3.x/core/units/phrases/phrase_tp.php	(revision 15697)
+++ branches/5.3.x/core/units/phrases/phrase_tp.php	(revision 15698)
@@ -1,115 +1,115 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class PhraseTagProcessor extends kDBTagProcessor {
 
 	/**
 	 * Determines, that we can close phrase editing form without parent window refreshing
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function UseQuickFormCancel($params)
 	{
 		return $this->Application->GetVar('simple_mode') && (int)$this->Application->ConfigValue('UsePopups');
 	}
 
 	/**
 	 * Returns phrase count for given module
 	 *
 	 * @param Array $params
 	 * @return int
 	 * @access protected
 	 */
 	protected function PhraseCount($params)
 	{
 		static $cache = null;
 
 		if (!isset($cache)) {
 			$sql = 'SELECT COUNT(*), Module
-					FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . '
+					FROM ' . $this->getUnitConfig()->getTableName() . '
 					GROUP BY Module';
 			$cache = $this->Conn->GetCol($sql, 'Module');
 		}
 
 		$module = $params['module'];
 
 		return array_key_exists($module, $cache) ? $cache[$module] : 0;
 	}
 
 	/**
 	 * Returns e-mail template count for given module
 	 *
 	 * @param Array $params
 	 * @return int
 	 * @access protected
 	 */
 	protected function TemplateCount($params)
 	{
 		static $cache = null;
 
-		if ( !isset($cache) ) {
+		if (!isset($cache)) {
 			$sql = 'SELECT COUNT(*), IF(Module LIKE "Core:%", "Core", Module) AS Module
-					FROM ' . $this->Application->getUnitOption('email-template', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('email-template')->getTableName() . '
 					GROUP BY Module';
 			$cache = $this->Conn->GetCol($sql, 'Module');
 		}
 
 		$module = $params['module'];
 
 		return array_key_exists($module, $cache) ? $cache[$module] : 0;
 	}
 
 	/**
 	 * Determine if primary translation should be shown
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function ShowSourceLanguage($params)
 	{
 		if ( $this->IsNewItem($params) ) {
 			return false;
 		}
 
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 		/* @var $ml_helper kMultiLanguageHelper */
 
 		return !$ml_helper->editingInSourceLanguage($object->GetDBField('TranslateFromLanguage'));
 	}
 
 	/**
 	 * Shows field label with %s replaced with source translation language
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function SourceLanguageTitle($params)
 	{
 		$object = $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 		/* @var $ml_helper kMultiLanguageHelper */
 
 		return $ml_helper->replaceSourceLanguage($object, $params['label']);
 	}
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/email_templates/email_template_eh.php
===================================================================
--- branches/5.3.x/core/units/email_templates/email_template_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/email_templates/email_template_eh.php	(revision 15698)
@@ -1,756 +1,756 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class EmailTemplateEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnFrontOnly' => Array ('self' => 'edit'),
 				'OnSaveSelected' => Array ('self' => 'view'),
 				'OnProcessEmailQueue' => Array ('self' => 'add|edit'),
 				'OnExportEmailTemplates' => Array ('self' => 'view'),
 
 				'OnSuggestAddress' => Array ('self' => 'add|edit'),
 
 				// events only for developers
 				'OnPreCreate' => Array ('self' => 'debug'),
 				'OnDelete' => Array ('self' => 'debug'),
 				'OnDeleteAll' => Array ('self' => 'debug'),
 				'OnMassDelete' => Array ('self' => 'debug'),
 				'OnMassApprove' => Array ('self' => 'debug'),
 				'OnMassDecline' => Array ('self' => 'debug'),
 				'OnSend' => Array ('self' => 'debug'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Changes permission section to one from REQUEST, not from config
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			$module = $this->Application->GetVar('module');
 
 			if ( strlen($module) > 0 ) {
 				// checking permission when lising module email events in separate section
 				$module = explode(':', $module, 2);
 
 				if ( count($module) == 1 ) {
 					$main_prefix = $this->Application->findModule('Name', $module[0], 'Var');
 				}
 				else {
 					$exceptions = Array ('Category' => 'c', 'Users' => 'u');
 					$main_prefix = $exceptions[$module[1]];
 				}
-				$section = $this->Application->getUnitOption($main_prefix . '.email', 'PermSection');
+				$section = $this->Application->getUnitConfig($main_prefix)->getPermSectionByName('email');
 
 				$event->setEventParam('PermSection', $section);
 			}
 
 			// checking permission when listing all email events when editing language
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			if ( $event->Special == 'module' ) {
 				$module = $this->Application->GetVar('module');
 				$object->addFilter('module_filter', '%1$s.Module = ' . $this->Conn->qstr($module));
 			}
 			else {
 				$object->addFilter('module_filter', '%1$s.Module IN (SELECT Name FROM ' . TABLE_PREFIX . 'Modules WHERE Loaded = 1)');
 			}
 
 			if ( !$event->Special && !$this->Application->isDebugMode() ) {
 				// no special
 				$object->addFilter('enabled_filter', '%1$s.Enabled <> ' . STATUS_DISABLED);
 			}
 		}
 
 		/**
 		 * Prepares new kDBItem object
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnNew(kEvent $event)
 		{
 			parent::OnNew($event);
 
 			$mapping = Array ('conf' => 'VariableValue', 'site-domain' => 'DefaultEmailRecipients');
 
 			if ( isset($mapping[$event->Special]) ) {
 				$object = $event->getObject();
 				/* @var $object kDBItem */
 
 				$target_object = $this->Application->recallObject($event->Special);
 				/* @var $target_object kDBList */
 
 				$object->SetDBField('Recipients', $target_object->GetDBField($mapping[$event->Special]));
 			}
 		}
 
 		/**
 		 * Set default headers
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			parent::OnPreCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('Headers', $this->Application->ConfigValue('Smtp_DefaultHeaders'));
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Sets status Front-End Only to selected email events
 		 *
 		 * @param kEvent $event
 		 */
 		function OnFrontOnly($event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
+			$config = $event->getUnitConfig();
 
-			$sql = 'UPDATE ' . $table_name . '
+			$sql = 'UPDATE ' . $config->getTableName() . '
 					SET FrontEndOnly = 1
-					WHERE ' . $id_field . ' IN (' . implode(',', $this->StoreSelectedIDs($event)) . ')';
+					WHERE ' . $config->getIDField() . ' IN (' . implode(',', $this->StoreSelectedIDs($event)) . ')';
 			$this->Conn->Query($sql);
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Sets selected user to email events selected
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSelectUser(kEvent $event)
 		{
 			if ( $event->Special != 'module' ) {
 				parent::OnSelectUser($event);
 				return;
 			}
 
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$items_info = $this->Application->GetVar('u');
 			if ( $items_info ) {
 				list ($user_id, ) = each($items_info);
 
+				$config = $event->getUnitConfig();
 				$ids = $this->Application->RecallVar($event->getPrefixSpecial() . '_selected_ids');
-				$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
-				$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
 
-				$sql = 'UPDATE ' . $table_name . '
+				$sql = 'UPDATE ' . $config->getTableName() . '
 						SET ' . $this->Application->RecallVar('dst_field') . ' = ' . $user_id . '
-						WHERE ' . $id_field . ' IN (' . $ids . ')';
+						WHERE ' . $config->getIDField() . ' IN (' . $ids . ')';
 				$this->Conn->Query($sql);
 			}
 
 			$this->finalizePopup($event);
 		}
 
 		/**
 		 * Saves selected ids to session
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveSelected($event)
 		{
 			$this->StoreSelectedIDs($event);
 		}
 
 		/**
 		 * Process emails from queue
 		 *
 		 * @param kEvent $event
 		 * @todo Move to MailingList
 		 */
 		function OnProcessEmailQueue($event)
 		{
 			$deliver_count = $event->getEventParam('deliver_count');
 			if ( $deliver_count === false ) {
 				$deliver_count = $this->Application->ConfigValue('MailingListSendPerStep');
 				if ( $deliver_count === false ) {
 					$deliver_count = 10; // 10 emails per script run (if not specified directly)
 				}
 			}
 
 			$processing_type = $this->Application->GetVar('type');
 			if ( $processing_type = 'return_progress' ) {
 				$email_queue_progress = $this->Application->RecallVar('email_queue_progress');
 				if ( $email_queue_progress === false ) {
 					$emails_sent = 0;
 					$sql = 'SELECT COUNT(*)
 							FROM ' . TABLE_PREFIX . 'EmailQueue
 							WHERE (SendRetries < 5) AND (LastSendRetry < ' . strtotime('-2 hours') . ')';
 					$total_emails = $this->Conn->GetOne($sql);
 					$this->Application->StoreVar('email_queue_progress', $emails_sent . ':' . $total_emails);
 				}
 				else {
 					list ($emails_sent, $total_emails) = explode(':', $email_queue_progress);
 				}
 			}
 
 			$sql = 'SELECT *
 					FROM ' . TABLE_PREFIX . 'EmailQueue
 					WHERE (SendRetries < 5) AND (LastSendRetry < ' . strtotime('-2 hours') . ')
 					LIMIT 0,' . $deliver_count;
 			$messages = $this->Conn->Query($sql);
 
 			$message_count = count($messages);
 			if ( !$message_count ) {
 				// no messages left to send in queue
 				if ( $processing_type = 'return_progress' ) {
 					$this->Application->RemoveVar('email_queue_progress');
 					$this->Application->Redirect($this->Application->GetVar('finish_template'));
 				}
 				return;
 			}
 
 			$mailing_list_helper = $this->Application->recallObject('MailingListHelper');
 			/* @var $mailing_list_helper MailingListHelper */
 
 			$mailing_list_helper->processQueue($messages);
 
 			if ( $processing_type = 'return_progress' ) {
 				$emails_sent += $message_count;
 				if ( $emails_sent >= $total_emails ) {
 					$this->Application->RemoveVar('email_queue_progress');
 					$this->Application->Redirect($this->Application->GetVar('finish_template'));
 				}
 
 				$this->Application->StoreVar('email_queue_progress', $emails_sent . ':' . $total_emails);
 				$event->status = kEvent::erSTOP;
 				echo ($emails_sent / $total_emails) * 100;
 			}
 		}
 
 		/**
 		 * Prefills module dropdown
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			$options = Array ();
 
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				if ( $module_name == 'In-Portal' ) {
 					continue;
 				}
 
 				$options[$module_name] = $module_name;
 			}
 
-			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
+			$config = $event->getUnitConfig();
+
+			$fields = $config->getFields();
 			$fields['Module']['options'] = $options;
-			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
+			$config->setFields($fields);
 
 			if ( $this->Application->GetVar('regional') ) {
-				$this->Application->setUnitOption($event->Prefix, 'PopulateMlFields', true);
+				$config->setPopulateMlFields(true);
 			}
 		}
 
 		/**
 		 * Prepare temp tables and populate it
 		 * with items selected in the grid
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnEdit(kEvent $event)
 		{
 			parent::OnEdit($event);
 
 			// use language from grid, instead of primary language used by default
 			$event->SetRedirectParam('m_lang', $this->Application->GetVar('m_lang'));
 		}
 
 		/**
 		 * Fixes default recipient type
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$this->Application->isDebugMode(false) ) {
 				if ( $object->GetDBField('AllowChangingRecipient') ) {
 					$object->SetDBField('RecipientType', EmailTemplate::RECIPIENT_TYPE_TO);
 				}
 				else {
 					$object->SetDBField('RecipientType', EmailTemplate::RECIPIENT_TYPE_CC);
 				}
 			}
 
 			// process replacement tags
 			$records = Array ();
 			$replacement_tags = $object->GetDBField('ReplacementTags');
 			$replacement_tags = $replacement_tags ? unserialize($replacement_tags) : Array ();
 
 			foreach ($replacement_tags as $tag => $replacement) {
 				$records[] = Array ('Tag' => $tag, 'Replacement' => $replacement);
 			}
 
 			$minput_helper = $this->Application->recallObject('MInputHelper');
 			/* @var $minput_helper MInputHelper */
 
 			$xml = $minput_helper->prepareMInputXML($records, Array ('Tag', 'Replacement'));
 			$object->SetDBField('ReplacementTagsXML', $xml);
 
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Performs custom validation + keep read-only fields
 		 *
 		 * @param kEvent $event
 		 */
 		function _itemChanged($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$this->Application->isDebugMode(false) ) {
 				// only allow to enable/disable event while in debug mode
 				$to_restore = Array ('Enabled', 'AllowChangingSender', 'AllowChangingRecipient');
 
 				if ( !$object->GetOriginalField('AllowChangingSender') ) {
 					$to_restore = array_merge($to_restore, Array ('CustomSender', 'SenderName', 'SenderAddressType', 'SenderAddress'));
 				}
 
 				if ( !$object->GetOriginalField('AllowChangingRecipient') ) {
 					$to_restore = array_merge($to_restore, Array ('CustomRecipient' /*, 'Recipients'*/));
 				}
 
 				// prevent specific fields from editing
 				foreach ($to_restore as $restore_field) {
 					$original_value = $object->GetOriginalField($restore_field);
 
 					if ( $object->GetDBField($restore_field) != $original_value ) {
 						$object->SetDBField($restore_field, $original_value);
 					}
 				}
 			}
 
 			// process replacement tags
 			if ( $object->GetDBField('ReplacementTagsXML') ) {
 				$minput_helper = $this->Application->recallObject('MInputHelper');
 				/* @var $minput_helper MInputHelper */
 
 				$replacement_tags = Array ();
 				$records = $minput_helper->parseMInputXML($object->GetDBField('ReplacementTagsXML'));
 
 				foreach ($records as $record) {
 					$replacement_tags[trim($record['Tag'])] = trim($record['Replacement']);
 				}
 
 				$object->SetDBField('ReplacementTags', $replacement_tags ? serialize($replacement_tags) : NULL);
 			}
 
 			if ( $this->translationChanged($object) ) {
 				$object->SetDBField('LastChanged_date', TIMENOW);
 				$object->SetDBField('LastChanged_time', TIMENOW);
 			}
 
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Dynamically changes required fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function setRequired(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$language_prefix = 'l' . $this->Application->GetVar('m_lang') . '_';
 
 			$object->setRequired($language_prefix . 'HtmlBody', !$object->GetField('PlainTextBody'));
 			$object->setRequired($language_prefix . 'PlainTextBody', !$object->GetField('HtmlBody'));
 		}
 
 		/**
 		 * Checks, that at least one of phrase's translations was changed
 		 *
 		 * @param kDBItem $object
 		 * @return bool
 		 */
 		function translationChanged($object)
 		{
 			$changed_fields = array_keys($object->GetChangedFields());
 			$translation_fields = Array ('Subject', 'HtmlBody', 'PlainTextBody');
 
 			foreach ($changed_fields as $changed_field) {
 				$changed_field = preg_replace('/^l[\d]+_/', '', $changed_field);
 
 				if ( in_array($changed_field, $translation_fields) ) {
 					return true;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Don't allow to enable/disable events in non-debug mode
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$this->_itemChanged($event);
 		}
 
 		/**
 		 * Don't allow to enable/disable events in non-debug mode
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->_itemChanged($event);
 		}
 
 		/**
 		 * Suggest address based on typed address and selected address type
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSuggestAddress($event)
 		{
 			$event->status = kEvent::erSTOP;
 
 			$address_type = $this->Application->GetVar('type');
 			$address = $this->Application->GetVar('value');
 			$limit = $this->Application->GetVar('limit');
 
 			if ( !$limit ) {
 				$limit = 20;
 			}
 
 			switch ($address_type) {
 				case EmailTemplate::ADDRESS_TYPE_EMAIL:
 					$field = 'Email';
 					$table_name = TABLE_PREFIX . 'Users';
 					break;
 
 				case EmailTemplate::ADDRESS_TYPE_USER:
 					$field = 'Username';
 					$table_name = TABLE_PREFIX . 'Users';
 					break;
 
 				case EmailTemplate::ADDRESS_TYPE_GROUP:
 					$field = 'Name';
 					$table_name = TABLE_PREFIX . 'UserGroups';
 					break;
 
 				default:
 					$field = $table_name = '';
 					break;
 			}
 
 			if ( $field ) {
 				$sql = 'SELECT DISTINCT ' . $field . '
 						FROM ' . $table_name . '
 						WHERE ' . $field . ' LIKE ' . $this->Conn->qstr($address . '%') . '
 						ORDER BY ' . $field . ' ASC
 						LIMIT 0,' . $limit;
 				$data = $this->Conn->GetCol($sql);
 			}
 			else {
 				$data = Array ();
 			}
 
 			$this->Application->XMLHeader();
 
 			echo '<suggestions>';
 
 			foreach ($data as $item) {
 				echo '<item>' . htmlspecialchars($item, null, CHARSET) . '</item>';
 			}
 
 			echo '</suggestions>';
 		}
 
 		/**
 		 * Does custom validation
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemValidate(kEvent $event)
 		{
 			parent::OnBeforeItemValidate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// validate email subject and body for parsing errors
 			$this->_validateEmailTemplate($object);
 
 			// validate sender and recipient addresses
 			if ( $object->GetDBField('CustomSender') ) {
 				$this->_validateAddress($event, 'Sender');
 			}
 
 			$this->_validateAddress($event, 'Recipient');
 			$this->_validateBindEvent($object);
 		}
 
 		/**
 		 * Validates subject and body fields of Email template
 		 *
 		 * @param kDBItem $object
 		 * @return void
 		 * @access protected
 		 */
 		protected function _validateEmailTemplate($object)
 		{
 			$email_template_helper = $this->Application->recallObject('kEmailTemplateHelper');
 			/* @var $email_template_helper kEmailTemplateHelper */
 
 			$email_template_helper->parseField($object, 'Subject');
 			$email_template_helper->parseField($object, 'HtmlBody');
 			$email_template_helper->parseField($object, 'PlainTextBody');
 		}
 
 		/**
 		 * Validates address using given field prefix
 		 *
 		 * @param kEvent $event
 		 * @param string $field_prefix
 		 * @return void
 		 * @access protected
 		 */
 		protected function _validateAddress($event, $field_prefix)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$address_type = $object->GetDBField($field_prefix . 'AddressType');
 			$object->setRequired($field_prefix . 'Address', $address_type > 0);
 			$address = $object->GetDBField($field_prefix . 'Address');
 
 			if ( !$address ) {
 				// don't validate against empty address
 				return;
 			}
 
 			switch ($address_type) {
 				case EmailTemplate::ADDRESS_TYPE_EMAIL:
 					if ( !preg_match('/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i', $address) ) {
 						$object->SetError($field_prefix . 'Address', 'invalid_email');
 					}
 					break;
 
 				case EmailTemplate::ADDRESS_TYPE_USER:
 					$sql = 'SELECT PortalUserId
 							FROM ' . TABLE_PREFIX . 'Users
 							WHERE Username = ' . $this->Conn->qstr($address);
 					if ( !$this->Conn->GetOne($sql) ) {
 						$object->SetError($field_prefix . 'Address', 'invalid_user');
 					}
 					break;
 
 				case EmailTemplate::ADDRESS_TYPE_GROUP:
 					$sql = 'SELECT GroupId
 							FROM ' . TABLE_PREFIX . 'UserGroups
 							WHERE Name = ' . $this->Conn->qstr($address);
 					if ( !$this->Conn->GetOne($sql) ) {
 						$object->SetError($field_prefix . 'Address', 'invalid_group');
 					}
 					break;
 			}
 		}
 
 		/**
 		 * Checks that bind event is specified in correct format and exists
 		 *
 		 * @param kDBItem $object
 		 */
 		protected function _validateBindEvent($object)
 		{
 			$event_string = $object->GetDBField('BindToSystemEvent');
 
 			if ( !$event_string ) {
 				return;
 			}
 
 			try {
 				$this->Application->eventImplemented(new kEvent($event_string));
 			}
 			catch (Exception $e) {
 				$object->SetError('BindToSystemEvent', 'invalid_event', '+' . $e->getMessage());
 			}
 		}
 
 		/**
 		 * Stores ids of selected phrases and redirects to export language step 1
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnExportEmailTemplates(kEvent $event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
-			$this->Application->setUnitOption('phrases', 'AutoLoad', false);
+			$this->Application->getUnitConfig('phrases')->setAutoLoad(false);
 
 			$this->StoreSelectedIDs($event);
 			$this->Application->StoreVar('export_language_ids', $this->Application->GetVar('m_lang'));
 
 			$event->setRedirectParams(
 				Array (
 					'phrases.export_event' => 'OnNew',
 					'pass' => 'all,phrases.export',
 					'export_mode' => $event->Prefix,
 				)
 			);
 		}
 
 		/**
 		 * Deletes all subscribers to e-mail event after it was deleted
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$sql = 'SELECT SubscriptionId
 					FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions
 					WHERE EmailTemplateId = ' . $object->GetID();
 			$ids = $this->Conn->GetCol($sql);
 
 			if ( !$ids ) {
 				return;
 			}
 
 			$temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems('system-event-subscription', '', $ids);
 		}
 
 		/**
 		 * Sends selected e-mail event
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSend(kEvent $event)
 		{
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$ids = $this->StoreSelectedIDs($event);
 
 			foreach ($ids as $id) {
 				$object->Load($id);
 
 				if ( $object->GetDBField('Type') == EmailTemplate::TEMPLATE_TYPE_ADMIN ) {
 					$this->Application->emailAdmin($object->GetDBField('TemplateName'));
 				}
 				else {
 					$this->Application->emailUser($object->GetDBField('TemplateName'));
 				}
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/email_templates/email_template_tp.php
===================================================================
--- branches/5.3.x/core/units/email_templates/email_template_tp.php	(revision 15697)
+++ branches/5.3.x/core/units/email_templates/email_template_tp.php	(revision 15698)
@@ -1,101 +1,102 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2010 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class EmailTemplateTagProcessor extends kDBTagProcessor
 	{
 		/**
 		 * Removes "Enabled" column, when not in debug mode
 		 *
 		 * @param Array $params
 		 */
 		function ModifyUnitConfig($params)
 		{
 			if ( !$this->Application->isDebugMode() ) {
-				$grids = $this->Application->getUnitOption($this->Prefix, 'Grids', Array ());
-				/* @var $grids Array */
+				$config = $this->getUnitConfig();
+				$grids = $config->getGrids(Array ());
 
 				foreach ($grids as $grid_name => $grid_data) {
 					if ( array_key_exists('Enabled', $grid_data['Fields']) ) {
 						unset($grids[$grid_name]['Fields']['Enabled']);
 					}
 				}
 
-				$this->Application->setUnitOption($this->Prefix, 'Grids', $grids);
+				$config->setGrids($grids);
 			}
 		}
 
 		/**
 		 * Checks, that field can be edited
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function IsEditable($params)
 		{
 			if ($this->Application->isDebugMode()) {
 				return true;
 			}
 
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			return $object->GetDBField($params['check_field']);
 		}
 
 		/**
 		 * To recipient read-only
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access protected
 		 */
 		protected function ToRecipientReadOnly($params)
 		{
 			return !$this->IsEditable(Array ('check_field' => 'AllowChangingRecipient'));
 		}
 
 		/**
 		 * Removes "To" options from possible options in "RecipientType" field
 		 *
 		 * @param Array $params
 		 */
 		function RemoveToRecipientType($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$field_options = $object->GetFieldOptions('RecipientType');
 			unset($field_options['options'][ EmailTemplate::RECIPIENT_TYPE_TO ]);
 			$object->SetFieldOptions('RecipientType', $field_options);
 		}
 
 		/**
 		 * Restores "To" option in possible option list in "RecipientType" field
 		 *
 		 * @param Array $params
 		 */
 		function RestoreRecipientType($params)
 		{
 			$object = $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$field_options = $object->GetFieldOptions('RecipientType');
-			$virtual_fields = $this->Application->getUnitOption($this->Prefix, 'VirtualFields');
-			$field_options['options'] = $virtual_fields['RecipientType']['options'];
+			$virtual_field_options = $this->getUnitConfig()->getVirtualFieldByName('RecipientType');
+
+			$field_options['options'] = $virtual_field_options['options'];
 			$object->SetFieldOptions('RecipientType', $field_options);
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/languages/languages_event_handler.php
===================================================================
--- branches/5.3.x/core/units/languages/languages_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/languages/languages_event_handler.php	(revision 15698)
@@ -1,792 +1,794 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class LanguagesEventHandler extends kDBEventHandler
 	{
 		/**
 		 * Allows to override standard permission mapping
 		 *
 		 * @return void
 		 * @access protected
 		 * @see kEventHandler::$permMapping
 		 */
 		protected function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnChangeLanguage' => Array ('self' => true),
 				'OnSetPrimary' => Array ('self' => 'advanced:set_primary|add|edit'),
 				'OnImportLanguage' => Array ('self' => 'advanced:import'),
 				'OnExportLanguage' => Array ('self' => 'advanced:export'),
 				'OnExportProgress' => Array ('self' => 'advanced:export'),
 				'OnReflectMultiLingualFields' => Array ('self' => 'view'),
 				'OnSynchronizeLanguages' => Array ('self' => 'edit'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( $event->Name == 'OnItemBuild' ) {
 				// check permission without using $event->getSection(),
 				// so first cache rebuild won't lead to "ldefault_Name" field being used
 				return true;
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Allows to get primary language object
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access public
 		 */
 		public function getPassedID(kEvent $event)
 		{
 			if ( $event->Special == 'primary' ) {
 				return $this->Application->GetDefaultLanguageId();
 			}
 
 			return parent::getPassedID($event);
 		}
 
 		/**
 		 * [HOOK] Updates table structure on new language adding/removing language
 		 *
 		 * @param kEvent $event
 		 */
 		function OnReflectMultiLingualFields($event)
 		{
 			if ($this->Application->GetVar('ajax') == 'yes') {
 				$event->status = kEvent::erSTOP;
 			}
 
 			if (is_object($event->MasterEvent)) {
 				if ($event->MasterEvent->status != kEvent::erSUCCESS) {
 					// only rebuild when all fields are validated
 					return ;
 				}
 
 				if (($event->MasterEvent->Name == 'OnSave') && !$this->Application->GetVar('new_language')) {
 					// only rebuild during new language adding
 					return ;
 				}
 			}
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$ml_helper->massCreateFields();
 			$event->SetRedirectParam('action_completed', 1);
 		}
 
 		/**
 		 * Allows to set selected language as primary
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSetPrimary($event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$this->StoreSelectedIDs($event);
 			$ids = $this->getSelectedIDs($event);
 			if ($ids) {
 				$id = array_shift($ids);
 				$object = $event->getObject( Array('skip_autoload' => true) );
 				/* @var $object LanguagesItem */
 
 				$object->Load($id);
 				$object->copyMissingData( $object->setPrimary() );
 			}
 		}
 
 		/**
 		 * [HOOK] Reset primary status of other languages if we are saving primary language
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUpdatePrimary($event)
 		{
 			if ($event->MasterEvent->status != kEvent::erSUCCESS) {
 				return ;
 			}
 
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object LanguagesItem */
 
 			$object->SwitchToLive();
 
 			// set primary for each languages, that have this checkbox checked
 			$ids = explode(',', $event->MasterEvent->getEventParam('ids'));
 			foreach ($ids as $id) {
 				$object->Load($id);
 				if ($object->GetDBField('PrimaryLang')) {
 					$object->copyMissingData( $object->setPrimary(true, false) );
 				}
 
 				if ($object->GetDBField('AdminInterfaceLang')) {
 					$object->setPrimary(true, true);
 				}
 			}
 
 			// if no primary language left, then set primary last language (not to load again) from edited list
 			$sql = 'SELECT '.$object->IDField.'
 					FROM '.$object->TableName.'
 					WHERE PrimaryLang = 1';
 			$primary_language = $this->Conn->GetOne($sql);
 
 			if (!$primary_language) {
 				$object->setPrimary(false, false); // set primary language
 			}
 
 			$sql = 'SELECT '.$object->IDField.'
 					FROM '.$object->TableName.'
 					WHERE AdminInterfaceLang = 1';
 			$primary_language = $this->Conn->GetOne($sql);
 
 			if (!$primary_language) {
 				$object->setPrimary(false, true); // set admin interface language
 			}
 		}
 
 		/**
 		 * Prefills options with dynamic values
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
-			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
+			$this->_evaluateFieldFormats($event, 'InputDateFormat');
+			$this->_evaluateFieldFormats($event, 'InputTimeFormat');
+		}
 
-			// set dynamic hints for options in date format fields
-			$options = $fields['InputDateFormat']['options'];
-			if ($options) {
-				foreach ($options as $i => $v) {
-					$options[$i] = $v . ' (' . adodb_date($i) . ')';
-				}
-				$fields['InputDateFormat']['options'] = $options;
-			}
+		/**
+		 * Set dynamic hints for options in date format fields
+		 *
+		 * @param kEvent $event
+		 * @param string $field
+		 * @return void
+		 * @access protected
+		 */
+		protected function _evaluateFieldFormats(kEvent $event, $field)
+		{
+			$config = $event->getUnitConfig();
+			$field_options = $config->getFieldByName($field);
 
-			$options = $fields['InputTimeFormat']['options'];
-			if ($options) {
-				foreach ($options as $i => $v) {
-					$options[$i] = $v . ' (' . adodb_date($i) . ')';
-				}
-				$fields['InputTimeFormat']['options'] = $options;
+			foreach ($field_options['options'] as $option_key => $option_title) {
+				$field_options['options'][$option_key] .= ' (' . adodb_date($option_key) . ')';
 			}
 
-			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
+			$config->addFields($field_options, $field);
 		}
 
 		/**
 		 * Occurs before creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$this->_itemChanged($event);
 		}
 
 		/**
 		 * Occurs before updating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
-			$status_fields = $this->Application->getUnitOption($event->Prefix, 'StatusField');
-			$status_field = array_shift($status_fields);
+			$status_field = $event->getUnitConfig()->getStatusField(true);
 
 			if ( $object->GetDBField('PrimaryLang') == 1 && $object->GetDBField($status_field) == 0 ) {
 				$object->SetDBField($status_field, 1);
 			}
 
 			$this->_itemChanged($event);
 		}
 
 
 		/**
 		 * Dynamically changes required fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function _itemChanged(kEvent $event)
 		{
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Dynamically changes required fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemValidate(kEvent $event)
 		{
 			parent::OnBeforeItemValidate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$email_template_helper = $this->Application->recallObject('kEmailTemplateHelper');
 			/* @var $email_template_helper kEmailTemplateHelper */
 
 			$email_template_helper->parseField($object, 'HtmlEmailTemplate');
 			$email_template_helper->parseField($object, 'TextEmailTemplate');
 
 			$check_field = $object->GetDBField('TextEmailTemplate') ? 'TextEmailTemplate' : 'HtmlEmailTemplate';
 			$check_value = $object->GetDBField($check_field);
 
 			if ( $check_value && strpos($check_value, '$body') === false ) {
 				$object->SetError($check_field, 'body_missing');
 			}
 		}
 
 		/**
 		 * Dynamically changes required fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function setRequired(kEvent $event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->setRequired('HtmlEmailTemplate', !$object->GetDBField('TextEmailTemplate'));
 			$object->setRequired('TextEmailTemplate', !$object->GetDBField('HtmlEmailTemplate'));
 		}
 
 		/**
 		 * Shows only enabled languages on front
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(kEvent $event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBList */
 
 			if ( in_array($event->Special, Array ('enabled', 'selected', 'available')) ) {
 				$object->addFilter('enabled_filter', '%1$s.Enabled = ' . STATUS_ACTIVE);
 			}
 
 			// site domain language picker
 			if ( $event->Special == 'selected' || $event->Special == 'available' ) {
 				$edit_picker_helper = $this->Application->recallObject('EditPickerHelper');
 				/* @var $edit_picker_helper EditPickerHelper */
 
 				$edit_picker_helper->applyFilter($event, 'Languages');
 			}
 
 			// apply domain-based language filtering
 			$languages = $this->Application->siteDomainField('Languages');
 
 			if ( strlen($languages) ) {
 				$languages = explode('|', substr($languages, 1, -1));
 				$object->addFilter('domain_filter', '%1$s.LanguageId IN (' . implode(',', $languages) . ')');
 			}
 		}
 
 		/**
 		 * Copy labels from another language
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(kEvent $event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$src_language = $object->GetDBField('CopyFromLanguage');
 
 			if ( $object->GetDBField('CopyLabels') && $src_language ) {
 				$dst_language = $object->GetID();
 
 				// 1. schedule data copy after OnSave event is executed
 				$var_name = $event->getPrefixSpecial() . '_copy_data' . $this->Application->GetVar('m_wid');
 				$pending_actions = $this->Application->RecallVar($var_name, Array ());
 
 				if ( $pending_actions ) {
 					$pending_actions = unserialize($pending_actions);
 				}
 
 				$pending_actions[$src_language] = $dst_language;
 				$this->Application->StoreVar($var_name, serialize($pending_actions));
 				$object->SetDBField('CopyLabels', 0);
 			}
 		}
 
 		/**
 		 * Saves language from temp table to live
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSave(kEvent $event)
 		{
 			parent::OnSave($event);
 
 			if ( $event->status != kEvent::erSUCCESS ) {
 				return;
 			}
 
 			$var_name = $event->getPrefixSpecial() . '_copy_data' . $this->Application->GetVar('m_wid');
 			$pending_actions = $this->Application->RecallVar($var_name, Array ());
 
 			if ( $pending_actions ) {
 				$pending_actions = unserialize($pending_actions);
 			}
 
 			// create multilingual columns for phrases & email events table first (actual for 6+ language)
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$ml_helper->createFields('phrases');
 			$ml_helper->createFields('email-template');
 
 			foreach ($pending_actions as $src_language => $dst_language) {
 				// phrases import
-				$sql = 'UPDATE ' . $this->Application->getUnitOption('phrases', 'TableName') . '
+				$sql = 'UPDATE ' . $this->Application->getUnitConfig('phrases')->getTableName() . '
 						SET l' . $dst_language . '_Translation = l' . $src_language . '_Translation';
 				$this->Conn->Query($sql);
 
 				// events import
-				$sql = 'UPDATE ' . $this->Application->getUnitOption('email-template', 'TableName') . '
+				$sql = 'UPDATE ' . $this->Application->getUnitConfig('email-template')->getTableName() . '
 						SET
 							l' . $dst_language . '_Subject = l' . $src_language . '_Subject,
 							l' . $dst_language . '_HtmlBody = l' . $src_language . '_HtmlBody,
 							l' . $dst_language . '_PlainTextBody = l' . $src_language . '_PlainTextBody';
 				$this->Conn->Query($sql);
 			}
 
 			$this->Application->RemoveVar($var_name);
 
 			$event->CallSubEvent('OnReflectMultiLingualFields');
 			$event->CallSubEvent('OnUpdatePrimary');
 		}
 
 		/**
 		 * Prepare temp tables for creating new item
 		 * but does not create it. Actual create is
 		 * done in OnPreSaveCreated
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(kEvent $event)
 		{
 			parent::OnPreCreate($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('CopyLabels', 1);
 
 			$sql = 'SELECT ' . $object->IDField . '
-					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+					FROM ' . $event->getUnitConfig()->getTableName() . '
 					WHERE PrimaryLang = 1';
 			$primary_lang_id = $this->Conn->GetOne($sql);
 
 			$object->SetDBField('CopyFromLanguage', $primary_lang_id);
 			$object->SetDBField('SynchronizationModes', Language::SYNCHRONIZE_DEFAULT);
 
 			$this->setRequired($event);
 		}
 
 		/**
 		 * Sets dynamic required fields
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$this->setRequired($event);
 		}
 		/**
 		 * Sets new language mark
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeDeleteFromLive(kEvent $event)
 		{
 			parent::OnBeforeDeleteFromLive($event);
 
-			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
+			$config = $event->getUnitConfig();
+			$id_field = $config->getIDField();
 
 			$sql = 'SELECT ' . $id_field . '
-					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+					FROM ' . $config->getTableName() . '
 					WHERE ' . $id_field . ' = ' . $event->getEventParam('id');
 			$id = $this->Conn->GetOne($sql);
 
 			if ( !$id ) {
 				$this->Application->SetVar('new_language', 1);
 			}
 		}
 
 		function OnChangeLanguage($event)
 		{
 			$language_id = $this->Application->GetVar('language');
 			$language_field = $this->Application->isAdmin ? 'AdminLanguage' : 'FrontLanguage';
 
 			$this->Application->SetVar('m_lang', $language_id);
 
 			// set new language for this session
 			$this->Application->Session->SetField('Language', $language_id);
 
 			// remember last user language
 			if ($this->Application->RecallVar('user_id') == USER_ROOT) {
 				$this->Application->StorePersistentVar($language_field, $language_id);
 			}
 			else {
 				$object = $this->Application->recallObject('u.current');
 				/* @var $object kDBItem */
 
 				$object->SetDBField($language_field, $language_id);
 				$object->Update();
 			}
 
 			// without this language change in admin will cause erase of last remembered tree section
 			$this->Application->SetVar('skip_last_template', 1);
 		}
 
 		/**
 		 * Parse language XML file into temp tables and redirect to progress bar screen
 		 *
 		 * @param kEvent $event
 		 */
 		function OnImportLanguage($event)
 		{
 			if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$items_info = $this->Application->GetVar('phrases_import');
 			if ($items_info) {
 				list ($id, $field_values) = each($items_info);
 
 				$object = $this->Application->recallObject('phrases.import', 'phrases', Array('skip_autoload' => true));
 				/* @var $object kDBItem */
 
 				$object->setID($id);
 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 				if (!$object->Validate()) {
 					$event->status = kEvent::erFAIL;
 					return ;
 				}
 
 				$filename = $object->GetField('LangFile', 'full_path');
 
 				if (!filesize($filename)) {
 					$object->SetError('LangFile', 'la_empty_file', 'la_EmptyFile');
 					$event->status = kEvent::erFAIL;
 				}
 
 				$language_import_helper = $this->Application->recallObject('LanguageImportHelper');
 				/* @var $language_import_helper LanguageImportHelper */
 
 				if ( $object->GetDBField('ImportOverwrite') ) {
 					$language_import_helper->setOption(LanguageImportHelper::OVERWRITE_EXISTING);
 				}
 
 				if ( $object->GetDBField('ImportSynced') ) {
 					$language_import_helper->setOption(LanguageImportHelper::SYNC_ADDED);
 				}
 
 				$language_import_helper->performImport($filename, $object->GetDBField('PhraseType'), $object->GetDBField('Module'));
 
 				// delete uploaded language pack after import is finished
 				unlink($filename);
 
 				$event->SetRedirectParam('opener', 'u');
 			}
 		}
 
 		/**
 		 * Stores ids of selected languages and redirects to export language step 1
 		 *
 		 * @param kEvent $event
 		 */
 		function OnExportLanguage($event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
-			$this->Application->setUnitOption('phrases', 'AutoLoad', false);
+			$this->Application->getUnitConfig('phrases')->setAutoLoad(false);
 
 			$this->StoreSelectedIDs($event);
 			$this->Application->StoreVar('export_language_ids', implode(',', $this->getSelectedIDs($event)));
 
 			$event->setRedirectParams(
 				Array (
 					'phrases.export_event' => 'OnNew',
 					'pass' => 'all,phrases.export',
 					'export_mode' => $event->Prefix,
 				)
 			);
 		}
 
 		/**
 		 * Saves selected languages to xml file passed
 		 *
 		 * @param kEvent $event
 		 */
 		function OnExportProgress($event)
 		{
 			$items_info = $this->Application->GetVar('phrases_export');
 			if ( $items_info ) {
 				list($id, $field_values) = each($items_info);
 				$object = $this->Application->recallObject('phrases.export', null, Array ('skip_autoload' => true));
 				/* @var $object kDBItem */
 
 				$object->setID($id);
 				$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
 
 				if ( !$object->Validate() ) {
 					$event->status = kEvent::erFAIL;
 					return;
 				}
 
 				$file_helper = $this->Application->recallObject('FileHelper');
 				/* @var $file_helper FileHelper */
 
 				$file_helper->CheckFolder(EXPORT_PATH);
 
 				if ( !is_writable(EXPORT_PATH) ) {
 					$event->status = kEvent::erFAIL;
 					$object->SetError('LangFile', 'write_error', 'la_ExportFolderNotWritable');
 
 					return;
 				}
 
 				if ( substr($field_values['LangFile'], -5) != '.lang' ) {
 					$field_values['LangFile'] .= '.lang';
 				}
 
 				$filename = EXPORT_PATH . '/' . $field_values['LangFile'];
 
 				$language_import_helper = $this->Application->recallObject('LanguageImportHelper');
 				/* @var $language_import_helper LanguageImportHelper */
 
 				if ( $object->GetDBField('DoNotEncode') ) {
 					$language_import_helper->setExportEncoding('plain');
 				}
 
 				$data_types = Array (
 					'phrases' => 'ExportPhrases',
 					'email-template' => 'ExportEmailTemplates',
 					'country-state' => 'ExportCountries'
 				);
 
 				$export_mode = $this->Application->GetVar('export_mode');
 				$allowed_data_types = explode('|', substr($field_values['ExportDataTypes'], 1, -1));
 
 				if ( $export_mode == $event->Prefix ) {
 					foreach ($data_types as $prefix => $export_limit_field) {
 						$export_limit = in_array($prefix, $allowed_data_types) ? $field_values[$export_limit_field] : '-';
 						$language_import_helper->setExportLimit($prefix, $export_limit);
 					}
 				}
 				else {
 					foreach ($data_types as $prefix => $export_limit_field) {
 						$export_limit = in_array($prefix, $allowed_data_types) ? null : '-';
 						$language_import_helper->setExportLimit($prefix, $export_limit);
 					}
 				}
 
 				$lang_ids = explode(',', $this->Application->RecallVar('export_language_ids'));
 				$language_import_helper->performExport($filename, $field_values['PhraseType'], $lang_ids, $field_values['Module']);
 			}
 
 			$event->redirect = 'regional/languages_export_step2';
 			$event->SetRedirectParam('export_file', $field_values['LangFile']);
 		}
 
 		/**
 		 * Returns to previous template in opener stack
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnGoBack(kEvent $event)
 		{
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		function OnScheduleTopFrameReload($event)
 		{
 			$this->Application->StoreVar('RefreshTopFrame',1);
 		}
 
 		/**
 		 * Do now allow deleting current language
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemDelete(kEvent $event)
 		{
 			parent::OnBeforeItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( $object->GetDBField('PrimaryLang') || $object->GetDBField('AdminInterfaceLang') || $object->GetID() == $this->Application->GetVar('m_lang') ) {
 				$event->status = kEvent::erFAIL;
 			}
 		}
 
 		/**
 		 * Deletes phrases and email events on given language
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemDelete(kEvent $event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			// clean EmailTemplates table
 			$fields_hash = Array (
 				'l' . $object->GetID() . '_Subject' => NULL,
 				'l' . $object->GetID() . '_HtmlBody' => NULL,
 				'l' . $object->GetID() . '_PlainTextBody' => NULL,
 			);
-			$this->Conn->doUpdate($fields_hash, $this->Application->getUnitOption('email-template', 'TableName'), 1);
+			$this->Conn->doUpdate($fields_hash, $this->Application->getUnitConfig('email-template')->getTableName(), 1);
 
 			// clean Phrases table
 			$fields_hash = Array (
 				'l' . $object->GetID() . '_Translation' => NULL,
 				'l' . $object->GetID() . '_HintTranslation' => NULL,
 				'l' . $object->GetID() . '_ColumnTranslation' => NULL,
 			);
-			$this->Conn->doUpdate($fields_hash, $this->Application->getUnitOption('phrases', 'TableName'), 1);
+			$this->Conn->doUpdate($fields_hash, $this->Application->getUnitConfig('phrases')->getTableName(), 1);
 		}
 
 		/**
 		 * Copy missing phrases across all system languages (starting from primary)
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSynchronizeLanguages($event)
 		{
 			if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$event->status = kEvent::erFAIL;
 				return;
 			}
 
 			$source_languages = $target_languages = Array ();
 
 			// get language list with primary language first
 			$sql = 'SELECT SynchronizationModes, LanguageId
 					FROM ' . TABLE_PREFIX . 'Languages
 					WHERE SynchronizationModes <> ""
 					ORDER BY PrimaryLang DESC';
 			$languages = $this->Conn->GetCol($sql, 'LanguageId');
 
 			foreach ($languages as $language_id => $synchronization_modes) {
 				$synchronization_modes = explode('|', substr($synchronization_modes, 1, -1));
 
 				if ( in_array(Language::SYNCHRONIZE_TO_OTHERS, $synchronization_modes) ) {
 					$source_languages[] = $language_id;
 				}
 
 				if ( in_array(Language::SYNCHRONIZE_FROM_OTHERS, $synchronization_modes) ) {
 					$target_languages[] = $language_id;
 				}
 			}
 
 			foreach ($source_languages as $source_id) {
 				foreach ($target_languages as $target_id) {
 					if ( $source_id == $target_id ) {
 						continue;
 					}
 
 					$sql = 'UPDATE ' . TABLE_PREFIX . 'LanguageLabels
 							SET l' . $target_id . '_Translation = l' . $source_id . '_Translation
 							WHERE COALESCE(l' . $target_id . '_Translation, "") = "" AND COALESCE(l' . $source_id . '_Translation, "") <> ""';
 					$this->Conn->Query($sql);
 				}
 			}
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/languages/languages_item.php
===================================================================
--- branches/5.3.x/core/units/languages/languages_item.php	(revision 15697)
+++ branches/5.3.x/core/units/languages/languages_item.php	(revision 15698)
@@ -1,302 +1,304 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class LanguagesItem extends kDBItem
 	{
 		function generateID()
 		{
-			$sql = 'SELECT MAX('.$this->IDField.') FROM '.$this->Application->getUnitOption($this->Prefix, 'TableName');
+			$sql = 'SELECT MAX(' . $this->IDField . ')
+					FROM ' . $this->getUnitConfig()->getTableName();
+
 			return $this->Conn->GetOne($sql) + 1;
 		}
 
 		/**
 		 * Set's current language as new primary and return previous primary language
 		 *
 		 * @param bool $reset_primary
 		 * @param bool $admin_language
 		 * @return int
 		 */
 		function setPrimary($reset_primary = true, $admin_language = false)
 		{
 			$prev_primary = false;
 			$primary_field = $admin_language ? 'AdminInterfaceLang' : 'PrimaryLang';
 
 			if ($reset_primary) {
 				$sql = 'SELECT ' . $this->IDField . '
 						FROM ' . $this->TableName . '
 						WHERE ' . $primary_field . ' = 1';
 				$prev_primary = $this->Conn->GetOne($sql);
 
 				$sql = 'UPDATE '.$this->TableName.'
 						SET '.$primary_field.' = 0';
 				$this->Conn->Query($sql);
 			}
 
 			$sql = 'UPDATE '.$this->TableName.'
 					SET '.$primary_field.' = 1, Enabled = 1
 					WHERE '.$this->IDField.' = '.$this->GetID();
 			$this->Conn->Query($sql);
 
 			// in case, when Update method is called for this langauge object
 			$this->SetDBField($primary_field, 1);
 			$this->SetDBField('Enabled', 1);
 
 			// increment serial by hand, since no Update method is called
 			$this->Application->incrementCacheSerial($this->Prefix);
 			$this->Application->incrementCacheSerial($this->Prefix, $this->GetID());
 
 			return $prev_primary;
 		}
 
 		/**
 		 * Copies missing data on current language from given language
 		 *
 		 * @param int $from_language
 		 */
 		function copyMissingData($from_language)
 		{
 			if ( !is_numeric($from_language) || ($from_language == $this->GetID()) ) {
 				// invalid or same language
 				return ;
 			}
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$to_language = $this->GetID();
 			$this->Application->UnitConfigReader->ReReadConfigs();
 
-			foreach ($this->Application->UnitConfigReader->configData as $prefix => $config_data) {
+			foreach ($this->Application->UnitConfigReader->getPrefixes() as $prefix) {
 				$ml_helper->copyMissingData($prefix, $from_language, $to_language);
 			}
 		}
 
 		/**
 		 * Allows to format number according to regional settings
 		 *
 		 * @param float $number
 		 * @param int $precision
 		 * @return float
 		 */
 		function formatNumber($number, $precision = null)
 		{
 			if (is_null($precision)) {
 				$precision = preg_match('/[\.,]+/', $number) ? strlen(preg_replace('/^.*[\.,]+/', '', $number)) : 0;
 			}
 			return number_format($number, $precision, (string)$this->GetDBField('DecimalPoint'), (string)$this->GetDBField('ThousandSep'));
 		}
 
 		/**
 		 * Returns language id based on HTTP header "Accept-Language"
 		 *
 		 * @return int
 		 */
 		function processAcceptLanguage()
 		{
 			if (!array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER) || !$_SERVER['HTTP_ACCEPT_LANGUAGE']) {
 				return false;
 			}
 
 			$accepted_languages = Array ();
 			$language_string = explode(',', strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE']));
 
 			foreach ($language_string as $mixed_language) {
 				if (strpos($mixed_language, ';') !== false) {
 					list ($language_locale, $language_quality) = explode(';', $mixed_language);
 					$language_quality = (float)substr($language_quality, 2); // format "q=4.45"
 				}
 				else {
 					$language_locale = $mixed_language;
 					$language_quality = 1.00;
 				}
 
 				$accepted_languages[ trim($language_locale) ] = trim($language_quality);
 			}
 
 			arsort($accepted_languages, SORT_NUMERIC);
 
 			foreach ($accepted_languages as $language_locale => $language_quality) {
 				$language_id = $this->getAvailableLanguage($language_locale);
 
 				if ($language_id) {
 					return $language_id;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Returns language ID based on given locale
 		 *
 		 * @param string $locale
 		 * @return int
 		 */
 		function getAvailableLanguage($locale)
 		{
 			$cache_key = 'available_languages[%LangSerial%]';
 			$available_languages = $this->Application->getCache($cache_key);
 
 			if ($available_languages === false) {
 				$this->Conn->nextQueryCachable = true;
 				$sql = 'SELECT LanguageId, LOWER(Locale) AS Locale
 						FROM ' . TABLE_PREFIX . 'Languages
 						WHERE Enabled = 1';
 				$available_languages = $this->Conn->GetCol($sql, 'Locale');
 				$this->Application->setCache($cache_key, $available_languages);
 			}
 
 			if (strpos($locale, '-') !== false) {
 				// exact language match requested
 				$language_id = array_key_exists($locale, $available_languages) ? $available_languages[$locale] : false;
 
 				if ($language_id && $this->siteDomainLanguageEnabled($language_id)) {
 					return $language_id;
 				}
 
 				return false;
 			}
 
 			// partial (like "en" matches "en-GB" and "en-US") language match required
 			foreach ($available_languages as $language_code => $language_id) {
 				list ($language_code, ) = explode('-', $language_code);
 
 				if (($locale == $language_code) && $this->siteDomainLanguageEnabled($language_id)) {
 					return $language_id;
 				}
 			}
 
 			return false;
 		}
 
 		/**
 		 * Loads item from the database by given id
 		 *
 		 * @access public
 		 * @param mixed $id item id of keys->values hash to load item by
 		 * @param string $id_field_name Optional parameter to load item by given Id field
 		 * @param bool $cachable cache this query result based on it's prefix serial
 		 * @return bool True if item has been loaded, false otherwise
 		 * @throws kRedirectException
 		 */
 		public function Load($id, $id_field_name = null, $cachable = true)
 		{
 			$default = false;
 			if ($id == 'default') {
 				// domain based primary language
 				$default = true;
 				$id = $this->Application->siteDomainField('PrimaryLanguageId');
 
 				if ($id) {
 					$res = parent::Load($id, $id_field_name, $cachable);
 				}
 				else {
 					$res = parent::Load(1, 'PrimaryLang', $cachable);
 				}
 
 				if (
 					!$this->Application->isAdmin && !defined('GW_NOTIFY') && preg_match('/[\/]{0,1}index.php[\/]{0,1}/', $_SERVER['PHP_SELF']) &&
 					!$this->Application->HttpQuery->refererIsOurSite() && $this->Application->ConfigValue('UseContentLanguageNegotiation')
 				) {
 					$language_id = $this->processAcceptLanguage();
 
 					if ( $language_id != $this->GetID() ) {
 						// redirect to same page with found language
 						$url_params = Array (
 							'm_cat_id' => 0, 'm_cat_page' => 1,
 							'm_lang' => $language_id, 'm_opener' => 's',
 							'pass' => 'm'
 						);
 
 						$this->_addLoginState($url_params);
 						$exception = new kRedirectException('Redirect into language ID = <strong>' . $language_id . '</strong> according to "<strong>Accepted-Language</strong>" header "<strong>' . $_SERVER['HTTP_ACCEPT_LANGUAGE'] . '</strong>"');
 						$exception->setup('', $url_params);
 
 						throw $exception;
 					}
 				}
 			}
 			else {
 				$res = parent::Load($id, $id_field_name, $cachable);
 			}
 
 			if ($res) {
 				if (!$this->siteDomainLanguageEnabled($this->GetID())) {
 					// language isn't allowed in site domain
 					return $this->Clear();
 				}
 			}
 
 			if ($default) {
 				if (!$res) {
 					if ($this->Application->isAdmin) {
 						$res = parent::Load(1, $id_field_name, false);
 					}
 					else {
 						if (defined('IS_INSTALL')) {
 							// during first language import prevents sql errors
 							$this->setID(1);
 							$res = true;
 						}
 						else {
 							$this->Application->ApplicationDie('No Primary Language Selected');
 						}
 					}
 				}
 				$this->Application->SetVar('lang.current_id', $this->GetID() );
 				$this->Application->SetVar('m_lang', $this->GetID() );
 			}
 
 			return $res;
 		}
 
 		/**
 		 * Pass login state variables into new language url
 		 *
 		 * @param $url_params
 		 * @return void
 		 * @access protected
 		 */
 		protected function _addLoginState(&$url_params)
 		{
 			$pass_along = Array ('login', 'logout');
 
 			foreach ($pass_along as $pass_along_name) {
 				$pass_along_value = $this->Application->GetVar($pass_along_name);
 
 				if ( $pass_along_value !== false ) {
 					$url_params[$pass_along_name] = $pass_along_value;
 				}
 			}
 		}
 
 		/**
 		 * Checks, that language is enabled in site domain
 		 *
 		 * @param int $id
 		 * @return bool
 		 */
 		function siteDomainLanguageEnabled($id)
 		{
 			$available_languages = $this->Application->siteDomainField('Languages');
 
 			if ($available_languages) {
 				return strpos($available_languages, '|'  .$id . '|') !== false;
 			}
 
 			return true;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/permissions/permissions_event_handler.php
===================================================================
--- branches/5.3.x/core/units/permissions/permissions_event_handler.php	(revision 15697)
+++ branches/5.3.x/core/units/permissions/permissions_event_handler.php	(revision 15698)
@@ -1,266 +1,266 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class PermissionsEventHandler extends kDBEventHandler {
 
 	/**
 	 * Allows to override standard permission mapping
 	 *
 	 * @return void
 	 * @access protected
 	 * @see kEventHandler::$permMapping
 	 */
 	protected function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnGroupSavePermissions' => Array ('subitem' => 'advanced:manage_permissions'),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	/**
 	 * Save category permissions
 	 *
 	 * @param kEvent $event
 	 */
 	function OnCategorySavePermissions($event)
 	{
 		$group_id = $this->Application->GetVar('current_group_id');
 		$category_id = $this->Application->GetVar('c_id');
 		$permissions = $this->Application->GetVar($event->getPrefixSpecial(true));
 
 		if (isset($permissions[$group_id])) {
 			$permissions = $permissions[$group_id];
 
 			$object = $event->getObject( Array('skip_autoload' => true) );
 			$permissions_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $permissions_helper kPermissionsHelper */
 
 			$permissions_helper->LoadPermissions($group_id, $category_id, 0, 'c');
 
 			// format: <perm_name>['inherited'] || <perm_name>['value']
 
 			$delete_ids = Array();
 			$create_sql = Array();
 			$update_sql = Array();
 			$create_mask = '(%s,%s,'.$group_id.',%s,0,'.$category_id.')';
 			$new_id = (int)$this->Conn->GetOne('SELECT MIN('.$object->IDField.') FROM '.$object->TableName);
 			if($new_id > 0) $new_id = 0;
 			--$new_id;
 
 			foreach ($permissions as $perm_name => $perm_data) {
 				$inherited = $perm_data['inherited'];
 				$perm_value = isset($perm_data['value']) ? $perm_data['value'] : false;
 				$perm_id = $permissions_helper->getPermissionID($perm_name);
 
 				if ($inherited && ($perm_id != 0)) {
 					// permission become inherited (+ direct value was set before) => DELETE
 					$delete_ids[] = $permissions_helper->getPermissionID($perm_name);
 				}
 
 				if (!$inherited) {
 					// not inherited
 					if (($perm_id != 0) && ($perm_value != $permissions_helper->getPermissionValue($perm_name))) {
 						// record was found in db & new value differs from old one => UPDATE
 						$update_sql[$perm_id] = '	UPDATE '.$object->TableName.'
 													SET PermissionValue = '.$perm_value.'
 													WHERE (PermissionId = '.$perm_id.')';
 					}
 
 					if ($perm_id == 0) {
 						// not found in db, but set directly => INSERT
 						$create_sql[] = sprintf($create_mask, $new_id--, $this->Conn->qstr($perm_name), $this->Conn->qstr($perm_value));
 					}
 				}
 				// permission state was not changed in all other cases
 			}
 
 			$this->UpdatePermissions($event, $create_sql, $update_sql, $delete_ids);
 		}
 
 		$event->MasterEvent->SetRedirectParam('item_prefix', $this->Application->GetVar('item_prefix'));
 		$event->MasterEvent->SetRedirectParam('group_id', $this->Application->GetVar('group_id'));
 	}
 
 	/**
 	 * Saves permissions while editing group
 	 *
 	 * @param kEvent $event
 	 *
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnGroupSavePermissions($event)
 	{
 		if ( !$this->Application->CheckPermission('in-portal:user_groups.advanced:manage_permissions', 1) ) {
 			// no permission to save permissions
 			return ;
 		}
 
 		$permissions = $this->Application->GetVar($event->getPrefixSpecial(true));
 		if ( !$permissions ) {
 			return ;
 		}
 
 		$object = $event->getObject( Array ('skip_autoload' => true) );
 		/* @var $object kDBItem */
 
 		$group_id = $this->Application->GetVar('g_id');
 		$permissions_helper = $this->Application->recallObject('PermissionsHelper');
 		/* @var $permissions_helper kPermissionsHelper */
 
 		$permissions_helper->LoadPermissions($group_id, 0, 1, 'g');
 
 		$delete_ids = $create_sql = Array ();
 		$create_mask = '(%s,%s,' . $group_id . ',%s,1,0)';
 
 		$new_id = (int)$this->Conn->GetOne('SELECT MIN(' . $object->IDField . ') FROM ' . $object->TableName);
 		if ( $new_id > 0 ) {
 			$new_id = 0;
 		}
 		--$new_id;
 
 		$sections_helper = $this->Application->recallObject('SectionsHelper');
 		/* @var $sections_helper kSectionsHelper */
 
 		foreach ($permissions as $section_name => $section_permissions) {
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			if ( $section_data && isset($section_data['perm_prefix']) ) {
 				// using permission from other prefix
-				$section_name = $this->Application->getUnitOption($section_data['perm_prefix'] . '.main', 'PermSection');
+				$section_name = $this->Application->getUnitConfig($section_data['perm_prefix'])->getPermSectionByName('main');
 			}
 
 			foreach ($section_permissions as $perm_name => $perm_value) {
 				if ( !$permissions_helper->isOldPermission($section_name, $perm_name) ) {
 					$perm_name = $section_name . '.' . $perm_name;
 				}
 
 				$db_perm_value = $permissions_helper->getPermissionValue($perm_name);
 
 				if ( $db_perm_value == 1 && $perm_value == 0 ) {
 					// permission was disabled => delete it's record
 					$delete_ids[] = $permissions_helper->getPermissionID($perm_name);
 				}
 				elseif ( $db_perm_value == 0 && $perm_value == 1 ) {
 					// permission was enabled => created it's record
 					$create_sql[$perm_name] = sprintf($create_mask, $new_id--, $this->Conn->qstr($perm_name), $this->Conn->qstr($perm_value));
 				}
 				// permission state was not changed in all other cases
 			}
 		}
 
 		$this->UpdatePermissions($event, $create_sql, Array (), $delete_ids);
 
 		if ( $this->Application->GetVar('advanced_save') == 1 ) {
 			// advanced permission popup [save button]
 			$this->finalizePopup($event);
 //			$event->redirect = 'incs/just_close';
 		}
 		elseif ( $this->Application->GetVar('section_name') != '' ) {
 			// save simple permissions before opening advanced permission popup
 			$event->redirect = false;
 		}
 	}
 
 	/**
 	 * Apply modification sqls to permissions table
 	 *
 	 * @param kEvent $event
 	 * @param Array $create_sql
 	 * @param Array $update_sql
 	 * @param Array $delete_ids
 	 */
 	function UpdatePermissions($event, $create_sql, $update_sql, $delete_ids)
 	{
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ($delete_ids) {
 			$action = ChangeLog::DELETE;
 			$object->Load($delete_ids[count($delete_ids) - 1]);
 
 			$delete_sql = '	DELETE FROM '.$object->TableName.'
 							WHERE '.$object->IDField.' IN ('.implode(',', $delete_ids).')';
 			$this->Conn->Query($delete_sql);
 		}
 
 		if ($create_sql) {
 			$create_sql = '	INSERT INTO '.$object->TableName.'
 							VALUES '.implode(',', $create_sql);
 			$this->Conn->Query($create_sql);
 
 			$sql = 'SELECT MIN(' . $object->IDField . ')
 					FROM ' . $object->TableName;
 			$id = $this->Conn->GetOne($sql);
 
 			$action = ChangeLog::CREATE;
 			$object->Load($id);
 		}
 
 		if ($update_sql) {
 			foreach ($update_sql as $id => $sql) {
 				$this->Conn->Query($sql);
 			}
 
 			$action = ChangeLog::UPDATE;
 			$object->Load($id);
 			$object->SetDBField('PermissionValue', $object->GetDBField('PermissionValue') ? 0 : 1);
 		}
 
 		if ($delete_ids || $create_sql || $update_sql) {
 			$object->setModifiedFlag($action);
 
 			if ($event->Name == 'OnCategorySavePermissions') {
 				$this->Application->StoreVar('PermCache_UpdateRequired', 1);
 			}
 		}
 	}
 
 	/**
 	 * Don't delete permissions from live table in case of new category creation.
 	 * Called as much times as permission count for categories set, so don't
 	 * perform any sql queries here!
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeDeleteFromLive(kEvent $event)
 	{
 		parent::OnBeforeDeleteFromLive($event);
 
 		if ( $event->Prefix == 'c-perm' ) {
 			// only when saving category permissions, not group permissions
 			$foreign_keys = $event->getEventParam('foreign_key');
 
 			if ( (count($foreign_keys) == 1) && ($foreign_keys[0] == 0) ) {
 				// parent item has zero id
 				$temp_object = $this->Application->recallObject('c');
 				/* @var $temp_object CategoriesItem */
 
 				if ( $temp_object->isLoaded() ) {
 					// category with id = 0 found in temp table
 					$event->status = kEvent::erFAIL;
 				}
 			}
 		}
 	}
 
 }
\ No newline at end of file
Index: branches/5.3.x/core/units/permissions/permissions_tag_processor.php
===================================================================
--- branches/5.3.x/core/units/permissions/permissions_tag_processor.php	(revision 15697)
+++ branches/5.3.x/core/units/permissions/permissions_tag_processor.php	(revision 15698)
@@ -1,231 +1,230 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class PermissionsTagProcessor extends kDBTagProcessor {
 
 		function HasPermission($params)
 		{
 			$section_name = $params['section_name'];
 
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			return array_search($params['perm_name'], $section_data['permissions']) !== false;
 		}
 
 		function HasAdvancedPermissions($params)
 		{
 			$section_name = $params['section_name'];
 
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			$ret = false;
 			foreach ($section_data['permissions'] as $perm_name) {
 				if (preg_match('/^advanced:(.*)/', $perm_name)) {
 					$ret = true;
 					break;
 				}
 			}
 			return $ret;
 		}
 
 		function PermissionValue($params)
 		{
 			$section_name = $params['section_name'];
 			$perm_name = $params['perm_name'];
 
 			$sections_helper = $this->Application->recallObject('SectionsHelper');
 			/* @var $sections_helper kSectionsHelper */
 
 			$section_data =& $sections_helper->getSectionData($section_name);
 
 			if ($section_data && isset($section_data['perm_prefix'])) {
 				// using permission from other prefix
-				$section_name = $this->Application->getUnitOption($section_data['perm_prefix'].'.main', 'PermSection');
+				$section_name = $this->Application->getUnitConfig($section_data['perm_prefix'])->getPermSectionByName('main');
 			}
 
 			$permissions_helper = $this->Application->recallObject('PermissionsHelper');
 			/* @var $permissions_helper kPermissionsHelper */
 
 			if (!$permissions_helper->isOldPermission($section_name, $perm_name)) {
 				$perm_name = $section_name.'.'.$perm_name;
 			}
 
 			return $permissions_helper->getPermissionValue($perm_name);
 		}
 
 		function LoadPermissions($params)
 		{
 			$permissions_helper = $this->Application->recallObject('PermissionsHelper');
 			$prefix_parts = explode('-', $this->Prefix, 2);
 			/* @var $permissions_helper kPermissionsHelper */
 
 			$permissions_helper->LoadPermissions($this->Application->GetVar('g_id'), 0, 1, 'g');
 		}
 
 		function LevelIndicator($params)
 		{
 			return $params['level'] * $params['multiply'];
 		}
 
 		function PrintPermissions($params)
 		{
 			$category = $this->Application->recallObject('c');
 			/* @var $category kDBItem */
 
 			$group_id = $this->Application->GetVar('group_id');
 			$prefix = $this->Application->GetVar('item_prefix');
 			$module = $this->Application->findModule('Var', $prefix, 'Name');
 
-			$perm_live_table = $this->Application->getUnitOption('c-perm', 'TableName');
+			$perm_live_table = $this->Application->getUnitConfig('c-perm')->getTableName();
 			$perm_temp_table = $this->Application->GetTempName($perm_live_table, 'prefix:'.$this->Prefix);
 
 			if ($category->GetID() == 0) {
 				$categories = Array(0);
 			}
 			else {
 				$categories = explode('|', substr($category->GetDBField('ParentPath'), 1, -1));
 			}
 
 			if (count($categories) == 1 || $category->GetID() == 0) {
 				// category located in root category ("Home") => then add it to path virtually
 				array_unshift($categories, 0);
 			}
 			$this_cat = array_pop($categories);
 
 			// get permission name + category position in parent path that has value set for that permission
 			$case = 'MAX(CASE p.CatId';
 			foreach ($categories as $pos => $cat_id) {
 				$case .= ' WHEN '.$cat_id.' THEN '.$pos;
 			}
 			$case .= ' END) AS InheritedPosition';
 
 			$sql = 'SELECT '.$case.', p.Permission AS Perm
 					FROM '.$perm_live_table.' p
 					LEFT JOIN '.TABLE_PREFIX.'CategoryPermissionsConfig pc ON pc.PermissionName = p.Permission
 					WHERE
 						p.CatId IN ('.implode(',', $categories).') AND
 						pc.ModuleId = ' . $this->Conn->qstr($module) . ' AND
 						(
 							(p.GroupId = ' . (int)$group_id . ' AND p.Type = 0)
 						)
 					GROUP BY Perm';
 			$perm_positions = $this->Conn->GetCol($sql, 'Perm');
 
 			$pos_sql = '';
 			foreach ($perm_positions as $perm_name => $category_pos) {
 				$pos_sql .= '(#TABLE_PREFIX#.Permission = "'.$perm_name.'" AND #TABLE_PREFIX#.CatId = '.$categories[$category_pos].') OR ';
 			}
 			$pos_sql = $pos_sql ? substr($pos_sql, 0, -4) : '0';
 
 			// get all permissions list with iheritence status, inherited category id and permission value
 			$sql = 'SELECT 	pc.PermissionName,
 							pc.Description,
 							IF (tmp_p.PermissionValue IS NULL AND p.PermissionValue IS NULL,
 								0,
 								IF (tmp_p.PermissionValue IS NOT NULL, tmp_p.PermissionValue, p.PermissionValue)
 							) AS Value,
 							IF (tmp_p.CatId IS NOT NULL, tmp_p.CatId, IF(p.CatId IS NOT NULL, p.CatId, 0) ) AS InheritedFrom,
 							IF(tmp_p.CatId = '.$category->GetID().', 0, 1) AS Inherited,
 							IF(p.PermissionValue IS NOT NULL, p.PermissionValue, 0) AS InheritedValue
 					FROM '.TABLE_PREFIX.'CategoryPermissionsConfig pc
 					LEFT JOIN '.$perm_live_table.' p
 					ON (p.Permission = pc.PermissionName) AND ('.str_replace('#TABLE_PREFIX#', 'p', $pos_sql).') AND (p.GroupId = '.(int)$group_id.')
 					LEFT JOIN '.$perm_temp_table.' tmp_p
 					ON (tmp_p.Permission = pc.PermissionName) AND (tmp_p.CatId = '.$this_cat.') AND (tmp_p.GroupId = '.$group_id.')
 					WHERE ModuleId = "'.$module.'"';
 			$permissions = $this->Conn->Query($sql);
 
 			$ret = '';
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $params['render_as'];
 
 			foreach ($permissions as $perm_record) {
 				$block_params = array_merge($block_params, $perm_record);
 				$ret .= $this->Application->ParseBlock($block_params);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Print module tab for each module
 		 *
 		 * @param Array $params
 		 * @return string
 		 */
 		function PrintTabs($params)
 		{
 			$ret = '';
 			$block_params = $params;
 			foreach ($this->Application->ModuleInfo as $module_name => $module_data) {
-				if (!$this->Application->prefixRegistred($module_data['Var']) || !$this->Application->getUnitOption($module_data['Var'], 'CatalogItem')) continue;
+				if (!$this->Application->prefixRegistred($module_data['Var']) || !$this->Application->getUnitConfig($module_data['Var'])->getCatalogItem()) continue;
 				$params['item_prefix'] = $module_data['Var'];
 				$ret .= $this->Application->IncludeTemplate($params);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns category name by ID
 		 *
 		 * @param Array $params
 		 * @return string
 		 * @access protected
 		 */
 		protected function CategoryPath($params)
 		{
 			$category_id = $params['cat_id'];
 			$cache_key = 'category_paths[%CIDSerial:' . $category_id . '%][%PhrasesSerial%][Adm:' . (int)$this->Application->isAdmin . ']';
 			$category_path = $this->Application->getCache($cache_key);
 
 			if ( $category_path === false ) {
 				// not cached
 				if ( $category_id > 0 ) {
-					$id_field = $this->Application->getUnitOption('c', 'IDField');
-					$table_name = $this->Application->getUnitOption('c', 'TableName');
-
 					$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 					/* @var $ml_formatter kMultiLanguage */
 
+					$categories_config = $this->Application->getUnitConfig('c');
+
 					$sql = 'SELECT ' . $ml_formatter->LangFieldName('CachedNavbar') . '
-							FROM ' . $table_name . '
-							WHERE ' . $id_field . ' = ' . $category_id;
+							FROM ' . $categories_config->getTableName() . '
+							WHERE ' . $categories_config->getIDField() . ' = ' . $category_id;
 					$cached_navbar = preg_replace('/^Content(&\|&){0,1}/i', '', $this->Conn->GetOne($sql));
 					$category_path = trim($this->CategoryPath(Array ('cat_id' => 0)) . ' > ' . str_replace('&|&', ' > ', $cached_navbar), ' > ');
 				}
 				else {
 					$category_path = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 				}
 
 				$this->Application->setCache($cache_key, $category_path);
 			}
 
 			return $category_path;
 		}
 
 		function PermInputName($params)
 		{
 			 return $this->Prefix.'['.$this->Application->GetVar('group_id').']['.$this->Application->Parser->GetParam('PermissionName').']['.$params['sub_key'].']';
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/stylesheets/stylesheets_item.php
===================================================================
--- branches/5.3.x/core/units/stylesheets/stylesheets_item.php	(revision 15697)
+++ branches/5.3.x/core/units/stylesheets/stylesheets_item.php	(revision 15698)
@@ -1,64 +1,64 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class StylesheetsItem extends kDBItem
 	{
 
 		function Compile()
 		{
 			$selector_item = $this->Application->recallObject('selectors.item', 'selectors', Array('live_table'=>true, 'skip_autoload' => true) );
 			/* @var $selector_item SelectorsItem */
 
-			$parent_field = $this->Application->getUnitOption($selector_item->Prefix, 'ForeignKey');
+			$parent_field = $selector_item->getUnitConfig()->getForeignKey();
 
 			$sql_template = 'SELECT '.$selector_item->IDField.' FROM '.$selector_item->TableName.' WHERE '.$parent_field.' = %s ORDER BY SelectorName ASC';
 
 			$selectors_ids = $this->Conn->GetCol( sprintf($sql_template, $this->GetID() ) );
 			$ret = '/* This file is generated automatically. Don\'t edit it manually ! */'."\n\n";
 
 			foreach($selectors_ids as $selector_id)
 			{
 				$selector_item->Load($selector_id);
 				$ret .= $selector_item->CompileStyle()."\n";
 			}
 
 			$ret .= $this->GetDBField('AdvancedCSS');
 
 			$compile_ts = adodb_mktime();
 			$css_path = WRITEABLE . '/stylesheets/';
 
 			$file_helper = $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$file_helper->CheckFolder($css_path);
 
 			$css_file = $css_path.mb_strtolower($this->GetDBField('Name')).'-'.$compile_ts.'.css';
 
 			$fp = fopen($css_file,'w');
 			if($fp)
 			{
 				$prev_css = $css_path.mb_strtolower($this->GetDBField('Name')).'-'.$this->GetDBField('LastCompiled').'.css';
 				if( file_exists($prev_css) ) unlink($prev_css);
 
 				fwrite($fp, $ret);
 				fclose($fp);
 
 				$sql = 'UPDATE '.$this->TableName.' SET LastCompiled = '.$compile_ts.' WHERE '.$this->IDField.' = '.$this->GetID();
 				$this->Conn->Query($sql);
 			}
 		}
 
 	}
\ No newline at end of file
Index: branches/5.3.x/core/units/page_revisions/page_revision_eh.php
===================================================================
--- branches/5.3.x/core/units/page_revisions/page_revision_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/page_revisions/page_revision_eh.php	(revision 15698)
@@ -1,375 +1,377 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
 class PageRevisionEventHandler extends kDBEventHandler {
 
 	/**
 	 * Checks permissions of user
 	 *
 	 * @param kEvent $event
 	 * @return bool
 	 * @access public
 	 */
 	public function CheckPermission(kEvent $event)
 	{
 		if ( $event->Name == 'OnItemBuild' ) {
 			return true;
 		}
 
 		if ( $event->Name == 'OnGetInfo' || $event->Name == 'OnDiscard' ) {
 			return $this->Application->isAdminUser;
 		}
 
 		$perm_helper = $this->Application->recallObject('PermissionsHelper');
 		/* @var $perm_helper kPermissionsHelper */
 
 		if ( $event->Name == 'OnSave' ) {
 				$perm_status = $this->Application->CheckPermission('CATEGORY.REVISION.ADD', 0) || $this->Application->CheckPermission('CATEGORY.REVISION.ADD.PENDING', 0);
 
 			return $perm_helper->finalizePermissionCheck($event, $perm_status);
 		}
 
 		if ( $event->Name == 'OnPublish' || $event->Name == 'OnDecline' ) {
 			$perm_status = $this->Application->CheckPermission('CATEGORY.REVISION.MODERATE', 0);
 
 			return $perm_helper->finalizePermissionCheck($event, $perm_status);
 		}
 
 		return parent::CheckPermission($event);
 	}
 
 	/**
 	 * Lists all current page revisions
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function SetCustomQuery(kEvent $event)
 	{
 		parent::SetCustomQuery($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBList */
 
 		$page_id = $event->getEventParam('page_id');
 
 		if ( $this->Application->isAdmin ) {
 			$user_id = $this->Application->RecallVar('user_id');
 		}
 		else {
 			$user_id = $this->Application->RecallVar('admin_user_id');
 		}
 
 		$object->addFilter('draft_filter', 'IF(%1$s.IsDraft = 1, %1$s.CreatedById = ' . $user_id . ', TRUE)');
 
 		if ( $page_id !== false ) {
 			$object->addFilter('parent_filter', '%1$s.PageId = ' . $page_id);
 		}
 	}
 
 	/**
 	 * Returns current page revision
 	 *
 	 * @param kEvent $event
 	 * @return int
 	 * @access public
 	 */
 	public function getPassedID(kEvent $event)
 	{
 		if ( $event->Special == 'current' ) {
 			$page = $this->Application->recallObject('st.-virtual');
 			/* @var $page kDBItem */
 
 			$page_helper = $this->Application->recallObject('PageHelper');
 			/* @var $page_helper PageHelper */
 
 			$page_id = $page->GetID();
 			$revision_clause = $page_helper->getRevsionWhereClause($page_id, $page->GetDBField('LiveRevisionNumber'));
 
 			$sql = 'SELECT RevisionId
 					FROM ' . TABLE_PREFIX . 'PageRevisions
 					WHERE (PageId = ' . $page_id . ') AND (' . $revision_clause . ')
 					ORDER BY IsDraft DESC, RevisionNumber DESC';
 			$id = $this->Conn->GetOne($sql);
 
 			if ( $id ) {
 				return $id;
 			}
 
 			// no revisions -> create live revision
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('PageId', $page_id);
 			$object->SetDBField('RevisionNumber', 1);
 			$object->SetDBField('Status', STATUS_ACTIVE);
 			$object->Create();
 
 			return $object->GetID();
 		}
 
 		return parent::getPassedID($event);
 	}
 
 	/**
 	 * Remembers, who created revision
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemCreate(kEvent $event)
 	{
 		parent::OnBeforeItemCreate($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ( $this->Application->isAdmin ) {
 			$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 		}
 		else {
 			$object->SetDBField('CreatedById', $this->Application->RecallVar('admin_user_id'));
 		}
 	}
 
 	/**
 	 * Updates revision creation time
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnBeforeItemUpdate(kEvent $event)
 	{
 		parent::OnBeforeItemUpdate($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ( $object->GetDBField('IsDraft') == 0 && $object->GetOriginalField('IsDraft') == 1 ) {
 			$object->SetDBField('CreatedOn_date', adodb_mktime());
 			$object->SetDBField('CreatedOn_time', adodb_mktime());
 		}
 	}
 
 	/**
 	 * Creates new content blocks based on source revision
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemCreate(kEvent $event)
 	{
 		parent::OnAfterItemCreate($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		if ( !$object->GetDBField('FromRevisionId') ) {
 			return ;
 		}
 
 		$content = $this->Application->recallObject('content.-item', null, Array ('skip_autoload' => true));
 		/* @var $content kDBItem */
 
 		$sql = 	$content->GetSelectSQL() . '
 				WHERE pr.RevisionId = ' . $object->GetDBField('FromRevisionId');
 		$content_blocks = $this->Conn->Query($sql);
 
 		foreach ($content_blocks as $content_block) {
 			$content->LoadFromHash($content_block);
 			$content->SetDBField('RevisionId', $object->GetID());
 			$content->Create();
 		}
 	}
 
 	/**
 	 * Mark revision as current, once it's approved
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnAfterItemUpdate(kEvent $event)
 	{
 		parent::OnAfterItemUpdate($event);
 
 		$object = $event->getObject();
 		/* @var $object kDBItem */
 
 		$status = $object->GetDBField('Status');
 
 		if ( $status != $object->GetOriginalField('Status') && $status == STATUS_ACTIVE ) {
 			$page = $this->Application->recallObject('c.revision', null, Array ('skip_autoload' => true));
 			/* @var $page kDBItem */
 
 			$page->Load($object->GetDBField('PageId'));
 			$page->SetDBField('LiveRevisionNumber', $object->GetDBField('RevisionNumber'));
 			$page->Update();
 		}
 	}
 
 	/**
 	 * Returns user, who are editing current page right now
 	 *
 	 * @param kEvent $event
 	 */
 	function OnGetInfo($event)
 	{
 		$event->status = kEvent::erSTOP;
 
 		if ( $this->Application->GetVar('ajax') != 'yes' ) {
 			return ;
 		}
 
 		$page_helper = $this->Application->recallObject('PageHelper');
 		/* @var $page_helper PageHelper */
 
 		$page_id = $this->Application->GetVar('m_cat_id');
 		echo json_encode( $page_helper->getPageInfo($page_id) );
 	}
 
 	/**
 	 * Saves user draft into live revision
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function OnSave(kEvent $event)
 	{
 		$revision_id = $this->getCurrentDraftRevision($event);
 
 		if ( $revision_id ) {
 			$object = $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$object->Load($revision_id);
 			$object->SetDBField('IsDraft', 0);
 			$object->SetDBField('RevisionNumber', $this->getNextAvailableRevision($event));
 
 			if ( $this->Application->CheckPermission('CATEGORY.REVISION.ADD', 0) ) {
 				$object->SetDBField('Status', STATUS_ACTIVE);
 			}
 			elseif ( $this->Application->CheckPermission('CATEGORY.REVISION.ADD.PENDING', 0) ) {
 				$object->SetDBField('Status', STATUS_PENDING);
 			}
 
 			$object->Update();
 		}
 
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Discards user draft
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDiscard($event)
 	{
 		$revision_id = $this->getCurrentDraftRevision($event);
 
 		if ( $revision_id ) {
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			$temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($revision_id));
 		}
 
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Makes revision live
 	 *
 	 * @param kEvent $event
 	 */
 	function OnPublish($event)
 	{
 		$revision = $this->Application->recallObject('page-revision.current');
 		/* @var $revision kDBItem */
 
 		if ( !$revision->isLoaded() || $revision->GetDBField('Status') == STATUS_ACTIVE || $revision->GetDBField('IsDraft') ) {
 			return ;
 		}
 
 		$revision->SetDBField('Status', STATUS_ACTIVE);
 		$revision->Update();
 
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Denies changes made in revision
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDecline($event)
 	{
 		$revision = $this->Application->recallObject('page-revision.current');
 		/* @var $revision kDBItem */
 
 		if ( !$revision->isLoaded() || $revision->GetDBField('Status') == STATUS_DISABLED || $revision->GetDBField('IsLive') || $revision->GetDBField('IsDraft') ) {
 			return ;
 		}
 
 		$revision->SetDBField('Status', STATUS_DISABLED);
 		$revision->Update();
 
 		$event->SetRedirectParam('opener', 'u');
 	}
 
 	/**
 	 * Returns revision id of user's draft
 	 *
 	 * @param kEvent $event
 	 * @return int
 	 */
 	function getCurrentDraftRevision($event)
 	{
 		$where_clause = Array (
 			'IsDraft = 1',
 			'PageId = ' . $this->Application->GetVar('m_cat_id'),
 			'CreatedById = ' . $this->Application->RecallVar('user_id'),
 		);
 
-		$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
-				FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+		$config = $event->getUnitConfig();
+
+		$sql = 'SELECT ' . $config->getIDField() . '
+				FROM ' . $config->getTableName() . '
 				WHERE (' . implode(') AND (', $where_clause) . ')';
 
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Returns next available revision number for current page
 	 *
 	 * @param kEvent $event
 	 * @return int
 	 */
 	function getNextAvailableRevision($event)
 	{
 		$sql = 'SELECT MAX(RevisionNumber)
-				FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
+				FROM ' . $event->getUnitConfig()->getTableName() . '
 				WHERE PageId = ' . $this->Application->GetVar('m_cat_id');
 		$max_revision = (int)$this->Conn->GetOne($sql);
 
 		return $max_revision + 1;
 	}
 }
Index: branches/5.3.x/core/units/general/general_config.php
===================================================================
--- branches/5.3.x/core/units/general/general_config.php	(revision 15697)
+++ branches/5.3.x/core/units/general/general_config.php	(revision 15698)
@@ -1,46 +1,43 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 defined('FULL_PATH') or die('restricted access!');
 
-$config = Array (
-	'Prefix' => 'm',
-	'EventHandlerClass' => Array ('class' => 'kEventHandler', 'file' => '', 'build_event' => 'OnBuild'),
-//	'TagProcessorClass' => Array ('class' => 'kMainTagProcessor', 'file' => '', 'build_event' => 'OnBuild'),
-
-	'QueryString' => Array (
-		1 => 'cat_id',
-		2 => 'cat_page',
-		3 => 'lang',
-		4 => 'theme',
-		5 => 'opener',
-		6 => 'wid',
-	),
-
-	'TitleField' => 'CachedNavbar',
-	'TitlePhrase' => 'la_Text_Category',
-	'CatalogTabIcon' => 'icon16_section.png',
-	'ItemType' => 1,
-	'TableName' => TABLE_PREFIX . 'Categories',
-
-	'CatalogItem' => true,
-
-	'PortalStyleEnv' => true,
-
-	'RewritePriority' => 100,
-	'RewriteListener' => 'c_EventHandler:CategoryRewriteListener',
-
-	'PermTabText' => 'In-Portal',
-	'PermSection' => Array ('search' => 'in-portal:configuration_search', 'custom' => 'in-portal:configuration_custom'),
-);
\ No newline at end of file
+$config = new kUnitConfig('m', null, false);
+
+$config->setEventHandlerClass(Array ('class' => 'kEventHandler', 'file' => '', 'build_event' => 'OnBuild'));
+//$config->setTagProcessorClass(Array ('class' => 'kMainTagProcessor', 'file' => '', 'build_event' => 'OnBuild'));
+
+$config->setQueryString(Array (
+	1 => 'cat_id',
+	2 => 'cat_page',
+	3 => 'lang',
+	4 => 'theme',
+	5 => 'opener',
+	6 => 'wid',
+));
+
+$config->setTitleField('CachedNavbar');
+$config->setTitlePhrase('la_Text_Category');
+$config->setCatalogTabIcon('icon16_section.png');
+$config->setItemType(1);
+$config->setTableName(TABLE_PREFIX . 'Categories');
+$config->setCatalogItem(true);
+$config->setPortalStyleEnv(true);
+
+$config->setRewritePriority(100);
+$config->setRewriteListener('c_EventHandler:CategoryRewriteListener');
+
+$config->setPermTabText('In-Portal');
+$config->setPermSection(Array ('search' => 'in-portal:configuration_search', 'custom' => 'in-portal:configuration_custom'));
Index: branches/5.3.x/core/units/site_domains/site_domain_eh.php
===================================================================
--- branches/5.3.x/core/units/site_domains/site_domain_eh.php	(revision 15697)
+++ branches/5.3.x/core/units/site_domains/site_domain_eh.php	(revision 15698)
@@ -1,294 +1,295 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2010 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	class SiteDomainEventHandler extends kDBEventHandler {
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(kEvent $event)
 		{
 			if ( $event->Name == 'OnItemBuild' ) {
 				// check permission without using $event->getSection(),
 				// so first cache rebuild won't lead to "ldefault_Name" field being used
 				return true;
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Returns ID of site domain, that matches current url
 		 *
 		 * @param kEvent $event
 		 * @return int
 		 * @access public
 		 */
 		public function getPassedID(kEvent $event)
 		{
 			if ( $event->Special == 'current' ) {
 				if ( $this->Application->isAdmin ) {
 					$event->setEventParam('raise_warnings', 0);
 				}
 				else {
 					if ( PROTOCOL == 'https://' ) {
 						return $this->querySiteDomain('SSLUrl', rtrim($this->Application->BaseURL(), '/'));
 					}
 
 					return $this->querySiteDomain('DomainName', $_SERVER['HTTP_HOST']);
 				}
 			}
 
 			return parent::getPassedID($event);
 		}
 
 		function querySiteDomain($field, $value)
 		{
 			$site_helper = $this->Application->recallObject('SiteHelper');
 			/* @var $site_helper SiteHelper */
 
 			$site_domains = $site_helper->getSiteDomains();
 			$domain_by_name = $site_helper->getDomainByName($field, $value);
 			$domain_by_ip = $site_helper->getDomainByIP();
 
 			if ($domain_by_ip) {
 				$site_domain = $site_domains[$domain_by_ip];
 				$redirect_mode = $site_domain['RedirectOnIPMatch'];
 
 				if (($redirect_mode == SITE_DOMAIN_REDIRECT_EXTERNAL) && ($domain_by_ip == $domain_by_name)) {
 					// redirect to external url (when visiting protected domain)
 					$external_url = $site_domain['ExternalUrl'];
 
 					if (preg_match('/^http[s]{0,1}:\/\//', $external_url)) {
 						$this->Application->Redirect('external:' . $external_url);
 					}
 					else {
 						$this->Application->Redirect('external:' . PROTOCOL . $external_url . $_SERVER['REQUEST_URI']);
 					}
 				}
 				elseif (($redirect_mode == SITE_DOMAIN_REDIRECT_CURRENT) && ($domain_by_ip != $domain_by_name)) {
 					// redirect to a domain detected by IP (when not already on it)
 					if ((PROTOCOL == 'https://') && !$site_domain['SSLUrlUsesRegExp'] && $site_domain['SSLUrl']) {
 						// need to remove sub folder from ssl url
 						$ssl_url = preg_replace('/^(https:\/\/[^\/]*)(\/.*){0,1}$/', '\\1', $site_domain['SSLUrl']);
 						$this->Application->Redirect('external:' . $ssl_url . $_SERVER['REQUEST_URI']);
 					}
 					elseif ((PROTOCOL == 'http://') && !$site_domain['DomainNameUsesRegExp']) {
 						$this->Application->Redirect('external:http://' . $site_domain['DomainName'] . $_SERVER['REQUEST_URI']);
 					}
 				}
 
 				return $domain_by_ip;
 			}
 
 			return $domain_by_name;
 		}
 
 		/**
 		 * Load item if id is available
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function LoadItem(kEvent $event)
 		{
 			if ( $this->Application->isAdmin ) {
 				// don't load domain data from cache
 				parent::LoadItem($event);
 
 				return;
 			}
 
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$id = (int)$this->getPassedID($event);
 
 			if ( $object->isLoaded() && ($object->GetID() == $id) ) {
 				// object is already loaded by same id
 				return;
 			}
 
 			$site_helper = $this->Application->recallObject('SiteHelper');
 			/* @var $site_helper SiteHelper */
 
 			$site_domains = $site_helper->getSiteDomains();
 			$domain_data = array_key_exists($id, $site_domains) ? $site_domains[$id] : false;
 
 			if ( $object->LoadFromHash($domain_data) ) {
 				$actions = $this->Application->recallObject('kActions');
 				/* @var $actions Params */
 
 				$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
 			}
 			else {
 				$object->setID($id);
 			}
 		}
 
 		/**
 		 * Removes In-Commerce related fields, when it's not installed
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterConfigRead(kEvent $event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			if (!$this->Application->isModuleEnabled('In-Commerce')) {
 				$remove_fields = Array (
 					'BillingCountry', 'ShippingCountry',
 					'PrimaryCurrencyId', 'Currencies',
 					'PrimaryPaymentTypeId', 'PaymentTypes'
 				);
 
+				$config = $event->getUnitConfig();
+
 				// remove field definitions
-				$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
+				$fields = $config->getFields();
 
 				foreach ($remove_fields as $remove_field) {
 					unset($fields[$remove_field]);
 				}
 
-				$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
+				$config->setFields($fields);
 
 				// remove grid columns
-				$grids = $this->Application->getUnitOption($event->Prefix, 'Grids', Array ());
-				/* @var $grids Array */
+				$grids = $config->getGrids(Array ());
 
 				foreach ($grids as $grid_name => $grid_info) {
 					foreach ($remove_fields as $remove_field) {
 						if (array_key_exists($remove_field, $grid_info['Fields'])) {
 							unset($grids[$grid_name]['Fields'][$remove_field]);
 						}
 					}
 				}
 
-				$this->Application->setUnitOption($event->Prefix, 'Grids', $grids);
+				$config->setGrids($grids);
 			}
 		}
 
 		/**
 		 * Delete cached information about site domains
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSave(kEvent $event)
 		{
 			parent::OnSave($event);
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				$this->_deleteCache();
 			}
 		}
 
 		/**
 		 * Deletes cached information about site domains
 		 */
 		function _deleteCache()
 		{
 			if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
 				$this->Application->rebuildCache('master:domains_parsed', kCache::REBUILD_LATER, CacheSettings::$domainsParsedRebuildTime);
 			}
 			else {
 				$this->Application->rebuildDBCache('domains_parsed', kCache::REBUILD_LATER, CacheSettings::$domainsParsedRebuildTime);
 			}
 
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls';
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Sets required fields based on redirect mode
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(kEvent $event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$this->_setRequired($event);
 		}
 
 		/**
 		 * Set's required fields based on redirect mode
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(kEvent $event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$this->_itemChanged($event);
 		}
 
 		/**
 		 * Set's required fields based on redirect mode
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(kEvent $event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->_itemChanged($event);
 		}
 
 		/**
 		 * Occurs before item is changed
 		 *
 		 * @param kEvent $event
 		 */
 		function _itemChanged($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$email_event_data = $this->Application->GetVar('email-template_' . $event->Prefix);
 			$object->SetDBField('DefaultEmailRecipients', $email_event_data[0]['Recipients']);
 
 			$this->_setRequired($event);
 		}
 
 		/**
 		 * Set's required fields
 		 *
 		 * @param kEvent $event
 		 */
 		function _setRequired($event)
 		{
 			$object = $event->getObject();
 			/* @var $object kDBItem */
 
 			$redirect_mode = $object->GetDBField('RedirectOnIPMatch');
 			$object->setRequired('ExternalUrl', $redirect_mode == SITE_DOMAIN_REDIRECT_EXTERNAL);
 			$object->setRequired('DomainIPRange', $redirect_mode > 0);
 		}
 	}
Index: branches/5.3.x/core/install/upgrades.php
===================================================================
--- branches/5.3.x/core/install/upgrades.php	(revision 15697)
+++ branches/5.3.x/core/install/upgrades.php	(revision 15698)
@@ -1,2360 +1,2363 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	$upgrade_class = 'CoreUpgrades';
 
 	/**
 	 * Class, that holds all upgrade scripts for "Core" module
 	 *
 	 */
 	class CoreUpgrades extends kUpgradeHelper {
 
 		/**
 		 * Tables, that were renamed during 5.2.0 version upgrade
 		 *
 		 * @var Array
 		 * @access private
 		 */
 		private $renamedTables = Array (
 			'ban-rule' => Array ('from' => 'BanRules', 'to' => 'UserBanRules'),
 			'conf' => Array ('from' => 'ConfigurationValues', 'to' => 'SystemSettings'),
 			'c' => Array ('from' => 'Category', 'to' => 'Categories'),
 			'cf' => Array ('from' => 'CustomField', 'to' => 'CustomFields'),
 			'draft' => Array ('from' => 'Drafts', 'to' => 'FormSubmissionReplyDrafts'),
 			'email-template' => Array ('from' => 'Events', 'to' => 'EmailEvents'),
 			'fav' => Array ('from' => 'Favorites', 'to' => 'UserFavorites'),
 			'img' => Array ('from' => 'Images', 'to' => 'CatalogImages'),
 			'#file' => Array ('from' => 'ItemFiles', 'to' => 'CatalogFiles'),
 			'rev' => Array ('from' => 'ItemReview', 'to' => 'CatalogReviews'),
 			'lang' => Array ('from' => 'Language', 'to' => 'Languages'),
 			'permission-type' => Array ('from' => 'PermissionConfig', 'to' => 'CategoryPermissionsConfig'),
 			'phrases' => Array ('from' => 'Phrase', 'to' => 'LanguageLabels'),
 			'g' => Array ('from' => 'PortalGroup', 'to' => 'UserGroups'),
 			'user-profile' => Array ('from' => 'PersistantSessionData', 'to' => 'UserPersistentSessionData'),
 			'u' => Array ('from' => 'PortalUser', 'to' => 'Users'),
 			'u-cdata' => Array ('from' => 'PortalUserCustomData', 'to' => 'UserCustomData'),
 			'search' => Array ('from' => 'RelatedSearches', 'to' => 'CategoryRelatedSearches'),
 			'rel' => Array ('from' => 'Relationship', 'to' => 'CatalogRelationships'),
 			'search-log' => Array ('from' => 'SearchLog', 'to' => 'SearchLogs'),
 			'skin' => Array ('from' => 'Skins', 'to' => 'AdminSkins'),
 			'submission-log' => Array ('from' => 'SubmissionLog', 'to' => 'FormSubmissionReplies'),
 			'theme' => Array ('from' => 'Theme', 'to' => 'Themes'),
 			'ug' => Array ('from' => 'UserGroup', 'to' => 'UserGroupRelations'),
 			'visits' => Array ('from' => 'Visits', 'to' => 'UserVisits'),
 			'session-log' => Array ('from' => 'SessionLogs', 'to' => 'UserSessionLogs'),
 		);
 
 		/**
 		 * Changes table structure, where multilingual fields of TEXT type are present
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_4_1_0($mode)
 		{
 			if ($mode == 'before') {
 				// don't user after, because In-Portal calls this method too
 				$this->_toolkit->SaveConfig();
 			}
 
 			if ($mode == 'after') {
 				$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 				/* @var $ml_helper kMultiLanguageHelper */
 
 				$this->Application->UnitConfigReader->iterateConfigs(Array (&$this, 'updateTextFields'), $ml_helper->getLanguages());
 			}
 		}
 
 		/**
 		 * Moves ReplacementTags functionality from EmailMessage to Events table
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_4_1_1($mode)
 		{
 			if ($mode == 'after') {
 				$sql = 'SELECT ReplacementTags, EventId
 						FROM '.TABLE_PREFIX.'EmailMessage
 						WHERE (ReplacementTags IS NOT NULL) AND (ReplacementTags <> "") AND (LanguageId = 1)';
 				$replacement_tags = $this->Conn->GetCol($sql, 'EventId');
 
 				foreach ($replacement_tags as $event_id => $replacement_tag) {
 					$sql = 'UPDATE '.TABLE_PREFIX.'Events
 							SET ReplacementTags = '.$this->Conn->qstr($replacement_tag).'
 							WHERE EventId = '.$event_id;
 					$this->Conn->Query($sql);
 				}
 
 				// drop moved field from source table
 				$sql = 'ALTER TABLE '.TABLE_PREFIX.'EmailMessage
 						DROP `ReplacementTags`';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Callback function, that makes all ml fields of text type null with same default value
 		 *
 		 * @param string $prefix
 		 * @param Array $config_data
 		 * @param Array $languages
 		 * @return bool
 		 */
 		function updateTextFields($prefix, &$config_data, $languages)
 		{
 			if (!isset($config_data['TableName']) || !isset($config_data['Fields'])) {
 				// invalid config found or prefix not found
 				return false;
 			}
 
 			$table_name = $config_data['TableName'];
 			$table_structure = $this->Conn->Query('DESCRIBE '.$table_name, 'Field');
 			if (!$table_structure) {
 				// table not found
 				return false;
 			}
 
 			$sqls = Array ();
 			foreach ($config_data['Fields'] as $field => $options) {
 				if (isset($options['formatter']) && $options['formatter'] == 'kMultiLanguage' && !isset($options['master_field'])) {
 					// update all l<lang_id>_<field_name> fields (new format)
 
 					foreach ($languages as $language_id) {
 						$ml_field = 'l'.$language_id.'_'.$field;
 
 						if ($table_structure[$ml_field]['Type'] == 'text') {
 							$sqls[] = 'CHANGE '.$ml_field.' '.$ml_field.' TEXT NULL DEFAULT NULL';
 						}
 					}
 
 					// update <field_name> if found (old format)
 					if (isset($table_structure[$field]) && $table_structure[$field]['Type'] == 'text') {
 						$sqls[] = 'CHANGE '.$field.' '.$field.' TEXT NULL DEFAULT NULL';
 					}
 				}
 			}
 
 			if ($sqls) {
 				$sql = 'ALTER TABLE '.$table_name.' '.implode(', ', $sqls);
 				$this->Conn->Query($sql);
 			}
 
 			return true;
 		}
 
 		/**
 		 * Replaces In-Portal tags in Forgot Password related email events to K4 ones
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_4_2_0($mode)
 		{
 			if ($mode == 'after') {
 				// 1. get event ids based on their name and type combination
 				$event_names = Array (
 					'USER.PSWD_' . EmailTemplate::TEMPLATE_TYPE_ADMIN,
 					'USER.PSWD_' . EmailTemplate::TEMPLATE_TYPE_FRONTEND,
 					'USER.PSWDC_' . EmailTemplate::TEMPLATE_TYPE_FRONTEND,
 				);
 
 				$event_sql = Array ();
 				foreach ($event_names as $mixed_event) {
 					list ($event_name, $event_type) = explode('_', $mixed_event, 2);
 					$event_sql[] = 'Event = "'.$event_name.'" AND Type = '.$event_type;
 				}
 
 				$sql = 'SELECT EventId
 						FROM '.TABLE_PREFIX.'Events
 						WHERE ('.implode(') OR (', $event_sql).')';
 				$event_ids = implode(',', $this->Conn->GetCol($sql));
 
 				// 2. replace In-Portal tags to K4 tags
 				$replacements = Array (
 					'<inp:touser _Field="Password" />' => '<inp2:u_ForgottenPassword />',
 					'<inp:m_confirm_password_link />' => '<inp2:u_ConfirmPasswordLink no_amp="1"/>',
 				);
 
 				foreach ($replacements as $old_tag => $new_tag) {
 					$sql = 'UPDATE '.TABLE_PREFIX.'EmailMessage
 							SET Template = REPLACE(Template, '.$this->Conn->qstr($old_tag).', '.$this->Conn->qstr($new_tag).')
 							WHERE EventId IN ('.$event_ids.')';
 					$this->Conn->Query($sql);
 				}
 
 			}
 		}
 
 		/**
 		 * Makes admin primary language same as front-end - not needed, done in SQL
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_4_2_1($mode)
 		{
 
 		}
 
 		function Upgrade_4_2_2($mode)
 		{
 			if ( $mode == 'before' ) {
 				$sql = 'SELECT LanguageId
 						FROM ' . TABLE_PREFIX . 'Language
 						WHERE PrimaryLang = 1';
 
 				if ( $this->Conn->GetOne($sql) ) {
 					return;
 				}
 
 				$this->Conn->Query('UPDATE ' . TABLE_PREFIX . 'Language SET PrimaryLang = 1 ORDER BY LanguageId LIMIT 1');
 			}
 		}
 
 		/**
 		 * Adds index to "dob" field in "PortalUser" table when it's missing
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_4_3_1($mode)
 		{
 			if ($mode == 'after') {
 				$sql = 'DESCRIBE ' . TABLE_PREFIX . 'PortalUser';
 				$structure = $this->Conn->Query($sql);
 
 				foreach ($structure as $field_info) {
 					if ($field_info['Field'] == 'dob') {
 						if (!$field_info['Key']) {
 							$sql = 'ALTER TABLE ' . TABLE_PREFIX . 'PortalUser
 									ADD INDEX (dob)';
 							$this->Conn->Query($sql);
 						}
 						break;
 					}
 				}
 			}
 		}
 
 		/**
 		 * Removes duplicate phrases, update file paths in database
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_4_3_9($mode)
 		{
 			// 1. find In-Portal old <inp: tags
 			$sql = 'SELECT EmailMessageId
 					FROM '.TABLE_PREFIX.'EmailMessage
 					WHERE Template LIKE \'%<inp:%\'';
 			$event_ids = implode(',', $this->Conn->GetCol($sql));
 
 			// 2. replace In-Portal old <inp: tags to K4 tags
 			$replacements = Array (
 				'<inp:m_category_field _Field="Name" _StripHTML="1"' => '<inp2:c_Field name="Name"',
 				'<inp:touser _Field="password"' => '<inp2:u_Field name="Password_plain"',
 				'<inp:touser _Field="UserName"' => '<inp2:u_Field name="Login"',
 				'<inp:touser _Field="' => '<inp2:u_Field name="',
 				'<inp:m_page_title' => '<inp2:m_BaseUrl',
 				'<inp:m_theme_url _page="current"' => '<inp2:m_BaseUrl',
 				'<inp:topic _field="text"' => '<inp2:bb-post_Field name="PostingText"',
 				'<inp:topic _field="link" _Template="inbulletin/post_list"' => '<inp2:bb_TopicLink template="__default__"',
 			);
 
 			if ($event_ids) {
 				foreach ($replacements as $old_tag => $new_tag) {
 					$sql = 'UPDATE '.TABLE_PREFIX.'EmailMessage
 							SET Template = REPLACE(Template, '.$this->Conn->qstr($old_tag).', '.$this->Conn->qstr($new_tag).')
 							WHERE EventId IN ('.$event_ids.')';
 					$this->Conn->Query($sql);
 				}
 			}
 
 			if ($mode == 'after') {
 				$this->_insertInPortalData();
 				$this->_moveDatabaseFolders();
 
 				// in case, when In-Portal module is enabled -> turn AdvancedUserManagement on too
 				if ($this->Application->findModule('Name', 'In-Portal')) {
 					$sql = 'UPDATE ' . TABLE_PREFIX . 'ConfigurationValues
 							SET VariableValue = 1
 							WHERE VariableName = "AdvancedUserManagement"';
 					$this->Conn->Query($sql);
 				}
 			}
 
 			if ($mode == 'languagepack') {
 				$this->_removeDuplicatePhrases();
 			}
 		}
 
 		function _insertInPortalData()
 		{
 			$data = Array (
 				'ConfigurationAdmin' => Array (
 					'UniqueField' => 'VariableName',
 					'Records' => Array (
 						'AllowDeleteRootCats' => "('AllowDeleteRootCats', 'la_Text_General', 'la_AllowDeleteRootCats', 'checkbox', NULL , NULL , 10.09, 0, 0)",
 						'Catalog_PreselectModuleTab' => "('Catalog_PreselectModuleTab', 'la_Text_General', 'la_config_CatalogPreselectModuleTab', 'checkbox', NULL, NULL, 10.10, 0, 1)",
 						'RecycleBinFolder' => "('RecycleBinFolder', 'la_Text_General', 'la_config_RecycleBinFolder', 'text', NULL , NULL , 10.11, 0, 0)",
 						'AdvancedUserManagement' => "('AdvancedUserManagement', 'la_Text_General', 'la_prompt_AdvancedUserManagement', 'checkbox', NULL, NULL, '10.011', 0, 1)",
 					),
 				),
 
 				'ConfigurationValues' => Array (
 					'UniqueField' => 'VariableName',
 					'Records' => Array (
 						'AdvancedUserManagement' => "(DEFAULT, 'AdvancedUserManagement', 0, 'In-Portal:Users', 'in-portal:configure_users')",
 					),
 				),
 
 				'ItemTypes' => Array (
 					'UniqueField' => 'ItemType',
 					'Records' => Array (
 						'1' => "(1, 'In-Portal', 'c', 'Category', 'Name', 'CreatedById', NULL, NULL, 'la_ItemTab_Categories', 1, 'admin/category/addcategory.php', 'clsCategory', 'Category')",
 						'6' => "(6, 'In-Portal', 'u', 'PortalUser', 'Login', 'PortalUserId', NULL, NULL, '', 0, '', 'clsPortalUser', 'User')",
 					),
 				),
 
 				'PermissionConfig' => Array (
 					'UniqueField' => 'PermissionName',
 					'Records' => Array (
 						'CATEGORY.ADD' => "(DEFAULT, 'CATEGORY.ADD', 'lu_PermName_Category.Add_desc', 'lu_PermName_Category.Add_error', 'In-Portal')",
 						'CATEGORY.DELETE' => "(DEFAULT, 'CATEGORY.DELETE', 'lu_PermName_Category.Delete_desc', 'lu_PermName_Category.Delete_error', 'In-Portal')",
 						'CATEGORY.ADD.PENDING' => "(DEFAULT, 'CATEGORY.ADD.PENDING', 'lu_PermName_Category.AddPending_desc', 'lu_PermName_Category.AddPending_error', 'In-Portal')",
 						'CATEGORY.MODIFY' => "(DEFAULT, 'CATEGORY.MODIFY', 'lu_PermName_Category.Modify_desc', 'lu_PermName_Category.Modify_error', 'In-Portal')",
 						'ADMIN' => "(DEFAULT, 'ADMIN', 'lu_PermName_Admin_desc', 'lu_PermName_Admin_error', 'Admin')",
 						'LOGIN' => "(DEFAULT, 'LOGIN', 'lu_PermName_Login_desc', 'lu_PermName_Admin_error', 'Front')",
 						'DEBUG.ITEM' => "(DEFAULT, 'DEBUG.ITEM', 'lu_PermName_Debug.Item_desc', '', 'Admin')",
 						'DEBUG.LIST' => "(DEFAULT, 'DEBUG.LIST', 'lu_PermName_Debug.List_desc', '', 'Admin')",
 						'DEBUG.INFO' => "(DEFAULT, 'DEBUG.INFO', 'lu_PermName_Debug.Info_desc', '', 'Admin')",
 						'PROFILE.MODIFY' => "(DEFAULT, 'PROFILE.MODIFY', 'lu_PermName_Profile.Modify_desc', '', 'Admin')",
 						'SHOWLANG' => "(DEFAULT, 'SHOWLANG', 'lu_PermName_ShowLang_desc', '', 'Admin')",
 						'FAVORITES' => "(DEFAULT, 'FAVORITES', 'lu_PermName_favorites_desc', 'lu_PermName_favorites_error', 'In-Portal')",
 						'SYSTEM_ACCESS.READONLY' => "(DEFAULT, 'SYSTEM_ACCESS.READONLY', 'la_PermName_SystemAccess.ReadOnly_desc', 'la_PermName_SystemAccess.ReadOnly_error', 'Admin')",
 					),
 				),
 
 				'Permissions' => Array (
 					'UniqueField' => 'Permission;GroupId;Type;CatId',
 					'Records' => Array (
 						'LOGIN;12;1;0' => "(DEFAULT, 'LOGIN', 12, 1, 1, 0)",
 						'in-portal:site.view;11;1;0' => "(DEFAULT, 'in-portal:site.view', 11, 1, 1, 0)",
 						'in-portal:browse.view;11;1;0' => "(DEFAULT, 'in-portal:browse.view', 11, 1, 1, 0)",
 						'in-portal:advanced_view.view;11;1;0' => "(DEFAULT, 'in-portal:advanced_view.view', 11, 1, 1, 0)",
 						'in-portal:reviews.view;11;1;0' => "(DEFAULT, 'in-portal:reviews.view', 11, 1, 1, 0)",
 						'in-portal:configure_categories.view;11;1;0' => "(DEFAULT, 'in-portal:configure_categories.view', 11, 1, 1, 0)",
 						'in-portal:configure_categories.edit;11;1;0' => "(DEFAULT, 'in-portal:configure_categories.edit', 11, 1, 1, 0)",
 						'in-portal:configuration_search.view;11;1;0' => "(DEFAULT, 'in-portal:configuration_search.view', 11, 1, 1, 0)",
 						'in-portal:configuration_search.edit;11;1;0' => "(DEFAULT, 'in-portal:configuration_search.edit', 11, 1, 1, 0)",
 						'in-portal:configuration_email.view;11;1;0' => "(DEFAULT, 'in-portal:configuration_email.view', 11, 1, 1, 0)",
 						'in-portal:configuration_email.edit;11;1;0' => "(DEFAULT, 'in-portal:configuration_email.edit', 11, 1, 1, 0)",
 						'in-portal:configuration_custom.view;11;1;0' => "(DEFAULT, 'in-portal:configuration_custom.view', 11, 1, 1, 0)",
 						'in-portal:configuration_custom.add;11;1;0' => "(DEFAULT, 'in-portal:configuration_custom.add', 11, 1, 1, 0)",
 						'in-portal:configuration_custom.edit;11;1;0' => "(DEFAULT, 'in-portal:configuration_custom.edit', 11, 1, 1, 0)",
 						'in-portal:configuration_custom.delete;11;1;0' => "(DEFAULT, 'in-portal:configuration_custom.delete', 11, 1, 1, 0)",
 						'in-portal:users.view;11;1;0' => "(DEFAULT, 'in-portal:users.view', 11, 1, 1, 0)",
 						'in-portal:user_list.advanced:ban;11;1;0' => "(DEFAULT, 'in-portal:user_list.advanced:ban', 11, 1, 1, 0)",
 						'in-portal:user_list.advanced:send_email;11;1;0' => "(DEFAULT, 'in-portal:user_list.advanced:send_email', 11, 1, 1, 0)",
 						'in-portal:user_groups.view;11;1;0' => "(DEFAULT, 'in-portal:user_groups.view', 11, 1, 1, 0)",
 						'in-portal:user_groups.add;11;1;0' => "(DEFAULT, 'in-portal:user_groups.add', 11, 1, 1, 0)",
 						'in-portal:user_groups.edit;11;1;0' => "(DEFAULT, 'in-portal:user_groups.edit', 11, 1, 1, 0)",
 						'in-portal:user_groups.delete;11;1;0' => "(DEFAULT, 'in-portal:user_groups.delete', 11, 1, 1, 0)",
 						'in-portal:user_groups.advanced:send_email;11;1;0' => "(DEFAULT, 'in-portal:user_groups.advanced:send_email', 11, 1, 1, 0)",
 						'in-portal:user_groups.advanced:manage_permissions;11;1;0' => "(DEFAULT, 'in-portal:user_groups.advanced:manage_permissions', 11, 1, 1, 0)",
 						'in-portal:configure_users.view;11;1;0' => "(DEFAULT, 'in-portal:configure_users.view', 11, 1, 1, 0)",
 						'in-portal:configure_users.edit;11;1;0' => "(DEFAULT, 'in-portal:configure_users.edit', 11, 1, 1, 0)",
 						'in-portal:user_email.view;11;1;0' => "(DEFAULT, 'in-portal:user_email.view', 11, 1, 1, 0)",
 						'in-portal:user_email.edit;11;1;0' => "(DEFAULT, 'in-portal:user_email.edit', 11, 1, 1, 0)",
 						'in-portal:user_custom.view;11;1;0' => "(DEFAULT, 'in-portal:user_custom.view', 11, 1, 1, 0)",
 						'in-portal:user_custom.add;11;1;0' => "(DEFAULT, 'in-portal:user_custom.add', 11, 1, 1, 0)",
 						'in-portal:user_custom.edit;11;1;0' => "(DEFAULT, 'in-portal:user_custom.edit', 11, 1, 1, 0)",
 						'in-portal:user_custom.delete;11;1;0' => "(DEFAULT, 'in-portal:user_custom.delete', 11, 1, 1, 0)",
 						'in-portal:user_banlist.view;11;1;0' => "(DEFAULT, 'in-portal:user_banlist.view', 11, 1, 1, 0)",
 						'in-portal:user_banlist.add;11;1;0' => "(DEFAULT, 'in-portal:user_banlist.add', 11, 1, 1, 0)",
 						'in-portal:user_banlist.edit;11;1;0' => "(DEFAULT, 'in-portal:user_banlist.edit', 11, 1, 1, 0)",
 						'in-portal:user_banlist.delete;11;1;0' => "(DEFAULT, 'in-portal:user_banlist.delete', 11, 1, 1, 0)",
 						'in-portal:reports.view;11;1;0' => "(DEFAULT, 'in-portal:reports.view', 11, 1, 1, 0)",
 						'in-portal:log_summary.view;11;1;0' => "(DEFAULT, 'in-portal:log_summary.view', 11, 1, 1, 0)",
 						'in-portal:searchlog.view;11;1;0' => "(DEFAULT, 'in-portal:searchlog.view', 11, 1, 1, 0)",
 						'in-portal:searchlog.delete;11;1;0' => "(DEFAULT, 'in-portal:searchlog.delete', 11, 1, 1, 0)",
 						'in-portal:sessionlog.view;11;1;0' => "(DEFAULT, 'in-portal:sessionlog.view', 11, 1, 1, 0)",
 						'in-portal:sessionlog.delete;11;1;0' => "(DEFAULT, 'in-portal:sessionlog.delete', 11, 1, 1, 0)",
 						'in-portal:emaillog.view;11;1;0' => "(DEFAULT, 'in-portal:emaillog.view', 11, 1, 1, 0)",
 						'in-portal:emaillog.delete;11;1;0' => "(DEFAULT, 'in-portal:emaillog.delete', 11, 1, 1, 0)",
 						'in-portal:visits.view;11;1;0' => "(DEFAULT, 'in-portal:visits.view', 11, 1, 1, 0)",
 						'in-portal:visits.delete;11;1;0' => "(DEFAULT, 'in-portal:visits.delete', 11, 1, 1, 0)",
 						'in-portal:configure_general.view;11;1;0' => "(DEFAULT, 'in-portal:configure_general.view', 11, 1, 1, 0)",
 						'in-portal:configure_general.edit;11;1;0' => "(DEFAULT, 'in-portal:configure_general.edit', 11, 1, 1, 0)",
 						'in-portal:modules.view;11;1;0' => "(DEFAULT, 'in-portal:modules.view', 11, 1, 1, 0)",
 						'in-portal:mod_status.view;11;1;0' => "(DEFAULT, 'in-portal:mod_status.view', 11, 1, 1, 0)",
 						'in-portal:mod_status.edit;11;1;0' => "(DEFAULT, 'in-portal:mod_status.edit', 11, 1, 1, 0)",
 						'in-portal:mod_status.advanced:approve;11;1;0' => "(DEFAULT, 'in-portal:mod_status.advanced:approve', 11, 1, 1, 0)",
 						'in-portal:mod_status.advanced:decline;11;1;0' => "(DEFAULT, 'in-portal:mod_status.advanced:decline', 11, 1, 1, 0)",
 						'in-portal:addmodule.view;11;1;0' => "(DEFAULT, 'in-portal:addmodule.view', 11, 1, 1, 0)",
 						'in-portal:addmodule.add;11;1;0' => "(DEFAULT, 'in-portal:addmodule.add', 11, 1, 1, 0)",
 						'in-portal:addmodule.edit;11;1;0' => "(DEFAULT, 'in-portal:addmodule.edit', 11, 1, 1, 0)",
 						'in-portal:tag_library.view;11;1;0' => "(DEFAULT, 'in-portal:tag_library.view', 11, 1, 1, 0)",
 						'in-portal:configure_themes.view;11;1;0' => "(DEFAULT, 'in-portal:configure_themes.view', 11, 1, 1, 0)",
 						'in-portal:configure_themes.add;11;1;0' => "(DEFAULT, 'in-portal:configure_themes.add', 11, 1, 1, 0)",
 						'in-portal:configure_themes.edit;11;1;0' => "(DEFAULT, 'in-portal:configure_themes.edit', 11, 1, 1, 0)",
 						'in-portal:configure_themes.delete;11;1;0' => "(DEFAULT, 'in-portal:configure_themes.delete', 11, 1, 1, 0)",
 						'in-portal:configure_styles.view;11;1;0' => "(DEFAULT, 'in-portal:configure_styles.view', 11, 1, 1, 0)",
 						'in-portal:configure_styles.add;11;1;0' => "(DEFAULT, 'in-portal:configure_styles.add', 11, 1, 1, 0)",
 						'in-portal:configure_styles.edit;11;1;0' => "(DEFAULT, 'in-portal:configure_styles.edit', 11, 1, 1, 0)",
 						'in-portal:configure_styles.delete;11;1;0' => "(DEFAULT, 'in-portal:configure_styles.delete', 11, 1, 1, 0)",
 						'in-portal:configure_lang.advanced:set_primary;11;1;0' => "(DEFAULT, 'in-portal:configure_lang.advanced:set_primary', 11, 1, 1, 0)",
 						'in-portal:configure_lang.advanced:import;11;1;0' => "(DEFAULT, 'in-portal:configure_lang.advanced:import', 11, 1, 1, 0)",
 						'in-portal:configure_lang.advanced:export;11;1;0' => "(DEFAULT, 'in-portal:configure_lang.advanced:export', 11, 1, 1, 0)",
 						'in-portal:tools.view;11;1;0' => "(DEFAULT, 'in-portal:tools.view', 11, 1, 1, 0)",
 						'in-portal:backup.view;11;1;0' => "(DEFAULT, 'in-portal:backup.view', 11, 1, 1, 0)",
 						'in-portal:restore.view;11;1;0' => "(DEFAULT, 'in-portal:restore.view', 11, 1, 1, 0)",
 						'in-portal:export.view;11;1;0' => "(DEFAULT, 'in-portal:export.view', 11, 1, 1, 0)",
 						'in-portal:main_import.view;11;1;0' => "(DEFAULT, 'in-portal:main_import.view', 11, 1, 1, 0)",
 						'in-portal:sql_query.view;11;1;0' => "(DEFAULT, 'in-portal:sql_query.view', 11, 1, 1, 0)",
 						'in-portal:sql_query.edit;11;1;0' => "(DEFAULT, 'in-portal:sql_query.edit', 11, 1, 1, 0)",
 						'in-portal:server_info.view;11;1;0' => "(DEFAULT, 'in-portal:server_info.view', 11, 1, 1, 0)",
 						'in-portal:help.view;11;1;0' => "(DEFAULT, 'in-portal:help.view', 11, 1, 1, 0)",
 					),
 				),
 
 				'SearchConfig' => Array (
 					'UniqueField' => 'TableName;FieldName;ModuleName',
 					'Records' => Array (
 						'Category;NewItem;In-Portal' => "('Category', 'NewItem', 0, 1, 'lu_fielddesc_category_newitem', 'lu_field_newitem', 'In-Portal', 'la_text_category', 18, DEFAULT, 0, 'boolean', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;PopItem;In-Portal' => "('Category', 'PopItem', 0, 1, 'lu_fielddesc_category_popitem', 'lu_field_popitem', 'In-Portal', 'la_text_category', 19, DEFAULT, 0, 'boolean', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;HotItem;In-Portal' => "('Category', 'HotItem', 0, 1, 'lu_fielddesc_category_hotitem', 'lu_field_hotitem', 'In-Portal', 'la_text_category', 17, DEFAULT, 0, 'boolean', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;MetaDescription;In-Portal' => "('Category', 'MetaDescription', 0, 1, 'lu_fielddesc_category_metadescription', 'lu_field_metadescription', 'In-Portal', 'la_text_category', 16, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;ParentPath;In-Portal' => "('Category', 'ParentPath', 0, 1, 'lu_fielddesc_category_parentpath', 'lu_field_parentpath', 'In-Portal', 'la_text_category', 15, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;ResourceId;In-Portal' => "('Category', 'ResourceId', 0, 1, 'lu_fielddesc_category_resourceid', 'lu_field_resourceid', 'In-Portal', 'la_text_category', 14, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;CreatedById;In-Portal' => "('Category', 'CreatedById', 0, 1, 'lu_fielddesc_category_createdbyid', 'lu_field_createdbyid', 'In-Portal', 'la_text_category', 13, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;CachedNavbar;In-Portal' => "('Category', 'CachedNavbar', 0, 1, 'lu_fielddesc_category_cachednavbar', 'lu_field_cachednavbar', 'In-Portal', 'la_text_category', 12, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;CachedDescendantCatsQty;In-Portal' => "('Category', 'CachedDescendantCatsQty', 0, 1, 'lu_fielddesc_category_cacheddescendantcatsqty', 'lu_field_cacheddescendantcatsqty', 'In-Portal', 'la_text_category', 11, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;MetaKeywords;In-Portal' => "('Category', 'MetaKeywords', 0, 1, 'lu_fielddesc_category_metakeywords', 'lu_field_metakeywords', 'In-Portal', 'la_text_category', 10, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;Priority;In-Portal' => "('Category', 'Priority', 0, 1, 'lu_fielddesc_category_priority', 'lu_field_priority', 'In-Portal', 'la_text_category', 9, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;Status;In-Portal' => "('Category', 'Status', 0, 1, 'lu_fielddesc_category_status', 'lu_field_status', 'In-Portal', 'la_text_category', 7, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;EditorsPick;In-Portal' => "('Category', 'EditorsPick', 0, 1, 'lu_fielddesc_category_editorspick', 'lu_field_editorspick', 'In-Portal', 'la_text_category', 6, DEFAULT, 0, 'boolean', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;CreatedOn;In-Portal' => "('Category', 'CreatedOn', 0, 1, 'lu_fielddesc_category_createdon', 'lu_field_createdon', 'In-Portal', 'la_text_category', 5, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;Description;In-Portal' => "('Category', 'Description', 1, 1, 'lu_fielddesc_category_description', 'lu_field_description', 'In-Portal', 'la_text_category', 4, DEFAULT, 2, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;Name;In-Portal' => "('Category', 'Name', 1, 1, 'lu_fielddesc_category_name', 'lu_field_name', 'In-Portal', 'la_text_category', 3, DEFAULT, 2, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;ParentId;In-Portal' => "('Category', 'ParentId', 0, 1, 'lu_fielddesc_category_parentid', 'lu_field_parentid', 'In-Portal', 'la_text_category', 2, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;CategoryId;In-Portal' => "('Category', 'CategoryId', 0, 1, 'lu_fielddesc_category_categoryid', 'lu_field_categoryid', 'In-Portal', 'la_text_category', 0, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;Modified;In-Portal' => "('Category', 'Modified', 0, 1, 'lu_fielddesc_category_modified', 'lu_field_modified', 'In-Portal', 'la_text_category', 20, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'Category;ModifiedById;In-Portal' => "('Category', 'ModifiedById', 0, 1, 'lu_fielddesc_category_modifiedbyid', 'lu_field_modifiedbyid', 'In-Portal', 'la_text_category', 21, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 
 						'PortalUser;PortalUserId;In-Portal' => "('PortalUser', 'PortalUserId', -1, 0, 'lu_fielddesc_user_portaluserid', 'lu_field_portaluserid', 'In-Portal', 'la_text_user', 0, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Login;In-Portal' => "('PortalUser', 'Login', -1, 0, 'lu_fielddesc_user_login', 'lu_field_login', 'In-Portal', 'la_text_user', 1, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Password;In-Portal' => "('PortalUser', 'Password', -1, 0, 'lu_fielddesc_user_password', 'lu_field_password', 'In-Portal', 'la_text_user', 2, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;tz;In-Portal' => "('PortalUser', 'tz', -1, 0, 'lu_fielddesc_user_tz', 'lu_field_tz', 'In-Portal', 'la_text_user', 17, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;dob;In-Portal' => "('PortalUser', 'dob', -1, 0, 'lu_fielddesc_user_dob', 'lu_field_dob', 'In-Portal', 'la_text_user', 16, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Modified;In-Portal' => "('PortalUser', 'Modified', -1, 0, 'lu_fielddesc_user_modified', 'lu_field_modified', 'In-Portal', 'la_text_user', 15, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Status;In-Portal' => "('PortalUser', 'Status', -1, 0, 'lu_fielddesc_user_status', 'lu_field_status', 'In-Portal', 'la_text_user', 14, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;ResourceId;In-Portal' => "('PortalUser', 'ResourceId', -1, 0, 'lu_fielddesc_user_resourceid', 'lu_field_resourceid', 'In-Portal', 'la_text_user', 13, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Country;In-Portal' => "('PortalUser', 'Country', -1, 0, 'lu_fielddesc_user_country', 'lu_field_country', 'In-Portal', 'la_text_user', 12, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Zip;In-Portal' => "('PortalUser', 'Zip', -1, 0, 'lu_fielddesc_user_zip', 'lu_field_zip', 'In-Portal', 'la_text_user', 11, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;State;In-Portal' => "('PortalUser', 'State', -1, 0, 'lu_fielddesc_user_state', 'lu_field_state', 'In-Portal', 'la_text_user', 10, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;City;In-Portal' => "('PortalUser', 'City', -1, 0, 'lu_fielddesc_user_city', 'lu_field_city', 'In-Portal', 'la_text_user', 9, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Street;In-Portal' => "('PortalUser', 'Street', -1, 0, 'lu_fielddesc_user_street', 'lu_field_street', 'In-Portal', 'la_text_user', 8, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Phone;In-Portal' => "('PortalUser', 'Phone', -1, 0, 'lu_fielddesc_user_phone', 'lu_field_phone', 'In-Portal', 'la_text_user', 7, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;CreatedOn;In-Portal' => "('PortalUser', 'CreatedOn', -1, 0, 'lu_fielddesc_user_createdon', 'lu_field_createdon', 'In-Portal', 'la_text_user', 6, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;Email;In-Portal' => "('PortalUser', 'Email', -1, 0, 'lu_fielddesc_user_email', 'lu_field_email', 'In-Portal', 'la_text_user', 5, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;LastName;In-Portal' => "('PortalUser', 'LastName', -1, 0, 'lu_fielddesc_user_lastname', 'lu_field_lastname', 'In-Portal', 'la_text_user', 4, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 						'PortalUser;FirstName;In-Portal' => "('PortalUser', 'FirstName', -1, 0, 'lu_fielddesc_user_firstname', 'lu_field_firstname', 'In-Portal', 'la_text_user', 3, DEFAULT, 0, 'text', NULL, NULL, NULL, NULL, NULL, NULL, NULL)",
 					),
 				),
 
 				'StatItem' => Array (
 					'UniqueField' => 'Module;ListLabel',
 					'Records' => Array (
 						'In-Portal;la_prompt_ActiveCategories' => "(DEFAULT, 'In-Portal', 'SELECT count(*) FROM <%prefix%>Category WHERE Status=1 ', NULL, 'la_prompt_ActiveCategories', '0', '1')",
 						'In-Portal;la_prompt_ActiveUsers' => "(DEFAULT, 'In-Portal', 'SELECT count(*) FROM <%prefix%>PortalUser WHERE Status=1 ', NULL, 'la_prompt_ActiveUsers', '0', '1')",
 						'In-Portal;la_prompt_CurrentSessions' => "(DEFAULT, 'In-Portal', 'SELECT count(*) FROM <%prefix%>UserSession', NULL, 'la_prompt_CurrentSessions', '0', '1')",
 						'In-Portal;la_prompt_TotalCategories' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) as CategoryCount FROM <%prefix%>Category', NULL, 'la_prompt_TotalCategories', 0, 2)",
 						'In-Portal;la_prompt_ActiveCategories' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS ActiveCategories FROM <%prefix%>Category WHERE Status = 1', NULL, 'la_prompt_ActiveCategories', 0, 2)",
 						'In-Portal;la_prompt_PendingCategories' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS PendingCategories FROM <%prefix%>Category WHERE Status = 2', NULL, 'la_prompt_PendingCategories', 0, 2)",
 						'In-Portal;la_prompt_DisabledCategories' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS DisabledCategories FROM <%prefix%>Category WHERE Status = 0', NULL, 'la_prompt_DisabledCategories', 0, 2)",
 						'In-Portal;la_prompt_NewCategories' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS NewCategories FROM <%prefix%>Category WHERE (NewItem = 1) OR ( (UNIX_TIMESTAMP() - CreatedOn) <= <%m:config name=\"Category_DaysNew\"%>*86400 AND (NewItem = 2) )', NULL, 'la_prompt_NewCategories', 0, 2)",
 						'In-Portal;la_prompt_CategoryEditorsPick' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) FROM <%prefix%>Category WHERE EditorsPick = 1', NULL, 'la_prompt_CategoryEditorsPick', 0, 2)",
 						'In-Portal;la_prompt_NewestCategoryDate' => "(DEFAULT, 'In-Portal', 'SELECT <%m:post_format field=\"MAX(CreatedOn)\" type=\"date\"%> FROM <%prefix%>Category', NULL, 'la_prompt_NewestCategoryDate', 0, 2)",
 						'In-Portal;la_prompt_LastCategoryUpdate' => "(DEFAULT, 'In-Portal', 'SELECT <%m:post_format field=\"MAX(Modified)\" type=\"date\"%> FROM <%prefix%>Category', NULL, 'la_prompt_LastCategoryUpdate', 0, 2)",
 						'In-Portal;la_prompt_TopicsUsers' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS TotalUsers FROM <%prefix%>PortalUser', NULL, 'la_prompt_TopicsUsers', 0, 2)",
 						'In-Portal;la_prompt_UsersActive' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS ActiveUsers FROM <%prefix%>PortalUser WHERE Status = 1', NULL, 'la_prompt_UsersActive', 0, 2)",
 						'In-Portal;la_prompt_UsersPending' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS PendingUsers FROM <%prefix%>PortalUser WHERE Status = 2', NULL, 'la_prompt_UsersPending', 0, 2)",
 						'In-Portal;la_prompt_UsersDisabled' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS DisabledUsers FROM <%prefix%>PortalUser WHERE Status = 0', NULL, 'la_prompt_UsersDisabled', 0, 2)",
 						'In-Portal;la_prompt_NewestUserDate' => "(DEFAULT, 'In-Portal', 'SELECT <%m:post_format field=\"MAX(CreatedOn)\" type=\"date\"%> FROM <%prefix%>PortalUser', NULL, 'la_prompt_NewestUserDate', 0, 2)",
 						'In-Portal;la_prompt_UsersUniqueCountries' => "(DEFAULT, 'In-Portal', 'SELECT COUNT( DISTINCT LOWER( Country ) ) FROM <%prefix%>PortalUser WHERE LENGTH(Country) > 0', NULL, 'la_prompt_UsersUniqueCountries', 0, 2)",
 						'In-Portal;la_prompt_UsersUniqueStates' => "(DEFAULT, 'In-Portal', 'SELECT COUNT( DISTINCT LOWER( State ) ) FROM <%prefix%>PortalUser WHERE LENGTH(State) > 0', NULL, 'la_prompt_UsersUniqueStates', 0, 2)",
 						'In-Portal;la_prompt_TotalUserGroups' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS TotalUserGroups FROM <%prefix%>PortalGroup', NULL, 'la_prompt_TotalUserGroups', 0, 2)",
 						'In-Portal;la_prompt_BannedUsers' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS BannedUsers FROM <%prefix%>PortalUser WHERE IsBanned = 1', NULL, 'la_prompt_BannedUsers', 0, 2)",
 						'In-Portal;la_prompt_NonExpiredSessions' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS NonExipedSessions FROM <%prefix%>UserSession WHERE Status = 1', NULL, 'la_prompt_NonExpiredSessions', 0, 2)",
 						'In-Portal;la_prompt_ThemeCount' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS ThemeCount FROM <%prefix%>Theme', NULL, 'la_prompt_ThemeCount', 0, 2)",
 						'In-Portal;la_prompt_RegionsCount' => "(DEFAULT, 'In-Portal', 'SELECT COUNT(*) AS RegionsCount FROM <%prefix%>Language', NULL, 'la_prompt_RegionsCount', 0, 2)",
 						'In-Portal;la_prompt_TablesCount' => "(DEFAULT, 'In-Portal', '<%m:sql_action sql=\"SHOW+TABLES\" action=\"COUNT\" field=\"*\"%>', NULL, 'la_prompt_TablesCount', 0, 2)",
 						'In-Portal;la_prompt_RecordsCount' => "(DEFAULT, 'In-Portal', '<%m:sql_action sql=\"SHOW+TABLE+STATUS\" action=\"SUM\" field=\"Rows\"%>', NULL, 'la_prompt_RecordsCount', 0, 2)",
 						'In-Portal;la_prompt_SystemFileSize' => "(DEFAULT, 'In-Portal', '<%m:custom_action sql=\"empty\" action=\"SysFileSize\"%>', NULL, 'la_prompt_SystemFileSize', 0, 2)",
 						'In-Portal;la_prompt_DataSize' => "(DEFAULT, 'In-Portal', '<%m:sql_action sql=\"SHOW+TABLE+STATUS\" action=\"SUM\" format_as=\"file\" field=\"Data_length\"%>', NULL, 'la_prompt_DataSize', 0, 2)",
 					),
 				),
 
 				'StylesheetSelectors' => Array (
 					'UniqueField' => 'SelectorId',
 					'Records' => Array (
 						'169' => "(169, 8, 'Calendar''s selected days', '.calendar tbody .selected', 'a:0:{}', '', 1, 'font-weight: bold;\\\r\\nbackground-color: #9ED7ED;\\r\\nborder: 1px solid #83B2C5;', 0)",
 						'118' => "(118, 8, 'Data grid row', 'td.block-data-row', 'a:0:{}', '', 2, 'border-bottom: 1px solid #cccccc;', 48)",
 						'81' => "(81, 8, '\"More\" link', 'a.link-more', 'a:0:{}', '', 2, 'text-decoration: underline;', 64)",
 						'88' => "(88, 8, 'Block data, separated rows', 'td.block-data-grid', 'a:0:{}', '', 2, 'border: 1px solid #cccccc;', 48)",
 						'42' => "(42, 8, 'Block Header', 'td.block-header', 'a:4:{s:5:\"color\";s:16:\"rgb(0, 159, 240)\";s:9:\"font-size\";s:4:\"20px\";s:11:\"font-weight\";s:6:\"normal\";s:7:\"padding\";s:3:\"5px\";}', 'Block Header', 1, 'font-family: Verdana, Helvetica, sans-serif;', 0)",
 						'76' => "(76, 8, 'Navigation bar menu', 'tr.head-nav td', 'a:0:{}', '', 1, 'vertical-align: middle;', 0)",
 						'48' => "(48, 8, 'Block data', 'td.block-data', 'a:2:{s:9:\"font-size\";s:5:\"12px;\";s:7:\"padding\";s:3:\"5px\";}', '', 1, '', 0)",
 						'78' => "(78, 8, 'Body main style', 'body', 'a:0:{}', '', 1, 'padding: 0px; \\r\\nbackground-color: #ffffff; \\r\\nfont-family: arial, verdana, helvetica; \\r\\nfont-size: small;\\r\\nwidth: auto;\\r\\nmargin: 0px;', 0)",
 						'58' => "(58, 8, 'Main table', 'table.main-table', 'a:0:{}', '', 1, 'width: 770px;\\r\\nmargin: 0px;\\r\\n/*table-layout: fixed;*/', 0)",
 						'79' => "(79, 8, 'Block: header of data block', 'span.block-data-grid-header', 'a:0:{}', '', 1, 'font-family: Arial, Helvetica, sans-serif;\\r\\ncolor: #009DF6;\\r\\nfont-size: 12px;\\r\\nfont-weight: bold;\\r\\nbackground-color: #E6EEFF;\\r\\npadding: 6px;\\r\\nwhite-space: nowrap;', 0)",
 						'64' => "(64, 8, 'Link', 'a', 'a:0:{}', '', 1, '', 0)",
 						'46' => "(46, 8, 'Product title link', 'a.link-product1', 'a:0:{}', 'Product title link', 1, 'color: #62A1DE;\\r\\nfont-size: 14px;\\r\\nfont-weight: bold;\\r\\nline-height: 20px;\\r\\npadding-bottom: 10px;', 0)",
 						'75' => "(75, 8, 'Copy of Main path link', 'table.main-path td a:hover', 'a:0:{}', '', 1, 'color: #ffffff;', 0)",
 						'160' => "(160, 8, 'Current item in navigation bar', '.checkout-step-current', 'a:0:{}', '', 1, 'color: #A20303;\\r\\nfont-weight: bold;', 0)",
 						'51' => "(51, 8, 'Right block data', 'td.right-block-data', 'a:1:{s:9:\"font-size\";s:4:\"11px\";}', '', 2, 'padding: 7px;\\r\\nbackground: #e3edf6 url(\"/in-commerce4/themes/default/img/bgr_login.jpg\") repeat-y	scroll left top;\\r\\nborder-bottom: 1px solid #64a1df;', 48)",
 						'67' => "(67, 8, 'Pagination bar: text', 'table.block-pagination td', 'a:3:{s:5:\"color\";s:7:\"#8B898B\";s:9:\"font-size\";s:4:\"12px\";s:11:\"font-weight\";s:6:\"normal\";}', '', 1, '', 0)",
 						'45' => "(45, 8, 'Category link', 'a.subcat', 'a:0:{}', 'Category link', 1, 'color: #2069A4', 0)",
 						'68' => "(68, 8, 'Pagination bar: link', 'table.block-pagination td a', 'a:3:{s:5:\"color\";s:7:\"#8B898B\";s:9:\"font-size\";s:5:\"12px;\";s:11:\"font-weight\";s:6:\"normal\";}', '', 1, '', 0)",
 						'69' => "(69, 8, 'Product description in product list', '.product-list-description', 'a:2:{s:5:\"color\";s:7:\"#8B898B\";s:9:\"font-size\";s:4:\"12px\";}', '', 1, '', 0)",
 						'73' => "(73, 8, 'Main path link', 'table.main-path td a', 'a:0:{}', '', 1, 'color: #d5e231;', 0)",
 						'83' => "(83, 8, 'Product title link in list (shopping cart)', 'a.link-product-cart', 'a:0:{}', 'Product title link', 1, 'color: #18559C;\\r\\nfont-size: 12px;\\r\\nfont-weight: bold;\\r\\ntext-decoration: none;\\r\\n\\r\\n', 0)",
 						'72' => "(72, 8, 'Main path block text', 'table.main-path td', 'a:0:{}', '', 1, 'color: #ffffff;\\r\\nfont-size: 10px;\\r\\nfont-weight: normal;\\r\\npadding: 1px;\\r\\n', 0)",
 						'61' => "(61, 8, 'Block: header of data table', 'td.block-data-grid-header', 'a:6:{s:4:\"font\";s:28:\"Arial, Helvetica, sans-serif\";s:5:\"color\";s:7:\"#009DF6\";s:9:\"font-size\";s:4:\"12px\";s:11:\"font-weight\";s:4:\"bold\";s:16:\"background-color\";s:7:\"#E6EEFF\";s:7:\"padding\";s:3:\"6px\";}', '', 1, 'white-space: nowrap;\\r\\npadding-left: 10px;\\r\\n/*\\r\\nbackground-image: url(/in-commerce4/themes/default/img/bullet1.gif);\\r\\nbackground-position: 10px 12px;\\r\\nbackground-repeat: no-repeat;\\r\\n*/', 0)",
 						'65' => "(65, 8, 'Link in product list additional row', 'td.product-list-additional a', 'a:1:{s:5:\"color\";s:7:\"#8B898B\";}', '', 2, '', 64)",
 						'55' => "(55, 8, 'Main table, left column', 'td.main-column-left', 'a:0:{}', '', 1, 'width:180px;\\r\\nborder: 1px solid #62A1DE;\\r\\nborder-top: 0px;', 0)",
 						'70' => "(70, 8, 'Product title link in list (category)', 'a.link-product-category', 'a:0:{}', 'Product title link', 1, 'color: #18559C;\\r\\nfont-size: 12px;\\r\\nfont-weight: bold;\\r\\ntext-decoration: none;\\r\\n\\r\\n', 0)",
 						'66' => "(66, 8, 'Pagination bar block', 'table.block-pagination', 'a:0:{}', '', 1, '', 0)",
 						'49' => "(49, 8, 'Bulleted list inside block', 'td.block-data ul li', 'a:0:{}', '', 1, '    list-style-image: url(/in-commerce4/themes/default/img/bullet2.gif);\\r\\n    margin-bottom: 10px;\\r\\n    font-size: 11px;', 0)",
 						'87' => "(87, 8, 'Cart item input form element', 'td.cart-item-atributes input', 'a:0:{}', '', 1, 'border: 1px solid #7BB2E6;', 0)",
 						'119' => "(119, 8, 'Data grid row header', 'td.block-data-row-hdr', 'a:0:{}', 'Used in order preview', 2, 'background-color: #eeeeee;\\r\\nborder-bottom: 1px solid #dddddd;\\r\\nborder-top: 1px solid #cccccc;\\r\\nfont-weight: bold;', 48)",
 						'82' => "(82, 8, '\"More\" link image', 'a.link-more img', 'a:0:{}', '', 2, 'text-decoration: none;\\r\\npadding-left: 5px;', 64)",
 						'63' => "(63, 8, 'Additional info under product description in list', 'td.product-list-additional', 'a:5:{s:5:\"color\";s:7:\"#8B898B\";s:9:\"font-size\";s:4:\"11px\";s:11:\"font-weight\";s:6:\"normal\";s:10:\"border-top\";s:18:\"1px dashed #8B898B\";s:13:\"border-bottom\";s:18:\"1px dashed #8B898B\";}', '', 2, '', 48)",
 						'43' => "(43, 8, 'Block', 'table.block', 'a:2:{s:16:\"background-color\";s:7:\"#E3EEF9\";s:6:\"border\";s:17:\"1px solid #64A1DF\";}', 'Block', 1, 'border: 0; \\r\\nmargin-bottom: 1px;\\r\\nwidth: 100%;', 0)",
 						'84' => "(84, 8, 'Cart item cell', 'td.cart-item', 'a:0:{}', '', 1, 'background-color: #F6FAFF;\\r\\nborder-left: 1px solid #ffffff;\\r\\nborder-bottom: 1px solid #ffffff;\\r\\npadding: 4px;', 0)",
 						'57' => "(57, 8, 'Main table, right column', 'td.main-column-right', 'a:0:{}', '', 1, 'width:220px;\\r\\nborder: 1px solid #62A1DE;\\r\\nborder-top: 0px;', 0)",
 						'161' => "(161, 8, 'Block for sub categories', 'td.block-data-subcats', 'a:0:{}', '', 2, '  background: #FFFFFF\\r\\nurl(/in-commerce4/themes/default/in-commerce/img/bgr_categories.jpg);\\r\\n  background-repeat: no-repeat;\\r\\n  background-position: top right;\\r\\nborder-bottom: 5px solid #DEEAFF;\\r\\npadding-left: 10px;', 48)",
 						'77' => "(77, 8, 'Left block header', 'td.left-block-header', 'a:0:{}', '', 2, 'font-family : verdana, helvetica, sans-serif;\\r\\ncolor : #ffffff;\\r\\nfont-size : 12px;\\r\\nfont-weight : bold;\\r\\ntext-decoration : none;\\r\\nbackground-color: #64a1df;\\r\\npadding: 5px;\\r\\npadding-left: 7px;', 42)",
 						'80' => "(80, 8, 'Right block data - text', 'td.right-block-data td', 'a:1:{s:9:\"font-size\";s:5:\"11px;\";}', '', 2, '', 48)",
 						'53' => "(53, 8, 'Right block header', 'td.right-block-header', 'a:0:{}', '', 2, 'font-family : verdana, helvetica, sans-serif;\\r\\ncolor : #ffffff;\\r\\nfont-size : 12px;\\r\\nfont-weight : bold;\\r\\ntext-decoration : none;\\r\\nbackground-color: #64a1df;\\r\\npadding: 5px;\\r\\npadding-left: 7px;', 42)",
 						'85' => "(85, 8, 'Cart item cell with attributes', 'td.cart-item-attributes', 'a:0:{}', '', 1, 'background-color: #E6EEFF;\\r\\nborder-left: 1px solid #ffffff;\\r\\nborder-bottom: 1px solid #ffffff;\\r\\npadding: 4px;\\r\\ntext-align: center;\\r\\nvertical-align: middle;\\r\\nfont-size: 12px;\\r\\nfont-weight: normal;', 0)",
 						'86' => "(86, 8, 'Cart item cell with name', 'td.cart-item-name', 'a:0:{}', '', 1, 'background-color: #F6FAFF;\\r\\nborder-left: 1px solid #ffffff;\\r\\nborder-bottom: 1px solid #ffffff;\\r\\npadding: 3px;', 0)",
 						'47' => "(47, 8, 'Block content of featured product', 'td.featured-block-data', 'a:0:{}', '', 1, 'font-family: Arial,Helvetica,sans-serif;\\r\\nfont-size: 12px;', 0)",
 						'56' => "(56, 8, 'Main table, middle column', 'td.main-column-center', 'a:0:{}', '', 1, '\\r\\n', 0)",
 						'50' => "(50, 8, 'Product title link in list', 'a.link-product2', 'a:0:{}', 'Product title link', 1, 'color: #62A1DE;\\r\\nfont-size: 12px;\\r\\nfont-weight: bold;\\r\\ntext-decoration: none;\\r\\n\\r\\n', 0)",
 						'71' => "(71, 8, 'Main path block', 'table.main-path', 'a:0:{}', '', 1, 'background: #61b0ec url(\"/in-commerce4/themes/default/img/bgr_path.jpg\") repeat-y scroll left top;\\r\\nwidth: 100%;\\r\\nmargin-bottom: 1px;\\r\\nmargin-right: 1px; \\r\\nmargin-left: 1px;', 0)",
 						'62' => "(62, 8, 'Block: columns header for data table', 'table.block-no-border th', 'a:6:{s:4:\"font\";s:28:\"Arial, Helvetica, sans-serif\";s:5:\"color\";s:7:\"#18559C\";s:9:\"font-size\";s:4:\"11px\";s:11:\"font-weight\";s:4:\"bold\";s:16:\"background-color\";s:7:\"#B4D2EE\";s:7:\"padding\";s:3:\"6px\";}', '', 1, 'text-align: left;', 0)",
 						'59' => "(59, 8, 'Block without border', 'table.block-no-border', 'a:0:{}', '', 1, 'border: 0px; \\r\\nmargin-bottom: 10px;\\r\\nwidth: 100%;', 0)",
 						'74' => "(74, 8, 'Main path language selector cell', 'td.main-path-language', 'a:0:{}', '', 1, 'vertical-align: middle;\\r\\ntext-align: right;\\r\\npadding-right: 6px;', 0)",
 						'171' => "(171, 8, 'Calendar''s highlighted day', '.calendar tbody .hilite', 'a:0:{}', '', 1, 'background-color: #f6f6f6;\\r\\nborder: 1px solid #83B2C5 !important;', 0)",
 						'175' => "(175, 8, 'Calendar''s days', '.calendar tbody .day', 'a:0:{}', '', 1, 'text-align: right;\\r\\npadding: 2px 4px 2px 2px;\\r\\nwidth: 2em;\\r\\nborder: 1px solid #fefefe;', 0)",
 						'170' => "(170, 8, 'Calendar''s weekends', '.calendar .weekend', 'a:0:{}', '', 1, 'color: #990000;', 0)",
 						'173' => "(173, 8, 'Calendar''s control buttons', '.calendar .calendar_button', 'a:0:{}', '', 1, 'color: black;\\r\\nfont-size: 12px;\\r\\nbackground-color: #eeeeee;', 0)",
 						'174' => "(174, 8, 'Calendar''s day names', '.calendar thead .name', 'a:0:{}', '', 1, 'background-color: #DEEEF6;\\r\\nborder-bottom: 1px solid #000000;', 0)",
 						'172' => "(172, 8, 'Calendar''s top and bottom titles', '.calendar .title', 'a:0:{}', '', 1, 'color: #FFFFFF;\\r\\nbackground-color: #62A1DE;\\r\\nborder: 1px solid #107DC5;\\r\\nborder-top: 0px;\\r\\npadding: 1px;', 0)",
 						'60' => "(60, 8, 'Block header for featured product', 'td.featured-block-header', 'a:0:{}', '', 2, '\\r\\n', 42)",
 						'54' => "(54, 8, 'Right block', 'table.right-block', 'a:0:{}', '', 2, 'background-color: #E3EEF9;\\r\\nborder: 0px;\\r\\nwidth: 100%;', 43)",
 						'44' => "(44, 8, 'Block content', 'td.block-data-big', 'a:0:{}', 'Block content', 1, '  background: #DEEEF6\\r\\nurl(/in-commerce4/themes/default/img/menu_bg.gif);\\r\\n  background-repeat: no-repeat;\\r\\n  background-position: top right;\\r\\n', 0)",
 					),
 				),
 
 				'Stylesheets' => Array (
 					'UniqueField' => 'StylesheetId',
 					'Records' => Array (
 						'8' => "(8, 'Default', 'In-Portal Default Theme', '', 1124952555, 1)",
 					),
 				),
 
 				'Counters' => Array (
 					'UniqueField' => 'Name',
 					'Records' => Array (
 						'members_count' => "(DEFAULT, 'members_count', 'SELECT COUNT(*) FROM <%PREFIX%>PortalUser WHERE Status = 1', NULL , NULL , '3600', '0', '|PortalUser|')",
 						'members_online' => "(DEFAULT, 'members_online', 'SELECT COUNT(*) FROM <%PREFIX%>UserSession WHERE PortalUserId > 0', NULL , NULL , '3600', '0', '|UserSession|')",
 						'guests_online' => "(DEFAULT, 'guests_online', 'SELECT COUNT(*) FROM <%PREFIX%>UserSession WHERE PortalUserId <= 0', NULL , NULL , '3600', '0', '|UserSession|')",
 						'users_online' => "(DEFAULT, 'users_online', 'SELECT COUNT(*) FROM <%PREFIX%>UserSession', NULL , NULL , '3600', '0', '|UserSession|')",
 					),
 				),
 			);
 
 			// check & insert if not found defined before data
 			foreach ($data as $table_name => $table_info) {
 				$unique_fields = explode(';', $table_info['UniqueField']);
 				foreach ($table_info['Records'] as $unique_value => $insert_sql) {
 					$unique_values = explode(';', $unique_value);
 
 					$where_clause = Array ();
 					foreach ($unique_fields as $field_index => $unique_field) {
 						$where_clause[] = $unique_field . ' = ' . $this->Conn->qstr($unique_values[$field_index]);
 					}
 
 					$sql = 'SELECT ' . implode(', ', $unique_fields) . '
 							FROM ' . TABLE_PREFIX . $table_name . '
 							WHERE (' . implode(') AND (', $where_clause) . ')';
 					$found = $this->Conn->GetRow($sql);
 					if ($found) {
 						$found = implode(';', $found);
 					}
 
 					if ($found != $unique_value) {
 						$this->Conn->Query('INSERT INTO ' . TABLE_PREFIX . $table_name . ' VALUES ' . $insert_sql);
 					}
 				}
 			}
 		}
 
 		/**
 		 * Removes duplicate phrases per language basis (created during proj-base and in-portal shared installation)
 		 *
 		 */
 		function _removeDuplicatePhrases()
 		{
-			$id_field = $this->Application->getUnitOption('phrases', 'IDField');
-			$table_name = $this->Application->getUnitOption('phrases', 'TableName');
+			$config = $this->Application->getUnitConfig('phrases');
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			$sql = 'SELECT LanguageId, Phrase, MIN(LastChanged) AS LastChanged, COUNT(*) AS DupeCount
 					FROM ' . $table_name . '
 					GROUP BY LanguageId, Phrase
 					HAVING COUNT(*) > 1';
 			$duplicate_phrases = $this->Conn->Query($sql);
 
 			foreach ($duplicate_phrases as $phrase_record) {
 				// 1. keep phrase, that was added first, because it is selected in PhrasesCache::LoadPhraseByLabel
 				$where_clause = Array (
 					'LanguageId = ' . $phrase_record['LanguageId'],
 					'Phrase = ' . $this->Conn->qstr($phrase_record['Phrase']),
 					'LastChanged' . ' = ' . $phrase_record['LastChanged'],
 				);
 
 				$sql = 'SELECT ' . $id_field . '
 						FROM ' . $table_name . '
 						WHERE (' . implode(') AND (', $where_clause) . ')';
 				$phrase_id = $this->Conn->GetOne($sql);
 
 				// 2. delete all other duplicates
 				$where_clause = Array (
 					'LanguageId = ' . $phrase_record['LanguageId'],
 					'Phrase = ' . $this->Conn->qstr($phrase_record['Phrase']),
 					$id_field . ' <> ' . $phrase_id,
 				);
 
 				$sql = 'DELETE FROM ' . $table_name . '
 						WHERE (' . implode(') AND (', $where_clause) . ')';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		function _moveDatabaseFolders()
 		{
 			// Tables: PageContent, Images
 			if ($this->Conn->TableFound('PageContent', true)) {
 				// 1. replaces "/kernel/user_files/" references in content blocks
 				$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 				/* @var $ml_helper kMultiLanguageHelper */
 
 				$languages = $ml_helper->getLanguages();
 				$replace_sql = '%1$s = REPLACE(%1$s, "/kernel/user_files/", "/system/user_files/")';
 
 				$update_sqls = Array ();
 
 				foreach ($languages as $language_id) {
 					$update_sqls[] = sprintf($replace_sql, 'l' . $language_id . '_Content');
 				}
 
 				if ($update_sqls) {
 					$sql = 'UPDATE ' . TABLE_PREFIX . 'PageContent
 							SET ' . implode(', ', $update_sqls);
 					$this->Conn->Query($sql);
 				}
 			}
 
 			// 2. replace path of images uploaded via "Images" tab of category items
 			$this->_replaceImageFolder('/kernel/images/', '/system/images/');
 
 			// 3. replace path of images uploaded via "Images" tab of category items (when badly formatted)
 			$this->_replaceImageFolder('kernel/images/', 'system/images/');
 
 			// 4. replace images uploaded via "In-Bulletin -> Emoticons" section
 			$this->_replaceImageFolder('in-bulletin/images/emoticons/', 'system/images/emoticons/');
 
 			// 5. update backup path in config
 			$this->_toolkit->saveConfigValues(
 				Array (
 					'Backup_Path' => FULL_PATH . '/system/backupdata'
 				)
 			);
 		}
 
 		/**
 		 * Replaces mentions of "/kernel/images" folder in Images table
 		 *
 		 * @param string $from
 		 * @param string $to
 		 */
 		function _replaceImageFolder($from, $to)
 		{
 			$replace_sql = '%1$s = REPLACE(%1$s, "' . $from . '", "' . $to . '")';
 			$sql = 'UPDATE ' . TABLE_PREFIX . 'Images
 					SET ' . sprintf($replace_sql, 'ThumbPath') . ', ' . sprintf($replace_sql, 'LocalPath');
 			$this->Conn->Query($sql);
 		}
 
 
 		/**
 		 * Update colors in skin (only if they were not changed manually)
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_5_0_0($mode)
 		{
 			if ($mode == 'before') {
 				$this->_removeDuplicatePhrases(); // because In-Commerce & In-Link share some phrases with Proj-CMS
 				$this->_createProjCMSTables();
 				$this->_addMissingConfigurationVariables();
 			}
 
 			if ($mode == 'after') {
 				$this->_fixSkinColors();
 				$this->_restructureCatalog();
 				$this->_sortImages();
 
 //				$this->_sortConfigurationVariables('In-Portal', 'in-portal:configure_general');
 //				$this->_sortConfigurationVariables('In-Portal', 'in-portal:configure_advanced');
 			}
 		}
 
 		function _sortConfigurationVariables($module, $section)
 		{
 			$sql = 'SELECT ca.heading, cv.VariableName
 					FROM ' . TABLE_PREFIX . 'ConfigurationAdmin ca
 					LEFT JOIN ' . TABLE_PREFIX . 'ConfigurationValues cv USING(VariableName)
 					WHERE (cv.ModuleOwner = ' . $this->Conn->qstr($module) . ') AND (cv.Section = ' . $this->Conn->qstr($section) . ')
 					ORDER BY ca.DisplayOrder asc, ca.GroupDisplayOrder asc';
 			$variables = $this->Conn->GetCol($sql, 'VariableName');
 
 			if (!$variables) {
 				return ;
 			}
 
 			$variables = $this->_groupRecords($variables);
 
 			$group_number = 0;
 			$variable_order = 1;
 
 			$prev_heading = '';
 
 			foreach ($variables as $variable_name => $variable_heading) {
 				if ($prev_heading != $variable_heading) {
 					$group_number++;
 					$variable_order = 1;
 				}
 
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'ConfigurationAdmin
 						SET DisplayOrder = ' . $this->Conn->qstr($group_number * 10 + $variable_order / 100) . '
 						WHERE VariableName = ' . $this->Conn->qstr($variable_name);
 				$this->Conn->Query($sql);
 
 				$variable_order++;
 				$prev_heading = $variable_heading;
 			}
 		}
 
 		/**
 		 * Group list records by header, saves internal order in group
 		 *
 		 * @param Array $variables
 		 * @return Array
 		 */
 		function _groupRecords($variables)
 		{
 			$sorted = Array();
 
 			foreach ($variables as $variable_name => $variable_heading) {
 				$sorted[$variable_heading][] = $variable_name;
 			}
 
 			$variables = Array();
 			foreach ($sorted as $heading => $heading_records) {
 				foreach ($heading_records as $variable_name) {
 					$variables[$variable_name] = $heading;
 				}
 			}
 
 			return $variables;
 		}
 
 		/**
 		 * Returns module root category
 		 *
 		 * @param string $module_name
 		 * @param string $module_prefix
 		 * @return int
 		 */
 		function _getRootCategory($module_name, $module_prefix)
 		{
 			// don't cache anything here (like in static variables), because database value is changed on the fly !!!
 			$sql = 'SELECT RootCat
 					FROM ' . TABLE_PREFIX . 'Modules
 					WHERE LOWER(Name) = ' . $this->Conn->qstr( strtolower($module_name) );
 			$root_category = $this->Conn->GetOne($sql);
 
 			// put to cache too, because CategoriesEventHandler::_prepareAutoPage uses kApplication::findModule
 			$this->Application->ModuleInfo[$module_name]['Name'] = $module_name;
 			$this->Application->ModuleInfo[$module_name]['RootCat'] = $root_category;
 			$this->Application->ModuleInfo[$module_name]['Var'] = $module_prefix;
 
 			return $root_category;
 		}
 
 		/**
 		 * Move all categories (except "Content") from "Home" to "Content" category and hide them from menu
 		 *
 		 */
 		function _restructureCatalog()
 		{
 			$root_category = $this->_getRootCategory('Core', 'adm');
 
 			$sql = 'SELECT CategoryId
 					FROM ' . TABLE_PREFIX . 'Category
 					WHERE ParentId = 0 AND CategoryId <> ' . $root_category;
 			$top_categories = $this->Conn->GetCol($sql);
 
 			if ($top_categories) {
 				// hide all categories located outside "Content" category from menu
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'Category
 						SET IsMenu = 0
 						WHERE (ParentPath LIKE "|' . implode('|%") OR (ParentPath LIKE "|', $top_categories) . '|%")';
 				$this->Conn->Query($sql);
 
 				// move all top level categories under "Content" category and make them visible in menu
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'Category
 						SET IsMenu = 1, ParentId = ' . $root_category . '
 						WHERE ParentId = 0 AND CategoryId <> ' . $root_category;
 				$this->Conn->Query($sql);
 			}
 
 			// make sure, that all categories have valid value for Priority field
 
 			$priority_helper = $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			$event = new kEvent('c:OnListBuild');
 
 			// update all categories, because they are all under "Content" category now
 			$sql = 'SELECT CategoryId
 					FROM ' . TABLE_PREFIX . 'Category';
 			$categories = $this->Conn->GetCol($sql);
 
 			foreach ($categories as $category_id) {
 				$priority_helper->recalculatePriorities($event, 'ParentId = ' . $category_id);
 			}
 
 			// create initial theme structure in Category table
 			$this->_toolkit->rebuildThemes();
 
 			// make sure, that all system templates have ThemeId set (only possible during platform project upgrade)
 			$sql = 'SELECT ThemeId
 					FROM ' . TABLE_PREFIX . 'Theme
 					WHERE PrimaryTheme = 1';
 			$primary_theme_id = $this->Conn->GetOne($sql);
 
 			if ($primary_theme_id) {
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'Category
 						SET ThemeId = ' . $primary_theme_id . '
 						WHERE IsSystem = 1 AND ThemeId = 0';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Changes skin colors to match new ones (only in case, when they match default values)
 		 *
 		 */
 		function _fixSkinColors()
 		{
 			$skin = $this->Application->recallObject('skin', null, Array ('skip_autoload' => 1));
 			/* @var $skin kDBItem */
 
 			$skin->Load(1, 'IsPrimary');
 			if ($skin->isLoaded()) {
 				$skin_options = unserialize( $skin->GetDBField('Options') );
 
 				$changes = Array (
 					// option: from -> to
 					'HeadBgColor' => Array ('#1961B8', '#007BF4'),
 					'HeadBarColor' => Array ('#FFFFFF', '#000000'),
 
 					'HeadColor' => Array ('#CCFF00', '#FFFFFF'),
 					'TreeColor' => Array ('#006F99', '#000000'),
 					'TreeHoverColor' => Array ('', '#009FF0'),
 					'TreeHighHoverColor' => Array ('', '#FFFFFF'),
 					'TreeHighBgColor' => Array ('#4A92CE', '#4A92CE'),
 					'TreeBgColor' => Array ('#FFFFFF', '#DCECF6'),
 				);
 
 				$can_change = true;
 				foreach ($changes as $option_name => $change) {
 					list ($change_from, $change_to) = $change;
 
 					$can_change = $can_change && ($change_from == $skin_options[$option_name]['Value']);
 					if ($can_change) {
 						$skin_options[$option_name]['Value'] = $change_to;
 					}
 				}
 
 				if ($can_change) {
 					$skin->SetDBField('Options', serialize($skin_options));
 					$skin->Update();
 
 					$skin_helper = $this->Application->recallObject('SkinHelper');
 					/* @var $skin_helper SkinHelper */
 
 					$skin_file = $skin_helper->getSkinPath();
 					if (file_exists($skin_file)) {
 						unlink($skin_file);
 					}
 				}
 			}
 		}
 
 		/**
 		 * 1. Set root category not to generate filename automatically and hide it from catalog
 		 * 2. Hide root category of In-Edit and set it's fields
 		 *
 		 * @param int $category_id
 		 */
 		function _resetRootCategory($category_id)
 		{
 			$fields_hash = Array (
 				'l1_Name' => 'Content', 'Filename' => 'Content', 'AutomaticFilename' => 0,
 				'l1_Description' => 'Content', 'Status' => 4,
 			);
 
 			$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Category', 'CategoryId = ' . $category_id);
 		}
 
 		function _createProjCMSTables()
 		{
 			// 0. make sure, that Content category exists
 			$root_category = $this->_getRootCategory('Proj-CMS', 'st');
 			if ($root_category) {
 				// proj-cms module found -> remove it
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Modules
 						WHERE Name = "Proj-CMS"';
 				$this->Conn->Query($sql);
 				unset($this->Application->ModuleInfo['Proj-CMS']);
 
 				$this->_resetRootCategory($root_category);
 
 				// unhide all structure categories
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'Category
 						SET Status = 1
 						WHERE (Status = 4) AND (CategoryId <> ' . $root_category . ')';
 				$this->Conn->Query($sql);
 			} else {
 				$root_category = $this->_getRootCategory('In-Edit', 'cms');
 				if ($root_category) {
 					// in-edit module found -> remove it
 					$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Modules
 							WHERE Name = "In-Edit"';
 					$this->Conn->Query($sql);
 					unset($this->Application->ModuleInfo['In-Edit']);
 
 					$this->_resetRootCategory($root_category);
 				}
 			}
 
 			if (!$root_category) {
 				// create "Content" category when Proj-CMS/In-Edit module was not installed before
 				// use direct sql here, because category table structure doesn't yet match table structure in object
 
 				$fields_hash = Array (
 					'l1_Name' => 'Content', 'Filename' => 'Content', 'AutomaticFilename' => 0,
 					'l1_Description' => 'Content', 'Status' => 4,
 				);
 
 				$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Category');
 
 				$root_category = $this->Conn->getInsertID();
 			}
 
 			$this->_toolkit->deleteCache();
 			$this->_toolkit->SetModuleRootCategory('Core', $root_category);
 
 			// 1. process "Category" table
 			$structure = $this->Conn->Query('DESCRIBE ' . TABLE_PREFIX . 'Category', 'Field');
 			if (!array_key_exists('Template', $structure)) {
 				// fields from "Pages" table were not added to "Category" table (like before "Proj-CMS" module install)
 				$sql = "ALTER TABLE " . TABLE_PREFIX . "Category
 							ADD COLUMN Template varchar(255) default NULL,
 							ADD COLUMN l1_Title varchar(255) default '',
 							ADD COLUMN l2_Title varchar(255) default '',
 							ADD COLUMN l3_Title varchar(255) default '',
 							ADD COLUMN l4_Title varchar(255) default '',
 							ADD COLUMN l5_Title varchar(255) default '',
 							ADD COLUMN l1_MenuTitle varchar(255) NOT NULL default '',
 							ADD COLUMN l2_MenuTitle varchar(255) NOT NULL default '',
 							ADD COLUMN l3_MenuTitle varchar(255) NOT NULL default '',
 							ADD COLUMN l4_MenuTitle varchar(255) NOT NULL default '',
 							ADD COLUMN l5_MenuTitle varchar(255) NOT NULL default '',
 							ADD COLUMN MetaTitle text,
 							ADD COLUMN IndexTools text,
 							ADD COLUMN IsIndex tinyint(1) NOT NULL default '0',
 							ADD COLUMN IsMenu TINYINT(4) NOT NULL DEFAULT '1',
 							ADD COLUMN IsSystem tinyint(4) NOT NULL default '0',
 							ADD COLUMN FormId int(11) default NULL,
 							ADD COLUMN FormSubmittedTemplate varchar(255) default NULL,
 							ADD COLUMN l1_Translated tinyint(4) NOT NULL default '0',
 							ADD COLUMN l2_Translated tinyint(4) NOT NULL default '0',
 							ADD COLUMN l3_Translated tinyint(4) NOT NULL default '0',
 							ADD COLUMN l4_Translated tinyint(4) NOT NULL default '0',
 							ADD COLUMN l5_Translated tinyint(4) NOT NULL default '0',
 							ADD COLUMN FriendlyURL varchar(255) NOT NULL default '',
 							ADD INDEX IsIndex (IsIndex),
 							ADD INDEX l1_Translated (l1_Translated),
 							ADD INDEX l2_Translated (l2_Translated),
 							ADD INDEX l3_Translated (l3_Translated),
 							ADD INDEX l4_Translated (l4_Translated),
 							ADD INDEX l5_Translated (l5_Translated)";
 				$this->Conn->Query($sql);
 			}
 
 			if (array_key_exists('Path', $structure)) {
 				$sql = 'ALTER TABLE ' . TABLE_PREFIX . 'Category
 						DROP Path';
 				$this->Conn->Query($sql);
 			}
 
 			// 2. process "PageContent" table
 			if ($this->Conn->TableFound(TABLE_PREFIX . 'PageContent', true)) {
 				$structure = $this->Conn->Query('DESCRIBE ' . TABLE_PREFIX . 'PageContent', 'Field');
 				if (!array_key_exists('l1_Translated', $structure)) {
 					$sql = "ALTER TABLE " . TABLE_PREFIX . "PageContent
 								ADD COLUMN l1_Translated tinyint(4) NOT NULL default '0',
 								ADD COLUMN l2_Translated tinyint(4) NOT NULL default '0',
 								ADD COLUMN l3_Translated tinyint(4) NOT NULL default '0',
 								ADD COLUMN l4_Translated tinyint(4) NOT NULL default '0',
 								ADD COLUMN l5_Translated tinyint(4) NOT NULL default '0'";
 					$this->Conn->Query($sql);
 				}
 			}
 
 			// 3. process "FormFields" table
 			if ($this->Conn->TableFound(TABLE_PREFIX . 'FormFields', true)) {
 				$structure = $this->Conn->Query('DESCRIBE ' . TABLE_PREFIX . 'FormFields', 'Field');
 				if (!$structure['FormId']['Key']) {
 					$sql = "ALTER TABLE " . TABLE_PREFIX . "FormFields
 								CHANGE Validation Validation TINYINT NOT NULL DEFAULT '0',
 								ADD INDEX FormId (FormId),
 								ADD INDEX Priority (Priority),
 								ADD INDEX IsSystem (IsSystem),
 								ADD INDEX DisplayInGrid (DisplayInGrid)";
 					$this->Conn->Query($sql);
 				}
 			}
 			else {
 				$this->Conn->Query("INSERT INTO " . TABLE_PREFIX . "Permissions VALUES (DEFAULT, 'in-portal:forms.view', 11, 1, 1, 0)");
 				$this->Conn->Query("INSERT INTO " . TABLE_PREFIX . "Permissions VALUES (DEFAULT, 'in-portal:forms.add', 11, 1, 1, 0)");
 				$this->Conn->Query("INSERT INTO " . TABLE_PREFIX . "Permissions VALUES (DEFAULT, 'in-portal:forms.edit', 11, 1, 1, 0)");
 				$this->Conn->Query("INSERT INTO " . TABLE_PREFIX . "Permissions VALUES (DEFAULT, 'in-portal:forms.delete', 11, 1, 1, 0)");
 			}
 
 			// 4. process "FormSubmissions" table
 			if ($this->Conn->TableFound(TABLE_PREFIX . 'FormSubmissions', true)) {
 				$structure = $this->Conn->Query('DESCRIBE ' . TABLE_PREFIX . 'FormSubmissions', 'Field');
 				if (!$structure['SubmissionTime']['Key']) {
 					$sql = "ALTER TABLE " . TABLE_PREFIX . "FormSubmissions
 								ADD INDEX SubmissionTime (SubmissionTime)";
 					$this->Conn->Query($sql);
 				}
 			}
 			else {
 				$this->Conn->Query("INSERT INTO " . TABLE_PREFIX . "Permissions VALUES (DEFAULT, 'in-portal:submissions.view', 11, 1, 1, 0)");
 			}
 
 			// 5. add missing event
 			$sql = 'SELECT EventId
 					FROM ' . TABLE_PREFIX . 'Events
 					WHERE (Event = "FORM.SUBMITTED") AND (Type = 1)';
 			$event_id = $this->Conn->GetOne($sql);
 
 			if (!$event_id) {
 				$sql = "INSERT INTO " . TABLE_PREFIX . "Events VALUES (DEFAULT, 'FORM.SUBMITTED', NULL, 1, 0, 'Core:Category', 'la_event_FormSubmitted', 1)";
 				$this->Conn->Query($sql);
 			}
 
 			$sql = 'SELECT EventId
 					FROM ' . TABLE_PREFIX . 'Events
 					WHERE (Event = "FORM.SUBMITTED") AND (Type = 0)';
 			$event_id = $this->Conn->GetOne($sql);
 
 			if (!$event_id) {
 				$sql = "INSERT INTO " . TABLE_PREFIX . "Events VALUES (DEFAULT, 'FORM.SUBMITTED', NULL, 1, 0, 'Core:Category', 'la_event_FormSubmitted', 0)";
 				$this->Conn->Query($sql);
 			}
 		}
 
 		function _addMissingConfigurationVariables()
 		{
 			$variables = Array (
 				'cms_DefaultDesign' => Array (
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationAdmin VALUES ('cms_DefaultDesign', 'la_Text_General', 'la_prompt_DefaultDesignTemplate', 'text', NULL, NULL, 10.15, 0, 0)",
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationValues VALUES (DEFAULT, 'cms_DefaultDesign', '/platform/designs/general', 'In-Portal', 'in-portal:configure_categories')",
 				),
 
 				'Require_AdminSSL' => Array (
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationAdmin VALUES ('Require_AdminSSL', 'la_Text_Website', 'la_config_RequireSSLAdmin', 'checkbox', '', '', 10.105, 0, 1)",
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationValues VALUES (DEFAULT, 'Require_AdminSSL', '', 'In-Portal', 'in-portal:configure_advanced')",
 				),
 
 				'UsePopups' => Array (
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationAdmin VALUES ('UsePopups', 'la_Text_Website', 'la_config_UsePopups', 'radio', '', '1=la_Yes,0=la_No', 10.221, 0, 0)",
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationValues VALUES (DEFAULT, 'UsePopups', '1', 'In-Portal', 'in-portal:configure_advanced')",
 				),
 
 				'UseDoubleSorting' => Array (
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationAdmin VALUES ('UseDoubleSorting', 'la_Text_Website', 'la_config_UseDoubleSorting', 'radio', '', '1=la_Yes,0=la_No', 10.222, 0, 0)",
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationValues VALUES (DEFAULT, 'UseDoubleSorting', '0', 'In-Portal', 'in-portal:configure_advanced')",
 				),
 
 				'MenuFrameWidth' => Array (
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationAdmin VALUES ('MenuFrameWidth', 'la_title_General', 'la_prompt_MenuFrameWidth', 'text', NULL, NULL, 10.31, 0, 0)",
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationValues VALUES (DEFAULT, 'MenuFrameWidth', 200, 'In-Portal', 'in-portal:configure_advanced')",
 				),
 
 				'DefaultSettingsUserId' => Array (
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationAdmin VALUES ('DefaultSettingsUserId', 'la_title_General', 'la_prompt_DefaultUserId', 'text', NULL, NULL, '10.06', '0', '0')",
 					"INSERT INTO " . TABLE_PREFIX . "ConfigurationValues VALUES (DEFAULT, 'DefaultSettingsUserId', -1, 'In-Portal:Users', 'in-portal:configure_users')",
 				),
 			);
 
 			foreach ($variables as $variable_name => $variable_sqls) {
 				$sql = 'SELECT VariableId
 						FROM ' . TABLE_PREFIX . 'ConfigurationValues
 						WHERE VariableName = ' . $this->Conn->qstr($variable_name);
 				$variable_id = $this->Conn->GetOne($sql);
 
 				if ($variable_id) {
 					continue;
 				}
 
 				foreach ($variable_sqls as $variable_sql) {
 					$this->Conn->Query($variable_sql);
 				}
 			}
 		}
 
 		/**
 		 * Sort images in database (update Priority field)
 		 *
 		 */
 		function _sortImages()
 		{
 			$sql = 'SELECT *
 					FROM ' . TABLE_PREFIX . 'Images
 					ORDER BY ResourceId ASC , DefaultImg DESC , ImageId ASC';
 			$images = $this->Conn->Query($sql);
 
 			$priority = 0;
 			$last_resource_id = false;
 
 			foreach ($images as $image) {
 				if ($image['ResourceId'] != $last_resource_id) {
 					// each item have own priorities among it's images
 					$priority = 0;
 					$last_resource_id = $image['ResourceId'];
 				}
 
 				if (!$image['DefaultImg']) {
 					$priority--;
 				}
 
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'Images
 						SET Priority = ' . $priority . '
 						WHERE ImageId = ' . $image['ImageId'];
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Update to 5.0.1
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_5_0_1($mode)
 		{
 			if ($mode == 'after') {
 				// delete old events
 				$events_to_delete = Array ('CATEGORY.MODIFY', 'CATEGORY.DELETE');
 
 				$sql = 'SELECT EventId
 						FROM ' . TABLE_PREFIX . 'Events
 						WHERE Event IN ("' . implode('","', $events_to_delete) . '")';
 				$event_ids = $this->Conn->GetCol($sql);
 
 				if ($event_ids) {
 					$this->_deleteEvents($event_ids);
 
 					$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Phrase
 							WHERE Phrase IN ("la_event_category.modify", "la_event_category.delete")';
 					$this->Conn->Query($sql);
 				}
 
 				// partially delete events
 				$sql = 'SELECT EventId
 						FROM ' . TABLE_PREFIX . 'Events
 						WHERE (Event IN ("CATEGORY.APPROVE", "CATEGORY.DENY")) AND (Type = ' . EmailTemplate::TEMPLATE_TYPE_ADMIN . ')';
 				$event_ids = $this->Conn->GetCol($sql);
 
 				if ($event_ids) {
 					$this->_deleteEvents($event_ids);
 				}
 			}
 		}
 
 		function _deleteEvents($ids)
 		{
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'EmailMessage
 					WHERE EventId IN (' . implode(',', $ids) . ')';
 			$this->Conn->Query($sql);
 
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Events
 					WHERE EventId IN (' . implode(',', $ids) . ')';
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Update to 5.0.2-B2; Transforms IsIndex field values to SymLinkCategoryId field
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_5_0_2_B2($mode)
 		{
 			// 0 - Regular, 1 - Category Index, 2 - Container
 
 			if ($mode == 'before') {
 				// fix "Content" category
 				$fields_hash = Array (
 					'CreatedById' => USER_ROOT,
 					'CreatedOn' => time(),
 					'ResourceId' => $this->Application->NextResourceId(),
 				);
 
 				$category_id = $this->Application->findModule('Name', 'Core', 'RootCat');
 				$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Category', 'CategoryId = ' . $category_id);
 
 				// get all categories, marked as category index
 				$sql = 'SELECT ParentPath, CategoryId
 						FROM ' . TABLE_PREFIX . 'Category
 						WHERE IsIndex = 1';
 				$category_indexes = $this->Conn->GetCol($sql, 'CategoryId');
 
 				foreach ($category_indexes as $category_id => $parent_path) {
 					$parent_path = explode('|', substr($parent_path, 1, -1));
 
 					// set symlink to $category_id for each category, marked as container in given category path
 					$sql = 'SELECT CategoryId
 							FROM ' . TABLE_PREFIX . 'Category
 							WHERE CategoryId IN (' . implode(',', $parent_path) . ') AND (IsIndex = 2)';
 					$category_containers = $this->Conn->GetCol($sql);
 
 					if ($category_containers) {
 						$sql = 'UPDATE ' . TABLE_PREFIX . 'Category
 								SET SymLinkCategoryId = ' . $category_id . '
 								WHERE CategoryId IN (' . implode(',', $category_containers) . ')';
 						$this->Conn->Query($sql);
 					}
 
 				}
 			}
 
 			if ($mode == 'after') {
 				// scan theme to fill Theme.TemplateAliases and ThemeFiles.TemplateAlias fields
 				$this->_toolkit->rebuildThemes();
 
 				$sql = 'SELECT TemplateAliases, ThemeId
 						FROM ' . TABLE_PREFIX . 'Theme
 						WHERE (Enabled = 1) AND (TemplateAliases <> "")';
 				$template_aliases = $this->Conn->GetCol($sql, 'ThemeId');
 
 				$all_template_aliases = Array (); // reversed alias (from real template to alias)
 
 				foreach ($template_aliases as $theme_id => $theme_template_aliases) {
 					$theme_template_aliases = unserialize($theme_template_aliases);
 
 					if (!$theme_template_aliases) {
 						continue;
 					}
 
 					$all_template_aliases = array_merge($all_template_aliases, array_flip($theme_template_aliases));
 				}
 
 				$default_design_replaced = false;
 				$default_design = trim($this->Application->ConfigValue('cms_DefaultDesign'), '/');
 
 				foreach ($all_template_aliases as $from_template => $to_alias) {
 					// replace default design in configuration variable (when matches alias)
 					if ($from_template == $default_design) {
 						// specific alias matched
 						$sql = 'UPDATE ' . TABLE_PREFIX . 'ConfigurationValues
 								SET VariableValue = ' . $this->Conn->qstr($to_alias) . '
 								WHERE VariableName = "cms_DefaultDesign"';
 						$this->Conn->Query($sql);
 
 						$default_design_replaced = true;
 					}
 
 					// replace Category.Template and Category.CachedTemplate fields (when matches alias)
 					$sql = 'UPDATE ' . TABLE_PREFIX . 'Category
 							SET Template = ' . $this->Conn->qstr($to_alias) . '
 							WHERE Template IN (' . $this->Conn->qstr('/' . $from_template) . ',' . $this->Conn->qstr($from_template) . ')';
 					$this->Conn->Query($sql);
 
 					$sql = 'UPDATE ' . TABLE_PREFIX . 'Category
 							SET CachedTemplate = ' . $this->Conn->qstr($to_alias) . '
 							WHERE CachedTemplate IN (' . $this->Conn->qstr('/' . $from_template) . ',' . $this->Conn->qstr($from_template) . ')';
 					$this->Conn->Query($sql);
 				}
 
 				if (!$default_design_replaced) {
 					// in case if current default design template doesn't
 					// match any of aliases, then set it to #default_design#
 					$sql = 'UPDATE ' . TABLE_PREFIX . 'ConfigurationValues
 							SET VariableValue = "#default_design#"
 							WHERE VariableName = "cms_DefaultDesign"';
 					$this->Conn->Query($sql);
 				}
 
 				// replace data in category custom fields used for category item template storage
 				$rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor');
 				/* @var $rewrite_processor kRewriteUrlProcessor */
 
 				foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 					$custom_field_id = $rewrite_processor->getItemTemplateCustomField($module_info['Var']);
 
 					if (!$custom_field_id) {
 						continue;
 					}
 
 					foreach ($all_template_aliases as $from_template => $to_alias) {
 						$sql = 'UPDATE ' . TABLE_PREFIX . 'CategoryCustomData
 								SET l1_cust_' . $custom_field_id . ' = ' . $this->Conn->qstr($to_alias) . '
 								WHERE l1_cust_' . $custom_field_id . ' = ' . $this->Conn->qstr($from_template);
 						$this->Conn->Query($sql);
 					}
 				}
 			}
 		}
 
 		/**
 		 * Update to 5.0.3-B2; Moves CATEGORY.* permission from module root categories to Content category
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_5_0_3_B2($mode)
 		{
 			if ($mode == 'before') {
 				// get permissions
 				$sql = 'SELECT PermissionName
 						FROM ' . TABLE_PREFIX . 'PermissionConfig
 						WHERE PermissionName LIKE "CATEGORY.%"';
 				$permission_names = $this->Conn->GetCol($sql);
 
 				// get groups
 				$sql = 'SELECT GroupId
 						FROM ' . TABLE_PREFIX . 'PortalGroup';
 				$user_groups = $this->Conn->GetCol($sql);
 				$user_group_count = count($user_groups);
 
 				// get module root categories
 				$sql = 'SELECT RootCat
 						FROM ' . TABLE_PREFIX . 'Modules';
 				$module_categories = $this->Conn->GetCol($sql);
 
 				$module_categories[] = 0;
 				$module_categories = implode(',', array_unique($module_categories));
 
 				$permissions = $delete_permission_ids = Array ();
 
 				foreach ($permission_names as $permission_name) {
 					foreach ($user_groups as $group_id) {
 						$sql = 'SELECT PermissionId
 								FROM ' . TABLE_PREFIX . 'Permissions
 								WHERE (Permission = ' . $this->Conn->qstr($permission_name) . ') AND (PermissionValue = 1) AND (GroupId = ' . $group_id . ') AND (`Type` = 0) AND (CatId IN (' . $module_categories . '))';
 						$permission_ids = $this->Conn->GetCol($sql);
 
 						if ($permission_ids) {
 							if (!array_key_exists($permission_name, $permissions)) {
 								$permissions[$permission_name] = Array ();
 							}
 
 							$permissions[$permission_name][] = $group_id;
 							$delete_permission_ids = array_merge($delete_permission_ids, $permission_ids);
 						}
 					}
 				}
 
 				if ($delete_permission_ids) {
 					// here we can delete some of permissions that will be added later
 					$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Permissions
 							WHERE PermissionId IN (' . implode(',', $delete_permission_ids) . ')';
 					$this->Conn->Query($sql);
 				}
 
 				$home_category = $this->Application->findModule('Name', 'Core', 'RootCat');
 
 				foreach ($permissions as $permission_name => $permission_groups) {
 					// optimize a bit
 					$has_everyone = in_array(15, $permission_groups);
 
 					if ($has_everyone || (!$has_everyone && count($permission_groups) == $user_group_count - 1)) {
 						// has permission for "Everyone" group OR allowed in all groups except "Everyone" group
 						// so remove all other explicitly allowed permissions
 						$permission_groups = Array (15);
 					}
 
 					foreach ($permission_groups as $group_id) {
 						$fields_hash = Array (
 							'Permission' => $permission_name,
 							'GroupId' => $group_id,
 							'PermissionValue' => 1,
 							'Type' => 0, // category-based permission,
 							'CatId' => $home_category,
 						);
 
 						$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Permissions');
 					}
 				}
 
 				$updater = $this->Application->makeClass('kPermCacheUpdater');
 				/* @var $updater kPermCacheUpdater */
 
 				$updater->OneStepRun();
 			}
 		}
 
 		/**
 		 * Update to 5.1.0-B1; Makes email message fields multilingual
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_5_1_0_B1($mode)
 		{
 			if ( $mode == 'before' ) {
 				$this->_renameTables('from');
 
 				// migrate email events
 				$table_structure = $this->Conn->Query('DESCRIBE ' . TABLE_PREFIX . 'Events', 'Field');
 
 				if (!array_key_exists('Headers', $table_structure)) {
 					$sql = 'ALTER TABLE ' . TABLE_PREFIX . 'Events
 							ADD `Headers` TEXT NULL AFTER `ReplacementTags`,
 							ADD `MessageType` VARCHAR(4) NOT NULL default "text" AFTER `Headers`';
 					$this->Conn->Query($sql);
 				}
 
 				// alter here, because kMultiLanguageHelper::createFields
 				// method, called after will expect that to be in database
 				$sql = 'ALTER TABLE ' . TABLE_PREFIX . 'Events
 							ADD AllowChangingSender TINYINT NOT NULL DEFAULT "0" AFTER MessageType ,
 							ADD CustomSender TINYINT NOT NULL DEFAULT "0" AFTER AllowChangingSender ,
 							ADD SenderName VARCHAR(255) NOT NULL DEFAULT "" AFTER CustomSender ,
 							ADD SenderAddressType TINYINT NOT NULL DEFAULT "0" AFTER SenderName ,
 							ADD SenderAddress VARCHAR(255) NOT NULL DEFAULT "" AFTER SenderAddressType ,
 							ADD AllowChangingRecipient TINYINT NOT NULL DEFAULT "0" AFTER SenderAddress ,
 							ADD CustomRecipient TINYINT NOT NULL DEFAULT "0" AFTER AllowChangingRecipient ,
 							ADD Recipients TEXT AFTER CustomRecipient,
 							ADD INDEX (AllowChangingSender),
 							ADD INDEX (CustomSender),
 							ADD INDEX (SenderAddressType),
 							ADD INDEX (AllowChangingRecipient),
 							ADD INDEX (CustomRecipient)';
 				$this->Conn->Query($sql);
 
 				// create multilingual fields for phrases and email events
 				$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 				/* @var $ml_helper kMultiLanguageHelper */
 
 				$ml_helper->createFields('phrases');
 				$ml_helper->createFields('email-template');
 				$languages = $ml_helper->getLanguages();
 
 				if ($this->Conn->TableFound(TABLE_PREFIX . 'EmailMessage', true)) {
 					$email_template_helper = $this->Application->recallObject('kEmailTemplateHelper');
 					/* @var $email_template_helper kEmailTemplateHelper */
 
 					foreach ($languages as $language_id) {
 						$sql = 'SELECT EmailMessageId, Template, EventId
 								FROM ' . TABLE_PREFIX . 'EmailMessage
 								WHERE LanguageId = ' . $language_id;
 						$translations = $this->Conn->Query($sql, 'EventId');
 
 						foreach ($translations as $event_id => $translation_data) {
 							$parsed = $email_template_helper->parseTemplate($translation_data['Template'], 'html');
 
 							$fields_hash = Array (
 								'l' . $language_id . '_Subject' => $parsed['Subject'],
 								'l' . $language_id . '_Body' => $parsed['HtmlBody'],
 							);
 
 							if ( $parsed['Headers'] ) {
 								$fields_hash['Headers'] = $parsed['Headers'];
 							}
 
 							$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Events', 'EventId = ' . $event_id);
 
 							$sql = 'DELETE FROM ' . TABLE_PREFIX . 'EmailMessage
 									WHERE EmailMessageId = ' . $translation_data['EmailMessageId'];
 							$this->Conn->Query($sql);
 						}
 					}
 				}
 
 				// migrate phrases
 				$temp_table = $this->Application->GetTempName(TABLE_PREFIX . 'Phrase');
 
 				$sqls = Array (
 					'DROP TABLE IF EXISTS ' . $temp_table,
 					'CREATE TABLE ' . $temp_table . ' LIKE ' . TABLE_PREFIX . 'Phrase',
 					'ALTER TABLE ' . $temp_table . ' DROP LanguageId, DROP Translation',
 					'ALTER IGNORE TABLE ' . $temp_table . ' DROP INDEX LanguageId_2',
 					'ALTER TABLE ' . $temp_table . ' DROP PhraseId',
 					'ALTER TABLE ' . $temp_table . ' ADD PhraseId INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST',
 				);
 
 				foreach ($sqls as $sql) {
 					$this->Conn->Query($sql);
 				}
 
 				$already_added = Array ();
 				$primary_language_id = $this->Application->GetDefaultLanguageId();
 
 				foreach ($languages as $language_id) {
 					$sql = 'SELECT Phrase, PhraseKey, Translation AS l' . $language_id . '_Translation, PhraseType, LastChanged, LastChangeIP, Module
 							FROM ' . TABLE_PREFIX . 'Phrase
 							WHERE LanguageId = ' . $language_id;
 					$phrases = $this->Conn->Query($sql, 'Phrase');
 
 					foreach ($phrases as $phrase => $fields_hash) {
 						if (array_key_exists($phrase, $already_added)) {
 							$this->Conn->doUpdate($fields_hash, $temp_table, 'PhraseId = ' . $already_added[$phrase]);
 						}
 						else {
 							$this->Conn->doInsert($fields_hash, $temp_table);
 							$already_added[$phrase] = $this->Conn->getInsertID();
 						}
 					}
 
 					// in case some phrases were found in this language, but not in primary language -> copy them
 					if ($language_id != $primary_language_id) {
 						$sql = 'UPDATE ' . $temp_table . '
 								SET l' . $primary_language_id . '_Translation = l' . $language_id . '_Translation
 								WHERE l' . $primary_language_id . '_Translation IS NULL';
 						$this->Conn->Query($sql);
 					}
 				}
 
 				$this->Conn->Query('DROP TABLE IF EXISTS ' . TABLE_PREFIX . 'Phrase');
 				$this->Conn->Query('RENAME TABLE ' . $temp_table . ' TO ' . TABLE_PREFIX . 'Phrase');
 
 				$this->_updateCountryStatesTable();
 				$this->_replaceConfigurationValueSeparator();
 
 				// save "config.php" in php format, not ini format as before
 				$this->_toolkit->SaveConfig();
 			}
 
 			if ($mode == 'after') {
 				$this->_transformEmailRecipients();
 				$this->_fixSkinColors();
 			}
 		}
 
 		/**
 		 * Makes sure we rename tables to legacy names before doing other upgrades before 5.2.0-B1 upgrade
 		 *
 		 * @param string $name
 		 * @param Array $arguments
 		 */
 		public function __call($name, $arguments)
 		{
 			if ( substr($name, 0, 12) == 'Upgrade_5_1_' && $arguments[0] == 'before' ) {
 				$this->_renameTables('from');
 			}
 
 			if ( substr($name, 0, 13) == 'Upgrade_5_2_0' && $arguments[0] == 'before' ) {
 				$this->_renameTables('to');
 			}
 		}
 
 		/**
 		 * Move country/state translations from Phrase to CountryStates table
 		 *
 		 */
 		function _updateCountryStatesTable()
 		{
 			// refactor StdDestinations table
 			$sql = 'RENAME TABLE ' . TABLE_PREFIX . 'StdDestinations TO ' . TABLE_PREFIX . 'CountryStates';
 			$this->Conn->Query($sql);
 
 			$sql = 'ALTER TABLE ' . TABLE_PREFIX . 'CountryStates
 						CHANGE DestId CountryStateId INT(11) NOT NULL AUTO_INCREMENT,
 						CHANGE DestType Type INT(11) NOT NULL DEFAULT \'1\',
 						CHANGE DestParentId StateCountryId INT(11) NULL DEFAULT NULL,
 						CHANGE DestAbbr IsoCode CHAR(3) NOT NULL DEFAULT \'\',
 						CHANGE DestAbbr2 ShortIsoCode CHAR(2) NULL DEFAULT NULL,
 						DROP INDEX DestType,
 						DROP INDEX DestParentId,
 						ADD INDEX (`Type`),
 						ADD INDEX (StateCountryId)';
 			$this->Conn->Query($sql);
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$ml_helper->createFields('country-state');
 			$languages = $ml_helper->getLanguages();
 
 			foreach ($languages as $language_id) {
 				$sub_select = '	SELECT l' . $language_id . '_Translation
 								FROM ' . TABLE_PREFIX . 'Phrase
 								WHERE Phrase = DestName';
 
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'CountryStates
 						SET l' . $language_id . '_Name = (' . $sub_select . ')';
 				$this->Conn->Query($sql);
 			}
 
 			$sql = 'ALTER TABLE ' . TABLE_PREFIX . 'CountryStates
 					DROP DestName';
 			$this->Conn->Query($sql);
 
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Phrase
 					WHERE Phrase LIKE ' . $this->Conn->qstr('la_country_%') . ' OR Phrase LIKE ' . $this->Conn->qstr('la_state_%');
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Makes configuration values dropdowns use "||" as separator
 		 *
 		 */
 		function _replaceConfigurationValueSeparator()
 		{
 			$custom_field_helper = $this->Application->recallObject('InpCustomFieldsHelper');
 			/* @var $custom_field_helper InpCustomFieldsHelper */
 
 			$sql = 'SELECT ValueList, VariableName
 					FROM ' . TABLE_PREFIX . 'ConfigurationAdmin
 					WHERE ValueList LIKE "%,%"';
 			$variables = $this->Conn->GetCol($sql, 'VariableName');
 
 			foreach ($variables as $variable_name => $value_list) {
 				$ret = Array ();
 				$options = $custom_field_helper->GetValuesHash($value_list, ',', false);
 
 				foreach ($options as $option_key => $option_title) {
 					if (substr($option_key, 0, 3) == 'SQL') {
 						$ret[] = $option_title;
 					}
 					else {
 						$ret[] = $option_key . '=' . $option_title;
 					}
 				}
 
 				$fields_hash = Array (
 					'ValueList' => implode(VALUE_LIST_SEPARATOR, $ret),
 				);
 
 				$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'ConfigurationAdmin', 'VariableName = ' . $this->Conn->qstr($variable_name));
 			}
 		}
 
 		/**
 		 * Transforms "FromUserId" into Sender* and Recipients columns
 		 *
 		 */
 		function _transformEmailRecipients()
 		{
 			$sql = 'SELECT FromUserId, Type, EventId
 					FROM ' . TABLE_PREFIX . 'Events
 					WHERE FromUserId IS NOT NULL AND (FromUserId <> ' . USER_ROOT . ')';
 			$events = $this->Conn->Query($sql, 'EventId');
 
 			$minput_helper = $this->Application->recallObject('MInputHelper');
 			/* @var $minput_helper MInputHelper */
 
 			foreach ($events as $event_id => $event_data) {
 				$sql = 'SELECT Login
 						FROM ' . TABLE_PREFIX . 'PortalUser
 						WHERE PortalUserId = ' . $event_data['FromUserId'];
 				$username = $this->Conn->GetOne($sql);
 
 				if (!$username) {
 					continue;
 				}
 
 				if ($event_data['Type'] == EmailTemplate::TEMPLATE_TYPE_FRONTEND) {
 					// from user
 					$fields_hash = Array (
 						'CustomSender' => 1,
 						'SenderAddressType' => EmailTemplate::ADDRESS_TYPE_USER,
 						'SenderAddress' => $username
 					);
 				}
 
 				if ($event_data['Type'] == EmailTemplate::TEMPLATE_TYPE_ADMIN) {
 					// to user
 					$records = Array (
 						Array (
 							'RecipientType' => EmailTemplate::RECIPIENT_TYPE_TO,
 							'RecipientName' => '',
 							'RecipientAddressType' => EmailTemplate::ADDRESS_TYPE_USER,
 							'RecipientAddress' => $username
 						)
 					);
 
 					$fields_hash = Array (
 						'CustomRecipient' => 1,
 						'Recipients' => $minput_helper->prepareMInputXML($records, array_keys( reset($records) ))
 					);
 				}
 
 				$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Events', 'EventId = ' . $event_id);
 			}
 
 			$this->Conn->Query('ALTER TABLE ' . TABLE_PREFIX . 'Events DROP FromUserId');
 		}
 
 		/**
 		 * Update to 5.1.0; Fixes refferer of form submissions
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_5_1_0($mode)
 		{
 			if ( $mode == 'before' ) {
 				$this->_renameTables('from');
 			}
 
 			if ( $mode == 'after' ) {
 				$base_url = $this->Application->BaseURL();
 
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'FormSubmissions
 						SET ReferrerURL = REPLACE(ReferrerURL, ' . $this->Conn->qstr($base_url) . ', "/")';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Update to 5.1.1-B1; Transforms DisplayToPublic logic
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_5_1_1_B1($mode)
 		{
 			if ( $mode == 'before' ) {
 				$this->_renameTables('from');
 			}
 
 			if ($mode == 'after') {
 				$this->processDisplayToPublic();
 			}
 		}
 
 		function processDisplayToPublic()
 		{
 			$profile_mapping = Array (
 				'pp_firstname' => 'FirstName',
 				'pp_lastname' => 'LastName',
 				'pp_dob' => 'dob',
 				'pp_email' => 'Email',
 				'pp_phone' => 'Phone',
 				'pp_street' => 'Street',
 				'pp_city' => 'City',
 				'pp_state' => 'State',
 				'pp_zip' => 'Zip',
 				'pp_country' => 'Country',
 			);
 
 			$fields = array_keys($profile_mapping);
 			$fields = $this->Conn->qstrArray($fields);
 			$where_clause = 'VariableName IN (' . implode(',', $fields) . ')';
 
 			// 1. get user, that have saved their profile at least once
 			$sql = 'SELECT DISTINCT PortalUserId
 					FROM ' . TABLE_PREFIX . 'PersistantSessionData
 					WHERE ' . $where_clause;
 			$users = $this->Conn->GetCol($sql);
 
 			foreach ($users as $user_id) {
 				// 2. convert to new format
 				$sql = 'SELECT VariableValue, VariableName
 						FROM ' . TABLE_PREFIX . 'PersistantSessionData
 						WHERE (PortalUserId = ' . $user_id . ') AND ' . $where_clause;
 				$user_variables = $this->Conn->GetCol($sql, 'VariableName');
 
 				// go through mapping to preserve variable order
 				$value = Array ();
 
 				foreach ($profile_mapping as $from_name => $to_name) {
 					if (array_key_exists($from_name, $user_variables) && $user_variables[$from_name]) {
 						$value[] = $to_name;
 					}
 				}
 
 				if ($value) {
 					$fields_hash = Array (
 						'DisplayToPublic' => '|' . implode('|', $value) . '|',
 					);
 
 					$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'PortalUser', 'PortalUserId = ' . $user_id);
 				}
 
 				// 3. delete old style variables
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'PersistantSessionData
 						WHERE (PortalUserId = ' . $user_id . ') AND ' . $where_clause;
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Update to 5.1.3; Merges column and field phrases
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		function Upgrade_5_1_3($mode)
 		{
 			if ( $mode == 'before' ) {
 				$this->_renameTables('from');
 			}
 
 			if ( $mode == 'after' ) {
 				$this->moveTranslation('LA_COL_', 'LA_FLD_', 'ColumnTranslation');
 			}
 		}
 
 		/**
 		 * Makes sure table names match upgrade script
 		 *
 		 * @param string $key
 		 * @return void
 		 * @access private
 		 */
 		private function _renameTables($key)
 		{
 			foreach ($this->renamedTables as $prefix => $table_info) {
-				$this->Application->setUnitOption($prefix, 'TableName', TABLE_PREFIX . $table_info[$key]);
+				$this->Application->getUnitConfig($prefix)->setTableName(TABLE_PREFIX . $table_info[$key]);
 			}
 		}
 
 		/**
 		 * Update to 5.2.0-B1; Transform list sortings storage
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		public function Upgrade_5_2_0_B1($mode)
 		{
 			if ( $mode == 'before' ) {
 				$this->_renameTables('to');
 			}
 
 			if ( $mode == 'after' ) {
 				$this->transformSortings();
 				$this->moveTranslation('LA_COL_', 'LA_FLD_', 'ColumnTranslation'); // because of "la_col_ItemPrefix" phrase
 				$this->moveTranslation('LA_HINT_', 'LA_FLD_', 'HintTranslation');
 				$this->moveTranslation('LA_HINT_', 'LA_CONFIG_', 'HintTranslation');
 				$this->moveTranslation('LA_HINT_', 'LA_TITLE_', 'HintTranslation');
 				$this->createPageRevisions();
 			}
 		}
 
 		/**
 		 * Transforms a way, how list sortings are stored
 		 *
 		 * @return void
 		 */
 		function transformSortings()
 		{
 			$sql = 'SELECT VariableName, PortalUserId
 					FROM ' . TABLE_PREFIX . 'UserPersistentSessionData
 					WHERE VariableName LIKE "%_Sort1.%"';
 			$sortings = $this->Conn->Query($sql);
 
 			foreach ($sortings AS $sorting) {
 				if ( !preg_match('/^(.*)_Sort1.(.*)$/', $sorting['VariableName'], $regs) ) {
 					continue;
 				}
 
 				$user_id = $sorting['PortalUserId'];
 				$prefix_special = $regs[1] . '_';
 				$view_name = '.' . $regs[2];
 
 				$old_variable_names = Array (
 					$prefix_special . 'Sort1' . $view_name, $prefix_special . 'Sort1_Dir' . $view_name,
 					$prefix_special . 'Sort2' . $view_name, $prefix_special . 'Sort2_Dir' . $view_name,
 				);
 				$old_variable_names = $this->Conn->qstrArray($old_variable_names);
 
 				$sql = 'SELECT VariableValue, VariableName
 						FROM ' . TABLE_PREFIX . 'UserPersistentSessionData
 						WHERE PortalUserId = ' . $user_id . ' AND VariableName IN (' . implode(',', $old_variable_names) . ')';
 				$sorting_data = $this->Conn->GetCol($sql, 'VariableName');
 
 				// prepare & save new sortings
 				$new_sorting = Array (
 					'Sort1' => $sorting_data[$prefix_special . 'Sort1' . $view_name],
 					'Sort1_Dir' => $sorting_data[$prefix_special . 'Sort1_Dir' . $view_name],
 				);
 
 				if ( isset($sorting_data[$prefix_special . 'Sort2' . $view_name]) ) {
 					$new_sorting['Sort2'] = $sorting_data[$prefix_special . 'Sort2' . $view_name];
 					$new_sorting['Sort2_Dir'] = $sorting_data[$prefix_special . 'Sort2_Dir' . $view_name];
 				}
 
 				$fields_hash = Array (
 					'PortalUserId' => $user_id,
 					'VariableName' => $prefix_special . 'Sortings' . $view_name,
 					'VariableValue' => serialize($new_sorting),
 				);
 
 				$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'UserPersistentSessionData');
 
 				// delete sortings, that were already processed
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserPersistentSessionData
 						WHERE PortalUserId = ' . $user_id . ' AND VariableName IN (' . implode(',', $old_variable_names) . ')';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Merges several phrases into one (e.g. la_col_ + la_hint_ into designated columns of la_fld_ phrases)
 		 *
 		 * @param string $source_prefix
 		 * @param string $target_prefix
 		 * @param string $db_column
 		 * @return void
 		 * @access protected
 		 */
 		public function moveTranslation($source_prefix, $target_prefix, $db_column)
 		{
 			$source_phrases = $this->getPhrasesByMask($source_prefix . '%');
 			$target_phrases = $this->getPhrasesByMask($target_prefix . '%');
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$delete_ids = Array ();
 			$ml_helper->createFields('phrases');
 			$languages = $ml_helper->getLanguages();
-			$phrase_table = $this->Application->getUnitOption('phrases', 'TableName');
+			$phrase_table = $this->Application->getUnitConfig('phrases')->getTableName();
 
 			foreach ($source_phrases as $phrase_key => $phrase_info) {
 				$target_phrase_key = $target_prefix . substr($phrase_key, strlen($source_prefix));
 
 				if ( !isset($target_phrases[$target_phrase_key]) ) {
 					continue;
 				}
 
 				$fields_hash = Array ();
 
 				// copy column phrase main translation into field phrase column translation
 				foreach ($languages as $language_id) {
 					$fields_hash['l' . $language_id . '_' . $db_column] = $phrase_info['l' . $language_id . '_Translation'];
 				}
 
 				$delete_ids[] = $phrase_info['PhraseId'];
 				$this->Conn->doUpdate($fields_hash, $phrase_table, 'PhraseId = ' . $target_phrases[$target_phrase_key]['PhraseId']);
 			}
 
 			// delete all column phrases, that were absorbed by field phrases
 			if ( $delete_ids ) {
 				$sql = 'DELETE FROM ' . $phrase_table . '
 						WHERE PhraseId IN (' . implode(',', $delete_ids) . ')';
 				$this->Conn->Query($sql);
 
 				$sql = 'DELETE FROM ' . TABLE_PREFIX . 'PhraseCache';
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Returns phrases by mask
 		 *
 		 * @param string $mask
 		 * @return Array
 		 * @access protected
 		 */
 		protected function getPhrasesByMask($mask)
 		{
 			$sql = 'SELECT *
-					FROM ' . $this->Application->getUnitOption('phrases', 'TableName') . '
+					FROM ' . $this->Application->getUnitConfig('phrases')->getTableName() . '
 					WHERE PhraseKey LIKE ' . $this->Conn->qstr($mask);
 
 			return $this->Conn->Query($sql, 'PhraseKey');
 		}
 
 		protected function createPageRevisions()
 		{
 			$sql = 'SELECT DISTINCT PageId
 					FROM ' . TABLE_PREFIX . 'PageContent';
 			$page_ids = $this->Conn->GetCol($sql);
 
 			foreach ($page_ids as $page_id) {
 				$fields_hash = Array (
 					'PageId' => $page_id,
 					'RevisionNumber' => 1,
 					'IsDraft' => 0,
 					'FromRevisionId' => 0,
 					'CreatedById' => USER_ROOT,
 					'CreatedOn' => adodb_mktime(),
 					'Status' => STATUS_ACTIVE,
 				);
 
 				$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'PageRevisions');
 
 				$fields_hash = Array (
 					'RevisionId' => $this->Conn->getInsertID(),
 				);
 
 				$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'PageContent', 'PageId = ' . $page_id);
 			}
 		}
 
 		/**
 		 * Update to 5.2.0-B3; Introduces separate field for plain-text e-mail event translations
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		public function Upgrade_5_2_0_B3($mode)
 		{
 			if ( $mode == 'before' ) {
 				$this->_renameTables('to');
 			}
 
 			if ( $mode == 'after' ) {
 				$this->_splitEmailBody();
 				$this->_migrateCommonFooter();
 			}
 		}
 
 		/**
 		 * Splits e-mail body into HTML and Text fields
 		 *
 		 * @return void
 		 * @access private
 		 */
 		private function _splitEmailBody()
 		{
-			$id_field = $this->Application->getUnitOption('email-template', 'IDField');
-			$table_name = $this->Application->getUnitOption('email-template', 'TableName');
+			$config = $this->Application->getUnitConfig('email-template');
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 			$fields = $this->Conn->Query('DESCRIBE ' . $table_name, 'Field');
 
 			if ( !isset($fields['l1_Body']) ) {
 				// column dropped - nothing to convert anymore
 				return;
 			}
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$languages = $ml_helper->getLanguages();
 			$ml_helper->createFields('email-template');
 
 			$sql = 'SELECT *
 					FROM ' . $table_name;
 			$email_events = $this->Conn->Query($sql);
 
 			// 1. move data to new columns
 			foreach ($email_events as $email_event) {
 				$fields_hash = Array ();
 				$translation_field = $email_event['MessageType'] == 'html' ? 'HtmlBody' : 'PlainTextBody';
 
 				foreach ($languages as $language_id) {
 					$fields_hash['l' . $language_id . '_' . $translation_field] = $email_event['l' . $language_id . '_Body'];
 				}
 
 				if ( $fields_hash ) {
 					$this->Conn->doUpdate($fields_hash, $table_name, $id_field . ' = ' . $email_event[$id_field]);
 				}
 			}
 
 			// 2. drop old columns
 			$drops = Array ('DROP COLUMN MessageType');
 
 			foreach ($languages as $language_id) {
 				$lang_field = 'l' . $language_id . '_Body';
 
 				if ( isset($fields[$lang_field]) ) {
 					$drops[] = 'DROP COLUMN ' . $lang_field;
 				}
 			}
 
 			$this->Conn->Query('ALTER TABLE ' . $table_name . ' ' . implode(', ', $drops));
 		}
 
 		/**
 		 * Transforms COMMON.FOOTER e-mail event into new field in Languages table
 		 */
 		private function _migrateCommonFooter()
 		{
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$languages = $ml_helper->getLanguages();
 
-			$event_table = $this->Application->getUnitOption('email-template', 'TableName');
+			$event_table = $this->Application->getUnitConfig('email-template')->getTableName();
 
 			$sql = 'SELECT *
 					FROM ' . $event_table . '
 					WHERE Event = "COMMON.FOOTER"';
 			$footer_data = $this->Conn->GetRow($sql);
 
 			if ( !$footer_data ) {
 				return;
 			}
 
 			$primary_language_id = $this->Application->GetDefaultLanguageId();
-			$table_name = $this->Application->getUnitOption('lang', 'TableName');
+			$table_name = $this->Application->getUnitConfig('lang')->getTableName();
 
 			foreach ($languages as $language_id) {
 				$is_primary = $language_id == $primary_language_id;
 
 				$fields_hash = Array (
 					'HtmlEmailTemplate' => $this->_appendEmailDesignBody($footer_data['l' . $language_id . '_HtmlBody'], $is_primary),
 					'TextEmailTemplate' => $this->_appendEmailDesignBody($footer_data['l' . $language_id . '_PlainTextBody'], $is_primary),
 				);
 
 				$this->Conn->doUpdate($fields_hash, $table_name, 'LanguageId = ' . $language_id);
 			}
 
 			$sql = 'DELETE FROM ' . $event_table . '
 					WHERE EventId = ' . $footer_data['EventId'];
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Adds "$body" to given string
 		 *
 		 * @param string $string
 		 * @param bool $is_primary for primary language
 		 * @return string
 		 * @access private
 		 */
 		private function _appendEmailDesignBody($string, $is_primary)
 		{
 			if ( !$string ) {
 				return $is_primary ? '$body' : $string;
 			}
 
 			return '$body' . "\n" . str_replace(Array ("\r\n", "\r"), "\n", $string);
 		}
 
 		/**
 		 * Update to 5.2.0-RC1
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		public function Upgrade_5_2_0_RC1($mode)
 		{
 			if ( $mode != 'before' ) {
 				return;
 			}
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			// make some promo block fields translatable
 			$ml_helper->createFields('promo-block');
 
-			$table_name = $this->Application->getUnitOption('promo-block', 'TableName');
+			$table_name = $this->Application->getUnitConfig('promo-block')->getTableName();
 			$table_structure = $this->Conn->Query('DESCRIBE ' . $table_name, 'Field');
 
 			if ( isset($table_structure['Title']) ) {
 				$sql = 'UPDATE ' . $table_name . '
 						SET l' . $this->Application->GetDefaultLanguageId() . '_Title = Title';
 				$this->Conn->Query($sql);
 
 				$sql = 'ALTER TABLE ' . $table_name . ' DROP Title';
 				$this->Conn->Query($sql);
 			}
 
 			// fix e-mail event translations
 			$languages = $ml_helper->getLanguages();
-			$table_name = $this->Application->getUnitOption('email-template', 'TableName');
+			$table_name = $this->Application->getUnitConfig('email-template')->getTableName();
 
 			$change_fields = Array ('Subject', 'HtmlBody', 'PlainTextBody');
 
 			foreach ($languages as $language_id) {
 				foreach ($change_fields as $change_field) {
 					$change_field = 'l' . $language_id . '_' . $change_field;
 
 					$sql = "UPDATE " . $table_name . "
 							SET {$change_field} = REPLACE({$change_field}, '<inp2:m_BaseUrl/>', '<inp2:m_Link template=\"index\"/>')
 							WHERE  {$change_field} LIKE '%m_BaseURL%'";
 					$this->Conn->Query($sql);
 				}
 			}
 
 			// add new ml columns to phrases/e-mail events
 			$ml_helper->createFields('phrases');
 			$ml_helper->createFields('email-template');
 		}
 
 		/**
 		 * Update to 5.2.0
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		public function Upgrade_5_2_0($mode)
 		{
 			if ( $mode != 'after' ) {
 				return;
 			}
 
-			$table_name = $this->Application->getUnitOption('c', 'TableName');
+			$table_name = $this->Application->getUnitConfig('c')->getTableName();
 
 			$sql = 'SELECT NamedParentPath, CachedTemplate, CategoryId
 					FROM ' . $table_name;
 			$categories = $this->Conn->GetIterator($sql);
 
 			foreach ($categories as $category_data) {
 				$fields_hash = Array (
 					'NamedParentPathHash' => kUtil::crc32(mb_strtolower(preg_replace('/^Content\//i', '', $category_data['NamedParentPath']))),
 					'CachedTemplateHash' => kUtil::crc32(mb_strtolower($category_data['CachedTemplate'])),
 				);
 
 				$this->Conn->doUpdate($fields_hash, $table_name, 'CategoryId = ' . $category_data['CategoryId']);
 			}
 
 			$rebuild_mode = $this->Application->ConfigValue('QuickCategoryPermissionRebuild') ? CategoryPermissionRebuild::SILENT : CategoryPermissionRebuild::AUTOMATIC;
 			$this->Application->SetConfigValue('CategoryPermissionRebuildMode', $rebuild_mode);
 
 			$sql = 'DELETE FROM ' . TABLE_PREFIX . 'SystemSettings WHERE VariableName = "QuickCategoryPermissionRebuild"';
 			$this->Conn->Query($sql);
 
 			$this->_updateScheduledTaskRunSchedule();
 		}
 
 		/**
 		 * Transforms RunInterval into RunSchedule column for Scheduled Tasks
 		 *
 		 * @return void
 		 * @access protected
 		 */
 		protected function _updateScheduledTaskRunSchedule()
 		{
 			// minute hour day_of_month month day_of_week
-			$id_field = $this->Application->getUnitOption('scheduled-task', 'IDField');
-			$table_name = $this->Application->getUnitOption('scheduled-task', 'TableName');
+			$config = $this->Application->getUnitConfig('scheduled-task');
+			$id_field = $config->getIDField();
+			$table_name = $config->getTableName();
 
 			$sql = 'SELECT RunInterval, ' . $id_field . '
 					FROM ' . $table_name;
 			$run_intervals = $this->Conn->GetCol($sql, $id_field);
 
 			$ranges = Array (0 => 'min', 1 => 'hour', 2 => 'day', 3 => 'month');
 			$range_values = Array ('min' => 60, 'hour' => 60, 'day' => 24, 'month' => 30);
 			$range_masks = Array ('min' => '*/%s * * * *', 'hour' => '0 */%s * * *', 'day' => '0 0 */%s * *', 'month' => '0 0 1 */%s *');
 
 			foreach ($run_intervals as $scheduled_task_id => $interval) {
 				$mask_index = 'month';
 
 				foreach ($ranges as $range_index => $range_name) {
 					$range_value = $range_values[$range_name];
 
 					if ( $interval >= $range_value ) {
 						$interval = ceil($interval / $range_value);
 					}
 					else {
 						$mask_index = $ranges[$range_index - 1];
 						break;
 					}
 				}
 
 				$run_schedule = sprintf($range_masks[$mask_index], $interval);
 
 				if ( $run_schedule == '0 0 */7 * *' ) {
 					// once in 7 days = once in a week
 					$run_schedule = '0 0 * * 0';
 				}
 
 				$run_schedule = preg_replace('/(\*\/1( |$))/', '*\\2', $run_schedule);
 
 				$fields_hash = Array ('RunSchedule' => $run_schedule);
 				$this->Conn->doUpdate($fields_hash, $table_name, $id_field . ' = ' . $scheduled_task_id);
 			}
 
 			// drop RunInterval column
 			$this->Conn->Query('ALTER TABLE ' . $table_name . ' DROP RunInterval');
 		}
 
 		/**
 		 * Update to 5.2.1-B1
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		public function Upgrade_5_2_1_B1($mode)
 		{
 			if ( $mode != 'after' ) {
 				return;
 			}
 
 			$this->_updateUserPasswords();
 		}
 
 		protected function _updateUserPasswords()
 		{
-			$user_table = $this->Application->getUnitOption('u', 'TableName');
+			$user_table = $this->Application->getUnitConfig('u')->getTableName();
 
 			$sql = 'SELECT Password, PortalUserId
 					FROM ' . $user_table . '
 					WHERE PasswordHashingMethod = ' . PasswordHashingMethod::MD5;
 			$user_passwords = $this->Conn->GetColIterator($sql, 'PortalUserId');
 
 			if ( !count($user_passwords) ) {
 				// no users at all or existing users have converted passwords already
 				return;
 			}
 
 			kUtil::setResourceLimit();
 
 			$password_formatter = $this->Application->recallObject('kPasswordFormatter');
 			/* @var $password_formatter kPasswordFormatter */
 
 			foreach ($user_passwords as $user_id => $user_password) {
 				$fields_hash = Array (
 					'Password' => $password_formatter->hashPassword($user_password, '', PasswordHashingMethod::MD5_AND_PHPPASS),
 					'PasswordHashingMethod' => PasswordHashingMethod::MD5_AND_PHPPASS,
 				);
 
 				$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Users', 'PortalUserId = ' . $user_id);
 			}
 		}
-		
+
 		/**
 		 * Update to 5.3.0-B1
 		 *
 		 * @param string $mode when called mode {before, after)
 		 */
 		public function Upgrade_5_3_0_B1($mode)
 		{
 			if ( $mode != 'before' ) {
 				return;
 			}
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			// add new ml columns to phrases/e-mail events
 			$ml_helper->createFields('phrases');
 			$ml_helper->createFields('emailevents');
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/install/install_toolkit.php
===================================================================
--- branches/5.3.x/core/install/install_toolkit.php	(revision 15697)
+++ branches/5.3.x/core/install/install_toolkit.php	(revision 15698)
@@ -1,1187 +1,1188 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 
 	defined('FULL_PATH') or die('restricted access!');
 
 	/**
 	 * Upgrade sqls are located using this mask
 	 *
 	 */
 	define('UPGRADES_FILE', FULL_PATH.'/%sinstall/upgrades.%s');
 
 	/**
 	 * Prerequisit check classes are located using this mask
 	 *
 	 */
 	define('PREREQUISITE_FILE', FULL_PATH.'/%sinstall/prerequisites.php');
 
 	/**
 	 * Format of version identificator in upgrade files (normal, beta, release candidate)
 	 *
 	 */
 	define('VERSION_MARK', '# ===== v ([\d]+\.[\d]+\.[\d]+|[\d]+\.[\d]+\.[\d]+-B[\d]+|[\d]+\.[\d]+\.[\d]+-RC[\d]+) =====');
 
 	if (!defined('GET_LICENSE_URL')) {
 		/**
 		 * Url used for retrieving user licenses from Intechnic licensing server
 		 *
 		 */
 		define('GET_LICENSE_URL', 'http://www.in-portal.com/license.php');
 	}
 
 	/**
 	 * Misc functions, that are required during installation, when
 	 *
 	 */
 	class kInstallToolkit {
 
 		/**
 		 * Reference to kApplication class object
 		 *
 		 * @var kApplication
 		 */
 		var $Application = null;
 
 		/**
 		 * Connection to database
 		 *
 		 * @var kDBConnection
 		 */
 		var $Conn = null;
 
 		/**
 		 * Path to config.php
 		 *
 		 * @var string
 		 */
 		var $INIFile = '';
 
 		/**
 		 * Parsed data from config.php
 		 *
 		 * @var Array
 		 */
 		var $systemConfig = Array ();
 
 		/**
 		 * Tells, that system config was changed
 		 *
 		 * @var bool
 		 * @access public
 		 */
 		public $systemConfigChanged = false;
 
 		/**
 		 * Path, used by system to store data on filesystem
 		 *
 		 * @var string
 		 */
 		var $defaultWritablePath = '';
 
 		/**
 		 * Installator instance
 		 *
 		 * @var kInstallator
 		 */
 		var $_installator = null;
 
 		function kInstallToolkit()
 		{
 			$this->defaultWritablePath = DIRECTORY_SEPARATOR . 'system';
 
 			if ( class_exists('kApplication') ) {
 				// auto-setup in case of separate module install
 				$this->Application =& kApplication::Instance();
 				$this->Application->Init(); // needed for standalone module install
 
 				$this->Conn =& $this->Application->GetADODBConnection();
 			}
 
 			$this->INIFile = FULL_PATH . $this->defaultWritablePath . DIRECTORY_SEPARATOR . 'config.php';
 
 			$this->systemConfig = $this->ParseConfig(true);
 		}
 
 		/**
 		 * Sets installator
 		 *
 		 * @param kInstallator $instance
 		 */
 		function setInstallator(&$instance)
 		{
 			$this->_installator =& $instance;
 		}
 
 		/**
 		 * Checks prerequisities before module install or upgrade
 		 *
 		 * @param string $module_path
 		 * @param string $versions
 		 * @param string $mode upgrade mode = {install, standalone, upgrade}
 		 * @return bool
 		 */
 		function CheckPrerequisites($module_path, $versions, $mode)
 		{
 			if ( !$versions ) {
 				return Array ();
 			}
 
 			$prerequisite_object =& $this->getPrerequisiteObject($module_path);
 			/* @var $prerequisite_object InPortalPrerequisites */
 
 			// some errors possible
 			return is_object($prerequisite_object) ? $prerequisite_object->CheckPrerequisites($versions, $mode) : Array ();
 		}
 
 		/**
 		 * Call prerequisites method
 		 *
 		 * @param string $module_path
 		 * @param string $method
 		 * @return array
 		 */
 		function CallPrerequisitesMethod($module_path, $method)
 		{
 			$prerequisite_object =& $this->getPrerequisiteObject($module_path);
 			/* @var $prerequisite_object InPortalPrerequisites */
 
 			return is_object($prerequisite_object) ? $prerequisite_object->$method() : false;
 		}
 
 		/**
 		 * Returns prerequisite object to be used for checks
 		 *
 		 * @param string $module_path
 		 * @return kHelper
 		 * @access protected
 		 */
 		protected function &getPrerequisiteObject($module_path)
 		{
 			static $prerequisite_classes = Array ();
 
 			$prerequisites_file = sprintf(PREREQUISITE_FILE, $module_path);
 			if ( !file_exists($prerequisites_file) ) {
 				$false = false;
 
 				return $false;
 			}
 
 			if ( !isset($prerequisite_classes[$module_path]) ) {
 				// save class name, because 2nd time
 				// (in after call $prerequisite_class variable will not be present)
 				include_once $prerequisites_file;
 				$prerequisite_classes[$module_path] = $prerequisite_class;
 			}
 
 			$prerequisite_object = new $prerequisite_classes[$module_path]();
 			/* @var $prerequisite_object InPortalPrerequisites */
 
 			if ( method_exists($prerequisite_object, 'setToolkit') ) {
 				$prerequisite_object->setToolkit($this);
 			}
 
 			return $prerequisite_object;
 		}
 
 		/**
 		 * Processes one license, received from server
 		 *
 		 * @param string $file_data
 		 */
 		function processLicense($file_data)
 		{
 			$modules_helper = $this->Application->recallObject('ModulesHelper');
 			/* @var $modules_helper kModulesHelper */
 
 			$file_data = explode('Code==:', $file_data);
 			$file_data[0] = str_replace('In-Portal License File - do not edit!' . "\n", '', $file_data[0]);
 			$file_data = array_map('trim', $file_data);
 
 			if ($modules_helper->verifyLicense($file_data[0])) {
 				$this->setSystemConfig('Intechnic', 'License', $file_data[0]);
 				if (array_key_exists(1, $file_data)) {
 					$this->setSystemConfig('Intechnic', 'LicenseCode', $file_data[1]);
 				}
 				else {
 					$this->setSystemConfig('Intechnic', 'LicenseCode');
 				}
 				$this->SaveConfig();
 			}
 			else {
 				// invalid license received from licensing server
 				$this->_installator->errorMessage = 'Invalid License File';
 			}
 		}
 
 		/**
 		 * Saves given configuration values to database
 		 *
 		 * @param Array $config
 		 */
 		function saveConfigValues($config)
 		{
 			foreach ($config as $config_var => $value) {
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'SystemSettings
 						SET VariableValue = ' . $this->Conn->qstr($value) . '
 						WHERE VariableName = ' . $this->Conn->qstr($config_var);
 				$this->Conn->Query($sql);
 			}
 		}
 
 		/**
 		 * Sets module version to passed
 		 *
 		 * @param string $module_name
 		 * @param string|bool $module_path
 		 * @param string|bool $version
 		 */
 		function SetModuleVersion($module_name, $module_path = false, $version = false)
 		{
 			if ($version === false) {
 				if (!$module_path) {
 					throw new Exception('Module path must be given to "SetModuleVersion" method to auto-detect version');
 					return ;
 				}
 
 				$version = $this->GetMaxModuleVersion($module_path);
 			}
 
 			// get table prefix from config, because application may not be available here
 			$table_prefix = $this->getSystemConfig('Database', 'TablePrefix');
 
 			if ($module_name == 'kernel') {
 				$module_name = 'in-portal';
 			}
 
 			// don't use "adodb_mktime" here, because it's not yet included
 			$sql = 'UPDATE ' . $table_prefix . 'Modules
 					SET Version = "' . $version . '", BuildDate = ' . time() . '
 					WHERE LOWER(Name) = "' . strtolower($module_name) . '"';
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Sets module root category to passed
 		 *
 		 * @param string $module_name
 		 * @param int $category_id
 		 */
 		function SetModuleRootCategory($module_name, $category_id = 0)
 		{
 			// get table prefix from config, because application may not be available here
 			$table_prefix = $this->getSystemConfig('Database', 'TablePrefix');
 
 			if ($module_name == 'kernel') {
 				$module_name = 'in-portal';
 			}
 
 			$sql = 'UPDATE ' . $table_prefix . 'Modules
 					SET RootCat = ' . $category_id . '
 					WHERE LOWER(Name) = "' . strtolower($module_name) . '"';
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Returns maximal version of given module by scanning it's upgrade scripts
 		 *
 		 * @param string $module_path
 		 * @return string
 		 */
 		function GetMaxModuleVersion($module_path)
 		{
 			$module_path = rtrim(mb_strtolower($module_path), '/');
 			$upgrades_file = sprintf(UPGRADES_FILE, $module_path . '/', 'sql');
 
 			if (!file_exists($upgrades_file)) {
 				// no upgrade file
 				return '5.0.0';
 			}
 
 			$sqls = file_get_contents($upgrades_file);
 			$versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs);
 			if (!$versions_found) {
 				// upgrades file doesn't contain version definitions
 				return '5.0.0';
 			}
 
 			return end($regs[1]);
 		}
 
 		/**
 		 * Runs SQLs from file
 		 *
 		 * @param string $filename
 		 * @param mixed $replace_from
 		 * @param mixed $replace_to
 		 */
 		function RunSQL($filename, $replace_from = null, $replace_to = null)
 		{
 			if (!file_exists(FULL_PATH.$filename)) {
 				return ;
 			}
 
 			$sqls = file_get_contents(FULL_PATH.$filename);
 			if (!$this->RunSQLText($sqls, $replace_from, $replace_to)) {
 				if (is_object($this->_installator)) {
 					$this->_installator->Done();
 				}
 				else {
 					if (isset($this->Application)) {
 						$this->Application->Done();
 					}
 
 					exit;
 				}
 			}
 		}
 
 		/**
 		 * Runs SQLs from string
 		 *
 		 * @param string $sqls
 		 * @param mixed $replace_from
 		 * @param mixed $replace_to
 		 * @param int $start_from
 		 * @return bool
 		 */
 		function RunSQLText(&$sqls, $replace_from = null, $replace_to = null, $start_from = 0)
 		{
 			$table_prefix = $this->getSystemConfig('Database', 'TablePrefix');
 
 			// add prefix to all tables
 			if (strlen($table_prefix) > 0) {
 				$replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO ');
 				foreach ($replacements as $replacement) {
 					$sqls = str_replace($replacement, $replacement . $table_prefix, $sqls);
 				}
 			}
 
 			$sqls = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sqls);
 			$sqls = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sqls);
 			$sqls = str_replace('<%TABLE_PREFIX%>', $table_prefix, $sqls);
 
 			$primary_language = is_object($this->Application) ? $this->Application->GetDefaultLanguageId() : 1;
 			$sqls = str_replace('<%PRIMARY_LANGUAGE%>', $primary_language, $sqls);
 
 			if (isset($replace_from) && isset($replace_to)) {
 				// replace something additionally, e.g. module root category
 				$sqls = str_replace($replace_from, $replace_to, $sqls);
 			}
 
 			$sqls = str_replace("\r\n", "\n", $sqls);  // convert to linux line endings
 			$no_comment_sqls = preg_replace("/#\s([^;]*?)\n/is", '', $sqls); // remove all comments "#" on new lines
 
 			if ($no_comment_sqls === null) {
 				// "ini.pcre.backtrack-limit" reached and error happened
 				$sqls = explode(";\n", $sqls . "\n"); // ensures that last sql won't have ";" in it
 				$sqls = array_map('trim', $sqls);
 
 				// remove all comments "#" on new lines (takes about 2 seconds for 53000 sqls)
 				$sqls = preg_replace("/#\s([^;]*?)/", '', $sqls);
 			}
 			else {
 				$sqls = explode(";\n", $no_comment_sqls . "\n"); // ensures that last sql won't have ";" in it
 				$sqls = array_map('trim', $sqls);
 			}
 
 			$sql_count = count($sqls);
 			$db_collation = $this->getSystemConfig('Database', 'DBCollation');
 
 			for ($i = $start_from; $i < $sql_count; $i++) {
 				$sql = $sqls[$i];
 				if (!$sql || (substr($sql, 0, 1) == '#')) {
 					continue; // usually last line
 				}
 
 				if (substr($sql, 0, 13) == 'CREATE TABLE ' && $db_collation) {
 					// it is CREATE TABLE statement -> add collation
 					$sql .= ' COLLATE \'' . $db_collation . '\'';
 				}
 
 				$this->Conn->Query($sql);
 				if ($this->Conn->getErrorCode() != 0) {
 					if (is_object($this->_installator)) {
 		  				$this->_installator->errorMessage = 'Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg().'<br /><br />Last Database Query:<br /><textarea cols="70" rows="10" readonly>'.htmlspecialchars($sql, null, 'UTF-8').'</textarea>';
 		  				$this->_installator->LastQueryNum = $i + 1;
 					}
 	  				return false;
 	    		}
 			}
 			return true;
 		}
 
 		/**
 		 * Performs clean language import from given xml file
 		 *
 		 * @param string $lang_file
 		 * @param bool $upgrade
 		 * @todo Import for "core/install/english.lang" (322KB) takes 18 seconds to work on Windows
 		 */
 		function ImportLanguage($lang_file, $upgrade = false)
 		{
 			$lang_file = FULL_PATH.$lang_file.'.lang';
 			if (!file_exists($lang_file)) {
 				return ;
 			}
 
 			$language_import_helper = $this->Application->recallObject('LanguageImportHelper');
 			/* @var $language_import_helper LanguageImportHelper */
 
 			if ( !$upgrade ) {
 				$language_import_helper->setOption(LanguageImportHelper::OVERWRITE_EXISTING);
 			}
 
 			$language_import_helper->performImport($lang_file, '|0|1|2|', '');
 		}
 
 		/**
 		 * Converts module version in format X.Y.Z[-BN/-RCM] to signle integer
 		 *
 		 * @param string $version
 		 * @return int
 		 */
 		function ConvertModuleVersion($version)
 		{
 			if (preg_match('/(.*)-(B|RC)([\d]+)/', $version, $regs)) {
 				// -B<M> or RC-<N>
 				$parts = explode('.', $regs[1]);
 
 				$parts[] = $regs[2] == 'B' ? 1 : 2; // B reliases goes before RC releases
 				$parts[] = $regs[3];
 			}
 			else {
 				// releases without B/RC marks go after any B/RC releases
 				$parts = explode('.', $version . '.3.100');
 			}
 
 			$bin = '';
 
 			foreach ($parts as $part_index => $part) {
 				if ($part_index == 3) {
 					// version type only can be 1/2/3 (11 in binary form), so don't use padding at all
 					$pad_count = 2;
 				}
 				else {
 					$pad_count = 8;
 				}
 
 				$bin .= str_pad(decbin($part), $pad_count, '0', STR_PAD_LEFT);
 			}
 
 			return bindec($bin);
 		}
 
 		/**
 		 * Returns themes, found in system
 		 *
 		 * @param bool $rebuild
 		 * @return int
 		 */
 		function getThemes($rebuild = false)
 		{
 			if ($rebuild) {
 				$this->rebuildThemes();
 			}
 
-			$id_field = $this->Application->getUnitOption('theme', 'IDField');
-			$table_name = $this->Application->getUnitOption('theme', 'TableName');
+			$theme_config = $this->Application->getUnitConfig('theme');
+			$id_field = $theme_config->getIDField();
 
 			$sql = 'SELECT Name, ' . $id_field . '
-					FROM ' . $table_name . '
+					FROM ' . $theme_config->getTableName() . '
 					ORDER BY Name ASC';
+
 			return $this->Conn->GetCol($sql, $id_field);
 		}
 
 		function ParseConfig($parse_section = false)
 		{
 			if (!file_exists($this->INIFile)) {
 				return Array ();
 			}
 
 			if (file_exists($this->INIFile) && !is_readable($this->INIFile)) {
 				die('Could Not Open Ini File');
 			}
 
 			$contents = file($this->INIFile);
 
 			if ($contents && $contents[0] == '<' . '?' . 'php die() ?' . ">\n") {
 				// format of "config.php" file before 5.1.0 version
 				array_shift($contents);
 
 				return $this->parseIniString(implode('', $contents), $parse_section);
 			}
 
 			$_CONFIG = Array ();
 	    	require($this->INIFile);
 
 	    	if ($parse_section) {
 	    		return $_CONFIG;
 	    	}
 
 	    	$ret = Array ();
 
 	    	foreach ($_CONFIG as $section => $section_variables) {
 	    		$ret = array_merge($ret, $section_variables);
 	    	}
 
 	    	return $ret;
 		}
 
 		/**
 		 * Equivalent for "parse_ini_string" function available since PHP 5.3.0
 		 *
 		 * @param string $ini
 		 * @param bool $process_sections
 		 * @param int $scanner_mode
 		 * @return Array
 		 */
 		function parseIniString($ini, $process_sections = false, $scanner_mode = null)
 		{
 			# Generate a temporary file.
 			$tempname = tempnam('/tmp', 'ini');
 			$fp = fopen($tempname, 'w');
 			fwrite($fp, $ini);
 			$ini = parse_ini_file($tempname, !empty($process_sections));
 			fclose($fp);
 			@unlink($tempname);
 
 			return $ini;
 		}
 
 		function SaveConfig($silent = false)
 		{
 			if (!is_writable($this->INIFile) && !is_writable(dirname($this->INIFile))) {
 				$error_msg = 'Cannot write to "' . $this->INIFile . '" file';
 
 				if ($silent) {
 					trigger_error($error_msg, E_USER_WARNING);
 				}
 				else {
 					throw new Exception($error_msg);
 				}
 
 				return ;
 			}
 
 			$fp = fopen($this->INIFile, 'w');
 			fwrite($fp, '<' . '?' . 'php' . "\n\n");
 
 			foreach ($this->systemConfig as $section_name => $section_data) {
 				foreach ($section_data as $key => $value) {
 					fwrite($fp, '$_CONFIG[\'' . $section_name . '\'][\'' . $key . '\'] = \'' . addslashes($value) . '\';' . "\n");
 				}
 
 				fwrite($fp, "\n");
 			}
 
 			fclose($fp);
 
 			$this->systemConfigChanged = false;
 		}
 
 		/**
 		 * Sets value to system config (yet SaveConfig must be called to write it to file)
 		 *
 		 * @param string $section
 		 * @param string $key
 		 * @param string $value
 		 */
 		function setSystemConfig($section, $key, $value = null)
 		{
 			$this->systemConfigChanged = true;
 
 			if (isset($value)) {
 				if (!array_key_exists($section, $this->systemConfig)) {
 					// create section, when missing
 					$this->systemConfig[$section] = Array ();
 				}
 
 				// create key in section
 				$this->systemConfig[$section][$key] = $value;
 				return ;
 			}
 
 			unset($this->systemConfig[$section][$key]);
 		}
 
 		/**
 		 * Returns information from system config
 		 *
 		 * @param string $section
 		 * @param string $key
 		 * @param mixed $default
 		 * @return string|bool
 		 */
 		function getSystemConfig($section, $key, $default = false)
 		{
 			if ( !array_key_exists($section, $this->systemConfig) ) {
 				return $default;
 			}
 
 			if ( !array_key_exists($key, $this->systemConfig[$section]) ) {
 				return $default;
 			}
 
 			return isset($this->systemConfig[$section][$key]) ? $this->systemConfig[$section][$key] : $default;
 		}
 
 		/**
 		 * Checks if system config is present and is not empty
 		 *
 		 * @return bool
 		 */
 		function systemConfigFound()
 		{
 			return file_exists($this->INIFile) && $this->systemConfig;
 		}
 
 		/**
 		 * Checks if given section is present in config
 		 *
 		 * @param string $section
 		 * @return bool
 		 */
 		function sectionFound($section)
 		{
 			return array_key_exists($section, $this->systemConfig);
 		}
 
 		/**
 		 * Returns formatted module name based on it's root folder
 		 *
 		 * @param string $module_folder
 		 * @return string
 		 */
 		function getModuleName($module_folder)
 		{
 			return implode('-', array_map('ucfirst', explode('-', $module_folder)));
 		}
 
 		/**
 		 * Returns information about module (based on "install/module_info.xml" file)
 		 *
 		 * @param string $module_name
 		 * @return Array
 		 */
 		function getModuleInfo($module_name)
 		{
 			if ( $module_name == 'core' ) {
 				$info_file = FULL_PATH . '/' . $module_name . '/install/module_info.xml';
 			}
 			else {
 				$info_file = MODULES_PATH . '/' . $module_name . '/install/module_info.xml';
 			}
 
 			if ( !file_exists($info_file) ) {
 				return Array ();
 			}
 
 			$ret = Array ();
 			$module_info = simplexml_load_file($info_file);
 
 			if ( $module_info === false ) {
 				// non-valid xml file
 				return Array ();
 			}
 
 			foreach ($module_info as $node) {
 				/* @var $node SimpleXMLElement */
 				$ret[strtolower($node->getName())] = trim($node);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns nice module string to be used on install/upgrade screens
 		 *
 		 * @param string $module_name
 		 * @param string $version_string
 		 * @return string
 		 */
 		function getModuleString($module_name, $version_string)
 		{
 			// image (if exists) <description> (<name> <version>)
 
 			$ret = Array ();
 			$module_info = $this->getModuleInfo($module_name);
 
 			if (array_key_exists('name', $module_info) && $module_info['name']) {
 				$module_name = $module_info['name'];
 			}
 			else {
 				$module_name = $this->getModuleName($module_name);
 			}
 
 			if (array_key_exists('image', $module_info) && $module_info['image']) {
 				$image_src = $module_info['image'];
 
 				if (!preg_match('/^(http|https):\/\//', $image_src)) {
 					// local image -> make absolute url
 					$image_src = $this->Application->BaseURL() . $image_src;
 				}
 
 				$ret[] = '<img src="' . $image_src . '" alt="' . htmlspecialchars($module_name, null, 'UTF-8') . '" title="' . htmlspecialchars($module_name, null, 'UTF-8') . '" style="vertical-align:middle; margin: 3px 0 3px 5px"/>';
 			}
 
 			if (array_key_exists('description', $module_info) && $module_info['description']) {
 				$ret[] = $module_info['description'];
 			}
 			else {
 				$ret[] = $module_name;
 			}
 
 			$ret[] = '(' . $module_name . ' ' . $version_string . ')';
 
 			return implode(' ', $ret);
 		}
 
 		/**
 		 * Creates module root category in "Home" category using given data and returns it
 		 *
 		 * @param string $name
 		 * @param string $description
 		 * @param string $category_template
 		 * @param string $category_icon
 		 * @return kDBItem
 		 */
 		function &createModuleCategory($name, $description, $category_template = null, $category_icon = null)
 		{
 			static $fields = null;
 
 			if ( !isset($fields) ) {
 				$ml_formatter = $this->Application->recallObject('kMultiLanguage');
 				/* @var $ml_formatter kMultiLanguage */
 
 				$fields['name'] = $ml_formatter->LangFieldName('Name');
 				$fields['description'] = $ml_formatter->LangFieldName('Description');
 			}
 
 			$category = $this->Application->recallObject('c', null, Array ('skip_autoload' => true));
 			/* @var $category kDBItem */
 
 			$category_fields = Array (
 				$fields['name'] => $name, 'Filename' => $name, 'AutomaticFilename' => 1,
 				$fields['description'] => $description, 'Status' => STATUS_ACTIVE, 'Priority' => -9999,
 
 				// prevents empty link to module category on spearate module install
 				'NamedParentPath' => 'Content/' . $name,
 			);
 
 			$category_fields['ParentId'] = $this->Application->getBaseCategory();
 
 			if ( isset($category_template) ) {
 				$category_fields['Template'] = $category_template;
 				$category_fields['CachedTemplate'] = $category_template;
 			}
 
 			if ( isset($category_icon) ) {
 				$category_fields['UseMenuIconUrl'] = 1;
 				$category_fields['MenuIconUrl'] = $category_icon;
 			}
 
 			$category->Clear();
 			$category->SetDBFieldsFromHash($category_fields);
 
 			$category->Create();
 
 			$priority_helper = $this->Application->recallObject('PriorityHelper');
 			/* @var $priority_helper kPriorityHelper */
 
 			$event = new kEvent('c:OnListBuild');
 
 			// ensure, that newly created category has proper value in Priority field
 			$priority_helper->recalculatePriorities($event, 'ParentId = ' . $category_fields['ParentId']);
 
 			// update Priority field in object, becase "CategoriesItem::Update" method will be called
 			// from "kInstallToolkit::setModuleItemTemplate" and otherwise will set 0 to Priority field
 			$sql = 'SELECT Priority
 					FROM ' . $category->TableName . '
 					WHERE ' . $category->IDField . ' = ' . $category->GetID();
 			$category->SetDBField('Priority', $this->Conn->GetOne($sql));
 
 			return $category;
 		}
 
 		/**
 		 * Sets category item template into custom field for given prefix
 		 *
 		 * @param kDBItem $category
 		 * @param string $prefix
 		 * @param string $item_template
 		 */
 		function setModuleItemTemplate(&$category, $prefix, $item_template)
 		{
 			$this->Application->removeObject('c-cdata');
 
 			// recreate all fields, because custom fields are added during install script
 			$category->Configure();
 
 			$category->SetDBField('cust_' . $prefix  .'_ItemTemplate', $item_template);
 			$category->Update();
 		}
 
 		/**
 		 * Link custom field records with search config records + create custom field columns
 		 *
 		 * @param string $module_folder
 		 * @param string $prefix
 		 * @param int $item_type
 		 */
 		function linkCustomFields($module_folder, $prefix, $item_type)
 		{
 			$module_folder = strtolower($module_folder);
 			$module_name = $module_folder;
 
 			if ( $module_folder == 'kernel' ) {
 				$module_name = 'in-portal';
 				$module_folder = 'core';
 			}
 
 			$db =& $this->Application->GetADODBConnection();
 
 			$sql = 'SELECT FieldName, CustomFieldId
 					FROM ' . TABLE_PREFIX . 'CustomFields
-					WHERE Type = ' . $item_type . ' AND IsSystem = 0'; // config is not read here yet :( $this->Application->getUnitOption('p', 'ItemType');
+					WHERE Type = ' . $item_type . ' AND IsSystem = 0'; // config is not read here yet :( $this->Application->getUnitConfig('p')->getItemType();
 			$custom_fields = $db->GetCol($sql, 'CustomFieldId');
 
 			foreach ($custom_fields as $cf_id => $cf_name) {
 				$sql = 'UPDATE ' . TABLE_PREFIX . 'SearchConfig
 						SET CustomFieldId = ' . $cf_id . '
 						WHERE (TableName = "CustomFields") AND (LOWER(ModuleName) = "' . $module_name . '") AND (FieldName = ' . $db->qstr($cf_name) . ')';
 				$db->Query($sql);
 			}
 
 			$this->Application->refreshModuleInfo(); // this module configs are now processed
 
 			// because of configs was read only from installed before modules (in-portal), then reread configs
 			$this->Application->UnitConfigReader->scanModules(MODULES_PATH . DIRECTORY_SEPARATOR . $module_folder);
 
 			// create correct columns in CustomData table
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$ml_helper->createFields($prefix . '-cdata', true);
 		}
 
 		/**
 		 * Deletes cache, useful after separate module install and installator last step
 		 *
 		 * @param bool $refresh_permissions
 		 * @return void
 		 */
 		function deleteCache($refresh_permissions = false)
 		{
 			$this->Application->HandleEvent(new kEvent('adm:OnResetMemcache')); // not in DB = 100% invalidate
 			$this->Application->HandleEvent(new kEvent('adm:OnResetConfigsCache'));
 			$this->Application->HandleEvent(new kEvent('adm:OnResetSections'));
 			$this->Application->HandleEvent(new kEvent('c:OnResetCMSMenuCache'));
 
 			$this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls');
 
 			if ( $refresh_permissions ) {
 				$rebuild_mode = $this->Application->ConfigValue('CategoryPermissionRebuildMode');
 
 				if ( $rebuild_mode == CategoryPermissionRebuild::SILENT ) {
 					// refresh permission without progress bar
 					$updater = $this->Application->makeClass('kPermCacheUpdater');
 					/* @var $updater kPermCacheUpdater */
 
 					$updater->OneStepRun();
 				}
 				elseif ( $rebuild_mode == CategoryPermissionRebuild::AUTOMATIC ) {
 					// refresh permissions with ajax progress bar (when available)
 					$this->Application->setDBCache('ForcePermCacheUpdate', 1);
 				}
 			}
 		}
 
 		/**
 		 * Deletes all temp tables (from active sessions too)
 		 *
 		 */
 		function deleteEditTables()
 		{
 			$table_prefix = $this->getSystemConfig('Database', 'TablePrefix');
 
 			$tables = $this->Conn->GetCol('SHOW TABLES');
 			$mask_edit_table = '/' . $table_prefix . 'ses_(.*)_edit_(.*)/';
 			$mask_search_table = '/' . $table_prefix . 'ses_(.*?)_(.*)/';
 
 			foreach ($tables as $table) {
 				if ( preg_match($mask_edit_table, $table, $rets) || preg_match($mask_search_table, $table, $rets) ) {
 					$this->Conn->Query('DROP TABLE IF EXISTS ' . $table);
 				}
 			}
 		}
 
 		/**
 		 * Perform redirect after separate module install
 		 *
 		 * @param string $module_folder
 		 * @param bool $refresh_permissions
 		 */
 		function finalizeModuleInstall($module_folder, $refresh_permissions = false)
 		{
 			$this->SetModuleVersion(basename($module_folder), $module_folder);
 
 			if (!$this->Application->GetVar('redirect')) {
 				return ;
 			}
 
 			$themes_helper = $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			// use direct query, since module isn't yet in kApplication::ModuleInfo array
 			$sql = 'SELECT Name
 					FROM ' . TABLE_PREFIX . 'Modules
 					WHERE Path = ' . $this->Conn->qstr(rtrim($module_folder, '/') . '/');
 			$module_name = $this->Conn->GetOne($sql);
 
 			$themes_helper->synchronizeModule($module_name);
 
 			$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$ml_helper->massCreateFields();
 
 			$this->deleteCache($refresh_permissions);
 
 			$url_params = Array (
 				'pass' => 'm', 'admin' => 1,
 				'RefreshTree' => 1, 'index_file' => 'index.php',
 			);
 			$this->Application->Redirect('modules/modules_list', $url_params);
 		}
 
 		/**
 		 * Performs rebuild of themes
 		 *
 		 */
 		function rebuildThemes()
 		{
 			$this->Application->HandleEvent(new kEvent('adm:OnRebuildThemes'));
 		}
 
 		/**
 		 * Checks that file is writable by group or others
 		 *
 		 * @param string $file
 		 * @return boolean
 		 */
 		function checkWritePermissions($file)
 		{
 			if (DIRECTORY_SEPARATOR == '\\') {
 				// windows doen't allow to check permissions (always returns null)
 				return null;
 			}
 
 			$permissions = fileperms($file);
 			return $permissions & 0x0010 || $permissions & 0x0002;
 		}
 
 		/**
 		 * Upgrades primary skin to the latest version
 		 *
 		 * @param Array $module_info
 		 * @return string|bool
 		 */
 		function upgradeSkin($module_info)
 		{
 			$upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'css');
 			$data = file_get_contents($upgrades_file);
 
 			// get all versions with their positions in file
 			$versions = Array ();
 			preg_match_all('/(' . VERSION_MARK . ')/s', $data, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
 			$from_version_int = $this->ConvertModuleVersion($module_info['FromVersion']);
 
 			foreach ($matches as $index => $match) {
 				$version_int = $this->ConvertModuleVersion($match[2][0]);
 
 				if ( $version_int < $from_version_int ) {
 					// only process versions, that were released after currently used version
 					continue;
 				}
 
 				$start_pos = $match[0][1] + strlen($match[0][0]);
 				$end_pos = array_key_exists($index + 1, $matches) ? $matches[$index + 1][0][1] : mb_strlen($data);
 				$patch_data = str_replace("\r\n", "\n", substr($data, $start_pos, $end_pos - $start_pos));
 
 				$versions[] = Array (
 					'Version' => $match[2][0],
 					// fixes trimmed leading spaces by modern text editor
 					'Data' => ltrim( str_replace("\n\n", "\n \n", $patch_data) ),
 				);
 			}
 
 			if ( !$versions ) {
 				// not skin changes -> quit
 				return true;
 			}
 
 			$primary_skin = $this->Application->recallObject('skin.primary', null, Array ('skip_autoload' => true));
 			/* @var $primary_skin kDBItem */
 
 			$primary_skin->Load(1, 'IsPrimary');
 
 			if ( !$primary_skin->isLoaded() ) {
 				// we always got primary skin, but just in case
 				return false;
 			}
 
 			$temp_handler = $this->Application->recallObject('skin_TempHandler', 'kTempTablesHandler');
 			/* @var $temp_handler kTempTablesHandler */
 
 			// clone current skin
 			$cloned_ids = $temp_handler->CloneItems('skin', '', Array ($primary_skin->GetID()));
 
 			if ( !$cloned_ids ) {
 				// can't clone
 				return false;
 			}
 
 			$skin = $this->Application->recallObject('skin.tmp', null, Array ('skip_autoload' => true));
 			/* @var $skin kDBItem */
 
 			$skin->Load($cloned_ids[0]);
 
 			// save css to temp file (for patching)
 			$skin_file = tempnam('/tmp', 'skin_css_');
 			$fp = fopen($skin_file, 'w');
 			fwrite($fp, str_replace("\r\n", "\n", $skin->GetDBField('CSS')));
 			fclose($fp);
 
 			$output = Array ();
 			$patch_file = tempnam('/tmp', 'skin_patch_');
 
 			foreach ($versions as $version_info) {
 				// for each left version get it's patch and apply to temp file
 				$fp = fopen($patch_file, 'w');
 				fwrite($fp, $version_info['Data']);
 				fclose($fp);
 
 				$output[ $version_info['Version'] ] = shell_exec('patch ' . $skin_file . ' ' . $patch_file . ' 2>&1') . "\n";
 			}
 
 			// place temp file content into cloned skin
 			$skin->SetDBField('Name', 'Upgraded to ' . $module_info['ToVersion']);
 			$skin->SetDBField('CSS', file_get_contents($skin_file));
 			$skin->Update();
 
 			unlink($skin_file);
 			unlink($patch_file);
 
 			$has_errors = false;
 
 			foreach ($output as $version => $version_output) {
 				$version_errors = trim(preg_replace("/(^|\n)(patching file .*?|Hunk #.*?\.)(\n|$)/m", '', $version_output));
 
 				if ( $version_errors ) {
 					$has_errors = true;
 					$output[$version] = trim(preg_replace("/(^|\n)(patching file .*?)(\n|$)/m", '', $output[$version]));
 				}
 				else {
 					unset($output[$version]);
 				}
 			}
 
 			if ( !$has_errors ) {
 				// copy patched css back to primary skin
 				$primary_skin->SetDBField('CSS', $skin->GetDBField('CSS'));
 				$primary_skin->Update();
 
 				// delete temporary skin record
 				$temp_handler->DeleteItems('skin', '', Array ($skin->GetID()));
 
 				return true;
 			}
 
 			// put clean skin from new version
 			$skin->SetDBField('CSS', file_get_contents(FULL_PATH . '/core/admin_templates/incs/style_template.css'));
 			$skin->Update();
 
 			// return output in case of errors
 			return $output;
 		}
 
 		/**
 		 * Returns cache handlers, that are working
 		 *
 		 * @param string $current
 		 * @return Array
 		 */
 		public function getWorkingCacheHandlers($current = null)
 		{
 			if ( !isset($current) ) {
 				$current = $this->getSystemConfig('Misc', 'CacheHandler');
 			}
 
 			$cache_handler = $this->Application->makeClass('kCache');
 
 			$cache_handlers = Array (
 				'Fake' => 'None', 'Memcache' => 'Memcached', 'XCache' => 'XCache', 'Apc' => 'Alternative PHP Cache'
 			);
 
 			foreach ($cache_handlers AS $class_prefix => $title) {
 				$handler_class = $class_prefix . 'CacheHandler';
 
 				if ( !class_exists($handler_class) ) {
 					unset($cache_handlers[$class_prefix]);
 				}
 				else {
 					$handler = new $handler_class($cache_handler, 'localhost:11211');
 					/* @var $handler FakeCacheHandler */
 
 					if ( !$handler->isWorking() ) {
 						if ( $current == $class_prefix ) {
 							$cache_handlers[$class_prefix] .= ' (offline)';
 						}
 						else {
 							unset($cache_handlers[$class_prefix]);
 						}
 					}
 				}
 			}
 
 			return $cache_handlers;
 		}
 
 		/**
 		 * Returns compression engines, that are working
 		 *
 		 * @param string $current
 		 * @return Array
 		 */
 		public function getWorkingCompressionEngines($current = null)
 		{
 			if ( !isset($current) ) {
 				$current = $this->getSystemConfig('Misc', 'CompressionEngine');
 			}
 
 			$output = shell_exec('java -version 2>&1');
 			$compression_engines = Array ('' => 'None', 'yui' => 'YUICompressor (Java)', 'php' => 'PHP-based');
 
 			if ( stripos($output, 'java version') === false ) {
 				if ( $current == 'yui' ) {
 					$compression_engines['yui'] .= ' (offline)';
 				}
 				else {
 					unset($compression_engines['yui']);
 				}
 			}
 
 			return $compression_engines;
 		}
 	}
\ No newline at end of file
Index: branches/5.3.x/core/install.php
===================================================================
--- branches/5.3.x/core/install.php	(revision 15697)
+++ branches/5.3.x/core/install.php	(revision 15698)
@@ -1,1793 +1,1794 @@
 <?php
 /**
 * @version	$Id$
 * @package	In-Portal
 * @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
 * @license      GNU/GPL
 * In-Portal is Open Source software.
 * This means that this software may have been modified pursuant
 * the GNU General Public License, and as distributed it includes
 * or is derivative of works licensed under the GNU General Public License
 * or other free or open source software licenses.
 * See http://www.in-portal.org/license for copyright notices and details.
 */
 	ini_set('display_errors', 1);
 	error_reporting(E_ALL & ~E_STRICT);
 
 	define('IS_INSTALL', 1);
 	define('ADMIN', 1);
 	define('FULL_PATH', realpath(dirname(__FILE__).'/..') );
 	define('REL_PATH', '/core');
 
 	// run installator
 	$install_engine = new kInstallator();
 	$install_engine->Init();
 	$install_engine->Run();
 	$install_engine->Done();
 
 	class kInstallator {
 
 		/**
 		 * Reference to kApplication class object
 		 *
 		 * @var kApplication
 		 */
 		var $Application = null;
 
 		/**
 		 * Connection to database
 		 *
 		 * @var kDBConnection
 		 */
 		var $Conn = null;
 
 		/**
 		 * XML file containing steps information
 		 *
 		 * @var string
 		 */
 		var $StepDBFile = '';
 
 		/**
 		 * Step name, that currently being processed
 		 *
 		 * @var string
 		 */
 		var $currentStep = '';
 
 		/**
 		 * Steps list (preset) to use for current installation
 		 *
 		 * @var string
 		 */
 		var $stepsPreset = '';
 
 		/**
 		 * Installation steps to be done
 		 *
 		 * @var Array
 		 */
 		var $steps = Array (
 			'fresh_install'		=>	Array ('sys_requirements', 'check_paths', 'db_config', 'select_license', /*'download_license',*/ 'select_domain', 'root_password', 'choose_modules', 'post_config', 'sys_config', 'select_theme', 'security', 'finish'),
 			'clean_reinstall'	=>	Array ('install_setup', 'sys_requirements', 'check_paths', 'clean_db', 'db_config', 'select_license', /*'download_license',*/ 'select_domain', 'root_password', 'choose_modules', 'post_config', 'sys_config', 'select_theme', 'security', 'finish'),
 			'already_installed'	=>	Array ('check_paths', 'install_setup'),
 
 			'upgrade'			=>	Array ('check_paths', 'install_setup', 'sys_config', 'upgrade_modules', 'skin_upgrade', 'security', 'finish'),
 			'update_license'	=>	Array ('check_paths', 'install_setup', 'select_license', /*'download_license',*/ 'select_domain', 'security', 'finish'),
 			'update_config'		=>	Array ('check_paths', 'install_setup', 'sys_config', 'security', 'finish'),
 			'db_reconfig'		=>	Array ('check_paths', 'install_setup', 'db_reconfig', 'security', 'finish'),
 			'sys_requirements'	=>	Array ('check_paths', 'install_setup', 'sys_requirements', 'security', 'finish')
 		);
 
 		/**
 		 * Steps, that doesn't required admin to be logged-in to proceed
 		 *
 		 * @var Array
 		 */
 		var $skipLoginSteps = Array ('sys_requirements', 'check_paths', 'select_license', /*'download_license',*/ 'select_domain', 'root_password', 'choose_modules', 'post_config', 'select_theme', 'security', 'finish', -1);
 
 		/**
 		 * Steps, on which kApplication should not be initialized, because of missing correct db table structure
 		 *
 		 * @var Array
 		 */
 		var $skipApplicationSteps = Array ('sys_requirements', 'check_paths', 'clean_db', 'db_config', 'db_reconfig' /*, 'install_setup'*/); // remove install_setup when application will work separately from install
 
 		/**
 		 * Folders that should be writeable to continue installation. $1 - main writeable folder from config.php ("/system" by default)
 		 *
 		 * @var Array
 		 */
 		var $writeableFolders = Array (
 			'$1',
 			'$1/.restricted',
 			'$1/images',
 			'$1/images/pending',
 			'$1/images/emoticons', // for "In-Bulletin"
 			'$1/user_files',
 			'$1/cache',
 		);
 
 		/**
 		 * Contains last error message text
 		 *
 		 * @var string
 		 */
 		var $errorMessage = '';
 
 		/**
 		 * Base path for includes in templates
 		 *
 		 * @var string
 		 */
 		var $baseURL = '';
 
 		/**
 		 * Holds number of last executed query in the SQL
 		 *
 		 * @var int
 		 */
 		var $LastQueryNum = 0;
 
 		/**
 		 * Dependencies, that should be used in upgrade process
 		 *
 		 * @var Array
 		 */
 		var $upgradeDepencies = Array ();
 
 		/**
 		 * Log of upgrade - list of upgraded modules and their versions
 		 *
 		 * @var Array
 		 */
 		var $upgradeLog = Array ();
 
 		/**
 		 * Common tools required for installation process
 		 *
 		 * @var kInstallToolkit
 		 */
 		var $toolkit = null;
 
 		function Init()
 		{
 			include_once(FULL_PATH . REL_PATH . '/kernel/kbase.php'); // required by kDBConnection class
 			include_once(FULL_PATH . REL_PATH . '/kernel/utility/multibyte.php');	// emulating multi-byte php extension
 			require_once(FULL_PATH . REL_PATH . '/install/install_toolkit.php'); // toolkit required for module installations to installator
 			$this->toolkit = new kInstallToolkit();
 			$this->toolkit->setInstallator($this);
 
 			$this->StepDBFile = FULL_PATH.'/'.REL_PATH.'/install/steps_db.xml';
 
 			$base_path = rtrim(preg_replace('/'.preg_quote(rtrim(REL_PATH, '/'), '/').'$/', '', str_replace('\\', '/', dirname($_SERVER['PHP_SELF']))), '/');
 			$this->baseURL = 'http://'.$_SERVER['HTTP_HOST'].$base_path.'/core/install/';
 
 			set_error_handler( Array(&$this, 'ErrorHandler') );
 
 			if (file_exists($this->toolkit->INIFile)) {
 				// if config.php found, then check his write permission too
 				$this->writeableFolders[] = $this->toolkit->defaultWritablePath . '/config.php';
 			}
 
 			if ( !$this->toolkit->getSystemConfig('Misc', 'WriteablePath') ) {
 				$this->toolkit->setSystemConfig('Misc', 'WriteablePath', $this->toolkit->defaultWritablePath);
 			}
 
 			if ( !$this->toolkit->getSystemConfig('Misc', 'RestrictedPath') ) {
 				$this->toolkit->setSystemConfig('Misc', 'RestrictedPath', $this->toolkit->getSystemConfig('Misc', 'WriteablePath') . DIRECTORY_SEPARATOR . '.restricted');
 			}
 
 			if ( !$this->toolkit->getSystemConfig('Misc', 'WebsitePath') ) {
 				$this->toolkit->setSystemConfig('Misc', 'WebsitePath', $base_path);
 			}
 
 			if ( $this->toolkit->systemConfigChanged ) {
 				// immediately save, because this paths will be used in kApplication class later
 				$this->toolkit->SaveConfig(true);
 			}
 
 			$this->currentStep = $this->GetVar('step');
 
 			// can't check login on steps where no application present anyways :)
 			$this->skipLoginSteps = array_unique(array_merge($this->skipLoginSteps, $this->skipApplicationSteps));
 
 			$this->SelectPreset();
 
 			if (!$this->currentStep) {
 				$this->SetFirstStep(); // sets first step of current preset
 			}
 
 			$this->InitStep();
 		}
 
 		function SetFirstStep()
 		{
 			reset($this->steps[$this->stepsPreset]);
 			$this->currentStep = current($this->steps[$this->stepsPreset]);
 		}
 
 		/**
 		 * Selects preset to proceed based on various criteria
 		 *
 		 */
 		function SelectPreset()
 		{
 			$preset = $this->GetVar('preset');
 			if ($this->toolkit->systemConfigFound()) {
 				// only at installation first step
 				$status = $this->CheckDatabase(false);
 				if ($status && $this->AlreadyInstalled()) {
 					// if already installed, then all future actions need login to work
 					$this->skipLoginSteps = Array ('check_paths', -1);
 					if (!$preset) {
 						$preset = 'already_installed';
 						$this->currentStep = '';
 					}
 				}
 			}
 			if ($preset === false) {
 				$preset = 'fresh_install'; // default preset
 			}
 
 			$this->stepsPreset = $preset;
 		}
 
 		/**
 		 * Returns variable from request
 		 *
 		 * @param string $name
 		 * @param mixed $default
 		 * @return string|bool
 		 * @access private
 		 */
 		private function GetVar($name, $default = false)
 		{
 			if ( array_key_exists($name, $_COOKIE) ) {
 				return $_COOKIE[$name];
 			}
 
 			if ( array_key_exists($name, $_POST) ) {
 				return $_POST[$name];
 			}
 
 			return array_key_exists($name, $_GET) ? $_GET[$name] : $default;
 		}
 
 		/**
 		 * Sets new value for request variable
 		 *
 		 * @param string $name
 		 * @param mixed $value
 		 * @return void
 		 * @access private
 		 */
 		private function SetVar($name, $value)
 		{
 			$_POST[$name] = $value;
 		}
 
 		/**
 		 * Performs needed intialization of data, that step requires
 		 *
 		 */
 		function InitStep()
 		{
 			$require_login = !in_array($this->currentStep, $this->skipLoginSteps);
 			$this->InitApplication($require_login);
 
 			if ($require_login) {
 				// step require login to proceed
 				if (!$this->Application->LoggedIn()) {
 					$this->stepsPreset = 'already_installed';
 					$this->currentStep = 'install_setup'; // manually set 2nd step, because 'check_paths' step doesn't contain login form
 //					$this->SetFirstStep();
 				}
 			}
 
 			switch ($this->currentStep) {
 				case 'sys_requirements':
 					$required_checks = Array (
 						'php_version', 'curl', 'simplexml', 'freetype', 'gd_version',
 						'jpeg', 'mysql', 'json', 'date.timezone', 'output_buffering',
 					);
 
 					$check_results = $this->toolkit->CallPrerequisitesMethod('core/', 'CheckSystemRequirements');
 					$required_checks = array_diff($required_checks, array_keys( array_filter($check_results) ));
 
 					if ( $required_checks ) {
 						// php-based checks failed - show error
 						$this->errorMessage = '<br/>Installation can not continue until all required environment parameters are set correctly';
 					}
 					elseif ( $this->GetVar('js_enabled') === false ) {
 						// can't check JS without form submit - set some fake error, so user stays on this step
 						$this->errorMessage = '&nbsp;';
 					}
 					elseif ( !$this->GetVar('js_enabled') || !$this->GetVar('cookies_enabled') ) {
 						// js/cookies disabled
 						$this->errorMessage = '<br/>Installation can not continue until all required environment parameters are set correctly';
 					}
 					break;
 
 				case 'check_paths':
 					$writeable_base = $this->toolkit->getSystemConfig('Misc', 'WriteablePath');
 					foreach ($this->writeableFolders as $folder_path) {
 						$file_path = FULL_PATH . str_replace('$1', $writeable_base, $folder_path);
 						if (file_exists($file_path) && !is_writable($file_path)) {
 							$this->errorMessage = '<br/>Installation can not continue until all required permissions are set correctly';
 							break;
 						}
 					}
 					break;
 
 				case 'clean_db':
 					// don't use Application, because all tables will be erased and it will crash
 					$sql = 'SELECT Path
 							FROM ' . TABLE_PREFIX . 'Modules';
 					$modules = $this->Conn->GetCol($sql);
 
 					foreach ($modules as $module_folder) {
 						$remove_file = '/' . $module_folder . 'install/remove_schema.sql';
 						if (file_exists(FULL_PATH . $remove_file)) {
 							$this->toolkit->RunSQL($remove_file);
 						}
 					}
 
 					$this->toolkit->deleteEditTables();
 
 					$this->currentStep = $this->GetNextStep();
 					break;
 
 				case 'db_config':
 				case 'db_reconfig':
 					$fields = Array (
 						'DBType', 'DBHost', 'DBName', 'DBUser',
 						'DBUserPassword', 'DBCollation', 'TablePrefix'
 					);
 
 					// set fields
 					foreach ($fields as $field_name) {
 						$submit_value = $this->GetVar($field_name);
 
 						if ($submit_value !== false) {
 							$this->toolkit->setSystemConfig('Database', $field_name, $submit_value);
 						}
 						/*else {
 							$this->toolkit->setSystemConfig('Database', $field_name, '');
 						}*/
 					}
 			        break;
 
 				case 'download_license':
 					$license_source = $this->GetVar('license_source');
 					if ($license_source !== false && $license_source != 1) {
 						// previous step was "Select License" and not "Download from Intechnic" option was selected
 						$this->currentStep = $this->GetNextStep();
 					}
 					break;
 
 				case 'choose_modules':
 					// if no modules found, then proceed to next step
 					$modules = $this->ScanModules();
 					if (!$modules) {
 						$this->currentStep = $this->GetNextStep();
 					}
 					break;
 
 				case 'select_theme':
 					// put available theme list in database
 					$this->toolkit->rebuildThemes();
 					break;
 
 				case 'upgrade_modules':
 					// get installed modules from db and compare their versions to upgrade script
 					$modules = $this->GetUpgradableModules();
 					if (!$modules) {
 						$this->currentStep = $this->GetNextStep();
 					}
 					break;
 
 				case 'skin_upgrade':
 					if ($this->Application->RecallVar('SkinUpgradeLog') === false) {
 						// no errors during skin upgrade -> skip this step
 						$this->currentStep = $this->GetNextStep();
 					}
 					break;
 
 				case 'install_setup':
 					if ( $this->Application->TableFound(TABLE_PREFIX . 'UserSession', true) ) {
 						// update to 5.2.0 -> rename session table before using it
 						// don't rename any other table here, since their names could be used in upgrade script
 						$this->Conn->Query('RENAME TABLE ' . TABLE_PREFIX . 'UserSession TO ' . TABLE_PREFIX . 'UserSessions');
 						$this->Conn->Query('RENAME TABLE ' . TABLE_PREFIX . 'SessionData TO ' . TABLE_PREFIX . 'UserSessionData');
 					}
 
 					$next_preset = $this->Application->GetVar('next_preset');
 					if ($next_preset !== false) {
 						$user_helper = $this->Application->recallObject('UserHelper');
 						/* @var $user_helper UserHelper */
 
 						$username = $this->Application->GetVar('login');
 						$password = $this->Application->GetVar('password');
 
 						if ($username == 'root') {
 							// verify "root" user using configuration settings
 							$login_result = $user_helper->loginUser($username, $password);
 
 							if ($login_result != LoginResult::OK) {
 								$error_phrase = $login_result == LoginResult::NO_PERMISSION ? 'la_no_permissions' : 'la_invalid_password';
 								$this->errorMessage = $this->Application->Phrase($error_phrase) . '. If you don\'t know your username or password, contact Intechnic Support';
 							}
 						}
 						else {
 							// non "root" user -> verify using licensing server
 							$url_params = Array (
 								'login' => md5($username),
 								'password' => md5($password),
 								'action' => 'check',
 								'license_code' => base64_encode( $this->toolkit->getSystemConfig('Intechnic', 'LicenseCode') ),
 								'version' => '4.3.0',//$this->toolkit->GetMaxModuleVersion('core/'),
 								'domain' => base64_encode($_SERVER['HTTP_HOST']),
 							);
 
 							$curl_helper = $this->Application->recallObject('CurlHelper');
 							/* @var $curl_helper kCurlHelper */
 
 							$curl_helper->SetRequestData($url_params);
 							$file_data = $curl_helper->Send(GET_LICENSE_URL);
 
 							if ( !$curl_helper->isGoodResponseCode() ) {
 								$this->errorMessage = 'In-Portal servers temporarily unavailable. Please contact <a href="mailto:support@in-portal.com">In-Portal support</a> personnel directly.';
 							}
 							elseif (substr($file_data, 0, 5) == 'Error') {
 								$this->errorMessage = substr($file_data, 6) . ' If you don\'t know your username or password, contact Intechnic Support';
 							}
 
 							if ($this->errorMessage == '') {
 								$user_helper->loginUserById(USER_ROOT);
 							}
 						}
 
 						if ($this->errorMessage == '') {
 							// processed with redirect to selected step preset
 							if (!isset($this->steps[$next_preset])) {
 								$this->errorMessage = 'Preset "'.$next_preset.'" not yet implemented';
 							}
 							else {
 								$this->stepsPreset = $next_preset;
 							}
 						}
 					}
 					else {
 						// if preset was not choosen, then raise error
 						$this->errorMessage = 'Please select action to perform';
 					}
 					break;
 
 				case 'security':
 					// perform write check
 					if ($this->Application->GetVar('skip_security_check')) {
 						// administrator intensionally skips security checks
 						break;
 					}
 
 					$write_check = true;
 					$check_paths = Array ('/', '/index.php', $this->toolkit->defaultWritablePath . '/config.php', ADMIN_DIRECTORY . '/index.php');
 					foreach ($check_paths as $check_path) {
 						$path_check_status = $this->toolkit->checkWritePermissions(FULL_PATH . $check_path);
 
 						if (is_bool($path_check_status) && $path_check_status) {
 							$write_check = false;
 							break;
 						}
 					}
 
 					// script execute check
 					if (file_exists(WRITEABLE . '/install_check.php')) {
 						unlink(WRITEABLE . '/install_check.php');
 					}
 
 					$fp = fopen(WRITEABLE . '/install_check.php', 'w');
 					fwrite($fp, "<?php\n\techo 'OK';\n");
 					fclose($fp);
 
 					$curl_helper = $this->Application->recallObject('CurlHelper');
 					/* @var $curl_helper kCurlHelper */
 
 					$output = $curl_helper->Send($this->Application->BaseURL(WRITEBALE_BASE) . 'install_check.php');
 					unlink(WRITEABLE . '/install_check.php');
 					$execute_check = ($output !== 'OK');
 
 					$directive_check = true;
 					$ini_vars = Array ('register_globals' => false, 'open_basedir' => true, 'allow_url_fopen' => false);
 					foreach ($ini_vars as $var_name => $var_value) {
 						$current_value = ini_get($var_name);
 
 						if (($var_value && !$current_value) || (!$var_value && $current_value)) {
 							$directive_check = false;
 							break;
 						}
 					}
 
 					if (!$write_check || !$execute_check || !$directive_check) {
 						$this->errorMessage = true;
 					}
 					/*else {
 						$this->currentStep = $this->GetNextStep();
 					}*/
 					break;
 			}
 
 			$this->PerformValidation(); // returns validation status (just in case)
 		}
 
 		/**
 		 * Validates data entered by user
 		 *
 		 * @return bool
 		 */
 		function PerformValidation()
 		{
 			if ($this->GetVar('step') != $this->currentStep) {
 				// just redirect from previous step, don't validate
 				return true;
 			}
 
 			$status = true;
 
 			switch ($this->currentStep) {
 				case 'db_config':
 				case 'db_reconfig':
 					// 1. check if required fields are filled
 					$section_name = 'Database';
 					$required_fields = Array ('DBType', 'DBHost', 'DBName', 'DBUser', 'DBCollation');
 					foreach ($required_fields as $required_field) {
 						if (!$this->toolkit->getSystemConfig($section_name, $required_field)) {
 							$status = false;
 							$this->errorMessage = 'Please fill all required fields';
 							break;
 						}
 					}
 
 					if ( !$status ) {
 						break;
 					}
 
 					// 2. check permissions, that use have in this database
 					$status = $this->CheckDatabase(($this->currentStep == 'db_config') && !$this->GetVar('UseExistingSetup'));
 					break;
 
 				case 'select_license':
 					$license_source = $this->GetVar('license_source');
 					if ($license_source == 2) {
 						// license from file -> file must be uploaded
 						$upload_error = $_FILES['license_file']['error'];
 						if ($upload_error != UPLOAD_ERR_OK) {
 							$this->errorMessage = 'Missing License File';
 						}
 					}
 					elseif (!is_numeric($license_source)) {
 						$this->errorMessage = 'Please select license';
 					}
 
 					$status = $this->errorMessage == '';
 					break;
 
 				case 'root_password':
 					// check, that password & verify password match
 					$password = $this->Application->GetVar('root_password');
 					$password_verify = $this->Application->GetVar('root_password_verify');
 
 					if ($password != $password_verify) {
 						$this->errorMessage = 'Passwords does not match';
 					}
 					elseif (mb_strlen($password) < 4) {
 						$this->errorMessage = 'Root Password must be at least 4 characters';
 					}
 
 					$status = $this->errorMessage == '';
 					break;
 
 				case 'choose_modules':
 					break;
 
 				case 'upgrade_modules':
 					$modules = $this->Application->GetVar('modules');
 					if (!$modules) {
 						$modules = Array ();
 						$this->errorMessage = 'Please select module(-s) to ' . ($this->currentStep == 'choose_modules' ? 'install' : 'upgrade');
 					}
 
 					// check interface module
 					$upgrade_data = $this->GetUpgradableModules();
 
 					if (array_key_exists('core', $upgrade_data) && !in_array('core', $modules)) {
 						// core can be upgraded, but isn't selected
 						$this->errorMessage = 'Please select "Core" as interface module';
 					}
 
 					$status = $this->errorMessage == '';
 					break;
 			}
 
 			return $status;
 		}
 
 		/**
 		 * Perform installation step actions
 		 *
 		 */
 		function Run()
 		{
 			if ($this->errorMessage) {
 				// was error during data validation stage
 				return ;
 			}
 
 			switch ($this->currentStep) {
 				case 'db_config':
 				case 'db_reconfig':
 					// store db configuration
 					$sql = 'SHOW COLLATION
 							LIKE \''.$this->toolkit->getSystemConfig('Database', 'DBCollation').'\'';
 					$collation_info = $this->Conn->Query($sql);
 					if ($collation_info) {
 						$this->toolkit->setSystemConfig('Database', 'DBCharset', $collation_info[0]['Charset']);
 
 						// database is already connected, that's why set collation on the fly
 						$this->Conn->Query('SET NAMES \''.$this->toolkit->getSystemConfig('Database', 'DBCharset').'\' COLLATE \''.$this->toolkit->getSystemConfig('Database', 'DBCollation').'\'');
 					}
 
 					$this->toolkit->SaveConfig();
 
 					if ($this->currentStep == 'db_config') {
 						if ($this->GetVar('UseExistingSetup')) {
 							// abort clean install and redirect to already_installed
 							$this->stepsPreset = 'already_installed';
 							break;
 						}
 
 						// import base data into new database, not for db_reconfig
 						$this->toolkit->RunSQL('/core/install/install_schema.sql');
 						$this->toolkit->RunSQL('/core/install/install_data.sql');
 
 						// create category using sql, because Application is not available here
 						$table_name = $this->toolkit->getSystemConfig('Database', 'TablePrefix') . 'IdGenerator';
 						$this->Conn->Query('UPDATE ' . $table_name . ' SET lastid = lastid + 1');
 						$resource_id = $this->Conn->GetOne('SELECT lastid FROM ' . $table_name);
 						if ($resource_id === false) {
 							$this->Conn->Query('INSERT INTO '.$table_name.' (lastid) VALUES (2)');
 							$resource_id = 2;
 						}
 
 						// can't use USER_ROOT constant, since Application isn't available here
 						$fields_hash = Array (
 							'l1_Name' => 'Content', 'l1_MenuTitle' => 'Content', 'Filename' => 'Content',
 							'AutomaticFilename' => 0, 'CreatedById' => -1, 'CreatedOn' => time(),
 							'ResourceId' => $resource_id - 1, 'l1_Description' => 'Content', 'Status' => 4,
 						);
 
 						$this->Conn->doInsert($fields_hash, $this->toolkit->getSystemConfig('Database', 'TablePrefix') . 'Categories');
 
 						$this->toolkit->SetModuleRootCategory('Core', $this->Conn->getInsertID());
 
 						// set module "Core" version after install (based on upgrade scripts)
 						$this->toolkit->SetModuleVersion('Core', 'core/');
 
 						// for now we set "In-Portal" module version to "Core" module version (during clean install)
 						$this->toolkit->SetModuleVersion('In-Portal', 'core/');
 					}
 					break;
 
 				case 'select_license':
 					// reset memory cache, when application is first available (on fresh install and clean reinstall steps)
 					$this->Application->HandleEvent(new kEvent('adm:OnResetMemcache'));
 
 					$license_source = $this->GetVar('license_source');
 					switch ($license_source) {
 						case 1: // Download from Intechnic
 
 							break;
 
 						case 2: // Upload License File
 							$file_data = array_map('trim', file($_FILES['license_file']['tmp_name']));
 							if ((count($file_data) == 3) && $file_data[1]) {
 								$modules_helper = $this->Application->recallObject('ModulesHelper');
 								/* @var $modules_helper kModulesHelper */
 
 								if ($modules_helper->verifyLicense($file_data[1])) {
 									$this->toolkit->setSystemConfig('Intechnic', 'License', $file_data[1]);
 									$this->toolkit->setSystemConfig('Intechnic', 'LicenseCode', $file_data[2]);
 									$this->toolkit->SaveConfig();
 								}
 								else {
 									$this->errorMessage = 'Invalid License File';
 								}
 							}
 							else {
 								$this->errorMessage = 'Invalid License File';
 							}
 							break;
 
 						case 3: // Use Existing License
 							$license_hash = $this->toolkit->getSystemConfig('Intechnic', 'License');
 							if ($license_hash) {
 								$modules_helper = $this->Application->recallObject('ModulesHelper');
 								/* @var $modules_helper kModulesHelper */
 
 								if (!$modules_helper->verifyLicense($license_hash)) {
 									$this->errorMessage = 'Invalid or corrupt license detected';
 								}
 							}
 							else {
 								// happens, when browser's "Back" button is used
 								$this->errorMessage = 'Missing License File';
 							}
 							break;
 
 						case 4: // Skip License (Local Domain Installation)
 							if ($this->toolkit->sectionFound('Intechnic')) {
 								// remove any previous license information
 								$this->toolkit->setSystemConfig('Intechnic', 'License');
 								$this->toolkit->setSystemConfig('Intechnic', 'LicenseCode');
 								$this->toolkit->SaveConfig();
 							}
 							break;
 					}
 					break;
 
 				case 'download_license':
 					$license_login = $this->GetVar('login');
 					$license_password = $this->GetVar('password');
 					$license_id = $this->GetVar('licenses');
 
 					$curl_helper = $this->Application->recallObject('CurlHelper');
 					/* @var $curl_helper kCurlHelper */
 
 					if (strlen($license_login) && strlen($license_password) && !$license_id) {
 						// Here we determine weather login is ok & check available licenses
 						$url_params = Array (
 							'login' => md5($license_login),
 							'password' => md5($license_password),
 							'version' => $this->toolkit->GetMaxModuleVersion('core/'),
 							'domain' => base64_encode($_SERVER['HTTP_HOST']),
 						);
 
 						$curl_helper->SetRequestData($url_params);
 						$file_data = $curl_helper->Send(GET_LICENSE_URL);
 
 						if (!$file_data) {
 							// error connecting to licensing server
 							$this->errorMessage = 'Unable to connect to the Intechnic server! Please try again later!';
 						}
 						else {
 							if (substr($file_data, 0, 5) == 'Error') {
 								// after processing data server returned error
 								$this->errorMessage = substr($file_data, 6);
 							}
 							else {
 								// license received
 								if (substr($file_data, 0, 3) == 'SEL') {
 									// we have more, then one license -> let user choose
 									$this->SetVar('license_selection', base64_encode( substr($file_data, 4) )); // we received html with radio buttons with names "licenses"
 									$this->errorMessage = 'Please select which license to use';
 								}
 								else {
 									// we have one license
 									$this->toolkit->processLicense($file_data);
 								}
 							}
 						}
 					}
 					else if (!$license_id) {
 						// licenses were not queried AND user/password missing
 						$this->errorMessage = 'Incorrect Username or Password. If you don\'t know your username or password, contact Intechnic Support';
 					}
 					else {
 						// Here we download license
 						$url_params = Array (
 							'license_id' => md5($license_id),
 							'dlog' => md5($license_login),
 							'dpass' => md5($license_password),
 							'version' => $this->toolkit->GetMaxModuleVersion('core/'),
 							'domain' => base64_encode($_SERVER['HTTP_HOST']),
 						);
 
 						$curl_helper->SetRequestData($url_params);
 						$file_data = $curl_helper->Send(GET_LICENSE_URL);
 
 						if (!$file_data) {
 							// error connecting to licensing server
 							$this->errorMessage = 'Unable to connect to the Intechnic server! Please try again later!';
 						}
 						else {
 							if (substr($file_data, 0, 5) == 'Error') {
 								// after processing data server returned error
 								$this->errorMessage = substr($file_data, 6);
 							}
 							else {
 								$this->toolkit->processLicense($file_data);
 							}
 						}
 					}
 					break;
 
 				case 'select_domain':
 					$modules_helper = $this->Application->recallObject('ModulesHelper');
 					/* @var $modules_helper kModulesHelper */
 
 					// get domain name as entered by user on the form
 					$domain = $this->GetVar('domain') == 1 ? $_SERVER['HTTP_HOST'] : str_replace(' ', '', $this->GetVar('other'));
 
 					$license_hash = $this->toolkit->getSystemConfig('Intechnic', 'License');
 					if ($license_hash) {
 						// when license present, then extract domain from it
 						$license_hash = base64_decode($license_hash);
 						list ( , , $license_keys) = $modules_helper->_ParseLicense($license_hash);
 						$license_domain = $license_keys[0]['domain'];
 					}
 					else {
 						// when license missing, then use current domain or domain entered by user
 						$license_domain = $domain;
 					}
 
 					if ($domain != '') {
 						if (strstr($domain, $license_domain) || $modules_helper->_IsLocalSite($domain)) {
 							$this->toolkit->setSystemConfig('Misc', 'Domain', $domain);
 							$this->toolkit->SaveConfig();
 						}
 						else {
 							$this->errorMessage = 'Domain name entered does not match domain name in the license!';
 						}
 					}
 					else {
 						$this->errorMessage = 'Please enter valid domain!';
 					}
 					break;
 
 				case 'sys_config':
 					$config_data = $this->GetVar('system_config');
 
 					foreach ($config_data as $section => $section_vars) {
 						foreach ($section_vars as $var_name => $var_value) {
 							$this->toolkit->setSystemConfig($section, $var_name, $var_value);
 						}
 					}
 
 					$this->toolkit->SaveConfig();
 					break;
 
 				case 'root_password':
 					// update root password in database
 					$password_formatter = $this->Application->recallObject('kPasswordFormatter');
 					/* @var $password_formatter kPasswordFormatter */
 
 					$config_values = Array (
 						'RootPass' => $password_formatter->hashPassword($this->Application->GetVar('root_password')),
 						'Backup_Path' => FULL_PATH . $this->toolkit->getSystemConfig('Misc', 'WriteablePath') . DIRECTORY_SEPARATOR . 'backupdata',
 						'DefaultEmailSender' => 'portal@' . $this->toolkit->getSystemConfig('Misc', 'Domain')
 					);
 
 					$site_timezone = ini_get('date.timezone') ? ini_get('date.timezone') : getenv('TZ');
 
 					if ($site_timezone) {
 						$config_values['Config_Site_Time'] = $site_timezone;
 					}
 
 					$this->toolkit->saveConfigValues($config_values);
 
 					$user_helper = $this->Application->recallObject('UserHelper');
 					/* @var $user_helper UserHelper */
 
 					// login as "root", when no errors on password screen
 					$user_helper->loginUser('root', $this->Application->GetVar('root_password'));
 
 					// import base language for core (english)
 					$this->toolkit->ImportLanguage('/core/install/english');
 
 					// make sure imported language is set as active in session, created during installation
 					$this->Application->Session->SetField('Language', 1);
 
 					// set imported language as primary
 					$lang = $this->Application->recallObject('lang.-item', null, Array('skip_autoload' => true));
 					/* @var $lang LanguagesItem */
 
 					$lang->Load(1); // fresh install => ID=1
 					$lang->setPrimary(true); // for Front-End
 					break;
 
 				case 'choose_modules':
 					// run module install scripts
 					$modules = $this->Application->GetVar('modules');
 					if ($modules) {
 						foreach ($modules as $module) {
 							$install_file = MODULES_PATH.'/'.$module.'/install.php';
 							if (file_exists($install_file)) {
 								include_once($install_file);
 							}
 						}
 					}
 
 					// update category cache
 					$updater = $this->Application->makeClass('kPermCacheUpdater');
 					/* @var $updater kPermCacheUpdater */
 
 					$updater->OneStepRun();
 					break;
 
 				case 'post_config':
 					$this->toolkit->saveConfigValues( $this->GetVar('config') );
 					break;
 
 				case 'select_theme':
 					// 1. mark theme, that user is selected
 					$theme_id = $this->GetVar('theme');
-					$theme_table = $this->Application->getUnitOption('theme', 'TableName');
-					$theme_idfield = $this->Application->getUnitOption('theme', 'IDField');
+					$theme_config = $this->Application->getUnitConfig('theme');
+					$theme_table = $theme_config->getTableName();
+					$theme_id_field = $theme_config->getIDField();
 
 					$sql = 'UPDATE ' . $theme_table . '
 							SET Enabled = 1, PrimaryTheme = 1
-							WHERE ' . $theme_idfield . ' = ' . $theme_id;
+							WHERE ' . $theme_id_field . ' = ' . $theme_id;
 					$this->Conn->Query($sql);
 
 					$this->toolkit->rebuildThemes(); // rescan theme to create structure after theme is enabled !!!
 
 					// install theme dependent demo data
 					if ($this->Application->GetVar('install_demo_data')) {
 						$sql = 'SELECT Name
 								FROM ' . $theme_table . '
-								WHERE ' . $theme_idfield . ' = ' . $theme_id;
+								WHERE ' . $theme_id_field . ' = ' . $theme_id;
 						$theme_name = $this->Conn->GetOne($sql);
 						$site_path = $this->toolkit->getSystemConfig('Misc', 'WebsitePath') . '/';
 
 						$file_helper = $this->Application->recallObject('FileHelper');
 						/* @var $file_helper FileHelper */
 
 						foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 							if ($module_name == 'In-Portal') {
 								continue;
 							}
 
 							$template_path = '/themes' . '/' . $theme_name . '/' . $module_info['TemplatePath'];
 							$this->toolkit->RunSQL( $template_path . '_install/install_data.sql', Array('{ThemeId}', '{SitePath}'), Array($theme_id, $site_path) );
 
 							if ( file_exists(FULL_PATH . $template_path . '_install/images') ) {
 								// copy theme demo images into writable path accessible by FCKEditor
 								$file_helper->copyFolderRecursive(FULL_PATH . $template_path . '_install/images' . DIRECTORY_SEPARATOR, WRITEABLE . '/user_files/Images');
 							}
 						}
 					}
 					break;
 
 				case 'upgrade_modules':
 					// get installed modules from db and compare their versions to upgrade script
 					$modules = $this->Application->GetVar('modules');
 
 					if ($modules) {
 						$upgrade_data = $this->GetUpgradableModules();
 						$start_from_query = $this->Application->GetVar('start_from_query');
 						$this->upgradeDepencies = $this->getUpgradeDependencies($modules, $upgrade_data);
 
 						if ($start_from_query !== false) {
 							$this->upgradeLog = unserialize( $this->Application->RecallVar('UpgradeLog') );
 						}
 						else {
 							$start_from_query = 0;
 							$this->upgradeLog = Array ('ModuleVersions' => Array ());
 
 							// remember each module version, before upgrade scripts are executed
 							foreach ($modules as $module_name) {
 								$module_info = $upgrade_data[$module_name];
 								$this->upgradeLog['ModuleVersions'][$module_name] = $module_info['FromVersion'];
 							}
 
 							$this->Application->RemoveVar('UpgradeLog');
 						}
 
 						// 1. perform "php before", "sql", "php after" upgrades
 						foreach ($modules as $module_name) {
 							$module_info = $upgrade_data[$module_name];
 
 							/*echo '<h2>Upgrading "' . $module_info['Name'] . '" to "' . $module_info['ToVersion'] . '"</h2>' . "\n";
 							flush();*/
 
 							if (!$this->RunUpgrade($module_info['Name'], $module_info['ToVersion'], $upgrade_data, $start_from_query)) {
 								$this->Application->StoreVar('UpgradeLog', serialize($this->upgradeLog));
 								$this->Done();
 							}
 
 							// restore upgradable module version (makes sense after sql error processing)
 							$upgrade_data[$module_name]['FromVersion'] = $this->upgradeLog['ModuleVersions'][$module_name];
 						}
 
 						// 2. import language pack, perform "languagepack" upgrade for all upgraded versions
 						foreach ($modules as $module_name) {
 							$module_info = $upgrade_data[$module_name];
 							$sqls =& $this->getUpgradeQueriesFromVersion($module_info['Path'], $module_info['FromVersion']);
 							preg_match_all('/' . VERSION_MARK . '/s', $sqls, $regs);
 
 							// import module language pack
 							$this->toolkit->ImportLanguage('/' . $module_info['Path'] . 'install/english', true);
 
 							// perform advanced language pack upgrade
 							foreach ($regs[1] as $version) {
 								$this->RunUpgradeScript($module_info['Path'], $version, 'languagepack');
 							}
 						}
 
 						// 3. update all theme language packs
 						$themes_helper = $this->Application->recallObject('ThemesHelper');
 						/* @var $themes_helper kThemesHelper */
 
 						$themes_helper->synchronizeModule(false);
 
 						// 4. upgrade admin skin
 						if (in_array('core', $modules)) {
 							$skin_upgrade_log = $this->toolkit->upgradeSkin($upgrade_data['core']);
 
 							if ($skin_upgrade_log === true) {
 								$this->Application->RemoveVar('SkinUpgradeLog');
 							}
 							else {
 								$this->Application->StoreVar('SkinUpgradeLog', serialize($skin_upgrade_log));
 							}
 
 							// for now we set "In-Portal" module version to "Core" module version (during upgrade)
 							$this->toolkit->SetModuleVersion('In-Portal', false, $upgrade_data['core']['ToVersion']);
 						}
 					}
 					break;
 
 				case 'finish':
 					// delete cache
 					$this->toolkit->deleteCache();
 					$this->toolkit->rebuildThemes();
 
 					// compile admin skin, so it will be available in 3 frames at once
 					$skin_helper = $this->Application->recallObject('SkinHelper');
 					/* @var $skin_helper SkinHelper */
 
 					$skin = $this->Application->recallObject('skin', null, Array ('skip_autoload' => true));
 					/* @var $skin kDBItem */
 
 					$skin->Load(1, 'IsPrimary');
 					$skin_helper->compile($skin);
 
 					// set installation finished mark
 					if ($this->Application->ConfigValue('InstallFinished') === false) {
 						$fields_hash = Array (
 							'VariableName'	=>	'InstallFinished',
 							'VariableValue'	=>	1,
 						);
 						$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SystemSettings');
 					}
 
 					$random_string = $this->Application->ConfigValue('RandomString');
 
 					if ( !$random_string ) {
 						$user_helper = $this->Application->recallObject('UserHelper');
 						/* @var $user_helper UserHelper */
 
 						$random_string = $user_helper->generateRandomString(64, true, true);
 						$this->Application->SetConfigValue('RandomString', $random_string);
 					}
 
 					break;
 			}
 
 			if ($this->errorMessage) {
 				// was error during run stage
 				return ;
 			}
 
 			$this->currentStep = $this->GetNextStep();
 			$this->InitStep(); // init next step (that will be shown now)
 
 			$this->InitApplication();
 
 			if ($this->currentStep == -1) {
 				// step after last step -> redirect to admin
                 $user_helper = $this->Application->recallObject('UserHelper');
                 /* @var $user_helper UserHelper */
 
                 $user_helper->logoutUser();
 
 				$this->Application->Redirect($user_helper->event->redirect, $user_helper->event->getRedirectParams(), '', 'index.php');
 			}
 		}
 
 		function getUpgradeDependencies($modules, &$upgrade_data)
 		{
 			$dependencies = Array ();
 
 			foreach ($modules as $module_name) {
 				$module_info = $upgrade_data[$module_name];
 				$upgrade_object =& $this->getUpgradeObject($module_info['Path']);
 
 				if (!is_object($upgrade_object)) {
 					continue;
 				}
 
 				foreach ($upgrade_object->dependencies as $dependent_version => $version_dependencies) {
 					if (!$version_dependencies) {
 						// module is independent -> skip
 						continue;
 					}
 
 					list ($parent_name, $parent_version) = each($version_dependencies);
 
 					if (!array_key_exists($parent_name, $dependencies)) {
 						// parent module
 						$dependencies[$parent_name] = Array ();
 					}
 
 					if (!array_key_exists($parent_version, $dependencies[$parent_name])) {
 						// parent module versions, that are required by other module versions
 						$dependencies[$parent_name][$parent_version] = Array ();
 					}
 
 					$dependencies[$parent_name][$parent_version][] = Array ($module_info['Name'] => $dependent_version);
 				}
 			}
 
 			return $dependencies;
 		}
 
 		/**
 		 * Returns database queries, that should be executed to perform upgrade from given to lastest version of given module path
 		 *
 		 * @param string $module_path
 		 * @param string $from_version
 		 * @return string
 		 */
 		function &getUpgradeQueriesFromVersion($module_path, $from_version)
 		{
 			$upgrades_file = sprintf(UPGRADES_FILE, $module_path, 'sql');
 
 			$sqls = file_get_contents($upgrades_file);
 			$version_mark = preg_replace('/(\(.*?\))/', $from_version, VERSION_MARK);
 
 			// get only sqls from next (relative to current) version to end of file
 			$start_pos = strpos($sqls, $version_mark);
 			$sqls = substr($sqls, $start_pos);
 
 			return $sqls;
 		}
 
 		function RunUpgrade($module_name, $to_version, &$upgrade_data, &$start_from_query)
 		{
 			$module_info = $upgrade_data[ strtolower($module_name) ];
 
 			$sqls =& $this->getUpgradeQueriesFromVersion($module_info['Path'], $module_info['FromVersion']);
 			preg_match_all('/(' . VERSION_MARK . ')/s', $sqls, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
 
 			foreach ($matches as $index => $match) {
 				// upgrade version
 				$version = $match[2][0];
 
 				if ($this->toolkit->ConvertModuleVersion($version) > $this->toolkit->ConvertModuleVersion($to_version)) {
 					// only upgrade to $to_version, not further
 					break;
 				}
 
 				if (!in_array($module_name . ':' . $version, $this->upgradeLog)) {
 					if ($this->Application->isDebugMode()) {
 						$this->Application->Debugger->appendHTML('Upgrading "' . $module_name . '" to "' . $version . '" version: BEGIN.');
 					}
 
 					/*echo 'Upgrading "' . $module_name . '" to "' . $version . '".<br/>' . "\n";
 					flush();*/
 
 					// don't upgrade same version twice
 					$start_pos = $match[0][1] + strlen($match[0][0]);
 					$end_pos = array_key_exists($index + 1, $matches) ? $matches[$index + 1][0][1] : strlen($sqls);
 					$version_sqls = substr($sqls, $start_pos, $end_pos - $start_pos);
 
 					if ($start_from_query == 0) {
 						$this->RunUpgradeScript($module_info['Path'], $version, 'before');
 					}
 
 					if (!$this->toolkit->RunSQLText($version_sqls, null, null, $start_from_query)) {
 						$this->errorMessage .= '<input type="hidden" name="start_from_query" value="' . $this->LastQueryNum . '">';
 						$this->errorMessage .= '<br/>Module "' . $module_name . '" upgrade to "' . $version . '" failed.';
 						$this->errorMessage .= '<br/>Click Continue button below to skip this query and go further<br/>';
 
 						return false;
 					}
 					else {
 						// reset query counter, when all queries were processed
 						$start_from_query = 0;
 					}
 
 					$this->RunUpgradeScript($module_info['Path'], $version, 'after');
 
 					if ($this->Application->isDebugMode()) {
 						$this->Application->Debugger->appendHTML('Upgrading "' . $module_name . '" to "' . $version . '" version: END.');
 					}
 
 					// remember, that we've already upgraded given version
 					$this->upgradeLog[] = $module_name . ':' . $version;
 				}
 
 				if (array_key_exists($module_name, $this->upgradeDepencies) && array_key_exists($version, $this->upgradeDepencies[$module_name])) {
 					foreach ($this->upgradeDepencies[$module_name][$version] as $dependency_info) {
 						list ($dependent_module, $dependent_version) = each($dependency_info);
 
 						if (!$this->RunUpgrade($dependent_module, $dependent_version, $upgrade_data, $start_from_query)) {
 							return false;
 						}
 					}
 				}
 
 				// only mark module as updated, when all it's dependent modules are upgraded
 				$this->toolkit->SetModuleVersion($module_name, false, $version);
 			}
 
 			return true;
 		}
 
 		/**
 		 * Run upgrade PHP scripts for module with specified path
 		 *
 		 * @param string $module_path
 		 * @param Array $version
 		 * @param string $mode upgrade mode = {before,after,languagepack}
 		 */
 		function RunUpgradeScript($module_path, $version, $mode)
 		{
 			$upgrade_object =& $this->getUpgradeObject($module_path);
 
 			if (!is_object($upgrade_object)) {
 				return ;
 			}
 
 			$upgrade_method = 'Upgrade_' . str_replace(Array ('.', '-'), '_', $version);
 
 			if (method_exists($upgrade_object, $upgrade_method)) {
 				$upgrade_object->$upgrade_method($mode);
 			}
 		}
 
 		/**
 		 * Returns upgrade class for given module path
 		 *
 		 * @param string $module_path
 		 * @return kUpgradeHelper
 		 */
 		function &getUpgradeObject($module_path)
 		{
 			static $upgrade_classes = Array ();
 			$upgrades_file = sprintf(UPGRADES_FILE, $module_path, 'php');
 
 			if (!file_exists($upgrades_file)) {
 				$false = false;
 				return $false;
 			}
 
 			if (!isset($upgrade_classes[$module_path])) {
 				require_once(FULL_PATH . REL_PATH . '/install/upgrade_helper.php');
 
 				// save class name, because 2nd time (in after call)
 				// $upgrade_class variable will not be present
 				include_once $upgrades_file;
 				$upgrade_classes[$module_path] = $upgrade_class;
 			}
 
 			$upgrade_object = new $upgrade_classes[$module_path]();
 			/* @var $upgrade_object CoreUpgrades */
 
 			$upgrade_object->setToolkit($this->toolkit);
 
 			return $upgrade_object;
 		}
 
 		/**
 		 * Initialize kApplication
 		 *
 		 * @param bool $force initialize in any case
 		 */
 		function InitApplication($force = false)
 		{
 			if (($force || !in_array($this->currentStep, $this->skipApplicationSteps)) && !isset($this->Application)) {
 				// step is allowed for application usage & it was not initialized in previous step
 				global $start, $debugger, $dbg_options;
 				include_once(FULL_PATH.'/core/kernel/startup.php');
 
 				$this->Application =& kApplication::Instance();
 				$this->toolkit->Application =& kApplication::Instance();
 
 				$this->includeModuleConstants();
 				$this->Application->Init();
 
 				$this->Conn =& $this->Application->GetADODBConnection();
 				$this->toolkit->Conn =& $this->Application->GetADODBConnection();
 			}
 		}
 
 		/**
 		 * When no modules installed, then pre-include all modules contants, since they are used in unit configs
 		 *
 		 */
 		function includeModuleConstants()
 		{
 			$modules = $this->ScanModules();
 
 			foreach ($modules as $module_path) {
 				$constants_file = MODULES_PATH . '/' . $module_path . '/constants.php';
 
 				if ( file_exists($constants_file) ) {
 					kUtil::includeOnce($constants_file);
 				}
 			}
 		}
 
 		/**
 		 * Show next step screen
 		 *
 		 * @param string $error_message
 		 * @return void
 		 */
 		function Done($error_message = null)
 		{
 			if ( isset($error_message) ) {
 				$this->errorMessage = $error_message;
 			}
 
 			include_once (FULL_PATH . '/' . REL_PATH . '/install/incs/install.tpl');
 
 			if ( isset($this->Application) ) {
 				$this->Application->Done();
 			}
 
 			exit;
 		}
 
 		function ConnectToDatabase()
 		{
 			include_once FULL_PATH . '/core/kernel/db/db_connection.php';
 
 			$required_keys = Array ('DBType', 'DBUser', 'DBName');
 			foreach ($required_keys as $required_key) {
 				if (!$this->toolkit->getSystemConfig('Database', $required_key)) {
 					// one of required db connection settings missing -> abort connection
 					return false;
 				}
 			}
 
 			$this->Conn = new kDBConnection($this->toolkit->getSystemConfig('Database', 'DBType'), Array(&$this, 'DBErrorHandler'));
 			$this->Conn->setup( $this->toolkit->systemConfig );
 
 			// setup toolkit too
 			$this->toolkit->Conn =& $this->Conn;
 
 			return !$this->Conn->hasError();
 		}
 
 		/**
 		 * Checks if core is already installed
 		 *
 		 * @return bool
 		 */
 		function AlreadyInstalled()
 		{
 			$table_prefix = $this->toolkit->getSystemConfig('Database', 'TablePrefix');
 			$settings_table = $this->TableExists('ConfigurationValues') ? 'ConfigurationValues' : 'SystemSettings';
 
 			$sql = 'SELECT VariableValue
 					FROM ' . $table_prefix . $settings_table . '
 					WHERE VariableName = "InstallFinished"';
 
 			return $this->TableExists($settings_table) && $this->Conn->GetOne($sql);
 		}
 
 		function CheckDatabase($check_installed = true)
 		{
 			// perform various check type to database specified
 			// 1. user is allowed to connect to database
 			// 2. user has all types of permissions in database
 			// 3. database environment settings met minimum requirements
 
 			if (mb_strlen($this->toolkit->getSystemConfig('Database', 'TablePrefix')) > 7) {
 				$this->errorMessage = 'Table prefix should not be longer than 7 characters';
 				return false;
 			}
 
 			// connect to database
 			$status = $this->ConnectToDatabase();
 			if ($status) {
 				// if connected, then check if all sql statements work
 				$sql_tests[] = 'DROP TABLE IF EXISTS test_table';
 				$sql_tests[] = 'CREATE TABLE test_table(test_col mediumint(6))';
 				$sql_tests[] = 'LOCK TABLES test_table WRITE';
 				$sql_tests[] = 'INSERT INTO test_table(test_col) VALUES (5)';
 				$sql_tests[] = 'UPDATE test_table SET test_col = 12';
 				$sql_tests[] = 'UNLOCK TABLES';
 				$sql_tests[] = 'ALTER TABLE test_table ADD COLUMN new_col varchar(10)';
 				$sql_tests[] = 'SELECT * FROM test_table';
 				$sql_tests[] = 'DELETE FROM test_table';
 				$sql_tests[] = 'DROP TABLE IF EXISTS test_table';
 
 				foreach ($sql_tests as $sql_test) {
 					$this->Conn->Query($sql_test);
 					if ($this->Conn->getErrorCode() != 0) {
 						$status = false;
 						break;
 					}
 				}
 
 				if ($status) {
 					// if statements work & connection made, then check table existance
 					if ($check_installed && $this->AlreadyInstalled()) {
 						$this->errorMessage = 'An In-Portal Database already exists at this location';
 						return false;
 					}
 
 					$requirements_error = Array ();
 					$db_check_results = $this->toolkit->CallPrerequisitesMethod('core/', 'CheckDBRequirements');
 
 					if ( !$db_check_results['version'] ) {
 						$requirements_error[] = '- MySQL Version is below 5.0';
 					}
 
 					if ( !$db_check_results['packet_size'] ) {
 						$requirements_error[] = '- MySQL Packet Size is below 1 MB';
 					}
 
 					if ( $requirements_error ) {
 						$this->errorMessage = 'Connection successful, but following system requirements were not met:<br/>' . implode('<br/>', $requirements_error);
 						return false;
 					}
 				}
 				else {
 					// user has insufficient permissions in database specified
 					$this->errorMessage = 'Permission Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg();
 					return false;
 				}
 			}
 			else {
 				// was error while connecting
 				if (!$this->Conn) return false;
 				$this->errorMessage = 'Connection Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg();
 				return false;
 			}
 
 			return true;
 		}
 
 		/**
 		 * Checks if all passed tables exists
 		 *
 		 * @param string $tables comma separated tables list
 		 * @return bool
 		 */
 		function TableExists($tables)
 		{
 			$prefix = $this->toolkit->getSystemConfig('Database', 'TablePrefix');
 
 			$all_found = true;
 			$tables = explode(',', $tables);
 			foreach ($tables as $table_name) {
 				$sql = 'SHOW TABLES LIKE "'.$prefix.$table_name.'"';
 				if (count($this->Conn->Query($sql)) == 0) {
 					$all_found = false;
 					break;
 				}
 			}
 
 			return $all_found;
 		}
 
 		/**
 		 * Returns modules list found in modules folder
 		 *
 		 * @return Array
 		 */
 		function ScanModules()
 		{
 			static $modules = null;
 
 			if ( !isset($modules) ) {
 				// use direct include, because it's called before kApplication::Init, that creates class factory
 				kUtil::includeOnce( KERNEL_PATH . kApplication::MODULE_HELPER_PATH );
 
 				$modules_helper = new kModulesHelper();
 				$modules = $modules_helper->getModules();
 			}
 
 			return $modules;
 		}
 
 		/**
 		 * Virtually place module under "modules" folder or it won't be recognized during upgrade to 5.1.0 version
 		 *
 		 * @param string $name
 		 * @param string $path
 		 * @param string $version
 		 * @return string
 		 */
 		function getModulePath($name, $path, $version)
 		{
 			if ($name == 'Core') {
 				// don't transform path for Core module
 				return $path;
 			}
 
 			if (!preg_match('/^modules\//', $path)) {
 				// upgrade from 5.0.x/1.0.x to 5.1.x/1.1.x
 				return 'modules/' . $path;
 			}
 
 			return $path;
 		}
 
 		/**
 		 * Returns list of modules, that can be upgraded
 		 *
 		 */
 		function GetUpgradableModules()
 		{
 			$ret = Array ();
 
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				if ($module_name == 'In-Portal') {
 					// don't show In-Portal, because it shares upgrade scripts with Core module
 					continue;
 				}
 
 				$module_info['Path'] = $this->getModulePath($module_name, $module_info['Path'], $module_info['Version']);
 				$upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'sql');
 
 				if (!file_exists($upgrades_file)) {
 					// no upgrade file
 					continue;
 				}
 
 				$sqls = file_get_contents($upgrades_file);
 				$versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs);
 				if (!$versions_found) {
 					// upgrades file doesn't contain version definitions
 					continue;
 				}
 
 				$to_version = end($regs[1]);
 				$this_version = $this->toolkit->ConvertModuleVersion($module_info['Version']);
 				if ($this->toolkit->ConvertModuleVersion($to_version) > $this_version) {
 					// destination version is greather then current
 					foreach ($regs[1] as $version) {
 						if ($this->toolkit->ConvertModuleVersion($version) > $this_version) {
 							$from_version = $version;
 							break;
 						}
 					}
 
 					$version_info = Array (
 						'FromVersion'	=>	$from_version,
 						'ToVersion'		=>	$to_version,
 					);
 
 					$ret[ strtolower($module_name) ] = array_merge($module_info, $version_info);
 				}
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns content to show for current step
 		 *
 		 * @return string
 		 */
 		function GetStepBody()
 		{
 			$step_template = FULL_PATH.'/core/install/step_templates/'.$this->currentStep.'.tpl';
 			if (file_exists($step_template)) {
 				ob_start();
 				include_once ($step_template);
 				return ob_get_clean();
 			}
 
 			return '{step template "'.$this->currentStep.'" missing}';
 		}
 
 		/**
 		 * Parses step information file, cache result for current step ONLY & return it
 		 *
 		 * @return Array
 		 */
 		function &_getStepInfo()
 		{
 			static $info = Array('help_title' => null, 'step_title' => null, 'help_body' => null, 'queried' => false);
 
 			if (!$info['queried']) {
 				$fdata = file_get_contents($this->StepDBFile);
 
 				$parser = xml_parser_create();
 				xml_parse_into_struct($parser, $fdata, $values, $index);
 				xml_parser_free($parser);
 
 				foreach ($index['STEP'] as $section_index) {
 					$step_data =& $values[$section_index];
 
 					if ($step_data['attributes']['NAME'] == $this->currentStep) {
 						$info['step_title'] = $step_data['attributes']['TITLE'];
 						if (isset($step_data['attributes']['HELP_TITLE'])) {
 							$info['help_title'] = $step_data['attributes']['HELP_TITLE'];
 						}
 						else {
 							// if help title not set, then use step title
 							$info['help_title'] = $step_data['attributes']['TITLE'];
 						}
 						$info['help_body'] = trim($step_data['value']);
 						break;
 					}
 				}
 
 				$info['queried'] = true;
 			}
 
 			return $info;
 		}
 
 		/**
 		 * Returns particular information abou current step
 		 *
 		 * @param string $info_type
 		 * @return string
 		 */
 		function GetStepInfo($info_type)
 		{
 			$step_info =& $this->_getStepInfo();
 
 			if (isset($step_info[$info_type])) {
 				return $step_info[$info_type];
 			}
 
 			return '{step "'.$this->currentStep.'"; param "'.$info_type.'" missing}';
 		}
 
 		/**
 		 * Returns passed steps titles
 		 *
 		 * @param Array $steps
 		 * @return Array
 		 * @see kInstaller:PrintSteps
 		 */
 		function _getStepTitles($steps)
 		{
 			$fdata = file_get_contents($this->StepDBFile);
 
 			$parser = xml_parser_create();
 			xml_parse_into_struct($parser, $fdata, $values, $index);
 			xml_parser_free($parser);
 
 			$ret = Array ();
 			foreach ($index['STEP'] as $section_index) {
 				$step_data =& $values[$section_index];
 				if (in_array($step_data['attributes']['NAME'], $steps)) {
 					$ret[ $step_data['attributes']['NAME'] ] = $step_data['attributes']['TITLE'];
 				}
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns current step number in active steps_preset.
 		 * Value can't be cached, because same step can have different number in different presets
 		 *
 		 * @return int
 		 */
 		function GetStepNumber()
 		{
 			return array_search($this->currentStep, $this->steps[$this->stepsPreset]) + 1;
 		}
 
 		/**
 		 * Returns step name to process next
 		 *
 		 * @return string
 		 */
 		function GetNextStep()
 		{
 			$next_index = $this->GetStepNumber();
 			if ($next_index > count($this->steps[$this->stepsPreset]) - 1) {
 				return -1;
 			}
 
 			return $this->steps[$this->stepsPreset][$next_index];
 		}
 
 		/**
 		 * Returns step name, that was processed before this step
 		 *
 		 * @return string
 		 */
 		function GetPreviousStep()
 		{
 			$next_index = $this->GetStepNumber() - 1;
 			if ($next_index < 0) {
 				$next_index = 0;
 			}
 
 			return $this->steps[$this->stepsPreset][$next_index];
 		}
 
 		/**
 		 * Prints all steps from active steps preset and highlights current step
 		 *
 		 * @param string $active_tpl
 		 * @param string $passive_tpl
 		 * @return string
 		 */
 		function PrintSteps($active_tpl, $passive_tpl)
 		{
 			$ret = '';
 			$step_titles = $this->_getStepTitles($this->steps[$this->stepsPreset]);
 
 			foreach ($this->steps[$this->stepsPreset] as $step_name) {
 				$template = $step_name == $this->currentStep ? $active_tpl : $passive_tpl;
 				$ret .= sprintf($template, $step_titles[$step_name]);
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Installation error handler for sql errors
 		 *
 		 * @param int $code
 		 * @param string $msg
 		 * @param string $sql
 		 * @return bool
 		 * @access private
 		 */
 		function DBErrorHandler($code, $msg, $sql)
 		{
 			$this->errorMessage = 'Query: <br />'.htmlspecialchars($sql, null, 'UTF-8').'<br />execution result is error:<br />['.$code.'] '.$msg;
 			return true;
 		}
 
 		/**
 		 * Installation error handler
 		 *
 		 * @param int $errno
 		 * @param string $errstr
 		 * @param string $errfile
 		 * @param int $errline
 		 * @param Array|string $errcontext
 		 */
 		function ErrorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = '')
 		{
 			if ($errno == E_USER_ERROR) {
 				// only react on user fatal errors
 				$this->Done($errstr);
 			}
 		}
 
 		/**
 		 * Checks, that given button should be visible on current installation step
 		 *
 		 * @param string $name
 		 * @return bool
 		 */
 		function buttonVisible($name)
 		{
 			$button_visibility = Array (
 				'continue' => $this->GetNextStep() != -1 || ($this->stepsPreset == 'already_installed'),
 				'refresh' => in_array($this->currentStep, Array ('sys_requirements', 'check_paths', 'security')),
 				'back' => in_array($this->currentStep, Array (/*'select_license',*/ 'download_license', 'select_domain')),
 			);
 
 			if ($name == 'any') {
 				foreach ($button_visibility as $button_name => $button_visible) {
 					if ($button_visible) {
 						return true;
 					}
 				}
 
 				return false;
 			}
 
 			return array_key_exists($name, $button_visibility) ? $button_visibility[$name] : true;
 		}
 	}