diff --git a/src/RestApi/Version4/class-wc-admin-rest-leaderboards-controller.php b/src/RestApi/Version4/Controllers/Leaderboards.php similarity index 98% rename from src/RestApi/Version4/class-wc-admin-rest-leaderboards-controller.php rename to src/RestApi/Version4/Controllers/Leaderboards.php index ee05e836193..6e22b5ddbf5 100644 --- a/src/RestApi/Version4/class-wc-admin-rest-leaderboards-controller.php +++ b/src/RestApi/Version4/Controllers/Leaderboards.php @@ -4,24 +4,17 @@ * * Handles requests to /leaderboards * - * @package WooCommerce Admin/API + * @package WooCommerce/RestApi */ +namespace WooCommerce\RestApi\Version4\Controllers; + defined( 'ABSPATH' ) || exit; /** - * Leaderboards controller. - * - * @package WooCommerce Admin/API - * @extends WC_REST_Data_Controller + * REST API Leaderboards class. */ -class WC_Admin_REST_Leaderboards_Controller extends WC_REST_Data_Controller { - /** - * Endpoint namespace. - * - * @var string - */ - protected $namespace = 'wc/v4'; +class Leaderboards extends AbstractController { /** * Route base. diff --git a/src/RestApi/Version4/class-wc-rest-product-variations-v2-controller.php b/src/RestApi/Version4/Controllers/ProductVariations.php similarity index 77% rename from src/RestApi/Version4/class-wc-rest-product-variations-v2-controller.php rename to src/RestApi/Version4/Controllers/ProductVariations.php index d1fdb90e72f..5df9f81eb1a 100644 --- a/src/RestApi/Version4/class-wc-rest-product-variations-v2-controller.php +++ b/src/RestApi/Version4/Controllers/ProductVariations.php @@ -5,25 +5,16 @@ * Handles requests to the /products//variations endpoints. * * @package WooCommerce/RestApi - * @since 3.0.0 */ +namespace WooCommerce\RestApi\Version4\Controllers; + defined( 'ABSPATH' ) || exit; /** * REST API variations controller class. - * - * @package WooCommerce/RestApi - * @extends WC_REST_Products_V2_Controller */ -class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Controller { - - /** - * Endpoint namespace. - * - * @var string - */ - protected $namespace = 'wc/v2'; +class ProductVariations extends Products { /** * Route base. @@ -52,7 +43,9 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr */ public function register_routes() { register_rest_route( - $this->namespace, '/' . $this->rest_base, array( + $this->namespace, + '/' . $this->rest_base, + array( 'args' => array( 'product_id' => array( 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ), @@ -75,7 +68,9 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr ) ); register_rest_route( - $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)', array( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( 'args' => array( 'product_id' => array( 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ), @@ -120,7 +115,8 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr ) ); register_rest_route( - $this->namespace, '/' . $this->rest_base . '/batch', array( + $this->namespace, '/' . $this->rest_base . '/batch', + array( 'args' => array( 'product_id' => array( 'description' => __( 'Unique identifier for the variable product.', 'woocommerce' ), @@ -173,7 +169,6 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr /** * Prepare a single variation output for response. * - * @since 3.0.0 * @param WC_Data $object Object data. * @param WP_REST_Request $request Request object. * @return WP_REST_Response @@ -181,6 +176,9 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr public function prepare_object_for_response( $object, $request ) { $data = array( 'id' => $object->get_id(), + 'name' => $object->get_name( $context ), + 'type' => $object->get_type(), + 'parent_id' => $object->get_parent_id( $context ), 'date_created' => wc_rest_prepare_date_response( $object->get_date_created(), false ), 'date_created_gmt' => wc_rest_prepare_date_response( $object->get_date_created() ), 'date_modified' => wc_rest_prepare_date_response( $object->get_date_modified(), false ), @@ -196,7 +194,7 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr 'date_on_sale_to' => wc_rest_prepare_date_response( $object->get_date_on_sale_to(), false ), 'date_on_sale_to_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_to() ), 'on_sale' => $object->is_on_sale(), - 'visible' => $object->is_visible(), + 'status' => $object->get_status(), 'purchasable' => $object->is_purchasable(), 'virtual' => $object->is_virtual(), 'downloadable' => $object->is_downloadable(), @@ -207,7 +205,7 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr 'tax_class' => $object->get_tax_class(), 'manage_stock' => $object->managing_stock(), 'stock_quantity' => $object->get_stock_quantity(), - 'in_stock' => $object->is_in_stock(), + 'stock_status' => $object->get_stock_status(), 'backorders' => $object->get_backorders(), 'backorders_allowed' => $object->backorders_allowed(), 'backordered' => $object->is_on_backorder(), @@ -219,7 +217,7 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr ), 'shipping_class' => $object->get_shipping_class(), 'shipping_class_id' => $object->get_shipping_class_id(), - 'image' => current( $this->get_images( $object ) ), + 'image' => $this->get_image( $object ), 'attributes' => $this->get_attributes( $object ), 'menu_order' => $object->get_menu_order(), 'meta_data' => $object->get_meta_data(), @@ -244,6 +242,90 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request ); } + /** + * Get the image for a product variation. + * + * @param WC_Product_Variation $variation Variation data. + * @return array + */ + protected function get_image( $variation ) { + if ( ! $variation->get_image_id() ) { + return; + } + + $attachment_id = $variation->get_image_id(); + $attachment_post = get_post( $attachment_id ); + if ( is_null( $attachment_post ) ) { + return; + } + + $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); + if ( ! is_array( $attachment ) ) { + return; + } + + if ( ! isset( $image ) ) { + return array( + 'id' => (int) $attachment_id, + 'date_created' => wc_rest_prepare_date_response( $attachment_post->post_date, false ), + 'date_created_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_date_gmt ) ), + 'date_modified' => wc_rest_prepare_date_response( $attachment_post->post_modified, false ), + 'date_modified_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_modified_gmt ) ), + 'src' => current( $attachment ), + 'name' => get_the_title( $attachment_id ), + 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ), + ); + } + } + + /** + * Set variation image. + * + * @throws WC_REST_Exception REST API exceptions. + * @param WC_Product_Variation $variation Variation instance. + * @param array $image Image data. + * @return WC_Product_Variation + */ + protected function set_variation_image( $variation, $image ) { + $attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0; + + if ( 0 === $attachment_id && isset( $image['src'] ) ) { + $upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) ); + + if ( is_wp_error( $upload ) ) { + if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) { + throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 ); + } + } + + $attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $variation->get_id() ); + } + + if ( ! wp_attachment_is_image( $attachment_id ) ) { + /* translators: %s: attachment ID */ + throw new WC_REST_Exception( 'woocommerce_variation_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 ); + } + + $variation->set_image_id( $attachment_id ); + + // Set the image alt if present. + if ( ! empty( $image['alt'] ) ) { + update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) ); + } + + // Set the image name if present. + if ( ! empty( $image['name'] ) ) { + wp_update_post( + array( + 'ID' => $attachment_id, + 'post_title' => $image['name'], + ) + ); + } + + return $variation; + } + /** * Prepare objects query. * @@ -254,8 +336,79 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr protected function prepare_objects_query( $request ) { $args = parent::prepare_objects_query( $request ); + // Set post_status. + $args['post_status'] = $request['status']; + + // Filter by sku. + if ( ! empty( $request['sku'] ) ) { + $skus = explode( ',', $request['sku'] ); + // Include the current string as a SKU too. + if ( 1 < count( $skus ) ) { + $skus[] = $request['sku']; + } + + $args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok. + $args, + array( + 'key' => '_sku', + 'value' => $skus, + 'compare' => 'IN', + ) + ); + } + + // Filter by tax class. + if ( ! empty( $request['tax_class'] ) ) { + $args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok. + $args, + array( + 'key' => '_tax_class', + 'value' => 'standard' !== $request['tax_class'] ? $request['tax_class'] : '', + ) + ); + } + + // Price filter. + if ( ! empty( $request['min_price'] ) || ! empty( $request['max_price'] ) ) { + $args['meta_query'] = $this->add_meta_query( $args, wc_get_min_max_price_meta_query( $request ) ); // WPCS: slow query ok. + } + + // Filter product based on stock_status. + if ( ! empty( $request['stock_status'] ) ) { + $args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok. + $args, + array( + 'key' => '_stock_status', + 'value' => $request['stock_status'], + ) + ); + } + + // Filter by on sale products. + if ( is_bool( $request['on_sale'] ) ) { + $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in'; + $on_sale_ids = wc_get_product_ids_on_sale(); + + // Use 0 when there's no on sale products to avoid return all products. + $on_sale_ids = empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids; + + $args[ $on_sale_key ] += $on_sale_ids; + } + + // Force the post_type argument, since it's not a user input variable. + if ( ! empty( $request['sku'] ) ) { + $args['post_type'] = array( 'product', 'product_variation' ); + } else { + $args['post_type'] = $this->post_type; + } + $args['post_parent'] = $request['product_id']; + if ( ! empty( $request['search'] ) ) { + $args['search'] = $request['search']; + unset( $args['s'] ); + } + return $args; } @@ -273,14 +426,11 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr $variation = new WC_Product_Variation(); } - // Update parent ID just once. - if ( 0 === $variation->get_parent_id() ) { - $variation->set_parent_id( absint( $request['product_id'] ) ); - } + $variation->set_parent_id( absint( $request['product_id'] ) ); // Status. - if ( isset( $request['visible'] ) ) { - $variation->set_status( false === $request['visible'] ? 'private' : 'publish' ); + if ( isset( $request['status'] ) ) { + $variation->set_status( get_post_status_object( $request['status'] ) ? $request['status'] : 'draft' ); } // SKU. @@ -290,13 +440,8 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr // Thumbnail. if ( isset( $request['image'] ) ) { - if ( is_array( $request['image'] ) && ! empty( $request['image'] ) ) { - $image = $request['image']; - if ( is_array( $image ) ) { - $image['position'] = 0; - } - - $variation = $this->set_product_images( $variation, array( $image ) ); + if ( is_array( $request['image'] ) ) { + $variation = $this->set_variation_image( $variation, $request['image'] ); } else { $variation->set_image_id( '' ); } @@ -335,15 +480,11 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr // Stock handling. if ( isset( $request['manage_stock'] ) ) { - if ( 'parent' === $request['manage_stock'] ) { - $variation->set_manage_stock( false ); // This just indicates the variation does not manage stock, but the parent does. - } else { - $variation->set_manage_stock( wc_string_to_bool( $request['manage_stock'] ) ); - } + $variation->set_manage_stock( $request['manage_stock'] ); } - if ( isset( $request['in_stock'] ) ) { - $variation->set_stock_status( true === $request['in_stock'] ? 'instock' : 'outofstock' ); + if ( isset( $request['stock_status'] ) ) { + $variation->set_stock_status( $request['stock_status'] ); } if ( isset( $request['backorders'] ) ) { @@ -401,8 +542,18 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr // Update taxonomies. if ( isset( $request['attributes'] ) ) { - $attributes = array(); - $parent = wc_get_product( $variation->get_parent_id() ); + $attributes = array(); + $parent = wc_get_product( $variation->get_parent_id() ); + + if ( ! $parent ) { + return new WP_Error( + // Translators: %d parent ID. + "woocommerce_rest_{$this->post_type}_invalid_parent", sprintf( __( 'Cannot set attributes due to invalid parent product.', 'woocommerce' ), $variation->get_parent_id() ), array( + 'status' => 404, + ) + ); + } + $parent_attributes = $parent->get_attributes(); foreach ( $request['attributes'] as $attribute ) { @@ -411,18 +562,16 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr // Check ID for global attributes or name for product attributes. if ( ! empty( $attribute['id'] ) ) { - $attribute_id = absint( $attribute['id'] ); - $raw_attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); + $attribute_id = absint( $attribute['id'] ); + $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); } elseif ( ! empty( $attribute['name'] ) ) { - $raw_attribute_name = sanitize_title( $attribute['name'] ); + $attribute_name = sanitize_title( $attribute['name'] ); } - if ( ! $attribute_id && ! $raw_attribute_name ) { + if ( ! $attribute_id && ! $attribute_name ) { continue; } - $attribute_name = sanitize_title( $raw_attribute_name ); - if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) { continue; } @@ -432,7 +581,7 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) { // If dealing with a taxonomy, we need to get the slug from the name posted to the API. - $term = get_term_by( 'name', $attribute_value, $raw_attribute_name ); // @codingStandardsIgnoreLine + $term = get_term_by( 'name', $attribute_value, $attribute_name ); if ( $term && ! is_wp_error( $term ) ) { $attribute_value = $term->slug; @@ -659,6 +808,23 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'name' => array( + 'description' => __( 'Product parent name.', 'woocommerce' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'type' => array( + 'description' => __( 'Product type.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'variation', + 'enum' => array( 'variation' ), + 'context' => array( 'view', 'edit' ), + ), + 'parent_id' => array( + 'description' => __( 'Product parent ID.', 'woocommerce' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), 'date_created' => array( 'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ), 'type' => 'date-time', @@ -720,7 +886,7 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr 'context' => array( 'view', 'edit' ), ), 'date_on_sale_to_gmt' => array( - 'description' => __( 'End date of sale price, as GMT.', 'woocommerce' ), + 'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ), 'type' => 'date-time', 'context' => array( 'view', 'edit' ), ), @@ -730,10 +896,11 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'visible' => array( - 'description' => __( "Define if the variation is visible on the product's page.", 'woocommerce' ), - 'type' => 'boolean', - 'default' => true, + 'status' => array( + 'description' => __( 'Variation status.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'publish', + 'enum' => array_keys( get_post_statuses() ), 'context' => array( 'view', 'edit' ), ), 'purchasable' => array( @@ -805,7 +972,7 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr ), 'manage_stock' => array( 'description' => __( 'Stock management at variation level.', 'woocommerce' ), - 'type' => 'mixed', + 'type' => 'boolean', 'default' => false, 'context' => array( 'view', 'edit' ), ), @@ -814,10 +981,11 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), - 'in_stock' => array( - 'description' => __( 'Controls whether or not the variation is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ), - 'type' => 'boolean', - 'default' => true, + 'stock_status' => array( + 'description' => __( 'Controls the stock status of the product.', 'woocommerce' ), + 'type' => 'string', + 'default' => 'instock', + 'enum' => array_keys( wc_get_product_stock_status_options() ), 'context' => array( 'view', 'edit' ), ), 'backorders' => array( @@ -931,11 +1099,6 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'position' => array( - 'description' => __( 'Image position. 0 means that the image is featured.', 'woocommerce' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit' ), - ), ), ), 'attributes' => array( @@ -996,7 +1159,58 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr ), ), ); - return $this->add_additional_fields_schema( $schema ); } + + /** + * Get the query params for collections of attachments. + * + * @return array + */ + public function get_collection_params() { + $params = parent::get_collection_params(); + + unset( + $params['in_stock'], + $params['type'], + $params['featured'], + $params['category'], + $params['tag'], + $params['shipping_class'], + $params['attribute'], + $params['attribute_term'] + ); + + $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'] = array( + 'description' => __( 'Search by similar product name or sku.', 'woocommerce' ), + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + + return $params; + } + + /** + * Get a collection of posts and add the post title filter option to WP_Query. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response + */ + public function get_items( $request ) { + add_filter( 'posts_where', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_filter' ), 10, 2 ); + add_filter( 'posts_join', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_join' ), 10, 2 ); + add_filter( 'posts_groupby', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_group_by' ), 10, 2 ); + $response = parent::get_items( $request ); + remove_filter( 'posts_where', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_filter' ), 10 ); + remove_filter( 'posts_join', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_join' ), 10 ); + remove_filter( 'posts_groupby', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_group_by' ), 10 ); + return $response; + } } diff --git a/src/RestApi/Version4/class-wc-rest-setting-options-v2-controller.php b/src/RestApi/Version4/Controllers/SettingsOptions.php similarity index 92% rename from src/RestApi/Version4/class-wc-rest-setting-options-v2-controller.php rename to src/RestApi/Version4/Controllers/SettingsOptions.php index 7136459b925..6d3437b5e58 100644 --- a/src/RestApi/Version4/class-wc-rest-setting-options-v2-controller.php +++ b/src/RestApi/Version4/Controllers/SettingsOptions.php @@ -5,25 +5,16 @@ * Handles requests to the /settings/$group/$setting endpoints. * * @package WooCommerce/RestApi - * @since 3.0.0 */ +namespace WooCommerce\RestApi\Version4\Controllers; + defined( 'ABSPATH' ) || exit; /** * REST API Setting Options controller class. - * - * @package WooCommerce/RestApi - * @extends WC_REST_Controller */ -class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { - - /** - * WP REST API namespace/version. - * - * @var string - */ - protected $namespace = 'wc/v2'; +class SettingsOptions extends AbstractController { /** * Route base. @@ -39,7 +30,9 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { */ public function register_routes() { register_rest_route( - $this->namespace, '/' . $this->rest_base, array( + $this->namespace, + '/' . $this->rest_base, + array( 'args' => array( 'group' => array( 'description' => __( 'Settings group ID.', 'woocommerce' ), @@ -56,7 +49,9 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { ); register_rest_route( - $this->namespace, '/' . $this->rest_base . '/batch', array( + $this->namespace, + '/' . $this->rest_base . '/batch', + array( 'args' => array( 'group' => array( 'description' => __( 'Settings group ID.', 'woocommerce' ), @@ -74,7 +69,9 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { ); register_rest_route( - $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', array( + $this->namespace, + '/' . $this->rest_base . '/(?P[\w-]+)', + array( 'args' => array( 'group' => array( 'description' => __( 'Settings group ID.', 'woocommerce' ), @@ -150,7 +147,6 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { /** * Get all settings in a group. * - * @since 3.0.0 * @param string $group_id Group ID. * @return array|WP_Error */ @@ -159,7 +155,7 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) ); } - $settings = apply_filters( 'woocommerce_settings-' . $group_id, array() ); + $settings = apply_filters( 'woocommerce_settings-' . $group_id, array() ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores if ( empty( $settings ) ) { return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) ); @@ -185,6 +181,20 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { } elseif ( 'single_select_country' === $setting['type'] ) { $setting['type'] = 'select'; $setting['options'] = $this->get_countries_and_states(); + } elseif ( 'single_select_page' === $setting['type'] ) { + $pages = get_pages( + array( + 'sort_column' => 'menu_order', + 'sort_order' => 'ASC', + 'hierarchical' => 0, + ) + ); + $options = array(); + foreach ( $pages as $page ) { + $options[ $page->ID ] = ! empty( $page->post_title ) ? $page->post_title : '#' . $page->ID; + } + $setting['type'] = 'select'; + $setting['options'] = $options; } $filtered_settings[] = $setting; @@ -204,11 +214,10 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { if ( ! $countries ) { return array(); } - $output = array(); - foreach ( $countries as $key => $value ) { $states = WC()->countries->get_states( $key ); + if ( $states ) { foreach ( $states as $state_key => $state_value ) { $output[ $key . ':' . $state_key ] = $value . ' - ' . $state_value; @@ -217,7 +226,6 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { $output[ $key ] = $value; } } - return $output; } @@ -252,6 +260,12 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) ); } + if ( is_wp_error( $setting ) ) { + return $setting; + } + + $setting['group_id'] = $group_id; + return $setting; } @@ -442,7 +456,6 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { /** * Callback for allowed keys for each setting response. * - * @since 3.0.0 * @param string $key Key to check. * @return boolean */ @@ -450,6 +463,7 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { return in_array( $key, array( 'id', + 'group_id', 'label', 'description', 'default', @@ -459,7 +473,7 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { 'options', 'value', 'option_key', - ) + ), true ); } @@ -492,7 +506,6 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { /** * Get the settings schema, conforming to JSON Schema. * - * @since 3.0.0 * @return array */ public function get_item_schema() { @@ -510,6 +523,15 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'group_id' => array( + 'description' => __( 'An identifier for the group this setting belongs to.', 'woocommerce' ), + 'type' => 'string', + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_title', + ), + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), 'label' => array( 'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ), 'type' => 'string', @@ -564,7 +586,7 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller { 'sanitize_callback' => 'sanitize_text_field', ), 'context' => array( 'view', 'edit' ), - 'enum' => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox', 'thumbnail_cropping' ), + 'enum' => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox' ), 'readonly' => true, ), 'options' => array( diff --git a/src/RestApi/Version4/changelog.md b/src/RestApi/Version4/changelog.md index 4db377e409b..070667c9c7c 100644 --- a/src/RestApi/Version4/changelog.md +++ b/src/RestApi/Version4/changelog.md @@ -7,6 +7,8 @@ - Orders - Added order number to schema. - Product Reviews - Updated response links. - Products - Added `low_in_stock` and `search` parameter. +- Product Variations - Added `search` parameter. +- Product Variations - Added `name`, `type`, `parent_id` to schema. - Reports - Updated with updated list of available reports. - Taxes - Added `code` and `include` params. @@ -32,6 +34,7 @@ - `reports/downloads/stats` - `reports/import` - `data/download-ips` +- `leaderboards` ## Removed endpoints diff --git a/src/RestApi/Version4/class-wc-admin-rest-product-variations-controller.php b/src/RestApi/Version4/class-wc-admin-rest-product-variations-controller.php deleted file mode 100644 index 1e96ce78c99..00000000000 --- a/src/RestApi/Version4/class-wc-admin-rest-product-variations-controller.php +++ /dev/null @@ -1,124 +0,0 @@ - __( 'Search by similar product name or sku.', 'woocommerce' ), - 'type' => 'string', - 'validate_callback' => 'rest_validate_request_arg', - ); - return $params; - } - - /** - * Add product name and sku filtering to the WC API. - * - * @param WP_REST_Request $request Request data. - * @return array - */ - protected function prepare_objects_query( $request ) { - $args = parent::prepare_objects_query( $request ); - - if ( ! empty( $request['search'] ) ) { - $args['search'] = $request['search']; - unset( $args['s'] ); - } - - return $args; - } - - /** - * Get a collection of posts and add the post title filter option to WP_Query. - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|WP_REST_Response - */ - public function get_items( $request ) { - add_filter( 'posts_where', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_filter' ), 10, 2 ); - add_filter( 'posts_join', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_join' ), 10, 2 ); - add_filter( 'posts_groupby', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_group_by' ), 10, 2 ); - $response = parent::get_items( $request ); - remove_filter( 'posts_where', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_filter' ), 10 ); - remove_filter( 'posts_join', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_join' ), 10 ); - remove_filter( 'posts_groupby', array( 'WC_Admin_REST_Products_Controller', 'add_wp_query_group_by' ), 10 ); - return $response; - } - - /** - * Get the Product's schema, conforming to JSON Schema. - * - * @return array - */ - public function get_item_schema() { - $schema = parent::get_item_schema(); - - $schema['properties']['name'] = array( - 'description' => __( 'Product parent name.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ); - $schema['properties']['type'] = array( - 'description' => __( 'Product type.', 'woocommerce' ), - 'type' => 'string', - 'default' => 'variation', - 'enum' => array( 'variation' ), - 'context' => array( 'view', 'edit' ), - ); - $schema['properties']['parent_id'] = array( - 'description' => __( 'Product parent ID.', 'woocommerce' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit' ), - ); - - return $schema; - } - - /** - * Prepare a single variation output for response. - * - * @param WC_Data $object Object data. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response - */ - public function prepare_object_for_response( $object, $request ) { - $context = empty( $request['context'] ) ? 'view' : $request['context']; - $response = parent::prepare_object_for_response( $object, $request ); - $data = $response->get_data(); - - $data['name'] = $object->get_name( $context ); - $data['type'] = $object->get_type(); - $data['parent_id'] = $object->get_parent_id( $context ); - - $response->set_data( $data ); - - return $response; - } -} diff --git a/src/RestApi/Version4/class-wc-rest-product-variations-controller.php b/src/RestApi/Version4/class-wc-rest-product-variations-controller.php deleted file mode 100644 index 5b69656b0ab..00000000000 --- a/src/RestApi/Version4/class-wc-rest-product-variations-controller.php +++ /dev/null @@ -1,860 +0,0 @@ -/variations endpoints. - * - * @package WooCommerce/RestApi - * @since 3.0.0 - */ - -defined( 'ABSPATH' ) || exit; - -/** - * REST API variations controller class. - * - * @package WooCommerce/RestApi - * @extends WC_REST_Product_Variations_V2_Controller - */ -class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V2_Controller { - - /** - * Endpoint namespace. - * - * @var string - */ - protected $namespace = 'wc/v3'; - - /** - * Prepare a single variation output for response. - * - * @param WC_Data $object Object data. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response - */ - public function prepare_object_for_response( $object, $request ) { - $data = array( - 'id' => $object->get_id(), - 'date_created' => wc_rest_prepare_date_response( $object->get_date_created(), false ), - 'date_created_gmt' => wc_rest_prepare_date_response( $object->get_date_created() ), - 'date_modified' => wc_rest_prepare_date_response( $object->get_date_modified(), false ), - 'date_modified_gmt' => wc_rest_prepare_date_response( $object->get_date_modified() ), - 'description' => wc_format_content( $object->get_description() ), - 'permalink' => $object->get_permalink(), - 'sku' => $object->get_sku(), - 'price' => $object->get_price(), - 'regular_price' => $object->get_regular_price(), - 'sale_price' => $object->get_sale_price(), - 'date_on_sale_from' => wc_rest_prepare_date_response( $object->get_date_on_sale_from(), false ), - 'date_on_sale_from_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_from() ), - 'date_on_sale_to' => wc_rest_prepare_date_response( $object->get_date_on_sale_to(), false ), - 'date_on_sale_to_gmt' => wc_rest_prepare_date_response( $object->get_date_on_sale_to() ), - 'on_sale' => $object->is_on_sale(), - 'status' => $object->get_status(), - 'purchasable' => $object->is_purchasable(), - 'virtual' => $object->is_virtual(), - 'downloadable' => $object->is_downloadable(), - 'downloads' => $this->get_downloads( $object ), - 'download_limit' => '' !== $object->get_download_limit() ? (int) $object->get_download_limit() : -1, - 'download_expiry' => '' !== $object->get_download_expiry() ? (int) $object->get_download_expiry() : -1, - 'tax_status' => $object->get_tax_status(), - 'tax_class' => $object->get_tax_class(), - 'manage_stock' => $object->managing_stock(), - 'stock_quantity' => $object->get_stock_quantity(), - 'stock_status' => $object->get_stock_status(), - 'backorders' => $object->get_backorders(), - 'backorders_allowed' => $object->backorders_allowed(), - 'backordered' => $object->is_on_backorder(), - 'weight' => $object->get_weight(), - 'dimensions' => array( - 'length' => $object->get_length(), - 'width' => $object->get_width(), - 'height' => $object->get_height(), - ), - 'shipping_class' => $object->get_shipping_class(), - 'shipping_class_id' => $object->get_shipping_class_id(), - 'image' => $this->get_image( $object ), - 'attributes' => $this->get_attributes( $object ), - 'menu_order' => $object->get_menu_order(), - 'meta_data' => $object->get_meta_data(), - ); - - $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; - $data = $this->add_additional_fields_to_object( $data, $request ); - $data = $this->filter_response_by_context( $data, $context ); - $response = rest_ensure_response( $data ); - $response->add_links( $this->prepare_links( $object, $request ) ); - - /** - * Filter the data for a response. - * - * The dynamic portion of the hook name, $this->post_type, - * refers to object type being prepared for the response. - * - * @param WP_REST_Response $response The response object. - * @param WC_Data $object Object data. - * @param WP_REST_Request $request Request object. - */ - return apply_filters( "woocommerce_rest_prepare_{$this->post_type}_object", $response, $object, $request ); - } - - /** - * Prepare a single variation for create or update. - * - * @param WP_REST_Request $request Request object. - * @param bool $creating If is creating a new object. - * @return WP_Error|WC_Data - */ - protected function prepare_object_for_database( $request, $creating = false ) { - if ( isset( $request['id'] ) ) { - $variation = wc_get_product( absint( $request['id'] ) ); - } else { - $variation = new WC_Product_Variation(); - } - - $variation->set_parent_id( absint( $request['product_id'] ) ); - - // Status. - if ( isset( $request['status'] ) ) { - $variation->set_status( get_post_status_object( $request['status'] ) ? $request['status'] : 'draft' ); - } - - // SKU. - if ( isset( $request['sku'] ) ) { - $variation->set_sku( wc_clean( $request['sku'] ) ); - } - - // Thumbnail. - if ( isset( $request['image'] ) ) { - if ( is_array( $request['image'] ) ) { - $variation = $this->set_variation_image( $variation, $request['image'] ); - } else { - $variation->set_image_id( '' ); - } - } - - // Virtual variation. - if ( isset( $request['virtual'] ) ) { - $variation->set_virtual( $request['virtual'] ); - } - - // Downloadable variation. - if ( isset( $request['downloadable'] ) ) { - $variation->set_downloadable( $request['downloadable'] ); - } - - // Downloads. - if ( $variation->get_downloadable() ) { - // Downloadable files. - if ( isset( $request['downloads'] ) && is_array( $request['downloads'] ) ) { - $variation = $this->save_downloadable_files( $variation, $request['downloads'] ); - } - - // Download limit. - if ( isset( $request['download_limit'] ) ) { - $variation->set_download_limit( $request['download_limit'] ); - } - - // Download expiry. - if ( isset( $request['download_expiry'] ) ) { - $variation->set_download_expiry( $request['download_expiry'] ); - } - } - - // Shipping data. - $variation = $this->save_product_shipping_data( $variation, $request ); - - // Stock handling. - if ( isset( $request['manage_stock'] ) ) { - $variation->set_manage_stock( $request['manage_stock'] ); - } - - if ( isset( $request['stock_status'] ) ) { - $variation->set_stock_status( $request['stock_status'] ); - } - - if ( isset( $request['backorders'] ) ) { - $variation->set_backorders( $request['backorders'] ); - } - - if ( $variation->get_manage_stock() ) { - if ( isset( $request['stock_quantity'] ) ) { - $variation->set_stock_quantity( $request['stock_quantity'] ); - } elseif ( isset( $request['inventory_delta'] ) ) { - $stock_quantity = wc_stock_amount( $variation->get_stock_quantity() ); - $stock_quantity += wc_stock_amount( $request['inventory_delta'] ); - $variation->set_stock_quantity( $stock_quantity ); - } - } else { - $variation->set_backorders( 'no' ); - $variation->set_stock_quantity( '' ); - } - - // Regular Price. - if ( isset( $request['regular_price'] ) ) { - $variation->set_regular_price( $request['regular_price'] ); - } - - // Sale Price. - if ( isset( $request['sale_price'] ) ) { - $variation->set_sale_price( $request['sale_price'] ); - } - - if ( isset( $request['date_on_sale_from'] ) ) { - $variation->set_date_on_sale_from( $request['date_on_sale_from'] ); - } - - if ( isset( $request['date_on_sale_from_gmt'] ) ) { - $variation->set_date_on_sale_from( $request['date_on_sale_from_gmt'] ? strtotime( $request['date_on_sale_from_gmt'] ) : null ); - } - - if ( isset( $request['date_on_sale_to'] ) ) { - $variation->set_date_on_sale_to( $request['date_on_sale_to'] ); - } - - if ( isset( $request['date_on_sale_to_gmt'] ) ) { - $variation->set_date_on_sale_to( $request['date_on_sale_to_gmt'] ? strtotime( $request['date_on_sale_to_gmt'] ) : null ); - } - - // Tax class. - if ( isset( $request['tax_class'] ) ) { - $variation->set_tax_class( $request['tax_class'] ); - } - - // Description. - if ( isset( $request['description'] ) ) { - $variation->set_description( wp_kses_post( $request['description'] ) ); - } - - // Update taxonomies. - if ( isset( $request['attributes'] ) ) { - $attributes = array(); - $parent = wc_get_product( $variation->get_parent_id() ); - - if ( ! $parent ) { - return new WP_Error( - // Translators: %d parent ID. - "woocommerce_rest_{$this->post_type}_invalid_parent", sprintf( __( 'Cannot set attributes due to invalid parent product.', 'woocommerce' ), $variation->get_parent_id() ), array( - 'status' => 404, - ) - ); - } - - $parent_attributes = $parent->get_attributes(); - - foreach ( $request['attributes'] as $attribute ) { - $attribute_id = 0; - $attribute_name = ''; - - // Check ID for global attributes or name for product attributes. - if ( ! empty( $attribute['id'] ) ) { - $attribute_id = absint( $attribute['id'] ); - $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); - } elseif ( ! empty( $attribute['name'] ) ) { - $attribute_name = sanitize_title( $attribute['name'] ); - } - - if ( ! $attribute_id && ! $attribute_name ) { - continue; - } - - if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) { - continue; - } - - $attribute_key = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() ); - $attribute_value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : ''; - - if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) { - // If dealing with a taxonomy, we need to get the slug from the name posted to the API. - $term = get_term_by( 'name', $attribute_value, $attribute_name ); - - if ( $term && ! is_wp_error( $term ) ) { - $attribute_value = $term->slug; - } else { - $attribute_value = sanitize_title( $attribute_value ); - } - } - - $attributes[ $attribute_key ] = $attribute_value; - } - - $variation->set_attributes( $attributes ); - } - - // Menu order. - if ( $request['menu_order'] ) { - $variation->set_menu_order( $request['menu_order'] ); - } - - // Meta data. - if ( is_array( $request['meta_data'] ) ) { - foreach ( $request['meta_data'] as $meta ) { - $variation->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' ); - } - } - - /** - * Filters an object before it is inserted via the REST API. - * - * The dynamic portion of the hook name, `$this->post_type`, - * refers to the object type slug. - * - * @param WC_Data $variation Object object. - * @param WP_REST_Request $request Request object. - * @param bool $creating If is creating a new object. - */ - return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}_object", $variation, $request, $creating ); - } - - /** - * Get the image for a product variation. - * - * @param WC_Product_Variation $variation Variation data. - * @return array - */ - protected function get_image( $variation ) { - if ( ! $variation->get_image_id() ) { - return; - } - - $attachment_id = $variation->get_image_id(); - $attachment_post = get_post( $attachment_id ); - if ( is_null( $attachment_post ) ) { - return; - } - - $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); - if ( ! is_array( $attachment ) ) { - return; - } - - if ( ! isset( $image ) ) { - return array( - 'id' => (int) $attachment_id, - 'date_created' => wc_rest_prepare_date_response( $attachment_post->post_date, false ), - 'date_created_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_date_gmt ) ), - 'date_modified' => wc_rest_prepare_date_response( $attachment_post->post_modified, false ), - 'date_modified_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_modified_gmt ) ), - 'src' => current( $attachment ), - 'name' => get_the_title( $attachment_id ), - 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ), - ); - } - } - - /** - * Set variation image. - * - * @throws WC_REST_Exception REST API exceptions. - * @param WC_Product_Variation $variation Variation instance. - * @param array $image Image data. - * @return WC_Product_Variation - */ - protected function set_variation_image( $variation, $image ) { - $attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0; - - if ( 0 === $attachment_id && isset( $image['src'] ) ) { - $upload = wc_rest_upload_image_from_url( esc_url_raw( $image['src'] ) ); - - if ( is_wp_error( $upload ) ) { - if ( ! apply_filters( 'woocommerce_rest_suppress_image_upload_error', false, $upload, $variation->get_id(), array( $image ) ) ) { - throw new WC_REST_Exception( 'woocommerce_variation_image_upload_error', $upload->get_error_message(), 400 ); - } - } - - $attachment_id = wc_rest_set_uploaded_image_as_attachment( $upload, $variation->get_id() ); - } - - if ( ! wp_attachment_is_image( $attachment_id ) ) { - /* translators: %s: attachment ID */ - throw new WC_REST_Exception( 'woocommerce_variation_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 ); - } - - $variation->set_image_id( $attachment_id ); - - // Set the image alt if present. - if ( ! empty( $image['alt'] ) ) { - update_post_meta( $attachment_id, '_wp_attachment_image_alt', wc_clean( $image['alt'] ) ); - } - - // Set the image name if present. - if ( ! empty( $image['name'] ) ) { - wp_update_post( - array( - 'ID' => $attachment_id, - 'post_title' => $image['name'], - ) - ); - } - - return $variation; - } - - /** - * Get the Variation's schema, conforming to JSON Schema. - * - * @return array - */ - public function get_item_schema() { - $weight_unit = get_option( 'woocommerce_weight_unit' ); - $dimension_unit = get_option( 'woocommerce_dimension_unit' ); - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => $this->post_type, - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'date_created' => array( - 'description' => __( "The date the variation was created, in the site's timezone.", 'woocommerce' ), - 'type' => 'date-time', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'date_modified' => array( - 'description' => __( "The date the variation was last modified, in the site's timezone.", 'woocommerce' ), - 'type' => 'date-time', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'description' => array( - 'description' => __( 'Variation description.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'permalink' => array( - 'description' => __( 'Variation URL.', 'woocommerce' ), - 'type' => 'string', - 'format' => 'uri', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'sku' => array( - 'description' => __( 'Unique identifier.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'price' => array( - 'description' => __( 'Current variation price.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'regular_price' => array( - 'description' => __( 'Variation regular price.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'sale_price' => array( - 'description' => __( 'Variation sale price.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'date_on_sale_from' => array( - 'description' => __( "Start date of sale price, in the site's timezone.", 'woocommerce' ), - 'type' => 'date-time', - 'context' => array( 'view', 'edit' ), - ), - 'date_on_sale_from_gmt' => array( - 'description' => __( 'Start date of sale price, as GMT.', 'woocommerce' ), - 'type' => 'date-time', - 'context' => array( 'view', 'edit' ), - ), - 'date_on_sale_to' => array( - 'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ), - 'type' => 'date-time', - 'context' => array( 'view', 'edit' ), - ), - 'date_on_sale_to_gmt' => array( - 'description' => __( "End date of sale price, in the site's timezone.", 'woocommerce' ), - 'type' => 'date-time', - 'context' => array( 'view', 'edit' ), - ), - 'on_sale' => array( - 'description' => __( 'Shows if the variation is on sale.', 'woocommerce' ), - 'type' => 'boolean', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'status' => array( - 'description' => __( 'Variation status.', 'woocommerce' ), - 'type' => 'string', - 'default' => 'publish', - 'enum' => array_keys( get_post_statuses() ), - 'context' => array( 'view', 'edit' ), - ), - 'purchasable' => array( - 'description' => __( 'Shows if the variation can be bought.', 'woocommerce' ), - 'type' => 'boolean', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'virtual' => array( - 'description' => __( 'If the variation is virtual.', 'woocommerce' ), - 'type' => 'boolean', - 'default' => false, - 'context' => array( 'view', 'edit' ), - ), - 'downloadable' => array( - 'description' => __( 'If the variation is downloadable.', 'woocommerce' ), - 'type' => 'boolean', - 'default' => false, - 'context' => array( 'view', 'edit' ), - ), - 'downloads' => array( - 'description' => __( 'List of downloadable files.', 'woocommerce' ), - 'type' => 'array', - 'context' => array( 'view', 'edit' ), - 'items' => array( - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'description' => __( 'File ID.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'name' => array( - 'description' => __( 'File name.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'file' => array( - 'description' => __( 'File URL.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - ), - ), - ), - 'download_limit' => array( - 'description' => __( 'Number of times downloadable files can be downloaded after purchase.', 'woocommerce' ), - 'type' => 'integer', - 'default' => -1, - 'context' => array( 'view', 'edit' ), - ), - 'download_expiry' => array( - 'description' => __( 'Number of days until access to downloadable files expires.', 'woocommerce' ), - 'type' => 'integer', - 'default' => -1, - 'context' => array( 'view', 'edit' ), - ), - 'tax_status' => array( - 'description' => __( 'Tax status.', 'woocommerce' ), - 'type' => 'string', - 'default' => 'taxable', - 'enum' => array( 'taxable', 'shipping', 'none' ), - 'context' => array( 'view', 'edit' ), - ), - 'tax_class' => array( - 'description' => __( 'Tax class.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'manage_stock' => array( - 'description' => __( 'Stock management at variation level.', 'woocommerce' ), - 'type' => 'boolean', - 'default' => false, - 'context' => array( 'view', 'edit' ), - ), - 'stock_quantity' => array( - 'description' => __( 'Stock quantity.', 'woocommerce' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit' ), - ), - 'stock_status' => array( - 'description' => __( 'Controls the stock status of the product.', 'woocommerce' ), - 'type' => 'string', - 'default' => 'instock', - 'enum' => array_keys( wc_get_product_stock_status_options() ), - 'context' => array( 'view', 'edit' ), - ), - 'backorders' => array( - 'description' => __( 'If managing stock, this controls if backorders are allowed.', 'woocommerce' ), - 'type' => 'string', - 'default' => 'no', - 'enum' => array( 'no', 'notify', 'yes' ), - 'context' => array( 'view', 'edit' ), - ), - 'backorders_allowed' => array( - 'description' => __( 'Shows if backorders are allowed.', 'woocommerce' ), - 'type' => 'boolean', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'backordered' => array( - 'description' => __( 'Shows if the variation is on backordered.', 'woocommerce' ), - 'type' => 'boolean', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'weight' => array( - /* translators: %s: weight unit */ - 'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'dimensions' => array( - 'description' => __( 'Variation dimensions.', 'woocommerce' ), - 'type' => 'object', - 'context' => array( 'view', 'edit' ), - 'properties' => array( - 'length' => array( - /* translators: %s: dimension unit */ - 'description' => sprintf( __( 'Variation length (%s).', 'woocommerce' ), $dimension_unit ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'width' => array( - /* translators: %s: dimension unit */ - 'description' => sprintf( __( 'Variation width (%s).', 'woocommerce' ), $dimension_unit ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'height' => array( - /* translators: %s: dimension unit */ - 'description' => sprintf( __( 'Variation height (%s).', 'woocommerce' ), $dimension_unit ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - ), - ), - '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, - ), - 'image' => array( - 'description' => __( 'Variation image data.', 'woocommerce' ), - 'type' => 'object', - 'context' => array( 'view', 'edit' ), - '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' ), - ), - ), - ), - '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' ), - ), - 'option' => array( - 'description' => __( 'Selected attribute term name.', 'woocommerce' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - ), - ), - ), - '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' ), - ), - ), - ), - ), - ), - ); - return $this->add_additional_fields_schema( $schema ); - } - - /** - * Prepare objects query. - * - * @since 3.0.0 - * @param WP_REST_Request $request Full details about the request. - * @return array - */ - protected function prepare_objects_query( $request ) { - $args = WC_REST_CRUD_Controller::prepare_objects_query( $request ); - - // Set post_status. - $args['post_status'] = $request['status']; - - // Filter by sku. - if ( ! empty( $request['sku'] ) ) { - $skus = explode( ',', $request['sku'] ); - // Include the current string as a SKU too. - if ( 1 < count( $skus ) ) { - $skus[] = $request['sku']; - } - - $args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok. - $args, - array( - 'key' => '_sku', - 'value' => $skus, - 'compare' => 'IN', - ) - ); - } - - // Filter by tax class. - if ( ! empty( $request['tax_class'] ) ) { - $args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok. - $args, - array( - 'key' => '_tax_class', - 'value' => 'standard' !== $request['tax_class'] ? $request['tax_class'] : '', - ) - ); - } - - // Price filter. - if ( ! empty( $request['min_price'] ) || ! empty( $request['max_price'] ) ) { - $args['meta_query'] = $this->add_meta_query( $args, wc_get_min_max_price_meta_query( $request ) ); // WPCS: slow query ok. - } - - // Filter product based on stock_status. - if ( ! empty( $request['stock_status'] ) ) { - $args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok. - $args, - array( - 'key' => '_stock_status', - 'value' => $request['stock_status'], - ) - ); - } - - // Filter by on sale products. - if ( is_bool( $request['on_sale'] ) ) { - $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in'; - $on_sale_ids = wc_get_product_ids_on_sale(); - - // Use 0 when there's no on sale products to avoid return all products. - $on_sale_ids = empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids; - - $args[ $on_sale_key ] += $on_sale_ids; - } - - // Force the post_type argument, since it's not a user input variable. - if ( ! empty( $request['sku'] ) ) { - $args['post_type'] = array( 'product', 'product_variation' ); - } else { - $args['post_type'] = $this->post_type; - } - - $args['post_parent'] = $request['product_id']; - - return $args; - } - - /** - * Get the query params for collections of attachments. - * - * @return array - */ - public function get_collection_params() { - $params = parent::get_collection_params(); - - unset( - $params['in_stock'], - $params['type'], - $params['featured'], - $params['category'], - $params['tag'], - $params['shipping_class'], - $params['attribute'], - $params['attribute_term'] - ); - - $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', - ); - - return $params; - } -} diff --git a/src/RestApi/Version4/class-wc-rest-setting-options-controller.php b/src/RestApi/Version4/class-wc-rest-setting-options-controller.php deleted file mode 100644 index 41ae7ae86f7..00000000000 --- a/src/RestApi/Version4/class-wc-rest-setting-options-controller.php +++ /dev/null @@ -1,250 +0,0 @@ - 404 ) ); - } - - $settings = apply_filters( 'woocommerce_settings-' . $group_id, array() ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - - if ( empty( $settings ) ) { - return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) ); - } - - $filtered_settings = array(); - foreach ( $settings as $setting ) { - $option_key = $setting['option_key']; - $setting = $this->filter_setting( $setting ); - $default = isset( $setting['default'] ) ? $setting['default'] : ''; - // Get the option value. - if ( is_array( $option_key ) ) { - $option = get_option( $option_key[0] ); - $setting['value'] = isset( $option[ $option_key[1] ] ) ? $option[ $option_key[1] ] : $default; - } else { - $admin_setting_value = WC_Admin_Settings::get_option( $option_key, $default ); - $setting['value'] = $admin_setting_value; - } - - if ( 'multi_select_countries' === $setting['type'] ) { - $setting['options'] = WC()->countries->get_countries(); - $setting['type'] = 'multiselect'; - } elseif ( 'single_select_country' === $setting['type'] ) { - $setting['type'] = 'select'; - $setting['options'] = $this->get_countries_and_states(); - } elseif ( 'single_select_page' === $setting['type'] ) { - $pages = get_pages( - array( - 'sort_column' => 'menu_order', - 'sort_order' => 'ASC', - 'hierarchical' => 0, - ) - ); - $options = array(); - foreach ( $pages as $page ) { - $options[ $page->ID ] = ! empty( $page->post_title ) ? $page->post_title : '#' . $page->ID; - } - $setting['type'] = 'select'; - $setting['options'] = $options; - } - - $filtered_settings[] = $setting; - } - - return $filtered_settings; - } - - /** - * Returns a list of countries and states for use in the base location setting. - * - * @since 3.0.7 - * @return array Array of states and countries. - */ - private function get_countries_and_states() { - $countries = WC()->countries->get_countries(); - if ( ! $countries ) { - return array(); - } - $output = array(); - foreach ( $countries as $key => $value ) { - $states = WC()->countries->get_states( $key ); - - if ( $states ) { - foreach ( $states as $state_key => $state_value ) { - $output[ $key . ':' . $state_key ] = $value . ' - ' . $state_value; - } - } else { - $output[ $key ] = $value; - } - } - return $output; - } - - /** - * Get the settings schema, conforming to JSON Schema. - * - * @return array - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'setting', - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'description' => __( 'A unique identifier for the setting.', 'woocommerce' ), - 'type' => 'string', - 'arg_options' => array( - 'sanitize_callback' => 'sanitize_title', - ), - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'group_id' => array( - 'description' => __( 'An identifier for the group this setting belongs to.', 'woocommerce' ), - 'type' => 'string', - 'arg_options' => array( - 'sanitize_callback' => 'sanitize_title', - ), - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'label' => array( - 'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce' ), - 'type' => 'string', - 'arg_options' => array( - 'sanitize_callback' => 'sanitize_text_field', - ), - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'description' => array( - 'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce' ), - 'type' => 'string', - 'arg_options' => array( - 'sanitize_callback' => 'sanitize_text_field', - ), - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'value' => array( - 'description' => __( 'Setting value.', 'woocommerce' ), - 'type' => 'mixed', - 'context' => array( 'view', 'edit' ), - ), - 'default' => array( - 'description' => __( 'Default value for the setting.', 'woocommerce' ), - 'type' => 'mixed', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'tip' => array( - 'description' => __( 'Additional help text shown to the user about the setting.', 'woocommerce' ), - 'type' => 'string', - 'arg_options' => array( - 'sanitize_callback' => 'sanitize_text_field', - ), - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'placeholder' => array( - 'description' => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce' ), - 'type' => 'string', - 'arg_options' => array( - 'sanitize_callback' => 'sanitize_text_field', - ), - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - 'type' => array( - 'description' => __( 'Type of setting.', 'woocommerce' ), - 'type' => 'string', - 'arg_options' => array( - 'sanitize_callback' => 'sanitize_text_field', - ), - 'context' => array( 'view', 'edit' ), - 'enum' => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox' ), - 'readonly' => true, - ), - 'options' => array( - 'description' => __( 'Array of options (key value pairs) for inputs such as select, multiselect, and radio buttons.', 'woocommerce' ), - 'type' => 'object', - 'context' => array( 'view', 'edit' ), - 'readonly' => true, - ), - ), - ); - - return $this->add_additional_fields_schema( $schema ); - } -}