Commit 98c8895d0f3 for woocommerce
commit 98c8895d0f380146275e148251eeecc6041191b0
Author: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
Date: Tue Oct 13 11:52:56 2026 +0300
Fix the per-product "Stock notifications" checkbox not saving when unticked (#69497)
* fix: save unticked per-product stock notifications checkbox by default
* fix: skip BIS product save when its nonce field was not submitted
diff --git a/plugins/woocommerce/changelog/fix-wooplug-7910-bis-product-checkbox-save b/plugins/woocommerce/changelog/fix-wooplug-7910-bis-product-checkbox-save
new file mode 100644
index 00000000000..e57ad639889
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-wooplug-7910-bis-product-checkbox-save
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Allow turning off "Stock notifications" for a product that has never had the setting changed.
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Admin/SettingsController.php b/plugins/woocommerce/src/Internal/StockNotifications/Admin/SettingsController.php
index e9aaa7dc819..15ebfc8c50e 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Admin/SettingsController.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Admin/SettingsController.php
@@ -276,6 +276,11 @@ class SettingsController {
*/
public static function process_product_object( $product ) {
+ // Without the nonce field the checkbox was not rendered, so there is nothing to save.
+ if ( ! isset( $_POST['customer_stock_notifications_edit_product_security'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Only checks presence; the nonce is verified before saving.
+ return;
+ }
+
if ( ! Config::allows_signups() ) {
return;
}
@@ -289,8 +294,8 @@ class SettingsController {
}
$posted_is_enabled = isset( $_POST[ Config::get_product_signups_meta_key() ] );
- $current_value = $product->get_meta( Config::get_product_signups_meta_key() );
- if ( ( $posted_is_enabled && 'no' === $current_value ) || ( ! $posted_is_enabled && 'yes' === $current_value ) ) {
+ $current_enabled = 'no' !== $product->get_meta( Config::get_product_signups_meta_key() );
+ if ( $posted_is_enabled !== $current_enabled ) {
check_admin_referer( 'woocommerce-customer-stock-notifications-edit-product', 'customer_stock_notifications_edit_product_security' );
$product->update_meta_data( Config::get_product_signups_meta_key(), $posted_is_enabled ? 'yes' : 'no' );
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Admin/SettingsControllerTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Admin/SettingsControllerTests.php
index 36ce2280c0c..053f3752124 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Admin/SettingsControllerTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Admin/SettingsControllerTests.php
@@ -4,7 +4,9 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Tests\Internal\StockNotifications\Admin;
use Automattic\WooCommerce\Internal\StockNotifications\Admin\SettingsController;
+use Automattic\WooCommerce\Internal\StockNotifications\Config;
use Automattic\WooCommerce\Internal\StockNotifications\Frontend\MyAccountEndpoint;
+use WC_Helper_Product;
use WC_Settings_Advanced;
use WC_Settings_Products;
use WP_REST_Request;
@@ -167,9 +169,68 @@ class SettingsControllerTests extends \WC_Settings_Unit_Test_Case {
}
/**
- * Reset the REST server so this test does not leak it into tests that run after it.
+ * @return array<string, array{0: string, 1: bool, 2: string}>
+ */
+ public function provider_product_signups_checkbox(): array {
+ return array(
+ 'no meta, checkbox unticked' => array( '', false, 'no' ),
+ 'no meta, checkbox ticked' => array( '', true, '' ),
+ 'disabled, checkbox ticked' => array( 'no', true, 'yes' ),
+ 'enabled, checkbox unticked' => array( 'yes', false, 'no' ),
+ 'disabled, checkbox unticked' => array( 'no', false, 'no' ),
+ );
+ }
+
+ /**
+ * @testdox Saving a product stores the "Stock notifications" checkbox when it differs from the effective value.
+ *
+ * @dataProvider provider_product_signups_checkbox
+ *
+ * @param string $stored_value Meta value before saving.
+ * @param bool $posted_enabled Whether the checkbox is posted.
+ * @param string $expected_value Meta value after saving.
+ */
+ public function test_process_product_object_saves_signups_checkbox( string $stored_value, bool $posted_enabled, string $expected_value ): void {
+ update_option( 'woocommerce_customer_stock_notifications_allow_signups', 'yes' );
+ $meta_key = Config::get_product_signups_meta_key();
+ $product = WC_Helper_Product::create_simple_product();
+ if ( '' !== $stored_value ) {
+ $product->update_meta_data( $meta_key, $stored_value );
+ $product->save();
+ }
+
+ $nonce = wp_create_nonce( 'woocommerce-customer-stock-notifications-edit-product' );
+ $_POST['customer_stock_notifications_edit_product_security'] = $nonce;
+ $_REQUEST['customer_stock_notifications_edit_product_security'] = $nonce;
+ if ( $posted_enabled ) {
+ $_POST[ $meta_key ] = 'on';
+ }
+
+ SettingsController::process_product_object( $product );
+
+ $this->assertSame( $expected_value, $product->get_meta( $meta_key ) );
+ }
+
+ /**
+ * @testdox Saving a product from a form without the stock notifications nonce field leaves the setting unchanged.
+ */
+ public function test_process_product_object_skips_save_when_nonce_field_is_absent(): void {
+ update_option( 'woocommerce_customer_stock_notifications_allow_signups', 'yes' );
+ $meta_key = Config::get_product_signups_meta_key();
+ $product = WC_Helper_Product::create_simple_product();
+ $product->update_meta_data( $meta_key, 'yes' );
+ $product->save();
+
+ SettingsController::process_product_object( $product );
+
+ $this->assertSame( 'yes', $product->get_meta( $meta_key ) );
+ }
+
+ /**
+ * Reset the REST server and request globals so this test does not leak them into tests that run after it.
*/
public function tearDown(): void {
+ unset( $_POST['customer_stock_notifications_edit_product_security'], $_REQUEST['customer_stock_notifications_edit_product_security'], $_POST[ Config::get_product_signups_meta_key() ] );
$this->clear_rest_server();
parent::tearDown();
}