@@ -168,6 +168,87 @@ public static function evaluate_rule( $rule_slug, $args = null, $user_id = null
168168 return call_user_func ( $ rule ['callback ' ], $ user_id , $ args );
169169 }
170170
171+ /**
172+ * Evaluate access rules with OR logic between groups and AND logic within groups.
173+ *
174+ * Rules structure: [ [ rule1, rule2 ], [ rule3, rule4 ] ]
175+ * - Groups use OR logic: reader must pass at least one group
176+ * - Rules within a group use AND logic: reader must pass all rules in the group
177+ *
178+ * @param array $access_rules The access rules (array of groups, each group is an array of rules).
179+ *
180+ * @return bool True if access is granted, false if restricted.
181+ */
182+ public static function evaluate_rules ( $ access_rules ) {
183+ if ( empty ( $ access_rules ) ) {
184+ return true ;
185+ }
186+
187+ // Normalize legacy flat rules structure to grouped format.
188+ $ access_rules = self ::normalize_rules ( $ access_rules );
189+
190+ // Evaluate each group with OR logic - if any group passes, grant access.
191+ foreach ( $ access_rules as $ group ) {
192+ if ( self ::evaluate_rules_group ( $ group ) ) {
193+ return true ;
194+ }
195+ }
196+
197+ // No group passed - restrict access.
198+ return false ;
199+ }
200+
201+ /**
202+ * Evaluate a single group of access rules with AND logic.
203+ *
204+ * @param array $group Array of rules in the group.
205+ *
206+ * @return bool True if all rules in the group pass, false otherwise.
207+ */
208+ private static function evaluate_rules_group ( $ group ) {
209+ if ( empty ( $ group ) || ! is_array ( $ group ) ) {
210+ return true ;
211+ }
212+
213+ foreach ( $ group as $ rule ) {
214+ if ( ! isset ( $ rule ['slug ' ] ) ) {
215+ continue ;
216+ }
217+ if ( ! self ::evaluate_rule ( $ rule ['slug ' ], $ rule ['value ' ] ?? null ) ) {
218+ return false ;
219+ }
220+ }
221+
222+ return true ;
223+ }
224+
225+ /**
226+ * Normalize access rules to grouped format.
227+ *
228+ * Converts legacy flat rules [ rule1, rule2 ] to grouped format [ [ rule1, rule2 ] ].
229+ *
230+ * @param array $access_rules The access rules.
231+ *
232+ * @return array Normalized access rules in grouped format.
233+ */
234+ public static function normalize_rules ( $ access_rules ) {
235+ if ( empty ( $ access_rules ) ) {
236+ return [];
237+ }
238+
239+ // Check if already in grouped format (array of arrays with rules).
240+ // A grouped format has arrays as first-level elements.
241+ // A flat format has rule objects (with 'slug' key) as first-level elements.
242+ $ first_element = reset ( $ access_rules );
243+ if ( is_array ( $ first_element ) && ! isset ( $ first_element ['slug ' ] ) ) {
244+ // Already in grouped format.
245+ return $ access_rules ;
246+ }
247+
248+ // Convert flat format to single group.
249+ return [ $ access_rules ];
250+ }
251+
171252 /**
172253 * Get subscriptions eligible for access rules.
173254 *
@@ -195,13 +276,50 @@ public static function get_subscription_products_options() {
195276
196277 /**
197278 * Whether the user has an active subscription for one of the given products.
279+ * Also checks if the user is a member of a group subscription with the required products.
198280 *
199281 * @param int $user_id User ID.
200282 * @param array $product_ids Required product IDs.
201283 * @return bool
202284 */
203285 public static function has_active_subscription ( $ user_id , $ product_ids ) {
204- return ! empty ( WooCommerce_Connection::get_active_subscriptions_for_user ( $ user_id , $ product_ids ) );
286+ $ has_subscription = false ;
287+
288+ // Check user's own subscriptions.
289+ if ( ! empty ( WooCommerce_Connection::get_active_subscriptions_for_user ( $ user_id , $ product_ids ) ) ) {
290+ $ has_subscription = true ;
291+ }
292+
293+ // Check group subscriptions the user is a member of.
294+ if ( ! $ has_subscription && function_exists ( 'wcs_get_subscription ' ) ) {
295+ $ group_subscriptions = Group_Subscription::get_group_subscriptions_for_user ( $ user_id );
296+ foreach ( $ group_subscriptions as $ subscription ) {
297+ if ( ! $ subscription || ! $ subscription ->has_status ( WooCommerce_Connection::ACTIVE_SUBSCRIPTION_STATUSES ) ) {
298+ continue ;
299+ }
300+ // If no product filter, any active group subscription grants access.
301+ if ( empty ( $ product_ids ) ) {
302+ $ has_subscription = true ;
303+ break ;
304+ }
305+ // Check if the subscription has any of the required products.
306+ foreach ( $ product_ids as $ product_id ) {
307+ if ( $ subscription ->has_product ( $ product_id ) ) {
308+ $ has_subscription = true ;
309+ break 2 ;
310+ }
311+ }
312+ }
313+ }
314+
315+ /**
316+ * Filters whether a user has an active subscription for the given products.
317+ *
318+ * @param bool $has_subscription Whether the user has an active subscription.
319+ * @param int $user_id User ID.
320+ * @param array $product_ids Required product IDs.
321+ */
322+ return apply_filters ( 'newspack_access_rules_has_active_subscription ' , $ has_subscription , $ user_id , $ product_ids );
205323 }
206324
207325 /**
0 commit comments