Skip to content

feat(content-gate): add per-block access control for Group, Stack, and Row blocks#4646

Open
dkoo wants to merge 28 commits intotrunkfrom
feat/block-access-control
Open

feat(content-gate): add per-block access control for Group, Stack, and Row blocks#4646
dkoo wants to merge 28 commits intotrunkfrom
feat/block-access-control

Conversation

@dkoo
Copy link
Copy Markdown
Contributor

@dkoo dkoo commented Apr 8, 2026

All Submissions:

Changes proposed in this Pull Request:

Adds per-block visibility controls to core/group, core/stack, and core/row blocks, allowing editors to show or hide individual blocks based on one or more content gates, or custom rules using the same Registered and Paid access rules used by content gates.

"Gate" mode:
Screenshot 2026-04-08 at 3 31 34 PM

"Custom" mode:
Screenshot 2026-04-08 at 3 35 15 PM

"Custom" mode exists because publishers often want to control per-block visibility at a different and/or more granular level than content gates. To use a real-world example, a subscriber might have access to restricted content across the site if they have an active subscription to either a Digital-Only or Print+Digital subscription product, but PDF issue embeds on magazine-focused pages should be visible only to Print+Digital subscribers.

Editor UI (edit_others_posts capability required, on supported post types only):

  • A new Access Control panel appears in the block Inspector for Group, Stack, and Row blocks.
  • A "Visible to / Hidden to" toggle sets the visibility mode. It is disabled until at least one rule is configured.
  • A Registered readers toggle restricts the block to logged-in readers, with an optional Require verification sub-option.
  • Additional toggles appear for each registered custom access rule (e.g. subscription products, institution access). Rules with options render a FormTokenField; free-text rules render a TextControl.
  • When all rules are cleared the visibility mode resets to "Visible to" automatically.

Frontend evaluation (via render_block filter):

  • If a block has no active rules, it renders normally with no overhead.
  • When rules are configured: visible mode renders the block only to matching readers; hidden mode renders it only to non-matching readers.
  • Registration rules check login status and optionally the email-verified user meta.
  • Custom access rules delegate to Access_Rules::evaluate_rules(), using the same AND/OR grouped logic as content gates.
  • Results are cached per request (keyed by user_id + md5(rules)) so repeated blocks with identical rules evaluate only once.

New files:

  • includes/content-gate/class-block-visibility.php — PHP class with render_block filter, server-side attribute registration, and asset enqueue.
  • src/content-gate/editor/block-visibility.tsx — React/TS entry: attribute registration filter, Inspector HOC, and all panel components.
  • src/content-gate/editor/index.d.ts — Shared TypeScript types for the content-gate editor directory.
  • tests/unit-tests/content-gate/class-block-visibility.php — 24 PHPUnit tests.
  • src/content-gate/editor/block-visibility.test.ts — 7 Jest tests for the attribute registration filter.

Closes NPPD-1430.

How to test the changes in this Pull Request:

Prerequisites:

  • Ensure NEWSPACK_CONTENT_GATES is true (n wp config set NEWSPACK_CONTENT_GATES true --raw).
  • Build assets: n build newspack-plugin. Confirm dist/content-gate-block-visibility.js exists.
  • Log in as an Editor or Administrator.

Inspector panel:

  1. Create or edit a post. Add a Group block. Open the block Inspector (right sidebar).
  2. Confirm an "Access Control" panel appears near the bottom, above "Advanced".
  3. Expand the panel. Confirm the "Visible to / Hidden to" toggle is visible but disabled (greyed out).
  4. Toggle "Registered readers" on. Confirm the visibility toggle becomes enabled and a "Require verification" checkbox appears beneath it.
  5. Toggle all rules off. Confirm the visibility toggle resets to "Visible to".
  6. Repeat steps 4–8 for a Stack block and a Row block.
  7. Add a Paragraph block. Confirm the Access Control panel does not appear.
  8. Log in as a Contributor. Confirm the Access Control panel does not appear.

Frontend — "Custom" mode with "visible" visibility:

  1. Configure a Group block in "Custom" mode and enable the "Registered readers" rule and "Visible" visibility. Add content inside. Save the post.
  2. Open the post while logged out. Confirm the Group block content is absent from the page source.
  3. Log in as a subscriber. Open the post. Confirm the Group block content is present.
  4. Edit the Group block to add "Active subscription" and choose at least two products. Save.
  5. Open the post while logged out. Confirm the Group block content is absent from the page source.
  6. Log in as a reader who does NOT have an active subscription with any of the required products. Confirm the Group block is absent.
  7. Log in as a reader who has an active subscription with one of the required products and refresh. Confirm the Group block is present.

Frontend — "Custom" mode with "hidden" visibility:

  1. Switch the same block to "Hidden to". Save.
  2. Open while logged in. Confirm block content is absent.
  3. Open while logged out. Confirm block content is present.

Verification sub-option:

  1. Enable "Require verification". Save.
  2. Log in as an unverified reader. Confirm the block is hidden (for "Visible to" mode).
  3. Verify the reader's email. Confirm the block becomes visible.

Front-end — "Gate" mode

  1. Change the block configuration to "Gate" mode and select several content gates. Save.
  2. Repeat block visibility tests for logged-out readers, logged-in readers who can't bypass any of the selected gates, and logged-in readers who can bypass at least one selected gate.

Post type guard:

  1. Open a post type not in Content_Restriction_Control::get_available_post_types(). Confirm the Access Control panel does not appear and the JS asset is not enqueued.

Editors are exempt:

  1. While logged in as a user who has edit access to the containing post (such as an admin, editor, or author/contributor for that post), preview or view the post on the front-end. Confirm that all blocks are visible regardless of access requirements.

Graceful degradation:

  1. Check out another branch of this repo, or deactivate the Newspack Plugin entirely.
  2. Refresh the post in the editor and confirm that all Group/Row/Stack blocks continue to render without block validation errors.
  3. View the post on the front-end and confirm that all Group/Row/Stack blocks render and are visible to all readers.

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 ran tests with your changes locally?

dkoo and others added 19 commits April 7, 2026 16:32
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds per-block visibility controls for core/group, core/stack, and core/row blocks using Newspack Content Gate access rules, enabling editors to show/hide individual blocks to specific reader segments without creating a full gate.

Changes:

  • Adds a new editor Inspector “Access Control” panel and registers block attributes for visibility mode + rule configuration.
  • Adds a PHP render_block filter to enforce per-block visibility on the frontend, with per-request caching.
  • Adds PHPUnit + Jest coverage and wires the new editor bundle into Webpack.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
webpack.config.js Adds a new build entry for the block-visibility editor script.
includes/content-gate/class-content-gate.php Includes the new Block_Visibility feature file.
includes/content-gate/class-block-visibility.php Implements server-side attribute registration, editor asset enqueue, and frontend render filtering/caching.
src/content-gate/editor/block-visibility.tsx Registers attributes and injects the Inspector panel UI for target blocks.
src/content-gate/editor/editor.scss Styles the Access Control panel UI elements.
src/content-gate/editor/index.d.ts Adds shared TS types used by the content-gate editor code.
tests/unit-tests/content-gate/class-block-visibility.php Adds PHPUnit tests for rule evaluation, rendering behavior, and caching.
src/content-gate/editor/block-visibility.test.ts Adds Jest tests for the attribute registration filter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

dkoo and others added 3 commits April 8, 2026 13:54
- Use [] not stdClass as default for newspackAccessControlRules block attribute
- Add comment on get_post_type() === false in enqueue_block_editor_assets
- Fix setRegistration to not mutate its parameter; use immutable spread
- Fix counting_rule test to use unique ID per run to avoid closure issues
- Fix "Visiblity" typo in VisibilityControl help text
- Guard test_rule registration against duplicate registration across tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bypass access control during REST requests so blocks are never hidden
  in the editor's block renderer or preview contexts
- Defensively cast newspackAccessControlRules to array in case the block
  parser yields a stdClass for the object-typed attribute
- Fix FormTokenField empty label; use config.name + hideLabelFromVision

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dkoo dkoo requested a review from thomasguillot April 8, 2026 20:18
@dkoo dkoo self-assigned this Apr 8, 2026
@dkoo dkoo added [Status] Needs Review The issue or pull request needs to be reviewed [Status] Needs Design Review labels Apr 8, 2026
Cover the case where a user with edit_post capability on the current
post sees all blocks regardless of access rules, and verify that a
non-editor is still subject to normal rule evaluation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dkoo dkoo marked this pull request as ready for review April 8, 2026 20:24
@dkoo dkoo requested a review from a team as a code owner April 8, 2026 20:24
dkoo and others added 5 commits April 8, 2026 14:39
Blocks can now be linked to one or more existing content gates instead
of defining access rules inline. Gate rules are resolved server-side at
render time so any change to a gate is immediately reflected in every
block that references it.

- Add newspackAccessControlMode attribute ('gate' default, 'custom')
- Add newspackAccessControlGateIds array attribute for gate links
- Gate mode uses OR logic: reader must satisfy any one selected gate
- Deleted/unpublished gates are silently skipped; a block with only
  inactive gates passes through with no restriction
- Localize available published gates to newspackBlockVisibility JS data
- Add GateControls FormTokenField component to the inspector panel
- Add mode toggle (Gate / Custom) with Gate as the default
- Add 7 new PHPUnit tests covering gate mode evaluation paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Status] Needs Design Review [Status] Needs Review The issue or pull request needs to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants