Merge branch 'trunk' into e2e/e2e-merchant-analytics-pagechecks

This commit is contained in:
Veljko V 2021-03-30 18:19:21 +02:00 committed by GitHub
commit a8ab058fb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 734 additions and 176 deletions

View File

@ -35,3 +35,9 @@ Support requests in issues on this repository will be closed on sight.
## Contributing to WooCommerce
If you have a patch or have stumbled upon an issue with WooCommerce core, you can contribute this back to the code. Please read our [contributor guidelines](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md) for more information how you can do this.
<p align="center">
<br/><br/>
Made with 💜 by <a href="https://woocommerce.com/">WooCommerce</a>.<br/>
<a href="https://woocommerce.com/careers/">We're hiring</a>! Come work with us!
</p>

View File

@ -348,6 +348,12 @@ a.button {
.woocommerce,
.woocommerce-page {
&.is-dark-theme {
.select2-dropdown {
color: var(--global--color-dark-gray);
}
}
table.shop_table {
td,
@ -1312,6 +1318,30 @@ a.reset_variations {
}
}
&.woocommerce-lost-password {
.woocommerce {
max-width: var(--responsive--alignwide-width) !important;
padding: 0 !important;
flex-wrap: wrap;
.woocommerce-notices-wrapper {
flex: 1 0 100%;
}
.woocommerce-ResetPassword {
.woocommerce-form-row--first {
float: none;
}
#user_login {
margin-bottom: 10px;
}
}
}
}
table.account-orders-table {
margin-top: 0;
border: 0;

View File

@ -2474,6 +2474,12 @@ a.reset_variations {
margin: 1.5rem 0;
}
}
.woocommerce-ResetPassword {
.woocommerce-form-row--first {
float: none;
}
}
}
/**

View File

@ -62,6 +62,9 @@
shippingMethod.trigger( 'change:methods' );
shippingMethod.changes = {};
shippingMethod.trigger( 'saved:methods' );
// Overrides the onbeforeunload callback added by settings.js.
window.onbeforeunload = null;
} else {
window.alert( data.strings.save_failed );
}

View File

@ -57,6 +57,28 @@ jQuery( function( $ ) {
$node.removeClass( 'processing' ).unblock();
};
/**
* Removes duplicate notices.
*
* @param {JQuery Object} notices
*/
var remove_duplicate_notices = function( notices ) {
var seen = [];
var new_notices = notices;
notices.each( function( index ) {
var text = $( this ).text();
if ( 'undefined' === typeof seen[ text ] ) {
seen[ text ] = true;
} else {
new_notices.splice( index, 1 );
}
} );
return new_notices;
};
/**
* Update the .woocommerce div with a string of html.
*
@ -67,7 +89,7 @@ jQuery( function( $ ) {
var $html = $.parseHTML( html_str );
var $new_form = $( '.woocommerce-cart-form', $html );
var $new_totals = $( '.cart_totals', $html );
var $notices = $( '.woocommerce-error, .woocommerce-message, .woocommerce-info', $html );
var $notices = remove_duplicate_notices( $( '.woocommerce-error, .woocommerce-message, .woocommerce-info', $html ) );
// No form, cannot do this.
if ( $( '.woocommerce-cart-form' ).length === 0 ) {

View File

@ -1,5 +1,15 @@
== Changelog ==
= 5.2.0 RC 2021-03-30 =
* Update - WooCommerce Admin package 2.1.4. #29520
* Fix - Don't remove existing coupons from order when an invalid REST API request for updating coupons is submitted. #29474
* Fix - Wrong logic for including or excluding the payments step in the list of completed tasks in the onboarding wizard. #29518
**WooCommerce Admin - 2.1.4**
* Fix - Adding New Zealand and Ireland to selective bundle option, previously missed. #6649
= 5.2.0 beta 2021-03-23 =
**WooCommerce**
@ -42,6 +52,8 @@
* Fix - add validation of the posted country codes on checkout. #28849
* Fix - Correctly display pagination arrows on RTL languages. #28523
* Fix - Invalid refund amount error on $0 refund when number of decimals is equal to 0. #27277
* Fix - "Sale" badge misaligned on products when displaying 1 item per row. #29425
* Fix - Revert a replacement of wp_redirect to wp_safe_redirect in WC_Checkout::process_order_payment that caused issues in the default PayPal interface. #29459
* Tweak - Added the Mercado Pago logo into the assets/images folder in order to use it in the payments setup task. #29365
* Tweak - Update the contributor guidelines. #29150
* Tweak - Introduced phone number input validation. #27242
@ -1342,6 +1354,7 @@
* Fix - Don't show duplicated update notifications on Woo Screens. #25828
* Fix - Escape MaxMind database URL. #25682
* Fix - System status report should correctly identify inactive package. #25830
* Fix - "Sale" badge misaligned on products when displaying 1 item per row. #29425
* Dev - Added support for placeholder attribute in quantity inputs. #25418
* Dev - Added `tax_status` and `tax_class` columns to the product meta data lookup table. #25428
* Dev - Introduced `woocommerce_top_rated_widget_args` filter. #25320

View File

@ -21,7 +21,7 @@
"pelago/emogrifier": "3.1.0",
"psr/container": "1.0.0",
"woocommerce/action-scheduler": "3.1.6",
"woocommerce/woocommerce-admin": "2.1.3",
"woocommerce/woocommerce-admin": "2.1.4",
"woocommerce/woocommerce-blocks": "4.7.0"
},
"require-dev": {

14
composer.lock generated
View File

@ -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": "dc5e21e29d4fb70bba776d20112c74f0",
"content-hash": "d0a0153dda851ca2c8e79bed1813ba5f",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@ -523,16 +523,16 @@
},
{
"name": "woocommerce/woocommerce-admin",
"version": "2.1.3",
"version": "2.1.4",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "60f4297838569341ae88738a4a8a8090889faaac"
"reference": "f992b8c8664e72b00ee7283ba1d34e74e4b67ab0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/60f4297838569341ae88738a4a8a8090889faaac",
"reference": "60f4297838569341ae88738a4a8a8090889faaac",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/f992b8c8664e72b00ee7283ba1d34e74e4b67ab0",
"reference": "f992b8c8664e72b00ee7283ba1d34e74e4b67ab0",
"shasum": ""
},
"require": {
@ -566,9 +566,9 @@
"homepage": "https://github.com/woocommerce/woocommerce-admin",
"support": {
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.1.3"
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.1.4"
},
"time": "2021-03-15T04:42:40+00:00"
"time": "2021-03-29T11:59:33+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",

View File

@ -178,7 +178,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) :
}
// payments can't be used when woocommerce-payments exists and country is US.
if ( $is_woo_payment_installed || 'US' === $country ) {
if ( $is_woo_payment_installed && 'US' === $country ) {
unset( $this->tasks['payments'] );
}

View File

@ -2,14 +2,12 @@
/**
* WooCommerce Settings Page/Tab
*
* @author WooThemes
* @category Admin
* @package WooCommerce\Admin
* @version 2.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
exit; // Exit if accessed directly.
}
if ( ! class_exists( 'WC_Settings_Page', false ) ) :
@ -66,7 +64,7 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
/**
* Add this page to settings.
*
* @param array $pages
* @param array $pages The pages array to add this page to.
*
* @return mixed
*/
@ -102,7 +100,7 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
$sections = $this->get_sections();
if ( empty( $sections ) || 1 === sizeof( $sections ) ) {
if ( empty( $sections ) || 1 === count( $sections ) ) {
return;
}
@ -111,7 +109,8 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
$array_keys = array_keys( $sections );
foreach ( $sections as $id => $label ) {
echo '<li><a href="' . admin_url( 'admin.php?page=wc-settings&tab=' . $this->id . '&section=' . sanitize_title( $id ) ) . '" class="' . ( $current_section == $id ? 'current' : '' ) . '">' . $label . '</a> ' . ( end( $array_keys ) == $id ? '' : '|' ) . ' </li>';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<li><a href="' . admin_url( 'admin.php?page=wc-settings&tab=' . $this->id . '&section=' . sanitize_title( $id ) ) . '" class="' . ( $current_section === $id ? 'current' : '' ) . '">' . esc_html( $label ) . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>';
}
echo '</ul><br class="clear" />';

View File

@ -2,8 +2,6 @@
/**
* WooCommerce Tax Settings
*
* @author WooThemes
* @category Admin
* @package WooCommerce\Admin
* @version 2.1.0
*/
@ -66,6 +64,7 @@ class WC_Settings_Tax extends WC_Settings_Page {
$tax_classes = WC_Tax::get_tax_classes();
foreach ( $tax_classes as $class ) {
/* translators: $s tax rate section name */
$sections[ sanitize_title( $class ) ] = sprintf( __( '%s rates', 'woocommerce' ), $class );
}
@ -95,7 +94,7 @@ class WC_Settings_Tax extends WC_Settings_Page {
$tax_classes = WC_Tax::get_tax_class_slugs();
if ( 'standard' === $current_section || in_array( $current_section, $tax_classes, true ) ) {
if ( 'standard' === $current_section || in_array( $current_section, array_filter( $tax_classes ), true ) ) {
$this->output_tax_rates();
} else {
$settings = $this->get_settings();
@ -149,7 +148,19 @@ class WC_Settings_Tax extends WC_Settings_Page {
}
foreach ( $added as $name ) {
WC_Tax::create_tax_class( $name );
$tax_class = WC_Tax::create_tax_class( $name );
// Display any error that could be triggered while creating tax classes.
if ( is_wp_error( $tax_class ) ) {
WC_Admin_Settings::add_error(
sprintf(
/* translators: 1: tax class name 2: error message */
esc_html__( 'Additional tax class "%1$s" couldn\'t be saved. %2$s.', 'woocommerce' ),
esc_html( $name ),
$tax_class->get_error_message()
)
);
}
}
return null;
@ -201,6 +212,7 @@ class WC_Settings_Tax extends WC_Settings_Page {
'wc_tax_nonce' => wp_create_nonce( 'wc_tax_nonce-class:' . $current_class ),
'base_url' => $base_url,
'rates' => array_values( WC_Tax::get_rates_for_tax_class( $current_class ) ),
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
'page' => ! empty( $_GET['p'] ) ? absint( $_GET['p'] ) : 1,
'limit' => 100,
'countries' => $countries,
@ -278,6 +290,7 @@ class WC_Settings_Tax extends WC_Settings_Page {
'tax_rate_priority',
);
// phpcs:disable WordPress.Security.NonceVerification.Missing
foreach ( $tax_rate_keys as $tax_rate_key ) {
if ( isset( $_POST[ $tax_rate_key ], $_POST[ $tax_rate_key ][ $key ] ) ) {
$tax_rate[ $tax_rate_key ] = wc_clean( wp_unslash( $_POST[ $tax_rate_key ][ $key ] ) );
@ -288,6 +301,7 @@ class WC_Settings_Tax extends WC_Settings_Page {
$tax_rate['tax_rate_shipping'] = isset( $_POST['tax_rate_shipping'][ $key ] ) ? 1 : 0;
$tax_rate['tax_rate_order'] = $order;
$tax_rate['tax_rate_class'] = $class;
// phpcs:enable WordPress.Security.NonceVerification.Missing
return $tax_rate;
}
@ -298,7 +312,8 @@ class WC_Settings_Tax extends WC_Settings_Page {
public function save_tax_rates() {
global $wpdb;
$current_class = sanitize_title( $this->get_current_tax_class() );
$current_class = sanitize_title( $this->get_current_tax_class() );
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Missing
$posted_countries = wc_clean( wp_unslash( $_POST['tax_rate_country'] ) );
// get the tax rate id of the first submited row.
@ -310,13 +325,14 @@ class WC_Settings_Tax extends WC_Settings_Page {
$index = isset( $tax_rate_order ) ? $tax_rate_order : 0;
// Loop posted fields.
// phpcs:disable WordPress.Security.NonceVerification.Missing
foreach ( $posted_countries as $key => $value ) {
$mode = ( 0 === strpos( $key, 'new-' ) ) ? 'insert' : 'update';
$tax_rate = $this->get_posted_tax_rate( $key, $index ++, $current_class );
if ( 'insert' === $mode ) {
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
} elseif ( 1 === absint( $_POST['remove_tax_rate'][ $key ] ) ) {
} elseif ( isset( $_POST['remove_tax_rate'][ $key ] ) && 1 === absint( $_POST['remove_tax_rate'][ $key ] ) ) {
$tax_rate_id = absint( $key );
WC_Tax::_delete_tax_rate( $tax_rate_id );
continue;
@ -332,6 +348,7 @@ class WC_Settings_Tax extends WC_Settings_Page {
WC_Tax::_update_tax_rate_cities( $tax_rate_id, wc_clean( wp_unslash( $_POST['tax_rate_city'][ $key ] ) ) );
}
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
}

View File

@ -795,10 +795,14 @@ class WC_AJAX {
$loop = intval( $_POST['loop'] );
$file_counter = 0;
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
$files = $product->get_downloads();
foreach ( $items as $item ) {
$product = $item->get_product();
if ( ! in_array( $product->get_id(), $product_ids, true ) ) {
continue;
}
$files = $product->get_downloads();
if ( ! $order->get_billing_email() ) {
wp_die();
@ -806,7 +810,7 @@ class WC_AJAX {
if ( ! empty( $files ) ) {
foreach ( $files as $download_id => $file ) {
$inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order );
$inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order, $item->get_quantity(), $item );
if ( $inserted_id ) {
$download = new WC_Customer_Download( $inserted_id );
$loop ++;

View File

@ -51,6 +51,9 @@ class WC_Comments {
// Set comment type.
add_action( 'preprocess_comment', array( __CLASS__, 'update_comment_type' ), 1 );
// Validate product reviews if requires verified owners.
add_action( 'pre_comment_on_post', array( __CLASS__, 'validate_product_review_verified_owners' ) );
}
/**
@ -444,6 +447,36 @@ class WC_Comments {
return $comment_data;
}
/**
* Validate product reviews if requires a verified owner.
*
* @param int $comment_post_id Post ID.
*/
public static function validate_product_review_verified_owners( $comment_post_id ) {
// Only validate if option is enabled.
if ( 'yes' !== get_option( 'woocommerce_review_rating_verification_required' ) ) {
return;
}
// Validate only products.
if ( 'product' !== get_post_type( $comment_post_id ) ) {
return;
}
// Skip if is a verified owner.
if ( wc_customer_bought_product( '', get_current_user_id(), $comment_post_id ) ) {
return;
}
wp_die(
esc_html__( 'Only logged in customers who have purchased this product may leave a review.', 'woocommerce' ),
esc_html__( 'Reviews can only be left by "verified owners"', 'woocommerce' ),
array(
'code' => 403,
)
);
}
/**
* Determines if a comment is of the default type.
*

View File

@ -198,7 +198,7 @@ class WC_Structured_Data {
$markup = array(
'@type' => 'Product',
'@id' => $permalink . '#product', // Append '#product' to differentiate between this @id and the @id generated for the Breadcrumblist.
'name' => $product->get_name(),
'name' => wp_kses_post( $product->get_name() ),
'url' => $permalink,
'description' => wp_strip_all_tags( do_shortcode( $product->get_short_description() ? $product->get_short_description() : $product->get_description() ) ),
);
@ -477,7 +477,7 @@ class WC_Structured_Data {
),
'itemOffered' => array(
'@type' => 'Product',
'name' => apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, $is_visible ),
'name' => wp_kses_post( apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, $is_visible ) ),
'sku' => $product_exists ? $product->get_sku() : '',
'image' => $product_exists ? wp_get_attachment_image_url( $product->get_image_id() ) : '',
'url' => $is_visible ? get_permalink( $product->get_id() ) : get_home_url(),

View File

@ -815,6 +815,7 @@ class WC_Tax {
$existing = self::get_tax_classes();
$existing_slugs = self::get_tax_class_slugs();
$name = wc_clean( $name );
if ( in_array( $name, $existing, true ) ) {
return new WP_Error( 'tax_class_exists', __( 'Tax class already exists', 'woocommerce' ) );
@ -824,6 +825,11 @@ class WC_Tax {
$slug = sanitize_title( $name );
}
// Stop if there's no slug.
if ( ! $slug ) {
return new WP_Error( 'tax_class_slug_invalid', __( 'Tax class slug is invalid', 'woocommerce' ) );
}
if ( in_array( $slug, $existing_slugs, true ) ) {
return new WP_Error( 'tax_class_slug_exists', __( 'Tax class slug already exists', 'woocommerce' ) );
}

View File

@ -34,31 +34,58 @@ class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller {
* @return bool
*/
protected function calculate_coupons( $request, $order ) {
if ( ! isset( $request['coupon_lines'] ) || ! is_array( $request['coupon_lines'] ) ) {
if ( ! isset( $request['coupon_lines'] ) ) {
return false;
}
// Remove all coupons first to ensure calculation is correct.
foreach ( $order->get_items( 'coupon' ) as $coupon ) {
$order->remove_coupon( $coupon->get_code() );
}
// Validate input and at the same time store the processed coupon codes to apply.
$coupon_codes = array();
$discounts = new WC_Discounts( $order );
$current_order_coupons = array_values( $order->get_coupons() );
$current_order_coupon_codes = array_map(
function( $coupon ) {
return $coupon->get_code();
},
$current_order_coupons
);
foreach ( $request['coupon_lines'] as $item ) {
if ( is_array( $item ) ) {
if ( ! empty( $item['id'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_coupon_item_id_readonly', __( 'Coupon item ID is readonly.', 'woocommerce' ), 400 );
}
if ( ! empty( $item['id'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_coupon_item_id_readonly', __( 'Coupon item ID is readonly.', 'woocommerce' ), 400 );
}
if ( empty( $item['code'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
}
if ( empty( $item['code'] ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
}
$results = $order->apply_coupon( wc_clean( $item['code'] ) );
$coupon_code = wc_format_coupon_code( wc_clean( $item['code'] ) );
$coupon = new WC_Coupon( $coupon_code );
if ( is_wp_error( $results ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_' . $results->get_error_code(), $results->get_error_message(), 400 );
// Skip check if the coupon is already applied to the order, as this could wrongly throw an error for single-use coupons.
if ( ! in_array( $coupon_code, $current_order_coupon_codes, true ) ) {
$check_result = $discounts->is_coupon_valid( $coupon );
if ( is_wp_error( $check_result ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_' . $check_result->get_error_code(), $check_result->get_error_message(), 400 );
}
}
$coupon_codes[] = $coupon_code;
}
// Remove all coupons first to ensure calculation is correct.
foreach ( $order->get_items( 'coupon' ) as $existing_coupon ) {
$order->remove_coupon( $existing_coupon->get_code() );
}
// Apply the coupons.
foreach ( $coupon_codes as $new_coupon ) {
$results = $order->apply_coupon( $new_coupon );
if ( is_wp_error( $results ) ) {
throw new WC_REST_Exception( 'woocommerce_rest_' . $results->get_error_code(), $results->get_error_message(), 400 );
}
}
return true;

View File

@ -366,9 +366,10 @@ function wc_orders_count( $status ) {
* @param int|WC_Product $product Product instance or ID.
* @param WC_Order $order Order data.
* @param int $qty Quantity purchased.
* @param WC_Order_Item $item Item of the order.
* @return int|bool insert id or false on failure.
*/
function wc_downloadable_file_permission( $download_id, $product, $order, $qty = 1 ) {
function wc_downloadable_file_permission( $download_id, $product, $order, $qty = 1, $item = null ) {
if ( is_numeric( $product ) ) {
$product = wc_get_product( $product );
}
@ -390,7 +391,7 @@ function wc_downloadable_file_permission( $download_id, $product, $order, $qty =
$download->set_access_expires( strtotime( $from_date . ' + ' . $expiry . ' DAY' ) );
}
$download = apply_filters( 'woocommerce_downloadable_file_permission', $download, $product, $order, $qty );
$download = apply_filters( 'woocommerce_downloadable_file_permission', $download, $product, $order, $qty, $item );
return $download->save();
}
@ -420,7 +421,7 @@ function wc_downloadable_product_permissions( $order_id, $force = false ) {
$downloads = $product->get_downloads();
foreach ( array_keys( $downloads ) as $download_id ) {
wc_downloadable_file_permission( $download_id, $product, $order, $item->get_quantity() );
wc_downloadable_file_permission( $download_id, $product, $order, $item->get_quantity(), $item );
}
}
}

View File

@ -1,5 +1,5 @@
=== WooCommerce ===
Contributors: automattic, mikejolley, jameskoster, claudiosanches, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, wpmuguru, royho
Contributors: automattic, mikejolley, jameskoster, claudiosanches, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, wpmuguru, royho, barryhughes-1
Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, downloads, payments, paypal, storefront, stripe, woo commerce
Requires at least: 5.5
Tested up to: 5.7
@ -160,6 +160,16 @@ WooCommerce comes with some sample data you can use to see how products look; im
== Changelog ==
= 5.2.0 RC 2021-03-30 =
* Update - WooCommerce Admin package 2.1.4. #29520
* Fix - Don't remove existing coupons from order when an invalid REST API request for updating coupons is submitted. #29474
* Fix - Wrong logic for including or excluding the payments step in the list of completed tasks in the onboarding wizard. #29518
**WooCommerce Admin - 2.1.4**
* Fix - Adding New Zealand and Ireland to selective bundle option, previously missed. #6649
= 5.2.0 beta 2021-03-23 =
**WooCommerce**
@ -202,6 +212,8 @@ WooCommerce comes with some sample data you can use to see how products look; im
* Fix - add validation of the posted country codes on checkout. #28849
* Fix - Correctly display pagination arrows on RTL languages. #28523
* Fix - Invalid refund amount error on $0 refund when number of decimals is equal to 0. #27277
* Fix - Revert a replacement of wp_redirect to wp_safe_redirect in WC_Checkout::process_order_payment that caused issues in the default PayPal interface. #29459
* Fix - "Sale" badge misaligned on products when displaying 1 item per row. #29425
* Tweak - Added the Mercado Pago logo into the assets/images folder in order to use it in the payments setup task. #29365
* Tweak - Update the contributor guidelines. #29150
* Tweak - Introduced phone number input validation. #27242

View File

@ -14,7 +14,7 @@
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates
* @version 3.7.0
* @version 5.2.0
*/
defined( 'ABSPATH' ) || exit;
@ -53,10 +53,10 @@ do_action( 'woocommerce_before_mini_cart' ); ?>
);
?>
<?php if ( empty( $product_permalink ) ) : ?>
<?php echo $thumbnail . $product_name; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo $thumbnail . wp_kses_post( $product_name ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php else : ?>
<a href="<?php echo esc_url( $product_permalink ); ?>">
<?php echo $thumbnail . $product_name; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo $thumbnail . wp_kses_post( $product_name ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</a>
<?php endif; ?>
<?php echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>

View File

@ -12,7 +12,7 @@
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates
* @version 3.4.0
* @version 5.2.0
*/
defined( 'ABSPATH' ) || exit;
@ -40,7 +40,7 @@ $totals = $order->get_order_item_totals(); // phpcs:ignore WordPress.WP.GlobalVa
<tr class="<?php echo esc_attr( apply_filters( 'woocommerce_order_item_class', 'order_item', $item, $order ) ); ?>">
<td class="product-name">
<?php
echo apply_filters( 'woocommerce_order_item_name', esc_html( $item->get_name() ), $item, false ); // @codingStandardsIgnoreLine
echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ) );
do_action( 'woocommerce_order_item_meta_start', $item_id, $item, $order, false );

View File

@ -12,7 +12,7 @@
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates
* @version 3.8.0
* @version 5.2.0
*/
defined( 'ABSPATH' ) || exit;
@ -35,7 +35,7 @@ defined( 'ABSPATH' ) || exit;
?>
<tr class="<?php echo esc_attr( apply_filters( 'woocommerce_cart_item_class', 'cart_item', $cart_item, $cart_item_key ) ); ?>">
<td class="product-name">
<?php echo apply_filters( 'woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key ) . '&nbsp;'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key ) ) . '&nbsp;'; ?>
<?php echo apply_filters( 'woocommerce_checkout_cart_item_quantity', ' <strong class="product-quantity">' . sprintf( '&times;&nbsp;%s', $cart_item['quantity'] ) . '</strong>', $cart_item, $cart_item_key ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo wc_get_formatted_cart_item_data( $cart_item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</td>

View File

@ -21,14 +21,27 @@ defined( 'ABSPATH' ) || exit;
<li>
<?php do_action( 'woocommerce_widget_product_review_item_start', $args ); ?>
<?php
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>">
<?php echo $product->get_image(); ?>
<span class="product-title"><?php echo $product->get_name(); ?></span>
<span class="product-title"><?php echo wp_kses_post( $product->get_name() ); ?></span>
</a>
<?php echo wc_get_rating_html( intval( get_comment_meta( $comment->comment_ID, 'rating', true ) ) ); ?>
<span class="reviewer"><?php echo sprintf( esc_html__( 'by %s', 'woocommerce' ), get_comment_author( $comment->comment_ID ) ); ?></span>
<span class="reviewer">
<?php
/* translators: %s: Comment author. */
echo sprintf( esc_html__( 'by %s', 'woocommerce' ), get_comment_author( $comment->comment_ID ) );
?>
</span>
<?php
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
?>
<?php do_action( 'woocommerce_widget_product_review_item_end', $args ); ?>
</li>

View File

@ -12,11 +12,11 @@
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates\Emails\Plain
* @version 3.7.0
* @version 5.2.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
exit; // Exit if accessed directly.
}
foreach ( $items as $item_id => $item ) :
@ -30,15 +30,18 @@ foreach ( $items as $item_id => $item ) :
$purchase_note = $product->get_purchase_note();
}
echo apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false );
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ) );
if ( $show_sku && $sku ) {
echo ' (#' . $sku . ')';
}
echo ' X ' . apply_filters( 'woocommerce_email_order_item_quantity', $item->get_quantity(), $item );
echo ' = ' . $order->get_formatted_line_subtotal( $item ) . "\n";
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
// allow other plugins to add additional product information here
// allow other plugins to add additional product information here.
do_action( 'woocommerce_order_item_meta_start', $item_id, $item, $order, $plain_text );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo strip_tags(
wc_display_item_meta(
$item,
@ -52,10 +55,10 @@ foreach ( $items as $item_id => $item ) :
)
);
// allow other plugins to add additional product information here
// allow other plugins to add additional product information here.
do_action( 'woocommerce_order_item_meta_end', $item_id, $item, $order, $plain_text );
}
// Note
// Note.
if ( $show_purchase_note && $purchase_note ) {
echo "\n" . do_shortcode( wp_kses_post( $purchase_note ) );
}

View File

@ -12,7 +12,7 @@
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates
* @version 3.7.0
* @version 5.2.0
*/
if ( ! defined( 'ABSPATH' ) ) {
@ -30,7 +30,7 @@ if ( ! apply_filters( 'woocommerce_order_item_visible', true, $item ) ) {
$is_visible = $product && $product->is_visible();
$product_permalink = apply_filters( 'woocommerce_order_item_permalink', $is_visible ? $product->get_permalink( $item ) : '', $item, $order );
echo apply_filters( 'woocommerce_order_item_name', $product_permalink ? sprintf( '<a href="%s">%s</a>', $product_permalink, $item->get_name() ) : $item->get_name(), $item, $is_visible ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', $product_permalink ? sprintf( '<a href="%s">%s</a>', $product_permalink, $item->get_name() ) : $item->get_name(), $item, $is_visible ) );
$qty = $item->get_quantity();
$refunded_qty = $order->get_qty_refunded_for_item( $item_id );

View File

@ -1,5 +1,8 @@
# Unreleased
# 0.1.2
## Added
- Support for the external product type.

View File

@ -1,6 +1,6 @@
{
"name": "@woocommerce/api",
"version": "0.1.1",
"version": "0.1.2",
"author": "Automattic",
"description": "A simple interface for interacting with a WooCommerce installation.",
"homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/api/README.md",

View File

@ -1,26 +1,24 @@
# Unreleased
# 0.1.3
## Fixed
- removed use of ES6 `import`
# 0.1.2
## Added
- api package test for variable products and product variations
- api package test for grouped products
- api package test for external products
- api package test for coupons
# 0.1.1
## Added
- Registered Shopper Checkout tests
- Merchant Order Status Filter tests
- Merchant Order Refund tests
- Merchant Apply Coupon tests
- Added new config variable for Simple Product price to `tests/e2e/env/config/default.json`. Defaults to 9.99
- Merchant Product Edit tests
- Merchant Product Search tests
- Shopper Single Product tests
- Shopper My Account Pay Order
- Shopper Checkout Apply Coupon
- Shopper Shop Browse Search Sort
- Merchant Orders Customer Checkout Page
- Shopper Cart Apply Coupon
@ -29,6 +27,17 @@
- Shopper Variable product info updates on different variations
- Merchant order emails flow
- Merchant analytics page load tests
- Shopper Checkout Create Account
# 0.1.1
## Added
- Merchant Order Status Filter tests
- Merchant Order Refund tests
- Merchant Apply Coupon tests
- Added new config variable for Simple Product price to `tests/e2e/env/config/default.json`. Defaults to 9.99
- Shopper Checkout Apply Coupon
## Fixed

View File

@ -76,6 +76,7 @@ The functions to access the core tests are:
- `runProductBrowseSearchSortTest` - Shopper can browse, search & sort products
- `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
### REST API

View File

@ -1,6 +1,6 @@
{
"name": "@woocommerce/e2e-core-tests",
"version": "0.1.1",
"version": "0.1.3",
"description": "End-To-End (E2E) tests for WooCommerce",
"homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/core-tests/README.md",
"repository": {
@ -14,8 +14,8 @@
"config": "3.3.3"
},
"peerDependencies": {
"@woocommerce/api": "^0.1.1",
"@woocommerce/e2e-utils": "^0.1.2"
"@woocommerce/api": "^0.1.2",
"@woocommerce/e2e-utils": "^0.1.4"
},
"publishConfig": {
"access": "public"

View File

@ -18,6 +18,7 @@ const runMyAccountPageTest = require( './shopper/front-end-my-account.test' );
const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.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' );
// Merchant tests
const runAddNewShippingZoneTest = require ( './merchant/wp-admin-settings-shipping-zones.test' );
@ -61,6 +62,7 @@ const runShopperTests = () => {
runMyAccountPayOrderTest();
runSingleProductPageTest();
runVariableProductUpdateTest();
runCheckoutCreateAccountTest();
};
const runMerchantTests = () => {
@ -130,4 +132,5 @@ module.exports = {
runProductBrowseSearchSortTest,
runApiTests,
runAnalyticsPageLoadsTest,
runCheckoutCreateAccountTest,
};

View File

@ -1,5 +1,5 @@
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/no-standalone-expect */
import {createSimpleProduct} from "@woocommerce/e2e-utils";
const { createSimpleProduct } = require( '@woocommerce/e2e-utils' );
/**
* Internal dependencies

View File

@ -0,0 +1,62 @@
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
/**
* Internal dependencies
*/
const {
shopper,
merchant,
createSimpleProduct,
uiUnblocked,
setCheckbox,
settingsPageSaveChanges,
} = require( '@woocommerce/e2e-utils' );
/**
* External dependencies
*/
const {
it,
describe,
beforeAll,
} = require( '@jest/globals' );
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const runCheckoutCreateAccountTest = () => {
describe('Shopper Checkout Create Account', () => {
beforeAll(async () => {
await merchant.login();
await createSimpleProduct();
await merchant.openSettings('account');
await setCheckbox('#woocommerce_enable_signup_and_login_from_checkout');
await settingsPageSaveChanges();
await merchant.logout();
await shopper.goToShop();
await shopper.addToCartFromShopPage(simpleProductName);
await uiUnblocked();
await shopper.goToCheckout();
});
it('can create an account during checkout', async () => {
// Fill all the details for a new customer
await shopper.fillBillingDetails(config.get('addresses.customer.billing'));
await uiUnblocked();
// Set checkbox for creating account during checkout
await setCheckbox('#createaccount');
// Place an order
await shopper.placeOrder();
await expect(page).toMatchElement('h1.entry-title', {text: 'Order received'});
});
it('can verify that the customer has been created', async () => {
await merchant.login();
await merchant.openAllUsersView();
await expect(page).toMatchElement('td.email.column-email > a', {text: 'john.doe@example.com'});
});
});
};
module.exports = runCheckoutCreateAccountTest;

View File

@ -4,9 +4,7 @@
*/
const {
shopper,
merchant,
createSimpleProductWithCategory,
uiUnblocked,
} = require( '@woocommerce/e2e-utils' );
/**

View File

@ -1,5 +1,8 @@
# Unreleased
# 0.2.1
## Added
- Support for screenshots on test errors

View File

@ -1,6 +1,6 @@
{
"name": "@woocommerce/e2e-environment",
"version": "0.2.0",
"version": "0.2.1",
"description": "WooCommerce End to End Testing Environment Configuration.",
"author": "Automattic",
"license": "GPL-3.0-or-later",

View File

@ -0,0 +1,6 @@
/*
* Internal dependencies
*/
const { runCheckoutCreateAccountTest } = require( '@woocommerce/e2e-core-tests' );
runCheckoutCreateAccountTest();

View File

@ -1,5 +1,30 @@
# Unreleased
# 0.1.4
## Fixed
- build issue with faker import
# 0.1.3
## Added
- `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field
- `searchForOrder( value, orderId, customerName )` util helper method that search order with different terms
- `addShippingZoneAndMethod( zoneName, zoneLocation, zipCode, zoneMethod )` util helper method for adding shipping zones with shipping methods
- `createSimpleProductWithCategory` component which creates a simple product with categories, containing three parameters for title, price and category name.
- `applyCoupon( couponName )` util helper method which applies previously created coupon to cart or checkout
- `removeCoupon()` util helper method that removes a single coupon within cart or checkout
- `selectOrderAction( action )` util helper method to select and initiate an order action in the Order Action postbox
- `merchant.openEmailLog()` go to the WP Mail Log page
- `deleteAllEmailLogs` delete all email logs in the WP Mail Log plugin
- `clickUpdateOrder( noticeText, waitForSave )` util helper that clicks the `Update` button on an order
## Changed
- Added coupon type parameter to `createCoupon( couponAmount, couponType )`. Default coupon type is fixed cart.
# 0.1.2
## Fixed
@ -16,16 +41,6 @@
- `addProductToOrder( orderId, productName )` component which adds the provided productName to the passed in orderId
- `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code.
- `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element.
- `selectOptionInSelect2( selector, value )` util helper method that search and select in any select2 type field
- `searchForOrder( value, orderId, customerName )` util helper method that search order with different terms
- `addShippingZoneAndMethod( zoneName, zoneLocation, zipCode, zoneMethod )` util helper method for adding shipping zones with shipping methods
- `createSimpleProductWithCategory` component which creates a simple product with categories, containing three parameters for title, price and category name.
- `applyCoupon( couponName )` util helper method which applies previously created coupon to cart or checkout
- `removeCoupon()` util helper method that removes a single coupon within cart or checkout
- `selectOrderAction( action )` util helper method to select and initiate an order action in the Order Action postbox
- `merchant.openEmailLog()` go to the WP Mail Log page
- `deleteAllEmailLogs` delete all email logs in the WP Mail Log plugin
- `clickUpdateOrder( noticeText, waitForSave )` util helper that clicks the `Update` button on an order
## Changes

View File

@ -54,6 +54,7 @@ describe( 'Cart page', () => {
| `updateOrderStatus` | `orderId, status` | Update the status of an order |
| `openEmailLog` | | Open the WP Mail Log page |
| `openAnalyticsPage` | | Open any Analytics page |
| `openAllUsersView` | | Open the All Users page |
### Shopper `shopper`

View File

@ -1,6 +1,6 @@
{
"name": "@woocommerce/e2e-utils",
"version": "0.1.2",
"version": "0.1.4",
"description": "End-To-End (E2E) test utils for WooCommerce",
"homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e-utils/README.md",
"repository": {
@ -18,7 +18,7 @@
"fishery": "^1.2.0"
},
"peerDependencies": {
"@woocommerce/api": "^0.1.0"
"@woocommerce/api": "^0.1.2"
},
"publishConfig": {
"access": "public"

View File

@ -1,5 +1,5 @@
import { SimpleProduct } from '@woocommerce/api';
import faker from 'faker/locale/en';
const faker = require( 'faker/locale/en' );
import { Factory } from 'fishery';
/**

View File

@ -18,6 +18,7 @@ export const WP_ADMIN_WC_SETTINGS = baseUrl + 'wp-admin/admin.php?page=wc-settin
export const WP_ADMIN_PERMALINK_SETTINGS = baseUrl + 'wp-admin/options-permalink.php';
export const WP_ADMIN_NEW_SHIPPING_ZONE = baseUrl + 'wp-admin/admin.php?page=wc-settings&tab=shipping&zone_id=new';
export const WP_ADMIN_ANALYTICS_PAGES = baseUrl + 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2F';
export const WP_ADMIN_ALL_USERS_VIEW = baseUrl + 'wp-admin/users.php';
export const SHOP_PAGE = baseUrl + 'shop';
export const SHOP_PRODUCT_PAGE = baseUrl + '?p=';

View File

@ -21,6 +21,7 @@ const {
WP_ADMIN_WC_SETTINGS,
WP_ADMIN_NEW_SHIPPING_ZONE,
WP_ADMIN_ANALYTICS_PAGES,
WP_ADMIN_ALL_USERS_VIEW,
} = require( './constants' );
const baseUrl = config.get( 'url' );
@ -186,6 +187,14 @@ const merchant = {
openAnalyticsPage: async ( pageName ) => {
await page.goto( WP_ADMIN_ANALYTICS_PAGES + pageName, {
waitUntil: 'networkidle0',
} );
},
openAllUsersView: async () => {
await page.goto( WP_ADMIN_ALL_USERS_VIEW, {
waitUntil: 'networkidle0',
} );
},

View File

@ -6,6 +6,9 @@
* @since 3.5.0
*/
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\CouponHelper;
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
/**
* Class WC_Tests_API_Orders
*/
@ -51,7 +54,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
// Create 10 orders.
for ( $i = 0; $i < 10; $i++ ) {
$this->orders[] = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order( $this->user );
$this->orders[] = OrderHelper::create_order( $this->user );
}
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/orders' ) );
@ -67,8 +70,8 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
public function test_get_items_ordered_by_modified() {
wp_set_current_user( $this->user );
$order1 = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order( $this->user );
$order2 = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order( $this->user );
$order1 = OrderHelper::create_order( $this->user );
$order2 = OrderHelper::create_order( $this->user );
$order1->set_status( 'completed' );
$order1->save();
@ -80,7 +83,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
$request->set_query_params(
array(
'orderby' => 'modified',
'order' => 'asc',
'order' => 'asc',
)
);
$response = $this->server->dispatch( $request );
@ -90,7 +93,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
$request->set_query_params(
array(
'orderby' => 'modified',
'order' => 'desc',
'order' => 'desc',
)
);
$response = $this->server->dispatch( $request );
@ -105,7 +108,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_get_items_without_permission() {
wp_set_current_user( 0 );
$this->orders[] = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$this->orders[] = OrderHelper::create_order();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/orders' ) );
$this->assertEquals( 401, $response->get_status() );
}
@ -116,7 +119,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_get_item() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$order->add_meta_data( 'key', 'value' );
$order->add_meta_data( 'key2', 'value2' );
$order->save();
@ -140,7 +143,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_get_item_without_permission() {
wp_set_current_user( 0 );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$this->orders[] = $order;
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/orders/' . $order->get_id() ) );
$this->assertEquals( 401, $response->get_status() );
@ -152,18 +155,18 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
public function test_get_item_with_line_items_meta_data() {
wp_set_current_user( $this->user );
$attribute_name = 'Site Level Type';
$site_level_attribute_id = wc_create_attribute( array( 'name' => $attribute_name ) );
$attribute_name = 'Site Level Type';
$site_level_attribute_id = wc_create_attribute( array( 'name' => $attribute_name ) );
$site_level_attribute_slug = wc_attribute_taxonomy_name_by_id( $site_level_attribute_id );
// Register the attribute so that wp_insert_term will be successful.
register_taxonomy( $site_level_attribute_slug, array( 'product' ), array() );
$term_name = 'Site Level Value - Wood';
$term_name = 'Site Level Value - Wood';
$site_level_term_insertion_result = wp_insert_term( $term_name, $site_level_attribute_slug );
$site_level_term = get_term( $site_level_term_insertion_result['term_id'] );
$site_level_term = get_term( $site_level_term_insertion_result['term_id'] );
$product = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_variation_product();
$product = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_variation_product();
$variation = wc_get_product( $product->get_children()[0] );
$line_item = new WC_Order_Item_Product();
@ -172,12 +175,12 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
array( 'variation' => array( "attribute_{$site_level_attribute_slug}" => $site_level_term->slug ) )
);
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$order->add_item( $line_item );
$order->save();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/orders/' . $order->get_id() ) );
$data = $response->get_data();
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( $order->get_id(), $data['id'] );
@ -205,18 +208,18 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
public function test_get_item_with_variation_parent_name() {
wp_set_current_user( $this->user );
$product = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_variation_product();
$product = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_variation_product();
$variation = wc_get_product( $product->get_children()[0] );
$line_item = new WC_Order_Item_Product();
$line_item->set_product( $variation );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$order->add_item( $line_item );
$order->save();
$response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/v3/orders/' . $order->get_id() ) );
$data = $response->get_data();
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( $order->get_id(), $data['id'] );
@ -248,7 +251,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_get_item_refund_id() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$refund = wc_create_refund(
array(
'order_id' => $order->get_id(),
@ -352,7 +355,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
$this->assertEquals( 1, count( $data['line_items'] ) );
$this->assertEquals( 1, count( $data['shipping_lines'] ) );
$shipping = current( $order->get_items( 'shipping' ) );
$shipping = current( $order->get_items( 'shipping' ) );
$expected_shipping_line = array(
'id' => $shipping->get_id(),
'method_title' => $shipping->get_method_title(),
@ -542,7 +545,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_update_order() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order->get_id() );
$request->set_body_params(
array(
@ -569,7 +572,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_update_order_remove_items() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$fee = new WC_Order_Item_Fee();
$fee->set_props(
array(
@ -610,7 +613,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
public function test_update_order_after_delete_product() {
wp_set_current_user( $this->user );
$product = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_simple_product();
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order( 1, $product );
$order = OrderHelper::create_order( 1, $product );
$product->delete( true );
$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order->get_id() );
@ -621,8 +624,8 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
array(
'line_items' => array(
array(
'id' => $item->get_id(),
'quantity' => 10,
'id' => $item->get_id(),
'quantity' => 10,
),
),
)
@ -652,56 +655,274 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
$this->assertEquals( $expected, $data['line_items'][0] );
}
/**
* Data provider for test_update_order_add_coupons.
*
* @return array Data for test_update_order_add_coupons.
*/
public function data_provider_for_test_update_order_add_coupons() {
return array(
// Successful case, no previous coupon, it gets created.
array(
'request_body' => array(
'coupon_lines' => array(
array(
'code' => 'fake-coupon-2',
),
),
),
'order_has_coupon_before_request' => false,
'expected_request_result' => array(
'code' => 200,
),
'expected_order_coupon_code_after_request' => 'fake-coupon-2',
),
// Successful case with previous coupon, it gets replaced.
array(
'request_body' => array(
'coupon_lines' => array(
array(
'code' => 'fake-coupon-2',
),
),
),
'order_has_coupon_before_request' => true,
'expected_request_result' => array(
'code' => 200,
),
'expected_order_coupon_code_after_request' => 'fake-coupon-2',
),
// Bad request: invalid coupon name, no previous coupon, it doesn't get added.
array(
'request_body' => array(
'coupon_lines' => array(
array(
'code' => 'not-existing-coupon',
),
),
),
'order_has_coupon_before_request' => false,
'expected_request_result' => array(
'code' => 400,
'message' => 'Coupon "not-existing-coupon" does not exist!',
),
'expected_order_coupon_code_after_request' => null,
),
// Bad request: invalid coupon name, coupon existed, it's kept.
array(
'request_body' => array(
'coupon_lines' => array(
array(
'code' => 'not-existing-coupon',
),
),
),
'order_has_coupon_before_request' => true,
'expected_request_result' => array(
'code' => 400,
'message' => 'Coupon "not-existing-coupon" does not exist!',
),
'expected_order_coupon_code_after_request' => 'fake-coupon',
),
// Bad request: has coupon id, no previous coupon, it doesn't get added.
array(
'request_body' => array(
'coupon_lines' => array(
array(
'id' => '1234',
'code' => 'fake-coupon-2',
),
),
),
'order_has_coupon_before_request' => false,
'expected_request_result' => array(
'code' => 400,
'message' => 'Coupon item ID is readonly.',
),
'expected_order_coupon_code_after_request' => null,
),
// Bad request: has coupon id, previous coupon existed, it's kept.
array(
'request_body' => array(
'coupon_lines' => array(
array(
'id' => '1234',
'code' => 'fake-coupon-2',
),
),
),
'order_has_coupon_before_request' => true,
'expected_request_result' => array(
'code' => 400,
'message' => 'Coupon item ID is readonly.',
),
'expected_order_coupon_code_after_request' => 'fake-coupon',
),
// Bad request: no coupon code, no previous coupon, it doesn't get added.
array(
'request_body' => array(
'coupon_lines' => array(
array(),
),
),
'order_has_coupon_before_request' => false,
'expected_request_result' => array(
'code' => 400,
'message' => 'Coupon code is required.',
),
'expected_order_coupon_code_after_request' => null,
),
// Bad request: no coupon code, previous coupon existed, it's kept.
array(
'request_body' => array(
'coupon_lines' => array(
array(),
),
),
'order_has_coupon_before_request' => true,
'expected_request_result' => array(
'code' => 400,
'message' => 'Coupon code is required.',
),
'expected_order_coupon_code_after_request' => 'fake-coupon',
),
// Bad request: invalid input ('coupon_lines' is not an array), no previous coupon, it doesn't get added.
array(
'request_body' => array(
'coupon_lines' => 1234,
),
'order_has_coupon_before_request' => false,
'expected_request_result' => array(
'code' => 400,
'message' => 'Invalid parameter(s): coupon_lines',
),
'expected_order_coupon_code_after_request' => null,
),
// Bad request: invalid input ('coupon_lines' is not an array), previous coupon existed, it's kept.
array(
'request_body' => array(
'coupon_lines' => 1234,
),
'order_has_coupon_before_request' => true,
'expected_request_result' => array(
'code' => 400,
'message' => 'Invalid parameter(s): coupon_lines',
),
'expected_order_coupon_code_after_request' => 'fake-coupon',
),
// Bad request: invalid input ('coupon_lines' has non-array elements), no previous coupon, it doesn't get added.
array(
'request_body' => array(
'coupon_lines' => array( 1234 ),
),
'order_has_coupon_before_request' => false,
'expected_request_result' => array(
'code' => 400,
'message' => 'Invalid parameter(s): coupon_lines',
),
'expected_order_coupon_code_after_request' => null,
),
// Bad request: invalid input ('coupon_lines' has non-array elements), previous coupon existed, it's kept.
array(
'request_body' => array(
'coupon_lines' => array( 1234 ),
),
'order_has_coupon_before_request' => true,
'expected_request_result' => array(
'code' => 400,
'message' => 'Invalid parameter(s): coupon_lines',
),
'expected_order_coupon_code_after_request' => 'fake-coupon',
),
);
}
/**
* Tests updating an order and adding a coupon.
*
* @dataProvider data_provider_for_test_update_order_add_coupons
*
* @param array $request_body The body for the API request.
* @param bool $order_has_coupon_before_request If true, the order will have 'fake-coupon' applied before the API request.
* @param array $expected_request_result Expected result from the API request, with 'code' and optionally 'message'.
* @param string $expected_order_coupon_code_after_request Code of the expected applied coupon after the API request, null if it shouldn't have a coupon applied.
*
* @since 3.5.0
*/
public function test_update_order_add_coupons() {
public function test_update_order_add_coupons( $request_body, $order_has_coupon_before_request, $expected_request_result, $expected_order_coupon_code_after_request ) {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order_item = current( $order->get_items() );
$coupon = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\CouponHelper::create_coupon( 'fake-coupon' );
// Create order and coupons.
$order = OrderHelper::create_order();
$original_order_amount = $order->get_total();
$coupons = array();
$coupon = CouponHelper::create_coupon( 'fake-coupon' );
$coupon->set_amount( 5 );
$coupon->save();
$coupons['fake-coupon'] = $coupon;
$coupon = CouponHelper::create_coupon( 'fake-coupon-2' );
$coupon->set_amount( 10 );
$coupon->save();
$coupons['fake-coupon-2'] = $coupon;
if ( $order_has_coupon_before_request ) {
$order->apply_coupon( $coupons['fake-coupon'] );
}
// Perform the request.
// Let's try a well-formed request first of all.
$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order->get_id() );
$request->set_body_params(
array(
'coupon_lines' => array(
array(
'code' => 'fake-coupon',
),
),
)
);
$request->set_body_params( $request_body );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 1, $data['coupon_lines'] );
$this->assertEquals( '45.00', $data['total'] );
// Check the response and the actual order data after the operation.
// Let's repeat, but this time we'll specify the item ID for the coupon: this is
// a readonly property and we expect the request to fail as a result.
$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order->get_id() );
$request->set_body_params(
array(
'coupon_lines' => array(
array(
'id' => 123,
'code' => 'fake-coupon',
),
),
)
);
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( $expected_request_result['code'], $response->get_status() );
$this->assertEquals( 400, $response->get_status() );
$this->assertEquals( 'woocommerce_rest_coupon_item_id_readonly', $data['code'] );
$order = wc_get_order( $order->get_id() );
$order_coupons = array_values( $order->get_coupons() );
if ( is_null( $expected_order_coupon_code_after_request ) ) {
$expected_coupon = null;
$expected_order_amount = $original_order_amount;
} else {
$expected_coupon = $coupons[ $expected_order_coupon_code_after_request ];
$expected_order_amount = number_format( 50 - $expected_coupon->get_amount(), 2 );
}
$is_ok_status = $response->get_status() < 300;
if ( $is_ok_status ) {
$this->assertEquals( $expected_order_amount, $data['total'] );
$this->assertCount( 1, $data['coupon_lines'] );
} else {
$this->assertEquals( $expected_request_result['message'], $data['message'] );
}
if ( is_null( $expected_order_coupon_code_after_request ) ) {
$this->assertEquals( '50.00', $order->get_total() );
$this->assertCount( 0, $order_coupons );
} else {
$this->assertEquals( number_format( $expected_order_amount, 2 ), $order->get_total() );
$this->assertCount( 1, $order_coupons );
$this->assertEquals( $expected_coupon->get_code(), $order_coupons[0]->get_code() );
}
}
/**
@ -711,9 +932,9 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_update_order_remove_coupons() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$order_item = current( $order->get_items() );
$coupon = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\CouponHelper::create_coupon( 'fake-coupon' );
$coupon = CouponHelper::create_coupon( 'fake-coupon' );
$coupon->set_amount( 5 );
$coupon->save();
@ -723,7 +944,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
// Check that the coupon is applied.
$this->assertEquals( '45.00', $order->get_total() );
$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order->get_id() );
$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order->get_id() );
$request->set_body_params(
array(
@ -752,7 +973,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_invalid_coupon() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order->get_id() );
$request->set_body_params(
@ -779,7 +1000,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_update_order_without_permission() {
wp_set_current_user( 0 );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order->get_id() );
$request->set_body_params(
array(
@ -822,7 +1043,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_delete_order() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$request = new WP_REST_Request( 'DELETE', '/wc/v3/orders/' . $order->get_id() );
$request->set_param( 'force', true );
$response = $this->server->dispatch( $request );
@ -837,7 +1058,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_delete_order_without_permission() {
wp_set_current_user( 0 );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$request = new WP_REST_Request( 'DELETE', '/wc/v3/orders/' . $order->get_id() );
$request->set_param( 'force', true );
$response = $this->server->dispatch( $request );
@ -865,9 +1086,9 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
public function test_orders_batch() {
wp_set_current_user( $this->user );
$order1 = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order2 = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order3 = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order1 = OrderHelper::create_order();
$order2 = OrderHelper::create_order();
$order3 = OrderHelper::create_order();
$request = new WP_REST_Request( 'POST', '/wc/v3/orders/batch' );
$request->set_body_params(
@ -904,7 +1125,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_order_schema() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$order = OrderHelper::create_order();
$request = new WP_REST_Request( 'OPTIONS', '/wc/v3/orders/' . $order->get_id() );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
@ -919,8 +1140,8 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
*/
public function test_order_line_items_schema() {
wp_set_current_user( $this->user );
$order = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_order();
$request = new WP_REST_Request( 'OPTIONS', '/wc/v3/orders/' . $order->get_id() );
$order = OrderHelper::create_order();
$request = new WP_REST_Request( 'OPTIONS', '/wc/v3/orders/' . $order->get_id() );
$response = $this->server->dispatch( $request );
$data = $response->get_data();

View File

@ -14,12 +14,22 @@ class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case {
* Set up
*/
public function setUp() {
// set default country to US so that 'payments' task does not get added.
// we want to remove payment tasks as they depend on installation & activation.
update_option( 'woocommerce_default_country', 'US' );
// Set default country to non-US so that 'payments' task gets added but 'woocommerce-payments' doesn't,
// by default it won't be considered completed but we can manually change that as needed.
update_option( 'woocommerce_default_country', 'JP' );
parent::setUp();
}
/**
* Tear down
*/
public function tearDown() {
remove_all_filters( 'woocommerce_available_payment_gateways' );
parent::tearDown();
}
/**
* Includes widget class and return the class.
*
@ -75,13 +85,21 @@ class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case {
}
/**
* Tests the widget output when 0 task has been completed.
* Tests the widget output when 1 task has been completed.
*/
public function test_initial_widget_output() {
// Force the "payments" task to be considered incomplete.
add_filter(
'woocommerce_available_payment_gateways',
function() {
return array();
}
);
$html = $this->get_widget_output();
$required_strings = array(
'Step 0 of 5',
'Step 0 of 6',
'You&#039;re almost there! Once you complete store setup you can start receiving orders.',
'Start selling',
'admin.php\?page=wc-admin&amp;path=%2Fsetup-wizard',
@ -96,9 +114,22 @@ class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case {
* Tests completed task count as it completes one by one
*/
public function test_widget_renders_completed_task_count() {
$completed_tasks = array();
// Force the "payments" task to be considered completed
// by faking a valid payment gateway.
add_filter(
'woocommerce_available_payment_gateways',
function() {
return array(
new class() extends WC_Payment_Gateway {
},
);
}
);
$completed_tasks = array( 'payments' );
$tasks = $this->get_widget()->get_tasks();
$tasks_count = count( $tasks );
unset( $tasks['payments'] ); // That one is completed already.
foreach ( $tasks as $key => $task ) {
array_push( $completed_tasks, $key );
update_option( 'woocommerce_task_list_tracked_completed_tasks', $completed_tasks );
@ -108,7 +139,7 @@ class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case {
if ( $completed_tasks_count === $tasks_count ) {
$this->assertEmpty( $this->get_widget_output() );
} else {
$this->assertRegexp( "/Step ${completed_tasks_count} of 5/", $this->get_widget_output() );
$this->assertRegexp( "/Step ${completed_tasks_count} of 6/", $this->get_widget_output() );
}
}
}
@ -122,13 +153,13 @@ class WC_Admin_Dashboard_Setup_Test extends WC_Unit_Test_Case {
array(
array(
'woocommerce_task_list_complete' => 'yes',
'woocommerce_task_list_hidden' => 'no',
'woocommerce_task_list_hidden' => 'no',
),
),
array(
array(
'woocommerce_task_list_complete' => 'no',
'woocommerce_task_list_hidden' => 'yes',
'woocommerce_task_list_hidden' => 'yes',
),
),
);