Skip to content

Cookie Consent: config-driven core with per-feature toggles#50114

Draft
chihsuan wants to merge 14 commits into
trunkfrom
wooa7s-1639-cookie-consent-config-driven-core
Draft

Cookie Consent: config-driven core with per-feature toggles#50114
chihsuan wants to merge 14 commits into
trunkfrom
wooa7s-1639-cookie-consent-config-driven-core

Conversation

@chihsuan

@chihsuan chihsuan commented Jul 1, 2026

Copy link
Copy Markdown
Member

Fixes WOOA7S-1639

Proposed changes

Part of the Cookie Consent all-sites hardening. Today Cookie_Consent::init() takes no arguments and wires up everything at once. This makes the core config-driven: a consumer passes a config to init(), an enabled switch turns the whole feature on/off, and each capability is an independent toggle. Called with no arguments, behavior is unchanged.

  • Cookie_Consent::init( array $config = [] ) — resolves the config once, bails early if enabled is false, and registers each feature (banner, ccpa_page, footer_links, consent_log, tracks, geo) only when its toggle is on.
  • New Config_Schema — one declarative schema (types, defaults, validation) that resolve() applies. Stateless; reads no options.
  • The consent-log controller now receives its log settings (ip_mode, retention_days, versions) as an argument instead of reaching into globals.
  • Kept the jetpack_cookie_consent_config and jetpack_cookie_consent_log_retention_days filters as override points for code that doesn't own the init() call. init() is the primary path (for the host plugin); the config filter runs over the resolved config and is re-resolved through Config_Schema, so third-party input is still validated. jetpack_cookie_consent_config stays a documented, versioned public API.
  • Tests for the schema, the full on/off toggle matrix, the enqueued frontend config, and the override filters.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No. Behavior-preserving refactor — default IP handling stays drop, log retention stays 30 days.

Testing instructions

Unit tests (the primary check):

cd projects/packages/cookie-consent
composer test-php   # 124 tests, all green

Try the new API — the package runs inside a consumer (e.g. premium-analytics). To exercise the toggles, drop a mu-plugin on a site that has the package, e.g. wp-content/mu-plugins/cookie-consent-config.php:

<?php
use Automattic\Jetpack\CookieConsent\Cookie_Consent;

// Default — everything on, identical to before this PR.
Cookie_Consent::init();

// Selective — banner only; no CCPA page, Tracks, or consent log.
Cookie_Consent::init( [
    'features' => [
        'ccpa_page'   => false,
        'tracks'      => false,
        'consent_log' => false,
    ],
] );

// Kill switch — register nothing at all for this site.
Cookie_Consent::init( [ 'enabled' => false ] );

Add ?preview_cookie_consent=1 to a front-end URL to force the banner to show outside a GDPR region. With the selective config, confirm no CCPA page is auto-created and stats.wp.com/w.js is not enqueued, while the banner still renders. With enabled = false, confirm nothing is registered.

Third-party override — with the package booted, confirm another plugin can still adjust config without owning the init() call:

add_filter( 'jetpack_cookie_consent_config', function ( $config ) {
    $config['links']['cookie_policy_url'] = 'https://example.com/cookie-policy/';
    return $config;
} );
chihsuan added 8 commits July 1, 2026 15:33
Cookie_Consent::get_config() now resolves Config_Schema once and stashes
the result instead of double-resolving through the jetpack_cookie_consent_config
filter. Drops that filter and the temporary get_default_config()/normalize_config()
delegates now that nothing needs them. get_copy()/get_consent_categories() read
the stash by default, and the banner/CCPA templates reuse the already-resolved
copy/categories instead of re-resolving on every render.

Also locks in that consent.categories is derived from the resolved copy (not
pristine defaults) when categories aren't explicitly supplied, which regressed
under the old double-resolve path.
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@chihsuan chihsuan requested a review from Copilot July 1, 2026 09:28
@chihsuan chihsuan changed the title Cookie Consent: config-driven core — init( $config ) + feature toggles + Config_Schema Jul 1, 2026

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.

Pull request overview

This PR refactors the cookie-consent package toward third-party consumption by introducing a schema-driven configuration resolver and a config-driven Cookie_Consent::init( array $config = [] ) entry point with per-feature hook gating.

Changes:

  • Add Config_Schema as a single source of truth for config shape/defaults and a resolve() routine that stashes resolved config.
  • Update Cookie_Consent::init() to accept config, support a master enabled switch, and register hooks conditionally per features.*.
  • Update consent-log initialization to accept injected log config, and add/adjust PHPUnit coverage for schema resolution, feature toggles, and asset enqueue behavior.

Reviewed changes

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

Show a summary per file
File Description
projects/packages/cookie-consent/tests/php/Init_Feature_Toggles_Test.php New tests asserting feature toggles gate hook registration.
projects/packages/cookie-consent/tests/php/Enqueue_Assets_Test.php New tests asserting enqueue emissions are gated and geo config contract is stable.
projects/packages/cookie-consent/tests/php/Cookie_Consent_Log_Versions_Test.php Updates tests to inject config via stashed config instead of removed filters.
projects/packages/cookie-consent/tests/php/Consent_Log_Controller_Test.php Updates controller tests for injected log config and new config injection approach.
projects/packages/cookie-consent/tests/php/Consent_Log_Controller_Log_Versions_Test.php Updates log-version tests to use config injection helper.
projects/packages/cookie-consent/tests/php/Config_Test.php Updates config tests to use public get_config() and stashed defaults behavior.
projects/packages/cookie-consent/tests/php/Config_Schema_Test.php New unit tests for Config_Schema::schema() and resolve() behavior.
projects/packages/cookie-consent/tests/php/Config_Normalization_Test.php Migrates normalization coverage from Cookie_Consent to Config_Schema.
projects/packages/cookie-consent/tests/php/class-testcase.php Adds helpers to set/reset the private config stash for tests via reflection.
projects/packages/cookie-consent/src/schema/class-config-schema.php Introduces schema + resolver for configuration defaults, coercion, and validation.
projects/packages/cookie-consent/src/cookie-banner-content.php Allows templates to accept pre-resolved $copy/$categories (fallback when absent).
projects/packages/cookie-consent/src/class-cookie-consent.php Implements config-driven init(), feature gating, stashed config, and updated asset emission.
projects/packages/cookie-consent/src/class-consent-log-controller.php Accepts injected log config; uses it for IP mode/retention and updates docs accordingly.
projects/packages/cookie-consent/src/ccpa-content.php Allows template to accept pre-resolved $copy (fallback when absent).
projects/packages/cookie-consent/README.md Documents new init( $config ) API, master switch, feature toggles, and config structure.
projects/packages/cookie-consent/changelog/wooa7s-1639-config-driven-core Adds changelog entry for the new config-driven core API.
Comment thread projects/packages/cookie-consent/tests/php/Enqueue_Assets_Test.php
Comment thread projects/packages/cookie-consent/src/class-consent-log-controller.php Outdated
Comment thread projects/packages/cookie-consent/src/schema/class-config-schema.php
Comment thread projects/packages/cookie-consent/src/schema/class-config-schema.php
Comment thread projects/packages/cookie-consent/src/schema/class-config-schema.php Outdated
Comment thread projects/packages/cookie-consent/changelog/wooa7s-1639-config-driven-core Outdated
chihsuan added 6 commits July 1, 2026 17:52
…build

- init(): latch on the first call including when enabled=false, so the master
  switch is a sticky no-op that a later caller can't re-enable; docblock now
  matches the behavior.
- init(): drop the dead `|| $features['geo']` enqueue branch. Geo emission lives
  after the banner guard in enqueue_assets(), so geo alone enqueues nothing.
- Config_Schema::resolve(): build schema() once and index into it instead of
  rebuilding it ~5x per resolve (each build re-materializes copy/category/country
  defaults); remove the now-unused defaults() wrapper.
- Tests: assert enabled=false leaves the durable side effects (consent-log cron,
  Boost filter, CCPA REST hook) unregistered; cover injected retention_days and
  its malformed-value fallback; invert the geo-only enqueue expectation.
…nums

Config_Schema now owns the GEO_PROVIDERS, IP_MODES, and DEFAULT_IP_MODE constants;
the schema descriptor, resolve_geo(), and Consent_Log_Controller::get_ip_mode() all
read their allowed values from them, so each enum is declared exactly once instead
of being re-implemented per validator.

- resolve_geo() validates against GEO_PROVIDERS and resets to the default provider,
  dropping the hardcoded 'wpcom'/'custom' literals.
- Consent_Log_Controller drops its local IP_MODES/DEFAULT_IP_MODE constants and
  consumes Config_Schema::ip_modes()/default_ip_mode().
- Tests: an unknown injected ip_mode falls back via the schema; the ip_mode
  accessors are asserted to match the schema enum so the two can't drift.
…onsent-config-driven-core

# Conflicts:
#	projects/packages/cookie-consent/README.md
@jp-launch-control

Copy link
Copy Markdown

Code Coverage Summary

This PR did not change code coverage!

That could be good or bad, depending on the situation. Everything covered before, and still is? Great! Nothing was covered before? Not so great. 🤷

Full summary · PHP report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment