Commit a54159e07fb for woocommerce

commit a54159e07fb2fdd0efde19060484cfe8476deca4
Author: Samrat Biswas <samrat.biswas@automattic.com>
Date:   Fri Jul 3 03:20:09 2026 -0700

    Add setting to enable/disable backorder notifications (#65891)

    * Add setting to enable/disable backorder notifications

    Adds a woocommerce_notify_backorder setting (Settings > Products >
    Inventory > Notifications) and a woocommerce_should_send_backorder_notification
    filter, mirroring the existing low stock / out of stock notifications.
    Defaults to enabled, so existing stores are unaffected on upgrade.

    Closes #31365

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    * Add woocommerce_notify_backorder to inventory settings test expectations

    The new backorder notification setting added to the inventory section was
    missing from the expected settings array in
    test_get_inventory_settings_returns_all_settings, causing the assertEquals
    to fail.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    * Add tests for backorder notification setting and filter gates

    Cover the new gating in WC_Emails::backorder(): the email is sent when
    woocommerce_notify_backorder is enabled, and suppressed when the option is
    disabled or when the woocommerce_should_send_backorder_notification filter
    returns false.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    * Remove stray blank line in backorder filter docblock

    Align the woocommerce_should_send_backorder_notification docblock with the
    existing low_stock/no_stock filter docblocks, which have no blank line
    before @since.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    * Fix PHPStan error in backorder notification gate

    The new woocommerce_should_send_backorder_notification filter calls
    $args['product']->get_id(), but PHPStan typed $args['product'] as a bare
    object (no get_id()), failing CI. Narrow the existing guard from
    is_object() to an instanceof WC_Product check so the product is properly
    typed. This also resolves the two previously-baselined object:: method
    errors (get_stock_quantity, get_formatted_name) on the same method, which
    are removed from phpstan-baseline.neon.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---------

    Co-authored-by: Samrat Biswas <samrat.biswas@a8c.com>
    Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Co-authored-by:  Ján Mikláš <neosinner@gmail.com>

diff --git a/plugins/woocommerce/changelog/31365-add-backorder-notification-setting b/plugins/woocommerce/changelog/31365-add-backorder-notification-setting
new file mode 100644
index 00000000000..1915a0908ed
--- /dev/null
+++ b/plugins/woocommerce/changelog/31365-add-backorder-notification-setting
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add a setting to enable or disable backorder stock notification emails, plus a woocommerce_should_send_backorder_notification filter, mirroring the existing low stock and out of stock notifications.
diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php
index a61dc47e687..f3fe6fd4419 100644
--- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php
+++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-products.php
@@ -289,6 +289,16 @@ class WC_Settings_Products extends WC_Settings_Page {
 					'id'            => 'woocommerce_notify_no_stock',
 					'default'       => 'yes',
 					'type'          => 'checkbox',
+					'checkboxgroup' => '',
+					'autoload'      => false,
+					'class'         => 'manage_stock_field',
+				),
+
+				array(
+					'desc'          => __( 'Enable backorder notifications', 'woocommerce' ),
+					'id'            => 'woocommerce_notify_backorder',
+					'default'       => 'yes',
+					'type'          => 'checkbox',
 					'checkboxgroup' => 'end',
 					'autoload'      => false,
 					'class'         => 'manage_stock_field',
diff --git a/plugins/woocommerce/includes/class-wc-emails.php b/plugins/woocommerce/includes/class-wc-emails.php
index 3ec1c534a11..bea9a7b3073 100644
--- a/plugins/woocommerce/includes/class-wc-emails.php
+++ b/plugins/woocommerce/includes/class-wc-emails.php
@@ -1197,13 +1197,28 @@ class WC_Emails {
 		$order = wc_get_order( $args['order_id'] );
 		if (
 		! $args['product'] ||
-		! is_object( $args['product'] ) ||
+		! $args['product'] instanceof WC_Product ||
 		! $args['quantity'] ||
 		! $order
 		) {
 			return;
 		}

+		if ( 'no' === get_option( 'woocommerce_notify_backorder', 'yes' ) ) {
+			return;
+		}
+
+		/**
+		 * Determine if the current product should trigger a backorder notification.
+		 *
+		 * @param bool $send       Whether the backorder notification should be sent.
+		 * @param int  $product_id The backordered product id.
+		 * @since 11.0.0
+		 */
+		if ( false === apply_filters( 'woocommerce_should_send_backorder_notification', true, $args['product']->get_id() ) ) {
+			return;
+		}
+
 		$stock_before         = $args['quantity'] + $args['product']->get_stock_quantity();
 		$backordered_quantity = $args['quantity'] - max( 0, $stock_before );

diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 3f9e35eca02..09ad470e704 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -11556,18 +11556,6 @@ parameters:
 			count: 1
 			path: includes/class-wc-emails.php

-		-
-			message: '#^Call to an undefined method object\:\:get_formatted_name\(\)\.$#'
-			identifier: method.notFound
-			count: 1
-			path: includes/class-wc-emails.php
-
-		-
-			message: '#^Call to an undefined method object\:\:get_stock_quantity\(\)\.$#'
-			identifier: method.notFound
-			count: 1
-			path: includes/class-wc-emails.php
-
 		-
 			message: '#^Cannot call method get_id\(\) on WC_Order\|WC_Order_Refund\|false\.$#'
 			identifier: method.nonObject
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-emails-tests.php b/plugins/woocommerce/tests/php/includes/class-wc-emails-tests.php
index d8e4261b443..69f4a628c7d 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-emails-tests.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-emails-tests.php
@@ -163,4 +163,71 @@ class WC_Emails_Tests extends \WC_Unit_Test_Case {
 		$this->assertStringContainsString( 'Test meta key', $content );
 		$this->assertStringContainsString( 'test_meta_value', $content );
 	}
+
+	/**
+	 * Build a valid set of arguments for WC_Emails::backorder().
+	 *
+	 * @return array
+	 */
+	private function get_backorder_args() {
+		$product = WC_Helper_Product::create_simple_product();
+		$order   = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
+
+		return array(
+			'product'  => $product,
+			'quantity' => 2,
+			'order_id' => $order->get_id(),
+		);
+	}
+
+	/**
+	 * @testdox backorder() sends a notification when the setting is enabled (default).
+	 */
+	public function test_backorder_sends_when_setting_enabled() {
+		update_option( 'woocommerce_notify_backorder', 'yes' );
+		$args = $this->get_backorder_args();
+
+		$mailer = tests_retrieve_phpmailer_instance();
+		$before = count( $mailer->mock_sent );
+		( new WC_Emails() )->backorder( $args );
+		$after = count( $mailer->mock_sent );
+
+		$this->assertSame( $before + 1, $after, 'Backorder notification should be sent when the setting is enabled.' );
+	}
+
+	/**
+	 * @testdox backorder() does not send a notification when the setting is disabled.
+	 */
+	public function test_backorder_does_not_send_when_setting_disabled() {
+		update_option( 'woocommerce_notify_backorder', 'no' );
+		$args = $this->get_backorder_args();
+
+		$mailer = tests_retrieve_phpmailer_instance();
+		$before = count( $mailer->mock_sent );
+		( new WC_Emails() )->backorder( $args );
+		$after = count( $mailer->mock_sent );
+
+		$this->assertSame( $before, $after, 'Backorder notification must not be sent when the setting is disabled.' );
+
+		update_option( 'woocommerce_notify_backorder', 'yes' );
+	}
+
+	/**
+	 * @testdox backorder() does not send a notification when the woocommerce_should_send_backorder_notification filter returns false.
+	 */
+	public function test_backorder_does_not_send_when_filter_returns_false() {
+		update_option( 'woocommerce_notify_backorder', 'yes' );
+		$args = $this->get_backorder_args();
+
+		add_filter( 'woocommerce_should_send_backorder_notification', '__return_false' );
+
+		$mailer = tests_retrieve_phpmailer_instance();
+		$before = count( $mailer->mock_sent );
+		( new WC_Emails() )->backorder( $args );
+		$after = count( $mailer->mock_sent );
+
+		remove_filter( 'woocommerce_should_send_backorder_notification', '__return_false' );
+
+		$this->assertSame( $before, $after, 'Backorder notification must not be sent when the filter returns false.' );
+	}
 }
diff --git a/plugins/woocommerce/tests/php/includes/settings/class-wc-settings-products-test.php b/plugins/woocommerce/tests/php/includes/settings/class-wc-settings-products-test.php
index c461549dc3b..d44e67d03ac 100644
--- a/plugins/woocommerce/tests/php/includes/settings/class-wc-settings-products-test.php
+++ b/plugins/woocommerce/tests/php/includes/settings/class-wc-settings-products-test.php
@@ -128,6 +128,7 @@ class WC_Settings_Products_Test extends WC_Settings_Unit_Test_Case {
 			'woocommerce_hold_stock_minutes'      => 'number',
 			'woocommerce_notify_low_stock'        => 'checkbox',
 			'woocommerce_notify_no_stock'         => 'checkbox',
+			'woocommerce_notify_backorder'        => 'checkbox',
 			'woocommerce_stock_email_recipient'   => 'text',
 			'woocommerce_notify_low_stock_amount' => 'number',
 			'woocommerce_notify_no_stock_amount'  => 'number',