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();
 	}