From 9a8cbfbd73d78820d88a91582a6b170a5bac45b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Valney?= Date: Tue, 16 Feb 2021 14:49:25 -0300 Subject: [PATCH 01/32] Support to optgroups on select from Settings API Add the optgroups support to single select from Settings API. The multiselect input already has this feature. --- includes/abstracts/abstract-wc-settings-api.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/includes/abstracts/abstract-wc-settings-api.php b/includes/abstracts/abstract-wc-settings-api.php index f33a62365fc..095403d65c0 100644 --- a/includes/abstracts/abstract-wc-settings-api.php +++ b/includes/abstracts/abstract-wc-settings-api.php @@ -696,6 +696,7 @@ abstract class WC_Settings_API { ); $data = wp_parse_args( $data, $defaults ); + $value = $this->get_option( $key ); ob_start(); ?> @@ -708,7 +709,15 @@ abstract class WC_Settings_API { get_description_html( $data ); // WPCS: XSS ok. ?> From 323fc329599f6e0f843f802a0ff63ed1385ea208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Valney?= Date: Tue, 16 Feb 2021 15:01:07 -0300 Subject: [PATCH 02/32] Added esc_attr to selected like before --- includes/abstracts/abstract-wc-settings-api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/abstracts/abstract-wc-settings-api.php b/includes/abstracts/abstract-wc-settings-api.php index 095403d65c0..35bdbe522b7 100644 --- a/includes/abstracts/abstract-wc-settings-api.php +++ b/includes/abstracts/abstract-wc-settings-api.php @@ -712,11 +712,11 @@ abstract class WC_Settings_API { $option_value_inner ) : ?> - + - + From d1926e52b2e0da5937ffe2e1ef8efa1f7ca32fe7 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 16 Mar 2021 12:12:49 +0000 Subject: [PATCH 03/32] Update dependency league/container to v3.3.5 --- lib/composer.json | 2 +- lib/composer.lock | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/composer.json b/lib/composer.json index 7e9e4481f95..f3fa6563375 100644 --- a/lib/composer.json +++ b/lib/composer.json @@ -8,7 +8,7 @@ "psr/container": "^1.0" }, "require-dev": { - "league/container": "3.3.3" + "league/container": "3.3.5" }, "config": { "platform": { diff --git a/lib/composer.lock b/lib/composer.lock index ac31c819004..bff7bf66ac0 100644 --- a/lib/composer.lock +++ b/lib/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "df548645b5c00d585705cd10c6ffd3f7", + "content-hash": "9ae561875707d59bc392f6329d4f565a", "packages": [ { "name": "psr/container", @@ -59,21 +59,21 @@ "packages-dev": [ { "name": "league/container", - "version": "3.3.3", + "version": "3.3.5", "source": { "type": "git", "url": "https://github.com/thephpleague/container.git", - "reference": "7dc67bdf89efc338e674863c0ea70a63efe4de05" + "reference": "048ab87810f508dbedbcb7ae941b606eb8ee353b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/7dc67bdf89efc338e674863c0ea70a63efe4de05", - "reference": "7dc67bdf89efc338e674863c0ea70a63efe4de05", + "url": "https://api.github.com/repos/thephpleague/container/zipball/048ab87810f508dbedbcb7ae941b606eb8ee353b", + "reference": "048ab87810f508dbedbcb7ae941b606eb8ee353b", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/container": "^1.0" + "psr/container": "^1.0.0 || ^2.0.0" }, "provide": { "psr/container-implementation": "^1.0" @@ -83,11 +83,14 @@ }, "require-dev": { "phpunit/phpunit": "^6.0", - "squizlabs/php_codesniffer": "^3.3" + "roave/security-advisories": "dev-master", + "scrutinizer/ocular": "^1.8", + "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { "branch-alias": { + "dev-master": "3.x-dev", "dev-3.x": "3.x-dev", "dev-2.x": "2.x-dev", "dev-1.x": "1.x-dev" @@ -127,7 +130,7 @@ "type": "github" } ], - "time": "2020-09-28T13:38:44+00:00" + "time": "2021-03-16T09:42:56+00:00" } ], "aliases": [], From 0baa2d631428e5e785e15d69ff4a3af461cf4cfc Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 25 Mar 2021 09:35:03 +0100 Subject: [PATCH 04/32] Fix code sniffer errors in WC_REST_Taxes_V1_Controller --- .../class-wc-rest-taxes-v1-controller.php | 250 +++++++++--------- 1 file changed, 130 insertions(+), 120 deletions(-) diff --git a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php index 785df5eae48..db46d9f12da 100644 --- a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php +++ b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php @@ -4,8 +4,6 @@ * * Handles requests to the /taxes endpoint. * - * @author WooThemes - * @category API * @package WooCommerce\RestApi * @since 3.0.0 */ @@ -40,67 +38,79 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { * Register the routes for taxes. */ public function register_routes() { - register_rest_route( $this->namespace, '/' . $this->rest_base, array( + register_rest_route( + $this->namespace, + '/' . $this->rest_base, array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - 'args' => $this->get_collection_params(), - ), - array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => array( $this, 'create_item' ), - 'permission_callback' => array( $this, 'create_item_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) ); + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); - register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)', array( - 'args' => array( - 'id' => array( - 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), - 'type' => 'integer', - ), - ), + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), - 'args' => array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), - ), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'update_item' ), - 'permission_callback' => array( $this, 'update_item_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ), - array( - 'methods' => WP_REST_Server::DELETABLE, - 'callback' => array( $this, 'delete_item' ), - 'permission_callback' => array( $this, 'delete_item_permissions_check' ), - 'args' => array( - 'force' => array( - 'default' => false, - 'type' => 'boolean', - 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ), + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), + 'type' => 'integer', ), ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) ); + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'args' => array( + 'force' => array( + 'default' => false, + 'type' => 'boolean', + 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ), + ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); - register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array( + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/batch', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'batch_items' ), - 'permission_callback' => array( $this, 'batch_items_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ), - 'schema' => array( $this, 'get_public_batch_schema' ), - ) ); + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'batch_items' ), + 'permission_callback' => array( $this, 'batch_items_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + 'schema' => array( $this, 'get_public_batch_schema' ), + ) + ); } /** @@ -200,7 +210,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { public function get_items( $request ) { global $wpdb; - $prepared_args = array(); + $prepared_args = array(); $prepared_args['order'] = $request['order']; $prepared_args['number'] = $request['per_page']; if ( ! empty( $request['offset'] ) ) { @@ -208,9 +218,9 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { } else { $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; } - $orderby_possibles = array( - 'id' => 'tax_rate_id', - 'order' => 'tax_rate_order', + $orderby_possibles = array( + 'id' => 'tax_rate_id', + 'order' => 'tax_rate_order', ); $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ]; $prepared_args['class'] = $request['class']; @@ -246,7 +256,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { $taxes = array(); foreach ( $results as $tax ) { - $data = $this->prepare_item_for_response( $tax, $request ); + $data = $this->prepare_item_for_response( $tax, $request ); $taxes[] = $this->prepare_response_for_collection( $data ); } @@ -254,7 +264,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { // Store pagination values for headers then unset for count query. $per_page = (int) $prepared_args['number']; - $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); + $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); // Query only for ids. $wpdb->get_results( str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ) ); @@ -287,13 +297,13 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { * Take tax data from the request and return the updated or newly created rate. * * @param WP_REST_Request $request Full details about the request. - * @param stdClass|null $current Existing tax object. + * @param stdClass|null $current Existing tax object. * @return object */ protected function create_or_update_tax( $request, $current = null ) { - $id = absint( isset( $request['id'] ) ? $request['id'] : 0 ); - $data = array(); - $fields = array( + $id = absint( isset( $request['id'] ) ? $request['id'] : 0 ); + $data = array(); + $fields = array( 'tax_rate_country', 'tax_rate_state', 'tax_rate', @@ -321,16 +331,16 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { // Add to data array. switch ( $key ) { - case 'tax_rate_priority' : - case 'tax_rate_compound' : - case 'tax_rate_shipping' : - case 'tax_rate_order' : + case 'tax_rate_priority': + case 'tax_rate_compound': + case 'tax_rate_shipping': + case 'tax_rate_order': $data[ $field ] = absint( $request[ $key ] ); break; - case 'tax_rate_class' : + case 'tax_rate_class': $data[ $field ] = 'standard' !== $request['tax_rate_class'] ? $request['tax_rate_class'] : ''; break; - default : + default: $data[ $field ] = wc_clean( $request[ $key ] ); break; } @@ -538,7 +548,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { */ protected function prepare_links( $tax ) { $links = array( - 'self' => array( + 'self' => array( 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $tax->tax_rate_id ) ), ), 'collection' => array( @@ -592,18 +602,18 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { 'title' => 'tax', 'type' => 'object', 'properties' => array( - 'id' => array( + 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'country' => array( + 'country' => array( 'description' => __( 'Country ISO 3166 code.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'state' => array( + 'state' => array( 'description' => __( 'State code.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), @@ -613,17 +623,17 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'city' => array( + 'city' => array( 'description' => __( 'City name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'rate' => array( + 'rate' => array( 'description' => __( 'Tax rate.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'name' => array( + 'name' => array( 'description' => __( 'Tax rate name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), @@ -646,12 +656,12 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { 'default' => true, 'context' => array( 'view', 'edit' ), ), - 'order' => array( + 'order' => array( 'description' => __( 'Indicates the order that will appear in queries.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), - 'class' => array( + 'class' => array( 'description' => __( 'Tax class.', 'woocommerce' ), 'type' => 'string', 'default' => 'standard', @@ -674,54 +684,54 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { $params['context'] = $this->get_context_param(); $params['context']['default'] = 'view'; - $params['page'] = array( - 'description' => __( 'Current page of the collection.', 'woocommerce' ), - 'type' => 'integer', - 'default' => 1, - 'sanitize_callback' => 'absint', - 'validate_callback' => 'rest_validate_request_arg', - 'minimum' => 1, + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, ); $params['per_page'] = array( - 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), - 'type' => 'integer', - 'default' => 10, - 'minimum' => 1, - 'maximum' => 100, - 'sanitize_callback' => 'absint', - 'validate_callback' => 'rest_validate_request_arg', + 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', ); - $params['offset'] = array( - 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'validate_callback' => 'rest_validate_request_arg', + $params['offset'] = array( + 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', ); - $params['order'] = array( - 'default' => 'asc', - 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), - 'enum' => array( 'asc', 'desc' ), - 'sanitize_callback' => 'sanitize_key', - 'type' => 'string', - 'validate_callback' => 'rest_validate_request_arg', + $params['order'] = array( + 'default' => 'asc', + 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), + 'enum' => array( 'asc', 'desc' ), + 'sanitize_callback' => 'sanitize_key', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', ); - $params['orderby'] = array( - 'default' => 'order', - 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), - 'enum' => array( + $params['orderby'] = array( + 'default' => 'order', + 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), + 'enum' => array( 'id', 'order', ), - 'sanitize_callback' => 'sanitize_key', - 'type' => 'string', - 'validate_callback' => 'rest_validate_request_arg', + 'sanitize_callback' => 'sanitize_key', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', ); - $params['class'] = array( - 'description' => __( 'Sort by tax class.', 'woocommerce' ), - 'enum' => array_merge( array( 'standard' ), WC_Tax::get_tax_class_slugs() ), - 'sanitize_callback' => 'sanitize_title', - 'type' => 'string', - 'validate_callback' => 'rest_validate_request_arg', + $params['class'] = array( + 'description' => __( 'Sort by tax class.', 'woocommerce' ), + 'enum' => array_merge( array( 'standard' ), WC_Tax::get_tax_class_slugs() ), + 'sanitize_callback' => 'sanitize_title', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', ); return $params; From c1427fc5ee8f9dbcf6011e7f881662ccb5bdae0c Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 25 Mar 2021 09:38:23 +0100 Subject: [PATCH 05/32] Improvements in WC_REST_Taxes_V1_Controller::get_items : - Use wpdb->prepare to compose the queries. - Actually make use of the 'order' parameter. --- .../class-wc-rest-taxes-v1-controller.php | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php index db46d9f12da..a9434d6234a 100644 --- a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php +++ b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php @@ -233,26 +233,35 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { */ $prepared_args = apply_filters( 'woocommerce_rest_tax_query', $prepared_args, $request ); - $query = " + $orderby = sanitize_key( $prepared_args['orderby'] ) . ' ' . sanitize_key( $prepared_args['order'] ); + $query = " SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates - WHERE 1 = 1 + %s + ORDER BY {$orderby} + LIMIT %%d, %%d "; // Filter by tax class. - if ( ! empty( $prepared_args['class'] ) ) { + if ( empty( $prepared_args['class'] ) ) { + $class = null; + $query = sprintf( $query, '' ); + } else { $class = 'standard' !== $prepared_args['class'] ? sanitize_title( $prepared_args['class'] ) : ''; - $query .= " AND tax_rate_class = '$class'"; + $query = sprintf( $query, 'WHERE tax_rate_class = %s' ); } - // Order tax rates. - $order_by = sprintf( ' ORDER BY %s', sanitize_key( $prepared_args['orderby'] ) ); - - // Pagination. - $pagination = sprintf( ' LIMIT %d, %d', $prepared_args['offset'], $prepared_args['number'] ); - // Query taxes. - $results = $wpdb->get_results( $query . $order_by . $pagination ); + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared + $results = $wpdb->get_results( + $wpdb->prepare( + $query, + $prepared_args['offset'], + $prepared_args['number'], + $class + ) + ); + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared $taxes = array(); foreach ( $results as $tax ) { @@ -267,7 +276,17 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); // Query only for ids. - $wpdb->get_results( str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ) ); + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared + $query = str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ); + $wpdb->get_results( + $wpdb->prepare( + $query, + $prepared_args['offset'], + $prepared_args['number'], + $class + ) + ); + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared // Calculate totals. $total_taxes = (int) $wpdb->num_rows; From 4c07ab6ae6efb5ec1d241b48756c5895db055cac Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 25 Mar 2021 09:41:10 +0100 Subject: [PATCH 06/32] WC_REST_Taxes_V1_Controller::get_items: add 'priority' to the schema for 'orderby' --- .../Controllers/Version1/class-wc-rest-taxes-v1-controller.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php index a9434d6234a..708b7b65935 100644 --- a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php +++ b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php @@ -221,6 +221,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { $orderby_possibles = array( 'id' => 'tax_rate_id', 'order' => 'tax_rate_order', + 'priority' => 'tax_rate_priority', ); $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ]; $prepared_args['class'] = $request['class']; @@ -740,6 +741,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { 'enum' => array( 'id', 'order', + 'priority', ), 'sanitize_callback' => 'sanitize_key', 'type' => 'string', From 8ec8daf65a2676d807f59d5ea69236b7cca3f25a Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 25 Mar 2021 14:31:52 +0100 Subject: [PATCH 07/32] Add a do_rest_request method to WC_REST_Unit_Test_Case. --- .../class-wc-rest-unit-test-case.php | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/legacy/framework/class-wc-rest-unit-test-case.php b/tests/legacy/framework/class-wc-rest-unit-test-case.php index 5ad76c053e7..013ec0ace8d 100644 --- a/tests/legacy/framework/class-wc-rest-unit-test-case.php +++ b/tests/legacy/framework/class-wc-rest-unit-test-case.php @@ -4,11 +4,18 @@ * * Provides REST API specific methods and setup/teardown. * + * @package WooCommerce\Tests * @since 3.0 */ +/** + * Base class for REST related unit test classes. + */ class WC_REST_Unit_Test_Case extends WC_Unit_Test_Case { + /** + * @var WP_REST_Server + */ protected $server; /** @@ -36,4 +43,64 @@ class WC_REST_Unit_Test_Case extends WC_Unit_Test_Case { unset( $this->server ); $wp_rest_server = null; } + + /** + * Perform a REST request. + * + * @param string $url The endpopint url, if it doesn't start with '/' it'll be prepended with '/wc/v3/'. + * @param string $verb HTTP verb for the request, default is GET. + * @param array|null $body_params Body parameters for the request, null if none are required. + * @param array|null $query_params Query string parameters for the request, null if none are required. + * @return array Result from the request. + */ + public function do_rest_request( $url, $verb = 'GET', $body_params = null, $query_params = null ) { + if ( '/' !== $url[0] ) { + $url = '/wc/v3/' . $url; + } + + $request = new WP_REST_Request( $verb, $url ); + if ( ! is_null( $query_params ) ) { + $request->set_query_params( $query_params ); + } + if ( ! is_null( $body_params ) ) { + $request->set_body_params( $body_params ); + } + + return $this->server->dispatch( $request ); + } + + /** + * Perform a GET REST request. + * + * @param string $url The endpopint url, if it doesn't start with '/' it'll be prepended with '/wc/v3/'. + * @param array|null $query_params Query string parameters for the request, null if none are required. + * @return WP_REST_Response The response for the request. + */ + public function do_rest_get_request( $url, $query_params = null ) { + return $this->do_rest_request( $url, 'GET', null, $query_params ); + } + + /** + * Perform a POST REST request. + * + * @param string $url The endpopint url, if it doesn't start with '/' it'll be prepended with '/wc/v3/'. + * @param array|null $body_params Body parameters for the request, null if none are required. + * @param array|null $query_params Query string parameters for the request, null if none are required. + * @return array Result from the request. + */ + public function do_rest_post_request( $url, $body_params = null, $query_params = null ) { + return $this->do_rest_request( $url, 'POST', $body_params, $query_params ); + } + + /** + * Perform a PUT REST request. + * + * @param string $url The endpopint url, if it doesn't start with '/' it'll be prepended with '/wc/v3/'. + * @param array|null $body_params Body parameters for the request, null if none are required. + * @param array|null $query_params Query string parameters for the request, null if none are required. + * @return array Result from the request. + */ + public function do_rest_put_request( $url, $body_params = null, $query_params = null ) { + return $this->do_rest_request( $url, 'PUT', $body_params, $query_params ); + } } From cd82c48a73869fd2221ed78c7004852b9f30545c Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 25 Mar 2021 14:32:46 +0100 Subject: [PATCH 08/32] Fix: WC_REST_Taxes_V1_Controller::create_or_update_tax erroring when no core fields were included in the request (e.g. only postcodes or cities were specified) --- .../Version1/class-wc-rest-taxes-v1-controller.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php index 708b7b65935..e6fd4926cdd 100644 --- a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php +++ b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php @@ -366,10 +366,10 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { } } - if ( $id ) { - WC_Tax::_update_tax_rate( $id, $data ); - } else { + if ( ! $id ) { $id = WC_Tax::_insert_tax_rate( $data ); + } elseif ( $data ) { + WC_Tax::_update_tax_rate( $id, $data ); } // Add locales. From b5e13bd7719aad14e3741bf63751fad64297f5ca Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 25 Mar 2021 14:35:12 +0100 Subject: [PATCH 09/32] Add support for "postcodes" and "cities" (arrays) fields to the REST API endpoints for creating and updating taxes. --- .../class-wc-rest-taxes-controller.php | 39 +++ .../class-wc-rest-taxes-controller-tests.php | 241 ++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php index 758686fbc44..8e1141a555a 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php @@ -93,4 +93,43 @@ class WC_REST_Taxes_Controller extends WC_REST_Taxes_V2_Controller { return $schema; } + + /** + * Create a single tax. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response The response, or an error. + */ + public function create_item( $request ) { + $this->adjust_cities_and_postcodes( $request ); + + return parent::create_item( $request ); + } + + /** + * Update a single tax. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response The response, or an error. + */ + public function update_item( $request ) { + $this->adjust_cities_and_postcodes( $request ); + + return parent::update_item( $request ); + } + + /** + * Convert array "cities" and "postcodes" parameters + * into semicolon-separated strings "city" and "postcode". + * + * @param WP_REST_Request $request The request to adjust. + */ + private function adjust_cities_and_postcodes( &$request ) { + if ( isset( $request['cities'] ) ) { + $request['city'] = join( ';', $request['cities'] ); + } + if ( isset( $request['postcodes'] ) ) { + $request['postcode'] = join( ';', $request['postcodes'] ); + } + } } diff --git a/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php b/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php new file mode 100644 index 00000000000..6f3ae0e8556 --- /dev/null +++ b/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php @@ -0,0 +1,241 @@ +user = $this->factory->user->create( + array( + 'role' => 'administrator', + ) + ); + } + + /** + * Data provider for test_can_create_and_update_tax_rates_with_multiple_cities_and_postcodes. + * + * @return array + */ + public function data_provider_for_test_can_create_and_update_tax_rates_with_multiple_cities_and_postcodes() { + return array( + array( + array( + 'city' => 'Osaka;Kyoto;Kobe', + 'postcode' => '5555;7777;8888', + ), + 'create', + ), + array( + array( + 'cities' => array( + 'Osaka', + 'Kyoto', + 'Kobe', + ), + 'postcodes' => array( + '5555', + '7777', + '8888', + ), + ), + 'create', + ), + array( + array( + 'city' => 'Osaka;Kyoto;Kobe', + 'postcode' => '5555;7777;8888', + ), + 'update', + ), + array( + array( + 'cities' => array( + 'Osaka', + 'Kyoto', + 'Kobe', + ), + 'postcodes' => array( + '5555', + '7777', + '8888', + ), + ), + 'update', + ), + ); + } + + /** + * @testdox It is possible to create or update a tax rate passing either "city"/"postcode" (strings) or "cities"/"postcodes" (arrays) fields. + * + * @dataProvider data_provider_for_test_can_create_and_update_tax_rates_with_multiple_cities_and_postcodes + * + * @param array $request_body The body for the REST request. + * @param string $action The action to perform, 'create' or 'update'. + */ + public function test_can_create_and_update_tax_rates_with_multiple_cities_and_postcodes( $request_body, $action ) { + global $wpdb; + + wp_set_current_user( $this->user ); + + if ( 'create' === $action ) { + $tax_rate_id = null; + + $request_body = array_merge( + $request_body, + array( + 'country' => 'JP', + 'rate' => '1', + 'name' => 'Fake Tax', + ) + ); + + $verb = 'POST'; + $url = 'taxes'; + $success_status = 201; + } else { + $tax_rate_id = WC_Tax::_insert_tax_rate( + array( + 'tax_rate_country' => 'JP', + 'tax_rate' => '1', + 'tax_rate_name' => 'Fake Tax', + ) + ); + + WC_Tax::_update_tax_rate_cities( $tax_rate_id, 'Tokyo' ); + WC_Tax::_update_tax_rate_postcodes( $tax_rate_id, '0000' ); + + $verb = 'PUT'; + $url = 'taxes/' . $tax_rate_id; + $success_status = 200; + } + + $response = $this->do_rest_request( $url, $verb, $request_body ); + $this->assertEquals( $success_status, $response->get_status() ); + if ( ! $tax_rate_id ) { + $tax_rate_id = $response->get_data()['id']; + } + + $data = $wpdb->get_results( + $wpdb->prepare( + "SELECT location_type, GROUP_CONCAT(location_code SEPARATOR ';') as items + FROM {$wpdb->prefix}woocommerce_tax_rate_locations + WHERE tax_rate_id=%d + GROUP BY location_type", + $tax_rate_id + ), + OBJECT_K + ); + + $this->assertEquals( 'OSAKA;KYOTO;KOBE', $data['city']->items ); + $this->assertEquals( '5555;7777;8888', $data['postcode']->items ); + } + + /** + * @testdox The response for tax rate(s) includes the "city"/"postcode" (strings) and "cities"/"postcodes" (arrays) fields. + * + * @testWith [true] + * [false] + * + * @param bool $request_one True to request only one tax, false to request all the taxes. + */ + public function test_get_tax_response_includes_cities_and_postcodes_as_arrays( $request_one ) { + wp_set_current_user( $this->user ); + + $tax_id = WC_Tax::_insert_tax_rate( + array( + 'tax_rate_country' => 'JP', + 'tax_rate' => '1', + 'tax_rate_name' => 'Fake Tax', + ) + ); + + WC_Tax::_update_tax_rate_cities( $tax_id, 'Osaka;Kyoto;Kobe' ); + WC_Tax::_update_tax_rate_postcodes( $tax_id, '5555;7777;8888' ); + + if ( $request_one ) { + $response = $this->do_rest_get_request( 'taxes/' . $tax_id ); + } else { + $response = $this->do_rest_get_request( 'taxes' ); + } + + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + if ( ! $request_one ) { + $data = current( $data ); + } + + $this->assertEquals( 'KOBE', $data['city'] ); + $this->assertEquals( '8888', $data['postcode'] ); + $this->assertEquals( array( 'OSAKA', 'KYOTO', 'KOBE' ), $data['cities'] ); + $this->assertEquals( array( '5555', '7777', '8888' ), $data['postcodes'] ); + } + + /** + * @testdox The response of a REST API request for taxes can be sorted by priority. + * + * @testWith ["asc"] + * ["desc"] + * + * @param string $order_type Sort type, 'asc' or 'desc'. + */ + public function test_get_tax_response_can_be_sorted_by_priority( $order_type ) { + wp_set_current_user( $this->user ); + + $tax_id_1 = WC_Tax::_insert_tax_rate( + array( + 'tax_rate_country' => 'JP', + 'tax_rate' => '1', + 'tax_rate_priority' => 1, + 'tax_rate_name' => 'Fake Tax 1', + ) + ); + $tax_id_3 = WC_Tax::_insert_tax_rate( + array( + 'tax_rate_country' => 'JP', + 'tax_rate' => '1', + 'tax_rate_priority' => 3, + 'tax_rate_name' => 'Fake Tax 3', + ) + ); + $tax_id_2 = WC_Tax::_insert_tax_rate( + array( + 'tax_rate_country' => 'JP', + 'tax_rate' => '1', + 'tax_rate_priority' => 2, + 'tax_rate_name' => 'Fake Tax 2', + ) + ); + + $response = $this->do_rest_get_request( + 'taxes', + array( + 'orderby' => 'priority', + 'order' => $order_type, + ) + ); + + $this->assertEquals( 200, $response->get_status() ); + $data = array_values( $response->get_data() ); + $ids = array_map( + function( $item ) { + return $item['id']; + }, + $data + ); + + if ( 'asc' === $order_type ) { + $expected = array( $tax_id_1, $tax_id_2, $tax_id_3 ); + } else { + $expected = array( $tax_id_3, $tax_id_2, $tax_id_1 ); + } + $this->assertEquals( $expected, $ids ); + } +} From 214ca384ea91bf2a0c3d4cc6358da76c3de1da39 Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Fri, 26 Mar 2021 14:29:53 -0300 Subject: [PATCH 10/32] add stalebot action with dry run enabled --- .github/workflows/stalebot.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/stalebot.yml diff --git a/.github/workflows/stalebot.yml b/.github/workflows/stalebot.yml new file mode 100644 index 00000000000..0c91b9a5410 --- /dev/null +++ b/.github/workflows/stalebot.yml @@ -0,0 +1,20 @@ +name: 'Close stale needs-feedback issues' +on: + schedule: + - cron: '0 21 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "As a part of this repository’s maintenance, this issue is being marked as stale due to inactivity. Please feel free to comment on it in case we missed something.\n\n###### After 7 days with no activity this issue will be automatically be closed." + close-issue-message: 'This issue was closed because it has been 14 days with no activity.' + days-before-issue-stale: 7 + days-before-issue-close: 7 + days-before-pr-close: -1 + only-issue-label: 'needs feedback' + close-issue-label: "category: can't reproduce" + debug-only: true From ea41379b7b523b50bf1effab0cf7f087ed13528c Mon Sep 17 00:00:00 2001 From: James Allan Date: Mon, 29 Mar 2021 15:28:34 +1000 Subject: [PATCH 11/32] Update the persistant cart after it's loaded on log in --- includes/class-wc-cart-session.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/includes/class-wc-cart-session.php b/includes/class-wc-cart-session.php index d8eb30ca8e9..50f2291a68c 100644 --- a/includes/class-wc-cart-session.php +++ b/includes/class-wc-cart-session.php @@ -175,6 +175,10 @@ final class WC_Cart_Session { if ( $update_cart_session || is_null( WC()->session->get( 'cart_totals', null ) ) ) { WC()->session->set( 'cart', $this->get_cart_for_session() ); $this->cart->calculate_totals(); + + if ( $merge_saved_cart ) { + $this->persistent_cart_update(); + } } // If this is a re-order, redirect to the cart page to get rid of the `order_again` query string. From 35b02e1660e634e5ba33b007b1df942dc17f6ed8 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 30 Mar 2021 16:27:09 +0100 Subject: [PATCH 12/32] Customer billing and shipping getter should merge changed data --- includes/class-wc-customer.php | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-customer.php b/includes/class-wc-customer.php index 3c5c53079f3..d66efff9d2c 100644 --- a/includes/class-wc-customer.php +++ b/includes/class-wc-customer.php @@ -449,7 +449,19 @@ class WC_Customer extends WC_Legacy_Customer { * @return array */ public function get_billing( $context = 'view' ) { - return $this->get_prop( 'billing', $context ); + $value = null; + $prop = 'billing'; + + if ( array_key_exists( $prop, $this->data ) ) { + $changes = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : array(); + $value = array_merge( $this->data[ $prop ], $changes ); + + if ( 'view' === $context ) { + $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this ); + } + } + + return $value; } /** @@ -580,7 +592,19 @@ class WC_Customer extends WC_Legacy_Customer { * @return array */ public function get_shipping( $context = 'view' ) { - return $this->get_prop( 'shipping', $context ); + $value = null; + $prop = 'shipping'; + + if ( array_key_exists( $prop, $this->data ) ) { + $changes = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : array(); + $value = array_merge( $this->data[ $prop ], $changes ); + + if ( 'view' === $context ) { + $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this ); + } + } + + return $value; } /** From b2c81135256dacfe96a596eccff740e881fa2839 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 30 Mar 2021 13:10:21 -0300 Subject: [PATCH 13/32] Change UID only for WooCommerce cookies --- includes/class-wc-session-handler.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/includes/class-wc-session-handler.php b/includes/class-wc-session-handler.php index cacda9e59a2..163b78fec4a 100644 --- a/includes/class-wc-session-handler.php +++ b/includes/class-wc-session-handler.php @@ -75,7 +75,7 @@ class WC_Session_Handler extends WC_Session { add_action( 'wp_logout', array( $this, 'destroy_session' ) ); if ( ! is_user_logged_in() ) { - add_filter( 'nonce_user_logged_out', array( $this, 'nonce_user_logged_out' ) ); + add_filter( 'nonce_user_logged_out', array( $this, 'nonce_user_logged_out' ), 10, 2 ); } } @@ -288,11 +288,16 @@ class WC_Session_Handler extends WC_Session { /** * When a user is logged out, ensure they have a unique nonce by using the customer/session ID. * - * @param int $uid User ID. - * @return string + * @param int $uid User ID. + * @param string $action The nonce action. + * @return int|string */ - public function nonce_user_logged_out( $uid ) { - return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid; + public function nonce_user_logged_out( $uid, $action ) { + if ( Automattic\WooCommerce\Utilities\StringUtil::starts_with( $action, 'woocommerce' ) ) { + return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid; + } + + return $uid; } /** From d6e023592523a080b95ab8183d8929470abc6374 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 30 Mar 2021 16:19:44 +0000 Subject: [PATCH 14/32] Update dependency automattic/jetpack-autoloader to v2.10.1 --- composer.json | 2 +- composer.lock | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 4b3c4d352bd..385182966bc 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ ], "require": { "php": ">=7.0", - "automattic/jetpack-autoloader": "2.9.1", + "automattic/jetpack-autoloader": "2.10.1", "automattic/jetpack-constants": "1.5.1", "composer/installers": "~1.7", "maxmind-db/reader": "1.6.0", diff --git a/composer.lock b/composer.lock index a5c1f0ab2ad..2eb76ff282e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,32 +4,39 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d0a0153dda851ca2c8e79bed1813ba5f", + "content-hash": "b1d6d94c8cfae572ab27c288c6865787", "packages": [ { "name": "automattic/jetpack-autoloader", - "version": "v2.9.1", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-autoloader.git", - "reference": "d6ca2cc26ad6963e1be19b3338a9e98f40d9bd88" + "reference": "20393c4677765c3e737dcb5aee7a3f7b90dce4b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/d6ca2cc26ad6963e1be19b3338a9e98f40d9bd88", - "reference": "d6ca2cc26ad6963e1be19b3338a9e98f40d9bd88", + "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/20393c4677765c3e737dcb5aee7a3f7b90dce4b3", + "reference": "20393c4677765c3e737dcb5aee7a3f7b90dce4b3", "shasum": "" }, "require": { "composer-plugin-api": "^1.1 || ^2.0" }, "require-dev": { + "automattic/jetpack-changelogger": "^1.1", "yoast/phpunit-polyfills": "0.2.0" }, "type": "composer-plugin", "extra": { "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin", - "mirror-repo": "Automattic/jetpack-autoloader" + "mirror-repo": "Automattic/jetpack-autoloader", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-master": "2.10.x-dev" + } }, "autoload": { "classmap": [ @@ -45,9 +52,9 @@ ], "description": "Creates a custom autoloader for a plugin or theme.", "support": { - "source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.9.1" + "source": "https://github.com/Automattic/jetpack-autoloader/tree/2.10.1" }, - "time": "2021-02-05T19:07:06+00:00" + "time": "2021-03-30T15:15:59+00:00" }, { "name": "automattic/jetpack-constants", From 65b024a96d2330298aa4e2f758041dba9039e86f Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 30 Mar 2021 14:11:24 -0300 Subject: [PATCH 15/32] Moved to a new function --- includes/class-wc-session-handler.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-session-handler.php b/includes/class-wc-session-handler.php index 163b78fec4a..1ba0095d3d0 100644 --- a/includes/class-wc-session-handler.php +++ b/includes/class-wc-session-handler.php @@ -75,7 +75,7 @@ class WC_Session_Handler extends WC_Session { add_action( 'wp_logout', array( $this, 'destroy_session' ) ); if ( ! is_user_logged_in() ) { - add_filter( 'nonce_user_logged_out', array( $this, 'nonce_user_logged_out' ), 10, 2 ); + add_filter( 'nonce_user_logged_out', array( $this, 'maybe_update_nonce_user_logged_out' ), 10, 2 ); } } @@ -288,11 +288,24 @@ class WC_Session_Handler extends WC_Session { /** * When a user is logged out, ensure they have a unique nonce by using the customer/session ID. * + * @deprecated 5.3.0 + * @param int $uid User ID. + * @return int|string + */ + public function nonce_user_logged_out( $uid ) { + return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid; + } + + /** + * When a user is logged out, ensure they have a unique nonce to manage cart and more using the customer/session ID. + * This filter runs everything `wp_verify_nonce()` and `wp_create_nonce()` gets called. + * + * @since 5.3.0 * @param int $uid User ID. * @param string $action The nonce action. * @return int|string */ - public function nonce_user_logged_out( $uid, $action ) { + public function maybe_update_nonce_user_logged_out( $uid, $action ) { if ( Automattic\WooCommerce\Utilities\StringUtil::starts_with( $action, 'woocommerce' ) ) { return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid; } From 2da3a3745077cfad6a486d2d9220336e1130e83c Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 30 Mar 2021 14:11:33 -0300 Subject: [PATCH 16/32] Added unit test --- includes/class-wc-session-handler.php | 19 +++++++++++ .../class-wc-tests-session-handler.php | 34 ++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-session-handler.php b/includes/class-wc-session-handler.php index 1ba0095d3d0..e486d3666ff 100644 --- a/includes/class-wc-session-handler.php +++ b/includes/class-wc-session-handler.php @@ -187,6 +187,25 @@ class WC_Session_Handler extends WC_Session { return $customer_id; } + /** + * Get session unique ID for quests if session is initialized or user ID if logged in. + * Introduced to help with unit tests. + * + * @since 5.3.0 + * @return string + */ + public function get_customer_unique_id() { + $customer_id = ''; + + if ( $this->has_session() && $this->_customer_id ) { + $customer_id = $this->_customer_id; + } elseif ( is_user_logged_in() ) { + $customer_id = (string) get_current_user_id(); + } + + return $customer_id; + } + /** * Get the session cookie, if set. Otherwise return false. * diff --git a/tests/legacy/unit-tests/session/class-wc-tests-session-handler.php b/tests/legacy/unit-tests/session/class-wc-tests-session-handler.php index 4149f90fbb6..7af97b30165 100644 --- a/tests/legacy/unit-tests/session/class-wc-tests-session-handler.php +++ b/tests/legacy/unit-tests/session/class-wc-tests-session-handler.php @@ -10,6 +10,9 @@ */ class WC_Tests_Session_Handler extends WC_Unit_Test_Case { + /** + * Setup. + */ public function setUp() { parent::setUp(); @@ -17,6 +20,9 @@ class WC_Tests_Session_Handler extends WC_Unit_Test_Case { $this->create_session(); } + /** + * @testdox Test that save data should insert new row. + */ public function test_save_data_should_insert_new_row() { $current_session_data = $this->get_session_from_db( $this->session_key ); // delete session to make sure a new row is created in the DB. @@ -35,6 +41,9 @@ class WC_Tests_Session_Handler extends WC_Unit_Test_Case { $this->assertEquals( array( 'cart' => 'new cart' ), wp_cache_get( $this->cache_prefix . $this->session_key, WC_SESSION_CACHE_GROUP ) ); } + /** + * @testdox Test that save data should replace existing row. + */ public function test_save_data_should_replace_existing_row() { $current_session_data = $this->get_session_from_db( $this->session_key ); @@ -49,23 +58,35 @@ class WC_Tests_Session_Handler extends WC_Unit_Test_Case { $this->assertTrue( is_numeric( $updated_session_data->session_expiry ) ); } + /** + * @testdox Test that get_setting() should use cache. + */ public function test_get_session_should_use_cache() { $session = $this->handler->get_session( $this->session_key ); $this->assertEquals( array( 'cart' => 'fake cart' ), $session ); } + /** + * @testdox Test that get_setting() shouldn't use cache. + */ public function test_get_session_should_not_use_cache() { wp_cache_delete( $this->cache_prefix . $this->session_key, WC_SESSION_CACHE_GROUP ); $session = $this->handler->get_session( $this->session_key ); $this->assertEquals( array( 'cart' => 'fake cart' ), $session ); } + /** + * @testdox Test that get_setting() should return default value. + */ public function test_get_session_should_return_default_value() { $default_session = array( 'session' => 'default' ); $session = $this->handler->get_session( 'non-existent key', $default_session ); $this->assertEquals( $default_session, $session ); } + /** + * @testdox Test delete_session(). + */ public function test_delete_session() { global $wpdb; @@ -82,6 +103,9 @@ class WC_Tests_Session_Handler extends WC_Unit_Test_Case { $this->assertNull( $session_id ); } + /** + * @testdox Test update_session_timestamp(). + */ public function test_update_session_timestamp() { global $wpdb; @@ -98,6 +122,14 @@ class WC_Tests_Session_Handler extends WC_Unit_Test_Case { $this->assertEquals( $timestamp, $session_expiry ); } + /** + * @testdox Test that nonce of user logged out is only changed by WooCommerce. + */ + public function test_maybe_update_nonce_user_logged_out() { + $this->assertEquals( 1, $this->handler->maybe_update_nonce_user_logged_out( 1, 'wp_rest' ) ); + $this->assertEquals( $this->handler->get_customer_unique_id(), $this->handler->maybe_update_nonce_user_logged_out( 1, 'woocommerce-something' ) ); + } + /** * Helper function to create a WC session and save it to the DB. */ @@ -113,7 +145,7 @@ class WC_Tests_Session_Handler extends WC_Unit_Test_Case { /** * Helper function to get session data from DB. * - * @param string $session_key + * @param string $session_key Session key. * @return stdClass */ protected function get_session_from_db( $session_key ) { From f913dc09dda51a4be2beb410cb77c3f84e7cab29 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 30 Mar 2021 14:53:37 -0300 Subject: [PATCH 17/32] Improve get_customer_unique_id() --- includes/class-wc-session-handler.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/includes/class-wc-session-handler.php b/includes/class-wc-session-handler.php index e486d3666ff..4113e4147ce 100644 --- a/includes/class-wc-session-handler.php +++ b/includes/class-wc-session-handler.php @@ -195,11 +195,9 @@ class WC_Session_Handler extends WC_Session { * @return string */ public function get_customer_unique_id() { - $customer_id = ''; + $customer_id = $this->has_session() && $this->_customer_id ? $this->_customer_id : ''; - if ( $this->has_session() && $this->_customer_id ) { - $customer_id = $this->_customer_id; - } elseif ( is_user_logged_in() ) { + if ( is_user_logged_in() ) { $customer_id = (string) get_current_user_id(); } From 076248aca00f776398da642c740c950cb4fc832b Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 30 Mar 2021 18:17:12 -0300 Subject: [PATCH 18/32] Fixed logic of get_customer_unique_id --- includes/class-wc-session-handler.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-session-handler.php b/includes/class-wc-session-handler.php index 4113e4147ce..e486d3666ff 100644 --- a/includes/class-wc-session-handler.php +++ b/includes/class-wc-session-handler.php @@ -195,9 +195,11 @@ class WC_Session_Handler extends WC_Session { * @return string */ public function get_customer_unique_id() { - $customer_id = $this->has_session() && $this->_customer_id ? $this->_customer_id : ''; + $customer_id = ''; - if ( is_user_logged_in() ) { + if ( $this->has_session() && $this->_customer_id ) { + $customer_id = $this->_customer_id; + } elseif ( is_user_logged_in() ) { $customer_id = (string) get_current_user_id(); } From c9cd12f5e820ee301717cd97b8c2cf76e1a91b11 Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 31 Mar 2021 10:01:41 +0200 Subject: [PATCH 19/32] Fix flakiness in the test --- .../wp-admin-settings-shipping-zones.test.js | 2 +- tests/e2e/utils/src/components.js | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index 1a49f147158..5ffecc538b2 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -17,7 +17,7 @@ const { const config = require( 'config' ); const simpleProductPrice = config.has( 'products.simple.price' ) ? config.get( 'products.simple.price' ) : '9.99'; const simpleProductName = config.get( 'products.simple.name' ); -const california = 'California, United States (US)'; +const california = 'state:US:CA'; const sanFranciscoZIP = '94107'; const shippingZoneNameUS = 'US with Flat rate'; const shippingZoneNameFL = 'CA with Free shipping'; diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index cc6de0161fe..24c07fb7417 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -464,11 +464,11 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc * Adds a shipping zone along with a shipping method. * * @param zoneName Shipping zone name. - * @param zoneLocation Shiping zone location. Defaults to United States (US). + * @param zoneLocation Shiping zone location. Defaults to country:US. For states use: state:US:CA * @param zipCode Shipping zone zip code. Defaults to empty one space. * @param zoneMethod Shipping method type. Defaults to flat_rate (use also: free_shipping or local_pickup) */ -const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States (US)', zipCode = ' ', zoneMethod = 'flat_rate' ) => { +const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'country:US', zipCode = ' ', zoneMethod = 'flat_rate' ) => { await merchant.openNewShipping(); // Fill shipping zone name @@ -476,12 +476,7 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States await expect(page).toFill('input#zone_name', zoneName); // Select shipping zone location - // (.toSelect is not best option here because a lot of   are present in country/state names) - await expect(page).toFill('#zone_locations', zoneLocation); - await uiUnblocked(); - await page.keyboard.press('Tab'); - await uiUnblocked(); - await page.keyboard.press('Enter'); + await expect(page).toSelect('select[name="zone_locations"]', zoneLocation); // Fill shipping zone postcode if needed otherwise just put empty space await page.waitForSelector('a.wc-shipping-zone-postcodes-toggle'); @@ -491,14 +486,12 @@ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'United States await expect(page).toClick('button#submit'); // Add shipping zone method - await uiUnblocked(); + await page.waitFor(1000); await expect(page).toClick('button.wc-shipping-zone-add-method', {text:'Add shipping method'}); await page.waitForSelector('.wc-shipping-zone-method-selector'); await expect(page).toSelect('select[name="add_method_id"]', zoneMethod); - await uiUnblocked(); await expect(page).toClick('button#btn-ok'); await page.waitForSelector('#zone_locations'); - await uiUnblocked(); }; /** From 110c31d0786e0426ff3f50f1a7e3564525272ec5 Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 31 Mar 2021 10:36:47 +0200 Subject: [PATCH 20/32] Remove unnecessary postcode --- .../specs/merchant/wp-admin-settings-shipping-zones.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js index 5ffecc538b2..e7da3ffe377 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-settings-shipping-zones.test.js @@ -90,10 +90,6 @@ const runAddNewShippingZoneTest = () => { await selectOptionInSelect2('New York'); await expect(page).toClick('button[name="calc_shipping"]'); - // Set shipping postcode to 10010 - await clearAndFillInput('#calc_shipping_postcode', '10010'); - await expect(page).toClick('button[name="calc_shipping"]'); - // Verify shipping costs await page.waitForSelector('.order-total'); await expect(page).toMatchElement('.shipping .amount', {text: '$10.00'}); @@ -102,6 +98,7 @@ const runAddNewShippingZoneTest = () => { it('allows customer to benefit from a Free shipping if in CA', async () => { await page.reload(); + // Set shipping state to California await expect(page).toClick('a.shipping-calculator-button'); await expect(page).toClick('#select2-calc_shipping_state-container'); @@ -119,6 +116,7 @@ const runAddNewShippingZoneTest = () => { it('allows customer to benefit from a free Local pickup if in SF', async () => { await page.reload(); + // Set shipping postcode to 94107 await expect(page).toClick('a.shipping-calculator-button'); await clearAndFillInput('#calc_shipping_postcode', '94107'); From 8ff664e7583e01ff6aaf5ab6d7f8bdbfd6df3817 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 31 Mar 2021 12:09:20 +0200 Subject: [PATCH 21/32] Fix "doing wrong wpdb::prepare" notice for REST API get taxes endpoint. --- .../class-wc-rest-taxes-v1-controller.php | 15 ++++--- .../class-wc-rest-taxes-controller-tests.php | 45 +++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php index e6fd4926cdd..19ba9b67ff5 100644 --- a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php +++ b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php @@ -243,12 +243,17 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { LIMIT %%d, %%d "; + $wpdb_prepare_args = array( + $prepared_args['offset'], + $prepared_args['number'], + ); + // Filter by tax class. if ( empty( $prepared_args['class'] ) ) { - $class = null; $query = sprintf( $query, '' ); } else { $class = 'standard' !== $prepared_args['class'] ? sanitize_title( $prepared_args['class'] ) : ''; + array_unshift( $wpdb_prepare_args, $class ); $query = sprintf( $query, 'WHERE tax_rate_class = %s' ); } @@ -257,9 +262,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { $results = $wpdb->get_results( $wpdb->prepare( $query, - $prepared_args['offset'], - $prepared_args['number'], - $class + $wpdb_prepare_args ) ); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared @@ -282,9 +285,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { $wpdb->get_results( $wpdb->prepare( $query, - $prepared_args['offset'], - $prepared_args['number'], - $class + $wpdb_prepare_args ) ); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared diff --git a/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php b/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php index 6f3ae0e8556..e1cfd6062e9 100644 --- a/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php +++ b/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php @@ -238,4 +238,49 @@ class WC_REST_Taxes_Controller_Tests extends WC_REST_Unit_Test_Case { } $this->assertEquals( $expected, $ids ); } + + /** + * @testdox Tax rates can be queries filtering by tax class. + * + * @testWith ["standard"] + * ["reduced-rate"] + * ["zero-rate"] + * + * @param string $class The tax class name to try getting the taxes for. + */ + public function test_can_get_taxes_filtering_by_class( $class ) { + $classes = array( 'standard', 'reduced-rate', 'zero-rate' ); + + $tax_ids_by_class = array(); + foreach ( $classes as $class ) { + $tax_id = WC_Tax::_insert_tax_rate( + array( + 'tax_rate_country' => 'JP', + 'tax_rate' => '1', + 'tax_rate_priority' => 1, + 'tax_rate_name' => 'Fake Tax', + 'tax_rate_class' => $class, + ) + ); + $tax_ids_by_class[ $class ] = $tax_id; + } + + $response = $this->do_rest_get_request( + 'taxes', + array( + 'class' => $class, + ) + ); + + $this->assertEquals( 200, $response->get_status() ); + $data = array_values( $response->get_data() ); + $ids = array_map( + function( $item ) { + return $item['id']; + }, + $data + ); + + $this->assertEquals( array( $tax_ids_by_class[ $class ] ), $ids ); + } } From 64e903f65f289559df21c196408c01241f8290ad Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 31 Mar 2021 12:27:37 +0200 Subject: [PATCH 22/32] Update REST API v3 taxes endpoint descriptions with deprecation/introduction information. --- .../Version3/class-wc-rest-taxes-controller.php | 10 ++++++++-- .../Version3/class-wc-rest-taxes-controller-tests.php | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php index 8e1141a555a..6fef3703eb6 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php @@ -74,7 +74,7 @@ class WC_REST_Taxes_Controller extends WC_REST_Taxes_V2_Controller { $schema = parent::get_item_schema(); $schema['properties']['postcodes'] = array( - 'description' => __( 'List of postcodes / ZIPs.', 'woocommerce' ), + 'description' => __( 'List of postcodes / ZIPs. Introduced in WooCommerce 5.3.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', @@ -83,7 +83,7 @@ class WC_REST_Taxes_Controller extends WC_REST_Taxes_V2_Controller { ); $schema['properties']['cities'] = array( - 'description' => __( 'List of city names.', 'woocommerce' ), + 'description' => __( 'List of city names. Introduced in WooCommerce 5.3.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', @@ -91,6 +91,12 @@ class WC_REST_Taxes_Controller extends WC_REST_Taxes_V2_Controller { 'context' => array( 'view', 'edit' ), ); + $schema['properties']['postcode']['description'] = + __( "Postcode/ZIP, it doesn't support multiple values. Deprecated as of WooCommerce 5.3, 'postcodes' should be used instead.", 'woocommerce' ); + + $schema['properties']['city']['description'] = + __( "City name, it doesn't support multiple values. Deprecated as of WooCommerce 5.3, 'cities' should be used instead.", 'woocommerce' ); + return $schema; } diff --git a/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php b/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php index e1cfd6062e9..e991716cc93 100644 --- a/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php +++ b/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller-tests.php @@ -249,6 +249,8 @@ class WC_REST_Taxes_Controller_Tests extends WC_REST_Unit_Test_Case { * @param string $class The tax class name to try getting the taxes for. */ public function test_can_get_taxes_filtering_by_class( $class ) { + wp_set_current_user( $this->user ); + $classes = array( 'standard', 'reduced-rate', 'zero-rate' ); $tax_ids_by_class = array(); From 4e1b24044f9cf1e011017718ad0e29bf5ff11e86 Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 31 Mar 2021 13:59:00 +0200 Subject: [PATCH 23/32] Fix flakiness in the test --- .../merchant/wp-admin-order-searching.test.js | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index ce392d06303..95f93b288b6 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -20,32 +20,30 @@ const runOrderSearchingTest = () => { await merchant.login(); await createSimpleProduct('Wanted Product'); - await Promise.all([ - // Create new order for testing - await merchant.openNewOrder(), - await page.waitForSelector('#order_status'), - await page.click('#customer_user'), - await page.click('span.select2-search > input.select2-search__field'), - await page.type('span.select2-search > input.select2-search__field', 'Customer'), - await page.waitFor(2000), // to avoid flakyness - await page.keyboard.press('Enter'), - ]); + // Create new order for testing + await merchant.openNewOrder(); + await page.waitForSelector('#order_status'); + await page.click('#customer_user'); + await page.click('span.select2-search > input.select2-search__field'); + await page.type('span.select2-search > input.select2-search__field', 'Customer'); + await page.waitFor(2000); // to avoid flakyness + await page.keyboard.press('Enter'); - await Promise.all([ - // Change the shipping data - await page.waitFor(1000), // to avoid flakiness - await page.waitForSelector('#_shipping_first_name'), - await clearAndFillInput('#_shipping_first_name', 'Tim'), - await clearAndFillInput('#_shipping_last_name', 'Clark'), - await clearAndFillInput('#_shipping_address_1', 'Oxford Ave'), - await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'), - await clearAndFillInput('#_shipping_city', 'Buffalo'), - await clearAndFillInput('#_shipping_postcode', '14201'), - await page.keyboard.press('Tab'), - await page.keyboard.press('Tab'), - await page.keyboard.press('Enter'), - await page.select('select[name="_shipping_state"]', 'NY'), - ]); + // Change the shipping data + await page.waitFor(1000); // to avoid flakiness + await page.waitForSelector('#_shipping_first_name'); + await clearAndFillInput('#_shipping_first_name', 'Tim'); + await clearAndFillInput('#_shipping_last_name', 'Clark'); + await clearAndFillInput('#_shipping_address_1', 'Oxford Ave'); + await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); + await clearAndFillInput('#_shipping_city', 'Buffalo'); + await clearAndFillInput('#_shipping_postcode', '14201'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Enter'); + await page.select('select[name="_shipping_state"]', 'NY'); + // Select again in case it ignores above command, this is a workaround to avoid flakiness + await page.select('select[name="_shipping_state"]', 'NY'); // Get the post id const variablePostId = await page.$('#post_ID'); @@ -54,6 +52,7 @@ const runOrderSearchingTest = () => { // Save new order await clickUpdateOrder('Order updated.', true); await addProductToOrder(orderId, 'Wanted Product'); + await page.waitFor(1000); // to avoid flakiness await merchant.openAllOrdersView(); }); From b2dd7f90575623ecf9179ef07cba0a80a7aff67d Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 31 Mar 2021 16:06:07 +0200 Subject: [PATCH 24/32] Add type instead of select --- .../specs/merchant/wp-admin-order-searching.test.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index 95f93b288b6..da4ac6ff09f 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -6,7 +6,6 @@ const { merchant, clearAndFillInput, - selectOptionInSelect2, searchForOrder, createSimpleProduct, addProductToOrder, @@ -41,18 +40,18 @@ const runOrderSearchingTest = () => { await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); await page.keyboard.press('Enter'); - await page.select('select[name="_shipping_state"]', 'NY'); - // Select again in case it ignores above command, this is a workaround to avoid flakiness - await page.select('select[name="_shipping_state"]', 'NY'); + await page.type('input.select2-search__field', 'New York'); + await page.keyboard.press('Enter'); // Get the post id const variablePostId = await page.$('#post_ID'); orderId = (await(await variablePostId.getProperty('value')).jsonValue()); - // Save new order + // Save new order and add desired product to order await clickUpdateOrder('Order updated.', true); await addProductToOrder(orderId, 'Wanted Product'); - await page.waitFor(1000); // to avoid flakiness + + // Open All Orders view await merchant.openAllOrdersView(); }); From f882aba6706893b6e23669e0a83c6cf6584cb7f6 Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 31 Mar 2021 16:50:41 +0200 Subject: [PATCH 25/32] Another attempt to fix flakiness --- .../specs/merchant/wp-admin-order-searching.test.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index da4ac6ff09f..c4bf237cce9 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -37,11 +37,9 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); - await page.keyboard.press('Tab'); - await page.keyboard.press('Tab'); - await page.keyboard.press('Enter'); - await page.type('input.select2-search__field', 'New York'); - await page.keyboard.press('Enter'); + await page.waitFor(1000); // to avoid flakyness + await page.click('#select2-_shipping_state-container'); + await page.select('select[name="_shipping_state"]', 'NY'); // Get the post id const variablePostId = await page.$('#post_ID'); From 6cb79fb90da965c73a231b6ac987f1e480b2289c Mon Sep 17 00:00:00 2001 From: Veljko Date: Wed, 31 Mar 2021 17:59:09 +0200 Subject: [PATCH 26/32] Improve test and fix flakiness --- .../specs/merchant/wp-admin-order-searching.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js index c4bf237cce9..465d68583a7 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-searching.test.js @@ -30,6 +30,8 @@ const runOrderSearchingTest = () => { // Change the shipping data await page.waitFor(1000); // to avoid flakiness + await page.click('.billing-same-as-shipping'); + await page.keyboard.press('Enter'); await page.waitForSelector('#_shipping_first_name'); await clearAndFillInput('#_shipping_first_name', 'Tim'); await clearAndFillInput('#_shipping_last_name', 'Clark'); @@ -37,9 +39,6 @@ const runOrderSearchingTest = () => { await clearAndFillInput('#_shipping_address_2', 'Linwood Ave'); await clearAndFillInput('#_shipping_city', 'Buffalo'); await clearAndFillInput('#_shipping_postcode', '14201'); - await page.waitFor(1000); // to avoid flakyness - await page.click('#select2-_shipping_state-container'); - await page.select('select[name="_shipping_state"]', 'NY'); // Get the post id const variablePostId = await page.$('#post_ID'); @@ -122,7 +121,7 @@ const runOrderSearchingTest = () => { }) it('can search for order by shipping state name', async () => { - await searchForOrder('NY', orderId, 'John Doe'); + await searchForOrder('CA', orderId, 'John Doe'); }) it('can search for order by item name', async () => { From beb63625ac2aeb9afa43f531f013d3bac1ed1077 Mon Sep 17 00:00:00 2001 From: zhongruige Date: Wed, 31 Mar 2021 14:26:36 -0600 Subject: [PATCH 27/32] Use checkbox specific utils and verify it's checked before continuing --- tests/e2e/utils/src/components.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index cc6de0161fe..0d31b08d5b5 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -6,7 +6,14 @@ * Internal dependencies */ import { merchant } from './flows'; -import { clickTab, uiUnblocked, verifyCheckboxIsUnset, evalAndClick, selectOptionInSelect2, setCheckbox } from './page-utils'; +import { + clickTab, + uiUnblocked, + verifyCheckboxIsUnset, + selectOptionInSelect2, + setCheckbox, + unsetCheckbox +} from './page-utils'; import factories from './factories'; const config = require( 'config' ); @@ -143,7 +150,8 @@ const completeOnboardingWizard = async () => { await waitAndClickPrimary( false ); // Skip installing extensions - await evalAndClick( '.components-checkbox-control__input' ); + await unsetCheckbox( '.components-checkbox-control__input' ); + await verifyCheckboxIsUnset( '.components-checkbox-control__input' ); await waitAndClickPrimary(); // Theme section From 5ebab07677625e3919ce663075062a36c822d21d Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Wed, 31 Mar 2021 19:25:28 -0300 Subject: [PATCH 28/32] Fixed typo --- includes/class-wc-session-handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-session-handler.php b/includes/class-wc-session-handler.php index e486d3666ff..13771e21f31 100644 --- a/includes/class-wc-session-handler.php +++ b/includes/class-wc-session-handler.php @@ -188,7 +188,7 @@ class WC_Session_Handler extends WC_Session { } /** - * Get session unique ID for quests if session is initialized or user ID if logged in. + * Get session unique ID for requests if session is initialized or user ID if logged in. * Introduced to help with unit tests. * * @since 5.3.0 From 6540b804b7e8530c10744a7a0950f9cc3d675be7 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Wed, 31 Mar 2021 19:29:26 -0300 Subject: [PATCH 29/32] Added deprecated notice to nonce_user_logged_out --- includes/class-wc-session-handler.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/class-wc-session-handler.php b/includes/class-wc-session-handler.php index 13771e21f31..0c6dda12484 100644 --- a/includes/class-wc-session-handler.php +++ b/includes/class-wc-session-handler.php @@ -312,6 +312,8 @@ class WC_Session_Handler extends WC_Session { * @return int|string */ public function nonce_user_logged_out( $uid ) { + wc_deprecated_function( 'WC_Session_Handler::nonce_user_logged_out', '5.3', 'WC_Session_Handler::maybe_update_nonce_user_logged_out' ); + return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid; } From 1676c0d8a314a7b23023bbf067ca216a14d48ac8 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 1 Apr 2021 15:38:53 +0200 Subject: [PATCH 30/32] Added new e2e test shopper create account --- tests/e2e/core-tests/CHANGELOG.md | 4 ++ tests/e2e/core-tests/README.md | 1 + tests/e2e/core-tests/specs/index.js | 3 ++ ...ront-end-my-account-create-account.test.js | 41 +++++++++++++++++++ .../test-my-account-create-account.js | 6 +++ 5 files changed, 55 insertions(+) create mode 100644 tests/e2e/core-tests/specs/shopper/front-end-my-account-create-account.test.js create mode 100644 tests/e2e/specs/front-end/test-my-account-create-account.js diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 098aa07caff..614f8691dde 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -2,6 +2,10 @@ # 0.1.3 +## Added + +- Shopper My Account Create Account + ## Fixed - removed use of ES6 `import` diff --git a/tests/e2e/core-tests/README.md b/tests/e2e/core-tests/README.md index b85cf9d56db..c6b162afa1f 100644 --- a/tests/e2e/core-tests/README.md +++ b/tests/e2e/core-tests/README.md @@ -77,6 +77,7 @@ The functions to access the core tests are: - `runSingleProductPageTest` - Shopper can view single product page in many variations (simple, variable, grouped) - `runVariableProductUpdateTest` - Shopper can view and update variations on a variable product - `runCheckoutCreateAccountTest` - Shopper can create an account during checkout + - `runMyAccountCreateAccountTest` - Shopper can create an account via my account page ### REST API diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 3f133ba80ae..6f5725ae2e0 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -16,6 +16,7 @@ const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupo const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.test' ); +const runMyAccountCreateAccountTest = require( './shopper/front-end-my-account-create-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); const runVariableProductUpdateTest = require( './shopper/front-end-variable-product-updates.test' ); const runCheckoutCreateAccountTest = require( './shopper/front-end-checkout-create-account.test' ); @@ -60,6 +61,7 @@ const runShopperTests = () => { runCheckoutPageTest(); runMyAccountPageTest(); runMyAccountPayOrderTest(); + runMyAccountCreateAccountTest(); runSingleProductPageTest(); runVariableProductUpdateTest(); runCheckoutCreateAccountTest(); @@ -133,4 +135,5 @@ module.exports = { runApiTests, runAnalyticsPageLoadsTest, runCheckoutCreateAccountTest, + runMyAccountCreateAccountTest, }; diff --git a/tests/e2e/core-tests/specs/shopper/front-end-my-account-create-account.test.js b/tests/e2e/core-tests/specs/shopper/front-end-my-account-create-account.test.js new file mode 100644 index 00000000000..ae685b3f6e4 --- /dev/null +++ b/tests/e2e/core-tests/specs/shopper/front-end-my-account-create-account.test.js @@ -0,0 +1,41 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests */ +/** + * Internal dependencies + */ +const { + shopper, + merchant, + setCheckbox, + settingsPageSaveChanges, +} = require( '@woocommerce/e2e-utils' ); + +const runMyAccountCreateAccountTest = () => { + describe('Shopper My Account Create Account', () => { + beforeAll(async () => { + await merchant.login(); + + // Set checkbox in the settings to enable registration in my account + await merchant.openSettings('account'); + await setCheckbox('#woocommerce_enable_myaccount_registration'); + await settingsPageSaveChanges(); + + await merchant.logout(); + }); + + it('can create a new account via my account', async () => { + await shopper.gotoMyAccount(); + await page.waitForSelector('.woocommerce-form-register'); + await expect(page).toFill('input#reg_email', 'john.doe.test@example.com'); + await expect(page).toClick('button[name="register"]'); + await page.waitForNavigation({waitUntil: 'networkidle0'}); + await expect(page).toMatchElement('h1', 'My account'); + + // Verify user has been created successfully + await merchant.login(); + await merchant.openAllUsersView(); + await expect(page).toMatchElement('td.email.column-email > a', {text: 'john.doe.test@example.com'}); + }); + }); +}; + +module.exports = runMyAccountCreateAccountTest; diff --git a/tests/e2e/specs/front-end/test-my-account-create-account.js b/tests/e2e/specs/front-end/test-my-account-create-account.js new file mode 100644 index 00000000000..cfcbfdc780b --- /dev/null +++ b/tests/e2e/specs/front-end/test-my-account-create-account.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runMyAccountCreateAccountTest } = require( '@woocommerce/e2e-core-tests' ); + +runMyAccountCreateAccountTest(); From 29f47f22897f6a37fd936bb4108c40aea9fda550 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 1 Apr 2021 16:24:26 +0200 Subject: [PATCH 31/32] Fixed spaces in the code --- ...ront-end-my-account-create-account.test.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-my-account-create-account.test.js b/tests/e2e/core-tests/specs/shopper/front-end-my-account-create-account.test.js index ae685b3f6e4..c48e9cd9e9f 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-my-account-create-account.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-my-account-create-account.test.js @@ -14,26 +14,26 @@ const runMyAccountCreateAccountTest = () => { beforeAll(async () => { await merchant.login(); - // Set checkbox in the settings to enable registration in my account + // Set checkbox in the settings to enable registration in my account await merchant.openSettings('account'); await setCheckbox('#woocommerce_enable_myaccount_registration'); await settingsPageSaveChanges(); - + await merchant.logout(); }); it('can create a new account via my account', async () => { - await shopper.gotoMyAccount(); + await shopper.gotoMyAccount(); await page.waitForSelector('.woocommerce-form-register'); - await expect(page).toFill('input#reg_email', 'john.doe.test@example.com'); - await expect(page).toClick('button[name="register"]'); - await page.waitForNavigation({waitUntil: 'networkidle0'}); - await expect(page).toMatchElement('h1', 'My account'); + await expect(page).toFill('input#reg_email', 'john.doe.test@example.com'); + await expect(page).toClick('button[name="register"]'); + await page.waitForNavigation({waitUntil: 'networkidle0'}); + await expect(page).toMatchElement('h1', 'My account'); - // Verify user has been created successfully - await merchant.login(); - await merchant.openAllUsersView(); - await expect(page).toMatchElement('td.email.column-email > a', {text: 'john.doe.test@example.com'}); + // Verify user has been created successfully + await merchant.login(); + await merchant.openAllUsersView(); + await expect(page).toMatchElement('td.email.column-email > a', {text: 'john.doe.test@example.com'}); }); }); }; From 30960140571b5156da5fdaa2ea9e534ddb1feecf Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Thu, 1 Apr 2021 14:54:59 -0300 Subject: [PATCH 32/32] fix typo in stalebot parameter name --- .github/workflows/stalebot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stalebot.yml b/.github/workflows/stalebot.yml index 0c91b9a5410..b52a926ea49 100644 --- a/.github/workflows/stalebot.yml +++ b/.github/workflows/stalebot.yml @@ -15,6 +15,6 @@ jobs: days-before-issue-stale: 7 days-before-issue-close: 7 days-before-pr-close: -1 - only-issue-label: 'needs feedback' + only-issue-labels: 'needs feedback' close-issue-label: "category: can't reproduce" debug-only: true