diff --git a/.gitattributes b/.gitattributes index dc41a13..7eafcaf 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,4 +13,5 @@ tests/ export-ignore .gitattributes export-ignore phpunit.xml.* export-ignore README.md export-ignore -docs/images/ export-ignore +docs/ export-ignore +.editorconfig export-ignore diff --git a/docs/events/forumlist_body_forum_desc_after.txt b/docs/events/forumlist_body_forum_desc_after.txt index 868ca4e..57ec0fd 100644 --- a/docs/events/forumlist_body_forum_desc_after.txt +++ b/docs/events/forumlist_body_forum_desc_after.txt @@ -1,3 +1,13 @@ +STATUS +====== +Filed: https://tracker.phpbb.com/browse/PHPBB-17748 +Merged: https://github.com/phpbb/phpbb/pull/7095 (3.3.x) and + https://github.com/phpbb/phpbb/pull/7096 (master), 11/03/2026 +Since: 3.3.20-RC1 +Feed Links uses the event when the user's style has it, and falls back to +appending to FORUM_DESC for styles without it (phpBB 3.3.19 Prosilver and +older custom styles). + PROJECT: phpBB (PHPBB) ISSUE TYPE: Improvement SUMMARY: New template event forumlist_body_forum_desc_after diff --git a/event/listener.php b/event/listener.php index 576e45b..2ae553e 100644 --- a/event/listener.php +++ b/event/listener.php @@ -52,6 +52,9 @@ class listener implements EventSubscriberInterface /** @var bool Whether the front-end language file is loaded */ protected $language_loaded = false; + /** @var bool|null Whether the user's style has the forumlist_body_forum_desc_after event, once looked up */ + protected $forumlist_event; + /** * @param config $config * @param language $language @@ -102,8 +105,11 @@ public function add_rss_head_switch() /** * Add a forum's feed links below its description on forum lists * - * phpBB has no template event after the description of a forum row (see - * docs/events/), so the rendered links are appended to FORUM_DESC. + * Styles with the forumlist_body_forum_desc_after template event (phpBB + * 3.3.20 and 4.0 Prosilver, or a style that adds it) get the links in + * their own forumrow variable, shown by that event. Older styles have no + * place after the description, so there the links are appended to + * FORUM_DESC as before. * * @param data $event * @return void @@ -118,10 +124,50 @@ public function add_forumlist_links(data $event) } $forum_row = $event['forum_row']; - $forum_row['FORUM_DESC'] .= $this->render_links($urls, $event['row']['forum_name'], true, $forum_row['FORUM_DESC'] !== ''); + + if ($this->style_has_forumlist_event()) + { + $forum_row['FEEDLINKS_FORUMLIST_LINKS'] = $this->render_links($urls, $event['row']['forum_name'], true, true); + } + else + { + $forum_row['FORUM_DESC'] .= $this->render_links($urls, $event['row']['forum_name'], true, $forum_row['FORUM_DESC'] !== ''); + } + $event['forum_row'] = $forum_row; } + /** + * Whether the forum list template of the user's style has the + * forumlist_body_forum_desc_after event + * + * Looks at the first forumlist_body.html in the style and its parents, + * the one phpBB uses, once per request. + * + * @return bool + */ + protected function style_has_forumlist_event() + { + if ($this->forumlist_event === null) + { + $this->forumlist_event = false; + $root = $this->twig->get_phpbb_root_path(); + + foreach ($this->template->get_user_style() as $style) + { + $file = $root . 'styles/' . $style . '/template/forumlist_body.html'; + + if (is_file($file)) + { + $this->forumlist_event = strpos((string) file_get_contents($file), 'forumlist_body_forum_desc_after') !== false; + break; + } + } + } + + return $this->forumlist_event; + } + /** * Add a forum's feed links to its own page, and its RSS link to the head * diff --git a/styles/all/template/event/forumlist_body_forum_desc_after.html b/styles/all/template/event/forumlist_body_forum_desc_after.html new file mode 100644 index 0000000..310a754 --- /dev/null +++ b/styles/all/template/event/forumlist_body_forum_desc_after.html @@ -0,0 +1 @@ +{% if forumrow.FEEDLINKS_FORUMLIST_LINKS %}{{ forumrow.FEEDLINKS_FORUMLIST_LINKS }}{% endif %} diff --git a/tests/event/listener_test.php b/tests/event/listener_test.php index 717e9d5..cce2a0c 100644 --- a/tests/event/listener_test.php +++ b/tests/event/listener_test.php @@ -26,9 +26,12 @@ class listener_test extends \phpbb_test_case /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\template\template */ protected $template; - /** @var \PHPUnit\Framework\MockObject\MockObject|\Twig\Environment */ + /** @var \PHPUnit\Framework\MockObject\MockObject|\phpbb\template\twig\environment */ protected $twig; + /** @var string A throwaway board root holding the styles a test needs */ + protected $style_root = ''; + /** @var \PHPUnit\Framework\MockObject\MockObject|links */ protected $links; @@ -44,11 +47,57 @@ protected function setUp(): void $this->request = $this->getMockBuilder('\phpbb\request\request_interface')->getMock(); $this->template = $this->getMockBuilder('\phpbb\template\template')->getMock(); - $this->twig = $this->getMockBuilder('\Twig\Environment')->disableOriginalConstructor()->getMock(); + $this->twig = $this->getMockBuilder('\phpbb\template\twig\environment')->disableOriginalConstructor()->getMock(); $this->links = $this->getMockBuilder(links::class)->disableOriginalConstructor()->getMock(); $this->config = new config(['feed_topic' => 1, 'feedlinks_rss_head' => 1]); } + protected function tearDown(): void + { + if ($this->style_root !== '') + { + foreach (glob($this->style_root . 'styles/*/template/forumlist_body.html') as $file) + { + unlink($file); + rmdir(dirname($file)); + rmdir(dirname($file, 2)); + } + foreach (glob($this->style_root . 'styles/*', GLOB_ONLYDIR) as $dir) + { + rmdir($dir); + } + rmdir($this->style_root . 'styles'); + rmdir($this->style_root); + } + + parent::tearDown(); + } + + /** + * Give the user a style tree, and a forumlist_body.html to the styles named + * in $templates (style name => whether it has the forumlist_body_forum_desc_after event) + * + * @param array $tree Style names, the user's style first, then its parents + * @param array $templates + * @return void + */ + protected function set_styles(array $tree, array $templates) + { + $this->style_root = sys_get_temp_dir() . '/feedlinks_test_' . uniqid() . '/'; + foreach ($tree as $style) + { + mkdir($this->style_root . 'styles/' . $style, 0777, true); + } + foreach ($templates as $style => $has_event) + { + mkdir($this->style_root . 'styles/' . $style . '/template'); + file_put_contents($this->style_root . 'styles/' . $style . '/template/forumlist_body.html', + '' . ($has_event ? '{% EVENT forumlist_body_forum_desc_after %}' : '')); + } + $this->twig->method('get_phpbb_root_path')->willReturn($this->style_root); + $this->template->method('get_user_style')->willReturn($tree); + } + /** * Record the arguments of every call to a template method, in order * (withConsecutive() is gone from PHPUnit 10) @@ -103,6 +152,7 @@ public function test_rss_head_switch() public function test_forumlist_links() { + $this->set_styles(['prosilver'], ['prosilver' => false]); $this->links->method('get_urls')->willReturn(['atom' => 'atom-url', 'rss' => '']); $this->twig->expects($this->once()) ->method('render') @@ -124,6 +174,39 @@ public function test_forumlist_links() $this->assertSame(['FORUM_DESC' => 'Description'], $event['forum_row']); } + /** + * Styles with the forumlist_body_forum_desc_after event, directly or from + * a parent style, get the links in their own forumrow variable + */ + public static function forumlist_event_styles() + { + return [ + 'the style has the event' => [['prosilver'], ['prosilver' => true]], + 'a child style inherits it' => [['child', 'prosilver'], ['prosilver' => true]], + 'the child overrides it without the event' => [['child', 'prosilver'], ['child' => false, 'prosilver' => true]], + ]; + } + + /** @dataProvider forumlist_event_styles */ + public function test_forumlist_links_with_event(array $tree, array $templates) + { + $this->set_styles($tree, $templates); + $uses_event = $templates[array_values(array_filter($tree, function ($s) use ($templates) { return isset($templates[$s]); }))[0]]; + $this->links->method('get_urls')->willReturn(['atom' => 'atom-url', 'rss' => 'rss-url']); + $this->twig->method('render')->willReturn(''); + + $event = new data([ + 'row' => ['forum_name' => 'Forum'], + 'forum_row' => ['FORUM_DESC' => 'Description'], + ]); + $this->get_listener()->add_forumlist_links($event); + + $expected = $uses_event + ? ['FORUM_DESC' => 'Description', 'FEEDLINKS_FORUMLIST_LINKS' => ''] + : ['FORUM_DESC' => 'Description']; + $this->assertSame($expected, $event['forum_row']); + } + public function test_forumlist_without_links() { $this->links->method('get_urls')->willReturn(['atom' => '', 'rss' => '']);