Skip to content

Redesign Topic Prefixes into Tagging system - #35

Open
iMattPro wants to merge 62 commits into
phpbb-extensions:masterfrom
iMattPro:redesign
Open

iMattPro wants to merge 62 commits into
phpbb-extensions:masterfrom
iMattPro:redesign

Conversation

@iMattPro

@iMattPro iMattPro commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Updates the topic title prefixing into a full fledges topic tagging system with filtering/search and prettier badges.

Will require this PR: phpbb/phpbb#7052 - adds events that will allow the tags to appear in most places where topics appear (such as in UCP and MCP panels).

ACP panel
Screenshot 2026-09-23 at 5 53 37 PM

Posting panel:
Screenshot 2026-09-20 at 6 43 24 AM

Viewing forum:
Screenshot 2026-09-23 at 5 52 50 PM

Viewing topic:
Screenshot 2026-09-23 at 5 53 16 PM

Closes #17
Closes #18

@codecov-commenter

codecov-commenter commented Sep 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (eba4b2d) to head (ad8b023).

Additional details and impacted files
@@              Coverage Diff              @@
##             master       #35      +/-   ##
=============================================
+ Coverage     99.11%   100.00%   +0.88%     
- Complexity       83       448     +365     
=============================================
  Files             7        15       +8     
  Lines           225      1618    +1393     
=============================================
+ Hits            223      1618    +1395     
+ Misses            2         0       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

A nonexistent phpBB lifecycle event leaves ACP-relocated topics without tags, and global-topic badges produce inaccessible no-op links.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Redesigns topic prefixes as relational, multi-tag metadata with filtering, colored badges, lifecycle handling, and legacy-data migration.

Changes:

  • Adds tag management, rendering, filtering, and phpBB event integrations.
  • Migrates legacy prefixes into normalized tag relationships.
  • Replaces legacy tests and UI with comprehensive tagging coverage.
File Description
tests/​tags/​renderer_test.php Tests badge rendering and filter URLs.
tests/​event/​viewforum_listener_test.php Tests forum filtering and pagination.
tests/​event/​posting_listener_test.php Tests posting and editing assignments.
tests/​event/​listener_test.php Removes legacy listener tests.
tests/​event/​lifecycle_listener_test.php Tests lifecycle tag preservation.
tests/​event/​display_listener_test.php Tests tags across display surfaces.
tests/​dbal/​tags_base.php Adds shared database test setup.
tests/​dbal/​tag_manager_test.php Tests tag catalog and CRUD behavior.
tests/​dbal/​simple_test.php Removes legacy schema test.
tests/​dbal/​manager_update_prefix_test.php Removes legacy update tests.
tests/​dbal/​manager_prepend_prefix_test.php Removes title-prefix tests.
tests/​dbal/​manager_move_prefix_test.php Removes legacy ordering tests.
tests/​dbal/​manager_get_prefixes_test.php Removes legacy retrieval tests.
tests/​dbal/​manager_get_prefix_test.php Removes single-prefix tests.
tests/​dbal/​manager_get_active_prefixes_test.php Removes active-prefix tests.
tests/​dbal/​manager_delete_prefix_test.php Removes legacy deletion tests.
tests/​dbal/​manager_base.php Removes legacy test base.
tests/​dbal/​manager_add_prefix_test.php Removes legacy creation tests.
tests/​dbal/​legacy_migration_test.php Tests legacy data conversion.
tests/​dbal/​fixtures/​topic_tags.xml Adds relational tag fixtures.
tests/​dbal/​fixtures/​topic_prefixes.xml Removes legacy fixtures.
tests/​dbal/​filter_test.php Tests multi-tag filtering.
tests/​dbal/​assignment_manager_test.php Tests assignment persistence and copying.
tests/​controller/​move_prefix_test.php Removes legacy controller test.
tests/​controller/​main_test.php Removes legacy routing test.
tests/​controller/​edit_prefix_test.php Removes legacy editing test.
tests/​controller/​display_settings_test.php Removes legacy display test.
tests/​controller/​delete_prefix_test.php Removes legacy deletion test.
tests/​controller/​admin_controller_test.php Tests new ACP tag controller.
tests/​controller/​admin_controller_base.php Removes legacy controller fixture.
tests/​controller/​add_prefix_test.php Removes legacy creation test.
tests/​acp/​acp_module_test.php Updates namespaced ACP tests.
tags/​renderer.php Builds badges and filter URLs.
tags/​manager.php Manages tag definitions and availability.
tags/​filter.php Implements tag-aware SQL filtering.
styles/​all/​theme/​topic_tags.css Styles badges and controls.
styles/​all/​template/​topic_tag_badges.html Renders reusable tag badges.
styles/​all/​template/​event/​viewtopic_topic_title_prepend.html Adds view-topic badges.
styles/​all/​template/​event/​viewforum_body_topic_row_before.html Adds forum filter controls.
styles/​all/​template/​event/​topiclist_row_prepend.html Adds topic-list badges.
styles/​all/​template/​event/​search_results_topic_title_prepend.html Adds post-search badges.
styles/​all/​template/​event/​posting_editor_subject_before.html Adds tag checkboxes.
styles/​all/​template/​event/​overall_header_head_append.html Loads frontend tag styles.
styles/​all/​template/​event/​mcp_forum_topic_title_before.html Adds MCP badges.
README.md Documents the tagging system.
prefixes/​nestedset_prefixes.php Removes nested-set implementation.
prefixes/​manager.php Removes legacy prefix manager.
prefixes/​manager_interface.php Removes legacy manager interface.
migrations/​v200_schema.php Adds relational tagging schema.
migrations/​v200_data.php Migrates legacy prefix data.
migrations/​v200_cleanup.php Removes obsolete columns.
language/​en/​topic_prefixes.php Adds frontend tag messages.
language/​en/​info_acp_topic_prefixes.php Updates ACP metadata and logs.
language/​en/​acp_topic_prefixes.php Adds ACP tag language.
ext.php Raises runtime requirements.
event/​viewforum_listener.php Integrates filters and forum badges.
event/​posting_listener.php Validates and saves assignments.
event/​listener.php Removes legacy listener.
event/​lifecycle_listener.php Handles topic lifecycle operations.
event/​display_listener.php Integrates non-forum displays.
config/​tables.yml Registers relationship tables.
config/​services.yml Registers tag services and listeners.
composer.json Declares version 2 requirements.
CHANGELOG.md Documents the 2.0 redesign.
adm/​style/​acp_topic_prefixes.js Removes obsolete forum switching.
adm/​style/​acp_topic_prefixes.html Rebuilds the ACP interface.
adm/​style/​acp_topic_prefixes.css Styles ACP badges and controls.
acp/​topic_prefixes_module.php Updates the ACP page title.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread event/lifecycle_listener.php Outdated
Comment thread styles/all/template/topic_tag_badges.html
@iMattPro
iMattPro requested a review from rxu September 20, 2026 21:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Disabled assigned tags are incorrectly exposed as available forum filter choices.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Prevent disabled tags from appearing as filter choices

event/​viewforum_listener.php:125

Disabled tags become visible as filter choices whenever they remain assigned to a moved/global topic. This bypasses the enabled-only filtering above; the later loop already restores a disabled tag when it is actually selected, so only enabled unavailable tags should be added here.

@rxu rxu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't really test the functionality due to phpBB 3.3.18 requirement (new events are pending to be merged probably).

Comment thread migrations/v200_data.php Outdated
Comment thread migrations/v200_data.php Outdated
Comment thread migrations/v200_data.php
Comment thread migrations/v200_data.php Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Verified security and functional defects remain unresolved.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
Resolved since last review (4)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Account for gradient when calculating ACP text contrast

adm/​style/​acp_topic_prefixes.css:15

The ACP renderer chooses the foreground against the solid background, but this white gradient lightens the background under the text. For the default #4A76A8 badge, white-text contrast falls from 4.72:1 to 3.41:1 at the midpoint, below the 4.5:1 requirement for small text. Remove the overlay or calculate contrast across the rendered gradient.

Medium severity Validate pagination offset against filtered topic counts

event/​viewforum_listener.php:141

phpBB validates start against the unfiltered count before this event, so replacing only topics_count leaves stale filtered-page offsets valid. For example, with 100 forum topics, 10 matching topics and 25 per page, start=25 produces a reverse-query limit of one and displays page 2 of 1. Validate or reset the offset against the filtered count before query limits are calculated, using a hook that exposes start, or redirect an out-of-range request.

Medium severity Persist distinct orders when swapping tied tags

tags/​manager.php:352

Swapping these values does nothing when adjacent tags share prefix_order; the ID tie-breaker keeps their display order unchanged. Both legacy splitting (migrations/v200_data.php:190) and repairs (tags/repairer.php:289) give new components their source's order, so affected tags cannot be reordered relative to each other in the ACP. Swap the entries in the sorted list and persist distinct order positions, with a regression test for tied orders.

Comment thread console/command/repair_tags.php Outdated
Comment thread event/viewforum_listener.php Outdated
@DavidIQ

DavidIQ commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

@DavidIQ I just noticed that phpbb.com uses combined tags, ie: a single tag is actually something like [3.3][DEV]

These are the tags on dot com:

[3.3][DEV]
[3.3][ALPHA]
[3.3][BETA]
[3.3][RC]
[CDB]
[4.0][DEV]
[4.0][ALPHA]
[4.0][BETA]
[4.0][RC]

In its current state, this extension would not split out those combo tags, they would be turned into a single tag.

That would actually defeat some of the purpose, since the idea here would be for 3.3 to be its own tag, and ALPHA to be its own separate tag, for filtering/search purposes as well as the visual distinctions.

We make the migration specifically discover these multi-bracketed prefixes and split them out into individual tags, such that the above 9 tags would become the following 7:

[DEV]
[ALPHA]
[BETA]
[RC]
[CDB]
[3.3]
[4.0]

Associations would still be maintained, so Legacy title: [3.3][DEV] My Extension Becomes: Tags: [3.3], [DEV] Title: My Extension

Can this be covered by a migration? That would be preferred.

@iMattPro

iMattPro commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Can this be covered by a migration? That would be preferred.

Yes, I updated the migration to do this splitting work.

In addition to that, since the migration is only doing this splitting for multi-bracketed prefixes, I also added a CLI command that can be run at any time where you are walked through each tag, and you can split apart any tags through that tool as well - for example, if anybody was using multi-prefixes with some other delimiter than brackets which the migration alone would not convert. So this CLI tool could be used to split A|B or (A)(B) into A and B tags, for example.

Here is an AI-generated breakdown of what the migrations in this PR do:

Summary

Version 2 changes prefixes from text embedded inside topic titles into real topic tags stored separately.

Before:

[3.3][DEV] My Extension

After:

  • Tags: [3.3], [DEV]
  • Topic title: My Extension

No topic or post is deleted. Migration converts legacy prefix records into tag records, consolidates equivalent legacy definitions, connects tags to existing topics, safely removes old prefix text from titles and subjects, then removes obsolete legacy database fields.

Three migrations run in order.

1. v200_schema: Builds new storage

v200_schema.php prepares database without removing old data.

It adds:

  • Tag color. Every migrated tag initially receives blue #4A76A8.
  • Tag display order.
  • A table recording which tags are available in which forums.
  • A table recording which tags belong to which topics.

Old prefix data still exists during this stage. Nothing visible should change yet.

2. v200_data: Converts existing prefixes and topics

v200_data.php performs actual conversion.

Existing prefix definitions

Every legacy prefix maps to one or more tag definitions. Equivalent legacy definitions may be consolidated into one shared tag.

Examples:

Legacy prefix Result
[CDB] One tag named [CDB]
Bug One tag named Bug
[3.3][DEV] Two tags: [3.3] and [DEV]
[a][b][n] Three tags: [a], [b], [n]

Brackets remain part of tag names.

Exactly when splitting happens

Migration splits only a value made entirely from two or more adjacent bracket groups.

These split:

[A][B]
[A][B][C]
[日本語][😇]

These do not split:

[CDB]
[A] [B]
[A][B] extra
Prefix [A][B]
[A][]
[A][ ]

If any extracted tag is invalid or too long for new tag rules, entire legacy value remains one tag. Migration chooses preservation over guessing or partial conversion.

Duplicate handling

Migration reuses an existing exact tag when possible. Matching uses decoded tag names, is case-sensitive, and also considers whether tag is enabled or disabled.

Given enabled prefixes:

[3.3][DEV]
[4.0][DEV]

Migration creates only one enabled [DEV] definition. Both topics point to it.

Existing standalone duplicates are also consolidated when their decoded names and enabled states match, even if they belonged to different forums. Their forum availability and topic assignments are combined and de-duplicated.

When duplicates are consolidated, migration retains first definition in legacy display order. Prefix ID breaks a display-order tie.

Other rules:

  • [DEV][DEV] produces one [DEV] assignment.
  • [DEV] and [dev] remain different because matching is case-sensitive.
  • Enabled [DEV] and disabled [DEV] remain separate tags.
  • Duplicate topic/tag and forum/tag relationships are not inserted.

Forum availability

Legacy prefixes belonged to one forum. New tags can belong to multiple forums.

If [DEV] comes from combined or duplicate prefixes in several forums, resulting shared [DEV] tag becomes available in all those forums.

References to forums that no longer exist are ignored instead of creating invalid relationships.

Enabled and disabled prefixes

Enabled state is part of tag identity during migration.

Sources with matching names but different enabled states remain separate tags. An enabled source does not globally enable a matching disabled tag.

Existing topics retain disabled tags for display, but disabled tags cannot normally be selected for new topics.

Topic assignments

Each topic’s old prefix ID becomes one or more tag relationships.

Example:

Legacy prefix: [3.3][DEV]
Legacy title:  [3.3][DEV] My Extension

Becomes:

Tags:  [3.3], [DEV]
Title: My Extension

Topics with ordinary single prefixes receive one tag unless their prefix was consolidated with an equivalent definition.

Topics without a legacy prefix remain untouched.

If a topic references a deleted or missing legacy prefix definition, migration cannot recover its tag or safely remove its text. That topic remains unchanged.

Text removed from titles and subjects

Migration removes prefix text only when field begins with exact stored prefix followed by one space.

For [DEV], this changes:

[DEV] My Topic

to:

My Topic

It does not change:

[DEV]My Topic
Different title

Even when text does not match, topic still receives tag based on stored legacy prefix ID. This avoids deleting text based on guesses, but could leave both badge and old prefix text visible on unusual manually edited topics.

Migration checks:

  • Topic title
  • First post subject
  • Cached last-post subject on topic
  • Cached last-post subject on forum

Ordinary reply subjects are not rewritten. A reply subject containing old prefix text may therefore retain it.

Moved-topic links

phpBB creates shadow rows when topics move. Migration does not assign tags directly to shadow row. Display code resolves shadow to real destination topic and shows real topic’s tags when viewer has permission to access destination.

Large boards and interrupted upgrades

Topics process in groups of 500. Within each group:

  • New tag relationships are inserted.
  • Relevant titles and subjects are cleaned.
  • Entire group commits together.

If group fails, transaction should roll back that group.

Combined legacy definitions and redundant duplicate definitions are deleted only after all topic groups finish. If upgrade stops early, old definitions remain available for retry. Migration also checks existing relationships to avoid duplicates when rerun.

Migration completion marker is written only after conversion finishes.

3. v200_cleanup: Removes obsolete legacy structure

v200_cleanup.php runs only after data conversion succeeds.

It removes:

  • Old single-prefix field from topics.
  • Old one-forum field from prefix definitions.
  • Old tree-ordering fields.
  • Other obsolete nested-set fields.

After this point, topics use only new many-to-many tag relationships.

What you will notice after upgrading

  • Prefixes appear as separate tag badges instead of being part of title text.
  • Topics can have multiple tags.
  • Combined prefixes become reusable individual tags.
  • Equivalent legacy prefixes with same name and enabled state become one shared tag.
  • Same-name prefixes with different enabled states remain separate.
  • All migrated tags initially use same blue color.
  • Tag order may differ slightly, but can be changed in ACP.
  • Shared tags may become available across several forums.
  • Existing disabled tags still display on old topics.
  • Topic titles become cleaner because leading prefix text is removed where it matches exactly.

What migration does not change

  • Topic IDs
  • Post IDs
  • Authors
  • Post bodies
  • Dates
  • Permissions
  • Topics without prefixes
  • Ordinary reply subjects
  • Text not matching exact legacy prefix pattern

Important cautions discovered

  1. Treat upgrade as one-way.
    Reverse migrations recreate old columns but do not reconstruct original single-prefix IDs or forum assignments from new relationship tables. Consolidated definitions also cannot be separated automatically. Do not downgrade by replacing files. Restore pre-upgrade database backup instead.

  2. Take full database backup first.
    Migration is retry-aware, but schema cleanup permanently removes legacy columns. Some database systems cannot fully roll back every schema operation.

  3. Manually altered titles may retain old prefix text.
    If exact prefix + space is missing, migration preserves title. Topic still receives tag, potentially showing tag badge plus prefix-like title text.

  4. Equivalent duplicate prefixes are consolidated.
    Legacy definitions with same decoded name and enabled state become one tag. Their topic assignments and forum availability are combined. This removes their former independent administrative identity. Same-name definitions with different enabled states remain separate.

  5. Long or malformed combined prefixes remain combined.
    This avoids destructive guesses. Very long preserved tags may need shortening before ACP allows editing them under new 50-character rule.

  6. Corrupt orphan assignments are skipped.
    If topic references missing legacy prefix definition, migration cannot determine tag or safe text removal. Topic remains unchanged. References to missing forums are also skipped when forum availability is copied.

Recommended upgrade procedure: database backup, maintenance mode, upgrade files, run migration, inspect tag ACP, then spot-check representative topics—especially combined prefixes, disabled prefixes, moved topics, duplicate definitions, and manually edited titles. Extension blocks legacy upgrade while board remains enabled.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Legacy-data rewrites require human upgrade validation, and unresolved access-control and pagination issues need correction.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (2)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Remove badge gradient to preserve calculated contrast

adm/​style/​acp_topic_prefixes.css:15

This white gradient undermines the ACP badge contrast calculated from the solid background. For the default #4A76A8, the renderer selects white text, but contrast drops from 4.72:1 on the base color to 3.41:1 at the gradient midpoint, below 4.5:1 for normal-sized text. Remove the overlay so the rendered background matches the contrast calculation.

Medium severity Revalidate pagination offset against the filtered topic count

event/​viewforum_listener.php:143

phpBB has already validated start against the unfiltered forum count before this event runs. A stale filtered URL with start=100 and only 10 matching regular topics therefore keeps an invalid offset: at 25 topics per page, the reverse query returns one topic and pagination reports “page 5 of 1.” Revalidate the offset against the filtered count through a hook that exposes start, before reverse-query offsets and pagination are calculated.

Medium severity Preserve selected tags in the pagination base URL

event/​viewforum_listener.php:208

The pagination hook preserves tags in individual page links, but phpBB subsequently assigns the unfiltered URL to BASE_URL. The “Jump to page” control uses that value when there are more than six pages, so jumping drops the selected tags. This core.viewforum_modify_topics_data callback runs after pagination generation; update BASE_URL here with the selected tags and saved sorting parameters.

Comment thread event/display_listener.php Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature request] Copy prefixes Feature suggestions to improve the functionality and visual design this extension

5 participants