Commit 379245925cb for woocommerce

commit 379245925cbb631494ec241a9f7dc3e2411af3bb
Author: Adam Grzybkowski <agrzybkowski@outlook.com>
Date:   Fri May 29 20:54:39 2026 +0200

    Push notifications: extract auth trait and fix @since tags (#65380)

    * Extract authorize_as_authenticated into a shared trait

    Moves the duplicated permission callback used by both
    NotificationPreferencesRestController and PushTokenRestController into a
    new AuthorizesPushNotificationRequests trait, mirroring the existing
    ConvertsExceptionsToWpError trait pattern. PushNotifications is now
    looked up via the container in one place rather than re-implemented per
    controller, and the now-unused dependency injection of PushNotifications
    in NotificationPreferencesRestController has been dropped.

    Follow-up to https://github.com/woocommerce/woocommerce/pull/64351#discussion_r3218483918.

    * Correct @since tags on push notification preference filter methods

    PR #64487 was merged after the version bump to 10.9.0-dev, so the
    @since 10.8.0 annotations on the two methods it introduced
    (Notification::should_send_to_user and
    NotificationProcessor::filter_tokens_by_preferences) should be
    @since 10.9.0.

    Follow-up to https://github.com/woocommerce/woocommerce/pull/64487#issuecomment-4448627834.

diff --git a/plugins/woocommerce/changelog/64351-followup-authorize-trait b/plugins/woocommerce/changelog/64351-followup-authorize-trait
new file mode 100644
index 00000000000..a68b7c1a4aa
--- /dev/null
+++ b/plugins/woocommerce/changelog/64351-followup-authorize-trait
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Extract shared authorize_as_authenticated logic from the push notifications REST controllers into a new AuthorizesPushNotificationRequests trait.
diff --git a/plugins/woocommerce/changelog/64487-followup-since-tags b/plugins/woocommerce/changelog/64487-followup-since-tags
new file mode 100644
index 00000000000..56f6870bae6
--- /dev/null
+++ b/plugins/woocommerce/changelog/64487-followup-since-tags
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Correct @since tags on push notification preference filtering methods introduced in 10.9.0.
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Controllers/NotificationPreferencesRestController.php b/plugins/woocommerce/src/Internal/PushNotifications/Controllers/NotificationPreferencesRestController.php
index 5478baebc12..db41f8d0d3f 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/Controllers/NotificationPreferencesRestController.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Controllers/NotificationPreferencesRestController.php
@@ -6,8 +6,8 @@ namespace Automattic\WooCommerce\Internal\PushNotifications\Controllers;

 defined( 'ABSPATH' ) || exit;

-use Automattic\WooCommerce\Internal\PushNotifications\PushNotifications;
 use Automattic\WooCommerce\Internal\PushNotifications\Services\NotificationPreferencesService;
+use Automattic\WooCommerce\Internal\PushNotifications\Traits\AuthorizesPushNotificationRequests;
 use Automattic\WooCommerce\Internal\PushNotifications\Traits\ConvertsExceptionsToWpError;
 use Automattic\WooCommerce\Internal\RestApiControllerBase;
 use Exception;
@@ -24,6 +24,7 @@ use WP_REST_Server;
  * @since 10.8.0
  */
 class NotificationPreferencesRestController extends RestApiControllerBase {
+	use AuthorizesPushNotificationRequests;
 	use ConvertsExceptionsToWpError;

 	/**
@@ -47,29 +48,17 @@ class NotificationPreferencesRestController extends RestApiControllerBase {
 	 */
 	private NotificationPreferencesService $preferences_service;

-	/**
-	 * The push notifications module enablement gate.
-	 *
-	 * @var PushNotifications
-	 */
-	private PushNotifications $push_notifications;
-
 	/**
 	 * Initialize injected dependencies.
 	 *
 	 * @internal
 	 *
 	 * @param NotificationPreferencesService $preferences_service The preferences service.
-	 * @param PushNotifications              $push_notifications  The push notifications module.
 	 *
 	 * @since 10.8.0
 	 */
-	final public function init(
-		NotificationPreferencesService $preferences_service,
-		PushNotifications $push_notifications
-	): void {
+	final public function init( NotificationPreferencesService $preferences_service ): void {
 		$this->preferences_service = $preferences_service;
-		$this->push_notifications  = $push_notifications;
 	}

 	/**
@@ -154,37 +143,6 @@ class NotificationPreferencesRestController extends RestApiControllerBase {
 		return new WP_REST_Response( $merged, WP_Http::OK );
 	}

-	/**
-	 * Checks user is authenticated and authorized to access this endpoint.
-	 *
-	 * @since 10.8.0
-	 *
-	 * @param WP_REST_Request $request The request object.
-	 * @phpstan-param WP_REST_Request<array<string, mixed>> $request
-	 * @return bool|WP_Error
-	 */
-	public function authorize_as_authenticated( WP_REST_Request $request ) {
-		if ( ! get_current_user_id() ) {
-			return new WP_Error(
-				'woocommerce_rest_cannot_view',
-				__( 'Sorry, you are not allowed to do that.', 'woocommerce' ),
-				array( 'status' => rest_authorization_required_code() )
-			);
-		}
-
-		if ( ! $this->push_notifications->should_be_enabled() ) {
-			return false;
-		}
-
-		$has_valid_role = array_reduce(
-			PushNotifications::ROLES_WITH_PUSH_NOTIFICATIONS_ENABLED,
-			fn ( $carry, $role ) => $this->check_permission( $request, $role ) === true ? true : $carry,
-			false
-		);
-
-		return $has_valid_role ? true : false;
-	}
-
 	/**
 	 * Get the accepted arguments for the POST request.
 	 *
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Controllers/PushTokenRestController.php b/plugins/woocommerce/src/Internal/PushNotifications/Controllers/PushTokenRestController.php
index db890d8068a..47e6abb839c 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/Controllers/PushTokenRestController.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Controllers/PushTokenRestController.php
@@ -11,6 +11,7 @@ use Automattic\WooCommerce\Internal\PushNotifications\DataStores\PushTokensDataS
 use Automattic\WooCommerce\Internal\PushNotifications\Entities\PushToken;
 use Automattic\WooCommerce\Internal\PushNotifications\Exceptions\PushTokenNotFoundException;
 use Automattic\WooCommerce\Internal\PushNotifications\PushNotifications;
+use Automattic\WooCommerce\Internal\PushNotifications\Traits\AuthorizesPushNotificationRequests;
 use Automattic\WooCommerce\Internal\PushNotifications\Traits\ConvertsExceptionsToWpError;
 use Automattic\WooCommerce\Internal\PushNotifications\Validators\PushTokenValidator;
 use Automattic\WooCommerce\Internal\RestApiControllerBase;
@@ -29,6 +30,7 @@ use WP_Http;
  * @since 10.6.0
  */
 class PushTokenRestController extends RestApiControllerBase {
+	use AuthorizesPushNotificationRequests;
 	use ConvertsExceptionsToWpError;

 	/**
@@ -293,41 +295,6 @@ class PushTokenRestController extends RestApiControllerBase {
 		);
 	}

-	/**
-	 * Checks user is authenticated and authorized to access this endpoint.
-	 *
-	 * @since 10.6.0
-	 *
-	 * @param WP_REST_Request $request The request object.
-	 * @phpstan-param WP_REST_Request<array<string, mixed>> $request
-	 * @return bool|WP_Error
-	 */
-	public function authorize_as_authenticated( WP_REST_Request $request ) {
-		if ( ! get_current_user_id() ) {
-			return new WP_Error(
-				'woocommerce_rest_cannot_view',
-				__( 'Sorry, you are not allowed to do that.', 'woocommerce' ),
-				array( 'status' => rest_authorization_required_code() )
-			);
-		}
-
-		if ( ! wc_get_container()->get( PushNotifications::class )->should_be_enabled() ) {
-			return false;
-		}
-
-		$has_valid_role = array_reduce(
-			PushNotifications::ROLES_WITH_PUSH_NOTIFICATIONS_ENABLED,
-			fn ( $carry, $role ) => $this->check_permission( $request, $role ) === true ? true : $carry,
-			false
-		);
-
-		if ( ! $has_valid_role ) {
-			return false;
-		}
-
-		return true;
-	}
-
 	/**
 	 * Validates that the request is signed with a Jetpack blog token,
 	 * ensuring only WPCOM can access this endpoint.
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Notifications/Notification.php b/plugins/woocommerce/src/Internal/PushNotifications/Notifications/Notification.php
index 35404ad7445..bfe2b0352f9 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/Notifications/Notification.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Notifications/Notification.php
@@ -196,7 +196,7 @@ abstract class Notification {
 	 * @param mixed $pref_value The user's stored preference value, or null.
 	 * @return bool True if this notification should be sent to that user.
 	 *
-	 * @since 10.8.0
+	 * @since 10.9.0
 	 */
 	public function should_send_to_user( $pref_value ): bool {
 		if ( null === $pref_value ) {
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Services/NotificationProcessor.php b/plugins/woocommerce/src/Internal/PushNotifications/Services/NotificationProcessor.php
index 058cdb13294..07d553f2a64 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/Services/NotificationProcessor.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Services/NotificationProcessor.php
@@ -208,7 +208,7 @@ class NotificationProcessor {
 	 *
 	 * @return PushToken[] The tokens whose owner wants the notification.
 	 *
-	 * @since 10.8.0
+	 * @since 10.9.0
 	 */
 	private function filter_tokens_by_preferences( array $tokens, Notification $notification ): array {
 		$type           = $notification->get_type();
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Traits/AuthorizesPushNotificationRequests.php b/plugins/woocommerce/src/Internal/PushNotifications/Traits/AuthorizesPushNotificationRequests.php
new file mode 100644
index 00000000000..e83711a7b1a
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Traits/AuthorizesPushNotificationRequests.php
@@ -0,0 +1,51 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Internal\PushNotifications\Traits;
+
+defined( 'ABSPATH' ) || exit;
+
+use Automattic\WooCommerce\Internal\PushNotifications\PushNotifications;
+use WP_Error;
+use WP_REST_Request;
+
+/**
+ * Shared "is this caller an authenticated push-notifications user?" check for
+ * REST controllers in the PushNotifications module.
+ *
+ * Implementing classes must extend {@see \Automattic\WooCommerce\Internal\RestApiControllerBase}
+ * so that `check_permission()` is available.
+ */
+trait AuthorizesPushNotificationRequests {
+	/**
+	 * Checks the user is authenticated, the push notifications module is
+	 * enabled, and the user holds at least one role allowed to interact with
+	 * push notifications.
+	 *
+	 * @param WP_REST_Request $request The request object.
+	 * @phpstan-param WP_REST_Request<array<string, mixed>> $request
+	 * @return bool|WP_Error
+	 */
+	public function authorize_as_authenticated( WP_REST_Request $request ) {
+		if ( ! get_current_user_id() ) {
+			return new WP_Error(
+				'woocommerce_rest_cannot_view',
+				__( 'Sorry, you are not allowed to do that.', 'woocommerce' ),
+				array( 'status' => rest_authorization_required_code() )
+			);
+		}
+
+		if ( ! wc_get_container()->get( PushNotifications::class )->should_be_enabled() ) {
+			return false;
+		}
+
+		$has_valid_role = array_reduce(
+			PushNotifications::ROLES_WITH_PUSH_NOTIFICATIONS_ENABLED,
+			fn ( $carry, $role ) => $this->check_permission( $request, $role ) === true ? true : $carry,
+			false
+		);
+
+		return $has_valid_role ? true : false;
+	}
+}