You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(access-control): add group subscription identifier to metadata (#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 (—, ’, 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>
0 commit comments