Merge branch 'new/puppeteer-screenshot-tester' into new/front-end-checkout-e2e-test

This commit is contained in:
Julia Amosova 2019-12-11 14:54:39 +00:00
commit 90ddb08765
86 changed files with 1661 additions and 433 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ none
# Compiled CSS
/assets/css/*.css
/assets/css/photoswipe/**/*.min.css
# Minified JS
/assets/js/admin/*.min.js

View File

@ -148,11 +148,29 @@ module.exports = function( grunt ) {
// Minify all .css files.
cssmin: {
minify: {
expand: true,
cwd: '<%= dirs.css %>/',
src: ['*.css'],
dest: '<%= dirs.css %>/',
ext: '.css'
files: [
{
expand: true,
cwd: '<%= dirs.css %>/',
src: ['*.css'],
dest: '<%= dirs.css %>/',
ext: '.css'
},
{
expand: true,
cwd: '<%= dirs.css %>/photoswipe/',
src: ['*.css', '!*.min.css'],
dest: '<%= dirs.css %>/photoswipe/',
ext: '.min.css'
},
{
expand: true,
cwd: '<%= dirs.css %>/photoswipe/default-skin/',
src: ['*.css', '!*.min.css'],
dest: '<%= dirs.css %>/photoswipe/default-skin/',
ext: '.min.css'
}
]
}
},

View File

@ -6818,50 +6818,47 @@ table.bar_chart {
/**
* Select2 colors for built-in admin color themes.
*/
.branch-5-3 {
.admin-color {
$wp_admin_colors: (
blue: #096484,
coffee: #c7a589,
ectoplasm: #a3b745,
midnight: #e14d43,
ocean: #9ebaa0,
sunrise: #dd823b,
light: #04a4cc
);
.admin-color {
$wp_admin_colors: (
blue: #096484,
coffee: #c7a589,
ectoplasm: #a3b745,
midnight: #e14d43,
ocean: #9ebaa0,
sunrise: #dd823b,
light: #04a4cc
);
@each $name, $color in $wp_admin_colors {
@each $name, $color in $wp_admin_colors {
&-#{$name}.branch-5-3 {
&-#{$name} {
.select2-dropdown {
border-color: $color;
}
.select2-dropdown {
border-color: $color;
}
.select2-dropdown--below {
box-shadow: 0 0 0 1px $color, 0 2px 1px rgba(0, 0, 0, 0.1);
}
.select2-dropdown--below {
box-shadow: 0 0 0 1px $color, 0 2px 1px rgba(0, 0, 0, 0.1);
}
.select2-dropdown--above {
box-shadow: 0 0 0 1px $color, 0 -2px 1px rgba(0, 0, 0, 0.1);
}
.select2-dropdown--above {
box-shadow: 0 0 0 1px $color, 0 -2px 1px rgba(0, 0, 0, 0.1);
}
.select2-selection--single .select2-selection__rendered:hover {
color: $color;
}
.select2-selection--single .select2-selection__rendered:hover {
color: $color;
}
.select2-container.select2-container--focus .select2-selection--single,
.select2-container.select2-container--open .select2-selection--single,
.select2-container.select2-container--open .select2-selection--multiple {
border-color: $color;
box-shadow: 0 0 0 1px $color;
}
.select2-container.select2-container--focus .select2-selection--single,
.select2-container.select2-container--open .select2-selection--single,
.select2-container.select2-container--open .select2-selection--multiple {
border-color: $color;
box-shadow: 0 0 0 1px $color;
}
.select2-container--default .select2-results__option--highlighted[aria-selected],
.select2-container--default .select2-results__option--highlighted[data-selected] {
background-color: $color;
}
.select2-container--default .select2-results__option--highlighted[aria-selected],
.select2-container--default .select2-results__option--highlighted[data-selected] {
background-color: $color;
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@ body {
padding: 0;
}
#wc-logo {
.wc-logo {
border: 0;
margin: 0 0 24px;
padding: 0;
@ -1070,6 +1070,31 @@ h3.jetpack-reasons {
}
}
.wc-setup-step__new_onboarding {
.wc-logo,
.wc-setup-steps {
display: none;
}
.wc-setup-step__new_onboarding-wrapper {
.wc-logo {
display: block;
}
p {
text-align: center;
}
.wc-setup-step__new_onboarding-welcome,
.wc-setup-step__new_onboarding-plugin-info {
color: #7c7c7c;
font-size: 12px;
}
}
}
.step {
text-align: center;
}
@ -1146,7 +1171,7 @@ h3.jetpack-reasons {
border-color: #ddd;
border-radius: 4px;
height: 30px;
width: 100%;
width: calc(100% - 8px - 8px - 2px);
padding-left: 8px;
padding-right: 8px;
font-size: 16px;
@ -1159,11 +1184,12 @@ h3.jetpack-reasons {
}
}
.branch-5-2,
.branch-5-3 {
.location-input {
margin: 0;
width: 100%;
}
}
@ -1388,6 +1414,7 @@ p.jetpack-terms {
}
}
.branch-5-2,
.branch-5-3 {
.wc-wizard-service-setting-stripe_create_account,
@ -1523,7 +1550,7 @@ p.jetpack-terms {
@media only screen and (max-width: 400px) {
#wc-logo img {
.wc-logo img {
max-width: 80%;
}

View File

@ -6,17 +6,19 @@
/**
* Imports
*/
@import 'mixins';
@import 'variables';
@import "mixins";
@import "variables";
/**
* Styling begins
*/
.woocommerce, .woocommerce-page {
.woocommerce,
.woocommerce-page {
.woocommerce-message,
.woocommerce-error,
.woocommerce-info {
.button {
float: right;
}
@ -26,6 +28,7 @@
* General layout styles
*/
.col2-set {
@include clearfix();
width: 100%;
@ -33,6 +36,7 @@
float: left;
width: 48%;
}
.col-2 {
float: right;
width: 48%;
@ -49,12 +53,14 @@
*/
div.product,
#content div.product {
div.images {
float: left;
width: 48%;
}
div.thumbnails {
@include clearfix();
a {
@ -73,6 +79,7 @@
}
&.columns-1 {
a {
width: 100%;
margin-right: 0;
@ -81,18 +88,21 @@
}
&.columns-2 {
a {
width: 48%;
}
}
&.columns-4 {
a {
width: 22.05%;
}
}
&.columns-5 {
a {
width: 16.9%;
}
@ -109,12 +119,15 @@
clear: both;
ul.tabs {
@include menu();
}
}
#reviews {
.comment {
@include mediaright();
}
}
@ -125,6 +138,7 @@
*/
ul.products {
clear: both;
@include clearfix();
li.product {
@ -146,28 +160,38 @@
}
ul.products {
&.columns-1 {
li.product {
width: 100%;
margin-right: 0;
}
}
&.columns-2 {
li.product {
width: 48%;
}
}
&.columns-3 {
li.product {
width: 30.75%;
}
}
&.columns-5 {
li.product {
width: 16.95%;
}
}
&.columns-6 {
li.product {
width: 13.5%;
}
@ -175,7 +199,9 @@
}
&.columns-1 {
ul.products {
li.product {
width: 100%;
margin-right: 0;
@ -184,7 +210,9 @@
}
&.columns-2 {
ul.products {
li.product {
width: 48%;
}
@ -192,7 +220,9 @@
}
&.columns-3 {
ul.products {
li.product {
width: 30.75%;
}
@ -200,7 +230,9 @@
}
&.columns-5 {
ul.products {
li.product {
width: 16.95%;
}
@ -208,7 +240,9 @@
}
&.columns-6 {
ul.products {
li.product {
width: 13.5%;
}
@ -218,12 +252,15 @@
.woocommerce-result-count {
float: left;
}
.woocommerce-ordering {
float: right;
}
.woocommerce-pagination {
ul.page-numbers {
@include menu();
}
}
@ -233,6 +270,7 @@
*/
table.cart,
#content table.cart {
img {
height: auto;
}
@ -255,6 +293,7 @@
}
.cart-collaterals {
@include clearfix();
width: 100%;
@ -278,11 +317,13 @@
.shipping_calculator {
width: 48%;
@include clearfix();
clear: right;
float: right;
.col2-set {
.col-1,
.col-2 {
width: 47%;
@ -301,7 +342,9 @@
*/
ul.cart_list,
ul.product_list_widget {
li {
@include mediaright();
}
}
@ -310,7 +353,9 @@
* Forms
*/
form {
.form-row {
@include clearfix();
label {
@ -352,19 +397,54 @@
.form-row-wide {
clear: both;
}
.password-input {
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
input[type="password"] {
padding-right: 2.5rem;
}
/* Hide the Edge "reveal password" native button */
input::-ms-reveal {
display: none;
}
}
.show-password-input {
position: absolute;
right: 0.7em;
top: 0.7em;
cursor: pointer;
}
.show-password-input::after {
@include iconafter( "\e010" ); // Icon styles and glyph
}
.show-password-input.display-password::after {
color: #e8e8e8;
}
}
#payment {
.form-row {
select {
width: auto;
}
}
.wc-terms-and-conditions, .terms {
.wc-terms-and-conditions,
.terms {
text-align: left;
padding: 0 1em 0 0;
float:left;
float: left;
}
#place_order {
@ -374,6 +454,7 @@
.woocommerce-billing-fields,
.woocommerce-shipping-fields {
@include clearfix();
}
@ -391,6 +472,7 @@
}
.woocommerce-account {
.woocommerce-MyAccount-navigation {
float: left;
width: 30%;
@ -406,7 +488,9 @@
* Twenty Eleven specific styles
*/
.woocommerce-page {
&.left-sidebar {
#content.twentyeleven {
width: 58.4%;
margin: 0 7.6%;
@ -415,6 +499,7 @@
}
&.right-sidebar {
#content.twentyeleven {
margin: 0 7.6%;
width: 58.4%;
@ -427,6 +512,7 @@
* Twenty Fourteen specific styles
*/
.twentyfourteen {
.tfwc {
padding: 12px 10px 0;
max-width: 474px;
@ -444,7 +530,9 @@
}
@media screen and (min-width: 673px) {
.twentyfourteen {
.tfwc {
padding-right: 30px;
padding-left: 30px;
@ -453,7 +541,9 @@
}
@media screen and (min-width: 1040px) {
.twentyfourteen {
.tfwc {
padding-right: 15px;
padding-left: 15px;
@ -462,7 +552,9 @@
}
@media screen and (min-width: 1110px) {
.twentyfourteen {
.tfwc {
padding-right: 30px;
padding-left: 30px;
@ -471,13 +563,18 @@
}
@media screen and (min-width: 1218px) {
.twentyfourteen {
.tfwc {
margin-right: 54px;
}
}
.full-width {
.twentyfourteen {
.tfwc {
margin-right: auto;
}
@ -489,6 +586,7 @@
* Twenty Fifteen specific styles
*/
.twentyfifteen {
.t15wc {
padding-left: 7.6923%;
padding-right: 7.6923%;
@ -504,7 +602,9 @@
}
@media screen and (min-width: 38.75em) {
.twentyfifteen {
.t15wc {
margin-right: 7.6923%;
margin-left: 7.6923%;
@ -514,7 +614,9 @@
}
@media screen and (min-width: 59.6875em) {
.twentyfifteen {
.t15wc {
margin-left: 8.3333%;
margin-right: 8.3333%;
@ -523,7 +625,9 @@
}
.single-product {
.twentyfifteen {
.entry-summary {
padding: 0 !important;
}
@ -535,6 +639,7 @@
* Twenty Sixteen specific styles
*/
.twentysixteen {
.site-main {
margin-right: 7.6923%;
margin-left: 7.6923%;
@ -547,8 +652,11 @@
}
#content {
.twentysixteen {
div.product {
div.images,
div.summary {
width: 46.42857%;
@ -558,7 +666,9 @@
}
@media screen and (min-width: 44.375em) {
.twentysixteen {
.site-main {
margin-right: 23.0769%;
}
@ -566,7 +676,9 @@
}
@media screen and (min-width: 56.875em) {
.twentysixteen {
.site-main {
margin-right: 0;
margin-left: 0;
@ -574,7 +686,9 @@
}
.no-sidebar {
.twentysixteen {
.site-main {
margin-right: 15%;
margin-left: 15%;
@ -592,11 +706,16 @@
* RTL styles.
*/
.rtl {
.woocommerce, .woocommerce-page {
.woocommerce,
.woocommerce-page {
.col2-set {
.col-1 {
float: right;
}
.col-2 {
float: left;
}

View File

@ -383,10 +383,12 @@ jQuery( function ( $ ) {
var unit_total_tax = accounting.unformat( $line_total_tax.attr( 'data-total_tax' ), woocommerce_admin.mon_decimal_point ) / o_qty;
var $line_subtotal_tax = $( 'input.line_subtotal_tax[data-tax_id="' + tax_id + '"]', $row );
var unit_subtotal_tax = accounting.unformat( $line_subtotal_tax.attr( 'data-subtotal_tax' ), woocommerce_admin.mon_decimal_point ) / o_qty;
var round_at_subtotal = 'yes' === woocommerce_admin_meta_boxes.round_at_subtotal;
var precision = woocommerce_admin_meta_boxes[ round_at_subtotal ? 'rounding_precision' : 'currency_format_num_decimals' ];
if ( 0 < unit_total_tax ) {
$line_total_tax.val(
parseFloat( accounting.formatNumber( unit_total_tax * qty, woocommerce_admin_meta_boxes.rounding_precision, '' ) )
parseFloat( accounting.formatNumber( unit_total_tax * qty, precision, '' ) )
.toString()
.replace( '.', woocommerce_admin.mon_decimal_point )
);
@ -394,7 +396,7 @@ jQuery( function ( $ ) {
if ( 0 < unit_subtotal_tax ) {
$line_subtotal_tax.val(
parseFloat( accounting.formatNumber( unit_subtotal_tax * qty, woocommerce_admin_meta_boxes.rounding_precision, '' ) )
parseFloat( accounting.formatNumber( unit_subtotal_tax * qty, precision, '' ) )
.toString()
.replace( '.', woocommerce_admin.mon_decimal_point )
);
@ -1019,8 +1021,11 @@ jQuery( function ( $ ) {
var unit_total_tax = accounting.unformat( line_total_tax.data( 'total_tax' ), woocommerce_admin.mon_decimal_point ) / qty;
if ( 0 < unit_total_tax ) {
var round_at_subtotal = 'yes' === woocommerce_admin_meta_boxes.round_at_subtotal;
var precision = woocommerce_admin_meta_boxes[ round_at_subtotal ? 'rounding_precision' : 'currency_format_num_decimals' ];
$refund_line_total_tax.val(
parseFloat( accounting.formatNumber( unit_total_tax * refund_qty, woocommerce_admin_meta_boxes.rounding_precision, '' ) )
parseFloat( accounting.formatNumber( unit_total_tax * refund_qty, precision, '' ) )
.toString()
.replace( '.', woocommerce_admin.mon_decimal_point )
).change();

View File

@ -91,6 +91,7 @@ jQuery( function( $ ) {
action : $( this ).data( 'action' ) || 'woocommerce_json_search_products_and_variations',
security : wc_enhanced_select_params.search_products_nonce,
exclude : $( this ).data( 'exclude' ),
exclude_type : $( this ).data( 'exclude_type' ),
include : $( this ).data( 'include' ),
limit : $( this ).data( 'limit' ),
display_stock: $( this ).data( 'display_stock' )
@ -260,14 +261,16 @@ jQuery( function( $ ) {
// WooCommerce Backbone Modal
.on( 'wc_backbone_modal_before_remove', function() {
$( '.wc-enhanced-select, :input.wc-product-search, :input.wc-customer-search' ).filter( '.select2-hidden-accessible' ).selectWoo( 'close' );
$( '.wc-enhanced-select, :input.wc-product-search, :input.wc-customer-search' ).filter( '.select2-hidden-accessible' )
.selectWoo( 'close' );
})
.trigger( 'wc-enhanced-select-init' );
$( 'html' ).on( 'click', function( event ) {
if ( this === event.target ) {
$( '.wc-enhanced-select, :input.wc-product-search, :input.wc-customer-search' ).filter( '.select2-hidden-accessible' ).selectWoo( 'close' );
$( '.wc-enhanced-select, :input.wc-product-search, :input.wc-customer-search' ).filter( '.select2-hidden-accessible' )
.selectWoo( 'close' );
}
} );
} catch( err ) {

View File

@ -216,6 +216,11 @@ jQuery( function( $ ) {
waitForJetpackInstall();
} );
$( '.activate-new-onboarding' ).on( 'click', '.button-primary', function() {
// Show pending spinner while activate happens.
blockWizardUI();
} );
$( '.wc-wizard-services' ).on( 'change', 'input#stripe_create_account, input#ppec_paypal_reroute_requests', function() {
if ( $( this ).is( ':checked' ) ) {
$( this ).closest( '.wc-wizard-service-settings' )

View File

@ -61,16 +61,18 @@
})
.on( 'change', '.wc_input_price[type=text], .wc_input_decimal[type=text], .wc-order-totals #refund_amount[type=text]', function() {
var regex;
var regex, decimalRegex,
decimailPoint = woocommerce_admin.decimal_point;
if ( $( this ).is( '.wc_input_price' ) || $( this ).is( '#refund_amount' ) ) {
regex = new RegExp( '[^\-0-9\%\\' + woocommerce_admin.mon_decimal_point + ']+', 'gi' );
} else {
regex = new RegExp( '[^\-0-9\%\\' + woocommerce_admin.decimal_point + ']+', 'gi' );
decimailPoint = woocommerce_admin.mon_decimal_point;
}
regex = new RegExp( '[^\-0-9\%\\' + decimailPoint + ']+', 'gi' );
decimalRegex = new RegExp( '\\' + decimailPoint + '+', 'gi' );
var value = $( this ).val();
var newvalue = value.replace( regex, '' );
var newvalue = value.replace( regex, '' ).replace( decimalRegex, decimailPoint );
if ( value !== newvalue ) {
$( this ).val( newvalue );
@ -78,22 +80,32 @@
})
.on( 'keyup', '.wc_input_price[type=text], .wc_input_decimal[type=text], .wc_input_country_iso[type=text], .wc-order-totals #refund_amount[type=text]', function() {
var regex, error;
var regex, error, decimalRegex;
var checkDecimalNumbers = false;
if ( $( this ).is( '.wc_input_price' ) || $( this ).is( '#refund_amount' ) ) {
checkDecimalNumbers = true;
regex = new RegExp( '[^\-0-9\%\\' + woocommerce_admin.mon_decimal_point + ']+', 'gi' );
decimalRegex = new RegExp( '[^\\' + woocommerce_admin.mon_decimal_point + ']', 'gi' );
error = 'i18n_mon_decimal_error';
} else if ( $( this ).is( '.wc_input_country_iso' ) ) {
regex = new RegExp( '([^A-Z])+|(.){3,}', 'im' );
error = 'i18n_country_iso_error';
} else {
checkDecimalNumbers = true;
regex = new RegExp( '[^\-0-9\%\\' + woocommerce_admin.decimal_point + ']+', 'gi' );
decimalRegex = new RegExp( '[^\\' + woocommerce_admin.decimal_point + ']', 'gi' );
error = 'i18n_decimal_error';
}
var value = $( this ).val();
var newvalue = value.replace( regex, '' );
// Check if newvalue have more than one decimal point.
if ( checkDecimalNumbers && 1 < newvalue.replace( decimalRegex, '' ).length ) {
newvalue = newvalue.replace( decimalRegex, '' );
}
if ( value !== newvalue ) {
$( document.body ).triggerHandler( 'wc_add_error_tip', [ $( this ), error ] );
} else {

View File

@ -41,16 +41,16 @@ jQuery( function( $ ) {
this.$checkout_form.on( 'update', this.trigger_update_checkout );
// Inputs/selects which update totals
this.$checkout_form.on( 'change', 'select.shipping_method, input[name^="shipping_method"], #ship-to-different-address input, .update_totals_on_change select, .update_totals_on_change input[type="radio"], .update_totals_on_change input[type="checkbox"]', this.trigger_update_checkout );
this.$checkout_form.on( 'change', 'select.shipping_method, input[name^="shipping_method"], #ship-to-different-address input, .update_totals_on_change select, .update_totals_on_change input[type="radio"], .update_totals_on_change input[type="checkbox"]', this.trigger_update_checkout ); // eslint-disable-line max-len
this.$checkout_form.on( 'change', '.address-field select', this.input_changed );
this.$checkout_form.on( 'change', '.address-field input.input-text, .update_totals_on_change input.input-text', this.maybe_input_changed );
this.$checkout_form.on( 'keydown', '.address-field input.input-text, .update_totals_on_change input.input-text', this.queue_update_checkout );
this.$checkout_form.on( 'change', '.address-field input.input-text, .update_totals_on_change input.input-text', this.maybe_input_changed ); // eslint-disable-line max-len
this.$checkout_form.on( 'keydown', '.address-field input.input-text, .update_totals_on_change input.input-text', this.queue_update_checkout ); // eslint-disable-line max-len
// Address fields
this.$checkout_form.on( 'change', '#ship-to-different-address input', this.ship_to_different_address );
// Trigger events
this.$checkout_form.find( '#ship-to-different-address input' ).change();
this.init_ship_to_different_address();
this.init_payment_methods();
// Update on page load
@ -135,7 +135,10 @@ jQuery( function( $ ) {
}
},
init_checkout: function() {
$( document.body ).trigger( 'update_checkout' );
// Fire updated_checkout event after existing ready event handlers.
$( function() {
$( document.body ).trigger( 'updated_checkout' );
} );
},
maybe_input_changed: function( e ) {
if ( wc_checkout_form.dirtyInput ) {
@ -180,10 +183,31 @@ jQuery( function( $ ) {
wc_checkout_form.trigger_update_checkout();
}
},
init_ship_to_different_address: function() {
var $checkbox = $( '#ship-to-different-address input' );
if ( ! $checkbox.prop( 'checked' ) ) {
var $billing = $( 'div.woocommerce-billing-fields' );
// Find shipping field values that diverge from billing.
var $differentFields = $( 'div.shipping_address' ).find( 'input, select' ).filter( function() {
$( this ).attr( 'id' ).replace( 'shipping', 'billing' );
var id = $( this ).attr( 'id' ).replace( 'shipping', 'billing' );
return $( this ).val() !== $billing.find( '#' + id ).val();
} );
if ( $differentFields.length > 0 ) {
$checkbox.prop( 'checked', true );
}
}
$( 'div.shipping_address' ).toggle( $checkbox.prop( 'checked' ) );
},
ship_to_different_address: function() {
$( 'div.shipping_address' ).hide();
if ( $( this ).is( ':checked' ) ) {
$( 'div.shipping_address' ).slideDown();
} else {
$( 'div.shipping_address' ).slideUp();
}
},
reset_update_checkout_timer: function() {
@ -207,7 +231,7 @@ jQuery( function( $ ) {
event_type = e.type;
if ( 'input' === event_type ) {
$parent.removeClass( 'woocommerce-invalid woocommerce-invalid-required-field woocommerce-invalid-email woocommerce-validated' );
$parent.removeClass( 'woocommerce-invalid woocommerce-invalid-required-field woocommerce-invalid-email woocommerce-validated' ); // eslint-disable-line max-len
}
if ( 'validate' === event_type || 'change' === event_type ) {
@ -225,7 +249,7 @@ jQuery( function( $ ) {
if ( validate_email ) {
if ( $this.val() ) {
/* https://stackoverflow.com/questions/2855865/jquery-validate-e-mail-address-regex */
var pattern = new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i);
var pattern = new RegExp(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i); // eslint-disable-line max-len
if ( ! pattern.test( $this.val() ) ) {
$parent.removeClass( 'woocommerce-validated' ).addClass( 'woocommerce-invalid woocommerce-invalid-email' );
@ -235,7 +259,7 @@ jQuery( function( $ ) {
}
if ( validated ) {
$parent.removeClass( 'woocommerce-invalid woocommerce-invalid-required-field woocommerce-invalid-email' ).addClass( 'woocommerce-validated' );
$parent.removeClass( 'woocommerce-invalid woocommerce-invalid-required-field woocommerce-invalid-email' ).addClass( 'woocommerce-validated' ); // eslint-disable-line max-len
}
}
},
@ -311,6 +335,7 @@ jQuery( function( $ ) {
if ( false !== args.update_shipping_method ) {
var shipping_methods = {};
// eslint-disable-next-line max-len
$( 'select.shipping_method, input[name^="shipping_method"][type="radio"]:checked, input[name^="shipping_method"][type="hidden"]' ).each( function() {
shipping_methods[ $( this ).data( 'index' ) ] = $( this ).val();
} );
@ -399,7 +424,7 @@ jQuery( function( $ ) {
// Add new errors returned by this event
if ( data.messages ) {
$form.prepend( '<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-updateOrderReview">' + data.messages + '</div>' );
$form.prepend( '<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-updateOrderReview">' + data.messages + '</div>' ); // eslint-disable-line max-len
} else {
$form.prepend( data );
}
@ -419,6 +444,26 @@ jQuery( function( $ ) {
});
},
handleUnloadEvent: function( e ) {
// Modern browsers have their own standard generic messages that they will display.
// Confirm, alert, prompt or custom message are not allowed during the unload event
// Browsers will display their own standard messages
// Check if the browser is Internet Explorer
if((navigator.userAgent.indexOf('MSIE') !== -1 ) || (!!document.documentMode)) {
// IE handles unload events differently than modern browsers
e.preventDefault();
return undefined;
}
return true;
},
attachUnloadEventsOnSubmit: function() {
$( window ).on('beforeunload', this.handleUnloadEvent);
},
detachUnloadEventsOnSubmit: function() {
$( window ).unbind('beforeunload', this.handleUnloadEvent);
},
blockOnSubmit: function( $form ) {
var form_data = $form.data();
@ -444,12 +489,16 @@ jQuery( function( $ ) {
}
// Trigger a handler to let gateways manipulate the checkout if needed
// eslint-disable-next-line max-len
if ( $form.triggerHandler( 'checkout_place_order' ) !== false && $form.triggerHandler( 'checkout_place_order_' + wc_checkout_form.get_payment_method() ) !== false ) {
$form.addClass( 'processing' );
wc_checkout_form.blockOnSubmit( $form );
// Attach event to block reloading the page when the form has been submitted
wc_checkout_form.attachUnloadEventsOnSubmit();
// ajaxSetup is global, but we use it to ensure JSON is valid once returned.
$.ajaxSetup( {
dataFilter: function( raw_response, dataType ) {
@ -485,6 +534,9 @@ jQuery( function( $ ) {
data: $form.serialize(),
dataType: 'json',
success: function( result ) {
// Detach the unload handler that prevents a reload / redirect
wc_checkout_form.detachUnloadEventsOnSubmit();
try {
if ( 'success' === result.result ) {
if ( -1 === result.redirect.indexOf( 'https://' ) || -1 === result.redirect.indexOf( 'http://' ) ) {
@ -513,11 +565,14 @@ jQuery( function( $ ) {
if ( result.messages ) {
wc_checkout_form.submit_error( result.messages );
} else {
wc_checkout_form.submit_error( '<div class="woocommerce-error">' + wc_checkout_params.i18n_checkout_error + '</div>' );
wc_checkout_form.submit_error( '<div class="woocommerce-error">' + wc_checkout_params.i18n_checkout_error + '</div>' ); // eslint-disable-line max-len
}
}
},
error: function( jqXHR, textStatus, errorThrown ) {
// Detach the unload handler that prevents a reload / redirect
wc_checkout_form.detachUnloadEventsOnSubmit();
wc_checkout_form.submit_error( '<div class="woocommerce-error">' + errorThrown + '</div>' );
}
});
@ -527,7 +582,7 @@ jQuery( function( $ ) {
},
submit_error: function( error_message ) {
$( '.woocommerce-NoticeGroup-checkout, .woocommerce-error, .woocommerce-message' ).remove();
wc_checkout_form.$checkout_form.prepend( '<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout">' + error_message + '</div>' );
wc_checkout_form.$checkout_form.prepend( '<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout">' + error_message + '</div>' ); // eslint-disable-line max-len
wc_checkout_form.$checkout_form.removeClass( 'processing' ).unblock();
wc_checkout_form.$checkout_form.find( '.input-text, select, input:checkbox' ).trigger( 'validate' ).blur();
wc_checkout_form.scroll_to_notices();

View File

@ -78,4 +78,19 @@ jQuery( function( $ ) {
}, 1000 );
}
};
// Show password visiblity hover icon on woocommerce forms
$( '.woocommerce form input[type="password"]' ).wrap( '<span class="password-input"></span>' );
$( '.password-input' ).append( '<span class="show-password-input"></span>' );
$( '.show-password-input' ).click(
function() {
$( this ).toggleClass( 'display-password' );
if ( $( this ).hasClass( 'display-password' ) ) {
$( this ).siblings( ['input[name="password"]', 'input[type="password"]'] ).prop( 'type', 'text' );
} else {
$( this ).siblings( 'input[name="password"]' ).prop( 'type', 'password' );
}
}
);
});

View File

@ -10,11 +10,11 @@
"automattic/jetpack-autoloader": "^1.2.0",
"php": ">=5.6|>=7.0",
"composer/installers": "1.7.0",
"woocommerce/woocommerce-blocks": "2.4.5",
"woocommerce/woocommerce-rest-api": "1.0.3"
"woocommerce/woocommerce-blocks": "2.5.3",
"woocommerce/woocommerce-rest-api": "1.0.4"
},
"require-dev": {
"phpunit/phpunit": "7.5.17",
"phpunit/phpunit": "7.5.18",
"woocommerce/woocommerce-sniffs": "0.0.9"
},
"config": {

110
composer.lock generated
View File

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a5cb448a1d94f10a40207b6f0dfa1c50",
"content-hash": "ef714343ae6391837070a82b2c800e56",
"packages": [
{
"name": "automattic/jetpack-autoloader",
"version": "v1.2.0",
"version": "v1.3.2",
"source": {
"type": "git",
"url": "https://github.com/Automattic/jetpack-autoloader.git",
"reference": "4ad9631e68e9da8b8a764615766287becfb27f81"
"reference": "301c2fbcf070d4f0147753447616b6e982bda09e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/4ad9631e68e9da8b8a764615766287becfb27f81",
"reference": "4ad9631e68e9da8b8a764615766287becfb27f81",
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/301c2fbcf070d4f0147753447616b6e982bda09e",
"reference": "301c2fbcf070d4f0147753447616b6e982bda09e",
"shasum": ""
},
"require": {
@ -40,7 +40,7 @@
"GPL-2.0-or-later"
],
"description": "Creates a custom autoloader for a plugin or theme.",
"time": "2019-06-24T15:13:23+00:00"
"time": "2019-09-24T06:39:29+00:00"
},
{
"name": "composer/installers",
@ -166,25 +166,25 @@
},
{
"name": "woocommerce/woocommerce-blocks",
"version": "v2.4.5",
"version": "v2.5.3",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
"reference": "130bc40824f844c0870c94c0ad0aa7a6abb74f6f"
"reference": "0f9220ef14e63447d81c60e68366ddf194e3ab40"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/130bc40824f844c0870c94c0ad0aa7a6abb74f6f",
"reference": "130bc40824f844c0870c94c0ad0aa7a6abb74f6f",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/0f9220ef14e63447d81c60e68366ddf194e3ab40",
"reference": "0f9220ef14e63447d81c60e68366ddf194e3ab40",
"shasum": ""
},
"require": {
"automattic/jetpack-autoloader": "1.2.0",
"automattic/jetpack-autoloader": "1.3.2",
"composer/installers": "1.7.0"
},
"require-dev": {
"phpunit/phpunit": "6.5.14",
"woocommerce/woocommerce-sniffs": "0.0.6"
"woocommerce/woocommerce-sniffs": "0.0.7"
},
"type": "wordpress-plugin",
"extra": {
@ -209,28 +209,28 @@
"gutenberg",
"woocommerce"
],
"time": "2019-11-02T13:48:18+00:00"
"time": "2019-12-09T16:58:15+00:00"
},
{
"name": "woocommerce/woocommerce-rest-api",
"version": "1.0.3",
"version": "1.0.4",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-rest-api.git",
"reference": "7d9babf1c25890c32df3edb28b8351732640f5ce"
"reference": "8d2eb27637184add937e5bbd6b13b486b70da355"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-rest-api/zipball/7d9babf1c25890c32df3edb28b8351732640f5ce",
"reference": "7d9babf1c25890c32df3edb28b8351732640f5ce",
"url": "https://api.github.com/repos/woocommerce/woocommerce-rest-api/zipball/8d2eb27637184add937e5bbd6b13b486b70da355",
"reference": "8d2eb27637184add937e5bbd6b13b486b70da355",
"shasum": ""
},
"require": {
"automattic/jetpack-autoloader": "1.2.0"
"automattic/jetpack-autoloader": "^1.2.0"
},
"require-dev": {
"phpunit/phpunit": "6.5.14",
"woocommerce/woocommerce-sniffs": "0.0.6"
"woocommerce/woocommerce-sniffs": "0.0.9"
},
"type": "wordpress-plugin",
"autoload": {
@ -249,7 +249,7 @@
],
"description": "The WooCommerce core REST API.",
"homepage": "https://github.com/woocommerce/woocommerce-rest-api",
"time": "2019-07-16T14:27:40+00:00"
"time": "2019-12-06T01:44:20+00:00"
}
],
"packages-dev": [
@ -321,16 +321,16 @@
},
{
"name": "doctrine/instantiator",
"version": "1.2.0",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "a2c590166b2133a4633738648b6b064edae0814a"
"reference": "ae466f726242e637cebdd526a7d991b9433bacf1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a",
"reference": "a2c590166b2133a4633738648b6b064edae0814a",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1",
"reference": "ae466f726242e637cebdd526a7d991b9433bacf1",
"shasum": ""
},
"require": {
@ -373,7 +373,7 @@
"constructor",
"instantiate"
],
"time": "2019-03-17T17:37:11+00:00"
"time": "2019-10-21T16:45:58+00:00"
},
{
"name": "myclabs/deep-copy",
@ -1152,16 +1152,16 @@
},
{
"name": "phpunit/phpunit",
"version": "7.5.17",
"version": "7.5.18",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a"
"reference": "fcf6c4bfafaadc07785528b06385cce88935474d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4c92a15296e58191a4cd74cff3b34fc8e374174a",
"reference": "4c92a15296e58191a4cd74cff3b34fc8e374174a",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fcf6c4bfafaadc07785528b06385cce88935474d",
"reference": "fcf6c4bfafaadc07785528b06385cce88935474d",
"shasum": ""
},
"require": {
@ -1232,7 +1232,7 @@
"testing",
"xunit"
],
"time": "2019-10-28T10:37:36+00:00"
"time": "2019-12-06T05:14:37+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@ -1401,16 +1401,16 @@
},
{
"name": "sebastian/environment",
"version": "4.2.2",
"version": "4.2.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404"
"reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
"reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
"reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368",
"shasum": ""
},
"require": {
@ -1450,7 +1450,7 @@
"environment",
"hhvm"
],
"time": "2019-05-05T09:05:15+00:00"
"time": "2019-11-20T08:46:58+00:00"
},
{
"name": "sebastian/exporter",
@ -1802,16 +1802,16 @@
},
{
"name": "squizlabs/php_codesniffer",
"version": "3.5.2",
"version": "3.5.3",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7"
"reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/65b12cdeaaa6cd276d4c3033a95b9b88b12701e7",
"reference": "65b12cdeaaa6cd276d4c3033a95b9b88b12701e7",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/557a1fc7ac702c66b0bbfe16ab3d55839ef724cb",
"reference": "557a1fc7ac702c66b0bbfe16ab3d55839ef724cb",
"shasum": ""
},
"require": {
@ -1849,20 +1849,20 @@
"phpcs",
"standards"
],
"time": "2019-10-28T04:36:32+00:00"
"time": "2019-12-04T04:46:47+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.12.0",
"version": "v1.13.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "550ebaac289296ce228a706d0867afc34687e3f4"
"reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
"reference": "550ebaac289296ce228a706d0867afc34687e3f4",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
"reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
"shasum": ""
},
"require": {
@ -1874,7 +1874,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.12-dev"
"dev-master": "1.13-dev"
}
},
"autoload": {
@ -1907,7 +1907,7 @@
"polyfill",
"portable"
],
"time": "2019-08-06T08:03:45+00:00"
"time": "2019-11-27T13:56:44+00:00"
},
{
"name": "theseer/tokenizer",
@ -1951,31 +1951,29 @@
},
{
"name": "webmozart/assert",
"version": "1.5.0",
"version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "88e6d84706d09a236046d686bbea96f07b3a34f4"
"reference": "573381c0a64f155a0d9a23f4b0c797194805b925"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4",
"reference": "88e6d84706d09a236046d686bbea96f07b3a34f4",
"url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925",
"reference": "573381c0a64f155a0d9a23f4b0c797194805b925",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"vimeo/psalm": "<3.6.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^7.5.13"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
@ -1997,7 +1995,7 @@
"check",
"validate"
],
"time": "2019-08-24T08:43:50+00:00"
"time": "2019-11-24T13:36:37+00:00"
},
{
"name": "woocommerce/woocommerce-sniffs",

View File

@ -698,6 +698,26 @@ return array(
),
'KR' => array(),
'KW' => array(),
'LA' => array(
'AT' => __( 'Attapeu', 'woocommerce' ),
'BK' => __( 'Bokeo', 'woocommerce' ),
'BL' => __( 'Bolikhamsai', 'woocommerce' ),
'CH' => __( 'Champasak', 'woocommerce' ),
'HO' => __( 'Houaphanh', 'woocommerce' ),
'KH' => __( 'Khammouane', 'woocommerce' ),
'LM' => __( 'Luang Namtha', 'woocommerce' ),
'LP' => __( 'Luang Prabang', 'woocommerce' ),
'OU' => __( 'Oudomxay', 'woocommerce' ),
'PH' => __( 'Phongsaly', 'woocommerce' ),
'SL' => __( 'Salavan', 'woocommerce' ),
'SV' => __( 'Savannakhet', 'woocommerce' ),
'VI' => __( 'Vientiane Province', 'woocommerce' ),
'VT' => __( 'Vientiane', 'woocommerce' ),
'XA' => __( 'Sainyabuli', 'woocommerce' ),
'XE' => __( 'Sekong', 'woocommerce' ),
'XI' => __( 'Xiangkhouang', 'woocommerce' ),
'XS' => __( 'Xaisomboun', 'woocommerce' ),
),
'LB' => array(),
'LR' => array( // Liberia provinces.
'BM' => __( 'Bomi', 'woocommerce' ),

View File

@ -18,6 +18,7 @@ require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php';
* WC_Abstract_Order class.
*/
abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
use WC_Item_Totals;
/**
* Order Data array. This is the core order data exposed in APIs since 3.0.0.
@ -769,6 +770,23 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
return apply_filters( 'woocommerce_order_get_items', $items, $this, $types );
}
/**
* Return array of values for calculations.
*
* @param string $field Field name to return.
*
* @return array Array of values.
*/
protected function get_values_for_total( $field ) {
$items = array_map(
function ( $item ) use ( $field ) {
return wc_add_number_precision( $item[ $field ], false );
},
array_values( $this->get_items() )
);
return $items;
}
/**
* Return an array of coupons within this order.
*
@ -1498,6 +1516,34 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$this->save();
}
/**
* Helper function.
* If you add all items in this order in cart again, this would be the cart subtotal (assuming all other settings are same).
*
* @return float Cart subtotal.
*/
protected function get_cart_subtotal_for_order() {
return wc_remove_number_precision(
$this->get_rounded_items_total(
$this->get_values_for_total( 'subtotal' )
)
);
}
/**
* Helper function.
* If you add all items in this order in cart again, this would be the cart total (assuming all other settings are same).
*
* @return float Cart total.
*/
protected function get_cart_total_for_order() {
return wc_remove_number_precision(
$this->get_rounded_items_total(
$this->get_values_for_total( 'total' )
)
);
}
/**
* Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
*
@ -1508,18 +1554,13 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
public function calculate_totals( $and_taxes = true ) {
do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
$cart_subtotal = 0;
$cart_total = 0;
$fees_total = 0;
$fees_total = 0;
$shipping_total = 0;
$cart_subtotal_tax = 0;
$cart_total_tax = 0;
// Sum line item costs.
foreach ( $this->get_items() as $item ) {
$cart_subtotal += round( $item->get_subtotal(), wc_get_price_decimals() );
$cart_total += round( $item->get_total(), wc_get_price_decimals() );
}
$cart_subtotal = $this->get_cart_subtotal_for_order();
$cart_total = $this->get_cart_total_for_order();
// Sum shipping costs.
foreach ( $this->get_shipping_methods() as $shipping ) {
@ -1740,15 +1781,16 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
*/
public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
$subtotal = 0;
$subtotal = $this->get_cart_subtotal_for_order();
if ( ! $compound ) {
foreach ( $this->get_items() as $item ) {
$subtotal += $item->get_subtotal();
if ( 'incl' === $tax_display ) {
$subtotal += $item->get_subtotal_tax();
if ( 'incl' === $tax_display ) {
$subtotal_taxes = 0;
foreach ( $this->get_items() as $item ) {
$subtotal_taxes += self::round_line_tax( $item->get_subtotal_tax(), false );
}
$subtotal += wc_round_tax_total( $subtotal_taxes );
}
$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
@ -1761,10 +1803,6 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
return '';
}
foreach ( $this->get_items() as $item ) {
$subtotal += $item->get_subtotal();
}
// Add Shipping Costs.
$subtotal += $this->get_shipping_total();

View File

@ -131,6 +131,13 @@ abstract class WC_Payment_Gateway extends WC_Settings_API {
*/
public $new_method_label = '';
/**
* Pay button ID if supported.
*
* @var string
*/
public $pay_button_id = '';
/**
* Contains a users saved tokens for this gateway.
*
@ -317,6 +324,16 @@ abstract class WC_Payment_Gateway extends WC_Settings_API {
return apply_filters( 'woocommerce_gateway_icon', $icon, $this->id );
}
/**
* Return the gateway's pay button ID.
*
* @since 3.9.0
* @return string
*/
public function get_pay_button_id() {
return sanitize_html_class( $this->pay_button_id );
}
/**
* Set as current gateway.
*
@ -439,7 +456,9 @@ abstract class WC_Payment_Gateway extends WC_Settings_API {
);
wp_localize_script(
'woocommerce-tokenization-form', 'wc_tokenization_form_params', array(
'woocommerce-tokenization-form',
'wc_tokenization_form_params',
array(
'is_registration_required' => WC()->checkout()->is_registration_required(),
'is_logged_in' => is_user_logged_in(),
)
@ -521,7 +540,7 @@ abstract class WC_Payment_Gateway extends WC_Settings_API {
esc_html__( 'Save to account', 'woocommerce' )
);
echo apply_filters( 'woocommerce_payment_gateway_save_new_payment_method_option_html', $html, $this );
echo apply_filters( 'woocommerce_payment_gateway_save_new_payment_method_option_html', $html, $this ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**

View File

@ -1859,7 +1859,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
}
if ( ! $image && $placeholder ) {
$image = wc_placeholder_img( $size );
$image = wc_placeholder_img( $size, $attr );
}
return apply_filters( 'woocommerce_product_get_image', $image, $this, $size, $attr, $placeholder, $image );

View File

@ -169,9 +169,9 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
$params = array(
/* translators: %s: decimal */
'i18n_decimal_error' => sprintf( __( 'Please enter in decimal (%s) format without thousand separators.', 'woocommerce' ), $decimal ),
'i18n_decimal_error' => sprintf( __( 'Please enter with one decimal point (%s) without thousand separators.', 'woocommerce' ), $decimal ),
/* translators: %s: price decimal separator */
'i18n_mon_decimal_error' => sprintf( __( 'Please enter in monetary decimal (%s) format without thousand separators and currency symbols.', 'woocommerce' ), wc_get_price_decimal_separator() ),
'i18n_mon_decimal_error' => sprintf( __( 'Please enter with one monetary decimal point (%s) without thousand separators and currency symbols.', 'woocommerce' ), wc_get_price_decimal_separator() ),
'i18n_country_iso_error' => __( 'Please enter in country code with two capital letters.', 'woocommerce' ),
'i18n_sale_less_than_regular_error' => __( 'Please enter in a value less than the regular price.', 'woocommerce' ),
'i18n_delete_product_notice' => __( 'This product has produced sales and may be linked to existing orders. Are you sure you want to delete it?', 'woocommerce' ),

View File

@ -642,6 +642,10 @@ class WC_Admin_Post_Types {
wc_update_product_stock( $product, $stock_amount, 'set', true );
break;
}
} else {
// Reset values if WooCommerce Setting - Manage Stock status is disabled.
$product->set_stock_quantity( '' );
$product->set_manage_stock( 'no' );
}
// Apply product type constraints to stock status.

View File

@ -155,6 +155,27 @@ class WC_Admin_Setup_Wizard {
return current_user_can( 'install_plugins' );
}
/**
* Should we show the new WooCommerce Admin onboarding experience?
*
* @return boolean
*/
protected function should_show_wc_admin_onboarding() {
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
$ab_test = get_option( 'woocommerce_setup_ab_wc_admin_onboarding' );
// If it doesn't exist yet, generate it for later use and save it, so we always show the same to this user.
if ( ! $ab_test ) {
$ab_test = 1 !== rand( 1, 10 ) ? 'a' : 'b'; // 10% of users. b gets the new experience.
update_option( 'woocommerce_setup_ab_wc_admin_onboarding', $ab_test );
}
return 'b' === $ab_test;
}
/**
* Should we display the 'Recommended' step?
* True if at least one of the recommendations will be displayed.
@ -251,38 +272,48 @@ class WC_Admin_Setup_Wizard {
return;
}
$default_steps = array(
'store_setup' => array(
'name' => __( 'Store setup', 'woocommerce' ),
'view' => array( $this, 'wc_setup_store_setup' ),
'handler' => array( $this, 'wc_setup_store_setup_save' ),
'new_onboarding' => array(
'name' => '',
'view' => array( $this, 'wc_setup_new_onboarding' ),
'handler' => array( $this, 'wc_setup_new_onboarding_save' ),
),
'payment' => array(
'name' => __( 'Payment', 'woocommerce' ),
'view' => array( $this, 'wc_setup_payment' ),
'handler' => array( $this, 'wc_setup_payment_save' ),
'store_setup' => array(
'name' => __( 'Store setup', 'woocommerce' ),
'view' => array( $this, 'wc_setup_store_setup' ),
'handler' => array( $this, 'wc_setup_store_setup_save' ),
),
'shipping' => array(
'name' => __( 'Shipping', 'woocommerce' ),
'view' => array( $this, 'wc_setup_shipping' ),
'handler' => array( $this, 'wc_setup_shipping_save' ),
'payment' => array(
'name' => __( 'Payment', 'woocommerce' ),
'view' => array( $this, 'wc_setup_payment' ),
'handler' => array( $this, 'wc_setup_payment_save' ),
),
'recommended' => array(
'name' => __( 'Recommended', 'woocommerce' ),
'view' => array( $this, 'wc_setup_recommended' ),
'handler' => array( $this, 'wc_setup_recommended_save' ),
'shipping' => array(
'name' => __( 'Shipping', 'woocommerce' ),
'view' => array( $this, 'wc_setup_shipping' ),
'handler' => array( $this, 'wc_setup_shipping_save' ),
),
'activate' => array(
'name' => __( 'Activate', 'woocommerce' ),
'view' => array( $this, 'wc_setup_activate' ),
'handler' => array( $this, 'wc_setup_activate_save' ),
'recommended' => array(
'name' => __( 'Recommended', 'woocommerce' ),
'view' => array( $this, 'wc_setup_recommended' ),
'handler' => array( $this, 'wc_setup_recommended_save' ),
),
'next_steps' => array(
'name' => __( 'Ready!', 'woocommerce' ),
'view' => array( $this, 'wc_setup_ready' ),
'handler' => '',
'activate' => array(
'name' => __( 'Activate', 'woocommerce' ),
'view' => array( $this, 'wc_setup_activate' ),
'handler' => array( $this, 'wc_setup_activate_save' ),
),
'next_steps' => array(
'name' => __( 'Ready!', 'woocommerce' ),
'view' => array( $this, 'wc_setup_ready' ),
'handler' => '',
),
);
// Hide the new/improved onboarding experience screen if the user is not part of the a/b test.
if ( ! $this->should_show_wc_admin_onboarding() ) {
unset( $default_steps['new_onboarding'] );
}
// Hide recommended step if nothing is going to be shown there.
if ( ! $this->should_show_recommended_step() ) {
unset( $default_steps['recommended'] );
@ -346,6 +377,9 @@ class WC_Admin_Setup_Wizard {
* Setup Wizard Header.
*/
public function setup_wizard_header() {
// same as default WP from wp-admin/admin-header.php.
$wp_version_class = 'branch-' . str_replace( array( '.', ',' ), '-', floatval( get_bloginfo( 'version' ) ) );
set_current_screen();
?>
<!DOCTYPE html>
@ -359,8 +393,8 @@ class WC_Admin_Setup_Wizard {
<?php do_action( 'admin_print_styles' ); ?>
<?php do_action( 'admin_head' ); ?>
</head>
<body class="wc-setup wp-core-ui">
<h1 id="wc-logo"><a href="https://woocommerce.com/"><img src="<?php echo esc_url( WC()->plugin_url() ); ?>/assets/images/woocommerce_logo.png" alt="<?php esc_attr_e( 'WooCommerce', 'woocommerce' ); ?>" /></a></h1>
<body class="wc-setup wp-core-ui <?php echo esc_attr( 'wc-setup-step__' . $this->step ); ?> <?php echo esc_attr( $wp_version_class ); ?>">
<h1 class="wc-logo"><a href="https://woocommerce.com/"><img src="<?php echo esc_url( WC()->plugin_url() ); ?>/assets/images/woocommerce_logo.png" alt="<?php esc_attr_e( 'WooCommerce', 'woocommerce' ); ?>" /></a></h1>
<?php
}
@ -369,7 +403,9 @@ class WC_Admin_Setup_Wizard {
*/
public function setup_wizard_footer() {
?>
<?php if ( 'store_setup' === $this->step ) : ?>
<?php if ( 'new_onboarding' === $this->step ) : ?>
<a class="wc-setup-footer-links" href="<?php echo esc_url( $this->get_next_step_link() ); ?>"><?php esc_html_e( 'Continue with the old setup wizard', 'woocommerce' ); ?></a>
<?php elseif ( 'store_setup' === $this->step ) : ?>
<a class="wc-setup-footer-links" href="<?php echo esc_url( admin_url() ); ?>"><?php esc_html_e( 'Not right now', 'woocommerce' ); ?></a>
<?php elseif ( 'recommended' === $this->step || 'activate' === $this->step ) : ?>
<a class="wc-setup-footer-links" href="<?php echo esc_url( $this->get_next_step_link() ); ?>"><?php esc_html_e( 'Skip this step', 'woocommerce' ); ?></a>
@ -393,6 +429,8 @@ class WC_Admin_Setup_Wizard {
unset( $output_steps['activate'] );
}
unset( $output_steps['new_onboarding'] );
?>
<ol class="wc-setup-steps">
<?php
@ -431,6 +469,72 @@ class WC_Admin_Setup_Wizard {
echo '</div>';
}
/**
* Display's a prompt for users to try out the new improved WooCommerce onboarding experience in WooCommerce Admin.
*/
public function wc_setup_new_onboarding() {
?>
<div class="wc-setup-step__new_onboarding-wrapper">
<p class="wc-setup-step__new_onboarding-welcome"><?php esc_html_e( 'Welcome to', 'woocommerce' ); ?></p>
<h1 class="wc-logo"><a href="https://woocommerce.com/"><img src="<?php echo esc_url( WC()->plugin_url() ); ?>/assets/images/woocommerce_logo.png" alt="<?php esc_attr_e( 'WooCommerce', 'woocommerce' ); ?>" /></a></h1>
<p><?php esc_html_e( 'Get your store up and running more quickly with our new and improved setup experience', 'woocommerce' ); ?></p>
<form method="post" class="activate-new-onboarding">
<?php wp_nonce_field( 'wc-setup' ); ?>
<input type="hidden" name="save_step" value="new_onboarding" />
<p class="wc-setup-actions step">
<button class="button-primary button button-large" value="<?php esc_attr_e( 'Yes please', 'woocommerce' ); ?>" name="save_step"><?php esc_html_e( 'Yes please', 'woocommerce' ); ?></button>
</p>
</form>
<p class="wc-setup-step__new_onboarding-plugin-info"><?php esc_html_e( 'The "WooCommerce Admin" plugin will be installed and activated', 'woocommerce' ); ?></p>
</div>
<?php
}
/**
* Installs WooCommerce admin and redirects to the new onboarding experience.
*/
public function wc_setup_new_onboarding_save() {
check_admin_referer( 'wc-setup' );
update_option( 'wc_onboarding_opt_in', 'yes' );
if ( function_exists( 'wc_admin_url' ) ) {
$this->wc_setup_redirect_to_wc_admin_onboarding();
exit;
}
WC_Install::background_installer(
'woocommerce-admin',
array(
'name' => __( 'WooCommerce Admin', 'woocommerce' ),
'repo-slug' => 'woocommerce-admin',
)
);
// The plugin was not successfully installed, so continue with normal setup.
if ( ! function_exists( 'wc_admin_url' ) ) {
wp_safe_redirect( esc_url_raw( $this->get_next_step_link() ) );
exit;
}
$this->wc_setup_redirect_to_wc_admin_onboarding();
exit;
}
/**
* Redirects to the onboarding wizard in WooCommerce Admin.
*/
private function wc_setup_redirect_to_wc_admin_onboarding() {
// Renables the wizard.
$profile_updates = array( 'completed' => false );
$onboarding_data = get_option( 'wc_onboarding_profile', array() );
update_option( 'wc_onboarding_profile', array_merge( $onboarding_data, $profile_updates ) );
wp_safe_redirect( esc_url_raw( admin_url( 'admin.php?page=wc-admin' ) ) );
}
/**
* Initial "store setup" step.
* Location, product type, page setup, and tracking opt-in.

View File

@ -453,7 +453,7 @@ class WC_Meta_Box_Product_Data {
continue;
}
$variation_id = absint( $_POST['variable_post_id'][ $i ] );
$variation = new WC_Product_Variation( $variation_id );
$variation = wc_get_product_object( 'variation', $variation_id );
$stock = null;
// Handle stock changes.

View File

@ -115,6 +115,10 @@ $row_class = apply_filters( 'woocommerce_admin_html_order_item_class', ! empt
$tax_item_id = $tax_item->get_rate_id();
$tax_item_total = isset( $tax_data['total'][ $tax_item_id ] ) ? $tax_data['total'][ $tax_item_id ] : '';
$tax_item_subtotal = isset( $tax_data['subtotal'][ $tax_item_id ] ) ? $tax_data['subtotal'][ $tax_item_id ] : '';
$round_at_subtotal = 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
$tax_item_total = wc_round_tax_total( $tax_item_total, $round_at_subtotal ? wc_get_rounding_precision() : null );
$tax_item_subtotal = wc_round_tax_total( $tax_item_subtotal, $round_at_subtotal ? wc_get_rounding_precision() : null );
?>
<td class="line_tax" width="1%">
<div class="view">

View File

@ -345,7 +345,7 @@ if ( wc_tax_enabled() ) {
</thead>
<?php
$row = '
<td><select class="wc-product-search" name="item_id" data-allow_clear="true" data-display_stock="true" data-placeholder="' . esc_attr__( 'Search for a product&hellip;', 'woocommerce' ) . '"></select></td>
<td><select class="wc-product-search" name="item_id" data-allow_clear="true" data-display_stock="true" data-exclude_type="variable" data-placeholder="' . esc_attr__( 'Search for a product&hellip;', 'woocommerce' ) . '"></select></td>
<td><input type="number" step="1" min="0" max="9999" autocomplete="off" name="item_qty" placeholder="1" size="4" class="quantity" /></td>';
?>
<tbody data-row="<?php echo esc_attr( $row ); ?>">

View File

@ -33,7 +33,11 @@ class WC_Settings_Accounts extends WC_Settings_Page {
public function get_settings() {
$erasure_text = esc_html__( 'account erasure request', 'woocommerce' );
if ( current_user_can( 'manage_privacy_options' ) ) {
$erasure_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'tools.php?page=remove_personal_data' ) ), $erasure_text );
if ( version_compare( get_bloginfo( 'version' ), '5.3', '<' ) ) {
$erasure_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'tools.php?page=remove_personal_data' ) ), $erasure_text );
} else {
$erasure_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'erase-personal-data.php' ) ), $erasure_text );
}
}
$account_settings = array(

View File

@ -429,6 +429,11 @@ function wc_render_action_buttons( $actions ) {
function wc_render_invalid_variation_notice( $product_object ) {
global $wpdb;
// Give ability for extensions to hide this notice.
if ( ! apply_filters( 'woocommerce_show_invalid_variations_notice', true, $product_object ) ) {
return;
}
$variation_ids = $product_object ? $product_object->get_children() : array();
if ( empty( $variation_ids ) ) {

View File

@ -707,8 +707,8 @@ class WC_AJAX {
$product_id = intval( $_POST['post_id'] );
$post = get_post( $product_id ); // phpcs:ignore
$loop = intval( $_POST['loop'] );
$product_object = new WC_Product_Variable( $product_id ); // Forces type to variable in case product is unsaved.
$variation_object = new WC_Product_Variation();
$product_object = wc_get_product_object( 'variable', $product_id ); // Forces type to variable in case product is unsaved.
$variation_object = wc_get_product_object( 'variation' );
$variation_object->set_parent_id( $product_id );
$variation_object->set_attributes( array_fill_keys( array_map( 'sanitize_title', array_keys( $product_object->get_variation_attributes() ) ), '' ) );
$variation_id = $variation_object->save();
@ -898,6 +898,10 @@ class WC_AJAX {
if ( ! $product ) {
throw new Exception( __( 'Invalid product ID', 'woocommerce' ) . ' ' . $product_id );
}
if ( 'variable' === $product->get_type() ) {
/* translators: %s product name */
throw new Exception( sprintf( __( '%s is a variable product parent and cannot be added.', 'woocommerce' ), $product->get_name() ) );
}
$validation_error = new WP_Error();
$validation_error = apply_filters( 'woocommerce_ajax_add_order_item_validation', $validation_error, $product, $order, $qty );
@ -1536,6 +1540,24 @@ class WC_AJAX {
$include_ids = ! empty( $_GET['include'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['include'] ) ) : array();
$exclude_ids = ! empty( $_GET['exclude'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) : array();
$exclude_types = array();
if ( ! empty( $_GET['exclude_type'] ) ) {
// Support both comma-delimited and array format inputs.
$exclude_types = wp_unslash( $_GET['exclude_type'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( ! is_array( $exclude_types ) ) {
$exclude_types = explode( ',', $exclude_types );
}
// Sanitize the excluded types against valid product types.
foreach ( $exclude_types as &$exclude_type ) {
$exclude_type = strtolower( trim( $exclude_type ) );
}
$exclude_types = array_intersect(
array_merge( array( 'variation' ), array_keys( wc_get_product_types() ) ),
$exclude_types
);
}
$data_store = WC_Data_Store::load( 'product' );
$ids = $data_store->search_products( $term, '', (bool) $include_variations, false, $limit, $include_ids, $exclude_ids );
@ -1546,6 +1568,10 @@ class WC_AJAX {
$formatted_name = $product_object->get_formatted_name();
$managing_stock = $product_object->managing_stock();
if ( in_array( $product_object->get_type(), $exclude_types, true ) ) {
continue;
}
if ( $managing_stock && ! empty( $_GET['display_stock'] ) ) {
$stock_amount = $product_object->get_stock_quantity();
/* Translators: %d stock amount */

View File

@ -23,6 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
* @since 3.2.0
*/
final class WC_Cart_Totals {
use WC_Item_Totals;
/**
* Reference to cart object.
@ -199,15 +200,6 @@ final class WC_Cart_Totals {
);
}
/**
* Should we round at subtotal level only?
*
* @return bool
*/
protected function round_at_subtotal() {
return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
}
/**
* Handles a cart or order object passed in for calculation. Normalises data
* into the same format for use by this class.
@ -561,6 +553,16 @@ final class WC_Cart_Totals {
return $in_cents ? $this->totals : wc_remove_number_precision_deep( $this->totals );
}
/**
* Returns array of values for totals calculation.
*
* @param string $field Field name. Will probably be `total` or `subtotal`.
* @return array Items object
*/
protected function get_values_for_total( $field ) {
return array_values( wp_list_pluck( $this->items, $field ) );
}
/**
* Get taxes merged by type.
*
@ -598,6 +600,7 @@ final class WC_Cart_Totals {
/**
* Round merged taxes.
*
* @deprecated 3.9.0 `calculate_item_subtotals` should already appropriately round the tax values.
* @since 3.5.4
* @param array $taxes Taxes to round.
* @return array
@ -681,12 +684,8 @@ final class WC_Cart_Totals {
$this->cart->cart_contents[ $item_key ]['line_tax'] = wc_remove_number_precision( $item->total_tax );
}
$items_total = array_sum(
array_map(
array( $this, 'round_item_subtotal' ),
array_values( wp_list_pluck( $this->items, 'total' ) )
)
);
$items_total = $this->get_rounded_items_total( $this->get_values_for_total( 'total' ) );
$this->set_total( 'items_total', round( $items_total ) );
$this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
@ -712,11 +711,14 @@ final class WC_Cart_Totals {
protected function calculate_item_subtotals() {
$merged_subtotal_taxes = array(); // Taxes indexed by tax rate ID for storage later.
$adjust_non_base_location_prices = apply_filters( 'woocommerce_adjust_non_base_location_prices', true );
$is_customer_vat_exempt = $this->cart->get_customer()->get_is_vat_exempt();
foreach ( $this->items as $item_key => $item ) {
if ( $item->price_includes_tax ) {
if ( $this->cart->get_customer()->get_is_vat_exempt() ) {
if ( $is_customer_vat_exempt ) {
$item = $this->remove_item_base_taxes( $item );
} elseif ( apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
} elseif ( $adjust_non_base_location_prices ) {
$item = $this->adjust_non_base_location_price( $item );
}
}
@ -745,12 +747,8 @@ final class WC_Cart_Totals {
$this->cart->cart_contents[ $item_key ]['line_subtotal_tax'] = wc_remove_number_precision( $item->subtotal_tax );
}
$items_subtotal = array_sum(
array_map(
array( $this, 'round_item_subtotal' ),
array_values( wp_list_pluck( $this->items, 'subtotal' ) )
)
);
$items_subtotal = $this->get_rounded_items_total( $this->get_values_for_total( 'subtotal' ) );
$this->set_total( 'items_subtotal', round( $items_subtotal ) );
$this->set_total( 'items_subtotal_tax', wc_round_tax_total( array_sum( $merged_subtotal_taxes ), 0 ) );
@ -862,7 +860,7 @@ final class WC_Cart_Totals {
* @since 3.2.0
*/
protected function calculate_totals() {
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( true ) ), 0 ) );
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + wc_round_tax_total( array_sum( $this->get_merged_taxes( true ) ), 0 ), 0 ) );
$this->cart->set_total_tax( array_sum( $this->get_merged_taxes( false ) ) );
// Allow plugins to hook and alter totals before final total is calculated.
@ -873,32 +871,4 @@ final class WC_Cart_Totals {
// Allow plugins to filter the grand total, and sum the cart totals in case of modifications.
$this->cart->set_total( max( 0, apply_filters( 'woocommerce_calculated_total', $this->get_total( 'total' ), $this->cart ) ) );
}
/**
* Apply rounding to an array of taxes before summing. Rounds to store DP setting, ignoring precision.
*
* @since 3.2.6
* @param float $value Tax value.
* @return float
*/
protected function round_line_tax( $value ) {
if ( ! $this->round_at_subtotal() ) {
$value = wc_round_tax_total( $value, 0 );
}
return $value;
}
/**
* Apply rounding to item subtotal before summing.
*
* @since 3.7.0
* @param float $value Item subtotal value.
* @return float
*/
protected function round_item_subtotal( $value ) {
if ( ! $this->round_at_subtotal() ) {
$value = round( $value );
}
return $value;
}
}

View File

@ -712,11 +712,11 @@ class WC_Checkout {
switch ( $fieldset_key ) {
case 'shipping':
/* translators: %s: field name */
$field_label = sprintf( __( 'Shipping %s', 'woocommerce' ), $field_label );
$field_label = sprintf( _x( 'Shipping %s', 'checkout-validation', 'woocommerce' ), $field_label );
break;
case 'billing':
/* translators: %s: field name */
$field_label = sprintf( __( 'Billing %s', 'woocommerce' ), $field_label );
$field_label = sprintf( _x( 'Billing %s', 'checkout-validation', 'woocommerce' ), $field_label );
break;
}
@ -734,14 +734,14 @@ class WC_Checkout {
/* translators: %s: field name */
$postcode_validation_notice = sprintf( __( '%s is not a valid postcode / ZIP.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' );
}
$errors->add( 'validation', apply_filters( 'woocommerce_checkout_postcode_validation_notice', $postcode_validation_notice, $country, $data[ $key ] ) );
$errors->add( 'validation', apply_filters( 'woocommerce_checkout_postcode_validation_notice', $postcode_validation_notice, $country, $data[ $key ] ), array( 'id' => $key ) );
}
}
if ( in_array( 'phone', $format, true ) ) {
if ( $validate_fieldset && '' !== $data[ $key ] && ! WC_Validation::is_phone( $data[ $key ] ) ) {
/* translators: %s: phone number */
$errors->add( 'validation', sprintf( __( '%s is not a valid phone number.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ) );
$errors->add( 'validation', sprintf( __( '%s is not a valid phone number.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ), array( 'id' => $key ) );
}
}
@ -751,7 +751,7 @@ class WC_Checkout {
if ( $validate_fieldset && ! $email_is_valid ) {
/* translators: %s: email address */
$errors->add( 'validation', sprintf( __( '%s is not a valid email address.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ) );
$errors->add( 'validation', sprintf( __( '%s is not a valid email address.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ), array( 'id' => $key ) );
continue;
}
}
@ -771,14 +771,14 @@ class WC_Checkout {
if ( $validate_fieldset && ! in_array( $data[ $key ], $valid_state_values, true ) ) {
/* translators: 1: state field 2: valid states */
$errors->add( 'validation', sprintf( __( '%1$s is not valid. Please enter one of the following: %2$s', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>', implode( ', ', $valid_states ) ) );
$errors->add( 'validation', sprintf( __( '%1$s is not valid. Please enter one of the following: %2$s', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>', implode( ', ', $valid_states ) ), array( 'id' => $key ) );
}
}
}
if ( $validate_fieldset && $required && '' === $data[ $key ] ) {
/* translators: %s: field name */
$errors->add( 'required-field', apply_filters( 'woocommerce_checkout_required_field_notice', sprintf( __( '%s is a required field.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ), $field_label ) );
$errors->add( 'required-field', apply_filters( 'woocommerce_checkout_required_field_notice', sprintf( __( '%s is a required field.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ), $field_label ), array( 'id' => $key ) );
}
}
}
@ -1101,8 +1101,11 @@ class WC_Checkout {
// Validate posted data and cart items before proceeding.
$this->validate_checkout( $posted_data, $errors );
foreach ( $errors->get_error_messages() as $message ) {
wc_add_notice( $message, 'error' );
foreach ( $errors->errors as $code => $messages ) {
$data = $errors->get_error_data( $code );
foreach ( $messages as $message ) {
wc_add_notice( $message, 'error', $data );
}
}
if ( empty( $posted_data['woocommerce_checkout_update_totals'] ) && 0 === wc_notice_count( 'error' ) ) {

View File

@ -113,7 +113,7 @@ class WC_Form_Handler {
// Validation: Required fields.
if ( ! empty( $field['required'] ) && empty( $value ) ) {
/* translators: %s: Field name. */
wc_add_notice( sprintf( __( '%s is a required field.', 'woocommerce' ), $field['label'] ), 'error' );
wc_add_notice( sprintf( __( '%s is a required field.', 'woocommerce' ), $field['label'] ), 'error', array( 'id' => $key ) );
}
if ( ! empty( $value ) ) {
@ -259,7 +259,7 @@ class WC_Form_Handler {
foreach ( $required_fields as $field_key => $field_name ) {
if ( empty( $_POST[ $field_key ] ) ) {
/* translators: %s: Field name. */
wc_add_notice( sprintf( __( '%s is a required field.', 'woocommerce' ), '<strong>' . esc_html( $field_name ) . '</strong>' ), 'error' );
wc_add_notice( sprintf( __( '%s is a required field.', 'woocommerce' ), '<strong>' . esc_html( $field_name ) . '</strong>' ), 'error', array( 'id' => $field_key ) );
}
}
@ -340,7 +340,7 @@ class WC_Form_Handler {
* Process the checkout form.
*/
public static function checkout_action() {
if ( isset( $_POST['woocommerce_checkout_place_order'] ) || isset( $_POST['woocommerce_checkout_update_totals'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
if ( isset( $_POST['woocommerce_checkout_place_order'] ) || isset( $_POST['woocommerce_checkout_update_totals'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
wc_nocache_headers();
if ( WC()->cart->is_empty() ) {
@ -459,6 +459,27 @@ class WC_Form_Handler {
return;
}
// Test rate limit.
$current_user_id = get_current_user_id();
$rate_limit_id = 'add_payment_method_' . $current_user_id;
$delay = (int) apply_filters( 'woocommerce_payment_gateway_add_payment_method_delay', 20 );
if ( WC_Rate_Limiter::retried_too_soon( $rate_limit_id ) ) {
wc_add_notice(
/* translators: %d number of seconds */
_n(
'You cannot add a new payment method so soon after the previous one. Please wait for %d second.',
'You cannot add a new payment method so soon after the previous one. Please wait for %d seconds.',
$delay,
'woocommerce'
),
'error'
);
return;
}
WC_Rate_Limiter::set_rate_limit( $rate_limit_id, $delay );
ob_start();
$payment_method_id = wc_clean( wp_unslash( $_POST['payment_method'] ) );

View File

@ -307,13 +307,13 @@ class WC_Frontend_Scripts {
private static function register_styles() {
$register_styles = array(
'photoswipe' => array(
'src' => self::get_asset_url( 'assets/css/photoswipe/photoswipe.css' ),
'src' => self::get_asset_url( 'assets/css/photoswipe/photoswipe.min.css' ),
'deps' => array(),
'version' => WC_VERSION,
'has_rtl' => false,
),
'photoswipe-default-skin' => array(
'src' => self::get_asset_url( 'assets/css/photoswipe/default-skin/default-skin.css' ),
'src' => self::get_asset_url( 'assets/css/photoswipe/default-skin/default-skin.min.css' ),
'deps' => array( 'photoswipe' ),
'version' => WC_VERSION,
'has_rtl' => false,

View File

@ -46,7 +46,7 @@ class WC_Order_Factory {
try {
return new $classname( $order_id );
} catch ( Exception $e ) {
wc_caught_exception( $e, __FUNCTION__, func_get_args() );
wc_caught_exception( $e, __FUNCTION__, array( $order_id ) );
return false;
}
}

View File

@ -234,7 +234,7 @@ class WC_Query {
* @return bool
*/
private function is_showing_page_on_front( $q ) {
return $q->is_home() && 'page' === get_option( 'show_on_front' );
return ( $q->is_home() && ! $q->is_posts_page ) && 'page' === get_option( 'show_on_front' );
}
/**

View File

@ -0,0 +1,79 @@
<?php
/**
* Provide basic rate limiting functionality via WP Options API.
*
* Currently only provides a simple limit by delaying action by X seconds.
*
* Example usage:
*
* When an action runs, call set_rate_limit, e.g.:
*
* WC_Rate_Limiter::set_rate_limit( "{$my_action_name}_{$user_id}", $delay );
*
* This sets a timestamp for future timestamp after which action can run again.
*
*
* Then before running the action again, check if the action is allowed to run, e.g.:
*
* if ( WC_Rate_Limiter::retried_too_soon( "{$my_action_name}_{$user_id}" ) ) {
* add_notice( 'Sorry, too soon!' );
* }
*
* @package WooCommerce/Classes
* @version 3.9.0
* @since 3.9.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Rate limit class.
*/
class WC_Rate_Limiter {
/**
* Constructs Option name from action identifier.
*
* @param string $action_id Identifier of the action.
* @return string
*/
public static function storage_id( $action_id ) {
return 'woocommerce_rate_limit_' . $action_id;
}
/**
* Returns true if the action is not allowed to be run by the rate limiter yet, false otherwise.
*
* @param string $action_id Identifier of the action.
* @return bool
*/
public static function retried_too_soon( $action_id ) {
$next_try_allowed_at = get_option( self::storage_id( $action_id ) );
// No record of action running, so action is allowed to run.
if ( false === $next_try_allowed_at ) {
return false;
}
// Before the next run is allowed, retry forbidden.
if ( time() <= $next_try_allowed_at ) {
return true;
}
// After the next run is allowed, retry allowed.
return false;
}
/**
* Sets the rate limit delay in seconds for action with identifier $id.
*
* @param string $action_id Identifier of the action.
* @param int $delay Delay in seconds.
* @return bool True if the option setting was successful, false otherwise.
*/
public static function set_rate_limit( $action_id, $delay ) {
$option_name = self::storage_id( $action_id );
$next_try_allowed_at = time() + $delay;
return update_option( $option_name, $next_try_allowed_at );
}
}

View File

@ -512,7 +512,7 @@ class WC_Shortcodes {
// Check if sku is a variation.
if ( isset( $atts['sku'] ) && $single_product->have_posts() && 'product_variation' === $single_product->post->post_type ) {
$variation = new WC_Product_Variation( $single_product->post->ID );
$variation = wc_get_product_object( 'variation', $single_product->post->ID );
$attributes = $variation->get_attributes();
// Set preselected id to be used by JS to provide context.

View File

@ -96,7 +96,9 @@ class WC_Validation {
case 'NL':
$valid = (bool) preg_match( '/^([1-9][0-9]{3})(\s?)(?!SA|SD|SS)[A-Z]{2}$/i', $postcode );
break;
case 'SI':
$valid = (bool) preg_match( '/^([1-9][0-9]{3})$/', $postcode );
break;
default:
$valid = true;
break;

View File

@ -20,7 +20,7 @@ final class WooCommerce {
*
* @var string
*/
public $version = '3.8.0';
public $version = '3.9.0';
/**
* The single instance of the class.
@ -337,6 +337,11 @@ final class WooCommerce {
include_once WC_ABSPATH . 'includes/interfaces/class-wc-webhooks-data-store-interface.php';
include_once WC_ABSPATH . 'includes/interfaces/class-wc-queue-interface.php';
/**
* Core traits.
*/
include_once WC_ABSPATH . 'includes/traits/trait-wc-item-totals.php';
/**
* Abstract classes.
*/

View File

@ -224,7 +224,6 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
* @since 3.0.0
*/
private function update_post_meta( &$coupon ) {
$updated_props = array();
$meta_key_to_props = array(
'discount_type' => 'discount_type',
'coupon_amount' => 'amount',
@ -278,7 +277,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
}
}
do_action( 'woocommerce_coupon_object_updated_props', $coupon, $updated_props );
do_action( 'woocommerce_coupon_object_updated_props', $coupon, $this->updated_props );
}
/**

View File

@ -1178,7 +1178,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
if ( in_array( $possible_attribute, $existing_attributes ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
continue;
}
$variation = new WC_Product_Variation();
$variation = wc_get_product_object( 'variation' );
$variation->set_parent_id( $product->get_id() );
$variation->set_attributes( $possible_attribute );
$variation_id = $variation->save();

View File

@ -361,7 +361,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
$prices_array['price'][ $variation_id ] = wc_format_decimal( $price, wc_get_price_decimals() );
$prices_array['regular_price'][ $variation_id ] = wc_format_decimal( $regular_price, wc_get_price_decimals() );
$prices_array['sale_price'][ $variation_id ] = wc_format_decimal( $sale_price . '.00', wc_get_price_decimals() );
$prices_array['sale_price'][ $variation_id ] = wc_format_decimal( $sale_price, wc_get_price_decimals() );
$prices_array = apply_filters( 'woocommerce_variation_prices_array', $prices_array, $variation, $for_display );
}

View File

@ -48,10 +48,14 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
$post_object = get_post( $product->get_id() );
if ( ! $post_object || ! in_array( $post_object->post_type, array( 'product', 'product_variation' ), true ) ) {
if ( ! $post_object ) {
return;
}
if ( 'product_variation' !== $post_object->post_type ) {
throw new Exception( 'Invalid product type: passed ID does not correspond to a product variation.' );
}
$product->set_props(
array(
'name' => $post_object->post_title,
@ -116,7 +120,7 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
*/
public function create( &$product ) {
if ( ! $product->get_date_created() ) {
$product->set_date_created( current_time( 'timestamp', true ) );
$product->set_date_created( time() );
}
$new_title = $this->generate_product_title( $product );
@ -186,7 +190,7 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
$product->save_meta_data();
if ( ! $product->get_date_created() ) {
$product->set_date_created( current_time( 'timestamp', true ) );
$product->set_date_created( time() );
}
$new_title = $this->generate_product_title( $product );

View File

@ -184,6 +184,7 @@ class WC_Webhook_Data_Store implements WC_Webhook_Data_Store_Interface {
); // WPCS: cache ok, DB call ok.
$this->delete_transients( 'all' );
wp_cache_delete( $webhook->get_id(), 'webhooks' );
WC_Cache_Helper::invalidate_cache_group( 'webhooks' );
do_action( 'woocommerce_webhook_deleted', $webhook->get_id(), $webhook );
}

View File

@ -173,13 +173,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
return new WP_Error( 'woocommerce_product_importer_invalid_type', __( 'Invalid product type.', 'woocommerce' ), array( 'status' => 401 ) );
}
$classname = WC_Product_Factory::get_classname_from_product_type( $data['type'] );
if ( ! class_exists( $classname ) ) {
$classname = 'WC_Product_Simple';
}
$product = new $classname( $id );
$product = wc_get_product_object( $data['type'], $id );
} elseif ( ! empty( $data['id'] ) ) {
$product = wc_get_product( $id );
@ -195,7 +189,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
);
}
} else {
$product = new WC_Product_Simple( $id );
$product = wc_get_product_object( 'simple', $id );
}
return apply_filters( 'woocommerce_product_import_get_product_object', $product, $data );

View File

@ -183,7 +183,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
// If we're not updating existing posts, we may need a placeholder product to map to.
if ( ! $this->params['update_existing'] ) {
$product = new WC_Product_Simple();
$product = wc_get_product_object( 'simple' );
$product->set_name( 'Import placeholder for ' . $id );
$product->set_status( 'importing' );
$product->add_meta_data( '_original_id', $id, true );
@ -200,7 +200,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
}
try {
$product = new WC_Product_Simple();
$product = wc_get_product_object( 'simple' );
$product->set_name( 'Import placeholder for ' . $value );
$product->set_status( 'importing' );
$product->set_sku( $value );
@ -254,7 +254,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
return $id_from_sku;
}
$product = new WC_Product_Simple();
$product = wc_get_product_object( 'simple' );
$product->set_name( 'Import placeholder for ' . $id );
$product->set_status( 'importing' );
$product->add_meta_data( '_original_id', $id, true );

View File

@ -40,7 +40,9 @@ class WC_Shortcode_Cart {
}
if ( $address['country'] ) {
WC()->customer->set_billing_location( $address['country'], $address['state'], $address['postcode'], $address['city'] );
if ( ! WC()->customer->get_billing_first_name() ) {
WC()->customer->set_billing_location( $address['country'], $address['state'], $address['postcode'], $address['city'] );
}
WC()->customer->set_shipping_location( $address['country'], $address['state'], $address['postcode'], $address['city'] );
} else {
WC()->customer->set_billing_address_to_base();

View File

@ -115,8 +115,8 @@ class WC_Shortcode_Products {
'limit' => '-1', // Results limit.
'columns' => '', // Number of columns.
'rows' => '', // Number of rows. If defined, limit will be ignored.
'orderby' => 'title', // menu_order, title, date, rand, price, popularity, rating, or id.
'order' => 'ASC', // ASC or DESC.
'orderby' => '', // menu_order, title, date, rand, price, popularity, rating, or id.
'order' => '', // ASC or DESC.
'ids' => '', // Comma separated IDs.
'skus' => '', // Comma separated SKUs.
'category' => '', // Comma separated category slugs or ids.
@ -189,7 +189,7 @@ class WC_Shortcode_Products {
$query_args['order'] = $order;
if ( wc_string_to_bool( $this->attributes['paginate'] ) ) {
$this->attributes['page'] = absint( empty( $_GET['product-page'] ) ? 1 : $_GET['product-page'] ); // WPCS: input var ok, CSRF ok.
$this->attributes['page'] = absint( empty( $_GET['product-page'] ) ? 1 : $_GET['product-page'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
if ( ! empty( $this->attributes['rows'] ) ) {
@ -643,7 +643,7 @@ class WC_Shortcode_Products {
if ( wc_get_loop_prop( 'total' ) ) {
foreach ( $products->ids as $product_id ) {
$GLOBALS['post'] = get_post( $product_id ); // WPCS: override ok.
$GLOBALS['post'] = get_post( $product_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
setup_postdata( $GLOBALS['post'] );
// Set custom product visibility when quering hidden products.
@ -657,7 +657,7 @@ class WC_Shortcode_Products {
}
}
$GLOBALS['post'] = $original_post; // WPCS: override ok.
$GLOBALS['post'] = $original_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
woocommerce_product_loop_end();
// Fire standard shop loop hooks when paginating results so we can show result counts and so on.

View File

@ -0,0 +1,89 @@
<?php
/**
* This ongoing trait will have shared calculation logic between WC_Abstract_Order and WC_Cart_Totals classes.
*
* @package WooCommerce/Traits
* @version 3.9.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Trait WC_Item_Totals.
*
* Right now this do not have much, but plan is to eventually move all shared calculation logic between Orders and Cart in this file.
*
* @since 3.9.0
*/
trait WC_Item_Totals {
/**
* Line items to calculate. Define in child class.
*
* @since 3.9.0
* @param string $field Field name to calculate upon.
*
* @return array having `total`|`subtotal` property.
*/
abstract protected function get_values_for_total( $field );
/**
* Return rounded total based on settings. Will be used by Cart and Orders.
*
* @since 3.9.0
*
* @param array $values Values to round. Should be with precision.
*
* @return float|int Appropriately rounded value.
*/
public static function get_rounded_items_total( $values ) {
return array_sum(
array_map(
array( self::class, 'round_item_subtotal' ),
$values
)
);
}
/**
* Apply rounding to item subtotal before summing.
*
* @since 3.9.0
* @param float $value Item subtotal value.
* @return float
*/
public static function round_item_subtotal( $value ) {
if ( ! self::round_at_subtotal() ) {
$value = round( $value );
}
return $value;
}
/**
* Should always round at subtotal?
*
* @since 3.9.0
* @return bool
*/
protected static function round_at_subtotal() {
return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
}
/**
* Apply rounding to an array of taxes before summing. Rounds to store DP setting, ignoring precision.
*
* @since 3.2.6
* @param float $value Tax value.
* @param bool $in_cents Whether precision of value is in cents.
* @return float
*/
protected static function round_line_tax( $value, $in_cents = true ) {
if ( ! self::round_at_subtotal() ) {
$value = wc_round_tax_total( $value, $in_cents ? 0 : null );
}
return $value;
}
}

View File

@ -292,7 +292,9 @@ function wc_format_decimal( $number, $dp = false, $trim_zeros = false ) {
// Remove locale from string.
if ( ! is_float( $number ) ) {
$number = str_replace( $decimals, '.', $number );
$number = preg_replace( '/[^0-9\.,-]/', '', wc_clean( $number ) );
// Convert multiple dots to just one.
$number = preg_replace( '/\.(?![^.]+$)|[^0-9.-]/', '', wc_clean( $number ) );
}
if ( false !== $dp ) {
@ -735,7 +737,8 @@ function wc_timezone_string() {
// Last try, guess timezone string manually.
foreach ( timezone_abbreviations_list() as $abbr ) {
foreach ( $abbr as $city ) {
if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) {
// WordPress restrict the use of date(), since it's affected by timezone settings, but in this case is just what we need to guess the correct timezone.
if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
return $city['timezone_id'];
}
}

View File

@ -59,17 +59,18 @@ function wc_has_notice( $message, $notice_type = 'success' ) {
$notices = WC()->session->get( 'wc_notices', array() );
$notices = isset( $notices[ $notice_type ] ) ? $notices[ $notice_type ] : array();
return array_search( $message, $notices, true ) !== false;
return array_search( $message, wp_list_pluck( $notices, 'notice' ), true ) !== false;
}
/**
* Add and store a notice.
*
* @since 2.1
* @param string $message The text to display in the notice.
* @param string $message The text to display in the notice.
* @param string $notice_type Optional. The name of the notice type - either error, success or notice.
* @param array $data Optional notice data.
*/
function wc_add_notice( $message, $notice_type = 'success' ) {
function wc_add_notice( $message, $notice_type = 'success', $data = array() ) {
if ( ! did_action( 'woocommerce_init' ) ) {
wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.3' );
return;
@ -82,7 +83,10 @@ function wc_add_notice( $message, $notice_type = 'success' ) {
$message = apply_filters( 'woocommerce_add_message', $message );
}
$notices[ $notice_type ][] = apply_filters( 'woocommerce_add_' . $notice_type, $message );
$notices[ $notice_type ][] = array(
'notice' => apply_filters( 'woocommerce_add_' . $notice_type, $message ),
'data' => $data,
);
WC()->session->set( 'wc_notices', $notices );
}
@ -91,17 +95,17 @@ function wc_add_notice( $message, $notice_type = 'success' ) {
* Set all notices at once.
*
* @since 2.6.0
* @param mixed $notices Array of notices.
* @param array[] $notices Array of notices.
*/
function wc_set_notices( $notices ) {
if ( ! did_action( 'woocommerce_init' ) ) {
wc_doing_it_wrong( __FUNCTION__, __( 'This function should not be called before woocommerce_init.', 'woocommerce' ), '2.6' );
return;
}
WC()->session->set( 'wc_notices', $notices );
}
/**
* Unset all notices.
*
@ -136,10 +140,17 @@ function wc_print_notices( $return = false ) {
foreach ( $notice_types as $notice_type ) {
if ( wc_notice_count( $notice_type ) > 0 ) {
$messages = array();
foreach ( $all_notices[ $notice_type ] as $notice ) {
$messages[] = isset( $notice['notice'] ) ? $notice['notice'] : $notice;
}
wc_get_template(
"notices/{$notice_type}.php",
array(
'messages' => array_filter( $all_notices[ $notice_type ] ),
'messages' => array_filter( $messages ), // @deprecated 3.9.0
'notices' => array_filter( $all_notices[ $notice_type ] ),
)
);
}
@ -153,7 +164,7 @@ function wc_print_notices( $return = false ) {
return $notices;
}
echo $notices; // WPCS: XSS ok.
echo $notices; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
@ -162,16 +173,25 @@ function wc_print_notices( $return = false ) {
* @since 2.1
* @param string $message The text to display in the notice.
* @param string $notice_type Optional. The singular name of the notice type - either error, success or notice.
* @param array $data Optional notice data. @since 3.9.0.
*/
function wc_print_notice( $message, $notice_type = 'success' ) {
function wc_print_notice( $message, $notice_type = 'success', $data = array() ) {
if ( 'success' === $notice_type ) {
$message = apply_filters( 'woocommerce_add_message', $message );
}
$message = apply_filters( 'woocommerce_add_' . $notice_type, $message );
wc_get_template(
"notices/{$notice_type}.php",
array(
'messages' => array( apply_filters( 'woocommerce_add_' . $notice_type, $message ) ),
'messages' => array( $message ), // @deprecated 3.9.0
'notices' => array(
array(
'notice' => $message,
'data' => $data,
),
),
)
);
}
@ -181,7 +201,7 @@ function wc_print_notice( $message, $notice_type = 'success' ) {
*
* @since 2.1
* @param string $notice_type Optional. The singular name of the notice type - either error, success or notice.
* @return array|mixed
* @return array[]
*/
function wc_get_notices( $notice_type = '' ) {
if ( ! did_action( 'woocommerce_init' ) ) {
@ -240,3 +260,28 @@ function wc_kses_notice( $message ) {
*/
return wp_kses( $message, apply_filters( 'woocommerce_kses_notice_allowed_tags', $allowed_tags ) );
}
/**
* Get notice data attribute.
*
* @since 3.9.0
* @param array $notice Notice data.
* @return string
*/
function wc_get_notice_data_attr( $notice ) {
if ( empty( $notice['data'] ) ) {
return;
}
$attr = '';
foreach ( $notice['data'] as $key => $value ) {
$attr .= sprintf(
' data-%1$s="%2$s"',
sanitize_title( $key ),
esc_attr( $value )
);
}
return $attr;
}

View File

@ -46,6 +46,9 @@ function wc_get_products( $args ) {
/**
* Main function for returning products, uses the WC_Product_Factory class.
*
* This function should only be called after 'init' action is finished, as there might be taxonomies that are getting
* registered during the init action.
*
* @since 2.2.0
*
* @param mixed $the_product Post object or post ID of the product.
@ -53,9 +56,9 @@ function wc_get_products( $args ) {
* @return WC_Product|null|false
*/
function wc_get_product( $the_product = false, $deprecated = array() ) {
if ( ! did_action( 'woocommerce_init' ) ) {
/* translators: 1: wc_get_product 2: woocommerce_init */
wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%1$s should not be called before the %2$s action.', 'woocommerce' ), 'wc_get_product', 'woocommerce_init' ), '2.5' );
if ( ! did_action( 'woocommerce_init' ) || ! did_action( 'woocommerce_after_register_taxonomy' ) || ! did_action( 'woocommerce_after_register_post_type' ) ) {
/* translators: 1: wc_get_product 2: woocommerce_init 3: woocommerce_after_register_taxonomy 4: woocommerce_after_register_post_type */
wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%1$s should not be called before the %2$s, %3$s and %4$s actions have finished.', 'woocommerce' ), 'wc_get_product', 'woocommerce_init', 'woocommerce_after_register_taxonomy', 'woocommerce_after_register_post_type' ), '3.9' );
return false;
}
if ( ! empty( $deprecated ) ) {
@ -64,6 +67,21 @@ function wc_get_product( $the_product = false, $deprecated = array() ) {
return WC()->product_factory->get_product( $the_product, $deprecated );
}
/**
* Get a product object.
*
* @see WC_Product_Factory::get_product_classname
* @since 3.9.0
* @param string $product_type Product type. If used an invalid type a WC_Product_Simple instance will be returned.
* @param int $product_id Product ID.
* @return WC_Product
*/
function wc_get_product_object( $product_type, $product_id = 0 ) {
$classname = WC_Product_Factory::get_product_classname( $product_id, $product_type );
return new $classname( $product_id );
}
/**
* Returns whether or not SKUS are enabled.
*
@ -288,26 +306,38 @@ function wc_placeholder_img_src( $size = 'woocommerce_thumbnail' ) {
*
* Uses wp_get_attachment_image if using an attachment ID @since 3.6.0 to handle responsiveness.
*
* @param string $size Image size.
* @param string $size Image size.
* @param string|array $attr Optional. Attributes for the image markup. Default empty.
* @return string
*/
function wc_placeholder_img( $size = 'woocommerce_thumbnail' ) {
function wc_placeholder_img( $size = 'woocommerce_thumbnail', $attr = '' ) {
$dimensions = wc_get_image_size( $size );
$placeholder_image = get_option( 'woocommerce_placeholder_image', 0 );
$default_attr = array(
'class' => 'woocommerce-placeholder wp-post-image',
'alt' => __( 'Placeholder', 'woocommerce' ),
);
$attr = wp_parse_args( $attr, $default_attr );
if ( wp_attachment_is_image( $placeholder_image ) ) {
$image_html = wp_get_attachment_image(
$placeholder_image,
$size,
false,
array(
'alt' => __( 'Placeholder', 'woocommerce' ),
'class' => 'woocommerce-placeholder wp-post-image',
)
$attr
);
} else {
$image = wc_placeholder_img_src( $size );
$image_html = '<img src="' . esc_attr( $image ) . '" alt="' . esc_attr__( 'Placeholder', 'woocommerce' ) . '" width="' . esc_attr( $dimensions['width'] ) . '" class="woocommerce-placeholder wp-post-image" height="' . esc_attr( $dimensions['height'] ) . '" />';
$hwstring = image_hwstring( $dimensions['width'], $dimensions['height'] );
$attributes = array();
foreach ( $attr as $name => $value ) {
$attribute[] = esc_attr( $name ) . '="' . esc_attr( $value ) . '"';
}
$image_html = '<img src="' . esc_url( $image ) . '" ' . $hwstring . implode( ' ', $attribute ) . '/>';
}
return apply_filters( 'woocommerce_placeholder_img', $image_html, $size, $dimensions );
@ -1389,7 +1419,6 @@ function wc_update_product_lookup_tables_column( $column ) {
return;
}
global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
switch ( $column ) {
case 'min_max_price':
$wpdb->query(
@ -1435,7 +1464,8 @@ function wc_update_product_lookup_tables_column( $column ) {
} else {
$meta_key = '_' . $column;
}
$column = esc_sql( $column );
$column = esc_sql( $column );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query(
$wpdb->prepare(
"
@ -1448,11 +1478,13 @@ function wc_update_product_lookup_tables_column( $column ) {
$meta_key
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
break;
case 'downloadable':
case 'virtual':
$column = esc_sql( $column );
$column = esc_sql( $column );
$meta_key = '_' . $column;
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query(
$wpdb->prepare(
"
@ -1465,11 +1497,13 @@ function wc_update_product_lookup_tables_column( $column ) {
$meta_key
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
break;
case 'onsale':
$column = esc_sql( $column );
$decimals = absint( wc_get_price_decimals() );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query(
$wpdb->prepare(
"
@ -1488,11 +1522,11 @@ function wc_update_product_lookup_tables_column( $column ) {
$decimals
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
delete_option( 'woocommerce_product_lookup_table_is_generating' ); // Complete.
break;
}
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
}
add_action( 'wc_update_product_lookup_tables_column', 'wc_update_product_lookup_tables_column' );

View File

@ -80,7 +80,8 @@ add_action( 'template_redirect', 'wc_template_redirect' );
* @since 2.3.10
*/
function wc_send_frame_options_header() {
if ( is_checkout() || is_account_page() ) {
if ( ( is_checkout() || is_account_page() ) && ! is_customize_preview() ) {
send_frame_options_header();
}
}
@ -3666,3 +3667,29 @@ if ( ! function_exists( 'woocommerce_product_reviews_tab' ) ) {
wc_deprecated_function( 'woocommerce_product_reviews_tab', '2.4' );
}
}
/**
* Display pay buttons HTML.
*
* @since 3.9.0
*/
function wc_get_pay_buttons() {
$supported_gateways = array();
$available_gateways = WC()->payment_gateways()->get_available_payment_gateways();
foreach ( $available_gateways as $gateway ) {
if ( $gateway->supports( 'pay_button' ) ) {
$supported_gateways[] = $gateway->get_pay_button_id();
}
}
if ( ! $supported_gateways ) {
return;
}
echo '<div class="woocommerce-pay-buttons">';
foreach ( $supported_gateways as $pay_button_id ) {
echo sprintf( '<div class="woocommerce-pay-button__%1$s %1$s" id="%1$s"></div>', esc_attr( $pay_button_id ) );
}
echo '</div>';
}

View File

@ -207,6 +207,9 @@ add_action( 'woocommerce_product_additional_information', 'wc_display_product_at
* @see woocommerce_checkout_coupon_form()
* @see woocommerce_order_review()
* @see woocommerce_checkout_payment()
* @see wc_checkout_privacy_policy_text()
* @see wc_terms_and_conditions_page_content()
* @see wc_get_pay_buttons()
*/
add_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_login_form', 10 );
add_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form', 10 );
@ -214,6 +217,7 @@ add_action( 'woocommerce_checkout_order_review', 'woocommerce_order_review', 10
add_action( 'woocommerce_checkout_order_review', 'woocommerce_checkout_payment', 20 );
add_action( 'woocommerce_checkout_terms_and_conditions', 'wc_checkout_privacy_policy_text', 20 );
add_action( 'woocommerce_checkout_terms_and_conditions', 'wc_terms_and_conditions_page_content', 30 );
add_action( 'woocommerce_checkout_before_customer_details', 'wc_get_pay_buttons', 30 );
/**
* Cart widget
@ -227,10 +231,13 @@ add_action( 'woocommerce_widget_shopping_cart_total', 'woocommerce_widget_shoppi
*
* @see woocommerce_cross_sell_display()
* @see woocommerce_cart_totals()
* @see wc_get_pay_buttons()
* @see woocommerce_button_proceed_to_checkout()
* @see wc_empty_cart_message()
*/
add_action( 'woocommerce_cart_collaterals', 'woocommerce_cross_sell_display' );
add_action( 'woocommerce_cart_collaterals', 'woocommerce_cart_totals', 10 );
add_action( 'woocommerce_proceed_to_checkout', 'wc_get_pay_buttons', 10 );
add_action( 'woocommerce_proceed_to_checkout', 'woocommerce_button_proceed_to_checkout', 20 );
add_action( 'woocommerce_cart_is_empty', 'wc_empty_cart_message', 10 );

View File

@ -257,7 +257,7 @@ class WC_WCCOM_Site_Installer {
break;
case 'move_product':
$state_steps[ $product_id ]['installed_path'] = $result['destination'];
if ( $result[ self::$folder_exists ] ) {
if ( isset( $result[ self::$folder_exists ] ) ) {
$state_steps[ $product_id ]['warning'] = array(
'message' => self::$folder_exists,
'plugin_info' => self::get_plugin_info( $state_steps[ $product_id ]['installed_path'] ),

View File

@ -201,11 +201,16 @@ class WC_WCCOM_Site {
* @return bool
*/
protected static function is_request_to_wccom_site_rest_api() {
$request_uri = add_query_arg( array() );
$rest_prefix = trailingslashit( rest_get_url_prefix() );
$request_uri = esc_url_raw( wp_unslash( $request_uri ) );
$rest_prefix = '';
return false !== strpos( $request_uri, $rest_prefix . 'wccom-site/' );
if ( isset( $_REQUEST['rest_route'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$route = wp_unslash( $_REQUEST['rest_route'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
} else {
$route = wp_unslash( add_query_arg( array() ) );
$rest_prefix = trailingslashit( rest_get_url_prefix() );
}
return false !== strpos( $route, $rest_prefix . 'wccom-site/' );
}
/**

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "woocommerce",
"version": "3.7.0",
"version": "3.9.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,7 +1,7 @@
{
"name": "woocommerce",
"title": "WooCommerce",
"version": "3.7.0",
"version": "3.9.0",
"homepage": "https://woocommerce.com/",
"repository": {
"type": "git",

View File

@ -1,10 +1,10 @@
=== WooCommerce ===
Contributors: automattic, mikejolley, jameskoster, claudiosanches, kloon, rodrigosprimo, peterfabian1000, vedjain
Tags: ecommerce, e-commerce, store, sales, sell, shop, cart, checkout, downloadable, downloads, payments, paypal, storefront, stripe, woo commerce, woo
Requires at least: 4.9
Requires at least: 5.0
Tested up to: 5.3
Requires PHP: 5.6
Stable tag: 3.8.0
Requires PHP: 7.0
Stable tag: 3.8.1
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -179,7 +179,88 @@ INTERESTED IN DEVELOPMENT?
== Changelog ==
= 3.9.0 - 2020-01-xx =
= 3.9.0 - 2020-01-07 =
* Enhancement - Added a "Show" button next to the password field on the login fields. #24915
* Enhancement - New WooCommerce Onboarding experience (shows to only 10% of new users). #24991
* Enhancement - Introduced Payment Gateway API to support "pay button". #25000
* Enhancement - Includes WooCommerce Blocks 2.5.3, introducing an "All Products" block, a new block listing products using client side rendering (requires WordPress 5.3), and more. #25181
* Tweak - Updated PayPal standard "Thank you" page message to comply with PayPal Guidelines. #24756
* Tweak - Account for non-EU countries that collect VAT and rename tax to VAT on the frontend. #24999
* Tweak - Cache checkout fragments and update DOM on change only. #24227
* Tweak - Eliminate extra update order AJAX request on checkout page load. #24271
* Tweak - Prevent billing address from being updated on shipping update. #24374
* Tweak - Added a tooltip in the "Coupon expity date" field. #24749
* Tweak - Make phone numbers clickable in emails. #24786
* Tweak - Prevent PHP warnings in tracker if order doesn't have a created date yet. #24846
* Tweak - Capitalize "T" in "Move to Trash" phrase on order page in wp-admin to be consistent with product and coupon pages. #24867
* Tweak - Changed `wp_cache` invalidation from using increment to using microtime. #24961
* Tweak - Made the usage tracking link on the setup wizard more transparent. #25026
* Tweak - Fixed menu highlight of My Account page when browsing "Add payment method" page. #25041
* Tweak - Prevent creating products before registering related post types and taxonomies. #25049
* Tweak - Include processing orders in tracker data when opted in. #25071
* Fix - Honor tax rounding preference in edit item and refund flows. #24208
* Fix - Prevent incorrect number of decimal points in prices. #24281
* Fix - Fixed initial support for Gutenberg's Experimental Legacy Widget block. #24292
* Fix - Fix overriding of query when using orderby on archives with a static homepage. #24683
* Fix - Use of `wp_unslash()` function when escaping admin settings values. #24793
* Fix - Do not set the tracking cookie when doing ajax requests. #24798
* Fix - Display button to delete images from product galleries in the admin when using a mobile device. #24840
* Fix - Fixed order note's date format. #24843
* Fix - Refactored `WC_Order_Factory::get_order()` to remove function deprecated in PHP 7.0. #24852
* Fix - Fixed product stock status changes on Bulk Edit save when "Enable stock management" is disabled. #24876
* Fix - Fixed default country code fallback in wc_get_customer_default_location(). #24884
* Fix - Fixed misleading message for Shipping options in cart. #24914
* Fix - Customizer not loading when viewing from WordPress.com. #24935
* Fix - Prevent notice when a variable product has no images. #24986
* Fix - Adjusted the slug generation for duplicated variable products to prevent performance degradation when using templates. #25064
* Fix - Added appropriate minification to photoswipe.css. #25074
* Fix - Corrected the sorting behavior for the "products" shortcode when manually sorting products. #25084
* Fix - Fixed invalid backlinks for in-app purchases. #25098
* Fix - Corrected the media element player initialization for product variation descriptions. #25103
* Fix - Enable WooCommerce.com Site API on installations not using permalink. #25131
* Fix - WooCommerce.com Site API now returns success if the plugin was previously installed. #25140
* Fix - WooCommerce.com Site API checks to `move_product` case to make sure result array contains `folder_exists` item and doesn't return a warning. #25160
* Fix - Ensure that categories containing only private products are selectable in the product exporter. #25132
* Fix - Prevent variable product parents from being added to orders. #25162
* Fix - Use sorting settings as a default to product shortcodes. #25180
* Fix - Applied setup wizard CSS fixes to the respective WP versions. #25197
* Fix - Fixed "account erasure request" URL in WordPress 5.3. #25208
* Fix - Ensure all cache get removed on webhook deletion. #25164
* Template - Introduced `woocommerce_product_related_products_heading` filter. #25059
* Template - Introduced `woocommerce_before_lost_password_confirmation_message` and `woocommerce_after_lost_password_confirmation_message` hooks. #25096
* REST API - Fixed `date_created` and `date_created_gmt` for customers v2. #25181
* REST API - Fixed Restored "Total post count" section on System Status endpoint v2 and v3. #25181
* REST API - Filter empty objects from results before loop. #25181
* Dev - Introduce new PHP 7.0 minimum requirement.
* Dev - Introduce new WordPress 5.0 minimum requirement.
* Dev - Check for max discount to be "-ve" to prevent overwriting refunded fee amount. #24341
* Dev - Add unload event to the checkout page to prevent reloading during checkout after placing an order. #24609
* Dev - Only toggle form field description if element exists. #24752
* Dev - Introduced `woocommerce_{$export_type}_export_delimiter` filter to change separator string while exporting CSV files. #24759
* Dev - Introduced `woocommerce_after_order_refund_item_name` hook. #24760
* Dev - Introduced `woocommerce_kses_notice_allowed_tags` filter. #24849
* Dev - Introduced `woocommerce_shipping_not_enabled_on_cart_html` filter. #24914
* Dev - Introduced `woocommerce_show_invalid_variations_notice` filter. #24934
* Dev - Introduced `woocommerce_upsells_order` filter. #25017
* Dev - Introduced `woocommerce_before_settings_{current_tab}` and `woocommerce_after_settings_{current_tab}` hooks. #25028
* Dev - Included third parameter `$order` to `woocommerce_order_get_formatted_billing_address` and `woocommerce_order_get_formatted_shipping_address` filters. #24870
* Dev - Pass the `$clear_persistent_cart` variable to the `woocommerce_before_cart_emptied` and `woocommerce_cart_emptied actions`. #24930
* Dev - Made variables in `assets/css/_variables.scss` default. #24822
* Dev - Refactor to use the same rounding logic in orders and cart. #24828
* Dev - Add order note immediately after status change before the `woocommerce_order_status_changed action. #24879
* Dev - Added support for custom attributes in `wc_placeholder_img()`. #24937
* Dev - Added initial support for inline notices on checkout. #25001
* Dev - Introduced wc_get_product_object() helper. #25031
* Dev - Pass the correct `$this->updated_props` variable to the `woocommerce_coupon_object_updated_props` action's second paramater. #25077
* Dev - Remove a few calls to `func_get_args()` and `call_user_func_array()` with the spread operator for better code legibility and performance gains. #25101
- Dev - New `woocommerce_valid_order_statuses_for_payment` hook that triggers when an order is paid. Use this new hook instead of `woocommerce_order_status_changed` or *woocommerce_order_status_{old_status}}_to_{new_status}` to trigger code for payment completion. #25158
* Dev - Ability to exclude certain product types from product search calls. #25162
* Dev - Raise exception when `WC_Product_Variation` is instantiated with an ID that belongs to an object that is not a variation. #25178
* Localization - Add subdivisions of Laos. #24765
* Localization - Fixed translatable string in WooCommerce's libraries. #24892 #24894
* Localization - Fixed translatable string comments for translators. #24928
* Localization - Add postcode validation for Slovenia. #25174
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/CHANGELOG.txt).

View File

@ -58,7 +58,11 @@ $calculator_text = '';
<?php endif; ?>
<?php
elseif ( ! $has_calculated_shipping || ! $formatted_destination ) :
echo wp_kses_post( apply_filters( 'woocommerce_shipping_may_be_available_html', __( 'Enter your address to view shipping options.', 'woocommerce' ) ) );
if ( is_cart() && 'no' === get_option( 'woocommerce_enable_shipping_calc' ) ) {
echo wp_kses_post( apply_filters( 'woocommerce_shipping_not_enabled_on_cart_html', __( 'Shipping costs are calculated during checkout.', 'woocommerce' ) ) );
} else {
echo wp_kses_post( apply_filters( 'woocommerce_shipping_may_be_available_html', __( 'Enter your address to view shipping options.', 'woocommerce' ) ) );
}
elseif ( ! is_cart() ) :
echo wp_kses_post( apply_filters( 'woocommerce_no_shipping_available_html', __( 'There are no shipping options available. Please ensure that your address has been entered correctly, or contact us if you need any help.', 'woocommerce' ) ) );
else :

View File

@ -19,12 +19,12 @@ defined( 'ABSPATH' ) || exit;
do_action( 'woocommerce_email_header', $email_heading, $email ); ?>
<?php /* translators: %s Customer username */ ?>
<?php /* translators: %s: Customer username */ ?>
<p><?php printf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $user_login ) ); ?></p>
<?php /* translators: %1$s: Site title, %2$s: Username, %3$s: My account link */ ?>
<p><?php printf( esc_html__( 'Thanks for creating an account on %1$s. Your username is %2$s. You can access your account area to view orders, change your password, and more at: %3$s', 'woocommerce' ), esc_html( $blogname ), '<strong>' . esc_html( $user_login ) . '</strong>', make_clickable( esc_url( wc_get_page_permalink( 'myaccount' ) ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
<?php if ( 'yes' === get_option( 'woocommerce_registration_generate_password' ) && $password_generated ) : ?>
<?php /* translators: %s Auto generated password */ ?>
<?php /* translators: %s: Auto generated password */ ?>
<p><?php printf( esc_html__( 'Your password has been automatically generated: %s', 'woocommerce' ), '<strong>' . esc_html( $user_pass ) . '</strong>' ); ?></p>
<?php endif; ?>

View File

@ -23,11 +23,11 @@ if ( ! defined( 'ABSPATH' ) ) {
<?php do_action( 'woocommerce_email_header', $email_heading, $email ); ?>
<?php /* translators: %s: Customer first name */ ?>
<?php /* translators: %s: Customer username */ ?>
<p><?php printf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $user_login ) ); ?>
<?php /* translators: %s: Store name */ ?>
<p><?php printf( esc_html__( 'Someone has requested a new password for the following account on %s:', 'woocommerce' ), esc_html( wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ) ); ?></p>
<?php /* translators: %s Customer username */ ?>
<?php /* translators: %s: Customer username */ ?>
<p><?php printf( esc_html__( 'Username: %s', 'woocommerce' ), esc_html( $user_login ) ); ?></p>
<p><?php esc_html_e( 'If you didn\'t make this request, just ignore this email. If you\'d like to proceed:', 'woocommerce' ); ?></p>
<p>

View File

@ -12,7 +12,7 @@
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates/Emails
* @version 3.5.4
* @version 3.9.0
*/
if ( ! defined( 'ABSPATH' ) ) {

View File

@ -27,7 +27,7 @@ echo sprintf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $order->get_billi
if ( $order->has_status( 'pending' ) ) {
echo wp_kses_post(
sprintf(
/* translators: %1$s Site title, %2$s Order pay link */
/* translators: %1$s: Site title, %2$s: Order pay link */
__( 'An order has been created for you on %1$s. Your invoice is below, with a link to make payment when youre ready: %2$s', 'woocommerce' ),
esc_html( get_bloginfo( 'name', 'display' ) ),
esc_url( $order->get_checkout_payment_url() )
@ -35,7 +35,7 @@ if ( $order->has_status( 'pending' ) ) {
) . "\n\n";
} else {
/* translators: %s Order date */
/* translators: %s: Order date */
echo sprintf( esc_html__( 'Here are the details of your order placed on %s:', 'woocommerce' ), esc_html( wc_format_datetime( $order->get_date_created() ) ) ) . "\n\n";
}

View File

@ -21,13 +21,13 @@ echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
echo esc_html( wp_strip_all_tags( $email_heading ) );
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
/* translators: %s Customer first name */
/* translators: %s: Customer username */
echo sprintf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $user_login ) ) . "\n\n";
/* translators: %1$s: Site title, %2$s: Username, %3$s: My account link */
echo sprintf( esc_html__( 'Thanks for creating an account on %1$s. Your username is %2$s. You can access your account area to view orders, change your password, and more at: %3$s', 'woocommerce' ), esc_html( $blogname ), '<strong>' . esc_html( $user_login ) . '</strong>', esc_html( wc_get_page_permalink( 'myaccount' ) ) ) . "\n\n";
if ( 'yes' === get_option( 'woocommerce_registration_generate_password' ) && $password_generated ) {
/* translators: %s Auto generated password */
/* translators: %s: Auto generated password */
echo sprintf( esc_html__( 'Your password has been automatically generated: %s.', 'woocommerce' ), esc_html( $user_pass ) ) . "\n\n";
}

View File

@ -21,7 +21,7 @@ echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
echo esc_html( wp_strip_all_tags( $email_heading ) );
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
/* translators: %s Customer first name */
/* translators: %s: Customer first name */
echo sprintf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $order->get_billing_first_name() ) ) . "\n\n";
echo esc_html__( 'The following note has been added to your order:', 'woocommerce' ) . "\n\n";

View File

@ -21,7 +21,7 @@ echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
echo esc_html( wp_strip_all_tags( $email_heading ) );
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
/* translators: %s: Customer first name */
/* translators: %s: Customer username */
echo sprintf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $user_login ) ) . "\n\n";
/* translators: %s: Store name */
echo sprintf( esc_html__( 'Someone has requested a new password for the following account on %s:', 'woocommerce' ), esc_html( wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ) ) . "\n\n";

View File

@ -12,7 +12,7 @@
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.5.2
* @version 3.9.0
*/
defined( 'ABSPATH' ) || exit;
@ -20,4 +20,8 @@ defined( 'ABSPATH' ) || exit;
wc_print_notice( esc_html__( 'Password reset email has been sent.', 'woocommerce' ) );
?>
<?php do_action( 'woocommerce_before_lost_password_confirmation_message' ); ?>
<p><?php echo esc_html( apply_filters( 'woocommerce_lost_password_confirmation_message', esc_html__( 'A password reset email has been sent to the email address on file for your account, but may take several minutes to show up in your inbox. Please wait at least 10 minutes before attempting another reset.', 'woocommerce' ) ) ); ?></p>
<?php do_action( 'woocommerce_after_lost_password_confirmation_message' ); ?>

View File

@ -10,26 +10,24 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.5.0
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.9.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! $messages ) {
if ( ! $notices ) {
return;
}
?>
<ul class="woocommerce-error" role="alert">
<?php foreach ( $messages as $message ) : ?>
<li>
<?php
echo wc_kses_notice( $message );
?>
<?php foreach ( $notices as $notice ) : ?>
<li<?php echo wc_get_notice_data_attr( $notice ); ?>>
<?php echo wc_kses_notice( $notice['notice'] ); ?>
</li>
<?php endforeach; ?>
</ul>

View File

@ -10,25 +10,23 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.5.0
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.9.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! $messages ) {
if ( ! $notices ) {
return;
}
?>
<?php foreach ( $messages as $message ) : ?>
<div class="woocommerce-info">
<?php
echo wc_kses_notice( $message );
?>
<?php foreach ( $notices as $notice ) : ?>
<div class="woocommerce-info"<?php echo wc_get_notice_data_attr( $notice ); ?>>
<?php echo wc_kses_notice( $notice['notice'] ); ?>
</div>
<?php endforeach; ?>

View File

@ -10,25 +10,23 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.5.0
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.9.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! $messages ) {
if ( ! $notices ) {
return;
}
?>
<?php foreach ( $messages as $message ) : ?>
<div class="woocommerce-message" role="alert">
<?php
echo wc_kses_notice( $message );
?>
<?php foreach ( $notices as $notice ) : ?>
<div class="woocommerce-message"<?php echo wc_get_notice_data_attr( $notice ); ?> role="alert">
<?php echo wc_kses_notice( $notice['notice'] ); ?>
</div>
<?php endforeach; ?>

View File

@ -10,9 +10,9 @@
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.0.0
* @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce/Templates
* @version 3.9.0
*/
if ( ! defined( 'ABSPATH' ) ) {
@ -23,25 +23,32 @@ if ( $related_products ) : ?>
<section class="related products">
<h2><?php esc_html_e( 'Related products', 'woocommerce' ); ?></h2>
<?php
$heading = apply_filters( 'woocommerce_product_related_products_heading', __( 'Related products', 'woocommerce' ) );
if ( $heading ) :
?>
<h2><?php echo esc_html( $heading ); ?></h2>
<?php endif; ?>
<?php woocommerce_product_loop_start(); ?>
<?php foreach ( $related_products as $related_product ) : ?>
<?php
$post_object = get_post( $related_product->get_id() );
<?php
$post_object = get_post( $related_product->get_id() );
setup_postdata( $GLOBALS['post'] =& $post_object );
setup_postdata( $GLOBALS['post'] =& $post_object ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited, Squiz.PHP.DisallowMultipleAssignments.Found
wc_get_template_part( 'content', 'product' ); ?>
wc_get_template_part( 'content', 'product' );
?>
<?php endforeach; ?>
<?php woocommerce_product_loop_end(); ?>
</section>
<?php endif;
<?php
endif;
wp_reset_postdata();

View File

@ -1,21 +1,47 @@
<?php
/**
* Class WC_Mock_Payment_Gateway
*
* @package WooCommerce\Tests\Framework
*/
/**
* Class WC_Mock_Payment_Gateway
*/
class WC_Mock_Payment_Gateway extends WC_Payment_Gateway {
/**
* Constructor for the gateway.
*/
public function __construct() {
$this->enabled = 'yes';
$this->id = 'mock';
$this->has_fields = false;
$this->order_button_text = __( 'Proceed to PayPal', 'woocommerce' );
$this->method_title = 'Mock Gateway';
$this->method_description = 'Mock Gateway for unit tests';
$this->pay_button_id = 'mock-pay-button';
$this->supports = array(
'products',
'pay_button',
);
// Load the settings.
$this->init_form_fields();
$this->init_settings();
}
/**
* Initialise Gateway Settings Form Fields.
*/
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => '',
'type' => 'checkbox',
'label' => '',
'default' => 'yes',
),
);
}
}

View File

@ -20,6 +20,62 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC()->customer->set_is_vat_exempt( false );
}
/**
* Test whether totals are correct when discount is applied.
*/
public function test_cart_total_with_discount_and_taxes() {
update_option( 'woocommerce_prices_include_tax', 'yes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_tax_round_at_subtotal', 'yes' );
WC()->cart->empty_cart();
$tax_rate = array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '20.0000',
'tax_rate_name' => 'TAX20',
'tax_rate_priority' => '1',
'tax_rate_compound' => '0',
'tax_rate_shipping' => '0',
'tax_rate_order' => '1',
'tax_rate_class' => '20percent',
);
$tax_rate_20 = WC_Tax::_insert_tax_rate( $tax_rate );
// Create product with price 19.
$product = WC_Helper_Product::create_simple_product();
$product->set_price( 8.99 );
$product->set_regular_price( 8.99 );
$product->set_tax_class( '20percent' );
$product->save();
$coupon = WC_Helper_Coupon::create_coupon( 'off5', array( 'coupon_amount' => 5 ) );
// Create a flat rate method.
$flat_rate_settings = array(
'enabled' => 'yes',
'title' => 'Flat rate',
'availability' => 'all',
'countries' => '',
'tax_status' => 'taxable',
'cost' => '9.59',
);
update_option( 'woocommerce_flat_rate_settings', $flat_rate_settings );
WC()->cart->add_to_cart( $product->get_id(), 1 );
WC()->cart->add_discount( $coupon->get_code() );
WC()->session->set( 'chosen_shipping_methods', array( 'flat_rate' ) );
WC()->cart->calculate_totals();
$this->assertEquals( '13.58', WC()->cart->get_total( 'edit' ) );
$this->assertEquals( 0.66, WC()->cart->get_total_tax() );
$this->assertEquals( 4.17, WC()->cart->get_discount_total() );
$this->assertEquals( 0.83, WC()->cart->get_discount_tax() );
}
/**
* Test for subtotals and multiple tax rounding.
* Ticket:

View File

@ -298,6 +298,15 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
// Given string.
$this->assertEquals( '9.99', wc_format_decimal( '9.99' ) );
// Given string with multiple decimals points.
$this->assertEquals( '9.99', wc_format_decimal( '9...99' ) );
// Given string with multiple decimals points.
$this->assertEquals( '99.9', wc_format_decimal( '9...9....9' ) );
// Negative string.
$this->assertEquals( '-9.99', wc_format_decimal( '-9.99' ) );
// Float.
$this->assertEquals( '9.99', wc_format_decimal( 9.99 ) );
@ -324,7 +333,16 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
update_option( 'woocommerce_price_thousand_sep', '.' );
// Given string.
$this->assertEquals( '9.99', wc_format_decimal( '9.99' ) );
$this->assertEquals( '9.99', wc_format_decimal( '9,99' ) );
// Given string with multiple decimals points.
$this->assertEquals( '9.99', wc_format_decimal( '9,,,99' ) );
// Given string with multiple decimals points.
$this->assertEquals( '99.9', wc_format_decimal( '9,,,9,,,,9' ) );
// Negative string.
$this->assertEquals( '-9.99', wc_format_decimal( '-9,99' ) );
// Float.
$this->assertEquals( '9.99', wc_format_decimal( 9.99 ) );

View File

@ -4,6 +4,10 @@
*
* @package WooCommerce\Tests\Gateways
*/
/**
* Unit tests for gateways.
*/
class WC_Tests_Gateways extends WC_Unit_Test_Case {
/**
@ -64,5 +68,20 @@ class WC_Tests_Gateways extends WC_Unit_Test_Case {
$order->save();
$this->assertFalse( $gateway->can_refund_order( $order ) );
}
/**
* Test WC_Payment_Gateway::get_pay_button_id();
*
* @return void
*/
public function test_get_pay_button_id() {
$gateway = new WC_Mock_Payment_Gateway();
$this->assertEquals( $gateway->pay_button_id, $gateway->get_pay_button_id() );
$gateway->pay_button_id = 'new-pay-button';
$this->assertEquals( $gateway->pay_button_id, $gateway->get_pay_button_id() );
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* Class WC_Tests_Order file.
*
* @package WooCommerce|Tests|Order
*/
/**
* Class WC_Tests_Order.
*/
class WC_Tests_Order extends WC_Unit_Test_Case {
/**
* Test for total when round at subtotal is enabled.
*
* @link https://github.com/woocommerce/woocommerce/issues/24695
*/
public function test_order_calculate_total_rounding_24695() {
update_option( 'woocommerce_prices_include_tax', 'yes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_tax_round_at_subtotal', 'yes' );
$tax_rate = array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '7.0000',
'tax_rate_name' => 'CGST',
'tax_rate_priority' => '1',
'tax_rate_compound' => '0',
'tax_rate_shipping' => '0',
'tax_rate_order' => '1',
'tax_rate_class' => 'tax_1',
);
WC_Tax::_insert_tax_rate( $tax_rate );
$product1 = WC_Helper_Product::create_simple_product();
$product1->set_regular_price( 2 );
$product1->save();
$product2 = WC_Helper_Product::create_simple_product();
$product2->set_regular_price( 2.5 );
$product2->save();
$order = new WC_Order();
$order->add_product( $product1, 1 );
$order->add_product( $product2, 4 );
$order->save();
$order->calculate_totals( true );
$this->assertEquals( 12, $order->get_total() );
$this->assertEquals( 0.79, $order->get_total_tax() );
}
}

View File

@ -62,7 +62,7 @@ class WC_Tests_Order_Coupons extends WC_Unit_Test_Case {
array(
'product' => $product,
'quantity' => 1,
'subtotal' => 909.09, // Ex tax.
'subtotal' => 909.09, // Ex tax 10%.
'total' => 726.36,
)
);

View File

@ -101,6 +101,7 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
$product->set_image_id( $image_id[0] );
$product->save();
$this->assertEquals( $image_id[0], $product->get_image_id() );
wp_delete_attachment( $image_id[0], true ); // Remove attachment.
}
/**
@ -273,23 +274,25 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
* Test: test_get_image_should_return_product_image.
*/
public function test_get_image_should_return_product_image() {
$product = new WC_Product();
$image_url = $this->set_product_image( $product );
$product = new WC_Product();
$image = $this->set_product_image( $product );
$this->assertEquals(
'<img width="186" height="144" src="' . $image_url . '" class="attachment-woocommerce_thumbnail size-woocommerce_thumbnail" alt="" />',
'<img width="186" height="144" src="' . $image['url'] . '" class="attachment-woocommerce_thumbnail size-woocommerce_thumbnail" alt="" />',
$product->get_image()
);
$this->assertEquals(
'<img width="186" height="144" src="' . $image_url . '" class="attachment-single size-single" alt="" />',
'<img width="186" height="144" src="' . $image['url'] . '" class="attachment-single size-single" alt="" />',
$product->get_image( 'single' )
);
$this->assertEquals(
'<img width="186" height="144" src="' . $image_url . '" class="custom-class" alt="" />',
'<img width="186" height="144" src="' . $image['url'] . '" class="custom-class" alt="" />',
$product->get_image( 'single', array( 'class' => 'custom-class' ) )
);
wp_delete_attachment( $image['id'], true ); // Remove attachment.
}
/**
@ -299,22 +302,24 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
$variable_product = WC_Helper_Product::create_variation_product();
$variations = $variable_product->get_children();
$variation_1 = wc_get_product( $variations[0] );
$image_url = $this->set_product_image( $variable_product );
$image = $this->set_product_image( $variable_product );
$this->assertEquals(
'<img width="186" height="144" src="' . $image_url . '" class="attachment-woocommerce_thumbnail size-woocommerce_thumbnail" alt="" />',
'<img width="186" height="144" src="' . $image['url'] . '" class="attachment-woocommerce_thumbnail size-woocommerce_thumbnail" alt="" />',
$variation_1->get_image()
);
$this->assertContains(
'<img width="186" height="144" src="' . $image_url . '" class="attachment-single size-single" alt="" />',
'<img width="186" height="144" src="' . $image['url'] . '" class="attachment-single size-single" alt="" />',
$variation_1->get_image( 'single' )
);
$this->assertEquals(
'<img width="186" height="144" src="' . $image_url . '" class="custom-class" alt="" />',
'<img width="186" height="144" src="' . $image['url'] . '" class="custom-class" alt="" />',
$variation_1->get_image( 'single', array( 'class' => 'custom-class' ) )
);
wp_delete_attachment( $image['id'], true ); // Remove attachment.
}
/**
@ -324,6 +329,10 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
$product = new WC_Product();
$this->assertContains( wc_placeholder_img_src(), $product->get_image() );
// Test custom class attribute is honoured.
$image = $product->get_image( 'woocommerce_thumbnail', array( 'class' => 'custom-class' ) );
$this->assertContains( 'class="custom-class"', $image );
}
/**
@ -338,7 +347,7 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
* Helper method to define a image for a product and return its URL.
*
* @param WC_Product $product Product object.
* @return string image URL.
* @return array Image ID and URL.
*/
protected function set_product_image( $product ) {
global $wpdb;
@ -352,6 +361,9 @@ class WC_Tests_Product_Data extends WC_Unit_Test_Case {
$product->set_image_id( $image_id );
$product->save();
return $image_url;
return array(
'id' => $image_id,
'url' => $image_url,
);
}
}

View File

@ -740,6 +740,22 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case {
$this->assertEquals( $product->get_id(), $product_copy->get_id() );
}
/**
* Test wc_get_product_object().
*
* @since 3.9.0
*/
public function test_wc_get_product_object() {
$this->assertInstanceOf( 'WC_Product_Simple', wc_get_product_object( 'simple' ) );
$this->assertInstanceOf( 'WC_Product_Grouped', wc_get_product_object( 'grouped' ) );
$this->assertInstanceOf( 'WC_Product_External', wc_get_product_object( 'external' ) );
$this->assertInstanceOf( 'WC_Product_Variable', wc_get_product_object( 'variable' ) );
$this->assertInstanceOf( 'WC_Product_Variation', wc_get_product_object( 'variation' ) );
// Test incorrect type.
$this->assertInstanceOf( 'WC_Product_Simple', wc_get_product_object( 'foo+bar' ) );
}
/**
* Test wc_update_product_stock().
*
@ -885,6 +901,10 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case {
*/
public function test_wc_placeholder_img() {
$this->assertTrue( (bool) strstr( wc_placeholder_img(), wc_placeholder_img_src() ) );
// Test custom class attribute is honoured.
$attr = array( 'class' => 'custom-class' );
$this->assertContains( 'class="custom-class"', wc_placeholder_img( 'woocommerce_thumbnail', $attr ) );
}
/**

View File

@ -77,4 +77,18 @@ class WC_Tests_Product_Variation extends WC_Unit_Test_Case {
$this->assertEquals( 'parent', $variation->get_tax_class( 'edit' ) );
$this->assertEquals( 'zero-rate', $variation->get_tax_class( 'view' ) );
}
/**
* Test that WC_Product_Variation throws an exception
* when called with a product ID that belongs to a product
* of a different type.
*
* Ticket: https://github.com/woocommerce/woocommerce/issues/24956
*/
public function test_product_variation_should_throw_exception_when_instantiated_with_invalid_id() {
$this->expectExceptionMessage( 'Invalid product type: passed ID does not correspond to a product variation.' );
$variable_product = WC_Helper_Product::create_variation_product();
new WC_Product_Variation( $variable_product->get_id() );
}
}

View File

@ -18,8 +18,8 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
$expected = array(
'limit' => '-1',
'columns' => 4,
'orderby' => 'title',
'order' => 'ASC',
'orderby' => '',
'order' => '',
'ids' => '',
'skus' => '',
'category' => '',
@ -84,9 +84,9 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'orderby' => 'title',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => '-1',
'posts_per_page' => -1,
'meta_query' => $meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'tax_query' => $tax_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'fields' => 'ids',
@ -125,9 +125,9 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'orderby' => 'title',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => '-1',
'posts_per_page' => -1,
'meta_query' => $meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'tax_query' => $tax_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'post__in' => array( '1', '2', '3' ),
@ -249,9 +249,9 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'orderby' => 'title',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => '1',
'posts_per_page' => 1,
'meta_query' => $meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
'tax_query' => $tax_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'p' => '1',
@ -465,7 +465,7 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'orderby' => 'title',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => -1,
'meta_query' => $meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
@ -493,7 +493,7 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'orderby' => 'title',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => -1,
'meta_query' => $meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
@ -528,7 +528,7 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'orderby' => 'title',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => -1,
'meta_query' => $meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
@ -565,7 +565,7 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'orderby' => 'title',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => -1,
'meta_query' => $meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
@ -598,7 +598,7 @@ class WC_Test_Shortcode_Products extends WC_Unit_Test_Case {
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'orderby' => 'title',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => -1,
'meta_query' => $meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query

View File

@ -142,4 +142,40 @@ class WC_Tests_Template_Functions extends WC_Unit_Test_Case {
$expected_html = '<input type="hidden" name="test_something" value="something else" />';
$this->assertEquals( $expected_html, $actual_html );
}
/**
* Test test_wc_get_pay_buttons().
*/
public function test_wc_get_pay_buttons() {
// Test default.
ob_start();
wc_get_pay_buttons();
$actual_html = ob_get_clean();
$this->assertEquals( '', $actual_html );
// Include a payment gateway that supports "pay button".
add_filter(
'woocommerce_payment_gateways',
function( $gateways ) {
$gateways[] = 'WC_Mock_Payment_Gateway';
return $gateways;
}
);
WC()->payment_gateways()->init();
// Test pay buttons HTML.
ob_start();
wc_get_pay_buttons();
$actual_html = ob_get_clean();
$gateway = new WC_Mock_Payment_Gateway();
$expected_html = sprintf(
'<div class="woocommerce-pay-buttons"><div class="woocommerce-pay-button__%1$s %1$s" id="%1$s"></div></div>',
$gateway->get_pay_button_id()
);
$this->assertEquals( $expected_html, $actual_html );
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* File for the WC_Rate_Limiter class.
*
* @package WooCommerce\Tests\Util
*/
/**
* Test class for WC_Rate_Limiter.
* @since 3.9.0
*/
class WC_Tests_Rate_Limiter extends WC_Unit_Test_Case {
/**
* Run setup code for unit tests.
*/
public function setUp() {
parent::setUp();
}
/**
* Run tear down code for unit tests.
*/
public function tearDown() {
parent::tearDown();
}
/**
* Test setting the limit and running rate limited actions.
*/
public function test_rate_limit_limits() {
$action_identifier = 'action_1';
$user_1_id = 10;
$user_2_id = 15;
$rate_limit_id_1 = $action_identifier . $user_1_id;
$rate_limit_id_2 = $action_identifier . $user_2_id;
WC_Rate_Limiter::set_rate_limit( $rate_limit_id_1, 1 );
$this->assertEquals( true, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon allowed action to run too soon before the delay.' );
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user before the delay.' );
// As retired_too_soon bails if current time <= limit, the actual time needs to be at least 1 second after the limit.
sleep( 2 );
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_1 ), 'retried_too_soon did not allow action to run after the designated delay.' );
$this->assertEquals( false, WC_Rate_Limiter::retried_too_soon( $rate_limit_id_2 ), 'retried_too_soon did not allow action to run for another user after the designated delay.' );
}
}

View File

@ -1,10 +1,13 @@
<?php
/**
* Class Notice_Functions.
* @package WooCommerce\Tests\Util
* @since 2.2
*/
/**
* WC_Tests_Notice_Functions class.
*/
class WC_Tests_Notice_Functions extends WC_Unit_Test_Case {
/**
@ -22,20 +25,20 @@ class WC_Tests_Notice_Functions extends WC_Unit_Test_Case {
*
* @since 2.2
*/
function test_wc_notice_count() {
public function test_wc_notice_count() {
// no error notices
// No error notices.
$this->assertEquals( 0, wc_notice_count( 'error' ) );
// single notice
// Single notice.
wc_add_notice( 'Bogus Notice', 'success' );
$this->assertEquals( 1, wc_notice_count() );
// specific notice
// Specific notice.
wc_add_notice( 'Bogus Error Notice', 'error' );
$this->assertEquals( 1, wc_notice_count( 'error' ) );
// multiple notices of different types.
// Multiple notices of different types.
wc_clear_notices();
wc_add_notice( 'Bogus 1', 'success' );
wc_add_notice( 'Bogus 2', 'success' );
@ -60,13 +63,13 @@ class WC_Tests_Notice_Functions extends WC_Unit_Test_Case {
*
* @since 2.2
*/
function test_wc_has_notice() {
public function test_wc_has_notice() {
// negative
// Negative.
wc_add_notice( 'Bogus Notice', 'success' );
$this->assertFalse( wc_has_notice( 'Legit Notice' ) );
// positive
// Positive.
wc_add_notice( 'One True Notice', 'notice' );
$this->assertTrue( wc_has_notice( 'One True Notice', 'notice' ) );
}
@ -76,22 +79,23 @@ class WC_Tests_Notice_Functions extends WC_Unit_Test_Case {
*
* @since 2.2
*/
function test_wc_add_notice() {
public function test_wc_add_notice() {
// default type
// Default type.
wc_add_notice( 'Test Notice' );
$notices = wc_get_notices();
$this->assertArrayHasKey( 'success', $notices );
$this->assertEquals( 'Test Notice', $notices['success'][0] );
$this->assertEquals( 'Test Notice', $notices['success'][0]['notice'] );
// clear notices
// Clear notices.
WC()->session->set( 'wc_notices', null );
// specific type
wc_add_notice( 'Test Error Notice', 'error' );
// Specific type.
wc_add_notice( 'Test Error Notice', 'error', array( 'id' => 'billing_postcode' ) );
$notices = wc_get_notices();
$this->assertArrayHasKey( 'error', $notices );
$this->assertEquals( 'Test Error Notice', $notices['error'][0] );
$this->assertEquals( 'Test Error Notice', $notices['error'][0]['notice'] );
$this->assertEquals( array( 'id' => 'billing_postcode' ), $notices['error'][0]['data'] );
}
/**
@ -99,7 +103,7 @@ class WC_Tests_Notice_Functions extends WC_Unit_Test_Case {
*
* @since 2.2
*/
function test_wc_clear_notices() {
public function test_wc_clear_notices() {
wc_add_notice( 'Test Notice' );
wc_clear_notices();
@ -112,16 +116,28 @@ class WC_Tests_Notice_Functions extends WC_Unit_Test_Case {
* @since 2.2
*/
public function test_wc_print_notices() {
wc_add_notice( 'One True Notice', 'notice' );
wc_add_notice( 'Second True Notice', 'notice', array( 'id' => 'second_notice' ) );
$this->expectOutputString( '<div class="woocommerce-info">One True Notice</div>' );
$this->expectOutputString( '<div class="woocommerce-info">One True Notice</div><div class="woocommerce-info" data-id="second_notice">Second True Notice</div>' );
wc_print_notices();
$this->assertEmpty( WC()->session->get( 'wc_notices' ) );
}
/**
* Test wc_print_notices() should return notices
* when first parameter is set to true.
*/
public function test_wc_print_notices_should_return_notices() {
$expected_return = "\n <div class=\"woocommerce-info\">\n One True Notice </div>\n";
wc_add_notice( 'One True Notice', 'notice' );
$this->assertEquals( $expected_return, wc_print_notices( true ) );
}
/**
* Test wc_print_notice() w/ success type.
*
@ -153,12 +169,25 @@ class WC_Tests_Notice_Functions extends WC_Unit_Test_Case {
*/
public function test_wc_print_error_notice() {
// specific type
// Specific type.
$this->expectOutputString( '<ul class="woocommerce-error" role="alert"><li>Error!</li></ul>' );
wc_print_notice( 'Error!', 'error' );
}
/**
* Test wc_print_notice() w/ data.
*
* @since 2.2
*/
public function test_wc_print_notice_data() {
// Specific type.
$this->expectOutputString( '<ul class="woocommerce-error" role="alert"><li data-id="billing_postcode">Error!</li></ul>' );
wc_print_notice( 'Error!', 'error', array( 'id' => 'billing_postcode' ) );
}
/**
* Test wc_get_notices().
*
@ -166,20 +195,38 @@ class WC_Tests_Notice_Functions extends WC_Unit_Test_Case {
*/
public function test_wc_get_notices() {
// no notices
// No notices.
$notices = wc_get_notices();
$this->assertInternalType( 'array', $notices );
$this->assertEmpty( $notices );
// default type
// Default type.
wc_add_notice( 'Another Notice' );
$this->assertEquals( array( 'success' => array( 'Another Notice' ) ), wc_get_notices() );
$this->assertEquals(
array(
'success' => array(
array(
'notice' => 'Another Notice',
'data' => array(),
),
),
),
wc_get_notices()
);
// specific type
wc_add_notice( 'Error Notice', 'error' );
$this->assertEquals( array( 'Error Notice' ), wc_get_notices( 'error' ) );
// Specific type.
wc_add_notice( 'Error Notice', 'error', array( 'id' => 'billing_email' ) );
$this->assertEquals(
array(
array(
'notice' => 'Error Notice',
'data' => array( 'id' => 'billing_email' ),
),
),
wc_get_notices( 'error' )
);
// invalid type
// Invalid type.
$notices = wc_get_notices( 'bogus_type' );
$this->assertInternalType( 'array', $notices );
$this->assertEmpty( $notices );

View File

@ -107,7 +107,15 @@ class WC_Tests_Validation extends WC_Unit_Test_Case {
array( false, WC_Validation::is_postcode( '3852 sa', 'NL' ) ),
);
return array_merge( $it, $gb, $us, $ch, $br, $ca, $nl );
$si = array(
array( true, WC_Validation::is_postcode( '1234', 'SI' ) ),
array( true, WC_Validation::is_postcode( '1000', 'SI' ) ),
array( true, WC_Validation::is_postcode( '9876', 'SI' ) ),
array( false, WC_Validation::is_postcode( '12345', 'SI' ) ),
array( false, WC_Validation::is_postcode( '0123', 'SI' ) ),
);
return array_merge( $it, $gb, $us, $ch, $br, $ca, $nl, $si );
}
/**

View File

@ -189,4 +189,17 @@ class WC_Tests_CRUD_Webhooks extends WC_Unit_Test_Case {
$object = new WC_Webhook();
$this->assertEquals( 'GBDo00G55h6IiV+6CxqivQPLbI//KzaOZm747971tPs=', $object->generate_signature( 'secret' ) );
}
/**
* Test: webhook deletion invalidates caches
*/
public function test_webhook_deletion() {
$object = new WC_Webhook();
$id = $object->save();
$object = new WC_Webhook( $id );
$this->assertEquals( $id, $object->get_id() );
$this->assertTrue( $object->delete() );
$object = new WC_Webhook( $id );
$this->assertNotEquals( $id, $object->get_id() );
}
}

View File

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce
* Plugin URI: https://woocommerce.com/
* Description: An eCommerce toolkit that helps you sell anything. Beautifully.
* Version: 3.9.0-dev
* Version: 3.9.0-beta.1
* Author: Automattic
* Author URI: https://woocommerce.com
* Text Domain: woocommerce