Skip to content

Initial FrontConsent plugin: cookie consent banner extracted from FrontBlocks - #1

Merged
davidperezgar merged 10 commits into
mainfrom
init-dev
Sep 28, 2026
Merged

davidperezgar merged 10 commits into
mainfrom
init-dev

Conversation

@davidperezgar

Copy link
Copy Markdown
Contributor

Summary

  • New standalone, free WordPress plugin: FrontConsent — a GDPR/ePrivacy-compliant cookie consent banner, extracted from FrontBlocks Site Tools' bundled Cookie Notice module (see docs/plan.md for the full product plan).
  • Full frontend banner (Accept/Reject, full-width bar / boxed panel / centered popup layouts, Google Consent Mode v2, GTM/GA4 and Clientify/Brevo/ChatGPT Ads tracking integrations gated behind consent) ported to its own FrontConsent\ namespace and frontconsent_settings option.
  • includes/Migration.php: one-time migration on activation that copies Cookie Notice settings and accepted/rejected stats from FrontBlocks' frontblocks_settings option, and disables FrontBlocks' own banner so the two never overlap.
  • Standalone admin settings page (Settings → FrontConsent) with the same banner configuration UI (message, labels, policy page, layout/colors/expiration, tracking integrations) as a plain-CSS page (no Tailwind build step).
  • AGENTS.md/CLAUDE.md, readme.txt/readme.md, phpcs.xml.dist, composer.json scaffolded following the same conventions as FrontBlocks/its other CLOSE plugins.

Companion change in FrontBlocks: closemarketing/frontblocks#314 (Cookie Notice tab there now promotes installing this plugin instead of showing config fields).

Test plan

  • php -l on all PHP files
  • composer lint / composer phpstan — not run here yet (fresh vendor/ installed locally); please run in CI
  • composer test — no WordPress test environment available in this environment
  • Manual: activate on a site that has FrontBlocks Cookie Notice enabled and confirm settings/stats migrate, and the FrontBlocks banner turns off
  • Manual: configure and preview the banner (bar/box/popup) under Settings → FrontConsent

🤖 Generated with Claude Code

…ntBlocks

Standalone free plugin providing the cookie consent banner (Accept/Reject,
Google Consent Mode v2, GTM/GA4 and other tracking integrations gated
behind consent) previously bundled inside FrontBlocks' Cookie Notice
module. Includes a one-time migration that copies settings and stats
from FrontBlocks' frontblocks_settings option on activation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-27T09:58:38.626576Z 9b12a8c New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

- phpstan.neon.dist + tests/phpstan-bootstrap.php (level 1, WordPress stubs)
- .phplint.yml
- .github/workflows/phplint.yml and phpunit.yml (target main)
- phpunit.xml.dist, tests/bootstrap.php, bin/install-wp-tests.sh
- Ported Cookie Notice unit/JS tests from FrontBlocks (namespace,
  option names and hook prefixes updated to FrontConsent's own)
- phpcbf formatting fix in Settings.php

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b90c60fbac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread assets/cookie-notice/frontconsent-cookie-notice.js
Comment thread includes/Migration.php
Comment thread frontconsent.php Outdated
Comment thread includes/Frontend/CookieNotice.php Outdated
Comment thread includes/Frontend/CookieNotice.php Outdated
Comment thread includes/Frontend/CookieNotice.php Outdated
Comment thread includes/Frontend/CookieNotice.php Outdated
davidperezgar and others added 3 commits September 26, 2026 14:02
- Add a persistent "Cookie preferences" reopen trigger so a visitor can
  withdraw an acceptance or replace a rejection at any time, instead of
  the decision being permanent once made (readme.txt already claimed
  this was possible).
- Migration: convert FrontBlocks' retired cookie_notice_gtm_id /
  cookie_notice_ga4_id fields into the shared
  cookie_notice_tracking_integrations list during migration — copying
  them verbatim left them dead, since CookieNotice never reads those
  two keys directly.
- Fix activation redirect to the actual registered settings URL
  (options-general.php, since the page is added via add_options_page())
  instead of a nonexistent admin.php one.
- Compare Site Kit's configured GTM/GA4 ID against FrontConsent's own
  before suppressing it, in both the AJAX config endpoint and the
  settings page UI — a different Site Kit tag no longer hides/drops a
  distinct one configured here.
- Replace the default banner message's implied-consent wording ("by
  browsing this website, you agree...") with one that asks for an
  affirmative decision, matching docs/plan.md's own AEPD requirement.
- Set Google Consent Mode's default state from the registered JS file
  too, not only the inline wp_head script, so a strict CSP that blocks
  the unnonced inline script no longer leaves Consent Mode without a
  default entirely.
- Add regression tests for all of the above (Migration, Site Kit ID
  comparison, reopen trigger) plus JS coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02063eb8f7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread frontconsent.php
Comment thread assets/cookie-notice/frontconsent-cookie-notice.js Outdated
Comment thread includes/Frontend/CookieNotice.php
Comment thread frontconsent.php
Comment thread includes/Frontend/CookieNotice.php
- Require Migration.php and Frontend/CookieNotice.php directly in
  Plugin_Main, instead of relying on vendor/autoload.php — the plugin
  ships no runtime Composer dependencies, so that autoloader is only
  ever present in a local dev checkout, never in an installable plugin
  zip. Without this, activation fataled on any such install.
- Send the accept/reject consent update through gtag() (queuing the
  same arguments-object shape gtag() itself produces), instead of
  pushing a plain array onto dataLayer directly, which a running
  Consent Mode-aware tag does not process as a consent command.
- Let a visitor reopen consent from the cookie policy page: the reopen
  trigger's click handler now navigates to the home page there instead
  of reloading in place, since the banner never renders on the policy
  page itself.
- Clear the deferred activation-redirect flag on a bulk/network
  activation bail, instead of leaving it to redirect an unrelated later
  admin request.
- Fix get_readable_on_white_color() to test contrast against the
  panel's actual configured background instead of always assuming
  white, so a dark background with a light accent no longer falls back
  to a dark neutral that would be nearly invisible there.
- Add regression tests for all of the above (PHP + JS).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a30ce20de

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread includes/Frontend/CookieNotice.php Outdated
Comment thread assets/cookie-notice/frontconsent-cookie-notice.js
Comment thread includes/Migration.php Outdated
Comment thread assets/cookie-notice/frontconsent-cookie-notice.js
Comment thread includes/Plugin_Main.php
- Run the CSP-fallback Consent Mode default immediately at script parse
  time (not gated by DOMContentLoaded) and enqueue the script in <head>
  (in_footer: false) instead of the footer — otherwise it ran far too
  late to matter under a CSP that blocks the inline wp_head script.
- Apply a stale-consent override after any per-category override state,
  not before — an add-on reporting stale consent must always win over a
  (possibly stale) granted override, matching the inline PHP script's
  own ordering.
- Claim the one-time migration guard atomically via add_option()
  instead of a plain get_option()/update_option() check-then-act pair,
  so two concurrent first requests can no longer both run the
  migration and double-count migrate_stats()'s additive counters.
- Reveal the "Cookie preferences" reopen trigger immediately after an
  in-page accept/reject, not only on the next page load.
- Construct Frontend\CookieNotice (which registers the cache-purge and
  frcn_cookie_notice_settings_updated hooks) before Migration::maybe_run()
  writes frontconsent_settings for the first time, so those hooks are
  registered when the migration's own write happens.
- Add regression tests for all of the above (PHP + JS).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7104480028

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread includes/Plugin_Main.php Outdated
Comment thread .github/workflows/phplint.yml
Comment thread includes/Frontend/CookieNotice.php Outdated
- Convert both wp_head inline <script> tags (Consent Mode default and
  the tracking bootstrap) to wp_add_inline_script() attached to a
  source-less registered handle, instead of raw <script> echoes.
- Convert both <noscript><style> fallback blocks to wp_add_inline_style()
  the same way.
- Replace the inline cookie-icon SVG markup (previously echoed with a
  phpcs:ignore) with an external SVG file loaded via CSS mask-image, so
  the icon still picks up the configured accent color through
  currentColor-equivalent background-color, without ever echoing raw
  SVG markup from PHP.
- Add an "External services" section to readme.txt documenting Google
  Tag Manager/GA4, Clientify Analytics, Brevo and ChatGPT Ads (OpenAI),
  with what's sent, when, and links to each provider's terms/privacy
  policy.
- Remove the dead Donate link (404) and add the submitting account to
  Contributors.
- Update JS test regex extraction to match the new PHP string-concat
  script bodies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a9baafa0a3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread includes/Frontend/CookieNotice.php
Comment thread includes/Migration.php Outdated
Comment thread includes/Frontend/CookieNotice.php
Comment thread frontconsent.php
- Run Migration::maybe_run() before constructing Frontend\CookieNotice,
  instead of after — its constructor reads is_enabled() once,
  synchronously, so constructing it first meant the very first request
  after activation saw the pre-migration (disabled) state and never
  registered the frontend hooks for that request. Migration now calls
  CookieNotice::handle_settings_changed() directly for its own write
  (both methods made public static), instead of relying on
  CookieNotice's own option hooks to have been registered in time.
- Add a real no-JS <form> fallback for Accept/Reject: the buttons were
  previously type="button" with JS-only click handlers, so without
  JavaScript neither control did anything, permanently blocking content
  in the box/popup layouts for a no-JS visitor. They now also submit a
  form to admin-post.php (log_consent_form_callback(), processed by the
  newly extracted, directly testable process_consent_form_submission())
  which sets the same cookie server-side and redirects back; JS
  continues to intercept the click and handle it entirely client-side
  when it can run.
- Use an atomic SQL addition for migrate_stats() instead of a plain
  get_option()/update_option() round trip, so a decision logged by a
  concurrent visitor between migration's read and write is no longer
  silently overwritten.
- Always set the consent cookie's path to '/' instead of COOKIEPATH —
  on an install where Home URL and Site URL have different paths,
  COOKIEPATH (Home URL-derived) scoped the cookie to a path the
  admin-ajax.php request (Site URL-derived) fell outside of, so the
  browser omitted it and configured tracking never loaded after
  consent.
- Skip the deferred activation redirect during admin AJAX requests
  (wp_doing_ajax()) — an unrelated background request like Heartbeat
  could otherwise consume the flag before the activating administrator
  ever saw the settings redirect.
- Add regression tests for all of the above.

Not changed (dismissed in review thread, with reasoning):
- actions/checkout@v7 in phplint.yml: the tag exists and CI has been
  passing with it; kept as-is.
- Binary Consent Mode "denied" not fully blocking an independently
  loaded Site Kit tag: inherent to how Google Consent Mode Advanced
  mode works for any integration, not something this plugin's binary
  accept/reject can change without actively intercepting network
  requests — the documented differentiator of the planned Pro tier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 757b1623f5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread includes/Frontend/CookieNotice.php Outdated
Comment thread includes/Frontend/CookieNotice.php Outdated
Comment thread includes/Frontend/CookieNotice.php Outdated
Comment thread .github/workflows/phpunit.yml
Comment thread assets/admin/settings.css Outdated
- Set the no-JS form's consent cookie via a hand-built Set-Cookie
  header() instead of setcookie()'s options-array form, which requires
  PHP 7.3+ — this plugin's own declared minimum is PHP 7.0, so that
  form silently failed to store anything on 7.0-7.2. The header string
  itself is built in a separately testable method
  (build_consent_cookie_header_value()).
- Drop the nonce check on the no-JS form entirely: the banner it's
  embedded in is cache-neutral HTML (rendered identically for every
  visitor of a URL), so any nonce baked into it goes stale the moment
  a full-page cache keeps that page around, and unlike the AJAX path a
  no-JS visitor has no way to fetch a fresh one first. Same accepted
  risk profile as the already-unauthenticated get_config_callback().
- Post the no-JS form's action to a frontend-origin-forced admin-post.php
  URL (get_frontend_origin_admin_url(), generalized from get_ajax_url())
  instead of a raw admin_url() call, so the cookie set on submission
  isn't scoped to a different backend host that a redirect back to the
  frontend host would never see again.
- Run the existing tests/js/ suite in CI (a separate js-tests job),
  instead of it existing only as a local npm script nothing invoked.
- Scope the admin settings preview CSS rules under .frcn-settings-wrapper,
  matching every other rule in the stylesheet, so they can't leak into
  markup emitted elsewhere in wp-admin.
- Update/add tests for all of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b12a8ce95

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// and Site URL have different paths, COOKIEPATH would scope
// the cookie to a path admin-ajax.php falls outside of, and
// the browser would silently omit it from that request.
'cookiePath' => '/',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Isolate consent cookies per WordPress installation

When two independent WordPress installations share a hostname under different paths, such as /shop/ and /blog/, this root-scoped cookie is sent to both sites and both use the same non-multisite name, frcn_cookie_consent. Accepting on one installation can therefore make the other suppress its banner and load its configured trackers without a decision there; use an installation-specific cookie name or another scope that remains shared with the AJAX endpoint without colliding with sibling installations.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

// Without JS, nothing would ever remove '--init' (see the class list
// above), so the banner would stay invisible forever — this resets it
// back to plain visible/static for a no-JS visitor.
$noscript_css = '#frcn-cookie-notice.frcn-cookie-notice--init { opacity: 1; pointer-events: auto; transform: none; }';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the no-JS banner dismissed after a decision

When JavaScript is disabled, submitting the new fallback form stores the decision and redirects, but this unconditional <noscript> rule reveals the cache-neutral banner again and no server-side rendering path consumes the cookie. Consequently every subsequent non-policy page shows the controls again after either decision, making the fallback appear not to persist and repeatedly occupying content; fresh evidence beyond the earlier inert-control report is that the form now successfully stores a decision that this no-JS rendering path never honors.

Useful? React with 👍 / 👎.

Comment on lines +1245 to +1248
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- deliberately unauthenticated, see this method's own docblock above.
$decision = isset( $_POST['frcn_decision'] ) ? sanitize_key( wp_unslash( $_POST['frcn_decision'] ) ) : '';

if ( in_array( $decision, array( 'accepted', 'rejected' ), true ) ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore CSRF protection for no-JS consent submissions

Because this public admin-post.php handler accepts the decision without a nonce or equivalent origin-bound token, an external site can auto-submit frcn_decision=accepted; the target-origin response then sets the consent cookie and redirects, so a later visit can load tracking even though the visitor never chose Accept. The current revision removed the stale embedded nonce entirely rather than obtaining a fresh token through an uncached step, and it also lets forged requests inflate the aggregate counters; restore CSRF protection for this state-changing form.

AGENTS.md reference: AGENTS.md:L83-L83

Useful? React with 👍 / 👎.

Comment on lines +21 to +22
if (typeof frcnCookieNotice !== 'undefined') {
setConsentModeDefault();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Make the CSP fallback independent of inline localization

Under the documented strict-CSP scenario where same-origin external scripts are allowed but unnonced inline scripts are blocked, the frcnCookieNotice assignment emitted by wp_localize_script() is blocked just like the PHP bootstrap. This guard therefore skips setConsentModeDefault(), and init() later returns for the same missing object, leaving independently loaded Consent Mode-aware tags without the default-denied state; fresh evidence after the earlier CSP fixes is that the external fallback still depends on inline-localized data before it can run.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

@davidperezgar
davidperezgar merged commit 98c4ddb into main Sep 28, 2026
6 checks passed
@davidperezgar
davidperezgar deleted the init-dev branch September 28, 2026 06:19
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