Index: branches/5.2.x/core/kernel/nparser/ntags.php
===================================================================
--- branches/5.2.x/core/kernel/nparser/ntags.php	(revision 16622)
+++ branches/5.2.x/core/kernel/nparser/ntags.php	(revision 16623)
@@ -1,729 +1,744 @@
 <?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 _BlockTag extends kBase {
 
 	/**
 	 * Enter description here...
 	 *
 	 * @var NParser
 	 */
 	var $Parser = null;
 	var $Tag = null;
 
 	/**
 	 * Contains parameter names, that should be given to tag in any case
 	 *
 	 * @var Array
 	 */
 	var $_requiredParams = Array ();
 
 	/**
 	 * Creates new tag
 	 *
 	 * @param Array $tag
 	 * @access public
 	 */
 	public function __construct($tag)
 	{
 		parent::__construct();
 
 		$this->Tag = $tag;
 	}
 
 	function Open($tag)
 	{
 		if (!$this->_checkRequiredParams($tag)) {
 			return false;
 		}
 
 		return '';
 	}
 
 	/**
 	 * Checks, that all required attributes for tag are passed
 	 *
 	 * @param Array $tag
 	 * @return bool
 	 */
 	function _checkRequiredParams($tag)
 	{
 		$missing_params = array_diff($this->_requiredParams, array_keys($tag['NP']));
 		if (!$missing_params) {
 			return true;
 		}
 
 		$error_msg = 'Tag ' . $this->Parser->TagInfo($tag, true) . ' called <strong>without required ' . implode(', ', $missing_params) . '</strong> attribute';
 
 		if (count($missing_params) > 1) {
 			$error_msg .= '(-s)';
 		}
 
 		throw new ParserException($error_msg, 0, null, $tag);
 
 		return false;
 	}
 
 	/**
 	 * All tags inside block tag are passed through to this method
 	 * Any returned result is appened to current level's buffer
 	 * This can be used to implement special handling of such tags as <inp2:m_else/>
 	 *
 	 * @param unknown_type $tag
 	 * @return unknown
 	 */
 	function PassThrough(&$tag)
 	{
 		return '';
 	}
 
 	function Close($tag)
 	{
 		return $this->Parser->Buffers[$this->Parser->Level];
 	}
 
 	function AppendCode(&$o, $code, $php_tags=true)
 	{
 		if ($php_tags) {
 			$o .= '<?'."php\n" . ( is_array($code) ? "\t".implode("\n\t", $code)."\n" : $code) .'?>';
 		}
 		else {
 			$o .= ( is_array($code) ? "\t".implode("\n\t", $code)."\n" : $code);
 		}
 	}
 }
 
 class _Tag_Comment extends _BlockTag {
 
 	function Open($tag)
 	{
 		$this->Parser->SkipComments = false;
 
 		return '';
 	}
 
 	function Close($tag)
 	{
 		$this->Parser->SkipComments = true;
 
 		return $this->Parser->Buffers[$this->Parser->Level];
 	}
 }
 
 class _Tag_DefineElement extends _BlockTag {
 	/*var $ElemName;
 
 	function Open($tag)
 	{
 		$o = '';
 
 		$pointer = abs(crc32($tag['file'])).'_'.$tag['line'];
 		$f_name = $tag['NP']['name'].'_'.$pointer;
 		$this->ElemName = $tag['NP']['name'];
 
 		$code[] = "\$_parser->Elements['{$tag['NP']['name']}'] = '$f_name';";
 
 		// function_exists is required here, because a template may be included more than once
 		// leading to Cannot redeclare error
 		$code[] = "if (!function_exists('{$f_name}')) {";
 		$code[] = "function $f_name(&\$_parser, \$params) {";
 		$code[] = "global \$application;";
 		$code[] = "if (!\$_output = \$_parser->CacheStart('{$pointer}')) {";
 
 		$defaults = $this->Parser->CompileParamsArray($tag['NP']);
 
 		$code[] = "\$params = array_merge($defaults, \$params);";
 		$code[] = "if (!isset(\$params['PrefixSpecial']) && isset(\$params['prefix'])) {\$params['PrefixSpecial'] = \$params['prefix'];};";
 		$code[] = "extract(\$params);";
 		$code[] = "\$_parser->SetParams(\$params);";
 		$code[] = 'ob_start();';
 		$this->AppendCode($o, $code, false);
 		return $o . '?'.'>';
 	}
 
 	function Close($tag)
 	{
 		$o = $this->Parser->Buffers[$this->Parser->Level];
 		$code[] = "\$_parser->CacheEnd();\n";
 		$code[] = '$_output = ob_get_contents();';
 		$code[] = 'ob_end_clean();';
 		$code[] = '}';
 		$code[] = "return \$_output === true ? '' : \$_output;";
 		$code[] = '}}';
 
 		$code[] = "\$_parser->CachableElements['".$this->ElemName."'] = ".($this->Parser->Cachable[$this->Parser->Level] ? 'true':'false').';';
 
 		$o .= '<?'.'php';
 		$this->AppendCode($o, $code, false);
 
 		return $o;
 //		$this->Parser->Definitions .= $o."\n";
 //		return '';
 	}  */
 
 	public function __construct($tag)
 	{
 		parent::__construct($tag);
 
 		$this->_requiredParams = Array ('name');
 	}
 
 	function Open($tag)
 	{
 		$o = parent::Open($tag);
 
 		if ($o === false) {
 			// some required params not passed
 			return $o;
 		}
 
 		$f_name = $tag['NP']['name'].'_'.abs(crc32($tag['file'])).'_'.$tag['line'];
 		$this->Tag['function_name'] = $f_name; // for later use in closing tag
 
 		$code[] = "\$_parser->Elements['{$tag['NP']['name']}'] = '$f_name';";
 
 		// function_exists is required here, because a template may be included more than once
 		// leading to Cannot redeclare error
 		$code[] = "if (!function_exists('{$f_name}')) {";
 		$code[] = "function $f_name(&\$_parser, \$params) {";
 		$code[] = "global \$application;";
 
 		$tag['NP'] = $this->_extractParams($tag['NP']);
 		$defaults = $this->Parser->CompileParamsArray($tag['NP']);
 
 		$code[] = "\$params = array_merge($defaults, \$params);";
 		$code[] = "if (!isset(\$params['PrefixSpecial']) && isset(\$params['prefix'])) {\$params['PrefixSpecial'] = \$params['prefix'];};";
 		$code[] = 'extract($params, EXTR_SKIP);';
 		$code[] = "\$_parser->SetParams(\$params);";
 		$code[] = 'ob_start();';
 		$this->AppendCode($o, $code);
 		return $o;
 	}
 
 	/**
 	 * Converts $param_name to $params['param_name']
 	 *
 	 * @param Array $params
 	 * @return Array
 	 */
 	function _extractParams($params)
 	{
 		foreach ($params as $param_name => $param_value) {
 			$params[$param_name] = preg_replace('/[\{]{0,1}([\$])(.*?[^\$\s\{\}]*)[\}]{0,1}/', '{$params[\'\\2\']}', $param_value);
 		}
 
 		return $params;
 	}
 
 	function Close($tag)
 	{
 		$o = $this->Parser->Buffers[$this->Parser->Level];
 		$code[] = '$_output = ob_get_contents();';
 		$code[] = 'ob_end_clean();';
 		$code[] = 'return $_output;';
 		$code[] = '}}';
 
 		$end_pos = $tag['pos'] + strlen($tag['opening']) + strlen($tag['tag']) + strlen($tag['closing']) + TAG_NAMESPACE_LENGTH;
 		$code[] = "\$_parser->ElementLocations['{$this->Tag['function_name']}'] = Array('template' => '{$this->Tag['template']}', 'start_pos' => {$this->Tag['pos']}, 'end_pos' => {$end_pos});";
 
 		$this->AppendCode($o, $code);
 		return $o;
 	}
 }
 
 class _Tag_Capture extends _Tag_DefineElement {
 
 	public function __construct($tag)
 	{
 		parent::__construct($tag);
 
 		$this->_requiredParams = Array ('to_var');
 	}
 
 	function Open($tag)
 	{
 		if (!$this->_checkRequiredParams($tag)) {
 			return false;
 		}
 
 		$tag['NP']['name'] = '__capture_'.$tag['NP']['to_var'];
 
 		$o = '';
 		// $this->AppendCode($o, "\$_parser->Captures['{$tag['NP']['to_var']}'] = 1;", false);
 		$this->AppendCode($o, "\$_parser->Captures['{$tag['NP']['to_var']}'] = 1;");
 		$o .= parent::Open($tag);
 		return $o;
 	}
 }
 
 class _Tag_RenderElement extends _Tag_DefineElement {
 	var $Single = true;
 	var $OriginalTag;
 
 	var $_lambdaName = '';
 
 	public function __construct($tag)
 	{
 		parent::__construct($tag);
 
 		if (!$tag['is_closing']) {
 			$this->_requiredParams = Array ('design');
 		}
 	}
 
 	function Open($tag)
 	{
 		if (!$this->_checkRequiredParams($tag)) {
 			return false;
 		}
 
 		$o = '';
 		if ($tag['is_closing']) {
 			if (isset($tag['NP']['design'])) {
 				$this->RenderDesignCode($o, $tag['NP']);
 				return $o;
 			}
 			$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
 /*			$pointer = abs(crc32($tag['file'])).'_'.$tag['line'];
 //			$code[] = "}";
 //			$code[] = "if (!\$_parser->CachableElements['".$tag['NP']['name']."']) {";
 //			$code[] = "\$_parser->CacheEndInside();";
 //			$code[] = "}";
 			$o .= '<?'.'php ';
 
 			$this->AppendCode($o, $this->Parser->BreakCache('', $pointer.'a', "\$_parser->CachableElements['".$tag['NP']['name']."']"), false);
 			$this->AppendCode($o, "echo (\$_parser->ParseBlock($to_pass));\n", false);
 			$this->AppendCode($o, $this->Parser->BreakCache('', $pointer.'b') . " ?".">\n", false);
 //			$this->AppendCode($o, "if (!\$_parser->CacheStartOrContinue(\$_parser->CachableElements['".$tag['NP']['name']."'], '{$pointer}')) {".' ?'.'>', false);
 */
 			$code = array("echo (\$_parser->ParseBlock($to_pass));");
 
 			if ( array_key_exists('result_to_var', $tag['NP']) && $tag['NP']['result_to_var'] ) {
 				$param_name = $tag['NP']['result_to_var'];
 				$code[] = "\$params['{$param_name}'] = \$_parser->GetParam('{$param_name}');";
 				$code[] = "\${$param_name} = \$params['{$param_name}'];";
 			}
 
 			$this->AppendCode($o, $code);
 
 			return $o;
 		}
 		$this->Single = false;
 		$this->OriginalTag = $tag;
-		$tag['NP']['name'] = $tag['NP']['design'] . '_' . abs(crc32($tag['file'])) . '_' . $tag['line']; //'__lambda';
+		$tag['NP']['name'] = $this->createSafeFunctionName($tag);
 
 		return parent::Open($tag);
 	}
 
 	function RenderDesignCode(&$o, $params)
 	{
 		$to_pass = $this->Parser->CompileParamsArray($params);
 		$code[] = "echo (\$_parser->ParseBlock(array_merge($to_pass, array('name'=>\"{$params['design']}\",'content'=>\$_parser->ParseBlock($to_pass), 'keep_data_exists'=>1))));";
 		$this->AppendCode($o, $code);
 	}
 
 	function Close($tag)
 	{
 		if ($this->Single) {
 			return $this->Parser->Buffers[$this->Parser->Level];
 		}
 		$o = parent::Close($tag);
-		$this->OriginalTag['NP']['name'] = $this->OriginalTag['NP']['design'] . '_' . abs(crc32($this->OriginalTag['file'])) . '_' . $this->OriginalTag['line']; //'__lambda';
+		$this->OriginalTag['NP']['name'] = $this->createSafeFunctionName($this->OriginalTag);
 
 		$this->RenderDesignCode($o, $this->OriginalTag['NP']);
 		return $o;
 	}
+
+	/**
+	 * Creates safe function name based on design used in a given tag.
+	 *
+	 * @param array $tag Tag.
+	 *
+	 * @return string
+	 */
+	protected function createSafeFunctionName(array $tag)
+	{
+		$safe_design = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $tag['NP']['design']);
+
+		return $safe_design . '_' . abs(crc32($tag['file'])) . '_' . $tag['line']; // Former name '__lambda'.
+	}
+
 }
 
 class _Tag_RenderElements extends _BlockTag {
 
 	public function __construct($tag)
 	{
 		parent::__construct($tag);
 
 		$this->_requiredParams = Array ('elements');
 	}
 
 	function Open($tag)
 	{
 		$o = parent::Open($tag);
 
 		if ($o === false) {
 			// some required params not passed
 			return $o;
 		}
 
 		$element_names = array_map('trim', explode(',', $tag['NP']['elements']));
 		unset($tag['NP']['elements']);
 
 		$class = '_Tag_RenderElement';
 
 		/** @var _Tag_RenderElement $instance */
 		$instance = new $class($tag);
 
 		$instance->Parser =& $this->Parser;
 
 		$skip_elements = array_key_exists('skip', $tag['NP']) ? array_map('trim', explode(',', $tag['NP']['skip'])) : Array ();
 
 		foreach ($element_names as $element_name) {
 			if (in_array($element_name, $skip_elements) || !$element_name) {
 				// empty element name OR element should be excluded
 				continue;
 			}
 
 			$tag['NP']['name'] = $element_name;
 			$o .= $instance->Open($tag);
 		}
 
 		return $o;
 	}
 }
 
 class _Tag_Param extends _BlockTag {
 
 	public function __construct($tag)
 	{
 		parent::__construct($tag);
 
 		$this->_requiredParams = Array ('name');
 	}
 
 	function Open($tag)
 	{
 		$o = parent::Open($tag);
 
 		if ( $o === false ) {
 			// some required params not passed
 			return $o;
 		}
 
 		$param_name = $tag['NP']['name'];
 		$capture_params = $tag['NP'];
 		$capture_params['name'] = '__capture_' . $param_name;
 
 		$capture_to_pass = $this->Parser->CompileParamsArray($capture_params);
 		$code[] = "if (isset(\$_parser->Captures[\"{$param_name}\"])) {";
 		$code[] = "\t\${$param_name} = \$_parser->ParseBlock($capture_to_pass);";
 		$code[] = "\t\$params[\"{$param_name}\"] = \${$param_name};";
 		$code[] = "\t\$_param_value = \${$param_name};";
 		$code[] = "}";
 		$code[] = "else {";
 
 		$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
 		$code[] = "\t" . '$_p_ =& $_parser->GetProcessor(\'m\');';
 		$code[] = "\t" . '$_tag_params = ' . $to_pass . ';';
 		$code[] = "\t\$_param_value = \$_p_->PostProcess(\${$param_name}, \$_p_->PreparePostProcess(\$_tag_params));";
 		$code[] = "}";
 
 		if ( array_key_exists('result_to_var', $tag['NP']) && $tag['NP']['result_to_var'] ) {
 			$result_to_var = $tag['NP']['result_to_var'];
 			$code[] = "\${$result_to_var} = \$_parser->GetParam('{$result_to_var}');";
 
 			if ( array_key_exists('plus', $tag['NP']) ) {
 				$code[] = "\${$result_to_var} += {$tag['NP']['plus']};";
 			}
 
 			$code[] = "\$params['{$result_to_var}'] = \${$result_to_var};";
 		}
 		elseif ( array_key_exists('plus', $tag['NP']) ) {
 			$code[] = "\$_param_value += {$tag['NP']['plus']};";
 		}
 
 		$code[] = "echo (\$_param_value);";
 
 		$this->AppendCode($o, $code);
 
 		return $o;
 	}
 }
 
 class _Tag_Include extends _BlockTag {
 
 	function Open($tag)
 	{
 		$o = '';
 		$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
 		$this->AppendCode($o, "echo (\$_parser->IncludeTemplate($to_pass))");
 		return $o;
 	}
 }
 
 class _Tag_If extends _BlockTag {
 
 	/**
 	 * Permanently inverses if tag (used in ifnot tag)
 	 *
 	 * @var bool
 	 */
 	var $_Inversed = false;
 
 	/**
 	 * Count of "elseif" tags inside
 	 *
 	 * @var int
 	 */
 	var $_elseIfCount = 0;
 
 	public function __construct($tag)
 	{
 		parent::__construct($tag);
 
 		$this->_requiredParams = Array ('check');
 	}
 
 	function Open($tag)
 	{
 		$o = parent::Open($tag);
 
 		if ($o === false) {
 			// some required params not passed
 			return $o;
 		}
 
 		$this->AppendCheckCode($o, $tag);
 
 		return $o;
 	}
 
 	/**
 	 * Adds check code to $o
 	 *
 	 * @param string $o
 	 * @param Array $tag
 	 * @return void
 	 * @access protected
 	 */
 	protected function AppendCheckCode(&$o, $tag)
 	{
 		$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
 
 		$code = Array ();
 		$code[] = "\$_splited = \$_parser->SplitTag(array('tag'=>\"{$tag['NP']['check']}\", 'file'=>'{$tag['file']}', 'line'=>{$tag['line']}));";
 		$code[] = 'if ($_splited[\'prefix\'] == \'__auto__\') {$_splited[\'prefix\'] = $PrefixSpecial;}';
 		$code[] = '$_p_ =& $_parser->GetProcessor($_splited[\'prefix\']);';
 
 		if ( isset($tag['NP']['inverse']) || $this->_Inversed ) {
 			$code[] = "if (!\$_p_->ProcessParsedTag(\$_splited['name'], $to_pass, \$_splited['prefix'], '{$tag['file']}', {$tag['line']})) {";
 		}
 		else {
 			$code[] = "if (\$_p_->ProcessParsedTag(\$_splited['name'], $to_pass, \$_splited['prefix'], '{$tag['file']}', {$tag['line']})) {";
 		}
 
 		$this->AppendCode($o, $code);
 	}
 
 	function PassThrough(&$tag)
 	{
 		$o = '';
 		if ($tag['name'] == 'else') {
 			$this->AppendCode($o, '} else {');
 			$tag['processed'] = true;
 		}
 		if ($tag['name'] == 'elseif') {
 			if (!$this->_checkRequiredParams($tag)) {
 				return '';
 			}
 
 			$this->_elseIfCount++; // add same count of closing brackets in closing tag
 
 			$this->AppendCode($o, "} else {");
 			$this->AppendCheckCode($o, $tag);
 
 			$tag['processed'] = true;
 		}
 		return $o;
 	}
 
 	function Close($tag)
 	{
 		$o = $this->Parser->Buffers[$this->Parser->Level];
 
 		$code = str_repeat("}\n", $this->_elseIfCount + 1);
 		$this->AppendCode($o, $code);
 
 		return $o;
 	}
 }
 
 class _Tag_IfNot extends _Tag_If {
 
 	public function __construct($tag)
 	{
 		parent::__construct($tag);
 
 		$this->_Inversed = true;
 	}
 
 }
 
 class _Tag_DefaultParam extends _BlockTag {
 
 	function Open($tag)
 	{
 		$o = '';
 		foreach ($tag['NP'] as $key => $val)	{
 			$code[] = 'if (!isset($' . $key . ')) $params["' . $key . '"] = "' . $val . '";';
 			$code[] = '$' . $key . ' = isset($' . $key . ') ? $' . $key . ' : "' . $val . '";';
 			$code[] = '$_parser->SetParam("' . $key . '", $' . $key . ');';
 		}
 		$this->AppendCode($o, $code);
 		return $o;
 	}
 }
 
 class _Tag_SetParam extends _BlockTag {
 
 	function Open($tag)
 	{
 		$o = '';
 		foreach ($tag['NP'] as $key => $val)	{
 			$code[] = '$params[\'' . $key . '\'] = "' . $val . '";';
 			$code[] = '$' . $key . ' = "' . $val . '";';
 			$code[] = '$_parser->SetParam(\'' . $key . '\', $' . $key . ');';
 		}
 		$this->AppendCode($o, $code);
 		return $o;
 	}
 }
 
 class _Tag_Cache extends _BlockTag {
 
 	function Open($tag)
 	{
 		$o = '';
 		$pointer = abs(crc32($tag['file'])).'_'.$tag['line'];
 		$key = array_key_exists('key', $tag['NP']) ? $tag['NP']['key'] : '';
 		$this->AppendCode($o, "if (!\$_parser->CacheStart('{$pointer}', \"{$key}\")) {\n");
 
 		return $o;
 	}
 
 	function Close($tag)
 	{
 		$o = $this->Parser->Buffers[$this->Parser->Level];
 		$cache_timeout = array_key_exists('cache_timeout', $this->Tag['NP']) ? $this->Tag['NP']['cache_timeout'] : 0;
 		$this->AppendCode($o, "\$_parser->CacheEnd(" . (int)$cache_timeout . ");\n}\n");
 
 		return $o;
 	}
 
 }
 
 class _Tag_IfDataExists extends _BlockTag {
 
 	function Open($tag)
 	{
 		$o = '';
 		$code = array();
 		$code[] = "ob_start();\n";
 		$code[] = '$_tmp_data_exists = $_parser->DataExists;';
 		$code[] = '$_parser->DataExists = false;';
 		$this->AppendCode($o, $code);
 		return $o;
 	}
 
 	function Close($tag)
 	{
 		$o = $this->Parser->Buffers[$this->Parser->Level];
 		$code = array();
 		$code[] = '$res = ob_get_clean();';
 		$code[] = 'if ($_parser->DataExists) {echo $res;}';
 
 		if (array_key_exists('block_no_data', $this->Tag['NP'])) {
 			$tag_params = $this->Tag['NP'];
 			$tag_params['name'] = $tag_params['block_no_data'];
 			unset($tag_params['block_no_data']);
 
 			$to_pass = $this->Parser->CompileParamsArray($tag_params);
 			$code[] = "else { echo (\$_parser->ParseBlock($to_pass)); }";
 		}
 
 		$code[] = '$_parser->DataExists = $_tmp_data_exists;';
 
 		$this->AppendCode($o, $code);
 		return $o;
 	}
 
 }
 
 class _Tag_Compress extends _BlockTag {
 
 	public function __construct($tag)
 	{
 		parent::__construct($tag);
 
 		if ($tag['is_closing']) {
 			if (!array_key_exists('files', $tag['NP'])) {
 				$this->_requiredParams = Array ('from');
 			}
 			else {
 				$this->_requiredParams = Array ('files');
 			}
 		}
 		else {
 			$this->_requiredParams = Array ('type'); // js/css
 		}
 	}
 
 	/**
 	 * When used as non-block tag, then compress given files and return url to result
 	 *
 	 * @param Array $tag
 	 * @return string
 	 */
 	function Open($tag)
 	{
 		$o = parent::Open($tag);
 
 		if ($o === false) {
 			// some required params not passed
 			return $o;
 		}
 
 		if ($tag['is_closing']) {
 			$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
 			$this->AppendCode($o, "echo \$_parser->CompressScript($to_pass, false);");
 		}
 		else {
 			$this->AppendCode($o, "ob_start();");
 		}
 
 		return $o;
 	}
 
 	/**
 	 * When used as block tag, then compress contents between tags
 	 *
 	 * @param Array $tag
 	 * @return string
 	 */
 	function Close($tag)
 	{
 		if ($this->Tag['is_closing']) {
 			return parent::Close($tag);
 		}
 
 		$o = parent::Close($tag);
 
 		$code = Array ();
 		$code[] = '$res = ob_get_clean();';
 		$code[] = 'echo $_parser->CompressScript($res, true, \'' . $this->Tag['NP']['type'] . '\');';
 
 		$this->AppendCode($o, $code);
 
 		return $o;
 	}
 }