Commit 20949b8c161 for woocommerce

commit 20949b8c161e518123bd3afabf0f439c92786498
Author: Taha Paksu <3295+tpaksu@users.noreply.github.com>
Date:   Mon Nov 9 22:29:14 2026 +0300

    Fix fulfillment erasure skipped by a non-array filter return (#70725)

diff --git a/plugins/woocommerce/changelog/70725-wooairr-717-ai-regression-reviewtrunk-an-extension-returning-a-non-array b/plugins/woocommerce/changelog/70725-wooairr-717-ai-regression-reviewtrunk-an-extension-returning-a-non-array
new file mode 100644
index 00000000000..4058ec0bec1
--- /dev/null
+++ b/plugins/woocommerce/changelog/70725-wooairr-717-ai-regression-reviewtrunk-an-extension-returning-a-non-array
@@ -0,0 +1,5 @@
+Significance: patch
+Type: dev
+Comment: Fix an unreleased fulfillment privacy filter fallback; nothing merchant-facing changes.
+
+
diff --git a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsPrivacy.php b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsPrivacy.php
index d3a2f9dc5ff..50f7dcf9738 100644
--- a/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsPrivacy.php
+++ b/plugins/woocommerce/src/Admin/Features/Fulfillments/FulfillmentsPrivacy.php
@@ -263,9 +263,13 @@ class FulfillmentsPrivacy {
 			return;
 		}

+		$default_keys = array( '_tracking_number', '_tracking_url' );
+
 		/**
 		 * Filters the fulfillment meta keys redacted during personal-data erasure.
 		 *
+		 * A non-array return is ignored and the default keys are cleared instead. An empty array clears nothing.
+		 *
 		 * @since 11.4.0
 		 *
 		 * @param string[] $meta_keys The meta keys to clear.
@@ -273,12 +277,16 @@ class FulfillmentsPrivacy {
 		 */
 		$meta_keys = apply_filters(
 			'woocommerce_privacy_remove_fulfillment_personal_data_meta',
-			array( '_tracking_number', '_tracking_url' ),
+			$default_keys,
 			$order
 		);

 		if ( ! is_array( $meta_keys ) ) {
-			return;
+			wc_get_logger()->warning(
+				'The woocommerce_privacy_remove_fulfillment_personal_data_meta filter returned a non-array value for order ' . $order->get_id() . ', so the default keys were used.',
+				array( 'source' => 'fulfillments' )
+			);
+			$meta_keys = $default_keys;
 		}

 		$meta_keys = array_values(
diff --git a/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/FulfillmentsPrivacyTest.php b/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/FulfillmentsPrivacyTest.php
index 95bdff0c82a..e6fff756191 100644
--- a/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/FulfillmentsPrivacyTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/Features/Fulfillments/FulfillmentsPrivacyTest.php
@@ -620,4 +620,78 @@ class FulfillmentsPrivacyTest extends WC_Unit_Test_Case {
 		$reloaded = $this->reload_fulfillment( $order->get_id(), $fulfillment->get_id() );
 		$this->assertNull( $reloaded->get_tracking_number(), 'The valid key should be redacted and the non-scalar entry ignored.' );
 	}
+
+	/**
+	 * @testdox The eraser falls back to the default meta keys and logs a warning when the filter returns a non-array.
+	 *
+	 * @dataProvider provider_non_array_filter_values
+	 *
+	 * @param mixed $filtered_value The invalid value returned by the filter.
+	 */
+	public function test_eraser_falls_back_to_default_keys_for_non_array_filter_value( $filtered_value ): void {
+		list( $order, $fulfillment ) = $this->create_order_with_fulfillment(
+			array(
+				'_tracking_number' => 'TRACK123456',
+				'_tracking_url'    => 'https://example.com/track/TRACK123456',
+			)
+		);
+
+		$logger = $this->createMock( \WC_Logger_Interface::class );
+		$logger->expects( $this->once() )
+			->method( 'warning' )
+			->with( $this->stringContains( 'order ' . $order->get_id() ), $this->arrayHasKey( 'source' ) );
+		add_filter(
+			'woocommerce_logging_class',
+			function () use ( $logger ) {
+				return $logger;
+			}
+		);
+		add_filter(
+			'woocommerce_privacy_remove_fulfillment_personal_data_meta',
+			function () use ( $filtered_value ) {
+				return $filtered_value;
+			}
+		);
+
+		$this->sut->remove_fulfillment_personal_data( $order );
+
+		$reloaded = $this->reload_fulfillment( $order->get_id(), $fulfillment->get_id() );
+		$this->assertNull( $reloaded->get_tracking_number(), 'The default tracking number key should still be cleared.' );
+		$this->assertNull( $reloaded->get_tracking_url(), 'The default tracking URL key should still be cleared.' );
+	}
+
+	/**
+	 * @return array<string, array<mixed>>
+	 */
+	public function provider_non_array_filter_values(): array {
+		return array(
+			'string' => array( 'invalid' ),
+			'null'   => array( null ),
+			'false'  => array( false ),
+		);
+	}
+
+	/**
+	 * @testdox The eraser clears nothing, without a warning, when the filter explicitly returns an empty array.
+	 */
+	public function test_eraser_clears_nothing_when_filter_returns_empty_array(): void {
+		list( $order, $fulfillment ) = $this->create_order_with_fulfillment(
+			array( '_tracking_number' => 'TRACK123456' )
+		);
+
+		$logger = $this->createMock( \WC_Logger_Interface::class );
+		$logger->expects( $this->never() )->method( 'warning' );
+		add_filter(
+			'woocommerce_logging_class',
+			function () use ( $logger ) {
+				return $logger;
+			}
+		);
+		add_filter( 'woocommerce_privacy_remove_fulfillment_personal_data_meta', '__return_empty_array' );
+
+		$this->sut->remove_fulfillment_personal_data( $order );
+
+		$reloaded = $this->reload_fulfillment( $order->get_id(), $fulfillment->get_id() );
+		$this->assertSame( 'TRACK123456', $reloaded->get_tracking_number(), 'An explicit empty array is an opt-out and should clear nothing.' );
+	}
 }