Fail-closed: once the rate limit window is claimed it is not released, so a failure or an exception raised further down still holds the customer back until the window expires. A window that cannot be claimed at all is the exception, and lets the sign-up through.
public function signup( int $product_id, int $user_id, string $user_email, array $posted_attributes = array() ) {
$user_email = EmailNormalizer::normalize( $user_email );
// Sanity checks.
if ( ! Config::allows_signups() ) {
return new \WP_Error( self::ERROR_FAILED );
}
if ( empty( $user_email ) && empty( $user_id ) ) {
return new \WP_Error( self::ERROR_INVALID_REQUEST );
}
$product = wc_get_product( $product_id );
if ( ! $product ) {
return new \WP_Error( self::ERROR_INVALID_PRODUCT );
}
if ( ! $this->eligibility_service->is_product_eligible( $product ) ) {
return new \WP_Error( self::ERROR_INVALID_PRODUCT );
}
if ( $this->eligibility_service->is_stock_status_eligible( $product->get_stock_status() ) ) {
return new \WP_Error( self::ERROR_INVALID_REQUEST );
}
if ( ! $this->eligibility_service->product_allows_signups( $product ) ) {
return new \WP_Error( self::ERROR_INVALID_PRODUCT );
}
// Attempts that only find an existing active or pending sign-up, or activate an existing
// pending one, create nothing new and send no verification mail, so they are answered
// before the rate limit is consulted or claimed.
$notification = $this->is_already_signed_up( $product_id, $user_id, $user_email, $posted_attributes );
if ( $notification instanceof Notification ) {
if ( NotificationStatus::ACTIVE === $notification->get_status() ) {
return new SignupResult( self::SIGNUP_ALREADY_JOINED, $notification );
}
if ( NotificationStatus::PENDING === $notification->get_status() ) {
if ( Config::requires_double_opt_in() ) {
return new SignupResult( self::SIGNUP_ALREADY_JOINED_DOUBLE_OPT_IN, $notification );
}
// Double opt-in is not required, so activate the pending notification instead of creating one.
$notification->set_status( NotificationStatus::ACTIVE );
$saved = $notification->save();
if ( \is_wp_error( $saved ) || ! $saved ) {
return new \WP_Error( self::ERROR_FAILED );
}
/**
* Action: woocommerce_customer_stock_notifications_signup
*
* @since 10.2.0
*
* @param Notification $notification The notification.
*/
do_action( 'woocommerce_customer_stock_notifications_signup', $notification );
return new SignupResult( self::SIGNUP_SUCCESS, $notification );
}
}
if ( SignupRateLimiter::is_rate_limited( $user_email ) ) {
return new \WP_Error( self::ERROR_RATE_LIMITED );
}
// Claim the rate limit window before storing a notification or sending mail. This
// narrows the window in which two near-simultaneous requests both get through; it
// does not close it.
//
// A claim only fails when the rate limit table cannot be written to, which a shopper
// can neither cause nor resolve. Let the sign-up through rather than turn a broken
// limiter into a store-wide sign-up outage.
if ( ! SignupRateLimiter::apply( $user_email ) ) {
$this->logger->warning(
'Could not claim the stock notification sign-up rate limit window. Allowing the sign-up to proceed.',
array( 'source' => 'stock-notifications-signup-errors' )
);
}
$notification = new Notification();
$notification->set_status( NotificationStatus::ACTIVE );
$notification->set_product_id( $product_id );
$notification->set_user_id( $user_id );
$notification->set_user_email( $user_email );
if ( ! empty( $posted_attributes ) ) {
$notification->update_meta_data( 'posted_attributes', $posted_attributes );
}
if ( Config::requires_double_opt_in() ) {
$notification->set_status( NotificationStatus::PENDING );
}
$saved = $notification->save();
if ( \is_wp_error( $saved ) || ! $saved ) {
return new \WP_Error( self::ERROR_FAILED );
}
/**
* Action: woocommerce_customer_stock_notifications_signup
*
* @since 10.2.0
*
* @param Notification $notification The notification.
*/
do_action( 'woocommerce_customer_stock_notifications_signup', $notification );
if ( Config::requires_double_opt_in() && NotificationStatus::PENDING === $notification->get_status() ) {
$this->email_manager->send_verify_email( $notification );
}
$signup_code = Config::requires_double_opt_in() ? self::SIGNUP_SUCCESS_DOUBLE_OPT_IN : self::SIGNUP_SUCCESS;
return new SignupResult( $signup_code, $notification );
}