1
0
mirror of https://github.com/phpbb/phpbb.git synced 2025-07-17 07:01:22 +02:00

Merge remote-tracking branch 'github-p/feature/template-engine' into develop

* github-p/feature/template-engine: (87 commits)
  [feature/template-engine] Delete _get_locator function.
  [feature/template-engine] Clean up template locator usage in bbcode.
  [feature/template-engine] Need to call set_template on template.
  [feature/template-engine] Update installer for template engine changes.
  [feature/template-engine] Dependency inject locator into template.
  [feature/template-engine] Delete useless code from set_template.
  [feature/template-engine] Delete no longer used $template_filename property.
  [feature/template-engine] Delete useless $template globalization.
  [feature/template-engine] Use template engine class in bbcode class.
  [feature/template-engine] Corrected an error message in template locator.
  [feature/template-engine] Remaining documentation.
  [feature/template-engine] More documentation for template class.
  [feature/template-engine] Create load_and_render to reduce code duplication.
  [feature/template-engine] Get rid of orig_tpl_* in template engine.
  [feature/template-engine] Delete $style_name param from locator's set_custom_template.
  [feature/template-engine] Add constructor to template locator.
  [feature/template-engine] Factor template locator out of template class.
  [feature/template-engine] Delete $files_template property.
  [feature/template-engine] Rename is_absolute to phpbb_is_absolute.
  [feature/template-engine] Test template DEFINE statements across files
  ...

Conflicts:
	.gitignore
	phpBB/includes/template.php
This commit is contained in:
Nils Adermann
2011-08-13 23:59:15 -04:00
43 changed files with 2771 additions and 1712 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
*~
/phpunit.xml
/phpBB/cache/*.html
/phpBB/cache/*.php
/phpBB/cache/queue.php.lock
/phpBB/config.php

View File

@@ -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')));

View File

@@ -0,0 +1,24 @@
<?php
// -------------------------------------------------------------
//
// $Id$
//
// FILENAME : compile_template.php
// STARTED : Sun Apr 24, 2011
// COPYRIGHT : <20> 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);

View File

@@ -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-&gt;display($handle, $include_once = true);</code> which is called directly before outputting the (not-yet-compiled) template.<br />
<code>$template-&gt;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-&gt;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-&gt;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>

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View File

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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

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

View File

@@ -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]. '\']';
}
}
}

View File

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

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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');

View File

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

View File

@@ -0,0 +1 @@
Parent template.

View File

@@ -0,0 +1 @@
Only in parent.

View File

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

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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',
@@ -226,6 +176,15 @@ class phpbb_template_template_test extends phpbb_test_case
array('loop.inner'),
"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(),
@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -16,5 +16,8 @@ fail
<!-- BEGINELSE -->
pass
<!-- END empty -->
<!-- IF not S_EMPTY -->
pass
<!-- ENDIF -->
<!-- DUMMY var -->

View File

@@ -0,0 +1 @@
Only in child.

View File

@@ -2,6 +2,9 @@
{$VALUE}
<!-- DEFINE $VALUE = 'abc' -->
{$VALUE}
<!-- INCLUDE define_include.html -->
{$INCLUDED_VALUE}
{$VALUE}
<!-- UNDEFINE $VALUE -->
{$VALUE}
<!-- DEFINE $VALUE -->

View File

@@ -0,0 +1,3 @@
{$VALUE}
<!-- DEFINE $INCLUDED_VALUE = 'bar' -->
{$INCLUDED_VALUE}

View File

@@ -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 -->

View File

@@ -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 -->

View File

@@ -1 +1,2 @@
Path is relative to board root.
<!-- INCLUDEPHP ../tests/template/templates/_dummy_include.php.inc -->

View File

@@ -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 -->

View File

@@ -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 -->

View File

@@ -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 -->

View File

@@ -0,0 +1,6 @@
<!-- BEGIN one -->
{one.VAR}
<!-- BEGIN one -->
{one.one.VAR}
<!-- END one -->
<!-- END one -->

View File

@@ -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 -->

View File

@@ -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 -->

View File

@@ -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 -->

View File

@@ -0,0 +1 @@
Child template.

View File

@@ -0,0 +1 @@
This is a trivial template.