Skip to content

Add subscriber gating blocks and settings - #201

Draft
phpbits wants to merge 34 commits into
mainfrom
feature/19815117-subscriber-gating
Draft

phpbits wants to merge 34 commits into
mainfrom
feature/19815117-subscriber-gating

Conversation

@phpbits

@phpbits phpbits commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds subscriber gating to the plugin (Teamwork task 19815117). Editors wrap content in a Gated Content block. Readers see a preview, then blurred locked content behind a subscribe card. Submitting the block's own beehiiv form unlocks the content in place and sets a site-wide cookie.

  • Gated Content block (beehiiv/gated-content) holds inner blocks and renders on the server. The first inner block is the preview, or everything before an optional Gate Divider block. Per-block form ID, headline and text, plus card background and text colours.
  • Gate Divider block (beehiiv/gated-content-divider) is allowed only directly inside a gated block.
  • Settings page: a new "Subscriber gating" section with the default headline, text, subscribe form ID and unlock cookie lifetime (1 to 365 days, default 30).
  • Front end: the script listens for BEEHIIV_SUBSCRIBER_FORM_SUBMITTED from subscribe-forms.beehiiv.com (and the legacy embed host) and unlocks only when externalEmbedId matches the block's form ID.
  • Editor: missing-publication notice with a link to the settings page, dotted block outline, and an editor marker where the wall falls.

Behavior worth knowing

  • Logged-in users always see the full post (client decision).
  • Only the first gated block on a page counts.
  • The server always renders the gate and the browser removes it when the cookie exists, so it works behind page caching. Returning readers may see a brief flash.
  • Gated content stays in the page source (soft wall).
  • Added at the team's request, not in the client's original spec: card colour options and the default subscribe form ID.

Testing

  • Jest: tests/js/gated-content-view.test.js (6 tests) passes.
  • Lint: ESLint, stylelint and phpcs clean on the changed source.
  • Confirmed manually against a real beehiiv form: the submit message matches and the gate unlocks.
  • Not run: PHPUnit (GatedContentRenderTest, SubscriberGateDefaultsTest) and the Playwright e2e suite for this feature.

Known gaps

  • Publication select on the settings page is a required field, so gate defaults cannot be saved until a publication is chosen.
  • The missing-publication notice only shows on post screens, because the plugin loads its editor settings there only.

Feature: admin-interface/settings-page/subscriber-gate-defaults

Implements:
- US-001: default gate headline and text with fallback on read
- US-002: unlock cookie lifetime, whole days 1 to 365, else 30
- US-003: Publication and Subscriber gating saves keep each other's values

The Subscriber gating section prints for every administrator, including
when the Publication section is hidden. Options::get_gate_settings()
returns final values for the gated content block.
Feature: custom-blocks/gated-content-block/gated-content-block

Implements:
- US-001: gated block with Subscribe form ID, Headline and Text
- US-002: optional gate divider, valid only directly inside the gated block
- US-003: server-rendered gate card over blurred, height-limited locked content
- US-004: unlock in place when the block's own form is submitted
- US-005: site-wide unlock cookie, logged-in bypass, first gate per page
- US-006: locked content is inert and hidden from assistive technology

The gate view script loads only on pages that render a gated block.
…ew blocks

has_divider() re-iterated the shared inner block list from inside the loop
over that same list, so the cursor reset and the loop never ended whenever
two or more blocks came before the divider (or no divider was present).
…gated block, restore publication reset on save
…ard"

The form's alignment is set inside the beehiiv form itself, so the card CSS had no effect.
…specs

Options::sanitize() ignored the publication fields unless the section flag was present. WordPress runs the callback twice on the first save, when no option row exists, and the second run has no flag. The first save therefore stored a blank publication and template. Submitted fields are now always applied, and a shown section still clears missing ones.

E2E specs now send the section flag as the real form does, expect the gate defaults and the gateFormId key, and no longer expect a required publication select. The gated-content spec makes sure a theme is active, because a tests site with a missing theme renders blank front-end pages.
The settings page left the post template select enabled when no publication was chosen, which did not match the PRD. It is now disabled on page load when there is no publication, and the settings script disables it again when the publication is cleared. A disabled field is not submitted, and the shown Publication section already saves a missing template as blank.

The publication spec no longer expects this to fail and now checks the field both ways. One PHPUnit case used a lone paragraph as the nothing-to-gate example, but a block with no divider is fully gated. It now uses a divider with nothing after it.
Choosing a publication and then clearing it started two template loads. The first load finished last and re-enabled the post template dropdown with templates for a publication that was no longer selected. Each load now checks that it is still the newest one before it changes the page. Clearing the publication also resets the refresh button label.
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.

1 participant