diff --git a/packages/js/product-editor/changelog/poc-unique_id_field b/packages/js/product-editor/changelog/poc-unique_id_field new file mode 100644 index 000000000000..f73a1290bada --- /dev/null +++ b/packages/js/product-editor/changelog/poc-unique_id_field @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Handle unique id error message diff --git a/packages/js/product-editor/src/utils/get-product-error-message-and-props.ts b/packages/js/product-editor/src/utils/get-product-error-message-and-props.ts index c37d6023da15..6d86db931373 100644 --- a/packages/js/product-editor/src/utils/get-product-error-message-and-props.ts +++ b/packages/js/product-editor/src/utils/get-product-error-message-and-props.ts @@ -7,6 +7,7 @@ export type WPErrorCode = | 'variable_product_no_variation_prices' | 'product_form_field_error' | 'product_invalid_sku' + | 'product_invalid_global_unique_id' | 'product_create_error' | 'product_publish_error' | 'product_preview_error'; @@ -56,6 +57,15 @@ export function getProductErrorMessageAndProps( response.errorProps = { explicitDismiss: true }; } break; + case 'product_invalid_global_unique_id': + response.message = __( + 'Invalid or duplicated GTIN, UPC, EAN or ISBN.', + 'woocommerce' + ); + if ( visibleTab !== 'inventory' ) { + response.errorProps = { explicitDismiss: true }; + } + break; case 'product_create_error': response.message = __( 'Failed to create product.', 'woocommerce' ); break; diff --git a/plugins/woocommerce/changelog/add-unique-id-field b/plugins/woocommerce/changelog/add-unique-id-field new file mode 100644 index 000000000000..1ba5e6f7e005 --- /dev/null +++ b/plugins/woocommerce/changelog/add-unique-id-field @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Adds global_unique_id field to product and product variations diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php index 9200e884f71f..6e85be8b8398 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-product.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-product.php @@ -65,6 +65,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { 'description' => '', 'short_description' => '', 'sku' => '', + 'global_unique_id' => '', 'price' => '', 'regular_price' => '', 'sale_price' => '', @@ -251,7 +252,7 @@ public function get_short_description( $context = 'view' ) { } /** - * Get SKU (Stock-keeping unit) - product unique ID. + * Get SKU (Stock-keeping unit). * * @param string $context What the value is for. Valid values are view and edit. * @return string @@ -260,6 +261,17 @@ public function get_sku( $context = 'view' ) { return $this->get_prop( 'sku', $context ); } + /** + * Get Unique ID. + * + * @since 9.1.0 + * @param string $context What the value is for. Valid values are view and edit. + * @return string + */ + public function get_global_unique_id( $context = 'view' ) { + return $this->get_prop( 'global_unique_id', $context ); + } + /** * Returns the product's active price. * @@ -835,6 +847,29 @@ public function set_sku( $sku ) { $this->set_prop( 'sku', $sku ); } + /** + * Set global_unique_id + * + * @since 9.1.0 + * @param string $global_unique_id Unique ID. + */ + public function set_global_unique_id( $global_unique_id ) { + $global_unique_id = (string) $global_unique_id; + if ( $this->get_object_read() && ! empty( $global_unique_id ) && ! wc_product_has_global_unique_id( $this->get_id(), $global_unique_id ) ) { + $global_unique_id_found = wc_get_product_id_by_global_unique_id( $global_unique_id ); + + $this->error( + 'product_invalid_global_unique_id', + __( 'Invalid or duplicated Unique ID.', 'woocommerce' ), + 400, + array( + 'resource_id' => $global_unique_id_found, + ) + ); + } + $this->set_prop( 'global_unique_id', $global_unique_id ); + } + /** * Set the product's active price. * diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index 7a6a437c23b4..20ca22cd8a0e 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -1635,6 +1635,7 @@ private static function get_schema() { CREATE TABLE {$wpdb->prefix}wc_product_meta_lookup ( `product_id` bigint(20) NOT NULL, `sku` varchar(100) NULL default '', + `global_unique_id` varchar(100) NULL default '', `virtual` tinyint(1) NULL default 0, `downloadable` tinyint(1) NULL default 0, `min_price` decimal(19,4) NULL default NULL, diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php index eaa23276f9f9..7bdc8bd33687 100644 --- a/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/plugins/woocommerce/includes/data-stores/class-wc-product-data-store-cpt.php @@ -29,6 +29,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da protected $internal_meta_keys = array( '_visibility', '_sku', + '_global_unique_id', '_price', '_regular_price', '_sale_price', @@ -386,6 +387,7 @@ protected function read_product_data( &$product ) { $post_meta_values = get_post_meta( $id ); $meta_key_to_props = array( '_sku' => 'sku', + '_global_unique_id' => 'global_unique_id', '_regular_price' => 'regular_price', '_sale_price' => 'sale_price', '_price' => 'price', @@ -577,6 +579,7 @@ protected function read_downloads( &$product ) { protected function update_post_meta( &$product, $force = false ) { $meta_key_to_props = array( '_sku' => 'sku', + '_global_unique_id' => 'global_unique_id', '_regular_price' => 'regular_price', '_sale_price' => 'sale_price', '_sale_price_dates_from' => 'date_on_sale_from', @@ -743,7 +746,7 @@ protected function handle_updated_props( &$product ) { } } - if ( array_intersect( $this->updated_props, array( 'sku', 'regular_price', 'sale_price', 'date_on_sale_from', 'date_on_sale_to', 'total_sales', 'average_rating', 'stock_quantity', 'stock_status', 'manage_stock', 'downloadable', 'virtual', 'tax_status', 'tax_class' ) ) ) { + if ( array_intersect( $this->updated_props, array( 'sku', 'global_unique_id', 'regular_price', 'sale_price', 'date_on_sale_from', 'date_on_sale_to', 'total_sales', 'average_rating', 'stock_quantity', 'stock_status', 'manage_stock', 'downloadable', 'virtual', 'tax_status', 'tax_class' ) ) ) { $this->update_lookup_table( $product->get_id(), 'wc_product_meta_lookup' ); } @@ -1069,6 +1072,37 @@ public function is_existing_sku( $product_id, $sku ) { ); } + /** + * Check if product sku is found for any other product IDs. + * + * @since 9.1.0 + * @param int $product_id Product ID. + * @param string $global_unique_id Will be slashed to work around https://core.trac.wordpress.org/ticket/27421. + * @return bool + */ + public function is_existing_global_unique_id( $product_id, $global_unique_id ) { + global $wpdb; + + // phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery + return (bool) $wpdb->get_var( + $wpdb->prepare( + " + SELECT posts.ID + FROM {$wpdb->posts} as posts + INNER JOIN {$wpdb->wc_product_meta_lookup} AS lookup ON posts.ID = lookup.product_id + WHERE + posts.post_type IN ( 'product', 'product_variation' ) + AND posts.post_status != 'trash' + AND lookup.global_unique_id = %s + AND lookup.product_id <> %d + LIMIT 1 + ", + wp_slash( $global_unique_id ), + $product_id + ) + ); + } + /** * Return product ID based on SKU. * @@ -1099,6 +1133,42 @@ public function get_product_id_by_sku( $sku ) { return (int) apply_filters( 'woocommerce_get_product_id_by_sku', $id, $sku ); } + /** + * Return product ID based on Unique ID. + * + * @since 9.1.0 + * @param string $global_unique_id Product Unique ID. + * @return int + */ + public function get_product_id_by_global_unique_id( $global_unique_id ) { + global $wpdb; + + // phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery + $id = $wpdb->get_var( + $wpdb->prepare( + " + SELECT posts.ID + FROM {$wpdb->posts} as posts + INNER JOIN {$wpdb->wc_product_meta_lookup} AS lookup ON posts.ID = lookup.product_id + WHERE + posts.post_type IN ( 'product', 'product_variation' ) + AND posts.post_status != 'trash' + AND lookup.global_unique_id = %s + LIMIT 1 + ", + $global_unique_id + ) + ); + /** + * Hook woocommerce_get_product_id_by_global_unique_id. + * + * @since 9.1.0 + * @param mixed $id List of post statuses. + * @param string $global_unique_id Unique ID. + */ + return (int) apply_filters( 'woocommerce_get_product_id_by_global_unique_id', $id, $global_unique_id ); + } + /** * Returns an array of IDs of products that have sales starting soon. * @@ -2168,20 +2238,21 @@ protected function get_data_for_lookup_table( $id, $table ) { $price = wc_format_decimal( get_post_meta( $id, '_price', true ) ); $sale_price = wc_format_decimal( get_post_meta( $id, '_sale_price', true ) ); return array( - 'product_id' => absint( $id ), - 'sku' => get_post_meta( $id, '_sku', true ), - 'virtual' => 'yes' === get_post_meta( $id, '_virtual', true ) ? 1 : 0, - 'downloadable' => 'yes' === get_post_meta( $id, '_downloadable', true ) ? 1 : 0, - 'min_price' => reset( $price_meta ), - 'max_price' => end( $price_meta ), - 'onsale' => $sale_price && $price === $sale_price ? 1 : 0, - 'stock_quantity' => $stock, - 'stock_status' => get_post_meta( $id, '_stock_status', true ), - 'rating_count' => array_sum( array_map( 'intval', (array) get_post_meta( $id, '_wc_rating_count', true ) ) ), - 'average_rating' => get_post_meta( $id, '_wc_average_rating', true ), - 'total_sales' => get_post_meta( $id, 'total_sales', true ), - 'tax_status' => get_post_meta( $id, '_tax_status', true ), - 'tax_class' => get_post_meta( $id, '_tax_class', true ), + 'product_id' => absint( $id ), + 'sku' => get_post_meta( $id, '_sku', true ), + 'global_unique_id' => get_post_meta( $id, '_global_unique_id', true ), + 'virtual' => 'yes' === get_post_meta( $id, '_virtual', true ) ? 1 : 0, + 'downloadable' => 'yes' === get_post_meta( $id, '_downloadable', true ) ? 1 : 0, + 'min_price' => reset( $price_meta ), + 'max_price' => end( $price_meta ), + 'onsale' => $sale_price && $price === $sale_price ? 1 : 0, + 'stock_quantity' => $stock, + 'stock_status' => get_post_meta( $id, '_stock_status', true ), + 'rating_count' => array_sum( array_map( 'intval', (array) get_post_meta( $id, '_wc_rating_count', true ) ) ), + 'average_rating' => get_post_meta( $id, '_wc_average_rating', true ), + 'total_sales' => get_post_meta( $id, 'total_sales', true ), + 'tax_status' => get_post_meta( $id, '_tax_status', true ), + 'tax_class' => get_post_meta( $id, '_tax_class', true ), ); } return array(); diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-variation-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-variation-data-store-cpt.php index cf481afc41a3..131f6aa43816 100644 --- a/plugins/woocommerce/includes/data-stores/class-wc-product-variation-data-store-cpt.php +++ b/plugins/woocommerce/includes/data-stores/class-wc-product-variation-data-store-cpt.php @@ -367,6 +367,7 @@ protected function read_product_data( &$product ) { 'image_id' => get_post_thumbnail_id( $id ), 'backorders' => get_post_meta( $id, '_backorders', true ), 'sku' => get_post_meta( $id, '_sku', true ), + 'global_unique_id' => get_post_meta( $id, '_global_unique_id', true ), 'stock_quantity' => get_post_meta( $id, '_stock', true ), 'weight' => get_post_meta( $id, '_weight', true ), 'length' => get_post_meta( $id, '_length', true ), @@ -403,6 +404,7 @@ protected function read_product_data( &$product ) { 'title' => $parent_object ? $parent_object->post_title : '', 'status' => $parent_object ? $parent_object->post_status : '', 'sku' => get_post_meta( $product->get_parent_id(), '_sku', true ), + 'global_unique_id' => get_post_meta( $product->get_parent_id(), '_global_unique_id', true ), 'manage_stock' => get_post_meta( $product->get_parent_id(), '_manage_stock', true ), 'backorders' => get_post_meta( $product->get_parent_id(), '_backorders', true ), 'stock_quantity' => wc_stock_amount( get_post_meta( $product->get_parent_id(), '_stock', true ) ), diff --git a/plugins/woocommerce/includes/interfaces/class-wc-product-data-store-interface.php b/plugins/woocommerce/includes/interfaces/class-wc-product-data-store-interface.php index 4c1a68c94ec6..e1e8150ac5e6 100644 --- a/plugins/woocommerce/includes/interfaces/class-wc-product-data-store-interface.php +++ b/plugins/woocommerce/includes/interfaces/class-wc-product-data-store-interface.php @@ -41,6 +41,15 @@ public function get_featured_product_ids(); */ public function is_existing_sku( $product_id, $sku ); + /** + * Check if product unique ID is found for any other product IDs. + * + * @param int $product_id Product ID. + * @param string $global_unique_id Unique ID. + * @return bool + */ + public function is_existing_global_unique_id( $product_id, $global_unique_id ); + /** * Return product ID based on SKU. * @@ -49,6 +58,14 @@ public function is_existing_sku( $product_id, $sku ); */ public function get_product_id_by_sku( $sku ); + /** + * Return product ID based on Unique ID. + * + * @param string $global_unique_id Unique ID. + * @return int + */ + public function get_product_id_by_global_unique_id( $global_unique_id ); + /** * Returns an array of IDs of products that have sales starting soon. * diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php index 22516844f3e4..aa298f527491 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php @@ -108,6 +108,7 @@ public function prepare_object_for_response( $object, $request ) { 'description' => wc_format_content( $object->get_description() ), 'permalink' => $object->get_permalink(), 'sku' => $object->get_sku(), + 'global_unique_id' => $object->get_global_unique_id(), 'price' => $object->get_price(), 'regular_price' => $object->get_regular_price(), 'sale_price' => $object->get_sale_price(), @@ -193,6 +194,11 @@ protected function prepare_object_for_database( $request, $creating = false ) { $variation->set_sku( wc_clean( $request['sku'] ) ); } + // Unique ID. + if ( isset( $request['global_unique_id'] ) ) { + $variation->set_global_unique_id( wc_clean( $request['global_unique_id'] ) ); + } + // Thumbnail. if ( isset( $request['image'] ) ) { if ( is_array( $request['image'] ) ) { @@ -535,7 +541,12 @@ public function get_item_schema() { 'readonly' => true, ), 'sku' => array( - 'description' => __( 'Unique identifier.', 'woocommerce' ), + 'description' => __( 'Stock Keeping Unit.', 'woocommerce' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'global_unique_id' => array( + 'description' => __( 'GTIN, UPC, EAN or ISBN.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php index 70ac43bd9f41..b4a3a933faa9 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php @@ -561,6 +561,11 @@ protected function prepare_object_for_database( $request, $creating = false ) { $product->set_sku( wc_clean( $request['sku'] ) ); } + // Unique ID. + if ( isset( $request['global_unique_id'] ) ) { + $product->set_global_unique_id( wc_clean( $request['global_unique_id'] ) ); + } + // Attributes. if ( isset( $request['attributes'] ) ) { $attributes = array(); @@ -987,7 +992,12 @@ public function get_item_schema() { 'context' => array( 'view', 'edit' ), ), 'sku' => array( - 'description' => __( 'Unique identifier.', 'woocommerce' ), + 'description' => __( 'Stock Keeping Unit.', 'woocommerce' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'global_unique_id' => array( + 'description' => __( 'GTIN, UPC, EAN or ISBN.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), @@ -1662,6 +1672,10 @@ protected function get_product_data( $product, $context = 'view' ) { $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 ); diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-products-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-products-tracking.php index 7bdb1c8c5adf..3056f5b07d4e 100644 --- a/plugins/woocommerce/includes/tracks/events/class-wc-products-tracking.php +++ b/plugins/woocommerce/includes/tracks/events/class-wc-products-tracking.php @@ -344,6 +344,7 @@ public function track_product_published( $product_id, $product, $changes = null 'tags' => count( $product->get_tag_ids() ), 'upsells' => ! empty( $product->get_upsell_ids() ) ? 'yes' : 'no', 'weight' => $product->get_weight() ? 'yes' : 'no', + 'global_unique_id' => $product->get_global_unique_id() ? 'yes' : 'no', ); WC_Tracks::record_event( 'product_add_publish', $properties ); diff --git a/plugins/woocommerce/includes/wc-product-functions.php b/plugins/woocommerce/includes/wc-product-functions.php index f5dc753c9b04..92341e606ee2 100644 --- a/plugins/woocommerce/includes/wc-product-functions.php +++ b/plugins/woocommerce/includes/wc-product-functions.php @@ -639,6 +639,47 @@ function wc_product_has_unique_sku( $product_id, $sku ) { return true; } +/** + * Check if product unique ID is unique. + * + * @since 9.1.0 + * @param int $product_id Product ID. + * @param string $global_unique_id Product Unique ID. + * @return bool + */ +function wc_product_has_global_unique_id( $product_id, $global_unique_id ) { + /** + * Gives plugins an opportunity to verify Unique ID uniqueness themselves. + * + * @since 9.1.0 + * + * @param bool|null $has_global_unique_id Set to a boolean value to short-circuit the default Unique ID check. + * @param int $product_id The ID of the current product. + * @param string $sku The Unique ID to check for uniqueness. + */ + $has_global_unique_id = apply_filters( 'wc_product_pre_has_global_unique_id', null, $product_id, $global_unique_id ); + if ( ! is_null( $has_global_unique_id ) ) { + return boolval( $has_global_unique_id ); + } + + $data_store = WC_Data_Store::load( 'product' ); + $global_unique_id_found = $data_store->is_existing_global_unique_id( $product_id, $global_unique_id ); + /** + * Gives plugins an opportunity to verify Unique ID uniqueness themselves. + * + * @since 9.1.0 + * + * @param boolean $global_unique_id_found Whether the Unique ID is found. + * @param int $product_id The ID of the current product. + * @param string $sku The Unique ID to check for uniqueness. + */ + if ( apply_filters( 'wc_product_has_global_unique_id', $global_unique_id_found, $product_id, $global_unique_id ) ) { + return false; + } + + return true; +} + /** * Force a unique SKU. * @@ -692,6 +733,18 @@ function wc_get_product_id_by_sku( $sku ) { return $data_store->get_product_id_by_sku( $sku ); } +/** + * Get product ID by Unique ID. + * + * @since 9.1.0 + * @param string $global_unique_id Product Unique ID. + * @return int + */ +function wc_get_product_id_by_global_unique_id( $global_unique_id ) { + $data_store = WC_Data_Store::load( 'product' ); + return $data_store->get_product_id_by_global_unique_id( $global_unique_id ); +} + /** * Get attributes/data for an individual variation from the database and maintain it's integrity. * @@ -1421,6 +1474,7 @@ function wc_update_product_lookup_tables() { 'min_max_price', 'stock_quantity', 'sku', + 'global_unique_id', 'stock_status', 'average_rating', 'total_sales', diff --git a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php index facffee7a676..665ef1be5e9e 100644 --- a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php +++ b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php @@ -309,13 +309,41 @@ protected function add_inventory_group_blocks() { 'order' => 10, ) ); - $product_inventory_inner_section->add_block( + $inventory_columns = $product_inventory_inner_section->add_block( + array( + 'id' => 'product-inventory-inner-columns', + 'blockName' => 'core/columns', + ) + ); + $inventory_columns->add_block( + array( + 'id' => 'product-inventory-inner-column1', + 'blockName' => 'core/column', + ) + )->add_block( array( 'id' => 'product-variation-sku-field', 'blockName' => 'woocommerce/product-sku-field', 'order' => 10, ) ); + $inventory_columns->add_block( + array( + 'id' => 'product-inventory-inner-column2', + 'blockName' => 'core/column', + ) + )->add_block( + array( + 'id' => 'product-unique-id-field', + 'blockName' => 'woocommerce/product-text-field', + 'order' => 20, + 'attributes' => array( + 'property' => 'global_unique_id', + 'label' => __( 'GTIN, UPC, EAN or ISBN', 'woocommerce' ), + 'tooltip' => __( 'Enter a barcode or any other identifier unique to this product. It can help you list this product on other channels or marketplaces.', 'woocommerce' ), + ), + ) + ); $product_inventory_inner_section->add_block( array( 'id' => 'product-variation-track-stock', diff --git a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php index f8f14290f565..18a36daa6818 100644 --- a/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php +++ b/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php @@ -730,7 +730,18 @@ private function add_inventory_group_blocks() { 'order' => 10, ) ); - $product_inventory_inner_section->add_block( + $inventory_columns = $product_inventory_inner_section->add_block( + array( + 'id' => 'product-inventory-inner-columns', + 'blockName' => 'core/columns', + ) + ); + $inventory_columns->add_block( + array( + 'id' => 'product-inventory-inner-column1', + 'blockName' => 'core/column', + ) + )->add_block( array( 'id' => 'product-sku-field', 'blockName' => 'woocommerce/product-sku-field', @@ -742,6 +753,28 @@ private function add_inventory_group_blocks() { ), ) ); + $inventory_columns->add_block( + array( + 'id' => 'product-inventory-inner-column2', + 'blockName' => 'core/column', + ) + )->add_block( + array( + 'id' => 'product-unique-id-field', + 'blockName' => 'woocommerce/product-text-field', + 'order' => 20, + 'attributes' => array( + 'property' => 'global_unique_id', + 'label' => __( 'GTIN, UPC, EAN or ISBN', 'woocommerce' ), + 'tooltip' => __( 'Enter a barcode or any other identifier unique to this product. It can help you list this product on other channels or marketplaces.', 'woocommerce' ), + ), + 'disableConditions' => array( + array( + 'expression' => 'editedProduct.type === "variable"', + ), + ), + ) + ); $manage_stock = 'yes' === get_option( 'woocommerce_manage_stock' ); $product_inventory_inner_section->add_block( diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/product-variations.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/product-variations.php index 08d70d1200fe..a9c10ee364c5 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/product-variations.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/product-variations.php @@ -400,13 +400,14 @@ public function test_variation_schema() { $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 39, count( $properties ) ); + $this->assertEquals( 40, count( $properties ) ); $this->assertArrayHasKey( 'id', $properties ); $this->assertArrayHasKey( 'date_created', $properties ); $this->assertArrayHasKey( 'date_modified', $properties ); $this->assertArrayHasKey( 'description', $properties ); $this->assertArrayHasKey( 'permalink', $properties ); $this->assertArrayHasKey( 'sku', $properties ); + $this->assertArrayHasKey( 'global_unique_id', $properties ); $this->assertArrayHasKey( 'price', $properties ); $this->assertArrayHasKey( 'regular_price', $properties ); $this->assertArrayHasKey( 'sale_price', $properties ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/products.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/products.php index 94a3d2159b50..b05e7dc4da6a 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/products.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/products.php @@ -649,7 +649,7 @@ public function test_product_schema() { $response = $this->server->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 70, count( $properties ) ); + $this->assertEquals( 71, count( $properties ) ); } /** diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php index 71d588907a5e..a8846c0416b0 100644 --- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php +++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php @@ -97,6 +97,7 @@ public function get_expected_response_fields() { 'description', 'short_description', 'sku', + 'global_unique_id', 'price', 'regular_price', 'sale_price',