Commit 06526c15bd5 for woocommerce

commit 06526c15bd55f23115c5d494a7396a4f3d193a72
Author: Saskia Teichmann <s-a-s-k-i-a@users.noreply.github.com>
Date:   Tue Sep 8 12:13:44 2026 +0200

    Pass include/exclude through to the downloadable product search (#68166)

diff --git a/plugins/woocommerce/changelog/68101-grant-access-search-exclude b/plugins/woocommerce/changelog/68101-grant-access-search-exclude
new file mode 100644
index 00000000000..8d2906325c2
--- /dev/null
+++ b/plugins/woocommerce/changelog/68101-grant-access-search-exclude
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Pass the include/exclude parameters through to the downloadable product search so the Grant access field on the order edit screen no longer lists products that are already selected.
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index b9b0396d27f..eaddf7e50a7 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -2181,7 +2181,7 @@ class WC_AJAX {

 		$term       = isset( $_GET['term'] ) ? (string) wc_clean( wp_unslash( $_GET['term'] ) ) : '';
 		$data_store = WC_Data_Store::load( 'product' );
-		$ids        = $data_store->search_products( $term, 'downloadable', true, false, $limit );
+		$ids        = $data_store->search_products( $term, 'downloadable', true, false, $limit, $include_ids, $exclude_ids );

 		_prime_post_caches( $ids );
 		$product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' );
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
index a650c71fd3e..24cc3dbafb9 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
@@ -2385,6 +2385,47 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
 		$this->assertEquals( $original_qty, $fresh_item->get_quantity() );
 	}

+	/**
+	 * The Grant access product search must honor the include/exclude parameters so
+	 * already-granted products do not reappear in the results.
+	 *
+	 * @see https://github.com/woocommerce/woocommerce/issues/68101
+	 */
+	public function test_json_search_downloadable_products_honors_include_and_exclude(): void {
+		$product_one = WC_Helper_Product::create_simple_product();
+		$product_one->set_name( 'Exclusit Download One' );
+		$product_one->set_downloadable( true );
+		$product_one->save();
+
+		$product_two = WC_Helper_Product::create_simple_product();
+		$product_two->set_name( 'Exclusit Download Two' );
+		$product_two->set_downloadable( true );
+		$product_two->save();
+
+		$this->_setRole( 'administrator' );
+
+		$_GET['security'] = wp_create_nonce( 'search-products' );
+		$_GET['term']     = 'Exclusit Download';
+		$_GET['exclude']  = array( $product_one->get_id() );
+
+		$response = $this->do_ajax( 'woocommerce_json_search_downloadable_products_and_variations' );
+
+		$this->assertIsArray( $response, 'The search should return a result set.' );
+		$this->assertArrayHasKey( $product_two->get_id(), $response, 'The non-excluded product must be part of the results.' );
+		$this->assertArrayNotHasKey( $product_one->get_id(), $response, 'An excluded (already granted) product must not reappear in the results.' );
+
+		// The include allowlist must be honored as well.
+		unset( $_GET['exclude'] );
+		$_GET['security'] = wp_create_nonce( 'search-products' );
+		$_GET['include']  = array( $product_one->get_id() );
+
+		$response = $this->do_ajax( 'woocommerce_json_search_downloadable_products_and_variations' );
+
+		$this->assertIsArray( $response, 'The include search should return a result set.' );
+		$this->assertArrayHasKey( $product_one->get_id(), $response, 'The included product must be part of the results.' );
+		$this->assertArrayNotHasKey( $product_two->get_id(), $response, 'A product outside the include allowlist must not be part of the results.' );
+	}
+
 	/**
 	 * Does the 'hard work' of triggering an ajax endpoint and capturing the response.
 	 *