Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
0520fd5
Initial total redesign from title prefixes to topic tags
iMattPro Sep 19, 2026
ce0dd54
Updates and tweaky fixes and more tests
iMattPro Sep 19, 2026
e47df67
Add UCP listeners assuming we get these events in the core
iMattPro Sep 19, 2026
3a78f49
Ensure topic tags stay with a topic after being moved to other forums
iMattPro Sep 20, 2026
a1398e0
Add orphan handling
iMattPro Sep 20, 2026
6f716ef
Fix CSS issues
iMattPro Sep 20, 2026
393ffe0
Fix tests
iMattPro Sep 20, 2026
5b05fc9
Fix dates
iMattPro Sep 20, 2026
caf3bb7
Refactor functional tests
iMattPro Sep 20, 2026
d94fd00
Performance improvements pass
iMattPro Sep 20, 2026
797ba46
Revert handling of tags when all users posts moved to a new forum
iMattPro Sep 20, 2026
ded6748
CSS improvements
iMattPro Sep 20, 2026
db7c63c
Avoid no-op filter links for global topics
iMattPro Sep 20, 2026
3c4ef93
Small code fixes and improvements
iMattPro Sep 20, 2026
bac6367
Fix tests
iMattPro Sep 20, 2026
1f8a7ee
Styling fixes in the ACP
iMattPro Sep 20, 2026
a3f5ea3
Prevent disabled tags from appearing as filter choices
iMattPro Sep 20, 2026
30280e6
Add subtle depth effect to tags so they’r enot so flat
iMattPro Sep 21, 2026
d4dc4c1
Add tooltips to tags
iMattPro Sep 21, 2026
e0a8c32
Added multibyte prefix test coverage using Japanese text
iMattPro Sep 21, 2026
3983d76
Fix MSSQL - as usual
iMattPro Sep 21, 2026
ce9955d
Handling for 4-byte emojis
iMattPro Sep 21, 2026
215f7e0
Enforce a 50 char limit for topic prefix/tags
iMattPro Sep 21, 2026
f713670
Make all three stateless text utilities static
iMattPro Sep 22, 2026
d9e7d99
Rename to Topic Tags
iMattPro Sep 22, 2026
d20a8c6
Loosen phpBB version requirements and add notes about
iMattPro Sep 22, 2026
857b2d8
Default panel shows only enabled tags available in forum
iMattPro Sep 23, 2026
cb6242f
Implement lightweight retained-tag tooltip
iMattPro Sep 23, 2026
16c8785
Adjust topic tags in search profile
iMattPro Sep 24, 2026
2980294
Mark version as “dev” for now [ci skip]
iMattPro Sep 24, 2026
43253cc
Migrate stored for last post subject titles
iMattPro Sep 25, 2026
362dc14
Disable tag filtering in search results
iMattPro Sep 25, 2026
1782300
Display forums associated with tags as list
iMattPro Oct 3, 2026
5a74206
Split multi-bracketed prefixes into individual tags
iMattPro Oct 3, 2026
5ff2957
Enforce disabling board before updating to 2.0.0
iMattPro Oct 3, 2026
7460e03
Add a repair CLI tool to split tags that didn’t migrate as expected
iMattPro Oct 3, 2026
99603ea
Fix tests
iMattPro Oct 3, 2026
e431abf
Fix MSSQL
iMattPro Oct 3, 2026
c6fe298
Keep legacy log lang keys
iMattPro Oct 3, 2026
e0179b4
Enforce permissions when retaining tag filter IDs
iMattPro Oct 3, 2026
4b38bdf
Prevent double HTML-encoding of ACP tag names
iMattPro Oct 3, 2026
b13232e
Avoid visibility queries for unfiltered tag requests
iMattPro Oct 4, 2026
06503c5
Escape log names before HTML interpolation
iMattPro Oct 4, 2026
df2396c
Update tests
iMattPro Oct 4, 2026
c81bd66
Renamed tool to topicprefixes:repair-legacy-tags
iMattPro Oct 4, 2026
8f4d3c1
Re-word the repair tool text
iMattPro Oct 4, 2026
dae2845
Allow tag merging via repair tool
iMattPro Oct 4, 2026
51a0c30
Check destination permissions before displaying shadow topic tags
iMattPro Oct 4, 2026
165910f
Preserve selected tags in the pagination base URL
iMattPro Oct 4, 2026
12a50ec
Fix comments
iMattPro Oct 5, 2026
f5e974a
Fix changelog
iMattPro Oct 5, 2026
dca6807
Refactor cli lang keys and support pluralization
iMattPro Oct 5, 2026
9b1aba9
Add a missing unit test
iMattPro Oct 5, 2026
784d72c
Migration hardening
iMattPro Oct 5, 2026
38a8000
Harden duplicate consolidation in migration
iMattPro Oct 5, 2026
0596ccf
Implement Active Topics display-only behavior
iMattPro Oct 6, 2026
efca6e3
Update docs
iMattPro Oct 6, 2026
01222c1
Get tag filtering working for search results
iMattPro Oct 6, 2026
fe0bbcc
Remove skipped test now that events are available
iMattPro Oct 6, 2026
3406636
Add tag filtering to other search pages (new posts, unanswered etc)
iMattPro Oct 6, 2026
4094747
Remove redundant template flag
iMattPro Oct 6, 2026
ad8b023
Add missing test gaps
iMattPro Oct 6, 2026
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

### 2.0.0-dev - TBD

- Renamed phpBB Topic Prefixes to phpBB Topic Tags and replaced single text prefixes embedded in titles with relational topic metadata.
- Added multiple color-coded tags per topic with automatic badge text contrast and badges throughout forum, topic, MCP, and search displays.
- Added centralized ACP management for tag text, color, order, status, and availability across multiple forums.
- Added accessible tag selection when creating topics or editing their first posts.
- Added clickable filters that combine multiple tags using AND matching in forum lists, category Active Topics, and SQL-backed keyword or author search results shown as topics or posts.
- Preserved tags through topic moves, splits, forks, and merges, including tags retained after moving into forums where they are unavailable for new assignments.
- Added a safe, restartable upgrade that converts legacy prefixes and assignments into tags while removing matching prefix text from topic titles and first-post subjects.
- Added automatic splitting of complete adjacent bracket sequences and consolidation of duplicate legacy definitions with matching names and enabled states, while preserving malformed formats and same-name definitions with conflicting states.
- Required legacy upgrades to run with the board disabled and directed administrators to back up the database first.
- Added an interactive CLI for splitting tags into multiple separate tags or merging one tag into another.
- Raised minimum requirements to phpBB 3.3.5 and PHP 7.2.
- Note: implementations for showing tags on UCP main page, bookmarks, and subscriptions will take effect after phpBB adds new proposed events (targeted for 3.3.19).

### 1.0.2 - 2025-12-17

- Strengthened compatibility with both phpBB3 and phpBB4-alpha-1.
Expand Down
46 changes: 38 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,49 @@
# phpBB Topic Prefixes
# phpBB Topic Tags

This is the repository for the development of the Topic Prefixes phpBB extension.
This is the repository for development of phpBB Topic Tags, formerly named Topic Prefixes.

[![Build Status](https://github.com/phpbb-extensions/topicprefixes/actions/workflows/tests.yml/badge.svg)](https://github.com/phpbb-extensions/topicprefixes/actions)
[![codecov](https://codecov.io/gh/phpbb-extensions/topicprefixes/branch/master/graph/badge.svg?token=Dc0GWOeQWj)](https://codecov.io/gh/phpbb-extensions/topicprefixes)

The phpBB Topic Prefixes extension allows phpBB board administrators to create topic prefix tags that users can easily add to their topic titles. Features include:
The phpBB Topic Tags extension provides administrator-curated topic tags. Tags are structured topic metadata and are displayed separately from topic titles as colored badges. Features include:

- Create unique and unlimited prefixes for each forum in your phpBB board.
- Admins can sort, enable and disable prefix tags.
- Users will see a topic prefix menu when creating a new topic in a forum that uses topic prefixes.
- Topic prefixes can easily be changed when a user is editing their original first post.
- Because the topic prefix is added directly to the topic title, if an admin disables or deletes a topic prefix, existing topic titles using the deleted prefix will not be changed.
- Create, edit, order, enable, disable, and color topic tags.
- Make each tag available in one or more forums.
- Assign multiple tags while creating a topic or editing its first post.
- Display readable colored badges in topic lists, topic pages, and search results.
- Filter forum and category Active Topics lists by one or more tags using AND semantics.
- Filter keyword and author search results shown as topics or posts when using phpBB native, MySQL fulltext, or PostgreSQL search; Sphinx search remains display-only.
- Upgrade existing title prefixes safely into relational tag assignments.
- This is the same extension currently in use at phpbb.com in the Extensions and Styles in development forums.

## Upgrading from 1.x

Before enabling version 2, make a full database backup and disable the board in
General > Board settings. The extension blocks a legacy upgrade while the board
is enabled.

Migration splits complete adjacent bracket sequences such as `[3.3][DEV]` into
separate tags. Legacy prefixes duplicated within or across multiple forums are
consolidated into one tag available in each applicable forum when their decoded
names and enabled states match. Other combined formats and same-name definitions with
different enabled states remain separate. This upgrade is one-way; restore the
pre-upgrade database backup to undo it.

## Repairing topic tags

Use the interactive command to split one tag into multiple separate tags or
merge one tag into another existing tag. Splitting can repair combined formats
that could not be handled automatically, such as `(A)(B)` or `A|B`:

```shell
php bin/phpbbcli.php topicprefixes:repair-tags
```

Use `--tag-id=ID` to repair one source tag. Back up the database and disable the
board before running the command; the command enforces both an interactive
backup confirmation and maintenance mode. Every proposed change is previewed
and must be confirmed before any data changes.

📦 [Download](https://www.phpbb.com/customise/db/extension/topicprefixes/) the latest release of this extension.

🐞 [Report bugs](https://github.com/phpbb-extensions/topicprefixes/issues) to our Issue Tracker.
Expand Down
2 changes: 1 addition & 1 deletion acp/topic_prefixes_module.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function main()
$lang->add_lang('acp/forums');
$lang->add_lang('acp_topic_prefixes', 'phpbb/topicprefixes');
$this->tpl_name = 'acp_topic_prefixes';
$this->page_title = 'TOPIC_PREFIXES';
$this->page_title = 'TOPIC_TAGS';

$admin_controller = $phpbb_container->get('phpbb.topicprefixes.admin_controller');
$admin_controller->set_u_action($this->u_action)->main();
Expand Down
78 changes: 76 additions & 2 deletions adm/style/acp_topic_prefixes.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,84 @@
font-weight: 900;
}

.tp-toggle-on:before {
.tp-toggle-on::before {
content: "\f205";
}

.tp-toggle-off:before {
.tp-toggle-off::before {
content: "\f204";
}

.topic-tag {
background-image: linear-gradient(to top, transparent, rgba(255, 255, 255, 0.35));
border-radius: 3px;
display: inline-block;
font-weight: 600;
line-height: 1.5;
padding: 1px 7px;
}

.topic-tags-empty {
text-align: center;
}

.topic-tag-forums {
display: block;
line-height: 1.8;
}

.topic-tag-forum {
display: block;
padding-left: 1em;
position: relative;
}

.topic-tag-forum::before {
color: #6B7278;
content: "\2022";
left: 0;
position: absolute;
}

.topic-tag-color,
.topic-tag-color:hover,
.topic-tag-color:focus {
background: #FFFFFF;
border: 1px solid #AFAEAA;
border-radius: 4px;
box-sizing: border-box;
color: inherit;
cursor: pointer;
height: 24px;
margin: 2px 0;
padding: 3px;
width: 48px;
}

.topic-tag-color:hover {
border-color: #666666;
}

.topic-tag-color:focus {
border-color: #BC2A4D;
box-shadow: 0 0 0 1px #BC2A4D;
outline: none;
}

.topic-tag-color::-webkit-color-swatch-wrapper {
padding: 0;
}

.topic-tag-color::-webkit-color-swatch {
border: 0;
border-radius: 2px;
}

.topic-tag-color::-moz-color-swatch {
border: 0;
border-radius: 2px;
}

#forum_ids {
min-width: 22em;
}
136 changes: 74 additions & 62 deletions adm/style/acp_topic_prefixes.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,82 @@

<a id="maincontent"></a>

<h1>{{ lang('TOPIC_PREFIXES') }}</h1>
<h1>{{ lang('TOPIC_TAGS') }}</h1>
<p>{{ lang('TOPIC_TAGS_EXPLAIN') }}</p>
<p>{{ lang('TOPIC_TAGS_REPAIR_CLI') }} <code>php bin/phpbbcli.php topicprefixes:repair-tags</code></p>

<p>{{ lang('TOPIC_PREFIXES_EXPLAIN') }}</p>
<table class="table1 zebra-table fixed-width-table">
<thead>
<tr>
<th>{{ lang('TOPIC_TAG') }}</th>
<th style="width:50%">{{ lang('TOPIC_TAG_FORUMS') }}</th>
<th>{{ lang('TOPIC_TAG_ENABLED') }}</th>
<th>{{ lang('ACTION') }}</th>
</tr>
</thead>
<tbody>
{% for tag in loops.tags %}
<tr>
<td><span class="topic-tag" style="background-color: {{ tag.TAG_COLOR }}; color: {{ tag.TAG_TEXT_COLOR }}">{{ tag.TAG_NAME }}</span></td>
<td>
{% if tag.FORUM_NAMES %}
<span class="topic-tag-forums" role="list">
{% for forum_name in tag.FORUM_NAMES %}
<span class="topic-tag-forum" role="listitem">{{ forum_name }}</span>
{% endfor %}
</span>
{% else %}
{{ lang('TOPIC_TAG_NO_FORUMS') }}
{% endif %}
</td>
<td>
<a href="{{ tag.U_TOGGLE }}" class="topic-tag-toggle" title="{{ lang('TOPIC_TAG_TOGGLE_STATE') }}" data-ajax="tp_toggle">
<i class="fa fa-fw tp-toggle-{{ tag.TAG_ENABLED ? 'on' : 'off' }} acp-icon"></i>
</a>
</td>
<td class="actions">
<span class="up-disabled" style="display:none">{{ ICON_MOVE_UP_DISABLED }}</span>
<span class="up"><a href="{{ tag.U_MOVE_UP }}" data-ajax="row_up">{{ ICON_MOVE_UP }}</a></span>
<span class="down-disabled" style="display:none">{{ ICON_MOVE_DOWN_DISABLED }}</span>
<span class="down"><a href="{{ tag.U_MOVE_DOWN }}" data-ajax="row_down">{{ ICON_MOVE_DOWN }}</a></span>
<a href="{{ tag.U_EDIT }}">{{ ICON_EDIT }}</a>
<a href="{{ tag.U_DELETE }}">{{ ICON_DELETE }}</a>
</td>
</tr>
{% else %}
<tr><td colspan="4" class="topic-tags-empty">{{ lang('TOPIC_TAGS_EMPTY') }}</td></tr>
{% endfor %}
</tbody>
</table>

<form id="acp_topic_prefixes_forum" method="post" action="{{ U_ACTION }}">
<fieldset>
<dl>
<dt><label for="select_forum">{{ lang('SELECT_FORUM') ~ lang('COLON') }}</label></dt>
<dd>
<select id="select_forum" name="forum_id"><option value="0">{{ lang('SELECT_FORUM') }}</option>{{ S_FORUM_OPTIONS }}</select>
<input type="submit" value="{{ lang('GO') }}" class="button2">
</dd>
</dl>
</fieldset>
</form>

{% if FORUM_ID %}

<table class="table1 zebra-table fixed-width-table">
<thead>
<tr>
<th style="width:75%">{{ lang('TOPIC_PREFIX_TAG') }}</th>
<th>{{ lang('TOPIC_PREFIX_ENABLED') }}</th>
<th>{{ lang('ACTION') }}</th>
</tr>
</thead>
<tbody>
{% for prefix in loops.prefixes %}
<tr>
<td><strong>{{ prefix.PREFIX_TAG }}</strong></td>
<td>
<a href="{{ prefix.U_EDIT }}" style="text-decoration: none" title="{{ lang('TOPIC_PREFIX_TOGGLE_STATE') }}" data-ajax="tp_toggle">
<i class="fa fa-fw tp-toggle-{{ prefix.PREFIX_ENABLED ? 'on' : 'off' }} acp-icon"></i>
</a>
</td>
<td class="actions">
<span class="up-disabled" style="display:none;">{{ ICON_MOVE_UP_DISABLED }}</span>
<span class="up"><a href="{{ prefix.U_MOVE_UP }}" data-ajax="row_up">{{ ICON_MOVE_UP }}</a></span>
<span class="down-disabled" style="display:none;">{{ ICON_MOVE_DOWN_DISABLED }}</span>
<span class="down"><a href="{{ prefix.U_MOVE_DOWN }}" data-ajax="row_down">{{ ICON_MOVE_DOWN }}</a></span>
<a href="{{ prefix.U_DELETE }}" data-ajax="row_delete">{{ ICON_DELETE }}</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="3" style="text-align: center;">{{ lang('TOPIC_PREFIXES_EMPTY') }}</td>
</tr>
{% endfor %}
</tbody>
</table>

<form id="acp_add_topic_prefix" method="post" action="{{ U_ACTION }}">
<fieldset class="quick">
{{ lang('CREATE_TOPIC_PREFIX') ~ lang('COLON') }}
<input type="text" name="prefix_tag" value="" maxlength="200" placeholder="{{ lang('TOPIC_PREFIX_PLACEHOLDER') }}">
<input class="button2" name="submit" type="submit" value="{{ lang('SUBMIT') }}">
<input type="hidden" name="action" value="add">
<input type="hidden" name="forum_id" value="{{ FORUM_ID }}">
{{ S_FORM_TOKEN }}
{{ S_HIDDEN_FIELDS }}
</fieldset>
</form>

{% endif %}
<form id="acp_topic_tag" method="post" action="{{ U_ACTION }}">
<fieldset>
<legend>{{ lang(S_EDIT_TAG ? 'EDIT_TOPIC_TAG' : 'CREATE_TOPIC_TAG') }}</legend>
<dl>
<dt><label for="tag_name">{{ lang('TOPIC_TAG_TEXT') ~ lang('COLON') }}</label></dt>
<dd><input type="text" id="tag_name" name="tag_name" value="{{ TAG_NAME }}" maxlength="50" required></dd>
</dl>
<dl>
<dt><label for="tag_color">{{ lang('TOPIC_TAG_COLOR') ~ lang('COLON') }}</label><br><span>{{ lang('TOPIC_TAG_COLOR_EXPLAIN') }}</span></dt>
<dd><input type="color" class="topic-tag-color" id="tag_color" name="tag_color" value="{{ TAG_COLOR }}"></dd>
</dl>
<dl>
<dt><label for="tag_enabled">{{ lang('TOPIC_TAG_ENABLED') ~ lang('COLON') }}</label></dt>
<dd><input type="checkbox" class="radio" id="tag_enabled" name="tag_enabled" value="1"{% if TAG_ENABLED %} checked{% endif %}></dd>
</dl>
<dl>
<dt><label for="forum_ids">{{ lang('TOPIC_TAG_FORUMS') ~ lang('COLON') }}</label><br><span>{{ lang('TOPIC_TAG_FORUMS_EXPLAIN') }}</span></dt>
<dd><select id="forum_ids" name="forum_ids[]" multiple size="10">{{ S_FORUM_OPTIONS }}</select></dd>
</dl>
</fieldset>
<fieldset class="submit-buttons">
<input class="button1" name="submit" type="submit" value="{{ lang('SUBMIT') }}">
{% if S_EDIT_TAG %}<a class="button2" href="{{ U_ACTION }}">{{ lang('CANCEL') }}</a>{% endif %}
<input type="hidden" name="action" value="save">
<input type="hidden" name="tag_id" value="{{ TAG_ID }}">
{{ S_FORM_TOKEN }}
</fieldset>
</form>

{% INCLUDEJS '@phpbb_topicprefixes/acp_topic_prefixes.js' %}
{% INCLUDECSS '@phpbb_topicprefixes/acp_topic_prefixes.css' %}
Expand Down
4 changes: 0 additions & 4 deletions adm/style/acp_topic_prefixes.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
(function() {
'use strict';

document.getElementById('select_forum').addEventListener('change', function() {
this.closest('form').submit();
});

phpbb.addAjaxCallback('tp_toggle', function(res) {
if (typeof res.success === 'undefined' || !res.success) {
return;
Expand Down
2 changes: 1 addition & 1 deletion build.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="Topic Prefixes Extension Builder" description="Builds an extension.zip from a git repository" default="all">
<project name="Topic Tags Extension Builder" description="Builds an extension.zip from a git repository" default="all">
<property name="vendor-name" value="phpbb" />
<property name="extension-name" value="topicprefixes" />
<!--
Expand Down
12 changes: 6 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "phpbb/topicprefixes",
"type": "phpbb-extension",
"description": "An official phpBB extension for topic prefixes.",
"description": "An official phpBB extension for administrator-curated topic tags.",
"homepage": "https://www.phpbb.com/customise/db/extension/topicprefixes/",
"version": "1.0.2",
"version": "2.0.0-dev",
"license": "GPL-2.0-only",
"authors": [
{
Expand All @@ -13,21 +13,21 @@
},
{
"name": "Ruslan Uzdenov",
"homepage": "http://www.phpbbguru.net",
"homepage": "https://www.phpbbguru.net",
"role": "Extensions Development Team"
}
],
"require": {
"php": ">=5.4",
"php": ">=7.2",
"composer/installers": "^1.0 || ^2.0"
},
"require-dev": {
"phing/phing": "~2.4"
},
"extra": {
"display-name": "phpBB Topic Prefixes",
"display-name": "phpBB Topic Tags",
"soft-require": {
"phpbb/phpbb": ">=3.2.0"
"phpbb/phpbb": ">=3.3.5"
},
"version-check": {
"host": "www.phpbb.com",
Expand Down
Loading