Skip to content

feat(access-control): make Audience Management an explicit prerequisite (NPPD-1846) - #765

Open
adekbadek wants to merge 3 commits into
mainfrom
nppd-1846-make-audience-management-dependency-explicit-for-access
Open

feat(access-control): make Audience Management an explicit prerequisite (NPPD-1846)#765
adekbadek wants to merge 3 commits into
mainfrom
nppd-1846-make-audience-management-dependency-explicit-for-access

Conversation

@adekbadek

@adekbadek adekbadek commented Jul 29, 2026

Copy link
Copy Markdown
Member

All Submissions:

Changes proposed in this Pull Request:

Access Control appears whenever NEWSPACK_CONTENT_GATES is defined and never checked Reader Activation. The ticket describes the symptom as broken UI in /my-account, but that undersells it: class-content-gate.php has no Reader Activation dependency at all, so gates restrict content perfectly well — while everything a gate hands the reader off to is gated on Audience Management:

What Where
Reader registration hooks class-reader-registration.php:28
Magic links (passwordless login) class-magic-link.php, 7 guards
Newspack My Account shell class-my-account.php:116
Auth / account emails class-reader-activation-emails.php:39
Reader session hydration class-session-hydration.php:102
ESP contact sync sync/class-sync.php:76

A registration wall built without Audience Management locks readers out and then gives them no way in.

This lands two halves that are only correct together.

1. The gate-editing screens refuse to work without Audience Management. A shared Audience_Management_Dependency trait — consumed by both surfaces, so the dependency can't be enforced on one and forgotten on the other — provides the check, the localized script data, and a REST permission callback guarding gate creation and duplication. The screens are replaced by a prerequisite state built on the existing Institutions empty-state pattern. The submenu item deliberately stays visible: hiding it would turn "is this required?" into "where did it go?", which is the opposite of making the dependency explicit.

The guard is applied at the wizard router, so all 7 Access Control sections and both Premium Newsletters sections inherit it. Guarding only the gate list would have left #/edit/new/all reachable by bookmark or browser history, with a working Save — the publisher fills in an entire gate and only learns on save.

Premium Newsletters is included because the dependency there is real, and harder. filter_subscription_lists() is hooked unconditionally, so restricted lists vanish from every signup form whether or not Audience Management is on; meanwhile access is granted only via the reader_verified / product_subscription_changed / donation_subscription_changed data events, and reader_verified traces to set_reader_verified(), whose every trigger hook is registered inside if ( self::is_enabled() ). With Audience Management off, a premium newsletter is hidden from readers with no path to unhide it.

2. Audience Management can no longer be switched off while any gate exists. Content_Gate::has_any_gates() plus a 409 refusal in api_update_reader_activation_settings(), and a toggle that explains the lock instead of letting the publisher confirm a destructive dialog and then meet a REST error.

This second half is what makes the first one safe. The ticket's premise that nothing in the admin turns Audience Management off is wrong — Audience → Configuration → Reader Revenue Platform → Change → toggle calls saveConfig( { enabled: false } ) behind a confirm dialog. Without the disable guard, a site that used it would keep serving live gates while Content_Gate::redirect_cpt() bounced the gate CPT list back to the now-blocked wizard and filter_edit_post_link() rewrote every gate edit link to a blocked route — live paywalls with no admin surface left to lift them. The guard gives the invariant Audience Management off ⇒ no gates exist, which is exactly what the wholesale screen replacement relies on.

The guard counts drafts and premium newsletter gates (a draft is publishable; a newsletter gate restricts by the same mechanism) but not trashed gates — a gate the publisher already deleted must not hold the toggle hostage.

Closes NPPD-1846.

Supersedes the prototype in #737, now closed.

How to test the changes in this Pull Request:

  1. On a site with define( 'NEWSPACK_CONTENT_GATES', true ); in wp-config.php, ensure Audience Management is off (wp option update newspack_reader_activation_enabled 0).
  2. Visit Audience → Access Control. You should see the "Set up Audience Management first" state, with no "Add Content Gate" action, and the submenu item still present. Same at Newsletters → Premium, with newsletter-specific copy.
  3. Try the routes directly — #/edit/new/all, #/edit/new/custom, #/institutions, #/settings/countdown-banner, #/settings/content-gifting. All should show the prerequisite state.
  4. From the browser console, confirm the REST guard: a POST to /wp-json/newspack/v1/wizard/newspack-audience-access-control with a valid nonce returns 403 newspack_audience_management_required, and no gate is created. Same for .../newspack-premium-newsletters.
  5. Turn Audience Management on. Every screen and route renders normally; gate creation returns 200.
  6. Create a gate, then go to Audience → Configuration → Reader Revenue Platform → Change. The Audience Management toggle is disabled and explains why. A POST of { enabled: false } to /wp-json/newspack/v1/wizard/newspack-audience/audience-management returns 409 newspack_audience_management_required_by_gates.
  7. Delete the gate and reload. The toggle is interactive again.

Verification

Audience Management off — Access Control and Newsletters → Premium:

Access Control screen with Audience Management off: prerequisite state, no Add Content Gate action

Newsletters > Premium with Audience Management off: prerequisite state with newsletter-specific copy

The gate editor route is covered too, not just the list:

The gate editor route #/edit/new/all is blocked too, not just the gate list

Audience Management on — nothing is over-blocked:

With Audience Management on, the Access Control screen renders normally

The disable guard, locked while a gate exists and released once it is gone:

Audience Management toggle disabled while a content gate exists, with an explanation

Audience Management toggle interactive again once the gate is deleted

Automated:

PHP:  Tests: 2470, Assertions: 7029, Skipped: 7   (0 failures; the 7 skips pre-date this branch)
JS:   Test Suites: 54 passed, Tests: 496 passed   (0 failures)
PHPCS: clean on all 8 changed PHP files under BOTH the plugin ruleset and the
       workspace-root VIP ruleset the pre-commit hook uses
ESLint: 0 errors

Both regression tests were confirmed to actually bite: reverting a single permission_callback token turns the route-wiring test red, and un-wrapping one router section turns the section-completeness test red.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully run tests with your changes locally?

Rollout safety. Every one of the 19 production sites that currently has Audience Management off was checked directly: all have zero content gates, so no live site meets the newly-blocked condition and nothing needs migrating. NEWSPACK_CONTENT_GATES is a wp-config.php constant marked @status draft, so Access Control only exists where someone deliberately defined it.

Cross-repo note. includes/content-gate/class-content-gate.php is in the content-gate contract consumed by newspack-manager. The change there is purely additive (one new static method, has_any_gates()); no existing signature or behaviour changes, so no coordinated PR is needed.

Deliberately not included. The Access Control help doc still doesn't list Audience Management as a dependency — the question that started the thread. Not a code change, and still unowned.

Reviewer note. Codex was unavailable during review (timed out twice without producing a review body), so that reviewer slot is degraded rather than clean.

@adekbadek
adekbadek requested a review from Copilot July 29, 2026 08:42

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…te (NPPD-1846)

Access Control appeared whenever NEWSPACK_CONTENT_GATES was defined and never
checked Reader Activation. Gates restrict content perfectly well without it, but
everything a gate hands the reader off to is gated on it, so a registration wall
built without Audience Management locked readers out and gave them no way in.

Both gate-editing surfaces now refuse to work without it, via a shared
Audience_Management_Dependency trait: gate creation and duplication are guarded at
the REST layer, and the screens are replaced by a prerequisite state. The guard is
applied at the wizard router so every section inherits it, rather than only the
gate list.

Conversely, Audience Management can no longer be switched off while any gate
exists. That keeps "Audience Management off" and "no gates exist" the same state,
which is what makes replacing the screens safe: without it, a site that disabled
Audience Management would keep serving live gates with no admin surface left to
unpublish them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWDEh5hvsTEmMiSkfbrVuS
@adekbadek
adekbadek force-pushed the nppd-1846-make-audience-management-dependency-explicit-for-access branch from ada4cfd to c500bd8 Compare July 29, 2026 08:47
@adekbadek
adekbadek marked this pull request as ready for review July 29, 2026 09:23
@adekbadek
adekbadek requested a review from a team as a code owner July 29, 2026 09:23
…ce-management-dependency-explicit-for-access

# Conflicts:
#	plugins/newspack-plugin/includes/wizards/audience/class-audience-content-gates.php
#	plugins/newspack-plugin/src/wizards/types/window.d.ts
…ce-management-dependency-explicit-for-access

# Conflicts:
#	plugins/newspack-plugin/includes/wizards/audience/class-audience-content-gates.php
#	plugins/newspack-plugin/includes/wizards/newsletters/class-premium-newsletters-wizard.php
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.

2 participants