Index: branches/5.2.x/core/kernel/nparser/nparser.php
===================================================================
--- branches/5.2.x/core/kernel/nparser/nparser.php	(revision 16035)
+++ branches/5.2.x/core/kernel/nparser/nparser.php	(revision 16036)
@@ -1,1206 +1,1202 @@
 <?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_NAMESPACE . $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 = ' . $this->Conn->qstr($page_cache_key) . '
 					WHERE ' . $page->IDField . ' = ' . $page->GetID();
 			$this->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';
 		}
 
 		$icon_url = $base_url . 'core/admin_templates/img/top_frame/icons/' . $btn_name . '_mode.png';
 
 		$block_editor = '
 			<div id="' . $container_id . '" params="' . $param_string . '" class="' . $btn_container_class . '" title="' . kUtil::escape($block_title, kUtil::ESCAPE_HTML) . '">
 				<button style="background-image: url(' . $icon_url . ');" class="cms-btn-new ' . $btn_class . '" id="' . $container_id . '_btn">' . $btn_text . '</button>
 				<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);
 		}
 
 		$m_processor =& $this->GetProcessor('m');
 		$flag_values = $m_processor->PreparePostProcess($params);
 
 		$this->PushParams($params);
 		$ret = $this->Run($t, $silent);
 		$this->PopParams();
 
 		$ret = $m_processor->PostProcess($ret, $flag_values);
 
 		$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, $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
+}