a( self::CLAIMED_META_KEY ) ) { return true; } $notification->write_meta( self::CLAIMED_META_KEY ); } /** * Non-paginated result from get_tokens_for_roles. * * @var PushToken[] $tokens */ $tokens = $this->data_store->get_tokens_for_roles( PushNotifications::ROLES_WITH_PUSH_NOTIFICATIONS_ENABLED ); /** * Filter out tokens whose owning user does not want this notification. * The decision is delegated to the notification itself via * {@see Notification::should_send_to_user()} so per-type preference * shapes (simple bool today, parametrized arrays in the future) stay * encapsulated alongside the type's resource access. */ $tokens = $this->filter_tokens_by_preferences( $tokens, $notification ); /** * There are no recipients to send to (either no tokens at all, or * every owning user opted out of this notification type). We don't * want to retry as this isn't a 'recoverable error', so mark as sent * and return. */ if ( empty( $tokens ) ) { $notification->write_meta( self::SENT_META_KEY ); $this->cancel_safety_net( $notification ); return true; } $result = $this->dispatcher->dispatch( $notification, $tokens ); if ( ! empty( $result['success'] ) ) { $notification->write_meta( self::SENT_META_KEY ); $notification->delete_meta( self::CLAIMED_META_KEY ); $this->cancel_safety_net( $notification ); return true; } $this->retry_handler->schedule( $notification, $result['retry_after'] ?? null, $attempt ); $this->cancel_safety_net( $notification ); return false; } /** * Returns the subset of $tokens whose owning user wants $notification. * * The decision is delegated to {@see Notification::should_send_to_user()} * so per-type preference shapes (simple bool today, parametrized arrays * in the future) stay encapsulated alongside the type's resource access. * Tokens with no owning user are dropped — there are no preferences to * consult. * * Decisions are memoized per user for the duration of one call, since * the same user can have several registered tokens (iOS, iPad, Android, * browser) and we don't want to re-read user meta or re-fetch the * resource for every token. * * @param PushToken[] $tokens The tokens to filter. * @param Notification $notification The notification being processed. * * @return PushToken[] The tokens whose owner wants the notification. * * @since 10.9.0 */ private function filter_tokens_by_preferences( array $tokens, Notification $notification ): array { $type = $notification->get_type(); $decision_cache = array(); return array_values( array_filter( $tokens, function ( PushToken $token ) use ( $notification, $type, &$decision_cache ) { $user_id = $token->get_user_id(); if ( ! $user_id ) { return false; } if ( ! isset( $decision_cache[ $user_id ] ) ) { $prefs = $this->preferences_service->get_preferences( $user_id ); $decision_cache[ $user_id ] = $notification->should_send_to_user( $prefs[ $type ] ?? null ); } return $decision_cache[ $user_id ]; } ) ); } /** * Cancels the pending safety net ActionScheduler job for a notification. * * Called after the processor handles the notification (whether success or * failure with retry scheduled) so the safety net doesn't fire redundantly. * * @param Notification $notification The notification whose safety net to cancel. * @return void * * @since 10.9.0 */ private function cancel_safety_net( Notification $notification ): void { // Must match the shape PendingNotificationStore::schedule_safety_net() used; // both derive the args from Notification::get_safety_net_args() so the // exact-equality match Action Scheduler performs succeeds. as_unschedule_all_actions( self::SAFETY_NET_HOOK, $notification->get_safety_net_args(), self::ACTION_SCHEDULER_GROUP ); } /** * ActionScheduler callback for the safety net job. This will be scheduled * for 60 seconds in the future when a notification is added to the * `PendingNotificationStore`. If the initial send succeeds, or fails and is * able to schedule a retry, this action will be unscheduled. If the initial * send does not occur, or fails and cannot schedule a retry (e.g. out of * memory, retry scheduling error) then this safety net will run. * * @param string $type The notification type. * @param int $resource_id The resource ID. * @param array $extra Optional subclass-specific extras (e.g. event_type, stock_quantity_at_trigger). * Empty for notification types whose state is fully described by type + resource_id. * @return void * * @since 10.7.0 */ public function handle_safety_net( string $type, int $resource_id, array $extra = array() ): void { try { // Use the `+` array union operator (not array_merge) so the positional // $type and $resource_id always win over any colliding keys in $extra. // Defends against a malformed payload reconstructing the wrong target. $data = array( 'type' => $type, 'resource_id' => $resource_id, ) + $extra; $notification = Notification::from_array( $data ); } catch ( Exception $e ) { wc_get_logger()->error( sprintf( 'Safety net failed: %s', $e->getMessage() ), array( 'source' => PushNotifications::FEATURE_NAME ) ); return; } try { $this->process( $notification, true ); } catch ( Exception $e ) { wc_get_logger()->error( sprintf( 'Safety net failed: %s', $e->getMessage() ), array( 'source' => PushNotifications::FEATURE_NAME ) ); $this->retry_handler->schedule( $notification, null, 0 ); } } }
Fatal error: Uncaught Automattic\WooCommerce\Internal\DependencyManagement\ContainerException: Attempt to get an instance of class 'Automattic\WooCommerce\Internal\PushNotifications\Services\NotificationProcessor', which doesn't exist. in /htdocs/wp-content/plugins/woocommerce/src/Internal/DependencyManagement/RuntimeContainer.php:105 Stack trace: #0 /htdocs/wp-content/plugins/woocommerce/src/Internal/DependencyManagement/RuntimeContainer.php(78): Automattic\WooCommerce\Internal\DependencyManagement\RuntimeContainer->get_core('Automattic\\WooC...', Array) #1 /htdocs/wp-content/plugins/woocommerce/src/Container.php(68): Automattic\WooCommerce\Internal\DependencyManagement\RuntimeContainer->get('Automattic\\WooC...') #2 /htdocs/wp-content/plugins/woocommerce/src/Internal/PushNotifications/PushNotifications.php(90): Automattic\WooCommerce\Container->get('Automattic\\WooC...') #3 /htdocs/wp-includes/class-wp-hook.php(341): Automattic\WooCommerce\Internal\PushNotifications\PushNotifications->on_init('') #4 /htdocs/wp-includes/class-wp-hook.php(365): WP_Hook->apply_filters(NULL, Array) #5 /htdocs/wp-includes/plugin.php(522): WP_Hook->do_action(Array) #6 /htdocs/wp-settings.php(742): do_action('init') #7 /htdocs/wp-config.php(98): require_once('/htdocs/wp-sett...') #8 /htdocs/wp-load.php(50): require_once('/htdocs/wp-conf...') #9 /htdocs/wp-blog-header.php(13): require_once('/htdocs/wp-load...') #10 /htdocs/index.php(17): require('/htdocs/wp-blog...') #11 {main} thrown in /htdocs/wp-content/plugins/woocommerce/src/Internal/DependencyManagement/RuntimeContainer.php on line 105