diff --git a/phpBB/phpbb/template/twig/environment.php b/phpBB/phpbb/template/twig/environment.php index a5af650c66..68351076fd 100644 --- a/phpBB/phpbb/template/twig/environment.php +++ b/phpBB/phpbb/template/twig/environment.php @@ -161,6 +161,16 @@ class environment extends \Twig\Environment return $this->assets_bag; } + /** + * Gets the event dispatcher instance + * + * @return dispatcher_interface + */ + public function get_phpbb_dispatcher() + { + return $this->phpbb_dispatcher; + } + /** * Get the namespace look up order * diff --git a/phpBB/phpbb/template/twig/node/event.php b/phpBB/phpbb/template/twig/node/event.php index 4eddcbcf38..1334fe2ad2 100644 --- a/phpBB/phpbb/template/twig/node/event.php +++ b/phpBB/phpbb/template/twig/node/event.php @@ -24,9 +24,13 @@ class event extends \Twig\Node\Node /** @var \phpbb\template\twig\environment */ protected $environment; - public function __construct(\Twig\Node\Expression\AbstractExpression $expr, \phpbb\template\twig\environment $environment, $lineno, $tag = null) + /** @var array */ + protected $template_event_priority_array; + + public function __construct(\Twig\Node\Expression\AbstractExpression $expr, \phpbb\template\twig\environment $environment, $lineno, $tag = null, $template_event_priority_array = []) { $this->environment = $environment; + $this->template_event_priority_array = $template_event_priority_array; parent::__construct(array('expr' => $expr), array(), $lineno, $tag); } @@ -42,10 +46,20 @@ class event extends \Twig\Node\Node $location = $this->listener_directory . $this->getNode('expr')->getAttribute('name'); + $template_event_listeners = []; + + // Group and sort extension template events in according to their priority (0 by default if not set) foreach ($this->environment->get_phpbb_extensions() as $ext_namespace => $ext_path) { $ext_namespace = str_replace('/', '_', $ext_namespace); + $priority_key = intval($this->template_event_priority_array[$ext_namespace][$location] ?? 0); + $template_event_listeners[$priority_key][] = $ext_namespace; + } + krsort($template_event_listeners); + $template_event_listeners = array_merge(...$template_event_listeners); + foreach ($template_event_listeners as $ext_namespace) + { if ($this->environment->isDebug()) { // If debug mode is enabled, lets check for new/removed EVENT @@ -54,8 +68,7 @@ class event extends \Twig\Node\Node // purge the cache when a new event template file is added) $compiler ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/{$location}.html')) {\n") - ->indent() - ; + ->indent(); } if ($this->environment->isDebug() || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) @@ -66,16 +79,14 @@ class event extends \Twig\Node\Node // 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(\$this->env->getTemplateClass('@{$ext_namespace}/{$location}.html'), '@{$ext_namespace}/{$location}.html')->display(\$context);\n") - ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") - ; + ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n"); } if ($this->environment->isDebug()) { $compiler ->outdent() - ->write("}\n\n") - ; + ->write("}\n\n"); } } } diff --git a/phpBB/phpbb/template/twig/tokenparser/event.php b/phpBB/phpbb/template/twig/tokenparser/event.php index 7b9742cc95..d5a6cd46e3 100644 --- a/phpBB/phpbb/template/twig/tokenparser/event.php +++ b/phpBB/phpbb/template/twig/tokenparser/event.php @@ -18,6 +18,9 @@ class event extends \Twig\TokenParser\AbstractTokenParser /** @var \phpbb\template\twig\environment */ protected $environment; + /** @var array */ + protected $template_event_priority_array; + /** * Constructor * @@ -26,6 +29,25 @@ class event extends \Twig\TokenParser\AbstractTokenParser public function __construct(\phpbb\template\twig\environment $environment) { $this->environment = $environment; + $phpbb_dispatcher = $this->environment->get_phpbb_dispatcher(); + + $template_event_priority_array = []; + /** + * Allows assigning priority to template event listeners + * + * @event core.twig_event_tokenparser_constructor + * @var array template_event_priority_array Array with template event priority assignments per extension namespace + * + * @since 4.0.0-a1 + */ + if ($phpbb_dispatcher) + { + $vars = ['template_event_priority_array']; + extract($phpbb_dispatcher->trigger_event('core.twig_event_tokenparser_constructor', compact($vars))); + } + + $this->template_event_priority_array = $template_event_priority_array; + unset($template_event_priority_array); } /** @@ -42,7 +64,7 @@ class event extends \Twig\TokenParser\AbstractTokenParser $stream = $this->parser->getStream(); $stream->expect(\Twig\Token::BLOCK_END_TYPE); - return new \phpbb\template\twig\node\event($expr, $this->environment, $token->getLine(), $this->getTag()); + return new \phpbb\template\twig\node\event($expr, $this->environment, $token->getLine(), $this->getTag(), $this->template_event_priority_array); } /** diff --git a/tests/functional/extension_controller_test.php b/tests/functional/extension_controller_test.php index 5c42a2353b..4cc5e2d2a9 100644 --- a/tests/functional/extension_controller_test.php +++ b/tests/functional/extension_controller_test.php @@ -16,8 +16,6 @@ */ class phpbb_functional_extension_controller_test extends phpbb_functional_test_case { - protected $phpbb_extension_manager; - private static $helper; protected static $fixtures = array( diff --git a/tests/functional/extension_global_lang_test.php b/tests/functional/extension_global_lang_test.php index cf2d9dbf29..1fb1d053e3 100644 --- a/tests/functional/extension_global_lang_test.php +++ b/tests/functional/extension_global_lang_test.php @@ -16,8 +16,6 @@ */ class phpbb_functional_extension_global_lang_test extends phpbb_functional_test_case { - protected $phpbb_extension_manager; - private static $helper; protected static $fixtures = array( diff --git a/tests/functional/extension_module_test.php b/tests/functional/extension_module_test.php index 0e56e43c78..08ec7e5012 100644 --- a/tests/functional/extension_module_test.php +++ b/tests/functional/extension_module_test.php @@ -17,8 +17,6 @@ require_once __DIR__ . '/../../phpBB/includes/acp/acp_modules.php'; */ class phpbb_functional_extension_module_test extends phpbb_functional_test_case { - protected $phpbb_extension_manager; - private static $helper; protected static $fixtures = array( diff --git a/tests/functional/extension_permission_lang_test.php b/tests/functional/extension_permission_lang_test.php index 9516e351df..e7dcb4ebcd 100644 --- a/tests/functional/extension_permission_lang_test.php +++ b/tests/functional/extension_permission_lang_test.php @@ -16,8 +16,6 @@ */ class phpbb_functional_extension_permission_lang_test extends phpbb_functional_test_case { - protected $phpbb_extension_manager; - private static $helper; protected static $fixtures = array( diff --git a/tests/functional/extension_template_event_order_test.php b/tests/functional/extension_template_event_order_test.php new file mode 100644 index 0000000000..197efce746 --- /dev/null +++ b/tests/functional/extension_template_event_order_test.php @@ -0,0 +1,105 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +/** + * @group functional + */ +class phpbb_functional_extension_template_event_order_test extends phpbb_functional_test_case +{ + static private $helper; + + static protected $fixtures = [ + './', + ]; + + static public function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + self::$helper = new phpbb_test_case_helpers(__CLASS__); + self::$helper->copy_ext_fixtures(__DIR__ . '/fixtures/ext/', self::$fixtures); + } + + static public function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + + self::$helper->restore_original_ext_dir(); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->purge_cache(); + } + + protected function tearDown(): void + { + $this->uninstall_ext('foo/bar'); + $this->uninstall_ext('foo/foo'); + + parent::tearDown(); + } + + protected static function setup_extensions() + { + return ['foo/bar', 'foo/foo']; + } + + /** + * Check extensions template event listener prioritizing + */ + public function test_different_template_event_priority() + { + global $phpbb_root_path; + + $crawler = self::request('GET', 'index.php'); + $quick_links_menu = $crawler->filter('ul[role="menu"]')->eq(0); + $quick_links_menu_nodes_count = (int) $quick_links_menu->filter('li')->count(); + // Ensure foo/foo template event goes before foo/bar one + $this->assertStringContainsString('FOO_FOO_QUICK_LINK', $quick_links_menu->filter('li')->eq($quick_links_menu_nodes_count - 4)->filter('span')->text()); + $this->assertStringContainsString('FOO_BAR_QUICK_LINK', $quick_links_menu->filter('li')->eq($quick_links_menu_nodes_count - 3)->filter('span')->text()); + + // Change template events order to default, put foo/bar event before foo/foo one + $this->disable_ext('foo/bar'); + $this->disable_ext('foo/foo'); + + $this->assertTrue(copy(__DIR__ . '/fixtures/ext/foo/bar/event/template_event_order_higher.php', $phpbb_root_path . 'ext/foo/bar/event/template_event_order.php')); + $this->assertTrue(copy(__DIR__ . '/fixtures/ext/foo/foo/event/template_event_order_lower.php', $phpbb_root_path . 'ext/foo/foo/event/template_event_order.php')); + + $this->install_ext('foo/bar'); + $this->install_ext('foo/foo'); + + $crawler = self::request('GET', 'index.php'); + $quick_links_menu = $crawler->filter('ul[role="menu"]')->eq(0); + $quick_links_menu_nodes_count = (int) $quick_links_menu->filter('li')->count(); + // Ensure foo/foo template event goes before foo/bar one + $this->assertStringContainsString('FOO_BAR_QUICK_LINK', $quick_links_menu->filter('li')->eq($quick_links_menu_nodes_count - 4)->filter('span')->text()); + $this->assertStringContainsString('FOO_FOO_QUICK_LINK', $quick_links_menu->filter('li')->eq($quick_links_menu_nodes_count - 3)->filter('span')->text()); + } + + /** + * Check extensions template event listener equal (default - 0) priority rendering + * Should render in the order of reading listener files from the filesystem + */ + public function test_same_template_event_priority() + { + global $phpbb_root_path; + + $crawler = self::request('GET', 'index.php'); + // Ensure foo/bar template event goes before foo/foo one (assuming they have been read from the filesystem in alphabetical order) + $this->assertStringContainsString('FOO_BAR_FORUMLIST_BODY_BEFORE', $crawler->filter('p[id*="forumlist_body_before"]')->eq(0)->text()); + $this->assertStringContainsString('FOO_FOO_FORUMLIST_BODY_BEFORE', $crawler->filter('p[id*="forumlist_body_before"]')->eq(1)->text()); + } +} diff --git a/tests/functional/fixtures/ext/foo/bar/config/services.yml b/tests/functional/fixtures/ext/foo/bar/config/services.yml index 495c775a1f..cac5f9cd76 100644 --- a/tests/functional/fixtures/ext/foo/bar/config/services.yml +++ b/tests/functional/fixtures/ext/foo/bar/config/services.yml @@ -14,7 +14,13 @@ services: class: foo\bar\event\permission tags: - { name: event.listener } + foo_bar.listener.user_setup: class: foo\bar\event\user_setup tags: - { name: event.listener } + + foo_bar.listener.template_event_order: + class: foo\bar\event\template_event_order + tags: + - { name: event.listener } diff --git a/tests/functional/fixtures/ext/foo/bar/event/template_event_order.php b/tests/functional/fixtures/ext/foo/bar/event/template_event_order.php new file mode 100644 index 0000000000..16589591c9 --- /dev/null +++ b/tests/functional/fixtures/ext/foo/bar/event/template_event_order.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace foo\bar\event; + +/** +* Event listener +*/ +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class template_event_order implements EventSubscriberInterface +{ + static public function getSubscribedEvents() + { + return array( + 'core.twig_event_tokenparser_constructor' => 'set_template_event_priority', + ); + } + + public function set_template_event_priority($event) + { + $template_event_priority_array = $event['template_event_priority_array']; + $template_event_priority_array['foo_bar'] = [ + 'event/navbar_header_quick_links_after' => -1, + ]; + $event['template_event_priority_array'] = $template_event_priority_array; + } +} diff --git a/tests/functional/fixtures/ext/foo/bar/event/template_event_order_higher.php b/tests/functional/fixtures/ext/foo/bar/event/template_event_order_higher.php new file mode 100644 index 0000000000..6d3978a2ad --- /dev/null +++ b/tests/functional/fixtures/ext/foo/bar/event/template_event_order_higher.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace foo\bar\event; + +/** +* Event listener +*/ +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class template_event_order implements EventSubscriberInterface +{ + static public function getSubscribedEvents() + { + return array( + 'core.twig_event_tokenparser_constructor' => 'set_template_event_priority', + ); + } + + public function set_template_event_priority($event) + { + $template_event_priority_array = $event['template_event_priority_array']; + $template_event_priority_array['foo_bar'] = [ + 'event/navbar_header_quick_links_after' => 1, + ]; + $event['template_event_priority_array'] = $template_event_priority_array; + } +} diff --git a/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/event/index_body_forumlist_body_before.html b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/event/index_body_forumlist_body_before.html new file mode 100644 index 0000000000..2071af8433 --- /dev/null +++ b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/event/index_body_forumlist_body_before.html @@ -0,0 +1 @@ +

{{ lang('FOO_BAR_FORUMLIST_BODY_BEFORE') }}

\ No newline at end of file diff --git a/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/event/navbar_header_quick_links_after.html b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/event/navbar_header_quick_links_after.html new file mode 100644 index 0000000000..40be5d2b38 --- /dev/null +++ b/tests/functional/fixtures/ext/foo/bar/styles/prosilver/template/event/navbar_header_quick_links_after.html @@ -0,0 +1 @@ +
  • {{ lang('FOO_BAR_QUICK_LINK') }}
  • \ No newline at end of file diff --git a/tests/functional/fixtures/ext/foo/foo/config/services.yml b/tests/functional/fixtures/ext/foo/foo/config/services.yml index b3c7719715..fc10b65e56 100644 --- a/tests/functional/fixtures/ext/foo/foo/config/services.yml +++ b/tests/functional/fixtures/ext/foo/foo/config/services.yml @@ -1,3 +1,8 @@ services: foo_foo.controller: class: foo\foo\controller\controller + + foo_foo.listener.template_event_order: + class: foo\foo\event\template_event_order + tags: + - { name: event.listener } diff --git a/tests/functional/fixtures/ext/foo/foo/event/template_event_order.php b/tests/functional/fixtures/ext/foo/foo/event/template_event_order.php new file mode 100644 index 0000000000..09045e918e --- /dev/null +++ b/tests/functional/fixtures/ext/foo/foo/event/template_event_order.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace foo\foo\event; + +/** +* Event listener +*/ +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class template_event_order implements EventSubscriberInterface +{ + static public function getSubscribedEvents() + { + return array( + 'core.twig_event_tokenparser_constructor' => 'set_template_event_priority', + ); + } + + public function set_template_event_priority($event) + { + $template_event_priority_array = $event['template_event_priority_array']; + $template_event_priority_array['foo_foo'] = [ + 'event/navbar_header_quick_links_after' => 1, + ]; + $event['template_event_priority_array'] = $template_event_priority_array; + } +} diff --git a/tests/functional/fixtures/ext/foo/foo/event/template_event_order_lower.php b/tests/functional/fixtures/ext/foo/foo/event/template_event_order_lower.php new file mode 100644 index 0000000000..d18eb85baf --- /dev/null +++ b/tests/functional/fixtures/ext/foo/foo/event/template_event_order_lower.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace foo\foo\event; + +/** +* Event listener +*/ +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class template_event_order implements EventSubscriberInterface +{ + static public function getSubscribedEvents() + { + return array( + 'core.twig_event_tokenparser_constructor' => 'set_template_event_priority', + ); + } + + public function set_template_event_priority($event) + { + $template_event_priority_array = $event['template_event_priority_array']; + $template_event_priority_array['foo_foo'] = [ + 'event/navbar_header_quick_links_after' => -1, + ]; + $event['template_event_priority_array'] = $template_event_priority_array; + } +} diff --git a/tests/functional/fixtures/ext/foo/foo/styles/prosilver/template/event/index_body_forumlist_body_before.html b/tests/functional/fixtures/ext/foo/foo/styles/prosilver/template/event/index_body_forumlist_body_before.html new file mode 100644 index 0000000000..3b76634e18 --- /dev/null +++ b/tests/functional/fixtures/ext/foo/foo/styles/prosilver/template/event/index_body_forumlist_body_before.html @@ -0,0 +1 @@ +

    {{ lang('FOO_FOO_FORUMLIST_BODY_BEFORE') }}

    \ No newline at end of file diff --git a/tests/functional/fixtures/ext/foo/foo/styles/prosilver/template/event/navbar_header_quick_links_after.html b/tests/functional/fixtures/ext/foo/foo/styles/prosilver/template/event/navbar_header_quick_links_after.html new file mode 100644 index 0000000000..932c93caff --- /dev/null +++ b/tests/functional/fixtures/ext/foo/foo/styles/prosilver/template/event/navbar_header_quick_links_after.html @@ -0,0 +1 @@ +
  • {{ lang('FOO_FOO_QUICK_LINK') }}
  • \ No newline at end of file diff --git a/tests/functional/metadata_manager_test.php b/tests/functional/metadata_manager_test.php index 8b4eb8fde5..206a72b2eb 100644 --- a/tests/functional/metadata_manager_test.php +++ b/tests/functional/metadata_manager_test.php @@ -16,8 +16,6 @@ */ class phpbb_functional_metadata_manager_test extends phpbb_functional_test_case { - protected $phpbb_extension_manager; - private static $helper; protected static $fixtures = array( diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index c42e0aa60b..2ade7533cc 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -644,7 +644,7 @@ class phpbb_functional_test_case extends phpbb_test_case $meta_refresh = $crawler->filter('meta[http-equiv="refresh"]'); - // Wait for extension to be fully enabled + // Wait for extension to be fully disabled while (count($meta_refresh)) { preg_match('#url=.+/(adm+.+)#', $meta_refresh->attr('content'), $match);