diff options
Diffstat (limited to 'phpBB/includes')
38 files changed, 2420 insertions, 2056 deletions
diff --git a/phpBB/includes/acp/acp_bbcodes.php b/phpBB/includes/acp/acp_bbcodes.php index e537d7a8b9..9c430b5a0b 100644 --- a/phpBB/includes/acp/acp_bbcodes.php +++ b/phpBB/includes/acp/acp_bbcodes.php @@ -112,8 +112,8 @@ class acp_bbcodes { $template->assign_block_vars('token', array( 'TOKEN' => '{' . $token . '}', - 'EXPLAIN' => $token_explain) - ); + 'EXPLAIN' => ($token === 'LOCAL_URL') ? sprintf($token_explain, generate_board_url() . '/') : $token_explain, + )); } return; @@ -347,6 +347,9 @@ class acp_bbcodes 'LOCAL_URL' => array( '!(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('relative_url')) . ')!e' => "\$this->bbcode_specialchars('$1')" ), + 'RELATIVE_URL' => array( + '!(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('relative_url')) . ')!e' => "\$this->bbcode_specialchars('$1')" + ), 'EMAIL' => array( '!(' . get_preg_expression('email') . ')!ie' => "\$this->bbcode_specialchars('$1')" ), @@ -373,6 +376,7 @@ class acp_bbcodes $sp_tokens = array( 'URL' => '(?i)((?:' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('url')) . ')|(?:' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('www_url')) . '))(?-i)', 'LOCAL_URL' => '(?i)(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('relative_url')) . ')(?-i)', + 'RELATIVE_URL' => '(?i)(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('relative_url')) . ')(?-i)', 'EMAIL' => '(' . get_preg_expression('email') . ')', 'TEXT' => '(.*?)', 'SIMPLETEXT' => '([a-zA-Z0-9-+.,_ ]+)', @@ -429,7 +433,11 @@ class acp_bbcodes $fp_replace = str_replace($token, $replace, $fp_replace); $sp_match = str_replace(preg_quote($token, '!'), $sp_tokens[$token_type], $sp_match); - $sp_replace = str_replace($token, '${' . ($n + 1) . '}', $sp_replace); + + // Prepend the board url to local relative links + $replace_prepend = ($token_type === 'LOCAL_URL') ? generate_board_url() . '/' : ''; + + $sp_replace = str_replace($token, $replace_prepend . '${' . ($n + 1) . '}', $sp_replace); } $fp_match = '!' . $fp_match . '!' . $modifiers; diff --git a/phpBB/includes/bbcode.php b/phpBB/includes/bbcode.php index c198abeb54..fd00728510 100644 --- a/phpBB/includes/bbcode.php +++ b/phpBB/includes/bbcode.php @@ -134,11 +134,11 @@ class bbcode $style_resource_locator = new phpbb_style_resource_locator(); $style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider(), $phpbb_root_path); - $template = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context(), $phpbb_extension_manager); + $template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, new phpbb_template_context(), $phpbb_extension_manager); $style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $template); $style->set_style(); $template->set_filenames(array('bbcode.html' => 'bbcode.html')); - $this->template_filename = $style_resource_locator->get_source_file_for_handle('bbcode.html'); + $this->template_filename = $template->get_source_file_for_handle('bbcode.html'); } $bbcode_ids = $rowset = $sql = array(); diff --git a/phpBB/includes/controller/resolver.php b/phpBB/includes/controller/resolver.php index ee469aa9c8..95dfc8da8e 100644 --- a/phpBB/includes/controller/resolver.php +++ b/phpBB/includes/controller/resolver.php @@ -38,15 +38,23 @@ class phpbb_controller_resolver implements ControllerResolverInterface protected $container; /** + * phpbb_style object + * @var phpbb_style + */ + protected $style; + + /** * Construct method * * @param phpbb_user $user User Object * @param ContainerInterface $container ContainerInterface object + * @param phpbb_style $style */ - public function __construct(phpbb_user $user, ContainerInterface $container) + public function __construct(phpbb_user $user, ContainerInterface $container, phpbb_style $style = null) { $this->user = $user; $this->container = $container; + $this->style = $style; } /** @@ -80,6 +88,24 @@ class phpbb_controller_resolver implements ControllerResolverInterface $controller_object = $this->container->get($service); + /* + * If this is an extension controller, we'll try to automatically set + * the style paths for the extension (the ext author can change them + * if necessary). + */ + $controller_dir = explode('_', get_class($controller_object)); + + // 0 phpbb, 1 ext, 2 vendor, 3 extension name, ... + if (!is_null($this->style) && isset($controller_dir[3]) && $controller_dir[1] === 'ext') + { + $controller_style_dir = 'ext/' . $controller_dir[2] . '/' . $controller_dir[3] . '/styles'; + + if (is_dir($controller_style_dir)) + { + $this->style->set_style(array($controller_style_dir, 'styles')); + } + } + return array($controller_object, $method); } diff --git a/phpBB/includes/db/migration/data/30x/local_url_bbcode.php b/phpBB/includes/db/migration/data/30x/local_url_bbcode.php new file mode 100644 index 0000000000..f324b8880d --- /dev/null +++ b/phpBB/includes/db/migration/data/30x/local_url_bbcode.php @@ -0,0 +1,57 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_db_migration_data_30x_local_url_bbcode extends phpbb_db_migration +{ + static public function depends_on() + { + return array('phpbb_db_migration_data_30x_3_0_12_rc1'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_local_url_bbcode'))), + ); + } + + /** + * Update BBCodes that currently use the LOCAL_URL tag + * + * To fix http://tracker.phpbb.com/browse/PHPBB3-8319 we changed + * the second_pass_replace value, so that needs updating for existing ones + */ + public function update_local_url_bbcode() + { + $sql = 'SELECT * + FROM ' . BBCODES_TABLE . ' + WHERE bbcode_match ' . $this->db->sql_like_expression($this->db->any_char . 'LOCAL_URL' . $this->db->any_char); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!class_exists('acp_bbcodes')) + { + global $phpEx; + phpbb_require_updated('includes/acp/acp_bbcodes.' . $phpEx); + } + $bbcode_match = $row['bbcode_match']; + $bbcode_tpl = $row['bbcode_tpl']; + + $acp_bbcodes = new acp_bbcodes(); + $sql_ary = $acp_bbcodes->build_regexp($bbcode_match, $bbcode_tpl); + + $sql = 'UPDATE ' . BBCODES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE bbcode_id = ' . (int) $row['bbcode_id']; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } +} diff --git a/phpBB/includes/functions_container.php b/phpBB/includes/functions_container.php index 106b7d75cc..0575f00a0b 100644 --- a/phpBB/includes/functions_container.php +++ b/phpBB/includes/functions_container.php @@ -21,6 +21,67 @@ if (!defined('IN_PHPBB')) } /** +* Get DB connection from config.php. +* +* Used to bootstrap the container. +* +* @param string $config_file +* @return phpbb_db_driver +*/ +function phpbb_bootstrap_db_connection($config_file) +{ + require($config_file); + $dbal_driver_class = phpbb_convert_30_dbms_to_31($dbms); + + return new $dbal_driver_class($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, defined('PHPBB_DB_NEW_LINK')); +} + +/** +* Get table prefix from config.php. +* +* Used to bootstrap the container. +* +* @param string $config_file +* @return string table prefix +*/ +function phpbb_bootstrap_table_prefix($config_file) +{ + require($config_file); + return $table_prefix; +} + +/** +* Get enabled extensions. +* +* Used to bootstrap the container. +* +* @param string $config_file +* @return array enabled extensions +*/ +function phpbb_bootstrap_enabled_exts($config_file) +{ + $db = phpbb_bootstrap_db_connection($config_file); + $table_prefix = phpbb_bootstrap_table_prefix($config_file); + $extension_table = $table_prefix.'ext'; + + $sql = 'SELECT * + FROM ' . $extension_table . ' + WHERE ext_active = 1'; + + $result = $db->sql_query($sql); + $rows = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + $exts = array(); + foreach ($rows as $row) + { + $exts[$row['ext_name']] = $phpbb_root_path . 'ext/' . $row['ext_name'] . '/'; + } + + return $exts; +} + +/** * Create the ContainerBuilder object * * @param array $extensions Array of Container extension objects @@ -79,16 +140,9 @@ function phpbb_create_install_container($phpbb_root_path, $php_ext) * @param string $php_ext PHP Extension * @return ContainerBuilder object (compiled) */ -function phpbb_create_compiled_container(array $extensions, array $passes, $phpbb_root_path, $php_ext) +function phpbb_create_compiled_container($config_file, array $extensions, array $passes, $phpbb_root_path, $php_ext) { - // Create a temporary container for access to the ext.manager service - $tmp_container = phpbb_create_container($extensions, $phpbb_root_path, $php_ext); - $tmp_container->compile(); - - // XXX stop writing to global $cache when - // http://tracker.phpbb.com/browse/PHPBB3-11203 is fixed - $GLOBALS['cache'] = $tmp_container->get('cache'); - $installed_exts = $tmp_container->get('ext.manager')->all_enabled(); + $installed_exts = phpbb_bootstrap_enabled_exts($config_file); // Now pass the enabled extension paths into the ext compiler extension $extensions[] = new phpbb_di_extension_ext($installed_exts); @@ -115,7 +169,7 @@ function phpbb_create_compiled_container(array $extensions, array $passes, $phpb * @param string $php_ext PHP Extension * @return ContainerBuilder object (compiled) */ -function phpbb_create_dumped_container(array $extensions, array $passes, $phpbb_root_path, $php_ext) +function phpbb_create_dumped_container($config_file, array $extensions, array $passes, $phpbb_root_path, $php_ext) { // Check for our cached container; if it exists, use it $container_filename = phpbb_container_filename($phpbb_root_path, $php_ext); @@ -155,7 +209,7 @@ function phpbb_create_dumped_container(array $extensions, array $passes, $phpbb_ * @param string $php_ext PHP Extension * @return ContainerBuilder object (compiled) */ -function phpbb_create_dumped_container_unless_debug(array $extensions, array $passes, $phpbb_root_path, $php_ext) +function phpbb_create_dumped_container_unless_debug($config_file, array $extensions, array $passes, $phpbb_root_path, $php_ext) { $container_factory = defined('DEBUG') ? 'phpbb_create_compiled_container' : 'phpbb_create_dumped_container'; return $container_factory($extensions, $passes, $phpbb_root_path, $php_ext); @@ -172,9 +226,11 @@ function phpbb_create_dumped_container_unless_debug(array $extensions, array $pa */ function phpbb_create_default_container($phpbb_root_path, $php_ext) { + $config_file = $phpbb_root_path . 'config.' . $php_ext; return phpbb_create_dumped_container_unless_debug( + $config_file, array( - new phpbb_di_extension_config($phpbb_root_path . 'config.' . $php_ext), + new phpbb_di_extension_config($config_file), new phpbb_di_extension_core($phpbb_root_path), ), array( diff --git a/phpBB/includes/functions_messenger.php b/phpBB/includes/functions_messenger.php index a646f35fdd..0222a57bcc 100644 --- a/phpBB/includes/functions_messenger.php +++ b/phpBB/includes/functions_messenger.php @@ -27,8 +27,9 @@ class messenger var $mail_priority = MAIL_NORMAL_PRIORITY; var $use_queue = true; - var $tpl_obj = NULL; - var $tpl_msg = array(); + /** @var phpbb_template */ + protected $template; + var $eol = "\n"; /** @@ -55,10 +56,10 @@ class messenger $this->vars = $this->msg = $this->replyto = $this->from = ''; $this->mail_priority = MAIL_NORMAL_PRIORITY; } - + /** * Set addresses for to/im as available - * + * * @param array $user User row */ function set_addresses($user) @@ -210,6 +211,8 @@ class messenger { global $config, $phpbb_root_path, $phpEx, $user, $phpbb_extension_manager; + $this->setup_template(); + if (!trim($template_file)) { trigger_error('No template file for emailing set.', E_USER_ERROR); @@ -219,46 +222,43 @@ class messenger { // fall back to board default language if the user's language is // missing $template_file. If this does not exist either, - // $tpl->set_filenames will do a trigger_error + // $this->template->set_filenames will do a trigger_error $template_lang = basename($config['default_lang']); } - // tpl_msg now holds a template object we can use to parse the template file - if (!isset($this->tpl_msg[$template_lang . $template_file])) + if ($template_path) { - $style_resource_locator = new phpbb_style_resource_locator(); - $style_path_provider = new phpbb_style_extension_path_provider($phpbb_extension_manager, new phpbb_style_path_provider(), $phpbb_root_path); - $tpl = new phpbb_template($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, new phpbb_template_context(), $phpbb_extension_manager); - $style = new phpbb_style($phpbb_root_path, $phpEx, $config, $user, $style_resource_locator, $style_path_provider, $tpl); - - $this->tpl_msg[$template_lang . $template_file] = $tpl; + $template_paths = array( + $template_path, + ); + } + else + { + $template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/'; + $template_path .= $template_lang . '/email'; - $fallback_template_path = false; + $template_paths = array( + $template_path, + ); - if (!$template_path) + // we can only specify default language fallback when the path is not a custom one for which we + // do not know the default language alternative + if ($template_lang !== basename($config['default_lang'])) { - $template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/'; - $template_path .= $template_lang . '/email'; + $fallback_template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/'; + $fallback_template_path .= basename($config['default_lang']) . '/email'; - // we can only specify default language fallback when the path is not a custom one for which we - // do not know the default language alternative - if ($template_lang !== basename($config['default_lang'])) - { - $fallback_template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/'; - $fallback_template_path .= basename($config['default_lang']) . '/email'; - } + $template_paths[] = $fallback_template_path; } + } - $style->set_custom_style($template_lang . '_email', array($template_path, $fallback_template_path), array(), ''); + $this->set_template_paths($template_lang . '_email', $template_paths); - $tpl->set_filenames(array( - 'body' => $template_file . '.txt', - )); - } + $this->template->set_filenames(array( + 'body' => $template_file . '.txt', + )); - $this->tpl_obj = &$this->tpl_msg[$template_lang . $template_file]; - $this->vars = &$this->tpl_obj->_rootref; - $this->tpl_msg = ''; + $this->vars = $this->template->get_template_vars(); return true; } @@ -268,22 +268,16 @@ class messenger */ function assign_vars($vars) { - if (!is_object($this->tpl_obj)) - { - return; - } + $this->setup_template(); - $this->tpl_obj->assign_vars($vars); + $this->template->assign_vars($vars); } function assign_block_vars($blockname, $vars) { - if (!is_object($this->tpl_obj)) - { - return; - } + $this->setup_template(); - $this->tpl_obj->assign_block_vars($blockname, $vars); + $this->template->assign_block_vars($blockname, $vars); } /** @@ -316,7 +310,7 @@ class messenger } // Parse message through template - $this->msg = trim($this->tpl_obj->assign_display('body')); + $this->msg = trim($this->template->assign_display('body')); // Because we use \n for newlines in the body message we need to fix line encoding errors for those admins who uploaded email template files in the wrong encoding $this->msg = str_replace("\r\n", "\n", $this->msg); @@ -643,6 +637,31 @@ class messenger unset($addresses); return true; } + + /** + * Setup template engine + */ + protected function setup_template() + { + global $config, $phpbb_root_path, $phpEx, $user, $phpbb_extension_manager; + + if ($this->template instanceof phpbb_template) + { + return; + } + + $this->template = new phpbb_template_twig($phpbb_root_path, $phpEx, $config, $user, new phpbb_template_context(), $phpbb_extension_manager); + } + + /** + * Set template paths to load + */ + protected function set_template_paths($path_name, $paths) + { + $this->setup_template(); + + $this->template->set_style_names(array($path_name), $paths); + } } /** diff --git a/phpBB/includes/functions_module.php b/phpBB/includes/functions_module.php index 0d387ace6d..99c24fcb19 100644 --- a/phpBB/includes/functions_module.php +++ b/phpBB/includes/functions_module.php @@ -455,7 +455,7 @@ class p_master */ function load_active($mode = false, $module_url = false, $execute_module = true) { - global $phpbb_root_path, $phpbb_admin_path, $phpEx, $user; + global $phpbb_root_path, $phpbb_admin_path, $phpEx, $user, $phpbb_style; $module_path = $this->include_path . $this->p_class; $icat = request_var('icat', ''); @@ -494,6 +494,24 @@ class p_master // We pre-define the action parameter we are using all over the place if (defined('IN_ADMIN')) { + /* + * If this is an extension module, we'll try to automatically set + * the style paths for the extension (the ext author can change them + * if necessary). + */ + $module_dir = explode('_', get_class($this->module)); + + // 0 phpbb, 1 ext, 2 vendor, 3 extension name, ... + if (isset($module_dir[3]) && $module_dir[1] === 'ext') + { + $module_style_dir = $phpbb_root_path . 'ext/' . $module_dir[2] . '/' . $module_dir[3] . '/adm/style'; + + if (is_dir($module_style_dir)) + { + $phpbb_style->set_custom_style('admin', array($module_style_dir, $phpbb_admin_path . 'style'), array(), ''); + } + } + // Is first module automatically enabled a duplicate and the category not passed yet? if (!$icat && $this->module_ary[$this->active_module_row_id]['is_duplicate']) { @@ -505,6 +523,24 @@ class p_master } else { + /* + * If this is an extension module, we'll try to automatically set + * the style paths for the extension (the ext author can change them + * if necessary). + */ + $module_dir = explode('_', get_class($this->module)); + + // 0 phpbb, 1 ext, 2 vendor, 3 extension name, ... + if (isset($module_dir[3]) && $module_dir[1] === 'ext') + { + $module_style_dir = 'ext/' . $module_dir[2] . '/' . $module_dir[3] . '/styles'; + + if (is_dir($phpbb_root_path . $module_style_dir)) + { + $phpbb_style->set_style(array($module_style_dir, 'styles')); + } + } + // If user specified the module url we will use it... if ($module_url !== false) { diff --git a/phpBB/includes/style/style.php b/phpBB/includes/style/style.php index 4703c3a219..034f518091 100644 --- a/phpBB/includes/style/style.php +++ b/phpBB/includes/style/style.php @@ -85,28 +85,62 @@ class phpbb_style } /** - * Set style location based on (current) user's chosen style. + * Get the style tree of the style preferred by the current user + * + * @return array Style tree, most specific first */ - public function set_style() + public function get_user_style() { - $style_path = $this->user->style['style_path']; - $style_dirs = ($this->user->style['style_parent_id']) ? array_reverse(explode('/', $this->user->style['style_parent_tree'])) : array(); + $style_list = array( + $this->user->style['style_path'], + ); - $names = array($style_path); - foreach ($style_dirs as $dir) + if ($this->user->style['style_parent_id']) { - $names[] = $dir; + $style_list = array_merge($style_list, array_reverse(explode('/', $this->user->style['style_parent_tree']))); } - // Add 'all' path, used as last fallback path by events and extensions - //$names[] = 'all'; + + return $style_list; + } + + /** + * Set style location based on (current) user's chosen style. + * + * @param array $style_directories The directories to add style paths for + * E.g. array('ext/foo/bar/styles', 'styles') + * Default: array('styles') (phpBB's style directory) + * @return bool true + */ + public function set_style($style_directories = array('styles')) + { + $this->names = $this->get_user_style(); $paths = array(); - foreach ($names as $name) + foreach ($style_directories as $directory) { - $paths[] = $this->get_style_path($name); + foreach ($this->names as $name) + { + $path = $this->get_style_path($name, $directory); + + if (is_dir($path)) + { + $paths[] = $path; + } + } } - return $this->set_custom_style($style_path, $paths, $names); + $this->provider->set_styles($paths); + $this->locator->set_paths($this->provider); + + $new_paths = array(); + foreach ($paths as $path) + { + $new_paths[] = $path . '/template/'; + } + + $this->template->set_style_names($this->names, $new_paths, ($style_directories === array('styles'))); + + return true; } /** @@ -118,6 +152,7 @@ class phpbb_style * @param array or string $paths Array of style paths, relative to current root directory * @param array $names Array of names of templates in inheritance tree order, used by extensions. If empty, $name will be used. * @param string $template_path Path to templates, relative to style directory. False if path should be set to default (templates/). + * @return bool true */ public function set_custom_style($name, $paths, $names = array(), $template_path = false) { @@ -135,18 +170,18 @@ class phpbb_style $this->provider->set_styles($paths); $this->locator->set_paths($this->provider); - $this->template->set_style_names($names); - if ($template_path !== false) { $this->locator->set_template_path($template_path); } - else + + $new_paths = array(); + foreach ($paths as $path) { - $this->locator->set_default_template_path(); + $new_paths[] = $path . '/' . (($template_path !== false) ? $template_path : 'template/'); } - $this->template->cachepath = $this->phpbb_root_path . 'cache/tpl_' . str_replace('_', '-', $name) . '_'; + $this->template->set_style_names($names, $new_paths); return true; } @@ -155,11 +190,14 @@ class phpbb_style * Get location of style directory for specific style_path * * @param string $path Style path, such as "prosilver" + * @param string $style_base_directory The base directory the style is in + * E.g. 'styles', 'ext/foo/bar/styles' + * Default: 'styles' * @return string Path to style directory, relative to current path */ - public function get_style_path($path) + public function get_style_path($path, $style_base_directory = 'styles') { - return $this->phpbb_root_path . 'styles/' . $path; + return $this->phpbb_root_path . trim($style_base_directory, '/') . '/' . $path; } /** diff --git a/phpBB/includes/template/compile.php b/phpBB/includes/template/compile.php deleted file mode 100644 index 76cb3011df..0000000000 --- a/phpBB/includes/template/compile.php +++ /dev/null @@ -1,166 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @copyright (c) 2005 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -/** -* @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 -{ - /** - * Array of parameters to forward to template filter - * - * @var array - */ - private $filter_params; - - /** - * Array of default parameters - * - * @var array - */ - private $default_filter_params; - - /** - * Constructor. - * - * @param bool $allow_php Whether PHP code will be allowed in templates (inline PHP code, PHP tag and INCLUDEPHP tag) - * @param array $style_names Name of style to which the template being compiled belongs and parents in style tree order - * @param phpbb_style_resource_locator $locator Resource locator - * @param string $phpbb_root_path Path to phpBB root directory - * @param phpbb_extension_manager $extension_manager Extension manager to use for finding template fragments in extensions; if null, template events will not be invoked - * @param phpbb_user $user Current user - */ - public function __construct($allow_php, $style_names, $locator, $phpbb_root_path, $extension_manager = null, $user = null) - { - $this->filter_params = $this->default_filter_params = array( - 'allow_php' => $allow_php, - 'style_names' => $style_names, - 'locator' => $locator, - 'phpbb_root_path' => $phpbb_root_path, - 'extension_manager' => $extension_manager, - 'user' => $user, - 'template_compile' => $this, - 'cleanup' => true, - ); - } - - /** - * Set filter parameters - * - * @param array $params Array of parameters (will be merged onto $this->filter_params) - */ - public function set_filter_params($params) - { - $this->filter_params = array_merge( - $this->filter_params, - $params - ); - } - - /** - * Reset filter parameters to their default settings - */ - public function reset_filter_params() - { - $this->filter_params = $this->default_filter_params; - } - - /** - * 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) - { - $lock = new phpbb_lock_flock($compiled_file); - $lock->acquire(); - - $source_handle = @fopen($source_file, 'rb'); - $destination_handle = @fopen($compiled_file, 'wb'); - - if (!$source_handle || !$destination_handle) - { - return false; - } - - $this->compile_stream_to_stream($source_handle, $destination_handle); - - @fclose($source_handle); - @fclose($destination_handle); - - phpbb_chmod($compiled_file, CHMOD_READ | CHMOD_WRITE); - - $lock->release(); - - 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 null - */ - private function compile_stream_to_stream($source_stream, $dest_stream) - { - stream_filter_append($source_stream, 'phpbb_template', null, $this->filter_params); - stream_copy_to_stream($source_stream, $dest_stream); - } -} diff --git a/phpBB/includes/template/context.php b/phpBB/includes/template/context.php index ec09da1cf3..c5ce7422b9 100644 --- a/phpBB/includes/template/context.php +++ b/phpBB/includes/template/context.php @@ -138,7 +138,7 @@ class phpbb_template_context } $s_row_count = isset($str[$blocks[$blockcount]]) ? sizeof($str[$blocks[$blockcount]]) : 0; - $vararray['S_ROW_COUNT'] = $s_row_count; + $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; // Assign S_FIRST_ROW if (!$s_row_count) @@ -146,6 +146,9 @@ class phpbb_template_context $vararray['S_FIRST_ROW'] = true; } + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $blocks[$blockcount]; + // 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; @@ -158,12 +161,18 @@ class phpbb_template_context // We're adding a new iteration to this block with the given // variable assignments. $str[$blocks[$blockcount]][] = $vararray; + + // Set S_NUM_ROWS + foreach ($str[$blocks[$blockcount]] as &$mod_block) + { + $mod_block['S_NUM_ROWS'] = sizeof($str[$blocks[$blockcount]]); + } } else { // Top-level block. $s_row_count = (isset($this->tpldata[$blockname])) ? sizeof($this->tpldata[$blockname]) : 0; - $vararray['S_ROW_COUNT'] = $s_row_count; + $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; // Assign S_FIRST_ROW if (!$s_row_count) @@ -171,6 +180,9 @@ class phpbb_template_context $vararray['S_FIRST_ROW'] = true; } + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $blockname; + // We always assign S_LAST_ROW and remove the entry before $vararray['S_LAST_ROW'] = true; if ($s_row_count > 0) @@ -180,6 +192,12 @@ class phpbb_template_context // Add a new iteration to this block with the variable assignments we were given. $this->tpldata[$blockname][] = $vararray; + + // Set S_NUM_ROWS + foreach ($this->tpldata[$blockname] as &$mod_block) + { + $mod_block['S_NUM_ROWS'] = sizeof($this->tpldata[$blockname]); + } } return true; @@ -298,14 +316,26 @@ class phpbb_template_context $vararray['S_FIRST_ROW'] = true; } + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $blockname; + // Re-position template blocks for ($i = sizeof($block); $i > $key; $i--) { $block[$i] = $block[$i-1]; + + $block[$i]['S_ROW_COUNT'] = $block[$i]['S_ROW_NUM'] = $i; } // Insert vararray at given position $block[$key] = $vararray; + $block[$key]['S_ROW_COUNT'] = $block[$key]['S_ROW_NUM'] = $key; + + // Set S_NUM_ROWS + foreach ($this->tpldata[$blockname] as &$mod_block) + { + $mod_block['S_NUM_ROWS'] = sizeof($this->tpldata[$blockname]); + } return true; } diff --git a/phpBB/includes/template/filter.php b/phpBB/includes/template/filter.php deleted file mode 100644 index 1c0a56c9f5..0000000000 --- a/phpBB/includes/template/filter.php +++ /dev/null @@ -1,1261 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @copyright (c) 2011 phpBB Group, sections (c) 2001 ispi of Lincoln Inc -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ - exit; -} - -/** -* The template filter that does the actual compilation -* -* psoTFX, phpBB Development Team - Completion of file caching, decompilation -* routines and implementation of conditionals/keywords and associated changes -* -* The interface was inspired by PHPLib templates, and the template file (formats are -* quite similar) -* -* The keyword/conditional implementation is currently based on sections of code from -* the Smarty templating engine (c) 2001 ispi of Lincoln, Inc. which is released -* (on its own and in whole) under the LGPL. Section 3 of the LGPL states that any code -* derived from an LGPL application may be relicenced under the GPL, this applies -* to this source -* -* DEFINE directive inspired by a request by Cyberalien -* -* @see template_compile -* @package phpBB3 -*/ -class phpbb_template_filter extends php_user_filter -{ - const REGEX_NS = '[a-z_][a-z_0-9]+'; - - const REGEX_VAR = '[A-Z_][A-Z_0-9]+'; - const REGEX_VAR_SUFFIX = '[A-Z_0-9]+'; - - const REGEX_TAG = '<!-- ([A-Z][A-Z_0-9]+)(?: (.*?) ?)?-->'; - - const REGEX_TOKENS = '~<!-- ([A-Z][A-Z_0-9]+)(?: (.*?) ?)?-->|{((?:[a-z_][a-z_0-9]+\.)*\\$?[A-Z][A-Z_0-9]+)}~'; - - /** - * @var array - */ - private $block_names = array(); - - /** - * @var array - */ - private $block_else_level = array(); - - /** - * @var string - */ - private $chunk; - - /** - * @var bool - */ - private $in_php; - - /** - * Whether inline PHP code, <!-- PHP --> and <!-- INCLUDEPHP --> tags - * are allowed. If this is false all PHP code will be silently - * removed from the template during compilation. - * - * @var bool - */ - private $allow_php; - - /** - * Whether cleanup will be performed on resulting code, see compile() - * (Preserve whitespace) - * - * @var bool - */ - private $cleanup = true; - - /** - * Resource locator. - * - * @var phpbb_template_locator - */ - private $locator; - - /** - * @var string phpBB root path - */ - private $phpbb_root_path; - - /** - * Name of the style that the template being compiled and/or rendered - * belongs to, and its parents, in inheritance tree order. - * - * Used to invoke style-specific template events. - * - * @var array - */ - private $style_names; - - /** - * Extension manager. - * - * @var phpbb_extension_manager - */ - private $extension_manager; - - /** - * Current user - * - * @var phpbb_user - */ - private $user; - - /** - * Template compiler. - * - * @var phpbb_template_compile - */ - private $template_compile; - - /** - * 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, style_names, root directory and locator from params, - * which are passed to stream_filter_append. - * - * @return boolean Returns true - */ - public function onCreate() - { - $this->chunk = ''; - $this->in_php = false; - $this->allow_php = $this->params['allow_php']; - $this->locator = $this->params['locator']; - $this->phpbb_root_path = $this->params['phpbb_root_path']; - $this->style_names = $this->params['style_names']; - $this->extension_manager = $this->params['extension_manager']; - $this->cleanup = $this->params['cleanup']; - if (isset($this->params['user'])) - { - $this->user = $this->params['user']; - } - $this->template_compile = $this->params['template_compile']; - 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); - } - - if ($this->cleanup) - { - /* - - Preserve whitespace. - PHP removes a newline after the closing tag (if it's there). - This is by design: - - http://www.php.net/manual/en/language.basic-syntax.phpmode.php - http://www.php.net/manual/en/language.basic-syntax.instruction-separation.php - - - 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 - template 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 - are not the only statement on a line. It will NOT preserve - newlines at the end of statements in the above example. - 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 compiled template code. - * - * Compiled template code is an HTML stream with embedded PHP. - * - * @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 'ENDDEFINE': - return '<?php ' . $this->compile_tag_enddefine() . ' ?>'; - 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 'INCLUDEJS': - return '<?php ' . $this->compile_tag_include_js($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; - - case 'EVENT': - return '<?php ' . $this->compile_tag_event($matches[2]) . '?>'; - break; - - default: - return $matches[0]; - break; - - } - return ''; - } - - /** - * Convert template variables into PHP varrefs - * - * @param string $text_blocks Variable reference in source template - * @param bool $is_expr Returns whether the source was an expression type variable (i.e. S_FIRST_ROW) - * @return string PHP variable name - */ - private function get_varref($text_blocks, &$is_expr) - { - // change template varrefs into PHP varrefs - $varrefs = array(); - - // This one will handle varrefs WITH namespaces - preg_match_all('#\{((?:' . self::REGEX_NS . '\.)+)(\$)?(' . self::REGEX_VAR . ')\}#', $text_blocks, $varrefs, PREG_SET_ORDER); - - foreach ($varrefs as $var_val) - { - $namespace = $var_val[1]; - $varname = $var_val[3]; - $new = $this->generate_block_varref($namespace, $varname, $is_expr, $var_val[2]); - - $text_blocks = str_replace($var_val[0], $new, $text_blocks); - } - - // Language variables cannot be reduced to a single varref, so they must be skipped - // These two replacements would break language variables, so we can only run them on non-language types - if (strpos($text_blocks, '{L_') === false && strpos($text_blocks, '{LA_') === false) - { - // This will handle the remaining root-level varrefs - $text_blocks = preg_replace('#\{(' . self::REGEX_VAR . ')\}#', "\$_rootref['\\1']", $text_blocks); - $text_blocks = preg_replace('#\{\$(' . self::REGEX_VAR . ')\}#', "\$_tpldata['DEFINE']['.']['\\1']", $text_blocks); - } - - return $text_blocks; - } - - /** - * Parse paths of the form {FOO}/a/{BAR}/b - * - * Note: this method assumes at least one variable in the path, this should - * be checked before this method is called. - * - * @param string $path The path to parse - * @param string $include_type The type of template function to call - * @return string An appropriately formatted string to include in the - * template or an empty string if an expression like S_FIRST_ROW was - * incorrectly used - */ - private function parse_dynamic_path($path, $include_type) - { - $matches = array(); - $replace = array(); - $is_expr = true; - - preg_match_all('#\{((?:' . self::REGEX_NS . '\.)*)(\$)?(' . self::REGEX_VAR . ')\}#', $path, $matches); - foreach ($matches[0] as $var_str) - { - $tmp_is_expr = false; - $var = $this->get_varref($var_str, $tmp_is_expr); - $is_expr = $is_expr && $tmp_is_expr; - $replace[] = "' . $var . '"; - } - - if (!$is_expr) - { - return " \$_template->$include_type('" . str_replace($matches[0], $replace, $path) . "', true);"; - } - else - { - return ''; - } - } - - /** - * Compile variables - * - * @param string $text_blocks Variable reference in source template - * @return string compiled template code - */ - private function compile_var_tags(&$text_blocks) - { - $is_expr = null; - $text_blocks = $this->get_varref($text_blocks, $is_expr); - $lang_replaced = $this->compile_language_tags($text_blocks); - - if(!$lang_replaced) - { - $text_blocks = '<?php echo ' . ($is_expr ? "$text_blocks" : "(isset($text_blocks)) ? $text_blocks : ''") . '; /**/?>'; - } - - return $text_blocks; - } - - /** - * Handles special language tags L_ and LA_ - * - * @param string $text_blocks Variable reference in source template - * @return bool Whether a replacement occurred or not - */ - private function compile_language_tags(&$text_blocks) - { - $replacements = 0; - - // transform vars prefixed by L_ into their language variable pendant if nothing is set within the tpldata array - if (strpos($text_blocks, '{L_') !== false) - { - $text_blocks = preg_replace('#\{L_(' . self::REGEX_VAR_SUFFIX . ')\}#', "<?php echo ((isset(\$_rootref['L_\\1'])) ? \$_rootref['L_\\1'] : ((isset(\$_lang['\\1'])) ? \$_lang['\\1'] : '{ \\1 }')); /**/?>", $text_blocks, -1, $replacements); - return (bool) $replacements; - } - - // Handle addslashed language variables prefixed with LA_ - // If a template variable already exist, it will be used in favor of it... - if (strpos($text_blocks, '{LA_') !== false) - { - $text_blocks = preg_replace('#\{LA_(' . self::REGEX_VAR_SUFFIX . '+)\}#', "<?php echo ((isset(\$_rootref['LA_\\1'])) ? \$_rootref['LA_\\1'] : ((isset(\$_rootref['L_\\1'])) ? addslashes(\$_rootref['L_\\1']) : ((isset(\$_lang['\\1'])) ? addslashes(\$_lang['\\1']) : '{ \\1 }'))); /**/?>", $text_blocks, -1, $replacements); - return (bool) $replacements; - } - - return false; - } - - /** - * Compile blocks - * - * @param string $tag_args Block contents in source template - * @return string compiled template code - */ - private function compile_tag_block($tag_args) - { - $no_nesting = false; - - // Is the designer wanting to call another loop in a loop? - // <!-- BEGIN loop --> - // <!-- BEGIN !loop2 --> - // <!-- END !loop2 --> - // <!-- END loop --> - // 'loop2' is actually on the same nesting level as 'loop' you assign - // variables to it with template->assign_block_vars('loop2', array(...)) - if (strpos($tag_args, '!') === 0) - { - // Count the number if ! occurrences (not allowed in vars) - $no_nesting = substr_count($tag_args, '!'); - $tag_args = substr($tag_args, $no_nesting); - } - - // Allow for control of looping (indexes start from zero): - // foo(2) : Will start the loop on the 3rd entry - // foo(-2) : Will start the loop two entries from the end - // foo(3,4) : Will start the loop on the fourth entry and end it on the fifth - // foo(3,-4) : Will start the loop on the fourth entry and end it four from last - $match = array(); - - if (preg_match('#^([^()]*)\(([\-\d]+)(?:,([\-\d]+))?\)$#', $tag_args, $match)) - { - $tag_args = $match[1]; - - if ($match[2] < 0) - { - $loop_start = '($_' . $tag_args . '_count ' . $match[2] . ' < 0 ? 0 : $_' . $tag_args . '_count ' . $match[2] . ')'; - } - else - { - $loop_start = '($_' . $tag_args . '_count < ' . $match[2] . ' ? $_' . $tag_args . '_count : ' . $match[2] . ')'; - } - - if (!isset($match[3]) || strlen($match[3]) < 1 || $match[3] == -1) - { - $loop_end = '$_' . $tag_args . '_count'; - } - else if ($match[3] >= 0) - { - $loop_end = '(' . ($match[3] + 1) . ' > $_' . $tag_args . '_count ? $_' . $tag_args . '_count : ' . ($match[3] + 1) . ')'; - } - else //if ($match[3] < -1) - { - $loop_end = '$_' . $tag_args . '_count' . ($match[3] + 1); - } - } - else - { - $loop_start = 0; - $loop_end = '$_' . $tag_args . '_count'; - } - - $tag_template_php = ''; - array_push($this->block_names, $tag_args); - - if ($no_nesting !== false) - { - // We need to implode $no_nesting times from the end... - $block = array_slice($this->block_names, -$no_nesting); - } - else - { - $block = $this->block_names; - } - - if (sizeof($block) < 2) - { - // Block is not nested. - $tag_template_php = '$_' . $tag_args . "_count = (isset(\$_tpldata['$tag_args'])) ? sizeof(\$_tpldata['$tag_args']) : 0;"; - $varref = "\$_tpldata['$tag_args']"; - } - else - { - // This block is nested. - // Generate a namespace string for this block. - $namespace = implode('.', $block); - - // Get a reference to the data array for this block that depends on the - // current indices of all parent blocks. - $varref = $this->generate_block_data_ref($namespace, false); - - // Create the for loop code to iterate over this block. - $tag_template_php = '$_' . $tag_args . '_count = (isset(' . $varref . ')) ? sizeof(' . $varref . ') : 0;'; - } - - $tag_template_php .= 'if ($_' . $tag_args . '_count) {'; - - /** - * The following uses foreach for iteration instead of a for loop, foreach is faster but requires PHP to make a copy of the contents of the array which uses more memory - * <code> - * if (!$offset) - * { - * $tag_template_php .= 'foreach (' . $varref . ' as $_' . $tag_args . '_i => $_' . $tag_args . '_val){'; - * } - * </code> - */ - - $tag_template_php .= 'for ($_' . $tag_args . '_i = ' . $loop_start . '; $_' . $tag_args . '_i < ' . $loop_end . '; ++$_' . $tag_args . '_i){'; - $tag_template_php .= '$_' . $tag_args . '_val = &' . $varref . '[$_' . $tag_args . '_i];'; - - return $tag_template_php; - } - - /** - * Compile a general expression - much of this is from Smarty with - * some adaptions for our block level methods - * - * @param string $tag_args Expression (tag arguments) in source template - * @return string compiled template code - */ - private function compile_expression($tag_args) - { - $match = array(); - preg_match_all('/(?: - "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" | - \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' | - [(),] | - [^\s(),]+)/x', $tag_args, $match); - - $tokens = $match[0]; - $is_arg_stack = array(); - - for ($i = 0, $size = sizeof($tokens); $i < $size; $i++) - { - $token = &$tokens[$i]; - - switch ($token) - { - case '!==': - case '===': - case '<<': - case '>>': - case '|': - case '^': - case '&': - case '~': - case ')': - case ',': - case '+': - case '-': - case '*': - case '/': - case '@': - break; - - case '==': - case 'eq': - $token = '=='; - break; - - case '!=': - case '<>': - case 'ne': - case 'neq': - $token = '!='; - break; - - case '<': - case 'lt': - $token = '<'; - break; - - case '<=': - case 'le': - case 'lte': - $token = '<='; - break; - - case '>': - case 'gt': - $token = '>'; - break; - - case '>=': - case 'ge': - case 'gte': - $token = '>='; - break; - - case '&&': - case 'and': - $token = '&&'; - break; - - case '||': - case 'or': - $token = '||'; - break; - - case '!': - case 'not': - $token = '!'; - break; - - case '%': - case 'mod': - $token = '%'; - break; - - case '(': - array_push($is_arg_stack, $i); - break; - - case 'is': - $is_arg_start = ($tokens[$i-1] == ')') ? array_pop($is_arg_stack) : $i-1; - $is_arg = implode(' ', array_slice($tokens, $is_arg_start, $i - $is_arg_start)); - - $new_tokens = $this->_parse_is_expr($is_arg, array_slice($tokens, $i+1)); - - array_splice($tokens, $is_arg_start, sizeof($tokens), $new_tokens); - - $i = $is_arg_start; - - // no break - - default: - $varrefs = array(); - if (preg_match('#^((?:' . self::REGEX_NS . '\.)+)?(\$)?(?=[A-Z])([A-Z0-9\-_]+)#s', $token, $varrefs)) - { - if (!empty($varrefs[1])) - { - $namespace = substr($varrefs[1], 0, -1); - $dot_pos = strrchr($namespace, '.'); - if ($dot_pos !== false) - { - $namespace = substr($dot_pos, 1); - } - - // S_ROW_COUNT is deceptive, it returns the current row number not the number of rows - // hence S_ROW_COUNT is deprecated in favour of S_ROW_NUM - switch ($varrefs[3]) - { - case 'S_ROW_NUM': - case 'S_ROW_COUNT': - $token = "\$_${namespace}_i"; - break; - - case 'S_NUM_ROWS': - $token = "\$_${namespace}_count"; - break; - - case 'S_FIRST_ROW': - $token = "(\$_${namespace}_i == 0)"; - break; - - case 'S_LAST_ROW': - $token = "(\$_${namespace}_i == \$_${namespace}_count - 1)"; - break; - - case 'S_BLOCK_NAME': - $token = "'$namespace'"; - break; - - default: - $token = $this->generate_block_data_ref(substr($varrefs[1], 0, -1), true, $varrefs[2]) . '[\'' . $varrefs[3] . '\']'; - $token = '(isset(' . $token . ') ? ' . $token . ' : null)'; - break; - } - } - else - { - $token = ($varrefs[2]) ? '$_tpldata[\'DEFINE\'][\'.\'][\'' . $varrefs[3] . '\']' : '$_rootref[\'' . $varrefs[3] . '\']'; - $token = '(isset(' . $token . ') ? ' . $token . ' : null)'; - } - - } - else if (preg_match('#^\.((?:' . self::REGEX_NS . '\.?)+)$#s', $token, $varrefs)) - { - // Allow checking if loops are set with .loopname - // It is also possible to check the loop count by doing <!-- IF .loopname > 1 --> for example - $blocks = explode('.', $varrefs[1]); - - // If the block is nested, we have a reference that we can grab. - // If the block is not nested, we just go and grab the block from _tpldata - if (sizeof($blocks) > 1) - { - $block = array_pop($blocks); - $namespace = implode('.', $blocks); - $varref = $this->generate_block_data_ref($namespace, true); - - // Add the block reference for the last child. - $varref .= "['" . $block . "']"; - } - else - { - $varref = '$_tpldata'; - - // Add the block reference for the last child. - $varref .= "['" . $blocks[0] . "']"; - } - $token = "(isset($varref) ? sizeof($varref) : 0)"; - } - - break; - } - } - - return $tokens; - } - - /** - * Compile IF tags - * - * @param string $tag_args Expression given with IF in source template - * @param bool $elseif True if compiling an IF tag, false if compiling an ELSEIF tag - * @return string compiled template code - */ - private function compile_tag_if($tag_args, $elseif) - { - $tokens = $this->compile_expression($tag_args); - - $tpl = ($elseif) ? '} else if (' : 'if ('; - - $tpl .= implode(' ', $tokens); - $tpl .= ') { '; - - return $tpl; - } - - /** - * Compile DEFINE tags - * - * @param string $tag_args Expression given with DEFINE in source template - * @param bool $op True if compiling a DEFINE tag, false if compiling an UNDEFINE tag - * @return string compiled template code - */ - private function compile_tag_define($tag_args, $op) - { - $match = array(); - preg_match('#^((?:' . self::REGEX_NS . '\.)+)?\$(?=[A-Z])([A-Z0-9_\-]*)(?: = (.*?))?$#', $tag_args, $match); - - if (!empty($match[2]) && !isset($match[3]) && $op) - { - // DEFINE tag with ENDDEFINE - $array = "\$_tpldata['DEFINE']['.vars']"; - $code = 'ob_start(); '; - $code .= "if (!isset($array)) { $array = array(); } "; - $code .= "{$array}[] = '{$match[2]}'"; - return $code; - } - - 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] . '\']') . ');'; - } - - /* - * Define tags that contain template variables (enclosed in curly brackets) - * need to be treated differently. - */ - if (substr($match[3], 1, 1) == '{' && substr($match[3], -2, 1) == '}') - { - $parsed_statement = implode(' ', $this->compile_expression(substr($match[3], 2, -2))); - } - else - { - $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 ENDDEFINE tag - * - * @return string compiled template code - */ - private function compile_tag_enddefine() - { - $array = "\$_tpldata['DEFINE']['.vars']"; - $code = "if (!isset($array) || !sizeof($array)) { trigger_error('ENDDEFINE tag without DEFINE in ' . basename(__FILE__), E_USER_ERROR); }"; - $code .= "\$define_var = array_pop($array); "; - $code .= "\$_tpldata['DEFINE']['.'][\$define_var] = ob_get_clean();"; - return $code; - } - - /** - * Compile INCLUDE tag - * - * @param string $tag_args Expression given with INCLUDE in source template - * @return string compiled template code - */ - private function compile_tag_include($tag_args) - { - // Process dynamic includes - if (strpos($tag_args, '{') !== false) - { - return $this->parse_dynamic_path($tag_args, '_tpl_include'); - } - - 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) - { - if (strpos($tag_args, '{') !== false) - { - return $this->parse_dynamic_path($tag_args, '_php_include'); - } - - return "\$_template->_php_include('$tag_args');"; - } - - /** - * Compile EVENT tag. - * - * $tag_args should be a single string identifying the event. - * The event name can contain letters, numbers and underscores only. - * If an invalid event name is specified, an E_USER_ERROR will be - * triggered. - * - * Event tags are only functional when the template engine has - * an instance of the extension manager. Extension manager would - * be called upon to find all extensions listening for the specified - * event, and to obtain additional template fragments. All such - * template fragments will be compiled and included in the generated - * compiled template code for the current template being compiled. - * - * The above means that whenever an extension is enabled or disabled, - * template cache should be cleared in order to update the compiled - * template code for the active set of template event listeners. - * - * This also means that extensions cannot return different template - * fragments at different times. Once templates are compiled, changing - * such template fragments would have no effect. - * - * @param string $tag_args EVENT tag arguments, as a string - for EVENT this is the event name - * @return string compiled template code - */ - private function compile_tag_event($tag_args) - { - if (!preg_match('/^\w+$/', $tag_args)) - { - // The event location is improperly formatted, - if ($this->user) - { - trigger_error($this->user->lang('ERR_TEMPLATE_EVENT_LOCATION', $tag_args), E_USER_ERROR); - } - else - { - trigger_error(sprintf('The specified template event location <em>[%s]</em> is improperly formatted.', $tag_args), E_USER_ERROR); - } - } - $location = $tag_args; - - if ($this->extension_manager) - { - $finder = $this->extension_manager->get_finder(); - - $files = $finder - ->extension_prefix($location) - ->extension_suffix('.html') - ->extension_directory("/styles/all/template") - ->get_files(); - - foreach ($this->style_names as $style_name) - { - $more_files = $finder - ->extension_prefix($location) - ->extension_suffix('.html') - ->extension_directory("/styles/" . $style_name . "/template") - ->get_files(); - if (!empty($more_files)) - { - $files = array_merge($files, $more_files); - break; - } - } - - $all_compiled = ''; - foreach ($files as $file) - { - $this->template_compile->set_filter_params(array( - 'cleanup' => false, - )); - - $compiled = $this->template_compile->compile_file($file); - - $this->template_compile->reset_filter_params(); - - if ($compiled === false) - { - if ($this->user) - { - trigger_error($this->user->lang('ERR_TEMPLATE_COMPILATION', phpbb_filter_root_path($file)), E_USER_ERROR); - } - else - { - trigger_error(sprintf('The file could not be compiled: %s', phpbb_filter_root_path($file)), E_USER_ERROR); - } - } - - $all_compiled .= $compiled; - } - // Need spaces inside php tags as php cannot grok - // < ?php? > sans the spaces - return ' ?' . '>' . $all_compiled . '<?php '; - } - } - - /** - * 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; - } - - /** - * Compile INCLUDEJS tag - * - * @param string $tag_args Expression given with INCLUDEJS in source template - * @return string compiled template code - */ - private function compile_tag_include_js($tag_args) - { - // Process dynamic includes - if (strpos($tag_args, '{') !== false) - { - return $this->parse_dynamic_path($tag_args, '_js_include'); - } - - // Locate file - $filename = $this->locator->get_first_file_location(array($tag_args), false, true); - - if ($filename === false) - { - // File does not exist, find it during run time - return ' $_template->_js_include(\'' . addslashes($tag_args) . '\', true); '; - } - - if (substr($filename, 0, strlen($this->phpbb_root_path)) != $this->phpbb_root_path) - { - // Absolute path, include as is - return ' $_template->_js_include(\'' . addslashes($filename) . '\', false, false); '; - } - - // Relative path, remove root path from it - $filename = substr($filename, strlen($this->phpbb_root_path)); - return ' $_template->_js_include(\'' . addslashes($filename) . '\', false, true); '; - } - - /** - * Generates a reference to the given variable inside the given (possibly nested) - * block namespace. This is a string of the form: - * ' . $_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . ' - * It's ready to be inserted into an "echo" line in one of the templates. - * - * @param string $namespace Namespace to access (expects a trailing "." on the namespace) - * @param string $varname Variable name to use - * @param bool $expr Returns whether the source was an expression type - * @param bool $defop If true this is a variable created with the DEFINE construct, otherwise template variable - * @return string Code to access variable or echo it if $echo is true - */ - private function generate_block_varref($namespace, $varname, &$expr, $defop = false) - { - // Strip the trailing period. - $namespace = substr($namespace, 0, -1); - - if (($pos = strrpos($namespace, '.')) !== false) - { - $local_namespace = substr($namespace, $pos + 1); - } - else - { - $local_namespace = $namespace; - } - - $expr = true; - - // S_ROW_COUNT is deceptive, it returns the current row number now the number of rows - // hence S_ROW_COUNT is deprecated in favour of S_ROW_NUM - switch ($varname) - { - case 'S_ROW_NUM': - case 'S_ROW_COUNT': - $varref = "\$_${local_namespace}_i"; - break; - - case 'S_NUM_ROWS': - $varref = "\$_${local_namespace}_count"; - break; - - case 'S_FIRST_ROW': - $varref = "(\$_${local_namespace}_i == 0)"; - break; - - case 'S_LAST_ROW': - $varref = "(\$_${local_namespace}_i == \$_${local_namespace}_count - 1)"; - break; - - case 'S_BLOCK_NAME': - $varref = "'$local_namespace'"; - break; - - default: - // Get a reference to the data block for this namespace. - $varref = $this->generate_block_data_ref($namespace, true, $defop); - // Prepend the necessary code to stick this in an echo line. - - // Append the variable reference. - $varref .= "['$varname']"; - - $expr = false; - break; - } - // @todo Test the !$expr more - - return $varref; - } - - /** - * Generates a reference to the array of data values for the given - * (possibly nested) block namespace. This is a string of the form: - * $_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN'] - * - * @param string $blockname Block to access (does not expect a trailing "." on the blockname) - * @param bool $include_last_iterator If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above. - * @param bool $defop If true this is a variable created with the DEFINE construct, otherwise template variable - * @return string Code to access variable - */ - private function generate_block_data_ref($blockname, $include_last_iterator, $defop = false) - { - // Get an array of the blocks involved. - $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; - - // DEFINE is not an element of any referenced variable, we must use _tpldata to access it - if ($defop) - { - $varref = '$_tpldata[\'DEFINE\']'; - // Build up the string with everything but the last child. - for ($i = 0; $i < $blockcount; $i++) - { - $varref .= "['" . $blocks[$i] . "'][\$_" . $blocks[$i] . '_i]'; - } - // Add the block reference for the last child. - $varref .= "['" . $blocks[$blockcount] . "']"; - // Add the iterator for the last child if requried. - if ($include_last_iterator) - { - $varref .= '[$_' . $blocks[$blockcount] . '_i]'; - } - return $varref; - } - else if ($include_last_iterator) - { - return '$_'. $blocks[$blockcount] . '_val'; - } - else - { - return '$_'. $blocks[$blockcount - 1] . '_val[\''. $blocks[$blockcount]. '\']'; - } - } -} diff --git a/phpBB/includes/template/renderer.php b/phpBB/includes/template/renderer.php deleted file mode 100644 index 30e234a733..0000000000 --- a/phpBB/includes/template/renderer.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @copyright (c) 2011 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ - exit; -} - -/** -* Template renderer interface. -* -* Objects implementing this interface encapsulate a means of displaying -* a template. -* -* @package phpBB3 -*/ -interface phpbb_template_renderer -{ - /** - * Displays the template managed by this renderer. - * - * @param phpbb_template_context $context Template context to use - * @param array $lang Language entries to use - */ - public function render($context, $lang); -} diff --git a/phpBB/includes/template/renderer_eval.php b/phpBB/includes/template/renderer_eval.php deleted file mode 100644 index f8e4cb7b10..0000000000 --- a/phpBB/includes/template/renderer_eval.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @copyright (c) 2011 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ - exit; -} - -/** -* Template renderer that stores compiled template's php code and -* displays it via eval. -* -* @package phpBB3 -*/ -class phpbb_template_renderer_eval implements phpbb_template_renderer -{ - /** - * Template code to be eval'ed. - */ - private $code; - - /** - * Constructor. Stores provided code for future evaluation. - * Template includes are delegated to template object $template. - * - * @param string $code php code of the template - * @param phpbb_template $template template object - */ - public function __construct($code, $template) - { - $this->code = $code; - $this->template = $template; - } - - /** - * Displays the template managed by this renderer by eval'ing php code - * of the template. - * - * @param phpbb_template_context $context Template context to use - * @param array $lang Language entries to use - */ - public function render($context, $lang) - { - $_template = $this->template; - $_tpldata = &$context->get_data_ref(); - $_rootref = &$context->get_root_ref(); - $_lang = $lang; - - eval(' ?>' . $this->code . '<?php '); - } -} diff --git a/phpBB/includes/template/renderer_include.php b/phpBB/includes/template/renderer_include.php deleted file mode 100644 index f5c9026abf..0000000000 --- a/phpBB/includes/template/renderer_include.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** -* -* @package phpBB3 -* @copyright (c) 2011 phpBB Group -* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ - exit; -} - - -/** -* Template renderer that stores path to php file with template code -* and displays it by including the file. -* -* @package phpBB3 -*/ -class phpbb_template_renderer_include implements phpbb_template_renderer -{ - /** - * Template path to be included. - */ - private $path; - - /** - * Constructor. Stores path to the template for future inclusion. - * Template includes are delegated to template object $template. - * - * @param string $path path to the template - */ - public function __construct($path, $template) - { - $this->path = $path; - $this->template = $template; - } - - /** - * Displays the template managed by this renderer by including - * the php file containing the template. - * - * @param phpbb_template_context $context Template context to use - * @param array $lang Language entries to use - */ - public function render($context, $lang) - { - $_template = $this->template; - $_tpldata = &$context->get_data_ref(); - $_rootref = &$context->get_root_ref(); - $_lang = $lang; - - include($this->path); - } -} diff --git a/phpBB/includes/template/template.php b/phpBB/includes/template/template.php index bbec768613..89a01e924d 100644 --- a/phpBB/includes/template/template.php +++ b/phpBB/includes/template/template.php @@ -2,7 +2,7 @@ /** * * @package phpBB3 -* @copyright (c) 2005 phpBB Group, sections (c) 2001 ispi of Lincoln Inc +* @copyright (c) 2013 phpBB Group * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 * */ @@ -15,143 +15,48 @@ 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 +interface phpbb_template { - /** - * Template context. - * Stores template data used during template rendering. - * @var phpbb_template_context - */ - private $context; - - /** - * Path of the cache directory for the template - * @var string - */ - public $cachepath = ''; - - /** - * phpBB root path - * @var string - */ - private $phpbb_root_path; - - /** - * PHP file extension - * @var string - */ - private $php_ext; - - /** - * phpBB config instance - * @var phpbb_config - */ - private $config; - - /** - * Current user - * @var phpbb_user - */ - private $user; - - /** - * Template locator - * @var phpbb_template_locator - */ - private $locator; - - /** - * Extension manager. - * - * @var phpbb_extension_manager - */ - private $extension_manager; - - /** - * Name of the style that the template being compiled and/or rendered - * belongs to, and its parents, in inheritance tree order. - * - * Used to invoke style-specific template events. - * - * @var array - */ - private $style_names; /** - * Constructor. + * Clear the cache * - * @param string $phpbb_root_path phpBB root path - * @param user $user current user - * @param phpbb_template_locator $locator template locator - * @param phpbb_template_context $context template context - * @param phpbb_extension_manager $extension_manager extension manager, if null then template events will not be invoked + * @return phpbb_template */ - public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_locator $locator, phpbb_template_context $context, phpbb_extension_manager $extension_manager = null) - { - $this->phpbb_root_path = $phpbb_root_path; - $this->php_ext = $php_ext; - $this->config = $config; - $this->user = $user; - $this->locator = $locator; - $this->context = $context; - $this->extension_manager = $extension_manager; - } + public function clear_cache(); /** * Sets the template filenames for handles. * * @param array $filename_array Should be a hash of handle => filename pairs. + * @return phpbb_template $this */ - public function set_filenames(array $filename_array) - { - $this->locator->set_filenames($filename_array); - - return true; - } + public function set_filenames(array $filename_array); /** - * Sets the style names corresponding to style hierarchy being compiled + * Sets the style names/paths corresponding to style hierarchy being compiled * and/or rendered. * * @param array $style_names List of style names in inheritance tree order - * @return null + * @param array $style_paths List of style paths in inheritance tree order + * @return phpbb_template $this */ - public function set_style_names(array $style_names) - { - $this->style_names = $style_names; - } + public function set_style_names(array $style_names, array $style_paths); /** * Clears all variables and blocks assigned to this template. + * + * @return phpbb_template $this */ - public function destroy() - { - $this->context->clear(); - } + public function destroy(); /** * Reset/empty complete block * * @param string $blockname Name of block to destroy + * @return phpbb_template $this */ - public function destroy_block_vars($blockname) - { - $this->context->destroy_block_vars($blockname); - } + public function destroy_block_vars($blockname); /** * Display a template for provided handle. @@ -161,81 +66,9 @@ class phpbb_template * 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, __FUNCTION__); - 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. - * @param string $method Method name of the caller. + * @return phpbb_template $this */ - private function call_hook($handle, $method) - { - global $phpbb_hook; - - if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) - { - if ($phpbb_hook->hook_return(array(__CLASS__, $method))) - { - $result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); - 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; - } + public function display($handle); /** * Display the handle and assign the output to a template variable @@ -244,118 +77,17 @@ class phpbb_template * @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 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 phpbb_template_compile is used to compile template source - */ - private function _tpl_load($handle) - { - $output_file = $this->_compiled_file_for_handle($handle); - - $recompile = defined('DEBUG') || - !file_exists($output_file) || - @filesize($output_file) === 0; - - if ($recompile || $this->config['load_tplcompile']) - { - // Set only if a recompile or an mtime check are required. - $source_file = $this->locator->get_source_file_for_handle($handle); - - if (!$recompile && @filemtime($output_file) < @filemtime($source_file)) - { - $recompile = true; - } - } - - // Recompile page if the original template is newer, otherwise load the compiled version - if (!$recompile) - { - return new phpbb_template_renderer_include($output_file, $this); - } - - $compile = new phpbb_template_compile($this->config['tpl_allow_php'], $this->style_names, $this->locator, $this->phpbb_root_path, $this->extension_manager, $this->user); - - 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 + * @return phpbb_template|string if $return_content is true return string of the compiled handle, otherwise return $this */ - 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->php_ext; - return $compiled_file; - } + public function assign_display($handle, $template_var = '', $return_content = true); /** * Assign key variable pairs from an array * * @param array $vararray A hash of variable name => value pairs + * @return phpbb_template $this */ - public function assign_vars(array $vararray) - { - foreach ($vararray as $key => $val) - { - $this->assign_var($key, $val); - } - } + public function assign_vars(array $vararray); /** * Assign a single scalar value to a single key. @@ -364,11 +96,9 @@ class phpbb_template * * @param string $varname Variable name * @param string $varval Value to assign to variable + * @return phpbb_template $this */ - public function assign_var($varname, $varval) - { - $this->context->assign_var($varname, $varval); - } + public function assign_var($varname, $varval); /** * Append text to the string value stored in a key. @@ -377,24 +107,18 @@ class phpbb_template * * @param string $varname Variable name * @param string $varval Value to append to variable + * @return phpbb_template $this */ - public function append_var($varname, $varval) - { - $this->context->append_var($varname, $varval); - } + public function append_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 + * @return phpbb_template $this */ - public function assign_block_vars($blockname, array $vararray) - { - return $this->context->assign_block_vars($blockname, $vararray); - } + public function assign_block_vars($blockname, array $vararray); - // Docstring is copied from phpbb_template_context method with the same name. /** * Change already assigned key variable pair (one-dimensional - single loop entry) * @@ -422,94 +146,12 @@ class phpbb_template * * @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"; - } - } + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert'); /** - * 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. + * Get path to template for handle (required for BBCode parser) * - * 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 + * @return string */ - 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); - } - - /** - * Include JS file - * - * @param string $file file name - * @param bool $locate True if file needs to be located - * @param bool $relative True if path is relative to phpBB root directory. Ignored if $locate == true - */ - public function _js_include($file, $locate = false, $relative = false) - { - // Locate file - if ($locate) - { - $located = $this->locator->get_first_file_location(array($file), false, true); - if ($located) - { - $file = $located; - } - } - else if ($relative) - { - $file = $this->phpbb_root_path . $file; - } - - $file .= (strpos($file, '?') === false) ? '?' : '&'; - $file .= 'assets_version=' . $this->config['assets_version']; - - // Add HTML code - $code = '<script src="' . htmlspecialchars($file) . '"></script>'; - $this->context->append_var('SCRIPTS', $code); - } + public function get_source_file_for_handle($handle); } diff --git a/phpBB/includes/template/twig/definition.php b/phpBB/includes/template/twig/definition.php new file mode 100644 index 0000000000..6557b209eb --- /dev/null +++ b/phpBB/includes/template/twig/definition.php @@ -0,0 +1,69 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* This class holds all DEFINE variables from the current page load +*/ +class phpbb_template_twig_definition +{ + /** @var array **/ + protected $definitions = array(); + + /** + * Get a DEFINE'd variable + * + * @param string $name + * @return mixed Null if not found + */ + public function __call($name, $arguments) + { + return (isset($this->definitions[$name])) ? $this->definitions[$name] : null; + } + + /** + * DEFINE a variable + * + * @param string $name + * @param mixed $value + * @return phpbb_template_twig_definition + */ + public function set($name, $value) + { + $this->definitions[$name] = $value; + + return $this; + } + + /** + * Append to a variable + * + * @param string $name + * @param string $value + * @return phpbb_template_twig_definition + */ + public function append($name, $value) + { + if (!isset($this->definitions[$name])) + { + $this->definitions[$name] = ''; + } + + $this->definitions[$name] .= $value; + + return $this; + } +} diff --git a/phpBB/includes/template/twig/environment.php b/phpBB/includes/template/twig/environment.php new file mode 100644 index 0000000000..b60cd72325 --- /dev/null +++ b/phpBB/includes/template/twig/environment.php @@ -0,0 +1,140 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class phpbb_template_twig_environment extends Twig_Environment +{ + /** @var array */ + protected $phpbb_extensions; + + /** @var phpbb_config */ + protected $phpbb_config; + + /** @var string */ + protected $phpbb_root_path; + + /** @var array **/ + protected $namespace_look_up_order = array('__main__'); + + /** + * Constructor + * + * @param phpbb_config $phpbb_config + * @param array $phpbb_extensions Array of enabled extensions (name => path) + * @param string $phpbb_root_path + * @param Twig_LoaderInterface $loader + * @param array $options Array of options to pass to Twig + */ + public function __construct($phpbb_config, $phpbb_extensions, $phpbb_root_path, Twig_LoaderInterface $loader = null, $options = array()) + { + $this->phpbb_config = $phpbb_config; + $this->phpbb_extensions = $phpbb_extensions; + $this->phpbb_root_path = $phpbb_root_path; + + return parent::__construct($loader, $options); + } + + /** + * Get the list of enabled phpBB extensions + * + * Used in EVENT node + * + * @return array + */ + public function get_phpbb_extensions() + { + return $this->phpbb_extensions; + } + + /** + * Get phpBB config + * + * @return phpbb_config + */ + public function get_phpbb_config() + { + return $this->phpbb_config; + } + + /** + * Get the phpBB root path + * + * @return string + */ + public function get_phpbb_root_path() + { + return $this->phpbb_root_path; + } + + /** + * Get the namespace look up order + * + * @return array + */ + public function getNamespaceLookUpOrder() + { + return $this->namespace_look_up_order; + } + + /** + * Set the namespace look up order to load templates from + * + * @param array $namespace + * @return Twig_Environment + */ + public function setNamespaceLookUpOrder($namespace) + { + $this->namespace_look_up_order = $namespace; + + return $this; + } + + /** + * Loads a template by name. + * + * @param string $name The template name + * @param integer $index The index if it is an embedded template + * @return Twig_TemplateInterface A template instance representing the given template name + */ + public function loadTemplate($name, $index = null) + { + if (strpos($name, '@') === false) + { + foreach ($this->getNamespaceLookUpOrder() as $namespace) + { + try + { + if ($namespace === '__main__') + { + return parent::loadTemplate($name, $index); + } + + return parent::loadTemplate('@' . $namespace . '/' . $name, $index); + } + catch (Twig_Error_Loader $e) + { + } + } + + // We were unable to load any templates + throw $e; + } + else + { + return parent::loadTemplate($name, $index); + } + } +} diff --git a/phpBB/includes/template/twig/extension.php b/phpBB/includes/template/twig/extension.php new file mode 100644 index 0000000000..c279726434 --- /dev/null +++ b/phpBB/includes/template/twig/extension.php @@ -0,0 +1,188 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class phpbb_template_twig_extension extends Twig_Extension +{ + /** @var phpbb_template_context */ + protected $context; + + /** @var phpbb_user */ + protected $user; + + /** + * Constructor + * + * @param phpbb_template_context $context + * @param phpbb_user $user + * @return phpbb_template_twig_extension + */ + public function __construct(phpbb_template_context $context, $user) + { + $this->context = $context; + $this->user = $user; + } + + /** + * Get the name of this extension + * + * @return string + */ + public function getName() + { + return 'phpbb'; + } + + /** + * Returns the token parser instance to add to the existing list. + * + * @return array An array of Twig_TokenParser instances + */ + public function getTokenParsers() + { + return array( + new phpbb_template_twig_tokenparser_define, + new phpbb_template_twig_tokenparser_include, + new phpbb_template_twig_tokenparser_includejs, + new phpbb_template_twig_tokenparser_includecss, + new phpbb_template_twig_tokenparser_event, + new phpbb_template_twig_tokenparser_includephp, + new phpbb_template_twig_tokenparser_php, + ); + } + + /** + * Returns a list of filters to add to the existing list. + * + * @return array An array of filters + */ + public function getFilters() + { + return array( + new Twig_SimpleFilter('subset', array($this, 'loop_subset'), array('needs_environment' => true)), + new Twig_SimpleFilter('addslashes', 'addslashes'), + ); + } + + /** + * Returns a list of global functions to add to the existing list. + * + * @return array An array of global functions + */ + public function getFunctions() + { + return array( + new Twig_SimpleFunction('lang', array($this, 'lang')), + ); + } + + /** + * Returns a list of operators to add to the existing list. + * + * @return array An array of operators + */ + public function getOperators() + { + return array( + array( + '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), + ), + array( + // precedence settings are copied from similar operators in Twig core extension + '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + 'eq' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + 'ne' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'neq' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '<>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + '===' => array('precedence' => 20, 'class' => 'phpbb_template_twig_node_expression_binary_equalequal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + '!==' => array('precedence' => 20, 'class' => 'phpbb_template_twig_node_expression_binary_notequalequal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + 'gt' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'gte' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'ge' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'lt' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'lte' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + 'le' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + + 'mod' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), + ), + ); + } + + /** + * Grabs a subset of a loop + * + * @param Twig_Environment $env A Twig_Environment instance + * @param mixed $item A variable + * @param integer $start Start of the subset + * @param integer $end End of the subset + * @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + */ + function loop_subset(Twig_Environment $env, $item, $start, $end = null, $preserveKeys = false) + { + // We do almost the same thing as Twig's slice (array_slice), except when $end is positive + if ($end >= 1) + { + // When end is > 1, subset will end on the last item in an array with the specified $end + // This is different from slice in that it is the number we end on rather than the number + // of items to grab (length) + + // Start must always be the actual starting number for this calculation (not negative) + $start = ($start < 0) ? sizeof($item) + $start : $start; + $end = $end - $start; + } + + // We always include the last element (this was the past design) + $end = ($end == -1 || $end === null) ? null : $end + 1; + + return twig_slice($env, $item, $start, $end, $preserveKeys); + } + + /** + * Get output for a language variable (L_FOO, LA_FOO) + * + * This function checks to see if the language var was outputted to $context + * (e.g. in the ACP, L_TITLE) + * If not, we return the result of $user->lang() + * + * @param string $lang name + * @return string + */ + function lang() + { + $args = func_get_args(); + $key = $args[0]; + + $context = $this->context->get_data_ref(); + $context_vars = $context['.'][0]; + + if (isset($context_vars['L_' . $key])) + { + return $context_vars['L_' . $key]; + } + + // LA_ is transformed into lang(\'$1\')|addslashes, so we should not + // need to check for it + + return call_user_func_array(array($this->user, 'lang'), $args); + } +} diff --git a/phpBB/includes/template/twig/lexer.php b/phpBB/includes/template/twig/lexer.php new file mode 100644 index 0000000000..46412ad048 --- /dev/null +++ b/phpBB/includes/template/twig/lexer.php @@ -0,0 +1,300 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class phpbb_template_twig_lexer extends Twig_Lexer +{ + public function tokenize($code, $filename = null) + { + // Our phpBB tags + // Commented out tokens are handled separately from the main replace + $phpbb_tags = array( + /*'BEGIN', + 'BEGINELSE', + 'END', + 'IF', + 'ELSE', + 'ELSEIF', + 'ENDIF', + 'DEFINE', + 'UNDEFINE',*/ + 'ENDDEFINE', + 'INCLUDE', + 'INCLUDEPHP', + 'INCLUDEJS', + 'INCLUDECSS', + 'PHP', + 'ENDPHP', + 'EVENT', + ); + + // Twig tag masks + $twig_tags = array( + 'autoescape', + 'endautoescape', + 'if', + 'elseif', + 'else', + 'endif', + 'block', + 'endblock', + 'use', + 'extends', + 'embed', + 'filter', + 'endfilter', + 'flush', + 'for', + 'endfor', + 'macro', + 'endmacro', + 'import', + 'from', + 'sandbox', + 'endsandbox', + 'set', + 'endset', + 'spaceless', + 'endspaceless', + 'verbatim', + 'endverbatim', + ); + + // Fix tokens that may have inline variables (e.g. <!-- DEFINE $TEST = '{FOO}') + $code = $this->fix_inline_variable_tokens(array( + 'DEFINE.+=', + 'INCLUDE', + 'INCLUDEPHP', + 'INCLUDEJS', + 'INCLUDECSS', + ), $code); + + // Fix our BEGIN statements + $code = $this->fix_begin_tokens($code); + + // Fix our IF tokens + $code = $this->fix_if_tokens($code); + + // Fix our DEFINE tokens + $code = $this->fix_define_tokens($code); + + // Replace all of our starting tokens, <!-- TOKEN --> with Twig style, {% TOKEN %} + // This also strips outer parenthesis, <!-- IF (blah) --> becomes <!-- IF blah --> + $code = preg_replace('#<!-- (' . implode('|', $phpbb_tags) . ')(?: (.*?) ?)?-->#', '{% $1 $2 %}', $code); + + // Replace all of our twig masks with Twig code (e.g. <!-- BLOCK .+ --> with {% block $1 %}) + $code = $this->replace_twig_tag_masks($code, $twig_tags); + + // Replace all of our language variables, {L_VARNAME}, with Twig style, {{ lang('NAME') }} + // Appends any filters after lang() + $code = preg_replace('#{L_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2 }}', $code); + + // Replace all of our escaped language variables, {LA_VARNAME}, with Twig style, {{ lang('NAME')|addslashes }} + // Appends any filters after lang(), but before addslashes + $code = preg_replace('#{LA_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2|addslashes }}', $code); + + // Replace all of our variables, {VARNAME}, with Twig style, {{ VARNAME }} + // Appends any filters + $code = preg_replace('#{([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ $1$2 }}', $code); + + return parent::tokenize($code, $filename); + } + + /** + * Fix tokens that may have inline variables + * + * E.g. <!-- INCLUDE {TEST}.html + * + * @param array $tokens array of tokens to search for (imploded to a regular expression) + * @param string $code + * @return string + */ + protected function fix_inline_variable_tokens($tokens, $code) + { + $callback = function($matches) + { + // Remove any quotes that may have been used in different implementations + // E.g. DEFINE $TEST = 'blah' vs INCLUDE foo + // Replace {} with start/end to parse variables (' ~ TEST ~ '.html) + $matches[2] = str_replace(array('"', "'", '{', '}'), array('', '', "' ~ ", " ~ '"), $matches[2]); + + // Surround the matches in single quotes ('' ~ TEST ~ '.html') + return "<!-- {$matches[1]} '{$matches[2]}' -->"; + }; + + return preg_replace_callback('#<!-- (' . implode('|', $tokens) . ') (.+?) -->#', $callback, $code); + } + + /** + * Fix begin tokens (convert our BEGIN to Twig for) + * + * Not meant to be used outside of this context, public because the anonymous function calls this + * + * @param string $code + * @param array $parent_nodes (used in recursion) + * @return string + */ + public function fix_begin_tokens($code, $parent_nodes = array()) + { + // PHP 5.3 cannot use $this in an anonymous function, so use this as a work-around + $parent_class = $this; + $callback = function ($matches) use ($parent_class, $parent_nodes) + { + $name = $matches[1]; + $subset = trim(substr($matches[2], 1, -1)); // Remove parenthesis + $body = $matches[3]; + + // 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($name, '!') === 0) + { + // Count the number if ! occurrences + $count = substr_count($name, '!'); + for ($i = 0; $i < $count; $i++) + { + array_pop($parent_nodes); + $name = substr($name, 1); + } + } + + // Remove all parent nodes, e.g. foo, bar from foo.bar.foobar.VAR + foreach ($parent_nodes as $node) + { + $body = preg_replace('#([^a-zA-Z0-9_])' . $node . '\.([a-zA-Z0-9_]+)\.#', '$1$2.', $body); + } + + // Add current node to list of parent nodes for child nodes + $parent_nodes[] = $name; + + // Recursive...fix any child nodes + $body = $parent_class->fix_begin_tokens($body, $parent_nodes); + + // Rename loopname vars (to prevent collisions, loop children are named (loop name)_loop_element) + $body = str_replace($name . '.', $name . '_loop_element.', $body); + + // Need the parent variable name + array_pop($parent_nodes); + $parent = (!empty($parent_nodes)) ? end($parent_nodes) . '_loop_element.' : ''; + + if ($subset !== '') + { + $subset = '|subset(' . $subset . ')'; + } + + // Turn into a Twig for loop, using (loop name)_loop_element for each child + return "{% for {$name}_loop_element in {$parent}{$name}{$subset} %}{$body}{% endfor %}"; + }; + + // Replace <!-- BEGINELSE --> correctly, only needs to be done once + $code = str_replace('<!-- BEGINELSE -->', '{% else %}', $code); + + return preg_replace_callback('#<!-- BEGIN ([!a-zA-Z0-9_]+)(\([0-9,\-]+\))? -->(.+?)<!-- END \1 -->#s', $callback, $code); + } + + /** + * Fix IF statements + * + * @param string $code + * @return string + */ + protected function fix_if_tokens($code) + { + $callback = function($matches) + { + // Replace $TEST with definition.TEST + $matches[1] = preg_replace('#\s\$([a-zA-Z_0-9]+)#', ' definition.$1', $matches[1]); + + // Replace .test with test|length + $matches[1] = preg_replace('#\s\.([a-zA-Z_0-9\.]+)#', ' $1|length', $matches[1]); + + return '<!-- IF' . $matches[1] . '-->'; + }; + + // Replace our "div by" with Twig's divisibleby (Twig does not like test names with spaces) + $code = preg_replace('# div by ([0-9]+)#', ' divisibleby($1)', $code); + + return preg_replace_callback('#<!-- IF((.*)[\s][\$|\.|!]([^\s]+)(.*))-->#', $callback, $code); + } + + /** + * Fix DEFINE statements and {$VARNAME} variables + * + * @param string $code + * @return string + */ + protected function fix_define_tokens($code) + { + /** + * Changing $VARNAME to definition.varname because set is only local + * context (e.g. DEFINE $TEST will only make $TEST available in current + * template and any child templates, but not any parent templates). + * + * DEFINE handles setting it properly to definition in its node, but the + * variables reading FROM it need to be altered to definition.VARNAME + * + * Setting up definition as a class in the array passed to Twig + * ($context) makes set definition.TEST available in the global context + */ + + // Replace <!-- DEFINE $NAME with {% DEFINE definition.NAME + $code = preg_replace('#<!-- DEFINE \$(.*)-->#', '{% DEFINE $1 %}', $code); + + // Changing UNDEFINE NAME to DEFINE NAME = null to save from creating an extra token parser/node + $code = preg_replace('#<!-- UNDEFINE \$(.*)-->#', '{% DEFINE $1= null %}', $code); + + // Replace all of our variables, {$VARNAME}, with Twig style, {{ definition.VARNAME }} + $code = preg_replace('#{\$([a-zA-Z0-9_\.]+)}#', '{{ definition.$1 }}', $code); + + // Replace all of our variables, ~ $VARNAME ~, with Twig style, ~ definition.VARNAME ~ + $code = preg_replace('#~ \$([a-zA-Z0-9_\.]+) ~#', '~ definition.$1 ~', $code); + + return $code; + } + + /** + * Replace Twig tag masks with Twig tag calls + * + * E.g. <!-- BLOCK foo --> with {% block foo %} + * + * @param string $code + * @param array $twig_tags All tags we want to create a mask for + * @return string + */ + protected function replace_twig_tag_masks($code, $twig_tags) + { + $callback = function ($matches) + { + $matches[1] = strtolower($matches[1]); + + return "{% {$matches[1]}{$matches[2]}%}"; + }; + + foreach ($twig_tags as &$tag) + { + $tag = strtoupper($tag); + } + + // twig_tags is an array of the twig tags, which are all lowercase, but we use all uppercase tags + $code = preg_replace_callback('#<!-- (' . implode('|', $twig_tags) . ')(.*?)-->#',$callback, $code); + + return $code; + } +} diff --git a/phpBB/includes/template/twig/node/define.php b/phpBB/includes/template/twig/node/define.php new file mode 100644 index 0000000000..fcb19cc773 --- /dev/null +++ b/phpBB/includes/template/twig/node/define.php @@ -0,0 +1,58 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group, sections (c) 2009 Fabien Potencier, Armin Ronacher +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_node_define extends Twig_Node +{ + public function __construct($capture, Twig_NodeInterface $name, Twig_NodeInterface $value, $lineno, $tag = null) + { + parent::__construct(array('name' => $name, 'value' => $value), array('capture' => $capture, 'safe' => false), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->getAttribute('capture')) { + $compiler + ->write("ob_start();\n") + ->subcompile($this->getNode('value')) + ; + + $compiler->write("\$value = ('' === \$value = ob_get_clean()) ? '' : new Twig_Markup(\$value, \$this->env->getCharset());\n"); + } + else + { + $compiler + ->write("\$value = ") + ->subcompile($this->getNode('value')) + ->raw(";\n") + ; + } + + $compiler + ->write("\$context['definition']->set('") + ->raw($this->getNode('name')->getAttribute('name')) + ->raw("', \$value);\n") + ; + } +} diff --git a/phpBB/includes/template/twig/node/event.php b/phpBB/includes/template/twig/node/event.php new file mode 100644 index 0000000000..971dea14fa --- /dev/null +++ b/phpBB/includes/template/twig/node/event.php @@ -0,0 +1,79 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_node_event extends Twig_Node +{ + /** @var Twig_Environment */ + protected $environment; + + public function __construct(Twig_Node_Expression $expr, phpbb_template_twig_environment $environment, $lineno, $tag = null) + { + $this->environment = $environment; + + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $location = $this->getNode('expr')->getAttribute('name'); + + foreach ($this->environment->get_phpbb_extensions() as $ext_namespace => $ext_path) + { + $ext_namespace = str_replace('/', '_', $ext_namespace); + + if (defined('DEBUG')) + { + // If debug mode is enabled, lets check for new/removed EVENT + // templates on page load rather than at compile. This is + // slower, but makes developing extensions easier (no need to + // purge the cache when a new event template file is added) + $compiler + ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/{$location}.html')) {\n") + ->indent() + ; + } + + if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) + { + $compiler + ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") + + // We set the namespace lookup order to be this extension first, then the main path + ->write("\$this->env->setNamespaceLookUpOrder(array('{$ext_namespace}', '__main__'));\n") + ->write("\$this->env->loadTemplate('@{$ext_namespace}/{$location}.html')->display(\$context);\n") + ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") + ; + } + + if (defined('DEBUG')) + { + $compiler + ->outdent() + ->write("}\n\n") + ; + } + } + } +} diff --git a/phpBB/includes/template/twig/node/expression/binary/equalequal.php b/phpBB/includes/template/twig/node/expression/binary/equalequal.php new file mode 100644 index 0000000000..8ec2069114 --- /dev/null +++ b/phpBB/includes/template/twig/node/expression/binary/equalequal.php @@ -0,0 +1,25 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_node_expression_binary_equalequal extends Twig_Node_Expression_Binary +{ + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('==='); + } +} diff --git a/phpBB/includes/template/twig/node/expression/binary/notequalequal.php b/phpBB/includes/template/twig/node/expression/binary/notequalequal.php new file mode 100644 index 0000000000..96f32c502e --- /dev/null +++ b/phpBB/includes/template/twig/node/expression/binary/notequalequal.php @@ -0,0 +1,25 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_node_expression_binary_notequalequal extends Twig_Node_Expression_Binary +{ + public function operator(Twig_Compiler $compiler) + { + return $compiler->raw('!=='); + } +} diff --git a/phpBB/includes/template/twig/node/include.php b/phpBB/includes/template/twig/node/include.php new file mode 100644 index 0000000000..5c6ae1bbcf --- /dev/null +++ b/phpBB/includes/template/twig/node/include.php @@ -0,0 +1,56 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_node_include extends Twig_Node_Include +{ + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $compiler + ->write("\$location = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("\$namespace = false;\n") + ->write("if (strpos(\$location, '@') === 0) {\n") + ->indent() + ->write("\$namespace = substr(\$location, 1, strpos(\$location, '/') - 1);\n") + ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") + + // We set the namespace lookup order to be this namespace first, then the main path + ->write("\$this->env->setNamespaceLookUpOrder(array(\$namespace, '__main__'));\n") + ->outdent() + ->write("}\n") + ; + + parent::compile($compiler); + + $compiler + ->write("if (\$namespace) {\n") + ->indent() + ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/phpBB/includes/template/twig/node/includeasset.php b/phpBB/includes/template/twig/node/includeasset.php new file mode 100644 index 0000000000..5abff10e3f --- /dev/null +++ b/phpBB/includes/template/twig/node/includeasset.php @@ -0,0 +1,65 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_template_twig_node_includeasset extends Twig_Node +{ + /** @var Twig_Environment */ + protected $environment; + + public function __construct(Twig_Node_Expression $expr, phpbb_template_twig_environment $environment, $lineno, $tag = null) + { + $this->environment = $environment; + + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + $compiler + ->write("\$asset_file = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("\$argument_string = \$anchor_string = '';\n") + ->write("if ((\$argument_string_start = strpos(\$asset_file, '?')) !== false) {\n") + ->indent() + ->write("\$argument_string = substr(\$asset_file, \$argument_string_start);\n") + ->write("\$asset_file = substr(\$asset_file, 0, \$argument_string_start);\n") + ->write("if ((\$anchor_string_start = strpos(\$argument_string, '#')) !== false) {\n") + ->indent() + ->write("\$anchor_string = substr(\$argument_string, \$anchor_string_start);\n") + ->write("\$argument_string = substr(\$argument_string, 0, \$anchor_string_start);\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ->write("if (strpos(\$asset_file, '//') !== 0 && strpos(\$asset_file, 'http://') !== 0 && strpos(\$asset_file, 'https://') !== 0 && !file_exists(\$asset_file)) {\n") + ->indent() + ->write("\$asset_file = \$this->getEnvironment()->getLoader()->getCacheKey(\$asset_file);\n") + ->write("\$argument_string .= ((\$argument_string) ? '&' : '?') . 'assets_version={$config['assets_version']}';\n") + ->outdent() + ->write("}\n") + ->write("\$asset_file .= \$argument_string . \$anchor_string;\n") + ->write("\$context['definition']->append('{$this->get_definition_name()}', '") + ; + + $this->append_asset($compiler); + + $compiler + ->raw("\n');\n") + ; + } +} diff --git a/phpBB/includes/template/twig/node/includecss.php b/phpBB/includes/template/twig/node/includecss.php new file mode 100644 index 0000000000..01fda44aad --- /dev/null +++ b/phpBB/includes/template/twig/node/includecss.php @@ -0,0 +1,30 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_template_twig_node_includecss extends phpbb_template_twig_node_includeasset +{ + public function get_definition_name() + { + return 'STYLESHEETS'; + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function append_asset(Twig_Compiler $compiler) + { + $compiler + ->raw("<link href=\"' . ") + ->raw("\$asset_file . '\"") + ->raw(' rel="stylesheet" type="text/css" media="screen, projection" />') + ; + } +} diff --git a/phpBB/includes/template/twig/node/includejs.php b/phpBB/includes/template/twig/node/includejs.php new file mode 100644 index 0000000000..fdf2bea3ed --- /dev/null +++ b/phpBB/includes/template/twig/node/includejs.php @@ -0,0 +1,32 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_template_twig_node_includejs extends phpbb_template_twig_node_includeasset +{ + public function get_definition_name() + { + return 'SCRIPTS'; + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + protected function append_asset(Twig_Compiler $compiler) + { + $config = $this->environment->get_phpbb_config(); + + $compiler + ->raw("<script type=\"text/javascript\" src=\"' . ") + ->raw("\$asset_file") + ->raw(". '\"></script>\n") + ; + } +} diff --git a/phpBB/includes/template/twig/node/includephp.php b/phpBB/includes/template/twig/node/includephp.php new file mode 100644 index 0000000000..dbe54f0e1a --- /dev/null +++ b/phpBB/includes/template/twig/node/includephp.php @@ -0,0 +1,91 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group, sections (c) 2009 Fabien Potencier, Armin Ronacher +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_node_includephp extends Twig_Node +{ + /** @var Twig_Environment */ + protected $environment; + + public function __construct(Twig_Node_Expression $expr, phpbb_template_twig_environment $environment, $ignoreMissing = false, $lineno, $tag = null) + { + $this->environment = $environment; + + parent::__construct(array('expr' => $expr), array('ignore_missing' => (Boolean) $ignoreMissing), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + if (!$config['tpl_allow_php']) + { + $compiler + ->write("// INCLUDEPHP Disabled\n") + ; + + return; + } + + if ($this->getAttribute('ignore_missing')) { + $compiler + ->write("try {\n") + ->indent() + ; + } + + $compiler + ->write("\$location = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("if (phpbb_is_absolute(\$location)) {\n") + ->indent() + // Absolute path specified + ->write("require(\$location);\n") + ->outdent() + ->write("} else if (file_exists(\$this->getEnvironment()->get_phpbb_root_path() . \$location)) {\n") + ->indent() + // PHP file relative to phpbb_root_path + ->write("require(\$this->getEnvironment()->get_phpbb_root_path() . \$location);\n") + ->outdent() + ->write("} else {\n") + ->indent() + // Local path (behaves like INCLUDE) + ->write("require(\$this->getEnvironment()->getLoader()->getCacheKey(\$location));\n") + ->outdent() + ->write("}\n") + ; + + if ($this->getAttribute('ignore_missing')) { + $compiler + ->outdent() + ->write("} catch (Twig_Error_Loader \$e) {\n") + ->indent() + ->write("// ignore missing template\n") + ->outdent() + ->write("}\n\n") + ; + } + } +} diff --git a/phpBB/includes/template/twig/node/php.php b/phpBB/includes/template/twig/node/php.php new file mode 100644 index 0000000000..c11539ea7f --- /dev/null +++ b/phpBB/includes/template/twig/node/php.php @@ -0,0 +1,55 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_node_php extends Twig_Node +{ + /** @var Twig_Environment */ + protected $environment; + + public function __construct(Twig_Node_Text $text, phpbb_template_twig_environment $environment, $lineno, $tag = null) + { + $this->environment = $environment; + + parent::__construct(array('text' => $text), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + if (!$config['tpl_allow_php']) + { + $compiler + ->write("// PHP Disabled\n") + ; + + return; + } + + $compiler + ->raw($this->getNode('text')->getAttribute('data')) + ; + } +} diff --git a/phpBB/includes/template/twig/tokenparser/define.php b/phpBB/includes/template/twig/tokenparser/define.php new file mode 100644 index 0000000000..4ea15388c4 --- /dev/null +++ b/phpBB/includes/template/twig/tokenparser/define.php @@ -0,0 +1,66 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group, sections (c) 2009 Fabien Potencier +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_tokenparser_define extends Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param Twig_Token $token A Twig_Token instance + * + * @return Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(Twig_Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $this->parser->getExpressionParser()->parseExpression(); + + $capture = false; + if ($stream->test(Twig_Token::OPERATOR_TYPE, '=')) { + $stream->next(); + $value = $this->parser->getExpressionParser()->parseExpression(); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + } else { + $capture = true; + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $value = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + } + + return new phpbb_template_twig_node_define($capture, $name, $value, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Twig_Token $token) + { + return $token->test('ENDDEFINE'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'DEFINE'; + } +} diff --git a/phpBB/includes/template/twig/tokenparser/event.php b/phpBB/includes/template/twig/tokenparser/event.php new file mode 100644 index 0000000000..e4dddd6dcc --- /dev/null +++ b/phpBB/includes/template/twig/tokenparser/event.php @@ -0,0 +1,47 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_tokenparser_event extends Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param Twig_Token $token A Twig_Token instance + * + * @return Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_event($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'EVENT'; + } +} diff --git a/phpBB/includes/template/twig/tokenparser/include.php b/phpBB/includes/template/twig/tokenparser/include.php new file mode 100644 index 0000000000..520f9fd1a0 --- /dev/null +++ b/phpBB/includes/template/twig/tokenparser/include.php @@ -0,0 +1,46 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group, sections (c) 2009 Fabien Potencier +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_tokenparser_include extends Twig_TokenParser_Include +{ + /** + * Parses a token and returns a node. + * + * @param Twig_Token $token A Twig_Token instance + * + * @return Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + return new phpbb_template_twig_node_include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDE'; + } +} diff --git a/phpBB/includes/template/twig/tokenparser/includecss.php b/phpBB/includes/template/twig/tokenparser/includecss.php new file mode 100644 index 0000000000..6c24dda647 --- /dev/null +++ b/phpBB/includes/template/twig/tokenparser/includecss.php @@ -0,0 +1,38 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +class phpbb_template_twig_tokenparser_includecss extends Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param Twig_Token $token A Twig_Token instance + * + * @return Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_includecss($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDECSS'; + } +} diff --git a/phpBB/includes/template/twig/tokenparser/includejs.php b/phpBB/includes/template/twig/tokenparser/includejs.php new file mode 100644 index 0000000000..b02b2f89ba --- /dev/null +++ b/phpBB/includes/template/twig/tokenparser/includejs.php @@ -0,0 +1,47 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_tokenparser_includejs extends Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param Twig_Token $token A Twig_Token instance + * + * @return Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_includejs($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDEJS'; + } +} diff --git a/phpBB/includes/template/twig/tokenparser/includephp.php b/phpBB/includes/template/twig/tokenparser/includephp.php new file mode 100644 index 0000000000..13fe6de8a6 --- /dev/null +++ b/phpBB/includes/template/twig/tokenparser/includephp.php @@ -0,0 +1,56 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group, sections (c) 2009 Fabien Potencier, Armin Ronacher +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_tokenparser_includephp extends Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param Twig_Token $token A Twig_Token instance + * + * @return Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + + $ignoreMissing = false; + if ($stream->test(Twig_Token::NAME_TYPE, 'ignore')) { + $stream->next(); + $stream->expect(Twig_Token::NAME_TYPE, 'missing'); + + $ignoreMissing = true; + } + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_includephp($expr, $this->parser->getEnvironment(), $ignoreMissing, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDEPHP'; + } +} diff --git a/phpBB/includes/template/twig/tokenparser/php.php b/phpBB/includes/template/twig/tokenparser/php.php new file mode 100644 index 0000000000..197980a59a --- /dev/null +++ b/phpBB/includes/template/twig/tokenparser/php.php @@ -0,0 +1,55 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +class phpbb_template_twig_tokenparser_php extends Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param Twig_Token $token A Twig_Token instance + * + * @return Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(Twig_Token $token) + { + $stream = $this->parser->getStream(); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'decideEnd'), true); + + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new phpbb_template_twig_node_php($body, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + public function decideEnd(Twig_Token $token) + { + return $token->test('ENDPHP'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'PHP'; + } +} diff --git a/phpBB/includes/template/twig/twig.php b/phpBB/includes/template/twig/twig.php new file mode 100644 index 0000000000..f5638972a3 --- /dev/null +++ b/phpBB/includes/template/twig/twig.php @@ -0,0 +1,465 @@ +<?php +/** +* +* @package phpBB3 +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Twig Template class. +* @package phpBB3 +*/ +class phpbb_template_twig implements phpbb_template +{ + /** + * Template context. + * Stores template data used during template rendering. + * @var phpbb_template_context + */ + protected $context; + + /** + * Path of the cache directory for the template + * + * Cannot be changed during runtime. + * + * @var string + */ + private $cachepath = ''; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * adm relative path + * @var string + */ + protected $adm_relative_path; + + /** + * PHP file extension + * @var string + */ + protected $php_ext; + + /** + * phpBB config instance + * @var phpbb_config + */ + protected $config; + + /** + * Current user + * @var phpbb_user + */ + protected $user; + + /** + * Extension manager. + * + * @var phpbb_extension_manager + */ + protected $extension_manager; + + /** + * Name of the style that the template being compiled and/or rendered + * belongs to, and its parents, in inheritance tree order. + * + * Used to invoke style-specific template events. + * + * @var array + */ + protected $style_names; + + /** + * Twig Environment + * + * @var Twig_Environment + */ + protected $twig; + + /** + * Array of filenames assigned to set_filenames + * + * @var array + */ + protected $filenames = array(); + + /** + * Constructor. + * + * @param string $phpbb_root_path phpBB root path + * @param string $php_ext php extension (typically 'php') + * @param phpbb_config $config + * @param phpbb_user $user + * @param phpbb_template_context $context template context + * @param phpbb_extension_manager $extension_manager extension manager, if null then template events will not be invoked + * @param string $adm_relative_path relative path to adm directory + */ + public function __construct($phpbb_root_path, $php_ext, $config, $user, phpbb_template_context $context, phpbb_extension_manager $extension_manager = null, $adm_relative_path = null) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->adm_relative_path = $adm_relative_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->user = $user; + $this->context = $context; + $this->extension_manager = $extension_manager; + + $this->cachepath = $phpbb_root_path . 'cache/twig/'; + + // Initiate the loader, __main__ namespace paths will be setup later in set_style_names() + $loader = new Twig_Loader_Filesystem(''); + + $this->twig = new phpbb_template_twig_environment( + $this->config, + ($this->extension_manager) ? $this->extension_manager->all_enabled() : array(), + $this->phpbb_root_path, + $loader, + array( + 'cache' => (defined('IN_INSTALL')) ? false : $this->cachepath, + 'debug' => defined('DEBUG'), + 'auto_reload' => (bool) $this->config['load_tplcompile'], + 'autoescape' => false, + ) + ); + + $this->twig->addExtension( + new phpbb_template_twig_extension( + $this->context, + $this->user + ) + ); + + $lexer = new phpbb_template_twig_lexer($this->twig); + + $this->twig->setLexer($lexer); + } + + /** + * Clear the cache + * + * @return phpbb_template + */ + public function clear_cache() + { + if (is_dir($this->cachepath)) + { + $this->twig->clearCacheFiles(); + } + + return $this; + } + + /** + * Sets the template filenames for handles. + * + * @param array $filename_array Should be a hash of handle => filename pairs. + * @return phpbb_template $this + */ + public function set_filenames(array $filename_array) + { + $this->filenames = array_merge($filename_array, $this->filenames); + + return $this; + } + + /** + * Sets the style names/paths corresponding to style hierarchy being compiled + * and/or rendered. + * + * @param array $style_names List of style names in inheritance tree order + * @param array $style_paths List of style paths in inheritance tree order + * @param bool $is_core True if the style names are the "core" styles for this page load + * Core means the main phpBB template files + * @return phpbb_template $this + */ + public function set_style_names(array $style_names, array $style_paths, $is_core = false) + { + $this->style_names = $style_names; + + // Set as __main__ namespace + $this->twig->getLoader()->setPaths($style_paths); + + // Core style namespace from phpbb_style::set_style() + if ($is_core) + { + $this->twig->getLoader()->setPaths($style_paths, 'core'); + } + + // Add admin namespace + if (is_dir($this->phpbb_root_path . $this->adm_relative_path . 'style/')) + { + $this->twig->getLoader()->setPaths($this->phpbb_root_path . $this->adm_relative_path . 'style/', 'admin'); + } + + // Add all namespaces for all extensions + if ($this->extension_manager instanceof phpbb_extension_manager) + { + $style_names[] = 'all'; + + foreach ($this->extension_manager->all_enabled() as $ext_namespace => $ext_path) + { + // namespaces cannot contain / + $namespace = str_replace('/', '_', $ext_namespace); + $paths = array(); + + foreach ($style_names as $style_name) + { + $ext_style_path = $ext_path . 'styles/' . $style_name . '/template'; + + if (is_dir($ext_style_path)) + { + $paths[] = $ext_style_path; + } + } + + $this->twig->getLoader()->setPaths($paths, $namespace); + } + } + + return $this; + } + + /** + * Clears all variables and blocks assigned to this template. + * + * @return phpbb_template $this + */ + public function destroy() + { + $this->context = array(); + + return $this; + } + + /** + * Reset/empty complete block + * + * @param string $blockname Name of block to destroy + * @return phpbb_template $this + */ + public function destroy_block_vars($blockname) + { + $this->context->destroy_block_vars($blockname); + + return $this; + } + + /** + * 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 phpbb_template $this + */ + public function display($handle) + { + $result = $this->call_hook($handle, __FUNCTION__); + if ($result !== false) + { + return $result[0]; + } + + $this->twig->display($this->get_filename_from_handle($handle), $this->get_template_vars()); + + return $this; + } + + /** + * Calls hook if any is defined. + * + * @param string $handle Template handle being displayed. + * @param string $method Method name of the caller. + */ + protected function call_hook($handle, $method) + { + global $phpbb_hook; + + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array(__CLASS__, $method), $handle, $this)) + { + if ($phpbb_hook->hook_return(array(__CLASS__, $method))) + { + $result = $phpbb_hook->hook_return_result(array(__CLASS__, $method)); + return array($result); + } + } + + return false; + } + + /** + * 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 phpbb_template|string if $return_content is true return string of the compiled handle, otherwise return $this + */ + public function assign_display($handle, $template_var = '', $return_content = true) + { + if ($return_content) + { + return $this->twig->render($this->get_filename_from_handle($handle), $this->get_template_vars()); + } + + $this->assign_var($template_var, $this->twig->render($this->get_filename_from_handle($handle, $this->get_template_vars()))); + + return $this; + } + + /** + * Assign key variable pairs from an array + * + * @param array $vararray A hash of variable name => value pairs + * @return phpbb_template $this + */ + public function assign_vars(array $vararray) + { + foreach ($vararray as $key => $val) + { + $this->assign_var($key, $val); + } + + return $this; + } + + /** + * Assign a single scalar value to a single key. + * + * Value can be a string, an integer or a boolean. + * + * @param string $varname Variable name + * @param string $varval Value to assign to variable + * @return phpbb_template $this + */ + public function assign_var($varname, $varval) + { + $this->context->assign_var($varname, $varval); + + return $this; + } + + /** + * Append text to the string value stored in a key. + * + * Text is appended using the string concatenation operator (.). + * + * @param string $varname Variable name + * @param string $varval Value to append to variable + * @return phpbb_template $this + */ + public function append_var($varname, $varval) + { + $this->context->append_var($varname, $varval); + + return $this; + } + + /** + * 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 + * @return phpbb_template $this + */ + public function assign_block_vars($blockname, array $vararray) + { + $this->context->assign_block_vars($blockname, $vararray); + + return $this; + } + + /** + * 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); + } + + /** + * Get template vars in a format Twig will use (from the context) + * + * @return array + */ + public function get_template_vars() + { + $context_vars = $this->context->get_data_ref(); + + $vars = array_merge( + $context_vars['.'][0], // To get normal vars + $context_vars, // To get loops + array( + 'definition' => new phpbb_template_twig_definition(), + 'user' => $this->user, + ) + ); + + // cleanup + unset($vars['.']); + + return $vars; + } + + /** + * Get a filename from the handle + * + * @param string $handle + * @return string + */ + protected function get_filename_from_handle($handle) + { + return (isset($this->filenames[$handle])) ? $this->filenames[$handle] : $handle; + } + + /** + * Get path to template for handle (required for BBCode parser) + * + * @return string + */ + public function get_source_file_for_handle($handle) + { + return $this->twig->getLoader()->getCacheKey($this->get_filename_from_handle($handle)); + } +} diff --git a/phpBB/includes/ucp/ucp_groups.php b/phpBB/includes/ucp/ucp_groups.php index aada0525a8..8620e33e47 100644 --- a/phpBB/includes/ucp/ucp_groups.php +++ b/phpBB/includes/ucp/ucp_groups.php @@ -565,7 +565,7 @@ class ucp_groups if ($colour_error = validate_data($submit_ary, array('colour' => array('hex_colour', true)))) { // Replace "error" string with its real, localised form - $error = array_merge($error, array_map(array(&$user, 'lang'), $colour_error)); + $error = array_merge($error, $colour_error); } if (!sizeof($error)) @@ -613,6 +613,7 @@ class ucp_groups if (sizeof($error)) { + $error = array_map(array(&$user, 'lang'), $error); $group_rank = $submit_ary['rank']; $group_desc_data = array( |