From d000aa46dd7532aedc7aeed3d4de235daf520ff0 Mon Sep 17 00:00:00 2001 From: Justin Shreve Date: Thu, 1 Jun 2017 09:45:31 -0700 Subject: [PATCH 1/2] Removes the 'visible' property from the variations endpoint and adds 'status'. See https://github.com/woocommerce/woocommerce/pull/15216 --- ...rest-dev-product-variations-controller.php | 282 +++++++++++++++++- tests/unit-tests/product-variations.php | 5 +- 2 files changed, 282 insertions(+), 5 deletions(-) diff --git a/api/class-wc-rest-dev-product-variations-controller.php b/api/class-wc-rest-dev-product-variations-controller.php index 0372393..853aea3 100644 --- a/api/class-wc-rest-dev-product-variations-controller.php +++ b/api/class-wc-rest-dev-product-variations-controller.php @@ -28,6 +28,279 @@ class WC_REST_Dev_Product_Variations_Controller extends WC_REST_Product_Variatio */ 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(), + 'in_stock' => $object->is_in_stock(), + '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' => current( $this->get_images( $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'] ) ) { + $image = $request['image']; + $variation = $this->set_product_images( $variation, array( $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['in_stock'] ) ) { + $variation->set_stock_status( true === $request['in_stock'] ? 'instock' : 'outofstock' ); + } + + 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() ); + $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. * @@ -152,10 +425,11 @@ public function get_item_schema() { 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'visible' => array( - 'description' => __( "Define if the attribute is visible on the \"Additional information\" tab in 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( diff --git a/tests/unit-tests/product-variations.php b/tests/unit-tests/product-variations.php index 6ffd7e3..d4b1efa 100644 --- a/tests/unit-tests/product-variations.php +++ b/tests/unit-tests/product-variations.php @@ -169,6 +169,7 @@ public function test_update_variation() { $this->assertEquals( 10, $variation['regular_price'] ); $this->assertEmpty( $variation['sale_price'] ); $this->assertEquals( 'small', $variation['attributes'][0]['option'] ); + $this->assertEquals( 'publish', $variation['status'] ); $request = new WP_REST_Request( 'PUT', '/wc/v3/products/' . $product->get_id() . '/variations/' . $variation_id ); $request->set_body_params( array( @@ -177,6 +178,7 @@ public function test_update_variation() { 'description' => 'O_O', 'image' => array( 'position' => 0, 'src' => 'https://cldup.com/Dr1Bczxq4q.png', 'alt' => 'test upload image' ), 'attributes' => array( array( 'name' => 'pa_size', 'option' => 'medium' ) ), + 'status' => 'private', ) ); $response = $this->server->dispatch( $request ); $variation = $response->get_data(); @@ -189,6 +191,7 @@ public function test_update_variation() { $this->assertEquals( 'medium', $variation['attributes'][0]['option'] ); $this->assertContains( 'Dr1Bczxq4q', $variation['image']['src'] ); $this->assertContains( 'test upload image', $variation['image']['alt'] ); + $this->assertEquals( 'private', $variation['status'] ); $product->delete( true ); } @@ -356,13 +359,13 @@ public function test_variation_schema() { $this->assertArrayHasKey( 'date_on_sale_from', $properties ); $this->assertArrayHasKey( 'date_on_sale_to', $properties ); $this->assertArrayHasKey( 'on_sale', $properties ); - $this->assertArrayHasKey( 'visible', $properties ); $this->assertArrayHasKey( 'purchasable', $properties ); $this->assertArrayHasKey( 'virtual', $properties ); $this->assertArrayHasKey( 'downloadable', $properties ); $this->assertArrayHasKey( 'downloads', $properties ); $this->assertArrayHasKey( 'download_limit', $properties ); $this->assertArrayHasKey( 'download_expiry', $properties ); + $this->assertArrayHasKey( 'status', $properties ); $this->assertArrayHasKey( 'tax_status', $properties ); $this->assertArrayHasKey( 'tax_class', $properties ); $this->assertArrayHasKey( 'manage_stock', $properties ); From c16f02c73357687612f3d6eb63dc31bd5edbf64c Mon Sep 17 00:00:00 2001 From: Justin Shreve Date: Thu, 1 Jun 2017 10:07:56 -0700 Subject: [PATCH 2/2] Don't return parent image when no variation image is set. --- ...wc-rest-dev-product-variations-controller.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/api/class-wc-rest-dev-product-variations-controller.php b/api/class-wc-rest-dev-product-variations-controller.php index 853aea3..92cc71b 100644 --- a/api/class-wc-rest-dev-product-variations-controller.php +++ b/api/class-wc-rest-dev-product-variations-controller.php @@ -76,7 +76,7 @@ public function prepare_object_for_response( $object, $request ) { ), '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(), @@ -307,20 +307,24 @@ protected function prepare_object_for_database( $request, $creating = false ) { * @param WC_Product_Variation $variation Variation * @return array */ - protected function get_images( $variation ) { + protected function get_image( $variation ) { + if ( ! has_post_thumbnail( $variation->get_id() ) ) { + return; + } + $attachment_id = $variation->get_image_id(); $attachment_post = get_post( $attachment_id ); if ( is_null( $attachment_post ) ) { - $image = array(); + return; } $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); if ( ! is_array( $attachment ) ) { - $image = array(); + return; } if ( ! isset ( $image ) ) { - $image = array( + 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 ) ), @@ -332,7 +336,7 @@ protected function get_images( $variation ) { ); } - return array( $image ); + return; } /**