Commit 1778ca309ab for woocommerce

commit 1778ca309ab960f7708cf8ad8fbeeae6fe3f7d43
Author: Vincent Borgers <88669109+VincentBorgers@users.noreply.github.com>
Date:   Mon Oct 5 12:10:45 2026 +0200

    Fix product search for users who can read but not edit private products (#69362)

    * Fix product search for users who can read but not edit private products

    * Add changelog entry for private product search fix

    * Update docblock

    * Make sure user in test has no edit_private_products capability

    ---------

    Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>

diff --git a/plugins/woocommerce/changelog/31733-fix-private-product-search-capability b/plugins/woocommerce/changelog/31733-fix-private-product-search-capability
new file mode 100644
index 00000000000..f5c9db799c9
--- /dev/null
+++ b/plugins/woocommerce/changelog/31733-fix-private-product-search-capability
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Show private products in admin product searches to users who can read private products but not edit them.
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
index a054a78e43c..3f58cf54324 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php
@@ -2051,7 +2051,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
 	 * @param  string     $term Search term.
 	 * @param  string     $type Type of product.
 	 * @param  bool       $include_variations Include variations in search or not.
-	 * @param  bool       $all_statuses Should we search all statuses or limit to published.
+	 * @param  bool       $all_statuses True searches every status. False searches published products, plus private products when the current user can read them.
 	 * @param  null|int   $limit Limit returned results. @since 3.5.0.
 	 * @param  null|array $include Keep specific results. @since 3.6.0.
 	 * @param  null|array $exclude Discard specific results. @since 3.6.0.
@@ -2086,7 +2086,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
 		 */
 		$post_statuses = apply_filters(
 			'woocommerce_search_products_post_statuses',
-			current_user_can( 'edit_private_products' ) ? array( 'private', 'publish' ) : array( 'publish' )
+			current_user_can( 'read_private_products' ) ? array( 'private', 'publish' ) : array( 'publish' )
 		);

 		// See if search term contains OR keywords.
diff --git a/plugins/woocommerce/phpcs.xml b/plugins/woocommerce/phpcs.xml
index db630ac8883..4bddbc1c73c 100644
--- a/plugins/woocommerce/phpcs.xml
+++ b/plugins/woocommerce/phpcs.xml
@@ -133,6 +133,7 @@
 				<element key="15" value="manage_woocommerce"/>
 				<element key="16" value="manage_product_terms"/>
 				<element key="17" value="view_woocommerce_reports"/>
+				<element key="18" value="read_private_products"/>
 			</property>
 		</properties>
 	</rule>
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-data-store-cpt-test.php
index 8a76b6b050d..62d239c6d61 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-data-store-cpt-test.php
@@ -326,6 +326,26 @@ class WC_Product_Data_Store_CPT_Test extends WC_Unit_Test_Case {
 		}
 	}

+	/**
+	 * @testdox Search should include private products for users who can read them but not edit them.
+	 */
+	public function test_search_products_includes_private_products_for_users_who_can_read_them(): void {
+		$product = $this->create_search_test_product( 'Searchable private product', 'private' );
+		$user_id = self::factory()->user->create( array( 'role' => 'customer' ) );
+		wp_set_current_user( $user_id );
+
+		$data_store = WC_Data_Store::load( 'product' );
+
+		$this->assertNotContains( $product->get_id(), $data_store->search_products( 'Searchable private product', '', false, false ), 'Private product should not be returned to users who cannot read private products' );
+
+		wp_get_current_user()->add_cap( 'read_private_products' );
+
+		$this->assertTrue( current_user_can( 'read_private_products' ), 'The user under test should be able to read private products' );
+		$this->assertFalse( current_user_can( 'edit_private_products' ), 'Granting read access should leave the user unable to edit private products' );
+
+		$this->assertContains( $product->get_id(), $data_store->search_products( 'Searchable private product', '', false, false ), 'Private product should be returned to users who can read private products but not edit them' );
+	}
+
 	/**
 	 * Ensure product rating counts are calculated correctly.
 	 *