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' => '',
);