Merge branch 'master' into update/use-post-author-for-customer-id

This commit is contained in:
Rodrigo Primo 2018-06-18 15:17:19 -03:00
commit 43d22a2349
63 changed files with 4630 additions and 2312 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1347,11 +1347,6 @@
}
}
.wc-order-item-bulk-edit .cancel-action {
float: left;
margin-left: 0;
}
.add_meta {
margin-left: 0 !important;
}
@ -1450,19 +1445,6 @@
}
}
tbody tr {
td {
cursor: pointer;
}
&.selected {
background: #f5ebf3;
td {
border-color: #e6cce1;
opacity: 0.8;
}
}
}
tbody tr:last-child td {
border-bottom: 1px solid #dfdfdf;
}
@ -5894,20 +5876,58 @@
padding: 1.5em;
p {
margin: 1.5em 0;
margin: 1.5em 0;
}
p:first-child {
margin-top: 0;
margin-top: 0;
}
p:last-child {
margin-bottom: 0;
margin-bottom: 0;
}
.pagination {
padding: 10px 0 0;
text-align: center;
padding: 10px 0 0;
text-align: center;
}
}
table.widefat {
margin: 0;
width: 100%;
border: 0;
box-shadow: none;
thead th {
padding: 0 1em 1em 1em;
text-align: left;
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
text-align: right;
}
}
tbody td, tbody th {
padding: 1em;
text-align: left;
vertical-align: middle;
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
text-align: right;
}
select,
.select2-container {
width: 100%;
}
}
}
}
footer {
position: absolute;
@ -6311,11 +6331,13 @@
border: 0;
box-shadow: none;
width: 100%;
table-layout: fixed;
td, th {
border: 0;
padding: 12px;
vertical-align: middle;
word-wrap: break-word;
select {
width: 100%;

View File

@ -244,11 +244,6 @@ jQuery( function ( $ ) {
.on( 'click', 'button.calculate-action', this.recalculate )
.on( 'click', 'a.edit-order-item', this.edit_item )
.on( 'click', 'a.delete-order-item', this.delete_item )
.on( 'click', 'tr.item, tr.fee, tr.shipping, tr.refund', this.select_row )
.on( 'click', 'tr.item :input, tr.fee :input, tr.shipping :input, tr.refund :input, tr.item a, tr.fee a, tr.shipping a, tr.refund a', this.select_row_child )
.on( 'click', 'button.bulk-delete-items', this.bulk_actions.do_delete )
.on( 'click', 'button.bulk-increase-stock', this.bulk_actions.do_increase_stock )
.on( 'click', 'button.bulk-decrease-stock', this.bulk_actions.do_reduce_stock )
// Refunds
.on( 'click', '.delete_refund', this.refunds.delete_refund )
@ -900,194 +895,24 @@ jQuery( function ( $ ) {
}
},
select_row: function() {
var $row = false;
if ( $( this ).is( 'tr' ) ) {
$row = $( this );
} else {
$row = $( this ).closest( 'tr' );
}
var $table = $( this ).closest( 'table' );
if ( $row.is( '.selected' ) ) {
$row.removeClass( 'selected' );
} else {
$row.addClass( 'selected' );
}
var $rows = $table.find( 'tr.selected' );
var $bulk_edit_wraper = $( 'div.wc-order-item-bulk-edit' );
if ( $rows.length && $bulk_edit_wraper.children().length > 0 ) {
$bulk_edit_wraper.slideDown();
var selected_product = false;
$rows.each( function() {
if ( $( this ).is( 'tr.item' ) ) {
selected_product = true;
}
} );
if ( selected_product ) {
$( '.bulk-increase-stock, .bulk-decrease-stock' ).show();
} else {
$( '.bulk-increase-stock, .bulk-decrease-stock' ).hide();
}
} else {
$( 'div.wc-order-item-bulk-edit' ).slideUp();
}
},
select_row_child: function( e ) {
e.stopPropagation();
},
bulk_actions: {
do_delete: function( e ) {
e.preventDefault();
var $table = $( 'table.woocommerce_order_items' );
var $rows = $table.find( 'tr.selected' );
if ( $rows.length && window.confirm( woocommerce_admin_meta_boxes.remove_item_notice ) ) {
wc_meta_boxes_order_items.block();
var delete_items = [];
var delete_refunds = [];
var deferred = [];
$.map( $rows, function( row ) {
var $row = $( row );
if ( $row.is( '.refund' ) ) {
delete_refunds.push( parseInt( $( $row ).data( 'order_refund_id' ), 10 ) );
} else {
delete_items.push( parseInt( $( $row ).data( 'order_item_id' ), 10 ) );
}
return ;
});
if ( delete_items.length ) {
deferred.push( $.ajax({
url : woocommerce_admin_meta_boxes.ajax_url,
data: {
order_id : woocommerce_admin_meta_boxes.post_id,
order_item_ids: delete_items,
action: 'woocommerce_remove_order_item',
security: woocommerce_admin_meta_boxes.order_item_nonce
},
type: 'POST'
} ) );
}
if ( delete_refunds.length ) {
deferred.push( $.ajax( {
url : woocommerce_admin_meta_boxes.ajax_url,
data: {
action: 'woocommerce_delete_refund',
refund_id: delete_refunds,
security: woocommerce_admin_meta_boxes.order_item_nonce
},
type: 'POST'
} ) );
}
if ( deferred ) {
$.when.apply( $, deferred ).done( function() {
wc_meta_boxes_order_items.reload_items();
wc_meta_boxes_order_items.unblock();
} );
} else {
wc_meta_boxes_order_items.unblock();
}
}
},
modify_stock: function( e, action ) {
e.preventDefault();
wc_meta_boxes_order_items.block();
$( '#woocommerce-order-notes' ).block({
message: null,
overlayCSS: {
background: '#fff',
opacity: 0.6
}
});
var $table = $( 'table.woocommerce_order_items' );
var $rows = $table.find( 'tr.selected' );
var quantities = {};
var item_ids = $.map( $rows, function( $row ) {
return parseInt( $( $row ).data( 'order_item_id' ), 10 );
});
$rows.each(function() {
if ( $( this ).find( 'input.quantity' ).length ) {
quantities[ $( this ).attr( 'data-order_item_id' ) ] = $( this ).find( 'input.quantity' ).val();
}
});
var data = {
order_id: woocommerce_admin_meta_boxes.post_id,
order_item_ids: item_ids,
order_item_qty: quantities,
action: action,
security: woocommerce_admin_meta_boxes.order_item_nonce
};
$.ajax({
url: woocommerce_admin_meta_boxes.ajax_url,
data: data,
type: 'POST',
success: function( response ) {
wc_meta_boxes_order_items.unblock();
if ( true === response.success ) {
$.map( response.data, function( item ) {
// No items were updated.
if ( ! item.success ) {
window.alert( item.note );
return;
}
var order_note_data = {
action: 'woocommerce_add_order_note',
post_id: woocommerce_admin_meta_boxes.post_id,
note: item.note,
note_type: '',
security: woocommerce_admin_meta_boxes.add_order_note_nonce
};
$.post( woocommerce_admin_meta_boxes.ajax_url, order_note_data, function( response ) {
$( 'ul.order_notes' ).prepend( response );
});
});
}
$( '#woocommerce-order-notes' ).unblock();
}
});
},
do_increase_stock: function( e ) {
wc_meta_boxes_order_items.bulk_actions.modify_stock( e, 'woocommerce_increase_order_item_stock' );
},
do_reduce_stock: function( e ) {
wc_meta_boxes_order_items.bulk_actions.modify_stock( e, 'woocommerce_reduce_order_item_stock' );
}
},
backbone: {
init: function( e, target ) {
if ( 'wc-modal-add-products' === target ) {
$( document.body ).trigger( 'wc-enhanced-select-init' );
$( '#add_item_id' ).selectWoo( 'open' ).selectWoo( 'focus' );
$( this ).on( 'change', '.wc-product-search', function() {
if ( ! $( this ).closest( 'tr' ).is( ':last-child' ) ) {
return;
}
var item_table = $( this ).closest( 'table.widefat' ),
item_table_body = item_table.find( 'tbody' ),
index = item_table_body.find( 'tr' ).length,
row = item_table_body.data( 'row' ).replace( /\[0\]/g, '[' + index + ']' );
item_table_body.append( '<tr>' + row + '</tr>' );
$( document.body ).trigger( 'wc-enhanced-select-init' );
} );
}
},
@ -1103,39 +928,58 @@ jQuery( function ( $ ) {
wc_meta_boxes_order_items.backbone.add_tax( rate_id, manual_rate_id );
}
if ( 'wc-modal-add-products' === target ) {
wc_meta_boxes_order_items.backbone.add_item( data.add_order_items );
// Build array of data.
var item_table = $( this ).find( 'table.widefat' ),
item_table_body = item_table.find( 'tbody' ),
rows = item_table_body.find( 'tr' ),
add_items = [];
$( rows ).each( function() {
var item_id = $( this ).find( ':input[name="item_id"]' ).val(),
item_qty = $( this ).find( ':input[name="item_qty"]' ).val();
add_items.push( {
'id' : item_id,
'qty': item_qty ? item_qty: 1
} );
} );
return wc_meta_boxes_order_items.backbone.add_items( add_items );
}
},
add_item: function( add_item_ids ) {
if ( add_item_ids ) {
wc_meta_boxes_order_items.block();
add_items: function( add_items ) {
wc_meta_boxes_order_items.block();
var data = {
action : 'woocommerce_add_order_item',
item_to_add: add_item_ids,
dataType : 'json',
order_id : woocommerce_admin_meta_boxes.post_id,
security : woocommerce_admin_meta_boxes.order_item_nonce,
data : $( '#wc-backbone-modal-dialog form' ).serialize()
};
var data = {
action : 'woocommerce_add_order_item',
order_id : woocommerce_admin_meta_boxes.post_id,
security : woocommerce_admin_meta_boxes.order_item_nonce,
data : add_items
};
// Check if items have changed, if so pass them through so we can save them before adding a new item.
if ( 'true' === $( 'button.cancel-action' ).attr( 'data-reload' ) ) {
data.items = $( 'table.woocommerce_order_items :input[name], .wc-order-totals-items :input[name]' ).serialize();
}
// Check if items have changed, if so pass them through so we can save them before adding a new item.
if ( 'true' === $( 'button.cancel-action' ).attr( 'data-reload' ) ) {
data.items = $( 'table.woocommerce_order_items :input[name], .wc-order-totals-items :input[name]' ).serialize();
}
$.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) {
$.ajax({
type: 'POST',
url: woocommerce_admin_meta_boxes.ajax_url,
data: data,
success: function( response ) {
if ( response.success ) {
$( '#woocommerce-order-items' ).find( '.inside' ).empty();
$( '#woocommerce-order-items' ).find( '.inside' ).append( response.data.html );
wc_meta_boxes_order_items.reloaded_items();
wc_meta_boxes_order_items.unblock();
} else {
wc_meta_boxes_order_items.unblock();
window.alert( response.data.error );
}
wc_meta_boxes_order_items.unblock();
});
}
},
dataType: 'json'
});
},
add_tax: function( rate_id, manual_rate_id ) {

File diff suppressed because one or more lines are too long

View File

@ -129,6 +129,6 @@ jQuery(function( $ ) {
});
$( '#wpbody' ).on( 'click', '.trash-product', function() {
return window.confirm( woocommerce_admin.i18_delete_product_notice );
return window.confirm( woocommerce_admin.i18n_delete_product_notice );
});
});

View File

@ -87,12 +87,13 @@ jQuery( function( $ ) {
delay: 250,
data: function( params ) {
return {
term: params.term,
action: $( this ).data( 'action' ) || 'woocommerce_json_search_products_and_variations',
security: wc_enhanced_select_params.search_products_nonce,
exclude: $( this ).data( 'exclude' ),
include: $( this ).data( 'include' ),
limit: $( this ).data( 'limit' )
term : params.term,
action : $( this ).data( 'action' ) || 'woocommerce_json_search_products_and_variations',
security : wc_enhanced_select_params.search_products_nonce,
exclude : $( this ).data( 'exclude' ),
include : $( this ).data( 'include' ),
limit : $( this ).data( 'limit' ),
display_stock: $( this ).data( 'display_stock' )
};
},
processResults: function( data ) {

File diff suppressed because one or more lines are too long

View File

@ -138,8 +138,9 @@
var $tr = view.$el.find( 'tr[data-id="' + rowData.instance_id + '"]');
if ( ! rowData.has_settings ) {
$tr.find( '.wc-shipping-zone-method-title a').replaceWith( $tr.find( '.wc-shipping-zone-method-title' ).text() );
$tr.find( '.wc-shipping-zone-method-settings' ).remove();
$tr.find( '.wc-shipping-zone-method-title > a' ).replaceWith('<span>' + $tr.find( '.wc-shipping-zone-method-title > a' ).text() + '</span>' );
var $del = $tr.find( '.wc-shipping-zone-method-delete' );
$tr.find( '.wc-shipping-zone-method-title .row-actions' ).empty().html($del);
}
} );

File diff suppressed because one or more lines are too long

View File

@ -131,9 +131,9 @@
var regular_price = parseFloat( window.accounting.unformat( regular_price_field.val(), woocommerce_admin.mon_decimal_point ) );
if ( sale_price >= regular_price ) {
$( document.body ).triggerHandler( 'wc_add_error_tip', [ $(this), 'i18_sale_less_than_regular_error' ] );
$( document.body ).triggerHandler( 'wc_add_error_tip', [ $(this), 'i18n_sale_less_than_regular_error' ] );
} else {
$( document.body ).triggerHandler( 'wc_remove_error_tip', [ $(this), 'i18_sale_less_than_regular_error' ] );
$( document.body ).triggerHandler( 'wc_remove_error_tip', [ $(this), 'i18n_sale_less_than_regular_error' ] );
}
})
@ -334,5 +334,13 @@
return false;
});
$( '#wpbody' ).on( 'click', '#doaction, #doaction2', function() {
var action = $( this ).is( '#doaction' ) ? $( '#bulk-action-selector-top' ).val() : $( '#bulk-action-selector-bottom' ).val();
if ( 'remove_personal_data' === action ) {
return window.confirm( woocommerce_admin.i18n_remove_personal_data_notice );
}
});
});
})( jQuery, woocommerce_admin );

File diff suppressed because one or more lines are too long

View File

@ -89,7 +89,9 @@ jQuery( function( $ ) {
get_payment_method: function() {
return wc_checkout_form.$checkout_form.find( 'input[name="payment_method"]:checked' ).val();
},
payment_method_selected: function() {
payment_method_selected: function( e ) {
e.stopPropagation();
if ( $( '.payment_methods input.input-radio' ).length > 1 ) {
var target_payment_box = $( 'div.payment_box.' + $( this ).attr( 'ID' ) ),
is_checked = $( this ).is( ':checked' );

View File

@ -1,86 +1,115 @@
( function( $ ) {
$( function() {
var wcTokenizationForm = (function() {
function wcTokenizationForm( target ) {
var $target = $( target ),
$formWrap = $target.closest( '.payment_box' ),
$wcTokenizationForm = this;
/*global wc_tokenization_form_params */
jQuery( function( $ ) {
this.onTokenChange = function() {
if ( 'new' === $( this ).val() ) {
$wcTokenizationForm.showForm();
$wcTokenizationForm.showSaveNewCheckbox();
} else {
$wcTokenizationForm.hideForm();
$wcTokenizationForm.hideSaveNewCheckbox();
}
};
/**
* WCTokenizationForm class.
*/
var TokenizationForm = function( $target ) {
this.$target = $target;
this.$formWrap = $target.closest( '.payment_box' );
this.onCreateAccountChange = function() {
if ( $( this ).is( ':checked' ) ) {
$wcTokenizationForm.showSaveNewCheckbox();
} else {
$wcTokenizationForm.hideSaveNewCheckbox();
}
};
// Params.
this.params = $.extend( {}, {
'is_registration_required': false,
'is_logged_in' : false,
}, wc_tokenization_form_params );
this.onDisplay = function() {
// Make sure a radio button is selected if there is no is_default for this payment method..
if ( 0 === $( ':input.woocommerce-SavedPaymentMethods-tokenInput:checked', $target ).length ) {
$( ':input.woocommerce-SavedPaymentMethods-tokenInput:last', $target ).prop( 'checked', true );
}
// Bind functions to this.
this.onDisplay = this.onDisplay.bind( this );
this.hideForm = this.hideForm.bind( this );
this.showForm = this.showForm.bind( this );
this.showSaveNewCheckbox = this.showSaveNewCheckbox.bind( this );
this.hideSaveNewCheckbox = this.hideSaveNewCheckbox.bind( this );
// Don't show the "use new" radio button if we only have one method..
if ( 0 === $target.data( 'count' ) ) {
$( '.woocommerce-SavedPaymentMethods-new', $target ).hide();
}
// When a radio button is changed, make sure to show/hide our new CC info area.
this.$target.on( 'click change', ':input.woocommerce-SavedPaymentMethods-tokenInput', { tokenizationForm: this }, this.onTokenChange );
// Trigger change event
$( ':input.woocommerce-SavedPaymentMethods-tokenInput:checked', $target ).trigger( 'change' );
// OR if create account is checked.
$( 'input#createaccount' ).change( { tokenizationForm: this }, this.onCreateAccountChange );
// Hide "save card" if "Create Account" is not checked.
// Check that the field is shown in the form - some plugins and force create account remove it
if ( $( 'input#createaccount' ).length && ! $('input#createaccount').is( ':checked' ) ) {
$wcTokenizationForm.hideSaveNewCheckbox();
}
// First display.
this.onDisplay();
};
};
TokenizationForm.prototype.onDisplay = function() {
// Make sure a radio button is selected if there is no is_default for this payment method..
if ( 0 === $( ':input.woocommerce-SavedPaymentMethods-tokenInput:checked', this.$target ).length ) {
$( ':input.woocommerce-SavedPaymentMethods-tokenInput:last', this.$target ).prop( 'checked', true );
}
this.hideForm = function() {
$( '.wc-payment-form', $formWrap ).hide();
};
// Don't show the "use new" radio button if we only have one method..
if ( 0 === this.$target.data( 'count' ) ) {
$( '.woocommerce-SavedPaymentMethods-new', this.$target ).remove();
}
this.showForm = function() {
$( '.wc-payment-form', $formWrap ).show();
};
// Hide "save card" if "Create Account" is not checked and registration is not forced.
var hasCreateAccountCheckbox = 0 < $( 'input#createaccount' ).length,
createAccount = hasCreateAccountCheckbox && $( 'input#createaccount' ).is( ':checked' );
this.showSaveNewCheckbox = function() {
$( '.woocommerce-SavedPaymentMethods-saveNew', $formWrap ).show();
};
if ( createAccount || this.params.is_logged_in || this.params.is_registration_required ) {
this.showSaveNewCheckbox();
} else {
this.hideSaveNewCheckbox();
}
this.hideSaveNewCheckbox = function() {
$( '.woocommerce-SavedPaymentMethods-saveNew', $formWrap ).hide();
};
// Trigger change event
$( ':input.woocommerce-SavedPaymentMethods-tokenInput:checked', this.$target ).trigger( 'change' );
};
// When a radio button is changed, make sure to show/hide our new CC info area
$( ':input.woocommerce-SavedPaymentMethods-tokenInput', $target ).change( this.onTokenChange );
TokenizationForm.prototype.onTokenChange = function( event ) {
if ( 'new' === $( this ).val() ) {
event.data.tokenizationForm.showForm();
event.data.tokenizationForm.showSaveNewCheckbox();
} else {
event.data.tokenizationForm.hideForm();
event.data.tokenizationForm.hideSaveNewCheckbox();
}
};
// OR if create account is checked
$ ( 'input#createaccount' ).change( this.onCreateAccountChange );
TokenizationForm.prototype.onCreateAccountChange = function( event ) {
if ( $( this ).is( ':checked' ) ) {
event.data.tokenizationForm.showSaveNewCheckbox();
} else {
event.data.tokenizationForm.hideSaveNewCheckbox();
}
};
this.onDisplay();
}
TokenizationForm.prototype.hideForm = function() {
$( '.wc-payment-form', this.$formWrap ).hide();
};
return wcTokenizationForm;
})();
TokenizationForm.prototype.showForm = function() {
$( '.wc-payment-form', this.$formWrap ).show();
};
$( document.body ).on( 'updated_checkout wc-credit-card-form-init', function() {
// Loop over gateways with saved payment methods
var $saved_payment_methods = $( 'ul.woocommerce-SavedPaymentMethods' );
TokenizationForm.prototype.showSaveNewCheckbox = function() {
$( '.woocommerce-SavedPaymentMethods-saveNew', this.$formWrap ).show();
};
$saved_payment_methods.each( function() {
new wcTokenizationForm( this );
} );
TokenizationForm.prototype.hideSaveNewCheckbox = function() {
$( '.woocommerce-SavedPaymentMethods-saveNew', this.$formWrap ).hide();
};
/**
* Function to call wc_product_gallery on jquery selector.
*/
$.fn.wc_tokenization_form = function( args ) {
new TokenizationForm( this, args );
return this;
};
/**
* Initialize.
*/
$( document.body ).on( 'updated_checkout wc-credit-card-form-init', function() {
// Loop over gateways with saved payment methods
var $saved_payment_methods = $( 'ul.woocommerce-SavedPaymentMethods' );
$saved_payment_methods.each( function() {
$( this ).wc_tokenization_form();
} );
});
})( jQuery );
} );
// Alias.
var wcTokenizationForm = TokenizationForm;
} );

View File

@ -48,7 +48,7 @@ jQuery( function( $ ) {
return false;
}
} )
.on( 'focus', function() {
.on( 'click focus', function() {
var input = $( this ),
parent = input.parent(),
description = parent.find( 'span.description' );

View File

@ -420,11 +420,34 @@ abstract class WC_Data {
$this->maybe_read_meta_data();
$array_key = $meta_id ? array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id ) : '';
$array_key = false;
if ( $meta_id ) {
$array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id, true );
$array_key = $array_keys ? current( $array_keys ) : false;
} else {
// Find matches by key.
$matches = array();
$ids_to_keys = wp_list_pluck( $this->meta_data, 'key', 'id' );
foreach ( $this->meta_data as $meta_data_array_key => $meta ) {
if ( $meta->key === $key ) {
$matches[] = $meta_data_array_key;
}
}
if ( ! empty( $matches ) ) {
// Set matches to null so only one key gets the new value.
foreach ( $matches as $meta_data_array_key ) {
$this->meta_data[ $meta_data_array_key ]->value = null;
}
$array_key = current( $matches );
}
}
if ( $array_key ) {
$meta = $this->meta_data[ current( $array_key ) ];
$meta->key = $key;
$meta = $this->meta_data[ $array_key ];
$meta->key = $key;
$meta->value = $value;
} else {
$this->add_meta_data( $key, $value, true );

View File

@ -1372,7 +1372,13 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
foreach ( $this->get_shipping_methods() as $item_id => $item ) {
$taxes = $item->get_taxes();
foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + (float) $tax : (float) $tax;
$tax_amount = (float) $tax;
if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
$tax_amount = wc_round_tax_total( $tax_amount );
}
$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
}
}
@ -1429,13 +1435,13 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
// Sum line item costs.
foreach ( $this->get_items() as $item ) {
$cart_subtotal += $item->get_subtotal();
$cart_total += $item->get_total();
$cart_subtotal += round( $item->get_subtotal(), wc_get_price_decimals() );
$cart_total += round( $item->get_total(), wc_get_price_decimals() );
}
// Sum shipping costs.
foreach ( $this->get_shipping_methods() as $shipping ) {
$shipping_total += $shipping->get_total();
$shipping_total += round( $shipping->get_total(), wc_get_price_decimals() );
}
$this->set_shipping_total( $shipping_total );
@ -1703,7 +1709,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
// Show shipping excluding tax.
$shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
if ( $this->get_shipping_tax() !== 0 && $this->get_prices_include_tax() ) {
if ( (float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax() ) {
$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display );
}
} else {
@ -1711,7 +1717,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
// Show shipping including tax.
$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
if ( $this->get_shipping_tax() !== 0 && ! $this->get_prices_include_tax() ) {
if ( (float) $this->get_shipping_tax() > 0 && ! $this->get_prices_include_tax() ) {
$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display );
}
}

View File

@ -441,6 +441,13 @@ abstract class WC_Payment_Gateway extends WC_Settings_API {
array( 'jquery' ),
WC()->version
);
wp_localize_script(
'woocommerce-tokenization-form', 'wc_tokenization_form_params', array(
'is_registration_required' => WC()->checkout()->is_registration_required(),
'is_logged_in' => is_user_logged_in(),
)
);
}
/**

View File

@ -902,7 +902,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
$class = 'standard' === $class ? '' : $class;
$valid_classes = $this->get_valid_tax_classes();
if ( ! in_array( $class, $valid_classes ) ) {
if ( ! in_array( $class, $valid_classes, true ) ) {
$class = '';
}

View File

@ -187,7 +187,7 @@ abstract class WC_Widget extends WP_Widget {
$instance[ $key ] = empty( $new_instance[ $key ] ) ? 0 : 1;
break;
default:
$instance[ $key ] = sanitize_text_field( $new_instance[ $key ] );
$instance[ $key ] = isset( $new_instance[ $key ] ) ? sanitize_text_field( $new_instance[ $key ] ) : $setting['std'];
break;
}

View File

@ -32,7 +32,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
global $wp_scripts;
$screen = get_current_screen();
$screen_id = $screen ? $screen->id: '';
$screen_id = $screen ? $screen->id : '';
// Register admin styles.
wp_register_style( 'woocommerce_admin_menu_styles', WC()->plugin_url() . '/assets/css/menu.css', array(), WC_VERSION );
@ -161,23 +161,24 @@ 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 in decimal (%s) format 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_country_iso_error' => __( 'Please enter in country code with two capital letters.', 'woocommerce' ),
'i18_sale_less_than_regular_error' => __( 'Please enter in a value less than the regular price.', 'woocommerce' ),
'i18_delete_product_notice' => __( 'This product has produced sales and may be linked to existing orders. Are you sure you want to delete it?', 'woocommerce' ),
'decimal_point' => $decimal,
'mon_decimal_point' => wc_get_price_decimal_separator(),
'ajax_url' => admin_url( 'admin-ajax.php' ),
'strings' => array(
'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_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' ),
'i18n_remove_personal_data_notice' => __( 'This action cannot be reversed. Are you sure you wish to erase personal data from the selected orders?', 'woocommerce' ),
'decimal_point' => $decimal,
'mon_decimal_point' => wc_get_price_decimal_separator(),
'ajax_url' => admin_url( 'admin-ajax.php' ),
'strings' => array(
'import_products' => __( 'Import', 'woocommerce' ),
'export_products' => __( 'Export', 'woocommerce' ),
),
'nonces' => array(
'nonces' => array(
'gateway_toggle' => wp_create_nonce( 'woocommerce-toggle-payment-gateway-enabled' ),
),
'urls' => array(
'urls' => array(
'import_products' => current_user_can( 'import' ) ? esc_url_raw( admin_url( 'edit.php?post_type=product&page=product_importer' ) ) : null,
'export_products' => current_user_can( 'export' ) ? esc_url_raw( admin_url( 'edit.php?post_type=product&page=product_exporter' ) ) : null,
),
@ -268,18 +269,23 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
wp_enqueue_script( 'wc-admin-coupon-meta-boxes', WC()->plugin_url() . '/assets/js/admin/meta-boxes-coupon' . $suffix . '.js', array( 'wc-admin-meta-boxes' ), WC_VERSION );
}
if ( in_array( str_replace( 'edit-', '', $screen_id ), array_merge( array( 'shop_coupon', 'product' ), wc_get_order_types( 'order-meta-boxes' ) ) ) ) {
$post_id = isset( $post->ID ) ? $post->ID : '';
$currency = '';
$post_id = isset( $post->ID ) ? $post->ID : '';
$currency = '';
$remove_item_notice = __( 'Are you sure you want to remove the selected items?', 'woocommerce' );
if ( $post_id && in_array( get_post_type( $post_id ), wc_get_order_types( 'order-meta-boxes' ) ) ) {
$order = wc_get_order( $post_id );
if ( $order ) {
$currency = $order->get_currency();
$currency = $order->get_currency();
if ( ! $order->has_status( array( 'pending', 'failed', 'cancelled' ) ) ) {
$remove_item_notice = $remove_item_notice . ' ' . __( "You may need to manually restore the item's stock.", 'woocommerce' );
}
}
}
$params = array(
'remove_item_notice' => __( "Are you sure you want to remove the selected items? If you have previously reduced this item's stock, or this order was submitted by a customer, you will need to manually restore the item's stock.", 'woocommerce' ),
'remove_item_notice' => $remove_item_notice,
'i18n_select_items' => __( 'Please select some items.', 'woocommerce' ),
'i18n_do_refund' => __( 'Are you sure you wish to process this refund? This action cannot be undone.', 'woocommerce' ),
'i18n_delete_refund' => __( 'Are you sure you wish to delete this refund? This action cannot be undone.', 'woocommerce' ),

View File

@ -207,6 +207,22 @@ class WC_Admin_Webhooks_Table_List extends WP_List_Table {
);
}
/**
* Process bulk actions.
*/
public function process_bulk_action() {
$action = $this->current_action();
$webhooks = isset( $_REQUEST['webhook'] ) ? array_map( 'absint', (array) $_REQUEST['webhook'] ) : array(); // WPCS: input var okay, CSRF ok.
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You do not have permission to edit Webhooks', 'woocommerce' ) );
}
if ( 'delete' === $action ) {
WC_Admin_Webhooks::bulk_delete( $webhooks );
}
}
/**
* Generate the table navigation above or below the table.
* Included to remove extra nonce input.

View File

@ -150,7 +150,7 @@ class WC_Admin_Webhooks {
*
* @param array $webhooks List of webhooks IDs.
*/
private function bulk_delete( $webhooks ) {
public static function bulk_delete( $webhooks ) {
foreach ( $webhooks as $webhook_id ) {
$webhook = new WC_Webhook( (int) $webhook_id );
$webhook->delete( true );
@ -179,27 +179,6 @@ class WC_Admin_Webhooks {
}
}
/**
* Bulk actions.
*/
private function bulk_actions() {
check_admin_referer( 'woocommerce-settings' );
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You do not have permission to edit Webhooks', 'woocommerce' ) );
}
if ( isset( $_REQUEST['action'] ) ) { // WPCS: input var okay, CSRF ok.
$webhooks = isset( $_REQUEST['webhook'] ) ? array_map( 'absint', (array) $_REQUEST['webhook'] ) : array(); // WPCS: input var okay, CSRF ok.
$action = sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ); // WPCS: input var okay, CSRF ok.
if ( 'delete' === $action ) {
$this->bulk_delete( $webhooks );
}
}
}
/**
* Webhooks admin actions.
*/
@ -210,11 +189,6 @@ class WC_Admin_Webhooks {
$this->save();
}
// Bulk actions.
if ( isset( $_REQUEST['action'] ) && isset( $_REQUEST['webhook'] ) ) { // WPCS: input var okay, CSRF ok.
$this->bulk_actions();
}
// Delete webhook.
if ( isset( $_GET['delete'] ) ) { // WPCS: input var okay, CSRF ok.
$this->delete();
@ -299,6 +273,7 @@ class WC_Admin_Webhooks {
$count = count( $data_store->get_webhooks_ids() );
if ( 0 < $count ) {
$webhooks_table_list->process_bulk_action();
$webhooks_table_list->prepare_items();
echo '<input type="hidden" name="page" value="wc-settings" />';

View File

@ -575,7 +575,7 @@ class WC_Product_CSV_Importer_Controller {
* @return string
*/
protected function sanitize_special_column_name_regex( $value ) {
return '/' . str_replace( array( '%d', '%s' ), '(.*)', quotemeta( $value ) ) . '/';
return '/' . str_replace( array( '%d', '%s' ), '(.*)', trim( quotemeta( $value ) ) ) . '/';
}
/**

View File

@ -222,7 +222,12 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
* Render columm: order_date.
*/
protected function render_order_date_column() {
$order_timestamp = $this->object->get_date_created()->getTimestamp();
$order_timestamp = $this->object->get_date_created() ? $this->object->get_date_created()->getTimestamp() : '';
if ( ! $order_timestamp ) {
echo '&ndash;';
return;
}
// Check if the order was created within the last 24 hours, and not in the future.
if ( $order_timestamp > strtotime( '-1 day', current_time( 'timestamp', true ) ) && $order_timestamp <= current_time( 'timestamp', true ) ) {
@ -428,6 +433,7 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
'_line_tax',
'method_id',
'cost',
'_reduced_stock',
)
);
@ -644,6 +650,9 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table {
// Sanity check: bail out if this is actually not a status, or is not a registered status.
if ( isset( $order_statuses[ 'wc-' . $new_status ] ) ) {
// Initialize payment gateways in case order has hooked status transition actions.
wc()->payment_gateways();
foreach ( $ids as $id ) {
$order = wc_get_order( $id );
$order->update_status( $new_status, __( 'Order status changed by bulk edit:', 'woocommerce' ), true );

View File

@ -15,6 +15,7 @@ $hidden_order_itemmeta = apply_filters(
'_line_tax',
'method_id',
'cost',
'_reduced_stock',
)
);
?><div class="view">

View File

@ -1,15 +1,15 @@
<?php
/**
* Order items HTML for meta box.
*
* @package WooCommerce/Admin
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
defined( 'ABSPATH' ) || exit;
global $wpdb;
// Get the payment gateway
$payment_gateway = wc_get_payment_gateway_by_order( $order );
// Get line items
$payment_gateway = wc_get_payment_gateway_by_order( $order );
$line_items = $order->get_items( apply_filters( 'woocommerce_admin_order_item_types', 'line_item' ) );
$discounts = $order->get_items( 'discount' );
$line_items_fee = $order->get_items( 'fee' );
@ -83,7 +83,9 @@ if ( wc_tax_enabled() ) {
</tbody>
<tbody id="order_refunds">
<?php
if ( $refunds = $order->get_refunds() ) {
$refunds = $order->get_refunds();
if ( $refunds ) {
foreach ( $refunds as $refund ) {
include 'html-order-refund.php';
}
@ -93,18 +95,6 @@ if ( wc_tax_enabled() ) {
</tbody>
</table>
</div>
<div class="wc-order-data-row wc-order-item-bulk-edit" style="display:none;">
<?php if ( $order->is_editable() ) : ?>
<button type="button" class="button bulk-delete-items"><?php esc_html_e( 'Delete selected row(s)', 'woocommerce' ); ?></button>
<?php endif; ?>
<?php if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) : ?>
<button type="button" class="button bulk-decrease-stock"><?php esc_html_e( 'Reduce stock', 'woocommerce' ); ?></button>
<button type="button" class="button bulk-increase-stock"><?php esc_html_e( 'Increase stock', 'woocommerce' ); ?></button>
<?php endif; ?>
<?php do_action( 'woocommerce_admin_order_item_bulk_actions', $order ); ?>
</div>
<div class="wc-order-data-row wc-order-totals-items wc-order-items-editable">
<?php
$coupons = $order->get_items( 'coupon' );
@ -118,7 +108,7 @@ if ( wc_tax_enabled() ) {
$post_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status = 'publish' LIMIT 1;", $item->get_code() ) );
$class = $order->is_editable() ? 'code editable' : 'code';
?>
<li class="<?php echo $class; ?>">
<li class="<?php echo esc_attr( $class ); ?>">
<?php if ( $post_id ) : ?>
<?php
$post_url = apply_filters( 'woocommerce_admin_order_item_coupon_url', add_query_arg(
@ -151,7 +141,7 @@ if ( wc_tax_enabled() ) {
<td class="label"><?php esc_html_e( 'Discount:', 'woocommerce' ); ?></td>
<td width="1%"></td>
<td class="total">
<?php echo wc_price( $order->get_total_discount(), array( 'currency' => $order->get_currency() ) ); ?>
<?php echo wc_price( $order->get_total_discount(), array( 'currency' => $order->get_currency() ) ); // WPCS: XSS ok. ?>
</td>
</tr>
<?php endif; ?>
@ -166,9 +156,9 @@ if ( wc_tax_enabled() ) {
<?php
$refunded = $order->get_total_shipping_refunded();
if ( $refunded > 0 ) {
echo '<del>' . strip_tags( wc_price( $order->get_shipping_total(), array( 'currency' => $order->get_currency() ) ) ) . '</del> <ins>' . wc_price( $order->get_shipping_total() - $refunded, array( 'currency' => $order->get_currency() ) ) . '</ins>';
echo '<del>' . strip_tags( wc_price( $order->get_shipping_total(), array( 'currency' => $order->get_currency() ) ) ) . '</del> <ins>' . wc_price( $order->get_shipping_total() - $refunded, array( 'currency' => $order->get_currency() ) ) . '</ins>'; // WPCS: XSS ok.
} else {
echo wc_price( $order->get_shipping_total(), array( 'currency' => $order->get_currency() ) );
echo wc_price( $order->get_shipping_total(), array( 'currency' => $order->get_currency() ) ); // WPCS: XSS ok.
}
?>
</td>
@ -186,9 +176,9 @@ if ( wc_tax_enabled() ) {
<?php
$refunded = $order->get_total_tax_refunded_by_rate_id( $tax->rate_id );
if ( $refunded > 0 ) {
echo '<del>' . strip_tags( $tax->formatted_amount ) . '</del> <ins>' . wc_price( WC_Tax::round( $tax->amount, wc_get_price_decimals() ) - WC_Tax::round( $refunded, wc_get_price_decimals() ), array( 'currency' => $order->get_currency() ) ) . '</ins>';
echo '<del>' . strip_tags( $tax->formatted_amount ) . '</del> <ins>' . wc_price( WC_Tax::round( $tax->amount, wc_get_price_decimals() ) - WC_Tax::round( $refunded, wc_get_price_decimals() ), array( 'currency' => $order->get_currency() ) ) . '</ins>'; // WPCS: XSS ok.
} else {
echo $tax->formatted_amount;
echo wp_kses_post( $tax->formatted_amount );
}
?>
</td>
@ -202,7 +192,7 @@ if ( wc_tax_enabled() ) {
<td class="label"><?php esc_html_e( 'Total', 'woocommerce' ); ?>:</td>
<td width="1%"></td>
<td class="total">
<?php echo $order->get_formatted_order_total(); ?>
<?php echo $order->get_formatted_order_total(); // WPCS: XSS ok. ?>
</td>
</tr>
@ -212,7 +202,7 @@ if ( wc_tax_enabled() ) {
<tr>
<td class="label refunded-total"><?php esc_html_e( 'Refunded', 'woocommerce' ); ?>:</td>
<td width="1%"></td>
<td class="total refunded-total">-<?php echo wc_price( $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); ?></td>
<td class="total refunded-total">-<?php echo wc_price( $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); // WPCS: XSS ok. ?></td>
</tr>
<?php endif; ?>
@ -235,7 +225,7 @@ if ( wc_tax_enabled() ) {
<button type="button" class="button refund-items"><?php esc_html_e( 'Refund', 'woocommerce' ); ?></button>
<?php endif; ?>
<?php
// allow adding custom buttons
// Allow adding custom buttons.
do_action( 'woocommerce_order_item_add_action_buttons', $order );
?>
<?php if ( $order->is_editable() ) : ?>
@ -251,7 +241,7 @@ if ( wc_tax_enabled() ) {
<button type="button" class="button add-order-tax"><?php esc_html_e( 'Add tax', 'woocommerce' ); ?></button>
<?php endif; ?>
<?php
// allow adding custom buttons
// Allow adding custom buttons.
do_action( 'woocommerce_order_item_add_line_buttons', $order );
?>
<button type="button" class="button cancel-action"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></button>
@ -268,11 +258,11 @@ if ( wc_tax_enabled() ) {
<?php endif; ?>
<tr>
<td class="label"><?php esc_html_e( 'Amount already refunded', 'woocommerce' ); ?>:</td>
<td class="total">-<?php echo wc_price( $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); ?></td>
<td class="total">-<?php echo wc_price( $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); // WPCS: XSS ok. ?></td>
</tr>
<tr>
<td class="label"><?php esc_html_e( 'Total available to refund', 'woocommerce' ); ?>:</td>
<td class="total"><?php echo wc_price( $order->get_total() - $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); ?></td>
<td class="total"><?php echo wc_price( $order->get_total() - $order->get_total_refunded(), array( 'currency' => $order->get_currency() ) ); // WPCS: XSS ok. ?></td>
</tr>
<tr>
<td class="label"><label for="refund_amount"><?php esc_html_e( 'Refund amount', 'woocommerce' ); ?>:</label></td>
@ -301,7 +291,7 @@ if ( wc_tax_enabled() ) {
}
?>
<?php /* translators: refund amount */ ?>
<button type="button" class="button button-primary do-manual-refund tips" data-tip="<?php esc_attr_e( 'You will need to manually issue a refund through your payment gateway after using this.', 'woocommerce' ); ?>"><?php printf( esc_html__( 'Refund %s manually', 'woocommerce' ), $refund_amount ); ?></button>
<button type="button" class="button button-primary do-manual-refund tips" data-tip="<?php esc_attr_e( 'You will need to manually issue a refund through your payment gateway after using this.', 'woocommerce' ); ?>"><?php printf( esc_html__( 'Refund %s manually', 'woocommerce' ), wp_kses_post( $refund_amount ) ); ?></button>
<button type="button" class="button cancel-action"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></button>
<input type="hidden" id="refunded_amount" name="refunded_amount" value="<?php echo esc_attr( $order->get_total_refunded() ); ?>" />
<div class="clear"></div>
@ -321,7 +311,24 @@ if ( wc_tax_enabled() ) {
</header>
<article>
<form action="" method="post">
<select class="wc-product-search" multiple="multiple" style="width: 50%;" id="add_item_id" name="add_order_items[]" data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'woocommerce' ); ?>"></select>
<table class="widefat">
<thead>
<tr>
<th><?php esc_html_e( 'Product', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'Quantity', 'woocommerce' ); ?></th>
</tr>
</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><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 ); ?>">
<tr>
<?php echo $row; // WPCS: XSS ok. ?>
</tr>
</tbody>
</table>
</form>
</article>
<footer>
@ -369,7 +376,7 @@ if ( wc_tax_enabled() ) {
<td>' . WC_Tax::get_rate_code( $rate ) . '</td>
<td>' . WC_Tax::get_rate_percent( $rate ) . '</td>
</tr>
';
'; // WPCS: XSS ok.
}
?>
</table>

View File

@ -1064,9 +1064,9 @@ class WC_REST_Products_Controller extends WC_REST_Legacy_Products_Controller {
$images = is_array( $images ) ? array_filter( $images ) : array();
if ( ! empty( $images ) ) {
$gallery = array();
$gallery_positions = array();
foreach ( $images as $image ) {
foreach ( $images as $index => $image ) {
$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
if ( 0 === $attachment_id && isset( $image['src'] ) ) {
@ -1088,11 +1088,7 @@ class WC_REST_Products_Controller extends WC_REST_Legacy_Products_Controller {
throw new WC_REST_Exception( 'woocommerce_product_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 );
}
if ( isset( $image['position'] ) && 0 === absint( $image['position'] ) ) {
$product->set_image_id( $attachment_id );
} else {
$gallery[] = $attachment_id;
}
$gallery_positions[ $attachment_id ] = absint( isset( $image['position'] ) ? $image['position'] : $index );
// Set the image alt if present.
if ( ! empty( $image['alt'] ) ) {
@ -1115,6 +1111,17 @@ class WC_REST_Products_Controller extends WC_REST_Legacy_Products_Controller {
}
}
// Sort images and get IDs in correct order.
asort( $gallery_positions );
// Get gallery in correct order.
$gallery = array_keys( $gallery_positions );
// Featured image is in position 0.
$image_id = array_shift( $gallery );
// Set images.
$product->set_image_id( $image_id );
$product->set_gallery_image_ids( $gallery );
} else {
$product->set_image_id( '' );

View File

@ -409,6 +409,15 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
case 'clear_transients':
wc_delete_product_transients();
wc_delete_shop_order_transients();
$attribute_taxonomies = wc_get_attribute_taxonomies();
if ( $attribute_taxonomies ) {
foreach ( $attribute_taxonomies as $attribute ) {
delete_transient( 'wc_layered_nav_counts_pa_' . $attribute->attribute_name );
}
}
WC_Cache_Helper::get_transient_version( 'shipping', true );
$message = __( 'Product transients cleared', 'woocommerce' );
break;

View File

@ -360,8 +360,6 @@ class WC_API_Orders extends WC_API_Resource {
public function create_order( $data ) {
global $wpdb;
wc_transaction_query( 'start' );
try {
if ( ! isset( $data['order'] ) ) {
throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order' ), 400 );
@ -466,15 +464,10 @@ class WC_API_Orders extends WC_API_Resource {
do_action( 'woocommerce_api_create_order', $order->get_id(), $data, $this );
do_action( 'woocommerce_new_order', $order->get_id() );
wc_transaction_query( 'commit' );
return $this->get_order( $order->get_id() );
} catch ( WC_Data_Exception $e ) {
wc_transaction_query( 'rollback' );
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) );
} catch ( WC_API_Exception $e ) {
wc_transaction_query( 'rollback' );
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
}
}

View File

@ -389,16 +389,12 @@ class WC_API_Orders extends WC_API_Resource {
* Create an order
*
* @since 2.2
*
* @param array $data raw order data
*
* @return array|WP_Error
*/
public function create_order( $data ) {
global $wpdb;
wc_transaction_query( 'start' );
try {
if ( ! isset( $data['order'] ) ) {
throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order' ), 400 );
@ -508,15 +504,10 @@ class WC_API_Orders extends WC_API_Resource {
do_action( 'woocommerce_api_create_order', $order->get_id(), $data, $this );
do_action( 'woocommerce_new_order', $order->get_id() );
wc_transaction_query( 'commit' );
return $this->get_order( $order->get_id() );
} catch ( WC_Data_Exception $e ) {
wc_transaction_query( 'rollback' );
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => 400 ) );
} catch ( WC_API_Exception $e ) {
wc_transaction_query( 'rollback' );
return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
}
}

View File

@ -274,7 +274,7 @@ class WC_AJAX {
wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
if ( WC()->cart->is_empty() && ! is_customize_preview() ) {
if ( WC()->cart->is_empty() && ! is_customize_preview() && apply_filters( 'woocommerce_checkout_update_order_review_expired', true ) ) {
self::update_order_review_expired();
}
@ -818,17 +818,20 @@ class WC_AJAX {
}
try {
$order_id = absint( $_POST['order_id'] );
if ( ! isset( $_POST['order_id'] ) ) {
throw new Exception( __( 'Invalid order', 'woocommerce' ) );
}
$order_id = absint( wp_unslash( $_POST['order_id'] ) ); // WPCS: input var ok.
$order = wc_get_order( $order_id );
$items_to_add = wp_parse_id_list( is_array( $_POST['item_to_add'] ) ? $_POST['item_to_add'] : array( $_POST['item_to_add'] ) );
$items = ( ! empty( $_POST['items'] ) ) ? $_POST['items'] : '';
$added_items = array();
if ( ! $order ) {
throw new Exception( __( 'Invalid order', 'woocommerce' ) );
}
// If we passed through items it means we need to save first before adding a new one.
$items = ( ! empty( $_POST['items'] ) ) ? $_POST['items'] : '';
if ( ! empty( $items ) ) {
$save_items = array();
parse_str( $items, $save_items );
@ -836,23 +839,28 @@ class WC_AJAX {
wc_save_order_items( $order->get_id(), $save_items );
}
foreach ( $items_to_add as $item_to_add ) {
$items_to_add = array_filter( wp_unslash( (array) $_POST['data'] ) );
if ( ! in_array( get_post_type( $item_to_add ), array( 'product', 'product_variation' ) ) ) {
// Add items to order.
foreach ( $items_to_add as $item ) {
if ( ! isset( $item['id'], $item['qty'] ) || empty( $item['id'] ) ) {
continue;
}
$product_id = absint( $item['id'] );
$qty = wc_stock_amount( $item['qty'] );
$product = wc_get_product( $product_id );
$item_id = $order->add_product( wc_get_product( $item_to_add ) );
$item = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id );
if ( ! $product ) {
throw new Exception( __( 'Invalid product ID', 'woocommerce' ) . ' ' . $product_id );
}
$item_id = $order->add_product( $product, $qty );
$item = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id );
$added_items[ $item_id ] = $item;
do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item, $order );
}
$last_item = ! empty( $added_items ) ? end( $added_items ) : null;
wc_do_deprecated_action( 'woocommerce_ajax_added_order_items', array( is_a( $last_item, 'WC_Order_Item' ) ? $last_item->get_id() : null, $last_item, $order ), '3.4', 'woocommerce_ajax_order_items_added action instead.' );
do_action( 'woocommerce_ajax_order_items_added', $added_items, $order );
$data = get_post_meta( $order_id );
@ -1145,96 +1153,6 @@ class WC_AJAX {
}
}
/**
* Reduce order item stock.
*/
public static function reduce_order_item_stock() {
check_ajax_referer( 'order-item', 'security' );
if ( ! current_user_can( 'edit_shop_orders' ) ) {
wp_die( -1 );
}
$order_id = absint( $_POST['order_id'] );
$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
$order = wc_get_order( $order_id );
$order_items = $order->get_items();
$return = array();
if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
foreach ( $order_items as $item_id => $order_item ) {
// Only reduce checked items
if ( ! in_array( $item_id, $order_item_ids ) ) {
continue;
}
$_product = $order_item->get_product();
if ( $_product && $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
$stock_change = apply_filters( 'woocommerce_reduce_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
$new_stock = wc_update_product_stock( $_product, $stock_change, 'decrease' );
$item_name = $_product->get_formatted_name();
$return[] = array(
'note' => sprintf( wp_kses_post( __( '%1$s stock reduced from %2$s to %3$s.', 'woocommerce' ) ), $item_name, $new_stock + $stock_change, $new_stock ),
'success' => true,
);
}
}
do_action( 'woocommerce_reduce_order_stock', $order );
if ( empty( $return ) ) {
$return[] = array(
'note' => wp_kses_post( __( 'No products had their stock reduced - they may not have stock management enabled.', 'woocommerce' ) ),
'success' => false,
);
}
wp_send_json_success( $return );
}
wp_send_json_error();
}
/**
* Increase order item stock.
*/
public static function increase_order_item_stock() {
check_ajax_referer( 'order-item', 'security' );
if ( ! current_user_can( 'edit_shop_orders' ) ) {
wp_die( -1 );
}
$order_id = absint( $_POST['order_id'] );
$order_item_ids = isset( $_POST['order_item_ids'] ) ? $_POST['order_item_ids'] : array();
$order_item_qty = isset( $_POST['order_item_qty'] ) ? $_POST['order_item_qty'] : array();
$order = wc_get_order( $order_id );
$order_items = $order->get_items();
$return = array();
if ( $order && ! empty( $order_items ) && sizeof( $order_item_ids ) > 0 ) {
foreach ( $order_items as $item_id => $order_item ) {
// Only reduce checked items
if ( ! in_array( $item_id, $order_item_ids ) ) {
continue;
}
$_product = $order_item->get_product();
if ( $_product && $_product->exists() && $_product->managing_stock() && isset( $order_item_qty[ $item_id ] ) && $order_item_qty[ $item_id ] > 0 ) {
$old_stock = $_product->get_stock_quantity();
$stock_change = apply_filters( 'woocommerce_restore_order_stock_quantity', $order_item_qty[ $item_id ], $item_id );
$new_quantity = wc_update_product_stock( $_product, $stock_change, 'increase' );
$item_name = $_product->get_formatted_name();
$return[] = array(
'note' => sprintf( wp_kses_post( __( '%1$s stock increased from %2$s to %3$s.', 'woocommerce' ) ), $item_name, $old_stock, $new_quantity ),
'success' => true,
);
}
}
do_action( 'woocommerce_restore_order_stock', $order );
if ( empty( $return ) ) {
$return[] = array(
'note' => wp_kses_post( __( 'No products had their stock increased - they may not have stock management enabled.', 'woocommerce' ) ),
'success' => false,
);
}
wp_send_json_success( $return );
}
wp_send_json_error();
}
/**
* Calc line tax.
*/
@ -1392,8 +1310,14 @@ class WC_AJAX {
wp_die();
}
if ( ! empty( $_GET['limit'] ) ) {
$limit = absint( $_GET['limit'] );
} else {
$limit = absint( apply_filters( 'woocommerce_json_search_limit', 30 ) );
}
$data_store = WC_Data_Store::load( 'product' );
$ids = $data_store->search_products( $term, '', (bool) $include_variations );
$ids = $data_store->search_products( $term, '', (bool) $include_variations, false, $limit );
if ( ! empty( $_GET['exclude'] ) ) {
$ids = array_diff( $ids, (array) $_GET['exclude'] );
@ -1403,15 +1327,18 @@ class WC_AJAX {
$ids = array_intersect( $ids, (array) $_GET['include'] );
}
if ( ! empty( $_GET['limit'] ) ) {
$ids = array_slice( $ids, 0, absint( $_GET['limit'] ) );
}
$product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' );
$products = array();
foreach ( $product_objects as $product_object ) {
$products[ $product_object->get_id() ] = rawurldecode( $product_object->get_formatted_name() );
$formatted_name = $product_object->get_formatted_name();
$managing_stock = $product_object->managing_stock();
if ( $managing_stock && ! empty( $_GET['display_stock'] ) ) {
$formatted_name .= ' &ndash; ' . wc_format_stock_for_display( $product_object );
}
$products[ $product_object->get_id() ] = rawurldecode( $formatted_name );
}
wp_send_json( apply_filters( 'woocommerce_json_search_found_products', $products ) );

View File

@ -319,10 +319,6 @@ class WC_Auth {
$consumer_data = array();
try {
if ( 'yes' !== get_option( 'woocommerce_api_enabled' ) ) {
throw new Exception( __( 'API disabled!', 'woocommerce' ) );
}
$route = strtolower( wc_clean( $route ) );
$this->make_validation();

View File

@ -134,17 +134,35 @@ class WC_Cache_Helper {
public static function get_transient_version( $group, $refresh = false ) {
$transient_name = $group . '-transient-version';
$transient_value = get_transient( $transient_name );
$transient_value = strval( $transient_value ? $transient_value : '' );
if ( false === $transient_value || true === $refresh ) {
self::delete_version_transients( $transient_value );
if ( '' === $transient_value || true === $refresh ) {
$old_transient_value = $transient_value;
$transient_value = (string) time();
$transient_value = time();
if ( $old_transient_value === $transient_value ) {
// Time did not change but transient needs flushing now.
self::delete_version_transients( $transient_value );
} else {
self::queue_delete_version_transients( $transient_value );
}
set_transient( $transient_name, $transient_value );
}
return $transient_value;
}
/**
* Queues a cleanup event for version transients.
*
* @param string $version Version of the transient to remove.
*/
protected static function queue_delete_version_transients( $version = '' ) {
if ( ! wp_using_ext_object_cache() && ! empty( $version ) ) {
wp_schedule_single_event( time() + 30, 'delete_version_transients', array( $version ) );
}
}
/**
* When the transient version increases, this is used to remove all past transients to avoid filling the DB.
*
@ -165,9 +183,9 @@ class WC_Cache_Helper {
$affected = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s ORDER BY option_id LIMIT %d;", '\_transient\_%' . $version, $limit ) ); // WPCS: cache ok, db call ok.
// If affected rows is equal to limit, there are more rows to delete. Delete in 10 secs.
// If affected rows is equal to limit, there are more rows to delete. Delete in 30 secs.
if ( $affected === $limit ) {
wp_schedule_single_event( time() + 10, 'delete_version_transients', array( $version ) );
self::queue_delete_version_transients( $version );
}
}
}

View File

@ -757,66 +757,35 @@ class WC_Cart extends WC_Legacy_Cart {
* @return bool|WP_Error
*/
public function check_cart_item_stock() {
global $wpdb;
$error = new WP_Error();
$product_qty_in_cart = $this->get_cart_item_quantities();
$error = new WP_Error();
$product_qty_in_cart = $this->get_cart_item_quantities();
$hold_stock_minutes = (int) get_option( 'woocommerce_hold_stock_minutes', 0 );
$current_session_order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0;
foreach ( $this->get_cart() as $cart_item_key => $values ) {
$product = $values['data'];
/**
* Check stock based on stock-status.
*/
// Check stock based on stock-status.
if ( ! $product->is_in_stock() ) {
/* translators: %s: product name */
$error->add( 'out-of-stock', sprintf( __( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name() ) );
return $error;
}
if ( ! $product->managing_stock() ) {
// We only need to check products managing stock, with a limited stock qty.
if ( ! $product->managing_stock() || $product->backorders_allowed() ) {
continue;
}
/**
* Check stock based on all items in the cart.
*/
if ( ! $product->has_enough_stock( $product_qty_in_cart[ $product->get_stock_managed_by_id() ] ) ) {
// Check stock based on all items in the cart and consider any held stock within pending orders.
$held_stock = wc_get_held_stock_quantity( $product, $current_session_order_id );
$required_stock = $product_qty_in_cart[ $product->get_stock_managed_by_id() ];
if ( $product->get_stock_quantity() < ( $held_stock + $required_stock ) ) {
/* translators: 1: product name 2: quantity in stock */
$error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order (%2$s in stock). Please edit your cart and try again. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) );
$error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order (%2$s available). We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity() - $held_stock, $product ) ) );
return $error;
}
/**
* Finally consider any held stock, from pending orders.
*/
if ( get_option( 'woocommerce_hold_stock_minutes' ) > 0 && ! $product->backorders_allowed() ) {
$order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0;
$held_stock = $wpdb->get_var(
$wpdb->prepare(
"
SELECT SUM( order_item_meta.meta_value ) AS held_qty
FROM {$wpdb->posts} AS posts
LEFT JOIN {$wpdb->prefix}woocommerce_order_items as order_items ON posts.ID = order_items.order_id
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta2 ON order_items.order_item_id = order_item_meta2.order_item_id
WHERE order_item_meta.meta_key = '_qty'
AND order_item_meta2.meta_key = %s AND order_item_meta2.meta_value = %d
AND posts.post_type IN ( '" . implode( "','", wc_get_order_types() ) . "' )
AND posts.post_status = 'wc-pending'
AND posts.ID != %d;",
'variation' === get_post_type( $product->get_stock_managed_by_id() ) ? '_variation_id' : '_product_id',
$product->get_stock_managed_by_id(),
$order_id
)
); // WPCS: unprepared SQL ok.
if ( $product->get_stock_quantity() < ( $held_stock + $product_qty_in_cart[ $product->get_stock_managed_by_id() ] ) ) {
/* translators: 1: product name 2: minutes */
$error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order right now. Please try again in %2$d minutes or edit your cart and try again. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name(), get_option( 'woocommerce_hold_stock_minutes' ) ) );
return $error;
}
}
}
return true;

View File

@ -120,6 +120,7 @@ class WC_Countries {
'LB' => array(),
'LU' => array(),
'MQ' => array(),
'MT' => array(),
'NL' => array(),
'NO' => array(),
'PL' => array(),
@ -991,6 +992,11 @@ class WC_Countries {
'required' => false,
),
),
'MT' => array(
'state' => array(
'required' => false,
),
),
'NL' => array(
'postcode' => array(
'priority' => 65,

View File

@ -499,11 +499,28 @@ class WC_Discounts {
* @return int Total discounted.
*/
protected function apply_coupon_custom( $coupon, $items_to_apply ) {
$limit_usage_qty = 0;
$applied_count = 0;
if ( null !== $coupon->get_limit_usage_to_x_items() ) {
$limit_usage_qty = $coupon->get_limit_usage_to_x_items();
}
// Apply the coupon to each item.
foreach ( $items_to_apply as $item ) {
$discounted_price = $this->get_discounted_price_in_cents( $item );
// Find out how much price is available to discount for the item.
$discounted_price = $this->get_discounted_price_in_cents( $item );
// Get the price we actually want to discount, based on settings.
$price_to_discount = wc_remove_number_precision( ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $discounted_price : $item->price );
$discount = wc_add_number_precision( $coupon->get_discount_amount( $price_to_discount / $item->quantity, $item->object, true ) ) * $item->quantity;
$discount = min( $discounted_price, $discount );
// See how many and what price to apply to.
$apply_quantity = $limit_usage_qty && ( $limit_usage_qty - $applied_count ) < $item->quantity ? $limit_usage_qty - $applied_count : $item->quantity;
$apply_quantity = max( 0, apply_filters( 'woocommerce_coupon_get_apply_quantity', $apply_quantity, $item, $coupon, $this ) );
// Run coupon calculations.
$discount = wc_add_number_precision( $coupon->get_discount_amount( $price_to_discount / $item->quantity, $item->object, true ) ) * $apply_quantity;
$discount = wc_round_discount( min( $discounted_price, $discount ), 0 );
// Store code and discount amount per item.
$this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;

View File

@ -105,6 +105,10 @@ class WC_Install {
'wc_update_340_last_active',
'wc_update_340_db_version',
),
'3.4.3' => array(
'wc_update_343_cleanup_foreign_keys',
'wc_update_343_db_version',
),
'3.5.0' => array(
'wc_update_350_order_customer_id',
'wc_update_350_db_version',
@ -569,7 +573,22 @@ class WC_Install {
// Add constraint to download logs if the columns matches.
if ( ! empty( $download_permissions_column_type ) && ! empty( $download_log_column_type ) && $download_permissions_column_type === $download_log_column_type ) {
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_download_log ADD FOREIGN KEY (permission_id) REFERENCES {$wpdb->prefix}woocommerce_downloadable_product_permissions(permission_id) ON DELETE CASCADE" );
$fk_result = $wpdb->get_row( "
SELECT COUNT(*) AS fk_count
FROM information_schema.TABLE_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = '{$wpdb->dbname}'
AND CONSTRAINT_NAME = 'fk_wc_download_log_permission_id'
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
AND TABLE_NAME = '{$wpdb->prefix}wc_download_log'
" );
if ( 0 === (int) $fk_result->fk_count ) {
$wpdb->query( "
ALTER TABLE `{$wpdb->prefix}wc_download_log`
ADD CONSTRAINT `fk_wc_download_log_permission_id`
FOREIGN KEY (`permission_id`)
REFERENCES `{$wpdb->prefix}woocommerce_downloadable_product_permissions` (`permission_id`) ON DELETE CASCADE;
" );
}
}
}

View File

@ -102,8 +102,6 @@ class WC_Order extends WC_Abstract_Order {
}
try {
wc_transaction_query( 'start' );
do_action( 'woocommerce_pre_payment_complete', $this->get_id() );
if ( WC()->session ) {
@ -124,20 +122,18 @@ class WC_Order extends WC_Abstract_Order {
} else {
do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->get_id() );
}
wc_transaction_query( 'commit' );
} catch ( Exception $e ) {
wc_transaction_query( 'rollback' );
/**
* If there was an error completing the payment, log to a file and add an order note so the admin can take action.
*/
$logger = wc_get_logger();
$logger->error(
sprintf( 'Payment complete of order #%d failed!', $this->get_id() ), array(
sprintf( 'Error completing payment for order #%d', $this->get_id() ), array(
'order' => $this,
'error' => $e,
)
);
$this->add_order_note( __( 'Payment complete event failed.', 'woocommerce' ) . ' ' . $e->getMessage() );
return false;
}
return true;
@ -320,18 +316,12 @@ class WC_Order extends WC_Abstract_Order {
}
try {
wc_transaction_query( 'start' );
$this->set_status( $new_status, $note, $manual );
$this->save();
wc_transaction_query( 'commit' );
} catch ( Exception $e ) {
wc_transaction_query( 'rollback' );
$logger = wc_get_logger();
$logger->error(
sprintf( 'Update status of order #%d failed!', $this->get_id() ), array(
sprintf( 'Error updating status for order #%d', $this->get_id() ), array(
'order' => $this,
'error' => $e,
)

View File

@ -39,16 +39,16 @@ class WC_Privacy extends WC_Abstract_Privacy {
include_once 'class-wc-privacy-exporters.php';
// This hook registers WooCommerce data exporters.
$this->add_exporter( 'woocommerce-customer-data', __( 'Customer Data', 'woocommerce' ), array( 'WC_Privacy_Exporters', 'customer_data_exporter' ) );
$this->add_exporter( 'woocommerce-customer-orders', __( 'Customer Orders', 'woocommerce' ), array( 'WC_Privacy_Exporters', 'order_data_exporter' ) );
$this->add_exporter( 'woocommerce-customer-downloads', __( 'Customer Downloads', 'woocommerce' ), array( 'WC_Privacy_Exporters', 'download_data_exporter' ) );
$this->add_exporter( 'woocommerce-customer-tokens', __( 'Customer Payment Tokens', 'woocommerce' ), array( 'WC_Privacy_Exporters', 'customer_tokens_exporter' ) );
$this->add_exporter( 'woocommerce-customer-data', __( 'WooCommerce Customer Data', 'woocommerce' ), array( 'WC_Privacy_Exporters', 'customer_data_exporter' ) );
$this->add_exporter( 'woocommerce-customer-orders', __( 'WooCommerce Customer Orders', 'woocommerce' ), array( 'WC_Privacy_Exporters', 'order_data_exporter' ) );
$this->add_exporter( 'woocommerce-customer-downloads', __( 'WooCommerce Customer Downloads', 'woocommerce' ), array( 'WC_Privacy_Exporters', 'download_data_exporter' ) );
$this->add_exporter( 'woocommerce-customer-tokens', __( 'WooCommerce Customer Payment Tokens', 'woocommerce' ), array( 'WC_Privacy_Exporters', 'customer_tokens_exporter' ) );
// This hook registers WooCommerce data erasers.
$this->add_eraser( 'woocommerce-customer-data', __( 'Customer Data', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'customer_data_eraser' ) );
$this->add_eraser( 'woocommerce-customer-orders', __( 'Customer Orders', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'order_data_eraser' ) );
$this->add_eraser( 'woocommerce-customer-downloads', __( 'Customer Downloads', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'download_data_eraser' ) );
$this->add_eraser( 'woocommerce-customer-tokens', __( 'Customer Payment Tokens', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'customer_tokens_eraser' ) );
$this->add_eraser( 'woocommerce-customer-data', __( 'WooCommerce Customer Data', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'customer_data_eraser' ) );
$this->add_eraser( 'woocommerce-customer-orders', __( 'WooCommerce Customer Orders', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'order_data_eraser' ) );
$this->add_eraser( 'woocommerce-customer-downloads', __( 'WooCommerce Customer Downloads', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'download_data_eraser' ) );
$this->add_eraser( 'woocommerce-customer-tokens', __( 'WooCommerce Customer Payment Tokens', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'customer_tokens_eraser' ) );
// Cleanup orders daily - this is a callback on a daily cron event.
add_action( 'woocommerce_cleanup_personal_data', array( $this, 'queue_cleanup_personal_data' ) );

View File

@ -426,6 +426,33 @@ class WC_Product_Variation extends WC_Product_Simple {
* @param array $parent_data parent data array for this variation.
*/
public function set_parent_data( $parent_data ) {
$parent_data = wp_parse_args( $parent_data, array(
'title' => '',
'status' => '',
'sku' => '',
'manage_stock' => 'no',
'backorders' => 'no',
'stock_quantity' => '',
'weight' => '',
'length' => '',
'width' => '',
'height' => '',
'tax_class' => '',
'shipping_class_id' => 0,
'image_id' => 0,
'purchase_note' => '',
'catalog_visibility' => 'visible',
) );
// Normalize tax class.
$parent_data['tax_class'] = sanitize_title( $parent_data['tax_class'] );
$parent_data['tax_class'] = 'standard' === $parent_data['tax_class'] ? '' : $parent_data['tax_class'];
$valid_classes = $this->get_valid_tax_classes();
if ( ! in_array( $parent_data['tax_class'], $valid_classes, true ) ) {
$parent_data['tax_class'] = '';
}
$this->parent_data = $parent_data;
}

View File

@ -340,7 +340,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
global $wpdb;
// Get from cache if available.
$items = wp_cache_get( 'order-items-' . $order->get_id(), 'orders' );
$items = 0 < $order->get_id() ? wp_cache_get( 'order-items-' . $order->get_id(), 'orders' ) : false;
if ( false === $items ) {
$items = $wpdb->get_results(
@ -349,7 +349,9 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
foreach ( $items as $item ) {
wp_cache_set( 'item-' . $item->order_item_id, $item, 'order-items' );
}
wp_cache_set( 'order-items-' . $order->get_id(), $items, 'orders' );
if ( 0 < $order->get_id() ) {
wp_cache_set( 'order-items-' . $order->get_id(), $items, 'orders' );
}
}
$items = wp_list_filter( $items, array( 'order_item_type' => $type ) );

View File

@ -594,19 +594,24 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* @param WC_Product $product Product Object.
*/
protected function handle_updated_props( &$product ) {
if ( in_array( 'regular_price', $this->updated_props, true ) || in_array( 'sale_price', $this->updated_props, true ) ) {
if ( $product->get_sale_price( 'edit' ) >= $product->get_regular_price( 'edit' ) ) {
update_post_meta( $product->get_id(), '_sale_price', '' );
$product->set_sale_price( '' );
$price_is_synced = $product->is_type( array( 'variable', 'grouped' ) );
if ( ! $price_is_synced ) {
if ( in_array( 'regular_price', $this->updated_props, true ) || in_array( 'sale_price', $this->updated_props, true ) ) {
if ( $product->get_sale_price( 'edit' ) >= $product->get_regular_price( 'edit' ) ) {
update_post_meta( $product->get_id(), '_sale_price', '' );
$product->set_sale_price( '' );
}
}
}
if ( in_array( 'date_on_sale_from', $this->updated_props, true ) || in_array( 'date_on_sale_to', $this->updated_props, true ) || in_array( 'regular_price', $this->updated_props, true ) || in_array( 'sale_price', $this->updated_props, true ) || in_array( 'product_type', $this->updated_props, true ) ) {
if ( $product->is_on_sale( 'edit' ) ) {
update_post_meta( $product->get_id(), '_price', $product->get_sale_price( 'edit' ) );
$product->set_price( $product->get_sale_price( 'edit' ) );
} else {
update_post_meta( $product->get_id(), '_price', $product->get_regular_price( 'edit' ) );
$product->set_price( $product->get_regular_price( 'edit' ) );
if ( in_array( 'date_on_sale_from', $this->updated_props, true ) || in_array( 'date_on_sale_to', $this->updated_props, true ) || in_array( 'regular_price', $this->updated_props, true ) || in_array( 'sale_price', $this->updated_props, true ) || in_array( 'product_type', $this->updated_props, true ) ) {
if ( $product->is_on_sale( 'edit' ) ) {
update_post_meta( $product->get_id(), '_price', $product->get_sale_price( 'edit' ) );
$product->set_price( $product->get_sale_price( 'edit' ) );
} else {
update_post_meta( $product->get_id(), '_price', $product->get_regular_price( 'edit' ) );
$product->set_price( $product->get_regular_price( 'edit' ) );
}
}
}
@ -1344,13 +1349,14 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
/**
* Search product data for a term and return ids.
*
* @param string $term Search term.
* @param string $type Type of product.
* @param bool $include_variations Include variations in search or not.
* @param bool $all_statuses Should we search all statuses or limit to published.
* @param string $term Search term.
* @param string $type Type of product.
* @param bool $include_variations Include variations in search or not.
* @param bool $all_statuses Should we search all statuses or limit to published.
* @param null|int $limit Limit returned results. @since 3.5.0.
* @return array of ids
*/
public function search_products( $term, $type = '', $include_variations = false, $all_statuses = false ) {
public function search_products( $term, $type = '', $include_variations = false, $all_statuses = false, $limit = null ) {
global $wpdb;
$post_types = $include_variations ? array( 'product', 'product_variation' ) : array( 'product' );
@ -1358,6 +1364,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$type_join = '';
$type_where = '';
$status_where = '';
$limit_query = '';
$term = wc_strtolower( $term );
// See if search term contains OR keywords.
@ -1411,6 +1418,10 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$status_where = " AND posts.post_status IN ('" . implode( "','", $post_statuses ) . "') ";
}
if ( $limit ) {
$limit_query = $wpdb->prepare( ' LIMIT %d ', $limit );
}
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
$search_results = $wpdb->get_results(
// phpcs:disable
@ -1421,7 +1432,9 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$search_where
$status_where
$type_where
ORDER BY posts.post_parent ASC, posts.post_title ASC"
ORDER BY posts.post_parent ASC, posts.post_title ASC
$limit_query
"
// phpcs:enable
);

View File

@ -74,11 +74,13 @@ class WC_Product_Grouped_Data_Store_CPT extends WC_Product_Data_Store_CPT implem
foreach ( $product->get_children( 'edit' ) as $child_id ) {
$child = wc_get_product( $child_id );
if ( $child ) {
$child_prices[] = $child->get_price();
$child_prices[] = $child->get_price( 'edit' );
}
}
$child_prices = array_filter( $child_prices );
delete_post_meta( $product->get_id(), '_price' );
delete_post_meta( $product->get_id(), '_sale_price' );
delete_post_meta( $product->get_id(), '_regular_price' );
if ( ! empty( $child_prices ) ) {
add_post_meta( $product->get_id(), '_price', min( $child_prices ) );

View File

@ -515,6 +515,8 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
$prices = $children ? array_unique( $wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = '_price' AND post_id IN ( " . implode( ',', array_map( 'absint', $children ) ) . ' )' ) ) : array(); // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared
delete_post_meta( $product->get_id(), '_price' );
delete_post_meta( $product->get_id(), '_sale_price' );
delete_post_meta( $product->get_id(), '_regular_price' );
if ( $prices ) {
sort( $prices );

View File

@ -364,9 +364,6 @@ class WC_Gateway_BACS extends WC_Payment_Gateway {
$order->payment_complete();
}
// Reduce stock levels.
wc_reduce_stock_levels( $order_id );
// Remove cart.
WC()->cart->empty_cart();

View File

@ -124,9 +124,6 @@ class WC_Gateway_Cheque extends WC_Payment_Gateway {
$order->payment_complete();
}
// Reduce stock levels.
wc_reduce_stock_levels( $order_id );
// Remove cart.
WC()->cart->empty_cart();

View File

@ -180,8 +180,6 @@ class WC_Gateway_COD extends WC_Payment_Gateway {
}
}
}
} elseif ( WC()->cart && WC()->cart->needs_shipping() ) {
$needs_shipping = true;
}
$needs_shipping = apply_filters( 'woocommerce_cart_needs_shipping', $needs_shipping );
@ -193,7 +191,6 @@ class WC_Gateway_COD extends WC_Payment_Gateway {
// Only apply if all packages are being shipped via chosen method, or order is virtual.
if ( ! empty( $this->enable_for_methods ) && $needs_shipping ) {
$canonical_rate_ids = array();
$order_shipping_items = is_object( $order ) ? $order->get_shipping_methods() : false;
$chosen_shipping_methods_session = WC()->session->get( 'chosen_shipping_methods' );
@ -284,9 +281,6 @@ class WC_Gateway_COD extends WC_Payment_Gateway {
$order->payment_complete();
}
// Reduce stock levels.
wc_reduce_stock_levels( $order_id );
// Remove cart.
WC()->cart->empty_cart();

View File

@ -76,7 +76,6 @@ abstract class WC_Gateway_Paypal_Response {
*/
protected function payment_on_hold( $order, $reason = '' ) {
$order->update_status( 'on-hold', $reason );
wc_reduce_stock_levels( $order->get_id() );
WC()->cart->empty_cart();
}
}

View File

@ -216,9 +216,6 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce {
throw new Simplify_ApiException( $error_msg );
}
// Reduce stock levels
wc_reduce_stock_levels( $order->get_id() );
// Remove cart
WC()->cart->empty_cart();

View File

@ -114,14 +114,43 @@ class WC_Shortcode_Checkout {
throw new Exception( sprintf( __( 'This order&rsquo;s status is &ldquo;%s&rdquo;&mdash;it cannot be paid for. Please contact us if you need assistance.', 'woocommerce' ), wc_get_order_status_name( $order->get_status() ) ) );
}
// Ensure order items are still stocked.
foreach ( $order->get_items() as $item_key => $item ) {
if ( $item && is_callable( array( $item, 'get_product' ) ) ) {
$product = $item->get_product();
// Ensure order items are still stocked if paying for a failed order. Pending orders do not need this check because stock is held.
if ( ! $order->has_status( 'pending' ) ) {
$quantities = array();
if ( $product && ! apply_filters( 'woocommerce_pay_order_product_in_stock', $product->is_in_stock(), $product, $order ) ) {
/* translators: %s: product name */
throw new Exception( sprintf( __( 'Sorry, "%s" is no longer in stock so this order cannot be paid for. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name() ) );
foreach ( $order->get_items() as $item_key => $item ) {
if ( $item && is_callable( array( $item, 'get_product' ) ) ) {
$product = $item->get_product();
if ( ! $product ) {
continue;
}
$quantities[ $product->get_stock_managed_by_id() ] = isset( $quantities[ $product->get_stock_managed_by_id() ] ) ? $quantities[ $product->get_stock_managed_by_id() ] + $item->get_quantity() : $item->get_quantity();
}
}
foreach ( $order->get_items() as $item_key => $item ) {
if ( $item && is_callable( array( $item, 'get_product' ) ) ) {
$product = $item->get_product();
if ( ! $product ) {
continue;
}
if ( ! apply_filters( 'woocommerce_pay_order_product_in_stock', $product->is_in_stock(), $product, $order ) ) {
/* translators: %s: product name */
throw new Exception( sprintf( __( 'Sorry, "%s" is no longer in stock so this order cannot be paid for. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name() ) );
}
// Check stock based on all items in the cart and consider any held stock within pending orders.
$held_stock = wc_get_held_stock_quantity( $product, $order->get_id() );
$required_stock = $quantities[ $product->get_stock_managed_by_id() ];
if ( $product->get_stock_quantity() < ( $held_stock + $required_stock ) ) {
/* translators: 1: product name 2: quantity in stock */
throw new Exception( sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order (%2$s available). We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity() - $held_stock, $product ) ) );
}
}
}
}
@ -211,7 +240,7 @@ class WC_Shortcode_Checkout {
*/
private static function checkout() {
// Check cart has contents.
if ( WC()->cart->is_empty() && ! is_customize_preview() ) {
if ( WC()->cart->is_empty() && ! is_customize_preview() && apply_filters( 'woocommerce_checkout_redirect_empty_cart', true ) ) {
return;
}

View File

@ -102,7 +102,8 @@ function wc_rest_upload_image_from_url( $image_url ) {
if ( ! $wp_filetype['type'] ) {
$headers = wp_remote_retrieve_headers( $response );
if ( isset( $headers['content-disposition'] ) && strstr( $headers['content-disposition'], 'filename=' ) ) {
$disposition = end( explode( 'filename=', $headers['content-disposition'] ) );
$content = explode( 'filename=', $headers['content-disposition'] );
$disposition = end( $content );
$disposition = sanitize_file_name( $disposition );
$file_name = $disposition;
} elseif ( isset( $headers['content-type'] ) && strstr( $headers['content-type'], 'image/' ) ) {

View File

@ -79,14 +79,60 @@ function wc_update_product_stock_status( $product_id, $status ) {
*/
function wc_maybe_reduce_stock_levels( $order_id ) {
$order = wc_get_order( $order_id );
if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', $order && ! $order->get_data_store()->get_stock_reduced( $order_id ), $order_id ) ) {
wc_reduce_stock_levels( $order );
if ( ! $order ) {
return;
}
$stock_reduced = $order->get_data_store()->get_stock_reduced( $order_id );
$trigger_reduce = apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! $stock_reduced, $order_id );
// Only continue if we're reducing stock.
if ( ! $trigger_reduce ) {
return;
}
wc_reduce_stock_levels( $order );
// Ensure stock is marked as "reduced" in case payment complete or other stock actions are called.
$order->get_data_store()->set_stock_reduced( $order_id, true );
}
add_action( 'woocommerce_payment_complete', 'wc_maybe_reduce_stock_levels' );
add_action( 'woocommerce_order_status_completed', 'wc_maybe_reduce_stock_levels' );
add_action( 'woocommerce_order_status_processing', 'wc_maybe_reduce_stock_levels' );
add_action( 'woocommerce_order_status_on-hold', 'wc_maybe_reduce_stock_levels' );
/**
* Reduce stock levels for items within an order.
* When a payment is cancelled, restore stock.
*
* @since 3.0.0
* @param int $order_id Order ID.
*/
function wc_maybe_increase_stock_levels( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
$stock_reduced = $order->get_data_store()->get_stock_reduced( $order_id );
$trigger_reduce = (bool) $stock_reduced;
// Only continue if we're reducing stock.
if ( ! $trigger_reduce ) {
return;
}
wc_increase_stock_levels( $order );
// Ensure stock is marked as "reduced" in case payment complete or other stock actions are called.
$order->get_data_store()->set_stock_reduced( $order_id, false );
}
add_action( 'woocommerce_order_status_cancelled', 'wc_maybe_increase_stock_levels' );
add_action( 'woocommerce_order_status_pending', 'wc_maybe_increase_stock_levels' );
/**
* Reduce stock levels for items within an order, if stock has not already been reduced for the items.
*
* @since 3.0.0
* @param int|WC_Order $order_id Order ID or order instance.
@ -98,48 +144,175 @@ function wc_reduce_stock_levels( $order_id ) {
} else {
$order = wc_get_order( $order_id );
}
if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && $order && apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) && count( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item ) {
if ( ! $item->is_type( 'line_item' ) ) {
continue;
}
$product = $item->get_product();
// We need an order, and a store with stock management to continue.
if ( ! $order || 'yes' !== get_option( 'woocommerce_manage_stock' ) || ! apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) ) {
return;
}
if ( $product && $product->managing_stock() ) {
$qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item );
$item_name = $product->get_formatted_name();
$new_stock = wc_update_product_stock( $product, $qty, 'decrease' );
$changes = array();
if ( ! is_wp_error( $new_stock ) ) {
/* translators: 1: item name 2: old stock quantity 3: new stock quantity */
$order->add_order_note( sprintf( __( '%1$s stock reduced from %2$s to %3$s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock ) );
// Get the latest product data.
$product = wc_get_product( $product->get_id() );
if ( '' !== get_option( 'woocommerce_notify_no_stock_amount' ) && $new_stock <= get_option( 'woocommerce_notify_no_stock_amount' ) ) {
do_action( 'woocommerce_no_stock', $product );
} elseif ( '' !== get_option( 'woocommerce_notify_low_stock_amount' ) && $new_stock <= get_option( 'woocommerce_notify_low_stock_amount' ) ) {
do_action( 'woocommerce_low_stock', $product );
}
if ( $new_stock < 0 ) {
do_action(
'woocommerce_product_on_backorder', array(
'product' => $product,
'order_id' => $order_id,
'quantity' => $qty,
)
);
}
}
}
// Loop over all items.
foreach ( $order->get_items() as $item ) {
if ( ! $item->is_type( 'line_item' ) ) {
continue;
}
// Ensure stock is marked as "reduced" in case payment complete or other stock actions are called.
$order->get_data_store()->set_stock_reduced( $order_id, true );
// Only reduce stock once for each item.
$product = $item->get_product();
$item_stock_reduced = $item->get_meta( '_reduced_stock', true );
do_action( 'woocommerce_reduce_order_stock', $order );
if ( $item_stock_reduced || ! $product || ! $product->managing_stock() ) {
continue;
}
$qty = apply_filters( 'woocommerce_order_item_quantity', $item->get_quantity(), $order, $item );
$item_name = $product->get_formatted_name();
$new_stock = wc_update_product_stock( $product, $qty, 'decrease' );
if ( is_wp_error( $new_stock ) ) {
/* translators: %s item name. */
$order->add_order_note( sprintf( __( 'Unable to reduce stock for item %s.', 'woocommerce' ), $item_name ) );
continue;
}
$item->add_meta_data( '_reduced_stock', $qty, true );
$item->save();
$changes[] = array(
'product' => $product,
'from' => $new_stock + $qty,
'to' => $new_stock,
);
}
wc_trigger_stock_change_notifications( $order, $changes );
do_action( 'woocommerce_reduce_order_stock', $order );
}
/**
* After stock change events, triggers emails and adds order notes.
*
* @since 3.5.0
* @param WC_Order $order order object.
* @param array $changes Array of changes.
*/
function wc_trigger_stock_change_notifications( $order, $changes ) {
if ( empty( $changes ) ) {
return;
}
$order_notes = array();
$no_stock_amount = absint( get_option( 'woocommerce_notify_no_stock_amount', 0 ) );
$low_stock_amount = absint( get_option( 'woocommerce_notify_low_stock_amount', 2 ) );
foreach ( $changes as $change ) {
$order_notes[] = $change['product']->get_formatted_name() . ' ' . $change['from'] . '&rarr;' . $change['to'];
if ( $change['to'] <= $no_stock_amount ) {
do_action( 'woocommerce_no_stock', wc_get_product( $change['product']->get_id() ) );
} elseif ( $change['to'] <= $low_stock_amount ) {
do_action( 'woocommerce_low_stock', wc_get_product( $change['product']->get_id() ) );
}
if ( $change['to'] < 0 ) {
do_action(
'woocommerce_product_on_backorder', array(
'product' => wc_get_product( $change['product']->get_id() ),
'order_id' => $order->get_id(),
'quantity' => abs( $change['from'] - $change['to'] ),
)
);
}
}
$order->add_order_note( __( 'Stock levels reduced:', 'woocommerce' ) . ' ' . implode( ', ', $order_notes ) );
}
/**
* Increase stock levels for items within an order.
*
* @since 3.0.0
* @param int|WC_Order $order_id Order ID or order instance.
*/
function wc_increase_stock_levels( $order_id ) {
if ( is_a( $order_id, 'WC_Order' ) ) {
$order = $order_id;
$order_id = $order->get_id();
} else {
$order = wc_get_order( $order_id );
}
// We need an order, and a store with stock management to continue.
if ( ! $order || 'yes' !== get_option( 'woocommerce_manage_stock' ) || ! apply_filters( 'woocommerce_can_restore_order_stock', true, $order ) ) {
return;
}
// Loop over all items.
foreach ( $order->get_items() as $item ) {
if ( ! $item->is_type( 'line_item' ) ) {
continue;
}
// Only reduce stock once for each item.
$product = $item->get_product();
$item_stock_reduced = $item->get_meta( '_reduced_stock', true );
if ( ! $item_stock_reduced || ! $product || ! $product->managing_stock() ) {
continue;
}
$item_name = $product->get_formatted_name();
$new_stock = wc_update_product_stock( $product, $item_stock_reduced, 'increase' );
if ( is_wp_error( $new_stock ) ) {
/* translators: %s item name. */
$order->add_order_note( sprintf( __( 'Unable to restore stock for item %s.', 'woocommerce' ), $item_name ) );
continue;
}
$item->delete_meta_data( '_reduced_stock' );
$item->save();
$changes[] = $item_name . ' ' . ( $new_stock - $item_stock_reduced ) . '&rarr;' . $new_stock;
}
if ( $changes ) {
$order->add_order_note( __( 'Stock levels increased:', 'woocommerce' ) . ' ' . implode( ', ', $changes ) );
}
do_action( 'woocommerce_restore_order_stock', $order );
}
/**
* See how much stock is being held in pending orders.
*
* @since 3.5.0
* @param WC_Product $product Product to check.
* @param integer $exclude_order_id Order ID to exclude.
* @return int
*/
function wc_get_held_stock_quantity( $product, $exclude_order_id = 0 ) {
global $wpdb;
return $wpdb->get_var(
$wpdb->prepare(
"
SELECT SUM( order_item_meta.meta_value ) AS held_qty
FROM {$wpdb->posts} AS posts
LEFT JOIN {$wpdb->prefix}woocommerce_order_items as order_items ON posts.ID = order_items.order_id
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta2 ON order_items.order_item_id = order_item_meta2.order_item_id
WHERE order_item_meta.meta_key = '_qty'
AND order_item_meta2.meta_key = %s
AND order_item_meta2.meta_value = %d
AND posts.post_type IN ( '" . implode( "','", wc_get_order_types() ) . "' )
AND posts.post_status = 'wc-pending'
AND posts.ID != %d;",
'variation' === get_post_type( $product->get_stock_managed_by_id() ) ? '_variation_id' : '_product_id',
$product->get_stock_managed_by_id(),
$exclude_order_id
)
); // WPCS: unprepared SQL ok.
}

View File

@ -22,7 +22,7 @@ function wc_template_redirect() {
wp_safe_redirect( get_post_type_archive_link( 'product' ) );
exit;
} elseif ( is_page( wc_get_page_id( 'checkout' ) ) && wc_get_page_id( 'checkout' ) !== wc_get_page_id( 'cart' ) && WC()->cart->is_empty() && empty( $wp->query_vars['order-pay'] ) && ! isset( $wp->query_vars['order-received'] ) && ! is_customize_preview() ) {
} elseif ( is_page( wc_get_page_id( 'checkout' ) ) && wc_get_page_id( 'checkout' ) !== wc_get_page_id( 'cart' ) && WC()->cart->is_empty() && empty( $wp->query_vars['order-pay'] ) && ! isset( $wp->query_vars['order-received'] ) && ! is_customize_preview() && apply_filters( 'woocommerce_checkout_redirect_empty_cart', true ) ) {
// When on the checkout with an empty cart, redirect to cart page.
wc_add_notice( __( 'Checkout is not available whilst your cart is empty.', 'woocommerce' ), 'notice' );

View File

@ -1803,6 +1803,39 @@ function wc_update_340_db_version() {
WC_Install::update_db_version( '3.4.0' );
}
/**
* Remove duplicate foreign keys
*
* @return void
*/
function wc_update_343_cleanup_foreign_keys() {
global $wpdb;
$results = $wpdb->get_results( "
SELECT CONSTRAINT_NAME
FROM information_schema.TABLE_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = '{$wpdb->dbname}'
AND CONSTRAINT_NAME LIKE '%wc_download_log_ib%'
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
AND TABLE_NAME = '{$wpdb->prefix}wc_download_log'
" );
if ( $results ) {
foreach ( $results as $fk ) {
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_download_log DROP FOREIGN KEY {$fk->CONSTRAINT_NAME}" ); // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared
}
}
}
/**
* Update DB version.
*
* @return void
*/
function wc_update_343_db_version() {
WC_Install::update_db_version( '3.4.3' );
}
/**
* Copy order customer_id from post meta to post_author and set post_author to 0 for refunds.
*

View File

@ -106,7 +106,7 @@ if ( ! function_exists( 'wc_create_new_customer' ) ) {
$customer_id = wp_insert_user( $new_customer_data );
if ( is_wp_error( $customer_id ) ) {
return new WP_Error( 'registration-error', '<strong>' . __( 'Error:', 'woocommerce' ) . '</strong> ' . __( 'Couldn&#8217;t register you&hellip; please contact us if you continue to have problems.', 'woocommerce' ) );
return new WP_Error( 'registration-error', __( 'Couldn&#8217;t register you&hellip; please contact us if you continue to have problems.', 'woocommerce' ) );
}
do_action( 'woocommerce_created_customer', $customer_id, $new_customer_data, $password_generated );

5098
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "woocommerce",
"title": "WooCommerce",
"version": "3.3.0",
"version": "3.4.0",
"homepage": "https://woocommerce.com/",
"repository": {
"type": "git",
@ -12,42 +12,42 @@
"scripts": {
"build": "grunt",
"build-watch": "grunt watch",
"test": "cross-env NODE_CONFIG_DIR='./tests/e2e-tests/config' BABEL_ENV=commonjs mocha \"tests/e2e-tests\" --compilers js:babel-register --recursive",
"test:grep": "cross-env NODE_CONFIG_DIR='./tests/e2e-tests/config' BABEL_ENV=commonjs mocha \"tests/e2e-tests\" --compilers js:babel-register --grep ",
"test:single": "cross-env NODE_CONFIG_DIR='./tests/e2e-tests/config' BABEL_ENV=commonjs mocha --compilers js:babel-register"
"test": "cross-env NODE_CONFIG_DIR='./tests/e2e-tests/config' BABEL_ENV=commonjs mocha \"tests/e2e-tests\" --require babel-register --recursive",
"test:grep": "cross-env NODE_CONFIG_DIR='./tests/e2e-tests/config' BABEL_ENV=commonjs mocha \"tests/e2e-tests\" --require babel-register --grep ",
"test:single": "cross-env NODE_CONFIG_DIR='./tests/e2e-tests/config' BABEL_ENV=commonjs mocha --require babel-register"
},
"devDependencies": {
"autoprefixer": "~7.1.6",
"autoprefixer": "~8.6.2",
"babel": "^6.5.2",
"babel-cli": "^6.14.0",
"babel-eslint": "^7.0.0",
"babel-eslint": "^8.2.3",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-preset-es2015": "^6.14.0",
"babel-preset-stage-2": "^6.13.0",
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chromedriver": "^2.37.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chromedriver": "^2.40.0",
"config": "^1.24.0",
"cross-env": "~5.1.1",
"grunt": "~1.0.1",
"cross-env": "^5.1.6",
"grunt": "^1.0.3",
"grunt-checktextdomain": "~1.0.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-cssmin": "~2.2.1",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "~3.1.0",
"grunt-contrib-watch": "~1.0.0",
"grunt-contrib-uglify": "~3.3.0",
"grunt-contrib-watch": "^1.1.0",
"grunt-phpcs": "~0.4.0",
"grunt-postcss": "~0.9.0",
"grunt-prompt": "^1.3.3",
"grunt-rtlcss": "~2.0.1",
"grunt-sass": "~2.0.0",
"grunt-sass": "~2.1.0",
"grunt-shell": "~2.1.0",
"grunt-stylelint": "~0.9.0",
"grunt-stylelint": "~0.10.0",
"grunt-wp-i18n": "~1.0.1",
"istanbul": "^1.0.0-alpha",
"mocha": "^3.0.2",
"stylelint": "~8.2.0",
"mocha": "^5.2.0",
"stylelint": "~9.2.1",
"wc-e2e-page-objects": "0.10.0"
},
"engines": {

View File

@ -27,8 +27,12 @@ if [ $1 == 'after' ]; then
fi
if [[ ${RUN_E2E} == 1 && $(ls -A $TRAVIS_BUILD_DIR/screenshots) ]]; then
curl -sL https://raw.githubusercontent.com/travis-ci/artifacts/master/install | bash
artifacts upload
if [[ -z "${ARTIFACTS_KEY}" ]]; then
echo "Screenshots were not uploaded. Please run the e2e tests locally to see failures."
else
curl -sL https://raw.githubusercontent.com/travis-ci/artifacts/master/install | bash
artifacts upload
fi
fi
fi

File diff suppressed because one or more lines are too long

View File

@ -7,12 +7,17 @@
*/
class WC_Helper_Coupon {
protected static $custom_types = array();
/**
* Create a dummy coupon.
*
* @param string $coupon_code
* @param array $meta
*
* @return WC_Coupon
*/
public static function create_coupon( $coupon_code = 'dummycoupon' ) {
public static function create_coupon( $coupon_code = 'dummycoupon', $meta = array() ) {
// Insert post
$coupon_id = wp_insert_post( array(
'post_title' => $coupon_code,
@ -21,24 +26,30 @@ class WC_Helper_Coupon {
'post_excerpt' => 'This is a dummy coupon',
) );
// Update meta
update_post_meta( $coupon_id, 'discount_type', 'fixed_cart' );
update_post_meta( $coupon_id, 'coupon_amount', '1' );
update_post_meta( $coupon_id, 'individual_use', 'no' );
update_post_meta( $coupon_id, 'product_ids', '' );
update_post_meta( $coupon_id, 'exclude_product_ids', '' );
update_post_meta( $coupon_id, 'usage_limit', '' );
update_post_meta( $coupon_id, 'usage_limit_per_user', '' );
update_post_meta( $coupon_id, 'limit_usage_to_x_items', '' );
update_post_meta( $coupon_id, 'expiry_date', '' );
update_post_meta( $coupon_id, 'free_shipping', 'no' );
update_post_meta( $coupon_id, 'exclude_sale_items', 'no' );
update_post_meta( $coupon_id, 'product_categories', array() );
update_post_meta( $coupon_id, 'exclude_product_categories', array() );
update_post_meta( $coupon_id, 'minimum_amount', '' );
update_post_meta( $coupon_id, 'maximum_amount', '' );
update_post_meta( $coupon_id, 'customer_email', array() );
update_post_meta( $coupon_id, 'usage_count', '0' );
$meta = wp_parse_args( $meta, array(
'discount_type' => 'fixed_cart',
'coupon_amount' => '1',
'individual_use' => 'no',
'product_ids' => '',
'exclude_product_ids' => '',
'usage_limit' => '',
'usage_limit_per_user' => '',
'limit_usage_to_x_items' => '',
'expiry_date' => '',
'free_shipping' => 'no',
'exclude_sale_items' => 'no',
'product_categories' => array(),
'exclude_product_categories' => array(),
'minimum_amount' => '',
'maximum_amount' => '',
'customer_email' => array(),
'usage_count' => '0',
) );
// Update meta.
foreach ( $meta as $key => $value ) {
update_post_meta( $coupon_id, $key, $value );
}
return new WC_Coupon( $coupon_code );
}
@ -52,6 +63,71 @@ class WC_Helper_Coupon {
*/
public static function delete_coupon( $coupon_id ) {
wp_delete_post( $coupon_id, true );
return true;
}
/**
* Register a custom coupon type.
*
* @param string $coupon_type
*/
public static function register_custom_type( $coupon_type ) {
static $filters_added = false;
if ( isset( self::$custom_types[ $coupon_type ] ) ) {
return;
}
self::$custom_types[ $coupon_type ] = "Testing custom type {$coupon_type}";
if ( ! $filters_added ) {
add_filter( 'woocommerce_coupon_discount_types', array( __CLASS__, 'filter_discount_types' ) );
add_filter( 'woocommerce_coupon_get_discount_amount', array( __CLASS__, 'filter_get_discount_amount' ), 10, 5 );
add_filter( 'woocommerce_coupon_is_valid_for_product', '__return_true' );
$filters_added = true;
}
}
/**
* Unregister custom coupon type.
*
* @param $coupon_type
*/
public static function unregister_custom_type( $coupon_type ) {
unset( self::$custom_types[ $coupon_type ] );
if ( empty( self::$custom_types ) ) {
remove_filter( 'woocommerce_coupon_discount_types', array( __CLASS__, 'filter_discount_types' ) );
remove_filter( 'woocommerce_coupon_get_discount_amount', array( __CLASS__, 'filter_get_discount_amount' ) );
remove_filter( 'woocommerce_coupon_is_valid_for_product', '__return_true' );
}
}
/**
* Register custom discount types.
*
* @param array $discount_types
* @return array
*/
public static function filter_discount_types( $discount_types ) {
return array_merge( $discount_types, self::$custom_types );
}
/**
* Get custom discount type amount. Works like 'percent' type.
*
* @param float $discount
* @param float $discounting_amount
* @param array|null $item
* @param bool $single
* @param WC_Coupon $coupon
*
* @return float
*/
public static function filter_get_discount_amount( $discount, $discounting_amount, $item, $single, $coupon ) {
if ( ! isset( self::$custom_types [ $coupon->get_discount_type() ] ) ) {
return $discount;
}
return (float) $coupon->get_amount() * ( $discounting_amount / 100 );
}
}

View File

@ -3,6 +3,7 @@
/**
* Class Coupon.
* @package WooCommerce\Tests\Coupon
* @group coupons
*/
class WC_Tests_Coupon extends WC_Unit_Test_Case {
@ -315,4 +316,79 @@ class WC_Tests_Coupon extends WC_Unit_Test_Case {
$this->assertFalse( $expired_coupon->is_valid() );
$this->assertEquals( $expired_coupon->get_error_message(), $expired_coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_EXPIRED ) );
}
/**
* Test an item limit for percent discounts.
*/
public function test_percent_discount_item_limit() {
// Create product
$product = WC_Helper_Product::create_simple_product();
update_post_meta( $product->get_id(), '_price', '10' );
update_post_meta( $product->get_id(), '_regular_price', '10' );
// Create coupon
$coupon = WC_Helper_Coupon::create_coupon( 'dummycoupon', array(
'discount_type' => 'percent',
'coupon_amount' => '5',
'limit_usage_to_x_items' => 1,
) );
// We need this to have the calculate_totals() method calculate totals.
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
define( 'WOOCOMMERCE_CHECKOUT', true );
}
// Add 2 products and coupon to cart.
WC()->cart->add_to_cart( $product->get_id(), 2 );
WC()->cart->add_discount( $coupon->get_code() );
WC()->cart->calculate_totals();
// Test if the cart total amount is equal 19.5 (coupon only applying to one item).
$this->assertEquals( 19.5, WC()->cart->total );
// Clean up
wc_clear_notices();
WC()->cart->empty_cart();
WC()->cart->remove_coupons();
WC_Helper_Coupon::delete_coupon( $coupon->get_id() );
WC_Helper_Product::delete_product( $product->get_id() );
}
public function test_custom_discount_item_limit() {
// Register custom discount type.
WC_Helper_Coupon::register_custom_type( __FUNCTION__ );
// Create product
$product = WC_Helper_Product::create_simple_product();
update_post_meta( $product->get_id(), '_price', '10' );
update_post_meta( $product->get_id(), '_regular_price', '10' );
// Create coupon
$coupon = WC_Helper_Coupon::create_coupon( 'dummycoupon', array(
'discount_type' => __FUNCTION__,
'coupon_amount' => '5',
'limit_usage_to_x_items' => 1,
) );
// We need this to have the calculate_totals() method calculate totals.
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
define( 'WOOCOMMERCE_CHECKOUT', true );
}
// Add 4 products and coupon to cart.
WC()->cart->add_to_cart( $product->get_id(), 4 );
WC()->cart->add_discount( $coupon->get_code() );
WC()->cart->calculate_totals();
// Test if the cart total amount is equal 39.5 (coupon only applying to one item).
$this->assertEquals( 39.5, WC()->cart->total );
// Clean up
wc_clear_notices();
WC()->cart->empty_cart();
WC()->cart->remove_coupons();
WC_Helper_Coupon::delete_coupon( $coupon->get_id() );
WC_Helper_Product::delete_product( $product->get_id() );
WC_Helper_Coupon::unregister_custom_type( __FUNCTION__ );
}
}

View File

@ -31,7 +31,7 @@ class WC_Tests_WC_Product_Query extends WC_Unit_Test_Case {
$product1->set_sale_price( '5.00' );
$product1->save();
$product2 = new WC_Product_Grouped();
$product2 = new WC_Product_Simple();
$product2->set_sku( 'sku2' );
$product2->set_regular_price( '12.50' );
$product2->set_sale_price( '5.00' );