Commit 0a9f6280b20 for woocommerce
commit 0a9f6280b20343b1b64afe62050fe4887ebbfb71
Author: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
Date: Wed Sep 23 21:15:33 2026 +0300
Report stock notification save failures instead of silent success (#68812)
* fix: propagate stock notification data store write errors from save()
* fix: drop resolved WC_Data_Store return-type entries from PHPStan baseline
* fix: stop treating a zero-row update as a notification write failure
* fix: translate stock notification data store error messages
* fix: check save() result directly when cancelling a stock notification
diff --git a/plugins/woocommerce/changelog/wooplug-7732-stock-notification-save-errors b/plugins/woocommerce/changelog/wooplug-7732-stock-notification-save-errors
new file mode 100644
index 00000000000..cd38099eeb1
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-7732-stock-notification-save-errors
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Surface failed database writes when saving a back in stock notification instead of reporting success.
diff --git a/plugins/woocommerce/includes/class-wc-data-store.php b/plugins/woocommerce/includes/class-wc-data-store.php
index 89281adc573..c6d0ad58f86 100644
--- a/plugins/woocommerce/includes/class-wc-data-store.php
+++ b/plugins/woocommerce/includes/class-wc-data-store.php
@@ -181,9 +181,10 @@ class WC_Data_Store {
*
* @since 3.0.0
* @param WC_Data $data WooCommerce data instance.
+ * @return mixed Whatever the underlying data store returns. Most core data stores return nothing; some return a WP_Error on failure.
*/
public function create( &$data ) {
- $this->instance->create( $data );
+ return $this->instance->create( $data );
}
/**
@@ -191,9 +192,10 @@ class WC_Data_Store {
*
* @since 3.0.0
* @param WC_Data $data WooCommerce data instance.
+ * @return mixed Whatever the underlying data store returns. Most core data stores return nothing; some return a WP_Error on failure.
*/
public function update( &$data ) {
- $this->instance->update( $data );
+ return $this->instance->update( $data );
}
/**
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index d108826542f..8d4f5a6ce09 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -10897,12 +10897,6 @@ parameters:
count: 1
path: includes/class-wc-customer.php
- -
- message: '#^Method WC_Data_Store\:\:create\(\) has no return type specified\.$#'
- identifier: missingType.return
- count: 1
- path: includes/class-wc-data-store.php
-
-
message: '#^Method WC_Data_Store\:\:delete\(\) has no return type specified\.$#'
identifier: missingType.return
@@ -10927,12 +10921,6 @@ parameters:
count: 1
path: includes/class-wc-data-store.php
- -
- message: '#^Method WC_Data_Store\:\:update\(\) has no return type specified\.$#'
- identifier: missingType.return
- count: 1
- path: includes/class-wc-data-store.php
-
-
message: '#^PHPDoc tag @param for parameter \$objects contains unresolvable type\.$#'
identifier: parameter.unresolvableType
diff --git a/plugins/woocommerce/src/Internal/DataStores/StockNotifications/StockNotificationsDataStore.php b/plugins/woocommerce/src/Internal/DataStores/StockNotifications/StockNotificationsDataStore.php
index 6cc1e782235..6d0b2afc26b 100644
--- a/plugins/woocommerce/src/Internal/DataStores/StockNotifications/StockNotificationsDataStore.php
+++ b/plugins/woocommerce/src/Internal/DataStores/StockNotifications/StockNotificationsDataStore.php
@@ -190,7 +190,7 @@ CREATE TABLE $meta_table_name (
);
if ( false === $insert ) {
- return new \WP_Error( 'db_insert_error', 'Could not insert stock notification into the database.' );
+ return new \WP_Error( 'db_insert_error', __( 'Could not insert stock notification into the database.', 'woocommerce' ) );
}
$notification_id = (int) $wpdb->insert_id;
@@ -260,7 +260,7 @@ CREATE TABLE $meta_table_name (
global $wpdb;
if ( 0 === $notification->get_id() ) {
- return new \WP_Error( 'invalid_stock_notification', 'Invalid notification ID.' );
+ return new \WP_Error( 'invalid_stock_notification', __( 'Invalid notification ID.', 'woocommerce' ) );
}
$changes = $notification->get_changes();
@@ -293,11 +293,7 @@ CREATE TABLE $meta_table_name (
);
if ( false === $result ) {
- return new \WP_Error( 'db_update_error', 'Could not update stock notification in the database.' );
- }
-
- if ( 0 === $result ) {
- return new \WP_Error( 'db_update_error', 'Invalid notification ID.' );
+ return new \WP_Error( 'db_update_error', __( 'Could not update stock notification in the database.', 'woocommerce' ) );
}
}
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Frontend/MyAccountEndpoint.php b/plugins/woocommerce/src/Internal/StockNotifications/Frontend/MyAccountEndpoint.php
index ce30000bd0d..a0adbdfde78 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Frontend/MyAccountEndpoint.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Frontend/MyAccountEndpoint.php
@@ -536,12 +536,7 @@ class MyAccountEndpoint {
$notification->set_cancellation_source( NotificationCancellationSource::USER );
$notification->set_date_cancelled( time() );
- $notification->save();
-
- // `WC_Data_Store::update()` drops the data store's return value, so a failed write
- // reaches us as a successful save. Read the row back to confirm it really changed.
- $saved = Factory::get_notification( $notification->get_id() );
- if ( ! $saved instanceof Notification || NotificationStatus::CANCELLED !== $saved->get_status() ) {
+ if ( is_wp_error( $notification->save() ) ) {
return new \WP_Error( 'wc_bis_cancel_failed', __( 'We could not cancel that back in stock notification. Please try again.', 'woocommerce' ) );
}
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Notification.php b/plugins/woocommerce/src/Internal/StockNotifications/Notification.php
index d4f41a52260..796a5e24da5 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Notification.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Notification.php
@@ -370,9 +370,13 @@ class Notification extends \WC_Data {
}
if ( $this->get_id() ) {
- $this->data_store->update( $this );
+ $result = $this->data_store->update( $this );
} else {
- $this->data_store->create( $this );
+ $result = $this->data_store->create( $this );
+ }
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
}
return $this->get_id();
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Frontend/MyAccountEndpointTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Frontend/MyAccountEndpointTests.php
index 3c80440b3fe..1e488ad0054 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Frontend/MyAccountEndpointTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Frontend/MyAccountEndpointTests.php
@@ -582,50 +582,6 @@ class MyAccountEndpointTests extends \WC_Unit_Test_Case {
$this->assertCount( 1, \wc_get_notices( 'error' ) );
}
- /**
- * A failed database write reports an error instead of telling the customer the
- * notification was cancelled.
- */
- public function test_cancel_reports_an_error_when_the_save_fails(): void {
- $user_id = $this->factory->user->create( array( 'role' => 'customer' ) );
- \wp_set_current_user( $user_id );
- $notification = $this->create_notification( $user_id, NotificationStatus::ACTIVE );
-
- $this->simulate_action_request( MyAccountEndpoint::ACTION_CANCEL, $notification->get_id(), true );
-
- $neutralize_update = array( $this, 'neutralize_notification_update' );
- add_filter( 'query', $neutralize_update );
- try {
- $this->run_action_expecting_redirect();
- } finally {
- remove_filter( 'query', $neutralize_update );
- }
-
- $this->assertCount( 1, \wc_get_notices( 'error' ) );
- $this->assertEmpty( \wc_get_notices( 'success' ) );
-
- $reloaded = Factory::get_notification( $notification->get_id() );
- $this->assertInstanceOf( Notification::class, $reloaded );
- $this->assertSame( NotificationStatus::ACTIVE, $reloaded->get_status() );
- }
-
- /**
- * Make an UPDATE against the stock notifications table match no rows, so the data
- * store reports the failure the way a real database error would.
- *
- * @param string $query The query about to run.
- * @return string
- */
- public function neutralize_notification_update( $query ) {
- global $wpdb;
-
- if ( is_string( $query ) && 0 === stripos( $query, 'UPDATE' ) && false !== stripos( $query, $wpdb->prefix . 'wc_stock_notifications' ) ) {
- return $query . ' AND 1 = 0';
- }
-
- return $query;
- }
-
/**
* User A cannot cancel user B's notification even when the nonce validates against B's action name
* (WordPress nonces bind to the current user, so this effectively asserts the ownership check).
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php
index 202e941b437..251b2330f3f 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php
@@ -3,8 +3,12 @@
declare( strict_types = 1 );
namespace Automattic\WooCommerce\Tests\Internal\StockNotifications;
+use Automattic\WooCommerce\Internal\DataStores\StockNotifications\StockNotificationsDataStore;
+use Automattic\WooCommerce\Internal\DataStores\StockNotifications\StockNotificationsMetaDataStore;
use Automattic\WooCommerce\Internal\StockNotifications\Notification;
use Automattic\WooCommerce\Internal\StockNotifications\Config;
+use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
+use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
/**
* NotificationTests data tests.
@@ -296,4 +300,79 @@ class NotificationTests extends \WC_Unit_Test_Case {
$this->assertInstanceOf( \WP_Error::class, $result );
$this->assertSame( 'User Email is invalid.', $result->get_error_message() );
}
+
+ /**
+ * @testdox save() should return the data store error when the insert fails.
+ */
+ public function test_save_returns_data_store_error_when_create_fails(): void {
+ $this->use_failing_data_store();
+
+ $notification = new Notification();
+ $notification->set_product_id( 1 );
+ $notification->set_user_email( 'foo@bar.com' );
+
+ $result = $notification->save();
+
+ $this->assertInstanceOf( \WP_Error::class, $result );
+ $this->assertSame( 'db_insert_error', $result->get_error_code() );
+ }
+
+ /**
+ * @testdox save() should return the data store error when the update fails.
+ */
+ public function test_save_returns_data_store_error_when_update_fails(): void {
+ $notification = new Notification();
+ $notification->set_product_id( 1 );
+ $notification->set_user_email( 'foo@bar.com' );
+ $notification->save();
+
+ $this->use_failing_data_store();
+
+ $stored = new Notification( $notification->get_id() );
+ $stored->set_status( NotificationStatus::CANCELLED );
+
+ $result = $stored->save();
+
+ $this->assertInstanceOf( \WP_Error::class, $result );
+ $this->assertSame( 'db_update_error', $result->get_error_code() );
+ }
+
+ /**
+ * Swap the stock notification data store for one whose writes always fail.
+ */
+ private function use_failing_data_store(): void {
+ $store = new class() extends StockNotificationsDataStore {
+ /**
+ * Fail every insert.
+ *
+ * @param Notification $notification The data object to create.
+ * @return \WP_Error
+ */
+ public function create( &$notification ) {
+ return new \WP_Error( 'db_insert_error', 'Could not insert stock notification into the database.' );
+ }
+
+ /**
+ * Fail every update.
+ *
+ * @param Notification $notification The data object to update.
+ * @return \WP_Error
+ */
+ public function update( &$notification ) {
+ return new \WP_Error( 'db_update_error', 'Could not update stock notification in the database.' );
+ }
+ };
+
+ $store->init(
+ wc_get_container()->get( StockNotificationsMetaDataStore::class ),
+ wc_get_container()->get( DatabaseUtil::class )
+ );
+
+ add_filter(
+ 'woocommerce_stock_notification_data_store',
+ function () use ( $store ) {
+ return $store;
+ }
+ );
+ }
}