<?php
/**
* @version	$Id: ntags.php 14870 2011-12-16 11:54:01Z alex $
* @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);";
		$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);
*/
			$this->AppendCode($o, "echo (\$_parser->ParseBlock($to_pass));");
			return $o;
		}
		$this->Single = false;
		$this->OriginalTag = $tag;
		$tag['NP']['name'] = $tag['NP']['design'] . '_' . abs(crc32($tag['file'])) . '_' . $tag['line']; //'__lambda';

		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->RenderDesignCode($o, $this->OriginalTag['NP']);
		return $o;
	}
}

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';
		$instance = new $class($tag);
		/* @var $instance _Tag_RenderElement */

		$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;
		}

		$capture_params = $tag['NP'];
		$capture_params['name'] = '__capture_' . $tag['NP']['name'];

		$capture_to_pass = $this->Parser->CompileParamsArray($capture_params);
		$code[] = "if (isset(\$_parser->Captures['{$tag['NP']['name']}'])) {";
		$code[] = "\${$tag['NP']['name']} = \$_parser->ParseBlock($capture_to_pass);";
		$code[] = "}";
		$code[] = "else {";
		$to_pass = $this->Parser->CompileParamsArray($tag['NP']);
		$code[] = '$_p_ =& $_parser->GetProcessor(\'m\');';
		$code[] = '$_tag_params = ' . $to_pass . ';';
		$code[] = "\${$tag['NP']['name']} = \$_p_->PostProcess(\${$tag['NP']['name']}, \$_p_->PreparePostProcess(\$_tag_params));";
		$code[] = "}";

		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']}');";

			if (array_key_exists('plus', $tag['NP'])) {
				$code[] = "\$params['{$tag['NP']['result_to_var']}'] += {$tag['NP']['plus']};";
			}

			$code[] = "\${$tag['NP']['result_to_var']} = \$params['{$tag['NP']['result_to_var']}'];";
		}
		elseif (array_key_exists('plus', $tag['NP'])) {
			$code[] = "\${$tag['NP']['name']} += {$tag['NP']['plus']};";
		}

		$code[] = "\$params['{$tag['NP']['name']}'] = \${$tag['NP']['name']};";
		$code[] = "echo (\${$tag['NP']['name']});";

		$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;
	}
}