Skip to content

Conversation

@dkoo
Copy link
Contributor

@dkoo dkoo commented Jan 14, 2026

All Submissions:

Changes proposed in this Pull Request:

This PR adds the business logic for associating user IDs with a group subscription as group members, as well as getting the members for a given subscription and the group subscriptions for a given user ID. It also adds a feature missing from NPPD-1037, to allow admins to directly edit the members of any group subscription, which should aid in testing for this PR.

The UI in the subscription admin pages now perform operations via REST API instead of requiring

The logic to control content restriction by group subscription membership, as well as UI for group owners to invite and manage group members, will come in future PRs.

Closes NPPD-1119 and NPPD-1040.

How to test the changes in this Pull Request:

  1. Check out this branch.
  2. As an admin, edit an existing active subscription.
  3. If not already enabled, check the "Group subscription enabled" checkbox. (Note that this checkbox and the "limit" field are currently saved upon saving the subscription, not when interacting with their UI elements. However, the "enabled" setting is automatically turned on when calling Group_Subscription::update_members() to support the admin flow of enabling the checkbox and immediately adding new members.)
  4. Confirm that there's a new "Add new group members" autocomplete field that's only visible when "Group subscription enabled" is checked.
Screenshot 2026-01-16 at 4 02 25 PM
  1. Type a user search term in the "Add new group members" field. Confirm that the autocomplete suggests matching reader users (customer and subscriber roles by default), excluding the subscription purchaser (manager) and existing group members.
  2. Select one or more readers and confirm they're added to the "Group members" list. Save the subscription and confirm that the changes persist after a refresh.
  3. Add more readers and remove other existing ones. Confirm that these changes also persist after page reload.
  4. If you add more members than the defined "limit" setting, confirm that you see an error:
Screenshot 2026-01-16 at 4 10 20 PM
  1. Increase the limit field and save, then confirm that you can add more members up to but not past the new limit.
  2. Using wp shell, test the new utility methods which will lay the groundwork for future Group Subscription features:
> Newspack\Group_Subscription::is_group_subscription( $subscription_id );
= true // True if group subscription enabled for this sub

> Newspack\Group_Subscription::get_managers( $subscription_id );
// Array containing just the purchaser's user ID (this is an array so that we can more easily add features to allow multiple group managers per subscription in the future if we need to)
= [
    528,
  ]

> Newspack\Group_Subscription::get_members( $subscription_id );
// Array containing user IDs of all group members
= [
    419,
    96,
  ]

> Newspack\Group_Subscription::user_can_access( $user_id, $subscription_id );
= true // True if user is a member or owner of the group subscription

> Newspack\Group_Subscription::get_group_subscriptions_for_user( $user_id, $statuses );
// Array of all group subscriptions with matching $statuses for which the user is a member (not owner)
= [
    518520 => WC_Subscription {#18050
      +refunds: [],
      +order_type: "shop_subscription",
    },
  ]

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?

@dkoo dkoo requested a review from a team as a code owner January 14, 2026 22:14
@dkoo dkoo added the [Status] Needs Review The issue or pull request needs to be reviewed label Jan 14, 2026
Copilot AI review requested due to automatic review settings January 14, 2026 22:14
Copy link
Contributor

Copilot AI left a comment

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 implements the business logic for managing group subscription members in Newspack. It adds functionality to associate user IDs with group subscriptions, retrieve members/managers for subscriptions, and check user access to group subscriptions.

Changes:

  • Added member_ids field to subscription settings and UI for admins to manage group members via autocomplete
  • Implemented utility methods for checking group subscription status, retrieving members/managers, and verifying user access
  • Added AJAX handler for searching reader users to add as group members

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

dkoo and others added 6 commits January 14, 2026 15:35
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@dkoo dkoo requested a review from miguelpeixe January 14, 2026 23:09
@github-actions github-actions bot added the [Status] Needs Changes or Feedback The issue or pull request needs action from the original creator label Jan 15, 2026
@dkoo dkoo marked this pull request as draft January 16, 2026 00:53
@dkoo dkoo marked this pull request as ready for review January 16, 2026 23:50
@dkoo
Copy link
Contributor Author

dkoo commented Jan 17, 2026

@miguelpeixe this should be ready for another look. There are some substantial changes compared to the first review:

  • Group subscriptions are now stored as user meta with the _newspack_group_subscription key.
  • Group subscriptions code is now split across three classes: Group_Subscription, Group_Subscription_Settings, and Group_Subscription_API as the single class file was starting to get pretty big already.
  • The admin UI to add/remove members has been updated as we discussed: adding/removing of members now happens immediately via REST API instead of upon saving the subscription.
    • Maybe we can revisit this in a future PR as the current UI may be a little confusing (the "enabled" checkbox and "limit" setting are updated upon saving the subscription, while members are updated immediately).
    • To support this flow, group subscription is automatically enabled if not already enabled when adding or removing members, and get_members and get_managers methods now return found members/managers even if group subscription isn't enabled. This is so you can enable group subscriptions for a subscription and immediately start adding and removing members without saving first.

@dkoo dkoo requested a review from miguelpeixe January 17, 2026 00:19
Copy link
Member

@miguelpeixe miguelpeixe left a comment

Choose a reason for hiding this comment

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

Looking great! Thank you for revising the approach. Besides copilot's feedback, I just have a few more comments.

Comment on lines +263 to +270
\add_meta_box(
'newspack-group-subscription',
__( 'Group subscription', 'newspack-plugin' ),
[ __CLASS__, 'add_group_subscription_options' ],
$post_type,
'normal',
'high'
);
Copy link
Member

Choose a reason for hiding this comment

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

With the 'high' priority it renders above the subscription details, which I don't think is ideal:

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call, I had dragged it to another location at some point and forgot about the default placement. c00655e adds the metabox on priority 26 so that it appears underneath the "line items" meta box by default:

Screenshot 2026-01-20 at 11 21 11 AM

Comment on lines +184 to +192
$users = array_map(
function( $user ) {
return [
'id' => $user->ID,
'text' => $user->user_email . ' (#' . $user->ID . ')',
];
},
array_merge( $query1, $query2 )
);
Copy link
Member

Choose a reason for hiding this comment

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

Indeed, I'm getting duplicated users in my tests.

Comment on lines +96 to +105
/**
* Update the member IDs for a group subscription.
*
* @param WC_Subscription|int $subscription The subscription object or ID.
* @param int[] $members_to_add Group member user IDs to add the subscription.
* @param int[] $members_to_remove Group member user IDs to remove from the subscription.
*
* @return object|WP_Error Added/removed results.
*/
public static function update_members( $subscription, $members_to_add, $members_to_remove = [] ) {
Copy link
Member

Choose a reason for hiding this comment

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

Non-blocking NIT, but do we need an API designed for bulk operations like that? A simpler CRUD approach is more convenient and easier to maintain.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I initially wrote this as a bulk update method because of the original UI as a multi-select. I kept it because I thought it might be useful if we decide to implement a CLI command to bulk add/remove members for convenience in the future. Maybe this can be improved to abstract the add/remove actions into separate methods, though.

Comment on lines 168 to 176
/**
* Check if a user has access to a group subscription.
*
* @param int $user_id The user ID.
* @param WC_Subscription|int $subscription The subscription object or ID.
*
* @return bool|null Whether the user has access to the group subscription, or null if not a group subscription.
*/
public static function user_can_access( $user_id, $subscription ) {
Copy link
Member

Choose a reason for hiding this comment

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

This is also non-blocking: I'm not sure user_can_access is the best name here. "Access" in the context of the subscription alone makes me think of access to something (like content), which is not a concern here.

Even though we include members and "managers", I find user_is_member() to be more straightforward.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in 6f43ff8

if ( ! self::is_group_subscription( $subscription ) ) {
return null;
}
$can_access = in_array( $user_id, self::get_managers( $subscription ), true ) || in_array( $user_id, self::get_members( $subscription ), true );
Copy link
Member

Choose a reason for hiding this comment

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

The query inside get_members() is not optimal for this.

We could go straight to the meta item with select 1 from {$usermeta} where user_id = '{$user_id}' and meta_key = '{$meta_key}' and meta_value = '{$subscription_id}' limit 1.

Or, to benefit from object caching, filter through get_user_meta( $user_id, self::GROUP_SUBSCRIPTION_USER_META_KEY, false ).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I never updated this method after we switched the data to user meta. f194e06 updates it so that we use get_group_subscriptions_for_user() (which itself calls get_user_meta() instead of running a user meta query.

@dkoo
Copy link
Contributor Author

dkoo commented Jan 20, 2026

@thomasguillot as discussed on the content gating call, tagging you for a design review of the core UI proposed in this PR. If you think we should go in a very different direction for the UI in either the product or subscription admin pages (or both), then let's consider tackling that in a separate PR after this one lands.

@dkoo
Copy link
Contributor Author

dkoo commented Jan 20, 2026

@miguelpeixe pushed some other fixes to address your feedback:

  • 8cdcecc updates the docblock for get_subscription_product_id() to reflect that the arg can be either a subscription ID or object, and the return value can be int|false
  • c00655e tweaks the priority of the metabox so that it appears after the line items box, by default
  • b731b96 fixes the user search to use the search term with wildcards (*search* instead of search), and to avoid duplicates between the two queries (I wasn't seeing duplicates that I expected to see because the lack of wildcards meant I wasn't actually getting all of the matches in my site)
  • 6f43ff8 renames user_can_access to user_is_member
  • f194e06 updates the user_is_member() method to use get_group_subscriptions_for_user(), which should be more performant than running a user meta query by subscription ID.

@dkoo dkoo requested a review from miguelpeixe January 20, 2026 18:52
Copy link
Member

@miguelpeixe miguelpeixe left a comment

Choose a reason for hiding this comment

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

Thank you for the revisions! Changes look great and test well.

Just a minor question/suggestion that I missed in my previous review.

Comment on lines 217 to 222
foreach ( $subscription_ids as $subscription_id ) {
$subscription = \wcs_get_subscription( $subscription_id );
if ( $subscription && $subscription->has_status( $subscription_statuses ) ) {
$subscriptions[] = $ids_only ? $subscription_id : $subscription;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Apologies for not noticing this sooner, but should this be a concern here? It adds complexity and additional queries to the method. Regardless of the subscription status, the user is still a member of it.

IMO, status validation should happen by the feature that uses it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, 4e265e7 simplifies that method so that it takes just one arg ($user_id) and always returns an array of WC_Subscription objects.

Copy link
Member

Choose a reason for hiding this comment

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

I liked ids_only, though. It avoid running wcs_get_subscription() for every item if the caller knows it wants to match a particular subscription (like user_is_member())

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha, de139cd restores that arg

@dkoo dkoo requested a review from miguelpeixe January 21, 2026 18:08
@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 [Status] Needs Changes or Feedback The issue or pull request needs action from the original creator labels Jan 21, 2026
@dkoo
Copy link
Contributor Author

dkoo commented Jan 21, 2026

Thanks for the review and feedback, @miguelpeixe!

@dkoo dkoo merged commit da5f2fa into trunk Jan 21, 2026
10 checks passed
@dkoo dkoo deleted the feat/group-subscriptions-members branch January 21, 2026 22:22
@github-actions
Copy link

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! ❤️

@dkoo
Copy link
Contributor Author

dkoo commented Jan 21, 2026

Whoops, sorry, @thomasguillot! I forgot I tagged you on this before merging. If you do have any feedback on the core admin UI stuff here, let's handle changes in a new PR. You can comment on this PR with any feedback you have. Thanks!

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

Labels

[Status] Approved The pull request has been reviewed and is ready to merge [Status] Needs Design Review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants