Skip to content

feat(access-control): add group subscription identifier to metadata#4746

Merged
dkoo merged 2 commits into
releasefrom
fix/content-access-group-metadata
May 21, 2026
Merged

feat(access-control): add group subscription identifier to metadata#4746
dkoo merged 2 commits into
releasefrom
fix/content-access-group-metadata

Conversation

@dkoo

@dkoo dkoo commented May 20, 2026

Copy link
Copy Markdown
Contributor

All Submissions:

Changes proposed in this Pull Request:

Hotfix version of #4697.

How to test the changes in this Pull Request:

See #4697.

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?

…4697)

* fix(access-control): filter out nonexistent and non-group subs from user results

* chore: update deprecated `filter_enabled_fields` method

* feat(access-control): add Content_Access_Group ESP metadata field

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(access-control): include owned group sub names in Content_Access_Group

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(access-control): add `group` GA4 param for content access groups

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix(access-control): address Copilot review on PR #4697

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(access-control): address review blockers + extract shared name helpers

- Restore Institution::user_matches_institution() IP-cache tightening lost in
  trunk merge: only treat the request as uncached when the visitor is the user
  being evaluated (or the cache-bypass cookie is set). Prevents the requestor's
  IP from leaking to non-current users during background metadata sync.
- Guard get_group_labels() institution branch against non-array $value to avoid
  a PHP 8 TypeError when an institution rule has empty/scalar value.
- Decouple Group_Subscription::get_group_subscriptions_for_user() from
  is_group_subscription()'s My-Account/Memberships side effect. Data-layer
  callers must always see the canonical group-enabled state.
- Extract Group_Subscription::get_group_names_for_user() and
  Institution::get_matching_names_for_user() helpers, each with per-request
  static memoization. Use html_entity_decode( ENT_QUOTES | ENT_HTML5 ) so
  names with non-basic entities (&mdash;, &#8217;, etc.) decode cleanly for
  ESP/GA4 transport. Use WooCommerce_Connection::get_active_subscriptions_for_user()
  for the owned path so status + gifting filters live in one place.
- Update Content_Gate metadata get_group_labels() and GoogleSiteKit::get_user_group_names()
  to delegate to the shared helpers.
- GA4 helper now early-returns when ! Reader_Activation::is_user_reader( $user ),
  matching the framing of the surrounding is_reader / is_subscriber params.
- Replace brittle one-shot filter ordering in test_subscription_filter_source
  with an unconditional filter + unregistered product so the strict per-product
  lookup naturally falls through.
- Add regression tests: malformed institution rule values (data provider) and
  cross-user IP leak prevention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(access-control): address PR #4697 review round 2

T10 — Restore Memberships parity at the My-Account UI layer
- Gate Group_Subscription_MyAccount::inject_member_group_subscriptions() on
  ! Memberships::is_active() so the data-layer decoupling done in 7426ab3
  doesn't re-expose member group subs in the WC subscriptions list on
  Memberships sites. Mirrors the suppression that previously lived inside
  is_group_subscription(), but at the UI layer where it belongs.

T7 — $strict filter contract caveat
- Document on Access_Rules::has_active_subscription() that $strict only
  constrains the built-in ownership / group-membership checks. The
  newspack_access_rules_has_active_subscription filter is always applied;
  filter authors must opt into the 4th $strict arg and respect it, or
  callers relying on $strict (e.g. Content_Gate source labels) may
  misclassify filter-granted access as local ownership.

T8 / T9 — Helper cache lifecycle + ID accessors
- Move Group_Subscription and Institution per-request name caches to
  class-level statics so they can be reset.
- Add public reset_cache() / reset_matching_cache() methods.
- Hook reset on subscription status changes and group-member user-meta
  add/update/delete events so long-running CLI workers don't serve stale
  data across jobs.
- Refactor each helper to a shared get_settings_map_for_user() /
  get_matching_map_for_user() that returns [id => decoded_name]; the
  existing get_*_names_for_user() functions and the new get_*_ids_for_user()
  accessors project from it. Settings are read once per candidate sub.
- Institution cache key now includes get_current_user_id() so the IP-rule
  branch's current-user dependency doesn't produce stale hits.
- Docblock note on the gifting-filter asymmetry between owned and member
  subscription paths.

T11 — Anonymize GA4 group param
- GoogleSiteKit::get_user_group_labels() now emits "Group {sub_id}" /
  "Institution {inst_id}" identifiers instead of display names. The
  unnamed-group fallback synthesizes a name from the owner's billing full
  name, so sending names to GA4 would leak PII for every member of an
  unnamed group. ESP path keeps human-readable names — only GA4 is anonymized.

T14 — reader_data source label
- get_source_labels() returns [ 'reader_data' ] for the reader_data rule
  slug. Previously a reader_data-only access rule yielded Content_Access=Yes
  but empty Content_Access_Source.

T12 — Use get_post_field() for institution title transport
- Replace get_the_title( $inst_id ) so the value going to GA4/ESP isn't
  run through the_title filters (texturization or third-party hooks that
  can introduce entities or markup).

T13 — Document cookie-branch IP cross-attribution caveat
- One-line caveat on IP_Access_Rule::is_cookie_set() disjunct: it still
  treats any non-current $user_id as uncached when the current visitor
  carries the cache-bypass cookie. Narrow pre-existing edge case.

T18 — Direct $resolver(...) invocation
- Drop call_user_func() in Content_Gate::collect_labels(); PHP 7+ invokes
  array callables directly. (Reverts the Copilot autofix in d350ee4.)

T5 / T6 — Test isolation
- Track Institution::create() post IDs in test classes that use them and
  delete them in tear_down(). Institution::create() inserts real posts
  not tracked by $this->factory, so they were leaking into later tests.
- Reset Group_Subscription / Institution caches in set_up and tear_down.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: improve docblock for get_user_group_labels memoization

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@dkoo dkoo self-assigned this May 20, 2026
Copilot AI review requested due to automatic review settings May 20, 2026 21:09
@dkoo dkoo requested a review from a team as a code owner May 20, 2026 21:09
@dkoo dkoo added the [Status] Needs Review The issue or pull request needs to be reviewed label May 20, 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

Adds a new “content access group identity” surface across Reader Activation metadata and GA4 (Google Site Kit), including shared label-resolution helpers for group subscriptions and institutions plus accompanying regression/unit tests.

Changes:

  • Add Content_Access_Group contact metadata field and refactor label collection to share rule-walking logic.
  • Add GA4 group custom parameter (anonymized Group {sub_id} / Institution {inst_id} identifiers).
  • Introduce per-request memoized helpers for group-subscription and institution name/ID collection, plus expanded unit tests.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/unit-tests/plugins/google-site-kit.php New unit tests validating GA4 group parameter behavior and anonymization.
tests/unit-tests/content-gate/group-subscriptions.php Adds coverage for filtering stale/non-group subscription IDs from member meta.
tests/unit-tests/content-gate/class-institution.php Adds regression coverage for IP matching not leaking across evaluated users.
tests/unit-tests/content-gate/class-content-gate-metadata.php Expands metadata tests for Content_Access_Source and new Content_Access_Group.
includes/reader-activation/sync/contact-metadata/class-content-gate.php Adds Content_Access_Group and shared label collection; updates source labeling.
includes/plugins/woocommerce-subscriptions/group-subscription/class-group-subscription.php Adds memoized group name/ID helpers and cache invalidation hooks.
includes/plugins/woocommerce-subscriptions/group-subscription/class-group-subscription-myaccount.php Moves Memberships suppression to the UI injection layer.
includes/plugins/google-site-kit/class-googlesitekit.php Adds GA4 group parameter generation with anonymized labels.
includes/plugins/class-teams-for-memberships.php Switches enabled-field filtering to integration-driven filter_enabled_outgoing_fields.
includes/content-gate/class-institution.php Adds memoized institution name/ID helpers; tightens IP “uncached” determination.
includes/content-gate/class-access-rules.php Adds $strict parameter to distinguish owned vs group-member subscription access.

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

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added [Status] Approved The pull request has been reviewed and is ready to merge and removed [Status] Needs Review The issue or pull request needs to be reviewed labels May 21, 2026
@dkoo dkoo merged commit d201b9a into release May 21, 2026
9 checks passed
@dkoo dkoo deleted the fix/content-access-group-metadata branch May 21, 2026 15:26
@github-actions

Copy link
Copy Markdown

Hey @dkoo, good job getting this PR merged! 🎉

Now, the needs-changelog label has been added to it.

Please check if this PR needs to be included in the "Upcoming Changes" and "Release Notes" doc. If it doesn't, simply remove the label.

If it does, please add an entry to our shared document, with screenshots and testing instructions if applicable, then remove the label.

Thank you! ❤️

matticbot pushed a commit that referenced this pull request May 21, 2026
# [6.41.0](v6.40.1...v6.41.0) (2026-05-21)

### Features

* **access-control:** add group subscription identifier to metadata ([#4746](#4746)) ([d201b9a](d201b9a)), closes [#4697](#4697) [#4697](#4697) [#8217](https://github.com/Automattic/newspack-plugin/issues/8217) [#4697](#4697)
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 6.41.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

needs-changelog [Status] Approved The pull request has been reviewed and is ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants