Skip to content

9B: Notices queue (merge after #8 — independent of #9A) - #10

Merged
nikolaystrikhar merged 12 commits into
mainfrom
10-notices-queue
Aug 11, 2026
Merged

9B: Notices queue (merge after #8 — independent of #9A)#10
nikolaystrikhar merged 12 commits into
mainfrom
10-notices-queue

Conversation

@nikolaystrikhar

@nikolaystrikhar nikolaystrikhar commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What: the notice queue — merge, conflict and dependency messages — as Notices\Queue, Notices\Store and Notices\Renderer behind Notices\Queue_Interface.

Usage:

$queue = new Queue();
$queue->queue_conflict_notice( $sub_plugin );
$queue->render();                      // prints the queue, then consumes it

$queue = new Queue( null, $renderer ); // keep the queue, replace the markup
$queue = new Queue( $store );          // keep the markup, move the queue

@nikolaystrikhar nikolaystrikhar changed the title 10: Notices queue 10: Notices queue (merge after #9) Aug 11, 2026
Three defects, each of which loses the one warning a site owner gets that
their plugin was deactivated.

Storage was a transient. Verified in core: set_transient() short-circuits
to wp_cache_set() and never touches the database when an external object
cache is present, so on a Redis or Memcached site the queue lived only in
the cache -- where wp_cache_flush(), which deploy scripts and every purge
button call, destroys it. The merge notice is raised once and never
re-queued. It is an option now.

The queue was per-site while the deactivation is network-wide. The resolver
passes $network_wide to deactivate_plugins(), removing the plugin from every
site, but the explanation landed in whichever site's options table served
the request. On a fifty-site network the superadmin would never find it.
Multisite uses network options now.

render() consumes the queue and had no capability check, and it is wired to
a hook that fires for anyone who can reach wp-admin. A subscriber loading
profile.php silently swallowed the notice, and nothing re-queues it. It
checks activate_plugins first, which on multisite correctly resolves to
superadmins.

Also drop non-string entries instead of printing them, expose
option_name() so a host can render the same queue without replacing the
implementation, and cover the corrupted-queue, capability, cache-flush and
missing-prefix paths.
…ference

The queue's accessor moved out of this change, so the test suite no longer
imports Loader, resets it, or asserts that it resolves the default notices.
Nothing here depended on that reset for isolation: the option is deleted in
setUp() and tearDown(), and the hook prefix is reset through Config_State, so
each test starts from an empty queue and an unset prefix on its own.

The two raw queries that read the option row straight out of the database now
pass the table through the %i identifier placeholder instead of interpolating
it into the SQL. The query text is a literal again, which is what wpdb::prepare()
asks for and what static analysis was rejecting.

Verified against WordPress core before collapsing the multisite branching:
update_site_option() delegates to update_network_option( null, ... ), which
outside multisite ends in update_option( $option, $value, false ) — or
add_option( $option, $value, '', false ) on the first write. Autoload is off on
both paths, which is what the queue needs. get_site_option() and
delete_site_option() fall through to get_option() and delete_option() the same
way, so one call is correct on either install type.

Also gives the queue a README section of its own: the option name, that it is a
network option on multisite, that rendering is gated on activate_plugins and
consumes the queue, and that option_name() is public so a host can render the
queue without replacing anything.
@nikolaystrikhar nikolaystrikhar changed the title 10: Notices queue (merge after #9) 10: Notices queue (merge after #8 — independent of #9) Aug 11, 2026
@nikolaystrikhar
nikolaystrikhar changed the base branch from 09-loader-resolve to 08-registrar August 11, 2026 10:47
@nikolaystrikhar nikolaystrikhar changed the title 10: Notices queue (merge after #8 — independent of #9) 9B: Notices queue (merge after #8 — independent of #9A) Aug 11, 2026
One class decided what a notice said, where it was kept, who could consume
it and how it was drawn. Task 14 adds a fifth job to the same class by
hanging the activation-error rewrite off it.

Notice_Store owns the option, Notice_Renderer owns the markup and the
severity map, and Notices keeps the interface, the message defaults and the
capability gate. The gate stays with the orchestration because it guards
clearing the queue as much as drawing it.

Both collaborators are constructor arguments that default to the standard
implementations, so new Notices() — what Loader::resolve() builds when the
container holds no binding — behaves as before and Notices_Interface is
untouched. NoticesTest is unchanged apart from three added tests covering
the new seam.
Notices was a plural bag noun naming the subject rather than the job, and it
read worst of the three once the split landed beside Notice_Store and
Notice_Renderer, which do say what they do.

The folder now carries the subject and the class carries the job, following
Conflict\Resolver — which is also why Queue_Interface sits beside its
implementation rather than in Contracts\.

  Contracts\Notices_Interface -> Notices\Queue_Interface
  Notices                     -> Notices\Queue
  Notice_Store                -> Notices\Store
  Notice_Renderer             -> Notices\Renderer

No behaviour change: the interface keeps the same method set, and new Queue()
does what new Notices() did. Renaming is free now and a breaking change after
1.0.

The spec's collaborator table and the plan's deviation 7 record the move. The
plan's Step 4/5 listings still show the pre-rename shape, as they still show
the transient deviation 1 replaced.

Branches 09-loader-resolve, 11-loader-load-path and 12-conflict-resolver still
name the old type and will need the new import when they rebase.
@nikolaystrikhar
nikolaystrikhar requested review from d4mation and removed request for d4mation August 11, 2026 11:49
README keeps the docs-index structure from 08-registrar; this branch's
notices documentation moves to docs/notices.md rather than growing the
README back.

In the plan, the Task 11 teardown takes one line from each side:
all_admin_notices is this branch's deliberate hook change, and
Loader_State::reset() is 08-registrar's fix for the no-test-seams rule.

Tasks 1-6 shipped in PRs #1-#6, so their plan sections are removed and
the rule for doing so is recorded in CLAUDE.md. CLAUDE.md's collaborator
table, boot lifecycle and Keys section are corrected to match the split
Notices\ classes and the option-backed queue.
Comment thread src/Notices/Contracts/Queue_Interface.php
Comment thread src/Notices/Store.php Outdated
Comment thread src/Notices/Renderer.php Outdated
Comment thread tests/unit/Notices/StoreTest.php Outdated
- Introduced `Notices\Contracts\Queue_Interface` to define the structure for managing admin notices.
- Updated `Config` class to include methods for generating option names with normalized prefixes, ensuring consistent storage keys.
- Enhanced `Store` class to utilize the new option name generation method.
- Updated documentation to clarify the distinction between hook names and option names, including examples of their usage.
- Added unit tests to validate the new functionality and ensure proper handling of hook prefixes in option names.
- Updated `Notices\Queue` and `Notices\Contracts\Queue_Interface` to allow messages with markup, utilizing `wp_kses_post()` for safe rendering.
- Modified `Notices\Renderer` to reflect changes in message processing, ensuring links and formatting are preserved while disallowing scripts and event handlers.
- Revised documentation to clarify the handling of notices and the implications of markup in messages.
- Enhanced unit tests to validate the new rendering behavior and ensure proper handling of markup in notices.
- Renamed the newly created unit test file from `NoticesActivationErrorTest.php` to `QueueActivationErrorTest.php` for better clarity and organization.
- Made minor adjustments to the documentation to improve readability and consistency, including formatting changes in the `Notices\Contracts\Queue_Interface` section.
- Ensured that the new test file aligns with the updated structure of the Notices classes.
Base automatically changed from 08-registrar to main August 11, 2026 14:58
Comment thread src/Notices/Renderer.php Outdated
wp_kses_post() allows <p>, so a host message that already carries one was
being nested inside another -- markup the parser resolves by closing the
outer tag early and stranding a </p>, drawing an empty line above the
notice. A <ul>, which the same allowlist preserves, cannot sit in a <p>
at all. wpautop() wraps only what needs wrapping and turns a blank line
into a real paragraph break.
@nikolaystrikhar
nikolaystrikhar merged commit 7eb102b into main Aug 11, 2026
5 checks passed
@nikolaystrikhar
nikolaystrikhar deleted the 10-notices-queue branch August 11, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants