diff options
43 files changed, 2771 insertions, 1712 deletions
| diff --git a/.gitignore b/.gitignore index 145a050aaa..885e37f194 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@  *~  /phpunit.xml +/phpBB/cache/*.html  /phpBB/cache/*.php  /phpBB/cache/queue.php.lock  /phpBB/config.php diff --git a/phpBB/common.php b/phpBB/common.php index 54e7093c76..524c05ae70 100644 --- a/phpBB/common.php +++ b/phpBB/common.php @@ -82,7 +82,6 @@ if (!empty($load_extensions) && function_exists('dl'))  // Include files  require($phpbb_root_path . 'includes/class_loader.' . $phpEx); -require($phpbb_root_path . 'includes/template.' . $phpEx);  require($phpbb_root_path . 'includes/session.' . $phpEx);  require($phpbb_root_path . 'includes/auth.' . $phpEx); @@ -109,7 +108,6 @@ $class_loader->set_cache($cache->get_driver());  $request	= new phpbb_request();  $user		= new user();  $auth		= new auth(); -$template	= new template();  $db			= new $sql_db();  // make sure request_var uses this request instance @@ -126,6 +124,9 @@ $config = new phpbb_config_db($db, $cache->get_driver(), CONFIG_TABLE);  set_config(null, null, null, $config);  set_config_count(null, null, null, $config); +$template_locator = new phpbb_template_locator(); +$template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $template_locator); +  // Add own hook handler  require($phpbb_root_path . 'includes/hooks/index.' . $phpEx);  $phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('template', 'display'))); diff --git a/phpBB/develop/compile_template.php b/phpBB/develop/compile_template.php new file mode 100644 index 0000000000..e741e909d8 --- /dev/null +++ b/phpBB/develop/compile_template.php @@ -0,0 +1,24 @@ +<?php +// ------------------------------------------------------------- +// +// $Id$ +// +// FILENAME  : compile_template.php +// STARTED   : Sun Apr 24, 2011 +// COPYRIGHT : © 2011 phpBB Group +// WWW       : http://www.phpbb.com/ +// LICENCE   : GPL vs2.0 [ see /docs/COPYING ]  +//  +// ------------------------------------------------------------- + +define('IN_PHPBB', 1); +define('ANONYMOUS', 1); +$phpEx = substr(strrchr(__FILE__, '.'), 1); +$phpbb_root_path = './../'; + +include($phpbb_root_path . 'includes/template_compile.'.$phpEx); + +$file = $argv[1]; + +$compile = new phpbb_template_compile(); +echo $compile->compile_file($file); diff --git a/phpBB/docs/hook_system.html b/phpBB/docs/hook_system.html index 82dc44a082..0dc25630a8 100644 --- a/phpBB/docs/hook_system.html +++ b/phpBB/docs/hook_system.html @@ -370,10 +370,10 @@ a:active	{ color: #368AD2; }  <p><code>phpbb_user_session_handler();</code> which is called within user::setup after the session and the user object is correctly initialized.<br />  <code>append_sid($url, $params = false, $is_amp = true, $session_id = false);</code> which is called for building urls (appending the session id)<br /> -<code>$template->display($handle, $include_once = true);</code> which is called directly before outputting the (not-yet-compiled) template.<br /> +<code>$template->display($handle, $template);</code> which is called directly before outputting the (not-yet-compiled) template.<br />  <code>exit_handler();</code> which is called at the very end of phpBB3's execution.</p> -<p>Please note: The <code>$template->display</code> hook takes a third <code>$template</code> argument, which is the template instance being used, which should be used instead of the global.</p> +<p>Please note: The <code>$template->display</code> hook takes a <code>$template</code> argument, which is the template instance being used, which should be used instead of the global.</p>  <p>There are also valid external constants you may want to use if you embed phpBB3 into your application:</p> diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index 4d064a838b..c3367fbd46 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -30,7 +30,6 @@ class bbcode  	var $bbcodes = array();  	var $template_bitfield; -	var $template_filename = '';  	/**  	* Constructor @@ -128,28 +127,17 @@ class bbcode  	*/  	function bbcode_cache_init()  	{ -		global $phpbb_root_path, $template, $user; +		global $phpbb_root_path, $phpEx, $config, $user;  		if (empty($this->template_filename))  		{  			$this->template_bitfield = new bitfield($user->theme['bbcode_bitfield']); -			$this->template_filename = $phpbb_root_path . 'styles/' . $user->theme['template_path'] . '/template/bbcode.html'; -			if (!@file_exists($this->template_filename)) -			{ -				if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id']) -				{ -					$this->template_filename = $phpbb_root_path . 'styles/' . $user->theme['template_inherit_path'] . '/template/bbcode.html'; -					if (!@file_exists($this->template_filename)) -					{ -						trigger_error('The file ' . $this->template_filename . ' is missing.', E_USER_ERROR); -					} -				} -				else -				{ -					trigger_error('The file ' . $this->template_filename . ' is missing.', E_USER_ERROR); -				} -			} +			$template_locator = new phpbb_template_locator(); +			$template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $template_locator); +			$template->set_template(); +			$template_locator->set_filenames(array('bbcode.html' => 'bbcode.html')); +			$this->template_filename = $template_locator->get_source_file_for_handle('bbcode.html');  		}  		$bbcode_ids = $rowset = $sql = array(); diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 73f763c9f6..9d27a24c92 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -817,7 +817,7 @@ function phpbb_is_writable($file)  * @param string $path Path to check absoluteness of  * @return boolean  */ -function is_absolute($path) +function phpbb_is_absolute($path)  {  	return ($path[0] == '/' || (DIRECTORY_SEPARATOR == '\\' && preg_match('#^[a-z]:[/\\\]#i', $path))) ? true : false;  } @@ -837,7 +837,7 @@ function phpbb_own_realpath($path)  	$path_prefix = '';  	// Determine what sort of path we have -	if (is_absolute($path)) +	if (phpbb_is_absolute($path))  	{  		$absolute = true; diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index f5d102b1da..507f523866 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -175,7 +175,7 @@ class messenger  	*/  	function template($template_file, $template_lang = '', $template_path = '')  	{ -		global $config, $phpbb_root_path, $user; +		global $config, $phpbb_root_path, $phpEx, $user;  		if (!trim($template_file))  		{ @@ -193,7 +193,8 @@ class messenger  		// tpl_msg now holds a template object we can use to parse the template file  		if (!isset($this->tpl_msg[$template_lang . $template_file]))  		{ -			$this->tpl_msg[$template_lang . $template_file] = new template(); +			$template_locator = new phpbb_template_locator(); +			$this->tpl_msg[$template_lang . $template_file] = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $template_locator);  			$tpl = &$this->tpl_msg[$template_lang . $template_file];  			$fallback_template_path = false; diff --git a/phpBB/includes/functions_template.php b/phpBB/includes/functions_template.php deleted file mode 100644 index 8c58c33b0d..0000000000 --- a/phpBB/includes/functions_template.php +++ /dev/null @@ -1,812 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @version $Id$ -* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc -* @license http://opensource.org/licenses/gpl-license.php GNU Public License -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ -	exit; -} - -/** -* Extension of template class - Functions needed for compiling templates only. -* -* psoTFX, phpBB Development Team - Completion of file caching, decompilation -* routines and implementation of conditionals/keywords and associated changes -* -* The interface was inspired by PHPLib templates,  and the template file (formats are -* quite similar) -* -* The keyword/conditional implementation is currently based on sections of code from -* the Smarty templating engine (c) 2001 ispi of Lincoln, Inc. which is released -* (on its own and in whole) under the LGPL. Section 3 of the LGPL states that any code -* derived from an LGPL application may be relicenced under the GPL, this applies -* to this source -* -* DEFINE directive inspired by a request by Cyberalien -* -* @package phpBB3 -*/ -class template_compile -{ -	var $template; - -	// Various storage arrays -	var $block_names = array(); -	var $block_else_level = array(); - -	/** -	* constuctor -	*/ -	function template_compile(&$template) -	{ -		$this->template = &$template; -	} - -	/** -	* Load template source from file -	* @access private -	*/ -	function _tpl_load_file($handle, $store_in_db = false) -	{ -		// Try and open template for read -		if (!file_exists($this->template->files[$handle])) -		{ -			trigger_error("template->_tpl_load_file(): File {$this->template->files[$handle]} does not exist or is empty", E_USER_ERROR); -		} - -		$this->template->compiled_code[$handle] = $this->compile(trim(@file_get_contents($this->template->files[$handle]))); - -		// Actually compile the code now. -		$this->compile_write($handle, $this->template->compiled_code[$handle]); - -		// Store in database if required... -		if ($store_in_db) -		{ -			global $db, $user; - -			$sql_ary = array( -				'template_id'			=> $this->template->files_template[$handle], -				'template_filename'		=> $this->template->filename[$handle], -				'template_included'		=> '', -				'template_mtime'		=> time(), -				'template_data'			=> trim(@file_get_contents($this->template->files[$handle])), -			); - -			$sql = 'INSERT INTO ' . STYLES_TEMPLATE_DATA_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); -			$db->sql_query($sql); -		} -	} - -	/** -	* Remove any PHP tags that do not belong, these regular expressions are derived from -	* the ones that exist in zend_language_scanner.l -	* @access private -	*/ -	function remove_php_tags(&$code) -	{ -		// This matches the information gathered from the internal PHP lexer -		$match = array( -			'#<([\?%])=?.*?\1>#s', -			'#<script\s+language\s*=\s*(["\']?)php\1\s*>.*?</script\s*>#s', -			'#<\?php(?:\r\n?|[ \n\t]).*?\?>#s' -		); - -		$code = preg_replace($match, '', $code); -	} - -	/** -	* The all seeing all doing compile method. Parts are inspired by or directly from Smarty -	* @access private -	*/ -	function compile($code, $no_echo = false, $echo_var = '') -	{ -		global $config; - -		if ($echo_var) -		{ -			global $$echo_var; -		} - -		// Remove any "loose" php ... we want to give admins the ability -		// to switch on/off PHP for a given template. Allowing unchecked -		// php is a no-no. There is a potential issue here in that non-php -		// content may be removed ... however designers should use entities -		// if they wish to display < and > -		$this->remove_php_tags($code); - -		// Pull out all block/statement level elements and separate plain text -		preg_match_all('#<!-- PHP -->(.*?)<!-- ENDPHP -->#s', $code, $matches); -		$php_blocks = $matches[1]; -		$code = preg_replace('#<!-- PHP -->.*?<!-- ENDPHP -->#s', '<!-- PHP -->', $code); - -		preg_match_all('#<!-- INCLUDE (\{\$?[A-Z0-9\-_]+\}|[a-zA-Z0-9\_\-\+\./]+) -->#', $code, $matches); -		$include_blocks = $matches[1]; -		$code = preg_replace('#<!-- INCLUDE (?:\{\$?[A-Z0-9\-_]+\}|[a-zA-Z0-9\_\-\+\./]+) -->#', '<!-- INCLUDE -->', $code); - -		preg_match_all('#<!-- INCLUDEPHP ([a-zA-Z0-9\_\-\+\./]+) -->#', $code, $matches); -		$includephp_blocks = $matches[1]; -		$code = preg_replace('#<!-- INCLUDEPHP [a-zA-Z0-9\_\-\+\./]+ -->#', '<!-- INCLUDEPHP -->', $code); - -		preg_match_all('#<!-- ([^<].*?) (.*?)? ?-->#', $code, $blocks, PREG_SET_ORDER); - -		$text_blocks = preg_split('#<!-- [^<].*? (?:.*?)? ?-->#', $code); - -		for ($i = 0, $j = sizeof($text_blocks); $i < $j; $i++) -		{ -			$this->compile_var_tags($text_blocks[$i]); -		} -		$compile_blocks = array(); - -		for ($curr_tb = 0, $tb_size = sizeof($blocks); $curr_tb < $tb_size; $curr_tb++) -		{ -			$block_val = &$blocks[$curr_tb]; - -			switch ($block_val[1]) -			{ -				case 'BEGIN': -					$this->block_else_level[] = false; -					$compile_blocks[] = '<?php ' . $this->compile_tag_block($block_val[2]) . ' ?>'; -				break; - -				case 'BEGINELSE': -					$this->block_else_level[sizeof($this->block_else_level) - 1] = true; -					$compile_blocks[] = '<?php }} else { ?>'; -				break; - -				case 'END': -					array_pop($this->block_names); -					$compile_blocks[] = '<?php ' . ((array_pop($this->block_else_level)) ? '}' : '}}') . ' ?>'; -				break; - -				case 'IF': -					$compile_blocks[] = '<?php ' . $this->compile_tag_if($block_val[2], false) . ' ?>'; -				break; - -				case 'ELSE': -					$compile_blocks[] = '<?php } else { ?>'; -				break; - -				case 'ELSEIF': -					$compile_blocks[] = '<?php ' . $this->compile_tag_if($block_val[2], true) . ' ?>'; -				break; - -				case 'ENDIF': -					$compile_blocks[] = '<?php } ?>'; -				break; - -				case 'DEFINE': -					$compile_blocks[] = '<?php ' . $this->compile_tag_define($block_val[2], true) . ' ?>'; -				break; - -				case 'UNDEFINE': -					$compile_blocks[] = '<?php ' . $this->compile_tag_define($block_val[2], false) . ' ?>'; -				break; - -				case 'INCLUDE': -					$temp = array_shift($include_blocks); - -					// Dynamic includes -					// Cheap match rather than a full blown regexp, we already know -					// the format of the input so just use string manipulation. -					if ($temp[0] == '{') -					{ -						$file = false; - -						if ($temp[1] == '$') -						{ -							$var = substr($temp, 2, -1); -							//$file = $this->template->_tpldata['DEFINE']['.'][$var]; -							$temp = "\$this->_tpldata['DEFINE']['.']['$var']"; -						} -						else -						{ -							$var = substr($temp, 1, -1); -							//$file = $this->template->_rootref[$var]; -							$temp = "\$this->_rootref['$var']"; -						} -					} -					else -					{ -						$file = $temp; -					} - -					$compile_blocks[] = '<?php ' . $this->compile_tag_include($temp) . ' ?>'; - -					// No point in checking variable includes -					if ($file) -					{ -						$this->template->_tpl_include($file, false); -					} -				break; - -				case 'INCLUDEPHP': -					$compile_blocks[] = ($config['tpl_allow_php']) ? '<?php ' . $this->compile_tag_include_php(array_shift($includephp_blocks)) . ' ?>' : ''; -				break; - -				case 'PHP': -					$compile_blocks[] = ($config['tpl_allow_php']) ? '<?php ' . array_shift($php_blocks) . ' ?>' : ''; -				break; - -				default: -					$this->compile_var_tags($block_val[0]); -					$trim_check = trim($block_val[0]); -					$compile_blocks[] = (!$no_echo) ? ((!empty($trim_check)) ? $block_val[0] : '') : ((!empty($trim_check)) ? $block_val[0] : ''); -				break; -			} -		} - -		$template_php = ''; -		for ($i = 0, $size = sizeof($text_blocks); $i < $size; $i++) -		{ -			$trim_check_text = trim($text_blocks[$i]); -			$template_php .= (!$no_echo) ? (($trim_check_text != '') ? $text_blocks[$i] : '') . ((isset($compile_blocks[$i])) ? $compile_blocks[$i] : '') : (($trim_check_text != '') ? $text_blocks[$i] : '') . ((isset($compile_blocks[$i])) ? $compile_blocks[$i] : ''); -		} - -		// Remove unused opening/closing tags -		$template_php = str_replace(' ?><?php ', ' ', $template_php); - -		// Now add a newline after each php closing tag which already has a newline -		// PHP itself strips a newline if a closing tag is used (this is documented behaviour) and it is mostly not intended by style authors to remove newlines -		$template_php = preg_replace('#\?\>([\r\n])#', '?>\1\1', $template_php); - -		// There will be a number of occasions where we switch into and out of -		// PHP mode instantaneously. Rather than "burden" the parser with this -		// we'll strip out such occurences, minimising such switching -		if ($no_echo) -		{ -			return "\$$echo_var .= '" . $template_php . "'"; -		} - -		return $template_php; -	} - -	/** -	* Compile variables -	* @access private -	*/ -	function compile_var_tags(&$text_blocks) -	{ -		// change template varrefs into PHP varrefs -		$varrefs = array(); - -		// This one will handle varrefs WITH namespaces -		preg_match_all('#\{((?:[a-z0-9\-_]+\.)+)(\$)?([A-Z0-9\-_]+)\}#', $text_blocks, $varrefs, PREG_SET_ORDER); - -		foreach ($varrefs as $var_val) -		{ -			$namespace = $var_val[1]; -			$varname = $var_val[3]; -			$new = $this->generate_block_varref($namespace, $varname, true, $var_val[2]); - -			$text_blocks = str_replace($var_val[0], $new, $text_blocks); -		} - -		// This will handle the remaining root-level varrefs -		// transform vars prefixed by L_ into their language variable pendant if nothing is set within the tpldata array -		if (strpos($text_blocks, '{L_') !== false) -		{ -			$text_blocks = preg_replace('#\{L_([A-Z0-9\-_]+)\}#', "<?php echo ((isset(\$this->_rootref['L_\\1'])) ? \$this->_rootref['L_\\1'] : ((isset(\$user->lang['\\1'])) ? \$user->lang['\\1'] : '{ \\1 }')); ?>", $text_blocks); -		} - -		// Handle addslashed language variables prefixed with LA_ -		// If a template variable already exist, it will be used in favor of it... -		if (strpos($text_blocks, '{LA_') !== false) -		{ -			$text_blocks = preg_replace('#\{LA_([A-Z0-9\-_]+)\}#', "<?php echo ((isset(\$this->_rootref['LA_\\1'])) ? \$this->_rootref['LA_\\1'] : ((isset(\$this->_rootref['L_\\1'])) ? addslashes(\$this->_rootref['L_\\1']) : ((isset(\$user->lang['\\1'])) ? addslashes(\$user->lang['\\1']) : '{ \\1 }'))); ?>", $text_blocks); -		} - -		// Handle remaining varrefs -		$text_blocks = preg_replace('#\{([A-Z0-9\-_]+)\}#', "<?php echo (isset(\$this->_rootref['\\1'])) ? \$this->_rootref['\\1'] : ''; ?>", $text_blocks); -		$text_blocks = preg_replace('#\{\$([A-Z0-9\-_]+)\}#', "<?php echo (isset(\$this->_tpldata['DEFINE']['.']['\\1'])) ? \$this->_tpldata['DEFINE']['.']['\\1'] : ''; ?>", $text_blocks); - -		return; -	} - -	/** -	* Compile blocks -	* @access private -	*/ -	function compile_tag_block($tag_args) -	{ -		$no_nesting = false; - -		// Is the designer wanting to call another loop in a loop? -		if (strpos($tag_args, '!') === 0) -		{ -			// Count the number of ! occurrences (not allowed in vars) -			$no_nesting = substr_count($tag_args, '!'); -			$tag_args = substr($tag_args, $no_nesting); -		} - -		// Allow for control of looping (indexes start from zero): -		// foo(2)    : Will start the loop on the 3rd entry -		// foo(-2)   : Will start the loop two entries from the end -		// foo(3,4)  : Will start the loop on the fourth entry and end it on the fifth -		// foo(3,-4) : Will start the loop on the fourth entry and end it four from last -		if (preg_match('#^([^()]*)\(([\-\d]+)(?:,([\-\d]+))?\)$#', $tag_args, $match)) -		{ -			$tag_args = $match[1]; - -			if ($match[2] < 0) -			{ -				$loop_start = '($_' . $tag_args . '_count ' . $match[2] . ' < 0 ? 0 : $_' . $tag_args . '_count ' . $match[2] . ')'; -			} -			else -			{ -				$loop_start = '($_' . $tag_args . '_count < ' . $match[2] . ' ? $_' . $tag_args . '_count : ' . $match[2] . ')'; -			} - -			if (strlen($match[3]) < 1 || $match[3] == -1) -			{ -				$loop_end = '$_' . $tag_args . '_count'; -			} -			else if ($match[3] >= 0) -			{ -				$loop_end = '(' . ($match[3] + 1) . ' > $_' . $tag_args . '_count ? $_' . $tag_args . '_count : ' . ($match[3] + 1) . ')'; -			} -			else //if ($match[3] < -1) -			{ -				$loop_end = '$_' . $tag_args . '_count' . ($match[3] + 1); -			} -		} -		else -		{ -			$loop_start = 0; -			$loop_end = '$_' . $tag_args . '_count'; -		} - -		$tag_template_php = ''; -		array_push($this->block_names, $tag_args); - -		if ($no_nesting !== false) -		{ -			// We need to implode $no_nesting times from the end... -			$block = array_slice($this->block_names, -$no_nesting); -		} -		else -		{ -			$block = $this->block_names; -		} - -		if (sizeof($block) < 2) -		{ -			// Block is not nested. -			$tag_template_php = '$_' . $tag_args . "_count = (isset(\$this->_tpldata['$tag_args'])) ? sizeof(\$this->_tpldata['$tag_args']) : 0;"; -			$varref = "\$this->_tpldata['$tag_args']"; -		} -		else -		{ -			// This block is nested. -			// Generate a namespace string for this block. -			$namespace = implode('.', $block); - -			// Get a reference to the data array for this block that depends on the -			// current indices of all parent blocks. -			$varref = $this->generate_block_data_ref($namespace, false); - -			// Create the for loop code to iterate over this block. -			$tag_template_php = '$_' . $tag_args . '_count = (isset(' . $varref . ')) ? sizeof(' . $varref . ') : 0;'; -		} - -		$tag_template_php .= 'if ($_' . $tag_args . '_count) {'; - -		/** -		* The following uses foreach for iteration instead of a for loop, foreach is faster but requires PHP to make a copy of the contents of the array which uses more memory -		* <code> -		*	if (!$offset) -		*	{ -		*		$tag_template_php .= 'foreach (' . $varref . ' as $_' . $tag_args . '_i => $_' . $tag_args . '_val){'; -		*	} -		* </code> -		*/ - -		$tag_template_php .= 'for ($_' . $tag_args . '_i = ' . $loop_start . '; $_' . $tag_args . '_i < ' . $loop_end . '; ++$_' . $tag_args . '_i){'; -		$tag_template_php .= '$_'. $tag_args . '_val = &' . $varref . '[$_'. $tag_args. '_i];'; - -		return $tag_template_php; -	} - -	/** -	* Compile IF tags - much of this is from Smarty with -	* some adaptions for our block level methods -	* @access private -	*/ -	function compile_tag_if($tag_args, $elseif) -	{ -		// Tokenize args for 'if' tag. -		preg_match_all('/(?: -			"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"         | -			\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'     | -			[(),]                                  | -			[^\s(),]+)/x', $tag_args, $match); - -		$tokens = $match[0]; -		$is_arg_stack = array(); - -		for ($i = 0, $size = sizeof($tokens); $i < $size; $i++) -		{ -			$token = &$tokens[$i]; - -			switch ($token) -			{ -				case '!==': -				case '===': -				case '<<': -				case '>>': -				case '|': -				case '^': -				case '&': -				case '~': -				case ')': -				case ',': -				case '+': -				case '-': -				case '*': -				case '/': -				case '@': -				break; - -				case '==': -				case 'eq': -					$token = '=='; -				break; - -				case '!=': -				case '<>': -				case 'ne': -				case 'neq': -					$token = '!='; -				break; - -				case '<': -				case 'lt': -					$token = '<'; -				break; - -				case '<=': -				case 'le': -				case 'lte': -					$token = '<='; -				break; - -				case '>': -				case 'gt': -					$token = '>'; -				break; - -				case '>=': -				case 'ge': -				case 'gte': -					$token = '>='; -				break; - -				case '&&': -				case 'and': -					$token = '&&'; -				break; - -				case '||': -				case 'or': -					$token = '||'; -				break; - -				case '!': -				case 'not': -					$token = '!'; -				break; - -				case '%': -				case 'mod': -					$token = '%'; -				break; - -				case '(': -					array_push($is_arg_stack, $i); -				break; - -				case 'is': -					$is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1; -					$is_arg	= implode('	', array_slice($tokens,	$is_arg_start, $i -	$is_arg_start)); - -					$new_tokens	= $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1)); - -					array_splice($tokens, $is_arg_start, sizeof($tokens), $new_tokens); - -					$i = $is_arg_start; - -				// no break - -				default: -					if (preg_match('#^((?:[a-z0-9\-_]+\.)+)?(\$)?(?=[A-Z])([A-Z0-9\-_]+)#s', $token, $varrefs)) -					{ -						$token = (!empty($varrefs[1])) ? $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[2]) . '[\'' . $varrefs[3] . '\']' : (($varrefs[2]) ? '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[3] . '\']' : '$this->_rootref[\'' . $varrefs[3] . '\']'); -					} -					else if (preg_match('#^\.((?:[a-z0-9\-_]+\.?)+)$#s', $token, $varrefs)) -					{ -						// Allow checking if loops are set with .loopname -						// It is also possible to check the loop count by doing <!-- IF .loopname > 1 --> for example -						$blocks = explode('.', $varrefs[1]); - -						// If the block is nested, we have a reference that we can grab. -						// If the block is not nested, we just go and grab the block from _tpldata -						if (sizeof($blocks) > 1) -						{ -							$block = array_pop($blocks); -							$namespace = implode('.', $blocks); -							$varref = $this->generate_block_data_ref($namespace, true); - -							// Add the block reference for the last child. -							$varref .= "['" . $block . "']"; -						} -						else -						{ -							$varref = '$this->_tpldata'; - -							// Add the block reference for the last child. -							$varref .= "['" . $blocks[0] . "']"; -						} -						$token = "sizeof($varref)"; -					} -					else if (!empty($token)) -					{ -						$token = '(' . $token . ')'; -					} - -				break; -			} -		} - -		// If there are no valid tokens left or only control/compare characters left, we do skip this statement -		if (!sizeof($tokens) || str_replace(array(' ', '=', '!', '<', '>', '&', '|', '%', '(', ')'), '', implode('', $tokens)) == '') -		{ -			$tokens = array('false'); -		} -		return (($elseif) ? '} else if (' : 'if (') . (implode(' ', $tokens) . ') { '); -	} - -	/** -	* Compile DEFINE tags -	* @access private -	*/ -	function compile_tag_define($tag_args, $op) -	{ -		preg_match('#^((?:[a-z0-9\-_]+\.)+)?\$(?=[A-Z])([A-Z0-9_\-]*)(?: = (\'?)([^\']*)(\'?))?$#', $tag_args, $match); - -		if (empty($match[2]) || (!isset($match[4]) && $op)) -		{ -			return ''; -		} - -		if (!$op) -		{ -			return 'unset(' . (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ');'; -		} - -		// Are we a string? -		if ($match[3] && $match[5]) -		{ -			$match[4] = str_replace(array('\\\'', '\\\\', '\''), array('\'', '\\', '\\\''), $match[4]); - -			// Compile reference, we allow template variables in defines... -			$match[4] = $this->compile($match[4]); - -			// Now replace the php code -			$match[4] = "'" . str_replace(array('<?php echo ', '; ?>'), array("' . ", " . '"), $match[4]) . "'"; -		} -		else -		{ -			preg_match('#true|false|\.#i', $match[4], $type); - -			switch (strtolower($type[0])) -			{ -				case 'true': -				case 'false': -					$match[4] = strtoupper($match[4]); -				break; - -				case '.': -					$match[4] = doubleval($match[4]); -				break; - -				default: -					$match[4] = intval($match[4]); -				break; -			} -		} - -		return (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$this->_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ' = ' . $match[4] . ';'; -	} - -	/** -	* Compile INCLUDE tag -	* @access private -	*/ -	function compile_tag_include($tag_args) -	{ -		// Process dynamic includes -		if ($tag_args[0] == '$') -		{ -			return "if (isset($tag_args)) { \$this->_tpl_include($tag_args); }"; -		} - -		return "\$this->_tpl_include('$tag_args');"; -	} - -	/** -	* Compile INCLUDE_PHP tag -	* @access private -	*/ -	function compile_tag_include_php($tag_args) -	{ -		return "\$this->_php_include('$tag_args');"; -	} - -	/** -	* parse expression -	* This is from Smarty -	* @access private -	*/ -	function _parse_is_expr($is_arg, $tokens) -	{ -		$expr_end = 0; -		$negate_expr = false; - -		if (($first_token = array_shift($tokens)) == 'not') -		{ -			$negate_expr = true; -			$expr_type = array_shift($tokens); -		} -		else -		{ -			$expr_type = $first_token; -		} - -		switch ($expr_type) -		{ -			case 'even': -				if (@$tokens[$expr_end] == 'by') -				{ -					$expr_end++; -					$expr_arg = $tokens[$expr_end++]; -					$expr = "!(($is_arg / $expr_arg) % $expr_arg)"; -				} -				else -				{ -					$expr = "!($is_arg & 1)"; -				} -			break; - -			case 'odd': -				if (@$tokens[$expr_end] == 'by') -				{ -					$expr_end++; -					$expr_arg = $tokens[$expr_end++]; -					$expr = "(($is_arg / $expr_arg) % $expr_arg)"; -				} -				else -				{ -					$expr = "($is_arg & 1)"; -				} -			break; - -			case 'div': -				if (@$tokens[$expr_end] == 'by') -				{ -					$expr_end++; -					$expr_arg = $tokens[$expr_end++]; -					$expr = "!($is_arg % $expr_arg)"; -				} -			break; -		} - -		if ($negate_expr) -		{ -			$expr = "!($expr)"; -		} - -		array_splice($tokens, 0, $expr_end, $expr); - -		return $tokens; -	} - -	/** -	* Generates a reference to the given variable inside the given (possibly nested) -	* block namespace. This is a string of the form: -	* ' . $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . ' -	* It's ready to be inserted into an "echo" line in one of the templates. -	* NOTE: expects a trailing "." on the namespace. -	* @access private -	*/ -	function generate_block_varref($namespace, $varname, $echo = true, $defop = false) -	{ -		// Strip the trailing period. -		$namespace = substr($namespace, 0, -1); - -		// Get a reference to the data block for this namespace. -		$varref = $this->generate_block_data_ref($namespace, true, $defop); -		// Prepend the necessary code to stick this in an echo line. - -		// Append the variable reference. -		$varref .= "['$varname']"; -		$varref = ($echo) ? "<?php echo $varref; ?>" : ((isset($varref)) ? $varref : ''); - -		return $varref; -	} - -	/** -	* Generates a reference to the array of data values for the given -	* (possibly nested) block namespace. This is a string of the form: -	* $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN'] -	* -	* If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above. -	* NOTE: does not expect a trailing "." on the blockname. -	* @access private -	*/ -	function generate_block_data_ref($blockname, $include_last_iterator, $defop = false) -	{ -		// Get an array of the blocks involved. -		$blocks = explode('.', $blockname); -		$blockcount = sizeof($blocks) - 1; - -		// DEFINE is not an element of any referenced variable, we must use _tpldata to access it -		if ($defop) -		{ -			$varref = '$this->_tpldata[\'DEFINE\']'; -			// Build up the string with everything but the last child. -			for ($i = 0; $i < $blockcount; $i++) -			{ -				$varref .= "['" . $blocks[$i] . "'][\$_" . $blocks[$i] . '_i]'; -			} -			// Add the block reference for the last child. -			$varref .= "['" . $blocks[$blockcount] . "']"; -			// Add the iterator for the last child if requried. -			if ($include_last_iterator) -			{ -				$varref .= '[$_' . $blocks[$blockcount] . '_i]'; -			} -			return $varref; -		} -		else if ($include_last_iterator) -		{ -			return '$_'. $blocks[$blockcount] . '_val'; -		} -		else -		{ -			return '$_'. $blocks[$blockcount - 1] . '_val[\''. $blocks[$blockcount]. '\']'; -		} -	} - -	/** -	* Write compiled file to cache directory -	* @access private -	*/ -	function compile_write($handle, $data) -	{ -		global $phpEx; - -		$filename = $this->template->cachepath . str_replace('/', '.', $this->template->filename[$handle]) . '.' . $phpEx; - -		$data = "<?php if (!defined('IN_PHPBB')) exit;" . ((strpos($data, '<?php') === 0) ? substr($data, 5) : ' ?>' . $data); - -		if ($fp = @fopen($filename, 'wb')) -		{ -			@flock($fp, LOCK_EX); -			@fwrite ($fp, $data); -			@flock($fp, LOCK_UN); -			@fclose($fp); - -			phpbb_chmod($filename, CHMOD_READ | CHMOD_WRITE); -		} - -		return; -	} -} diff --git a/phpBB/includes/template.php b/phpBB/includes/template.php deleted file mode 100644 index 5192c334d8..0000000000 --- a/phpBB/includes/template.php +++ /dev/null @@ -1,690 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @version $Id$ -* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc -* @license http://opensource.org/licenses/gpl-license.php GNU Public License -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ -	exit; -} - -/** -* Base Template class. -* @package phpBB3 -*/ -class template -{ -	/** variable that holds all the data we'll be substituting into -	* the compiled templates. Takes form: -	* --> $this->_tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value -	* if it's a root-level variable, it'll be like this: -	* --> $this->_tpldata[.][0][varname] == value -	*/ -	var $_tpldata = array('.' => array(0 => array())); -	var $_rootref; - -	// Root dir and hash of filenames for each template handle. -	var $root = ''; -	var $cachepath = ''; -	var $files = array(); -	var $filename = array(); -	var $files_inherit = array(); -	var $files_template = array(); -	var $inherit_root = ''; -	var $orig_tpl_storedb; -	var $orig_tpl_inherits_id; - -	// this will hash handle names to the compiled/uncompiled code for that handle. -	var $compiled_code = array(); - -	/** -	* Set template location -	* @access public -	*/ -	function set_template() -	{ -		global $phpbb_root_path, $user; - -		if (file_exists($phpbb_root_path . 'styles/' . $user->theme['template_path'] . '/template')) -		{ -			$this->root = $phpbb_root_path . 'styles/' . $user->theme['template_path'] . '/template'; -			$this->cachepath = $phpbb_root_path . 'cache/tpl_' . str_replace('_', '-', $user->theme['template_path']) . '_'; - -			if ($this->orig_tpl_storedb === null) -			{ -				$this->orig_tpl_storedb = $user->theme['template_storedb']; -			} - -			if ($this->orig_tpl_inherits_id === null) -			{ -				$this->orig_tpl_inherits_id = $user->theme['template_inherits_id']; -			} - -			$user->theme['template_storedb'] = $this->orig_tpl_storedb; -			$user->theme['template_inherits_id'] = $this->orig_tpl_inherits_id; - -			if ($user->theme['template_inherits_id']) -			{ -				$this->inherit_root = $phpbb_root_path . 'styles/' . $user->theme['template_inherit_path'] . '/template'; -			} -		} -		else -		{ -			trigger_error('Template path could not be found: styles/' . $user->theme['template_path'] . '/template', E_USER_ERROR); -		} - -		$this->_rootref = &$this->_tpldata['.'][0]; - -		return true; -	} - -	/** -	* Set custom template location (able to use directory outside of phpBB) -	* @access public -	*/ -	function set_custom_template($template_path, $template_name, $fallback_template_path = false) -	{ -		global $phpbb_root_path, $user; - -		// Make sure $template_path has no ending slash -		if (substr($template_path, -1) == '/') -		{ -			$template_path = substr($template_path, 0, -1); -		} - -		$this->root = $template_path; -		$this->cachepath = $phpbb_root_path . 'cache/ctpl_' . str_replace('_', '-', $template_name) . '_'; - -		if ($fallback_template_path !== false) -		{ -			if (substr($fallback_template_path, -1) == '/') -			{ -				$fallback_template_path = substr($fallback_template_path, 0, -1); -			} - -			$this->inherit_root = $fallback_template_path; -			$this->orig_tpl_inherits_id = true; -		} -		else -		{ -			$this->orig_tpl_inherits_id = false; -		} - -		// the database does not store the path or name of a custom template -		// so there is no way we can properly store custom templates there -		$this->orig_tpl_storedb = false; - -		$this->_rootref = &$this->_tpldata['.'][0]; - -		return true; -	} - -	/** -	* Sets the template filenames for handles. $filename_array -	* should be a hash of handle => filename pairs. -	* @access public -	*/ -	function set_filenames($filename_array) -	{ -		if (!is_array($filename_array)) -		{ -			return false; -		} -		foreach ($filename_array as $handle => $filename) -		{ -			if (empty($filename)) -			{ -				trigger_error("template->set_filenames: Empty filename specified for $handle", E_USER_ERROR); -			} - -			$this->filename[$handle] = $filename; -			$this->files[$handle] = $this->root . '/' . $filename; - -			if ($this->inherit_root) -			{ -				$this->files_inherit[$handle] = $this->inherit_root . '/' . $filename; -			} -		} - -		return true; -	} - -	/** -	* Destroy template data set -	* @access public -	*/ -	function destroy() -	{ -		$this->_tpldata = array('.' => array(0 => array())); -		$this->_rootref = &$this->_tpldata['.'][0]; -	} - -	/** -	* Reset/empty complete block -	* @access public -	*/ -	function destroy_block_vars($blockname) -	{ -		if (strpos($blockname, '.') !== false) -		{ -			// Nested block. -			$blocks = explode('.', $blockname); -			$blockcount = sizeof($blocks) - 1; - -			$str = &$this->_tpldata; -			for ($i = 0; $i < $blockcount; $i++) -			{ -				$str = &$str[$blocks[$i]]; -				$str = &$str[sizeof($str) - 1]; -			} - -			unset($str[$blocks[$blockcount]]); -		} -		else -		{ -			// Top-level block. -			unset($this->_tpldata[$blockname]); -		} - -		return true; -	} - -	/** -	* Display handle -	* @access public -	*/ -	function display($handle, $include_once = true) -	{ -		global $user, $phpbb_hook; - -		if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, __FUNCTION__), $handle, $include_once, $this)) -		{ -			if ($phpbb_hook->hook_return(array(__CLASS__, __FUNCTION__))) -			{ -				return $phpbb_hook->hook_return_result(array(__CLASS__, __FUNCTION__)); -			} -		} - -		if (defined('IN_ERROR_HANDLER')) -		{ -			if ((E_NOTICE & error_reporting()) == E_NOTICE) -			{ -				error_reporting(error_reporting() ^ E_NOTICE); -			} -		} - -		if ($filename = $this->_tpl_load($handle)) -		{ -			($include_once) ? include_once($filename) : include($filename); -		} -		else -		{ -			eval(' ?>' . $this->compiled_code[$handle] . '<?php '); -		} - -		return true; -	} - -	/** -	* Display the handle and assign the output to a template variable or return the compiled result. -	* @access public -	*/ -	function assign_display($handle, $template_var = '', $return_content = true, $include_once = false) -	{ -		ob_start(); -		$this->display($handle, $include_once); -		$contents = ob_get_clean(); - -		if ($return_content) -		{ -			return $contents; -		} - -		$this->assign_var($template_var, $contents); - -		return true; -	} - -	/** -	* Load a compiled template if possible, if not, recompile it -	* @access private -	*/ -	function _tpl_load(&$handle) -	{ -		global $user, $phpEx, $config; - -		if (!isset($this->filename[$handle])) -		{ -			trigger_error("template->_tpl_load(): No file specified for handle $handle", E_USER_ERROR); -		} - -		// reload these settings to have the values they had when this object was initialised -		// using set_template or set_custom_template, they might otherwise have been overwritten -		// by other template class instances in between. -		$user->theme['template_storedb'] = $this->orig_tpl_storedb; -		$user->theme['template_inherits_id'] = $this->orig_tpl_inherits_id; - -		$filename = $this->cachepath . str_replace('/', '.', $this->filename[$handle]) . '.' . $phpEx; -		$this->files_template[$handle] = (isset($user->theme['template_id'])) ? $user->theme['template_id'] : 0; - -		$recompile = false; -		if (!file_exists($filename) || @filesize($filename) === 0 || defined('DEBUG_EXTRA')) -		{ -			$recompile = true; -		} -		else if ($config['load_tplcompile']) -		{ -			// No way around it: we need to check inheritance here -			if ($user->theme['template_inherits_id'] && !file_exists($this->files[$handle])) -			{ -				$this->files[$handle] = $this->files_inherit[$handle]; -				$this->files_template[$handle] = $user->theme['template_inherits_id']; -			} -			$recompile = (@filemtime($filename) < filemtime($this->files[$handle])) ? true : false; -		} - -		// Recompile page if the original template is newer, otherwise load the compiled version -		if (!$recompile) -		{ -			return $filename; -		} - -		global $db, $phpbb_root_path; - -		if (!class_exists('template_compile')) -		{ -			include($phpbb_root_path . 'includes/functions_template.' . $phpEx); -		} - -		// Inheritance - we point to another template file for this one. Equality is also used for store_db -		if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id'] && !file_exists($this->files[$handle])) -		{ -			$this->files[$handle] = $this->files_inherit[$handle]; -			$this->files_template[$handle] = $user->theme['template_inherits_id']; -		} - -		$compile = new template_compile($this); - -		// If we don't have a file assigned to this handle, die. -		if (!isset($this->files[$handle])) -		{ -			trigger_error("template->_tpl_load(): No file specified for handle $handle", E_USER_ERROR); -		} - -		// Just compile if no user object is present (happens within the installer) -		if (!$user) -		{ -			$compile->_tpl_load_file($handle); -			return false; -		} - -		if (isset($user->theme['template_storedb']) && $user->theme['template_storedb']) -		{ -			$rows = array(); -			$ids = array(); -			// Inheritance -			if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id']) -			{ -				$ids[] = $user->theme['template_inherits_id']; -			} -			$ids[] = $user->theme['template_id']; - -			foreach ($ids as $id) -			{ -				$sql = 'SELECT * -				FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' -				WHERE template_id = ' . $id . " -					AND (template_filename = '" . $db->sql_escape($this->filename[$handle]) . "' -						OR template_included " . $db->sql_like_expression($db->any_char . $this->filename[$handle] . ':' . $db->any_char) . ')'; - -				$result = $db->sql_query($sql); -				while ($row = $db->sql_fetchrow($result)) -				{ -					$rows[$row['template_filename']] = $row; -				} -				$db->sql_freeresult($result); -			} - -			if (sizeof($rows)) -			{ -				foreach ($rows as $row) -				{ -					$file = $this->root . '/' . $row['template_filename']; -					$force_reload = false; -					if ($row['template_id'] != $user->theme['template_id']) -					{ -						// make sure that we are not overlooking a file not in the db yet -						if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id'] && !file_exists($file)) -						{ -							$file = $this->inherit_root . '/' . $row['template_filename']; -							$this->files[$row['template_filename']] = $file; -							$this->files_inherit[$row['template_filename']] = $file; -							$this->files_template[$row['template_filename']] = $user->theme['template_inherits_id']; -						} -						else if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id']) -						{ -							// Ok, we have a situation. There is a file in the subtemplate, but nothing in the DB. We have to fix that. -							$force_reload = true; -							$this->files_template[$row['template_filename']] = $user->theme['template_inherits_id']; -						} -					} -					else -					{ -						$this->files_template[$row['template_filename']] = $user->theme['template_id']; -					} - -					if ($force_reload || $row['template_mtime'] < filemtime($file)) -					{ -						if ($row['template_filename'] == $this->filename[$handle]) -						{ -							$compile->_tpl_load_file($handle, true); -						} -						else -						{ -							$this->files[$row['template_filename']] = $file; -							$this->filename[$row['template_filename']] = $row['template_filename']; -							$compile->_tpl_load_file($row['template_filename'], true); -							unset($this->compiled_code[$row['template_filename']]); -							unset($this->files[$row['template_filename']]); -							unset($this->filename[$row['template_filename']]); -						} -					} - -					if ($row['template_filename'] == $this->filename[$handle]) -					{ -						$this->compiled_code[$handle] = $compile->compile(trim($row['template_data'])); -						$compile->compile_write($handle, $this->compiled_code[$handle]); -					} -					else -					{ -						// Only bother compiling if it doesn't already exist -						if (!file_exists($this->cachepath . str_replace('/', '.', $row['template_filename']) . '.' . $phpEx)) -						{ -							$this->filename[$row['template_filename']] = $row['template_filename']; -							$compile->compile_write($row['template_filename'], $compile->compile(trim($row['template_data']))); -							unset($this->filename[$row['template_filename']]); -						} -					} -				} -			} -			else -			{ -				$file = $this->root . '/' . $row['template_filename']; - -				if (isset($user->theme['template_inherits_id']) && $user->theme['template_inherits_id'] && !file_exists($file)) -				{ -					$file = $this->inherit_root . '/' . $row['template_filename']; -					$this->files[$row['template_filename']] = $file; -					$this->files_inherit[$row['template_filename']] = $file; -					$this->files_template[$row['template_filename']] = $user->theme['template_inherits_id']; -				} -				// Try to load from filesystem and instruct to insert into the styles table... -				$compile->_tpl_load_file($handle, true); -				return false; -			} - -			return false; -		} - -		$compile->_tpl_load_file($handle); -		return false; -	} - -	/** -	* Assign key variable pairs from an array -	* @access public -	*/ -	function assign_vars($vararray) -	{ -		foreach ($vararray as $key => $val) -		{ -			$this->_rootref[$key] = $val; -		} - -		return true; -	} - -	/** -	* Assign a single variable to a single key -	* @access public -	*/ -	function assign_var($varname, $varval) -	{ -		$this->_rootref[$varname] = $varval; - -		return true; -	} - -	/** -	* Assign key variable pairs from an array to a specified block -	* @access public -	*/ -	function assign_block_vars($blockname, $vararray) -	{ -		if (strpos($blockname, '.') !== false) -		{ -			// Nested block. -			$blocks = explode('.', $blockname); -			$blockcount = sizeof($blocks) - 1; - -			$str = &$this->_tpldata; -			for ($i = 0; $i < $blockcount; $i++) -			{ -				$str = &$str[$blocks[$i]]; -				$str = &$str[sizeof($str) - 1]; -			} - -			$s_row_count = isset($str[$blocks[$blockcount]]) ? sizeof($str[$blocks[$blockcount]]) : 0; -			$vararray['S_ROW_COUNT'] = $s_row_count; - -			// Assign S_FIRST_ROW -			if (!$s_row_count) -			{ -				$vararray['S_FIRST_ROW'] = true; -			} - -			// Now the tricky part, we always assign S_LAST_ROW and remove the entry before -			// This is much more clever than going through the complete template data on display (phew) -			$vararray['S_LAST_ROW'] = true; -			if ($s_row_count > 0) -			{ -				unset($str[$blocks[$blockcount]][($s_row_count - 1)]['S_LAST_ROW']); -			} - -			// Now we add the block that we're actually assigning to. -			// We're adding a new iteration to this block with the given -			// variable assignments. -			$str[$blocks[$blockcount]][] = $vararray; -		} -		else -		{ -			// Top-level block. -			$s_row_count = (isset($this->_tpldata[$blockname])) ? sizeof($this->_tpldata[$blockname]) : 0; -			$vararray['S_ROW_COUNT'] = $s_row_count; - -			// Assign S_FIRST_ROW -			if (!$s_row_count) -			{ -				$vararray['S_FIRST_ROW'] = true; -			} - -			// We always assign S_LAST_ROW and remove the entry before -			$vararray['S_LAST_ROW'] = true; -			if ($s_row_count > 0) -			{ -				unset($this->_tpldata[$blockname][($s_row_count - 1)]['S_LAST_ROW']); -			} - -			// Add a new iteration to this block with the variable assignments we were given. -			$this->_tpldata[$blockname][] = $vararray; -		} - -		return true; -	} - -	/** -	* Change already assigned key variable pair (one-dimensional - single loop entry) -	* -	* An example of how to use this function: -	* {@example alter_block_array.php} -	* -	* @param	string	$blockname	the blockname, for example 'loop' -	* @param	array	$vararray	the var array to insert/add or merge -	* @param	mixed	$key		Key to search for -	* -	* array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] -	* -	* int: Position [the position to change or insert at directly given] -	* -	* If key is false the position is set to 0 -	* If key is true the position is set to the last entry -	* -	* @param	string	$mode		Mode to execute (valid modes are 'insert' and 'change') -	* -	*	If insert, the vararray is inserted at the given position (position counting from zero). -	*	If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). -	* -	* Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) -	* and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) -	* -	* @return bool false on error, true on success -	* @access public -	*/ -	function alter_block_array($blockname, $vararray, $key = false, $mode = 'insert') -	{ -		if (strpos($blockname, '.') !== false) -		{ -			// Nested blocks are not supported -			return false; -		} - -		// Change key to zero (change first position) if false and to last position if true -		if ($key === false || $key === true) -		{ -			$key = ($key === false) ? 0 : sizeof($this->_tpldata[$blockname]); -		} - -		// Get correct position if array given -		if (is_array($key)) -		{ -			// Search array to get correct position -			list($search_key, $search_value) = @each($key); - -			$key = NULL; -			foreach ($this->_tpldata[$blockname] as $i => $val_ary) -			{ -				if ($val_ary[$search_key] === $search_value) -				{ -					$key = $i; -					break; -				} -			} - -			// key/value pair not found -			if ($key === NULL) -			{ -				return false; -			} -		} - -		// Insert Block -		if ($mode == 'insert') -		{ -			// Make sure we are not exceeding the last iteration -			if ($key >= sizeof($this->_tpldata[$blockname])) -			{ -				$key = sizeof($this->_tpldata[$blockname]); -				unset($this->_tpldata[$blockname][($key - 1)]['S_LAST_ROW']); -				$vararray['S_LAST_ROW'] = true; -			} -			else if ($key === 0) -			{ -				unset($this->_tpldata[$blockname][0]['S_FIRST_ROW']); -				$vararray['S_FIRST_ROW'] = true; -			} - -			// Re-position template blocks -			for ($i = sizeof($this->_tpldata[$blockname]); $i > $key; $i--) -			{ -				$this->_tpldata[$blockname][$i] = $this->_tpldata[$blockname][$i-1]; -				$this->_tpldata[$blockname][$i]['S_ROW_COUNT'] = $i; -			} - -			// Insert vararray at given position -			$vararray['S_ROW_COUNT'] = $key; -			$this->_tpldata[$blockname][$key] = $vararray; - -			return true; -		} - -		// Which block to change? -		if ($mode == 'change') -		{ -			if ($key == sizeof($this->_tpldata[$blockname])) -			{ -				$key--; -			} - -			$this->_tpldata[$blockname][$key] = array_merge($this->_tpldata[$blockname][$key], $vararray); -			return true; -		} - -		return false; -	} - -	/** -	* Include a separate template -	* @access private -	*/ -	function _tpl_include($filename, $include = true) -	{ -		$handle = $filename; -		$this->filename[$handle] = $filename; -		$this->files[$handle] = $this->root . '/' . $filename; -		if ($this->inherit_root) -		{ -			$this->files_inherit[$handle] = $this->inherit_root . '/' . $filename; -		} - -		$filename = $this->_tpl_load($handle); - -		if ($include) -		{ -			global $user; - -			if ($filename) -			{ -				include($filename); -				return; -			} -			eval(' ?>' . $this->compiled_code[$handle] . '<?php '); -		} -	} - -	/** -	* Include a php-file -	* @access private -	*/ -	function _php_include($filename) -	{ -		global $phpbb_root_path; - -		$file = $phpbb_root_path . $filename; - -		if (!file_exists($file)) -		{ -			// trigger_error cannot be used here, as the output already started -			echo 'template->_php_include(): File ' . htmlspecialchars($file) . ' does not exist or is empty'; -			return; -		} -		include($file); -	} -} diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php new file mode 100644 index 0000000000..8d8560ded5 --- /dev/null +++ b/phpBB/includes/template/compile.php @@ -0,0 +1,122 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +stream_filter_register('phpbb_template', 'phpbb_template_filter'); + +/** +* Extension of template class - Functions needed for compiling templates only. +* +* @package phpBB3 +* @uses template_filter As a PHP stream filter to perform compilation of templates +*/ +class phpbb_template_compile +{ +	/** +	* Whether <!-- PHP --> tags are allowed +	* +	* @var bool +	*/ +	private $allow_php; + +	/** +	* Constructor. +	* +	* @param bool @allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag) +	*/ +	public function __construct($allow_php) +	{ +		$this->allow_php = $allow_php; +	} + +	/** +	* Compiles template in $source_file and writes compiled template to +	* cache directory +	* +	* @param string $handle Template handle to compile +	* @param string $source_file Source template file +	* @return bool Return true on success otherwise false +	*/ +	public function compile_file_to_file($source_file, $compiled_file) +	{ +		$source_handle = @fopen($source_file, 'rb'); +		$destination_handle = @fopen($compiled_file, 'wb'); + +		if (!$source_handle || !$destination_handle) +		{ +			return false; +		} + +		@flock($destination_handle, LOCK_EX); + +		$this->compile_stream_to_stream($source_handle, $destination_handle); + +		@fclose($source_handle); +		@flock($destination_handle, LOCK_UN); +		@fclose($destination_handle); + +		phpbb_chmod($compiled_file, CHMOD_READ | CHMOD_WRITE); + +		clearstatcache(); + +		return true; +	} + +	/** +	* Compiles a template located at $source_file. +	* +	* Returns PHP source suitable for eval(). +	* +	* @param string $source_file Source template file +	* @return string|bool Return compiled code on successful compilation otherwise false +	*/ +	public function compile_file($source_file) +	{ +		$source_handle = @fopen($source_file, 'rb'); +		$destination_handle = @fopen('php://temp' ,'r+b'); + +		if (!$source_handle || !$destination_handle) +		{ +			return false; +		} + +		$this->compile_stream_to_stream($source_handle, $destination_handle); + +		@fclose($source_handle); + +		rewind($destination_handle); +		$contents = stream_get_contents($destination_handle); +		@fclose($dest_handle); + +		return $contents; +	} + +	/** +	* Compiles contents of $source_stream into $dest_stream. +	* +	* A stream filter is appended to $source_stream as part of the +	* process. +	* +	* @param resource $source_stream Source stream +	* @param resource $dest_stream Destination stream +	* @return void +	*/ +	private function compile_stream_to_stream($source_stream, $dest_stream) +	{ +		stream_filter_append($source_stream, 'phpbb_template', null, array('allow_php' => $this->allow_php)); +		stream_copy_to_stream($source_stream, $dest_stream); +	} +} diff --git a/phpBB/includes/template/context.php b/phpBB/includes/template/context.php new file mode 100644 index 0000000000..f85f86a0ec --- /dev/null +++ b/phpBB/includes/template/context.php @@ -0,0 +1,342 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Stores variables assigned to template. +* +* @package phpBB3 +*/ +class phpbb_template_context +{ +	/** +	* variable that holds all the data we'll be substituting into +	* the compiled templates. Takes form: +	* --> $this->tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value +	* if it's a root-level variable, it'll be like this: +	* --> $this->tpldata[.][0][varname] == value +	* +	* @var array +	*/ +	private $tpldata = array('.' => array(0 => array())); + +	/** +	* @var array Reference to template->tpldata['.'][0] +	*/ +	private $rootref; + +	public function __construct() +	{ +		$this->clear(); +	} + +	/** +	* Clears template data set. +	*/ +	public function clear() +	{ +		$this->tpldata = array('.' => array(0 => array())); +		$this->rootref = &$this->tpldata['.'][0]; +	} + +	/** +	* Assign a single variable to a single key +	* +	* @param string $varname Variable name +	* @param string $varval Value to assign to variable +	*/ +	public function assign_var($varname, $varval) +	{ +		$this->rootref[$varname] = $varval; + +		return true; +	} + +	/** +	* Returns a reference to template data array. +	* +	* This function is public so that template renderer may invoke it. +	* Users should alter template variables via functions in phpbb_template. +	* +	* Note: modifying returned array will affect data stored in the context. +	* +	* @return array template data +	*/ +	public function &get_data_ref() +	{ +		// returning a reference directly is not +		// something php is capable of doing +		$ref = &$this->tpldata; +		return $ref; +	} + +	/** +	* Returns a reference to template root scope. +	* +	* This function is public so that template renderer may invoke it. +	* Users should not need to invoke this function. +	* +	* Note: modifying returned array will affect data stored in the context. +	* +	* @return array template data +	*/ +	public function &get_root_ref() +	{ +		// rootref is already a reference +		return $this->rootref; +	} + +	/** +	* Assign key variable pairs from an array to a specified block +	* +	* @param string $blockname Name of block to assign $vararray to +	* @param array $vararray A hash of variable name => value pairs +	*/ +	public function assign_block_vars($blockname, array $vararray) +	{ +		if (strpos($blockname, '.') !== false) +		{ +			// Nested block. +			$blocks = explode('.', $blockname); +			$blockcount = sizeof($blocks) - 1; + +			$str = &$this->tpldata; +			for ($i = 0; $i < $blockcount; $i++) +			{ +				$str = &$str[$blocks[$i]]; +				$str = &$str[sizeof($str) - 1]; +			} + +			$s_row_count = isset($str[$blocks[$blockcount]]) ? sizeof($str[$blocks[$blockcount]]) : 0; +			$vararray['S_ROW_COUNT'] = $s_row_count; + +			// Assign S_FIRST_ROW +			if (!$s_row_count) +			{ +				$vararray['S_FIRST_ROW'] = true; +			} + +			// Now the tricky part, we always assign S_LAST_ROW and remove the entry before +			// This is much more clever than going through the complete template data on display (phew) +			$vararray['S_LAST_ROW'] = true; +			if ($s_row_count > 0) +			{ +				unset($str[$blocks[$blockcount]][($s_row_count - 1)]['S_LAST_ROW']); +			} + +			// Now we add the block that we're actually assigning to. +			// We're adding a new iteration to this block with the given +			// variable assignments. +			$str[$blocks[$blockcount]][] = $vararray; +		} +		else +		{ +			// Top-level block. +			$s_row_count = (isset($this->tpldata[$blockname])) ? sizeof($this->tpldata[$blockname]) : 0; +			$vararray['S_ROW_COUNT'] = $s_row_count; + +			// Assign S_FIRST_ROW +			if (!$s_row_count) +			{ +				$vararray['S_FIRST_ROW'] = true; +			} + +			// We always assign S_LAST_ROW and remove the entry before +			$vararray['S_LAST_ROW'] = true; +			if ($s_row_count > 0) +			{ +				unset($this->tpldata[$blockname][($s_row_count - 1)]['S_LAST_ROW']); +			} + +			// Add a new iteration to this block with the variable assignments we were given. +			$this->tpldata[$blockname][] = $vararray; +		} + +		return true; +	} + +	/** +	* Change already assigned key variable pair (one-dimensional - single loop entry) +	* +	* An example of how to use this function: +	* {@example alter_block_array.php} +	* +	* @param	string	$blockname	the blockname, for example 'loop' +	* @param	array	$vararray	the var array to insert/add or merge +	* @param	mixed	$key		Key to search for +	* +	* array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] +	* +	* int: Position [the position to change or insert at directly given] +	* +	* If key is false the position is set to 0 +	* If key is true the position is set to the last entry +	* +	* @param	string	$mode		Mode to execute (valid modes are 'insert' and 'change') +	* +	*	If insert, the vararray is inserted at the given position (position counting from zero). +	*	If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). +	* +	* Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) +	* and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) +	* +	* @return bool false on error, true on success +	*/ +	public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') +	{ +		if (strpos($blockname, '.') !== false) +		{ +			// Nested block. +			$blocks = explode('.', $blockname); +			$blockcount = sizeof($blocks) - 1; + +			$block = &$this->tpldata; +			for ($i = 0; $i < $blockcount; $i++) +			{ +				if (($pos = strpos($blocks[$i], '[')) !== false) +				{ +					$name = substr($blocks[$i], 0, $pos); + +					if (strpos($blocks[$i], '[]') === $pos) +					{ +						$index = sizeof($block[$name]) - 1; +					} +					else +					{ +						$index = min((int) substr($blocks[$i], $pos + 1, -1), sizeof($block[$name]) - 1); +					} +				} +				else +				{ +					$name = $blocks[$i]; +					$index = sizeof($block[$name]) - 1; +				} +				$block = &$block[$name]; +				$block = &$block[$index]; +			} + +			$block = &$block[$blocks[$i]]; // Traverse the last block +		} +		else +		{ +			// Top-level block. +			$block = &$this->tpldata[$blockname]; +		} + +		// Change key to zero (change first position) if false and to last position if true +		if ($key === false || $key === true) +		{ +			$key = ($key === false) ? 0 : sizeof($block); +		} + +		// Get correct position if array given +		if (is_array($key)) +		{ +			// Search array to get correct position +			list($search_key, $search_value) = @each($key); + +			$key = NULL; +			foreach ($block as $i => $val_ary) +			{ +				if ($val_ary[$search_key] === $search_value) +				{ +					$key = $i; +					break; +				} +			} + +			// key/value pair not found +			if ($key === NULL) +			{ +				return false; +			} +		} + +		// Insert Block +		if ($mode == 'insert') +		{ +			// Make sure we are not exceeding the last iteration +			if ($key >= sizeof($this->tpldata[$blockname])) +			{ +				$key = sizeof($this->tpldata[$blockname]); +				unset($this->tpldata[$blockname][($key - 1)]['S_LAST_ROW']); +				$vararray['S_LAST_ROW'] = true; +			} +			else if ($key === 0) +			{ +				unset($this->tpldata[$blockname][0]['S_FIRST_ROW']); +				$vararray['S_FIRST_ROW'] = true; +			} + +			// Re-position template blocks +			for ($i = sizeof($block); $i > $key; $i--) +			{ +				$block[$i] = $block[$i-1]; +			} + +			// Insert vararray at given position +			$block[$key] = $vararray; + +			return true; +		} + +		// Which block to change? +		if ($mode == 'change') +		{ +			if ($key == sizeof($block)) +			{ +				$key--; +			} + +			$block[$key] = array_merge($block[$key], $vararray); + +			return true; +		} + +		return false; +	} + +	/** +	* Reset/empty complete block +	* +	* @param string $blockname Name of block to destroy +	*/ +	public function destroy_block_vars($blockname) +	{ +		if (strpos($blockname, '.') !== false) +		{ +			// Nested block. +			$blocks = explode('.', $blockname); +			$blockcount = sizeof($blocks) - 1; + +			$str = &$this->tpldata; +			for ($i = 0; $i < $blockcount; $i++) +			{ +				$str = &$str[$blocks[$i]]; +				$str = &$str[sizeof($str) - 1]; +			} + +			unset($str[$blocks[$blockcount]]); +		} +		else +		{ +			// Top-level block. +			unset($this->tpldata[$blockname]); +		} + +		return true; +	} +} diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php new file mode 100644 index 0000000000..1d1d92378e --- /dev/null +++ b/phpBB/includes/template/filter.php @@ -0,0 +1,923 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group, sections (c) 2001 ispi of Lincoln Inc +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* The template filter that does the actual compilation +* +* psoTFX, phpBB Development Team - Completion of file caching, decompilation +* routines and implementation of conditionals/keywords and associated changes +* +* The interface was inspired by PHPLib templates, and the template file (formats are +* quite similar) +* +* The keyword/conditional implementation is currently based on sections of code from +* the Smarty templating engine (c) 2001 ispi of Lincoln, Inc. which is released +* (on its own and in whole) under the LGPL. Section 3 of the LGPL states that any code +* derived from an LGPL application may be relicenced under the GPL, this applies +* to this source +* +* DEFINE directive inspired by a request by Cyberalien +* +* @see template_compile +* @package phpBB3 +*/ +class phpbb_template_filter extends php_user_filter +{ +	const REGEX_NS = '[a-z_][a-z_0-9]+'; + +	const REGEX_VAR = '[A-Z_][A-Z_0-9]+'; + +	const REGEX_TAG = '<!-- ([A-Z][A-Z_0-9]+)(?: (.*?) ?)?-->'; + +	const REGEX_TOKENS = '~<!-- ([A-Z][A-Z_0-9]+)(?: (.*?) ?)?-->|{((?:[a-z_][a-z_0-9]+\.)*\\$?[A-Z][A-Z_0-9]+)}~'; + +	/** +	* @var array +	*/ +	private $block_names = array(); + +	/** +	* @var array +	*/ +	private $block_else_level = array(); + +	/** +	* @var string +	*/ +	private $chunk; + +	/** +	* @var bool +	*/ +	private $in_php; + +	/** +	* Whether inline PHP code, <!-- PHP --> and <!-- INCLUDEPHP --> tags +	* are allowed. If this is false all PHP code will be silently +	* removed from the template during compilation. +	* +	* @var bool +	*/ +	private $allow_php; + +	/** +	* Stream filter +	* +	* Is invoked for evey chunk of the stream, allowing us +	* to work on a chunk at a time, which saves memory. +	*/ +	public function filter($in, $out, &$consumed, $closing) +	{ +		$written = false; +		$first = false; + +		while ($bucket = stream_bucket_make_writeable($in)) +		{ +			$consumed += $bucket->datalen; + +			$data = $this->chunk . $bucket->data; +			$last_nl = strrpos($data, "\n"); +			$this->chunk = substr($data, $last_nl); +			$data = substr($data, 0, $last_nl); + +			if (!strlen($data)) +			{ +				continue; +			} + +			$written = true; + +			$data = $this->compile($data); +			if (!$first) +			{ +				$data = $this->prepend_preamble($data); +				$first = false; +			} +			$bucket->data = $data; +			$bucket->datalen = strlen($bucket->data); +			stream_bucket_append($out, $bucket); +		} + +		if ($closing && strlen($this->chunk)) +		{ +			$written = true; +			$bucket = stream_bucket_new($this->stream, $this->compile($this->chunk)); +			stream_bucket_append($out, $bucket); +		} + +		return $written ? PSFS_PASS_ON : PSFS_FEED_ME; +	} + +	/** +	* Initializer, called on creation. +	* +	* Get the allow_php option from params, which is passed +	* to stream_filter_append. +	*/ +	public function onCreate() +	{ +		$this->chunk = ''; +		$this->in_php = false; +		$this->allow_php = $this->params['allow_php']; +		return true; +	} + +	/** +	* Compiles a chunk of template. +	* +	* The chunk must comprise of one or more complete lines from the source +	* template. +	* +	* @param string $data Chunk of source template to compile +	* @return string Compiled PHP/HTML code +	*/ +	private function compile($data) +	{ +		$block_start_in_php = $this->in_php; + +		$data = preg_replace('#<(?:[\\?%]|script)#s', '<?php echo\'\\0\';?>', $data); +		$data = preg_replace_callback(self::REGEX_TOKENS, array($this, 'replace'), $data); + +		// Remove php +		if (!$this->allow_php) +		{ +			if ($block_start_in_php +				&& $this->in_php +				&& strpos($data, '<!-- PHP -->') === false +				&& strpos($data, '<!-- ENDPHP -->') === false) +			{ +				// This is just php code +				return ''; +			} +			$data = preg_replace('~^.*?<!-- ENDPHP -->~', '', $data); +			$data = preg_replace('~<!-- PHP -->.*?<!-- ENDPHP -->~', '', $data); +			$data = preg_replace('~<!-- ENDPHP -->.*?$~', '', $data); +		} + +		/* +			Preserve whitespace. +			PHP removes a newline after the closing tag (if it's there). This is by design. + + +			Consider the following template: + +				<!-- IF condition --> +				some content +				<!-- ENDIF --> + +			If we were to simply preserve all whitespace, we could simply replace all "?>" tags +			with "?>\n". +			Doing that, would add additional newlines to the compiled tempalte in place of the +			IF and ENDIF statements. These newlines are unwanted (and one is conditional). +			The IF and ENDIF are usually on their own line for ease of reading. + +			This replacement preserves newlines only for statements that aren't the only statement on a line. +			It will NOT preserve newlines at the end of statements in the above examle. +			It will preserve newlines in situations like: + +				<!-- IF condition -->inline content<!-- ENDIF --> + + +		*/ + +		$data = preg_replace('~(?<!^)(<\?php(?:(?<!\?>).)+(?<!/\*\*/)\?>)$~m', "$1\n", $data); +		$data = str_replace('/**/?>', "?>\n", $data); +		$data = str_replace('?><?php', '', $data); +		return $data; +	} + +	/** +	* Prepends a preamble to compiled template. +	* Currently preamble checks if IN_PHPBB is defined and calls exit() if it is not. +	* +	* @param string $data Compiled template chunk +	* @return string Compiled template chunk with preamble prepended +	*/ +	private function prepend_preamble($data) +	{ +		$data = "<?php if (!defined('IN_PHPBB')) exit;" . ((strncmp($data, '<?php', 5) === 0) ? substr($data, 5) : ' ?>' . $data); +		return $data; +	} + +	/** +	* Callback for replacing matched tokens with PHP code +	* +	* @param array $matches Regular expression matches +	* @return string compiled template code +	*/ +	private function replace($matches) +	{ +		if ($this->in_php && $matches[1] != 'ENDPHP') +		{ +			return ''; +		} + +		if (isset($matches[3])) +		{ +			return $this->compile_var_tags($matches[0]); +		} + +		switch ($matches[1]) +		{ +			case 'BEGIN': +				$this->block_else_level[] = false; +				return '<?php ' . $this->compile_tag_block($matches[2]) . ' ?>'; +			break; + +			case 'BEGINELSE': +				$this->block_else_level[sizeof($this->block_else_level) - 1] = true; +				return '<?php }} else { ?>'; +			break; + +			case 'END': +				array_pop($this->block_names); +				return '<?php ' . ((array_pop($this->block_else_level)) ? '}' : '}}') . ' ?>'; +			break; + +			case 'IF': +				return '<?php ' . $this->compile_tag_if($matches[2], false) . ' ?>'; +			break; + +			case 'ELSE': +				return '<?php } else { ?>'; +			break; + +			case 'ELSEIF': +				return '<?php ' . $this->compile_tag_if($matches[2], true) . ' ?>'; +			break; + +			case 'ENDIF': +				return '<?php } ?>'; +			break; + +			case 'DEFINE': +				return '<?php ' . $this->compile_tag_define($matches[2], true) . ' ?>'; +			break; + +			case 'UNDEFINE': +				return '<?php ' . $this->compile_tag_define($matches[2], false) . ' ?>'; +			break; + +			case 'INCLUDE': +				return '<?php ' . $this->compile_tag_include($matches[2]) . ' ?>'; +			break; + +			case 'INCLUDEPHP': +				return ($this->allow_php) ? '<?php ' . $this->compile_tag_include_php($matches[2]) . ' ?>' : ''; +			break; + +			case 'PHP': +				if ($this->allow_php) +				{ +					$this->in_php = true; +					return '<?php '; +				} +				return '<!-- PHP -->'; +			break; + +			case 'ENDPHP': +				if ($this->allow_php) +				{ +					$this->in_php = false; +					return ' ?>'; +				} +				return '<!-- ENDPHP -->'; +			break; + +			default: +				return $matches[0]; +			break; + +		} +		return ''; +	} + +	/** +	* Compile variables +	* +	* @param string $text_blocks Variable reference in source template +	* @return string compiled template code +	*/ +	private function compile_var_tags(&$text_blocks) +	{ +		// change template varrefs into PHP varrefs +		$varrefs = array(); + +		// This one will handle varrefs WITH namespaces +		preg_match_all('#\{((?:' . self::REGEX_NS . '\.)+)(\$)?(' . self::REGEX_VAR . ')\}#', $text_blocks, $varrefs, PREG_SET_ORDER); + +		foreach ($varrefs as $var_val) +		{ +			$namespace = $var_val[1]; +			$varname = $var_val[3]; +			$new = $this->generate_block_varref($namespace, $varname, true, $var_val[2]); + +			$text_blocks = str_replace($var_val[0], $new, $text_blocks); +		} + +		// Handle special language tags L_ and LA_ +		$this->compile_language_tags($text_blocks); + +		// This will handle the remaining root-level varrefs +		$text_blocks = preg_replace('#\{(' . self::REGEX_VAR . ')\}#', "<?php echo (isset(\$_rootref['\\1'])) ? \$_rootref['\\1'] : ''; /**/?>", $text_blocks); +		$text_blocks = preg_replace('#\{\$(' . self::REGEX_VAR . ')\}#', "<?php echo (isset(\$_tpldata['DEFINE']['.']['\\1'])) ? \$_tpldata['DEFINE']['.']['\\1'] : ''; /**/?>", $text_blocks); + +		return $text_blocks; +	} + +	/** +	* Handles special language tags L_ and LA_ +	* +	* @param string $text_blocks Variable reference in source template +	*/ +	private function compile_language_tags(&$text_blocks) +	{ +		// transform vars prefixed by L_ into their language variable pendant if nothing is set within the tpldata array +		if (strpos($text_blocks, '{L_') !== false) +		{ +			$text_blocks = preg_replace('#\{L_(' . self::REGEX_VAR . ')\}#', "<?php echo ((isset(\$_rootref['L_\\1'])) ? \$_rootref['L_\\1'] : ((isset(\$_lang['\\1'])) ? \$_lang['\\1'] : '{ \\1 }')); /**/?>", $text_blocks); +		} + +		// Handle addslashed language variables prefixed with LA_ +		// If a template variable already exist, it will be used in favor of it... +		if (strpos($text_blocks, '{LA_') !== false) +		{ +			$text_blocks = preg_replace('#\{LA_(' . self::REGEX_VAR . '+)\}#', "<?php echo ((isset(\$_rootref['LA_\\1'])) ? \$_rootref['LA_\\1'] : ((isset(\$_rootref['L_\\1'])) ? addslashes(\$_rootref['L_\\1']) : ((isset(\$_lang['\\1'])) ? addslashes(\$_lang['\\1']) : '{ \\1 }'))); /**/?>", $text_blocks); +		} +	} + +	/** +	* Compile blocks +	* +	* @param string $tag_args Block contents in source template +	* @return string compiled template code +	*/ +	private function compile_tag_block($tag_args) +	{ +		$no_nesting = false; + +		// Is the designer wanting to call another loop in a loop? +		// <!-- BEGIN loop --> +		// <!-- BEGIN !loop2 --> +		// <!-- END !loop2 --> +		// <!-- END loop --> +		// 'loop2' is actually on the same nesting level as 'loop' you assign +		// variables to it with template->assign_block_vars('loop2', array(...)) +		if (strpos($tag_args, '!') === 0) +		{ +			// Count the number if ! occurrences (not allowed in vars) +			$no_nesting = substr_count($tag_args, '!'); +			$tag_args = substr($tag_args, $no_nesting); +		} + +		// Allow for control of looping (indexes start from zero): +		// foo(2)    : Will start the loop on the 3rd entry +		// foo(-2)   : Will start the loop two entries from the end +		// foo(3,4)  : Will start the loop on the fourth entry and end it on the fifth +		// foo(3,-4) : Will start the loop on the fourth entry and end it four from last +		$match = array(); + +		if (preg_match('#^([^()]*)\(([\-\d]+)(?:,([\-\d]+))?\)$#', $tag_args, $match)) +		{ +			$tag_args = $match[1]; + +			if ($match[2] < 0) +			{ +				$loop_start = '($_' . $tag_args . '_count ' . $match[2] . ' < 0 ? 0 : $_' . $tag_args . '_count ' . $match[2] . ')'; +			} +			else +			{ +				$loop_start = '($_' . $tag_args . '_count < ' . $match[2] . ' ? $_' . $tag_args . '_count : ' . $match[2] . ')'; +			} + +			if (!isset($match[3]) || strlen($match[3]) < 1 || $match[3] == -1) +			{ +				$loop_end = '$_' . $tag_args . '_count'; +			} +			else if ($match[3] >= 0) +			{ +				$loop_end = '(' . ($match[3] + 1) . ' > $_' . $tag_args . '_count ? $_' . $tag_args . '_count : ' . ($match[3] + 1) . ')'; +			} +			else //if ($match[3] < -1) +			{ +				$loop_end = '$_' . $tag_args . '_count' . ($match[3] + 1); +			} +		} +		else +		{ +			$loop_start = 0; +			$loop_end = '$_' . $tag_args . '_count'; +		} + +		$tag_template_php = ''; +		array_push($this->block_names, $tag_args); + +		if ($no_nesting !== false) +		{ +			// We need to implode $no_nesting times from the end... +			$block = array_slice($this->block_names, -$no_nesting); +		} +		else +		{ +			$block = $this->block_names; +		} + +		if (sizeof($block) < 2) +		{ +			// Block is not nested. +			$tag_template_php = '$_' . $tag_args . "_count = (isset(\$_tpldata['$tag_args'])) ? sizeof(\$_tpldata['$tag_args']) : 0;"; +			$varref = "\$_tpldata['$tag_args']"; +		} +		else +		{ +			// This block is nested. +			// Generate a namespace string for this block. +			$namespace = implode('.', $block); + +			// Get a reference to the data array for this block that depends on the +			// current indices of all parent blocks. +			$varref = $this->generate_block_data_ref($namespace, false); + +			// Create the for loop code to iterate over this block. +			$tag_template_php = '$_' . $tag_args . '_count = (isset(' . $varref . ')) ? sizeof(' . $varref . ') : 0;'; +		} + +		$tag_template_php .= 'if ($_' . $tag_args . '_count) {'; + +		/** +		* The following uses foreach for iteration instead of a for loop, foreach is faster but requires PHP to make a copy of the contents of the array which uses more memory +		* <code> +		*	if (!$offset) +		*	{ +		*		$tag_template_php .= 'foreach (' . $varref . ' as $_' . $tag_args . '_i => $_' . $tag_args . '_val){'; +		*	} +		* </code> +		*/ + +		$tag_template_php .= 'for ($_' . $tag_args . '_i = ' . $loop_start . '; $_' . $tag_args . '_i < ' . $loop_end . '; ++$_' . $tag_args . '_i){'; +		$tag_template_php .= '$_' . $tag_args . '_val = &' . $varref . '[$_' . $tag_args . '_i];'; + +		return $tag_template_php; +	} + +	/** +	* Compile a general expression - much of this is from Smarty with +	* some adaptions for our block level methods +	* +	* @param string $tag_args Expression (tag arguments) in source template +	* @return string compiled template code +	*/ +	private function compile_expression($tag_args) +	{ +		$match = array(); +		preg_match_all('/(?: +			"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"         | +			\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'     | +			[(),]                                  | +			[^\s(),]+)/x', $tag_args, $match); + +		$tokens = $match[0]; +		$is_arg_stack = array(); + +		for ($i = 0, $size = sizeof($tokens); $i < $size; $i++) +		{ +			$token = &$tokens[$i]; + +			switch ($token) +			{ +				case '!==': +				case '===': +				case '<<': +				case '>>': +				case '|': +				case '^': +				case '&': +				case '~': +				case ')': +				case ',': +				case '+': +				case '-': +				case '*': +				case '/': +				case '@': +				break; + +				case '==': +				case 'eq': +					$token = '=='; +				break; + +				case '!=': +				case '<>': +				case 'ne': +				case 'neq': +					$token = '!='; +				break; + +				case '<': +				case 'lt': +					$token = '<'; +				break; + +				case '<=': +				case 'le': +				case 'lte': +					$token = '<='; +				break; + +				case '>': +				case 'gt': +					$token = '>'; +				break; + +				case '>=': +				case 'ge': +				case 'gte': +					$token = '>='; +				break; + +				case '&&': +				case 'and': +					$token = '&&'; +				break; + +				case '||': +				case 'or': +					$token = '||'; +				break; + +				case '!': +				case 'not': +					$token = '!'; +				break; + +				case '%': +				case 'mod': +					$token = '%'; +				break; + +				case '(': +					array_push($is_arg_stack, $i); +				break; + +				case 'is': +					$is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1; +					$is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start)); + +					$new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1)); + +					array_splice($tokens, $is_arg_start, sizeof($tokens), $new_tokens); + +					$i = $is_arg_start; + +				// no break + +				default: +					$varrefs = array(); +					if (preg_match('#^((?:' . self::REGEX_NS . '\.)+)?(\$)?(?=[A-Z])([A-Z0-9\-_]+)#s', $token, $varrefs)) +					{ +						if (!empty($varrefs[1])) +						{ +							$namespace = substr($varrefs[1], 0, -1); +							$dot_pos = strrchr($namespace, '.'); +							if ($dot_pos !== false) +							{ +								$namespace = substr($dot_pos, 1); +							} + +							// S_ROW_COUNT is deceptive, it returns the current row number not the number of rows +							// hence S_ROW_COUNT is deprecated in favour of S_ROW_NUM +							switch ($varrefs[3]) +							{ +								case 'S_ROW_NUM': +								case 'S_ROW_COUNT': +									$token = "\$_${namespace}_i"; +								break; + +								case 'S_NUM_ROWS': +									$token = "\$_${namespace}_count"; +								break; + +								case 'S_FIRST_ROW': +									$token = "(\$_${namespace}_i == 0)"; +								break; + +								case 'S_LAST_ROW': +									$token = "(\$_${namespace}_i == \$_${namespace}_count - 1)"; +								break; + +								case 'S_BLOCK_NAME': +									$token = "'$namespace'"; +								break; + +								default: +									$token = $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[2]) . '[\'' . $varrefs[3] . '\']'; +									$token = '(isset(' . $token . ') ? ' . $token . ' : null)'; +								break; +							} +						} +						else +						{ +							$token = ($varrefs[2]) ? '$_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[3] . '\']' : '$_rootref[\'' . $varrefs[3] . '\']'; +							$token = '(isset(' . $token . ') ? ' . $token . ' : null)'; +						} + +					} +					else if (preg_match('#^\.((?:' . self::REGEX_NS . '\.?)+)$#s', $token, $varrefs)) +					{ +						// Allow checking if loops are set with .loopname +						// It is also possible to check the loop count by doing <!-- IF .loopname > 1 --> for example +						$blocks = explode('.', $varrefs[1]); + +						// If the block is nested, we have a reference that we can grab. +						// If the block is not nested, we just go and grab the block from _tpldata +						if (sizeof($blocks) > 1) +						{ +							$block = array_pop($blocks); +							$namespace = implode('.', $blocks); +							$varref = $this->generate_block_data_ref($namespace, true); + +							// Add the block reference for the last child. +							$varref .= "['" . $block . "']"; +						} +						else +						{ +							$varref = '$_tpldata'; + +							// Add the block reference for the last child. +							$varref .= "['" . $blocks[0] . "']"; +						} +						$token = "(isset($varref) ? sizeof($varref) : 0)"; +					} + +				break; +			} +		} + +		return $tokens; +	} + +	/** +	* Compile IF tags +	* +	* @param string $tag_args Expression given with IF in source template +	* @param bool $elseif True if compiling an IF tag, false if compiling an ELSEIF tag +	* @return string compiled template code +	*/ +	private function compile_tag_if($tag_args, $elseif) +	{ +		$tokens = $this->compile_expression($tag_args); + +		$tpl = ($elseif) ? '} else if (' : 'if ('; + +		$tpl .= implode(' ', $tokens); +		$tpl .= ') { '; + +		return $tpl; +	} + +	/** +	* Compile DEFINE tags +	* +	* @param string $tag_args Expression given with DEFINE in source template +	* @param bool $op True if compiling a DEFINE tag, false if compiling an UNDEFINE tag +	* @return string compiled template code +	*/ +	private function compile_tag_define($tag_args, $op) +	{ +		$match = array(); +		preg_match('#^((?:' . self::REGEX_NS . '\.)+)?\$(?=[A-Z])([A-Z0-9_\-]*)(?: = (.*?))?$#', $tag_args, $match); + +		if (empty($match[2]) || (!isset($match[3]) && $op)) +		{ +			return ''; +		} + +		if (!$op) +		{ +			return 'unset(' . (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ');'; +		} + +		$parsed_statement = implode(' ', $this->compile_expression($match[3])); + +		return (($match[1]) ? $this->generate_block_data_ref(substr($match[1], 0, -1), true, true) . '[\'' . $match[2] . '\']' : '$_tpldata[\'DEFINE\'][\'.\'][\'' . $match[2] . '\']') . ' = ' . $parsed_statement . ';'; +	} + +	/** +	* Compile INCLUDE tag +	* +	* @param string $tag_args Expression given with INCLUDE in source template +	* @return string compiled template code +	*/ +	private function compile_tag_include($tag_args) +	{ +		return "\$_template->_tpl_include('$tag_args');"; +	} + +	/** +	* Compile INCLUDE_PHP tag +	* +	* @param string $tag_args Expression given with INCLUDEPHP in source template +	* @return string compiled template code +	*/ +	private function compile_tag_include_php($tag_args) +	{ +		return "\$_template->_php_include('$tag_args');"; +	} + +	/** +	* parse expression +	* This is from Smarty +	*/ +	private function _parse_is_expr($is_arg, $tokens) +	{ +		$expr_end = 0; +		$negate_expr = false; + +		if (($first_token = array_shift($tokens)) == 'not') +		{ +			$negate_expr = true; +			$expr_type = array_shift($tokens); +		} +		else +		{ +			$expr_type = $first_token; +		} + +		switch ($expr_type) +		{ +			case 'even': +				if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') +				{ +					$expr_end++; +					$expr_arg = $tokens[$expr_end++]; +					$expr = "!(($is_arg / $expr_arg) & 1)"; +				} +				else +				{ +					$expr = "!($is_arg & 1)"; +				} +			break; + +			case 'odd': +				if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') +				{ +					$expr_end++; +					$expr_arg = $tokens[$expr_end++]; +					$expr = "(($is_arg / $expr_arg) & 1)"; +				} +				else +				{ +					$expr = "($is_arg & 1)"; +				} +			break; + +			case 'div': +				if (isset($tokens[$expr_end]) && $tokens[$expr_end] == 'by') +				{ +					$expr_end++; +					$expr_arg = $tokens[$expr_end++]; +					$expr = "!($is_arg % $expr_arg)"; +				} +			break; +		} + +		if ($negate_expr) +		{ +			if ($expr[0] == '!') +			{ +				// Negated expression, de-negate it. +				$expr = substr($expr, 1); +			} +			else +			{ +				$expr = "!($expr)"; +			} +		} + +		array_splice($tokens, 0, $expr_end, $expr); + +		return $tokens; +	} + +	/** +	* Generates a reference to the given variable inside the given (possibly nested) +	* block namespace. This is a string of the form: +	* ' . $_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . ' +	* It's ready to be inserted into an "echo" line in one of the templates. +	* +	* @param string $namespace Namespace to access (expects a trailing "." on the namespace) +	* @param string $varname Variable name to use +	* @param bool $echo If true return an echo statement, otherwise a reference to the internal variable +	* @param bool $defop If true this is a variable created with the DEFINE construct, otherwise template variable +	* @return string Code to access variable or echo it if $echo is true +	*/ +	private function generate_block_varref($namespace, $varname, $echo = true, $defop = false) +	{ +		// Strip the trailing period. +		$namespace = substr($namespace, 0, -1); + +		$expr = true; +		$isset = false; + +		// S_ROW_COUNT is deceptive, it returns the current row number now the number of rows +		// hence S_ROW_COUNT is deprecated in favour of S_ROW_NUM +		switch ($varname) +		{ +			case 'S_ROW_NUM': +			case 'S_ROW_COUNT': +				$varref = "\$_${namespace}_i"; +			break; + +			case 'S_NUM_ROWS': +				$varref = "\$_${namespace}_count"; +			break; + +			case 'S_FIRST_ROW': +				$varref = "(\$_${namespace}_i == 0)"; +			break; + +			case 'S_LAST_ROW': +				$varref = "(\$_${namespace}_i == \$_${namespace}_count - 1)"; +			break; + +			case 'S_BLOCK_NAME': +				$varref = "'$namespace'"; +			break; + +			default: +				// Get a reference to the data block for this namespace. +				$varref = $this->generate_block_data_ref($namespace, true, $defop); +				// Prepend the necessary code to stick this in an echo line. + +				// Append the variable reference. +				$varref .= "['$varname']"; + +				$expr = false; +				$isset = true; +			break; +		} +		// @todo Test the !$expr more +		$varref = ($echo) ? '<?php echo ' . ($isset ? "isset($varref) ? $varref : ''" : $varref) . '; /**/?>' : (($expr || isset($varref)) ? $varref : ''); + +		return $varref; +	} + +	/** +	* Generates a reference to the array of data values for the given +	* (possibly nested) block namespace. This is a string of the form: +	* $_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN'] +	* +	* @param string $blockname Block to access (does not expect a trailing "." on the blockname) +	* @param bool $include_last_iterator If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above. +	* @param bool $defop If true this is a variable created with the DEFINE construct, otherwise template variable +	* @return string Code to access variable +	*/ +	private function generate_block_data_ref($blockname, $include_last_iterator, $defop = false) +	{ +		// Get an array of the blocks involved. +		$blocks = explode('.', $blockname); +		$blockcount = sizeof($blocks) - 1; + +		// DEFINE is not an element of any referenced variable, we must use _tpldata to access it +		if ($defop) +		{ +			$varref = '$_tpldata[\'DEFINE\']'; +			// Build up the string with everything but the last child. +			for ($i = 0; $i < $blockcount; $i++) +			{ +				$varref .= "['" . $blocks[$i] . "'][\$_" . $blocks[$i] . '_i]'; +			} +			// Add the block reference for the last child. +			$varref .= "['" . $blocks[$blockcount] . "']"; +			// Add the iterator for the last child if requried. +			if ($include_last_iterator) +			{ +				$varref .= '[$_' . $blocks[$blockcount] . '_i]'; +			} +			return $varref; +		} +		else if ($include_last_iterator) +		{ +			return '$_'. $blocks[$blockcount] . '_val'; +		} +		else +		{ +			return '$_'. $blocks[$blockcount - 1] . '_val[\''. $blocks[$blockcount]. '\']'; +		} +	} +} diff --git a/phpBB/includes/template/locator.php b/phpBB/includes/template/locator.php new file mode 100644 index 0000000000..35e33bb9e6 --- /dev/null +++ b/phpBB/includes/template/locator.php @@ -0,0 +1,211 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + + +/** +* Template locator. Maintains mapping from template handles to source paths. +* +* Template locator is aware of template inheritance, and can return actual +* filesystem paths (i.e., the "primary" template or the "parent" template) +* depending on what files exist. +* +* @package phpBB3 +*/ +class phpbb_template_locator +{ +	/** +	* @var string Path to directory that templates are stored in. +	*/ +	private $root = ''; + +	/** +	* @var string Path to parent/fallback template directory. +	*/ +	private $inherit_root = ''; + +	/** +	* @var array Map from handles to source template file paths. +	* Normally it only contains paths for handles that are used +	* (or are likely to be used) by the page being rendered and not +	* all templates that exist on the filesystem. +	*/ +	private $files = array(); + +	/** +	* @var array Map from handles to source template file names. +	* Covers the same data as $files property but maps to basenames +	* instead of paths. +	*/ +	private $filenames = array(); + +	/** +	* @var array Map from handles to parent/fallback source template +	* file paths. Covers the same data as $files. +	*/ +	private $files_inherit = array(); + +	/** +	* Set custom template location (able to use directory outside of phpBB). +	* +	* Note: Templates are still compiled to phpBB's cache directory. +	* +	* @param string $template_path Path to template directory +	* @param string|bool $fallback_template_path Path to fallback template, or false to disable fallback +	*/ +	public function set_custom_template($template_path, $fallback_template_path = false) +	{ +		// Make sure $template_path has no ending slash +		if (substr($template_path, -1) == '/') +		{ +			$template_path = substr($template_path, 0, -1); +		} + +		$this->root = $template_path; + +		if ($fallback_template_path !== false) +		{ +			if (substr($fallback_template_path, -1) == '/') +			{ +				$fallback_template_path = substr($fallback_template_path, 0, -1); +			} + +			$this->inherit_root = $fallback_template_path; +		} +	} + +	/** +	* Sets the template filenames for handles. $filename_array +	* should be a hash of handle => filename pairs. +	* +	* @param array $filname_array Should be a hash of handle => filename pairs. +	*/ +	public function set_filenames(array $filename_array) +	{ +		foreach ($filename_array as $handle => $filename) +		{ +			if (empty($filename)) +			{ +				trigger_error("template locator: set_filenames: Empty filename specified for $handle", E_USER_ERROR); +			} + +			$this->filename[$handle] = $filename; +			$this->files[$handle] = $this->root . '/' . $filename; + +			if ($this->inherit_root) +			{ +				$this->files_inherit[$handle] = $this->inherit_root . '/' . $filename; +			} +		} +	} + +	/** +	* Determines the filename for a template handle. +	* +	* The filename comes from array used in a set_filenames call, +	* which should have been performed prior to invoking this function. +	* Return value is a file basename (without path). +	* +	* @param $handle string Template handle +	* @return string Filename corresponding to the template handle +	*/ +	public function get_filename_for_handle($handle) +	{ +		if (!isset($this->filename[$handle])) +		{ +			trigger_error("template locator: get_filename_for_handle: No file specified for handle $handle", E_USER_ERROR); +		} +		return $this->filename[$handle]; +	} + +	/** +	* Determines the source file path for a template handle without +	* regard for template inheritance. +	* +	* This function returns the path in "primary" template directory +	* corresponding to the given template handle. That path may or +	* may not actually exist on the filesystem. Because this function +	* does not perform stat calls to determine whether the path it +	* returns actually exists, it is faster than get_source_file_for_handle. +	* +	* Use get_source_file_for_handle to obtain the actual path that is +	* guaranteed to exist (which might come from the parent/fallback +	* template directory if template inheritance is used). +	* +	* This function will trigger an error if the handle was never +	* associated with a template file via set_filenames. +	* +	* @param $handle string Template handle +	* @return string Path to source file path in primary template directory +	*/ +	public function get_virtual_source_file_for_handle($handle) +	{ +		// If we don't have a file assigned to this handle, die. +		if (!isset($this->files[$handle])) +		{ +			trigger_error("template locator: No file specified for handle $handle", E_USER_ERROR); +		} + +		$source_file = $this->files[$handle]; +		return $source_file; +	} + +	/** +	* Determines the source file path for a template handle, accounting +	* for template inheritance and verifying that the path exists. +	* +	* This function returns the actual path that may be compiled for +	* the specified template handle. It will trigger an error if +	* the template handle was never associated with a template path +	* via set_filenames or if the template file does not exist on the +	* filesystem. +	* +	* Use get_virtual_source_file_for_handle to just resolve a template +	* handle to a path without any filesystem or inheritance checks. +	* +	* @param string $handle Template handle (i.e. "friendly" template name) +	* @return string Source file path +	*/ +	public function get_source_file_for_handle($handle) +	{ +		// If we don't have a file assigned to this handle, die. +		if (!isset($this->files[$handle])) +		{ +			trigger_error("template locator: No file specified for handle $handle", E_USER_ERROR); +		} + +		$source_file = $this->files[$handle]; + +		// Try and open template for reading +		if (!file_exists($source_file)) +		{ +			if (isset($this->files_inherit[$handle]) && $this->files_inherit[$handle]) +			{ +				$parent_source_file = $this->files_inherit[$handle]; +				if (!file_exists($parent_source_file)) +				{ +					trigger_error("template locator: Neither $source_file nor $parent_source_file exist", E_USER_ERROR); +				} +				$source_file = $parent_source_file; +			} +			else +			{ +				trigger_error("template locator: File $source_file does not exist", E_USER_ERROR); +			} +		} +		return $source_file; +	} +} diff --git a/phpBB/includes/template/renderer.php b/phpBB/includes/template/renderer.php new file mode 100644 index 0000000000..59c85df443 --- /dev/null +++ b/phpBB/includes/template/renderer.php @@ -0,0 +1,35 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Template renderer interface. +* +* Objects implementing this interface encapsulate a means of displaying +* a template. +* +* @package phpBB3 +*/ +interface phpbb_template_renderer +{ +	/** +	* Displays the template managed by this renderer. +	* +	* @param phpbb_template_context $context Template context to use +	* @param array $lang Language entries to use +	*/ +	public function render($context, $lang); +} diff --git a/phpBB/includes/template/renderer_eval.php b/phpBB/includes/template/renderer_eval.php new file mode 100644 index 0000000000..11e2a30f06 --- /dev/null +++ b/phpBB/includes/template/renderer_eval.php @@ -0,0 +1,60 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* Template renderer that stores compiled template's php code and +* displays it via eval. +* +* @package phpBB3 +*/ +class phpbb_template_renderer_eval implements phpbb_template_renderer +{ +	/** +	* Template code to be eval'ed. +	*/ +	private $code; + +	/** +	* Constructor. Stores provided code for future evaluation. +	* Template includes are delegated to template object $template. +	* +	* @param string $code php code of the template +	* @param phpbb_template $template template object +	*/ +	public function __construct($code, $template) +	{ +		$this->code = $code; +		$this->template = $template; +	} + +	/** +	* Displays the template managed by this renderer by eval'ing php code +	* of the template. +	* +	* @param phpbb_template_context $context Template context to use +	* @param array $lang Language entries to use +	*/ +	public function render($context, $lang) +	{ +		$_template = $this->template; +		$_tpldata = &$context->get_data_ref(); +		$_rootref = &$context->get_root_ref(); +		$_lang = $lang; + +		eval($this->code); +	} +} diff --git a/phpBB/includes/template/renderer_include.php b/phpBB/includes/template/renderer_include.php new file mode 100644 index 0000000000..40e7a3376c --- /dev/null +++ b/phpBB/includes/template/renderer_include.php @@ -0,0 +1,60 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + + +/** +* Template renderer that stores path to php file with template code +* and displays it by including the file. +* +* @package phpBB3 +*/ +class phpbb_template_renderer_include implements phpbb_template_renderer +{ +	/** +	* Template path to be included. +	*/ +	private $path; + +	/** +	* Constructor. Stores path to the template for future inclusion. +	* Template includes are delegated to template object $template. +	* +	* @param string $path path to the template +	*/ +	public function __construct($path, $template) +	{ +		$this->path = $path; +		$this->template = $template; +	} + +	/** +	* Displays the template managed by this renderer by including +	* the php file containing the template. +	* +	* @param phpbb_template_context $context Template context to use +	* @param array $lang Language entries to use +	*/ +	public function render($context, $lang) +	{ +		$_template = $this->template; +		$_tpldata = &$context->get_data_ref(); +		$_rootref = &$context->get_root_ref(); +		$_lang = $lang; + +		include($this->path); +	} +} diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php new file mode 100644 index 0000000000..4007b77db8 --- /dev/null +++ b/phpBB/includes/template/template.php @@ -0,0 +1,491 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ +	exit; +} + +/** +* @todo +* IMG_ for image substitution? +* {IMG_[key]:[alt]:[type]} +* {IMG_ICON_CONTACT:CONTACT:full} -> $user->img('icon_contact', 'CONTACT', 'full'); +* +* More in-depth... +* yadayada +*/ + +/** +* Base Template class. +* @package phpBB3 +*/ +class phpbb_template +{ +	/** +	* @var phpbb_template_context Template context. +	* Stores template data used during template rendering. +	*/ +	private $context; + +	/** +	* @var string Path of the cache directory for the template +	*/ +	public $cachepath = ''; + +	/** +	* @var string phpBB root path +	*/ +	private $phpbb_root_path; + +	/** +	* @var phpEx PHP file extension +	*/ +	private $phpEx; + +	/** +	* @var phpbb_config phpBB config instance +	*/ +	private $config; + +	/** +	* @var user current user +	*/ +	private $user; + +	/** +	* @var locator template locator +	*/ +	private $locator; + +	/** +	* Constructor. +	* +	* @param string $phpbb_root_path phpBB root path +	* @param user $user current user +	* @param phpbb_template_locator $locator template locator +	*/ +	public function __construct($phpbb_root_path, $phpEx, $config, $user, phpbb_template_locator $locator) +	{ +		$this->phpbb_root_path = $phpbb_root_path; +		$this->phpEx = $phpEx; +		$this->config = $config; +		$this->user = $user; +		$this->locator = $locator; +	} + +	/** +	* Set template location based on (current) user's chosen style. +	*/ +	public function set_template() +	{ +		$style_name = $this->user->theme['template_path']; + +		$relative_template_root = $this->relative_template_root_for_style($style_name); +		$template_root = $this->phpbb_root_path . $relative_template_root; +		if (!file_exists($template_root)) +		{ +			trigger_error('template locator: Template path could not be found: ' . $relative_template_root, E_USER_ERROR); +		} + +		if ($this->user->theme['template_inherits_id']) +		{ +			$fallback_template_path = $this->phpbb_root_path . $this->relative_template_root_for_style($this->user->theme['template_inherit_path']); +		} +		else +		{ +			$fallback_template_path = null; +		} + +		return $this->set_custom_template($template_root, $style_name, $fallback_template_path); +	} + +	/** +	* Set custom template location (able to use directory outside of phpBB). +	* +	* Note: Templates are still compiled to phpBB's cache directory. +	* +	* @param string $template_path Path to template directory +	* @param string $template_name Name of template +	* @param string $fallback_template_path Path to fallback template +	*/ +	public function set_custom_template($template_path, $style_name, $fallback_template_path = false) +	{ +		$this->locator->set_custom_template($template_path, $fallback_template_path); + +		$this->cachepath = $this->phpbb_root_path . 'cache/ctpl_' . str_replace('_', '-', $style_name) . '_'; + +		$this->context = new phpbb_template_context(); + +		return true; +	} + +	/** +	* Converts a style name to relative (to board root) path to +	* the style's template files. +	* +	* @param $style_name string Style name +	* @return string Path to style template files +	*/ +	private function relative_template_root_for_style($style_name) +	{ +		return 'styles/' . $style_name . '/template'; +	} + +	/** +	* Sets the template filenames for handles. +	* +	* @param array $filname_array Should be a hash of handle => filename pairs. +	*/ +	public function set_filenames(array $filename_array) +	{ +		$this->locator->set_filenames($filename_array); + +		return true; +	} + +	/** +	* Clears all variables and blocks assigned to this template. +	*/ +	public function destroy() +	{ +		$this->context->clear(); +	} + +	/** +	* Reset/empty complete block +	* +	* @param string $blockname Name of block to destroy +	*/ +	public function destroy_block_vars($blockname) +	{ +		$this->context->destroy_block_vars($blockname); +	} + +	/** +	* Display a template for provided handle. +	* +	* The template will be loaded and compiled, if necessary, first. +	* +	* This function calls hooks. +	* +	* @param string $handle Handle to display +	* @return bool True on success, false on failure +	*/ +	public function display($handle) +	{ +		$result = $this->call_hook($handle); +		if ($result !== false) +		{ +			return $result[0]; +		} + +		return $this->load_and_render($handle); +	} + +	/** +	* Loads a template for $handle, compiling it if necessary, and +	* renders the template. +	* +	* @param string $handle Template handle to render +	* @return bool True on success, false on failure +	*/ +	private function load_and_render($handle) +	{ +		$renderer = $this->_tpl_load($handle); + +		if ($renderer) +		{ +			$renderer->render($this->context, $this->get_lang()); +			return true; +		} +		else +		{ +			return false; +		} +	} + +	/** +	* Calls hook if any is defined. +	* +	* @param string $handle Template handle being displayed. +	*/ +	private function call_hook($handle) +	{ +		global $phpbb_hook; + +		if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, __FUNCTION__), $handle, $this)) +		{ +			if ($phpbb_hook->hook_return(array(__CLASS__, __FUNCTION__))) +			{ +				$result = $phpbb_hook->hook_return_result(array(__CLASS__, __FUNCTION__)); +				return array($result); +			} +		} + +		return false; +	} + +	/** +	* Obtains language array. +	* This is either lang property of $user property, or if +	* it is not set an empty array. +	* @return array language entries +	*/ +	public function get_lang() +	{ +		if (isset($this->user->lang)) +		{ +			$lang = $this->user->lang; +		} +		else +		{ +			$lang = array(); +		} +		return $lang; +	} + +	/** +	* Display the handle and assign the output to a template variable +	* or return the compiled result. +	* +	* @param string $handle Handle to operate on +	* @param string $template_var Template variable to assign compiled handle to +	* @param bool $return_content If true return compiled handle, otherwise assign to $template_var +	* @return bool|string false on failure, otherwise if $return_content is true return string of the compiled handle, otherwise return true +	*/ +	public function assign_display($handle, $template_var = '', $return_content = true) +	{ +		ob_start(); +		$result = $this->display($handle); +		$contents = ob_get_clean(); +		if ($result === false) +		{ +			return false; +		} + +		if ($return_content) +		{ +			return $contents; +		} + +		$this->assign_var($template_var, $contents); + +		return true; +	} + +	/** +	* Obtains a template renderer for a template identified by specified +	* handle. The template renderer can display the template later. +	* +	* Template source will first be compiled into php code. +	* If template cache is writable the compiled php code will be stored +	* on filesystem and template will not be subsequently recompiled. +	* If template cache is not writable template source will be recompiled +	* every time it is needed. DEBUG_EXTRA define and load_tplcompile +	* configuration setting may be used to force templates to be always +	* recompiled. +	* +	* Returns an object implementing phpbb_template_renderer, or null +	* if template loading or compilation failed. Call render() on the +	* renderer to display the template. This will result in template +	* contents sent to the output stream (unless, of course, output +	* buffering is in effect). +	* +	* @param string $handle Handle of the template to load +	* @return phpbb_template_renderer Template renderer object, or null on failure +	* @uses template_compile is used to compile template source +	*/ +	private function _tpl_load($handle) +	{ +		$virtual_source_file = $this->locator->get_virtual_source_file_for_handle($handle); +		$source_file = null; + +		$compiled_path = $this->cachepath . str_replace('/', '.', $virtual_source_file) . '.' . $this->phpEx; + +		$recompile = defined('DEBUG_EXTRA') || +			!file_exists($compiled_path) || +			@filesize($compiled_path) === 0 || +			($this->config['load_tplcompile'] && @filemtime($compiled_path) < @filemtime($source_file)); + +		if (!$recompile && $this->config['load_tplcompile']) +		{ +			$source_file = $this->locator->get_source_file_for_handle($handle); +			$recompile = (@filemtime($compiled_path) < @filemtime($source_file)) ? true : false; +		} + +		// Recompile page if the original template is newer, otherwise load the compiled version +		if (!$recompile) +		{ +			return new phpbb_template_renderer_include($compiled_path, $this); +		} + +		if ($source_file === null) +		{ +			$source_file = $this->locator->get_source_file_for_handle($handle); +		} + +		$compile = new phpbb_template_compile($this->config['tpl_allow_php']); + +		$output_file = $this->_compiled_file_for_handle($handle); +		if ($compile->compile_file_to_file($source_file, $output_file) !== false) +		{ +			$renderer = new phpbb_template_renderer_include($output_file, $this); +		} +		else if (($code = $compile->compile_file($source_file)) !== false) +		{ +			$renderer = new phpbb_template_renderer_eval($code, $this); +		} +		else +		{ +			$renderer = null; +		} + +		return $renderer; +	} + +	/** +	* Determines compiled file path for handle $handle. +	* +	* @param string $handle Template handle (i.e. "friendly" template name) +	* @return string Compiled file path +	*/ +	private function _compiled_file_for_handle($handle) +	{ +		$source_file = $this->locator->get_filename_for_handle($handle); +		$compiled_file = $this->cachepath . str_replace('/', '.', $source_file) . '.' . $this->phpEx; +		return $compiled_file; +	} + +	/** +	* Assign key variable pairs from an array +	* +	* @param array $vararray A hash of variable name => value pairs +	*/ +	public function assign_vars(array $vararray) +	{ +		foreach ($vararray as $key => $val) +		{ +			$this->assign_var($key, $val); +		} +	} + +	/** +	* Assign a single variable to a single key +	* +	* @param string $varname Variable name +	* @param string $varval Value to assign to variable +	*/ +	public function assign_var($varname, $varval) +	{ +		$this->context->assign_var($varname, $varval); +	} + +	// Docstring is copied from phpbb_template_context method with the same name. +	/** +	* Assign key variable pairs from an array to a specified block +	* @param string $blockname Name of block to assign $vararray to +	* @param array $vararray A hash of variable name => value pairs +	*/ +	public function assign_block_vars($blockname, array $vararray) +	{ +		return $this->context->assign_block_vars($blockname, $vararray); +	} + +	// Docstring is copied from phpbb_template_context method with the same name. +	/** +	* Change already assigned key variable pair (one-dimensional - single loop entry) +	* +	* An example of how to use this function: +	* {@example alter_block_array.php} +	* +	* @param	string	$blockname	the blockname, for example 'loop' +	* @param	array	$vararray	the var array to insert/add or merge +	* @param	mixed	$key		Key to search for +	* +	* array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] +	* +	* int: Position [the position to change or insert at directly given] +	* +	* If key is false the position is set to 0 +	* If key is true the position is set to the last entry +	* +	* @param	string	$mode		Mode to execute (valid modes are 'insert' and 'change') +	* +	*	If insert, the vararray is inserted at the given position (position counting from zero). +	*	If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new value). +	* +	* Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) +	* and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) +	* +	* @return bool false on error, true on success +	*/ +	public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') +	{ +		return $this->context->alter_block_array($blockname, $vararray, $key, $mode); +	} + +	/** +	* Include a separate template. +	* +	* This function is marked public due to the way the template +	* implementation uses it. It is actually an implementation function +	* and should not be considered part of template class's public API. +	* +	* @param string $filename Template filename to include +	* @param bool $include True to include the file, false to just load it +	* @uses template_compile is used to compile uncached templates +	*/ +	public function _tpl_include($filename, $include = true) +	{ +		$this->locator->set_filenames(array($filename => $filename)); + +		if (!$this->load_and_render($filename)) +		{ +			// trigger_error cannot be used here, as the output already started +			echo 'template->_tpl_include(): Failed including ' . htmlspecialchars($handle) . "\n"; +		} +	} + +	/** +	* Include a PHP file. +	* +	* If a relative path is passed in $filename, it is considered to be +	* relative to board root ($phpbb_root_path). Absolute paths are +	* also allowed. +	* +	* This function is marked public due to the way the template +	* implementation uses it. It is actually an implementation function +	* and should not be considered part of template class's public API. +	* +	* @param string $filename Path to PHP file to include +	*/ +	public function _php_include($filename) +	{ +		if (phpbb_is_absolute($filename)) +		{ +			$file = $filename; +		} +		else +		{ +			$file = $this->phpbb_root_path . $filename; +		} + +		if (!file_exists($file)) +		{ +			// trigger_error cannot be used here, as the output already started +			echo 'template->_php_include(): File ' . htmlspecialchars($file) . " does not exist\n"; +			return; +		} +		include($file); +	} +} diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 79aa57aea0..37fe6817d5 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -84,7 +84,6 @@ if (!empty($load_extensions) && function_exists('dl'))  // Include files  require($phpbb_root_path . 'includes/class_loader.' . $phpEx); -require($phpbb_root_path . 'includes/template.' . $phpEx);  require($phpbb_root_path . 'includes/session.' . $phpEx);  require($phpbb_root_path . 'includes/auth.' . $phpEx); diff --git a/phpBB/install/index.php b/phpBB/install/index.php index f4815c3108..e3f9bda372 100644 --- a/phpBB/install/index.php +++ b/phpBB/install/index.php @@ -78,7 +78,6 @@ phpbb_require_updated('includes/functions_content.' . $phpEx, true);  include($phpbb_root_path . 'includes/auth.' . $phpEx);  include($phpbb_root_path . 'includes/session.' . $phpEx); -include($phpbb_root_path . 'includes/template.' . $phpEx);  include($phpbb_root_path . 'includes/functions_admin.' . $phpEx);  include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx);  require($phpbb_root_path . 'includes/functions_install.' . $phpEx); @@ -178,7 +177,6 @@ set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handle  $user = new user();  $auth = new auth(); -$template = new template();  // Add own hook handler, if present. :o  if (file_exists($phpbb_root_path . 'includes/hooks/index.' . $phpEx)) @@ -201,6 +199,8 @@ $config = new phpbb_config(array(  	'load_tplcompile'	=> '1'  )); +$template_locator = new phpbb_template_locator(); +$template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $template_locator);  $template->set_custom_template('../adm/style', 'admin');  $template->assign_var('T_TEMPLATE_PATH', '../adm/style'); diff --git a/tests/template/includephp_test.php b/tests/template/includephp_test.php new file mode 100644 index 0000000000..aac9cccc8a --- /dev/null +++ b/tests/template/includephp_test.php @@ -0,0 +1,47 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +require_once dirname(__FILE__) . '/template_test_case.php'; + +class phpbb_template_includephp_test extends phpbb_template_template_test_case +{ +	public function test_includephp_relative() +	{ +		$this->setup_engine(array('tpl_allow_php' => true)); + +		$cache_file = $this->template->cachepath . 'includephp_relative.html.php'; + +		$this->run_template('includephp_relative.html', array(), array(), array(), "Path is relative to board root.\ntesting included php", $cache_file); + +		$this->template->set_filenames(array('test' => 'includephp_relative.html')); +		$this->assertEquals("Path is relative to board root.\ntesting included php", $this->display('test'), "Testing INCLUDEPHP"); +	} + +	public function test_includephp_absolute() +	{ +		$path_to_php = dirname(__FILE__) . '/templates/_dummy_include.php.inc'; +		$this->assertTrue(phpbb_is_absolute($path_to_php)); +		$template_text = "Path is absolute.\n<!-- INCLUDEPHP $path_to_php -->"; + +		$cache_dir = dirname($this->template->cachepath) . '/'; +		$fp = fopen($cache_dir . 'includephp_absolute.html', 'w'); +		fputs($fp, $template_text); +		fclose($fp); + +		$this->setup_engine(array('tpl_allow_php' => true)); + +		$this->template->set_custom_template($cache_dir, 'tests'); +		$cache_file = $this->template->cachepath . 'includephp_absolute.html.php'; + +		$this->run_template('includephp_absolute.html', array(), array(), array(), "Path is absolute.\ntesting included php", $cache_file); + +		$this->template->set_filenames(array('test' => 'includephp_absolute.html')); +		$this->assertEquals("Path is absolute.\ntesting included php", $this->display('test'), "Testing INCLUDEPHP"); +	} +} diff --git a/tests/template/parent_templates/parent_and_child.html b/tests/template/parent_templates/parent_and_child.html new file mode 100644 index 0000000000..71984b48ad --- /dev/null +++ b/tests/template/parent_templates/parent_and_child.html @@ -0,0 +1 @@ +Parent template. diff --git a/tests/template/parent_templates/parent_only.html b/tests/template/parent_templates/parent_only.html new file mode 100644 index 0000000000..8cfb90eca2 --- /dev/null +++ b/tests/template/parent_templates/parent_only.html @@ -0,0 +1 @@ +Only in parent. diff --git a/tests/template/subdir/includephp_from_subdir_test.php b/tests/template/subdir/includephp_from_subdir_test.php new file mode 100644 index 0000000000..3cc632485d --- /dev/null +++ b/tests/template/subdir/includephp_from_subdir_test.php @@ -0,0 +1,29 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +require_once dirname(__FILE__) . '/../template_test_case.php'; + +class phpbb_template_subdir_includephp_from_subdir_test extends phpbb_template_template_test_case +{ +	// Exact copy of test_includephp_relatve from ../includephp_test.php. +	// Verifies that relative php inclusion works when including script +	// (and thus current working directory) is in a subdirectory of +	// board root. +	public function test_includephp_relative() +	{ +		$this->setup_engine(array('tpl_allow_php' => true)); + +		$cache_file = $this->template->cachepath . 'includephp_relative.html.php'; + +		$this->run_template('includephp_relative.html', array(), array(), array(), "Path is relative to board root.\ntesting included php", $cache_file); + +		$this->template->set_filenames(array('test' => 'includephp_relative.html')); +		$this->assertEquals("Path is relative to board root.\ntesting included php", $this->display('test'), "Testing INCLUDEPHP"); +	} +} diff --git a/tests/template/template_compile_test.php b/tests/template/template_compile_test.php new file mode 100644 index 0000000000..8c136c9985 --- /dev/null +++ b/tests/template/template_compile_test.php @@ -0,0 +1,31 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php'; + +class phpbb_template_template_compile_test extends phpbb_test_case +{ +	private $template_compile; +	private $template_path; + +	protected function setUp() +	{ +		$this->template_compile = new phpbb_template_compile(false); +		$this->template_path = dirname(__FILE__) . '/templates'; +	} + +	public function test_in_phpbb() +	{ +		$output = $this->template_compile->compile_file($this->template_path . '/trivial.html'); +		$this->assertTrue(strlen($output) > 0); +		$statements = explode(';', $output); +		$first_statement = $statements[0]; +		$this->assertTrue(!!preg_match('#if.*defined.*IN_PHPBB.*exit#', $first_statement)); +	} +} diff --git a/tests/template/template_inheritance_test.php b/tests/template/template_inheritance_test.php new file mode 100644 index 0000000000..d62562ff0d --- /dev/null +++ b/tests/template/template_inheritance_test.php @@ -0,0 +1,75 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +require_once dirname(__FILE__) . '/template_test_case.php'; + +class phpbb_template_template_inheritance_test extends phpbb_template_template_test_case +{ +	/** +	 * @todo put test data into templates/xyz.test +	 */ +	public static function template_data() +	{ +		return array( +			// First element of the array is test name - keep them distinct +			array( +				'simple inheritance - only parent template exists', +				'parent_only.html', +				array(), +				array(), +				array(), +				"Only in parent.", +			), +			array( +				'simple inheritance - only child template exists', +				'child_only.html', +				array(), +				array(), +				array(), +				"Only in child.", +			), +			array( +				'simple inheritance - both parent and child templates exist', +				'parent_and_child.html', +				array(), +				array(), +				array(), +				"Child template.", +			), +		); +	} + +	/** +	* @dataProvider template_data +	*/ +	public function test_template($name, $file, array $vars, array $block_vars, array $destroy, $expected) +	{ +		$cache_file = $this->template->cachepath . str_replace('/', '.', $file) . '.php'; + +		$this->assertFileNotExists($cache_file); + +		$this->run_template($file, $vars, $block_vars, $destroy, $expected, $cache_file); + +		// Reset the engine state +		$this->setup_engine(); + +		$this->run_template($file, $vars, $block_vars, $destroy, $expected, $cache_file); +	} + +	protected function setup_engine() +	{ +		global $phpbb_root_path, $phpEx, $config, $user; + +		$this->template_path = dirname(__FILE__) . '/templates'; +		$this->parent_template_path = dirname(__FILE__) . '/parent_templates'; +		$this->template_locator = new phpbb_template_locator(); +		$this->template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $this->template_locator); +		$this->template->set_custom_template($this->template_path, 'tests', $this->parent_template_path); +	} +} diff --git a/tests/template/template_test.php b/tests/template/template_test.php index 62f94f7d32..44baeaf8f0 100644 --- a/tests/template/template_test.php +++ b/tests/template/template_test.php @@ -8,69 +8,10 @@  */  require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php'; -require_once dirname(__FILE__) . '/../../phpBB/includes/template.php'; +require_once dirname(__FILE__) . '/template_test_case.php'; -class phpbb_template_template_test extends phpbb_test_case +class phpbb_template_template_test extends phpbb_template_template_test_case  { -	private $template; -	private $template_path; - -	// Keep the contents of the cache for debugging? -	const PRESERVE_CACHE = false; - -	private function display($handle) -	{ -		ob_start(); -		$this->assertTrue($this->template->display($handle, false)); -		return self::trim_template_result(ob_get_clean()); -	} - -	private static function trim_template_result($result) -	{ -		return str_replace("\n\n", "\n", implode("\n", array_map('trim', explode("\n", trim($result))))); -	} - -	private function setup_engine() -	{ -		$this->template_path = dirname(__FILE__) . '/templates'; -		$this->template = new template(); -		$this->template->set_custom_template($this->template_path, 'tests'); -	} - -	protected function setUp() -	{ -		$this->markTestIncomplete("template::display raises notices."); - -		// Test the engine can be used -		$this->setup_engine(); - -		if (!is_writable(dirname($this->template->cachepath))) -		{ -			$this->markTestSkipped("Template cache directory is not writable."); -		} - -		foreach (glob($this->template->cachepath . '*') as $file) -		{ -			unlink($file); -		} - -		$GLOBALS['config'] = array( -			'load_tplcompile'	=> true, -			'tpl_allow_php'		=> false, -		); -	} - -	protected function tearDown() -	{ -		if (is_object($this->template)) -		{ -			foreach (glob($this->template->cachepath . '*') as $file) -			{ -				unlink($file); -			} -		} -	} -  	/**  	 * @todo put test data into templates/xyz.test  	 */ @@ -91,7 +32,7 @@ class phpbb_template_template_test extends phpbb_test_case  				array(),  				array(),  				array(), -				"pass\npass\n<!-- DUMMY var -->", +				"pass\npass\npass\n<!-- DUMMY var -->",  			),  			array(  				'variable.html', @@ -105,14 +46,14 @@ class phpbb_template_template_test extends phpbb_test_case  				array(),  				array(),  				array(), -				'0', +				'03',  			),  			array(  				'if.html',  				array('S_VALUE' => true),  				array(),  				array(), -				"1\n0", +				'1',  			),  			array(  				'if.html', @@ -161,22 +102,22 @@ class phpbb_template_template_test extends phpbb_test_case  				array(),  				array('loop' => array(array('VARIABLE' => 'x'))),  				array(), -				"first\n0\nx\nset\nlast", -			),/* no nested top level loops +				"first\n0 - a\nx - b\nset\nlast", +			),  			array(  				'loop_vars.html',  				array(),  				array('loop' => array(array('VARIABLE' => 'x'), array('VARIABLE' => 'y'))),  				array(), -				"first\n0\n0\n2\nx\nset\n1\n1\n2\ny\nset\nlast", +				"first\n0 - a\nx - b\nset\n1 - a\ny - b\nset\nlast",  			),  			array(  				'loop_vars.html',  				array(),  				array('loop' => array(array('VARIABLE' => 'x'), array('VARIABLE' => 'y')), 'loop.inner' => array(array(), array())),  				array(), -				"first\n0\n0\n2\nx\nset\n1\n1\n2\ny\nset\nlast\n0\n\n1\nlast inner\ninner loop", -			),*/ +				"first\n0 - a\nx - b\nset\n1 - a\ny - b\nset\nlast\n0 - c\n1 - c\nlast inner\ninner loop", +			),  			array(  				'loop_advanced.html',  				array(), @@ -189,14 +130,23 @@ class phpbb_template_template_test extends phpbb_test_case  				array(),  				array('loop' => array(array(), array(), array(), array(), array(), array(), array()), 'test' => array(array()), 'test.deep' => array(array()), 'test.deep.defines' => array(array())),  				array(), -				"xyz\nabc", +				"xyz\nabc\nabc\nbar\nbar\nabc",  			),  			array(  				'expressions.html',  				array(),  				array(),  				array(), -				trim(str_repeat("pass", 39)), +				trim(str_repeat("pass\n", 10) . "\n" +					. str_repeat("pass\n", 4) . "\n" +					. str_repeat("pass\n", 2) . "\n" +					. str_repeat("pass\n", 6) . "\n" +					. str_repeat("pass\n", 2) . "\n" +					. str_repeat("pass\n", 6) . "\n" +					. str_repeat("pass\n", 2) . "\n" +					. str_repeat("pass\n", 2) . "\n" +					. str_repeat("pass\n", 3) . "\n" +					. str_repeat("pass\n", 2) . "\n"),  			),  			array(  				'php.html', @@ -227,6 +177,15 @@ class phpbb_template_template_test extends phpbb_test_case  				"first\n0\n0\n2\nx\nset\n1\n1\n2\ny\nset\nlast",  			),*/  			array( +				// Just like a regular loop but the name begins +				// with an underscore +				'loop_underscore.html', +				array(), +				array(), +				array(), +				"noloop\nnoloop", +			), +			array(  				'lang.html',  				array(),  				array(), @@ -247,6 +206,46 @@ class phpbb_template_template_test extends phpbb_test_case  				array(),  				"{ VARIABLE }\nValue'",  			), +			array( +				'loop_nested_multilevel_ref.html', +				array(), +				array(), +				array(), +				"top-level content", +			), +			array( +				'loop_nested_multilevel_ref.html', +				array(), +				array('outer' => array(array('VARIABLE' => 'x'), array('VARIABLE' => 'y')), 'outer.inner' => array(array('VARIABLE' => 'z'), array('VARIABLE' => 'zz'))), +				array(), +				// I don't completely understand this output, hopefully it's correct +				"top-level content\nouter x\nouter y\ninner z\nfirst row\n\ninner zz", +			), +			array( +				'loop_nested_deep_multilevel_ref.html', +				array(), +				array('outer' => array(array()), 'outer.middle' => array(array()), 'outer.middle.inner' => array(array('VARIABLE' => 'z'), array('VARIABLE' => 'zz'))), +				array(), +				// I don't completely understand this output, hopefully it's correct +				"top-level content\nouter\n\ninner z\nfirst row\n\ninner zz", +			), +			array( +				'loop_size.html', +				array(), +				array('loop' => array(array()), 'empty_loop' => array()), +				array(), +				"nonexistent = 0\n! nonexistent\n\nempty = 0\n! empty\nloop\n\nin loop", +			), +			/* Does not pass with the current implementation. +			array( +				'loop_reuse.html', +				array(), +				array('one' => array(array('VAR' => 'a'), array('VAR' => 'b')), 'one.one' => array(array('VAR' => 'c'), array('VAR' => 'd'))), +				array(), +				// Not entirely sure what should be outputted but the current output of "a" is most certainly wrong +				"a\nb\nc\nd", +			), +			*/  		);  	} @@ -257,7 +256,7 @@ class phpbb_template_template_test extends phpbb_test_case  		$this->template->set_filenames(array('test' => $filename));  		$this->assertFileNotExists($this->template_path . '/' . $filename, 'Testing missing file, file cannot exist'); -		$expecting = sprintf('template->_tpl_load_file(): File %s does not exist or is empty', realpath($this->template_path . '/../') . '/templates/' . $filename); +		$expecting = sprintf('template locator: File %s does not exist', realpath($this->template_path . '/../') . '/templates/' . $filename);  		$this->setExpectedTriggerError(E_USER_ERROR, $expecting);  		$this->display('test'); @@ -265,7 +264,7 @@ class phpbb_template_template_test extends phpbb_test_case  	public function test_empty_file()  	{ -		$expecting = 'template->set_filenames: Empty filename specified for test'; +		$expecting = 'template locator: set_filenames: Empty filename specified for test';  		$this->setExpectedTriggerError(E_USER_ERROR, $expecting);  		$this->template->set_filenames(array('test' => '')); @@ -273,52 +272,12 @@ class phpbb_template_template_test extends phpbb_test_case  	public function test_invalid_handle()  	{ -		$expecting = 'template->_tpl_load(): No file specified for handle test'; +		$expecting = 'No file specified for handle test';  		$this->setExpectedTriggerError(E_USER_ERROR, $expecting);  		$this->display('test');  	} -	private function run_template($file, array $vars, array $block_vars, array $destroy, $expected, $cache_file) -	{ -		$this->template->set_filenames(array('test' => $file)); -		$this->template->assign_vars($vars); - -		foreach ($block_vars as $block => $loops) -		{ -			foreach ($loops as $_vars) -			{ -				$this->template->assign_block_vars($block, $_vars); -			} -		} - -		foreach ($destroy as $block) -		{ -			$this->template->destroy_block_vars($block); -		} - -		try -		{ -			$this->assertEquals($expected, $this->display('test'), "Testing $file"); -			$this->assertFileExists($cache_file); -		} -		catch (ErrorException $e) -		{ -			if (file_exists($cache_file)) -			{ -				copy($cache_file, str_replace('ctpl_', 'tests_ctpl_', $cache_file)); -			} - -			throw $e; -		} - -		// For debugging -		if (self::PRESERVE_CACHE) -		{ -			copy($cache_file, str_replace('ctpl_', 'tests_ctpl_', $cache_file)); -		} -	} -  	/**  	* @dataProvider template_data  	*/ @@ -360,43 +319,22 @@ class phpbb_template_template_test extends phpbb_test_case  			$this->template->destroy_block_vars($block);  		} -		$error_level = error_reporting(); -		error_reporting($error_level & ~E_NOTICE); -  		$this->assertEquals($expected, self::trim_template_result($this->template->assign_display('test')), "Testing assign_display($file)");  		$this->template->assign_display('test', 'VARIABLE', false); -		error_reporting($error_level); -  		$this->assertEquals($expected, $this->display('container'), "Testing assign_display($file)");  	}  	public function test_php()  	{ -		$GLOBALS['config']['tpl_allow_php'] = true; +		$this->setup_engine(array('tpl_allow_php' => true));  		$cache_file = $this->template->cachepath . 'php.html.php';  		$this->assertFileNotExists($cache_file);  		$this->run_template('php.html', array(), array(), array(), 'test', $cache_file); - -		$GLOBALS['config']['tpl_allow_php'] = false; -	} - -	public function test_includephp() -	{ -		$GLOBALS['config']['tpl_allow_php'] = true; - -		$cache_file = $this->template->cachepath . 'includephp.html.php'; - -		$this->run_template('includephp.html', array(), array(), array(), 'testing included php', $cache_file); - -		$this->template->set_filenames(array('test' => 'includephp.html')); -		$this->assertEquals('testing included php', $this->display('test'), "Testing INCLUDEPHP"); - -		$GLOBALS['config']['tpl_allow_php'] = false;  	}  	public static function alter_block_array_data() @@ -507,5 +445,5 @@ EOT  		$this->template->alter_block_array($alter_block, $vararray, $key, $mode);  		$this->assertEquals($expect, $this->display('test'), $description);  	} -} +} diff --git a/tests/template/template_test_case.php b/tests/template/template_test_case.php new file mode 100644 index 0000000000..e475e4012f --- /dev/null +++ b/tests/template/template_test_case.php @@ -0,0 +1,118 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2011 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php'; + +class phpbb_template_template_test_case extends phpbb_test_case +{ +	protected $template; +	protected $template_path; +	protected $template_locator; + +	// Keep the contents of the cache for debugging? +	const PRESERVE_CACHE = true; + +	protected function display($handle) +	{ +		ob_start(); +		$this->assertTrue($this->template->display($handle)); +		return self::trim_template_result(ob_get_clean()); +	} + +	protected static function trim_template_result($result) +	{ +		return str_replace("\n\n", "\n", implode("\n", array_map('trim', explode("\n", trim($result))))); +	} + +	protected function setup_engine(array $new_config = array()) +	{ +		global $phpbb_root_path, $phpEx, $user; + +		$defaults = array( +			'load_tplcompile'	=> true, +			'tpl_allow_php'		=> false, +		); + +		$config = new phpbb_config(array_merge($defaults, $new_config)); + +		$this->template_path = dirname(__FILE__) . '/templates'; +		$this->template_locator = new phpbb_template_locator(); +		$this->template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $this->template_locator); +		$this->template->set_custom_template($this->template_path, 'tests'); +	} + +	protected function setUp() +	{ +		// Test the engine can be used +		$this->setup_engine(); + +		if (!is_writable(dirname($this->template->cachepath))) +		{ +			$this->markTestSkipped("Template cache directory is not writable."); +		} + +		foreach (glob($this->template->cachepath . '*') as $file) +		{ +			unlink($file); +		} + +		$this->setup_engine(); +	} + +	protected function tearDown() +	{ +		if (is_object($this->template)) +		{ +			foreach (glob($this->template->cachepath . '*') as $file) +			{ +				unlink($file); +			} +		} +	} + +	protected function run_template($file, array $vars, array $block_vars, array $destroy, $expected, $cache_file) +	{ +		$this->template->set_filenames(array('test' => $file)); +		$this->template->assign_vars($vars); + +		foreach ($block_vars as $block => $loops) +		{ +			foreach ($loops as $_vars) +			{ +				$this->template->assign_block_vars($block, $_vars); +			} +		} + +		foreach ($destroy as $block) +		{ +			$this->template->destroy_block_vars($block); +		} + +		try +		{ +			$this->assertEquals($expected, $this->display('test'), "Testing $file"); +			$this->assertFileExists($cache_file); +		} +		catch (ErrorException $e) +		{ +			if (file_exists($cache_file)) +			{ +				copy($cache_file, str_replace('ctpl_', 'tests_ctpl_', $cache_file)); +			} +			throw $e; +		} + +		// For debugging. +		// When testing eval path the cache file may not exist. +		if (self::PRESERVE_CACHE && file_exists($cache_file)) +		{ +			copy($cache_file, str_replace('ctpl_', 'tests_ctpl_', $cache_file)); +		} +	} +} diff --git a/tests/template/templates/basic.html b/tests/template/templates/basic.html index c1dd690260..e5c6f280fb 100644 --- a/tests/template/templates/basic.html +++ b/tests/template/templates/basic.html @@ -16,5 +16,8 @@ fail  <!-- BEGINELSE -->  pass  <!-- END empty --> +<!-- IF not S_EMPTY --> +pass +<!-- ENDIF -->  <!-- DUMMY var --> diff --git a/tests/template/templates/child_only.html b/tests/template/templates/child_only.html new file mode 100644 index 0000000000..6121fef5c5 --- /dev/null +++ b/tests/template/templates/child_only.html @@ -0,0 +1 @@ +Only in child. diff --git a/tests/template/templates/define.html b/tests/template/templates/define.html index 82237d21a3..4459fffbe0 100644 --- a/tests/template/templates/define.html +++ b/tests/template/templates/define.html @@ -2,6 +2,9 @@  {$VALUE}  <!-- DEFINE $VALUE = 'abc' -->  {$VALUE} +<!-- INCLUDE define_include.html --> +{$INCLUDED_VALUE} +{$VALUE}  <!-- UNDEFINE $VALUE -->  {$VALUE}  <!-- DEFINE $VALUE --> diff --git a/tests/template/templates/define_include.html b/tests/template/templates/define_include.html new file mode 100644 index 0000000000..9c470c296a --- /dev/null +++ b/tests/template/templates/define_include.html @@ -0,0 +1,3 @@ +{$VALUE} +<!-- DEFINE $INCLUDED_VALUE = 'bar' --> +{$INCLUDED_VALUE} diff --git a/tests/template/templates/expressions.html b/tests/template/templates/expressions.html index c40d967dab..e1283f165f 100644 --- a/tests/template/templates/expressions.html +++ b/tests/template/templates/expressions.html @@ -1,86 +1,57 @@  <!-- IF 10 is even -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 9 is even -->fail<!-- ELSE -->pass<!-- ENDIF --> -  <!-- IF not 390 is even -->fail<!-- ELSE -->pass<!-- ENDIF --> -  <!-- IF 9 is odd -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 32 is odd -->fail<!-- ELSE -->pass<!-- ENDIF --> -  <!-- IF 32 is div by 16 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 10 is not even -->fail<!-- ELSE -->pass<!-- ENDIF --> -  <!-- IF 24 == 24 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 24 eq 24 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF ((((((24 == 24)))))) -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF 24 != 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 24 <> 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 24 ne 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 24 neq 20 -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF 10 lt 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 10 < 20 -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF 10 le 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 10 lte 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 10 <= 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 20 le 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 20 lte 20 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 20 <= 20 -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF 9 gt 1 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 9 > 1 -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF 9 >= 1 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 9 gte 1 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 9 ge 1 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 9 >= 9 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 9 gte 9 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 9 ge 9 -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF true && (10 > 4) -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF true and (10 > 4) -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF false || true -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF false or true -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF !false -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF not false -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF not not not false -->pass<!-- ELSE -->fail<!-- ENDIF -->  <!-- IF 6 % 4 == 2 -->pass<!-- ELSE -->fail<!-- ENDIF --> -  <!-- IF 24 mod 12 == 0 -->pass<!-- ELSE -->fail<!-- ENDIF --> diff --git a/tests/template/templates/if.html b/tests/template/templates/if.html index c502e52f51..eed431019e 100644 --- a/tests/template/templates/if.html +++ b/tests/template/templates/if.html @@ -3,9 +3,9 @@  <!-- ELSEIF S_OTHER_VALUE -->  2  <!-- ELSE --> -0 +03  <!-- ENDIF --> -<!-- IF (S_VALUE > S_OTHER_VALUE) --> -0 +<!-- IF S_VALUE and S_OTHER_VALUE and (S_VALUE > S_OTHER_VALUE) --> +04  <!-- ENDIF --> diff --git a/tests/template/templates/includephp.html b/tests/template/templates/includephp_relative.html index 70ebdac0d0..297c9efcb0 100644 --- a/tests/template/templates/includephp.html +++ b/tests/template/templates/includephp_relative.html @@ -1 +1,2 @@ +Path is relative to board root.  <!-- INCLUDEPHP ../tests/template/templates/_dummy_include.php.inc --> diff --git a/tests/template/templates/loop_nested.html b/tests/template/templates/loop_nested.html index 9b251cd453..45b1ef85d4 100644 --- a/tests/template/templates/loop_nested.html +++ b/tests/template/templates/loop_nested.html @@ -1,8 +1,6 @@  <!-- BEGIN outer -->  	outer - {outer.S_ROW_COUNT}<!-- IF outer.VARIABLE --> - {outer.VARIABLE}<!-- ENDIF --> -  	<!-- BEGIN middle -->  		middle - {middle.S_ROW_COUNT}<!-- IF middle.VARIABLE --> - {middle.VARIABLE}<!-- ENDIF --> -  	<!-- END middle -->  <!-- END outer --> diff --git a/tests/template/templates/loop_nested_deep_multilevel_ref.html b/tests/template/templates/loop_nested_deep_multilevel_ref.html new file mode 100644 index 0000000000..60fad7b4cd --- /dev/null +++ b/tests/template/templates/loop_nested_deep_multilevel_ref.html @@ -0,0 +1,12 @@ +top-level content +<!-- BEGIN outer --> +	outer +	<!-- BEGIN middle --> +		<!-- BEGIN inner --> +			inner {inner.VARIABLE} +			<!-- IF outer.middle.inner.S_FIRST_ROW --> +				first row +			<!-- ENDIF --> +		<!-- END inner --> +	<!-- END middle --> +<!-- END outer --> diff --git a/tests/template/templates/loop_nested_multilevel_ref.html b/tests/template/templates/loop_nested_multilevel_ref.html new file mode 100644 index 0000000000..f2f1c746ed --- /dev/null +++ b/tests/template/templates/loop_nested_multilevel_ref.html @@ -0,0 +1,10 @@ +top-level content +<!-- BEGIN outer --> +	outer {outer.VARIABLE} +	<!-- BEGIN inner --> +		inner {inner.VARIABLE} +		<!-- IF outer.inner.S_FIRST_ROW --> +			first row +		<!-- ENDIF --> +	<!-- END inner --> +<!-- END outer --> diff --git a/tests/template/templates/loop_reuse.html b/tests/template/templates/loop_reuse.html new file mode 100644 index 0000000000..bd0354ae6f --- /dev/null +++ b/tests/template/templates/loop_reuse.html @@ -0,0 +1,6 @@ +<!-- BEGIN one --> +	{one.VAR} +	<!-- BEGIN one --> +		{one.one.VAR} +	<!-- END one --> +<!-- END one --> diff --git a/tests/template/templates/loop_size.html b/tests/template/templates/loop_size.html new file mode 100644 index 0000000000..f1938441df --- /dev/null +++ b/tests/template/templates/loop_size.html @@ -0,0 +1,39 @@ +<!-- IF .nonexistent_loop --> +	nonexistent +<!-- ENDIF --> + +<!-- IF .nonexistent_loop == 0 --> +	nonexistent = 0 +<!-- ENDIF --> + +<!-- IF ! .nonexistent_loop --> +	! nonexistent +<!-- ENDIF --> + +<!-- IF .empty_loop --> +	empty +<!-- ENDIF --> + +<!-- IF .empty_loop == 0 --> +	empty = 0 +<!-- ENDIF --> + +<!-- IF ! .empty_loop --> +	! empty +<!-- ENDIF --> + +<!-- IF .loop --> +	loop +<!-- ENDIF --> + +<!-- IF .loop == 0 --> +	loop = 0 +<!-- ENDIF --> + +<!-- IF ! .loop --> +	! loop +<!-- ENDIF --> + +<!-- BEGIN loop --> +	in loop +<!-- END --> diff --git a/tests/template/templates/loop_underscore.html b/tests/template/templates/loop_underscore.html new file mode 100644 index 0000000000..dafce5dea6 --- /dev/null +++ b/tests/template/templates/loop_underscore.html @@ -0,0 +1,21 @@ +<!-- BEGIN _underscore_loop --> +loop +<!-- BEGINELSE --> +noloop +<!-- END loop --> + +<!-- IF ._underscore_loop --> +loop +<!-- ELSE --> +noloop +<!-- ENDIF --> + +<!-- IF ._underscore_loop == 2 --> +loop +<!-- ENDIF --> + +<!-- BEGIN _underscore_loop --> +<!-- BEGIN !block --> +loop#{loop.S_ROW_COUNT}-block#{block.S_ROW_COUNT} +<!-- END !block --> +<!-- END _underscore_loop --> diff --git a/tests/template/templates/loop_vars.html b/tests/template/templates/loop_vars.html index 4f02fd2e6c..d94a0ae0f7 100644 --- a/tests/template/templates/loop_vars.html +++ b/tests/template/templates/loop_vars.html @@ -1,21 +1,14 @@  <!-- BEGIN loop -->  <!-- IF loop.S_FIRST_ROW -->first<!-- ENDIF --> - -{loop.S_ROW_COUNT} - -{loop.VARIABLE} - +{loop.S_ROW_NUM} - a +{loop.VARIABLE} - b  <!-- IF loop.VARIABLE -->set<!-- ENDIF --> -  <!-- IF loop.S_LAST_ROW -->  last  <!-- ENDIF -->  <!-- BEGIN inner --> - -{inner.S_ROW_COUNT} - +{inner.S_ROW_NUM} - c  <!-- IF inner.S_LAST_ROW and inner.S_ROW_COUNT and inner.S_NUM_ROWS -->last inner<!-- ENDIF --> -  <!-- END inner -->  <!-- END loop -->  <!-- IF .loop.inner -->inner loop<!-- ENDIF --> diff --git a/tests/template/templates/parent_and_child.html b/tests/template/templates/parent_and_child.html new file mode 100644 index 0000000000..16223d91e7 --- /dev/null +++ b/tests/template/templates/parent_and_child.html @@ -0,0 +1 @@ +Child template. diff --git a/tests/template/templates/trivial.html b/tests/template/templates/trivial.html new file mode 100644 index 0000000000..3a1c1c5324 --- /dev/null +++ b/tests/template/templates/trivial.html @@ -0,0 +1 @@ +This is a trivial template. | 
