diff options
Diffstat (limited to 'phpBB/includes/template')
| -rw-r--r-- | phpBB/includes/template/compile.php | 122 | ||||
| -rw-r--r-- | phpBB/includes/template/context.php | 342 | ||||
| -rw-r--r-- | phpBB/includes/template/filter.php | 971 | ||||
| -rw-r--r-- | phpBB/includes/template/locator.php | 211 | ||||
| -rw-r--r-- | phpBB/includes/template/renderer.php | 35 | ||||
| -rw-r--r-- | phpBB/includes/template/renderer_eval.php | 60 | ||||
| -rw-r--r-- | phpBB/includes/template/renderer_include.php | 60 | ||||
| -rw-r--r-- | phpBB/includes/template/template.php | 486 | 
8 files changed, 2287 insertions, 0 deletions
| 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..115fe21e35 --- /dev/null +++ b/phpBB/includes/template/filter.php @@ -0,0 +1,971 @@ +<?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_VAR_SUFFIX = '[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 ''; +	} + +	/** +	* Convert template variables into PHP varrefs +	* +	* @param string $text_blocks Variable reference in source template +	* @param bool $is_expr Returns whether the source was an expression type variable (i.e. S_FIRST_ROW) +	* @return string PHP variable name +	*/ +	private function get_varref($text_blocks, &$is_expr) +	{ +		// 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, $is_expr, $var_val[2]); + +			$text_blocks = str_replace($var_val[0], $new, $text_blocks); +		} + +		// Language variables cannot be reduced to a single varref, so they must be skipped +		// These two replacements would break language variables, so we can only run them on non-language types +		if (strpos($text_blocks, '{L_') === false && strpos($text_blocks, '{LA_') === false) +		{ +			// This will handle the remaining root-level varrefs +			$text_blocks = preg_replace('#\{(' . self::REGEX_VAR . ')\}#', "\$_rootref['\\1']", $text_blocks); +			$text_blocks = preg_replace('#\{\$(' . self::REGEX_VAR . ')\}#', "\$_tpldata['DEFINE']['.']['\\1']", $text_blocks); +		} + +		return $text_blocks; +	} + +	/** +	* Compile variables +	* +	* @param string $text_blocks Variable reference in source template +	* @return string compiled template code +	*/ +	private function compile_var_tags(&$text_blocks) +	{ +		$text_blocks = $this->get_varref($text_blocks, $is_expr); +		$lang_replaced = $this->compile_language_tags($text_blocks); + +		if(!$lang_replaced) +		{ +			$text_blocks = '<?php echo ' . ($is_expr ? "$text_blocks" : "(isset($text_blocks)) ? $text_blocks : ''") . '; /**/?>'; +		} + +		return $text_blocks; +	} + +	/** +	* Handles special language tags L_ and LA_ +	* +	* @param string $text_blocks Variable reference in source template +	* @return bool Whether a replacement occurred or not +	*/ +	private function compile_language_tags(&$text_blocks) +	{ +		$replacements = 0; + +		// 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_SUFFIX . ')\}#', "<?php echo ((isset(\$_rootref['L_\\1'])) ? \$_rootref['L_\\1'] : ((isset(\$_lang['\\1'])) ? \$_lang['\\1'] : '{ \\1 }')); /**/?>", $text_blocks, -1, $replacements); +			return (bool) $replacements; +		} + +		// 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_SUFFIX . '+)\}#', "<?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, -1, $replacements); +			return (bool) $replacements; +		} + +		return false; +	} + +	/** +	* 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) +	{ +		// Process dynamic includes +		if ($tag_args[0] == '{') +		{ +			$var = $this->get_varref($tag_args, $is_expr); + +			// Make sure someone didn't try to include S_FIRST_ROW or similar +			if (!$is_expr) +			{ +				return "if (isset($var)) { \$_template->_tpl_include($var); }"; +			} +		} + +		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 $expr Returns whether the source was an expression type +	* @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, &$expr, $defop = false) +	{ +		// Strip the trailing period. +		$namespace = substr($namespace, 0, -1); + +		if (($pos = strrpos($namespace, '.')) !== false) +		{ +			$local_namespace = substr($namespace, $pos + 1); +		} +		else +		{ +			$local_namespace = $namespace; +		} + +		$expr = true; + +		// 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 = "\$_${local_namespace}_i"; +			break; + +			case 'S_NUM_ROWS': +				$varref = "\$_${local_namespace}_count"; +			break; + +			case 'S_FIRST_ROW': +				$varref = "(\$_${local_namespace}_i == 0)"; +			break; + +			case 'S_LAST_ROW': +				$varref = "(\$_${local_namespace}_i == \$_${local_namespace}_count - 1)"; +			break; + +			case 'S_BLOCK_NAME': +				$varref = "'$local_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; +			break; +		} +		// @todo Test the !$expr more + +		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..2c05a1c1df --- /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 . '<?php '); +	} +} 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..ec5fbe2829 --- /dev/null +++ b/phpBB/includes/template/template.php @@ -0,0 +1,486 @@ +<?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/tpl_' . 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) +	{ +		$output_file = $this->_compiled_file_for_handle($handle); + +		$recompile = defined('DEBUG_EXTRA') || +			!file_exists($output_file) || +			@filesize($output_file) === 0; + +		if ($recompile || $this->config['load_tplcompile']) +		{ +			// Set only if a recompile or an mtime check are required. +			$source_file = $this->locator->get_source_file_for_handle($handle); + +			if (!$recompile && @filemtime($output_file) < @filemtime($source_file)) +			{ +				$recompile = true; +			} +		} + +		// Recompile page if the original template is newer, otherwise load the compiled version +		if (!$recompile) +		{ +			return new phpbb_template_renderer_include($output_file, $this); +		} + +		$compile = new phpbb_template_compile($this->config['tpl_allow_php']); + +		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); +	} +} | 
