Index: branches/5.2.x/core/kernel/event_handler.php
===================================================================
--- branches/5.2.x/core/kernel/event_handler.php	(revision 14674)
+++ branches/5.2.x/core/kernel/event_handler.php	(revision 14675)
@@ -1,191 +1,191 @@
 <?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 adressing 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 corrent Prefix_Special
 	 *	 	for variables beeing 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
 		 *
 		 * @see kEventHandler::$eventMethods
 		 * @access protected
 		 */
 		protected function mapEvents()
 		{
 
 		}
 
 		/**
-		 * Allows to override standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 * @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
 		 * @access public
 		 */
 		public function processEvent(&$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) ) {
 				$this->$event_name($event);
 			}
 			else {
 				throw new Exception('Event <strong>' . $event->Name . '</strong> not implemented in class <strong>' . get_class($this) . '</strong>');
 			}
 		}
 
 		/**
 		 * Sample dummy event
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function OnBuild(&$event)
 		{
 
 		}
 
 		/**
 		 * Returns to previous template in opener stack
 		 *
 		 * @param kEvent $event
 		 * @access protected
 		 */
 		protected function OnGoBack(&$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, &$event)
 		{
 
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(&$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
 		 */
 		protected function OnAfterConfigRead(&$event)
 		{
 
 		}
 
 	}
\ No newline at end of file
Index: branches/5.2.x/core/kernel/nparser/nparser.php
===================================================================
--- branches/5.2.x/core/kernel/nparser/nparser.php	(revision 14674)
+++ branches/5.2.x/core/kernel/nparser/nparser.php	(revision 14675)
@@ -1,1202 +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!');
 
 include_once(KERNEL_PATH.'/nparser/ntags.php');
 
 define('TAG_NAMESPACE', 'inp2:');
 define('TAG_NAMESPACE_LENGTH', 5);
 
 class NParser extends kBase {
 
 	var $Stack = Array ();
 	var $Level = 0;
 
 	var $Buffers = array();
 	var $InsideComment = false;
 
 	/**
 	 * Parse tags inside HTML comments
 	 *
 	 * @var bool
 	 */
 	var $SkipComments = true;
 
 	var $Params = array();
 	var $ParamsStack = array();
 	var $ParamsLevel = 0;
 
 	var $Definitions = '';
 
 	/**
 	 * Holds dynamic elements to function names mapping during execution
 	 *
 	 * @var Array
 	 */
 	var $Elements = Array ();
 
 	/**
 	 * Holds location of element definitions inside templates.
 	 * key - element function name, value - array of 2 keys: {from_pos, to_pos}
 	 *
 	 * @var Array
 	 */
 	var $ElementLocations = Array ();
 
 	var $DataExists = false;
 
 	var $TemplateName = null;
 	var $TempalteFullPath = null;
 
 	var $CachePointers = Array ();
 	var $Cachable = Array ();
 
 	/**
 	 * Deep level during parsing
-	 * 
+	 *
 	 * @var int
 	 */
 	var $CacheLevel = 0;
 
 	/**
 	 * Caching in templates enabled
 	 *
 	 * @var bool
 	 */
 	var $CachingEnabled = false;
 
 	/**
 	 * Completely cache given page
 	 *
 	 * @var bool
 	 */
 	var $FullCachePage = false;
 
 	/**
 	 * Prefixes, that are used on current page
 	 *
 	 * @var Array
 	 */
 	var $PrefixesInUse = Array ();
 
 	/**
 	 * Parser parameter names, that are created via m_Capture tag are listed here
 	 *
 	 * @var Array
 	 */
 	var $Captures = array();
 
 	/**
 	 * Phrases, used on "Edit" buttons, that parser adds during block decoration
 	 *
 	 * @var Array
 	 */
 	var $_btnPhrases = Array ();
 
 	/**
 	 * Mod-rewrite system enabled
 	 *
 	 * @var bool
 	 */
 	var $RewriteUrls = false;
 
 	/**
 	 * Current user is logged-in
 	 *
 	 * @var bool
 	 */
 	var $UserLoggedIn = false;
 
 	/**
 	 * Creates template parser object
 	 *
 	 * @access public
 	 */
 	public function __construct()
 	{
 		parent::__construct();
 
 		if (defined('EDITING_MODE') && (EDITING_MODE == EDITING_MODE_DESIGN)) {
 			$this->_btnPhrases['design'] = $this->Application->Phrase('la_btn_EditDesign', false, true);
 			$this->_btnPhrases['block'] = $this->Application->Phrase('la_btn_EditBlock', false, true);
 		}
 
 		$this->RewriteUrls = $this->Application->RewriteURLs();
 		$this->UserLoggedIn = $this->Application->LoggedIn();
 
 		// cache only Front-End templated, when memory caching is available and template caching is enabled in configuration
 		$this->CachingEnabled = !$this->Application->isAdmin && $this->Application->ConfigValue('SystemTagCache') && $this->Application->isCachingType(CACHING_TYPE_MEMORY);
 	}
 
 	function Compile($pre_parsed, $template_name = 'unknown')
 	{
 		$data = file_get_contents($pre_parsed['tname']);
 
 		if (!$this->CompileRaw($data, $pre_parsed['tname'], $template_name)) {
 			// compilation failed during errors in template
 //			trigger_error('Template "<strong>' . $template_name . '</strong>" not compiled because of errors', E_USER_WARNING);
 			return false;
 		}
 
 		// saving compiled version (only when compilation was successful)
 		$this->Application->TemplatesCache->saveTemplate($pre_parsed['fname'], $this->Buffers[0]);
 
 		return true;
 	}
 
 	function Parse($raw_template, $name = null)
 	{
 		$this->CompileRaw($raw_template, $name);
 		ob_start();
 		$_parser =& $this;
 		eval('?'.'>'.$this->Buffers[0]);
 
 		return ob_get_clean();
 	}
 
 	function CompileRaw($data, $t_name, $template_name = 'unknown')
 	{
 		$code = "extract (\$_parser->Params);\n";
 		$code .= "\$_parser->ElementLocations['{$template_name}'] = Array('template' => '{$template_name}', 'start_pos' => 0, 'end_pos' => " . strlen($data) . ");\n";
 
 //		$code .= "__@@__DefinitionsMarker__@@__\n";
 
 //		$code .= "if (!\$this->CacheStart('".abs(crc32($t_name))."_0')) {\n";
 		$this->Buffers[0] = '<?'."php $code ?>\n";
 		$this->Cacheable[0] = true;
 		$this->Definitions = '';
 
 		// finding all the tags
 		$reg = '(.*?)(<[\\/]?)' . TAG_NAMESPACE . '([^>]*?)([\\/]?>)(\r\n){0,1}';
 		preg_match_all('/'.$reg.'/s', $data, $results, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
 
 		$this->InsideComment = false;
 		foreach ($results as $tag_data) {
 			$tag = array(
 				'opening' => $tag_data[2][0],
 				'tag' => $tag_data[3][0],
 				'closing' => $tag_data[4][0],
 				'line' => substr_count(substr($data, 0, $tag_data[2][1]), "\n")+1,
 				'pos' => $tag_data[2][1],
 				'file' => $t_name,
 				'template' => $template_name,
 			);
 
 			// the idea is to count number of comment openings and closings before current tag
 			// if the numbers do not match we inverse the status of InsideComment
 			if ($this->SkipComments && (substr_count($tag_data[1][0], '<!--') != substr_count($tag_data[1][0], '-->'))) {
 				$this->InsideComment = !$this->InsideComment;
 			}
 
 			// appending any text/html data found before tag
 			$this->Buffers[$this->Level] .= $tag_data[1][0];
 			if (!$this->InsideComment) {
 				$tmp_tag = $this->Application->CurrentNTag;
 				$this->Application->CurrentNTag = $tag;
 				if ($this->ProcessTag($tag) === false) {
 					$this->Application->CurrentNTag = $tmp_tag;
 					return false;
 				}
 				$this->Application->CurrentNTag = $tmp_tag;
 			}
 			else {
 				$this->Buffers[$this->Level] .= $tag_data[2][0].$tag_data[3][0].$tag_data[4][0];
 			}
 		}
 
 		if ($this->Level > 0) {
 			$error_tag = Array (
 				'file' => $this->Stack[$this->Level]->Tag['file'],
 				'line' => $this->Stack[$this->Level]->Tag['line'],
 			);
 
 			throw new ParserException('Unclosed tag opened by ' . $this->TagInfo($this->Stack[$this->Level]->Tag), 0, null, $error_tag);
 
 			return false;
 		}
 
 		// appending text data after last tag (after its closing pos),
 		// if no tag was found at all ($tag_data is not set) - append the whole $data
 		$this->Buffers[$this->Level] .= isset($tag_data) ? substr($data, $tag_data[4][1]+strlen($tag_data[4][0])) : $data;
 		$this->Buffers[$this->Level] = preg_replace('/<!--##(.*?)##-->/s', '', $this->Buffers[$this->Level]); // remove hidden comments IB#23065
 //		$this->Buffers[$this->Level] .= '<?'.'php '."\n\$_parser->CacheEnd();\n}\n"." ?".">\n";
 //		$this->Buffers[$this->Level] = str_replace('__@@__DefinitionsMarker__@@__', $this->Definitions, $this->Buffers[$this->Level]);
 
 		return true;
 	}
 
 	function SplitParamsStr($params_str)
 	{
 		preg_match_all('/([\${}a-zA-Z0-9_.\\-\\\\#\\[\\]]+)=(["\']{1,1})(.*?)(?<!\\\)\\2/s', $params_str, $rets, PREG_SET_ORDER);
 
 		$values = Array();
 
 		// we need to replace all occurences of any current param $key with {$key} for correct variable substitution
 		foreach ($rets AS $key => $val){
 			$values[$val[1]] = str_replace('\\' . $val[2], $val[2], $val[3]);
 		}
 
 		return $values;
 	}
 
 	function SplitTag($tag)
 	{
 		if (!preg_match('/([^_ \t\r\n]*)[_]?([^ \t\r\n]*)[ \t\r\n]*(.*)$$/s', $tag['tag'], $parts)) {
 			// this is virtually impossible, but just in case
 			throw new ParserException('Incorrect tag format: ' . $tag['tag'], 0, null, $tag);
 
 			return false;
 		}
 
 		$splited['prefix'] = $parts[2] ? $parts[1] : '__auto__';
 		$splited['name'] = $parts[2] ? $parts[2] : $parts[1];
 		$splited['attrs'] = $parts[3];
 
 		return $splited;
 	}
 
 	function ProcessTag($tag)
 	{
 		$splited = $this->SplitTag($tag);
 		if ($splited === false) {
 			return false;
 		}
 
 		$tag = array_merge($tag, $splited);
 		$tag['processed'] = false;
 		$tag['NP'] = $this->SplitParamsStr($tag['attrs']);
 
 		$o = '';
 		$tag['is_closing'] = $tag['opening'] == '</' || $tag['closing'] == '/>';
 		if (class_exists('_Tag_'.$tag['name'])) { // block tags should have special handling class
 			if ($tag['opening'] == '<') {
 				$class = '_Tag_'.$tag['name'];
 				$instance = new $class($tag);
 				$instance->Parser =& $this;
 				/* @var $instance _BlockTag */
 				$this->Stack[++$this->Level] =& $instance;
 				$this->Buffers[$this->Level] = '';
 				$this->Cachable[$this->Level] = true;
 				$open_code = $instance->Open($tag);
 				if ($open_code === false) {
 					return false;
 				}
 				$o .= $open_code;
 			}
 
 			if ($tag['is_closing']) { // not ELSE here, because tag may be <empty/> and still has a handler-class
 				if ($this->Level == 0) {
 					$dump = array();
 					foreach ($this->Stack as $instance) {
 						$dump[] = $instance->Tag;
 					}
 
 					if ( $this->Application->isDebugMode() ) {
 						$this->Application->Debugger->dumpVars($dump);
 					}
 
 					$error_msg = 'Closing tag without an opening: ' . $this->TagInfo($tag) . ' - <strong>probably opening tag was removed or nested tags error</strong>';
 					throw new ParserException($error_msg, 0, null, $tag);
 
 					return false;
 				}
 
 				if ($this->Stack[$this->Level]->Tag['name'] != $tag['name']) {
 					$opening_tag = $this->Stack[$this->Level]->Tag;
 
 					$error_msg = '	Closing tag ' . $this->TagInfo($tag) . ' does not match
 									opening tag	at current nesting level
 									(' . $this->TagInfo($opening_tag) . '	opened at line ' . $opening_tag['line'] . ')';
 					throw new ParserException($error_msg, 0, null, $tag);
 
 					return false;
 				}
 
 				$o .= $this->Stack[$this->Level]->Close($tag); // DO NOT use $this->Level-- here because it's used inside Close
 				$this->Level--;
 			}
 		}
 		else { // regular tags - just compile
 			if (!$tag['is_closing']) {
 				$error_msg = 'Tag without a handler: ' . $this->TagInfo($tag) . ' - <strong>probably missing &lt;empty <span style="color: red">/</span>&gt; tag closing</strong>';
 				throw new ParserException($error_msg, 0, null, $tag);
 
 				return false;
 			}
 
 			if ($this->Level > 0) $o .= $this->Stack[$this->Level]->PassThrough($tag);
 			if (!$tag['processed']) {
 				$compiled = $this->CompileTag($tag);
 				if ($compiled === false) return false;
 				if (isset($tag['NP']['cachable']) && (!$tag['NP']['cachable'] || $tag['NP']['cachable'] ==  'false')) {
 					$this->Cachable[$this->Level] = false;
 				}
 				$o .= '<?'.'php ' . $compiled . " ?>\n";
 //				$o .= '<?'.'php ';
 //				$o .= (isset($tag['NP']['cachable']) && (!$tag['NP']['cachable'] || $tag['NP']['cachable'] ==  'false')) ? $this->BreakCache($compiled, $this->GetPointer($tag)) : $compiled;
 //				$o .= " ?".">\n";
 			}
 		}
 		$this->Buffers[$this->Level] .= $o;
 		return true;
 	}
 
 	function GetPointer($tag)
 	{
 		return abs(crc32($tag['file'])).'_'.$tag['line'];
 	}
 
 	function BreakCache($code, $pointer, $condition='')
 	{
 		return "\$_parser->CacheEnd();\n}\n" . $code."\nif ( !\$_parser->CacheStart('{$pointer}'" . ($condition ? ", {$condition}" : '') . ") ) {\n";
 	}
 
 	function TagInfo($tag, $with_params=false)
 	{
 		return "<b>{$tag['prefix']}_{$tag['name']}".($with_params ? ' '.$tag['attrs'] : '')."</b>";
 	}
 
 	function CompileParamsArray($arr)
 	{
 		$to_pass = 'Array(';
 		foreach ($arr as $name => $val) {
 			$to_pass .= '"'.$name.'" => "'.str_replace('"', '\"', $val).'",';
 		}
 		$to_pass .= ')';
 		return $to_pass;
 	}
 
 	function CompileTag($tag)
 	{
 		$code = '';
 		$to_pass = $this->CompileParamsArray($tag['NP']);
 
 		if ($tag['prefix'] == '__auto__') {
 			$prefix = $this->GetParam('PrefixSpecial');
 			$code .= '$_p_ =& $_parser->GetProcessor($PrefixSpecial);'."\n";
 			$code .= 'echo $_p_->ProcessParsedTag(\''.$tag['name'].'\', '.$to_pass.', "$PrefixSpecial", \''.$tag['file'].'\', '.$tag['line'].');'."\n";
 		}
 		else {
 			$prefix = $tag['prefix'];
 			$code .= '$_p_ =& $_parser->GetProcessor("'.$tag['prefix'].'");'."\n";
 			$code .= 'echo $_p_->ProcessParsedTag(\''.$tag['name'].'\', '.$to_pass.', "'.$tag['prefix'].'", \''.$tag['file'].'\', '.$tag['line'].');'."\n";
 		}
 
 		if (array_key_exists('result_to_var', $tag['NP']) && $tag['NP']['result_to_var']) {
 			$code .= "\$params['{$tag['NP']['result_to_var']}'] = \$_parser->GetParam('{$tag['NP']['result_to_var']}');\n";
 			$code .= "\${$tag['NP']['result_to_var']} = \$params['{$tag['NP']['result_to_var']}'];\n";
 		}
 
 		if ($prefix && strpos($prefix, '$') === false) {
 			$p =& $this->GetProcessor($prefix);
 			if (!is_object($p) || !$p->CheckTag($tag['name'], $tag['prefix'])) {
 				$error_msg = 'Unknown tag: ' . $this->TagInfo($tag) . ' - <strong>incorrect tag name or prefix</strong>';
 				throw new ParserException($error_msg, 0, null, $tag);
 
 				return false;
 			}
 		}
 
 		return $code;
 	}
 
 	function CheckTemplate($t, $silent = null)
 	{
 		$pre_parsed = $this->Application->TemplatesCache->GetPreParsed($t);
 		if (!$pre_parsed) {
 			if (!$silent) {
 				throw new ParserException('Cannot include "<strong>' . $t . '</strong>" - file does not exist');
 			}
 
 			return false;
 		}
 
 		$force_compile = defined('DBG_NPARSER_FORCE_COMPILE') && DBG_NPARSER_FORCE_COMPILE;
 		if (!$pre_parsed || !$pre_parsed['active'] || $force_compile) {
 			$inc_parser = new NParser();
 
 			if ($force_compile) {
 				// remove Front-End theme markings during total compilation
 				$t = preg_replace('/^theme:.*?\//', '', $t);
 			}
 
 			if (!$inc_parser->Compile($pre_parsed, $t)) {
 				return false;
 			}
 		}
 
 		return $pre_parsed;
 	}
 
 	function Run($t, $silent = null)
 	{
 		if ((strpos($t, '../') !== false) || (trim($t) !== $t)) {
 			// when relative paths or special chars are found template names from url, then it's hacking attempt
 			return false;
 		}
 
 		$pre_parsed = $this->CheckTemplate($t, $silent);
 		if (!$pre_parsed) {
 			return false;
 		}
 
 		$backup_template = $this->TemplateName;
 		$backup_fullpath = $this->TempalteFullPath;
 		$this->TemplateName = $t;
 		$this->TempalteFullPath = $pre_parsed['tname'];
 
 		if (!isset($backup_template) && $this->CachingEnabled && !$this->UserLoggedIn && !EDITING_MODE) {
 			// this is main page template -> check for page-based aggressive caching settings
 			$output =& $this->RunMainPage($pre_parsed);
 		}
 		else {
 			$output =& $this->Application->TemplatesCache->runTemplate($this, $pre_parsed);
 		}
 
 		$this->TemplateName = $backup_template;
 		$this->TempalteFullPath = $backup_fullpath;
 
 		return $output;
 	}
 
 	function &RunMainPage($pre_parsed)
 	{
 		$page =& $this->Application->recallObject('st.-virtual');
 		/* @var $page kDBItem */
 
 		if ($page->isLoaded()) {
 			// page found in database
 			$debug_mode = $this->Application->isDebugMode(); // don't cache debug output
 			$template_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $this->TempalteFullPath, 1);
 			$element = ($debug_mode ? 'DEBUG_MODE:' : '') . 'file=' . $template_path;
 			$this->FullCachePage = $page->GetDBField('EnablePageCache');
 
 			if ($this->FullCachePage && $page->GetDBField('PageCacheKey')) {
 				// page caching enabled -> try to get from cache
 				$cache_key = $this->FormCacheKey($element, $page->GetDBField('PageCacheKey'));
 				$output = $this->getCache($cache_key);
 
 				if ($output !== false) {
 					return $output;
 				}
 			}
 
 			// page not cached OR cache expired
 			$output =& $this->Application->TemplatesCache->runTemplate($this, $pre_parsed);
 			$this->generatePageCacheKey($page);
 
 			if ($this->FullCachePage && $page->GetDBField('PageCacheKey')) {
 				$cache_key = $this->FormCacheKey($element, $page->GetDBField('PageCacheKey'));
 				$this->setCache($cache_key, $output, (int)$page->GetDBField('PageExpiration'));
 			}
 		}
 		else {
 			// page not found in database
 			$output =& $this->Application->TemplatesCache->runTemplate($this, $pre_parsed);
 		}
 
 		return $output;
 	}
 
 	/**
 	 * Generate page caching key based on prefixes used on it + prefix IDs passed in url
 	 *
 	 * @param kDBItem $page
 	 */
 	function generatePageCacheKey(&$page)
 	{
 		if (!$page->isLoaded() || $page->GetDBField('OverridePageCacheKey')) {
 			return ;
 		}
 
 		$page_cache_key = Array ();
 		// nobody resets "m" prefix serial, don't count no user too
 		unset($this->PrefixesInUse['m'], $this->PrefixesInUse['u']);
 
 		if (array_key_exists('st', $this->PrefixesInUse)) {
 			// prefix "st" serial will never be changed
 			unset($this->PrefixesInUse['st']);
 			$this->PrefixesInUse['c'] = 1;
 		}
 
 		$prefix_ids = Array ();
 		$prefixes = array_keys($this->PrefixesInUse);
 		asort($prefixes);
 
 		foreach ($prefixes as $index => $prefix) {
 			$id = $this->Application->GetVar($prefix . '_id');
 
 			if (is_numeric($id)) {
 				if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) {
 					$this->Application->Debugger->appendHTML('Found: "' . $prefix . '_id" = ' . $id . ' during PageCacheKey forming.');
 				}
 
 				$prefix_ids[] = $prefix;
 				unset($prefixes[$index]);
 			}
 		}
 
 		if ($prefix_ids) {
 			$page_cache_key[] = 'prefix_id:' . implode(',', $prefix_ids);
 		}
 
 		if ($prefixes) {
 			$page_cache_key[] = 'prefix:' . implode(',', $prefixes);
 		}
 
 		$page_cache_key = implode(';', $page_cache_key);
 
 		if ($page_cache_key != $page->GetOriginalField('PageCacheKey')) {
 			if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) {
 				$this->Application->Debugger->appendHTML('Canging PageCacheKey from "<strong>' . $page->GetOriginalField('PageCacheKey') . '</strong>" to "<strong>' . $page_cache_key . '</strong>".');
 			}
 
 			$page->SetDBField('PageCacheKey', $page_cache_key);
 
 			// don't use kDBItem::Update(), because it will change ModifiedById to current front-end user
 			$sql = 'UPDATE ' . $page->TableName . '
 					SET PageCacheKey = ' . $page->Conn->qstr($page_cache_key) . '
 					WHERE ' . $page->IDField . ' = ' . $page->GetID();
 			$page->Conn->Query($sql);
 
 			// increment serial, because we issue direct sql above!
 			$this->Application->incrementCacheSerial('c');
 			$this->Application->incrementCacheSerial('c', $page->GetID());
 		}
 	}
 
 	/**
 	 * Creates tag processor and stores it in local cache + factory
 	 *
 	 * @param string $prefix
 	 * @return kTagProcessor
 	 */
 	function &GetProcessor($prefix)
 	{
 		static $processors = Array ();
 
 		if ( !isset($processors[$prefix]) ) {
 			$processors[$prefix] = $this->Application->recallObject($prefix . '_TagProcessor');
 		}
 
 		return $processors[$prefix];
 	}
 
 	/**
 	 * 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 '';
 	}
 
 	function SetParams($params)
 	{
 		$this->Params = $params;
 		$keys = array_keys($this->Params);
 	}
 
 	function GetParam($name)
 	{
 		return isset($this->Params[$name]) ? $this->Params[$name] : false;
 	}
 
 	function SetParam($name, $value)
 	{
 		$this->Params[$name] = $value;
 	}
 
 	function PushParams($params)
 	{
 		$this->ParamsStack[$this->ParamsLevel++] = $this->Params;
 		$this->Params = $params;
 	}
 
 	function PopParams()
 	{
 		$this->Params = $this->ParamsStack[--$this->ParamsLevel];
 	}
 
 	function ParseBlock($params, $pass_params=false)
 	{
 		if (array_key_exists('cache_timeout', $params) && $params['cache_timeout']) {
 			$ret = $this->getCache( $this->FormCacheKey('element_' . $params['name']) );
 
 			if ($ret) {
 				return $ret;
 			}
 		}
 
 		if (substr($params['name'], 0, 5) == 'html:') {
 			return substr($params['name'], 5);
 		}
 
 		if (!array_key_exists($params['name'], $this->Elements) && array_key_exists('default_element', $params)) {
 			// when given element not found, but default element name given, then render it instead
 			$params['name'] = $params['default_element'];
 			unset($params['default_element']);
 			return $this->ParseBlock($params, $pass_params);
 		}
 
 		$original_params = $params;
 
 		if ($pass_params || isset($params['pass_params'])) $params = array_merge($this->Params, $params);
 		$this->PushParams($params);
 		$data_exists_bak = $this->DataExists;
 
 		// if we are parsing design block and we have block_no_data - we need to wrap block_no_data into design,
 		// so we should set DataExists to true manually, otherwise the design block will be skipped because of data_exists in params (by Kostja)
 		//
 		// keep_data_exists is used by block RenderElement (always added in ntags.php), to keep the DataExists value
 		// from inside-content block, otherwise when parsing the design block DataExists will be reset to false resulting missing design block (by Kostja)
 		//
 		// Inside-content block parsing result is given to design block in "content" parameter (ntags.php) and "keep_data_exists"
 		// is only passed, when parsing design block. In case, when $this->DataExists is set to true, but
 		// zero-length content (in 2 cases: method NParser::CheckNoData set it OR really empty block content)
 		// is returned from inside-content block, then design block also should not be shown (by Alex)
 		$this->DataExists = (isset($params['keep_data_exists']) && isset($params['content']) && $params['content'] != '' && $this->DataExists) || (isset($params['design']) && isset($params['block_no_data']) && $params['name'] == $params['design']);
 
 		if (!array_key_exists($params['name'], $this->Elements)) {
 			$pre_parsed = $this->Application->TemplatesCache->GetPreParsed($params['name']);
 			if ($pre_parsed) {
 				$ret = $this->IncludeTemplate($params);
 
 				if (array_key_exists('no_editing', $params) && $params['no_editing']) {
 					// when individual render element don't want to be edited
 					return $ret;
 				}
 
 				return defined('EDITING_MODE') ? $this->DecorateBlock($ret, $params, true) : $ret;
 			}
 
 			$trace_results = debug_backtrace();
 
 			$error_tag = Array (
 				'file' => $trace_results[0]['file'],
 				'line' => $trace_results[0]['line'],
 			);
 
 			$error_msg = '<strong>Rendering of undefined element ' . $params['name'] . '</strong>';
 			throw new ParserException($error_msg, 0, null, $error_tag);
 
 			return false;
 		}
 
 		$m_processor =& $this->GetProcessor('m');
 		$flag_values = $m_processor->PreparePostProcess($params);
 
 		$f_name = $this->Elements[$params['name']];
+		/* @var $f_name Closure */
+
 		$ret = $f_name($this, $params);
 		$ret = $m_processor->PostProcess($ret, $flag_values);
 		$block_params = $this->Params; // input parameters, but modified inside rendered block
 		$this->PopParams();
 
 		if (array_key_exists('result_to_var', $flag_values) && $flag_values['result_to_var']) {
 			// when "result_to_var" used inside ParseBlock, then $$result_to_var parameter is set inside ParseBlock,
 			// but not outside it as expected and got lost at all after PopParams is called, so make it work by
 			// setting it's value on current parameter deep level (from where ParseBlock was called)
 			$this->SetParam($flag_values['result_to_var'], $block_params[ $flag_values['result_to_var'] ]);
 		}
 
 		$this->CheckNoData($ret, $params);
 
 		$this->DataExists = $data_exists_bak || $this->DataExists;
 
 		if (array_key_exists('cache_timeout', $original_params) && $original_params['cache_timeout']) {
 			$cache_key = $this->FormCacheKey('element_' . $original_params['name']);
 			$this->setCache($cache_key, $ret, (int)$original_params['cache_timeout']);
 		}
 
 		if (array_key_exists('no_editing', $block_params) && $block_params['no_editing']) {
 			// when individual render element don't want to be edited
 			return $ret;
 		}
 
 		return defined('EDITING_MODE') ? $this->DecorateBlock($ret, $params) : $ret;
 	}
 
 	/**
 	 * Checks, that given block is defined
 	 *
 	 * @param string $name
 	 * @return bool
 	 */
 	function blockFound($name)
 	{
 		return array_key_exists($name, $this->Elements);
 	}
 
 	function DecorateBlock($block_content, $block_params, $is_template = false)
 	{
 		static $used_ids = Array (), $base_url = null;
 
 		if (!isset($base_url)) {
 			$base_url = $this->Application->BaseURL();
 		}
 
 //		$prepend = '[name: ' . $block_params['name'] . '] [params: ' . implode(', ', array_keys($block_params)) . ']';
 
 		$decorate = false;
 		$design = false;
 
 		if (EDITING_MODE == EDITING_MODE_DESIGN) {
 			$decorate = true;
 
 			if ($is_template) {
 				// content inside pair RenderElement tag
 			}
 			else {
 				if (strpos($block_params['name'], '__capture_') === 0) {
 					// capture tag (usually inside pair RenderElement)
 					$decorate = false;
 				}
 				elseif (array_key_exists('content', $block_params)) {
 					// pair RenderElement (on template, were it's used)
 					$design = true;
 				}
 			}
 		}
 
 		if (!$decorate) {
 			return $block_content;
 		}
 		/*else {
 			$block_content = $prepend . $block_content;
 		}*/
 
 		$block_name = $block_params['name'];
 		$function_name = $is_template ? $block_name : $this->Elements[$block_name];
 
 		$block_title = '';
 		if (array_key_exists($function_name, $this->Application->Parser->ElementLocations)) {
 			$element_location = $this->Application->Parser->ElementLocations[$function_name];
 
 			$block_title .= $element_location['template'] . '.tpl';
 			$block_title .= ' (' . $element_location['start_pos'] . ' - ' . $element_location['end_pos'] . ')';
 		}
 
 		// ensure unique id for every div (used from print lists)
 		$container_num = 1;
 		$container_id = 'parser_block[' . $function_name . ']';
 		while (in_array($container_id . '_' . $container_num, $used_ids)) {
 			$container_num++;
 		}
 
 		$container_id .= '_' . $container_num;
 		$used_ids[] = $container_id;
 
 		// prepare parameter string
 		$param_string = $block_name . ':' . $function_name;
 
 		if ($design) {
 			$btn_text = $this->_btnPhrases['design'];
 			$btn_class = 'cms-edit-design-btn';
 			$btn_container_class = 'block-edit-design-btn-container';
 			$btn_name = 'design';
 		}
 		else {
 			$btn_text = $this->_btnPhrases['block'];
 			$btn_class = 'cms-edit-block-btn';
 			$btn_container_class = 'block-edit-block-btn-container';
 			$btn_name = 'content';
 		}
 
 		$block_editor = '
 			<div id="' . $container_id . '" params="' . $param_string . '" class="' . $btn_container_class . '" title="' . htmlspecialchars($block_title) . '">
 				<div class="' . $btn_class . '">
 					<div class="cms-btn-image">
 						<img src="' . $base_url . 'core/admin_templates/img/top_frame/icons/' . $btn_name . '_mode.png" width="15" height="16" alt=""/>
 					</div>
 					<div class="cms-btn-text" id="' . $container_id . '_btn">' . $btn_text . '</div>
 				</div>
 				<div class="cms-btn-content">
 					%s
 				</div>
 			</div>';
 
 		// 1 - text before, 2 - open tag, 3 - open tag attributes, 4 - content inside tag, 5 - closing tag, 6 - text after closing tag
 		if (preg_match('/^(\s*)<(td|span)(.*?)>(.*)<\/(td|span)>(.*)$/is', $block_content, $regs)) {
 			// div inside span -> put div outside span
 			return $regs[1] . '<' . $regs[2] . ' ' . $regs[3] . '>' . str_replace('%s', $regs[4], $block_editor) . '</' . $regs[5] . '>' . $regs[6];
 		}
 
 		return str_replace('%s', $block_content, $block_editor);
 	}
 
 	function IncludeTemplate($params, $silent=null)
 	{
 		$t = is_array($params) ? $this->SelectParam($params, 't,template,block,name') : $params;
 		$cache_timeout = array_key_exists('cache_timeout', $params) ? $params['cache_timeout'] : false;
 
 		if ($cache_timeout) {
 			$cache_key = $this->FormCacheKey('template:' . $t);
 			$ret = $this->getCache($cache_key);
 
 			if ($ret !== false) {
 				return $ret;
 			}
 		}
 
 		$t = preg_replace('/\.tpl$/', '', $t);
 		$data_exists_bak = $this->DataExists;
 		$this->DataExists = false;
 
 		if (!isset($silent) && array_key_exists('is_silent', $params)) {
 			$silent = $params['is_silent'];
 		}
 
 		if (isset($params['pass_params'])) {
 			// ability to pass params from block to template
 			$params = array_merge($this->Params, $params);
 		}
 
 		$this->PushParams($params);
 		$ret = $this->Run($t, $silent);
 		$this->PopParams();
 
 		$this->CheckNoData($ret, $params);
 		$this->DataExists = $data_exists_bak || $this->DataExists;
 
 		if ($cache_timeout) {
 			$this->setCache($cache_key, $ret, (int)$cache_timeout);
 		}
 
 		return $ret;
 	}
 
 	function CheckNoData(&$ret, $params)
 	{
 		if (array_key_exists('data_exists', $params) && $params['data_exists'] && !$this->DataExists) {
 			$block_no_data = isset($params['BlockNoData']) ? $params['BlockNoData'] : (isset($params['block_no_data']) ? $params['block_no_data'] : false);
 			if ($block_no_data)	{
 				$ret = $this->ParseBlock(array('name'=>$block_no_data));
 			}
 			else {
 				$ret = '';
 			}
 		}
 	}
 
 	function getCache($name)
 	{
 		if (!$this->CachingEnabled) {
 			return false;
 		}
 
 		$ret = $this->Application->getCache($name, false);
 
 		if (preg_match('/^\[DE_MARK:(.*?)\]$/', substr($ret, -11), $regs)) {
 			$this->DataExists = $regs[1] ? true : false;
 			$ret = substr($ret, 0, -11);
 		}
 
 		return $ret;
 	}
 
 	function setCache($name, $value, $expiration = 0)
 	{
 		if (!$this->CachingEnabled) {
 			return false;
 		}
 
 		// remeber DataExists in cache, because after cache will be restored
 		// it will not be available naturally (no tags, that set it will be called)
 		$value .= '[DE_MARK:' . (int)$this->DataExists . ']';
 
 		return $this->Application->setCache($name, $value, $expiration);
 	}
 
 	function FormCacheKey($element, $key_string = '')
 	{
 		if (strpos($key_string, 'guest_only') !== false && $this->UserLoggedIn) {
 			// don't cache, when user is logged-in "guest_only" is specified in key
 			return '';
 		}
 
 		$parts = Array ();
 
 		// 1. replace INLINE variable (from request) into key parts
 		if (preg_match_all('/\(%(.*?)\)/', $key_string, $regs)) {
 			// parts in form "(%variable_name)" were found
 			foreach ($regs[1] as $variable_name) {
 				$variable_value = $this->Application->GetVar($variable_name);
 				$key_string = str_replace('(%' . $variable_name . ')', $variable_value, $key_string);
 			}
 		}
 
 		// 2. replace INLINE serial numbers (they may not be related to any prefix at all)
 		// Serial number also could be composed of inline variables!
 		if (preg_match_all('/\[%(.*?)%\]/', $key_string, $regs)) {
 			// format "[%LangSerial%]" - prefix-wide serial in case of any change in "lang" prefix
 			// format "[%LangIDSerial:5%]" - one id-wide serial in case of data, associated with given id was changed
 			// format "[%CiIDSerial:ItemResourceId:5%]" - foreign key-based serial in case of data, associated with given foreign key was changed
 			foreach ($regs[1] as $serial_name) {
 				$serial_value = $this->Application->getCache('[%' . $serial_name . '%]');
 				$key_string = str_replace('[%' . $serial_name . '%]', '[%' . $serial_name . '=' . $serial_value . '%]', $key_string);
 			}
 		}
 
 		/*
 			Always add:
 			===========
 			* "var:m_lang" - show content on current language
 			* "var:t" - template from url, used to differ multiple pages using same physical template (like as design)
 			* "var:admin,editing_mode" - differ cached content when different editing modes are used
 			* "var:m_cat_id,m_cat_page" - pass current category
 			* "var:page,per_page,sort_by" - list pagination/sorting parameters
 			* "prefix:theme-file" - to be able to reset all cached templated using "Rebuild Theme Files" function
 			* "prefix:phrases" - use latest phrase translations
 			* "prefix:conf" - output could slighly differ based on configuration settings
 		*/
 		$key_string = rtrim('var:m_lang,t,admin,editing_mode,m_cat_id,m_cat_page,page,per_page,sort_by;prefix:theme-file,phrases,conf;' . $key_string, ';');
 
 		$keys = explode(';', $key_string);
 
 		/*
 			Possible parts of a $key_string (all can have multiple occurencies):
 			====================================================================
 			* prefix:<prefixA>[,<prefixB>,<prefixC>] - include global serial for given prefix(-es)
 			* skip_prefix:<prefix1>[,<prefix2>,<prefix3>] - exclude global serial for given prefix(-es)
 			* prefix_id:<prefixA>[,<prefixB>,<prefixC>] - include id-based serial for given prefix(-es)
 			* skip_prefix_id:<prefix1>[,<prefix2>,<prefix3>] - exclude id-based serial for given prefix(-es)
 			* var:<aaa>[,<bbb>,<ccc>] - include request variable value(-s)
 			* skip_var:<varA>[,<varB>,<varC>] - exclude request variable value(-s)
 			* (%variable_name) - include request variable value (only value without variable name ifself, like in "var:variable_name")
 			* [%SerialName%] - use to retrieve serial value in free form
 		*/
 
 		// 3. get variable names, prefixes and prefix ids, that should be skipped
 		$skip_prefixes = $skip_prefix_ids = $skip_variables = Array ();
 
 		foreach ($keys as $index => $key) {
 			if (preg_match('/^(skip_var|skip_prefix|skip_prefix_id):(.*?)$/i', $key, $regs)) {
 				unset($keys[$index]);
 				$tmp_parts = explode(',', $regs[2]);
 
 				switch ($regs[1]) {
 					case 'skip_var':
 						$skip_variables = array_merge($skip_variables, $tmp_parts);
 						break;
 
 					case 'skip_prefix':
 						$skip_prefixes = array_merge($skip_prefixes, $tmp_parts);
 						break;
 
 					case 'skip_prefix_id':
 						$skip_prefix_ids = array_merge($skip_prefix_ids, $tmp_parts);
 						break;
 				}
 			}
 		}
 
 		$skip_prefixes = array_unique($skip_prefixes);
 		$skip_variables = array_unique($skip_variables);
 		$skip_prefix_ids = array_unique($skip_prefix_ids);
 
 		// 4. process keys
 		foreach ($keys as $key) {
 			if (preg_match('/^(var|prefix|prefix_id):(.*?)$/i', $key, $regs)) {
 				$tmp_parts = explode(',', $regs[2]);
 
 				switch ($regs[1]) {
 					case 'var':
 						// format: "var:country_id" will become "country_id=<country_id>"
 						$tmp_parts = array_diff($tmp_parts, $skip_variables);
 
 						foreach ($tmp_parts as $variable_name) {
 							$variable_value = $this->Application->GetVar($variable_name);
 
 							if ($variable_value !== false) {
 								$parts[] = $variable_name . '=' . $variable_value;
 							}
 						}
 						break;
 
 					case 'prefix':
 						// format: "prefix:country" will become "[%CountrySerial%]"
 						$tmp_parts = array_diff($tmp_parts, $skip_prefixes);
 
 						foreach ($tmp_parts as $prefix) {
 							$serial_name = $this->Application->incrementCacheSerial($prefix, null, false);
 							$parts[] = '[%' . $serial_name . '=' . $this->Application->getCache($serial_name) . '%]';
 
 							if (!$this->RewriteUrls) {
 								// add env-style page and per-page variable, when mod-rewrite is off
 								$prefix_variables = Array ($prefix . '_Page', $prefix . '_PerPage');
 								foreach ($prefix_variables as $variable_name) {
 									$variable_value = $this->Application->GetVar($variable_name);
 
 									if ($variable_value !== false) {
 										$parts[] = $variable_name . '=' . $variable_value;
 									}
 								}
 							}
 						}
 						break;
 
 					case 'prefix_id':
 						// format: "id:country" will become "[%CountryIDSerial:5%]"
 						$tmp_parts = array_diff($tmp_parts, $skip_prefix_ids);
 
 						foreach ($tmp_parts as $prefix_id) {
 							$id = $this->Application->GetVar($prefix_id . '_id');
 
 							if ($id !== false) {
 								$serial_name = $this->Application->incrementCacheSerial($prefix_id, $id, false);
 								$parts[] = '[%' . $serial_name . '=' . $this->Application->getCache($serial_name) . '%]';
 							}
 						}
 						break;
 				}
 			}
 			elseif ($key == 'currency') {
 				// based on current currency
 				$parts[] = 'curr_iso=' . $this->Application->RecallVar('curr_iso');
 			}
 			elseif ($key == 'groups') {
 				// based on logged-in user groups
 				$parts[] = 'groups=' . $this->Application->RecallVar('UserGroups');
 			}
 			elseif ($key == 'guest_only') {
 				// we know this key, but process it at method beginning
 			}
 			else {
 				throw new ParserException('Unknown key part "<strong>' . $key . '</strong>" used in "<strong>key</strong>" parameter of <inp2:m_Cache key="..."/> tag');
 			}
 		}
 
 		// 5. add unique given cache key identifier on this page
 		$parts[] = $element;
 
 		$key = implode(':', $parts);
 
 		if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) {
 			$this->Application->Debugger->appendHTML('Parser Key: ' . $key);
 		}
 
 		return 'parser_' . crc32($key);
 	}
 
 	function PushPointer($pointer, $key)
 	{
 		$cache_key = $this->FullCachePage || !$this->CachingEnabled ? '' : $this->FormCacheKey('pointer:' . $pointer, $key);
 
 		$this->CachePointers[++$this->CacheLevel] = $cache_key;
 
 		return $this->CachePointers[$this->CacheLevel];
 	}
 
 	function PopPointer()
 	{
 		return $this->CachePointers[$this->CacheLevel--];
 	}
 
 	function CacheStart($pointer, $key)
 	{
 		$pointer = $this->PushPointer($pointer, $key);
 
 		if ($pointer) {
 			$ret = $this->getCache($pointer);
 
 			$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode();
 
 			if ($ret !== false) {
 				echo $debug_mode ? '<!-- CACHED OUTPUT START -->' . $ret . '<!-- /CACHED OUTPUT END -->' : $ret;
 				$this->PopPointer();
 
 				return true;
 			}
 
 			if ($debug_mode) {
 				echo '<!-- NO CACHE FOR POINTER: ' . $pointer . ' -->';
 			}
 		}
 
 		ob_start();
 
 		return false;
 	}
 
 	function CacheEnd($expiration = 0)
 	{
 		$ret = ob_get_clean();
 		$pointer = $this->PopPointer();
 
 		if ($pointer) {
 			$res = $this->setCache($pointer, $ret, $expiration);
 
 			if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) {
 				echo '<!-- STORING CACHE FOR POINTER: ' . $pointer . ' [' . $res . '] -->';
 			}
 		}
 
 		echo $ret;
 	}
 
 	/**
 	 * Performs compression of given files or text
 	 *
 	 * @param mixed $data
 	 * @param bool $raw_script
 	 * @param string $file_extension
 	 * @return string
 	 */
 	function CompressScript($data, $raw_script = false, $file_extension = '')
 	{
 		$minify_helper =& $this->Application->recallObject('MinifyHelper');
 		/* @var $minify_helper MinifyHelper */
 
 		if ($raw_script) {
 			$minify_helper->compressString($data, $file_extension);
 
 			return $data;
 		}
 
 		return $minify_helper->CompressScriptTag($data);
 	}
 }
 
 class ParserException extends Exception {
 
 	public function __construct($message = null, $code = 0, Exception $previous = null, $tag = null)
 	{
 		parent::__construct($message, $code, $previous);
 
 		if ( isset($tag) ) {
 			$this->file = $tag['file'];
     		$this->line = $tag['line'];
 		}
 	}
 }
\ No newline at end of file
Index: branches/5.2.x/core/units/priorites/priority_eh.php
===================================================================
--- branches/5.2.x/core/units/priorites/priority_eh.php	(revision 14674)
+++ branches/5.2.x/core/units/priorites/priority_eh.php	(revision 14675)
@@ -1,370 +1,370 @@
 <?php
 
 class PriorityEventHandler extends kDBEventHandler {
 
 	/**
-	 * Allows to override standart permission mapping
+	 * Allows to override standard permission mapping
 	 *
 	 */
 	function mapPermissions()
 	{
 		parent::mapPermissions();
 
 		$permissions = Array (
 			'OnRecalculatePriorities' => Array ('self' => true),
 		);
 
 		$this->permMapping = array_merge($this->permMapping, $permissions);
 	}
 
 	function mapEvents()
 	{
 		parent::mapEvents();
 
 		$events_map = Array (
 			'OnMassMoveUp' => 'OnChangePriority',
 			'OnMassMoveDown' => 'OnChangePriority',
 		);
 
 		$this->eventMethods = array_merge($this->eventMethods, $events_map);
 	}
 
 	/**
 	 * Enter description here...
 	 *
 	 * @param kEvent $event
 	 */
 	function 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 */
 
 		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();
 
 		$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');
 
 			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( array('prefix'=>$prefix, 'name'=>'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_event', $event->MasterEvent->Name);
 		$this->Application->HandleEvent($constrain_event);
 
 		return $constrain_event->getEventParam('constrain_info');
 	}
 }
Index: branches/5.2.x/core/units/visits/visits_event_handler.php
===================================================================
--- branches/5.2.x/core/units/visits/visits_event_handler.php	(revision 14674)
+++ branches/5.2.x/core/units/visits/visits_event_handler.php	(revision 14675)
@@ -1,138 +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 VisitsEventHandler extends kDBEventHandler {
 
 		/**
-		 * Allows to override standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 			$permissions = Array(
 				'OnItemBuild' => Array('self' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Registers user visit to site
 		 *
 		 * @param kEvent $event
 		 *
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnRegisterVisit(&$event)
 		{
 			if ( $this->Application->isAdmin || !$this->Application->ConfigValue('UseVisitorTracking') || $this->Application->RecallVar('visit_id') ) {
 				// admin logins are not registered in visits list
 				return ;
 			}
 
 			$object =& $event->getObject(Array ('skip_autoload' => true));
 			/* @var $object kDBItem */
 
 			$object->SetDBField('VisitDate_date', adodb_mktime());
 			$object->SetDBField('VisitDate_time', adodb_mktime());
 			$object->SetDBField('Referer', getArrayValue($_SERVER, 'HTTP_REFERER'));
 			$object->SetDBField('IPAddress', $_SERVER['REMOTE_ADDR']);
 
 			if ( $object->Create() ) {
 				$this->Application->StoreVar('visit_id', $object->GetID());
 				$this->Application->SetVar('visits_id', $object->GetID());
 			}
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBList */
 
 			$types = $event->getEventParam('types');
 
 			if ( $types == 'myvisitors' ) {
 				$user_id = $this->Application->RecallVar('user_id');
 				$object->addFilter('myitems_user1', 'au.PortalUserId = ' . $user_id);
 				$object->addFilter('myitems_user2', 'au.PortalUserId >0');
 				//$object->AddGroupByField('VisitDate');
 				$object->AddGroupByField('%1$s.VisitId');
 			}
 
 			if ( $types == 'myvisitororders' && $event->Special == 'incommerce' ) {
 				$user_id = $this->Application->RecallVar('user_id');
 				$object->addFilter('myitems_orders', 'ord.OrderId IS NOT NULL');
 				$object->addFilter('myitems_user1', 'au.PortalUserId = ' . $user_id);
 				$object->addFilter('myitems_user2', 'au.PortalUserId >0');
 				$object->addFilter('myitems_orders_processed', 'ord.Status = 4');
 			}
 		}
 
 		/**
 		 * 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, &$event)
 		{
 			$types = $event->getEventParam('types');
 			if(method_exists($object, 'AddGroupByField'))
 			{
 				if( ($types == 'myvisitors' || !$types) && $object->Special == 'incommerce')
 				{
 					$object->addCalculatedField('OrderTotalAmountSum', 'SUM(IF(ord.Status = 4, ord.SubTotal+ord.ShippingCost+ord.VAT, 0))');
 					$object->addCalculatedField('OrderAffiliateCommissionSum', 'SUM( IF(ord.Status = 4,ord.AffiliateCommission,0))');
 					$object->addCalculatedField('OrderCountByVisit', 'SUM( IF(ord.Status = 4, 1, 0) )');
 				}
 
 				if (!$types){
 						$object->AddGroupByField('%1$s.VisitId');
 				}
 			}
 		}
 
 		/**
 		 * [HOOK] Updates user_id in current visit
 		 *
 		 * @param kEvent $event
 		 */
 		function OnUserLogin(&$event)
 		{
 			if ($event->MasterEvent->status == kEvent::erSUCCESS) {
 				$user_id = $this->Application->RecallVar('user_id');
 				if ($user_id > 0) {
 					// for real users only, not root,guest
 					$this->Application->setVisitField('PortalUserId', $user_id);
 				}
 			}
 		}
 
 	}
\ No newline at end of file
Index: branches/5.2.x/core/units/theme_files/theme_file_eh.php
===================================================================
--- branches/5.2.x/core/units/theme_files/theme_file_eh.php	(revision 14674)
+++ branches/5.2.x/core/units/theme_files/theme_file_eh.php	(revision 14675)
@@ -1,228 +1,228 @@
 <?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 ThemeFileEventHandler extends kDBEventHandler {
 
 		/**
-		 * Allows to override standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnLoadBlock' => Array ('subitem' => true),
 				'OnSaveBlock' => Array ('subitem' => true),
 				'OnSaveLayout' => Array ('subitem' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Checks user permission to execute given $event
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(&$event)
 		{
 			if ($event->Name == 'OnLoadBlock' || $event->Name == 'OnSaveBlock') {
 				return $this->Application->isAdminUser;
 			}
 
 			return parent::CheckPermission($event);
 		}
 
 		/**
 		 * Loads template contents into virtual field
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemLoad(&$event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$filename = $this->_getTemplatePath($object);
 
 			if ( file_exists($filename) ) {
 				$object->SetDBField('FileContents', file_get_contents($filename));
 			}
 			else {
 				$object->SetError('FileContents', 'template_file_missing', 'la_error_TemplateFileMissing');
 			}
 		}
 
 		/**
 		 * Trim contents of edited template
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(&$event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$file_data = $object->GetDBField('FileContents');
 
 			$file_data = str_replace("\r\n", "\n", $file_data);
 			$file_data = str_replace("\r", "\n", $file_data);
 
 			$object->SetDBField('FileContents', trim($file_data));
 		}
 
 		/**
 		 * Saves updated content to template
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemUpdate(&$event)
 		{
 			parent::OnAfterItemUpdate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$filename = $this->_getTemplatePath($object);
 
 			if (file_exists($filename) && is_writable($filename)) {
 				$fp = fopen($filename, 'w');
 				fwrite($fp, $object->GetDBField('FileContents'));
 				fclose($fp);
 
 				$themes_helper =& $this->Application->recallObject('ThemesHelper');
 				/* @var $themes_helper kThemesHelper */
 
 				$meta_info = $themes_helper->parseTemplateMetaInfo($filename);
 				$file_description = array_key_exists('desc', $meta_info) ? $meta_info['desc'] : '';
 
 				$object->SetDBField('Description', $file_description);
 				$object->SetDBField('FileMetaInfo', serialize($meta_info));
 				$object->Update();
 			}
 		}
 
 		/**
 		 * Returns full path to template file
 		 *
 		 * @param kDBItem $object
 		 * @return string
 		 */
 		function _getTemplatePath(&$object)
 		{
 			$theme =& $this->Application->recallObject('theme');
 			/* @var $theme kDBItem */
 
 			$path = FULL_PATH . '/themes/' . $theme->GetDBField('Name');
 
 			$path .= $object->GetDBField('FilePath') . '/' . $object->GetDBField('FileName');
 
 			return $path;
 		}
 
 		/**
 		 * Loads block data based on it's name in request
 		 *
 		 * @param kEvent $event
 		 */
 		function OnLoadBlock(&$event)
 		{
 			parent::OnNew($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$template_helper =& $this->Application->recallObject('TemplateHelper');
 			/* @var $template_helper TemplateHelper */
 
 			$template_helper->InitHelper($object);
 
 			$object->SetDBField('FileName', $template_helper->blockInfo('template_file'));
 			$object->SetDBField('BlockPosition', $template_helper->blockInfo('start_pos') . ' - ' . $template_helper->blockInfo('end_pos'));
 			$object->SetDBField('FileContents', $template_helper->blockInfo('content'));
 
 		}
 
 		/**
 		 * Saves changed template block
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveBlock(&$event)
 		{
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
 			if ($items_info) {
 				list ($id, $field_values) = each($items_info);
 				$object->SetFieldsFromHash($field_values);
 				$object->setID($id);
 			}
 
 			$status = $object->Validate();
 
 			$template_helper =& $this->Application->recallObject('TemplateHelper');
 			/* @var $template_helper TemplateHelper */
 
 			$template_helper->InitHelper($object);
 
 			$status = $status && $template_helper->saveBlock($object);
 
 			if ($status) {
 				$event->SetRedirectParam('opener', 'u');
 			}
 			else {
 				$event->status = kEvent::erFAIL;
 			}
 		}
 
 		/**
 		 * Saves layout on given template
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveLayout(&$event)
 		{
 			$event->status = kEvent::erSTOP;
 			if (($this->Application->GetVar('ajax') != 'yes') || (EDITING_MODE != EDITING_MODE_DESIGN)) {
 				return ;
 			}
 
 			$target_order = $this->Application->GetVar('target_order');
 
 			$template_helper =& $this->Application->recallObject('TemplateHelper');
 			/* @var $template_helper TemplateHelper */
 
 			if ($template_helper->moveTemplateElements($target_order)) {
 				echo 'OK';
 				return ;
 			}
 
 			echo 'FAILED';
 		}
 	}
\ No newline at end of file
Index: branches/5.2.x/core/units/categories/categories_tag_processor.php
===================================================================
--- branches/5.2.x/core/units/categories/categories_tag_processor.php	(revision 14674)
+++ branches/5.2.x/core/units/categories/categories_tag_processor.php	(revision 14675)
@@ -1,2009 +1,2020 @@
 <?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.
 */
 
 class CategoriesTagProcessor extends kDBTagProcessor {
 
 	function SubCatCount($params)
 	{
 		$object =& $this->getObject($params);
+		/* @var $object kDBItem */
 
-		if (isset($params['today']) && $params['today']) {
+		if ( isset($params['today']) && $params['today'] ) {
 			$sql = 'SELECT COUNT(*)
-					FROM '.$object->TableName.'
-					WHERE (ParentPath LIKE "'.$object->GetDBField('ParentPath').'%") AND (CreatedOn > '.(adodb_mktime() - 86400).')';
+					FROM ' . $object->TableName . '
+					WHERE (ParentPath LIKE "' . $object->GetDBField('ParentPath') . '%") AND (CreatedOn > ' . (adodb_mktime() - 86400) . ')';
 			return $this->Conn->GetOne($sql) - 1;
 		}
 
 		return $object->GetDBField('CachedDescendantCatsQty');
 	}
 
 	/**
 	 * Returns category count in system
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function CategoryCount($params)
 	{
 		$count_helper =& $this->Application->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		$today_only = isset($params['today']) && $params['today'];
 		return $count_helper->CategoryCount($today_only);
 	}
 
 	function IsNew($params)
 	{
 		$object =& $this->getObject($params);
+		/* @var $object kDBItem */
+
 		return $object->GetDBField('IsNew') ? 1 : 0;
 	}
 
 	function IsPick($params)
 	{
 		return $this->IsEditorsPick($params);
 	}
 
 	/**
 	 * Returns item's editors pick status (using not formatted value)
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function IsEditorsPick($params)
 	{
 		$object =& $this->getObject($params);
+		/* @var $object kDBItem */
 
 		return $object->GetDBField('EditorsPick') == 1;
 	}
 
 	function ItemIcon($params)
 	{
 		$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
 		$grid = $grids[ $params['grid'] ];
 
 		if (!array_key_exists('Icons', $grid)) {
 			return '';
 		}
 
 		$icons = $grid['Icons'];
 		$icon_prefix = array_key_exists('icon_prefix', $params)? $params['icon_prefix'] : 'icon16_';
 
 		if (array_key_exists('name', $params)) {
 			$icon_name = $params['name'];
 			return array_key_exists($icon_name, $icons) ? $icons[$icon_name] : '';
 		}
 
 		$object =& $this->getObject($params);
 		/* @var $object kDBList */
 
 		if ($object->GetDBField('ThemeId') > 0) {
 			if (!$object->GetDBField('IsMenu')) {
 				return $icon_prefix . 'section_menuhidden_system.png';
 			}
 			return $icon_prefix . 'section_system.png';
 		}
 
 		$status = $object->GetDBField('Status');
 
 		if ($status == STATUS_DISABLED) {
 			return $icon_prefix . 'section_disabled.png';
 		}
 
 		if (!$object->GetDBField('IsMenu')) {
 			return $icon_prefix . 'section_menuhidden.png';
 		}
 
 		if ($status == STATUS_PENDING) {
 			return $icon_prefix . 'section_pending.png';
 		}
 
 		if ($object->GetDBField('IsNew') && ($icon_prefix == 'icon16_')) {
 			return $icon_prefix . 'section_new.png'; // show gris icon only in grids
 		}
 
 		return $icon_prefix . 'section.png';
 	}
 
 	function ItemCount($params)
 	{
 		$object =& $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$ci_table = $this->Application->getUnitOption('ci', 'TableName');
 
 		$sql = 'SELECT COUNT(*)
 				FROM ' . $object->TableName . ' c
 				LEFT JOIN ' . $ci_table . ' ci ON c.CategoryId = ci.CategoryId
 				WHERE (c.TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight') . ') AND NOT (ci.CategoryId IS NULL)';
 		return $this->Conn->GetOne($sql);
 	}
 
 	function ListCategories($params)
 	{
 		return $this->PrintList2($params);
 	}
 
 	function RootCategoryName($params)
 	{
 		return $this->Application->ProcessParsedTag('m', 'RootCategoryName', $params);
 	}
 
 	function CheckModuleRoot($params)
 	{
 		$module_name = getArrayValue($params, 'module') ? $params['module'] : 'In-Commerce';
 		$module_root_cat = $this->Application->findModule('Name', $module_name, 'RootCat');
 
 		$additional_cats = $this->SelectParam($params, 'add_cats');
 		if ($additional_cats) {
 			$additional_cats = explode(',', $additional_cats);
 		}
 		else {
 			$additional_cats = array();
 		}
 
 		if ($this->Application->GetVar('m_cat_id') == $module_root_cat || in_array($this->Application->GetVar('m_cat_id'), $additional_cats)) {
 			$home_template = getArrayValue($params, 'home_template');
 			if (!$home_template) return;
 			$this->Application->Redirect($home_template, Array('pass'=>'all'));
 		};
 	}
 
 	function CategoryPath($params)
 	{
 		$category_helper =& $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		return $category_helper->NavigationBar($params);
 	}
 
 	/**
 	 * Shows category path to specified category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function FieldCategoryPath($params)
 	{
 		$object =& $this->getObject();
 		/* @var $object kDBItem */
 
 		$field = $this->SelectParam($params, 'name,field');
 		$category_id = $object->GetDBField($field);
 
 		if ($category_id) {
 			$params['cat_id'] = $category_id;
 			return $this->CategoryPath($params);
 		}
 
 		return '';
 	}
 
 	function CurrentCategoryName($params)
 	{
 		$cat_object =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List');
 		/* @var $cat_object kDBList */
 
 		$sql = 'SELECT '.$this->getTitleField().'
 				FROM '.$cat_object->TableName.'
 				WHERE CategoryId = '.(int)$this->Application->GetVar('m_cat_id');
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Returns current category name
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Find where it's used
 	 */
 	function CurrentCategory($params)
 	{
 		return $this->CurrentCategoryName($params);
 	}
 
 	function getTitleField()
 	{
 		$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
 		/* @var $ml_formatter kMultiLanguage */
 
 		return $ml_formatter->LangFieldName('Name');
 	}
 
 	/**
 	 * Returns symlinked category for given category
 	 *
 	 * @param int $category_id
 	 * @return int
 	 */
 	function getCategorySymLink($category_id)
 	{
 		if (!$category_id) {
 			// don't bother to get symlink for "Home" category
 			return $category_id;
 		}
 
 		$cache_key = 'category_symlinks[%CSerial%]';
 		$cache = $this->Application->getCache($cache_key);
 
 		if ($cache === false) {
 			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 			// get symlinked categories, that are not yet deleted
 			$this->Conn->nextQueryCachable = true;
 			$sql = 'SELECT c1.SymLinkCategoryId, c1.' . $id_field . '
 					FROM ' . $table_name . ' c1
 					JOIN ' . $table_name . ' c2 ON c1.SymLinkCategoryId = c2.' . $id_field;
 			$cache = $this->Conn->GetCol($sql, $id_field);
 
 			$this->Application->setCache($cache_key, $cache);
 		}
 
 		return array_key_exists($category_id, $cache) ? $cache[$category_id] : $category_id;
 	}
 
 	function CategoryLink($params)
 	{
 		$category_id = getArrayValue($params, 'cat_id');
 
 		if ( $category_id === false ) {
 			$category_id = $this->Application->GetVar($this->getPrefixSpecial() . '_id');
 		}
 
 		if ( "$category_id" == 'Root' ) {
 			$category_id = $this->Application->findModule('Name', $params['module'], 'RootCat');
 		}
 		elseif ( "$category_id" == 'current' ) {
 			$category_id = $this->Application->GetVar('m_cat_id');
 		}
 
 		if ( !array_key_exists('direct_link', $params) || !$params['direct_link'] ) {
 			$category_id = $this->getCategorySymLink((int)$category_id);
 		}
 		else {
 			unset($params['direct_link']);
 		}
 
 		$virtual_template = $this->Application->getVirtualPageTemplate($category_id);
 
 		if ( ($virtual_template !== false) && preg_match('/external:(.*)/', $virtual_template, $rets) ) {
 			// external url (return here, instead of always replacing $params['t'] for kApplication::HREF to find it)
 			return $rets[1];
 		}
 
 		unset($params['cat_id'], $params['module']);
 		$new_params = Array ('pass' => 'm', 'm_cat_id' => $category_id, 'pass_category' => 1);
 		$params = array_merge($params, $new_params);
 
 		return $this->Application->ProcessParsedTag('m', 't', $params);
 	}
 
 	function CategoryList($params)
 	{
 		//$object =& $this->Application->recallObject( $this->getPrefixSpecial() , $this->Prefix.'_List', $params );
 		$object =& $this->GetList($params);
 
 
 		if ($object->GetRecordsCount() == 0)
 		{
 			if (isset($params['block_no_cats'])) {
 				$params['name'] = $params['block_no_cats'];
 				return $this->Application->ParseBlock($params);
 			}
 			else {
 				return '';
 			}
 		}
 
 		if (isset($params['block'])) {
 			return $this->PrintList($params);
 		}
 		else {
 			$params['block'] = $params['block_main'];
 			if (isset($params['block_row_start'])) {
 				$params['row_start_block'] = $params['block_row_start'];
 			}
 
 			if (isset($params['block_row_end'])) {
 				$params['row_end_block'] = $params['block_row_end'];
 			}
 			return $this->PrintList2($params);
 		}
 	}
 
 	function Meta($params)
 	{
 		$object =& $this->Application->recallObject($this->Prefix); // .'.-item'
 		/* @var $object CategoriesItem */
 
 		$meta_type = $params['name'];
 		if ($object->isLoaded()) {
 			// 1. get module prefix by current category
 			$category_helper =& $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$category_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 			$module_info = $category_helper->getCategoryModule($params,  $category_path);
 
 			// In-Edit & Proj-CMS module prefixes doesn't have custom field with item template
 			if ($module_info && $module_info['Var'] != 'adm' && $module_info['Var'] != 'st') {
 
 				// 2. get item template by current category & module prefix
 				$mod_rewrite_helper = $this->Application->recallObject('ModRewriteHelper');
 				/* @var $mod_rewrite_helper kModRewriteHelper */
 
 				$category_params = Array (
 				'CategoryId' => $object->GetID(),
 				'ParentPath' => $object->GetDBField('ParentPath'),
 				);
 
 				$item_template = $mod_rewrite_helper->GetItemTemplate($category_params, $module_info['Var']);
 
 				if ($this->Application->GetVar('t') == $item_template) {
 					// we are located on item's details page
 					$item =& $this->Application->recallObject($module_info['Var']);
 					/* @var $item kCatDBItem */
 
 					// 3. get item's meta data
 					$value = $item->GetField('Meta'.$meta_type);
 					if ($value) {
 						return $value;
 					}
 				}
 
 				// 4. get category meta data
 				$value = $object->GetField('Meta'.$meta_type);
 				if ($value) {
 					return $value;
 				}
 			}
 		}
 
 		// 5. get default meta data
 		switch ($meta_type) {
 			case 'Description':
 				$config_name = 'Category_MetaDesc';
 				break;
 			case 'Keywords':
 				$config_name = 'Category_MetaKey';
 				break;
 		}
 
 		return $this->Application->ConfigValue($config_name);
 	}
 
 	function BuildListSpecial($params)
 	{
 		if (($this->Special != '') && !is_numeric($this->Special)) {
 			// When recursive category list is printed (like in sitemap), then special
 			// should be generated even if it's already present. Without it list on this
 			// level will erase list on previous level, because it will be stored in same object.
 			return $this->Special;
 		}
 
 		if ( isset($params['parent_cat_id']) ) {
 			$parent_cat_id = $params['parent_cat_id'];
 		}
 		else {
 			$parent_cat_id = $this->Application->GetVar($this->Prefix.'_id');
 			if (!$parent_cat_id) {
 				$parent_cat_id = $this->Application->GetVar('m_cat_id');
 			}
 			if (!$parent_cat_id) {
 				$parent_cat_id = 0;
 			}
 		}
 
 		$list_unique_key = $this->getUniqueListKey($params);
 		// check for "admin" variable, because we are parsing front-end template from admin when using template editor feature
 		if ($this->Application->GetVar('admin') || !$this->Application->isAdmin) {
 			// add parent category to special, when on Front-End,
 			// because there can be many category lists on same page
 			$list_unique_key .= $parent_cat_id;
 		}
 
 		if ($list_unique_key == '') {
 			return parent::BuildListSpecial($params);
 		}
 
 		return crc32($list_unique_key);
 	}
 
 	function IsCurrent($params)
 	{
 		$object =& $this->getObject($params);
 		if ($object->GetID() == $this->Application->GetVar('m_cat_id')) {
 			return true;
 		}
 		else {
 			return false;
 		}
 	}
 
 	/**
 	 * Substitutes category in last template base on current category
 	 * This is required becasue when you navigate catalog using AJAX, last_template is not updated
 	 * but when you open item edit from catalog last_template is used to build opener_stack
 	 * So, if we don't substitute m_cat_id in last_template, after saving item we'll get redirected
 	 * to the first category we've opened, not the one we navigated to using AJAX
 	 *
 	 * @param Array $params
 	 */
 	function UpdateLastTemplate($params)
 	{
 		$category_id = $this->Application->GetVar('m_cat_id');
 
 		$wid = $this->Application->GetVar('m_wid');
 		list($index_file, $env) = explode('|', $this->Application->RecallVar(rtrim('last_template_'.$wid, '_')), 2);
 
 		$vars_backup = Array ();
 		$vars = $this->Application->HttpQuery->processQueryString( str_replace('%5C', '\\', $env) );
 
 		foreach ($vars as $var_name => $var_value) {
 			$vars_backup[$var_name] = $this->Application->GetVar($var_name);
 			$this->Application->SetVar($var_name, $var_value);
 		}
 
 		// update required fields
 		$this->Application->SetVar('m_cat_id', $category_id);
 		$this->Application->Session->SaveLastTemplate($params['template']);
 
 		foreach ($vars_backup as $var_name => $var_value) {
 			$this->Application->SetVar($var_name, $var_value);
 		}
 	}
 
 	function GetParentCategory($params)
 	{
 		$parent_id = $this->Application->getBaseCategory();
 		$category_id = $this->Application->GetVar('m_cat_id');
 
 		if ($category_id != $parent_id) {
 			$sql = 'SELECT ParentId
 					FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . '
 					WHERE ' . $this->Application->getUnitOption($this->Prefix, 'IDField') . ' = ' . $category_id;
 			$parent_id = $this->Conn->GetOne($sql);
 		}
 
 		return $parent_id;
 	}
 
 	function InitCacheUpdater($params)
 	{
 		kUtil::safeDefine('CACHE_PERM_CHUNK_SIZE', 30);
 
 		$continue = $this->Application->GetVar('continue');
 		$total_cats = (int) $this->Conn->GetOne('SELECT COUNT(*) FROM '.TABLE_PREFIX.'Category');
 
 		if ($continue === false && $total_cats > CACHE_PERM_CHUNK_SIZE) {
 			// first step, if category count > CACHE_PERM_CHUNK_SIZE, then ask for cache update
 			return true;
 		}
 
 		if ($continue === false) {
 			// if we don't have to ask, then assume user selected "Yes" in permcache update dialog
 			$continue = 1;
 		}
 
 		$updater =& $this->Application->makeClass('kPermCacheUpdater', Array($continue));
 		/* @var $updater kPermCacheUpdater */
 
 		if ($continue === '0') { // No in dialog
 			$updater->clearData();
 			$this->Application->Redirect($params['destination_template']);
 		}
 
 		$ret = false; // don't ask for update
 		if ($continue == 1) {  // Initial run
 			$updater->setData();
 		}
 		if ($continue == 2) { // Continuing
 			// called from AJAX request => returns percent
 			$needs_more = true;
 			while ($needs_more && $updater->iteration <= CACHE_PERM_CHUNK_SIZE) {
 				// until proceeeded in this step category count exceeds category per step limit
 				$needs_more = $updater->DoTheJob();
 			}
 
 			if ($needs_more) {
 				// still some categories are left for next step
 				$updater->setData();
 			}
 			else {
 				// all done, update left tree and redirect
 				$updater->SaveData();
 
 				$this->Application->HandleEvent($event, '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 Category, that use this tag processor
 			return parent::SaveWarning($params);
 		}
 
 		$main_prefix = getArrayValue($params, 'main_prefix');
 		if ( $main_prefix && $main_prefix != '$main_prefix' ) {
 			$top_prefix = $main_prefix;
 		}
 		else {
 			$top_prefix = $this->Application->GetTopmostPrefix($this->Prefix);
 		}
 
 		$temp_tables = substr($this->Application->GetVar($top_prefix . '_mode'), 0, 1) == 't';
 		$modified = $this->Application->RecallVar($top_prefix . '_modified');
 
 		if ( !$temp_tables ) {
 			$this->Application->RemoveVar($top_prefix . '_modified');
 			return '';
 		}
 
 		$block_name = $this->SelectParam($params, 'render_as,name');
 		if ( $block_name ) {
 			$block_params = $this->prepareTagParams($params);
 			$block_params['name'] = $block_name;
 			$block_params['edit_mode'] = $temp_tables ? 1 : 0;
 			$block_params['display'] = $temp_tables && $modified ? 1 : 0;
 			return $this->Application->ParseBlock($block_params);
 		}
 
 		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 . 'Category
 					WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id');
 			$current_path = $this->Conn->GetOne($sql);
 		}
 
 		if ( array_key_exists('parent_path', $params) ) {
 			$test_path = $params['parent_path'];
 		}
 		else {
 			$template = $params['template'];
 			if ( $template ) {
 				// when using from "c:CachedMenu" tag
 				$sql = 'SELECT ParentPath
 						FROM ' . TABLE_PREFIX . 'Category
 						WHERE NamedParentPath = ' . $this->Conn->qstr('Content/' . $template);
 				$test_path = $this->Conn->GetOne($sql);
 			}
 			else {
 				// when using from "c:PrintList" tag
 				$cat_id = array_key_exists('cat_id', $params) && $params['cat_id'] ? $params['cat_id'] : false;
 				if ( $cat_id === false ) {
 					// category not supplied -> get current from PrintList
 					$category =& $this->getObject($params);
 				}
 				else {
 					if ( "$cat_id" == 'Root' ) {
 						$cat_id = $this->Application->findModule('Name', $params['module'], 'RootCat');
 					}
 
 					$category =& $this->Application->recallObject($this->Prefix . '.-c' . $cat_id, $this->Prefix, Array ('skip_autoload' => true));
 					/* @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 . 'Category
 		        		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 . 'Category';
 			}
 
 			$this->Conn->nextQueryCachable = true;
 			$row_data = $this->Conn->GetRow($sql);
 			$this->Application->setCache($cache_key, $row_data);
 		}
 
 		if ( !$row_data ) {
 			return '';
 		}
 
 		$date = $row_data[$row_data['NewDate'] > $row_data['ModDate'] ? 'NewDate' : 'ModDate'];
 
 		// format date
 		$format = isset($params['format']) ? $params['format'] : '_regional_DateTimeFormat';
 
 		if ( preg_match("/_regional_(.*)/", $format, $regs) ) {
 			$lang =& $this->Application->recallObject('lang.current');
 			/* @var $lang LanguagesItem */
 
 			if ( $regs[1] == 'DateTimeFormat' ) {
 				// combined format
 				$format = $lang->GetDBField('DateFormat') . ' ' . $lang->GetDBField('TimeFormat');
 			}
 			else {
 				// simple format
 				$format = $lang->GetDBField($regs[1]);
 			}
 		}
 
 		return adodb_date($format, $date);
 	}
 
 	function CategoryItemCount($params)
 	{
 		$object =& $this->getObject($params);
 		/* @var $object kDBList */
 
 		$params['cat_id'] = $object->GetID();
 
 		$count_helper =& $this->Application->recallObject('CountHelper');
 		/* @var $count_helper kCountHelper */
 
 		return $count_helper->CategoryItemCount($params['prefix'], $params);
 	}
 
 	/**
 	 * Returns prefix + any word (used for shared between categories per page settings)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function VarName($params)
 	{
 		return $this->Prefix.'_'.$params['type'];
 	}
 
 	/**
 	 * Checks if current category is valid symbolic link to another category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function IsCategorySymLink($params)
 	{
 		$object =& $this->getObject($params);
 		/* @var $object kDBList */
 
 		$sym_category_id = $object->GetDBField('SymLinkCategoryId');
 
 		if (is_null($sym_category_id))
 		{
 			return false;
 		}
 
 		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 		$sql = 'SELECT '.$id_field.'
 				FROM '.$table_name.'
 				WHERE '.$id_field.' = '.$sym_category_id;
 
 		return $this->Conn->GetOne($sql)? true : false;
 	}
 
 	/**
 	 * Returns module prefix based on root category for given
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function GetModulePrefix($params)
 	{
 		$object =& $this->getObject($params);
 		/* @var $object kDBItem */
 
 		$parent_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 
 		$category_helper =& $this->Application->recallObject('CategoryHelper');
 		/* @var $category_helper CategoryHelper */
 
 		$module_info = $category_helper->getCategoryModule($params, $parent_path);
 		return $module_info['Var'];
 	}
 
 	function ImageSrc($params)
 	{
 		list ($ret, $tag_processed) = $this->processAggregatedTag('ImageSrc', $params, $this->getPrefixSpecial());
 		return $tag_processed ? $ret : false;
 	}
 
 	function PageLink($params)
 	{
 		$params['m_cat_page'] = $this->Application->GetVar($this->getPrefixSpecial() . '_Page');
 
 		return parent::PageLink($params);
 	}
 
 	/**
 	 * Returns spelling suggestions against search keyword
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @access protected
 	 */
 	protected function SpellingSuggestions($params)
 	{
 		$keywords = kUtil::unhtmlentities( trim($this->Application->GetVar('keywords')) );
 		if ( !$keywords ) {
 			return '';
 		}
 
 		// 1. try to get already cached suggestion
 		$cache_key = 'search.suggestion[%SpellingDictionary%]:' . $keywords;
 		$suggestion = $this->Application->getCache($cache_key);
 
 		if ( $suggestion !== false ) {
 			return $suggestion;
 		}
 
 		$table_name = $this->Application->getUnitOption('spelling-dictionary', 'TableName');
 
 		// 2. search suggestion in database
 		$this->Conn->nextQueryCachable = true;
 		$sql = 'SELECT SuggestedCorrection
 				FROM ' . $table_name . '
 				WHERE MisspelledWord = ' . $this->Conn->qstr($keywords);
 		$suggestion = $this->Conn->GetOne($sql);
 
 		if ( $suggestion !== false ) {
 			$this->Application->setCache($cache_key, $suggestion);
 
 			return $suggestion;
 		}
 
 		// 3. suggestion not found in database, ask webservice
 		$app_id = $this->Application->ConfigValue('YahooApplicationId');
 		$url = 'http://search.yahooapis.com/WebSearchService/V1/spellingSuggestion?appid=' . $app_id . '&query=';
 
 		$curl_helper =& $this->Application->recallObject('CurlHelper');
 		/* @var $curl_helper kCurlHelper */
 
 		$xml_data = $curl_helper->Send( $url . urlencode($keywords) );
 
 		$xml_helper =& $this->Application->recallObject('kXMLHelper');
 		/* @var $xml_helper kXMLHelper */
 
 		$root_node =& $xml_helper->Parse($xml_data);
 		/* @var $root_node kXMLNode */
 
 		$result = $root_node->FindChild('RESULT');
 		/* @var $result kXMLNode */
 
 		if ( is_object($result) ) {
 			// webservice responded -> save in local database
 			$fields_hash = Array ('MisspelledWord' => $keywords, 'SuggestedCorrection' => $result->Data);
 
 			$this->Conn->doInsert($fields_hash, $table_name);
 			$this->Application->setCache($cache_key, $result->Data);
 
 			return $result->Data;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Shows link for searching by suggested word
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SuggestionLink($params)
 	{
 		$params['keywords'] = $this->SpellingSuggestions($params);
 
 		return $this->Application->ProcessParsedTag('m', 'Link', $params);
 	}
 
 	function InitCatalogTab($params)
 	{
 		$tab_params['mode'] = $this->Application->GetVar('tm'); // single/multi selection possible
 		$tab_params['special'] = $this->Application->GetVar('ts'); // use special for this tab
 		$tab_params['dependant'] = $this->Application->GetVar('td'); // is grid dependant on categories grid
 
 		// set default params (same as in catalog)
 		if ($tab_params['mode'] === false) $tab_params['mode'] = 'multi';
 		if ($tab_params['special'] === false) $tab_params['special'] = '';
 		if ($tab_params['dependant'] === false) $tab_params['dependant'] = 'yes';
 
 		// pass params to block with tab content
 		$params['name'] = $params['render_as'];
 		$special = $tab_params['special'] ? $tab_params['special'] : $this->Special;
 		$params['prefix'] = trim($this->Prefix.'.'.$special, '.');
 
 		$prefix_append = $this->Application->GetVar('prefix_append');
 		if ($prefix_append) {
 			$params['prefix'] .= $prefix_append;
 		}
 
 		$default_grid = array_key_exists('default_grid', $params) ? $params['default_grid'] : 'Default';
 		$radio_grid = array_key_exists('radio_grid', $params) ? $params['radio_grid'] : 'Radio';
 
 		$params['cat_prefix'] = trim('c.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.');
 		$params['tab_mode'] = $tab_params['mode'];
 		$params['grid_name'] = ($tab_params['mode'] == 'multi') ? $default_grid : $radio_grid;
 		$params['tab_dependant'] = $tab_params['dependant'];
 		$params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name
 
 		if ($special == 'showall' || $special == 'user') {
 			$params['grid_name'] .= 'ShowAll';
 		}
 
 		// use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag
 		return $this->Application->ParseBlock($params, 1);
 	}
 
 	/**
 	 * Show CachedNavbar of current item primary category
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CategoryName($params)
 	{
 		// show category cachednavbar of
 		$object =& $this->getObject($params);
+		/* @var $object kDBItem */
+
 		$category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId');
 
 		$cache_key = 'category_paths[%CIDSerial:' . $category_id . '%][%PhrasesSerial%][Adm:' . (int)$this->Application->isAdmin . ']';
 		$category_path = $this->Application->getCache($cache_key);
 
 		if ($category_path === false) {
 			// not chached
 			if ($category_id > 0) {
 				$cached_navbar = $object->GetField('CachedNavbar');
 
 				if ($category_id == $object->GetDBField('ParentId')) {
 					// parent category cached navbar is one element smaller, then current ones
 					$cached_navbar = explode('&|&', $cached_navbar);
 					array_pop($cached_navbar);
 					$cached_navbar = implode('&|&', $cached_navbar);
 				}
 				else {
 					// no relation with current category object -> query from db
 					$language_id = (int)$this->Application->GetVar('m_lang');
 					if (!$language_id) {
 						$language_id = 1;
 					}
 
 					$sql = 'SELECT l' . $language_id . '_CachedNavbar
 							FROM ' . $object->TableName . '
 							WHERE ' . $object->IDField . ' = ' . $category_id;
 					$cached_navbar = $this->Conn->GetOne($sql);
 				}
 
 				$cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $cached_navbar);
 
 				$category_path = trim($this->CategoryName( Array('cat_id' => 0) ).' > '.str_replace('&|&', ' > ', $cached_navbar), ' > ');
 			}
 			else {
 				$category_path = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name');
 			}
 
 			$this->Application->setCache($cache_key, $category_path);
 		}
 
 		return $category_path;
 	}
 
 	// structure related
 
 	/**
 	 * Returns page object based on requested params
 	 *
 	 * @param Array $params
 	 * @return CategoriesItem
 	 */
 	function &_getPage($params)
 	{
 		$page =& $this->Application->recallObject($this->Prefix . '.-virtual', null, $params);
 		/* @var $page kDBItem */
 
 		// 1. load by given id
 		$page_id = array_key_exists('page_id', $params) ? $params['page_id'] : false;
 		if ($page_id) {
 			if ($page_id != $page->GetID()) {
 				// load if different
 				$page->Load($page_id);
 			}
 
 			return $page;
 		}
 
 		// 2. load by template
 		$template = array_key_exists('page', $params) ? $params['page'] : '';
 		if (!$template) {
 			$template = $this->Application->GetVar('t');
 		}
 
 		// different path in structure AND design template differes from requested template
 		$structure_path_match = strtolower( $page->GetDBField('NamedParentPath') ) == strtolower('Content/' . $template);
 		$design_match = $page->GetDBField('CachedTemplate') == $template;
 
 		if (!$structure_path_match && !$design_match) {
 			// Same sql like in "c:getPassedID". Load, when current page object doesn't match requested page object
 			$themes_helper =& $this->Application->recallObject('ThemesHelper');
 			/* @var $themes_helper kThemesHelper */
 
 			$page_id = $themes_helper->getPageByTemplate($template);
 
 			$page->Load($page_id);
 		}
 
 		return $page;
 	}
 
 	/**
 	 * Returns requested content block content of current or specified page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ContentBlock($params)
 	{
 		$num = getArrayValue($params, 'num');
 		if (!$num) {
 			return 'NO CONTENT NUM SPECIFIED';
 		}
 
 		$page =& $this->_getPage($params);
 		/* @var $page kDBItem */
 
 		if (!$page->isLoaded()) {
 			// page is not created yet => all blocks are empty
 			return '';
 		}
 
 		$page_id = $page->GetID();
 
 		$content =& $this->Application->recallObject('content.-block', null, Array ('skip_autoload' => true));
 		/* @var $content kDBItem */
 
 		$data = Array ('PageId' => $page_id, 'ContentNum' => $num);
 		$content->Load($data);
 
 		if (!$content->isLoaded()) {
 			// bug: missing content blocks are created even if user have no SMS-management rights
 			$content->SetFieldsFromHash($data);
 			$content->Create();
 		}
 
 		$edit_code_before = $edit_code_after = '';
 
 		if (EDITING_MODE == EDITING_MODE_CONTENT) {
 			$bg_color = isset($params['bgcolor']) ? $params['bgcolor'] : '#ffffff';
 			$url_params = Array (
 				'pass'			=>	'm,c,content',
 				'm_opener'		=>	'd',
 				'c_id'			=>	$page->GetID(),
 				'content_id'	=>	$content->GetID(),
 				'front'			=>	1,
 				'admin'			=>	1,
 				'__URLENCODE__'	=>	1,
 				'__NO_REWRITE__'=>	1,
 				'escape'		=>	1,
 				'index_file' => 'index.php',
 //				'bgcolor' => $bg_color,
 //				'__FORCE_SID__' => 1
 			);
 
 			// link from Front-End to admin, don't remove "index.php"
 			$edit_url = $this->Application->HREF('categories/edit_content', ADMIN_DIRECTORY, $url_params, 'index.php');
 			$edit_code_before = '
 				<div class="cms-edit-btn-container">
 					<div class="cms-edit-btn" onclick="$form_name=\'kf_cont_'.$content->GetID().'\'; std_edit_item(\'content\', \'categories/edit_content\');">
 						<div class="cms-btn-image">
 							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/content_mode.png" width="15" height="16" alt=""/>
 						</div>
 						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_EditContent', false, true) . ' '.(defined('DEBUG_MODE') && DEBUG_MODE ? " - #{$num}" : '').'</div>
 					</div>
 					<div class="cms-btn-content">';
 
 			$edit_form = '<form method="POST" style="display: inline; margin: 0px" name="kf_cont_'.$content->GetID().'" id="kf_cont_'.$content->GetID().'" action="'.$edit_url.'">';
 			$edit_form .= '<input type="hidden" name="c_id" value="'.$page->GetID().'"/>';
 			$edit_form .= '<input type="hidden" name="content_id" value="'.$content->GetID().'"/>';
 			$edit_form .= '<input type="hidden" name="front" value="1"/>';
 			$edit_form .= '<input type="hidden" name="bgcolor" value="'.$bg_color.'"/>';
 			$edit_form .= '<input type="hidden" name="m_lang" value="'.$this->Application->GetVar('m_lang').'"/>';
 			$edit_form .= '</form>';
 
 			$edit_code_after = '</div></div>';
 
 			if (array_key_exists('forms_later', $params) && $params['forms_later']) {
 				$all_forms = $this->Application->GetVar('all_forms');
 				$this->Application->SetVar('all_forms', $all_forms . $edit_form);
 			}
 			else {
 				$edit_code_after .= $edit_form;
 			}
 		}
 
 		if ($this->Application->GetVar('_editor_preview_') == 1) {
 			$data = $this->Application->RecallVar('_editor_preview_content_');
 		} else {
 			$data = $content->GetField('Content');
 		}
 
 		$data = $edit_code_before . $this->_transformContentBlockData($data, $params) . $edit_code_after;
 
 		if ($data != '') {
 			$this->Application->Parser->DataExists = true;
 		}
 
 		return $data;
 	}
 
 	/**
 	 * Apply all kinds of content block data transformations without rewriting ContentBlock tag
 	 *
 	 * @param string $data
 	 * @return string
 	 */
 	function _transformContentBlockData(&$data, $params)
 	{
 		return $data;
 	}
 
 	/**
 	 * Returns current page name or page based on page/page_id parameters
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used?
 	 */
 	function PageName($params)
 	{
 		$page =& $this->_getPage($params);
 
 		return $page->GetDBField('Name');
 	}
 
 	/**
 	 * Returns current/given page information
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PageInfo($params)
 	{
 		$page =& $this->_getPage($params);
 
 		switch ($params['type']) {
 			case 'title':
 				$db_field = 'Title';
 				break;
 
 			case 'htmlhead_title':
 				$db_field = 'Name';
 				break;
 
 			case 'meta_title':
 				$db_field = 'MetaTitle';
 				break;
 
 			case 'menu_title':
 				$db_field = 'MenuTitle';
 				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',
 		);
 
 		$css_compressed = $minify_helper->CompressScriptTag(Array ('files' => implode('|', $to_compress)));
 
 		$ret = '<link rel="stylesheet" href="' . $css_compressed . '" type="text/css" media="screen"/>' . "\n";
 
 		if ( EDITING_MODE == EDITING_MODE_DESIGN ) {
 			$ret .= '	<style type="text/css" media="all">
 							div.movable-element .movable-header { cursor: move; }
 						</style>';
 		}
 
 		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery.pack.js"></script>' . "\n";
 		$ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery-ui.custom.min.js"></script>' . "\n";
 
 		$to_compress = Array (
 			$js_url . '/is.js',
 			$js_url . '/application.js',
 			$js_url . '/script.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);
 
 		$this_url = $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', '__NO_REWRITE__' => 1, 'no_amp' => 1));
 		$ret .= "var aTemplateManager = new TemplateManager('" . $edit_template_url . "', '" . $this_url . "', '" . $save_layout_url . "', " . (int)EDITING_MODE . ");\n";
 		$ret .= "var main_title = '" . addslashes( $this->Application->ConfigValue('Site_Name') ) . "';" . "\n";
 
 		$use_popups = (int)$this->Application->ConfigValue('UsePopups');
 		$ret .= "var \$use_popups = " . ($use_popups > 0 ? 'true' : 'false') . ";\n";
 		$ret .= "var \$modal_windows = " . ($use_popups == 2 ? 'true' : 'false') . ";\n";
 
 		if ( EDITING_MODE != EDITING_MODE_BROWSE ) {
 			$ret .= "var base_url = '" . $this->Application->BaseURL() . "';" . "\n";
 			$ret .= 'TB.closeHtml = \'<img src="' . $js_url . '/../img/close_window15.gif" width="15" height="15" style="border-width: 0px;" alt="close"/><br/>\';' . "\n";
 
 			$url_params = Array ('m_theme' => '', 'pass' => 'm', 'm_opener' => 'r', '__NO_REWRITE__' => 1, 'no_amp' => 1);
 			$browse_url = $this->Application->HREF('catalog/catalog', ADMIN_DIRECTORY, $url_params, 'index.php');
 			$browse_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $browse_url);
 
 			$ret .= '
 				var topmost = window.top;
 
 				topmost.document.title = document.title + \' - ' . addslashes($this->Application->Phrase('la_AdministrativeConsole', false)) . '\';
 				t = \'' . $this->Application->GetVar('t') . '\';
 
 				if (window.parent.frames["menu"] != undefined) {
 					if ( $.isFunction(window.parent.frames["menu"].SyncActive) ) {
 						window.parent.frames["menu"].SyncActive("' . $browse_url . '");
 					}
 				}
 			';
 		}
 
 		$ret .= '</script>' . "\n";
 
 		if ( EDITING_MODE != EDITING_MODE_BROWSE ) {
 			// add form, so admin scripts could work
 			$ret .= '<form id="kernel_form" name="kernel_form" enctype="multipart/form-data" method="post" action="' . $browse_url . '">
 						<input type="hidden" name="MAX_FILE_SIZE" id="MAX_FILE_SIZE" value="' . MAX_UPLOAD_SIZE . '" />
 						<input type="hidden" name="sid" id="sid" value="' . $this->Application->GetSID() . '" />
 					</form>';
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Prints "Edit Page" button on cms page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function EditPage($params)
 	{
 		if (!EDITING_MODE) {
 			return '';
 		}
 
 		$display_mode = array_key_exists('mode', $params) ? $params['mode'] : false;
 		$edit_code = '';
 
 		$page =& $this->_getPage($params);
 
 		if (!$page->isLoaded() || (($display_mode != 'end') && (EDITING_MODE == EDITING_MODE_BROWSE))) {
 			// when "EditingScripts" tag is not used, make sure, that scripts are also included
 			return $this->EditingScripts($params);
 		}
 
 		// show "EditPage" button only for pages, that exists in structure
 		if ($display_mode != 'end') {
 			$edit_btn = '';
 
 			if (EDITING_MODE == EDITING_MODE_CONTENT) {
 				$url_params = Array(
 					'pass'			=>	'm,c',
 					'm_opener'		=>	'd',
 					'c_id'			=>	$page->GetID(),
 					'c_mode'		=>	't',
 					'c_event'		=>	'OnEdit',
 					'front'			=>	1,
 					'__URLENCODE__'	=>	1,
 					'__NO_REWRITE__'=>	1,
 					'index_file' => 'index.php',
 				);
 
 				$edit_url = $this->Application->HREF('categories/categories_edit', ADMIN_DIRECTORY, $url_params);
 
 				$edit_btn .= '
 					<div class="cms-section-properties-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onmouseover="window.status=\'' . addslashes($edit_url) . '\'; return true" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'c\', \'categories/categories_edit\');">
 						<div class="cms-btn-image">
 							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png" width="15" height="16" alt=""/>
 						</div>
 						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SectionProperties', false, true) . '</div>
 					</div>' . "\n";
 			} elseif (EDITING_MODE == EDITING_MODE_DESIGN) {
 				$url_params = Array(
 					'pass'			=>	'm,theme,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);
 
 				$edit_btn .= '
 					<div class="cms-layout-btn-container"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . '>
 						<div class="cms-save-layout-btn" onclick="aTemplateManager.saveLayout(); return false;">
 							<div class="cms-btn-image">
 								<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/save_button.gif" width="16" height="16" alt=""/>
 							</div>
 							<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SaveChanges', false, true) . '</div>
 						</div>
 						<div class="cms-cancel-layout-btn" onclick="aTemplateManager.cancelLayout(); return false;">
 							<div class="cms-btn-image">
 								<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/cancel_button.gif" width="16" height="16" alt=""/>
 							</div>
 							<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_Cancel', false, true) . '</div>
 						</div>
 					</div>
 
 					<div class="cms-section-properties-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onmouseover="window.status=\'' . addslashes($edit_url) . '\'; return true" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'theme\', \'themes/file_edit\');">
 						<div class="cms-btn-image">
 							<img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png" width="15" height="16" alt=""/>
 						</div>
 						<div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SectionTemplate', false, true) . '</div>
 					</div>' . "\n";
 			}
 
 			if ($display_mode == 'start') {
 				// button with border around the page
 				$edit_code .= '<div class="cms-section-properties-btn-container">' . $edit_btn . '<div class="cms-btn-content">';
 
 			}
 			else {
 				// button without border around the page
 				$edit_code .= $edit_btn;
 			}
 		}
 
 		if ($display_mode == 'end') {
 			// draw border around the page
 			$edit_code .= '</div></div>';
 		}
 
 		if ($display_mode != 'end') {
 			$edit_code .= '<form method="POST" style="display: inline; margin: 0px" name="kf_'.$page->GetID().'" id="kf_'.$page->GetID().'" action="'.$edit_url.'"></form>';
 
 			// when "EditingScripts" tag is not used, make sure, that scripts are also included
 			$edit_code .= $this->EditingScripts($params);
 		}
 
 		return $edit_code;
 	}
 
 	function _getThemeFileId()
 	{
 		$template = $this->Application->GetVar('t');
 
 		if (!$this->Application->TemplatesCache->TemplateExists($template) && !$this->Application->isAdmin) {
 			$cms_handler =& $this->Application->recallObject($this->Prefix . '_EventHandler');
 			/* @var $cms_handler CategoriesEventHandler */
 
 			$template = ltrim($cms_handler->GetDesignTemplate(), '/');
 		}
 
 		$file_path = dirname($template) == '.' ? '' : '/' . dirname($template);
 		$file_name = basename($template);
 
 		$sql = 'SELECT FileId
 				FROM ' . TABLE_PREFIX . 'ThemeFiles
 				WHERE (ThemeId = ' . (int)$this->Application->GetVar('m_theme') . ') AND (FilePath = ' . $this->Conn->qstr($file_path) . ') AND (FileName = ' . $this->Conn->qstr($file_name . '.tpl') . ')';
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Builds site menu
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CachedMenu($params)
 	{
 		$menu_helper =& $this->Application->recallObject('MenuHelper');
 		/* @var $menu_helper MenuHelper */
 
 		return $menu_helper->menuTag($this->getPrefixSpecial(), $params);
 	}
 
 	/**
 	 * Trick to allow some kind of output formatting when using CachedMenu tag
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function SplitColumn($params)
 	{
 		return $this->Application->GetVar($params['i']) > ceil($params['total'] / $params['columns']);
 	}
 
 	/**
 	 * Returns direct children count of given category
 	 *
 	 * @param Array $params
 	 * @return int
 	 */
 	function HasSubCats($params)
 	{
 		$sql = 'SELECT COUNT(*)
 				FROM ' . TABLE_PREFIX . 'Category
 				WHERE ParentId = ' . $params['cat_id'];
 
 		return $this->Conn->GetOne($sql);
 	}
 
 	/**
 	 * Prints sub-pages of given/current page.
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo This could be reached by using "parent_cat_id" parameter. Only difference here is new block parameter "path". Need to rewrite.
 	 */
 	function PrintSubPages($params)
 	{
 		$list =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List', $params);
 		/* @var $list kDBList */
 
 		$category_id = array_key_exists('category_id', $params) ? $params['category_id'] : $this->Application->GetVar('m_cat_id');
 
 		$list->addFilter('current_pages', TABLE_PREFIX . 'CategoryItems.CategoryId = ' . $category_id);
 		$list->Query();
 		$list->GoFirst();
 
 		$o = '';
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $params['render_as'];
 
 		while (!$list->EOL()) {
 			$block_params['path'] = $list->GetDBField('Path');
 			$o .= $this->Application->ParseBlock($block_params);
 
 			$list->GoNext();
 		}
 
 		return $o;
 	}
 
 	/**
 	 * Builds link for browsing current page on Front-End
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PageBrowseLink($params)
 	{
 		$object =& $this->getObject($params);
+		/* @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,
 			'index_file' => 'index.php'
 		);
 
 		if ($this->Application->ConfigValue('UseModRewrite')) {
 			$url_params['__MOD_REWRITE__'] = 1;
 		}
 
 		return $this->Application->HREF($object->GetDBField('NamedParentPath'), '_FRONT_END_', $url_params);
 	}
 
 	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',
 			'index_file' => 'index.php',
 			'authkey' => $object->GetDBField('DirectLinkAuthKey'),
 		);
 
 		if ($this->Application->ConfigValue('UseModRewrite')) {
 			$url_params['__MOD_REWRITE__'] = 1;
 		}
 
 		return $this->Application->HREF($object->GetDBField('NamedParentPath'), '_FRONT_END_', $url_params);
 	}
 
 	/**
 	 * Builds link to cms page (used?)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ContentPageLink($params)
 	{
 		$object =& $this->getObject($params);
+		/* @var $object kDBItem */
+
 		$params['t'] = $object->GetDBField('NamedParentPath');
 		$params['m_cat_id'] = 0;
 
 		return $this->Application->ProcessParsedTag('m', 'Link', $params);
 	}
 
 	/**
 	 * Prepares cms page description for search result page
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function SearchDescription($params)
 	{
 		$object =& $this->getObject($params);
 		$desc =  $object->GetField('MetaDescription');
 		if (!$desc) {
 			$sql = 'SELECT *
 					FROM ' . TABLE_PREFIX . 'PageContent
 					WHERE PageId = ' . $object->GetID() . ' AND ContentNum = 1';
 			$content = $this->Conn->GetRow($sql);
 
 			if ($content['l'.$this->Application->GetVar('m_lang').'_Content']) {
 				$desc = $content['l'.$this->Application->GetVar('m_lang').'_Content'];
 			}
 			else {
 				$desc = $content['l'.$this->Application->GetDefaultLanguageId().'_Content'];
 			}
 		}
 
 		return mb_substr($desc, 0, 300).(mb_strlen($desc) > 300 ? '...' : '');
 	}
 
 	/**
 	 * Simplified version of "c:CategoryLink" for "c:PrintList"
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used? Needs refactoring.
 	 */
 	function EnterCatLink($params)
 	{
 		$object =& $this->getObject($params);
 
 		$url_params = Array ('pass' => 'm', 'm_cat_id' => $object->GetID());
 		return $this->Application->HREF($params['template'], '', $url_params);
 	}
 
 	/**
 	 * Simplified version of "c:CategoryPath", that do not use blocks for rendering
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Used? Maybe needs to be removed.
 	 */
 	function PagePath($params)
 	{
 		$object =& $this->getObject($params);
 		$path = $object->GetField('CachedNavbar');
 		if ($path) {
 			$items = explode('&|&', $path);
 			array_shift($items);
 			return implode(' -&gt; ', $items);
 		}
 
 		return '';
 	}
 
 	/**
 	 * Returns configuration variable value
 	 *
 	 * @param Array $params
 	 * @return string
 	 * @todo Needs to be replaced with "m:GetConfig" tag; Not used now (were used on structure_edit.tpl).
 	 */
 	function AllowManualFilenames($params)
 	{
 		return $this->Application->ConfigValue('ProjCMSAllowManualFilenames');
 	}
 
 	/**
 	 * Draws path to current page (each page can be link to it)
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function CurrentPath($params)
 	{
 		$block_params = $this->prepareTagParams($params);
 		$block_params['name'] = $block_params['render_as'];
 
 		$object =& $this->Application->recallObject($this->Prefix);
 		/* @var $object kDBItem */
 
 		$category_ids = explode('|', substr($object->GetDBField('ParentPath'), 1, -1));
 
 		$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 		$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 		$language = (int)$this->Application->GetVar('m_lang');
 
 		if (!$language) {
 			$language = 1;
 		}
 
 		$sql = 'SELECT l'.$language.'_Name AS Name, NamedParentPath
 				FROM '.$table_name.'
 				WHERE '.$id_field.' IN ('.implode(',', $category_ids).')';
 		$categories_data = $this->Conn->Query($sql);
 
 		$ret = '';
 		foreach ($categories_data as $index => $category_data) {
 			if ($category_data['Name'] == 'Content') {
 				continue;
 			}
 			$block_params['title'] = $category_data['Name'];
 			$block_params['template'] = preg_replace('/^Content\//i', '', $category_data['NamedParentPath']);
 			$block_params['is_first'] = $index == 1; // because Content is 1st element
 			$block_params['is_last'] = $index == count($categories_data) - 1;
 
 			$ret .= $this->Application->ParseBlock($block_params);
 		}
 
 		return $ret;
 	}
 
 	/**
 	 * Synonim to PrintList2 for "onlinestore" theme
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ListPages($params)
 	{
 		return $this->PrintList2($params);
 	}
 
 	/**
 	 * Returns information about parser element locations in template
 	 *
 	 * @param Array $params
 	 * @return mixed
 	 */
 	function BlockInfo($params)
 	{
 		if (!EDITING_MODE) {
 			return '';
 		}
 
 		$template_helper =& $this->Application->recallObject('TemplateHelper');
 		/* @var $template_helper TemplateHelper */
 
 		return $template_helper->blockInfo( $params['name'] );
 	}
 
 	/**
 	 * Hide all editing tabs except permission tab, when editing "Home" (ID = 0) category
 	 *
 	 * @param Array $params
 	 */
 	function ModifyUnitConfig($params)
 	{
 		$root_category = $this->Application->RecallVar('IsRootCategory_' . $this->Application->GetVar('m_wid'));
 		if (!$root_category) {
 			return ;
 		}
 
 		$edit_tab_presets = $this->Application->getUnitOption($this->Prefix, 'EditTabPresets');
 		$edit_tab_presets['Default'] = Array (
 			'permissions' => $edit_tab_presets['Default']['permissions'],
 		);
 		$this->Application->setUnitOption($this->Prefix, 'EditTabPresets', $edit_tab_presets);
 	}
 
 	/**
 	 * Prints catalog export templates
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function PrintCatalogExportTemplates($params)
 	{
 		$prefixes = explode(',', $params['prefixes']);
 
 		$ret = Array ();
 		foreach ($prefixes as $prefix) {
 			if ($this->Application->prefixRegistred($prefix)) {
 				$module_path = $this->Application->getUnitOption($prefix, 'ModuleFolder') . '/';
 				$module_name = $this->Application->findModule('Path', $module_path, 'Name');
 
 				$ret[$prefix] = mb_strtolower($module_name) . '/export';
 			}
 		}
 
 		$json_helper =& $this->Application->recallObject('JSONHelper');
 		/* @var $json_helper JSONHelper */
 
 		return $json_helper->encode($ret);
 	}
 
 	/**
 	 * Checks, that "view in browse mode" functionality available
 	 *
 	 * @param Array $params
 	 * @return bool
 	 */
 	function BrowseModeAvailable($params)
 	{
 		$valid_special = $params['Special'] != 'user';
 		$not_selector = $this->Application->GetVar('type') != 'item_selector';
 
 		return $valid_special && $not_selector;
 	}
 
 	/**
 	 * Returns a link for editing product
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function ItemEditLink($params)
 	{
 		$object =& $this->getObject();
 		/* @var $object kDBList */
 
 		$edit_template = $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePath') . '/' . $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePrefix') . 'edit';
 
 		$url_params = Array (
 			'm_opener'				=>	'd',
 			$this->Prefix.'_mode'	=>	't',
 			$this->Prefix.'_event'	=>	'OnEdit',
 			$this->Prefix.'_id'		=>	$object->GetID(),
 			'm_cat_id'				=>	$object->GetDBField('ParentId'),
 			'pass'					=>	'all,'.$this->Prefix,
 			'no_pass_through'		=>	1,
 		);
 
 		return $this->Application->HREF($edit_template,'', $url_params);
 	}
 
 	function RelevanceIndicator($params)
 	{
 		$object =& $this->getObject($params);
+		/* @var $object kDBItem */
 
 		$search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
 		$sql = 'SELECT Relevance
 				FROM '.$search_results_table.'
 				WHERE ResourceId = '.$object->GetDBField('ResourceId');
 
     	$percents_off = (int)(100 - (100 * $this->Conn->GetOne($sql)));
     	$percents_off = ($percents_off < 0) ? 0 : $percents_off;
     	if ($percents_off) {
         	$params['percent_off'] = $percents_off;
     		$params['percent_on'] = 100 - $percents_off;
         	$params['name'] = $this->SelectParam($params, 'relevance_normal_render_as,block_relevance_normal');
     	}
     	else {
     		$params['name'] = $this->SelectParam($params, 'relevance_full_render_as,block_relevance_full');
     	}
     	return $this->Application->ParseBlock($params);
 	}
 
 	/**
 	 * Returns list of categories, that have category add/edit permission
 	 *
 	 * @param Array $params
 	 * @return string
 	 */
 	function AllowedCategoriesJSON($params)
 	{
 		if ($this->Application->RecallVar('user_id') == USER_ROOT) {
 			$categories = true;
 		}
 		else {
 			$object =& $this->getObject($params);
 			/* @var $object kDBItem */
 
 			$perm_helper =& $this->Application->recallObject('PermissionsHelper');
 			/* @var $perm_helper kPermissionsHelper */
 
 			$perm_prefix = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix');
 			$categories = $perm_helper->getPermissionCategories($perm_prefix . '.' . ($object->IsNewItem() ? 'ADD' : 'MODIFY'));
 		}
 
 		$json_helper =& $this->Application->recallObject('JSONHelper');
 		/* @var $json_helper JSONHelper */
 
 		return $json_helper->encode($categories);
 	}
 
 	function PageEditable($params)
 	{
 		if ($this->Application->isDebugMode()) {
 			return true;
 		}
 
 		$object =& $this->getObject($params);
 		/* @var $object kDBItem */
 
 		return !$object->GetDBField('Protected');
 	}
 }
\ No newline at end of file
Index: branches/5.2.x/core/units/themes/themes_eh.php
===================================================================
--- branches/5.2.x/core/units/themes/themes_eh.php	(revision 14674)
+++ branches/5.2.x/core/units/themes/themes_eh.php	(revision 14675)
@@ -1,202 +1,202 @@
 <?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 standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		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(&$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);
 
 				$rebuild_event = new kEvent('adm:OnRebuildThemes');
 				$this->Application->HandleEvent($rebuild_event);
 			}
 
 			$this->clearSelectedIDs($event);
 		}
 
 		function setPrimary($id)
 		{
 			$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
 			$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
 
 			$sql = 'UPDATE '.$table_name.'
 					SET PrimaryTheme = 0';
 			$this->Conn->Query($sql);
 
 
 			$sql = 'UPDATE '.$table_name.'
 					SET PrimaryTheme = 1, Enabled = 1
 					WHERE '.$id_field.' = '.$id;
 			$this->Conn->Query($sql);
 		}
 
 		/**
 		 * Set's primary theme (when checkbox used on editing form)
 		 *
 		 * @param kEvent $event
 		 */
 		function 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(&$event)
 		{
 			parent::OnSave($event);
 
 			if ( ($event->status != kEvent::erSUCCESS) || !$event->getEventParam('ids') ) {
 				return ;
 			}
 
 			$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)';
 			$enabled_themes = $this->Conn->GetOne($sql);
 
 			if ( $enabled_themes ) {
 				$rebuild_event = new kEvent('adm:OnRebuildThemes');
 				$this->Application->HandleEvent($rebuild_event);
 			}
 		}
 
 		/**
 		 * 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);
 
 			if (MOD_REWRITE) {
 				$mod_rewrite_helper =& $this->Application->recallObject('ModRewriteHelper');
 				/* @var $mod_rewrite_helper kModRewriteHelper */
 
 				$mod_rewrite_helper->removePages();
 			}
 
 			$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(&$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.2.x/core/units/translator/translator_event_handler.php
===================================================================
--- branches/5.2.x/core/units/translator/translator_event_handler.php	(revision 14674)
+++ branches/5.2.x/core/units/translator/translator_event_handler.php	(revision 14675)
@@ -1,174 +1,174 @@
 <?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 TranslatorEventHandler extends kDBEventHandler
 	{
 		/**
-		 * Allows to override standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 			$permissions = Array(
 									'OnChangeLanguage'	=>	Array('subitem' => 'add|edit'),
 									'OnSaveAndClose'	=>	Array('subitem' => 'add|edit'),
 							);
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Check permission of item, that being translated
 		 *
 		 * @param kEvent $event
 		 * @return bool
 		 * @access public
 		 */
 		public function CheckPermission(&$event)
 		{
 			list($prefix, $field) = $this->getPrefixAndField($event);
 
 			$top_prefix = $this->Application->GetTopmostPrefix($prefix, true);
 			$event->setEventParam('top_prefix', $top_prefix);
 			return parent::CheckPermission($event);
 		}
 
 
 		/**
 		 * Returns prefix and field being translated
 		 *
 		 * @param kEvent $event
 		 */
 		function getPrefixAndField(&$event)
 		{
 			$field = $this->Application->GetVar($event->getPrefixSpecial(true).'_field');
 
 			if (strpos($field,':') !== false) {
 				list($prefix, $field) = explode(':', $field);
 			}
 			else {
 				$prefix = $this->Application->GetVar($event->getPrefixSpecial(true).'_prefix');
 			}
 			return Array($prefix, $field);
 		}
 
 		/**
 		 * Loads record to be translated
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnLoad(&$event)
 		{
 			list($obj_prefix, $field) = $this->getPrefixAndField($event);
 
 			$object =& $this->Application->recallObject($obj_prefix);
 			/* @var $object kDBItem */
 
 			$translator =& $event->getObject();
 			/* @var $translator kDBItem */
 
 			$def_lang = $this->Application->GetDefaultLanguageId();
 
 			$current_lang = $translator->GetDBField('Language');
 			if (!$current_lang) $current_lang = $this->Application->RecallVar('trans_lang');
 			if (!$current_lang) $current_lang = $this->Application->GetVar('m_lang');
 			/*if ($current_lang == $def_lang) {
 				$current_lang = $def_lang + 1;
 			}*/
 			$this->Application->StoreVar('trans_lang', $current_lang); //remember translation language for user friendlyness
 
 			$translator->SetID(1);
 			$translator->SetDBField('Original', $object->GetDBField('l'.$this->Application->GetVar('m_lang').'_'.$field));
 			$translator->SetDBField('Language', $current_lang);
 			$translator->SetDBField('SwitchLanguage', $current_lang);
 
 			$translator->SetDBField('Translation', $object->GetDBField('l'.$current_lang.'_'.$field));
 
 			$cur_lang =& $this->Application->recallObject('lang.current');
 			/* @var $cur_lang LanguagesItem */
 
 			$cur_lang->Load($current_lang);
 
 			$translator->SetDBField('Charset', $cur_lang->GetDBField('Charset'));
 
 			$event->redirect = false;
 		}
 
 		/**
 		 * Saves changes into temporary table and closes editing window
-		 * 
+		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnSaveAndClose(&$event)
 		{
 			$event->CallSubEvent('OnPreSave');
 
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		/**
 		 * 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(&$event)
 		{
 			$translator =& $event->getObject();
 			/* @var $translator kDBItem */
 
 			$translator->SetFieldsFromHash( $this->getSubmittedFields($event) );
 
 			list($obj_prefix, $field) = $this->getPrefixAndField($event);
 
 			$object =& $this->Application->recallObject($obj_prefix);
 			/* @var $object kDBItem */
 
 			$lang = $translator->GetDBField('Language');
 
 			$object->SetFieldOptions('l' . $lang . '_' . $field, Array ());
 			$object->SetDBField('l' . $lang . '_' . $field, $translator->GetDBField('Translation'));
 			$this->RemoveRequiredFields($object);
 			$object->Update();
 		}
 
 		/**
 		 * Changes current language in translation popup
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnChangeLanguage(&$event)
 		{
 			$event->CallSubEvent('OnPreSave');
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('Language', $object->GetDBField('SwitchLanguage'));
 
 			$event->CallSubEvent('OnLoad');
 			$event->redirect = false;
 		}
 
 	}
\ No newline at end of file
Index: branches/5.2.x/core/units/mailing_lists/mailing_list_eh.php
===================================================================
--- branches/5.2.x/core/units/mailing_lists/mailing_list_eh.php	(revision 14674)
+++ branches/5.2.x/core/units/mailing_lists/mailing_list_eh.php	(revision 14675)
@@ -1,330 +1,330 @@
 <?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 standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnCancelMailing' => Array ('self' => 'edit'),
 				'OnGenerateEmailQueue' => Array ('self' => true),
 				'OnProcessEmailQueue' => Array ('self' => true),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Prepare recipient list
 		 *
 		 * @param kEvent $event
 		 */
 		function OnNew(&$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);
 			}
 		}
 
 		/**
 		 * Don't allow to delete mailings in progress
 		 *
 		 * @param kEvent $event
 		 * @param string $type
 		 * @return void
 		 * @access protected
 		 */
 		protected function customProcessing(&$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');
 
 					$sql = 'SELECT ' . $id_field . '
 							FROM ' . $table_name . '
 							WHERE ' . $id_field . ' IN (' . implode(',', $ids) . ') AND Status <> ' . MailingList::PARTIALLY_PROCESSED;
 					$allowed_ids = $this->Conn->GetCol($sql);
 
 					$event->setEventParam('ids', $allowed_ids);
 				}
 			}
 		}
 
 		/**
 		 * Delete all realted mails in email queue
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemDelete(&$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(&$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));
 			}
 
 			if ( !$object->GetDBField('MessageText') ) {
 				$object->setRequired('MessageHtml');
 			}
 
 			// 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);
 		}
 
 		/**
 		 * Deletes mailing list email queue, when it becomes cancelled
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemUpdate(&$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') . '
 					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');
 
 			$where_clause = Array (
 				'Status NOT IN (' . MailingList::CANCELLED . ',' . MailingList::PROCESSED . ')',
 				'(EmailsQueued < 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),
 					'EmailsQueued' => $mailing_data['EmailsQueued'] + $process_count,
 				);
 				$mailing_data = array_merge($mailing_data, $updated_fields);
 				$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');
 
 			// 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);
 		}
 	}
\ No newline at end of file
Index: branches/5.2.x/core/units/favorites/favorites_eh.php
===================================================================
--- branches/5.2.x/core/units/favorites/favorites_eh.php	(revision 14674)
+++ branches/5.2.x/core/units/favorites/favorites_eh.php	(revision 14675)
@@ -1,104 +1,104 @@
 <?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 standart permission mapping
+	 * Allows to override standard permission mapping
 	 *
 	 */
 	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');
 		$parent_object =& $this->Application->recallObject($parent_prefix);
 		/* @var $parent_object kDBItem */
 
 		if (!$parent_object->isLoaded() || !$this->Application->CheckPermission('FAVORITES', 0, $parent_object->GetDBField('ParentPath'))) {
 			$event->status = kEvent::erPERM_FAIL;
 			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').')';
 		$favorite_id = $this->Conn->GetOne($sql);
 
 		$object =& $event->getObject(Array('skip_autoload' => true));
 		/* @var $object kDBItem */
 
 		if ($favorite_id) {
 			$object->Delete($favorite_id);
 		}
 		else {
 			$object->Create();
 		}
 
 		$event->SetRedirectParam('pass', 'm,'.$parent_prefix);
 	}
 
 	/**
 	 * Prepares Favorite record fields
 	 *
 	 * @param kEvent $event
 	 * @return void
 	 * @access protected
 	 */
 	protected function 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_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'));
 	}
 
 	/**
 	 * [HOOK] Deletes favorite record to item, that is beeing deleted
 	 *
 	 * @param kEvent $event
 	 */
 	function OnDeleteFavoriteItem(&$event)
 	{
 		$main_object =& $event->MasterEvent->getObject();
 
 		$sql = 'DELETE FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').'
 				WHERE ResourceId = '.$main_object->GetDBField('ResourceId');
 		$this->Conn->Query($sql);
 	}
 
 }
\ No newline at end of file
Index: branches/5.2.x/core/units/files/file_eh.php
===================================================================
--- branches/5.2.x/core/units/files/file_eh.php	(revision 14674)
+++ branches/5.2.x/core/units/files/file_eh.php	(revision 14675)
@@ -1,107 +1,107 @@
 <?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 FileEventHandler extends kDBEventHandler {
 
 		/**
-		 * Allows to override standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 			$permissions = Array(
 				'OnDownloadFile'	=>	Array('subitem' => 'view'),
 			);
 
 			$this->permMapping = array_merge($this->permMapping, $permissions);
 		}
 
 		/**
 		 * Remembers user, who is created file record. Makes file primary if no other files are uploaded.
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(&$event)
 		{
 			parent::OnBeforeItemCreate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
 		}
 
 		/**
 		 * Resets primary file mark when more then one file is marked as primary
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(&$event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			if ( !$object->GetDBField('FileName') ) {
 				$object->SetDBField('FileName', basename($object->GetDBField('FilePath')));
 			}
 		}
 
 		/**
 		 * Apply any custom changes to list's sql query
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function SetCustomQuery(&$event)
 		{
 			parent::SetCustomQuery($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBList */
-			
+
 			if (!$this->Application->isAdminUser) {
 				$object->addFilter('active_filter', '%1$s.Status = '.STATUS_ACTIVE);
 			}
 		}
 
 		/**
 		 * Returns file contents associated with item
 		 *
 		 * @param kEvent $event
 		 */
 		function OnDownloadFile(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$file_helper =& $this->Application->recallObject('FileHelper');
 			/* @var $file_helper FileHelper */
 
 			$filename = $object->GetField('FilePath', 'full_path');
 			$file_helper->DownloadFile($filename);
 
 			$event->status = kEvent::erSTOP;
 		}
 	}
\ No newline at end of file
Index: branches/5.2.x/core/units/selectors/selectors_event_handler.php
===================================================================
--- branches/5.2.x/core/units/selectors/selectors_event_handler.php	(revision 14674)
+++ branches/5.2.x/core/units/selectors/selectors_event_handler.php	(revision 14675)
@@ -1,436 +1,436 @@
 <?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 standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		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(&$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(&$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(&$event)
 		{
 			parent::OnBeforeItemUpdate($event);
 
 			$this->SerializeSelectorData($event);
 		}
 
 		/**
 		 * Occurs before creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(&$event)
 		{
 			parent::OnBeforeItemCreate($event);
-			
+
 			$this->SerializeSelectorData($event);
 		}
 
 		/**
 		 * Enter description here...
 		 *
 		 * @param kEvent $event
 		 */
 		function OnAfterItemUpdate(&$event)
 		{
 			$this->UnserializeSelectorData($event);
 		}
 
 		/**
 		 * Occurs after creating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnAfterItemCreate(&$event)
 		{
 			parent::OnAfterItemCreate($event);
 
 			$this->UnserializeSelectorData($event);
 		}
 
 		/**
 		 * Gets special of main item for linking with sub-item
 		 *
 		 * @param kEvent $event
 		 * @return string
 		 */
 		function getMainSpecial(&$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);
 					$object->Create();
 
 					$this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID());
 				}
 				else {
 					$object->Load($id);
 					$object->SetFieldsFromHash($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(&$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;
 
 			$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
 		 */
 		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
 		 */
 		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 */
 
 			$object->SetFieldsFromHash( $this->getSubmittedFields($event) );
 			$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.2.x/core/units/forms/forms/forms_eh.php
===================================================================
--- branches/5.2.x/core/units/forms/forms/forms_eh.php	(revision 14674)
+++ branches/5.2.x/core/units/forms/forms/forms_eh.php	(revision 14675)
@@ -1,622 +1,622 @@
 <?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 standart permission mapping
+	 * Allows to override standard permission mapping
 	 *
 	 */
 	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)
 	{
 		if (defined('IS_INSTALL') && IS_INSTALL) {
 			// skip any processing, because Forms table doesn't exists until install is finished
 			return ;
 		}
 
 		$forms = $this->getForms();
 
 		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,
 		);
 
 		$priority = 1;
 		$sections = $this->Application->getUnitOption($event->Prefix, 'Sections');
 
 		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);
 	}
 
 	function getForms()
 	{
 		$cache_key = 'forms[%FormSerial%]';
 		$forms = $this->Application->getCache($cache_key);
 
 		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(&$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(&$event)
 	{
 		parent::OnMassDelete($event);
 
 		if ( $event->status == kEvent::erSUCCESS ) {
 			$this->_deleteSectionCache();
 		}
 	}
 
 	function _deleteSectionCache()
 	{
 		$reset_event = new kEvent('adm:OnResetSections');
 		$this->Application->HandleEvent($reset_event);
 
 		$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'));
 		$required_fields = explode(',', $this->Application->GetVar('required_fields'));
 		$fields_params = $this->Application->GetVar('fields_params');
 
 		$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
 
 		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])) {
 				$field_values[$checkbox] = 1;
 			}
 			else {
 				$field_values[$checkbox] = '0';
 			}
 		}
 
 		$object->SetFieldsFromHash($field_values);
 
 		if ( $object->Validate() ) {
 			$event->redirect = $this->Application->GetVar('success_template');
 			$this->Application->EmailEventAdmin($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->EmailEventUser($this->Application->GetVar('email_event'), null, $send_params);
 
 			if ( $field_values['MailingList'] ) {
 				$this->Application->StoreVar('SubscriberEmail', $field_values['Email']);
 				$this->Application->HandleEvent($sub_event, '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(&$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(&$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
 	 */
 	function _processMailbox(&$event, $bounce_mode = false)
 	{
 		$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 . '
 				WHERE EnableEmailCommunication = 1';
 		$forms = $this->Conn->Query($sql, $id_field);
 
 		$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) {
 				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']) {
 			// mark original message as bounced
 
 			$mailbox_helper =& $this->Application->recallObject('MailboxHelper');
 			/* @var $mailbox_helper MailboxHelper */
 
 			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']) {
 					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'];
 						$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)) {
 			// incorrect subject, no verification code
 			$form_info = $params['form_info'];
 
 			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') . '
 						WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']);
 				$found = $this->Conn->GetOne($sql);
 
 				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 ($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') . '
 				WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']);
 		$found = $this->Conn->GetOne($sql);
 
 		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()) {
 			// fake verification code OR feedback, containing submission log was deleted
 			return false;
 		}
 
 		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.2.x/core/units/user_profile/user_profile_eh.php
===================================================================
--- branches/5.2.x/core/units/user_profile/user_profile_eh.php	(revision 14674)
+++ branches/5.2.x/core/units/user_profile/user_profile_eh.php	(revision 14675)
@@ -1,87 +1,87 @@
 <?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 standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		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
 		 */
 		function OnUpdate(&$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');
 
 			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, kUtil::unhtmlentities($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.2.x/core/units/languages/languages_event_handler.php
===================================================================
--- branches/5.2.x/core/units/languages/languages_event_handler.php	(revision 14674)
+++ branches/5.2.x/core/units/languages/languages_event_handler.php	(revision 14675)
@@ -1,667 +1,667 @@
 <?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 standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		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(&$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
 		 */
 		function getPassedID(&$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 */
 
 			$this->Application->UnitConfigReader->ReReadConfigs();
 			foreach ($this->Application->UnitConfigReader->configData as $prefix => $config_data) {
 				$ml_helper->createFields($prefix);
 			}
 
 			$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
 		 */
 		function OnAfterConfigRead(&$event)
 		{
 			parent::OnAfterConfigRead($event);
 
 			$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
 
 			// 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;
 			}
 
 			$options = $fields['InputTimeFormat']['options'];
 			if ($options) {
 				foreach ($options as $i => $v) {
 					$options[$i] = $v . ' (' . adodb_date($i) . ')';
 				}
 				$fields['InputTimeFormat']['options'] = $options;
 			}
 
 			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
 		}
 
 		/**
 		 * Occurs before updating item
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemUpdate(&$event)
 		{
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$status_fields = $this->Application->getUnitOption($event->Prefix, 'StatusField');
 			$status_field = array_shift($status_fields);
 
 			if ( $object->GetDBField('PrimaryLang') == 1 && $object->GetDBField($status_field) == 0 ) {
 				$object->SetDBField($status_field, 1);
 			}
 		}
 
 		/**
 		 * Shows only enabled languages on front
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 * @see kDBEventHandler::OnListBuild()
 		 */
 		protected function 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(&$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(&$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('emailevents');
 
 			foreach ($pending_actions as $src_language => $dst_language) {
 				// phrases import
 				$sql = 'UPDATE ' . $this->Application->getUnitOption('phrases', 'TableName') . '
 						SET l' . $dst_language . '_Translation = l' . $src_language . '_Translation';
 				$this->Conn->Query($sql);
 
 				// events import
 				$sql = 'UPDATE ' . $this->Application->getUnitOption('emailevents', 'TableName') . '
 						SET
 							l' . $dst_language . '_Subject = l' . $src_language . '_Subject,
 							l' . $dst_language . '_Body = l' . $src_language . '_Body';
 				$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(&$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') . '
 					WHERE PrimaryLang = 1';
 			$primary_lang_id = $this->Conn->GetOne($sql);
 
 			$object->SetDBField('CopyFromLanguage', $primary_lang_id);
 			$object->SetDBField('SynchronizationModes', Language::SYNCHRONIZE_DEFAULT);
 		}
 
 		/**
 		 * Sets new language mark
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeDeleteFromLive(&$event)
 		{
 			parent::OnBeforeDeleteFromLive($event);
 
 			$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
 
 			$sql = 'SELECT ' . $id_field . '
 					FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
 					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');
 
 			if ($this->Application->isAdmin) {
 				// admin data only
 				$this->Application->SetVar('m_lang', $language_id);
 
 				// set new language for this session (admin interface only)
 				$this->Application->Session->SetField('Language', $language_id);
 
 				// remember last user language in administrative console
 				if ($this->Application->RecallVar('user_id') == USER_ROOT) {
 					$this->Application->StorePersistentVar('AdminLanguage', $language_id);
 				}
 				else {
 					$object =& $this->Application->recallObject('u.current');
 					/* @var $object kDBItem */
 
 					$object->SetDBField('AdminLanguage', $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);
 			}
 			else {
 				// changing language on Front-End
 				$this->Application->SetVar('m_lang', $language_id);
 
 				if (MOD_REWRITE) {
 					$mod_rewrite_helper =& $this->Application->recallObject('ModRewriteHelper');
 					/* @var $mod_rewrite_helper kModRewriteHelper */
 
 					$mod_rewrite_helper->removePages();
 				}
 			}
 		}
 
 		/**
 		 * 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);
 
 				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 */
 
 				$language_import_helper->performImport(
 					$filename,
 					$object->GetDBField('PhraseType'),
 					$object->GetDBField('Module'),
 					$object->GetDBField('ImportOverwrite') ? LANG_OVERWRITE_EXISTING : LANG_SKIP_EXISTING
 				);
 
 				// 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->StoreSelectedIDs($event);
 			$this->Application->StoreVar('export_language_ids', implode(',', $this->getSelectedIDs($event)) );
 
 			$event->setRedirectParams( Array('phrases.export_event' => 'OnNew', 'pass' => 'all,phrases.export') );
 		}
 
 		/**
 		 * 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);
 
 				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');
 				}
 
 				$language_import_helper->setExportLimits($field_values['ExportPhrases'], $field_values['ExportEmailEvents']);
 
 				$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
 		 */
 		function OnGoBack(&$event)
 		{
 			$event->SetRedirectParam('opener', 'u');
 		}
 
 		function OnScheduleTopFrameReload(&$event)
 		{
 			$this->Application->StoreVar('RefreshTopFrame',1);
 		}
 
 		/**
 		 * Do now allow deleting current language
 		 *
 		 * @param kEvent $event
 		 */
 		function 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
 		 */
 		function OnAfterItemDelete(&$event)
 		{
 			parent::OnAfterItemDelete($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			// clean Events table
 			$fields_hash = Array (
 				'l' . $object->GetID() . '_Subject' => NULL,
 				'l' . $object->GetID() . '_Body' => NULL,
 			);
 			$this->Conn->doUpdate($fields_hash, $this->Application->getUnitOption('emailevents', 'TableName'), 1);
 
 			// clean Phrases table
 			$fields_hash = Array (
 				'l' . $object->GetID() . '_Translation' => NULL,
 			);
 			$this->Conn->doUpdate($fields_hash, $this->Application->getUnitOption('phrases', 'TableName'), 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 . 'Language
 					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 . 'Phrase
 							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.2.x/core/units/permissions/permissions_event_handler.php
===================================================================
--- branches/5.2.x/core/units/permissions/permissions_event_handler.php	(revision 14674)
+++ branches/5.2.x/core/units/permissions/permissions_event_handler.php	(revision 14675)
@@ -1,257 +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 PermissionsEventHandler extends kDBEventHandler {
 
 	/**
-	 * Allows to override standart permission mapping
+	 * Allows to override standard permission mapping
 	 *
 	 */
 	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');
 			}
 
 			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
 	 */
 	function 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.2.x/core/units/email_events/email_events_event_handler.php
===================================================================
--- branches/5.2.x/core/units/email_events/email_events_event_handler.php	(revision 14674)
+++ branches/5.2.x/core/units/email_events/email_events_event_handler.php	(revision 14675)
@@ -1,1136 +1,1136 @@
 <?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 EmailEventsEventsHandler extends kDBEventHandler
 	{
 		/**
-		 * Allows to override standart permission mapping
+		 * Allows to override standard permission mapping
 		 *
 		 */
 		function mapPermissions()
 		{
 			parent::mapPermissions();
 
 			$permissions = Array (
 				'OnFrontOnly' => Array ('self' => 'edit'),
 				'OnSaveSelected' => Array ('self' => 'view'),
 				'OnProcessEmailQueue' => Array ('self' => 'add|edit'),
 
 				'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'),
 			);
 
 			$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(&$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');
 
 				$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(&$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));
 			}
 
 			if (!$event->Special && !$this->Application->isDebugMode()) {
 				// no special
 				$object->addFilter('enabled_filter', '%1$s.Enabled <> ' . STATUS_DISABLED);
 			}
 		}
 
 		/**
 		 * Set default headers
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnPreCreate(&$event)
 		{
 			parent::OnPreCreate($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			$object->SetDBField('Headers', $this->Application->ConfigValue('Smtp_DefaultHeaders'));
 		}
 
 		/**
 		 * 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 ;
 			}
 
 			$ids = implode(',', $this->StoreSelectedIDs($event));
 
 			$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
 			$sql = 'UPDATE '.$table_name.'
 					SET FrontEndOnly = 1
 					WHERE EventId IN ('.$ids.')';
 			$this->Conn->Query($sql);
 
 			$this->clearSelectedIDs($event);
 		}
 
 		/**
 		 * Sets selected user to email events selected
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSelectUser(&$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) {
 				$user_id = array_shift( array_keys($items_info) );
 
 				$selected_ids = $this->getSelectedIDs($event, true);
 
 				$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.'
 						SET '.$this->Application->RecallVar('dst_field').' = '.$user_id.'
 						WHERE '.$id_field.' IN ('.$ids.')';
 				$this->Conn->Query($sql);
 			}
 
 			$this->finalizePopup($event);
 		}
 
 		/**
 		 * Saves selected ids to session
 		 *
 		 * @param kEvent $event
 		 */
 		function OnSaveSelected(&$event)
 		{
 			$this->StoreSelectedIDs($event);
 		}
 
 		/**
 		 * Returns email event object based on given kEvent object
 		 *
 		 * @param kEvent $event
 		 * @return kDBItem
 		 */
 		function &_getEmailEvent(&$event)
 		{
 			$false = false;
 			$name = $event->getEventParam('EmailEventName');
 			$type = $event->getEventParam('EmailEventType');
 
 			$object =& $event->getObject( Array('skip_autoload' => true) );
 			/* @var $object kDBItem */
 
 			if (!$object->isLoaded() || ($object->GetDBField('Event') != $name || $object->GetDBField('Type') != $type)) {
 				// get event parameters by name & type
 				$load_keys = Array ('Event' => $name, 'Type' => $type);
 				$object->Load($load_keys);
 
 				if (!$object->isLoaded() || ($object->GetDBField('Enabled') == STATUS_DISABLED)) {
 					// event record not found OR is disabled
 					return $false;
 				}
 
 				if ($object->GetDBField('FrontEndOnly') && $this->Application->isAdmin) {
 					return $false;
 				}
 			}
 
 			return $object;
 		}
 
 		/**
 		 * Processes email sender
 		 *
 		 * @param kEvent $event
 		 * @param Array $direct_params
 		 */
 		function _processSender(&$event, $direct_params = Array ())
 		{
 			$this->Application->removeObject('u.email-from');
 
 			$object =& $this->_getEmailEvent($event);
 			/* @var $object kDBItem */
 
 			$email = $name = '';
 
 			// set defaults from event
 			if ($object->GetDBField('CustomSender')) {
 				$address = $object->GetDBField('SenderAddress');
 				$address_type = $object->GetDBField('SenderAddressType');
 
 				switch ($address_type) {
 					case EmailEvent::ADDRESS_TYPE_EMAIL:
 						$email = $address;
 						break;
 
 					case EmailEvent::ADDRESS_TYPE_USER:
 						$sql = 'SELECT FirstName, LastName, Email, PortalUserId
 								FROM ' . TABLE_PREFIX . 'PortalUser
 								WHERE Login = ' . $this->Conn->qstr($address);
 						$user_info = $this->Conn->GetRow($sql);
 
 						if ($user_info) {
 							// user still exists
 							$email = $user_info['Email'];
 							$name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']);
 
 							$user =& $this->Application->recallObject('u.email-from', null, Array('skip_autoload' => true));
 							/* @var $user UsersItem */
 
 							$user->Load($user_info['PortalUserId']);
 						}
 						break;
 				}
 
 				if ($object->GetDBField('SenderName')) {
 					$name = $object->GetDBField('SenderName');
 				}
 			}
 
 			// update with custom data given during event execution
 			if (array_key_exists('from_email', $direct_params)) {
 				$email = $direct_params['from_email'];
 			}
 
 			if (array_key_exists('from_name', $direct_params)) {
 				$name = $direct_params['from_name'];
 			}
 
 			// still nothing, set defaults
 			if (!$email) {
 				$email = $this->Application->ConfigValue('Smtp_AdminMailFrom');
 			}
 
 			if (!$name) {
 				$name = strip_tags( $this->Application->ConfigValue('Site_Name') );
 			}
 
 			$esender =& $this->Application->recallObject('EmailSender');
 			/* @var $esender kEmailSendingHelper */
 
 			$esender->SetFrom($email, $name);
 
 			return Array ($email, $name);
 		}
 
 		/**
 		 * Processes email recipients
 		 *
 		 * @param kEvent $event
 		 * @param Array $direct_params
 		 */
 		function _processRecipients(&$event, $direct_params = Array ())
 		{
 			$this->Application->removeObject('u.email-to');
 
 			$object =& $this->_getEmailEvent($event);
 			/* @var $object kDBItem */
 
 			$to_email = $to_name = '';
 			$all_recipients = Array ();
 			$recipients_xml = $object->GetDBField('Recipients');
 
 			if ($recipients_xml) {
 				$minput_helper =& $this->Application->recallObject('MInputHelper');
 				/* @var $minput_helper MInputHelper */
 
 				// group recipients by type
 				$records = $minput_helper->parseMInputXML($recipients_xml);
 
 				foreach ($records as $record) {
 					$recipient_type = $record['RecipientType'];
 
 					if (!array_key_exists($recipient_type, $all_recipients)) {
 						$all_recipients[$recipient_type] = Array ();
 					}
 
 					$all_recipients[$recipient_type][] = $record;
 				}
 			}
 
 			if (!array_key_exists(EmailEvent::RECIPIENT_TYPE_TO, $all_recipients)) {
 				$all_recipients[EmailEvent::RECIPIENT_TYPE_TO] = Array ();
 			}
 
 			// remove all "To" recipients, when not allowed
 			$overwrite_to_email = array_key_exists('overwrite_to_email', $direct_params) ? $direct_params['overwrite_to_email'] : false;
 
 			if (!$object->GetDBField('CustomRecipient') || $overwrite_to_email) {
 				$all_recipients[EmailEvent::RECIPIENT_TYPE_TO] = Array ();
 			}
 
 			// update with custom data given during event execution (user_id)
 			$to_user_id = $event->getEventParam('EmailEventToUserId');
 
 			if ($to_user_id > 0) {
 				$sql = 'SELECT FirstName, LastName, Email
 						FROM ' . TABLE_PREFIX . 'PortalUser
 						WHERE PortalUserId = ' . $to_user_id;
 				$user_info = $this->Conn->GetRow($sql);
 
 				if ($user_info) {
 					$add_recipient = Array (
 						'RecipientAddressType' => EmailEvent::ADDRESS_TYPE_EMAIL,
 						'RecipientAddress' => $user_info['Email'],
 						'RecipientName' => trim($user_info['FirstName'] . ' ' . $user_info['LastName']),
 					);
 
 					array_unshift($all_recipients[EmailEvent::RECIPIENT_TYPE_TO], $add_recipient);
 
 					$user =& $this->Application->recallObject('u.email-to', null, Array('skip_autoload' => true));
 					/* @var $user UsersItem */
 
 					$user->Load($to_user_id);
 				}
 			}
 			elseif (is_numeric($to_user_id)) {
 				// recipient is system user with negative ID (root, guest, etc.) -> send to admin
 				array_unshift($all_recipients[EmailEvent::RECIPIENT_TYPE_TO], $this->_getDefaultRepipient());
 			}
 
 			// update with custom data given during event execution (email + name)
 			$add_recipient = Array ();
 
 			if (array_key_exists('to_email', $direct_params)) {
 				$add_recipient['RecipientName'] = '';
 				$add_recipient['RecipientAddressType'] = EmailEvent::ADDRESS_TYPE_EMAIL;
 				$add_recipient['RecipientAddress'] = $direct_params['to_email'];
 			}
 
 			if (array_key_exists('to_name', $direct_params)) {
 				$add_recipient['RecipientName'] = $direct_params['to_name'];
 			}
 
 			if ($add_recipient) {
 				array_unshift($all_recipients[EmailEvent::RECIPIENT_TYPE_TO], $add_recipient);
 			}
 
 			if (($object->GetDBField('Type') == EmailEvent::EVENT_TYPE_ADMIN) && !$all_recipients[EmailEvent::RECIPIENT_TYPE_TO]) {
 				// admin email event without direct recipient -> send to admin
 				array_unshift($all_recipients[EmailEvent::RECIPIENT_TYPE_TO], $this->_getDefaultRepipient());
 			}
 
 			$esender =& $this->Application->recallObject('EmailSender');
 			/* @var $esender kEmailSendingHelper */
 
 			$header_mapping = Array (
 				EmailEvent::RECIPIENT_TYPE_TO => 'To',
 				EmailEvent::RECIPIENT_TYPE_CC => 'Cc',
 				EmailEvent::RECIPIENT_TYPE_BCC => 'Bcc',
 			);
 
 			$default_email = $this->Application->ConfigValue('Smtp_AdminMailFrom');
 
 			foreach ($all_recipients as $recipient_type => $recipients) {
 				// add recipients to email
 				$pairs = Array ();
 
 				foreach ($recipients as $recipient) {
 					$address = $recipient['RecipientAddress'];
 					$address_type = $recipient['RecipientAddressType'];
 					$repipient_name = $recipient['RecipientName'];
 
 					switch ($address_type) {
 						case EmailEvent::ADDRESS_TYPE_EMAIL:
 							$pairs[] = Array ('email' => $address, 'name' => $repipient_name);
 							break;
 
 						case EmailEvent::ADDRESS_TYPE_USER:
 							$sql = 'SELECT FirstName, LastName, Email
 									FROM ' . TABLE_PREFIX . 'PortalUser
 									WHERE Login = ' . $this->Conn->qstr($address);
 							$user_info = $this->Conn->GetRow($sql);
 
 							if ($user_info) {
 								// user still exists
 								$name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']);
 
 								$pairs[] = Array (
 									'email' => $user_info['Email'],
 									'name' => $name ? $name : $repipient_name,
 								);
 							}
 							break;
 
 						case EmailEvent::ADDRESS_TYPE_GROUP:
 							$sql = 'SELECT u.FirstName, u.LastName, u.Email
 									FROM ' . TABLE_PREFIX . 'PortalGroup g
 									JOIN ' . TABLE_PREFIX . 'UserGroup ug ON ug.GroupId = g.GroupId
 									JOIN ' . TABLE_PREFIX . 'PortalUser u ON u.PortalUserId = ug.PortalUserId
 									WHERE g.Name = ' . $this->Conn->qstr($address);
 							$users = $this->Conn->Query($sql);
 
 							foreach ($users as $user) {
 								$name = trim($user_info['FirstName'] . ' ' . $user_info['LastName']);
 
 								$pairs[] = Array (
 									'email' => $user_info['Email'],
 									'name' => $name ? $name : $repipient_name,
 								);
 							}
 							break;
 					}
 				}
 
 				if (!$pairs) {
 					continue;
 				}
 
 				if ($recipient_type == EmailEvent::RECIPIENT_TYPE_TO) {
 					$to_email = $pairs[0]['email'] ? $pairs[0]['email'] : $default_email;
 					$to_name = $pairs[0]['name'] ? $pairs[0]['name'] : $to_email;
 				}
 
 				$header_name = $header_mapping[$recipient_type];
 
 				foreach ($pairs as $pair) {
 					$email = $pair['email'] ? $pair['email'] : $default_email;
 					$name = $pair['name'] ? $pair['name'] : $email;
 
 					$esender->AddRecipient($header_name, $email, $name);
 				}
 			}
 
 			return Array ($to_email, $to_name);
 		}
 
 		/**
 		 * This is default recipient, when we can't determine actual one
 		 *
 		 * @return Array
 		 */
 		function _getDefaultRepipient()
 		{
 			return Array (
 				'RecipientName' => $this->Application->ConfigValue('Smtp_AdminMailFrom'),
 				'RecipientAddressType' => EmailEvent::ADDRESS_TYPE_EMAIL,
 				'RecipientAddress' => $this->Application->ConfigValue('Smtp_AdminMailFrom'),
 			);
 		}
 
 		/**
 		 * Returns email event message by ID (headers & body in one piece)
 		 *
 		 * @param kEvent $event
 		 * @param int $language_id
 		 */
 		function _getMessageBody(&$event, $language_id = null)
 		{
 			if (!isset($language_id)) {
 				$language_id = $this->Application->GetVar('m_lang');
 			}
 
 			$object =& $this->_getEmailEvent($event);
 
 			// 1. get message body
 			$message_body = $this->_formMessageBody($object, $language_id, $object->GetDBField('MessageType'));
 
 			// 2. replace tags if needed
 			$default_replacement_tags = Array (
 				'<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',
 			);
 
 			$replacement_tags = $object->GetDBField('ReplacementTags');
 			$replacement_tags = $replacement_tags ? unserialize($replacement_tags) : Array ();
 			$replacement_tags = array_merge($default_replacement_tags, $replacement_tags);
 
 			foreach ($replacement_tags as $replace_from => $replace_to) {
 				$message_body = str_replace($replace_from, $replace_to, $message_body);
 			}
 
 			return $message_body;
 		}
 
 		/**
 		 * Prepare email message body
 		 *
 		 * @param kDBItem $object
 		 * @param int $language_id
 		 * @return string
 		 */
 		function _formMessageBody(&$object, $language_id)
 		{
 			$default_language_id = $this->Application->GetDefaultLanguageId();
 
 			$fields_hash = Array (
 				'Headers' => $object->GetDBField('Headers'),
 			);
 
 			// prepare subject
 			$subject = $object->GetDBField('l' . $language_id . '_Subject');
 
 			if (!$subject) {
 				$subject = $object->GetDBField('l' . $default_language_id . '_Subject');
 			}
 
 			$fields_hash['Subject'] = $subject;
 
 			// prepare body
 			$body = $object->GetDBField('l' . $language_id . '_Body');
 
 			if (!$body) {
 				$body = $object->GetDBField('l' . $default_language_id . '_Body');
 			}
 
 			$fields_hash['Body'] = $body;
 
 			$email_message_helper =& $this->Application->recallObject('EmailMessageHelper');
 			/* @var $email_message_helper EmailMessageHelper */
 
 			$ret = $email_message_helper->buildTemplate($fields_hash);
 
 			// add footer
 			$footer = $this->_getFooter($language_id, $object->GetDBField('MessageType'));
 
 			if ($ret && $footer) {
 				$ret .= "\r\n" . $footer;
 			}
 
 			return $ret;
 		}
 
 		/**
 		 * Returns email footer
 		 *
 		 * @param int $language_id
 		 * @param string $message_type
 		 * @return string
 		 */
 		function _getFooter($language_id, $message_type)
 		{
 			static $footer = null;
 
 			if (!isset($footer)) {
 				$default_language_id = $this->Application->GetDefaultLanguageId();
 
 				$sql = 'SELECT l' . $language_id . '_Body, l' . $default_language_id . '_Body
 	           			FROM ' . $this->Application->getUnitOption('emailevents', 'TableName') . ' em
 	           			WHERE Event = "COMMON.FOOTER"';
 				$footer_data = $this->Conn->GetRow($sql);
 
 				$footer = $footer_data['l' . $language_id . '_Body'];
 
 				if (!$footer) {
 					$footer = $footer_data['l' . $default_language_id . '_Body'];
 				}
 
 				if ($message_type == 'text') {
 					$esender =& $this->Application->recallObject('EmailSender');
 					/* @var $esender kEmailSendingHelper */
 
 					$footer = $esender->ConvertToText($footer);
 				}
 			}
 
 			return $footer;
 		}
 
 		/**
 		 * Parse message template and return headers (as array) and message body part
 		 *
 		 * @param string $message
 		 * @param Array $direct_params
 		 * @return Array
 		 */
 		function ParseMessageBody($message, $direct_params = Array ())
 		{
 			$message_language = $this->_getSendLanguage($direct_params);
 			$this->_changeLanguage($message_language);
 
 			$direct_params['message_text'] = isset($direct_params['message']) ? $direct_params['message'] : ''; // parameter alias
 
 			// 1. parse template
 			$this->Application->InitParser();
 			$parser_params = $this->Application->Parser->Params; // backup parser params
 
 			$this->Application->Parser->SetParams( array_merge($parser_params, $direct_params) );
 
 			$message = implode('&|&', explode("\n\n", $message, 2)); // preserves double \n in case when tag is located in subject field
 			$message = $this->Application->Parser->Parse($message, 'email_template', 0);
 
 			$this->Application->Parser->SetParams($parser_params); // restore parser params
 
 			// 2. replace line endings, that are send with data submitted via request
 			$message = str_replace("\r\n", "\n", $message); // possible case
 			$message = str_replace("\r", "\n", $message); // impossible case, but just in case replace this too
 
 			// 3. separate headers from body
 			$message_headers = Array ();
 			list($headers, $message_body) = explode('&|&', $message, 2);
 
 			$category_helper =& $this->Application->recallObject('CategoryHelper');
 			/* @var $category_helper CategoryHelper */
 
 			$message_body = $category_helper->replacePageIds($message_body);
 
 			$headers = explode("\n", $headers);
 			foreach ($headers as $header) {
 				$header = explode(':', $header, 2);
 				$message_headers[ trim($header[0]) ] = trim($header[1]);
 			}
 
 			$this->_changeLanguage();
 			return Array ($message_headers, $message_body);
 		}
 
 		/**
 		 * Raised when email message should be sent
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnEmailEvent(&$event)
 		{
 			$email_event_name = $event->getEventParam('EmailEventName');
 			if ( strpos($email_event_name, '_') !== false ) {
 				throw new Exception('<span class="debug_error">Invalid email event name</span> <strong>' . $email_event_name . '</strong>. Use only <strong>UPPERCASE characters</strong> and <strong>dots</strong> as email event names');
 			}
 
 			$object =& $this->_getEmailEvent($event);
 
 			if ( !is_object($object) ) {
 				// email event not found OR it's won't be send under given circumstances
 				return ;
 			}
 
 			// additional parameters from kApplication->EmailEvent
 			$send_params = $event->getEventParam('DirectSendParams');
 
 			// 1. get information about message sender and recipient
 			list ($from_email, $from_name) = $this->_processSender($event, $send_params);
 			list ($to_email, $to_name) = $this->_processRecipients($event, $send_params);
 
 			// 2. prepare message to be sent
 			$message_language = $this->_getSendLanguage($send_params);
 			$message_template = $this->_getMessageBody($event, $message_language);
 			if ( !trim($message_template) ) {
 				trigger_error('Message template is empty', E_USER_WARNING);
 				return ;
 			}
 
 			list ($message_headers, $message_body) = $this->ParseMessageBody($message_template, $send_params);
 			if ( !trim($message_body) ) {
 				trigger_error('Message template is empty after parsing', E_USER_WARNING);
 				return ;
 			}
 
 			// 3. set headers & send message
 			$esender =& $this->Application->recallObject('EmailSender');
 			/* @var $esender kEmailSendingHelper */
 
 			$message_subject = isset($message_headers['Subject']) ? $message_headers['Subject'] : 'Mail message';
 			$esender->SetSubject($message_subject);
 
 			if ( $this->Application->isDebugMode() ) {
 				// set special header with event name, so it will be easier to determine what's actually was received
 				$message_headers['X-Event-Name'] = $email_event_name . ' - ' . ($object->GetDBField('Type') == EmailEvent::EVENT_TYPE_ADMIN ? 'ADMIN' : 'USER');
 			}
 
 			foreach ($message_headers as $header_name => $header_value) {
 				$esender->SetEncodedHeader($header_name, $header_value);
 			}
 
 			$esender->CreateTextHtmlPart($message_body, $object->GetDBField('MessageType') == 'html');
 
 			$event->status = $esender->Deliver() ? kEvent::erSUCCESS : kEvent::erFAIL;
 
 			if ( $event->status == kEvent::erSUCCESS ) {
 				// all keys, that are not used in email sending are written to log record
 				$send_keys = Array ('from_email', 'from_name', 'to_email', 'to_name', 'message');
 				foreach ($send_keys as $send_key) {
 					unset($send_params[$send_key]);
 				}
 
 				$fields_hash = Array (
 					'fromuser'		=> $from_name . ' (' . $from_email . ')',
 					'addressto'		=> $to_name . ' (' . $to_email . ')',
 					'subject'		=>	$message_subject,
 					'timestamp'		=>	adodb_mktime(),
 					'event'			=>	$email_event_name,
 					'EventParams'	=>	serialize($send_params),
 				);
 
 				$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'EmailLog');
 			}
 		}
 
 		function _getSendLanguage($send_params)
 		{
 			if (array_key_exists('language_id', $send_params)) {
 				return $send_params['language_id'];
 			}
 
 			return $this->Application->GetVar('m_lang');
 		}
 
 		function _changeLanguage($language_id = null)
 		{
 			static $prev_language_id = null;
 
 			if ( !isset($language_id) ) {
 				// restore language
 				$language_id = $prev_language_id;
 			}
 
 			$this->Application->SetVar('m_lang', $language_id);
 
 			$language =& $this->Application->recallObject('lang.current');
 			/* @var $language LanguagesItem */
 
 			$language->Load($language_id);
 
 			$this->Application->Phrases->LanguageId = $language_id;
 			$this->Application->Phrases->Phrases = Array ();
 
 			$prev_language_id = $language_id; // for restoring it later
 		}
 
 		/**
 		 * 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
 		 */
 		function OnAfterConfigRead(&$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');
 			$fields['Module']['options'] = $options;
 			$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
 
 			if ($this->Application->GetVar('regional')) {
 				$this->Application->setUnitOption($event->Prefix, 'PopulateMlFields', true);
 			}
 		}
 
 		/**
 		 * Prepare temp tables and populate it
 		 * with items selected in the grid
 		 *
 		 * @param kEvent $event
 		 */
 		function OnEdit(&$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(&$event)
 		{
 			parent::OnAfterItemLoad($event);
 
 			$object =& $event->getObject();
 			/* @var $object kDBItem */
 
 			if (!$this->Application->isDebugMode(false)) {
 				if ($object->GetDBField('AllowChangingRecipient')) {
 					$object->SetDBField('RecipientType', EmailEvent::RECIPIENT_TYPE_TO);
 				}
 				else {
 					$object->SetDBField('RecipientType', EmailEvent::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);
 		}
 
 		/**
 		 * Performs custom validation + keep read-only fields
 		 *
 		 * @param kEvent $event
 		 */
 		function _itemChanged(&$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');
 
 			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);
 			}
 		}
 
 		/**
 		 * Validates address using given field prefix
 		 *
 		 * @param kEvent $event
 		 * @param string $field_prefix
 		 */
 		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 EmailEvent::ADDRESS_TYPE_EMAIL:
 					if (!preg_match('/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i', $address)) {
 						$object->SetError($field_prefix . 'Address', 'invalid_email');
 					}
 					break;
 
 				case EmailEvent::ADDRESS_TYPE_USER:
 					$sql = 'SELECT PortalUserId
 							FROM ' . TABLE_PREFIX . 'PortalUser
 							WHERE Login = ' . $this->Conn->qstr($address);
 					if (!$this->Conn->GetOne($sql)) {
 						$object->SetError($field_prefix . 'Address', 'invalid_user');
 					}
 					break;
 
 				case EmailEvent::ADDRESS_TYPE_GROUP:
 					$sql = 'SELECT GroupId
 							FROM ' . TABLE_PREFIX . 'PortalGroup
 							WHERE Name = ' . $this->Conn->qstr($address);
 					if (!$this->Conn->GetOne($sql)) {
 						$object->SetError($field_prefix . 'Address', 'invalid_group');
 					}
 					break;
 			}
 		}
 
 		/**
 		 * Don't allow to enable/disable events in non-debug mode
 		 *
 		 * @param kEvent $event
 		 * @return void
 		 * @access protected
 		 */
 		protected function OnBeforeItemCreate(&$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(&$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 EmailEvent::ADDRESS_TYPE_EMAIL:
 					$field = 'Email';
 					$table_name = TABLE_PREFIX . 'PortalUser';
 					break;
 
 				case EmailEvent::ADDRESS_TYPE_USER:
 					$field = 'Login';
 					$table_name = TABLE_PREFIX . 'PortalUser';
 					break;
 
 				case EmailEvent::ADDRESS_TYPE_GROUP:
 					$field = 'Name';
 					$table_name = TABLE_PREFIX . 'PortalGroup';
 					break;
 			}
 
 			if (isset($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) . '</item>';
 			}
 
 			echo '</suggestions>';
 		}
 
 		/**
 		 * Validates subject and body fields of Email template
 		 * @param kDBItem $object
 		 */
 		function _validateEmailTemplate(&$object)
 		{
 			$this->parseField($object, 'Subject');
 			$this->parseField($object, 'Body');
 		}
 
 		/**
 		 * Parses contents of given object field and sets error, when invalid in-portal tags found
 		 * @param kDBItem $object
 		 * @param string $field
 		 * @return void
 		 */
 		function parseField(&$object, $field)
 		{
 			$this->Application->InitParser();
-			
+
 			try {
 				$this->Application->Parser->CompileRaw($object->GetField($field), 'email_template');
 			}
 			catch (ParserException $e) {
 				if ( $this->Application->isDebugMode() ) {
 					$this->Application->Debugger->appendHTML('<b style="color: red;">Error in Email Template:</b> ' . $e->getMessage() . ' (line: ' . $e->getLine() . ')');
 				}
 
 				$object->SetError($field, 'parsing_error');
 			}
 		}
 	}
\ No newline at end of file