Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 10 additions & 0 deletions docs/events/forumlist_body_forum_desc_after.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
52 changes: 49 additions & 3 deletions event/listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% if forumrow.FEEDLINKS_FORUMLIST_LINKS %}{{ forumrow.FEEDLINKS_FORUMLIST_LINKS }}{% endif %}
87 changes: 85 additions & 2 deletions tests/event/listener_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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',
'<a class="forumtitle"></a>' . ($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)
Expand Down Expand Up @@ -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')
Expand All @@ -124,6 +174,39 @@ public function test_forumlist_links()
$this->assertSame(['FORUM_DESC' => 'Description<links>'], $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('<links>');

$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' => '<links>']
: ['FORUM_DESC' => 'Description<links>'];
$this->assertSame($expected, $event['forum_row']);
}

public function test_forumlist_without_links()
{
$this->links->method('get_urls')->willReturn(['atom' => '', 'rss' => '']);
Expand Down
Loading