( /* translators: %s: dimension unit */ 'description' => sprintf( __( 'Product length (%s).', 'woocommerce' ), $dimension_unit_label ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'width' => array( /* translators: %s: dimension unit */ 'description' => sprintf( __( 'Product width (%s).', 'woocommerce' ), $dimension_unit_label ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'height' => array( /* translators: %s: dimension unit */ 'description' => sprintf( __( 'Product height (%s).', 'woocommerce' ), $dimension_unit_label ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), ), ), 'shipping_required' => array( 'description' => __( 'Shows if the product need to be shipped.', 'woocommerce' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'shipping_taxable' => array( 'description' => __( 'Shows whether or not the product shipping is taxable.', 'woocommerce' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'shipping_class' => array( 'description' => __( 'Shipping class slug.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'shipping_class_id' => array( 'description' => __( 'Shipping class ID.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'reviews_allowed' => array( 'description' => __( 'Allow reviews.', 'woocommerce' ), 'type' => 'boolean', 'default' => true, 'context' => array( 'view', 'edit' ), ), 'post_password' => array( 'description' => __( 'Post password.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'average_rating' => array( 'description' => __( 'Reviews average rating.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'rating_count' => array( 'description' => __( 'Amount of reviews that the product have.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'related_ids' => array( 'description' => __( 'List of related products IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'upsell_ids' => array( 'description' => __( 'List of up-sell products IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'context' => array( 'view', 'edit' ), ), 'cross_sell_ids' => array( 'description' => __( 'List of cross-sell products IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'context' => array( 'view', 'edit' ), ), 'parent_id' => array( 'description' => __( 'Product parent ID.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'purchase_note' => array( 'description' => __( 'Optional note to send the customer after purchase.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'categories' => array( 'description' => __( 'List of categories.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Category ID.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'name' => array( 'description' => __( 'Category name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'Category slug.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ), ), 'brands' => array( 'description' => __( 'List of brands.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Brand ID.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'name' => array( 'description' => __( 'Brand name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'Brand slug.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ), ), 'tags' => array( 'description' => __( 'List of tags.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Tag ID.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'name' => array( 'description' => __( 'Tag name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'Tag slug.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ), ), ), 'images' => array( 'description' => __( 'List of images.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Image ID.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'date_created' => array( 'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ), 'type' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'date_created_gmt' => array( 'description' => __( 'The date the image was created, as GMT.', 'woocommerce' ), 'type' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'date_modified' => array( 'description' => __( "The date the image was last modified, in the site's timezone.", 'woocommerce' ), 'type' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'date_modified_gmt' => array( 'description' => __( 'The date the image was last modified, as GMT.', 'woocommerce' ), 'type' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'src' => array( 'description' => __( 'Image URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit' ), ), 'name' => array( 'description' => __( 'Image name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'alt' => array( 'description' => __( 'Image alternative text.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), ), ), ), 'has_options' => array( 'description' => __( 'Shows if the product needs to be configured before it can be bought.', 'woocommerce' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'attributes' => array( 'description' => __( 'List of attributes.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Attribute ID.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'name' => array( 'description' => __( 'Attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'position' => array( 'description' => __( 'Attribute position.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'visible' => array( 'description' => __( "Define if the attribute is visible on the \"Additional information\" tab in the product's page.", 'woocommerce' ), 'type' => 'boolean', 'default' => false, 'context' => array( 'view', 'edit' ), ), 'variation' => array( 'description' => __( 'Define if the attribute can be used as variation.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, 'context' => array( 'view', 'edit' ), ), 'options' => array( 'description' => __( 'List of available term names of the attribute.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', ), 'context' => array( 'view', 'edit' ), ), ), ), ), 'default_attributes' => array( 'description' => __( 'Defaults variation attributes.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Attribute ID.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'name' => array( 'description' => __( 'Attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'option' => array( 'description' => __( 'Selected attribute term name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), ), ), ), 'variations' => array( 'description' => __( 'List of variations IDs.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'integer', ), 'readonly' => true, ), 'grouped_products' => array( 'description' => __( 'List of grouped products ID.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'menu_order' => array( 'description' => __( 'Menu order, used to custom sort products.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), 'meta_data' => array( 'description' => __( 'Meta data.', 'woocommerce' ), 'type' => 'array', 'context' => array( 'view', 'edit' ), 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'description' => __( 'Meta ID.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'key' => array( 'description' => __( 'Meta key.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), 'value' => array( 'description' => __( 'Meta value.', 'woocommerce' ), 'type' => 'mixed', 'context' => array( 'view', 'edit' ), ), ), ), ), ), ); $post_type_obj = get_post_type_object( $this->post_type ); if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { $schema['properties']['permalink_template'] = array( 'description' => __( 'Permalink template for the product.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'edit' ), 'readonly' => true, ); $schema['properties']['generated_slug'] = array( 'description' => __( 'Slug automatically generated from the product name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'edit' ), 'readonly' => true, ); } if ( $this->cogs_is_enabled() ) { $schema = $this->add_cogs_related_product_schema( $schema, false ); } return $this->add_additional_fields_schema( $schema ); } /** * Add new options for 'orderby' to the collection params. * * @return array */ public function get_collection_params() { $params = parent::get_collection_params(); $params['orderby']['enum'] = array_merge( $params['orderby']['enum'], array( 'price', 'popularity', 'rating' ) ); unset( $params['in_stock'] ); $params['stock_status'] = array( 'description' => __( 'Limit result set to products with specified stock status.', 'woocommerce' ), 'type' => 'string', 'enum' => array_keys( wc_get_product_stock_status_options() ), 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['search_sku'] = array( 'description' => __( "Limit results to those with a SKU that partial matches a string. This argument takes precedence over 'sku'.", 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['search_name_or_sku'] = array( 'description' => __( "Limit results to those with a name or SKU that partial matches a string. This argument takes precedence over 'search', 'sku' and 'search_sku'.", 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $search_fields_enum = array( 'name', 'global_unique_id', 'description', 'short_description' ); if ( wc_product_sku_enabled() ) { $search_fields_enum[] = 'sku'; } $params['search_fields'] = array( 'description' => __( 'Limit search to specific fields when used with search parameter. Available fields: name, sku, global_unique_id, description, short_description. This argument takes precedence over all other search parameters.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => $search_fields_enum, ), 'default' => array(), 'sanitize_callback' => 'wp_parse_slug_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['include_status'] = array( 'description' => __( 'Limit result set to products with any of the statuses.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => array_merge( array( 'any', ProductStatus::FUTURE, ProductStatus::TRASH ), array_keys( get_post_statuses() ) ), ), 'sanitize_callback' => 'wp_parse_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['exclude_status'] = array( 'description' => __( 'Exclude products with any of the statuses from result set.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => array_merge( array( ProductStatus::FUTURE, ProductStatus::TRASH ), array_keys( get_post_statuses() ) ), ), 'sanitize_callback' => 'wp_parse_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['include_types'] = array( 'description' => __( 'Limit result set to products with any of the types.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => array_keys( wc_get_product_types() ), ), 'sanitize_callback' => 'wp_parse_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['exclude_types'] = array( 'description' => __( 'Exclude products with any of the types from result set.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => array_keys( wc_get_product_types() ), ), 'sanitize_callback' => 'wp_parse_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['downloadable'] = array( 'description' => __( 'Limit result set to downloadable products.', 'woocommerce' ), 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'validate_callback' => 'rest_validate_request_arg', ); $params['virtual'] = array( 'description' => __( 'Limit result set to virtual products.', 'woocommerce' ), 'type' => 'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'validate_callback' => 'rest_validate_request_arg', ); $params['pos_products_only'] = array( 'description' => __( 'Limit result set to products visible in Point of Sale.', 'woocommerce' ), 'type' => 'boolean', 'sanitize_callback' => 'wc_string_to_bool', 'validate_callback' => 'rest_validate_request_arg', ); return $params; } /** * Add new options for the suggested-products endpoint. * * @return array */ public function get_suggested_products_collection_params() { $params = parent::get_collection_params(); $params['categories'] = array( 'description' => __( 'Limit result set to specific product categorie ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['tags'] = array( 'description' => __( 'Limit result set to specific product tag ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'validate_callback' => 'rest_validate_request_arg', 'sanitize_callback' => 'wp_parse_id_list', ); $params['limit'] = array( 'description' => __( 'Limit result set to specific amount of suggested products.', 'woocommerce' ), 'type' => 'integer', 'default' => 5, 'validate_callback' => 'rest_validate_request_arg', 'sanitize_callback' => 'absint', ); return $params; } /** * Get the downloads for a product. * * @param WC_Product $product Product instance. * * @return array */ protected function get_downloads( $product ) { $downloads = array(); $context = isset( $this->request ) && isset( $this->request['context'] ) ? $this->request['context'] : 'view'; if ( $product->is_downloadable() || 'edit' === $context ) { foreach ( $product->get_downloads() as $file_id => $file ) { $downloads[] = array( 'id' => $file_id, // MD5 hash. 'name' => $file['name'], 'file' => $file['file'], ); } } return $downloads; } /** * Get product data. * * @param WC_Product $product Product instance. * @param string $context Request context. Options: 'view' and 'edit'. * * @return array */ protected function get_product_data( $product, $context = 'view' ) { $data = parent::get_product_data( ...func_get_args() ); if ( isset( $this->request ) ) { $fields = $this->get_fields_for_response( $this->request ); // Add stock_status if needed. if ( in_array( 'stock_status', $fields, true ) ) { $data['stock_status'] = $product->get_stock_status( $context ); } // Add has_options if needed. if ( in_array( 'has_options', $fields, true ) ) { $data['has_options'] = $product->has_options( $context ); } if ( in_array( 'post_password', $fields, true ) ) { $data['post_password'] = $product->get_post_password( $context ); } if ( in_array( 'global_unique_id', $fields, true ) ) { $data['global_unique_id'] = $product->get_global_unique_id( $context ); } $post_type_obj = get_post_type_object( $this->post_type ); if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { $permalink_template_requested = in_array( 'permalink_template', $fields, true ); $generated_slug_requested = in_array( 'generated_slug', $fields, true ); if ( $permalink_template_requested || $generated_slug_requested ) { if ( ! function_exists( 'get_sample_permalink' ) ) { require_once ABSPATH . 'wp-admin/includes/post.php'; } $sample_permalink = get_sample_permalink( $product->get_id(), $product->get_name(), '' ); // Add permalink_template if needed. if ( $permalink_template_requested ) { $data['permalink_template'] = $sample_permalink[0]; } // Add generated_slug if needed. if ( $generated_slug_requested ) { $data['generated_slug'] = $sample_permalink[1]; } } } } return $data; } /** * Get the suggested products. * * @param WP_REST_Request $request Request object. * @return object */ public function get_suggested_products( $request ) { $categories = $request->get_param( 'categories' ); $tags = $request->get_param( 'tags' ); $exclude_ids = $request->get_param( 'exclude' ); $limit = $request->get_param( 'limit' ) ? $request->get_param( 'limit' ) : 5; $data_store = WC_Data_Store::load( 'product' ); $this->suggested_products_ids = $data_store->get_related_products( $categories, $tags, $exclude_ids, $limit, null // No need to pass the product ID. ); // When no suggested products are found, return an empty array. if ( empty( $this->suggested_products_ids ) ) { return array(); } // Ensure to respect the limit, since the data store may return more than the limit. $this->suggested_products_ids = array_slice( $this->suggested_products_ids, 0, $limit ); return parent::get_items( $request ); } /** * Core function to prepare a single product output for response * (doesn't fire hooks, ensure_response, or add links). * * @param WC_Data $object_data Object data. * @param WP_REST_Request $request Request object. * @param string $context Request context. * @return array Product data to be included in the response. */ protected function prepare_object_for_response_core( $object_data, $request, $context ): array { $data = parent::prepare_object_for_response_core( $object_data, $request, $context ); if ( $this->cogs_is_enabled() ) { $this->add_cogs_info_to_returned_product_data( $data, $object_data ); } return $data; } /** * Create a single item. * Handles cleanup of orphaned images if product creation fails. * * @param WP_REST_Request $request Request object. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { $this->processed_attachment_ids_for_request = array(); $response = parent::create_item( $request ); if ( is_wp_error( $response ) ) { if ( ! empty( $this->processed_attachment_ids_for_request ) ) { // Handle deletion of orphaned images. foreach ( $this->processed_attachment_ids_for_request as $attachment_id ) { wp_delete_attachment( (int) $attachment_id, true ); } } } $this->processed_attachment_ids_for_request = array(); return $response; } }