Commit b0606e83996 for woocommerce

commit b0606e83996f31ba8c3138c61eb3c09e7277ea19
Author: Jaclyn Chen <watertranquil@gmail.com>
Date:   Wed Jun 3 19:43:47 2026 +0800

    Remove unused `products-catalog-api feature` flag and mock controller (#65456)

    * Remove unused products-catalog-api feature flag and mock controller

    * Add changelog for products-catalog-api removal

diff --git a/plugins/woocommerce/changelog/remove-products-catalog-api b/plugins/woocommerce/changelog/remove-products-catalog-api
new file mode 100644
index 00000000000..62798b357fd
--- /dev/null
+++ b/plugins/woocommerce/changelog/remove-products-catalog-api
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Remove unused `products-catalog-api` feature flag and its mock `WC_REST_Products_Catalog_Controller`, superseded by the `wc/pos/v1/catalog` endpoint.
diff --git a/plugins/woocommerce/client/admin/config/core.json b/plugins/woocommerce/client/admin/config/core.json
index d28680e9d8e..ae942d24c40 100644
--- a/plugins/woocommerce/client/admin/config/core.json
+++ b/plugins/woocommerce/client/admin/config/core.json
@@ -23,7 +23,6 @@
 		"onboarding-tasks": true,
 		"pattern-toolkit-full-composability": true,
 		"product-custom-fields": true,
-		"products-catalog-api": false,
 		"remote-inbox-notifications": true,
 		"remote-free-extensions": true,
 		"payment-gateway-suggestions": true,
diff --git a/plugins/woocommerce/client/admin/config/development.json b/plugins/woocommerce/client/admin/config/development.json
index d2147d39992..c478e8e386f 100644
--- a/plugins/woocommerce/client/admin/config/development.json
+++ b/plugins/woocommerce/client/admin/config/development.json
@@ -24,7 +24,6 @@
 		"pattern-toolkit-full-composability": true,
 		"payment-gateway-suggestions": true,
 		"product-custom-fields": true,
-		"products-catalog-api": true,
 		"printful": true,
 		"remote-inbox-notifications": true,
 		"remote-free-extensions": true,
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-catalog-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-catalog-controller.php
deleted file mode 100644
index b989b81c865..00000000000
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-catalog-controller.php
+++ /dev/null
@@ -1,273 +0,0 @@
-<?php
-/**
- * REST API Products Catalog controller
- *
- * Handles requests to the products/catalog endpoint.
- *
- * @package WooCommerce\RestApi
- * @since   10.4.0
- */
-
-declare( strict_types = 1 );
-
-defined( 'ABSPATH' ) || exit;
-
-use Automattic\WooCommerce\Internal\Utilities\FilesystemUtil;
-
-/**
- * REST API Products Catalog controller class.
- *
- * @package WooCommerce\RestApi
- * @extends WC_REST_Controller
- */
-class WC_REST_Products_Catalog_Controller extends WC_REST_Controller {
-
-	/**
-	 * Endpoint namespace.
-	 *
-	 * @var string
-	 */
-	protected $namespace = 'wc/v3';
-
-	/**
-	 * Route base.
-	 *
-	 * @var string
-	 */
-	protected $rest_base = 'products/catalog';
-
-	/**
-	 * Register the routes for products catalog.
-	 */
-	public function register_routes() {
-		register_rest_route(
-			$this->namespace,
-			'/' . $this->rest_base,
-			array(
-				array(
-					'methods'             => WP_REST_Server::CREATABLE,
-					'callback'            => array( $this, 'request_catalog' ),
-					'permission_callback' => array( $this, 'request_catalog_permissions_check' ),
-					'args'                => array(
-						'fields'         => array(
-							'description'       => __( 'Product/variation fields to include in the catalog. Can be an array or comma-separated string.', 'woocommerce' ),
-							'type'              => array( 'array', 'string' ),
-							'items'             => array( 'type' => 'string' ),
-							'required'          => true,
-							'validate_callback' => array( $this, 'validate_fields_arg' ),
-							'sanitize_callback' => array( $this, 'sanitize_fields_arg' ),
-						),
-						'force_generate' => array(
-							'description'       => __( 'Whether to generate a new catalog file regardless of whether a catalog file already exists.', 'woocommerce' ),
-							'type'              => 'boolean',
-							'default'           => false,
-							'sanitize_callback' => 'rest_sanitize_boolean',
-						),
-					),
-				),
-				'schema' => array( $this, 'catalog_schema' ),
-			)
-		);
-	}
-
-	/**
-	 * Request products catalog.
-	 *
-	 * @param WP_REST_Request $request Request data.
-	 * @return WP_Error|WP_REST_Response
-	 *
-	 * @internal For exclusive usage within this class, backwards compatibility not guaranteed.
-	 */
-	public function request_catalog( $request ) {
-		$fields         = $this->sanitize_fields_arg( $request->get_param( 'fields' ) ?? array() );
-		$force_generate = $request->get_param( 'force_generate' ) ?? false;
-		$file_info      = $this->get_catalog_file_info( $fields );
-
-		if ( is_wp_error( $file_info ) ) {
-			return $file_info;
-		}
-
-		// Check if file exists and force_generate is false.
-		if ( ! $force_generate && file_exists( $file_info['filepath'] ) ) {
-			$response_data = array(
-				'status'       => 'complete',
-				'download_url' => $file_info['url'],
-			);
-			return rest_ensure_response( $response_data );
-		}
-
-		// Generate catalog and return response.
-		return $this->catalog_generation_response( $file_info );
-	}
-
-	/**
-	 * Checks if a given request has permission to request products catalog.
-	 *
-	 * @param WP_REST_Request $request Full details about the request.
-	 * @return WP_Error|bool
-	 *
-	 * @internal For exclusive usage within this class, backwards compatibility not guaranteed.
-	 */
-	public function request_catalog_permissions_check( $request ) {
-		if ( ! ( wc_rest_check_post_permissions( 'product', 'read' ) && wc_rest_check_post_permissions( 'product_variation', 'read' ) ) ) {
-			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
-		}
-		return true;
-	}
-
-	/**
-	 * Validate fields argument.
-	 *
-	 * @param mixed $value The value to validate.
-	 * @return true|WP_Error True if valid, WP_Error otherwise.
-	 *
-	 * @internal For exclusive usage within this class, backwards compatibility not guaranteed.
-	 */
-	public function validate_fields_arg( $value ) {
-		if ( ! is_array( $value ) && ! is_string( $value ) ) {
-			return new WP_Error( 'invalid_fields', __( 'fields must be an array of strings or a comma-separated string.', 'woocommerce' ) );
-		}
-
-		if ( ( is_array( $value ) && empty( $value ) ) || ( is_string( $value ) && '' === trim( $value ) ) ) {
-			return new WP_Error( 'invalid_fields', __( 'fields cannot be empty.', 'woocommerce' ) );
-		}
-
-		return true;
-	}
-
-	/**
-	 * Sanitize fields argument.
-	 *
-	 * @param mixed $value The value to sanitize. Can be an array or comma-separated string.
-	 * @return array Sanitized and canonicalized fields array.
-	 *
-	 * @internal For exclusive usage within this class, backwards compatibility not guaranteed.
-	 */
-	public function sanitize_fields_arg( $value ) {
-		if ( is_string( $value ) ) {
-			$value = array_map( 'trim', explode( ',', $value ) );
-		}
-		return $this->canonicalize_fields( is_array( $value ) ? $value : array() );
-	}
-
-	/**
-	 * Products catalog schema.
-	 *
-	 * @return array Products catalog schema data.
-	 *
-	 * @internal For exclusive usage within this class, backwards compatibility not guaranteed.
-	 */
-	public function catalog_schema() {
-		return array(
-			'$schema'    => 'http://json-schema.org/draft-04/schema#',
-			'title'      => 'products_catalog',
-			'type'       => 'object',
-			'properties' => array(
-				'status'       => array(
-					'description' => __( 'Products catalog generation status.', 'woocommerce' ),
-					'type'        => 'string',
-					'enum'        => array( 'pending', 'processing', 'complete', 'failed' ),
-				),
-				'download_url' => array(
-					'description' => __( 'Products catalog file URL. Null when catalog is not ready.', 'woocommerce' ),
-					'type'        => array( 'string', 'null' ),
-					'format'      => 'uri',
-				),
-			),
-			'required'   => array( 'status', 'download_url' ),
-		);
-	}
-
-	/**
-	 * Generate catalog and return REST response.
-	 *
-	 * This function orchestrates catalog generation and returns the appropriate response.
-	 * In the future, it will check if a generation based on the file_info is in progress.
-	 *
-	 * @param array $file_info File information with 'filepath', 'url', and 'directory' keys.
-	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error on failure.
-	 */
-	private function catalog_generation_response( $file_info ) {
-		// In the future, check if generation is in progress and return appropriate status.
-		// For now, generate synchronously.
-		$result = $this->generate_catalog_file( $file_info );
-		if ( is_wp_error( $result ) ) {
-			return $result;
-		}
-
-		return rest_ensure_response(
-			array(
-				'status'       => 'complete',
-				'download_url' => $file_info['url'],
-			)
-		);
-	}
-
-	/**
-	 * Generate catalog file and save it to the specified file path.
-	 *
-	 * @param array $file_info File information with 'filepath', 'url', and 'directory' keys.
-	 * @return true|WP_Error True on success, WP_Error on failure.
-	 */
-	private function generate_catalog_file( $file_info ) {
-		// Ensure directory exists and is not indexable.
-		try {
-			FilesystemUtil::mkdir_p_not_indexable( $file_info['directory'], true );
-		} catch ( \Exception $exception ) {
-			return new WP_Error( 'catalog_dir_creation_failed', $exception->getMessage(), array( 'status' => 500 ) );
-		}
-
-		// Generate empty catalog file.
-		$catalog_data = array();
-
-		// Write to file.
-		$json = wp_json_encode( $catalog_data );
-		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
-		$result = file_put_contents( $file_info['filepath'], $json, LOCK_EX );
-
-		if ( false === $result ) {
-			return new WP_Error( 'catalog_generation_failed', __( 'Failed to generate catalog file.', 'woocommerce' ), array( 'status' => 500 ) );
-		}
-
-		return true;
-	}
-
-	/**
-	 * Get catalog file information based on fields.
-	 *
-	 * @param array $fields Product/variation fields to include in the catalog.
-	 * @return array|WP_Error Array with 'filepath', 'url', and 'directory' keys, or WP_Error on failure.
-	 */
-	private function get_catalog_file_info( $fields ) {
-		$upload_dir = wp_upload_dir();
-
-		if ( ! empty( $upload_dir['error'] ) ) {
-			return new WP_Error( 'upload_dir_error', $upload_dir['error'], array( 'status' => 500 ) );
-		}
-
-		$catalog_dir = trailingslashit( $upload_dir['basedir'] ) . 'wc-catalog/';
-		$catalog_url = trailingslashit( $upload_dir['baseurl'] ) . 'wc-catalog/';
-
-		$today        = gmdate( 'Y-m-d' );
-		$catalog_hash = wp_hash( $today . wp_json_encode( $fields ) );
-		$filename     = "products-{$today}-{$catalog_hash}.json";
-
-		return array(
-			'filepath'  => $catalog_dir . $filename,
-			'url'       => $catalog_url . $filename,
-			'directory' => $catalog_dir,
-		);
-	}
-
-	/**
-	 * Canonicalize fields array for stable hashing.
-	 *
-	 * @param array $fields Product/variation fields.
-	 * @return array Canonicalized fields array.
-	 */
-	private function canonicalize_fields( array $fields ) {
-		$fields = array_values( array_unique( array_map( 'strval', $fields ) ) );
-		sort( $fields, SORT_STRING );
-		return $fields;
-	}
-}
diff --git a/plugins/woocommerce/includes/rest-api/Server.php b/plugins/woocommerce/includes/rest-api/Server.php
index 92f65ec8f0d..85ffd4ad8f5 100644
--- a/plugins/woocommerce/includes/rest-api/Server.php
+++ b/plugins/woocommerce/includes/rest-api/Server.php
@@ -170,7 +170,7 @@ class Server {
 	 * @return array
 	 */
 	protected function get_v3_controllers() {
-		$controllers = array(
+		return array(
 			'coupons'                  => 'WC_REST_Coupons_Controller',
 			'customer-downloads'       => 'WC_REST_Customer_Downloads_Controller',
 			'customers'                => 'WC_REST_Customers_Controller',
@@ -218,12 +218,6 @@ class Server {
 			'paypal-webhooks'          => 'WC_REST_Paypal_Webhooks_Controller',
 			'paypal-buttons'           => 'WC_REST_Paypal_Buttons_Controller',
 		);
-
-		if ( Features::is_enabled( 'products-catalog-api' ) ) {
-			$controllers['products-catalog'] = 'WC_REST_Products_Catalog_Controller';
-		}
-
-		return $controllers;
 	}

 	/**
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 2b07f1bd869..3f445a62f9a 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -30213,30 +30213,6 @@ parameters:
 			count: 1
 			path: includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php

-		-
-			message: '#^Method WC_REST_Products_Catalog_Controller\:\:register_routes\(\) has no return type specified\.$#'
-			identifier: missingType.return
-			count: 1
-			path: includes/rest-api/Controllers/Version3/class-wc-rest-products-catalog-controller.php
-
-		-
-			message: '#^Method WC_REST_Products_Catalog_Controller\:\:request_catalog\(\) has parameter \$request with generic class WP_REST_Request but does not specify its types\: T$#'
-			identifier: missingType.generics
-			count: 1
-			path: includes/rest-api/Controllers/Version3/class-wc-rest-products-catalog-controller.php
-
-		-
-			message: '#^Method WC_REST_Products_Catalog_Controller\:\:request_catalog_permissions_check\(\) has parameter \$request with generic class WP_REST_Request but does not specify its types\: T$#'
-			identifier: missingType.generics
-			count: 1
-			path: includes/rest-api/Controllers/Version3/class-wc-rest-products-catalog-controller.php
-
-		-
-			message: '#^PHPDoc tag @extends has invalid value \(WC_REST_Controller\)\: Unexpected token "\\n ", expected ''\<'' at offset 116 on line 5$#'
-			identifier: phpDoc.parseError
-			count: 1
-			path: includes/rest-api/Controllers/Version3/class-wc-rest-products-catalog-controller.php
-
 		-
 			message: '#^Access to offset ''cost_of_goods_sold'' on an unknown class Automattic\\WooCommerce\\Internal\\CostOfGoodsSold\\WP_Rest_Request\.$#'
 			identifier: class.notFound
diff --git a/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php b/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php
index 74cc7790a40..f8de3511d62 100644
--- a/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php
+++ b/plugins/woocommerce/src/Internal/Utilities/FilesystemUtil.php
@@ -77,10 +77,9 @@ class FilesystemUtil {
 	 * @since 9.3.0
 	 *
 	 * @param string $path Directory to create.
-	 * @param bool   $allow_file_access Whether to allow file access while preventing directory listing. Default false (deny all access).
 	 * @throws \Exception In case of error.
 	 */
-	public static function mkdir_p_not_indexable( string $path, bool $allow_file_access = false ): void {
+	public static function mkdir_p_not_indexable( string $path ): void {
 		$wp_fs = self::get_wp_filesystem();

 		if ( $wp_fs->is_dir( $path ) ) {
@@ -91,10 +90,8 @@ class FilesystemUtil {
 			throw new \Exception( esc_html( sprintf( 'Could not create directory: %s.', wp_basename( $path ) ) ) );
 		}

-		$htaccess_content = $allow_file_access ? 'Options -Indexes' : 'deny from all';
-
 		$files = array(
-			'.htaccess'  => $htaccess_content,
+			'.htaccess'  => 'deny from all',
 			'index.html' => '',
 		);