Merge remote-tracking branch 'upstream/master'

This commit is contained in:
adnanmultidots 2017-09-22 12:22:12 +05:30
commit 57b6a25bfe
148 changed files with 7405 additions and 3748 deletions

View File

@ -13,7 +13,7 @@
<!-- Mark completed items with an [x] -->
- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate
- [ ] The issue still exists against the latest `master` branch of WooCommerce on Github
- [ ] The issue still exists against the latest `master` branch of WooCommerce on Github (this is **not** the same version as on WordPress.org!)
- [ ] I have attempted to find the simplest possible steps to reproduce the issue
- [ ] I have included a failing test as a pull request (Optional)

View File

@ -15,10 +15,10 @@ build:
tests:
override:
-
command: "vendor/bin/phpunit -c phpunit.xml --coverage-clover=results"
command: "vendor/bin/phpunit -c phpunit.xml --coverage-clover=coverage.clover"
coverage:
file: results
format: clover
file: coverage.clover
format: php-clover
tools:
php_code_sniffer:
config:

View File

@ -2,7 +2,7 @@ language: php
sudo: false
# Test main supported versions of PHP and HHVM against latest WP.
# Test main supported versions of PHP against latest WP.
php:
- 5.6
- 7.0
@ -16,6 +16,7 @@ matrix:
include:
- php: 5.6
env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1
dist: precise
- php: 5.3
env: WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1
dist: precise
@ -29,6 +30,7 @@ before_script:
- bash tests/bin/travis.sh before
script:
- phpunit --version
- phpunit -c phpunit.xml
after_script:

View File

@ -13,7 +13,7 @@ If you are not a developer, please use the [WooCommerce plugin page](https://wor
To disclose a security issue to our team, [please submit a report via HackerOne here](https://hackerone.com/automattic/).
## Support
This repository is not suitable for support. Please don't use our issue tracker for support requests, but for core, WooCommerce issues only. Support can take place through the appropriate channels:
This repository is not suitable for support. Please don't use our issue tracker for support requests, but for core WooCommerce issues only. Support can take place through the appropriate channels:
* The [WooCommerce premium support portal](https://woocommerce.com/my-account/create-a-ticket/) for customers who have purchased themes or extensions.
* [Our community forum on wp.org](https://wordpress.org/support/plugin/woocommerce) which is available for all WooCommerce users.
@ -24,4 +24,4 @@ Support requests in issues on this repository will be closed on sight.
If you have a patch or have stumbled upon an issue with WooCommerce core, you can contribute this back to the code. Please read our [contributor guidelines](https://github.com/woocommerce/woocommerce/blob/master/.github/CONTRIBUTING.md) for more information how you can do this.
## Contributing new features to the WooCommerce REST API
If you're like to add a feature to the next version of the REST API, contribute here: https://github.com/woocommerce/wc-api-dev
If you'd like to add a feature to the next version of the REST API, contribute here: https://github.com/woocommerce/wc-api-dev

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,6 +21,19 @@
.wc_addons_wrap {
.update-plugins .update-count {
background-color: #d54e21;
border-radius: 10px;
color: #fff;
display: inline-block;
font-size: 9px;
font-weight: 600;
line-height: 17px;
margin: 1px 0 0 2px;
padding: 0 6px;
vertical-align: text-top;
}
.addons-featured {
max-width: 1140px;
margin: -1%;
@ -566,7 +579,6 @@ table.wc_status_table {
}
td, th {
padding: 2em;
font-size: 1.1em;
font-weight: normal;
@ -605,6 +617,12 @@ table.wc_status_table {
}
}
table.wc_status_table--tools {
td, th {
padding: 2em;
}
}
#debug-report {
display: none;
margin: 10px 0;
@ -3099,7 +3117,6 @@ img.help_tip {
input[type="text"],
input[type="number"],
input[type="email"] {
padding: 6px;
height: auto;
}
@ -3122,6 +3139,18 @@ img.help_tip {
box-sizing: border-box;
}
// Ignore nested inputs.
table {
select,
textarea,
input[type="text"],
input[type="email"],
input[type="number"],
input.regular-input {
width: auto;
}
}
textarea.wide-input {
width: 100%;
}
@ -3223,6 +3252,10 @@ img.help_tip {
label {
margin-left: 10px;
}
input {
width: auto;
}
}
.wc_emails_wrapper {
@ -4333,7 +4366,7 @@ img.help_tip {
width: 100%;
vertical-align: middle;
margin: 2px 0 0;
padding: 6px;
padding: 5px;
}
select {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -25,11 +25,11 @@ $color_button_secondary: $woo_pink2;
/*------------------------------------------------------------------------------
Tab navigation
------------------------------------------------------------------------------*/
.wc-helper {
.nav-tab-wrapper {
margin-bottom: 22px;
}
@media only screen and (max-width : 784px) {
.nav-tab {
max-width: 40%;
@ -88,24 +88,17 @@ $color_button_secondary: $woo_pink2;
line-height: 1;
padding: 0;
position: absolute;
top: 12px;
right: 12px;
top: 10px;
right: 14px;
}
}
span {
font-weight: bold;
padding-right: 4px;
}
a {
li {
color: #0073AA;
display: inline-block;
padding: 0 4px 0 8px;
position: relative;
text-decoration: none;
&:before {
&::before {
background-color: #979797;
content: " ";
position: absolute;
@ -114,21 +107,48 @@ $color_button_secondary: $woo_pink2;
bottom: 0;
width: 1px;
}
&:first-of-type {
&::before {
display: none;
}
}
}
a{
color: #0073AA;
text-decoration: none;
&.current{
color: #000;
font-weight: 600;
}
}
.count{
color: #555d66;
font-weight: 400;
}
@media only screen and (max-width : 600px) {
background-color: #fff;
border: 1px solid #E1E1E1;
border-radius: 2px;
border-radius: 4px;
font-size: 14px;
label,
span,
a {
border-bottom: 1px solid #E1E1E1;
line-height: 2;
li {
line-height: 21px;
padding: 8px 16px;
margin: 0;
&:last-child {
border-bottom: none;
}
}
li {
border-bottom: 1px solid #E1E1E1;
}
label,
@ -136,27 +156,39 @@ $color_button_secondary: $woo_pink2;
display: block;
}
span,
a {
label {
text-decoration: none;
}
li {
display: none;
}
a {
cursor: pointer;
li {
&::before {
display: none;
}
}
a {
cursor: pointer;
}
span.chevron {
transform: rotateX(0deg);
color: #555555;
opacity: 0.5;
transform: rotateX(180deg);
}
&:focus,
&:hover {
span,
a {
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2);
label {
border-bottom: 1px solid #E1E1E1;
}
li {
display: block;
}
@ -447,7 +479,7 @@ $color_button_secondary: $woo_pink2;
.wp-list-table__ext-title {
color: $color_text_blue;
font-size: 18px;
font-weight: 784;
font-weight: 600;
width: 60%;
@media only screen and (max-width : 782px) {
@ -472,7 +504,7 @@ $color_button_secondary: $woo_pink2;
.wp-list-table__ext-status {
position: relative;
&.update-available:after {
&.update-available::after {
background-color: #FFC322;
content: " ";
position: absolute;
@ -482,7 +514,7 @@ $color_button_secondary: $woo_pink2;
width: 5px;
}
&.expired:after {
&.expired::after {
background-color: #B81C23;
content: " ";
position: absolute;
@ -532,7 +564,7 @@ $color_button_secondary: $woo_pink2;
td {
position: relative;
&:before {
&::before {
background-color: #E1E1E1;
content: " ";
height: 1px;
@ -545,13 +577,13 @@ $color_button_secondary: $woo_pink2;
td.wp-list-table__ext-status,
td.wp-list-table__licence-container {
&:before {
&::before {
left: 22px !important;
width: auto !important;
}
}
td.wp-list-table__ext-actions:before {
td.wp-list-table__ext-actions::before {
right: 22px;
}
@ -896,8 +928,8 @@ $color_button_secondary: $woo_pink2;
transition: all .4s ease, box-shadow 0s;
vertical-align: middle;
&:before,
&:after {
&::before,
&::after {
content: "";
display: block;
position: relative;
@ -905,14 +937,14 @@ $color_button_secondary: $woo_pink2;
height: 20px;
}
&:after {
&::after {
border-radius: 50%;
background: $white;
left: 0;
transition: all .2s ease;
}
&:before {
&::before {
display: none;
}
@ -967,7 +999,7 @@ $color_button_secondary: $woo_pink2;
+ .form-toggle__label .form-toggle__switch {
background: $woo_pink1;
&:after {
&::after {
left: 8px;
}
}
@ -1002,15 +1034,15 @@ $color_button_secondary: $woo_pink2;
width: 24px;
height: 16px;
&:before,
&:after {
&::before,
&::after {
height: 12px;
width: 12px;
}
}
&:checked {
+ .form-toggle__label .form-toggle__switch {
&:after {
&::after {
left: 8px;
}
}

View File

@ -467,24 +467,32 @@ jQuery( function ( $ ) {
},
add_fee: function() {
wc_meta_boxes_order_items.block();
var value = window.prompt( woocommerce_admin_meta_boxes.i18n_add_fee );
var data = {
action : 'woocommerce_add_order_fee',
order_id : woocommerce_admin_meta_boxes.post_id,
dataType : 'json',
security : woocommerce_admin_meta_boxes.order_item_nonce
};
if ( value != null ) {
wc_meta_boxes_order_items.block();
$.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) {
if ( response.success ) {
$( 'table.woocommerce_order_items tbody#order_fee_line_items' ).append( response.data.html );
} else {
window.alert( response.data.error );
}
wc_meta_boxes_order_items.unblock();
});
var data = {
action : 'woocommerce_add_order_fee',
dataType: 'json',
order_id: woocommerce_admin_meta_boxes.post_id,
security: woocommerce_admin_meta_boxes.order_item_nonce,
amount : value
};
$.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) {
if ( response.success ) {
$( '#woocommerce-order-items' ).find( '.inside' ).empty();
$( '#woocommerce-order-items' ).find( '.inside' ).append( response.data.html );
wc_meta_boxes_order.init_tiptip();
wc_meta_boxes_order_items.unblock();
wc_meta_boxes_order_items.stupidtable.init();
} else {
window.alert( response.data.error );
}
wc_meta_boxes_order_items.unblock();
});
}
return false;
},
@ -1095,17 +1103,19 @@ jQuery( function ( $ ) {
item_to_add: add_item_ids,
dataType : 'json',
order_id : woocommerce_admin_meta_boxes.post_id,
security : woocommerce_admin_meta_boxes.order_item_nonce
security : woocommerce_admin_meta_boxes.order_item_nonce,
data : $( '#wc-backbone-modal-dialog form' ).serialize()
};
$.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) {
if ( response.success ) {
$( 'table.woocommerce_order_items tbody#order_line_items' ).append( response.data.html );
$( '#woocommerce-order-items' ).find( '.inside' ).empty();
$( '#woocommerce-order-items' ).find( '.inside' ).append( response.data.html );
wc_meta_boxes_order.init_tiptip();
wc_meta_boxes_order_items.stupidtable.init();
} else {
window.alert( response.data.error );
}
wc_meta_boxes_order.init_tiptip();
wc_meta_boxes_order_items.unblock();
});
}

File diff suppressed because one or more lines are too long

View File

@ -721,6 +721,8 @@ jQuery( function( $ ) {
} else {
data.value = accounting.unformat( value, woocommerce_admin.mon_decimal_point );
}
} else {
return;
}
break;
case 'variable_regular_price' :
@ -736,6 +738,8 @@ jQuery( function( $ ) {
if ( value != null ) {
data.value = value;
} else {
return;
}
break;
case 'variable_sale_schedule' :
@ -749,6 +753,10 @@ jQuery( function( $ ) {
if ( null === data.date_to ) {
data.date_to = false;
}
if ( false === data.date_to && false === data.date_from ) {
return;
}
break;
default :
$( 'select.variation_actions' ).trigger( do_variation_action );

File diff suppressed because one or more lines are too long

View File

@ -136,16 +136,16 @@ jQuery( function( $ ) {
$( this ).on( 'change', function(){
var $children = $( this ).children();
$children.sort(function(a, b){
var atext = a.text.toLowerCase();
var btext = b.text.toLowerCase();
var atext = a.text.toLowerCase();
var btext = b.text.toLowerCase();
if (atext > btext) {
return 1;
}
if (atext < btext) {
return -1;
}
return 0;
if ( atext > btext ) {
return 1;
}
if ( atext < btext ) {
return -1;
}
return 0;
});
$( this ).html( $children );
});
@ -164,7 +164,7 @@ jQuery( function( $ ) {
ajax: {
url: wc_enhanced_select_params.ajax_url,
dataType: 'json',
delay: 250,
delay: 1000,
data: function( params ) {
return {
term: params.term,

View File

@ -1,4 +1,4 @@
/*global wc_add_to_cart_variation_params, wc_cart_fragments_params */
/*global wc_add_to_cart_variation_params */
;(function ( $, window, document, undefined ) {
/**
* VariationForm class which handles variation forms and attributes.
@ -37,9 +37,11 @@
$form.on( 'check_variations.wc-variation-form', { variationForm: this }, this.onFindVariation );
$form.on( 'update_variation_values.wc-variation-form', { variationForm: this }, this.onUpdateAttributes );
// Check variations once init.
$form.trigger( 'check_variations' );
$form.trigger( 'wc_variation_form' );
// Init after gallery.
setTimeout( function() {
$form.trigger( 'check_variations' );
$form.trigger( 'wc_variation_form' );
}, 100 );
};
/**
@ -135,7 +137,7 @@
currentAttributes.product_id = parseInt( form.$form.data( 'product_id' ), 10 );
currentAttributes.custom_data = form.$form.data( 'custom_data' );
form.xhr = $.ajax( {
url: wc_cart_fragments_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_variation' ),
url: wc_add_to_cart_variation_params.wc_ajax_url.toString().replace( '%%endpoint%%', 'get_variation' ),
type: 'POST',
data: currentAttributes,
success: function( variation ) {
@ -553,8 +555,7 @@
if ( variation && variation.image && variation.image.src && variation.image.src.length > 1 ) {
if ( $( '.flex-control-nav li img[src="' + variation.image.thumb_src + '"]' ).length > 0 ) {
$gallery_img = $( '.flex-control-nav li img[src="' + variation.image.thumb_src + '"]' );
$gallery_img.trigger( 'click' );
$( '.flex-control-nav li img[src="' + variation.image.thumb_src + '"]' ).trigger( 'click' );
$form.attr( 'current-image', variation.image_id );
return;
} else {

File diff suppressed because one or more lines are too long

View File

@ -56,6 +56,12 @@ jQuery( function( $ ) {
if ( thislocale[ key ].placeholder ) {
field.find( 'input' ).attr( 'placeholder', thislocale[ key ].placeholder );
field.find( '.select2-selection__placeholder' ).text( thislocale[ key ].placeholder );
// Use the i18n label as a placeholder if there is no label element or i18n placeholder.
} else if ( thislocale[ key ].label && ! field.find( 'label' ).length ) {
field.find( 'input' ).attr( 'placeholder', thislocale[ key ].label );
field.find( '.select2-selection__placeholder' ).text( thislocale[ key ].label );
}
field_is_required( field, false );

View File

@ -1 +1 @@
jQuery(function(e){function a(e,a){a?(e.find("label").append(' <abbr class="required" title="'+wc_address_i18n_params.i18n_required_text+'">*</abbr>'),e.addClass("validate-required")):(e.find("label abbr").remove(),e.removeClass("validate-required"))}if("undefined"==typeof wc_address_i18n_params)return!1;var i=wc_address_i18n_params.locale.replace(/&quot;/g,'"'),t=e.parseJSON(i);e(document.body).bind("country_to_state_changing",function(i,d,r){var l,o=r;l="undefined"!=typeof t[d]?t[d]:t["default"];var n=o.find("#billing_postcode_field, #shipping_postcode_field"),f=o.find("#billing_city_field, #shipping_city_field"),s=o.find("#billing_state_field, #shipping_state_field");n.attr("data-o_class")||(n.attr("data-o_class",n.attr("class")),f.attr("data-o_class",f.attr("class")),s.attr("data-o_class",s.attr("class")));var p=e.parseJSON(wc_address_i18n_params.locale_fields);e.each(p,function(e,i){var d=o.find(i);l[e]?(l[e].label&&d.find("label").html(l[e].label),l[e].placeholder&&d.find("input").attr("placeholder",l[e].placeholder),a(d,!1),"undefined"==typeof l[e].required&&!0===t["default"][e].required?a(d,!0):!0===l[e].required&&a(d,!0),"state"!==e&&(!0===l[e].hidden?d.hide().find("input").val(""):d.show()),l[e].priority?d.data("priority",l[e].priority):t["default"][e].priority&&d.data("priority",t["default"][e].priority)):t["default"][e]&&("state"!==e&&("undefined"==typeof t["default"][e].hidden||!1===t["default"][e].hidden?d.show():!0===t["default"][e].hidden&&d.hide().find("input").val("")),"postcode"!==e&&"city"!==e&&"state"!==e||(t["default"][e].label&&d.find("label").html(t["default"][e].label),t["default"][e].placeholder&&d.find("input").attr("placeholder",t["default"][e].placeholder)),!0===t["default"][e].required&&0===d.find("label abbr").length&&a(d,!0),t["default"][e].priority&&d.data("priority",t["default"][e].priority))}),e(".woocommerce-billing-fields__field-wrapper, .woocommerce-shipping-fields__field-wrapper, .woocommerce-address-fields__field-wrapper, .woocommerce-additional-fields__field-wrapper .woocommerce-account-fields").each(function(a,i){var t=e(i).find(".form-row"),d=t.first().parent(),r=0;t.each(function(){e(this).data("priority")||e(this).data("priority",r+1),r=e(this).data("priority")}),t.sort(function(a,i){var t=e(a).data("priority"),d=e(i).data("priority");return t>d?1:t<d?-1:0}),t.detach().appendTo(d)})})});
jQuery(function(e){function a(e,a){a?(e.find("label").append(' <abbr class="required" title="'+wc_address_i18n_params.i18n_required_text+'">*</abbr>'),e.addClass("validate-required")):(e.find("label abbr").remove(),e.removeClass("validate-required"))}if("undefined"==typeof wc_address_i18n_params)return!1;var i=wc_address_i18n_params.locale.replace(/&quot;/g,'"'),t=e.parseJSON(i);e(document.body).bind("country_to_state_changing",function(i,d,l){var r,o=l;r="undefined"!=typeof t[d]?t[d]:t["default"];var n=o.find("#billing_postcode_field, #shipping_postcode_field"),f=o.find("#billing_city_field, #shipping_city_field"),s=o.find("#billing_state_field, #shipping_state_field");n.attr("data-o_class")||(n.attr("data-o_class",n.attr("class")),f.attr("data-o_class",f.attr("class")),s.attr("data-o_class",s.attr("class")));var p=e.parseJSON(wc_address_i18n_params.locale_fields);e.each(p,function(e,i){var d=o.find(i);r[e]?(r[e].label&&d.find("label").html(r[e].label),r[e].placeholder?(d.find("input").attr("placeholder",r[e].placeholder),d.find(".select2-selection__placeholder").text(r[e].placeholder)):r[e].label&&!d.find("label").length&&(d.find("input").attr("placeholder",r[e].label),d.find(".select2-selection__placeholder").text(r[e].label)),a(d,!1),"undefined"==typeof r[e].required&&!0===t["default"][e].required?a(d,!0):!0===r[e].required&&a(d,!0),"state"!==e&&(!0===r[e].hidden?d.hide().find("input").val(""):d.show()),r[e].priority?d.data("priority",r[e].priority):t["default"][e].priority&&d.data("priority",t["default"][e].priority)):t["default"][e]&&("state"!==e&&("undefined"==typeof t["default"][e].hidden||!1===t["default"][e].hidden?d.show():!0===t["default"][e].hidden&&d.hide().find("input").val("")),"postcode"!==e&&"city"!==e&&"state"!==e||(t["default"][e].label&&d.find("label").html(t["default"][e].label),t["default"][e].placeholder&&d.find("input").attr("placeholder",t["default"][e].placeholder)),!0===t["default"][e].required&&0===d.find("label abbr").length&&a(d,!0),t["default"][e].priority&&d.data("priority",t["default"][e].priority))}),e(".woocommerce-billing-fields__field-wrapper, .woocommerce-shipping-fields__field-wrapper, .woocommerce-address-fields__field-wrapper, .woocommerce-additional-fields__field-wrapper .woocommerce-account-fields").each(function(a,i){var t=e(i).find(".form-row"),d=t.first().parent(),l=0;t.each(function(){e(this).data("priority")||e(this).data("priority",l+1),l=e(this).data("priority")}),t.sort(function(a,i){var t=e(a).data("priority"),d=e(i).data("priority");return t>d?1:t<d?-1:0}),t.detach().appendTo(d)})})});

View File

@ -41,7 +41,7 @@ jQuery( function( $ ) {
this.$checkout_form.on( 'change', 'select.shipping_method, input[name^="shipping_method"], #ship-to-different-address input, .update_totals_on_change select, .update_totals_on_change input[type="radio"], .update_totals_on_change input[type="checkbox"]', this.trigger_update_checkout );
this.$checkout_form.on( 'change', '.address-field select', this.input_changed );
this.$checkout_form.on( 'change', '.address-field input.input-text, .update_totals_on_change input.input-text', this.maybe_input_changed );
this.$checkout_form.on( 'change keydown', '.address-field input.input-text, .update_totals_on_change input.input-text', this.queue_update_checkout );
this.$checkout_form.on( 'keydown', '.address-field input.input-text, .update_totals_on_change input.input-text', this.queue_update_checkout );
// Address fields
this.$checkout_form.on( 'change', '#ship-to-different-address input', this.ship_to_different_address );
@ -247,7 +247,17 @@ jQuery( function( $ ) {
s_postcode = postcode,
s_city = city,
s_address = address,
s_address_2 = address_2;
s_address_2 = address_2,
$required_inputs = $( wc_checkout_form.$checkout_form ).find( '.address-field.validate-required:visible' ),
has_full_address = true;
if ( $required_inputs.length ) {
$required_inputs.each( function() {
if ( $( this ).find( ':input' ).val() === '' ) {
has_full_address = false;
}
});
}
if ( $( '#ship-to-different-address' ).find( 'input' ).is( ':checked' ) ) {
s_country = $( '#shipping_country' ).val();
@ -259,21 +269,22 @@ jQuery( function( $ ) {
}
var data = {
security: wc_checkout_params.update_order_review_nonce,
payment_method: wc_checkout_form.get_payment_method(),
country: country,
state: state,
postcode: postcode,
city: city,
address: address,
address_2: address_2,
s_country: s_country,
s_state: s_state,
s_postcode: s_postcode,
s_city: s_city,
s_address: s_address,
s_address_2: s_address_2,
post_data: $( 'form.checkout' ).serialize()
security : wc_checkout_params.update_order_review_nonce,
payment_method : wc_checkout_form.get_payment_method(),
country : country,
state : state,
postcode : postcode,
city : city,
address : address,
address_2 : address_2,
s_country : s_country,
s_state : s_state,
s_postcode : s_postcode,
s_city : s_city,
s_address : s_address,
s_address_2 : s_address_2,
has_full_address: has_full_address,
post_data : $( 'form.checkout' ).serialize()
};
if ( false !== args.update_shipping_method ) {

File diff suppressed because one or more lines are too long

View File

@ -6,23 +6,6 @@ jQuery( function( $ ) {
return false;
}
// Get markup ready for slider
$( 'input#min_price, input#max_price' ).hide();
$( '.price_slider, .price_label' ).show();
// Price slider uses jquery ui
var min_price = $( '.price_slider_amount #min_price' ).data( 'min' ),
max_price = $( '.price_slider_amount #max_price' ).data( 'max' ),
current_min_price = parseInt( min_price, 10 ),
current_max_price = parseInt( max_price, 10 );
if ( woocommerce_price_slider_params.min_price ) {
current_min_price = parseInt( woocommerce_price_slider_params.min_price, 10 );
}
if ( woocommerce_price_slider_params.max_price ) {
current_max_price = parseInt( woocommerce_price_slider_params.max_price, 10 );
}
$( document.body ).bind( 'price_slider_create price_slider_slide', function( event, min, max ) {
$( '.price_slider_amount span.from' ).html( accounting.formatMoney( min, {
@ -44,30 +27,54 @@ jQuery( function( $ ) {
$( document.body ).trigger( 'price_slider_updated', [ min, max ] );
});
$( '.price_slider' ).slider({
range: true,
animate: true,
min: min_price,
max: max_price,
values: [ current_min_price, current_max_price ],
create: function() {
function init_price_filter() {
$( 'input#min_price, input#max_price' ).hide();
$( '.price_slider, .price_label' ).show();
$( '.price_slider_amount #min_price' ).val( current_min_price );
$( '.price_slider_amount #max_price' ).val( current_max_price );
var min_price = $( '.price_slider_amount #min_price' ).data( 'min' ),
max_price = $( '.price_slider_amount #max_price' ).data( 'max' ),
current_min_price = $( '.price_slider_amount #min_price' ).val(),
current_max_price = $( '.price_slider_amount #max_price' ).val();
$( document.body ).trigger( 'price_slider_create', [ current_min_price, current_max_price ] );
},
slide: function( event, ui ) {
$( '.price_slider:not(.ui-slider)' ).slider({
range: true,
animate: true,
min: min_price,
max: max_price,
values: [ current_min_price, current_max_price ],
create: function() {
$( 'input#min_price' ).val( ui.values[0] );
$( 'input#max_price' ).val( ui.values[1] );
$( '.price_slider_amount #min_price' ).val( current_min_price );
$( '.price_slider_amount #max_price' ).val( current_max_price );
$( document.body ).trigger( 'price_slider_slide', [ ui.values[0], ui.values[1] ] );
},
change: function( event, ui ) {
$( document.body ).trigger( 'price_slider_create', [ current_min_price, current_max_price ] );
},
slide: function( event, ui ) {
$( document.body ).trigger( 'price_slider_change', [ ui.values[0], ui.values[1] ] );
}
});
$( 'input#min_price' ).val( ui.values[0] );
$( 'input#max_price' ).val( ui.values[1] );
$( document.body ).trigger( 'price_slider_slide', [ ui.values[0], ui.values[1] ] );
},
change: function( event, ui ) {
$( document.body ).trigger( 'price_slider_change', [ ui.values[0], ui.values[1] ] );
}
});
}
init_price_filter();
hasSelectiveRefresh = (
'undefined' !== typeof wp &&
wp.customize &&
wp.customize.selectiveRefresh &&
wp.customize.widgetsPreview &&
wp.customize.widgetsPreview.WidgetPartial
);
if ( hasSelectiveRefresh ) {
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function() {
init_price_filter();
} );
}
});

View File

@ -772,6 +772,14 @@ S2.define('select2/utils',[
$element.append($nodes);
};
// Determine whether the browser is on a touchscreen device.
Utils.isTouchscreen = function() {
if ('undefined' === typeof Utils._isTouchscreenCache) {
Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement;
}
return Utils._isTouchscreenCache;
}
return Utils;
});
@ -791,7 +799,7 @@ S2.define('select2/results',[
Results.prototype.render = function () {
var $results = $(
'<ul class="select2-results__options" role="listbox"></ul>'
'<ul class="select2-results__options" role="listbox" tabindex="-1"></ul>'
);
if (this.options.get('multiple')) {
@ -949,7 +957,8 @@ S2.define('select2/results',[
var attrs = {
'role': 'option',
'data-selected': 'false'
'data-selected': 'false',
'tabindex': -1
};
if (data.disabled) {
@ -988,6 +997,7 @@ S2.define('select2/results',[
var $label = $(label);
this.template(data, label);
$label.attr('role', 'presentation');
var $children = [];
@ -1000,10 +1010,11 @@ S2.define('select2/results',[
}
var $childrenContainer = $('<ul></ul>', {
'class': 'select2-results__options select2-results__options--nested'
'class': 'select2-results__options select2-results__options--nested',
'role': 'listbox'
});
$childrenContainer.append($children);
$option.attr('role', 'list');
$option.append(label);
$option.append($childrenContainer);
@ -1695,7 +1706,7 @@ S2.define('select2/selection/multiple',[
var selection = data[d];
var $selection = this.selectionContainer();
var formatted = this.display(selection, $selection);
var formatted = this.display(selection, $selection).trim();
$selection.append(formatted);
$selection.prop('title', selection.title || selection.text);
@ -5400,9 +5411,16 @@ S2.define('select2/core',[
});
});
this.on('keypress', function (evt) {
var key = evt.which;
this.on('open', function(){
// Focus on the active element when opening dropdown.
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
self.focusOnActiveElement();
},1);
});
$(document).on('keydown', function (evt) {
var key = evt.which;
if (self.isOpen()) {
if (key === KEYS.ESC || key === KEYS.TAB ||
(key === KEYS.UP && evt.altKey)) {
@ -5426,17 +5444,42 @@ S2.define('select2/core',[
evt.preventDefault();
}
} else {
// Move the focus to the selected element on keyboard navigation.
// Required for screen readers to work properly.
if (key === KEYS.DOWN || key === KEYS.UP) {
self.focusOnActiveElement();
} else {
// Focus on the search if user starts typing.
var $searchField = self.$dropdown.find('.select2-search__field');
if (! $searchField.length) {
$searchField = self.$container.find('.select2-search__field');
}
$searchField.focus();
// Focus back to active selection when finished typing.
// Small delay so typed character can be read by screen reader.
setTimeout(function(){
self.focusOnActiveElement();
}, 1000);
}
} else if (self.hasFocus()) {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
(key === KEYS.DOWN && evt.altKey)) {
self.open();
evt.preventDefault();
}
}
});
};
Select2.prototype.focusOnActiveElement = function () {
// Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards.
if (! Utils.isTouchscreen()) {
this.$results.find('li.select2-results__option--highlighted').focus();
}
};
Select2.prototype._syncAttributes = function () {
this.options.set('disabled', this.$element.prop('disabled'));

File diff suppressed because one or more lines are too long

View File

@ -772,6 +772,14 @@ S2.define('select2/utils',[
$element.append($nodes);
};
// Determine whether the browser is on a touchscreen device.
Utils.isTouchscreen = function() {
if ('undefined' === typeof Utils._isTouchscreenCache) {
Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement;
}
return Utils._isTouchscreenCache;
}
return Utils;
});
@ -791,7 +799,7 @@ S2.define('select2/results',[
Results.prototype.render = function () {
var $results = $(
'<ul class="select2-results__options" role="listbox"></ul>'
'<ul class="select2-results__options" role="listbox" tabindex="-1"></ul>'
);
if (this.options.get('multiple')) {
@ -949,7 +957,8 @@ S2.define('select2/results',[
var attrs = {
'role': 'option',
'data-selected': 'false'
'data-selected': 'false',
'tabindex': -1
};
if (data.disabled) {
@ -988,6 +997,7 @@ S2.define('select2/results',[
var $label = $(label);
this.template(data, label);
$label.attr('role', 'presentation');
var $children = [];
@ -1000,10 +1010,11 @@ S2.define('select2/results',[
}
var $childrenContainer = $('<ul></ul>', {
'class': 'select2-results__options select2-results__options--nested'
'class': 'select2-results__options select2-results__options--nested',
'role': 'listbox'
});
$childrenContainer.append($children);
$option.attr('role', 'list');
$option.append(label);
$option.append($childrenContainer);
@ -1695,7 +1706,7 @@ S2.define('select2/selection/multiple',[
var selection = data[d];
var $selection = this.selectionContainer();
var formatted = this.display(selection, $selection);
var formatted = this.display(selection, $selection).trim();
$selection.append(formatted);
$selection.prop('title', selection.title || selection.text);
@ -5400,9 +5411,16 @@ S2.define('select2/core',[
});
});
this.on('keypress', function (evt) {
var key = evt.which;
this.on('open', function(){
// Focus on the active element when opening dropdown.
// Needs 1 ms delay because of other 1 ms setTimeouts when rendering.
setTimeout(function(){
self.focusOnActiveElement();
},1);
});
$(document).on('keydown', function (evt) {
var key = evt.which;
if (self.isOpen()) {
if (key === KEYS.ESC || key === KEYS.TAB ||
(key === KEYS.UP && evt.altKey)) {
@ -5426,17 +5444,42 @@ S2.define('select2/core',[
evt.preventDefault();
}
} else {
// Move the focus to the selected element on keyboard navigation.
// Required for screen readers to work properly.
if (key === KEYS.DOWN || key === KEYS.UP) {
self.focusOnActiveElement();
} else {
// Focus on the search if user starts typing.
var $searchField = self.$dropdown.find('.select2-search__field');
if (! $searchField.length) {
$searchField = self.$container.find('.select2-search__field');
}
$searchField.focus();
// Focus back to active selection when finished typing.
// Small delay so typed character can be read by screen reader.
setTimeout(function(){
self.focusOnActiveElement();
}, 1000);
}
} else if (self.hasFocus()) {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
(key === KEYS.DOWN && evt.altKey)) {
self.open();
evt.preventDefault();
}
}
});
};
Select2.prototype.focusOnActiveElement = function () {
// Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards.
if (! Utils.isTouchscreen()) {
this.$results.find('li.select2-results__option--highlighted').focus();
}
};
Select2.prototype._syncAttributes = function () {
this.options.set('disabled', this.$element.prop('disabled'));

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -277,7 +277,7 @@ abstract class WC_Data {
*/
protected function is_internal_meta_key( $key ) {
if ( $this->data_store && ! empty( $key ) && in_array( $key, $this->data_store->get_internal_meta_keys() ) ) {
wc_doing_it_wrong( __FUNCTION__, __( 'Meta properties should not be accessed directly. Use getters and setters.', 'woocommerce' ), '3.2.0' );
wc_doing_it_wrong( __FUNCTION__, sprintf( __( 'Generic add/update/get meta methods should not be used for internal meta data, including "%s". Use getters and setters.', 'woocommerce' ), $key ), '3.2.0' );
return true;
}

View File

@ -890,17 +890,21 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
* Apply a coupon to the order and recalculate totals.
*
* @since 3.2.0
* @param string|WC_Coupon $coupon Coupon code or object.
* @param string|WC_Coupon $raw_coupon Coupon code or object.
* @return true|WP_Error True if applied, error if not.
*/
public function apply_coupon( $coupon ) {
if ( ! is_a( $coupon, 'WC_Coupon' ) ) {
$code = wc_format_coupon_code( $coupon );
public function apply_coupon( $raw_coupon ) {
if ( is_a( $raw_coupon, 'WC_Coupon' ) ) {
$coupon = $raw_coupon;
} elseif ( is_string( $raw_coupon ) ) {
$code = wc_format_coupon_code( $raw_coupon );
$coupon = new WC_Coupon( $code );
if ( $coupon->get_code() !== $code || ! $coupon->is_valid() ) {
return new WP_Error( 'invalid_coupon', __( 'Invalid coupon code', 'woocommerce' ) );
}
} else {
return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
}
// Check to make sure coupon is not already applied.
@ -919,14 +923,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
}
$this->set_coupon_discount_amounts( $discounts );
// Add discounts to line items.
if ( $item_discounts = $discounts->get_discounts_by_item() ) {
foreach ( $item_discounts as $item_id => $amount ) {
$item = $this->get_item( $item_id, false );
$item->set_total( max( 0, $item->get_total() - $amount ) );
}
}
$this->set_item_discount_amounts( $discounts );
// Recalculate totals and taxes.
$this->calculate_totals( true );
@ -965,12 +962,10 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$this->remove_item( $item_id );
$coupon_object = new WC_Coupon( $code );
$coupon_object->decrease_usage_count( $this->get_user_id() );
$this->recalculate_coupons();
break;
}
}
// Reset line item totals.
$this->recalculate_coupons();
}
/**
@ -979,34 +974,86 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
* @since 3.2.0
*/
protected function recalculate_coupons() {
$discounts = new WC_Discounts( $this );
$coupons = $this->get_items( 'coupon' );
foreach ( $coupons as $coupon ) {
$coupon_object = new WC_Coupon( $coupon->get_code() );
$discounts->apply_coupon( $coupon_object );
}
$this->set_coupon_discount_amounts( $discounts );
// Reset line item totals.
foreach ( $this->get_items() as $item ) {
$item->set_total( $item->get_subtotal() );
$item->set_total_tax( $item->get_subtotal_tax() );
}
// Add discounts to line items.
if ( $item_discounts = $discounts->get_discounts_by_item() ) {
foreach ( $item_discounts as $item_id => $amount ) {
$item = $this->get_item( $item_id, false );
$item->set_total( max( 0, $item->get_total() - $amount ) );
$discounts = new WC_Discounts( $this );
foreach ( $this->get_items( 'coupon' ) as $coupon_item ) {
$coupon_code = $coupon_item->get_code();
$coupon_id = wc_get_coupon_id_by_code( $coupon_code );
$coupon_object = false;
// If we have a coupon ID (loaded via wc_get_coupon_id_by_code) we can simply load the new coupon object using the ID.
if ( $coupon_id ) {
$coupon_object = new WC_Coupon( $coupon_id );
} else {
// If we do not have a coupon ID (was it virtual? has it been deleted?) we must create a temporary coupon using what data we have stored during checkout.
$coupon_object = new WC_Coupon();
$coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) );
$coupon_object->set_code( $coupon_code );
$coupon_object->set_virtual( true );
// If there is no coupon amount (maybe dynamic?), set it to the given **discount** amount so the coupon's same value is applied.
if ( ! $coupon_object->get_amount() ) {
// If the order originally had prices including tax, remove the discount + discount tax.
if ( $this->get_prices_include_tax() ) {
$coupon_object->set_amount( $coupon_item->get_discount() + $coupon_item->get_discount_tax() );
} else {
$coupon_object->set_amount( $coupon_item->get_discount() );
}
$coupon_object->set_discount_type( 'fixed_cart' );
}
}
/**
* Allow developers to filter this coupon before it get's re-applied to the order.
*
* @since 3.2.0
*/
$coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this );
if ( $coupon_object ) {
$discounts->apply_coupon( $coupon_object, false );
}
}
$this->set_coupon_discount_amounts( $discounts );
$this->set_item_discount_amounts( $discounts );
// Recalculate totals and taxes.
$this->calculate_totals( true );
}
/**
* After applying coupons via the WC_Disounts class, update line items.
*
* @since 3.2.0
* @param WC_Discounts $discounts Discounts class.
*/
protected function set_item_discount_amounts( $discounts ) {
if ( $item_discounts = $discounts->get_discounts_by_item() ) {
foreach ( $item_discounts as $item_id => $amount ) {
$item = $this->get_item( $item_id, false );
// If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart.
if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
$amount_tax = WC_Tax::get_tax_total( WC_Tax::calc_tax( $amount, WC_Tax::get_rates( $item->get_tax_class() ), true ) );
$amount -= $amount_tax;
$item->set_total( max( 0, $item->get_total() - $amount ) );
} else {
$item->set_total( max( 0, $item->get_total() - $amount ) );
}
}
}
}
/**
* After applying coupons via the WC_Disounts class, update or create coupon items.
*
@ -1017,7 +1064,6 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$coupons = $this->get_items( 'coupon' );
$coupon_code_to_id = wc_list_pluck( $coupons, 'get_id', 'get_code' );
$all_discounts = $discounts->get_discounts();
$item_discounts = $discounts->get_discounts_by_item();
$coupon_discounts = $discounts->get_discounts_by_coupon();
if ( $coupon_discounts ) {
@ -1031,20 +1077,24 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$coupon_item = $this->get_item( $item_id, false );
}
$coupon_item->set_discount( $amount );
$discount_tax = 0;
// Work out how much tax has been removed as a result of the discount from this coupon.
if ( wc_tax_enabled() && isset( $all_discounts[ $coupon_code ] ) ) {
$discount_tax = 0;
foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
$item = $this->get_item( $item_id, false );
foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
$item = $this->get_item( $item_id, false );
if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
$amount_tax = array_sum( WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), true ) );
$discount_tax += $amount_tax;
$amount = $amount - $amount_tax;
} else {
$discount_tax += array_sum( WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ) ) );
}
$coupon_item->set_discount_tax( $discount_tax );
}
$coupon_item->set_discount( $amount );
$coupon_item->set_discount_tax( $discount_tax );
$this->add_item( $coupon_item );
}
}
@ -1324,11 +1374,6 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$cart_total += $item->get_total();
}
// Sum fee costs.
foreach ( $this->get_fees() as $item ) {
$fee_total += $item->get_total();
}
// Sum shipping costs.
foreach ( $this->get_shipping_methods() as $shipping ) {
$shipping_total += $shipping->get_total();
@ -1336,6 +1381,22 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$this->set_shipping_total( $shipping_total );
// Sum fee costs.
foreach ( $this->get_fees() as $item ) {
$amount = $item->get_amount();
if ( 0 > $amount ) {
$item->set_total( $amount );
$max_discount = round( $cart_total + $fee_total + $shipping_total, wc_get_price_decimals() ) * -1;
if ( $item->get_total() < $max_discount ) {
$item->set_total( $max_discount );
}
}
$fee_total += $item->get_total();
}
// Calculate taxes for items, shipping, discounts.
if ( $and_taxes ) {
$this->calculate_taxes();

View File

@ -1190,7 +1190,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
* Set download limit.
*
* @since 3.0.0
* @param int $download_limit
* @param int|string $download_limit
*/
public function set_download_limit( $download_limit ) {
$this->set_prop( 'download_limit', -1 === (int) $download_limit || '' === $download_limit ? -1 : absint( $download_limit ) );
@ -1200,7 +1200,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
* Set download expiry.
*
* @since 3.0.0
* @param int $download_expiry
* @param int|string $download_limit
*/
public function set_download_expiry( $download_expiry ) {
$this->set_prop( 'download_expiry', -1 === (int) $download_expiry || '' === $download_expiry ? -1 : absint( $download_expiry ) );
@ -1743,7 +1743,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
} else {
$image = '';
}
return str_replace( array( 'https://', 'http://' ), '//', $image );
return apply_filters( 'woocommerce_product_get_image', wc_get_relative_url( $image ), $this, $size, $attr, $placeholder );
}
/**

View File

@ -205,7 +205,6 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
public function validate_setting_text_field( $value, $setting ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses_post( trim( stripslashes( $value ) ) );
return $value;
}
/**

View File

@ -37,7 +37,7 @@ abstract class WC_REST_CRUD_Controller extends WC_REST_Posts_Controller {
* Get object.
*
* @param int $id Object ID.
* @return WP_Error|WC_Data
* @return object WC_Data object or WP_Error object.
*/
protected function get_object( $id ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass.", 'woocommerce' ), __METHOD__ ), array( 'status' => 405 ) );
@ -376,7 +376,6 @@ abstract class WC_REST_CRUD_Controller extends WC_REST_Posts_Controller {
* @return WP_REST_Response|WP_Error
*/
public function delete_item( $request ) {
$id = (int) $request['id'];
$force = (bool) $request['force'];
$object = $this->get_object( (int) $request['id'] );
$result = false;

View File

@ -477,6 +477,15 @@ class WC_Admin_Addons {
$theme = wp_get_theme();
$section_keys = array_keys( $sections );
$current_section = isset( $_GET['section'] ) ? sanitize_text_field( $_GET['section'] ) : current( $section_keys );
/**
* Addon page view.
*
* @uses $sections
* @uses $theme
* @uses $section_keys
* @uses $current_section
*/
include_once( dirname( __FILE__ ) . '/views/html-admin-page-addons.php' );
}

View File

@ -25,7 +25,6 @@ class WC_Admin_Assets {
public function __construct() {
add_action( 'admin_enqueue_scripts', array( $this, 'admin_styles' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
add_action( 'admin_head', array( $this, 'product_taxonomy_styles' ) );
}
/**
@ -306,6 +305,7 @@ class WC_Admin_Assets {
'i18n_tax_rate_already_exists' => __( 'You cannot add the same tax rate twice!', 'woocommerce' ),
'i18n_delete_note' => __( 'Are you sure you wish to delete this note? This action cannot be undone.', 'woocommerce' ),
'i18n_apply_coupon' => __( 'Enter a coupon code to apply to this order.', 'woocommerce' ),
'i18n_add_fee' => __( 'Enter a fixed amount or percentage to apply as a fee.', 'woocommerce' ),
);
wp_localize_script( 'wc-admin-meta-boxes', 'woocommerce_admin_meta_boxes', $params );
@ -385,27 +385,6 @@ class WC_Admin_Assets {
);
}
}
/**
* Admin Head.
*
* Outputs some styles in the admin <head> to show icons on the woocommerce admin pages.
*/
public function product_taxonomy_styles() {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return;
}
?>
<style type="text/css">
<?php if ( isset( $_GET['taxonomy'] ) && 'product_cat' === $_GET['taxonomy'] ) : ?>
.icon32-posts-product { background-position: -243px -5px !important; }
<?php elseif ( isset( $_GET['taxonomy'] ) && 'product_tag' === $_GET['taxonomy'] ) : ?>
.icon32-posts-product { background-position: -301px -5px !important; }
<?php endif; ?>
</style>
<?php
}
}
endif;

View File

@ -124,7 +124,9 @@ class WC_Admin_Menus {
* Addons menu item.
*/
public function addons_menu() {
add_submenu_page( 'woocommerce', __( 'WooCommerce extensions', 'woocommerce' ), __( 'Extensions', 'woocommerce' ) , 'manage_woocommerce', 'wc-addons', array( $this, 'addons_page' ) );
$count_html = WC_Helper_Updater::get_updates_count_html();
$menu_title = sprintf( __( 'Extensions %s', 'woocommerce' ), $count_html );
add_submenu_page( 'woocommerce', __( 'WooCommerce extensions', 'woocommerce' ), $menu_title, 'manage_woocommerce', 'wc-addons', array( $this, 'addons_page' ) );
}
/**

View File

@ -427,7 +427,7 @@ class WC_Admin_Settings {
case 'checkbox' :
$option_value = self::get_option( $value['id'], $value['default'] );
$visbility_class = array();
$visibility_class = array();
if ( ! isset( $value['hide_if_checked'] ) ) {
$value['hide_if_checked'] = false;
@ -436,25 +436,25 @@ class WC_Admin_Settings {
$value['show_if_checked'] = false;
}
if ( 'yes' == $value['hide_if_checked'] || 'yes' == $value['show_if_checked'] ) {
$visbility_class[] = 'hidden_option';
$visibility_class[] = 'hidden_option';
}
if ( 'option' == $value['hide_if_checked'] ) {
$visbility_class[] = 'hide_options_if_checked';
$visibility_class[] = 'hide_options_if_checked';
}
if ( 'option' == $value['show_if_checked'] ) {
$visbility_class[] = 'show_options_if_checked';
$visibility_class[] = 'show_options_if_checked';
}
if ( ! isset( $value['checkboxgroup'] ) || 'start' == $value['checkboxgroup'] ) {
?>
<tr valign="top" class="<?php echo esc_attr( implode( ' ', $visbility_class ) ); ?>">
<tr valign="top" class="<?php echo esc_attr( implode( ' ', $visibility_class ) ); ?>">
<th scope="row" class="titledesc"><?php echo esc_html( $value['title'] ) ?></th>
<td class="forminp forminp-checkbox">
<fieldset>
<?php
} else {
?>
<fieldset class="<?php echo esc_attr( implode( ' ', $visbility_class ) ); ?>">
<fieldset class="<?php echo esc_attr( implode( ' ', $visibility_class ) ); ?>">
<?php
}

View File

@ -692,14 +692,14 @@ class WC_Admin_Setup_Wizard {
*/
protected function get_wizard_payment_gateways() {
$gateways = array(
'paypal-braintree' => array(
'braintree_paypal' => array(
'name' => __( 'PayPal by Braintree', 'woocommerce' ),
'image' => WC()->plugin_url() . '/assets/images/paypal-braintree.png',
'description' => __( "Safe and secure payments using credit cards or your customer's PayPal account.", 'woocommerce' ) . ' <a href="https://wordpress.org/plugins/woocommerce-gateway-paypal-powered-by-braintree/" target="_blank">' . __( 'Learn more about PayPal', 'woocommerce' ) . '</a>',
'class' => 'featured featured-row-last',
'repo-slug' => 'woocommerce-gateway-paypal-powered-by-braintree',
),
'paypal-ec' => array(
'ppec_paypal' => array(
'name' => __( 'PayPal Express Checkout', 'woocommerce' ),
'image' => WC()->plugin_url() . '/assets/images/paypal.png',
'description' => __( "Safe and secure payments using credit cards or your customer's PayPal account.", 'woocommerce' ) . ' <a href="https://wordpress.org/plugins/woocommerce-gateway-paypal-express-checkout/" target="_blank">' . __( 'Learn more about PayPal', 'woocommerce' ) . '</a>',
@ -750,14 +750,14 @@ class WC_Admin_Setup_Wizard {
$country = WC()->countries->get_base_country();
if ( 'US' === $country ) {
unset( $gateways['paypal-ec'] );
unset( $gateways['ppec_paypal'] );
} else {
unset( $gateways['paypal-braintree'] );
unset( $gateways['braintree_paypal'] );
}
if ( ! current_user_can( 'install_plugins' ) ) {
unset( $gateways['paypal-braintree'] );
unset( $gateways['paypal-ec'] );
unset( $gateways['braintree_paypal'] );
unset( $gateways['ppec_paypal'] );
unset( $gateways['stripe'] );
}

View File

@ -234,8 +234,12 @@ class WC_Admin {
if ( isset( $current_screen->id ) && apply_filters( 'woocommerce_display_admin_footer_text', in_array( $current_screen->id, $wc_pages ) ) ) {
// Change the footer text
if ( ! get_option( 'woocommerce_admin_footer_text_rated' ) ) {
/* translators: %s: five stars */
$footer_text = sprintf( __( 'If you like <strong>WooCommerce</strong> please leave us a %s rating. A huge thanks in advance!', 'woocommerce' ), '<a href="https://wordpress.org/support/plugin/woocommerce/reviews?rate=5#new-post" target="_blank" class="wc-rating-link" data-rated="' . esc_attr__( 'Thanks :)', 'woocommerce' ) . '">&#9733;&#9733;&#9733;&#9733;&#9733;</a>' );
$footer_text = sprintf(
/* translators: 1: WooCommerce 2:: five stars */
__( 'If you like %1$s please leave us a %2$s rating. A huge thanks in advance!', 'woocommerce' ),
sprintf( '<strong>%s<strong>', esc_html__( 'WooCommerce', 'woocommerce' ) ),
'<a href="https://wordpress.org/support/plugin/woocommerce/reviews?rate=5#new-post" target="_blank" class="wc-rating-link" data-rated="' . esc_attr__( 'Thanks :)', 'woocommerce' ) . '">&#9733;&#9733;&#9733;&#9733;&#9733;</a>'
);
wc_enqueue_js( "
jQuery( 'a.wc-rating-link' ).click( function() {
jQuery.post( '" . WC()->ajax_url() . "', { action: 'woocommerce_rated' } );

View File

@ -66,7 +66,7 @@ class WC_Helper_API {
$request_uri = parse_url( $url, PHP_URL_PATH );
$query_string = parse_url( $url, PHP_URL_QUERY );
if ( $query_string ) {
if ( is_string( $query_string ) ) {
$request_uri .= '?' . $query_string;
}

View File

@ -36,26 +36,27 @@ class WC_Helper_Plugin_Info {
return $response;
}
$found_plugin = null;
// Look through local Woo plugins by slugs.
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
$slug = dirname( $plugin['_filename'] );
if ( dirname( $plugin['_filename'] ) === $args->slug ) {
$plugin['_slug'] = $args->slug;
$found_plugin = $plugin;
break;
}
}
if ( ! $found_plugin ) {
// Only for slugs that start with woo-
if ( 0 !== strpos( $args->slug, 'woocommerce-com-' ) ) {
return $response;
}
$clean_slug = str_replace( 'woocommerce-com-', '', $args->slug );
// Look through update data by slug.
$update_data = WC_Helper_Updater::get_update_data();
$products = wp_list_filter( $update_data, array( 'slug' => $clean_slug ) );
if ( empty( $products ) ) {
return $response;
}
$product_id = array_keys( $products );
$product_id = array_shift( $product_id );
// Fetch the product information from the Helper API.
$request = WC_Helper_API::get( add_query_arg( array(
'product_id' => absint( $plugin['_product_id'] ),
'product_slug' => rawurlencode( $plugin['_slug'] ),
'product_id' => absint( $product_id ),
), 'info' ), array( 'authenticated' => true ) );
$results = json_decode( wp_remote_retrieve_body( $request ), true );

View File

@ -17,6 +17,7 @@ class WC_Helper_Updater {
public static function load() {
add_action( 'pre_set_site_transient_update_plugins', array( __CLASS__, 'transient_update_plugins' ), 21, 1 );
add_action( 'pre_set_site_transient_update_themes', array( __CLASS__, 'transient_update_themes' ), 21, 1 );
add_action( 'upgrader_process_complete', array( __CLASS__, 'upgrader_process_complete' ) );
}
/**
@ -39,8 +40,8 @@ class WC_Helper_Updater {
$filename = $plugin['_filename'];
$item = array(
'id' => 'woo-' . $plugin['_product_id'],
'slug' => $data['slug'],
'id' => 'woocommerce-com-' . $plugin['_product_id'],
'slug' => 'woocommerce-com-' . $data['slug'],
'plugin' => $filename,
'new_version' => $data['version'],
'url' => $data['url'],
@ -229,11 +230,99 @@ class WC_Helper_Updater {
return false;
}
/**
* Get the number of products that have updates.
*
* @return int The number of products with updates.
*/
public static function get_updates_count() {
$cache_key = '_woocommerce_helper_updates_count';
if ( false !== ( $count = get_transient( $cache_key ) ) ) {
return $count;
}
// Don't fetch any new data since this function in high-frequency.
if ( ! get_transient( '_woocommerce_helper_subscriptions' ) ) {
return 0;
}
if ( ! get_transient( '_woocommerce_helper_updates' ) ) {
return 0;
}
$count = 0;
$update_data = self::get_update_data();
if ( empty( $update_data ) ) {
set_transient( $cache_key, $count, 12 * HOUR_IN_SECONDS );
return $count;
}
// Scan local plugins.
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
if ( empty( $update_data[ $plugin['_product_id'] ] ) ) {
continue;
}
if ( version_compare( $plugin['Version'], $update_data[ $plugin['_product_id'] ]['version'], '<' ) ) {
$count++;
}
}
// Scan local themes.
foreach ( WC_Helper::get_local_woo_themes() as $theme ) {
if ( empty( $update_data[ $theme['_product_id'] ] ) ) {
continue;
}
if ( version_compare( $theme['Version'], $update_data[ $theme['_product_id'] ]['version'], '<' ) ) {
$count++;
}
}
set_transient( $cache_key, $count, 12 * HOUR_IN_SECONDS );
return $count;
}
/**
* Return the updates count markup.
*
* @return string Updates count markup, empty string if no updates avairable.
*/
public static function get_updates_count_html() {
$count = self::get_updates_count();
if ( ! $count ) {
return '';
}
$count_html = sprintf( '<span class="update-plugins count-%d"><span class="update-count">%d</span></span>', $count, number_format_i18n( $count ) );
return $count_html;
}
/**
* Flushes cached update data.
*/
public static function flush_updates_cache() {
delete_transient( '_woocommerce_helper_updates' );
delete_transient( '_woocommerce_helper_updates_count' );
// Refresh update transients
$update_plugins = get_site_transient( 'update_plugins' );
if ( ! empty( $update_plugins ) ) {
set_site_transient( 'update_plugins', $update_plugins );
}
$update_themes = get_site_transient( 'update_themes' );
if ( ! empty( $update_themes ) ) {
set_site_transient( 'update_themes', $update_themes );
}
}
/**
* Fires when a user successfully updated a theme or a plugin.
*/
public static function upgrader_process_complete() {
delete_transient( '_woocommerce_helper_updates_count' );
}
}

View File

@ -74,9 +74,11 @@ class WC_Helper {
'wc-helper-nonce' => wp_create_nonce( 'disconnect' ),
), admin_url( 'admin.php' ) );
$current_filter = self::get_current_filter();
$refresh_url = add_query_arg( array(
'page' => 'wc-addons',
'section' => 'helper',
'filter' => $current_filter,
'wc-helper-refresh' => 1,
'wc-helper-nonce' => wp_create_nonce( 'refresh' ),
), admin_url( 'admin.php' ) );
@ -96,6 +98,7 @@ class WC_Helper {
$subscription['activate_url'] = add_query_arg( array(
'page' => 'wc-addons',
'section' => 'helper',
'filter' => $current_filter,
'wc-helper-activate' => 1,
'wc-helper-product-key' => $subscription['product_key'],
'wc-helper-product-id' => $subscription['product_id'],
@ -105,6 +108,7 @@ class WC_Helper {
$subscription['deactivate_url'] = add_query_arg( array(
'page' => 'wc-addons',
'section' => 'helper',
'filter' => $current_filter,
'wc-helper-deactivate' => 1,
'wc-helper-product-key' => $subscription['product_key'],
'wc-helper-product-id' => $subscription['product_id'],
@ -305,11 +309,118 @@ class WC_Helper {
uasort( $subscriptions, array( __CLASS__, '_sort_by_product_name' ) );
uasort( $no_subscriptions, array( __CLASS__, '_sort_by_name' ) );
// Filters
self::get_filters_counts( $subscriptions ); // Warm it up.
self::_filter( $subscriptions, self::get_current_filter() );
// We have an active connection.
include( self::get_view_filename( 'html-main.php' ) );
return;
}
/**
* Get available subscriptions filters.
*
* @param array Optional subscriptions array to generate counts.
*
* @return array An array of filter keys and labels.
*/
public static function get_filters( $subscriptions = null ) {
$filters = array(
'all' => __( 'All', 'woocommerce' ),
'active' => __( 'Active', 'woocommerce' ),
'inactive' => __( 'Inactive', 'woocommerce' ),
'update-available' => __( 'Update Available', 'woocommerce' ),
'expiring' => __( 'Expiring Soon', 'woocommerce' ),
'expired' => __( 'Expired', 'woocommerce' ),
'download' => __( 'Download', 'woocommerce' ),
);
return $filters;
}
/**
* Get counts data for the filters array.
*
* @param array $subscriptions The array of all available subscriptions.
*
* @return array Filter counts (filter => count)
*/
public static function get_filters_counts( $subscriptions = null ) {
static $filters;
if ( isset( $filters ) ) {
return $filters;
}
$filters = array_fill_keys( array_keys( self::get_filters() ), 0 );
if ( empty( $subscriptions ) ) {
return array();
}
foreach ( $filters as $key => $count ) {
$_subs = $subscriptions;
self::_filter( $_subs, $key );
$filters[ $key ] = count( $_subs );
}
return $filters;
}
/**
* Get current filter.
*
* @return string The current filter.
*/
public static function get_current_filter() {
$current_filter = 'all';
$valid_filters = array_keys( self::get_filters() );
if ( ! empty( $_GET['filter'] ) && in_array( $_GET['filter'], $valid_filters ) ) {
$current_filter = $_GET['filter'];
}
return $current_filter;
}
/**
* Filter an array of subscriptions by $filter.
*
* @param array $subscriptions The subscriptions array, passed by ref.
* @param string $filter The filter.
*/
private static function _filter( &$subscriptions, $filter ) {
switch ( $filter ) {
case 'active':
$subscriptions = wp_list_filter( $subscriptions, array( 'active' => true ) );
break;
case 'inactive':
$subscriptions = wp_list_filter( $subscriptions, array( 'active' => false ) );
break;
case 'update-available':
$subscriptions = wp_list_filter( $subscriptions, array( 'has_update' => true ) );
break;
case 'expiring':
$subscriptions = wp_list_filter( $subscriptions, array( 'expiring' => true ) );
break;
case 'expired':
$subscriptions = wp_list_filter( $subscriptions, array( 'expired' => true ) );
break;
case 'download':
foreach ( $subscriptions as $key => $subscription ) {
if ( $subscription['local']['installed'] || $subscription['expired'] ) {
unset( $subscriptions[ $key ] );
}
}
break;
}
}
/**
* Enqueue admin scripts and styles.
*/
@ -367,6 +478,7 @@ class WC_Helper {
$deactivate_plugin_url = add_query_arg( array(
'page' => 'wc-addons',
'section' => 'helper',
'filter' => self::get_current_filter(),
'wc-helper-deactivate-plugin' => 1,
'wc-helper-product-id' => $subscription['product_id'],
'wc-helper-nonce' => wp_create_nonce( 'deactivate-plugin:' . $subscription['product_id'] ),
@ -615,7 +727,7 @@ class WC_Helper {
'wc-helper-status' => 'helper-disconnected',
), admin_url( 'admin.php' ) );
$result = WC_Helper_API::post( 'oauth/invalidate_token', array(
WC_Helper_API::post( 'oauth/invalidate_token', array(
'authenticated' => true,
) );
@ -644,6 +756,7 @@ class WC_Helper {
$redirect_uri = add_query_arg( array(
'page' => 'wc-addons',
'section' => 'helper',
'filter' => self::get_current_filter(),
'wc-helper-status' => 'helper-refreshed',
), admin_url( 'admin.php' ) );
@ -690,6 +803,7 @@ class WC_Helper {
$redirect_uri = add_query_arg( array(
'page' => 'wc-addons',
'section' => 'helper',
'filter' => self::get_current_filter(),
'wc-helper-status' => $activated ? 'activate-success' : 'activate-error',
'wc-helper-product-id' => $product_id,
), admin_url( 'admin.php' ) );
@ -727,6 +841,7 @@ class WC_Helper {
$redirect_uri = add_query_arg( array(
'page' => 'wc-addons',
'section' => 'helper',
'filter' => self::get_current_filter(),
'wc-helper-status' => $deactivated ? 'deactivate-success' : 'deactivate-error',
'wc-helper-product-id' => $product_id,
), admin_url( 'admin.php' ) );
@ -767,6 +882,7 @@ class WC_Helper {
$redirect_uri = add_query_arg( array(
'page' => 'wc-addons',
'section' => 'helper',
'filter' => self::get_current_filter(),
'wc-helper-status' => $deactivated ? 'deactivate-plugin-success' : 'deactivate-plugin-error',
'wc-helper-product-id' => $product_id,
), admin_url( 'admin.php' ) );

View File

@ -12,6 +12,34 @@
<p><?php printf( __( 'Below is a list of extensions available on your WooCommerce.com account. To receive extension updates please make sure the extension is installed, and its subscription activated and connected to your WooCommerce.com account. Extensions can be activated from the <a href="%s">Plugins</a> screen.', 'woocommerce' ), admin_url( 'plugins.php' ) ); ?></p>
</div>
<ul class="subscription-filter">
<label><?php _e( 'Sort by:', 'woocommerce' ); ?> <span class="chevron dashicons dashicons-arrow-up-alt2"></span></label>
<?php
$filters = array_keys( WC_Helper::get_filters() );
$last_filter = array_pop( $filters );
$current_filter = WC_Helper::get_current_filter();
$counts = WC_Helper::get_filters_counts();
?>
<?php foreach ( WC_Helper::get_filters() as $key => $label ) : ?>
<?php
// Don't show empty filters.
if ( empty( $counts[ $key ] ) ) {
continue;
}
$url = admin_url( 'admin.php?page=wc-addons&section=helper&filter=' . $key );
$class_html = $current_filter === $key ? 'class="current"' : '';
?>
<li>
<a <?php echo $class_html; ?> href="<?php echo esc_url( $url ); ?>">
<?php echo esc_html( $label ); ?>
<span class="count">(<?php echo absint( $counts[ $key ] ); ?>)</span>
</a>
</li>
<?php endforeach; ?>
</ul>
<table class="wp-list-table widefat fixed striped">
<?php if ( ! empty( $subscriptions ) ) : ?>
<?php foreach ( $subscriptions as $subscription ) : ?>
@ -24,7 +52,11 @@
</div>
<div class="wp-list-table__ext-description">
<?php if ( $subscription['expired'] ) : ?>
<?php if ( $subscription['lifetime'] ) : ?>
<span class="renews">
<?php _e( 'Lifetime Subscription', 'woocommerce' ); ?>
</span>
<?php elseif ( $subscription['expired'] ) : ?>
<span class="renews">
<strong><?php _e( 'Expired :(', 'woocommerce' ); ?></strong>
<?php echo date_i18n( 'F jS, Y', $subscription['expires'] ); ?>
@ -58,7 +90,11 @@
} else {
_e( 'Subscription: Unlimited', 'woocommerce' );
}
if ( isset( $subscription['master_user_email'] ) ) {
// Check shared.
if ( ! empty( $subscription['is_shared'] ) && ! empty( $subscription['owner_email'] ) ) {
printf( '</br>' . __( 'Shared by %s', 'woocommerce' ), esc_html( $subscription['owner_email'] ) );
} elseif ( isset( $subscription['master_user_email'] ) ) {
printf( '</br>' . __( 'Shared by %s', 'woocommerce' ), esc_html( $subscription['master_user_email'] ) );
}
?>

View File

@ -2,5 +2,10 @@
<nav class="nav-tab-wrapper woo-nav-tab-wrapper">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons' ) ); ?>" class="nav-tab"><?php _e( 'Browse Extensions', 'woocommerce' ); ?></a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons&section=helper' ) ); ?>" class="nav-tab nav-tab-active"><?php _e( 'WooCommerce.com Subscriptions', 'woocommerce' ); ?></a>
<?php
$count_html = WC_Helper_Updater::get_updates_count_html();
$menu_title = sprintf( __( 'WooCommerce.com Subscriptions %s', 'woocommerce' ), $count_html );
?>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons&section=helper' ) ); ?>" class="nav-tab nav-tab-active"><?php echo $menu_title; ?></a>
</nav>

View File

@ -76,7 +76,7 @@ class WC_Product_CSV_Importer_Controller {
* Constructor.
*/
public function __construct() {
$this->steps = array(
$default_steps = array(
'upload' => array(
'name' => __( 'Upload CSV file', 'woocommerce' ),
'view' => array( $this, 'upload_form' ),
@ -98,6 +98,9 @@ class WC_Product_CSV_Importer_Controller {
'handler' => '',
),
);
$this->steps = apply_filters( 'woocommerce_product_csv_importer_steps', $default_steps );
$this->step = isset( $_REQUEST['step'] ) ? sanitize_key( $_REQUEST['step'] ) : current( array_keys( $this->steps ) );
$this->file = isset( $_REQUEST['file'] ) ? wc_clean( $_REQUEST['file'] ) : '';
$this->update_existing = isset( $_REQUEST['update_existing'] ) ? (bool) $_REQUEST['update_existing'] : false;
@ -402,28 +405,36 @@ class WC_Product_CSV_Importer_Controller {
__( 'Grouped products', 'woocommerce' ) => 'grouped_products',
__( 'External URL', 'woocommerce' ) => 'product_url',
__( 'Button text', 'woocommerce' ) => 'button_text',
__( 'Position', 'woocommerce' ) => 'menu_order',
) );
// Normalize the columns so they are case-insensitive.
$normalized_default_columns = array();
foreach ( $default_columns as $key => $val ) {
$normalized_default_columns[ strtolower( $key ) ] = $val;
}
$special_columns = $this->get_special_columns( apply_filters( 'woocommerce_csv_product_import_mapping_special_columns',
array(
__( 'Attribute %d name', 'woocommerce' ) => 'attributes:name',
__( 'Attribute %d value(s)', 'woocommerce' ) => 'attributes:value',
__( 'Attribute %d visible', 'woocommerce' ) => 'attributes:visible',
__( 'Attribute %d global', 'woocommerce' ) => 'attributes:taxonomy',
__( 'Attribute %d default', 'woocommerce' ) => 'attributes:default',
__( 'Download %d name', 'woocommerce' ) => 'downloads:name',
__( 'Download %d URL', 'woocommerce' ) => 'downloads:url',
__( 'Meta: %s', 'woocommerce' ) => 'meta:',
__( 'attribute %d name', 'woocommerce' ) => 'attributes:name',
__( 'attribute %d value(s)', 'woocommerce' ) => 'attributes:value',
__( 'attribute %d visible', 'woocommerce' ) => 'attributes:visible',
__( 'attribute %d global', 'woocommerce' ) => 'attributes:taxonomy',
__( 'attribute %d default', 'woocommerce' ) => 'attributes:default',
__( 'download %d name', 'woocommerce' ) => 'downloads:name',
__( 'download %d url', 'woocommerce' ) => 'downloads:url',
__( 'meta: %s', 'woocommerce' ) => 'meta:',
)
) );
$headers = array();
foreach ( $raw_headers as $key => $field ) {
$field = strtolower( $field );
$index = $num_indexes ? $key : $field;
$headers[ $index ] = $field;
if ( isset( $default_columns[ $field ] ) ) {
$headers[ $index ] = $default_columns[ $field ];
if ( isset( $normalized_default_columns[ $field ] ) ) {
$headers[ $index ] = $normalized_default_columns[ $field ];
} else {
foreach ( $special_columns as $regex => $special_key ) {
if ( preg_match( $regex, $field, $matches ) ) {
@ -560,6 +571,7 @@ class WC_Product_CSV_Importer_Controller {
'reviews_allowed' => __( 'Allow customer reviews?', 'woocommerce' ),
'purchase_note' => __( 'Purchase note', 'woocommerce' ),
'meta:' . $meta => __( 'Import as meta', 'woocommerce' ),
'menu_order' => __( 'Position', 'woocommerce' ),
);
return apply_filters( 'woocommerce_csv_product_import_mapping_options', $options, $item );

View File

@ -71,6 +71,7 @@ function wc_importer_default_english_mappings( $mappings ) {
'Grouped products' => 'grouped_products',
'External URL' => 'product_url',
'Button text' => 'button_text',
'Position' => 'menu_order',
);
return array_merge( $mappings, $new_mappings );

View File

@ -18,6 +18,7 @@ function wc_importer_generic_mappings( $mappings ) {
__( 'Price', 'woocommerce' ) => 'regular_price',
__( 'Parent SKU', 'woocommerce' ) => 'parent_id',
__( 'Quantity', 'woocommerce' ) => 'stock_quantity',
__( 'Menu order', 'woocommerce' ) => 'menu_order',
);
return array_merge( $mappings, $generic_mappings );

View File

@ -111,9 +111,12 @@ if ( wc_tax_enabled() ) {
echo '<li><strong>' . __( 'Coupon(s)', 'woocommerce' ) . '</strong></li>';
foreach ( $coupons as $item_id => $item ) {
$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() ) );
$link = $post_id ? add_query_arg( array( 'post' => $post_id, 'action' => 'edit' ), admin_url( 'post.php' ) ) : add_query_arg( array( 's' => $item->get_code(), 'post_status' => 'all', 'post_type' => 'shop_coupon' ), admin_url( 'edit.php' ) );
echo '<li class="code"><a href="' . esc_url( $link ) . '" class="tips" data-tip="' . esc_attr( wc_price( $item->get_discount(), array( 'currency' => $order->get_currency() ) ) ) . '"><span>' . esc_html( $item->get_code() ) . '</span></a> <a class="remove-coupon" href="javascript:void(0)" aria-label="Remove" data-code="' . esc_attr( $item->get_code() ) . '"></a></li>';
if ( $post_id ) {
echo '<li class="code"><a href="' . esc_url( add_query_arg( array( 'post' => $post_id, 'action' => 'edit' ), admin_url( 'post.php' ) ) ) . '" class="tips" data-tip="' . esc_attr( wc_price( $item->get_discount(), array( 'currency' => $order->get_currency() ) ) ) . '"><span>' . esc_html( $item->get_code() ) . '</span></a> <a class="remove-coupon" href="javascript:void(0)" aria-label="Remove" data-code="' . esc_attr( $item->get_code() ) . '"></a></li>';
} else {
echo '<li class="code"><span class="tips" data-tip="' . esc_attr( wc_price( $item->get_discount(), array( 'currency' => $order->get_currency() ) ) ) . '"><span>' . esc_html( $item->get_code() ) . '</span></span> <a class="remove-coupon" href="javascript:void(0)" aria-label="Remove" data-code="' . esc_attr( $item->get_code() ) . '"></a></li>';
}
}
?></ul>
</div>
@ -188,7 +191,7 @@ if ( wc_tax_enabled() ) {
<div class="wc-order-data-row wc-order-add-item wc-order-data-row-toggle" style="display:none;">
<button type="button" class="button add-order-item"><?php _e( 'Add product(s)', 'woocommerce' ); ?></button>
<button type="button" class="button add-order-fee"><?php _e( 'Add fee', 'woocommerce' ); ?></button>
<button type="button" class="button add-order-shipping"><?php _e( 'Add shipping cost', 'woocommerce' ); ?></button>
<button type="button" class="button add-order-shipping"><?php _e( 'Add shipping', 'woocommerce' ); ?></button>
<?php if ( wc_tax_enabled() ) : ?>
<button type="button" class="button add-order-tax"><?php _e( 'Add tax', 'woocommerce' ); ?></button>
<?php endif; ?>

View File

@ -364,7 +364,13 @@ class WC_Settings_Products extends WC_Settings_Page {
array(
'title' => __( 'File download method', 'woocommerce' ),
'desc' => __( 'Forcing downloads will keep URLs hidden, but some servers may serve large files unreliably. If supported, <code>X-Accel-Redirect</code>/ <code>X-Sendfile</code> can be used to serve downloads instead (server requires <code>mod_xsendfile</code>).', 'woocommerce' ),
'desc' => sprintf(
/* translators: 1: X-Accel-Redirect 2: X-Sendfile 3: mod_xsendfile */
__( 'Forcing downloads will keep URLs hidden, but some servers may serve large files unreliably. If supported, %1$s / %2$s can be used to serve downloads instead (server requires %3$s).', 'woocommerce' ),
'<code>X-Accel-Redirect</code>',
'<code>X-Sendfile</code>',
'<code>mod_xsendfile</code>'
),
'id' => 'woocommerce_file_download_method',
'type' => 'select',
'class' => 'wc-enhanced-select',

View File

@ -13,7 +13,12 @@ if ( ! defined( 'ABSPATH' ) ) {
<div class="wrap woocommerce wc_addons_wrap">
<nav class="nav-tab-wrapper woo-nav-tab-wrapper">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons' ) ); ?>" class="nav-tab nav-tab-active"><?php _e( 'Browse Extensions', 'woocommerce' ); ?></a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons&section=helper' ) ); ?>" class="nav-tab"><?php _e( 'WooCommerce.com Subscriptions', 'woocommerce' ); ?></a>
<?php
$count_html = WC_Helper_Updater::get_updates_count_html();
$menu_title = sprintf( __( 'WooCommerce.com Subscriptions %s', 'woocommerce' ), $count_html );
?>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons&section=helper' ) ); ?>" class="nav-tab"><?php echo $menu_title; ?></a>
</nav>
<h1 class="screen-reader-text"><?php _e( 'WooCommerce Extensions', 'woocommerce' ); ?></h1>

View File

@ -22,6 +22,8 @@ $theme = $system_status->get_theme_info();
$security = $system_status->get_security_info();
$settings = $system_status->get_settings();
$pages = $system_status->get_pages();
$plugin_updates = new WC_Plugin_Updates;
$untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, 'minor' );
?>
<div class="updated woocommerce-message inline">
<p><?php _e( 'Please copy and paste this information in your ticket when contacting support:', 'woocommerce' ); ?> </p>
@ -344,19 +346,19 @@ $pages = $system_status->get_pages();
<tr>
<td><?php _e( 'Total Database Size', 'woocommerce' ); ?></td>
<td class="help">&nbsp;</td>
<td><?php printf( '%.2fMB', $database['database_size']['data'] ); ?></td>
<td><?php printf( '%.2fMB', $database['database_size']['data'] + $database['database_size']['index'] ); ?></td>
</tr>
<tr>
<td><?php _e( 'Database Data Size', 'woocommerce' ); ?></td>
<td class="help">&nbsp;</td>
<td><?php printf( '%.2fMB', $database['database_size']['index'] ); ?></td>
<td><?php printf( '%.2fMB', $database['database_size']['data'] ); ?></td>
</tr>
<tr>
<td><?php _e( 'Database Index Size', 'woocommerce' ); ?></td>
<td class="help">&nbsp;</td>
<td><?php printf( '%.2fMB', $database['database_size']['data'] + $database['database_size']['index'] ); ?></td>
<td><?php printf( '%.2fMB', $database['database_size']['index'] ); ?></td>
</tr>
<?php foreach ( $database['database_tables']['woocommerce'] as $table => $table_data ) { ?>
@ -465,6 +467,10 @@ $pages = $system_status->get_pages();
$network_string = ' &ndash; <strong style="color:black;">' . __( 'Network enabled', 'woocommerce' ) . '</strong>';
}
}
$untested_string = '';
if ( array_key_exists( $plugin['plugin'], $untested_plugins ) ) {
$untested_string = ' &ndash; <strong style="color:red;">' . esc_html__( 'Not tested with the active version of WooCommerce', 'woocommerce' ) . '</strong>';
}
?>
<tr>
<td><?php echo $plugin_name; ?></td>
@ -472,7 +478,7 @@ $pages = $system_status->get_pages();
<td><?php
/* translators: %s: plugin author */
printf( __( 'by %s', 'woocommerce' ), $plugin['author_name'] );
echo ' &ndash; ' . esc_html( $plugin['version'] ) . $version_string . $network_string;
echo ' &ndash; ' . esc_html( $plugin['version'] ) . $version_string . $untested_string . $network_string;
?></td>
</tr>
<?php

View File

@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) {
?>
<form method="post" action="options.php">
<?php settings_fields( 'woocommerce_status_settings_fields' ); ?>
<table class="wc_status_table widefat" cellspacing="0">
<table class="wc_status_table wc_status_table--tools widefat" cellspacing="0">
<tbody class="tools">
<?php foreach ( $tools as $action => $tool ) : ?>
<tr class="<?php echo sanitize_html_class( $action ); ?>">

View File

@ -228,6 +228,10 @@ function wc_save_order_items( $order_id, $items ) {
),
) );
if ( 'fee' === $item->get_type() ) {
$item->set_amount( $item_data['line_total'] );
}
if ( isset( $items['meta_key'][ $item_id ], $items['meta_value'][ $item_id ] ) ) {
foreach ( $items['meta_key'][ $item_id ] as $meta_id => $meta_key ) {
$meta_value = isset( $items['meta_value'][ $item_id ][ $meta_id ] ) ? wp_unslash( $items['meta_value'][ $item_id ][ $meta_id ] ) : '';

View File

@ -354,7 +354,7 @@ class WC_REST_Authentication {
if ( substr( $request_path, 0, strlen( $wp_base ) ) === $wp_base ) {
$request_path = substr( $request_path, strlen( $wp_base ) );
}
$base_request_uri = rawurlencode( get_home_url( null, $request_path ) );
$base_request_uri = rawurlencode( get_home_url( null, $request_path, is_ssl() ? 'https' : 'http' ) );
// Get the signature provided by the consumer and remove it from the parameters prior to checking the signature.
$consumer_signature = rawurldecode( str_replace( ' ', '+', $params['oauth_signature'] ) );

View File

@ -292,6 +292,13 @@ class WC_REST_Order_Refunds_Controller extends WC_REST_Orders_Controller {
return new WP_Error( 'woocommerce_rest_cannot_create_order_refund', __( 'Cannot create order refund, please try again.', 'woocommerce' ), 500 );
}
if ( ! empty( $request['meta_data'] ) && is_array( $request['meta_data'] ) ) {
foreach ( $request['meta_data'] as $meta ) {
$refund->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' );
}
$refund->save_meta_data();
}
/**
* Filters an object before it is inserted via the REST API.
*

View File

@ -1033,7 +1033,7 @@ class WC_REST_Products_Controller extends WC_REST_Legacy_Products_Controller {
* @return WC_Product
*/
protected function set_product_images( $product, $images ) {
if ( is_array( $images ) ) {
if ( is_array( $images ) && ! empty( $images ) ) {
$gallery = array();
foreach ( $images as $image ) {

View File

@ -628,6 +628,17 @@ class WC_REST_System_Status_Controller extends WC_REST_Controller {
);
}
/**
* Add prefix to table.
*
* @param string $table table name
* @return stromg
*/
protected function add_db_table_prefix( $table ) {
global $wpdb;
return $wpdb->prefix . $table;
}
/**
* Get array of database information. Version, prefix, and table existence.
*
@ -637,11 +648,11 @@ class WC_REST_System_Status_Controller extends WC_REST_Controller {
global $wpdb;
$database_table_sizes = $wpdb->get_results( $wpdb->prepare( "
SELECT
table_name AS 'name',
SELECT
table_name AS 'name',
round( ( data_length / 1024 / 1024 ), 2 ) 'data',
round( ( index_length / 1024 / 1024 ), 2 ) 'index'
FROM information_schema.TABLES
FROM information_schema.TABLES
WHERE table_schema = %s
ORDER BY name ASC;
", DB_NAME ) );
@ -673,10 +684,7 @@ class WC_REST_System_Status_Controller extends WC_REST_Controller {
*
* If we changed the tables above to include the prefix, then any filters against that table could break.
*/
$core_tables = array_map( function( $table ) {
global $wpdb;
return $wpdb->prefix . $table;
}, $core_tables );
$core_tables = array_map( array( $this, 'add_db_table_prefix' ), $core_tables );
/**
* Organize WooCommerce and non-WooCommerce tables separately for display purposes later.

View File

@ -388,28 +388,7 @@ class WC_REST_System_Status_Tools_Controller extends WC_REST_Controller {
$message = __( 'Product transients cleared', 'woocommerce' );
break;
case 'clear_expired_transients' :
/*
* Deletes all expired transients. The multi-table delete syntax is used.
* to delete the transient record from table a, and the corresponding.
* transient_timeout record from table b.
*
* Based on code inside core's upgrade_network() function.
*/
$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
AND b.option_value < %d";
$rows = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', time() ) );
$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
AND b.option_value < %d";
$rows2 = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', time() ) );
$message = sprintf( __( '%d transients rows cleared', 'woocommerce' ), $rows + $rows2 );
$message = sprintf( __( '%d transients rows cleared', 'woocommerce' ), wc_delete_expired_transients() );
break;
case 'delete_orphaned_variations' :
/**

View File

@ -1034,7 +1034,7 @@ class WC_API_Products extends WC_API_Resource {
$product->set_attributes( $attributes );
}
// Sales and prices
// Sales and prices.
if ( in_array( $product->get_type(), array( 'variable', 'grouped' ) ) ) {
// Variable and grouped products have no prices.
@ -1046,23 +1046,18 @@ class WC_API_Products extends WC_API_Resource {
} else {
// Regular Price
// Regular Price.
if ( isset( $data['regular_price'] ) ) {
$regular_price = ( '' === $data['regular_price'] ) ? '' : $data['regular_price'];
} else {
$regular_price = $product->get_regular_price();
$product->set_regular_price( $regular_price );
}
// Sale Price
// Sale Price.
if ( isset( $data['sale_price'] ) ) {
$sale_price = ( '' === $data['sale_price'] ) ? '' : $data['sale_price'];
} else {
$sale_price = $product->get_sale_price();
$product->set_sale_price( $sale_price );
}
$product->set_regular_price( $regular_price );
$product->set_sale_price( $sale_price );
if ( isset( $data['sale_price_dates_from'] ) ) {
$date_from = $data['sale_price_dates_from'];
} else {

View File

@ -1536,23 +1536,18 @@ class WC_API_Products extends WC_API_Resource {
} else {
// Regular Price
// Regular Price.
if ( isset( $data['regular_price'] ) ) {
$regular_price = ( '' === $data['regular_price'] ) ? '' : $data['regular_price'];
} else {
$regular_price = $product->get_regular_price();
$product->set_regular_price( $regular_price );
}
// Sale Price
// Sale Price.
if ( isset( $data['sale_price'] ) ) {
$sale_price = ( '' === $data['sale_price'] ) ? '' : $data['sale_price'];
} else {
$sale_price = $product->get_sale_price();
$product->set_sale_price( $sale_price );
}
$product->set_regular_price( $regular_price );
$product->set_sale_price( $sale_price );
if ( isset( $data['sale_price_dates_from'] ) ) {
$date_from = $data['sale_price_dates_from'];
} else {

View File

@ -140,7 +140,7 @@ class WC_REST_Webhooks_V1_Controller extends WC_REST_Posts_Controller {
* @return string
*/
protected function get_default_api_version() {
return 'wp_api_v2';
return 'wp_api_v1';
}
/**

View File

@ -284,38 +284,38 @@ class WC_AJAX {
WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
WC()->session->set( 'chosen_payment_method', empty( $_POST['payment_method'] ) ? '' : $_POST['payment_method'] );
WC()->customer->set_props( array(
'billing_country' => isset( $_POST['country'] ) ? $_POST['country'] : null,
'billing_state' => isset( $_POST['state'] ) ? $_POST['state'] : null,
'billing_postcode' => isset( $_POST['postcode'] ) ? $_POST['postcode'] : null,
'billing_city' => isset( $_POST['city'] ) ? $_POST['city'] : null,
'billing_address_1' => isset( $_POST['address'] ) ? $_POST['address'] : null,
'billing_address_2' => isset( $_POST['address_2'] ) ? $_POST['address_2'] : null,
'billing_country' => isset( $_POST['country'] ) ? wp_unslash( $_POST['country'] ) : null,
'billing_state' => isset( $_POST['state'] ) ? wp_unslash( $_POST['state'] ) : null,
'billing_postcode' => isset( $_POST['postcode'] ) ? wp_unslash( $_POST['postcode'] ) : null,
'billing_city' => isset( $_POST['city'] ) ? wp_unslash( $_POST['city'] ) : null,
'billing_address_1' => isset( $_POST['address'] ) ? wp_unslash( $_POST['address'] ) : null,
'billing_address_2' => isset( $_POST['address_2'] ) ? wp_unslash( $_POST['address_2'] ) : null,
) );
if ( wc_ship_to_billing_address_only() ) {
WC()->customer->set_props( array(
'shipping_country' => isset( $_POST['country'] ) ? $_POST['country'] : null,
'shipping_state' => isset( $_POST['state'] ) ? $_POST['state'] : null,
'shipping_postcode' => isset( $_POST['postcode'] ) ? $_POST['postcode'] : null,
'shipping_city' => isset( $_POST['city'] ) ? $_POST['city'] : null,
'shipping_address_1' => isset( $_POST['address'] ) ? $_POST['address'] : null,
'shipping_address_2' => isset( $_POST['address_2'] ) ? $_POST['address_2'] : null,
'shipping_country' => isset( $_POST['country'] ) ? wp_unslash( $_POST['country'] ) : null,
'shipping_state' => isset( $_POST['state'] ) ? wp_unslash( $_POST['state'] ) : null,
'shipping_postcode' => isset( $_POST['postcode'] ) ? wp_unslash( $_POST['postcode'] ) : null,
'shipping_city' => isset( $_POST['city'] ) ? wp_unslash( $_POST['city'] ) : null,
'shipping_address_1' => isset( $_POST['address'] ) ? wp_unslash( $_POST['address'] ) : null,
'shipping_address_2' => isset( $_POST['address_2'] ) ? wp_unslash( $_POST['address_2'] ): null,
) );
if ( ! empty( $_POST['country'] ) ) {
WC()->customer->set_calculated_shipping( true );
}
} else {
WC()->customer->set_props( array(
'shipping_country' => isset( $_POST['s_country'] ) ? $_POST['s_country'] : null,
'shipping_state' => isset( $_POST['s_state'] ) ? $_POST['s_state'] : null,
'shipping_postcode' => isset( $_POST['s_postcode'] ) ? $_POST['s_postcode'] : null,
'shipping_city' => isset( $_POST['s_city'] ) ? $_POST['s_city'] : null,
'shipping_address_1' => isset( $_POST['s_address'] ) ? $_POST['s_address'] : null,
'shipping_address_2' => isset( $_POST['s_address_2'] ) ? $_POST['s_address_2'] : null,
'shipping_country' => isset( $_POST['s_country'] ) ? wp_unslash( $_POST['s_country'] ) : null,
'shipping_state' => isset( $_POST['s_state'] ) ? wp_unslash( $_POST['s_state'] ) : null,
'shipping_postcode' => isset( $_POST['s_postcode'] ) ? wp_unslash( $_POST['s_postcode'] ) : null,
'shipping_city' => isset( $_POST['s_city'] ) ? wp_unslash( $_POST['s_city'] ) : null,
'shipping_address_1' => isset( $_POST['s_address'] ) ? wp_unslash( $_POST['s_address'] ) : null,
'shipping_address_2' => isset( $_POST['s_address_2'] ) ? wp_unslash( $_POST['s_address_2'] ): null,
) );
if ( ! empty( $_POST['s_country'] ) ) {
WC()->customer->set_calculated_shipping( true );
}
}
if ( wc_string_to_bool( $_POST['has_full_address'] ) ) {
WC()->customer->set_calculated_shipping( true );
} else {
WC()->customer->set_calculated_shipping( false );
}
WC()->customer->save();
@ -775,21 +775,21 @@ class WC_AJAX {
throw new Exception( __( 'Invalid order', 'woocommerce' ) );
}
ob_start();
foreach ( $items_to_add as $item_to_add ) {
if ( ! in_array( get_post_type( $item_to_add ), array( 'product', 'product_variation' ) ) ) {
continue;
}
$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 );
$order_taxes = $order->get_taxes();
$class = 'new_row';
do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
include( 'admin/meta-boxes/views/html-order-item.php' );
$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 );
do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item, $order );
}
do_action( 'woocommerce_ajax_added_order_items', $item_id, $item, $order );
$data = get_post_meta( $order_id );
ob_start();
include( 'admin/meta-boxes/views/html-order-items.php' );
wp_send_json_success( array(
'html' => ob_get_clean(),
) );
@ -809,15 +809,34 @@ class WC_AJAX {
}
try {
$order_id = absint( $_POST['order_id'] );
$order = wc_get_order( $order_id );
$order_taxes = $order->get_taxes();
$item = new WC_Order_Item_Fee();
$item->set_order_id( $order_id );
$item_id = $item->save();
$order_id = absint( $_POST['order_id'] );
$amount = wc_clean( $_POST['amount'] );
$order = wc_get_order( $order_id );
if ( ! $order ) {
throw new exception( __( 'Invalid order', 'woocommerce' ) );
}
if ( strstr( $amount, '%' ) ) {
$formatted_amount = $amount;
$percent = floatval( trim( $amount, '%' ) );
$amount = $order->get_total() * ( $percent / 100 );
} else {
$amount = floatval( $amount );
$formatted_amount = wc_price( $amount, array( 'currency' => $order->get_currency() ) );
}
$fee = new WC_Order_Item_Fee();
$fee->set_amount( $amount );
$fee->set_total( $amount );
$fee->set_name( sprintf( __( '%s fee', 'woocommerce' ), $formatted_amount ) );
$order->add_item( $fee );
$order->calculate_totals( true );
$order->save();
ob_start();
include( 'admin/meta-boxes/views/html-order-fee.php' );
include( 'admin/meta-boxes/views/html-order-items.php' );
wp_send_json_success( array(
'html' => ob_get_clean(),

View File

@ -100,9 +100,7 @@ class WC_Background_Updater extends WP_Background_Process {
* @return mixed
*/
protected function task( $callback ) {
if ( ! defined( 'WC_UPDATING' ) ) {
define( 'WC_UPDATING', true );
}
wc_maybe_define_constant( 'WC_UPDATING', true );
$logger = wc_get_logger();

View File

@ -172,15 +172,9 @@ class WC_Cache_Helper {
* @return array
*/
public static function set_nocache_constants( $value ) {
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
define( "DONOTCACHEPAGE", true );
}
if ( ! defined( 'DONOTCACHEOBJECT' ) ) {
define( "DONOTCACHEOBJECT", true );
}
if ( ! defined( 'DONOTCACHEDB' ) ) {
define( "DONOTCACHEDB", true );
}
wc_maybe_define_constant( 'DONOTCACHEPAGE', true );
wc_maybe_define_constant( 'DONOTCACHEOBJECT', true );
wc_maybe_define_constant( 'DONOTCACHEDB', true );
return $value;
}

View File

@ -0,0 +1,143 @@
<?php
/**
* Cart fees API.
*
* Developers can add fees to the cart via WC()->cart->fees_api() which will reference this class.
*
* We suggest using the action woocommerce_cart_calculate_fees hook for adding fees.
*
* @author Automattic
* @package WooCommerce/Classes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Cart_Fees class.
*
* @since 3.2.0
*/
final class WC_Cart_Fees {
/**
* An array of fee objects.
*
* @var object[]
*/
private $fees = array();
/**
* Reference to cart object.
*
* @since 3.2.0
* @var array
*/
private $cart;
/**
* New fees are made out of these props.
*
* @var array
*/
private $default_fee_props = array(
'id' => '',
'name' => '',
'tax_class' => '',
'taxable' => false,
'amount' => 0,
'total' => 0,
);
/**
* Constructor. Reference to the cart.
*
* @since 3.2.0
* @param object $cart Cart object.
*/
public function __construct( &$cart = null ) {
$this->cart = $cart;
add_action( 'woocommerce_cart_emptied', array( $this, 'remove_all_fees' ) );
add_action( 'woocommerce_cart_reset', array( $this, 'remove_all_fees' ) );
}
/**
* Add a fee. Fee IDs must be unique.
*
* @since 3.2.0
* @param array $args Array of fee properties.
* @return object Either a fee object if added, or a WP_Error if it failed.
*/
public function add_fee( $args = array() ) {
$fee_props = (object) wp_parse_args( $args, $this->default_fee_props );
$fee_props->name = $fee_props->name ? $fee_props->name : __( 'Fee', 'woocommerce' );
$fee_props->tax_class = in_array( $fee_props->tax_class, WC_Tax::get_tax_classes(), true ) ? $fee_props->tax_class: '';
$fee_props->taxable = wc_string_to_bool( $fee_props->taxable );
$fee_props->amount = wc_format_decimal( $fee_props->amount );
if ( empty( $fee_props->id ) ) {
$fee_props->id = $this->generate_id( $fee_props );
}
if ( array_key_exists( $fee_props->id, $this->fees ) ) {
return new WP_Error( 'fee_exists', __( 'Fee has already been added.', 'woocommerce' ) );
}
return $this->fees[ $fee_props->id ] = $fee_props;
}
/**
* Get fees.
*
* @return array
*/
public function get_fees() {
uasort( $this->fees, array( $this, 'sort_fees_callback' ) );
return $this->fees;
}
/**
* Set fees.
*
* @param object[] $raw_fees Array of fees.
*/
public function set_fees( $raw_fees = array() ) {
$this->fees = array();
foreach ( $raw_fees as $raw_fee ) {
$this->add_fee( $raw_fee );
}
}
/**
* Remove all fees.
*
* @since 3.2.0
*/
public function remove_all_fees() {
$this->set_fees();
}
/**
* Sort fees by amount.
*
* @param WC_Coupon $a Coupon object.
* @param WC_Coupon $b Coupon object.
* @return int
*/
protected function sort_fees_callback( $a, $b ) {
return ( $a->amount > $b->amount ) ? -1 : 1;
}
/**
* Generate a unique ID for the fee being added.
*
* @param string $fee Fee object.
* @return string fee key.
*/
private function generate_id( $fee ) {
return sanitize_title( $fee->name );
}
}

View File

@ -87,13 +87,14 @@ final class WC_Cart_Session {
} else {
// Put session data into array. Run through filter so other plugins can load their own session data.
$session_data = array_merge( $values, array( 'data' => $product ) );
$session_data = array_merge( $values, array( 'data' => $product ) );
$cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key );
// Add to cart right away so the product is visible in woocommerce_get_cart_item_from_session hook.
$this->cart->set_cart_contents( $cart_contents );
}
}
}
$this->cart->set_cart_contents( $cart_contents );
}
do_action( 'woocommerce_cart_loaded_from_session', $this->cart );

View File

@ -230,6 +230,34 @@ final class WC_Cart_Totals {
}
}
/**
* Get item costs grouped by tax class.
*
* @since 3.2.0
* @return array
*/
protected function get_tax_class_costs() {
$item_tax_classes = wp_list_pluck( $this->items, 'tax_class' );
$shipping_tax_classes = wp_list_pluck( $this->shipping, 'tax_class' );
$fee_tax_classes = wp_list_pluck( $this->fees, 'tax_class' );
$costs = array_fill_keys( $item_tax_classes + $shipping_tax_classes + $fee_tax_classes, 0 );
$costs['non-taxable'] = 0;
foreach ( $this->items + $this->fees + $this->shipping as $item ) {
if ( 0 > $item->total ) {
continue;
}
if ( ! $item->taxable ) {
$costs['non-taxable'] += $item->total;
} elseif ( 'inherit' === $item->tax_class ) {
$costs[ reset( $item_tax_classes ) ] += $item->total;
} else {
$costs[ $item->tax_class ] += $item->total;
}
}
return array_filter( $costs );
}
/**
* Get fee objects from the cart. Normalises data
* into the same format for use by this class.
@ -240,6 +268,8 @@ final class WC_Cart_Totals {
$this->fees = array();
$this->cart->calculate_fees();
$fee_running_total = 0;
foreach ( $this->cart->get_fees() as $fee_key => $fee_object ) {
$fee = $this->get_default_fee_props();
$fee->object = $fee_object;
@ -247,15 +277,50 @@ final class WC_Cart_Totals {
$fee->taxable = $fee->object->taxable;
$fee->total = wc_add_number_precision_deep( $fee->object->amount );
if ( $this->calculate_tax && $fee->object->taxable ) {
$fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class, $this->cart->get_customer() ), false );
$fee->total_tax = array_sum( $fee->taxes );
// Negative fees should not make the order total go negative.
if ( 0 > $fee->total ) {
$max_discount = round( $this->get_total( 'items_total', true ) + $fee_running_total + $this->get_total( 'shipping_total', true ) ) * -1;
if ( ! $this->round_at_subtotal() ) {
$fee->total_tax = wc_round_tax_total( $fee->total_tax, wc_get_rounding_precision() );
if ( $fee->total < $max_discount ) {
$fee->total = $max_discount;
}
}
$fee_running_total += $fee->total;
if ( $this->calculate_tax ) {
if ( 0 > $fee->total ) {
// Negative fees should have the taxes split between all items so it works as a true discount.
$tax_class_costs = $this->get_tax_class_costs();
$total_cost = array_sum( $tax_class_costs );
if ( $total_cost ) {
foreach ( $tax_class_costs as $tax_class => $tax_class_cost ) {
if ( 'non-taxable' === $tax_class ) {
continue;
}
$proportion = $tax_class_cost / $total_cost;
$cart_discount_proportion = $fee->total * $proportion;
$fee->taxes = wc_array_merge_recursive_numeric( $fee->taxes, WC_Tax::calc_tax( $fee->total * $proportion, WC_Tax::get_rates( $tax_class ) ) );
}
}
} elseif ( $fee->object->taxable ) {
$fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->tax_class, $this->cart->get_customer() ), false );
}
}
$fee->total_tax = array_sum( $fee->taxes );
if ( ! $this->round_at_subtotal() ) {
$fee->total_tax = wc_round_tax_total( $fee->total_tax, wc_get_rounding_precision() );
}
// Set totals within object.
$fee->object->total = wc_remove_number_precision_deep( $fee->total );
$fee->object->tax_data = wc_remove_number_precision_deep( $fee->taxes );
$fee->object->tax = wc_remove_number_precision_deep( $fee->total_tax );
$this->fees[ $fee_key ] = $fee;
}
}
@ -315,7 +380,13 @@ final class WC_Cart_Totals {
}
/**
* Sort coupons so discounts apply consistently.
* Sort coupons so discounts apply consistently across installs.
*
* In order of priority;
* - sort param
* - usage restriction
* - coupon value
* - ID
*
* @param WC_Coupon $a Coupon object.
* @param WC_Coupon $b Coupon object.
@ -323,7 +394,13 @@ final class WC_Cart_Totals {
*/
protected function sort_coupons_callback( $a, $b ) {
if ( $a->sort === $b->sort ) {
return $a->get_id() - $b->get_id();
if ( $a->get_limit_usage_to_x_items() === $b->get_limit_usage_to_x_items() ) {
if ( $a->get_amount() === $b->get_amount() ) {
return $b->get_id() - $a->get_id();
}
return ( $a->get_amount() < $b->get_amount() ) ? -1 : 1;
}
return ( $a->get_limit_usage_to_x_items() < $b->get_limit_usage_to_x_items() ) ? -1 : 1;
}
return ( $a->sort < $b->sort ) ? -1 : 1;
}
@ -551,6 +628,10 @@ final class WC_Cart_Totals {
$this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) );
$this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
$this->cart->set_cart_contents_total( $this->get_total( 'items_total' ) );
$this->cart->set_cart_contents_tax( array_sum( $this->get_merged_taxes( false, 'items' ) ) );
$this->cart->set_cart_contents_taxes( $this->get_merged_taxes( false, 'items' ) );
}
/**
@ -640,6 +721,9 @@ final class WC_Cart_Totals {
$this->coupon_discount_totals = (array) $discounts->get_discounts_by_item( true );
$this->coupon_discount_tax_totals = $coupon_discount_tax_amounts;
$this->set_total( 'discounts_total', array_sum( $this->coupon_discount_totals ) );
$this->set_total( 'discounts_tax_total', array_sum( $this->coupon_discount_tax_totals ) );
$this->cart->set_coupon_discount_totals( wc_remove_number_precision_deep( $coupon_discount_amounts ) );
$this->cart->set_coupon_discount_tax_totals( wc_remove_number_precision_deep( $coupon_discount_tax_amounts ) );
}
@ -653,14 +737,11 @@ final class WC_Cart_Totals {
*/
protected function calculate_fee_totals() {
$this->get_fees_from_cart();
$this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
$this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) );
foreach ( $this->fees as $fee_key => $fee ) {
$this->cart->fees[ $fee_key ]->tax = wc_remove_number_precision_deep( $fee->total_tax );
$this->cart->fees[ $fee_key ]->tax_data = wc_remove_number_precision_deep( $fee->taxes );
}
$this->cart->fees_api()->set_fees( wp_list_pluck( $this->fees, 'object' ) );
$this->cart->set_fee_total( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total' ) ) ) );
$this->cart->set_fee_tax( wc_remove_number_precision_deep( array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) ) );
$this->cart->set_fee_taxes( wc_remove_number_precision_deep( $this->combine_item_taxes( wp_list_pluck( $this->fees, 'taxes' ) ) ) );
@ -687,14 +768,9 @@ final class WC_Cart_Totals {
* @since 3.2.0
*/
protected function calculate_totals() {
$this->set_total( 'discounts_total', array_sum( $this->coupon_discount_totals ) );
$this->set_total( 'discounts_tax_total', array_sum( $this->coupon_discount_tax_totals ) );
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + array_sum( $this->get_merged_taxes( true ) ) ) );
// Add totals to cart object.
$this->cart->set_cart_contents_total( $this->get_total( 'items_total' ) );
$this->cart->set_cart_contents_tax( array_sum( $this->get_merged_taxes( false, 'items' ) ) );
$this->cart->set_cart_contents_taxes( $this->get_merged_taxes( false, 'items' ) );
$this->cart->set_discount_total( $this->get_total( 'discounts_total' ) );
$this->cart->set_discount_tax( $this->get_total( 'discounts_tax_total' ) );
$this->cart->set_total_tax( array_sum( $this->get_merged_taxes( false ) ) );

View File

@ -44,13 +44,6 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public $applied_coupons = array();
/**
* An array of fees.
*
* @var array
*/
public $fees = array();
/**
* Are prices in the cart displayed inc or excl tax.
*
@ -101,11 +94,19 @@ class WC_Cart extends WC_Legacy_Cart {
*/
protected $session;
/**
* Reference to the cart fees API class.
*
* @var WC_Cart_Fees
*/
protected $fees_api;
/**
* Constructor for the cart class. Loads options and hooks in the init method.
*/
public function __construct() {
$this->session = new WC_Cart_Session( $this );
$this->fees_api = new WC_Cart_Fees( $this );
$this->tax_display_cart = get_option( 'woocommerce_tax_display_cart' );
add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 );
@ -183,6 +184,17 @@ class WC_Cart extends WC_Legacy_Cart {
return empty( $this->totals ) ? $this->default_totals : $this->totals;
}
/**
* Get a total.
*
* @since 3.2.0
* @param string $key Key of element in $totals array.
* @return mixed
*/
protected function get_totals_var( $key ) {
return isset( $this->totals[ $key ] ) ? $this->totals[ $key ] : $this->default_totals[ $key ];
}
/**
* Get subtotal.
*
@ -190,7 +202,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_subtotal() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['subtotal'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'subtotal' ) );
}
/**
@ -200,7 +212,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_subtotal_tax() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['subtotal_tax'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'subtotal_tax' ) );
}
/**
@ -210,7 +222,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_discount_total() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['discount_total'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'discount_total' ) );
}
/**
@ -220,7 +232,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_discount_tax() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['discount_tax'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'discount_tax' ) );
}
/**
@ -230,7 +242,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_shipping_total() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['shipping_total'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_total' ) );
}
/**
@ -240,7 +252,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_shipping_tax() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['shipping_tax'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_tax' ) );
}
/**
@ -250,7 +262,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_cart_contents_total() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['cart_contents_total'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_total' ) );
}
/**
@ -260,7 +272,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_cart_contents_tax() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['cart_contents_tax'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_tax' ) );
}
/**
@ -271,7 +283,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_total( $context = 'view' ) {
$total = apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['total'] );
$total = apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'total' ) );
return 'view' === $context ? apply_filters( 'woocommerce_cart_total', wc_price( $total ) ) : $total;
}
@ -282,7 +294,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_total_tax() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['total_tax'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'total_tax' ) );
}
/**
@ -292,7 +304,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_fee_total() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['fee_total'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_total' ) );
}
/**
@ -302,7 +314,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return float
*/
public function get_fee_tax() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['fee_tax'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_tax' ) );
}
/**
@ -311,7 +323,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @since 3.2.0
*/
public function get_shipping_taxes() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['shipping_taxes'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_taxes' ) );
}
/**
@ -320,7 +332,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @since 3.2.0
*/
public function get_cart_contents_taxes() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['cart_contents_taxes'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_taxes' ) );
}
@ -330,7 +342,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @since 3.2.0
*/
public function get_fee_taxes() {
return apply_filters( 'woocommerce_cart_' . __METHOD__, $this->totals['fee_taxes'] );
return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_taxes' ) );
}
/*
@ -609,7 +621,6 @@ class WC_Cart extends WC_Legacy_Cart {
$this->coupon_discount_totals = array();
$this->coupon_discount_tax_totals = array();
$this->applied_coupons = array();
$this->fees = array();
$this->totals = $this->default_totals;
if ( $clear_persistent_cart ) {
@ -919,7 +930,7 @@ class WC_Cart extends WC_Legacy_Cart {
foreach ( $taxes as $key => $tax ) {
$code = WC_Tax::get_rate_code( $key );
if ( $code || apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) === $key ) {
if ( $code || $key === apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) ) {
if ( ! isset( $tax_totals[ $code ] ) ) {
$tax_totals[ $code ] = new stdClass();
$tax_totals[ $code ]->amount = 0;
@ -1195,12 +1206,12 @@ class WC_Cart extends WC_Legacy_Cart {
*/
public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) {
if ( 0 === $quantity || $quantity < 0 ) {
do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key );
do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key, $this );
unset( $this->cart_contents[ $cart_item_key ] );
} else {
$old_quantity = $this->cart_contents[ $cart_item_key ]['quantity'];
$this->cart_contents[ $cart_item_key ]['quantity'] = $quantity;
do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity );
do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity, $this );
}
if ( $refresh_totals ) {
@ -1382,7 +1393,7 @@ class WC_Cart extends WC_Legacy_Cart {
* @return bool
*/
public function needs_shipping_address() {
return apply_filters( 'woocommerce_cart_needs_shipping_address', $this->needs_shipping() === true && ! wc_ship_to_billing_address_only() );
return apply_filters( 'woocommerce_cart_needs_shipping_address', true === $this->needs_shipping() && ! wc_ship_to_billing_address_only() );
}
/**
@ -1695,61 +1706,60 @@ class WC_Cart extends WC_Legacy_Cart {
}
/**
* Add additional fee to the cart.
* Trigger an action so 3rd parties can add custom fees.
*
* Fee is an amount of money charged for a particular piece of work
* or for a particular right or service, and not supposed to be negative.
* @since 2.0.0
*/
public function calculate_fees() {
do_action( 'woocommerce_cart_calculate_fees', $this );
}
/**
* Return reference to fees API.
*
* @since 3.2.0
* @return WC_Cart_Fees
*/
public function fees_api() {
return $this->fees_api;
}
/**
* Add additional fee to the cart.
*
* This method should be called on a callback attached to the
* woocommerce_cart_calculate_fees action during cart/checkout. Fees do not
* persist.
*
* @uses WC_Cart_Fees::add_fee
* @param string $name Unique name for the fee. Multiple fees of the same name cannot be added.
* @param float $amount Fee amount (do not enter negative amounts).
* @param bool $taxable Is the fee taxable? (default: false).
* @param string $tax_class The tax class for the fee if taxable. A blank string is standard tax class. (default: '').
*/
public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) {
$new_fee_id = sanitize_title( $name );
// Only add each fee once.
foreach ( $this->fees as $fee ) {
if ( $fee->id === $new_fee_id ) {
return;
}
}
$new_fee = new stdClass();
$new_fee->id = $new_fee_id;
$new_fee->name = esc_attr( $name );
$new_fee->amount = (float) esc_attr( $amount );
$new_fee->tax_class = $tax_class;
$new_fee->taxable = $taxable ? true : false;
$new_fee->tax = 0;
$new_fee->tax_data = array();
$this->fees[] = $new_fee;
$this->fees_api()->add_fee( array(
'name' => $name,
'amount' => (float) $amount,
'taxable' => $taxable,
'tax_class' => $tax_class,
) );
}
/**
* Get fees.
* Return all added fees from the Fees API.
*
* @uses WC_Cart_Fees::get_fees
* @return array
*/
public function get_fees() {
return array_filter( (array) $this->fees );
}
$fees = $this->fees_api()->get_fees();
/**
* Calculate fees.
*/
public function calculate_fees() {
$this->fees = array();
$this->set_fee_total( 0 );
$this->set_fee_tax( 0 );
$this->set_fee_taxes( array() );
// Fire an action where developers can add their fees.
do_action( 'woocommerce_cart_calculate_fees', $this );
if ( ! empty( $this->fees ) ) {
wc_deprecated_function( 'WC_Cart->fees', '3.2', sprintf( 'Fees should only be added through the Fees API (%s)', 'WC_Cart::add_fee()' ) );
$fees = $fees + $this->fees;
}
return $fees;
}
/**

View File

@ -299,7 +299,9 @@ class WC_Checkout {
$order->{"set_{$key}"}( $value );
// Store custom fields prefixed with wither shipping_ or billing_. This is for backwards compatibility with 2.6.x.
} elseif ( 0 === stripos( $key, 'billing_' ) || 0 === stripos( $key, 'shipping_' ) ) {
// TODO: Fix conditional to only include shipping/billing address fields in a smarter way without str(i)pos.
} elseif ( ( 0 === stripos( $key, 'billing_' ) || 0 === stripos( $key, 'shipping_' ) )
&& ! in_array( $key, array( 'shipping_method', 'shipping_total', 'shipping_tax' ) ) ) {
$order->update_meta_data( '_' . $key, $value );
}
}
@ -401,8 +403,9 @@ class WC_Checkout {
$item->legacy_fee_key = $fee_key; // @deprecated For legacy actions.
$item->set_props( array(
'name' => $fee->name,
'tax_class' => $fee->taxable ? $fee->tax_class : 0,
'total' => $fee->amount,
'tax_class' => $fee->taxable ? $fee->tax_class: 0,
'amount' => $fee->amount,
'total' => $fee->total,
'total_tax' => $fee->tax,
'taxes' => array(
'total' => $fee->tax_data,
@ -493,8 +496,8 @@ class WC_Checkout {
/**
* Add coupon lines to the order.
*
* @param WC_Order $order
* @param WC_Cart $cart
* @param WC_Order $order
* @param WC_Cart $cart
*/
public function create_order_coupon_lines( &$order, $cart ) {
foreach ( $cart->get_coupons() as $code => $coupon ) {
@ -504,6 +507,7 @@ class WC_Checkout {
'discount' => $cart->get_coupon_discount_amount( $code ),
'discount_tax' => $cart->get_coupon_discount_tax_amount( $code ),
) );
$item->add_meta_data( 'coupon_data', $coupon->get_data() );
/**
* Action hook to adjust item before save.
@ -588,7 +592,7 @@ class WC_Checkout {
// BW compatibility.
$this->legacy_posted_data = $data;
return $data;
return apply_filters( 'woocommerce_checkout_posted_data', $data );
}
/**
@ -645,7 +649,7 @@ class WC_Checkout {
if ( ! is_email( $data[ $key ] ) ) {
/* translators: %s: email address */
$errors->add( 'validation', sprintf( __( '%s is not a valid email address.', 'woocommerce' ), '<strong>' . $field_label . '</strong>' ) );
$errors->add( 'validation', sprintf( __( '%s is not a valid email address.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ) );
continue;
}
}
@ -665,14 +669,14 @@ class WC_Checkout {
if ( ! in_array( $data[ $key ], $valid_state_values ) ) {
/* translators: 1: state field 2: valid states */
$errors->add( 'validation', sprintf( __( '%1$s is not valid. Please enter one of the following: %2$s', 'woocommerce' ), '<strong>' . $field_label . '</strong>', implode( ', ', $valid_states ) ) );
$errors->add( 'validation', sprintf( __( '%1$s is not valid. Please enter one of the following: %2$s', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>', implode( ', ', $valid_states ) ) );
}
}
}
if ( $required && '' === $data[ $key ] ) {
/* translators: %s: field name */
$errors->add( 'required-field', apply_filters( 'woocommerce_checkout_required_field_notice', sprintf( __( '%s is a required field.', 'woocommerce' ), '<strong>' . $field_label . '</strong>' ), $field_label ) );
$errors->add( 'required-field', apply_filters( 'woocommerce_checkout_required_field_notice', sprintf( __( '%s is a required field.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ), $field_label ) );
}
}
}

View File

@ -215,7 +215,7 @@ class WC_Countries {
*/
public function get_allowed_countries() {
if ( 'all' === get_option( 'woocommerce_allowed_countries' ) ) {
return $this->countries;
return apply_filters( 'woocommerce_countries_allowed_countries', $this->countries );
}
if ( 'all_except' === get_option( 'woocommerce_allowed_countries' ) ) {

View File

@ -46,6 +46,7 @@ class WC_Coupon extends WC_Legacy_Coupon {
'maximum_amount' => '',
'email_restrictions' => array(),
'used_by' => array(),
'virtual' => false,
);
// Coupon message codes
@ -354,6 +355,17 @@ class WC_Coupon extends WC_Legacy_Coupon {
return $this->get_prop( 'used_by', $context );
}
/**
* If the filter is added through the woocommerce_get_shop_coupon_data filter, it's virtual and not in the DB.
*
* @since 3.2.0
* @param string $context
* @return boolean
*/
public function get_virtual( $context = 'view' ) {
return (bool) $this->get_prop( 'virtual', $context );
}
/**
* Get discount amount for a cart item.
*
@ -642,6 +654,15 @@ class WC_Coupon extends WC_Legacy_Coupon {
$this->set_prop( 'used_by', array_filter( $used_by ) );
}
/**
* Set coupon virtual state.
* @param boolean $virtual Whether it is virtual or not.
* @since 3.2.0
*/
public function set_virtual( $virtual ) {
$this->set_prop( 'virtual', (bool) $virtual );
}
/*
|--------------------------------------------------------------------------
| Other Actions
@ -690,8 +711,10 @@ class WC_Coupon extends WC_Legacy_Coupon {
break;
}
}
$this->set_code( $code );
$this->set_props( $coupon );
$this->set_code( $code );
$this->set_id( 0 );
$this->set_virtual( true );
}
/**

View File

@ -17,6 +17,14 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
class WC_Discounts {
/**
* Reference to cart or order object.
*
* @since 3.2.0
* @var array
*/
protected $object;
/**
* An array of items to discount.
*
@ -37,10 +45,12 @@ class WC_Discounts {
* @param array $object Cart or order object.
*/
public function __construct( $object = array() ) {
if ( is_a( $object, 'WC_Cart' ) ) {
$this->set_items_from_cart( $object );
} elseif ( is_a( $object, 'WC_Order' ) ) {
$this->set_items_from_order( $object );
$this->object = $object;
if ( is_a( $this->object, 'WC_Cart' ) ) {
$this->set_items_from_cart( $this->object );
} elseif ( is_a( $this->object, 'WC_Order' ) ) {
$this->set_items_from_order( $this->object );
}
}
@ -90,6 +100,11 @@ class WC_Discounts {
$item->product = $order_item->get_product();
$item->quantity = $order_item->get_quantity();
$item->price = wc_add_number_precision_deep( $order_item->get_total() );
if ( $order->get_prices_include_tax() ) {
$item->price += wc_add_number_precision_deep( $order_item->get_total_tax() );
}
$this->items[ $order_item->get_id() ] = $item;
}
@ -116,7 +131,7 @@ class WC_Discounts {
*/
public function get_discount( $key, $in_cents = false ) {
$item_discount_totals = $this->get_discounts_by_item( $in_cents );
return isset( $item_discount_totals[ $key ] ) ? ( $in_cents ? $item_discount_totals[ $key ] : wc_remove_number_precision( $item_discount_totals[ $key ] ) ) : 0;
return isset( $item_discount_totals[ $key ] ) ? $item_discount_totals[ $key ] : 0;
}
/**
@ -191,14 +206,15 @@ class WC_Discounts {
*
* @since 3.2.0
* @param WC_Coupon $coupon Coupon object being applied to the items.
* @param bool $validate Set to false to skip coupon validation.
* @return bool|WP_Error True if applied or WP_Error instance in failure.
*/
public function apply_coupon( $coupon ) {
public function apply_coupon( $coupon, $validate = true ) {
if ( ! is_a( $coupon, 'WC_Coupon' ) ) {
return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
}
$is_coupon_valid = $this->is_coupon_valid( $coupon );
$is_coupon_valid = $validate ? $this->is_coupon_valid( $coupon ) : true;
if ( is_wp_error( $is_coupon_valid ) ) {
return $is_coupon_valid;
@ -225,7 +241,7 @@ class WC_Discounts {
default :
foreach ( $items_to_apply as $item ) {
$discounted_price = $this->get_discounted_price_in_cents( $item );
$price_to_discount = wc_remove_number_precision( ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price : $discounted_price );
$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->object ) ) * $item->quantity;
$discount = min( $discounted_price, $discount );
@ -284,25 +300,31 @@ class WC_Discounts {
}
foreach ( $this->items as $item ) {
if ( 0 === $this->get_discounted_price_in_cents( $item ) ) {
$item_to_apply = clone $item; // Clone the item so changes to this item do not affect the originals.
if ( 0 === $this->get_discounted_price_in_cents( $item_to_apply ) ) {
continue;
}
if ( ! $coupon->is_valid_for_product( $item->product, $item->object ) && ! $coupon->is_valid_for_cart() ) {
if ( ! $coupon->is_valid_for_product( $item_to_apply->product, $item_to_apply->object ) && ! $coupon->is_valid_for_cart() ) {
continue;
}
if ( $limit_usage_qty && $applied_count > $limit_usage_qty ) {
if ( $limit_usage_qty && $applied_count >= $limit_usage_qty ) {
break;
}
if ( $limit_usage_qty && $item->quantity > ( $limit_usage_qty - $applied_count ) ) {
$limit_to_qty = absint( $limit_usage_qty - $applied_count );
$item->price = ( $item->price / $item->quantity ) * $limit_to_qty;
$item->quantity = $limit_to_qty; // Lower the qty so the discount is applied less.
if ( $limit_usage_qty && $item_to_apply->quantity > ( $limit_usage_qty - $applied_count ) ) {
$limit_to_qty = absint( $limit_usage_qty - $applied_count );
// Lower the qty and cost so the discount is applied less.
$item_to_apply->price = ( $item_to_apply->price / $item_to_apply->quantity ) * $limit_to_qty;
$item_to_apply->quantity = $limit_to_qty;
}
if ( 0 >= $item->quantity ) {
if ( 0 >= $item_to_apply->quantity ) {
continue;
}
$items_to_apply[] = $item;
$applied_count += $item->quantity;
$items_to_apply[] = $item_to_apply;
$applied_count += $item_to_apply->quantity;
}
return $items_to_apply;
}
@ -319,33 +341,37 @@ class WC_Discounts {
$total_discount = 0;
$cart_total = 0;
// Because get_amount() could return an empty string, let's be sure to set our local variable to a known good value.
$coupon_amount = ( '' === $coupon->get_amount() ) ? 0 : $coupon->get_amount();
foreach ( $items_to_apply as $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 = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $discounted_price : $item->price;
// Total up.
$cart_total += $price_to_discount;
// Run coupon calculations.
$discount = floor( $price_to_discount * ( $coupon->get_amount() / 100 ) );
$discount = floor( $price_to_discount * ( $coupon_amount / 100 ) );
if ( has_filter( 'woocommerce_coupon_get_discount_amount' ) ) {
if ( is_a( $this->object, 'WC_Cart' ) && has_filter( 'woocommerce_coupon_get_discount_amount' ) ) {
// Send through the legacy filter, but not as cents.
$discount = wc_add_number_precision( apply_filters( 'woocommerce_coupon_get_discount_amount', wc_remove_number_precision( $discount ), wc_remove_number_precision( $price_to_discount ), $item->object, false, $coupon ) );
}
$discount = min( $discounted_price, $discount );
$total_discount += $discount;
// Store code and discount amount per item.
$this->discounts[ $coupon->get_code() ][ $item->key ] += $discount;
}
// Work out how much discount would have been given to the cart has a whole and compare to what was discounted on all line items.
$cart_total_discount = wc_cart_round_discount( $cart_total * ( $coupon->get_amount() / 100 ), 0 );
// Work out how much discount would have been given to the cart as a whole and compare to what was discounted on all line items.
$cart_total_discount = wc_cart_round_discount( $cart_total * ( $coupon_amount / 100 ), 0 );
if ( $total_discount < $cart_total_discount ) {
$total_discount += $this->apply_coupon_remainder( $coupon, $items_to_apply, $cart_total_discount - $total_discount );
@ -372,12 +398,12 @@ class WC_Discounts {
$discounted_price = $this->get_discounted_price_in_cents( $item );
// Get the price we actually want to discount, based on settings.
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $discounted_price : $item->price;
// Run coupon calculations.
$discount = $amount * $item->quantity;
if ( has_filter( 'woocommerce_coupon_get_discount_amount' ) ) {
if ( is_a( $this->object, 'WC_Cart' ) && has_filter( 'woocommerce_coupon_get_discount_amount' ) ) {
// Send through the legacy filter, but not as cents.
$discount = wc_add_number_precision( apply_filters( 'woocommerce_coupon_get_discount_amount', wc_remove_number_precision( $discount ), wc_remove_number_precision( $price_to_discount ), $item->object, false, $coupon ) );
}
@ -450,10 +476,10 @@ class WC_Discounts {
$discounted_price = $this->get_discounted_price_in_cents( $item );
// Get the price we actually want to discount, based on settings.
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price;
$price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $discounted_price : $item->price;
// Run coupon calculations.
$discount = min( $discounted_price, 1 );
$discount = min( $price_to_discount, 1 );
// Store totals.
$total_discount += $discount;
@ -487,7 +513,7 @@ class WC_Discounts {
* @return bool
*/
protected function validate_coupon_exists( $coupon ) {
if ( ! $coupon->get_id() ) {
if ( ! $coupon->get_id() && ! $coupon->get_virtual() ) {
/* translators: %s: coupon code */
throw new Exception( sprintf( __( 'Coupon "%s" does not exist!', 'woocommerce' ), $coupon->get_code() ), 105 );
}
@ -561,10 +587,11 @@ class WC_Discounts {
* @since 3.2.0
* @throws Exception Error message.
* @param WC_Coupon $coupon Coupon data.
* @param float $subtotal Items subtotal.
* @return bool
*/
protected function validate_coupon_minimum_amount( $coupon, $subtotal = 0 ) {
protected function validate_coupon_minimum_amount( $coupon ) {
$subtotal = wc_remove_number_precision( array_sum( wp_list_pluck( $this->items, 'price' ) ) );
if ( $coupon->get_minimum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_minimum_amount', $coupon->get_minimum_amount() > $subtotal, $coupon, $subtotal ) ) {
/* translators: %s: coupon minimum amount */
throw new Exception( sprintf( __( 'The minimum spend for this coupon is %s.', 'woocommerce' ), wc_price( $coupon->get_minimum_amount() ) ), 108 );
@ -579,10 +606,11 @@ class WC_Discounts {
* @since 3.2.0
* @throws Exception Error message.
* @param WC_Coupon $coupon Coupon data.
* @param float $subtotal Items subtotal.
* @return bool
*/
protected function validate_coupon_maximum_amount( $coupon, $subtotal = 0 ) {
protected function validate_coupon_maximum_amount( $coupon ) {
$subtotal = wc_remove_number_precision( array_sum( wp_list_pluck( $this->items, 'price' ) ) );
if ( $coupon->get_maximum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_maximum_amount', $coupon->get_maximum_amount() < $subtotal, $coupon ) ) {
/* translators: %s: coupon maximum amount */
throw new Exception( sprintf( __( 'The maximum spend for this coupon is %s.', 'woocommerce' ), wc_price( $coupon->get_maximum_amount() ) ), 112 );
@ -837,7 +865,7 @@ class WC_Discounts {
* @param int $error_code Error code.
* @param WC_Coupon $coupon Coupon data.
*/
$message = apply_filters( 'woocommerce_coupon_error', $e->getMessage(), $e->getCode(), $coupon );
$message = apply_filters( 'woocommerce_coupon_error', is_numeric( $e->getMessage() ) ? $coupon->get_coupon_error( $e->getMessage() ) : $e->getMessage(), $e->getCode(), $coupon );
return new WP_Error( 'invalid_coupon', $message, array(
'status' => 400,

View File

@ -285,9 +285,7 @@ class WC_Form_Handler {
exit;
}
if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) {
define( 'WOOCOMMERCE_CHECKOUT', true );
}
wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
WC()->checkout()->process_checkout();
}
@ -308,7 +306,7 @@ class WC_Form_Handler {
$order_id = absint( $wp->query_vars['order-pay'] );
$order = wc_get_order( $order_id );
if ( $order->get_id() == $order_id && $order->get_order_key() == $order_key && $order->needs_payment() ) {
if ( $order_id === $order->get_id() && $order_key === $order->get_order_key() && $order->needs_payment() ) {
do_action( 'woocommerce_before_pay_action', $order );
@ -351,7 +349,7 @@ class WC_Form_Handler {
$available_gateways[ $payment_method ]->validate_fields();
// Process
if ( wc_notice_count( 'error' ) == 0 ) {
if ( 0 === wc_notice_count( 'error' ) ) {
$result = $available_gateways[ $payment_method ]->process_payment( $order_id );
@ -540,7 +538,7 @@ class WC_Form_Handler {
// Sanitize
$quantity = apply_filters( 'woocommerce_stock_amount_cart_item', wc_stock_amount( preg_replace( "/[^0-9\.]/", '', $cart_totals[ $cart_item_key ]['qty'] ) ), $cart_item_key );
if ( '' === $quantity || $quantity == $values['quantity'] ) {
if ( '' === $quantity || $quantity === $values['quantity'] ) {
continue;
}
@ -631,6 +629,11 @@ class WC_Form_Handler {
}
}
// Prevent reordering variable products if no selected variation.
if ( ! $variation_id && ( $product = $item->get_product() ) && $product->is_type( 'variable' ) ) {
continue;
}
// Add to cart validation
if ( ! apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variations, $cart_item_data ) ) {
continue;
@ -669,7 +672,12 @@ class WC_Form_Handler {
* Cancel a pending order.
*/
public static function cancel_order() {
if ( isset( $_GET['cancel_order'] ) && isset( $_GET['order'] ) && isset( $_GET['order_id'] ) ) {
if (
isset( $_GET['cancel_order'] ) &&
isset( $_GET['order'] ) &&
isset( $_GET['order_id'] ) &&
( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'woocommerce-cancel_order' ) )
) {
nocache_headers();
$order_key = $_GET['order'];
@ -729,30 +737,22 @@ class WC_Form_Handler {
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->get_type(), $adding_to_cart );
// Variable product handling
if ( 'variable' === $add_to_cart_handler ) {
if ( 'variable' === $add_to_cart_handler || 'variation' === $add_to_cart_handler ) {
$was_added_to_cart = self::add_to_cart_handler_variable( $product_id );
// Grouped Products
} elseif ( 'grouped' === $add_to_cart_handler ) {
$was_added_to_cart = self::add_to_cart_handler_grouped( $product_id );
// Custom Handler
} elseif ( has_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler ) ) {
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url );
// Simple Products
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url ); // Custom handler.
} else {
$was_added_to_cart = self::add_to_cart_handler_simple( $product_id );
}
// If we added the product to the cart we can now optionally do a redirect.
if ( $was_added_to_cart && wc_notice_count( 'error' ) === 0 ) {
// If has custom URL redirect there
if ( $was_added_to_cart && 0 === wc_notice_count( 'error' ) ) {
if ( $url = apply_filters( 'woocommerce_add_to_cart_redirect', $url ) ) {
wp_safe_redirect( $url );
exit;
} elseif ( get_option( 'woocommerce_cart_redirect_after_add' ) === 'yes' ) {
} elseif ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
wp_safe_redirect( wc_get_cart_url() );
exit;
}
@ -761,15 +761,16 @@ class WC_Form_Handler {
/**
* Handle adding simple products to the cart.
* @since 2.4.6 Split from add_to_cart_action
* @param int $product_id
*
* @since 2.4.6 Split from add_to_cart_action.
* @param int $product_id Product ID to add to the cart.
* @return bool success or not
*/
private static function add_to_cart_handler_simple( $product_id ) {
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( $passed_validation && WC()->cart->add_to_cart( $product_id, $quantity ) !== false ) {
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
return true;
}
@ -778,8 +779,9 @@ class WC_Form_Handler {
/**
* Handle adding grouped products to the cart.
* @since 2.4.6 Split from add_to_cart_action
* @param int $product_id
*
* @since 2.4.6 Split from add_to_cart_action.
* @param int $product_id Product ID to add to the cart.
* @return bool success or not
*/
private static function add_to_cart_handler_grouped( $product_id ) {
@ -801,7 +803,7 @@ class WC_Form_Handler {
// Suppress total recalculation until finished.
remove_action( 'woocommerce_add_to_cart', array( WC()->cart, 'calculate_totals' ), 20, 0 );
if ( $passed_validation && WC()->cart->add_to_cart( $item, $quantity ) !== false ) {
if ( $passed_validation && false !== WC()->cart->add_to_cart( $item, $quantity ) ) {
$was_added_to_cart = true;
$added_to_cart[ $item ] = $quantity;
}
@ -825,33 +827,48 @@ class WC_Form_Handler {
/**
* Handle adding variable products to the cart.
* @since 2.4.6 Split from add_to_cart_action
* @param int $product_id
*
* @since 2.4.6 Split from add_to_cart_action.
* @param int $product_id Product ID to add to the cart.
* @return bool success or not
*/
private static function add_to_cart_handler_variable( $product_id ) {
$adding_to_cart = wc_get_product( $product_id );
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( $_REQUEST['variation_id'] );
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$missing_attributes = array();
$variations = array();
$attributes = $adding_to_cart->get_attributes();
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $adding_to_cart, wp_unslash( $_POST ) );
}
// Validate the attributes.
try {
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( $_REQUEST['variation_id'] );
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$missing_attributes = array();
$variations = array();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return false;
}
// If the $product_id was in fact a variation ID, update the variables.
if ( $adding_to_cart->is_type( 'variation' ) ) {
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return false;
}
}
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $adding_to_cart, array_map( 'sanitize_title', wp_unslash( $_REQUEST ) ) );
}
// Validate the attributes.
if ( empty( $variation_id ) ) {
throw new Exception( __( 'Please choose product options&hellip;', 'woocommerce' ) );
}
$variation_data = wc_get_product_variation_attributes( $variation_id );
foreach ( $attributes as $attribute ) {
foreach ( $adding_to_cart->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
@ -859,22 +876,21 @@ class WC_Form_Handler {
$taxonomy = 'attribute_' . sanitize_title( $attribute['name'] );
if ( isset( $_REQUEST[ $taxonomy ] ) ) {
// Get value from post data
if ( $attribute['is_taxonomy'] ) {
// Don't use wc_clean as it destroys sanitized characters
$value = sanitize_title( stripslashes( $_REQUEST[ $taxonomy ] ) );
// Don't use wc_clean as it destroys sanitized characters.
$value = sanitize_title( wp_unslash( $_REQUEST[ $taxonomy ] ) );
} else {
$value = wc_clean( stripslashes( $_REQUEST[ $taxonomy ] ) );
$value = wc_clean( wp_unslash( $_REQUEST[ $taxonomy ] ) );
}
// Get valid value from variation
// Get valid value from variation data.
$valid_value = isset( $variation_data[ $taxonomy ] ) ? $variation_data[ $taxonomy ] : '';
// Allow if valid or show error.
if ( $valid_value === $value ) {
$variations[ $taxonomy ] = $value;
// If valid values are empty, this is an 'any' variation so get all possible values.
} elseif ( '' === $valid_value && in_array( $value, $attribute->get_slugs() ) ) {
// If valid values are empty, this is an 'any' variation so get all possible values.
$variations[ $taxonomy ] = $value;
} else {
throw new Exception( sprintf( __( 'Invalid value posted for %s', 'woocommerce' ), wc_attribute_label( $attribute['name'] ) ) );
@ -884,17 +900,16 @@ class WC_Form_Handler {
}
}
if ( ! empty( $missing_attributes ) ) {
throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', sizeof( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', count( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
}
} catch ( Exception $e ) {
wc_add_notice( $e->getMessage(), 'error' );
return false;
}
// Add to cart validation
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variations );
if ( $passed_validation && WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variations ) !== false ) {
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variations ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
return true;
}
@ -1061,11 +1076,6 @@ class WC_Form_Handler {
throw new Exception( $validation_error->get_error_message() );
}
// Anti-spam trap
if ( ! empty( $_POST['email_2'] ) ) {
throw new Exception( __( 'Anti-spam field was filled in.', 'woocommerce' ) );
}
$new_customer = wc_create_new_customer( sanitize_email( $email ), wc_clean( $username ), $password );
if ( is_wp_error( $new_customer ) ) {

View File

@ -17,7 +17,11 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
class WC_Install {
/** @var array DB updates and callbacks that need to be run per version */
/**
* DB updates and callbacks that need to be run per version.
*
* @var array
*/
private static $db_updates = array(
'2.0.0' => array(
'wc_update_200_file_paths',
@ -94,7 +98,11 @@ class WC_Install {
),
);
/** @var object Background update class */
/**
* Background update class.
*
* @var object
*/
private static $background_updater;
/**
@ -153,81 +161,103 @@ class WC_Install {
* Install WC.
*/
public static function install() {
global $wpdb;
if ( ! is_blog_installed() ) {
return;
}
if ( ! defined( 'WC_INSTALLING' ) ) {
define( 'WC_INSTALLING', true );
}
// Ensure needed classes are loaded
include_once( dirname( __FILE__ ) . '/admin/class-wc-admin-notices.php' );
wc_maybe_define_constant( 'WC_INSTALLING', true );
self::remove_admin_notices();
self::create_options();
self::create_tables();
self::create_roles();
self::setup_environment();
self::create_terms();
self::create_cron_jobs();
self::create_files();
self::maybe_enable_setup_wizard();
self::update_wc_version();
self::maybe_update_db_version();
// Register post types
do_action( 'woocommerce_flush_rewrite_rules' );
do_action( 'woocommerce_installed' );
}
/**
* Reset any notices added to admin.
*
* @since 3.2.0
*/
private static function remove_admin_notices() {
include_once( dirname( __FILE__ ) . '/admin/class-wc-admin-notices.php' );
WC_Admin_Notices::remove_all_notices();
}
/**
* Setup WC environment - post types, taxonomies, endpoints.
*
* @since 3.2.0
*/
private static function setup_environment() {
WC_Post_types::register_post_types();
WC_Post_types::register_taxonomies();
// Also register endpoints - this needs to be done prior to rewrite rule flush
WC()->query->init_query_vars();
WC()->query->add_endpoints();
WC_API::add_endpoint();
WC_Auth::add_endpoint();
}
self::create_terms();
self::create_cron_jobs();
self::create_files();
/**
* Is this a brand new WC install?
*
* @since 3.2.0
* @return boolean
*/
private static function is_new_install() {
return is_null( get_option( 'woocommerce_version', null ) ) && is_null( get_option( 'woocommerce_db_version', null ) );
}
// Queue upgrades/setup wizard
$current_wc_version = get_option( 'woocommerce_version', null );
$current_db_version = get_option( 'woocommerce_db_version', null );
/**
* Is a DB update needed?
*
* @since 3.2.0
* @return boolean
*/
private static function needs_db_update() {
$current_db_version = get_option( 'woocommerce_db_version', null );
$updates = self::get_db_update_callbacks();
WC_Admin_Notices::remove_all_notices();
return ! is_null( $current_db_version ) && version_compare( $current_db_version, max( array_keys( $updates ) ), '<' );
}
// No versions? This is a new install :)
if ( is_null( $current_wc_version ) && is_null( $current_db_version ) && apply_filters( 'woocommerce_enable_setup_wizard', true ) ) {
/**
* See if we need the wizard or not.
*
* @since 3.2.0
*/
private static function maybe_enable_setup_wizard() {
if ( apply_filters( 'woocommerce_enable_setup_wizard', self::is_new_install() ) ) {
WC_Admin_Notices::add_notice( 'install' );
set_transient( '_wc_activation_redirect', 1, 30 );
// No page? Let user run wizard again..
} elseif ( ! get_option( 'woocommerce_cart_page_id' ) ) {
WC_Admin_Notices::add_notice( 'install' );
}
}
if ( ! is_null( $current_db_version ) && version_compare( $current_db_version, max( array_keys( self::$db_updates ) ), '<' ) ) {
WC_Admin_Notices::add_notice( 'update' );
/**
* See if we need to show or run database updates during install.
*
* @since 3.2.0
*/
private static function maybe_update_db_version() {
if ( self::needs_db_update() ) {
if ( apply_filters( 'woocommerce_enable_auto_update_db', false ) ) {
self::init_background_updater();
self::update();
} else {
WC_Admin_Notices::add_notice( 'update' );
}
} else {
self::update_db_version();
}
self::update_wc_version();
// Flush rules after install
do_action( 'woocommerce_flush_rewrite_rules' );
delete_transient( 'wc_attribute_taxonomies' );
/*
* Deletes all expired transients. The multi-table delete syntax is used
* to delete the transient record from table a, and the corresponding
* transient_timeout record from table b.
*
* Based on code inside core's upgrade_network() function.
*/
$sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
WHERE a.option_name LIKE %s
AND a.option_name NOT LIKE %s
AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
AND b.option_value < %d";
$wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', time() ) );
// Trigger action
do_action( 'woocommerce_installed' );
}
/**
@ -308,7 +338,7 @@ class WC_Install {
$ve = get_option( 'gmt_offset' ) > 0 ? '-' : '+';
wp_schedule_event( strtotime( '00:00 tomorrow ' . $ve . get_option( 'gmt_offset' ) . ' HOURS' ), 'daily', 'woocommerce_scheduled_sales' );
wp_schedule_event( strtotime( '00:00 tomorrow ' . $ve . absint( get_option( 'gmt_offset' ) ) . ' HOURS' ), 'daily', 'woocommerce_scheduled_sales' );
$held_duration = get_option( 'woocommerce_hold_stock_minutes', '60' );
@ -796,6 +826,11 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
* Create files/directories.
*/
private static function create_files() {
// Bypass if filesystem is read-only and/or non-standard upload system is used
if ( apply_filters( 'woocommerce_install_skip_create_files', false ) ) {
return;
}
// Install files and folders for uploading files and prevent hotlinking
$upload_dir = wp_upload_dir();
$download_method = get_option( 'woocommerce_file_download_method', 'force' );

View File

@ -91,8 +91,8 @@ class WC_Legacy_API {
// REST API request.
if ( ! empty( $wp->query_vars['wc-api-version'] ) && ! empty( $wp->query_vars['wc-api-route'] ) ) {
define( 'WC_API_REQUEST', true );
define( 'WC_API_REQUEST_VERSION', absint( $wp->query_vars['wc-api-version'] ) );
wc_maybe_define_constant( 'WC_API_REQUEST', true );
wc_maybe_define_constant( 'WC_API_REQUEST_VERSION', absint( $wp->query_vars['wc-api-version'] ) );
// Legacy v1 API request.
if ( 1 === WC_API_REQUEST_VERSION ) {

View File

@ -18,12 +18,14 @@ class WC_Order_Item_Fee extends WC_Order_Item {
/**
* Order Data array. This is the core order data exposed in APIs since 3.0.0.
*
* @since 3.0.0
* @var array
*/
protected $extra_data = array(
'tax_class' => '',
'tax_status' => 'taxable',
'amount' => '',
'total' => '',
'total_tax' => '',
'taxes' => array(
@ -31,12 +33,88 @@ class WC_Order_Item_Fee extends WC_Order_Item {
),
);
/**
* Get item costs grouped by tax class.
*
* @since 3.2.0
* @param WC_Order $order Order object.
* @return array
*/
protected function get_tax_class_costs( $order ) {
$order_item_tax_classes = $order->get_items_tax_classes();
$costs = array_fill_keys( $order_item_tax_classes, 0 );
$costs['non-taxable'] = 0;
foreach ( $order->get_items( array( 'line_item', 'fee', 'shipping' ) ) as $item ) {
if ( 0 > $item->get_total() ) {
continue;
}
if ( 'taxable' !== $item->get_tax_status() ) {
$costs['non-taxable'] += $item->get_total();
} elseif ( 'inherit' === $item->get_tax_class() ) {
$inherit_class = reset( $order_item_tax_classes );
$costs[ $inherit_class ] += $item->get_total();
} else {
$costs[ $item->get_tax_class() ] += $item->get_total();
}
}
return array_filter( $costs );
}
/**
* Calculate item taxes.
*
* @since 3.2.0
* @param array $calculate_tax_for Location data to get taxes for. Required.
* @return bool True if taxes were calculated.
*/
public function calculate_taxes( $calculate_tax_for = array() ) {
if ( ! isset( $calculate_tax_for['country'], $calculate_tax_for['state'], $calculate_tax_for['postcode'], $calculate_tax_for['city'] ) ) {
return false;
}
// Use regular calculation unless the fee is negative.
if ( 0 <= $this->get_total() ) {
return parent::calculate_taxes( $calculate_tax_for );
}
if ( wc_tax_enabled() && ( $order = $this->get_order() ) ) {
// Apportion taxes to order items, shipping, and fees.
$order = $this->get_order();
$tax_class_costs = $this->get_tax_class_costs( $order );
$total_costs = array_sum( $tax_class_costs );
$discount_taxes = array();
if ( $total_costs ) {
foreach ( $tax_class_costs as $tax_class => $tax_class_cost ) {
if ( 'non-taxable' === $tax_class ) {
continue;
}
$proportion = $tax_class_cost / $total_costs;
$cart_discount_proportion = $this->get_total() * $proportion;
$discount_taxes = wc_array_merge_recursive_numeric( $discount_taxes, WC_Tax::calc_tax( $cart_discount_proportion, WC_Tax::get_rates( $tax_class ) ) );
}
}
$this->set_taxes( array( 'total' => $discount_taxes ) );
} else {
$this->set_taxes( false );
}
return true;
}
/*
|--------------------------------------------------------------------------
| Setters
|--------------------------------------------------------------------------
*/
/**
* Set fee amount.
*
* @param string $value
* @throws WC_Data_Exception
*/
public function set_amount( $value ) {
$this->set_prop( 'amount', wc_format_decimal( $value ) );
}
/**
* Set tax class.
*
@ -109,6 +187,16 @@ class WC_Order_Item_Fee extends WC_Order_Item {
|--------------------------------------------------------------------------
*/
/**
* Get fee amount.
*
* @param string $context
* @return string
*/
public function get_amount( $context = 'view' ) {
return $this->get_prop( 'amount', $context );
}
/**
* Get order item name.
*

View File

@ -265,7 +265,7 @@ class WC_Order_Item extends WC_Data implements ArrayAccess {
}
// Skip items with values already in the product details area of the product name.
if ( ! $include_all && $product && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) {
if ( ! $include_all && $product && $product->is_type( 'variation' ) && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) {
continue;
}

View File

@ -74,7 +74,7 @@ class WC_Order_Query extends WC_Object_Query {
/**
* Get orders matching the current query vars.
* @return array of WC_Order objects
* @return array|object of WC_Order objects
*/
public function get_orders() {
$args = apply_filters( 'woocommerce_order_query_args', $this->get_query_vars() );

View File

@ -1300,7 +1300,7 @@ class WC_Order extends WC_Abstract_Order {
*/
public function has_downloadable_item() {
foreach ( $this->get_items() as $item ) {
if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->is_downloadable() && $product->has_file() ) {
if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->has_file() ) {
return true;
}
}
@ -1751,7 +1751,7 @@ class WC_Order extends WC_Abstract_Order {
$refunded_item_id = (int) $refunded_item->get_meta( '_refunded_item_id' );
if ( $refunded_item_id === $item_id ) {
$taxes = $refunded_item->get_taxes();
$total += isset( $taxes['total'][ $tax_id ] ) ? $taxes['total'][ $tax_id ] : 0;
$total += isset( $taxes['total'][ $tax_id ] ) ? (float) $taxes['total'][ $tax_id ] : 0;
break;
}
}
@ -1838,6 +1838,7 @@ class WC_Order extends WC_Abstract_Order {
$total_rows = array();
$this->add_order_item_totals_subtotal_row( $total_rows, $tax_display );
$this->add_order_item_totals_discount_row( $total_rows, $tax_display );
$this->add_order_item_totals_shipping_row( $total_rows, $tax_display );
$this->add_order_item_totals_fee_rows( $total_rows, $tax_display );
$this->add_order_item_totals_tax_rows( $total_rows, $tax_display );

View File

@ -64,7 +64,7 @@ class WC_Product_Query extends WC_Object_Query {
/**
* Get products matching the current query vars.
* @return array of WC_Product objects
* @return array|object of WC_Product objects
*/
public function get_products() {
$args = apply_filters( 'woocommerce_product_object_query_args', $this->get_query_vars() );

View File

@ -64,7 +64,7 @@ class WC_Product_Variable extends WC_Product {
* Get an array of all sale and regular prices from all variations. This is used for example when displaying the price range at variable product level or seeing if the variable product is on sale.
*
* @param bool $include_taxes Should taxes be included in the prices.
* @return array() Array of RAW prices, regular prices, and sale prices with keys set to variation ID.
* @return array Array of RAW prices, regular prices, and sale prices with keys set to variation ID.
*/
public function get_variation_prices( $include_taxes = false ) {
$prices = $this->data_store->read_price_data( $this, $include_taxes );

View File

@ -291,9 +291,7 @@ class WC_Query {
}
// Define a variable so we know this is the front page shop later on
if ( ! defined( 'SHOP_IS_ON_FRONT' ) ) {
define( 'SHOP_IS_ON_FRONT', true );
}
wc_maybe_define_constant( 'SHOP_IS_ON_FRONT', true );
// Get the actual WP page to avoid errors and let us use is_front_page()
// This is hacky but works. Awaiting https://core.trac.wordpress.org/ticket/21096
@ -372,16 +370,11 @@ class WC_Query {
* @param mixed $q
*/
public function product_query( $q ) {
// Ordering query vars
if ( ! $q->is_search() ) {
$ordering = $this->get_catalog_ordering_args();
$q->set( 'orderby', $ordering['orderby'] );
$q->set( 'order', $ordering['order'] );
if ( isset( $ordering['meta_key'] ) ) {
$q->set( 'meta_key', $ordering['meta_key'] );
}
} else {
$q->set( 'orderby', 'relevance' );
$ordering = $this->get_catalog_ordering_args();
$q->set( 'orderby', $ordering['orderby'] );
$q->set( 'order', $ordering['order'] );
if ( isset( $ordering['meta_key'] ) ) {
$q->set( 'meta_key', $ordering['meta_key'] );
}
// Query vars that affect posts shown
@ -443,12 +436,18 @@ class WC_Query {
$orderby = strtolower( $orderby );
$order = strtoupper( $order );
$args = array();
$args = array(
'orderby' => 'relevance',
'order' => 'DESC',
'meta_key' => '',
);
// default - menu_order
$args['orderby'] = 'menu_order title';
$args['order'] = ( 'DESC' === $order ) ? 'DESC' : 'ASC';
$args['meta_key'] = '';
// Set to default. Menu order for non-searches, relevance for searches.
if ( ! is_search() ) {
$args['orderby'] = 'menu_order title';
$args['order'] = ( 'DESC' === $order ) ? 'DESC' : 'ASC';
$args['meta_key'] = '';
}
switch ( $orderby ) {
case 'rand' :
@ -482,6 +481,10 @@ class WC_Query {
$args['orderby'] = 'title';
$args['order'] = ( 'DESC' === $order ) ? 'DESC' : 'ASC';
break;
case 'relevance' :
$args['orderby'] = 'relevance';
$args['order'] = 'DESC';
break;
}
return apply_filters( 'woocommerce_get_catalog_ordering_args', $args );
@ -509,9 +512,27 @@ class WC_Query {
* @return array
*/
public function order_by_price_desc_post_clauses( $args ) {
global $wpdb;
$args['join'] .= " INNER JOIN ( SELECT post_id, max( meta_value+0 ) price FROM $wpdb->postmeta WHERE meta_key='_price' GROUP BY post_id ) as price_query ON $wpdb->posts.ID = price_query.post_id ";
global $wpdb, $wp_query;
if ( isset( $wp_query->queried_object, $wp_query->queried_object->term_taxonomy_id, $wp_query->queried_object->taxonomy ) && is_a( $wp_query->queried_object, 'WP_Term' ) ) {
$search_within_terms = get_term_children( $wp_query->queried_object->term_taxonomy_id, $wp_query->queried_object->taxonomy );
$search_within_terms[] = $wp_query->queried_object->term_taxonomy_id;
$args['join'] .= " INNER JOIN (
SELECT post_id, max( meta_value+0 ) price
FROM $wpdb->postmeta
INNER JOIN (
SELECT $wpdb->term_relationships.object_id
FROM $wpdb->term_relationships
WHERE 1=1
AND $wpdb->term_relationships.term_taxonomy_id IN (" . implode( ',', array_map( 'absint', $search_within_terms ) ) . ")
) as products_within_terms ON $wpdb->postmeta.post_id = products_within_terms.object_id
WHERE meta_key='_price' GROUP BY post_id ) as price_query ON $wpdb->posts.ID = price_query.post_id ";
} else {
$args['join'] .= " INNER JOIN ( SELECT post_id, max( meta_value+0 ) price FROM $wpdb->postmeta WHERE meta_key='_price' GROUP BY post_id ) as price_query ON $wpdb->posts.ID = price_query.post_id ";
}
$args['orderby'] = " price_query.price DESC ";
return $args;
}

View File

@ -1,17 +1,19 @@
<?php
/**
* Shortcodes
*
* @author Automattic
* @category Class
* @package WooCommerce/Classes
* @version 3.2.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
exit;
}
/**
* WC_Shortcodes class
*
* @class WC_Shortcodes
* @version 2.1.0
* @package WooCommerce/Classes
* @category Class
* @author WooThemes
* WooCommerce Shortcodes class.
*/
class WC_Shortcodes {
@ -45,22 +47,22 @@ class WC_Shortcodes {
add_shortcode( apply_filters( "{$shortcode}_shortcode_tag", $shortcode ), $function );
}
// Alias for pre 2.1 compatibility
// Alias for pre 2.1 compatibility.
add_shortcode( 'woocommerce_messages', __CLASS__ . '::shop_messages' );
}
/**
* Shortcode Wrapper.
*
* @param string[] $function
* @param array $atts (default: array())
* @param array $wrapper
* @param string[] $function Callback function.
* @param array $atts Attributes. Default to empty array.
* @param array $wrapper Customer wrapper data.
*
* @return string
*/
public static function shortcode_wrapper(
$function,
$atts = array(),
$atts = array(),
$wrapper = array(
'class' => 'woocommerce',
'before' => null,
@ -69,68 +71,15 @@ class WC_Shortcodes {
) {
ob_start();
// @codingStandardsIgnoreStart
echo empty( $wrapper['before'] ) ? '<div class="' . esc_attr( $wrapper['class'] ) . '">' : $wrapper['before'];
call_user_func( $function, $atts );
echo empty( $wrapper['after'] ) ? '</div>' : $wrapper['after'];
// @codingStandardsIgnoreEnd
return ob_get_clean();
}
/**
* Loop over found products.
* @param array $query_args
* @param array $atts
* @param string $loop_name
* @return string
*/
private static function product_loop( $query_args, $atts, $loop_name ) {
global $woocommerce_loop;
$columns = absint( $atts['columns'] );
$woocommerce_loop['columns'] = $columns;
$woocommerce_loop['name'] = $loop_name;
$query_args = apply_filters( 'woocommerce_shortcode_products_query', $query_args, $atts, $loop_name );
$transient_name = 'wc_loop' . substr( md5( json_encode( $query_args ) . $loop_name ), 28 ) . WC_Cache_Helper::get_transient_version( 'product_query' );
$products = get_transient( $transient_name );
if ( false === $products || ! is_a( $products, 'WP_Query' ) ) {
$products = new WP_Query( $query_args );
set_transient( $transient_name, $products, DAY_IN_SECONDS * 30 );
}
ob_start();
if ( $products->have_posts() ) {
// Prime caches before grabbing objects.
update_post_caches( $products->posts, array( 'product', 'product_variation' ) );
?>
<?php do_action( "woocommerce_shortcode_before_{$loop_name}_loop", $atts ); ?>
<?php woocommerce_product_loop_start(); ?>
<?php while ( $products->have_posts() ) : $products->the_post(); ?>
<?php wc_get_template_part( 'content', 'product' ); ?>
<?php endwhile; // end of the loop. ?>
<?php woocommerce_product_loop_end(); ?>
<?php do_action( "woocommerce_shortcode_after_{$loop_name}_loop", $atts ); ?>
<?php
} else {
do_action( "woocommerce_shortcode_{$loop_name}_loop_no_results", $atts );
}
woocommerce_reset_loop();
wp_reset_postdata();
return '<div class="woocommerce columns-' . $columns . '">' . ob_get_clean() . '</div>';
}
/**
* Cart page shortcode.
*
@ -143,7 +92,7 @@ class WC_Shortcodes {
/**
* Checkout page shortcode.
*
* @param mixed $atts
* @param array $atts Attributes.
* @return string
*/
public static function checkout( $atts ) {
@ -153,7 +102,7 @@ class WC_Shortcodes {
/**
* Order tracking page shortcode.
*
* @param mixed $atts
* @param array $atts Attributes.
* @return string
*/
public static function order_tracking( $atts ) {
@ -163,7 +112,7 @@ class WC_Shortcodes {
/**
* My account page shortcode.
*
* @param mixed $atts
* @param array $atts Attributes.
* @return string
*/
public static function my_account( $atts ) {
@ -173,63 +122,43 @@ class WC_Shortcodes {
/**
* List products in a category shortcode.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function product_category( $atts ) {
$atts = shortcode_atts( array(
'per_page' => '12',
'columns' => '4',
'orderby' => 'menu_order title',
'order' => 'asc',
'category' => '', // Slugs
'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'.
), $atts, 'product_category' );
if ( ! $atts['category'] ) {
if ( empty( $atts['category'] ) ) {
return '';
}
// Default ordering args
$ordering_args = WC()->query->get_catalog_ordering_args( $atts['orderby'], $atts['order'] );
$meta_query = WC()->query->get_meta_query();
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'orderby' => $ordering_args['orderby'],
'order' => $ordering_args['order'],
'posts_per_page' => $atts['per_page'],
'meta_query' => $meta_query,
'tax_query' => WC()->query->get_tax_query(),
);
$atts = array_merge( array(
'limit' => '12',
'columns' => '4',
'orderby' => 'menu_order title',
'order' => 'ASC',
'category' => '',
'cat_operator' => 'IN',
), (array) $atts );
$query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] );
$shortcode = new WC_Shortcode_Products( $atts, 'product_category' );
if ( isset( $ordering_args['meta_key'] ) ) {
$query_args['meta_key'] = $ordering_args['meta_key'];
}
$return = self::product_loop( $query_args, $atts, 'product_cat' );
// Remove ordering query arguments
WC()->query->remove_ordering_args();
return $return;
return $shortcode->get_content();
}
/**
* List all (or limited) product categories.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function product_categories( $atts ) {
global $woocommerce_loop;
if ( isset( $atts['number'] ) ) {
$atts['limit'] = $atts['number'];
}
$atts = shortcode_atts( array(
'number' => null,
'limit' => '-1',
'orderby' => 'name',
'order' => 'ASC',
'columns' => '4',
@ -241,7 +170,7 @@ class WC_Shortcodes {
$ids = array_filter( array_map( 'trim', explode( ',', $atts['ids'] ) ) );
$hide_empty = ( true === $atts['hide_empty'] || 'true' === $atts['hide_empty'] || 1 === $atts['hide_empty'] || '1' === $atts['hide_empty'] ) ? 1 : 0;
// get terms and workaround WP bug with parents/pad counts
// Get terms and workaround WP bug with parents/pad counts.
$args = array(
'orderby' => $atts['orderby'],
'order' => $atts['order'],
@ -254,19 +183,22 @@ class WC_Shortcodes {
$product_categories = get_terms( 'product_cat', $args );
if ( '' !== $atts['parent'] ) {
$product_categories = wp_list_filter( $product_categories, array( 'parent' => $atts['parent'] ) );
$product_categories = wp_list_filter( $product_categories, array(
'parent' => $atts['parent'],
) );
}
if ( $hide_empty ) {
foreach ( $product_categories as $key => $category ) {
if ( 0 == $category->count ) {
if ( 0 === $category->count ) {
unset( $product_categories[ $key ] );
}
}
}
if ( $atts['number'] ) {
$product_categories = array_slice( $product_categories, 0, $atts['number'] );
$atts['limit'] = '-1' === $atts['limit'] ? null : intval( $atts['limit'] );
if ( $atts['limit'] ) {
$product_categories = array_slice( $product_categories, 0, $atts['limit'] );
}
$columns = absint( $atts['columns'] );
@ -294,80 +226,52 @@ class WC_Shortcodes {
/**
* Recent Products shortcode.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function recent_products( $atts ) {
$atts = shortcode_atts( array(
'per_page' => '12',
'columns' => '4',
'orderby' => 'date',
'order' => 'desc',
'category' => '', // Slugs
'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'.
), $atts, 'recent_products' );
$atts = array_merge( array(
'limit' => '12',
'columns' => '4',
'orderby' => 'date',
'order' => 'DESC',
'category' => '',
'cat_operator' => 'IN',
), (array) $atts );
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'posts_per_page' => $atts['per_page'],
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'meta_query' => WC()->query->get_meta_query(),
'tax_query' => WC()->query->get_tax_query(),
);
$shortcode = new WC_Shortcode_Products( $atts, 'recent_products' );
$query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] );
return self::product_loop( $query_args, $atts, 'recent_products' );
return $shortcode->get_content();
}
/**
* List multiple products shortcode.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function products( $atts ) {
$atts = shortcode_atts( array(
'columns' => '4',
'orderby' => 'title',
'order' => 'asc',
'ids' => '',
'skus' => '',
), $atts, 'products' );
$atts = (array) $atts;
$type = 'products';
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'posts_per_page' => -1,
'meta_query' => WC()->query->get_meta_query(),
'tax_query' => WC()->query->get_tax_query(),
);
if ( ! empty( $atts['skus'] ) ) {
$query_args['meta_query'][] = array(
'key' => '_sku',
'value' => array_map( 'trim', explode( ',', $atts['skus'] ) ),
'compare' => 'IN',
);
// Allow list product based on specific cases.
if ( isset( $atts['on_sale'] ) && wc_string_to_bool( $atts['on_sale'] ) ) {
$type = 'sale_products';
} elseif ( isset( $atts['best_selling'] ) && wc_string_to_bool( $atts['best_selling'] ) ) {
$type = 'best_selling_products';
} elseif ( isset( $atts['top_rated'] ) && wc_string_to_bool( $atts['top_rated'] ) ) {
$type = 'top_rated_products';
}
if ( ! empty( $atts['ids'] ) ) {
$query_args['post__in'] = array_map( 'trim', explode( ',', $atts['ids'] ) );
}
$shortcode = new WC_Shortcode_Products( $atts, $type );
return self::product_loop( $query_args, $atts, 'products' );
return $shortcode->get_content();
}
/**
* Display a single product.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function product( $atts ) {
@ -375,62 +279,18 @@ class WC_Shortcodes {
return '';
}
$meta_query = WC()->query->get_meta_query();
$atts['skus'] = isset( $atts['sku'] ) ? $atts['sku'] : '';
$atts['ids'] = isset( $atts['id'] ) ? $atts['id'] : '';
$atts['limit'] = '1';
$shortcode = new WC_Shortcode_Products( (array) $atts, 'product' );
$args = array(
'post_type' => 'product',
'posts_per_page' => 1,
'no_found_rows' => 1,
'post_status' => 'publish',
'meta_query' => $meta_query,
'tax_query' => WC()->query->get_tax_query(),
);
if ( isset( $atts['sku'] ) ) {
$args['meta_query'][] = array(
'key' => '_sku',
'value' => $atts['sku'],
'compare' => '=',
);
}
if ( isset( $atts['id'] ) ) {
$args['p'] = $atts['id'];
}
ob_start();
$products = new WP_Query( apply_filters( 'woocommerce_shortcode_products_query', $args, $atts, null ) );
if ( $products->have_posts() ) : ?>
<?php woocommerce_product_loop_start(); ?>
<?php while ( $products->have_posts() ) : $products->the_post(); ?>
<?php wc_get_template_part( 'content', 'product' ); ?>
<?php endwhile; // end of the loop. ?>
<?php woocommerce_product_loop_end(); ?>
<?php endif;
wp_reset_postdata();
$css_class = 'woocommerce';
if ( isset( $atts['class'] ) ) {
$css_class .= ' ' . $atts['class'];
}
return '<div class="' . esc_attr( $css_class ) . '">' . ob_get_clean() . '</div>';
return $shortcode->get_content();
}
/**
* Display a single product price + cart button.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function product_add_to_cart( $atts ) {
@ -458,27 +318,29 @@ class WC_Shortcodes {
return '';
}
$product = is_object( $product_data ) && in_array( $product_data->post_type, array( 'product', 'product_variation' ) ) ? wc_setup_product_data( $product_data ) : false;
$product = is_object( $product_data ) && in_array( $product_data->post_type, array( 'product', 'product_variation' ), true ) ? wc_setup_product_data( $product_data ) : false;
if ( ! $product ) {
return '';
}
$styles = empty( $atts['style'] ) ? '' : ' style="' . esc_attr( $atts['style'] ) . '"';
ob_start();
?>
<p class="product woocommerce add_to_cart_inline <?php echo esc_attr( $atts['class'] ); ?>"<?php echo $styles; ?>>
<?php if ( 'true' == $atts['show_price'] ) : ?>
<?php echo $product->get_price_html(); ?>
<?php endif; ?>
echo '<p class="product woocommerce add_to_cart_inline ' . esc_attr( $atts['class'] ) . '" style="' . ( empty( $atts['style'] ) ? '' : esc_attr( $atts['style'] ) ) . '">';
<?php woocommerce_template_loop_add_to_cart( array( 'quantity' => $atts['quantity'] ) ); ?>
if ( wc_string_to_bool( $atts['show_price'] ) ) {
// @codingStandardsIgnoreStart
echo $product->get_price_html();
// @codingStandardsIgnoreEnd
}
</p><?php
woocommerce_template_loop_add_to_cart( array(
'quantity' => $atts['quantity'],
) );
// Restore Product global in case this is shown inside a product post
echo '</p>';
// Restore Product global in case this is shown inside a product post.
wc_setup_product_data( $post );
return ob_get_clean();
@ -487,7 +349,7 @@ class WC_Shortcodes {
/**
* Get the add to cart URL for a product.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function product_add_to_cart_url( $atts ) {
@ -504,7 +366,7 @@ class WC_Shortcodes {
return '';
}
$product = is_object( $product_data ) && in_array( $product_data->post_type, array( 'product', 'product_variation' ) ) ? wc_setup_product_data( $product_data ) : false;
$product = is_object( $product_data ) && in_array( $product_data->post_type, array( 'product', 'product_variation' ), true ) ? wc_setup_product_data( $product_data ) : false;
if ( ! $product ) {
return '';
@ -518,149 +380,91 @@ class WC_Shortcodes {
/**
* List all products on sale.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function sale_products( $atts ) {
$atts = shortcode_atts( array(
'per_page' => '12',
'columns' => '4',
'orderby' => 'title',
'order' => 'asc',
'category' => '', // Slugs
'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'.
), $atts, 'sale_products' );
$atts = array_merge( array(
'limit' => '12',
'columns' => '4',
'orderby' => 'title',
'order' => 'ASC',
'category' => '',
'cat_operator' => 'IN',
), (array) $atts );
$query_args = array(
'posts_per_page' => $atts['per_page'],
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'no_found_rows' => 1,
'post_status' => 'publish',
'post_type' => 'product',
'meta_query' => WC()->query->get_meta_query(),
'tax_query' => WC()->query->get_tax_query(),
'post__in' => array_merge( array( 0 ), wc_get_product_ids_on_sale() ),
);
$shortcode = new WC_Shortcode_Products( $atts, 'sale_products' );
$query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] );
return self::product_loop( $query_args, $atts, 'sale_products' );
return $shortcode->get_content();
}
/**
* List best selling products on sale.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function best_selling_products( $atts ) {
$atts = shortcode_atts( array(
'per_page' => '12',
'columns' => '4',
'category' => '', // Slugs
'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'.
), $atts, 'best_selling_products' );
$atts = array_merge( array(
'limit' => '12',
'columns' => '4',
'category' => '',
'cat_operator' => 'IN',
), (array) $atts );
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'posts_per_page' => $atts['per_page'],
'meta_key' => 'total_sales',
'orderby' => 'meta_value_num',
'meta_query' => WC()->query->get_meta_query(),
'tax_query' => WC()->query->get_tax_query(),
);
$shortcode = new WC_Shortcode_Products( $atts, 'best_selling_products' );
$query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] );
return self::product_loop( $query_args, $atts, 'best_selling_products' );
return $shortcode->get_content();
}
/**
* List top rated products on sale.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function top_rated_products( $atts ) {
$atts = shortcode_atts( array(
'per_page' => '12',
'columns' => '4',
'orderby' => 'title',
'order' => 'asc',
'category' => '', // Slugs
'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'.
), $atts, 'top_rated_products' );
$atts = array_merge( array(
'limit' => '12',
'columns' => '4',
'orderby' => 'title',
'order' => 'ASC',
'category' => '',
'cat_operator' => 'IN',
), (array) $atts );
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'posts_per_page' => $atts['per_page'],
'meta_query' => WC()->query->get_meta_query(),
'tax_query' => WC()->query->get_tax_query(),
);
$shortcode = new WC_Shortcode_Products( $atts, 'top_rated_products' );
$query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] );
add_filter( 'posts_clauses', array( __CLASS__, 'order_by_rating_post_clauses' ) );
$return = self::product_loop( $query_args, $atts, 'top_rated_products' );
remove_filter( 'posts_clauses', array( __CLASS__, 'order_by_rating_post_clauses' ) );
return $return;
return $shortcode->get_content();
}
/**
* Output featured products.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function featured_products( $atts ) {
$atts = shortcode_atts( array(
'per_page' => '12',
'columns' => '4',
'orderby' => 'date',
'order' => 'desc',
'category' => '', // Slugs
'operator' => 'IN', // Possible values are 'IN', 'NOT IN', 'AND'.
), $atts, 'featured_products' );
$atts = array_merge( array(
'limit' => '12',
'columns' => '4',
'orderby' => 'date',
'order' => 'DESC',
'category' => '',
'cat_operator' => 'IN',
), (array) $atts );
$meta_query = WC()->query->get_meta_query();
$tax_query = WC()->query->get_tax_query();
$tax_query[] = array(
'taxonomy' => 'product_visibility',
'field' => 'name',
'terms' => 'featured',
'operator' => 'IN',
);
$atts['visibility'] = 'featured';
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'posts_per_page' => $atts['per_page'],
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'meta_query' => $meta_query,
'tax_query' => $tax_query,
);
$shortcode = new WC_Shortcode_Products( $atts, 'featured_products' );
$query_args = self::_maybe_add_category_args( $query_args, $atts['category'], $atts['operator'] );
return self::product_loop( $query_args, $atts, 'featured_products' );
return $shortcode->get_content();
}
/**
* Show a single product page.
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function product_page( $atts ) {
@ -698,16 +502,16 @@ class WC_Shortcodes {
$preselected_id = '0';
// check if sku is a variation
// Check if sku is a variation.
if ( isset( $atts['sku'] ) && $single_product->have_posts() && 'product_variation' === $single_product->post->post_type ) {
$variation = new WC_Product_Variation( $single_product->post->ID );
$attributes = $variation->get_attributes();
// set preselected id to be used by JS to provide context
// Set preselected id to be used by JS to provide context.
$preselected_id = $single_product->post->ID;
// get the parent product object
// Get the parent product object.
$args = array(
'posts_per_page' => 1,
'post_type' => 'product',
@ -740,7 +544,9 @@ class WC_Shortcodes {
// Backup query object so following loops think this is a product page.
$previous_wp_query = $wp_query;
// @codingStandardsIgnoreStart
$wp_query = $single_product;
// @codingStandardsIgnoreEnd
wp_enqueue_script( 'wc-single-product' );
@ -753,8 +559,10 @@ class WC_Shortcodes {
<?php
}
// restore $previous_wp_query and reset post data.
// Restore $previous_wp_query and reset post data.
// @codingStandardsIgnoreStart
$wp_query = $previous_wp_query;
// @codingStandardsIgnoreEnd
wp_reset_postdata();
return '<div class="woocommerce">' . ob_get_clean() . '</div>';
@ -772,105 +580,68 @@ class WC_Shortcodes {
}
/**
* woocommerce_order_by_rating_post_clauses function.
* Order by rating.
*
* @param array $args
* @return array
* @deprecated 3.2.0 Use WC_Shortcode_Products::order_by_rating_post_clauses().
* @param array $args Query args.
* @return array
*/
public static function order_by_rating_post_clauses( $args ) {
global $wpdb;
$args['where'] .= " AND $wpdb->commentmeta.meta_key = 'rating' ";
$args['join'] .= "LEFT JOIN $wpdb->comments ON($wpdb->posts.ID = $wpdb->comments.comment_post_ID) LEFT JOIN $wpdb->commentmeta ON($wpdb->comments.comment_ID = $wpdb->commentmeta.comment_id)";
$args['orderby'] = "$wpdb->commentmeta.meta_value DESC";
$args['groupby'] = "$wpdb->posts.ID";
return $args;
return WC_Shortcode_Products::order_by_rating_post_clauses( $args );
}
/**
* List products with an attribute shortcode.
* Example [product_attribute attribute='color' filter='black'].
* Example [product_attribute attribute="color" filter="black"].
*
* @param array $atts
* @param array $atts Attributes.
* @return string
*/
public static function product_attribute( $atts ) {
$atts = shortcode_atts( array(
'per_page' => '12',
$atts = array_merge( array(
'limit' => '12',
'columns' => '4',
'orderby' => 'title',
'order' => 'asc',
'order' => 'ASC',
'attribute' => '',
'filter' => '',
), $atts, 'product_attribute' );
'terms' => '',
), (array) $atts );
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'posts_per_page' => $atts['per_page'],
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'meta_query' => WC()->query->get_meta_query(),
'tax_query' => WC()->query->get_tax_query(),
);
if ( empty( $atts['attribute'] ) ) {
return '';
}
$query_args['tax_query'][] = array(
'taxonomy' => strstr( $atts['attribute'], 'pa_' ) ? sanitize_title( $atts['attribute'] ) : 'pa_' . sanitize_title( $atts['attribute'] ),
'terms' => array_map( 'sanitize_title', explode( ',', $atts['filter'] ) ),
'field' => 'slug',
);
$shortcode = new WC_Shortcode_Products( $atts, 'product_attribute' );
return self::product_loop( $query_args, $atts, 'product_attribute' );
return $shortcode->get_content();
}
/**
* List related products.
* @param array $atts
*
* @param array $atts Attributes.
* @return string
*/
public static function related_products( $atts ) {
if ( isset( $atts['per_page'] ) ) {
$atts['limit'] = $atts['per_page'];
}
// @codingStandardsIgnoreStart
$atts = shortcode_atts( array(
'per_page' => '4',
'limit' => '4',
'columns' => '4',
'orderby' => 'rand',
), $atts, 'related_products' );
// @codingStandardsIgnoreEnd
ob_start();
// Rename arg
$atts['posts_per_page'] = absint( $atts['per_page'] );
// Rename arg.
$atts['posts_per_page'] = absint( $atts['limit'] );
woocommerce_related_products( $atts );
return ob_get_clean();
}
/**
* Adds a tax_query index to the query to filter by category.
*
* @param array $args
* @param string $category
* @param string $operator
* @return array;
* @access private
*/
private static function _maybe_add_category_args( $args, $category, $operator ) {
if ( ! empty( $category ) ) {
if ( empty( $args['tax_query'] ) ) {
$args['tax_query'] = array();
}
$args['tax_query'][] = array(
array(
'taxonomy' => 'product_cat',
'terms' => array_map( 'sanitize_title', explode( ',', $category ) ),
'field' => 'slug',
'operator' => $operator,
),
);
}
return $args;
}
}

View File

@ -816,7 +816,11 @@ class WC_Tax {
private static function prepare_tax_rate( $tax_rate ) {
foreach ( $tax_rate as $key => $value ) {
if ( method_exists( __CLASS__, 'format_' . $key ) ) {
$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), $value );
if ( 'tax_rate_state' === $key ) {
$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), sanitize_key( $value ) );
} else {
$tax_rate[ $key ] = call_user_func( array( __CLASS__, 'format_' . $key ), $value );
}
}
}
return $tax_rate;

View File

@ -97,6 +97,10 @@ class WC_Template_Loader {
$search_files = apply_filters( 'woocommerce_template_loader_files', array(), $default_file );
$search_files[] = 'woocommerce.php';
if ( is_page_template() ) {
$search_files[] = get_page_template_slug();
}
if ( is_product_taxonomy() ) {
$term = get_queried_object();
$search_files[] = 'taxonomy-' . $term->taxonomy . '-' . $term->slug . '.php';

View File

@ -248,9 +248,8 @@ class WC_CLI_REST_Command {
* @return array
*/
private function do_request( $method, $route, $assoc_args ) {
if ( ! defined( 'REST_REQUEST' ) ) {
define( 'REST_REQUEST', true );
}
wc_maybe_define_constant( 'REST_REQUEST', true );
$request = new WP_REST_Request( $method, $route );
if ( in_array( $method, array( 'POST', 'PUT' ) ) ) {
$request->set_body_params( $assoc_args );

View File

@ -233,9 +233,7 @@ class WC_CLI_Runner {
$before_invoke = null;
if ( empty( $command_args['when'] ) && \WP_CLI::get_config( 'debug' ) ) {
$before_invoke = function() {
if ( ! defined( 'SAVEQUERIES' ) ) {
define( 'SAVEQUERIES', true );
}
wc_maybe_define_constant( 'SAVEQUERIES', true );
};
}

View File

@ -81,9 +81,7 @@ class WC_CLI_Tool_Command {
$before_invoke = null;
if ( empty( $command_args['when'] ) && WP_CLI::get_config( 'debug' ) ) {
$before_invoke = function() {
if ( ! defined( 'SAVEQUERIES' ) ) {
define( 'SAVEQUERIES', true );
}
wc_maybe_define_constant( 'SAVEQUERIES', true );
};
}

View File

@ -185,6 +185,9 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
if ( $args['force_delete'] ) {
wp_delete_post( $id );
wp_cache_delete( WC_Cache_Helper::get_cache_prefix( 'coupons' ) . 'coupon_id_from_code_' . $coupon->get_code(), 'coupons' );
$coupon->set_id( 0 );
do_action( 'woocommerce_delete_coupon', $id );
} else {

View File

@ -375,6 +375,11 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
* @return array
*/
public function search_customers( $term, $limit = '' ) {
$results = apply_filters( 'woocommerce_customer_pre_search_customers', false, $term, $limit );
if ( is_array( $results ) ) {
return $results;
}
$query = new WP_User_Query( apply_filters( 'woocommerce_customer_search_customers', array(
'search' => '*' . esc_attr( $term ) . '*',
'search_columns' => array( 'user_login', 'user_url', 'user_email', 'user_nicename', 'display_name' ),
@ -400,7 +405,7 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
),
), $term, $limit, 'meta_query' ) );
$results = wp_parse_id_list( array_merge( $query->get_results(), $query2->get_results() ) );
$results = wp_parse_id_list( array_merge( (array) $query->get_results(), (array) $query2->get_results() ) );
if ( $limit && count( $results ) > $limit ) {
$results = array_slice( $results, 0, $limit );

View File

@ -66,7 +66,6 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
'_shipping_address_index',
'_recorded_sales',
'_recorded_coupon_usage_counts',
'_shipping_method',
);
/**

View File

@ -14,10 +14,11 @@ class WC_Order_Item_Fee_Data_Store extends Abstract_WC_Order_Item_Type_Data_Stor
/**
* Data stored in meta keys.
*
* @since 3.0.0
* @var array
*/
protected $internal_meta_keys = array( '_tax_class', '_tax_status', '_line_subtotal', '_line_subtotal_tax', '_line_total', '_line_tax', '_line_tax_data' );
protected $internal_meta_keys = array( '_fee_amount', '_tax_class', '_tax_status', '_line_subtotal', '_line_subtotal_tax', '_line_total', '_line_tax', '_line_tax_data' );
/**
* Read/populate data properties specific to this order item.
@ -29,6 +30,7 @@ class WC_Order_Item_Fee_Data_Store extends Abstract_WC_Order_Item_Type_Data_Stor
parent::read( $item );
$id = $item->get_id();
$item->set_props( array(
'amount' => get_metadata( 'order_item', $id, '_fee_amount', true ),
'tax_class' => get_metadata( 'order_item', $id, '_tax_class', true ),
'tax_status' => get_metadata( 'order_item', $id, '_tax_status', true ),
'total' => get_metadata( 'order_item', $id, '_line_total', true ),
@ -47,6 +49,7 @@ class WC_Order_Item_Fee_Data_Store extends Abstract_WC_Order_Item_Type_Data_Stor
public function save_item_data( &$item ) {
$id = $item->get_id();
$save_values = array(
'_fee_amount' => $item->get_amount( 'edit' ),
'_tax_class' => $item->get_tax_class( 'edit' ),
'_tax_status' => $item->get_tax_status( 'edit' ),
'_line_total' => $item->get_total( 'edit' ),

View File

@ -85,7 +85,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* @param WC_Product $product
*/
public function create( &$product ) {
if ( ! $product->get_date_created() ) {
if ( ! $product->get_date_created( 'edit' ) ) {
$product->set_date_created( current_time( 'timestamp', true ) );
}
@ -544,10 +544,16 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* Handle updated meta props after updating meta data.
*
* @since 3.0.0
* @param WC_Product $product
* @param WC_Product $product Product Object.
*/
protected function handle_updated_props( &$product ) {
if ( in_array( 'date_on_sale_from', $this->updated_props ) || in_array( 'date_on_sale_to', $this->updated_props ) || in_array( 'regular_price', $this->updated_props ) || in_array( 'sale_price', $this->updated_props ) ) {
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' ) );
@ -557,11 +563,11 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
}
}
if ( in_array( 'stock_quantity', $this->updated_props ) ) {
if ( in_array( 'stock_quantity', $this->updated_props, true ) ) {
do_action( $product->is_type( 'variation' ) ? 'woocommerce_variation_set_stock' : 'woocommerce_product_set_stock' , $product );
}
if ( in_array( 'stock_status', $this->updated_props ) ) {
if ( in_array( 'stock_status', $this->updated_props, true ) ) {
do_action( $product->is_type( 'variation' ) ? 'woocommerce_variation_set_stock_status' : 'woocommerce_product_set_stock_status' , $product->get_id(), $product->get_stock_status(), $product );
}
@ -735,6 +741,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
// Action for the transition.
if ( $old_type !== $new_type ) {
$this->updated_props[] = 'product_type';
do_action( 'woocommerce_product_type_changed', $product, $old_type, $new_type );
}
}
@ -840,12 +847,13 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* Return product ID based on SKU.
*
* @since 3.0.0
* @param string $sku
* @param string $sku Product SKU.
* @return int
*/
public function get_product_id_by_sku( $sku ) {
global $wpdb;
return $wpdb->get_var( $wpdb->prepare( "
$id = $wpdb->get_var( $wpdb->prepare( "
SELECT posts.ID
FROM $wpdb->posts AS posts
LEFT JOIN $wpdb->postmeta AS postmeta ON ( posts.ID = postmeta.post_id )
@ -855,6 +863,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
AND postmeta.meta_value = '%s'
LIMIT 1
", $sku ) );
return (int) apply_filters( 'woocommerce_get_product_id_by_sku', $id, $sku );
}
/**
@ -1534,7 +1544,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
if ( isset( $query_vars['return'] ) && 'objects' === $query_vars['return'] && ! empty( $query->posts ) ) {
// Prime caches before grabbing objects.
update_post_caches( $products->posts, array( 'product', 'product_variation' ) );
update_post_caches( $query->posts, array( 'product', 'product_variation' ) );
}
$products = ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) ? $query->posts : array_filter( array_map( 'wc_get_product', $query->posts ) );

View File

@ -19,20 +19,6 @@ if ( ! class_exists( 'WC_Email_Customer_Invoice', false ) ) :
*/
class WC_Email_Customer_Invoice extends WC_Email {
/**
* Strings to find in subjects/headings.
*
* @var array
*/
public $find;
/**
* Strings to replace in subjects/headings.
*
* @var array
*/
public $replace;
/**
* Constructor.
*/

View File

@ -190,7 +190,7 @@ class WC_Email extends WC_Settings_API {
*/
protected $placeholders = array();
/**
/**
* Strings to find in subjects/headings.
*
* @deprecated 3.2.0 in favour of placeholders
@ -236,7 +236,7 @@ class WC_Email extends WC_Settings_API {
/**
* Handle multipart mail.
*
* @param PHPMailer $mailer
* @param PHPMailer $mailer
* @return PHPMailer
*/
public function handle_multipart( $mailer ) {
@ -250,13 +250,37 @@ class WC_Email extends WC_Settings_API {
/**
* Format email string.
*
* @param mixed $string
* @param mixed $string Text to replace placeholders in.
* @return string
*/
public function format_string( $string ) {
// handle legacy find and replace.
$string = str_replace( $this->find, $this->replace, $string );
return str_replace( apply_filters( 'woocommerce_email_format_string_find', array_keys( $this->placeholders ), $this ), apply_filters( 'woocommerce_email_format_string_replace', array_values( $this->placeholders ), $this ), $string );
$find = array_keys( $this->placeholders );
$replace = array_values( $this->placeholders );
// If using legacy find replace, add those to our find/replace arrays first. @todo deprecate in 4.0.0.
$find = array_merge( (array) $this->find, $find );
$replace = array_merge( (array) $this->replace, $replace );
// If using the older style filters for find and replace, ensure the array is associative and then pass through filters. @todo deprecate in 4.0.0.
if ( has_filter( 'woocommerce_email_format_string_replace' ) || has_filter( 'woocommerce_email_format_string_find' ) ) {
$legacy_find = $this->find;
$legacy_replace = $this->replace;
foreach ( $this->placeholders as $find => $replace ) {
$legacy_key = sanitize_title( str_replace( '_', '-', trim( $find, '{}' ) ) );
$legacy_find[ $legacy_key ] = $find;
$legacy_replace[ $legacy_key ] = $replace;
}
$string = str_replace( apply_filters( 'woocommerce_email_format_string_find', $legacy_find, $this ), apply_filters( 'woocommerce_email_format_string_replace', $legacy_replace, $this ), $string );
}
/**
* woocommerce_email_format_string filter for main find/replace code.
*
* @since 3.2.0
*/
return apply_filters( 'woocommerce_email_format_string', str_replace( $find, $replace, $string ), $this );
}
/**

View File

@ -114,6 +114,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
'cross_sell_ids' => __( 'Cross-sells', 'woocommerce' ),
'product_url' => __( 'External URL', 'woocommerce' ),
'button_text' => __( 'Button text', 'woocommerce' ),
'menu_order' => __( 'Position', 'woocommerce' ),
) );
}

View File

@ -525,6 +525,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
'download_limit' => 'absint',
'download_expiry' => 'absint',
'product_url' => 'esc_url_raw',
'menu_order' => 'intval',
);
/**
@ -581,12 +582,6 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
protected function expand_data( $data ) {
$data = apply_filters( 'woocommerce_product_importer_pre_expand_data', $data );
// Status is mapped from a special published field.
if ( isset( $data['published'] ) ) {
$data['status'] = ( $data['published'] ? 'publish' : 'draft' );
unset( $data['published'] );
}
// Images field maps to image and gallery id fields.
if ( isset( $data['images'] ) ) {
$images = $data['images'];
@ -608,6 +603,13 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$data['type'] = current( array_diff( $data['type'], array( 'virtual', 'downloadable' ) ) );
}
// Status is mapped from a special published field.
if ( isset( $data['published'] ) ) {
$non_published_status = isset( $data['type'] ) && 'variation' === $data['type'] ? 'private' : 'draft';
$data['status'] = ( $data['published'] ? 'publish' : $non_published_status );
unset( $data['published'] );
}
if ( isset( $data['stock_quantity'] ) ) {
if ( '' === $data['stock_quantity'] ) {
$data['manage_stock'] = false;
@ -805,6 +807,8 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
);
foreach ( $this->parsed_data as $parsed_data_key => $parsed_data ) {
do_action( 'woocommerce_product_import_before_import', $parsed_data );
$id = isset( $parsed_data['id'] ) ? absint( $parsed_data['id'] ) : 0;
$sku = isset( $parsed_data['sku'] ) ? esc_attr( $parsed_data['sku'] ) : '';
$id_exists = false;

View File

@ -58,7 +58,7 @@ abstract class WC_Legacy_Cart {
* @param mixed $value Value to set.
*/
public function __isset( $name ) {
if ( array_key_exists( $name, $cart_session_data ) ) {
if ( array_key_exists( $name, $this->cart_session_data ) || 'fees' === $name ) {
return true;
}
return false;
@ -70,60 +70,94 @@ abstract class WC_Legacy_Cart {
* @param string $name Property name.
* @return mixed
*/
public function __get( $name ) {
public function &__get( $name ) {
$value = '';
switch ( $name ) {
case 'dp' :
return wc_get_price_decimals();
$value = wc_get_price_decimals();
break;
case 'prices_include_tax' :
return wc_prices_include_tax();
$value = wc_prices_include_tax();
case 'round_at_subtotal' :
return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
// map old public props to methods.
$value = 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' );
break;
case 'cart_contents_total' :
return $this->get_cart_contents_total();
$value = $this->get_cart_contents_total();
break;
case 'total' :
return $this->get_total( 'edit' );
$value = $this->get_total( 'edit' );
break;
case 'subtotal' :
return $this->get_subtotal() + $this->get_subtotal_tax();
$value = $this->get_subtotal() + $this->get_subtotal_tax();
break;
case 'subtotal_ex_tax' :
return $this->get_subtotal();
$value = $this->get_subtotal();
break;
case 'tax_total' :
return $this->get_fee_tax() + $this->get_cart_contents_tax();
case 'taxes' :
return $this->get_taxes();
case 'shipping_taxes' :
return $this->get_shipping_taxes();
$value = $this->get_fee_tax() + $this->get_cart_contents_tax();
break;
case 'fee_total' :
return $this->get_fee_total();
$value = $this->get_fee_total();
break;
case 'discount_cart' :
return $this->get_discount_total();
$value = $this->get_discount_total();
break;
case 'discount_cart_tax' :
return $this->get_discount_tax();
$value = $this->get_discount_tax();
break;
case 'shipping_total' :
return $this->get_shipping_total();
$value = $this->get_shipping_total();
break;
case 'shipping_tax_total' :
return $this->get_shipping_tax();
case 'coupon_discount_amounts' :
return $this->get_coupon_discount_totals();
case 'coupon_discount_tax_amounts' :
return $this->get_coupon_discount_tax_totals();
$value = $this->get_shipping_tax();
break;
case 'display_totals_ex_tax' :
case 'display_cart_ex_tax' :
return 'excl' === $this->tax_display_cart;
$value = 'excl' === $this->tax_display_cart;
break;
case 'cart_contents_weight' :
return $this->get_cart_contents_weight();
$value = $this->get_cart_contents_weight();
break;
case 'cart_contents_count' :
return $this->get_cart_contents_count();
$value = $this->get_cart_contents_count();
break;
case 'coupons' :
$value = $this->get_coupons();
break;
// Arrays returned by reference to allow modification without notices. TODO: Remove in 4.0.
case 'taxes' :
wc_deprecated_function( 'WC_Cart->taxes', '3.2', sprintf( 'getters (%s) and setters (%s)', 'WC_Cart::get_cart_contents_taxes()', 'WC_Cart::set_cart_contents_taxes()' ) );
$value = &$this->totals[ 'cart_contents_taxes' ];
break;
case 'shipping_taxes' :
wc_deprecated_function( 'WC_Cart->shipping_taxes', '3.2', sprintf( 'getters (%s) and setters (%s)', 'WC_Cart::get_shipping_taxes()', 'WC_Cart::set_shipping_taxes()' ) );
$value = &$this->totals[ 'shipping_taxes' ];
break;
case 'coupon_discount_amounts' :
$value = &$this->coupon_discount_totals;
break;
case 'coupon_discount_tax_amounts' :
$value = &$this->coupon_discount_tax_totals;
break;
case 'fees' :
$this->fees = $this->get_fees();
$value = &$this->fees;
break;
// Deprecated args. TODO: Remove in 4.0.
case 'tax' :
wc_deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax:: directly' );
wc_deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax directly' );
$this->tax = new WC_Tax();
return $this->tax;
$value = $this->tax;
break;
case 'discount_total':
wc_deprecated_argument( 'WC_Cart->discount_total', '2.3', 'After tax coupons are no longer supported. For more information see: https://woocommerce.wordpress.com/2014/12/upcoming-coupon-changes-in-woocommerce-2-3/' );
return 0;
case 'coupons' :
return $this->get_coupons();
$value = 0;
break;
}
return $value;
}
/**
@ -177,6 +211,9 @@ abstract class WC_Legacy_Cart {
case 'coupon_discount_tax_amounts' :
$this->set_coupon_discount_tax_totals( $value );
break;
case 'fees' :
$this->fees_api->set_fees( $value );
break;
default :
$this->$name = $value;
break;

View File

@ -192,7 +192,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
$column = 'meta_key';
}
$key = $this->identifier . '_batch_%';
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$count = $wpdb->get_var( $wpdb->prepare( "
SELECT COUNT(*)
@ -267,7 +267,7 @@ abstract class WP_Background_Process extends WP_Async_Request {
$value_column = 'meta_value';
}
$key = $this->identifier . '_batch_%';
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
$query = $wpdb->get_row( $wpdb->prepare( "
SELECT *

View File

@ -62,10 +62,8 @@ class WC_Shortcode_Cart {
* @param array $atts
*/
public static function output( $atts ) {
// Constants
if ( ! defined( 'WOOCOMMERCE_CART' ) ) {
define( 'WOOCOMMERCE_CART', true );
}
// Constants.
wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
$atts = shortcode_atts( array(), $atts, 'woocommerce_cart' );

Some files were not shown because too many files have changed in this diff Show More