diff --git a/phpBB/phpbb/template/twig/twig.php b/phpBB/phpbb/template/twig/twig.php index e8b584c0c3..6f442401ff 100644 --- a/phpBB/phpbb/template/twig/twig.php +++ b/phpBB/phpbb/template/twig/twig.php @@ -21,71 +21,59 @@ use phpbb\template\exception\user_object_not_available; class twig extends \phpbb\template\base { /** - * Path of the cache directory for the template - * - * Cannot be changed during runtime. - * - * @var string - */ + * Path of the cache directory for the template + * Cannot be changed during runtime. + * + * @var string + */ private $cachepath = ''; - /** - * phpBB path helper - * @var \phpbb\path_helper - */ + /** @var \phpbb\path_helper */ protected $path_helper; - /** - * phpBB root path - * @var string - */ + /** @var string phpBB root path */ protected $phpbb_root_path; - /** - * PHP file extension - * @var string - */ + /** @var string php File extension */ protected $php_ext; - /** - * phpBB config instance - * @var \phpbb\config\config - */ + /** @var \phpbb\config\config */ protected $config; - /** - * Current user - * @var \phpbb\user - */ + /** @var \phpbb\user */ protected $user; - /** - * Extension manager. - * - * @var \phpbb\extension\manager - */ + /** @var \phpbb\extension\manager */ protected $extension_manager; - /** - * Twig Environment - * - * @var \Twig\Environment - */ + /** @var environment */ protected $twig; + /** @var loader */ + protected $loader; + /** * Constructor. * - * @param \phpbb\path_helper $path_helper - * @param \phpbb\config\config $config - * @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 + * @param \phpbb\path_helper $path_helper Path helper object + * @param \phpbb\config\config $config Config object + * @param \phpbb\template\context $context Template context + * @param environment $twig_environment Twig environment + * @param string $cache_path Template's cache directory path + * @param null|\phpbb\user $user User object + * @param array|\ArrayAccess $extensions Template extensions + * @param null|\phpbb\extension\manager $extension_manager If null then template events will not be invoked */ - 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) + public function __construct( + \phpbb\path_helper $path_helper, + \phpbb\config\config $config, + \phpbb\template\context $context, + environment $twig_environment, + $cache_path, + \phpbb\user $user = null, + $extensions = [], + \phpbb\extension\manager $extension_manager = null + ) { $this->path_helper = $path_helper; $this->phpbb_root_path = $path_helper->get_phpbb_root_path(); @@ -96,6 +84,7 @@ class twig extends \phpbb\template\base $this->extension_manager = $extension_manager; $this->cachepath = $cache_path; $this->twig = $twig_environment; + $this->loader = $twig_environment->getLoader(); foreach ($extensions as $extension) { @@ -105,7 +94,7 @@ class twig extends \phpbb\template\base // 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/')) { - $this->twig->getLoader()->setPaths($this->phpbb_root_path . $this->path_helper->get_adm_relative_path() . 'style/', 'admin'); + $this->loader->setPaths($this->phpbb_root_path . $this->path_helper->get_adm_relative_path() . 'style/', 'admin'); } } @@ -138,9 +127,9 @@ class twig extends \phpbb\template\base throw new user_object_not_available(); } - $style_list = array( + $style_list = [ $this->user->style['style_path'], - ); + ]; if ($this->user->style['style_parent_id']) { @@ -158,55 +147,61 @@ class twig extends \phpbb\template\base * Default: array('styles') (phpBB's style directory) * @return \phpbb\template\template $this */ - public function set_style($style_directories = array('styles')) + public function set_style($style_directories = ['styles']) { - if ($style_directories !== array('styles') && $this->twig->getLoader()->getPaths('core') === array()) + if ($style_directories !== ['styles'] && $this->loader->getPaths('core') === []) { // We should set up the core styles path since not already setup $this->set_style(); } - $names = $this->get_user_style(); + $paths = []; + // Add 'all' folder to $names array // It allows extensions to load a template file from 'all' folder, // if a style doesn't include it. + $names = $this->get_user_style(); $names[] = 'all'; - $paths = array(); foreach ($style_directories as $directory) { foreach ($names as $name) { - $path = $this->phpbb_root_path . trim($directory, '/') . "/{$name}/"; - $template_path = $path . 'template/'; - $theme_path = $path . 'theme/'; + $path = $this->phpbb_root_path . trim($directory, '/') . "/{$name}/"; + $handle = @opendir($path); + $valid = false; - $is_valid_dir = false; - if (is_dir($template_path)) + if ($handle) { - $is_valid_dir = true; - $paths[] = $template_path; - } - if (is_dir($theme_path)) - { - $is_valid_dir = true; - $paths[] = $theme_path; + while (($file = readdir($handle)) !== false) + { + $dir = $path . $file; + + if ($file[0] !== '.' && is_dir($dir)) + { + $paths[] = $dir; + + $valid = true; + } + } + + closedir($handle); } - if ($is_valid_dir) + if ($valid) { // Add the base style directory as a safe directory - $this->twig->getLoader()->addSafeDirectory($path); + $this->loader->addSafeDirectory($path); } } } // If we're setting up the main phpBB styles directory and the core // namespace isn't setup yet, we will set it up now - if ($style_directories === array('styles') && $this->twig->getLoader()->getPaths('core') === array()) + if ($style_directories === ['styles'] && $this->loader->getPaths('core') === []) { // Set up the core style paths namespace - $this->twig->getLoader()->setPaths($paths, 'core'); + $this->loader->setPaths($paths, 'core'); } $this->set_custom_style($names, $paths); @@ -229,11 +224,11 @@ class twig extends \phpbb\template\base */ public function set_custom_style($names, $paths) { - $paths = (is_string($paths)) ? array($paths) : $paths; - $names = (is_string($names)) ? array($names) : $names; + $paths = (is_string($paths)) ? [$paths] : $paths; + $names = (is_string($names)) ? [$names] : $names; // Set as __main__ namespace - $this->twig->getLoader()->setPaths($paths); + $this->loader->setPaths($paths); // Add all namespaces for all extensions if ($this->extension_manager instanceof \phpbb\extension\manager) @@ -244,7 +239,7 @@ class twig extends \phpbb\template\base { // namespaces cannot contain / $namespace = str_replace('/', '_', $ext_namespace); - $paths = array(); + $paths = []; foreach ($names as $template_dir) { @@ -285,11 +280,11 @@ class twig extends \phpbb\template\base if ($is_valid_dir) { // Add the base style directory as a safe directory - $this->twig->getLoader()->addSafeDirectory($ext_style_path); + $this->loader->addSafeDirectory($ext_style_path); } } - $this->twig->getLoader()->setPaths($paths, $namespace); + $this->loader->setPaths($paths, $namespace); } } @@ -345,10 +340,10 @@ class twig extends \phpbb\template\base $vars = array_merge( $context_vars['.'][0], // To get normal vars - array( + [ 'definition' => new \phpbb\template\twig\definition(), 'loops' => $context_vars, // To get loops - ) + ] ); if ($this->user instanceof \phpbb\user) @@ -373,6 +368,6 @@ class twig extends \phpbb\template\base */ public function get_source_file_for_handle($handle) { - return $this->twig->getLoader()->getCacheKey($this->get_filename_from_handle($handle)); + return $this->loader->getCacheKey($this->get_filename_from_handle($handle)); } } diff --git a/tests/template/twig_test.php b/tests/template/twig_test.php new file mode 100644 index 0000000000..74220a98cb --- /dev/null +++ b/tests/template/twig_test.php @@ -0,0 +1,146 @@ + + * @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\tests\template; + +use phpbb\filesystem\helper as filesystem_helper; +use phpbb\template\twig\twig; + +class twig_test extends \phpbb_test_case +{ + /** @var twig */ + public $twig; + /** + * @var string + */ + private $template_path; + /** + * @var twig + */ + private $template; + /** + * @var \phpbb\user + */ + private $user; + /** + * @var \phpbb\language\language + */ + private $lang; + + protected function setUp(): void + { + global $phpbb_root_path, $phpEx; + + $config = new \phpbb\config\config([]); + $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); + $this->lang = $lang = new \phpbb\language\language($lang_loader); + $user = new \phpbb\user($lang, '\phpbb\datetime'); + $this->user = $user; + + $filesystem = new \phpbb\filesystem\filesystem(); + + $path_helper = new \phpbb\path_helper( + new \phpbb\symfony_request( + new \phpbb_mock_request() + ), + $this->createMock('\phpbb\request\request'), + $phpbb_root_path, + $phpEx + ); + + $this->template_path = 'tests/template/templates'; + + $cache_path = $phpbb_root_path . 'cache/twig'; + $context = new \phpbb\template\context(); + $loader = new \phpbb\template\twig\loader(''); + $twig = new \phpbb\template\twig\environment( + $config, + $filesystem, + $path_helper, + $cache_path, + null, + $loader, + new \phpbb\event\dispatcher(), + [ + 'cache' => false, + 'debug' => false, + 'auto_reload' => true, + 'autoescape' => false, + ] + ); + $this->template = new \phpbb\template\twig\twig($path_helper, $config, $context, $twig, $cache_path, $this->user, array(new \phpbb\template\twig\extension($context, $twig, $this->user))); + $twig->setLexer(new \phpbb\template\twig\lexer($twig)); + } + + public function test_get_user_style_invalid_user() + { + // Add closure to override user method + $set_user_closure = function ($user) { + $this->user = $user; + }; + + $run_set_user_closure = $set_user_closure->bindTo($this->template, get_class($this->template)); + $run_set_user_closure(null); + + $this->expectException('\phpbb\template\exception\user_object_not_available'); + $this->template->get_user_style(); + $run_set_user_closure($this->user); + } + + public function data_get_user_style(): array + { + return [ + [['style_path' => 'prosilver', 'style_parent_id' => 0], ['prosilver']], + [['style_path' => 'prosilver_se', 'style_parent_id' => 5, 'style_parent_tree' => 'prosilver'], ['prosilver_se', 'prosilver']], + ]; + } + + /** + * @dataProvider data_get_user_style + */ + public function test_get_user_style($user_style, $expected) + { + $this->user->style = $user_style; + $this->assertEquals($expected, $this->template->get_user_style()); + } + + public function test_set_style() + { + global $phpbb_root_path; + + // User style is left empty on purpose to see template as valid directory + $tests_template_relative_path = '../tests/template'; + $test_template_absolute_path = filesystem_helper::realpath($phpbb_root_path . trim($tests_template_relative_path, '/')); + + // Get loader instance + $template_reflection = new \ReflectionObject($this->template); + $loader_reflection = $template_reflection->getProperty('loader'); + $loader_reflection->setAccessible(true); + /** @var \phpbb\template\twig\loader $loader */ + $loader = $loader_reflection->getValue($this->template); + + // set_style() not called yet + $this->assertEmpty($loader->getSafeDirectories()); + + // set_style() to add default elements + $this->user->style = ['style_path' => '', 'style_parent_id' => 0]; + $this->template->set_style(); + $safe_directories = $loader->getSafeDirectories(); + $this->assertFalse(in_array($test_template_absolute_path, $safe_directories)); + $this->assertFalse(empty($safe_directories)); + + // set_style() with tests template folder + $this->template->set_style([$tests_template_relative_path]); + $this->assertTrue(in_array($test_template_absolute_path, $loader->getSafeDirectories())); + } +}