diff options
Diffstat (limited to 'phpBB/phpbb/template')
-rw-r--r-- | phpBB/phpbb/template/asset.php | 15 | ||||
-rw-r--r-- | phpBB/phpbb/template/assets_bag.php | 95 | ||||
-rw-r--r-- | phpBB/phpbb/template/base.php | 29 | ||||
-rw-r--r-- | phpBB/phpbb/template/context.php | 275 | ||||
-rw-r--r-- | phpBB/phpbb/template/exception/user_object_not_available.php | 22 | ||||
-rw-r--r-- | phpBB/phpbb/template/template.php | 27 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/environment.php | 138 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/extension.php | 19 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/extension/routing.php | 43 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/lexer.php | 22 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/loader.php | 32 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/node/event.php | 6 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/node/includeasset.php | 32 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/node/includecss.php | 16 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/node/includejs.php | 18 | ||||
-rw-r--r-- | phpBB/phpbb/template/twig/twig.php | 63 |
16 files changed, 645 insertions, 207 deletions
diff --git a/phpBB/phpbb/template/asset.php b/phpBB/phpbb/template/asset.php index ff9366af4a..cb00f16549 100644 --- a/phpBB/phpbb/template/asset.php +++ b/phpBB/phpbb/template/asset.php @@ -20,15 +20,20 @@ class asset /** @var \phpbb\path_helper **/ protected $path_helper; + /** @var \phpbb\filesystem\filesystem */ + protected $filesystem; + /** * Constructor * * @param string $url URL * @param \phpbb\path_helper $path_helper Path helper object + * @param \phpbb\filesystem\filesystem $filesystem */ - public function __construct($url, \phpbb\path_helper $path_helper) + public function __construct($url, \phpbb\path_helper $path_helper, \phpbb\filesystem\filesystem $filesystem) { $this->path_helper = $path_helper; + $this->filesystem = $filesystem; $this->set_url($url); } @@ -152,18 +157,18 @@ class asset */ public function set_path($path, $urlencode = false) { - // Since 1.7.0 Twig returns the real path of the file. We need it to be relative to the working directory. - $real_root_path = realpath($this->path_helper->get_phpbb_root_path()) . DIRECTORY_SEPARATOR; + // Since 1.7.0 Twig returns the real path of the file. We need it to be relative. + $real_root_path = $this->filesystem->realpath($this->path_helper->get_phpbb_root_path()) . DIRECTORY_SEPARATOR; // If the asset is under the phpBB root path we need to remove its path and then prepend $phpbb_root_path - if (substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path) + if ($real_root_path && substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path) { $path = $this->path_helper->get_phpbb_root_path() . str_replace('\\', '/', substr($path, strlen($real_root_path))); } else { // Else we make the path relative to the current working directory - $real_root_path = realpath('.') . DIRECTORY_SEPARATOR; + $real_root_path = $this->filesystem->realpath('.') . DIRECTORY_SEPARATOR; if ($real_root_path && substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path) { $path = str_replace('\\', '/', substr($path, strlen($real_root_path))); diff --git a/phpBB/phpbb/template/assets_bag.php b/phpBB/phpbb/template/assets_bag.php new file mode 100644 index 0000000000..067b0eb8f1 --- /dev/null +++ b/phpBB/phpbb/template/assets_bag.php @@ -0,0 +1,95 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template; + +class assets_bag +{ + /** @var asset[] */ + protected $stylesheets = []; + + /** @var asset[] */ + protected $scripts = []; + + /** + * Add a css asset to the bag + * + * @param asset $asset + */ + public function add_stylesheet(asset $asset) + { + $this->stylesheets[] = $asset; + } + + /** + * Add a js script asset to the bag + * + * @param asset $asset + */ + public function add_script(asset $asset) + { + $this->scripts[] = $asset; + } + + /** + * Returns all css assets + * + * @return asset[] + */ + public function get_stylesheets() + { + return $this->stylesheets; + } + + /** + * Returns all js assets + * + * @return asset[] + */ + public function get_scripts() + { + return $this->scripts; + } + + /** + * Returns the HTML code to includes all css assets + * + * @return string + */ + public function get_stylesheets_content() + { + $output = ''; + foreach ($this->stylesheets as $stylesheet) + { + $output .= "<link href=\"{$stylesheet->get_url()}\" rel=\"stylesheet\" media=\"screen\" />\n"; + } + + return $output; + } + + /** + * Returns the HTML code to includes all js assets + * + * @return string + */ + public function get_scripts_content() + { + $output = ''; + foreach ($this->scripts as $script) + { + $output .= "<script src=\"{$script->get_url()}\"></script>\n"; + } + + return $output; + } +} diff --git a/phpBB/phpbb/template/base.php b/phpBB/phpbb/template/base.php index 41c0a01ba8..d502aceab8 100644 --- a/phpBB/phpbb/template/base.php +++ b/phpBB/phpbb/template/base.php @@ -107,6 +107,27 @@ abstract class base implements template /** * {@inheritdoc} */ + public function retrieve_vars(array $vararray) + { + $result = array(); + foreach ($vararray as $varname) + { + $result[$varname] = $this->retrieve_var($varname); + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function retrieve_var($varname) + { + return $this->context->retrieve_var($varname); + } + + /** + * {@inheritdoc} + */ public function assign_block_vars($blockname, array $vararray) { $this->context->assign_block_vars($blockname, $vararray); @@ -127,6 +148,14 @@ abstract class base implements template /** * {@inheritdoc} */ + public function retrieve_block_vars($blockname, array $vararray) + { + return $this->context->retrieve_block_vars($blockname, $vararray); + } + + /** + * {@inheritdoc} + */ public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') { return $this->context->alter_block_array($blockname, $vararray, $key, $mode); diff --git a/phpBB/phpbb/template/context.php b/phpBB/phpbb/template/context.php index 5d04a09865..202e29ce00 100644 --- a/phpBB/phpbb/template/context.php +++ b/phpBB/phpbb/template/context.php @@ -87,6 +87,17 @@ class context } /** + * Retreive a single scalar value from a single key. + * + * @param string $varname Variable name + * @return mixed Variable value, or null if not set + */ + public function retrieve_var($varname) + { + return isset($this->rootref[$varname]) ? $this->rootref[$varname] : null; + } + + /** * Returns a reference to template data array. * * This function is public so that template renderer may invoke it. @@ -131,7 +142,7 @@ class context */ protected function set_num_rows(&$loop_data) { - $s_num_rows = sizeof($loop_data); + $s_num_rows = count($loop_data); foreach ($loop_data as &$mod_block) { foreach ($mod_block as $sub_block_name => &$sub_block) @@ -179,70 +190,51 @@ class context public function assign_block_vars($blockname, array $vararray) { $this->num_rows_is_set = false; - if (strpos($blockname, '.') !== false) - { - // Nested block. - $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; - - $str = &$this->tpldata; - for ($i = 0; $i < $blockcount; $i++) - { - $str = &$str[$blocks[$i]]; - $str = &$str[sizeof($str) - 1]; - } - $s_row_count = isset($str[$blocks[$blockcount]]) ? sizeof($str[$blocks[$blockcount]]) : 0; - $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; + // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 + $blocks = explode('.', $blockname); + $blockcount = count($blocks) - 1; - // Assign S_FIRST_ROW - if (!$s_row_count) - { - $vararray['S_FIRST_ROW'] = true; - } + $block = &$this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + $pos = strpos($blocks[$i], '['); + $name = ($pos !== false) ? substr($blocks[$i], 0, $pos) : $blocks[$i]; + $block = &$block[$name]; + $block_count = empty($block) ? 0 : count($block) - 1; + $index = (!$pos || strpos($blocks[$i], '[]') === $pos) ? $block_count : (min((int) substr($blocks[$i], $pos + 1, -1), $block_count)); + $block = &$block[$index]; + } - // Assign S_BLOCK_NAME - $vararray['S_BLOCK_NAME'] = $blocks[$blockcount]; + // $block = &$block[$blocks[$i]]; // Do not traverse the last block as it might be empty + $name = $blocks[$i]; - // Now the tricky part, we always assign S_LAST_ROW and remove the entry before - // This is much more clever than going through the complete template data on display (phew) - $vararray['S_LAST_ROW'] = true; - if ($s_row_count > 0) - { - unset($str[$blocks[$blockcount]][($s_row_count - 1)]['S_LAST_ROW']); - } + // Assign S_ROW_COUNT and S_ROW_NUM + $s_row_count = isset($block[$name]) ? count($block[$name]) : 0; + $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; - // Now we add the block that we're actually assigning to. - // We're adding a new iteration to this block with the given - // variable assignments. - $str[$blocks[$blockcount]][] = $vararray; - } - else + // Assign S_FIRST_ROW + if (!$s_row_count) { - // Top-level block. - $s_row_count = (isset($this->tpldata[$blockname])) ? sizeof($this->tpldata[$blockname]) : 0; - $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; - - // Assign S_FIRST_ROW - if (!$s_row_count) - { - $vararray['S_FIRST_ROW'] = true; - } + $vararray['S_FIRST_ROW'] = true; + } - // Assign S_BLOCK_NAME - $vararray['S_BLOCK_NAME'] = $blockname; + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $name; - // We always assign S_LAST_ROW and remove the entry before - $vararray['S_LAST_ROW'] = true; - if ($s_row_count > 0) - { - unset($this->tpldata[$blockname][($s_row_count - 1)]['S_LAST_ROW']); - } - - // Add a new iteration to this block with the variable assignments we were given. - $this->tpldata[$blockname][] = $vararray; + // Now the tricky part, we always assign S_LAST_ROW and remove the entry before + // This is much more clever than going through the complete template data on display (phew) + $vararray['S_LAST_ROW'] = true; + if ($s_row_count > 0) + { + unset($block[$name][($s_row_count - 1)]['S_LAST_ROW']); } + // Now we add the block that we're actually assigning to. + // We're adding a new iteration to this block with the given + // variable assignments. + $block[$name][] = $vararray; + return true; } @@ -264,57 +256,116 @@ class context } /** - * Find the index for a specified key in the innermost specified block - * - * @param string $blockname the blockname, for example 'loop' - * @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 search for] + * Retrieve key variable pairs from the specified block * - * If key is false the position is set to 0 - * If key is true the position is set to the last entry - * - * @return mixed false if not found, index position otherwise; be sure to test with === + * @param string $blockname Name of block to retrieve $vararray from + * @param array $vararray An array of variable names, empty array retrieves all vars + * @return array of hashes with variable name as key and retrieved value or null as value */ - public function find_key_index($blockname, $key) + public function retrieve_block_vars($blockname, array $vararray) { // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; + $blockcount = count($blocks) - 1; $block = $this->tpldata; - for ($i = 0; $i < $blockcount; $i++) + for ($i = 0; $i <= $blockcount; $i++) { if (($pos = strpos($blocks[$i], '[')) !== false) { $name = substr($blocks[$i], 0, $pos); + if (empty($block[$name])) + { + return array(); + } + if (strpos($blocks[$i], '[]') === $pos) { - $index = sizeof($block[$name]) - 1; + $index = count($block[$name]) - 1; } else { - $index = min((int) substr($blocks[$i], $pos + 1, -1), sizeof($block[$name]) - 1); + $index = min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1); } } else { $name = $blocks[$i]; - $index = sizeof($block[$name]) - 1; + if (empty($block[$name])) + { + return array(); + } + + $index = count($block[$name]) - 1; } + $block = $block[$name]; + $block = $block[$index]; + } + + $result = array(); + if ($vararray === array()) + { + // The calculated vars that depend on the block position are excluded from the complete block returned results + $excluded_vars = array('S_FIRST_ROW', 'S_LAST_ROW', 'S_BLOCK_NAME', 'S_NUM_ROWS', 'S_ROW_COUNT', 'S_ROW_NUM'); + + foreach ($block as $varname => $varvalue) + { + if ($varname === strtoupper($varname) && !is_array($varvalue) && !in_array($varname, $excluded_vars)) + { + $result[$varname] = $varvalue; + } + } + } + else + { + foreach ($vararray as $varname) + { + $result[$varname] = isset($block[$varname]) ? $block[$varname] : null; + } + } + return $result; + } + + /** + * Find the index for a specified key in the innermost specified block + * + * @param string $blockname the blockname, for example 'loop' + * @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 search for] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @return mixed false if not found, index position otherwise; be sure to test with === + */ + public function find_key_index($blockname, $key) + { + // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 + $blocks = explode('.', $blockname); + $blockcount = count($blocks) - 1; + + $block = $this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + $pos = strpos($blocks[$i], '['); + $name = ($pos !== false) ? substr($blocks[$i], 0, $pos) : $blocks[$i]; + if (!isset($block[$name])) { return false; } - $block = $block[$name]; - if (!isset($block[$index])) + + $index = (!$pos || strpos($blocks[$i], '[]') === $pos) ? (count($block[$name]) - 1) : (min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1)); + + if (!isset($block[$name][$index])) { return false; } - $block = $block[$index]; + $block = $block[$name][$index]; } if (!isset($block[$blocks[$i]])) @@ -324,9 +375,9 @@ class context $block = $block[$blocks[$i]]; // Traverse the last block // Change key to zero (change first position) if false and to last position if true - if ($key === false || $key === true) + if (is_bool($key)) { - return ($key === false) ? 0 : sizeof($block) - 1; + return (!$key) ? 0 : count($block) - 1; } // Get correct position if array given @@ -343,7 +394,7 @@ class context } } - return (is_int($key) && ((0 <= $key) && ($key < sizeof($block)))) ? $key : false; + return (is_int($key) && ((0 <= $key) && ($key < count($block)))) ? $key : false; } /** @@ -363,10 +414,11 @@ class context * 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') + * @param string $mode Mode to execute (valid modes are 'insert', 'change' and 'delete') * * 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). + * If delete, the vararray is ignored, and the block at the given position (counting from zero) is removed. * * 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) @@ -379,7 +431,7 @@ class context // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; + $blockcount = count($blocks) - 1; $block = &$this->tpldata; for ($i = 0; $i < $blockcount; $i++) @@ -390,17 +442,17 @@ class context if (strpos($blocks[$i], '[]') === $pos) { - $index = sizeof($block[$name]) - 1; + $index = count($block[$name]) - 1; } else { - $index = min((int) substr($blocks[$i], $pos + 1, -1), sizeof($block[$name]) - 1); + $index = min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1); } } else { $name = $blocks[$i]; - $index = sizeof($block[$name]) - 1; + $index = count($block[$name]) - 1; } $block = &$block[$name]; $block = &$block[$index]; @@ -422,7 +474,7 @@ class context // Change key to zero (change first position) if false and to last position if true if ($key === false || $key === true) { - $key = ($key === false) ? 0 : sizeof($block); + $key = ($key === false) ? 0 : count($block); } // Get correct position if array given @@ -452,9 +504,9 @@ class context if ($mode == 'insert') { // Make sure we are not exceeding the last iteration - if ($key >= sizeof($block)) + if ($key >= count($block)) { - $key = sizeof($block); + $key = count($block); unset($block[($key - 1)]['S_LAST_ROW']); $vararray['S_LAST_ROW'] = true; } @@ -469,7 +521,7 @@ class context $vararray['S_BLOCK_NAME'] = $name; // Re-position template blocks - for ($i = sizeof($block); $i > $key; $i--) + for ($i = count($block); $i > $key; $i--) { $block[$i] = $block[$i-1]; @@ -487,12 +539,12 @@ class context if ($mode == 'change') { // If key is out of bounds, do not change anything - if ($key > sizeof($block) || $key < 0) + if ($key > count($block) || $key < 0) { return false; } - if ($key == sizeof($block)) + if ($key == count($block)) { $key--; } @@ -502,6 +554,45 @@ class context return true; } + // Delete Block + if ($mode == 'delete') + { + // If we are exceeding last iteration, do not delete anything + if ($key > count($block) || $key < 0) + { + return false; + } + + // If we are positioned at the end, we remove the last element + if ($key == count($block)) + { + $key--; + } + + // We are deleting the last element in the block, so remove the block + if (count($block) === 1) + { + $block = null; // unset($block); does not work on references + return true; + } + + // Re-position template blocks + for ($i = $key; $i < count($block)-1; $i++) + { + $block[$i] = $block[$i+1]; + $block[$i]['S_ROW_COUNT'] = $block[$i]['S_ROW_NUM'] = $i; + } + + // Remove the last element + unset($block[$i]); + + // Set first and last elements again, in case they were removed + $block[0]['S_FIRST_ROW'] = true; + $block[count($block)-1]['S_LAST_ROW'] = true; + + return true; + } + return false; } @@ -518,13 +609,13 @@ class context { // Nested block. $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; + $blockcount = count($blocks) - 1; $str = &$this->tpldata; for ($i = 0; $i < $blockcount; $i++) { $str = &$str[$blocks[$i]]; - $str = &$str[sizeof($str) - 1]; + $str = &$str[count($str) - 1]; } unset($str[$blocks[$blockcount]]); diff --git a/phpBB/phpbb/template/exception/user_object_not_available.php b/phpBB/phpbb/template/exception/user_object_not_available.php new file mode 100644 index 0000000000..62fd2743c1 --- /dev/null +++ b/phpBB/phpbb/template/exception/user_object_not_available.php @@ -0,0 +1,22 @@ +<?php +/** + * + * This file is part of the phpBB Forum Software package. + * + * @copyright (c) phpBB Limited <https://www.phpbb.com> + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\template\exception; + +/** + * This exception is thrown when the user object was not set but it is required by the called method + */ +class user_object_not_available extends \phpbb\exception\runtime_exception +{ + +} diff --git a/phpBB/phpbb/template/template.php b/phpBB/phpbb/template/template.php index 9e3d658ca8..df83d5bc43 100644 --- a/phpBB/phpbb/template/template.php +++ b/phpBB/phpbb/template/template.php @@ -128,6 +128,22 @@ interface template public function append_var($varname, $varval); /** + * Retrieve multiple template values + * + * @param array $vararray An array with variable names + * @return array A hash of variable name => value pairs (value is null if not set) + */ + public function retrieve_vars(array $vararray); + + /** + * Retreive a single scalar value from a single key. + * + * @param string $varname Variable name + * @return mixed Variable value, or null if not set + */ + public function retrieve_var($varname); + + /** * 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 @@ -144,6 +160,14 @@ interface template public function assign_block_vars_array($blockname, array $block_vars_array); /** + * Retrieve variable values from an specified block + * @param string $blockname Name of block to retrieve $vararray from + * @param array $vararray An array with variable names, empty array gets all vars + * @return array A hash of variable name => value pairs (value is null if not set) + */ + public function retrieve_block_vars($blockname, array $vararray); + + /** * Change already assigned key variable pair (one-dimensional - single loop entry) * * An example of how to use this function: @@ -160,10 +184,11 @@ interface template * 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') + * @param string $mode Mode to execute (valid modes are 'insert', 'change' and 'delete') * * 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). + * If delete, the vararray is ignored, and the block at the given position (counting from zero) is removed. * * 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) diff --git a/phpBB/phpbb/template/twig/environment.php b/phpBB/phpbb/template/twig/environment.php index 476ffd935e..ac4b16e457 100644 --- a/phpBB/phpbb/template/twig/environment.php +++ b/phpBB/phpbb/template/twig/environment.php @@ -13,17 +13,28 @@ namespace phpbb\template\twig; +use phpbb\template\assets_bag; + class environment extends \Twig_Environment { /** @var \phpbb\config\config */ protected $phpbb_config; + /** @var \phpbb\filesystem\filesystem */ + protected $filesystem; + /** @var \phpbb\path_helper */ protected $phpbb_path_helper; + /** @var \Symfony\Component\DependencyInjection\ContainerInterface */ + protected $container; + /** @var \phpbb\extension\manager */ protected $extension_manager; + /** @var \phpbb\event\dispatcher_interface */ + protected $phpbb_dispatcher; + /** @var string */ protected $phpbb_root_path; @@ -33,26 +44,43 @@ class environment extends \Twig_Environment /** @var array **/ protected $namespace_look_up_order = array('__main__'); + /** @var assets_bag */ + protected $assets_bag; + /** * Constructor * * @param \phpbb\config\config $phpbb_config The phpBB configuration + * @param \phpbb\filesystem\filesystem $filesystem * @param \phpbb\path_helper $path_helper phpBB path helper + * @param string $cache_path The path to the cache directory * @param \phpbb\extension\manager $extension_manager phpBB extension manager * @param \Twig_LoaderInterface $loader Twig loader interface + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object * @param array $options Array of options to pass to Twig */ - public function __construct($phpbb_config, \phpbb\path_helper $path_helper, \phpbb\extension\manager $extension_manager = null, \Twig_LoaderInterface $loader = null, $options = array()) + public function __construct(\phpbb\config\config $phpbb_config, \phpbb\filesystem\filesystem $filesystem, \phpbb\path_helper $path_helper, $cache_path, \phpbb\extension\manager $extension_manager = null, \Twig_LoaderInterface $loader = null, \phpbb\event\dispatcher_interface $phpbb_dispatcher = null, $options = array()) { $this->phpbb_config = $phpbb_config; + $this->filesystem = $filesystem; $this->phpbb_path_helper = $path_helper; $this->extension_manager = $extension_manager; + $this->phpbb_dispatcher = $phpbb_dispatcher; $this->phpbb_root_path = $this->phpbb_path_helper->get_phpbb_root_path(); $this->web_root_path = $this->phpbb_path_helper->get_web_root_path(); - return parent::__construct($loader, $options); + $this->assets_bag = new assets_bag(); + + $options = array_merge(array( + 'cache' => (defined('IN_INSTALL')) ? false : $cache_path, + 'debug' => false, + 'auto_reload' => (bool) $this->phpbb_config['load_tplcompile'], + 'autoescape' => false, + ), $options); + + parent::__construct($loader, $options); } /** @@ -78,16 +106,26 @@ class environment extends \Twig_Environment } /** - * Get the phpBB root path - * - * @return string - */ + * Get the phpBB root path + * + * @return string + */ public function get_phpbb_root_path() { return $this->phpbb_root_path; } /** + * Get the filesystem object + * + * @return \phpbb\filesystem\filesystem + */ + public function get_filesystem() + { + return $this->filesystem; + } + + /** * Get the web root path * * @return string @@ -108,6 +146,16 @@ class environment extends \Twig_Environment } /** + * Gets the assets bag + * + * @return assets_bag + */ + public function get_assets_bag() + { + return $this->assets_bag; + } + + /** * Get the namespace look up order * * @return array @@ -131,6 +179,84 @@ class environment extends \Twig_Environment } /** + * {@inheritdoc} + */ + public function render($name, array $context = []) + { + return $this->display_with_assets($name, $context); + } + + /** + * {@inheritdoc} + */ + public function display($name, array $context = []) + { + echo $this->display_with_assets($name, $context); + } + + /** + * {@inheritdoc} + */ + private function display_with_assets($name, array $context = []) + { + $placeholder_salt = unique_id(); + + if (array_key_exists('definition', $context)) + { + $context['definition']->set('SCRIPTS', '__SCRIPTS_' . $placeholder_salt . '__'); + $context['definition']->set('STYLESHEETS', '__STYLESHEETS_' . $placeholder_salt . '__'); + } + + /** + * Allow changing the template output stream before rendering + * + * @event core.twig_environment_render_template_before + * @var array context Array with template variables + * @var string name The template name + * @since 3.2.1-RC1 + */ + if ($this->phpbb_dispatcher) + { + $vars = array('context', 'name'); + extract($this->phpbb_dispatcher->trigger_event('core.twig_environment_render_template_before', compact($vars))); + } + + $output = parent::render($name, $context); + + /** + * Allow changing the template output stream after rendering + * + * @event core.twig_environment_render_template_after + * @var array context Array with template variables + * @var string name The template name + * @var string output Rendered template output stream + * @since 3.2.1-RC1 + */ + if ($this->phpbb_dispatcher) + { + $vars = array('context', 'name', 'output'); + extract($this->phpbb_dispatcher->trigger_event('core.twig_environment_render_template_after', compact($vars))); + } + + return $this->inject_assets($output, $placeholder_salt); + } + + /** + * Injects the assets (from INCLUDECSS/JS) in the output. + * + * @param string $output + * + * @return string + */ + private function inject_assets($output, $placeholder_salt) + { + $output = str_replace('__STYLESHEETS_' . $placeholder_salt . '__', $this->assets_bag->get_stylesheets_content(), $output); + $output = str_replace('__SCRIPTS_' . $placeholder_salt . '__', $this->assets_bag->get_scripts_content(), $output); + + return $output; + } + + /** * Loads a template by name. * * @param string $name The template name diff --git a/phpBB/phpbb/template/twig/extension.php b/phpBB/phpbb/template/twig/extension.php index d5b14129b5..f6f8e03ca2 100644 --- a/phpBB/phpbb/template/twig/extension.php +++ b/phpBB/phpbb/template/twig/extension.php @@ -18,20 +18,20 @@ class extension extends \Twig_Extension /** @var \phpbb\template\context */ protected $context; - /** @var \phpbb\user */ - protected $user; + /** @var \phpbb\language\language */ + protected $language; /** * Constructor * * @param \phpbb\template\context $context - * @param \phpbb\user $user + * @param \phpbb\language\language $language * @return \phpbb\template\twig\extension */ - public function __construct(\phpbb\template\context $context, $user) + public function __construct(\phpbb\template\context $context, $language) { $this->context = $context; - $this->user = $user; + $this->language = $language; } /** @@ -71,6 +71,7 @@ class extension extends \Twig_Extension { return array( new \Twig_SimpleFilter('subset', array($this, 'loop_subset'), array('needs_environment' => true)), + // @deprecated 3.2.0 Uses twig's JS escape method instead of addslashes new \Twig_SimpleFilter('addslashes', 'addslashes'), ); } @@ -145,7 +146,7 @@ class extension extends \Twig_Extension // 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; + $start = ($start < 0) ? count($item) + $start : $start; $end = $end - $start; } @@ -171,14 +172,14 @@ class extension extends \Twig_Extension $context_vars = $this->context->get_root_ref(); - if (isset($context_vars['L_' . $key])) + if (is_string($key) && isset($context_vars['L_' . $key])) { return $context_vars['L_' . $key]; } - // LA_ is transformed into lang(\'$1\')|addslashes, so we should not + // LA_ is transformed into lang(\'$1\')|escape('js'), so we should not // need to check for it - return call_user_func_array(array($this->user, 'lang'), $args); + return call_user_func_array(array($this->language, 'lang'), $args); } } diff --git a/phpBB/phpbb/template/twig/extension/routing.php b/phpBB/phpbb/template/twig/extension/routing.php new file mode 100644 index 0000000000..829ce738eb --- /dev/null +++ b/phpBB/phpbb/template/twig/extension/routing.php @@ -0,0 +1,43 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\extension; + +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +class routing extends RoutingExtension +{ + /** @var \phpbb\controller\helper */ + protected $helper; + + /** + * Constructor + * + * @param \phpbb\routing\helper $helper + */ + public function __construct(\phpbb\routing\helper $helper) + { + $this->helper = $helper; + } + + public function getPath($name, $parameters = array(), $relative = false) + { + return $this->helper->route($name, $parameters, true, false, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function getUrl($name, $parameters = array(), $schemeRelative = false) + { + return $this->helper->route($name, $parameters, true, false, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); + } +} diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index c5dc7273ba..d0bcfa615e 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -15,8 +15,21 @@ namespace phpbb\template\twig; class lexer extends \Twig_Lexer { + public function set_environment(\Twig_Environment $env) + { + $this->env = $env; + } + public function tokenize($code, $filename = null) { + // Handle \Twig_Source format input + if ($code instanceof \Twig_Source) + { + $source = $code; + $code = $source->getCode(); + $filename = $source->getName(); + } + // Our phpBB tags // Commented out tokens are handled separately from the main replace $phpbb_tags = array( @@ -112,15 +125,16 @@ class lexer extends \Twig_Lexer // 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 escaped language variables, {LA_VARNAME}, with Twig style, {{ lang('NAME')|escape('js') }} + // Appends any filters after lang(), but before escape('js') + $code = preg_replace('#{LA_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2|escape(\'js\') }}', $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); + // Tokenize \Twig_Source instance + return parent::tokenize(new \Twig_Source($code, $filename)); } /** diff --git a/phpBB/phpbb/template/twig/loader.php b/phpBB/phpbb/template/twig/loader.php index 139a413b70..c13e3ee298 100644 --- a/phpBB/phpbb/template/twig/loader.php +++ b/phpBB/phpbb/template/twig/loader.php @@ -21,6 +21,24 @@ class loader extends \Twig_Loader_Filesystem protected $safe_directories = array(); /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * Constructor + * + * @param \phpbb\filesystem\filesystem_interface $filesystem + * @param string|array $paths + */ + public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $paths = array()) + { + $this->filesystem = $filesystem; + + parent::__construct($paths, $this->filesystem->realpath(dirname(__FILE__))); + } + + /** * Set safe directories * * @param array $directories Array of directories that are safe (empty to clear) @@ -49,7 +67,7 @@ class loader extends \Twig_Loader_Filesystem */ public function addSafeDirectory($directory) { - $directory = phpbb_realpath($directory); + $directory = $this->filesystem->realpath($directory); if ($directory !== false) { @@ -83,6 +101,16 @@ class loader extends \Twig_Loader_Filesystem } /** + * Adds a realpath call to fix a BC break in Twig 1.26 (https://github.com/twigphp/Twig/issues/2145) + * + * {@inheritdoc} + */ + public function addPath($path, $namespace = self::MAIN_NAMESPACE) + { + return parent::addPath($this->filesystem->realpath($path), $namespace); + } + + /** * Find the template * * Override for Twig_Loader_Filesystem::findTemplate to add support @@ -119,7 +147,7 @@ class loader extends \Twig_Loader_Filesystem // can now check if we're within a "safe" directory // Find the real path of the directory the file is in - $directory = phpbb_realpath(dirname($file)); + $directory = $this->filesystem->realpath(dirname($file)); if ($directory === false) { diff --git a/phpBB/phpbb/template/twig/node/event.php b/phpBB/phpbb/template/twig/node/event.php index b765bde98d..11fdb75247 100644 --- a/phpBB/phpbb/template/twig/node/event.php +++ b/phpBB/phpbb/template/twig/node/event.php @@ -46,7 +46,7 @@ class event extends \Twig_Node { $ext_namespace = str_replace('/', '_', $ext_namespace); - if (defined('DEBUG')) + if ($this->environment->isDebug()) { // If debug mode is enabled, lets check for new/removed EVENT // templates on page load rather than at compile. This is @@ -58,7 +58,7 @@ class event extends \Twig_Node ; } - if (defined('DEBUG') || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) + if ($this->environment->isDebug() || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) { $compiler ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") @@ -70,7 +70,7 @@ class event extends \Twig_Node ; } - if (defined('DEBUG')) + if ($this->environment->isDebug()) { $compiler ->outdent() diff --git a/phpBB/phpbb/template/twig/node/includeasset.php b/phpBB/phpbb/template/twig/node/includeasset.php index 15195a226b..12034b7820 100644 --- a/phpBB/phpbb/template/twig/node/includeasset.php +++ b/phpBB/phpbb/template/twig/node/includeasset.php @@ -39,7 +39,7 @@ abstract class includeasset extends \Twig_Node ->write("\$asset_file = ") ->subcompile($this->getNode('expr')) ->raw(";\n") - ->write("\$asset = new \phpbb\\template\\asset(\$asset_file, \$this->getEnvironment()->get_path_helper());\n") + ->write("\$asset = new \phpbb\\template\\asset(\$asset_file, \$this->getEnvironment()->get_path_helper(), \$this->getEnvironment()->get_filesystem());\n") ->write("if (substr(\$asset_file, 0, 2) !== './' && \$asset->is_relative()) {\n") ->indent() ->write("\$asset_path = \$asset->get_path();") @@ -49,33 +49,23 @@ abstract class includeasset extends \Twig_Node ->write("\$local_file = \$this->getEnvironment()->findTemplate(\$asset_path);\n") ->write("\$asset->set_path(\$local_file, true);\n") ->outdent() - ->write("\$asset->add_assets_version('{$config['assets_version']}');\n") - ->write("\$asset_file = \$asset->get_url();\n") ->write("}\n") ->outdent() ->write("}\n") - ->write("\$context['definition']->append('{$this->get_definition_name()}', '") - ; - - $this->append_asset($compiler); - - $compiler - ->raw("\n');\n") + ->write("\n") + ->write("if (\$asset->is_relative()) {\n") + ->indent() + ->write("\$asset->add_assets_version('{$config['assets_version']}');\n") + ->outdent() + ->write("}\n") + ->write("\$this->getEnvironment()->get_assets_bag()->add_{$this->get_setters_name()}(\$asset);") ; } /** - * Get the definition name - * - * @return string (e.g. 'SCRIPTS') - */ - abstract public function get_definition_name(); - - /** - * Append the output code for the asset + * Get the name of the assets bag setter * - * @param \Twig_Compiler A Twig_Compiler instance - * @return null + * @return string (e.g. 'script') */ - abstract protected function append_asset(\Twig_Compiler $compiler); + abstract public function get_setters_name(); } diff --git a/phpBB/phpbb/template/twig/node/includecss.php b/phpBB/phpbb/template/twig/node/includecss.php index 2dac154036..2e97d4972d 100644 --- a/phpBB/phpbb/template/twig/node/includecss.php +++ b/phpBB/phpbb/template/twig/node/includecss.php @@ -18,20 +18,8 @@ class includecss extends \phpbb\template\twig\node\includeasset /** * {@inheritdoc} */ - public function get_definition_name() + public function get_setters_name() { - return 'STYLESHEETS'; - } - - /** - * {@inheritdoc} - */ - public function append_asset(\Twig_Compiler $compiler) - { - $compiler - ->raw("<link href=\"' . ") - ->raw("\$asset_file . '\"") - ->raw(' rel="stylesheet" type="text/css" media="screen" />') - ; + return 'stylesheet'; } } diff --git a/phpBB/phpbb/template/twig/node/includejs.php b/phpBB/phpbb/template/twig/node/includejs.php index 0f67f9ff60..505b49757b 100644 --- a/phpBB/phpbb/template/twig/node/includejs.php +++ b/phpBB/phpbb/template/twig/node/includejs.php @@ -18,22 +18,8 @@ class includejs extends \phpbb\template\twig\node\includeasset /** * {@inheritdoc} */ - public function get_definition_name() + public function get_setters_name() { - return 'SCRIPTS'; - } - - /** - * {@inheritdoc} - */ - 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") - ; + return 'script'; } } diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index d1bbb2b55a..f322778eda 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -13,6 +13,8 @@ namespace phpbb\template\twig; +use phpbb\template\exception\user_object_not_available; + /** * Twig Template class. */ @@ -76,11 +78,14 @@ class twig extends \phpbb\template\base * * @param \phpbb\path_helper $path_helper * @param \phpbb\config\config $config - * @param \phpbb\user $user * @param \phpbb\template\context $context template context + * @param \phpbb\template\twig\environment $twig_environment + * @param string $cache_path + * @param \phpbb\user|null $user + * @param array|\ArrayAccess $extensions * @param \phpbb\extension\manager $extension_manager extension manager, if null then template events will not be invoked */ - public function __construct(\phpbb\path_helper $path_helper, $config, $user, \phpbb\template\context $context, \phpbb\extension\manager $extension_manager = null) + public function __construct(\phpbb\path_helper $path_helper, $config, \phpbb\template\context $context, \phpbb\template\twig\environment $twig_environment, $cache_path, \phpbb\user $user = null, $extensions = array(), \phpbb\extension\manager $extension_manager = null) { $this->path_helper = $path_helper; $this->phpbb_root_path = $path_helper->get_phpbb_root_path(); @@ -89,41 +94,14 @@ class twig extends \phpbb\template\base $this->user = $user; $this->context = $context; $this->extension_manager = $extension_manager; + $this->cachepath = $cache_path; + $this->twig = $twig_environment; - $this->cachepath = $this->phpbb_root_path . 'cache/twig/'; - - // Initiate the loader, __main__ namespace paths will be setup later in set_style_names() - $loader = new \phpbb\template\twig\loader(''); - - $this->twig = new \phpbb\template\twig\environment( - $this->config, - $this->path_helper, - $this->extension_manager, - $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 - ) - ); - - if (defined('DEBUG')) + foreach ($extensions as $extension) { - $this->twig->addExtension(new \Twig_Extension_Debug()); + $this->twig->addExtension($extension); } - $lexer = new \phpbb\template\twig\lexer($this->twig); - - $this->twig->setLexer($lexer); - // Add admin namespace if ($this->path_helper->get_adm_relative_path() !== null && is_dir($this->phpbb_root_path . $this->path_helper->get_adm_relative_path() . 'style/')) { @@ -150,9 +128,16 @@ class twig extends \phpbb\template\base * Get the style tree of the style preferred by the current user * * @return array Style tree, most specific first + * + * @throws \phpbb\template\exception\user_object_not_available When user service was not set */ public function get_user_style() { + if ($this->user === null) + { + throw new user_object_not_available(); + } + $style_list = array( $this->user->style['style_path'], ); @@ -368,14 +353,24 @@ class twig extends \phpbb\template\base $context_vars['.'][0], // To get normal vars array( 'definition' => new \phpbb\template\twig\definition(), - 'user' => $this->user, 'loops' => $context_vars, // To get loops ) ); + if ($this->user instanceof \phpbb\user) + { + $vars['user'] = $this->user; + } + // cleanup unset($vars['loops']['.']); + // Inject in the main context the value added by assign_block_vars() to be able to use directly the Twig loops. + foreach ($vars['loops'] as $key => &$value) + { + $vars[$key] = $value; + } + return $vars; } |