Merge branch 'master' into e2e-shopper-cart-apply-coupon

This commit is contained in:
Ron Rennick 2021-01-25 12:24:19 -04:00
commit 0eafe83be6
135 changed files with 7433 additions and 12903 deletions

4
.gitignore vendored
View File

@ -24,8 +24,7 @@ none
/assets/css/photoswipe/**/*.min.css
# Minified JS
/assets/js/admin/*.min.js
/assets/js/frontend/*.min.js
/assets/js/**/*.min.js
# OS X metadata
.DS_Store
@ -76,3 +75,4 @@ i18n/languages/woocommerce.pot
# Build
build/
woocommerce.zip

View File

@ -19,6 +19,7 @@ php:
- "7.2"
- "7.3"
- "7.4"
- "8.0"
env:
- WP_VERSION=latest WP_MULTISITE=0
@ -45,10 +46,10 @@ jobs:
env: WP_VERSION=nightly WP_MULTISITE=0
- name: "WP Latest - 1"
php: "7.2"
env: WP_VERSION=5.4 WP_MULTISITE=0
env: WP_VERSION=5.5 WP_MULTISITE=0
- name: "WP Latest - 2"
php: "7.2"
env: WP_VERSION=5.3 WP_MULTISITE=0
env: WP_VERSION=5.4 WP_MULTISITE=0
- name: "Code Standards"
php: "7.4"
env: WP_VERSION=latest WP_MULTISITE=0 RUN_PHPCS=1
@ -87,6 +88,14 @@ install:
echo "xdebug.ini does not exist"
fi
- composer install
- |
if [ "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]; then
curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip
unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip
composer bin phpunit config --unset platform
composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}'
composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs
fi
- |
# Install WP Test suite:
if [[ ! -z "$WP_VERSION" ]]; then

View File

@ -45,67 +45,18 @@ module.exports = function( grunt ) {
comments : /@license|@preserve|^!/
}
},
admin: {
js_assets: {
files: [{
expand: true,
cwd: '<%= dirs.js %>/admin/',
cwd: '<%= dirs.js %>/',
src: [
'*.js',
'!*.min.js'
'**/*.js',
'!**/*.min.js'
],
dest: '<%= dirs.js %>/admin/',
extDot: 'last',
dest: '<%= dirs.js %>',
ext: '.min.js'
}]
},
vendor: {
files: {
'<%= dirs.js %>/accounting/accounting.min.js': ['<%= dirs.js %>/accounting/accounting.js'],
'<%= dirs.js %>/jquery-blockui/jquery.blockUI.min.js': ['<%= dirs.js %>/jquery-blockui/jquery.blockUI.js'],
'<%= dirs.js %>/jquery-cookie/jquery.cookie.min.js': ['<%= dirs.js %>/jquery-cookie/jquery.cookie.js'],
'<%= dirs.js %>/js-cookie/js.cookie.min.js': ['<%= dirs.js %>/js-cookie/js.cookie.js'],
'<%= dirs.js %>/jquery-flot/jquery.flot.min.js': ['<%= dirs.js %>/jquery-flot/jquery.flot.js'],
'<%= dirs.js %>/jquery-flot/jquery.flot.pie.min.js': ['<%= dirs.js %>/jquery-flot/jquery.flot.pie.js'],
'<%= dirs.js %>/jquery-flot/jquery.flot.resize.min.js': ['<%= dirs.js %>/jquery-flot/jquery.flot.resize.js'],
'<%= dirs.js %>/jquery-flot/jquery.flot.stack.min.js': ['<%= dirs.js %>/jquery-flot/jquery.flot.stack.js'],
'<%= dirs.js %>/jquery-flot/jquery.flot.time.min.js': ['<%= dirs.js %>/jquery-flot/jquery.flot.time.js'],
'<%= dirs.js %>/jquery-payment/jquery.payment.min.js': ['<%= dirs.js %>/jquery-payment/jquery.payment.js'],
'<%= dirs.js %>/jquery-qrcode/jquery.qrcode.min.js': ['<%= dirs.js %>/jquery-qrcode/jquery.qrcode.js'],
'<%= dirs.js %>/jquery-serializejson/jquery.serializejson.min.js': [
'<%= dirs.js %>/jquery-serializejson/jquery.serializejson.js'
],
'<%= dirs.js %>/jquery-tiptip/jquery.tipTip.min.js': ['<%= dirs.js %>/jquery-tiptip/jquery.tipTip.js'],
'<%= dirs.js %>/jquery-ui-touch-punch/jquery-ui-touch-punch.min.js': [
'<%= dirs.js %>/jquery-ui-touch-punch/jquery-ui-touch-punch.js'
],
'<%= dirs.js %>/prettyPhoto/jquery.prettyPhoto.init.min.js': ['<%= dirs.js %>/prettyPhoto/jquery.prettyPhoto.init.js'],
'<%= dirs.js %>/prettyPhoto/jquery.prettyPhoto.min.js': ['<%= dirs.js %>/prettyPhoto/jquery.prettyPhoto.js'],
'<%= dirs.js %>/flexslider/jquery.flexslider.min.js': ['<%= dirs.js %>/flexslider/jquery.flexslider.js'],
'<%= dirs.js %>/zoom/jquery.zoom.min.js': ['<%= dirs.js %>/zoom/jquery.zoom.js'],
'<%= dirs.js %>/photoswipe/photoswipe.min.js': ['<%= dirs.js %>/photoswipe/photoswipe.js'],
'<%= dirs.js %>/photoswipe/photoswipe-ui-default.min.js': ['<%= dirs.js %>/photoswipe/photoswipe-ui-default.js'],
'<%= dirs.js %>/round/round.min.js': ['<%= dirs.js %>/round/round.js'],
'<%= dirs.js %>/selectWoo/selectWoo.full.min.js': ['<%= dirs.js %>/selectWoo/selectWoo.full.js'],
'<%= dirs.js %>/selectWoo/selectWoo.min.js': ['<%= dirs.js %>/selectWoo/selectWoo.js'],
'<%= dirs.js %>/stupidtable/stupidtable.min.js': ['<%= dirs.js %>/stupidtable/stupidtable.js'],
'<%= dirs.js %>/zeroclipboard/jquery.zeroclipboard.min.js': ['<%= dirs.js %>/zeroclipboard/jquery.zeroclipboard.js']
}
},
frontend: {
files: [{
expand: true,
cwd: '<%= dirs.js %>/frontend/',
src: [
'*.js',
'!*.min.js'
],
dest: '<%= dirs.js %>/frontend/',
ext: '.min.js'
}]
},
flexslider: {
files: [{
'<%= dirs.js %>/flexslider/jquery.flexslider.min.js': ['<%= dirs.js %>/flexslider/jquery.flexslider.js']
}]
}
},
@ -189,12 +140,10 @@ module.exports = function( grunt ) {
js: {
files: [
'GruntFile.js',
'<%= dirs.js %>/admin/*js',
'<%= dirs.js %>/frontend/*js',
'!<%= dirs.js %>/admin/*.min.js',
'!<%= dirs.js %>/frontend/*.min.js'
'<%= dirs.js %>/**/*.js',
'!<%= dirs.js %>/**/*.min.js'
],
tasks: ['eslint','uglify']
tasks: ['eslint','newer:uglify']
}
},
@ -244,6 +193,7 @@ module.exports = function( grunt ) {
grunt.loadNpmTasks( 'grunt-contrib-copy' );
grunt.loadNpmTasks( 'grunt-contrib-watch' );
grunt.loadNpmTasks( 'grunt-contrib-clean' );
grunt.loadNpmTasks( 'grunt-newer' );
// Register tasks.
grunt.registerTask( 'default', [
@ -253,8 +203,7 @@ module.exports = function( grunt ) {
grunt.registerTask( 'js', [
'eslint',
'uglify:admin',
'uglify:frontend'
'uglify:js_assets'
]);
grunt.registerTask( 'css', [
@ -271,9 +220,7 @@ module.exports = function( grunt ) {
]);
grunt.registerTask( 'e2e-build', [
'uglify:admin',
'uglify:frontend',
'uglify:flexslider',
'uglify:js_assets',
'css'
]);

View File

@ -181,7 +181,7 @@
@mixin icon( $glyph: "\e001" ) {
font-family: "WooCommerce";
speak: none;
speak: never;
font-weight: normal;
font-variant: normal;
text-transform: none;
@ -199,7 +199,7 @@
@mixin icon_dashicons( $glyph: "\f333" ) {
font-family: "Dashicons";
speak: none;
speak: never;
font-weight: normal;
font-variant: normal;
text-transform: none;
@ -218,7 +218,7 @@
@mixin iconbefore( $glyph: "\e001" ) {
font-family: "WooCommerce";
speak: none;
speak: never;
font-weight: normal;
font-variant: normal;
text-transform: none;
@ -231,7 +231,7 @@
@mixin iconbeforedashicons( $glyph: "\f333" ) {
font-family: "Dashicons";
speak: none;
speak: never;
font-weight: normal;
font-variant: normal;
text-transform: none;
@ -243,7 +243,7 @@
@mixin iconafter( $glyph: "\e001" ) {
font-family: "WooCommerce";
speak: none;
speak: never;
font-weight: normal;
font-variant: normal;
text-transform: none;

View File

@ -8,7 +8,7 @@ $red: #a00 !default;
$orange: #ffba00 !default;
$blue: #2ea2cc !default;
$primary: #a46497 !default; // Primary color for buttons (alt)
$primary: #a46497 !default; // Primary color for buttons (alt)
$primarytext: desaturate(lighten($primary, 50%), 18%) !default; // Text on primary color bg
$secondary: desaturate(lighten($primary, 40%), 21%) !default; // Secondary buttons
@ -17,5 +17,22 @@ $secondarytext: desaturate(darken($secondary, 60%), 21%) !default; // Text
$highlight: adjust-hue($primary, 150deg) !default; // Prices, In stock labels, sales flash
$highlightext: desaturate(lighten($highlight, 50%), 18%) !default; // Text on highlight color bg
$contentbg: #fff !default; // Content BG - Tabs (active state)
$subtext: #767676 !default; // small, breadcrumbs etc
$contentbg: #fff !default; // Content BG - Tabs (active state)
$subtext: #767676 !default; // small, breadcrumbs etc
// export vars as CSS vars
:root {
--woocommerce: $woocommerce;
--wc-green: $green;
--wc-red: $red;
--wc-orange: $orange;
--wc-blue: $blue;
--wc-primary: $primary;
--wc-primary-text: $primarytext;
--wc-secondary: $secondary;
--wc-secondary-text: $secondarytext;
--wc-highlight: $highlight;
--wc-highligh-text: $highlightext;
--wc-content-bg: $contentbg;
--wc-subtext: $subtext;
}

View File

@ -360,7 +360,7 @@
}
.addons-button-solid {
background-color:#674399;
background-color: #674399;
color: #fff;
}
@ -2128,7 +2128,7 @@ ul.wc_coupon_list_block {
&::after {
font-family: "Dashicons";
speak: none;
speak: never;
font-weight: normal;
font-variant: normal;
text-transform: none;
@ -3170,6 +3170,7 @@ table.wc_input_table {
}
table.wc_tax_rates {
td.country {
position: relative;
}

View File

@ -63,7 +63,7 @@ span.mce_woocommerce_shortcodes_button {
content: '\f348';
display: inline-block;
font: 400 18px/1 dashicons;
speak: none;
speak: never;
margin: 0 8px 0 -2px;
vertical-align: top;
}

View File

@ -295,11 +295,11 @@ a.button {
.woocommerce-store-notice__dismiss-link {
float: right;
color: #fff;
color: #000;
&:hover {
text-decoration: underline;
color: #fff;
text-decoration: none;
color: #000;
}
}

View File

@ -279,11 +279,11 @@ a.button {
.woocommerce-store-notice__dismiss-link {
float: right;
color: #fff;
color: #000;
&:hover {
text-decoration: underline;
color: #fff;
text-decoration: none;
color: #000;
}
}

View File

@ -374,7 +374,7 @@ body {
li a::before {
color: #82878c;
font: 400 20px/1 dashicons; /* stylelint-disable-line font-family-no-missing-generic-family-keyword */
speak: none;
speak: never;
display: inline-block;
padding: 0 10px 0 0;
top: 1px;

View File

@ -1,21 +0,0 @@
/*!
* accounting.js v0.4.2
* Copyright 2014 Open Exchange Rates
*
* Freely distributable under the MIT license.
* Portions of accounting.js are inspired or borrowed from underscore.js
*
* Full details and documentation:
* http://openexchangerates.github.io/accounting.js/
*/
!function(n,r){function e(n){return!!(""===n||n&&n.charCodeAt&&n.substr)}function t(n){return p?p(n):"[object Array]"===l.call(n)}function o(n){return n&&"[object Object]"===l.call(n)}function a(n,r){var e;n=n||{},r=r||{};for(e in r)r.hasOwnProperty(e)&&null==n[e]&&(n[e]=r[e]);return n}function i(n,r,e){var t,o,a=[];if(!n)return a;if(f&&n.map===f)return n.map(r,e);for(t=0,o=n.length;t<o;t++)a[t]=r.call(e,n[t],t,n);return a}function u(n,r){return n=Math.round(Math.abs(n)),isNaN(n)?r:n}function c(n){var r=s.settings.currency.format;return"function"==typeof n&&(n=n()),e(n)&&n.match("%v")?{pos:n,neg:n.replace("-","").replace("%v","-%v"),zero:n}:n&&n.pos&&n.pos.match("%v")?n:e(r)?s.settings.currency.format={pos:r,neg:r.replace("%v","-%v"),zero:r}:r}var s={};s.version="0.4.1",s.settings={currency:{symbol:"$",format:"%s%v",decimal:".",thousand:",",precision:2,grouping:3},number:{precision:0,grouping:3,thousand:",",decimal:"."}};var f=Array.prototype.map,p=Array.isArray,l=Object.prototype.toString,m=s.unformat=s.parse=function(n,r){if(t(n))return i(n,function(n){return m(n,r)});if("number"==typeof(n=n||0))return n;r=r||s.settings.number.decimal;var e=new RegExp("[^0-9-"+r+"]",["g"]),o=parseFloat((""+n).replace(/\((.*)\)/,"-$1").replace(e,"").replace(r,"."));return isNaN(o)?0:o},d=s.toFixed=function(n,r){r=u(r,s.settings.number.precision);var e=Math.pow(10,r);return(Math.round(s.unformat(n)*e)/e).toFixed(r)},g=s.formatNumber=s.format=function(n,r,e,c){if(t(n))return i(n,function(n){return g(n,r,e,c)});n=m(n);var f=a(o(r)?r:{precision:r,thousand:e,decimal:c},s.settings.number),p=u(f.precision),l=n<0?"-":"",h=parseInt(d(Math.abs(n||0),p),10)+"",y=h.length>3?h.length%3:0;return l+(y?h.substr(0,y)+f.thousand:"")+h.substr(y).replace(/(\d{3})(?=\d)/g,"$1"+f.thousand)+(p?f.decimal+d(Math.abs(n),p).split(".")[1]:"")},h=s.formatMoney=function(n,r,e,f,p,l){if(t(n))return i(n,function(n){return h(n,r,e,f,p,l)});n=m(n);var d=a(o(r)?r:{symbol:r,precision:e,thousand:f,decimal:p,format:l},s.settings.currency),y=c(d.format);return(n>0?y.pos:n<0?y.neg:y.zero).replace("%s",d.symbol).replace("%v",g(Math.abs(n),u(d.precision),d.thousand,d.decimal))};s.formatColumn=function(n,r,f,p,l,d){if(!n)return[];var h=a(o(r)?r:{symbol:r,precision:f,thousand:p,decimal:l,format:d},s.settings.currency),y=c(h.format),b=y.pos.indexOf("%s")<y.pos.indexOf("%v"),v=0;return i(i(n,function(n,r){if(t(n))return s.formatColumn(n,h);var e=((n=m(n))>0?y.pos:n<0?y.neg:y.zero).replace("%s",h.symbol).replace("%v",g(Math.abs(n),u(h.precision),h.thousand,h.decimal));return e.length>v&&(v=e.length),e}),function(n,r){return e(n)&&n.length<v?b?n.replace(h.symbol,h.symbol+new Array(v-n.length+1).join(" ")):new Array(v-n.length+1).join(" ")+n:n})},"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=s),exports.accounting=s):"function"==typeof define&&define.amd?define([],function(){return s}):(s.noConflict=function(r){return function(){/*!
* accounting.js v0.4.2
* Copyright 2014 Open Exchange Rates
*
* Freely distributable under the MIT license.
* Portions of accounting.js are inspired or borrowed from underscore.js
*
* Full details and documentation:
* http://openexchangerates.github.io/accounting.js/
*/
return n.accounting=r,s.noConflict=void 0,s}}(n.accounting),n.accounting=s)}(this);

View File

@ -355,7 +355,7 @@ jQuery( function( $ ) {
});
$( '.product_attributes' ).on( 'click', 'button.select_all_attributes', function() {
$( this ).closest( 'td' ).find( 'select option' ).attr( 'selected', 'selected' );
$( this ).closest( 'td' ).find( 'select option' ).prop( 'selected', 'selected' );
$( this ).closest( 'td' ).find( 'select' ).change();
return false;
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,8 +0,0 @@
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e("object"==typeof exports?require("jquery"):jQuery)}(function(e){function n(e){return c.raw?e:encodeURIComponent(e)}function i(e){return c.raw?e:decodeURIComponent(e)}function o(e){return n(c.json?JSON.stringify(e):String(e))}function r(e){0===e.indexOf('"')&&(e=e.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return e=decodeURIComponent(e.replace(u," ")),c.json?JSON.parse(e):e}catch(n){}}function t(n,i){var o=c.raw?n:r(n);return e.isFunction(i)?i(o):o}var u=/\+/g,c=e.cookie=function(r,u,f){if(u!==undefined&&!e.isFunction(u)){if("number"==typeof(f=e.extend({},c.defaults,f)).expires){var d=f.expires,a=f.expires=new Date;a.setTime(+a+864e5*d)}return document.cookie=[n(r),"=",o(u),f.expires?"; expires="+f.expires.toUTCString():"",f.path?"; path="+f.path:"",f.domain?"; domain="+f.domain:"",f.secure?"; secure":""].join("")}for(var p=r?undefined:{},s=document.cookie?document.cookie.split("; "):[],m=0,x=s.length;m<x;m++){var k=s[m].split("="),l=i(k.shift()),j=k.join("=");if(r&&r===l){p=t(j,u);break}r||(j=t(j))===undefined||(p[l]=j)}return p};c.defaults={},e.removeCookie=function(n,i){return e.cookie(n)!==undefined&&(e.cookie(n,"",e.extend({},i,{expires:-1})),!e.cookie(n))}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
!function(t,e,i){function n(){h=e[o](function(){r.each(function(){var e=t(this),i=e.width(),n=e.height(),h=t.data(this,u);i===h.w&&n===h.h||e.trigger(a,[h.w=i,h.h=n])}),n()},s[d])}var h,r=t([]),s=t.resize=t.extend(t.resize,{}),o="setTimeout",a="resize",u=a+"-special-event",d="delay",c="throttleWindow";s[d]=250,s[c]=!0,t.event.special[a]={setup:function(){if(!s[c]&&this[o])return!1;var e=t(this);r=r.add(e),t.data(this,u,{w:e.width(),h:e.height()}),1===r.length&&n()},teardown:function(){if(!s[c]&&this[o])return!1;var e=t(this);r=r.not(e),e.removeData(u),r.length||clearTimeout(h)},add:function(e){function n(e,n,r){var s=t(this),o=t.data(this,u);o.w=n!==i?n:s.width(),o.h=r!==i?r:s.height(),h.apply(this,arguments)}if(!s[c]&&this[o])return!1;var h;if(t.isFunction(e))return h=e,n;h=e.handler,e.handler=n}}}(jQuery,this),function(t){var e={};jQuery.plot.plugins.push({init:function(t){function e(){var e=t.getPlaceholder();0!=e.width()&&0!=e.height()&&(t.resize(),t.setupGrid(),t.draw())}t.hooks.bindEvents.push(function(t,i){t.getPlaceholder().resize(e)}),t.hooks.shutdown.push(function(t,i){t.getPlaceholder().unbind("resize",e)})},options:e,name:"resize",version:"1.0"})}();

View File

@ -1 +0,0 @@
!function(s){var n={series:{stack:null}};jQuery.plot.plugins.push({init:function(s){function n(s,n){for(var t=null,i=0;i<n.length&&s!=n[i];++i)n[i].stack==s.stack&&(t=n[i]);return t}s.hooks.processDatapoints.push(function(s,t,i){if(null!=t.stack&&!1!==t.stack){var l=n(t,s.getData());if(l){for(var o,e,u,f,a,p,r,h,c=i.pointsize,g=i.points,k=l.datapoints.pointsize,v=l.datapoints.points,m=[],z=t.lines.show,d=t.bars.horizontal,y=c>2&&(d?i.format[2].x:i.format[2].y),D=z&&t.lines.steps,b=!0,j=d?1:0,w=d?0:1,x=0,Q=0;!(x>=g.length);){if(r=m.length,null==g[x]){for(h=0;h<c;++h)m.push(g[x+h]);x+=c}else if(Q>=v.length){if(!z)for(h=0;h<c;++h)m.push(g[x+h]);x+=c}else if(null==v[Q]){for(h=0;h<c;++h)m.push(null);b=!0,Q+=k}else{if(o=g[x+j],e=g[x+w],f=v[Q+j],a=v[Q+w],p=0,o==f){for(h=0;h<c;++h)m.push(g[x+h]);m[r+w]+=a,p=a,x+=c,Q+=k}else if(o>f){if(z&&x>0&&null!=g[x-c]){for(u=e+(g[x-c+w]-e)*(f-o)/(g[x-c+j]-o),m.push(f),m.push(u+a),h=2;h<c;++h)m.push(g[x+h]);p=a}Q+=k}else{if(b&&z){x+=c;continue}for(h=0;h<c;++h)m.push(g[x+h]);z&&Q>0&&null!=v[Q-k]&&(p=a+(v[Q-k+w]-a)*(o-f)/(v[Q-k+j]-f)),m[r+w]+=p,x+=c}b=!1,r!=m.length&&y&&(m[r+2]+=p)}if(D&&r!=m.length&&r>0&&null!=m[r]&&m[r]!=m[r-c]&&m[r+1]!=m[r-c+1]){for(h=0;h<c;++h)m[r+c+h]=m[r+h];m[r+1]=m[r-c+1]}}i.points=m}}})},options:n,name:"stack",version:"1.2"})}();

View File

@ -1 +0,0 @@
!function(e){function t(e,t){return t*Math.floor(e/t)}function n(e,t,n,r){if("function"==typeof e.strftime)return e.strftime(t);var i=function(e,t){return e=""+e,t=""+(null==t?"0":t),1==e.length?t+e:e},a=[],o=!1,s=e.getHours(),u=s<12;null==n&&(n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]),null==r&&(r=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]);var c;c=s>12?s-12:0==s?12:s;for(var m=0;m<t.length;++m){var l=t.charAt(m);if(o){switch(l){case"a":l=""+r[e.getDay()];break;case"b":l=""+n[e.getMonth()];break;case"d":l=i(e.getDate());break;case"e":l=i(e.getDate()," ");break;case"h":case"H":l=i(s);break;case"I":l=i(c);break;case"l":l=i(c," ");break;case"m":l=i(e.getMonth()+1);break;case"M":l=i(e.getMinutes());break;case"q":l=""+(Math.floor(e.getMonth()/3)+1);break;case"S":l=i(e.getSeconds());break;case"y":l=i(e.getFullYear()%100);break;case"Y":l=""+e.getFullYear();break;case"p":l=u?"am":"pm";break;case"P":l=u?"AM":"PM";break;case"w":l=""+e.getDay()}a.push(l),o=!1}else"%"==l?o=!0:a.push(l)}return a.join("")}function r(e){function t(e,t,n,r){e[t]=function(){return n[r].apply(n,arguments)}}var n={date:e};e.strftime!=undefined&&t(n,"strftime",e,"strftime"),t(n,"getTime",e,"getTime"),t(n,"setTime",e,"setTime");for(var r=["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds"],i=0;i<r.length;i++)t(n,"get"+r[i],e,"getUTC"+r[i]),t(n,"set"+r[i],e,"setUTC"+r[i]);return n}function i(e,t){if("browser"==t.timezone)return new Date(e);if(t.timezone&&"utc"!=t.timezone){if("undefined"!=typeof timezoneJS&&"undefined"!=typeof timezoneJS.Date){var n=new timezoneJS.Date;return n.setTimezone(t.timezone),n.setTime(e),n}return r(new Date(e))}return r(new Date(e))}var a={xaxis:{timezone:null,timeformat:null,twelveHourClock:!1,monthNames:null}},o={second:1e3,minute:6e4,hour:36e5,day:864e5,month:2592e6,quarter:7776e6,year:525949.2*60*1e3},s=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"]],u=s.concat([[3,"month"],[6,"month"],[1,"year"]]),c=s.concat([[1,"quarter"],[2,"quarter"],[1,"year"]]);e.plot.plugins.push({init:function(r){r.hooks.processOptions.push(function(r,a){e.each(r.getAxes(),function(e,r){var a=r.options;"time"==a.mode&&(r.tickGenerator=function(e){var n=[],r=i(e.min,a),s=0,m=a.tickSize&&"quarter"===a.tickSize[1]||a.minTickSize&&"quarter"===a.minTickSize[1]?c:u;null!=a.minTickSize&&(s="number"==typeof a.tickSize?a.tickSize:a.minTickSize[0]*o[a.minTickSize[1]]);for(var l=0;l<m.length-1&&!(e.delta<(m[l][0]*o[m[l][1]]+m[l+1][0]*o[m[l+1][1]])/2&&m[l][0]*o[m[l][1]]>=s);++l);var h=m[l][0],f=m[l][1];if("year"==f){if(null!=a.minTickSize&&"year"==a.minTickSize[1])h=Math.floor(a.minTickSize[0]);else{var k=Math.pow(10,Math.floor(Math.log(e.delta/o.year)/Math.LN10)),d=e.delta/o.year/k;h=d<1.5?1:d<3?2:d<7.5?5:10,h*=k}h<1&&(h=1)}e.tickSize=a.tickSize||[h,f];var M=e.tickSize[0];f=e.tickSize[1];var g=M*o[f];"second"==f?r.setSeconds(t(r.getSeconds(),M)):"minute"==f?r.setMinutes(t(r.getMinutes(),M)):"hour"==f?r.setHours(t(r.getHours(),M)):"month"==f?r.setMonth(t(r.getMonth(),M)):"quarter"==f?r.setMonth(3*t(r.getMonth()/3,M)):"year"==f&&r.setFullYear(t(r.getFullYear(),M)),r.setMilliseconds(0),g>=o.minute&&r.setSeconds(0),g>=o.hour&&r.setMinutes(0),g>=o.day&&r.setHours(0),g>=4*o.day&&r.setDate(1),g>=2*o.month&&r.setMonth(t(r.getMonth(),3)),g>=2*o.quarter&&r.setMonth(t(r.getMonth(),6)),g>=o.year&&r.setMonth(0);var y,S=0,z=Number.NaN;do{if(y=z,z=r.getTime(),n.push(z),"month"==f||"quarter"==f)if(M<1){r.setDate(1);var p=r.getTime();r.setMonth(r.getMonth()+("quarter"==f?3:1));var v=r.getTime();r.setTime(z+S*o.hour+(v-p)*M),S=r.getHours(),r.setHours(0)}else r.setMonth(r.getMonth()+M*("quarter"==f?3:1));else"year"==f?r.setFullYear(r.getFullYear()+M):r.setTime(z+g)}while(z<e.max&&z!=y);return n},r.tickFormatter=function(e,t){var r=i(e,t.options);if(null!=a.timeformat)return n(r,a.timeformat,a.monthNames,a.dayNames);var s=t.options.tickSize&&"quarter"==t.options.tickSize[1]||t.options.minTickSize&&"quarter"==t.options.minTickSize[1],u=t.tickSize[0]*o[t.tickSize[1]],c=t.max-t.min,m=a.twelveHourClock?" %p":"",l=a.twelveHourClock?"%I":"%H";return n(r,u<o.minute?l+":%M:%S"+m:u<o.day?c<2*o.day?l+":%M"+m:"%b %d "+l+":%M"+m:u<o.month?"%b %d":s&&u<o.quarter||!s&&u<o.year?c<o.year?"%b":"%b %Y":s&&u<o.year?c<o.year?"Q%q":"Q%q %Y":"%Y",a.monthNames,a.dayNames)})})})},options:a,name:"time",version:"1.0"}),e.plot.formatDate=n}(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
!function(O){O.fn.tipTip=function(t){var g,b,M,w=O.extend({activation:"hover",keepAlive:!1,maxWidth:"200px",edgeOffset:3,defaultPosition:"bottom",delay:400,fadeIn:200,fadeOut:200,attribute:"title",content:!1,enter:function(){},exit:function(){}},t);return O("#tiptip_holder").length<=0?(g=O('<div id="tiptip_holder" style="max-width:'+w.maxWidth+';"></div>'),b=O('<div id="tiptip_content"></div>'),M=O('<div id="tiptip_arrow"></div>'),O("body").append(g.html(b).prepend(M.html('<div id="tiptip_arrow_inner"></div>')))):(g=O("#tiptip_holder"),b=O("#tiptip_content"),M=O("#tiptip_arrow")),this.each(function(){var _,v,m=O(this);function t(){w.enter.call(this),b.html(_),g.hide().removeAttr("class").css("margin","0"),M.removeAttr("style");var t=parseInt(m.offset().top),e=parseInt(m.offset().left),o=parseInt(m.outerWidth()),i=parseInt(m.outerHeight()),n=g.outerWidth(),r=g.outerHeight(),a=Math.round((o-n)/2),f=Math.round((i-r)/2),d=Math.round(e+a),u=Math.round(t+i+w.edgeOffset),p="",h="",l=Math.round(n-12)/2;"bottom"==w.defaultPosition?p="_bottom":"top"==w.defaultPosition?p="_top":"left"==w.defaultPosition?p="_left":"right"==w.defaultPosition&&(p="_right");var c=a+e<parseInt(O(window).scrollLeft()),s=n+e>parseInt(O(window).width());c&&a<0||"_right"==p&&!s||"_left"==p&&e<n+w.edgeOffset+5?(p="_right",h=Math.round(r-13)/2,l=-12,d=Math.round(e+o+w.edgeOffset),u=Math.round(t+f)):(s&&a<0||"_left"==p&&!c)&&(p="_left",h=Math.round(r-13)/2,l=Math.round(n),d=Math.round(e-(n+w.edgeOffset+5)),u=Math.round(t+f));n=t+i+w.edgeOffset+r+8>parseInt(O(window).height()+O(window).scrollTop()),f=t+i-(w.edgeOffset+r+8)<0;n||"_bottom"==p&&n||"_top"==p&&!f?("_top"==p||"_bottom"==p?p="_top":p+="_top",h=r,u=Math.round(t-(r+5+w.edgeOffset))):(f|("_top"==p&&f)||"_bottom"==p&&!n)&&("_top"==p||"_bottom"==p?p="_bottom":p+="_bottom",h=-12,u=Math.round(t+i+w.edgeOffset)),"_right_top"==p||"_left_top"==p?u+=5:"_right_bottom"!=p&&"_left_bottom"!=p||(u-=5),"_left_top"!=p&&"_left_bottom"!=p||(d+=5),M.css({"margin-left":l+"px","margin-top":h+"px"}),g.css({"margin-left":d+"px","margin-top":u+"px"}).attr("class","tip"+p),v&&clearTimeout(v),v=setTimeout(function(){g.stop(!0,!0).fadeIn(w.fadeIn)},w.delay)}function e(){w.exit.call(this),v&&clearTimeout(v),g.fadeOut(w.fadeOut)}""!=(_=w.content?w.content:m.attr(w.attribute))&&(w.content||m.removeAttr(w.attribute),v=!1,"hover"==w.activation?(m.hover(function(){t()},function(){w.keepAlive&&g.is(":hover")||e()}),w.keepAlive&&g.hover(function(){},function(){e()})):"focus"==w.activation?m.focus(function(){t()}).blur(function(){e()}):"click"==w.activation&&(m.click(function(){return t(),!1}).hover(function(){},function(){w.keepAlive||e()}),w.keepAlive&&g.hover(function(){},function(){e()})))})}}(jQuery);

View File

@ -1,11 +0,0 @@
/*!
* jQuery UI Touch Punch 0.2.3
*
* Copyright 20112014, Dave Furfero
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Depends:
* jquery.ui.widget.js
* jquery.ui.mouse.js
*/
!function(o){function t(o,t){if(!(o.originalEvent.touches.length>1)){o.preventDefault();var e=o.originalEvent.changedTouches[0],u=document.createEvent("MouseEvents");u.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),o.target.dispatchEvent(u)}}if(o.support.touch="ontouchend"in document,o.support.touch){var e,u=o.ui.mouse.prototype,n=u._mouseInit,c=u._mouseDestroy;u._touchStart=function(o){var u=this;!e&&u._mouseCapture(o.originalEvent.changedTouches[0])&&(e=!0,u._touchMoved=!1,t(o,"mouseover"),t(o,"mousemove"),t(o,"mousedown"))},u._touchMove=function(o){e&&(this._touchMoved=!0,t(o,"mousemove"))},u._touchEnd=function(o){e&&(t(o,"mouseup"),t(o,"mouseout"),this._touchMoved||t(o,"click"),e=!1)},u._mouseInit=function(){var t=this;t.element.bind({touchstart:o.proxy(t,"_touchStart"),touchmove:o.proxy(t,"_touchMove"),touchend:o.proxy(t,"_touchEnd")}),n.call(t)},u._mouseDestroy=function(){var t=this;t.element.unbind({touchstart:o.proxy(t,"_touchStart"),touchmove:o.proxy(t,"_touchMove"),touchend:o.proxy(t,"_touchEnd")}),c.call(t)}}}(jQuery);

View File

@ -1,8 +0,0 @@
/*!
* JavaScript Cookie v2.1.4
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
!function(e){var n=!1;if("function"==typeof define&&define.amd&&(define(e),n=!0),"object"==typeof exports&&(module.exports=e(),n=!0),!n){var o=window.Cookies,t=window.Cookies=e();t.noConflict=function(){return window.Cookies=o,t}}}(function(){function e(){for(var e=0,n={};e<arguments.length;e++){var o=arguments[e];for(var t in o)n[t]=o[t]}return n}function n(o){function t(n,r,i){var c;if("undefined"!=typeof document){if(arguments.length>1){if("number"==typeof(i=e({path:"/"},t.defaults,i)).expires){var a=new Date;a.setMilliseconds(a.getMilliseconds()+864e5*i.expires),i.expires=a}i.expires=i.expires?i.expires.toUTCString():"";try{c=JSON.stringify(r),/^[\{\[]/.test(c)&&(r=c)}catch(m){}r=o.write?o.write(r,n):encodeURIComponent(String(r)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=(n=(n=encodeURIComponent(String(n))).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent)).replace(/[\(\)]/g,escape);var f="";for(var s in i)i[s]&&(f+="; "+s,!0!==i[s]&&(f+="="+i[s]));return document.cookie=n+"="+r+f}n||(c={});for(var p=document.cookie?document.cookie.split("; "):[],d=/(%[0-9A-Z]{2})+/g,u=0;u<p.length;u++){var l=p[u].split("="),C=l.slice(1).join("=");'"'===C.charAt(0)&&(C=C.slice(1,-1));try{var g=l[0].replace(d,decodeURIComponent);if(C=o.read?o.read(C,g):o(C,g)||C.replace(d,decodeURIComponent),this.json)try{C=JSON.parse(C)}catch(m){}if(n===g){c=C;break}n||(c[g]=C)}catch(m){}}return c}}return t.set=t,t.get=function(e){return t.call(t,e)},t.getJSON=function(){return t.apply({json:!0},[].slice.call(arguments))},t.defaults={},t.remove=function(n,o){t(n,"",e(o,{expires:-1}))},t.withConverter=n,t}return n(function(){})});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
!function(o){o(function(){o("a.zoom").prettyPhoto({hook:"data-rel",social_tools:!1,theme:"pp_woocommerce",horizontal_padding:20,opacity:.8,deeplinking:!1}),o("a[data-rel^='prettyPhoto']").prettyPhoto({hook:"data-rel",social_tools:!1,theme:"pp_woocommerce",horizontal_padding:20,opacity:.8,deeplinking:!1})})}(jQuery);

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
function round(a,e,r){var _,c,s,t;if(e|=0,_=Math.pow(10,e),a*=_,t=a>0|-(a<0),s=a%1==.5*t,c=Math.floor(a),s)switch(r){case"2":case"PHP_ROUND_HALF_DOWN":a=c+(t<0);break;case"3":case"PHP_ROUND_HALF_EVEN":a=c+c%2*t;break;case"4":case"PHP_ROUND_HALF_ODD":a=c+!(c%2);break;default:a=c+(t>0)}return(s?a:Math.round(a))/_}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
!function(t){t.fn.stupidtable=function(r){return this.each(function(){var n=t(this);r=r||{},r=t.extend({},t.fn.stupidtable.default_sort_fns,r),n.on("click.stupidtable","thead th",function(){var a=t(this),e=0,i=t.fn.stupidtable.dir;a.parents("tr").find("th").slice(0,a.index()+1).each(function(){var r=t(this).attr("colspan")||1;e+=parseInt(r,10)}),e-=1;var s=a.data("sort-default")||i.ASC;a.data("sort-dir")&&(s=a.data("sort-dir")===i.ASC?i.DESC:i.ASC);var o=a.data("sort")||null;null!==o&&(n.trigger("beforetablesort",{column:a.index(),direction:s}),n.css("display"),setTimeout(function(){var d=r[o];n.children("tbody").each(function(r,n){var a=[],o=t(n),c=o.children("tr").not("[data-sort-ignore]");c.each(function(r,n){var i=t(n).children().eq(e),s=i.data("sort-value"),o=void 0!==s?s:i.text();a.push([o,n])}),a.sort(function(t,r){return d(t[0],r[0])}),s!=i.ASC&&a.reverse(),c=t.map(a,function(t){return t[1]}),o.append(c)}),n.find("th").data("sort-dir",null).removeClass("sorting-desc sorting-asc"),a.data("sort-dir",s).addClass("sorting-"+s),n.trigger("aftertablesort",{column:a.index(),direction:s}),n.css("display")},10))})})},t.fn.stupidtable.dir={ASC:"asc",DESC:"desc"},t.fn.stupidtable.default_sort_fns={"int":function(t,r){return parseInt(t,10)-parseInt(r,10)},"float":function(t,r){return parseFloat(t)-parseFloat(r)},string:function(t,r){return t.localeCompare(r)},"string-ins":function(t,r){return t=t.toLocaleLowerCase(),r=r.toLocaleLowerCase(),t.localeCompare(r)}}}(jQuery);

File diff suppressed because one or more lines are too long

View File

@ -1,6 +0,0 @@
/*!
Zoom 1.7.21
license: MIT
http://www.jacklmoore.com/zoom
*/
!function(d){var n={url:!1,callback:!1,target:!1,duration:120,on:"mouseover",touch:!0,onZoomIn:!1,onZoomOut:!1,magnify:1};d.zoom=function(o,t,n,e){var i,u,a,c,r,l,m,s=d(o),f=s.css("position"),h=d(t);return o.style.position=/(absolute|fixed)/.test(f)?f:"relative",o.style.overflow="hidden",n.style.width=n.style.height="",d(n).addClass("zoomImg").css({position:"absolute",top:0,left:0,opacity:0,width:n.width*e,height:n.height*e,border:"none",maxWidth:"none",maxHeight:"none"}).appendTo(o),{init:function(){u=s.outerWidth(),i=s.outerHeight(),a=t===o?(c=u,i):(c=h.outerWidth(),h.outerHeight()),r=(n.width-u)/c,l=(n.height-i)/a,m=h.offset()},move:function(o){var t=o.pageX-m.left,e=o.pageY-m.top;e=Math.max(Math.min(e,a),0),t=Math.max(Math.min(t,c),0),n.style.left=t*-r+"px",n.style.top=e*-l+"px"}}},d.fn.zoom=function(e){return this.each(function(){var i=d.extend({},n,e||{}),u=i.target&&d(i.target)[0]||this,o=this,a=d(o),c=document.createElement("img"),r=d(c),l="mousemove.zoom",m=!1,s=!1;if(!i.url){var t=o.querySelector("img");if(t&&(i.url=t.getAttribute("data-src")||t.currentSrc||t.src,i.alt=t.getAttribute("data-alt")||t.alt),!i.url)return}a.one("zoom.destroy",function(o,t){a.off(".zoom"),u.style.position=o,u.style.overflow=t,c.onload=null,r.remove()}.bind(this,u.style.position,u.style.overflow)),c.onload=function(){var t=d.zoom(u,o,c,i.magnify);function e(o){t.init(),t.move(o),r.stop().fadeTo(d.support.opacity?i.duration:0,1,!!d.isFunction(i.onZoomIn)&&i.onZoomIn.call(c))}function n(){r.stop().fadeTo(i.duration,0,!!d.isFunction(i.onZoomOut)&&i.onZoomOut.call(c))}"grab"===i.on?a.on("mousedown.zoom",function(o){1===o.which&&(d(document).one("mouseup.zoom",function(){n(),d(document).off(l,t.move)}),e(o),d(document).on(l,t.move),o.preventDefault())}):"click"===i.on?a.on("click.zoom",function(o){return m?void 0:(m=!0,e(o),d(document).on(l,t.move),d(document).one("click.zoom",function(){n(),m=!1,d(document).off(l,t.move)}),!1)}):"toggle"===i.on?a.on("click.zoom",function(o){m?n():e(o),m=!m}):"mouseover"===i.on&&(t.init(),a.on("mouseenter.zoom",e).on("mouseleave.zoom",n).on(l,t.move)),i.touch&&a.on("touchstart.zoom",function(o){o.preventDefault(),s?(s=!1,n()):(s=!0,e(o.originalEvent.touches[0]||o.originalEvent.changedTouches[0]))}).on("touchmove.zoom",function(o){o.preventDefault(),t.move(o.originalEvent.touches[0]||o.originalEvent.changedTouches[0])}).on("touchend.zoom",function(o){o.preventDefault(),s&&(s=!1,n())}),d.isFunction(i.callback)&&i.callback.call(c)},c.setAttribute("role","presentation"),c.alt=i.alt||"",c.src=i.url})},d.fn.zoom.defaults=n}(window.jQuery);

View File

@ -27,6 +27,7 @@
* Fix - Also count comment types with '' and 'comment' in the review count query. #28624
* Fix - Better error messages when usage limit are reached. #28592
* Fix - Adjust stock items only for statuses which reduces the stock. #28620
* Fix - Named parameter to fix DB update routine on PHP8. #28537
* Dev - Hook for intializing price slider in frontend. #28014
* Dev - Add support for running a custom initialization script for tests. #28041
* Dev - Use the coenjacobs/mozart package to renamespace vendor packages. #28147
@ -67,6 +68,8 @@
* Fix - Stop order panels flickering on load. #5655
* Fix - Load wc-tracks to avoid fatal errors. #5645 #5638
* Fix - Preventing desktop-sized navigation placeholder from appearing on mobile during load. #5616
* Fix - Completed tasks tracking causing infinite loop #5941
* Fix - Remove Navigation access #5940
* Tweak - Fix inconsistent REST API parameter name for customer type filtering. #5823
* Tweak - Improve styles of the tax task. #5709
* Tweak - Do not show store setup link on the homescreen. #5801

View File

@ -14,20 +14,21 @@
],
"require": {
"php": ">=7.0",
"automattic/jetpack-autoloader": "2.6.0",
"automattic/jetpack-autoloader": "2.7.1",
"automattic/jetpack-constants": "1.5.1",
"composer/installers": "~1.7",
"maxmind-db/reader": "1.6.0",
"pelago/emogrifier": "3.1.0",
"psr/container": "1.0.0",
"woocommerce/action-scheduler": "3.1.6",
"woocommerce/woocommerce-admin": "1.8.2",
"woocommerce/woocommerce-admin": "1.8.3",
"woocommerce/woocommerce-blocks": "4.0.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4"
},
"config": {
"optimize-autoloader": true,
"platform": {
"php": "7.0"
},

28
composer.lock generated
View File

@ -4,27 +4,27 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "35507616193389119dda0f5a44bf46b3",
"content-hash": "758b097c13e89200b28bf3a3b5fc2752",
"packages": [
{
"name": "automattic/jetpack-autoloader",
"version": "v2.6.0",
"version": "v2.7.1",
"source": {
"type": "git",
"url": "https://github.com/Automattic/jetpack-autoloader.git",
"reference": "47dde8dbca6b1e30f176725f2f748a9abefcaf58"
"reference": "5437697a56aefbdf707849b9833e1b36093d7a73"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/47dde8dbca6b1e30f176725f2f748a9abefcaf58",
"reference": "47dde8dbca6b1e30f176725f2f748a9abefcaf58",
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/5437697a56aefbdf707849b9833e1b36093d7a73",
"reference": "5437697a56aefbdf707849b9833e1b36093d7a73",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.1 || ^2.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
"yoast/phpunit-polyfills": "0.2.0"
},
"type": "composer-plugin",
"extra": {
@ -44,9 +44,9 @@
],
"description": "Creates a custom autoloader for a plugin or theme.",
"support": {
"source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.6.0"
"source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.7.1"
},
"time": "2020-11-19T21:20:12+00:00"
"time": "2020-12-18T22:33:59+00:00"
},
{
"name": "automattic/jetpack-constants",
@ -515,16 +515,16 @@
},
{
"name": "woocommerce/woocommerce-admin",
"version": "1.8.2",
"version": "1.8.3",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "a890bc4131d3cf8c93bb8aeefff96db1a95f547d"
"reference": "534442980c34369f711efdb8e85fdcb1363be712"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/a890bc4131d3cf8c93bb8aeefff96db1a95f547d",
"reference": "a890bc4131d3cf8c93bb8aeefff96db1a95f547d",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/534442980c34369f711efdb8e85fdcb1363be712",
"reference": "534442980c34369f711efdb8e85fdcb1363be712",
"shasum": ""
},
"require": {
@ -558,9 +558,9 @@
"homepage": "https://github.com/woocommerce/woocommerce-admin",
"support": {
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v1.8.2"
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v1.8.3"
},
"time": "2020-12-23T01:26:52+00:00"
"time": "2021-01-06T00:00:42+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",

View File

@ -306,6 +306,40 @@ return array(
'CZ' => array(),
'DE' => array(),
'DK' => array(),
'DO' => array( // Dominican Republic.
'DO-01' => __( 'Distrito Nacional', 'woocommerce' ),
'DO-02' => __( 'Azua', 'woocommerce' ),
'DO-03' => __( 'Baoruco', 'woocommerce' ),
'DO-04' => __( 'Barahona', 'woocommerce' ),
'DO-05' => __( 'Dajabón', 'woocommerce' ),
'DO-06' => __( 'Duarte', 'woocommerce' ),
'DO-07' => __( 'Elías Piña', 'woocommerce' ),
'DO-08' => __( 'El Seibo', 'woocommerce' ),
'DO-09' => __( 'Espaillat', 'woocommerce' ),
'DO-10' => __( 'Independencia', 'woocommerce' ),
'DO-11' => __( 'La Altagracia', 'woocommerce' ),
'DO-12' => __( 'La Romana', 'woocommerce' ),
'DO-13' => __( 'La Vega', 'woocommerce' ),
'DO-14' => __( 'María Trinidad Sánchez', 'woocommerce' ),
'DO-15' => __( 'Monte Cristi', 'woocommerce' ),
'DO-16' => __( 'Pedernales', 'woocommerce' ),
'DO-17' => __( 'Peravia', 'woocommerce' ),
'DO-18' => __( 'Puerto Plata', 'woocommerce' ),
'DO-19' => __( 'Hermanas Mirabal', 'woocommerce' ),
'DO-20' => __( 'Samaná', 'woocommerce' ),
'DO-21' => __( 'San Cristóbal', 'woocommerce' ),
'DO-22' => __( 'San Juan', 'woocommerce' ),
'DO-23' => __( 'San Pedro de Macorís', 'woocommerce' ),
'DO-24' => __( 'Sánchez Ramírez', 'woocommerce' ),
'DO-25' => __( 'Santiago', 'woocommerce' ),
'DO-26' => __( 'Santiago Rodríguez', 'woocommerce' ),
'DO-27' => __( 'Valverde', 'woocommerce' ),
'DO-28' => __( 'Monseñor Nouel', 'woocommerce' ),
'DO-29' => __( 'Monte Plata', 'woocommerce' ),
'DO-30' => __( 'Hato Mayor', 'woocommerce' ),
'DO-31' => __( 'San José de Ocoa', 'woocommerce' ),
'DO-32' => __( 'Santo Domingo', 'woocommerce' ),
),
'DZ' => array(
'DZ-01' => __( 'Adrar', 'woocommerce' ),
'DZ-02' => __( 'Chlef', 'woocommerce' ),
@ -442,6 +476,7 @@ return array(
),
'FI' => array(),
'FR' => array(),
'GF' => array(),
'GH' => array( // Ghanaian Regions.
'AF' => __( 'Ahafo', 'woocommerce' ),
'AH' => __( 'Ashanti', 'woocommerce' ),
@ -477,7 +512,30 @@ return array(
'L' => __( 'South Aegean', 'woocommerce' ),
'M' => __( 'Crete', 'woocommerce' ),
),
'GF' => array(),
'GT' => array( // Guatemalan states.
'AV' => __( 'Alta Verapaz', 'woocommerce' ),
'BV' => __( 'Baja Verapaz', 'woocommerce' ),
'CM' => __( 'Chimaltenango', 'woocommerce' ),
'CQ' => __( 'Chiquimula', 'woocommerce' ),
'PR' => __( 'El Progreso', 'woocommerce' ),
'ES' => __( 'Escuintla', 'woocommerce' ),
'GU' => __( 'Guatemala', 'woocommerce' ),
'HU' => __( 'Huehuetenango', 'woocommerce' ),
'IZ' => __( 'Izabal', 'woocommerce' ),
'JA' => __( 'Jalapa', 'woocommerce' ),
'JU' => __( 'Jutiapa', 'woocommerce' ),
'PE' => __( 'Petén', 'woocommerce' ),
'QZ' => __( 'Quetzaltenango', 'woocommerce' ),
'QC' => __( 'Quiché', 'woocommerce' ),
'RE' => __( 'Retalhuleu', 'woocommerce' ),
'SA' => __( 'Sacatepéquez', 'woocommerce' ),
'SM' => __( 'San Marcos', 'woocommerce' ),
'SR' => __( 'Santa Rosa', 'woocommerce' ),
'SO' => __( 'Sololá', 'woocommerce' ),
'SU' => __( 'Suchitepéquez', 'woocommerce' ),
'TO' => __( 'Totonicapán', 'woocommerce' ),
'ZA' => __( 'Zacapa', 'woocommerce' )
),
'HK' => array( // Hong Kong states.
'HONG KONG' => __( 'Hong Kong Island', 'woocommerce' ),
'KOWLOON' => __( 'Kowloon', 'woocommerce' ),
@ -590,7 +648,7 @@ return array(
'ML' => __( 'Meghalaya', 'woocommerce' ),
'MZ' => __( 'Mizoram', 'woocommerce' ),
'NL' => __( 'Nagaland', 'woocommerce' ),
'OR' => __( 'Orissa', 'woocommerce' ),
'OR' => __( 'Odisha', 'woocommerce' ),
'PB' => __( 'Punjab', 'woocommerce' ),
'RJ' => __( 'Rajasthan', 'woocommerce' ),
'SK' => __( 'Sikkim', 'woocommerce' ),
@ -1304,7 +1362,6 @@ return array(
'VS' => __( 'Vaslui', 'woocommerce' ),
'VN' => __( 'Vrancea', 'woocommerce' ),
),
'RS' => array(),
'SG' => array(),
'SK' => array(),
'SI' => array(),

View File

@ -1349,7 +1349,6 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
* @param int $qty Quantity to add.
* @param array $args Args for the added product.
* @return int
* @throws WC_Data_Exception Exception thrown if the item cannot be added to the cart.
*/
public function add_product( $product, $qty = 1, $args = array() ) {
if ( $product ) {

View File

@ -25,11 +25,25 @@ class WC_Admin_Addons {
public static function get_featured() {
$featured = get_transient( 'wc_addons_featured' );
if ( false === $featured ) {
$raw_featured = wp_safe_remote_get( 'https://d3t0oesq8995hv.cloudfront.net/add-ons/featured-v2.json', array( 'user-agent' => 'WooCommerce Addons Page' ) );
$headers = array();
$auth = WC_Helper_Options::get( 'auth' );
if ( ! empty( $auth['access_token'] ) ) {
$headers['Authorization'] = 'Bearer ' . $auth['access_token'];
}
$raw_featured = wp_safe_remote_get(
'https://woocommerce.com/wp-json/wccom-extensions/1.0/featured',
array(
'headers' => $headers,
'user-agent' => 'WooCommerce Addons Page',
)
);
if ( ! is_wp_error( $raw_featured ) ) {
$featured = json_decode( wp_remote_retrieve_body( $raw_featured ) );
if ( $featured ) {
set_transient( 'wc_addons_featured', $featured, WEEK_IN_SECONDS );
set_transient( 'wc_addons_featured', $featured, DAY_IN_SECONDS );
}
}
}
@ -71,9 +85,19 @@ class WC_Admin_Addons {
*/
public static function get_extension_data( $category, $term, $country ) {
$parameters = self::build_parameter_string( $category, $term, $country );
$raw_extensions = wp_remote_get(
'https://woocommerce.com/wp-json/wccom-extensions/1.0/search' . $parameters
$headers = array();
$auth = WC_Helper_Options::get( 'auth' );
if ( ! empty( $auth['access_token'] ) ) {
$headers['Authorization'] = 'Bearer ' . $auth['access_token'];
}
$raw_extensions = wp_safe_remote_get(
'https://woocommerce.com/wp-json/wccom-extensions/1.0/search' . $parameters,
array( 'headers' => $headers )
);
if ( ! is_wp_error( $raw_extensions ) ) {
$addons = json_decode( wp_remote_retrieve_body( $raw_extensions ) )->products;
}

View File

@ -377,7 +377,7 @@ class WC_Admin_Status {
private static function output_plugins_info( $plugins, $untested_plugins ) {
$wc_version = Constants::get_constant( 'WC_VERSION' );
if ( 'major' === WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE ) {
if ( 'major' === Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) ) {
// Since we're only testing against major, we don't need to show minor and patch version.
$wc_version = $wc_version[0] . '.0';
}

View File

@ -118,7 +118,9 @@ class WC_Meta_Box_Order_Actions {
WC()->payment_gateways();
WC()->shipping();
WC()->mailer()->emails['WC_Email_New_Order']->trigger( $order->get_id(), $order );
add_filter( 'woocommerce_new_order_email_allows_resend', '__return_true' );
WC()->mailer()->emails['WC_Email_New_Order']->trigger( $order->get_id(), $order, true );
remove_filter( 'woocommerce_new_order_email_allows_resend', '__return_true' );
do_action( 'woocommerce_after_resend_order_email', $order, 'new_order' );

View File

@ -116,7 +116,7 @@ class WC_Meta_Box_Product_Data {
'variations' => array(
'label' => __( 'Variations', 'woocommerce' ),
'target' => 'variable_product_options',
'class' => array( 'variations_tab', 'show_if_variable' ),
'class' => array( 'show_if_variable' ),
'priority' => 60,
),
'advanced' => array(
@ -177,7 +177,7 @@ class WC_Meta_Box_Product_Data {
$variations_per_page = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_per_page', 15 ) );
$variations_total_pages = ceil( $variations_count / $variations_per_page );
include __DIR__ . '/views/html-product-data-variations.php';
include __DIR__ . '/views/html-product-data-variations.php';
}
/**
@ -349,7 +349,7 @@ class WC_Meta_Box_Product_Data {
$date_on_sale_from = wc_clean( wp_unslash( $_POST['_sale_price_dates_from'] ) );
if ( ! empty( $date_on_sale_from ) ) {
$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) );
$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
}
}
@ -358,7 +358,7 @@ class WC_Meta_Box_Product_Data {
$date_on_sale_to = wc_clean( wp_unslash( $_POST['_sale_price_dates_to'] ) );
if ( ! empty( $date_on_sale_to ) ) {
$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) );
$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
}
}
@ -475,7 +475,7 @@ class WC_Meta_Box_Product_Data {
$date_on_sale_from = wc_clean( wp_unslash( $_POST['variable_sale_price_dates_from'][ $i ] ) );
if ( ! empty( $date_on_sale_from ) ) {
$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) );
$date_on_sale_from = date( 'Y-m-d 00:00:00', strtotime( $date_on_sale_from ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
}
}
@ -484,7 +484,7 @@ class WC_Meta_Box_Product_Data {
$date_on_sale_to = wc_clean( wp_unslash( $_POST['variable_sale_price_dates_to'][ $i ] ) );
if ( ! empty( $date_on_sale_to ) ) {
$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) );
$date_on_sale_to = date( 'Y-m-d 23:59:59', strtotime( $date_on_sale_to ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
}
}

View File

@ -150,10 +150,15 @@ class WC_Plugin_Updates {
* with the $new_version.
*
* @param string $new_version WooCommerce version to test against.
* @param string $release 'major' or 'minor'.
* @param string $release 'major', 'minor', or 'none'.
* @return array of plugin info arrays
*/
public function get_untested_plugins( $new_version, $release ) {
// Since 5.0 all versions are backwards compatible.
if ( 'none' === $release ) {
return array();
}
$extensions = array_merge( $this->get_plugins_with_header( self::VERSION_TESTED_HEADER ), $this->get_plugins_for_woocommerce() );
$untested = array();
$new_version_parts = explode( '.', $new_version );

View File

@ -42,9 +42,14 @@ class WC_Plugins_Screen_Updates extends WC_Plugin_Updates {
* @param stdClass $response Plugin update response.
*/
public function in_plugin_update_message( $args, $response ) {
$version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
if ( ! is_string( $version_type ) ) {
$version_type = 'none';
}
$this->new_version = $response->new_version;
$this->upgrade_notice = $this->get_upgrade_notice( $response->new_version );
$this->major_untested_plugins = $this->get_untested_plugins( $response->new_version, 'major' );
$this->major_untested_plugins = $this->get_untested_plugins( $response->new_version, $version_type );
$current_version_parts = explode( '.', Constants::get_constant( 'WC_VERSION' ) );
$new_version_parts = explode( '.', $this->new_version );

View File

@ -18,7 +18,7 @@ $untested_plugins_msg = sprintf(
?>
<div id="wc_untested_extensions_modal">
<div class="wc_untested_extensions_modal--content">
<h1><?php esc_html_e( "This is a major update, are you sure you're ready?", 'woocommerce' ); ?></h1>
<h1><?php esc_html_e( "Are you sure you're ready?", 'woocommerce' ); ?></h1>
<div class="wc_plugin_upgrade_notice extensions_warning">
<p><?php echo esc_html( $untested_plugins_msg ); ?></p>
@ -41,7 +41,7 @@ $untested_plugins_msg = sprintf(
</table>
</div>
<p><?php esc_html_e( 'As this is a major update, we strongly recommend creating a backup of your site before updating.', 'woocommerce' ); ?> <a href="https://woocommerce.com/2017/05/create-use-backups-woocommerce/" target="_blank"><?php esc_html_e( 'Learn more', 'woocommerce' ); ?></a></p>
<p><?php esc_html_e( 'We strongly recommend creating a backup of your site before updating.', 'woocommerce' ); ?> <a href="https://woocommerce.com/2017/05/create-use-backups-woocommerce/" target="_blank"><?php esc_html_e( 'Learn more', 'woocommerce' ); ?></a></p>
<?php if ( current_user_can( 'update_plugins' ) ) : ?>
<div class="actions">

View File

@ -191,6 +191,22 @@ class WC_Settings_Emails extends WC_Settings_Page {
'id' => 'email_template_options',
),
array(
'title' => __( 'Store management insights', 'woocommerce' ),
'type' => 'title',
'id' => 'email_merchant_notes',
),
array(
'title' => __( 'Enable email insights', 'woocommerce' ),
'desc' => __( 'Receive email notifications with additional guidance to complete the basic store setup and helpful insights', 'woocommerce' ),
'id' => 'woocommerce_merchant_email_notifications',
'type' => 'checkbox',
'checkboxgroup' => 'start',
'default' => 'yes',
'autoload' => false,
),
)
);

View File

@ -9,6 +9,16 @@ if ( ! defined( 'ABSPATH' ) ) {
?>
<div class="wrap woocommerce">
<div id="message" class="error inline" style="margin-top:30px">
<p>
<strong>
<?php
/* translators: 1: Link URL */
echo wp_kses_post( sprintf( __( 'With the release of WooCommerce 4.0, these reports are being replaced. There is a new and better Analytics section available for users running WordPress 5.3+. Head on over to the <a href="%1$s">WooCommerce Analytics</a> or learn more about the new experience in the <a href="https://docs.woocommerce.com/document/woocommerce-analytics/" target="_blank">WooCommerce Analytics documentation</a>.', 'woocommerce' ), esc_url( wc_admin_url( '&path=/analytics/overview' ) ) ) );
?>
</strong>
</p>
</div>
<nav class="nav-tab-wrapper woo-nav-tab-wrapper">
<?php
foreach ( $reports as $key => $report_group ) {

View File

@ -11,7 +11,8 @@ global $wpdb;
if ( ! defined( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) ) {
// Define if we're checking against major or minor versions.
define( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE', 'major' );
// Since 5.0 all versions are backwards compatible, so there's no more check.
define( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE', 'none' );
}
$report = wc()->api->get_endpoint_data( '/wc/v3/system_status' );
@ -844,10 +845,11 @@ if ( 0 < count( $dropins_mu_plugins['mu_plugins'] ) ) :
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . wp_kses_post( sprintf( __( 'Page visibility should be <a href="%s" target="_blank">public</a>', 'woocommerce' ), 'https://wordpress.org/support/article/content-visibility/' ) ) . '</mark>';
$found_error = true;
} else {
// Shortcode check.
if ( $_page['shortcode_required'] ) {
if ( ! $_page['shortcode_present'] ) {
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . sprintf( esc_html__( 'Page does not contain the shortcode.', 'woocommerce' ), esc_html( $_page['shortcode'] ) ) . '</mark>';
// Shortcode and block check.
if ( $_page['shortcode_required'] || $_page['block_required'] ) {
if ( ! $_page['shortcode_present'] && ! $_page['block_present'] ) {
/* Translators: %1$s: shortcode text, %2$s: block slug. */
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . ( $_page['block_required'] ? sprintf( esc_html__( 'Page does not contain the %1$s shortcode or the %2$s block.', 'woocommerce' ), esc_html( $_page['shortcode'] ), esc_html( $_page['block'] ) ) : sprintf( esc_html__( 'Page does not contain the %s shortcode.', 'woocommerce' ), esc_html( $_page['shortcode'] ) ) ) . '</mark>'; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */
$found_error = true;
}
}

View File

@ -8,9 +8,7 @@
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?>
<div id="poststuff" class="woocommerce-reports-wide">
<div class="postbox">

View File

@ -0,0 +1,84 @@
<?php
/**
* Blocks Utils
*
* Used by core components that need to work with blocks.
*
* @package WooCommerce\Blocks\Utils
* @version 5.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* Blocks Utility class.
*/
class WC_Blocks_Utils {
/**
* Get blocks from a woocommerce page.
*
* @param string $woo_page_name A woocommerce page e.g. `checkout` or `cart`.
* @return array Array of blocks as returned by parse_blocks().
*/
private static function get_all_blocks_from_page( $woo_page_name ) {
$page_id = wc_get_page_id( $woo_page_name );
$page = get_post( $page_id );
if ( ! $page ) {
return array();
}
$blocks = parse_blocks( $page->post_content );
if ( ! $blocks ) {
return array();
}
return $blocks;
}
/**
* Get all instances of the specified block on a specific woo page
* (e.g. `cart` or `checkout` page).
*
* @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`.
* @param string $woo_page_name The woo page to search, e.g. `cart`.
* @return array Array of blocks as returned by parse_blocks().
*/
public static function get_blocks_from_page( $block_name, $woo_page_name ) {
$page_blocks = self::get_all_blocks_from_page( $woo_page_name );
// Get any instances of the specified block.
return array_values(
array_filter(
$page_blocks,
function ( $block ) use ( $block_name ) {
return ( $block_name === $block['blockName'] );
}
)
);
}
/**
* Check if a given page contains a particular block.
*
* @param int|WP_Post $page Page post ID or post object.
* @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`.
* @return bool Boolean value if the page contains the block or not. Null in case the page does not exist.
*/
public static function has_block_in_page( $page, $block_name ) {
$page_to_check = get_post( $page );
if ( null === $page_to_check ) {
return false;
}
$blocks = parse_blocks( $page_to_check->post_content );
foreach ( $blocks as $block ) {
if ( $block_name === $block['blockName'] ) {
return true;
}
}
return false;
}
}

View File

@ -340,6 +340,49 @@ class WC_Comments {
return $average;
}
/**
* Utility function for getting review counts for multiple products in one query. This is not cached.
*
* @since 5.0.0
*
* @param array $product_ids Array of product IDs.
*
* @return array
*/
public static function get_review_counts_for_product_ids( $product_ids ) {
global $wpdb;
if ( empty( $product_ids ) ) {
return array();
}
$product_id_string_placeholder = substr( str_repeat( ',%s', count( $product_ids ) ), 1 );
$review_counts = $wpdb->get_results(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Ignored for allowing interpolation in IN query.
$wpdb->prepare(
"
SELECT comment_post_ID as product_id, COUNT( comment_post_ID ) as review_count
FROM $wpdb->comments
WHERE
comment_parent = 0
AND comment_post_ID IN ( $product_id_string_placeholder )
AND comment_approved = '1'
AND comment_type in ( 'review', '', 'comment' )
GROUP BY product_id
",
$product_ids
),
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared.
ARRAY_A
);
// Convert to key value pairs.
$counts = array_replace( array_fill_keys( $product_ids, 0 ), array_column( $review_counts, 'review_count', 'product_id' ) );
return $counts;
}
/**
* Get product review count for a product (not replies). Please note this is not cached.
*
@ -348,22 +391,9 @@ class WC_Comments {
* @return int
*/
public static function get_review_count_for_product( &$product ) {
global $wpdb;
$counts = self::get_review_counts_for_product_ids( array( $product->get_id() ) );
$count = $wpdb->get_var(
$wpdb->prepare(
"
SELECT COUNT(*) FROM $wpdb->comments
WHERE comment_parent = 0
AND comment_post_ID = %d
AND comment_approved = '1'
AND comment_type in ( 'review', '', 'comment' )
",
$product->get_id()
)
);
return $count;
return $counts[ $product->get_id() ];
}
/**

View File

@ -978,6 +978,15 @@ class WC_Countries {
'required' => false,
),
),
'GT' => array(
'postcode' => array(
'required' => false,
'hidden' => true,
),
'state' => array(
'label' => __( 'Department', 'woocommerce' ),
),
),
'HK' => array(
'postcode' => array(
'required' => false,

View File

@ -111,7 +111,7 @@ class WC_Customer extends WC_Legacy_Customer {
}
// If this is a session, set or change the data store to sessions. Changes do not persist in the database.
if ( $is_session ) {
if ( $is_session && isset( WC()->session ) ) {
$this->data_store = WC_Data_Store::load( 'customer-session' );
$this->data_store->read( $this );
}

View File

@ -190,7 +190,7 @@ class WC_Emails {
$this->init();
// Email Header, Footer and content hooks.
add_action( 'woocommerce_email_header', array( $this, 'email_header' ), 10, 2 );
add_action( 'woocommerce_email_header', array( $this, 'email_header' ) );
add_action( 'woocommerce_email_footer', array( $this, 'email_footer' ) );
add_action( 'woocommerce_email_order_details', array( $this, 'order_downloads' ), 10, 4 );
add_action( 'woocommerce_email_order_details', array( $this, 'order_details' ), 10, 4 );
@ -263,26 +263,17 @@ class WC_Emails {
/**
* Get the email header.
*
* @param mixed $email_heading Heading for the email.
* @param WC_Email $email Email object for the email.
* @param mixed $email_heading Heading for the email.
*/
public function email_header( $email_heading, $email ) {
wc_get_template(
'emails/email-header.php',
array(
'email_heading' => $email_heading,
'email' => $email,
)
);
public function email_header( $email_heading ) {
wc_get_template( 'emails/email-header.php', array( 'email_heading' => $email_heading ) );
}
/**
* Get the email footer.
*
* @param WC_Email $email Email object for the email.
*/
public function email_footer( $email ) {
wc_get_template( 'emails/email-footer.php', array( 'email' => $email ) );
public function email_footer() {
wc_get_template( 'emails/email-footer.php' );
}
/**

View File

@ -157,6 +157,10 @@ class WC_Install {
'wc_update_450_sanitize_coupons_code',
'wc_update_450_db_version',
),
'5.0.0' => array(
'wc_update_500_fix_product_review_count',
'wc_update_500_db_version',
),
);
/**

View File

@ -41,6 +41,7 @@ class WC_Post_Data {
add_filter( 'update_post_metadata', array( __CLASS__, 'update_post_metadata' ), 10, 5 );
add_filter( 'wp_insert_post_data', array( __CLASS__, 'wp_insert_post_data' ) );
add_filter( 'oembed_response_data', array( __CLASS__, 'filter_oembed_response_data' ), 10, 2 );
add_filter( 'wp_untrash_post_status', array( __CLASS__, 'wp_untrash_post_status' ), 10, 3 );
// Status transitions.
add_action( 'transition_post_status', array( __CLASS__, 'transition_post_status' ), 10, 3 );
@ -122,12 +123,23 @@ class WC_Post_Data {
* Handle type changes.
*
* @since 3.0.0
*
* @param WC_Product $product Product data.
* @param string $from Origin type.
* @param string $to New type.
*/
public static function product_type_changed( $product, $from, $to ) {
if ( 'variable' === $from && 'variable' !== $to ) {
/**
* Filter to prevent variations from being deleted while switching from a variable product type to a variable product type.
*
* @since 5.0.0
*
* @param bool A boolean value of true will delete the variations.
* @param WC_Product $product Product data.
* @return string $from Origin type.
* @param string $to New type.
*/
if ( apply_filters( 'woocommerce_delete_variations_on_product_type_change', 'variable' === $from && 'variable' !== $to, $product, $from, $to ) ) {
// If the product is no longer variable, we should ensure all variations are removed.
$data_store = WC_Data_Store::load( 'product-variable' );
$data_store->delete_variations( $product->get_id(), true );
@ -490,6 +502,24 @@ class WC_Post_Data {
}
}
/**
* Ensure statuses are correctly reassigned when restoring orders and products.
*
* @param string $new_status The new status of the post being restored.
* @param int $post_id The ID of the post being restored.
* @param string $previous_status The status of the post at the point where it was trashed.
* @return string
*/
public static function wp_untrash_post_status( $new_status, $post_id, $previous_status ) {
$post_types = array( 'shop_order', 'shop_coupon', 'product', 'product_variation' );
if ( in_array( get_post_type( $post_id ), $post_types, true ) ) {
$new_status = $previous_status;
}
return $new_status;
}
/**
* When setting stock level, ensure the stock status is kept in sync.
*

View File

@ -313,63 +313,66 @@ class WC_Shipping {
$package['rates'] = array();
// If the package is not shippable, e.g. trying to ship to an invalid country, do not calculate rates.
if ( $this->is_package_shippable( $package ) ) {
// Check if we need to recalculate shipping for this package.
$package_to_hash = $package;
// Remove data objects so hashes are consistent.
foreach ( $package_to_hash['contents'] as $item_id => $item ) {
unset( $package_to_hash['contents'][ $item_id ]['data'] );
}
// Get rates stored in the WC session data for this package.
$wc_session_key = 'shipping_for_package_' . $package_key;
$stored_rates = WC()->session->get( $wc_session_key );
// Calculate the hash for this package so we can tell if it's changed since last calculation.
$package_hash = 'wc_ship_' . md5( wp_json_encode( $package_to_hash ) . WC_Cache_Helper::get_transient_version( 'shipping' ) );
if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) ) {
foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) {
if ( ! $shipping_method->supports( 'shipping-zones' ) || $shipping_method->get_instance_id() ) {
/**
* Fires before getting shipping rates for a package.
*
* @since 4.3.0
* @param array $package Package of cart items.
* @param WC_Shipping_Method $shipping_method Shipping method instance.
*/
do_action( 'woocommerce_before_get_rates_for_package', $package, $shipping_method );
// Use + instead of array_merge to maintain numeric keys.
$package['rates'] = $package['rates'] + $shipping_method->get_rates_for_package( $package );
/**
* Fires after getting shipping rates for a package.
*
* @since 4.3.0
* @param array $package Package of cart items.
* @param WC_Shipping_Method $shipping_method Shipping method instance.
*/
do_action( 'woocommerce_after_get_rates_for_package', $package, $shipping_method );
}
}
// Filter the calculated rates.
$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
// Store in session to avoid recalculation.
WC()->session->set(
$wc_session_key,
array(
'package_hash' => $package_hash,
'rates' => $package['rates'],
)
);
} else {
$package['rates'] = $stored_rates['rates'];
}
if ( ! $this->is_package_shippable( $package ) ) {
return $package;
}
// Check if we need to recalculate shipping for this package.
$package_to_hash = $package;
// Remove data objects so hashes are consistent.
foreach ( $package_to_hash['contents'] as $item_id => $item ) {
unset( $package_to_hash['contents'][ $item_id ]['data'] );
}
// Get rates stored in the WC session data for this package.
$wc_session_key = 'shipping_for_package_' . $package_key;
$stored_rates = WC()->session->get( $wc_session_key );
// Calculate the hash for this package so we can tell if it's changed since last calculation.
$package_hash = 'wc_ship_' . md5( wp_json_encode( $package_to_hash ) . WC_Cache_Helper::get_transient_version( 'shipping' ) );
if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) ) {
foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) {
if ( ! $shipping_method->supports( 'shipping-zones' ) || $shipping_method->get_instance_id() ) {
/**
* Fires before getting shipping rates for a package.
*
* @since 4.3.0
* @param array $package Package of cart items.
* @param WC_Shipping_Method $shipping_method Shipping method instance.
*/
do_action( 'woocommerce_before_get_rates_for_package', $package, $shipping_method );
// Use + instead of array_merge to maintain numeric keys.
$package['rates'] = $package['rates'] + $shipping_method->get_rates_for_package( $package );
/**
* Fires after getting shipping rates for a package.
*
* @since 4.3.0
* @param array $package Package of cart items.
* @param WC_Shipping_Method $shipping_method Shipping method instance.
*/
do_action( 'woocommerce_after_get_rates_for_package', $package, $shipping_method );
}
}
// Filter the calculated rates.
$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
// Store in session to avoid recalculation.
WC()->session->set(
$wc_session_key,
array(
'package_hash' => $package_hash,
'rates' => $package['rates'],
)
);
} else {
$package['rates'] = $stored_rates['rates'];
}
return $package;
}

View File

@ -352,30 +352,105 @@ class WC_Tracker {
}
/**
* Get order counts
*
* @return array
*/
private static function get_order_counts() {
$order_count = array();
$order_count_data = wp_count_posts( 'shop_order' );
foreach ( wc_get_order_statuses() as $status_slug => $status_name ) {
$order_count[ $status_slug ] = $order_count_data->{ $status_slug };
}
return $order_count;
}
/**
* Combine all order data.
* Get all order data.
*
* @return array
*/
private static function get_orders() {
$order_dates = self::get_order_dates();
$order_counts = self::get_order_counts();
$order_totals = self::get_order_totals();
$args = array(
'type' => array( 'shop_order', 'shop_order_refund' ),
'limit' => get_option( 'posts_per_page' ),
'paged' => 1,
);
return array_merge( $order_dates, $order_counts, $order_totals );
$first = time();
$last = 0;
$processing_first = time();
$processing_last = 0;
$orders = wc_get_orders( $args );
$orders_count = count( $orders );
while ( $orders_count ) {
foreach ( $orders as $order ) {
$date_created = (int) $order->get_date_created()->getTimestamp();
$type = $order->get_type();
$status = $order->get_status();
if ( 'shop_order' == $type ) {
// Find the first and last order dates for completed and processing statuses.
if ( 'completed' == $status && $date_created < $first ) {
$first = $date_created;
}
if ( 'completed' == $status && $date_created > $last ) {
$last = $date_created;
}
if ( 'processing' == $status && $date_created < $processing_first ) {
$processing_first = $date_created;
}
if ( 'processing' == $status && $date_created > $processing_last ) {
$processing_last = $date_created;
}
// Get order counts by status.
$status = 'wc-' . $status;
if ( ! isset( $order_data[ $status ] ) ) {
$order_data[ $status ] = 1;
} else {
$order_data[ $status ] += 1;
}
// Count number of orders by gateway used.
$gateway = $order->get_payment_method();
if ( ! empty( $gateway ) && in_array( $status, array( 'wc-completed', 'wc-refunded', 'wc-processing' ) ) ) {
$gateway = 'gateway_' . $gateway;
if ( ! isset( $order_data[ $gateway ] ) ) {
$order_data[ $gateway ] = 1;
} else {
$order_data[ $gateway ] += 1;
}
}
} else {
// If it is a refunded order (shop_order_refunnd type), add the prefix as this prefix gets
// added midway in the if clause.
$status = 'wc-' . $status;
}
// Calculate the gross total for 'completed' and 'processing' orders.
$total = $order->get_total();
if ( in_array( $status, array( 'wc-completed', 'wc-refunded' ) ) ) {
if ( ! isset( $order_data['gross'] ) ) {
$order_data['gross'] = $total;
} else {
$order_data['gross'] += $total;
}
} elseif ( 'wc-processing' == $status ) {
if ( ! isset( $order_data['processing_gross'] ) ) {
$order_data['processing_gross'] = $total;
} else {
$order_data['processing_gross'] += $total;
}
}
}
$args['paged']++;
$orders = wc_get_orders( $args );
$orders_count = count( $orders );
}
$order_data['first'] = gmdate( 'Y-m-d H:i:s', $first );
$order_data['last'] = gmdate( 'Y-m-d H:i:s', $last );
$order_data['processing_first'] = gmdate( 'Y-m-d H:i:s', $processing_first );
$order_data['processing_last'] = gmdate( 'Y-m-d H:i:s', $processing_last );
return $order_data;
}
/**
@ -543,94 +618,12 @@ class WC_Tracker {
/**
* Get order totals
*
* @deprecated 5.1.0 Logic moved to get_orders.
* @return array
*/
public static function get_order_totals() {
global $wpdb;
$gross_total = $wpdb->get_var(
"
SELECT
SUM( order_meta.meta_value ) AS 'gross_total'
FROM {$wpdb->prefix}posts AS orders
LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID
WHERE order_meta.meta_key = '_order_total'
AND orders.post_status in ( 'wc-completed', 'wc-refunded' )
GROUP BY order_meta.meta_key
"
);
if ( is_null( $gross_total ) ) {
$gross_total = 0;
}
$processing_gross_total = $wpdb->get_var(
"
SELECT
SUM( order_meta.meta_value ) AS 'gross_total'
FROM {$wpdb->prefix}posts AS orders
LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID
WHERE order_meta.meta_key = '_order_total'
AND orders.post_status = 'wc-processing'
GROUP BY order_meta.meta_key
"
);
if ( is_null( $processing_gross_total ) ) {
$processing_gross_total = 0;
}
return array(
'gross' => $gross_total,
'processing_gross' => $processing_gross_total,
);
}
/**
* Get last order date
*
* @return string
*/
private static function get_order_dates() {
global $wpdb;
$min_max = $wpdb->get_row(
"
SELECT
MIN( post_date_gmt ) as 'first', MAX( post_date_gmt ) as 'last'
FROM {$wpdb->prefix}posts
WHERE post_type = 'shop_order'
AND post_status = 'wc-completed'
",
ARRAY_A
);
if ( is_null( $min_max ) ) {
$min_max = array(
'first' => '-',
'last' => '-',
);
}
$processing_min_max = $wpdb->get_row(
"
SELECT
MIN( post_date_gmt ) as 'processing_first', MAX( post_date_gmt ) as 'processing_last'
FROM {$wpdb->prefix}posts
WHERE post_type = 'shop_order'
AND post_status = 'wc-processing'
",
ARRAY_A
);
if ( is_null( $processing_min_max ) ) {
$processing_min_max = array(
'processing_first' => '-',
'processing_last' => '-',
);
}
return array_merge( $min_max, $processing_min_max );
wc_deprecated_function( 'WC_Tracker::get_order_totals', '5.1.0', '' );
return self::get_orders();
}
/**
@ -660,49 +653,6 @@ class WC_Tracker {
return ( '0' !== $result ) ? 'Yes' : 'No';
}
/**
* Get blocks from a woocommerce page.
*
* @param string $woo_page_name A woocommerce page e.g. `checkout` or `cart`.
* @return array Array of blocks as returned by parse_blocks().
*/
private static function get_all_blocks_from_page( $woo_page_name ) {
$page_id = wc_get_page_id( $woo_page_name );
$page = get_post( $page_id );
if ( ! $page ) {
return array();
}
$blocks = parse_blocks( $page->post_content );
if ( ! $blocks ) {
return array();
}
return $blocks;
}
/**
* Get all instances of the specified block on a specific woo page
* (e.g. `cart` or `checkout` page).
*
* @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`.
* @param string $woo_page_name The woo page to search, e.g. `cart`.
* @return array Array of blocks as returned by parse_blocks().
*/
private static function get_blocks_from_page( $block_name, $woo_page_name ) {
$page_blocks = self::get_all_blocks_from_page( $woo_page_name );
// Get any instances of the specified block.
return array_values(
array_filter(
$page_blocks,
function ( $block ) use ( $block_name ) {
return ( $block_name === $block['blockName'] );
}
)
);
}
/**
* Get tracker data for a specific block type on a woocommerce page.
@ -714,7 +664,7 @@ class WC_Tracker {
* - block_attributes
*/
public static function get_block_tracker_data( $block_name, $woo_page_name ) {
$blocks = self::get_blocks_from_page( $block_name, $woo_page_name );
$blocks = WC_Blocks_Utils::get_blocks_from_page( $block_name, $woo_page_name );
$block_present = false;
$attributes = array();

View File

@ -8,6 +8,7 @@
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
use Automattic\WooCommerce\Proxies\LegacyProxy;
/**
@ -202,6 +203,9 @@ final class WooCommerce {
add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 );
add_action( 'activated_plugin', array( $this, 'activated_plugin' ) );
add_action( 'deactivated_plugin', array( $this, 'deactivated_plugin' ) );
// These classes set up hooks on instantiation.
wc_get_container()->get( DownloadPermissionsAdjuster::class );
}
/**
@ -421,6 +425,7 @@ final class WooCommerce {
include_once WC_ABSPATH . 'includes/queue/class-wc-action-queue.php';
include_once WC_ABSPATH . 'includes/queue/class-wc-queue.php';
include_once WC_ABSPATH . 'includes/admin/marketplace-suggestions/class-wc-marketplace-updater.php';
include_once WC_ABSPATH . 'includes/blocks/class-wc-blocks-utils.php';
/**
* Data stores - used to store and retrieve CRUD object data from the database.
@ -801,6 +806,10 @@ final class WooCommerce {
public function activated_plugin( $filename ) {
include_once dirname( __FILE__ ) . '/admin/helper/class-wc-helper.php';
if ( '/woocommerce.php' === substr( $filename, -16 ) ) {
set_transient( 'woocommerce_activated_plugin', $filename );
}
WC_Helper::activated_plugin( $filename );
}
@ -897,7 +906,7 @@ final class WooCommerce {
'https://wordpress.org/plugins/woocommerce/',
'https://github.com/woocommerce/woocommerce/releases'
);
printf( '<div class="error"><p>%s %s</p></div>', $message_one, $message_two ); /* WPCS: xss ok. */
printf( '<div class="error"><p>%s %s</p></div>', $message_one, $message_two ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**

View File

@ -428,7 +428,7 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
*
* @since 3.0.0
* @param WC_Coupon $coupon Coupon object.
* @param id $user_id User ID.
* @param int $user_id User ID.
* @return int
*/
public function get_usage_by_user_id( &$coupon, $user_id ) {

View File

@ -16,6 +16,38 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store_Interface {
/**
* Names of the database fields for the download permissions table.
*/
const DOWNLOAD_PERMISSION_DB_FIELDS = array(
'download_id',
'product_id',
'user_id',
'user_email',
'order_id',
'order_key',
'downloads_remaining',
'access_granted',
'download_count',
'access_expires',
);
/**
* Create download permission for a user, from an array of data.
*
* @param array $data Data to create the permission for.
* @returns int The database id of the created permission, or false if the permission creation failed.
*/
public function create_from_data( $data ) {
$data = array_intersect_key( $data, array_flip( self::DOWNLOAD_PERMISSION_DB_FIELDS ) );
$id = $this->insert_new_download_permission( $data );
do_action( 'woocommerce_grant_product_download_access', $data );
return $id;
}
/**
* Create download permission for a user.
*
@ -29,18 +61,41 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
$download->set_access_granted( time() );
}
$data = array(
'download_id' => $download->get_download_id( 'edit' ),
'product_id' => $download->get_product_id( 'edit' ),
'user_id' => $download->get_user_id( 'edit' ),
'user_email' => $download->get_user_email( 'edit' ),
'order_id' => $download->get_order_id( 'edit' ),
'order_key' => $download->get_order_key( 'edit' ),
'downloads_remaining' => $download->get_downloads_remaining( 'edit' ),
'access_granted' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ),
'download_count' => $download->get_download_count( 'edit' ),
'access_expires' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null,
);
$data = array();
foreach ( self::DOWNLOAD_PERMISSION_DB_FIELDS as $db_field_name ) {
$value = call_user_func( array( $download, 'get_' . $db_field_name ), 'edit' );
$data[ $db_field_name ] = $value;
}
$inserted_id = $this->insert_new_download_permission( $data );
if ( $inserted_id ) {
$download->set_id( $inserted_id );
$download->apply_changes();
}
do_action( 'woocommerce_grant_product_download_access', $data );
}
/**
* Create download permission for a user, from an array of data.
* Assumes that all the keys in the passed data are valid.
*
* @param array $data Data to create the permission for.
* @return int The database id of the created permission, or false if the permission creation failed.
*/
private function insert_new_download_permission( $data ) {
global $wpdb;
// Always set a access granted date.
if ( ! isset( $data['access_granted'] ) ) {
$data['access_granted'] = time();
}
$data['access_granted'] = $this->adjust_date_for_db( $data['access_granted'] );
if ( isset( $data['access_expires'] ) ) {
$data['access_expires'] = $this->adjust_date_for_db( $data['access_expires'] );
}
$format = array(
'%s',
@ -61,12 +116,29 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
apply_filters( 'woocommerce_downloadable_file_permission_format', $format, $data )
);
if ( $result ) {
$download->set_id( $wpdb->insert_id );
$download->apply_changes();
return $result ? $wpdb->insert_id : false;
}
/**
* Adjust a date value to be inserted in the database.
*
* @param mixed $date The date value. Can be a WC_DateTime, a timestamp, or anything else that "date" recognizes.
* @return string The date converted to 'Y-m-d' format.
* @throws Exception The passed value can't be converted to a date.
*/
private function adjust_date_for_db( $date ) {
if ( 'WC_DateTime' === get_class( $date ) ) {
$date = $date->getTimestamp();
}
do_action( 'woocommerce_grant_product_download_access', $data );
$adjusted_date = date( 'Y-m-d', $date ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
if ( $adjusted_date ) {
return $adjusted_date;
}
$msg = sprintf( __( "I don't know how to get a date from a %s", 'woocommerce' ), is_object( $date ) ? get_class( $date ) : gettype( $date ) );
throw new Exception( $msg );
}
/**
@ -128,8 +200,10 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
'order_id' => $download->get_order_id( 'edit' ),
'order_key' => $download->get_order_key( 'edit' ),
'downloads_remaining' => $download->get_downloads_remaining( 'edit' ),
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
'access_granted' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ),
'download_count' => $download->get_download_count( 'edit' ),
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
'access_expires' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null,
);
@ -412,7 +486,7 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
)
ORDER BY permissions.order_id, permissions.product_id, permissions.permission_id;",
$customer_id,
date( 'Y-m-d', current_time( 'timestamp' ) )
date( 'Y-m-d', current_time( 'timestamp' ) ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
)
);
}

View File

@ -305,7 +305,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
* Should contain the fields token_id, gateway_id, token, user_id, type, is_default.
*
* @since 3.0.0
* @param id $token_id Token ID.
* @param int $token_id Token ID.
* @return object
*/
public function get_token_by_id( $token_id ) {
@ -322,7 +322,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
* Returns metadata for a specific payment token.
*
* @since 3.0.0
* @param id $token_id Token ID.
* @param int $token_id Token ID.
* @return array
*/
public function get_metadata( $token_id ) {
@ -333,7 +333,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
* Get a token's type by ID.
*
* @since 3.0.0
* @param id $token_id Token ID.
* @param int $token_id Token ID.
* @return string
*/
public function get_token_type_by_id( $token_id ) {
@ -353,7 +353,7 @@ class WC_Payment_Token_Data_Store extends WC_Data_Store_WP implements WC_Payment
*
* @since 3.0.0
*
* @param id $token_id Token ID.
* @param int $token_id Token ID.
* @param bool $status Whether given payment token is the default payment token or not.
*
* @return void

View File

@ -6,6 +6,7 @@
*/
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
use Automattic\WooCommerce\Utilities\NumberUtil;
if ( ! defined( 'ABSPATH' ) ) {
@ -265,6 +266,10 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$this->handle_updated_props( $product );
$this->clear_caches( $product );
wc_get_container()
->get( DownloadPermissionsAdjuster::class )
->maybe_schedule_adjust_download_permissions( $product );
$product->apply_changes();
do_action( 'woocommerce_update_product', $product->get_id(), $product );

View File

@ -74,33 +74,41 @@ class WC_Shipping_Zone_Data_Store extends WC_Data_Store_WP implements WC_Shippin
public function read( &$zone ) {
global $wpdb;
$zone_data = false;
if ( 0 !== $zone->get_id() || '0' !== $zone->get_id() ) {
$zone_data = $wpdb->get_row(
$wpdb->prepare(
"SELECT zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1",
$zone->get_id()
)
);
}
// Zone 0 is used as a default if no other zones fit.
if ( 0 === $zone->get_id() || '0' === $zone->get_id() ) {
$this->read_zone_locations( $zone );
$zone->set_zone_name( __( 'Locations not covered by your other zones', 'woocommerce' ) );
$zone->read_meta_data();
$zone->set_object_read( true );
/**
* Indicate that the WooCommerce shipping zone has been loaded.
*
* @param WC_Shipping_Zone $zone The shipping zone that has been loaded.
*/
do_action( 'woocommerce_shipping_zone_loaded', $zone );
} elseif ( $zone_data ) {
$zone->set_zone_name( $zone_data->zone_name );
$zone->set_zone_order( $zone_data->zone_order );
$this->read_zone_locations( $zone );
$zone->read_meta_data();
$zone->set_object_read( true );
do_action( 'woocommerce_shipping_zone_loaded', $zone );
} else {
return;
}
$zone_data = $wpdb->get_row(
$wpdb->prepare(
"SELECT zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1",
$zone->get_id()
)
);
if ( ! $zone_data ) {
throw new Exception( __( 'Invalid data store.', 'woocommerce' ) );
}
$zone->set_zone_name( $zone_data->zone_name );
$zone->set_zone_order( $zone_data->zone_order );
$this->read_zone_locations( $zone );
$zone->read_meta_data();
$zone->set_object_read( true );
/** This action is documented in includes/datastores/class-wc-shipping-zone-data-store.php. */
do_action( 'woocommerce_shipping_zone_loaded', $zone );
}
/**

View File

@ -92,10 +92,25 @@ if ( ! class_exists( 'WC_Email_New_Order' ) ) :
$this->object = $order;
$this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
$this->placeholders['{order_number}'] = $this->object->get_order_number();
$email_already_sent = $order->get_meta( '_new_order_email_sent' );
}
/**
* Controls if new order emails can be resend multiple times.
*
* @since 5.0.0
* @param bool $allows Defaults to true.
*/
if ( 'true' === $email_already_sent && ! apply_filters( 'woocommerce_new_order_email_allows_resend', false ) ) {
return;
}
if ( $this->is_enabled() && $this->get_recipient() ) {
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
$order->update_meta_data( '_new_order_email_sent', 'true' );
$order->save();
}
$this->restore_locale();

View File

@ -260,18 +260,30 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
$extra_fields = array( 'meta_data', 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines', 'refunds' );
$format_decimal = array( 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'shipping_total', 'shipping_tax', 'cart_tax', 'total', 'total_tax' );
$format_date = array( 'date_created', 'date_modified', 'date_completed', 'date_paid' );
// These fields are dependent on other fields.
$dependent_fields = array(
'date_created_gmt' => 'date_created',
'date_modified_gmt' => 'date_modified',
'date_completed_gmt' => 'date_completed',
'date_paid_gmt' => 'date_paid',
);
$format_line_items = array( 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines' );
// Only fetch fields that we need.
$request = func_get_arg( 1 );
if ( $request ) {
$fields = $this->get_fields_for_response( $request );
$extra_fields = array_intersect( $extra_fields, $fields );
$format_decimal = array_intersect( $format_decimal, $fields );
$format_date = array_intersect( $format_date, $fields );
$format_line_items = array_intersect( $format_line_items, $fields );
$fields = $this->get_fields_for_response( $this->request );
foreach ( $dependent_fields as $field_key => $dependency ) {
if ( in_array( $field_key, $fields ) && ! in_array( $dependency, $fields ) ) {
$fields[] = $dependency;
}
}
$extra_fields = array_intersect( $extra_fields, $fields );
$format_decimal = array_intersect( $format_decimal, $fields );
$format_date = array_intersect( $format_date, $fields );
$format_line_items = array_intersect( $format_line_items, $fields );
$data = $order->get_base_data();
// Add extra data as necessary.
@ -281,7 +293,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
$data['meta_data'] = $order->get_meta_data();
break;
case 'line_items':
$data['line_items'] = $order->get_items( 'line_item');
$data['line_items'] = $order->get_items( 'line_item' );
break;
case 'tax_lines':
$data['tax_lines'] = $order->get_items( 'tax' );
@ -296,6 +308,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
$data['coupon_lines'] = $order->get_items( 'coupon' );
break;
case 'refunds':
$data['refunds'] = array();
foreach ( $order->get_refunds() as $refund ) {
$data['refunds'][] = array(
'id' => $refund->get_id(),
@ -387,10 +400,10 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
public function prepare_object_for_response( $object, $request ) {
$this->request = $request;
$this->request['dp'] = is_null( $this->request['dp'] ) ? wc_get_price_decimals() : absint( $this->request['dp'] );
$data = $this->get_formatted_item_data( $object, $request );
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$request['context'] = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->get_formatted_item_data( $object );
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$data = $this->filter_response_by_context( $data, $request['context'] );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $object, $request ) );

View File

@ -158,8 +158,9 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
* @return WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->get_product_data( $object, $context, $request );
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$this->request = $request;
$data = $this->get_product_data( $object, $context, $request );
// Add variations to variable products.
if ( $object->is_type( 'variable' ) && $object->has_child() ) {
@ -591,6 +592,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
/**
* Fetch price HTML.
*
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
@ -602,6 +604,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
/**
* Fetch related IDs.
*
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
@ -613,6 +616,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
/**
* Fetch meta data.
*
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
@ -625,18 +629,20 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
/**
* Get product data.
*
* @param WC_Product $product Product instance.
* @param string $context Request context. Options: 'view' and 'edit'.
* @param WP_REST_Request $request Current request object. For backward compatibility, we pass this argument silently.
* @param WC_Product $product Product instance.
* @param string $context Request context. Options: 'view' and 'edit'.
*
* @return array
*/
protected function get_product_data( $product, $context = 'view' ) {
$fields = array();
$request = func_get_arg( 2 );
if ( $request instanceof WP_REST_Request ) {
$fields = $this->get_fields_for_response( $request );
}
/*
* @param WP_REST_Request $request Current request object. For backward compatibility, we pass this argument silently.
*
* TODO: Refactor to fix this behavior when DI gets included to make it obvious and clean.
*/
$request = func_num_args() >= 2 ? func_get_arg( 2 ) : new WP_REST_Request( '', '', array( 'context' => $context ) );
$fields = $this->get_fields_for_response( $request );
$base_data = array();
foreach ( $fields as $field ) {
switch ( $field ) {
@ -649,7 +655,6 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
case 'slug':
$base_data['slug'] = $product->get_slug( $context );
break;
case 'permalink':
$base_data['permalink'] = $product->get_permalink();
break;
@ -848,7 +853,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
*
* @return array Links for the given post.
* @return array Links for the given post.
*/
protected function prepare_links( $object, $request ) {
$links = array(

View File

@ -1162,7 +1162,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
/**
* Returns a mini-report on WC pages and if they are configured correctly:
* Present, visible, and including the correct shortcode.
* Present, visible, and including the correct shortcode or block.
*
* @return array
*/
@ -1172,22 +1172,27 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
_x( 'Shop base', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_shop_page_id',
'shortcode' => '',
'block' => '',
),
_x( 'Cart', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_cart_page_id',
'shortcode' => '[' . apply_filters( 'woocommerce_cart_shortcode_tag', 'woocommerce_cart' ) . ']',
'block' => 'woocommerce/cart',
),
_x( 'Checkout', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_checkout_page_id',
'shortcode' => '[' . apply_filters( 'woocommerce_checkout_shortcode_tag', 'woocommerce_checkout' ) . ']',
'block' => 'woocommerce/checkout',
),
_x( 'My account', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_myaccount_page_id',
'shortcode' => '[' . apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) . ']',
'block' => '',
),
_x( 'Terms and conditions', 'Page setting', 'woocommerce' ) => array(
'option' => 'woocommerce_terms_page_id',
'shortcode' => '',
'block' => '',
),
);
@ -1199,6 +1204,8 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
$page_visible = false;
$shortcode_present = false;
$shortcode_required = false;
$block_present = false;
$block_required = false;
// Page checks.
if ( $page_id ) {
@ -1220,6 +1227,12 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
}
}
// Block checks.
if ( $values['block'] && get_post( $page_id ) ) {
$block_required = true;
$block_present = WC_Blocks_Utils::has_block_in_page( $page_id, $values['block'] );
}
// Wrap up our findings into an output array.
$pages_output[] = array(
'page_name' => $page_name,
@ -1228,8 +1241,11 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
'page_exists' => $page_exists,
'page_visible' => $page_visible,
'shortcode' => $values['shortcode'],
'block' => $values['block'],
'shortcode_required' => $shortcode_required,
'shortcode_present' => $shortcode_present,
'block_present' => $block_present,
'block_required' => $block_required,
);
}

View File

@ -25,25 +25,6 @@ class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller {
*/
protected $namespace = 'wc/v3';
/**
* Get Orders.
*
* @param array $query_args Query args.
*
* @return array Products.
*/
protected function get_objects( $query_args ) {
$query_args['paginate'] = true;
$results = wc_get_orders( $query_args );
return array(
'objects' => $results->orders,
'total' => $results->total,
'pages' => $results->max_num_pages,
);
}
/**
* Calculate coupons.
*

View File

@ -153,7 +153,8 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
}
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
$args, array(
$args,
array(
'key' => '_sku',
'value' => $skus,
'compare' => 'IN',
@ -164,7 +165,8 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
// Filter by tax class.
if ( ! empty( $request['tax_class'] ) ) {
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
$args, array(
$args,
array(
'key' => '_tax_class',
'value' => 'standard' !== $request['tax_class'] ? $request['tax_class'] : '',
)
@ -179,7 +181,8 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
// Filter product by stock_status.
if ( ! empty( $request['stock_status'] ) ) {
$args['meta_query'] = $this->add_meta_query( // WPCS: slow query ok.
$args, array(
$args,
array(
'key' => '_stock_status',
'value' => $request['stock_status'],
)
@ -217,25 +220,6 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
return $args;
}
/**
* Get products.
*
* @param array $query_args Query args.
*
* @return array Products.
*/
protected function get_objects( $query_args ) {
$query_args['paginate'] = true;
$query_args['return'] = 'objects';
$results = wc_get_products( $query_args );
return array(
'objects' => $results->products,
'total' => $results->total,
'pages' => $results->max_num_pages,
);
}
/**
* Set product images.
*
@ -332,7 +316,9 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
if ( 'variation' === $product->get_type() ) {
return new WP_Error(
"woocommerce_rest_invalid_{$this->post_type}_id", __( 'To manipulate product variations you should use the /products/&lt;product_id&gt;/variations/&lt;id&gt; endpoint.', 'woocommerce' ), array(
"woocommerce_rest_invalid_{$this->post_type}_id",
__( 'To manipulate product variations you should use the /products/&lt;product_id&gt;/variations/&lt;id&gt; endpoint.', 'woocommerce' ),
array(
'status' => 404,
)
);
@ -614,7 +600,34 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
// Product tags.
if ( isset( $request['tags'] ) && is_array( $request['tags'] ) ) {
$product = $this->save_taxonomy_terms( $product, $request['tags'], 'tag' );
$new_tags = array();
foreach ( $request['tags'] as $tag ) {
if ( ! isset( $tag['name'] ) ) {
$new_tags[] = $tag;
continue;
}
if ( ! term_exists( $tag['name'], 'product_tag' ) ) {
// Create the tag if it doesn't exist.
$term = wp_insert_term( $tag['name'], 'product_tag' );
if ( ! is_wp_error( $term ) ) {
$new_tags[] = array(
'id' => $term['term_id'],
);
continue;
}
} else {
// Tag exists, assume user wants to set the product with this tag.
$new_tags[] = array(
'id' => get_term_by( 'name', $tag['name'], 'product_tag' )->term_id,
);
}
}
$product = $this->save_taxonomy_terms( $product, $new_tags, 'tag' );
}
// Downloadable.
@ -1343,24 +1356,19 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
* Get product data.
*
* @param WC_Product $product Product instance.
* @param string $context Request context.
* Options: 'view' and 'edit'.
* @param array $fields List of fields to fetch. If empty, then all fields will be returned.
* For backward compatibility, we pass this argument silently.
* @param string $context Request context. Options: 'view' and 'edit'.
*
* @return array
*/
protected function get_product_data( $product, $context = 'view' ) {
$request = func_get_arg( 2 );
$data = parent::get_product_data( $product, $context, $request );
// Replace in_stock with stock_status.
$pos = array_search( 'in_stock', array_keys( $data ), true );
if ( false !== $pos ) {
$array_section_1 = array_slice( $data, 0, $pos, true );
$array_section_2 = array_slice( $data, $pos + 1, null, true );
$data = $array_section_1 + array( 'stock_status' => $product->get_stock_status( $context ) ) + $array_section_2;
$data = parent::get_product_data( ...func_get_args() );
// Add stock_status if needed.
if ( isset( $this->request ) ) {
$fields = $this->get_fields_for_response( $this->request );
if ( in_array( 'stock_status', $fields ) ) {
$data['stock_status'] = $product->get_stock_status( $context );
}
}
return $data;
}
}

View File

@ -1771,6 +1771,11 @@ function wc_uasort_comparison( $a, $b ) {
* @return int
*/
function wc_ascii_uasort_comparison( $a, $b ) {
// 'setlocale' is required for compatibility with PHP 8.
// Without it, 'iconv' will return '?'s instead of transliterated characters.
$prev_locale = setlocale( LC_CTYPE, 0 );
setlocale( LC_ALL, 'C.UTF-8' );
// phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
if ( function_exists( 'iconv' ) && defined( 'ICONV_IMPL' ) && @strcasecmp( ICONV_IMPL, 'unknown' ) !== 0 ) {
$a = @iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $a );
@ -1778,6 +1783,7 @@ function wc_ascii_uasort_comparison( $a, $b ) {
}
// phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
setlocale( LC_ALL, $prev_locale );
return strcmp( $a, $b );
}

View File

@ -2219,3 +2219,67 @@ function wc_update_450_sanitize_coupons_code() {
delete_option( 'woocommerce_update_450_last_coupon_id' );
return false;
}
/**
* Fixes product review count that might have been incorrect.
*
* See @link https://github.com/woocommerce/woocommerce/issues/27688.
*/
function wc_update_500_fix_product_review_count() {
global $wpdb;
$product_id = 0;
$last_product_id = get_option( 'woocommerce_update_500_last_product_id', '0' );
$products_data = $wpdb->get_results(
$wpdb->prepare(
"
SELECT post_id, meta_value
FROM $wpdb->postmeta
JOIN $wpdb->posts
ON $wpdb->postmeta.post_id = $wpdb->posts.ID
WHERE
post_type = 'product'
AND post_status = 'publish'
AND post_id > %d
AND meta_key = '_wc_review_count'
ORDER BY post_id ASC
LIMIT 10
",
$last_product_id
),
ARRAY_A
);
if ( empty( $products_data ) ) {
delete_option( 'woocommerce_update_500_last_product_id' );
return false;
}
$product_ids_to_check = array_column( $products_data, 'post_id' );
$actual_review_counts = WC_Comments::get_review_counts_for_product_ids( $product_ids_to_check );
foreach ( $products_data as $product_data ) {
$product_id = intval( $product_data['post_id'] );
$current_review_count = intval( $product_data['meta_value'] );
if ( intval( $actual_review_counts[ $product_id ] ) !== $current_review_count ) {
WC_Comments::clear_transients( $product_id );
}
}
// Start the run again.
if ( $product_id ) {
return update_option( 'woocommerce_update_500_last_product_id', $product_id );
}
delete_option( 'woocommerce_update_500_last_product_id' );
return false;
}
/**
* Update DB version to 5.0.0.
*/
function wc_update_500_db_version() {
WC_Install::update_db_version( '5.0.0' );
}

46
package-lock.json generated
View File

@ -6150,7 +6150,8 @@
"fsevents": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ=="
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
"optional": true
},
"gensync": {
"version": "1.0.0-beta.1",
@ -6790,6 +6791,7 @@
"@types/graceful-fs": "^4.1.2",
"anymatch": "^3.0.3",
"fb-watchman": "^2.0.0",
"fsevents": "^2.1.2",
"graceful-fs": "^4.2.4",
"jest-serializer": "^25.5.0",
"jest-util": "^25.5.0",
@ -8838,7 +8840,7 @@
"dev": true,
"requires": {
"@jest/globals": "^26.4.2",
"@woocommerce/e2e-utils": "file:tests/e2e/utils"
"config": "3.3.3"
},
"dependencies": {
"@jest/environment": {
@ -20613,8 +20615,8 @@
"version": "file:tests/e2e/utils",
"dev": true,
"requires": {
"@woocommerce/api": "0.1.0",
"@wordpress/e2e-test-utils": "^4.6.0",
"config": "3.3.3",
"faker": "^5.1.0",
"fishery": "^1.0.1"
},
@ -21081,7 +21083,7 @@
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=",
"dev": true
},
"acorn": {
@ -21278,7 +21280,7 @@
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=",
"dev": true
},
"are-we-there-yet": {
@ -21327,7 +21329,7 @@
"arr-flatten": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
"integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
"integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=",
"dev": true
},
"arr-union": {
@ -22051,7 +22053,7 @@
"babylon": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=",
"dev": true
},
"bail": {
@ -24020,7 +24022,7 @@
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=",
"dev": true,
"requires": {
"ms": "2.0.0"
@ -24821,7 +24823,7 @@
},
"es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"dev": true,
"requires": {
@ -25962,7 +25964,7 @@
"fs-readdir-recursive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
"integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
"integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=",
"dev": true
},
"fs-write-stream-atomic": {
@ -27354,6 +27356,16 @@
}
}
},
"grunt-newer": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/grunt-newer/-/grunt-newer-1.3.0.tgz",
"integrity": "sha1-g8y3od2ny9irI7BZAk6+YUrS80I=",
"dev": true,
"requires": {
"async": "^1.5.2",
"rimraf": "^2.5.2"
}
},
"grunt-phpcs": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/grunt-phpcs/-/grunt-phpcs-0.4.0.tgz",
@ -27363,7 +27375,7 @@
"grunt-postcss": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/grunt-postcss/-/grunt-postcss-0.9.0.tgz",
"integrity": "sha512-lglLcVaoOIqH0sFv7RqwUKkEFGQwnlqyAKbatxZderwZGV1nDyKHN7gZS9LUiTx1t5GOvRBx0BEalHMyVwFAIA==",
"integrity": "sha1-++WTSmvp6siTr20FfiMYyX+unaM=",
"dev": true,
"requires": {
"chalk": "^2.1.0",
@ -28726,7 +28738,7 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=",
"dev": true
},
"is-callable": {
@ -28893,7 +28905,7 @@
},
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"dev": true
},
@ -31954,7 +31966,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
@ -33122,7 +33134,7 @@
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=",
"dev": true,
"requires": {
"are-we-there-yet": "~1.1.2",
@ -35059,7 +35071,7 @@
},
"safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true,
"requires": {
@ -36843,7 +36855,7 @@
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.2"

View File

@ -22,12 +22,13 @@
"build:zip": "npm run build && composer install && npm run build:dev",
"build:assets": "grunt assets",
"lint:js": "eslint assets/js --ext=js",
"docker:down": "npm explore @woocommerce/e2e-environment -- npm run docker:down",
"docker:ssh": "npm explore @woocommerce/e2e-environment -- npm run docker:ssh",
"docker:up": "npm explore @woocommerce/e2e-environment -- npm run docker:up",
"test:e2e": "npm explore @woocommerce/e2e-environment -- npm run test:e2e",
"test:e2e-debug": "npm explore @woocommerce/e2e-environment -- npm run test:e2e-debug",
"test:e2e-dev": "npm explore @woocommerce/e2e-environment -- npm run test:e2e-dev",
"docker:down": "npx wc-e2e docker:down",
"docker:ssh": "npx wc-e2e docker:ssh",
"docker:up": "npx wc-e2e docker:up",
"test:e2e": "npx wc-e2e test:e2e",
"test:e2e-debug": "npx wc-e2e test:e2e-debug",
"test:e2e-dev": "npx wc-e2e test:e2e-dev",
"test:unit": "./vendor/bin/phpunit -c ./phpunit.xml",
"makepot": "composer run-script makepot",
"packages:fix:textdomain": "node ./bin/package-update-textdomain.js",
"publish-packages": "lerna publish from-package",
@ -69,6 +70,7 @@
"grunt-contrib-cssmin": "3.0.0",
"grunt-contrib-uglify": "4.0.1",
"grunt-contrib-watch": "1.1.0",
"grunt-newer": "^1.3.0",
"grunt-phpcs": "0.4.0",
"grunt-postcss": "0.9.0",
"grunt-rtlcss": "2.0.2",

View File

@ -5,8 +5,9 @@
namespace Automattic\WooCommerce;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
/**
* PSR11 compliant dependency injection container for WooCommerce.
@ -33,6 +34,7 @@ final class Container implements \Psr\Container\ContainerInterface {
*/
private $service_providers = array(
ProxiesServiceProvider::class,
DownloadPermissionsAdjusterServiceProvider::class,
);
/**

View File

@ -0,0 +1,31 @@
<?php
/**
* DownloadPermissionsAdjusterServiceProvider class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
/**
* Service provider for the DownloadPermissionsAdjuster class.
*/
class DownloadPermissionsAdjusterServiceProvider extends AbstractServiceProvider {
/**
* The classes/interfaces that are serviced by this service provider.
*
* @var array
*/
protected $provides = array(
DownloadPermissionsAdjuster::class,
);
/**
* Register the classes.
*/
public function register() {
$this->share( DownloadPermissionsAdjuster::class );
}
}

View File

@ -1,6 +1,6 @@
<?php
/**
* Proxies class file.
* ProxiesServiceProvider class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;

View File

@ -0,0 +1,160 @@
<?php
/**
* DownloadPermissionsAdjuster class file.
*/
namespace Automattic\WooCommerce\Internal;
use Automattic\WooCommerce\Proxies\LegacyProxy;
defined( 'ABSPATH' ) || exit;
/**
* Class to adjust download permissions on product save.
*/
class DownloadPermissionsAdjuster {
/**
* The downloads data store to use.
*
* @var WC_Data_Store
*/
private $downloads_data_store;
/**
* Class initialization, to be executed when the class is resolved by the container.
*
* @internal
*/
final public function init() {
$this->downloads_data_store = wc_get_container()->get( LegacyProxy::class )->get_instance_of( \WC_Data_Store::class, 'customer-download' );
add_action( 'adjust_download_permissions', array( $this, 'adjust_download_permissions' ), 10, 1 );
}
/**
* Schedule a download permissions adjustment for a product if necessary.
* This should be executed whenever a product is saved.
*
* @param \WC_Product $product The product to schedule a download permission adjustments for.
*/
public function maybe_schedule_adjust_download_permissions( \WC_Product $product ) {
$children_ids = $product->get_children();
if ( ! $children_ids ) {
return;
}
$scheduled_action_args = array( $product->get_id() );
$already_scheduled_actions =
WC()->call_function(
'as_get_scheduled_actions',
array(
'hook' => 'adjust_download_permissions',
'args' => $scheduled_action_args,
'status' => \ActionScheduler_Store::STATUS_PENDING,
),
'ids'
);
if ( empty( $already_scheduled_actions ) ) {
WC()->call_function(
'as_schedule_single_action',
WC()->call_function( 'time' ) + 1,
'adjust_download_permissions',
$scheduled_action_args
);
}
}
/**
* Create additional download permissions for variations if necessary.
*
* When a simple downloadable product is converted to a variable product,
* existing download permissions are still present in the database but they don't apply anymore.
* This method creates additional download permissions for the variations based on
* the old existing ones for the main product.
*
* The procedure is as follows. For each existing download permission for the parent product,
* check if there's any variation offering the same file for download (the file URL, not name, is checked).
* If that is found, check if an equivalent permission exists (equivalent means for the same file and with
* the same order id and customer id). If no equivalent permission exists, create it.
*
* @param int $product_id The id of the product to check permissions for.
*/
public function adjust_download_permissions( int $product_id ) {
$product = wc_get_product( $product_id );
$children_ids = $product->get_children();
if ( ! $children_ids ) {
return;
}
$parent_downloads = $this->get_download_files_and_permissions( $product );
if ( ! $parent_downloads ) {
return;
}
$children_with_downloads = array();
foreach ( $children_ids as $child_id ) {
$child = wc_get_product( $child_id );
$children_with_downloads[ $child_id ] = $this->get_download_files_and_permissions( $child );
}
foreach ( $parent_downloads['permission_data_by_file_order_user'] as $parent_file_order_and_user => $parent_download_data ) {
foreach ( $children_with_downloads as $child_id => $child_download_data ) {
$file_url = $parent_download_data['file'];
$must_create_permission =
// The variation offers the same file as the parent for download...
in_array( $file_url, array_keys( $child_download_data['download_ids_by_file_url'] ), true ) &&
// ...but no equivalent download permission (same file URL, order id and user id) exists.
! array_key_exists( $parent_file_order_and_user, $child_download_data['permission_data_by_file_order_user'] );
if ( $must_create_permission ) {
// The new child download permission is a copy of the parent's,
// but with the product and download ids changed to match those of the variation.
$new_download_data = $parent_download_data['data'];
$new_download_data['product_id'] = $child_id;
$new_download_data['download_id'] = $child_download_data['download_ids_by_file_url'][ $file_url ];
$this->downloads_data_store->create_from_data( $new_download_data );
}
}
}
}
/**
* Get the existing downloadable files and download permissions for a given product.
* The returned value is an array with two keys:
*
* - download_ids_by_file_url: an associative array of file url => download_id.
* - permission_data_by_file_order_user: an associative array where key is "file_url:customer_id:order_id" and value is the full permission data set.
*
* @param \WC_Product $product The product to get the downloadable files and permissions for.
* @return array[] Information about the downloadable files and permissions for the product.
*/
private function get_download_files_and_permissions( \WC_Product $product ) {
$result = array(
'permission_data_by_file_order_user' => array(),
'download_ids_by_file_url' => array(),
);
$downloads = $product->get_downloads();
foreach ( $downloads as $download ) {
$result['download_ids_by_file_url'][ $download->get_file() ] = $download->get_id();
}
$permissions = $this->downloads_data_store->get_downloads( array( 'product_id' => $product->get_id() ) );
foreach ( $permissions as $permission ) {
$permission_data = (array) $permission->data;
if ( array_key_exists( $permission_data['download_id'], $downloads ) ) {
$file = $downloads[ $permission_data['download_id'] ]->get_file();
$data = array(
'file' => $file,
'data' => (array) $permission->data,
);
$result['permission_data_by_file_order_user'][ "${file}:${permission_data['user_id']}:${permission_data['order_id']}" ] = $data;
}
}
return $result;
}
}

View File

@ -68,6 +68,24 @@ class Packages {
}
call_user_func( array( $package_class, 'init' ) );
}
// Proxies "activated_plugin" hook for embedded packages listen on WC plugin activation
// https://github.com/woocommerce/woocommerce/issues/28697.
if ( is_admin() ) {
$activated_plugin = get_transient( 'woocommerce_activated_plugin' );
if ( $activated_plugin ) {
delete_transient( 'woocommerce_activated_plugin' );
/**
* WooCommerce is activated hook.
*
* @since 5.0.0
* @param bool $activated_plugin Activated plugin path,
* generally woocommerce/woocommerce.php.
*/
do_action( 'woocommerce_activated_plugin', $activated_plugin );
}
}
}
/**

View File

@ -52,6 +52,11 @@ class LegacyProxy {
return $class_name::instance( ...$args );
}
// If the class has a "load" method, use it.
if ( method_exists( $class_name, 'load' ) ) {
return $class_name::load( ...$args );
}
// Fallback to simply creating a new instance of the class.
return new $class_name( ...$args );
}

View File

@ -1,5 +1,5 @@
# Prevent anyone from accidentally adding code to these directories.
# This will break any PRs that do, revealing ths mistake they made.
README.md
# This will break any PRs that do, revealing the mistake they made.
*
!.gitignore
!README.md

View File

@ -55,6 +55,30 @@ A text code coverage summary can be displayed using the `--coverage-text` option
$ vendor/bin/phpunit --coverage-text
### Running tests in PHP 8
WooCommerce currently supports PHP versions from 7.0 up to 8.0, and this poses an issue with PHPUnit:
* The latest PHPUnit version that supports PHP 7.0 is 6.5.14
* The latest PHPUnit version that WordPress (and thus WooCommerce) supports is 7.5.20, but that version doesn't work on PHP 8
To workaround this, the testing strategy used by WooCommerce is as follows:
* We normally use PHPUnit 6.5.14
* For PHP 8 we use [a custom fork of PHPUnit 7.5.20 with support for PHP 8](https://github.com/woocommerce/phpunit/pull/1). The Travis build is configured to use this fork instead of the old version 6 when running in PHP 8.
If you want to run the tests locally under PHP 8 you'll need to temporarily modify `composer.json` to use the custom PHPUnit fork in the same way that the Travis setup script does. These are the commands that you'll need (run them after a regular `composer install`):
```shell
curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip
unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip
composer bin phpunit config --unset platform
composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}'
composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs
```
Just remember that you can't include the modified `composer.json` in any commit!
## Writing Tests

View File

@ -113,15 +113,15 @@ Puppeteer will still automatically download Chromium when needed.
- Run `npm install jest --global`
- Run `npm run docker:up` - it will build the test site using Docker.
- Run `npx wc-e2e docker:up` - it will build the test site using Docker.
- Run `docker ps` - to confirm that the Docker containers were built and running. You should see the log that looks similar to below indicating that everything had been built as expected:
- Run `docker ps` - to confirm that the Docker containers are running. You should see the log that looks similar to below indicating that everything had been built as expected:
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c380e1964506 env_wordpress-cli "entrypoint.sh" 7 seconds ago Up 5 seconds woocommerce_wordpress-cli
2ab8e8439e9f wordpress:5.5.1 "docker-entrypoint.s…" 8 seconds ago Up 7 seconds 0.0.0.0:8084->80/tcp woocommerce_wordpress-www
4c1e3f2a49db mariadb:10.5.5 "docker-entrypoint.s…" 10 seconds ago Up 8 seconds 3306/tcp woocommerce_db
c380e1964506 env_wordpress-cli "entrypoint.sh" 7 seconds ago Up 5 seconds woocommerce_e2e_wordpress-cli
2ab8e8439e9f wordpress:5.5.1 "docker-entrypoint.s…" 8 seconds ago Up 7 seconds 0.0.0.0:8084->80/tcp woocommerce_e2e_wordpress-www
4c1e3f2a49db mariadb:10.5.5 "docker-entrypoint.s…" 10 seconds ago Up 8 seconds 3306/tcp woocommerce_e2e_db
```
Note that by default, Docker will download the latest images available for WordPress, PHP and MariaDB. In the example above, you can see that WordPress 5.5.1 and MariaDB 10.5.5 were used.
@ -139,16 +139,16 @@ Username: admin
PW: password
```
- Run `npm run docker:down` when you are done with running e2e tests or when making any changes to test suite.
- Run `npx wc-e2e docker:down` when you are done with running e2e tests or when making any changes to test suite.
Note that running `npm run docker:down` and then `npm run docker:up` re-initializes the test container.
Note that running `npx wc-e2e docker:down` and then `npx wc-e2e docker:up` re-initializes the test container.
### How to run tests in headless mode
To run e2e tests in headless mode use the following command:
```bash
npm run test:e2e
npx wc-e2e test:e2e
```
### How to run tests in non-headless mode
@ -156,7 +156,7 @@ npm run test:e2e
Tests are run headless by default. However, sometimes it's useful to observe the browser while running tests. To do so, you can run tests in a non-headless (dev) mode:
```bash
npm run test:e2e-dev
npx wc-e2e test:e2e-dev
```
The dev mode also enables SlowMo mode. SlowMo slows down Puppeteers operations so we can better see what is happening in the browser.
@ -164,7 +164,7 @@ The dev mode also enables SlowMo mode. SlowMo slows down Puppeteers operation
By default, SlowMo mode is set to slow down running of tests by 50 milliseconds. If you'd like to override it and have the tests run faster or slower in the `-dev` mode, pass `PUPPETEER_SLOWMO` variable when running tests as shown below:
```
PUPPETEER_SLOWMO=10 npm run test:e2e-dev
PUPPETEER_SLOWMO=10 npx wc-e2e test:e2e-dev
```
The faster you want the tests to run, the lower the value should be of `PUPPETEER_SLOWMO` should be.
@ -179,7 +179,7 @@ For example:
Tests are run headless by default. While writing tests it may be useful to have the debugger loaded while running a test in non-headless mode. To run tests in debug mode:
```bash
npm run test:e2e-debug
npx wc-e2e test:e2e-debug
```
When all tests have been completed the debugger is left active. Control doesn't return to the command line until the debugger is closed. Otherwise, debug mode functions the same as non-headless mode.
@ -189,7 +189,7 @@ When all tests have been completed the debugger is left active. Control doesn't
To run an individual test, use the direct path to the spec. For example:
```bash
npm run test:e2e ./tests/e2e/specs/wp-admin/test-create-order.js
npx wc-e2e test:e2e ./tests/e2e/specs/wp-admin/test-create-order.js
```
### How to skip tests

View File

@ -0,0 +1,76 @@
{
"url": "http://localhost:8084/",
"appName": "woocommerce_e2e",
"users": {
"admin": {
"username": "admin",
"password": "password"
},
"customer": {
"username": "customer",
"password": "password"
}
},
"products": {
"simple": {
"name": "Simple product"
},
"variable": {
"name": "Variable Product with Three Variations"
}
},
"addresses": {
"admin": {
"store": {
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"countryandstate": "United States (US) — California",
"city": "San Francisco",
"state": "CA",
"postcode": "94107"
}
},
"customer": {
"billing": {
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"city": "San Francisco",
"state": "CA",
"postcode": "94107",
"phone": "123456789",
"email": "john.doe@example.com"
},
"shipping": {
"firstname": "John",
"lastname": "Doe",
"company": "Automattic",
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"city": "San Francisco",
"state": "CA",
"postcode": "94107"
}
}
},
"onboardingwizard": {
"industry": "Test industry",
"numberofproducts": "1 - 10",
"sellingelsewhere": "No"
},
"settings": {
"shipping": {
"zonename": "United States",
"zoneregions": "United States (US)",
"shippingmethod": "Free shipping"
}
}
}

View File

@ -1,18 +1,19 @@
const {
switchUserToAdmin,
visitAdminPage,
switchUserToTest,
clearLocalStorage,
setBrowserViewport
} = require( "@wordpress/e2e-test-utils" );
const { merchant } = require( '@woocommerce/e2e-utils' );
/**
* Navigates to the post listing screen and bulk-trashes any posts which exist.
*
* @return {Promise} Promise resolving once posts have been trashed.
*/
async function trashExistingPosts() {
await switchUserToAdmin();
await merchant.login();
// Visit `/wp-admin/edit.php` so we can see a list of posts and delete them.
await visitAdminPage( 'edit.php' );
@ -41,7 +42,7 @@ async function trashExistingPosts() {
* @return {Promise} Promise resolving once products have been trashed.
*/
async function trashExistingProducts() {
await switchUserToAdmin();
await merchant.login();
// Visit `/wp-admin/edit.php?post_type=product` so we can see a list of products and delete them.
await visitAdminPage( 'edit.php', 'post_type=product' );

View File

@ -4,6 +4,9 @@
- Merchant Order Status Filter tests
- Merchant Order Refund tests
- Merchant Apply Coupon tests
- Added new config variable for Simple Product price to `tests/e2e/env/config/default.json`. Defaults to 9.99
- Shopper Checkout Apply Coupon
- Shopper Cart Apply Coupon

View File

@ -37,32 +37,32 @@ The functions to access the core tests are:
### Activation and setup
- `runSetupOnboardingTests` - Run all setup and onboarding tests
- `runActivationTest` - Merchant can activate WooCommerce
- `runOnboardingFlowTest` - Merchant can complete onboarding flow
- `runTaskListTest` - Merchant can complete onboarding task list
- `runInitialStoreSettingsTest` - Merchant can complete initial settings
- `runActivationTest` - Merchant can activate WooCommerce
- `runOnboardingFlowTest` - Merchant can complete onboarding flow
- `runTaskListTest` - Merchant can complete onboarding task list
- `runInitialStoreSettingsTest` - Merchant can complete initial settings
### Merchant
- `runMerchantTests` - Run all merchant tests
- `runCreateCouponTest` - Merchant can create coupon
- `runCreateOrderTest` - Merchant can create order
- `runAddSimpleProductTest` - Merchant can create a simple product
- `runAddVariableProductTest` - Merchant can create a variable product
- `runUpdateGeneralSettingsTest` - Merchant can update general settings
- `runProductSettingsTest` - Merchant can update product settings
- `runTaxSettingsTest` - Merchant can update tax settings
- `runOrderStatusFilterTest` - Merchant can filter orders by order status
- `runOrderRefundTest` - Merchant can refund an order
- `runOrderApplyCouponTest` - Merchant can apply a coupon to an order
- `runCreateCouponTest` - Merchant can create coupon
- `runCreateOrderTest` - Merchant can create order
- `runAddSimpleProductTest` - Merchant can create a simple product
- `runAddVariableProductTest` - Merchant can create a variable product
- `runUpdateGeneralSettingsTest` - Merchant can update general settings
- `runProductSettingsTest` - Merchant can update product settings
- `runTaxSettingsTest` - Merchant can update tax settings
- `runOrderStatusFilterTest` - Merchant can filter orders by order status
- `runOrderRefundTest` - Merchant can refund an order
- `runOrderApplyCouponTest` - Merchant can apply a coupon to an order
### Shopper
- `runShopperTests` - Run all shopper tests
- `runCartPageTest` - Shopper can view and update cart
- `runCheckoutPageTest` - Shopper can complete checkout
- `runMyAccountPageTest` - Shopper can access my account page
- `runSingleProductPageTest` - Shopper can view single product page
- `runCartPageTest` - Shopper can view and update cart
- `runCheckoutPageTest` - Shopper can complete checkout
- `runMyAccountPageTest` - Shopper can access my account page
- `runSingleProductPageTest` - Shopper can view single product page
## Contributing a new test

View File

@ -1,4 +1,14 @@
/* eslint-disable jest/no-export, jest/no-disabled-tests */
/**
* External dependencies
*/
const { HTTPClientFactory } = require( '@woocommerce/api' );
const {
it,
describe,
beforeAll,
} = require( '@jest/globals' );
/**
* Internal dependencies
*/
@ -16,16 +26,6 @@ const {
waitAndClick
} = require( '@woocommerce/e2e-environment' );
/**
* External dependencies
*/
const { HTTPClientFactory } = require( '@woocommerce/api' );
const {
it,
describe,
beforeAll,
} = require( '@jest/globals' );
const runInitialStoreSettingsTest = () => {
describe('Store owner can finish initial store setup', () => {

View File

@ -11,6 +11,7 @@ const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' )
// Shopper tests
const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test');
const runCartPageTest = require( './shopper/front-end-cart.test' );
const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test');
const runCheckoutPageTest = require( './shopper/front-end-checkout.test' );
const runMyAccountPageTest = require( './shopper/front-end-my-account.test' );
const runSingleProductPageTest = require( './shopper/front-end-single-product.test' );
@ -36,6 +37,7 @@ const runSetupOnboardingTests = () => {
const runShopperTests = () => {
runCartApplyCouponsTest();
runCartPageTest();
runCheckoutApplyCouponsTest();
runCheckoutPageTest();
runMyAccountPageTest();
runSingleProductPageTest();
@ -62,6 +64,7 @@ module.exports = {
runSetupOnboardingTests,
runCartApplyCouponsTest,
runCartPageTest,
runCheckoutApplyCouponsTest,
runCheckoutPageTest,
runMyAccountPageTest,
runSingleProductPageTest,

View File

@ -1,4 +1,4 @@
/* eslint-disable jest/no-export */
/* eslint-disable jest/no-export, jest/no-standalone-expect */
/**
* Internal dependencies
@ -10,10 +10,13 @@ const {
createCoupon,
uiUnblocked,
addProductToOrder,
evalAndClick,
} = require( '@woocommerce/e2e-utils' );
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const simpleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
const discountedPrice = simpleProductPrice - 5.00;
const couponDialogMessage = 'Enter a coupon code to apply. Discounts are applied to line totals, before taxes.';
@ -33,6 +36,9 @@ const runOrderApplyCouponTest = () => {
// We need to remove any listeners on the `dialog` event otherwise we can't catch the dialog below
page.removeAllListeners('dialog'),
]);
// Make sure the simple product price is greater than the coupon amount
await expect(Number(simpleProductPrice)).toBeGreaterThan(5.00);
} );
it('can apply a coupon', async () => {
@ -54,7 +60,7 @@ const runOrderApplyCouponTest = () => {
// Check that the coupon has been applied
await expect(page).toMatchElement('.wc-order-item-discount', { text: '5.00' });
await expect(page).toMatchElement('.line_cost > .view > .woocommerce-Price-amount', { text: '4.99' });
await expect(page).toMatchElement('.line_cost > .view > .woocommerce-Price-amount', { text: discountedPrice });
});
it('can remove a coupon', async () => {
@ -68,10 +74,10 @@ const runOrderApplyCouponTest = () => {
// Verify the coupon pricing has been removed
await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() });
await expect(page).not.toMatchElement('.wc-order-item-discount', { text: '5.00' });
await expect(page).not.toMatchElement('.line-cost .view .woocommerce-Price-amount', { text: '4.99' });
await expect(page).not.toMatchElement('.line-cost .view .woocommerce-Price-amount', { text: discountedPrice });
// Verify the original price is the order total
await expect(page).toMatchElement('.line_cost > .view > .woocommerce-Price-amount', { text: '9.99' });
await expect(page).toMatchElement('.line_cost > .view > .woocommerce-Price-amount', { text: simpleProductPrice });
});
});

View File

@ -11,10 +11,12 @@ const {
verifyValueOfInputField,
uiUnblocked,
addProductToOrder,
evalAndClick,
} = require( '@woocommerce/e2e-utils' );
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const simpleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
let orderId;
let currencySymbol;
@ -49,9 +51,9 @@ const runRefundOrderTest = () => {
await expect(page).toFill('#refund_reason', 'No longer wanted');
await Promise.all([
verifyValueOfInputField('.refund_line_total', '9.99'),
verifyValueOfInputField('#refund_amount', '9.99'),
expect(page).toMatchElement('.do-manual-refund', { text: `Refund ${currencySymbol}9.99 manually` }),
verifyValueOfInputField('.refund_line_total', simpleProductPrice),
verifyValueOfInputField('#refund_amount', simpleProductPrice),
expect(page).toMatchElement('.do-manual-refund', { text: `Refund ${currencySymbol + simpleProductPrice} manually` }),
]);
await expect(page).toClick('.do-manual-refund');
@ -61,11 +63,11 @@ const runRefundOrderTest = () => {
await Promise.all([
// Verify the product line item shows the refunded quantity and amount
expect(page).toMatchElement('.quantity .refunded', { text: '-1' }),
expect(page).toMatchElement('.line_cost .refunded', { text: `-${currencySymbol}9.99` }),
expect(page).toMatchElement('.line_cost .refunded', { text: `-${currencySymbol + simpleProductPrice}` }),
// Verify the refund shows in the list with the amount
expect(page).toMatchElement('.refund .description', { text: 'No longer wanted' }),
expect(page).toMatchElement('.refund > .line_cost', { text: `-${currencySymbol}9.99` }),
expect(page).toMatchElement('.refund > .line_cost', { text: `-${currencySymbol + simpleProductPrice}` }),
// Verify system note was added
expect(page).toMatchElement('.system-note', { text: 'Order status changed from Completed to Refunded.' }),
@ -74,10 +76,7 @@ const runRefundOrderTest = () => {
});
it('can delete an issued refund', async () => {
// We need to use this here as `expect(page).toClick()` was unable to find the element
// See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219
page.$eval('a.delete_refund', elem => elem.click());
await evalAndClick( 'a.delete_refund' );
await uiUnblocked();
// Verify the refunded row item is no longer showing
@ -86,11 +85,11 @@ const runRefundOrderTest = () => {
await Promise.all([
// Verify the product line item shows the refunded quantity and amount
expect(page).not.toMatchElement('.quantity .refunded', { text: '-1' }),
expect(page).not.toMatchElement('.line_cost .refunded', { text: `-${currencySymbol}9.99` }),
expect(page).not.toMatchElement('.line_cost .refunded', { text: `-${currencySymbol + simpleProductPrice}` }),
// Verify the refund shows in the list with the amount
expect(page).not.toMatchElement('.refund .description', { text: 'No longer wanted' }),
expect(page).not.toMatchElement('.refund > .line_cost', { text: `-${currencySymbol}9.99` }),
expect(page).not.toMatchElement('.refund > .line_cost', { text: `-${currencySymbol + simpleProductPrice}` }),
]);
});

View File

@ -5,8 +5,14 @@
const {
merchant,
clickTab,
uiUnblocked
uiUnblocked,
evalAndClick,
setCheckbox,
} = require( '@woocommerce/e2e-utils' );
const {
waitAndClick,
waitForSelector,
} = require( '@woocommerce/e2e-environment' );
/**
* External dependencies
@ -19,6 +25,7 @@ const {
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const simpleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
const verifyPublishAndTrash = async () => {
// Wait for auto save
@ -40,6 +47,14 @@ const verifyPublishAndTrash = async () => {
await expect( page ).toMatchElement( '.updated.notice', { text: '1 product moved to the Trash.' } );
};
const openNewProductAndVerify = async () => {
// Go to "add product" page
await merchant.openNewProduct();
// Make sure we're on the add product page
await expect(page.title()).resolves.toMatch('Add new product');
}
const runAddSimpleProductTest = () => {
describe('Add New Simple Product Page', () => {
beforeAll(async () => {
@ -47,17 +62,13 @@ const runAddSimpleProductTest = () => {
});
it('can create simple virtual product titled "Simple Product" with regular price $9.99', async () => {
// Go to "add product" page
await merchant.openNewProduct();
// Make sure we're on the add order page
await expect(page.title()).resolves.toMatch('Add new product');
await openNewProductAndVerify();
// Set product data
await expect(page).toFill('#title', simpleProductName);
await expect(page).toClick('#_virtual');
await clickTab('General');
await expect(page).toFill('#_regular_price', '9.99');
await expect(page).toFill('#_regular_price', simpleProductPrice);
// Publish product, verify that it was published. Trash product, verify that it was trashed.
await verifyPublishAndTrash(
@ -72,66 +83,62 @@ const runAddSimpleProductTest = () => {
};
const runAddVariableProductTest = () => {
describe.skip('Add New Variable Product Page', () => {
describe('Add New Variable Product Page', () => {
it('can create product with variations', async () => {
// Go to "add product" page
await merchant.openNewProduct();
// Make sure we're on the add order page
await expect(page.title()).resolves.toMatch('Add new product');
await openNewProductAndVerify();
// Set product data
await expect(page).toFill('#title', 'Variable Product with Three Variations');
await expect(page).toSelect('#product-type', 'Variable product');
// Create attributes for variations
await clickTab('Attributes');
await expect(page).toSelect('select[name="attribute_taxonomy"]', 'Custom product attribute');
await waitAndClick( page, '.attribute_tab a' );
await expect( page ).toSelect( 'select[name="attribute_taxonomy"]', 'Custom product attribute' );
for (let i = 0; i < 3; i++) {
await expect(page).toClick('button.add_attribute', {text: 'Add'});
for ( let i = 0; i < 3; i++ ) {
await expect(page).toClick( 'button.add_attribute', {text: 'Add'} );
// Wait for attribute form to load
await uiUnblocked();
await page.focus(`input[name="attribute_names[${i}]"]`);
await expect(page).toFill(`input[name="attribute_names[${i}]"]`, 'attr #' + (i + 1));
await expect(page).toFill(`textarea[name="attribute_values[${i}]"]`, 'val1 | val2');
await expect(page).toClick(`input[name="attribute_variation[${i}]"]`);
await waitAndClick( page, `input[name="attribute_variation[${i}]"]`);
}
await expect(page).toClick('button', {text: 'Save attributes'});
await expect(page).toClick( 'button', {text: 'Save attributes'});
// Wait for attribute form to save (triggers 2 UI blocks)
await uiUnblocked();
await page.waitFor(1000);
await uiUnblocked();
// Create variations from attributes
await clickTab('Variations');
await page.waitForSelector('select.variation_actions:not([disabled])');
await waitForSelector( page, '.variations_tab' );
await waitAndClick( page, '.variations_tab a' );
await waitForSelector( page, 'select.variation_actions:not(:disabled)');
await page.focus('select.variation_actions');
await expect(page).toSelect('select.variation_actions', 'Create variations from all attributes');
// headless: false doesn't require this
const firstDialog = await expect(page).toDisplayDialog(async () => {
// Using this technique since toClick() isn't working.
// See: https://github.com/GoogleChrome/puppeteer/issues/1805#issuecomment-464802876
page.$eval('a.do_variation_action', elem => elem.click());
await evalAndClick( 'a.do_variation_action' );
});
expect(firstDialog.message()).toMatch('Are you sure you want to link all variations?');
const secondDialog = await expect(page).toDisplayDialog(async () => {
await firstDialog.accept();
});
expect(secondDialog.message()).toMatch('8 variations added');
await secondDialog.dismiss();
await expect(firstDialog.message()).toMatch('Are you sure you want to link all variations?');
// Set some variation data
await uiUnblocked();
await uiUnblocked();
await page.waitForSelector('.woocommerce_variation .handlediv');
await waitAndClick( page, '.variations_tab a' );
await waitForSelector(
page,
'select[name="attribute_attr-1[0]"]',
{
visible: true,
timeout: 5000
}
);
// Verify that variations were created
await Promise.all([
@ -168,22 +175,19 @@ const runAddVariableProductTest = () => {
expect(page).toMatchElement('select[name="attribute_attr-3[7]"]', {text: 'val2'}),
]);
await expect(page).toClick('.woocommerce_variation:nth-of-type(2) .handlediv');
await page.waitFor(2000);
await page.focus('input[name="variable_is_virtual[0]"]');
await expect(page).toClick('input[name="variable_is_virtual[0]"]');
/*
Puppeteer seems unable to find the individual variation fields in headless mode on MacOS
This section of the test runs fine in both Travis and non-headless mode on Mac
Disabling temporarily to allow the test to be re-enabled without local testing headache
await waitAndClick( page, '.variations-pagenav .expand_all');
await page.waitFor( 2000 );
await setCheckbox('input[name="variable_is_virtual[0]"]');
await expect(page).toFill('input[name="variable_regular_price[0]"]', '9.99');
await expect(page).toClick('.woocommerce_variation:nth-of-type(3) .handlediv');
await page.waitFor(2000);
await page.focus('input[name="variable_is_virtual[1]"]');
await expect(page).toClick('input[name="variable_is_virtual[1]"]');
await setCheckbox('input[name="variable_is_virtual[1]"]');
await expect(page).toFill('input[name="variable_regular_price[1]"]', '11.99');
await expect(page).toClick('.woocommerce_variation:nth-of-type(4) .handlediv');
await page.waitFor(2000);
await page.focus('input[name="variable_manage_stock[2]"]');
await expect(page).toClick('input[name="variable_manage_stock[2]"]');
await setCheckbox('input[name="variable_manage_stock[2]"]');
await expect(page).toFill('input[name="variable_regular_price[2]"]', '20');
await expect(page).toFill('input[name="variable_weight[2]"]', '200');
await expect(page).toFill('input[name="variable_length[2]"]', '10');
@ -192,6 +196,7 @@ const runAddVariableProductTest = () => {
await page.focus('button.save-variation-changes');
await expect(page).toClick('button.save-variation-changes', {text: 'Save changes'});
/**/
// Publish product, verify that it was published. Trash product, verify that it was trashed.
await verifyPublishAndTrash(

View File

@ -18,6 +18,11 @@ const {
beforeAll,
} = require( '@jest/globals' );
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const singleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
const twoProductPrice = singleProductPrice * 2;
const runCartPageTest = () => {
describe('Cart page', () => {
beforeAll(async () => {
@ -33,32 +38,32 @@ const runCartPageTest = () => {
it('should add the product to the cart when "Add to cart" is clicked', async () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage('Simple product');
await shopper.addToCartFromShopPage(simpleProductName);
await shopper.goToCart();
await shopper.productIsInCart('Simple product');
await shopper.productIsInCart(simpleProductName);
});
it('should increase item qty when "Add to cart" of the same product is clicked', async () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage('Simple product');
await shopper.addToCartFromShopPage(simpleProductName);
await shopper.goToCart();
await shopper.productIsInCart('Simple product', 2);
await shopper.productIsInCart(simpleProductName, 2);
});
it('should update qty when updated via qty input', async () => {
await shopper.goToCart();
await shopper.setCartQuantity('Simple product', 4);
await shopper.setCartQuantity(simpleProductName, 4);
await expect(page).toClick('button', {text: 'Update cart'});
await uiUnblocked();
await shopper.productIsInCart('Simple product', 4);
await shopper.productIsInCart(simpleProductName, 4);
});
it('should remove the item from the cart when remove is clicked', async () => {
await shopper.goToCart();
await shopper.removeFromCart('Simple product');
await shopper.removeFromCart(simpleProductName);
await uiUnblocked();
await expect(page).toMatchElement('.cart-empty', {text: 'Your cart is currently empty.'});
@ -66,17 +71,17 @@ const runCartPageTest = () => {
it('should update subtotal in cart totals when adding product to the cart', async () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage('Simple product');
await shopper.addToCartFromShopPage(simpleProductName);
await shopper.goToCart();
await shopper.productIsInCart('Simple product', 1);
await expect(page).toMatchElement('.cart-subtotal .amount', {text: '$9.99'});
await shopper.productIsInCart(simpleProductName, 1);
await expect(page).toMatchElement('.cart-subtotal .amount', {text: `$${ singleProductPrice }`});
await shopper.setCartQuantity('Simple product', 2);
await shopper.setCartQuantity(simpleProductName, 2);
await expect(page).toClick('button', {text: 'Update cart'});
await uiUnblocked();
await expect(page).toMatchElement('.cart-subtotal .amount', {text: '$19.98'});
await expect(page).toMatchElement('.cart-subtotal .amount', {text: `$${ twoProductPrice }`});
});
it('should go to the checkout page when "Proceed to Checkout" is clicked', async () => {

View File

@ -0,0 +1,118 @@
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect, jest/no-standalone-expect */
/**
* Internal dependencies
*/
const {
shopper,
merchant,
createCoupon,
createSimpleProduct,
uiUnblocked
} = require( '@woocommerce/e2e-utils' );
/**
* External dependencies
*/
const {
it,
describe,
beforeAll,
} = require( '@jest/globals' );
const runCheckoutApplyCouponsTest = () => {
describe('Checkout applying coupons', () => {
let couponFixedCart;
let couponPercentage;
let couponFixedProduct;
beforeAll(async () => {
await merchant.login();
await createSimpleProduct();
couponFixedCart = await createCoupon();
couponPercentage = await createCoupon('50', 'Percentage discount');
couponFixedProduct = await createCoupon('5', 'Fixed product discount');
await merchant.logout();
});
it('allows customer to apply coupons in the checkout', async () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage('Simple product');
await uiUnblocked();
await shopper.goToCheckout();
// Apply Fixed cart discount coupon
await expect(page).toClick('a', {text: 'Click here to enter your code'});
await uiUnblocked();
await expect(page).toFill('#coupon_code', couponFixedCart);
await expect(page).toClick('button', {text: 'Apply coupon'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
// Wait for page to expand total calculations to avoid flakyness
await page.waitForSelector('.order-total');
// Verify discount applied and order total
await page.waitForSelector('.cart-discount .amount', {text: '$5.00'});
await page.waitForSelector('.order-total .amount', {text: '$4.99'});
// Remove coupon
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
// Apply Percentage discount coupon
await expect(page).toClick('a', {text: 'Click here to enter your code'});
await uiUnblocked();
await expect(page).toFill('#coupon_code', couponPercentage);
await expect(page).toClick('button', {text: 'Apply coupon'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
await page.waitForSelector('.cart-discount .amount', {text: '$4.99'});
await page.waitForSelector('.order-total .amount', {text: '$5.00'});
// Remove coupon
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
// Apply Fixed product discount coupon
await expect(page).toClick('a', {text: 'Click here to enter your code'});
await uiUnblocked();
await expect(page).toFill('#coupon_code', couponFixedProduct);
await expect(page).toClick('button', {text: 'Apply coupon'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
await page.waitForSelector('.cart-discount .amount', {text: '$5.00'});
await page.waitForSelector('.order-total .amount', {text: '$4.99'});
// Try to apply the same coupon
await expect(page).toClick('a', {text: 'Click here to enter your code'});
await uiUnblocked();
await expect(page).toFill('#coupon_code', couponFixedProduct);
await expect(page).toClick('button', {text: 'Apply coupon'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' });
// Try to apply multiple coupons
await expect(page).toClick('a', {text: 'Click here to enter your code'});
await uiUnblocked();
await expect(page).toFill('#coupon_code', couponFixedCart);
await expect(page).toClick('button', {text: 'Apply coupon'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
await page.waitForSelector('.order-total .amount', {text: '$0.00'});
// Remove coupon
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
await uiUnblocked();
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
// Verify the total amount after all coupons removal
await page.waitForSelector('.order-total .amount', {text: '$9.99'});
});
});
};
module.exports = runCheckoutApplyCouponsTest;

View File

@ -14,6 +14,12 @@ const {
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const singleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99';
const twoProductPrice = singleProductPrice * 2;
const threeProductPrice = singleProductPrice * 3;
const fourProductPrice = singleProductPrice * 4;
const fiveProductPrice = singleProductPrice * 5;
let orderId;
const runCheckoutPageTest = () => {
@ -74,14 +80,14 @@ const runCheckoutPageTest = () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage(simpleProductName);
await shopper.goToCheckout();
await shopper.productIsInCheckout(simpleProductName, `1`, `9.99`, `9.99`);
await shopper.productIsInCheckout(simpleProductName, `1`, singleProductPrice, singleProductPrice);
});
it('allows customer to choose available payment methods', async () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage(simpleProductName);
await shopper.goToCheckout();
await shopper.productIsInCheckout(simpleProductName, `2`, `19.98`, `19.98`);
await shopper.productIsInCheckout(simpleProductName, `2`, twoProductPrice, twoProductPrice);
await expect(page).toClick('.wc_payment_method label', {text: 'PayPal'});
await expect(page).toClick('.wc_payment_method label', {text: 'Direct bank transfer'});
@ -92,7 +98,7 @@ const runCheckoutPageTest = () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage(simpleProductName);
await shopper.goToCheckout();
await shopper.productIsInCheckout(simpleProductName, `3`, `29.97`, `29.97`);
await shopper.productIsInCheckout(simpleProductName, `3`, threeProductPrice, threeProductPrice);
await shopper.fillBillingDetails(config.get('addresses.customer.billing'));
});
@ -100,7 +106,7 @@ const runCheckoutPageTest = () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage(simpleProductName);
await shopper.goToCheckout();
await shopper.productIsInCheckout(simpleProductName, `4`, `39.96`, `39.96`);
await shopper.productIsInCheckout(simpleProductName, `4`, fourProductPrice, fourProductPrice);
// Select checkbox to ship to a different address
await page.evaluate(() => {
@ -115,7 +121,7 @@ const runCheckoutPageTest = () => {
await shopper.goToShop();
await shopper.addToCartFromShopPage(simpleProductName);
await shopper.goToCheckout();
await shopper.productIsInCheckout(simpleProductName, `5`, `49.95`, `49.95`);
await shopper.productIsInCheckout(simpleProductName, `5`, fiveProductPrice, fiveProductPrice);
await shopper.fillBillingDetails(config.get('addresses.customer.billing'));
await uiUnblocked();
@ -151,13 +157,13 @@ const runCheckoutPageTest = () => {
await expect(page).toMatchElement('.wc-order-item-name', {text: simpleProductName});
// Verify product cost
await expect(page).toMatchElement('.woocommerce-Price-amount.amount', {text: '9.99'});
await expect(page).toMatchElement('.woocommerce-Price-amount.amount', {text: singleProductPrice});
// Verify product quantity
await expect(page).toMatchElement('.quantity', {text: '5'});
// Verify total order amount without shipping
await expect(page).toMatchElement('.line_cost', {text: '49.95'});
await expect(page).toMatchElement('.line_cost', {text: fiveProductPrice});
});
});
};

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