Merge branch 'master' into package/e2e-factories/product

This commit is contained in:
Christopher Allford 2020-07-01 14:11:25 -07:00
commit 0785e6caa7
93 changed files with 9554 additions and 1094 deletions

View File

@ -1,48 +1,7 @@
<!-- This form is for reporting bugs and issues specific to the WooCommerce plugin. This is not a support portal. If you need technical support from a human being, please submit a ticket via the helpdesk instead: https://woocommerce.com/contact-us/ -->
<!-- This form is for other issue types specific to the WooCommerce plugin. This is not a support portal. -->
<!-- Usage questions can also be directed to the public support forum here: https://wordpress.org/support/plugin/woocommerce, unless this is a question about a premium extension in which case you should use the helpdesk. -->
**Prerequisites (mark completed items with an [x]):**
- [ ] I have checked that my issue type is not listed here https://github.com/woocommerce/woocommerce/issues/new/choose
- [ ] My issue is not a security issue, support request, bug report, enhancement or feature request (Please use the link above if it is).
<!-- If you have a feature request, submit it to: http://ideas.woocommerce.com/forums/133476-woocommerce -->
<!-- If you are a developer who needs a new filter/hook raise a PR instead :) -->
<!-- Please be as descriptive as possible; issues lacking the below details, or for any other reason than to report a bug, may be closed without action. -->
## Prerequisites
<!-- MARK COMPLETED ITEMS WITH AN [x] -->
- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate
- [ ] The issue still exists against the latest `master` branch of WooCommerce on Github (this is **not** the same version as on WordPress.org!)
- [ ] I have attempted to find the simplest possible steps to reproduce the issue
- [ ] I have included a failing test as a pull request (Optional)
## Steps to reproduce the issue
<!-- We need to be able to reproduce the bug in order to fix it so please be descriptive! -->
1.
2.
3.
## Expected/actual behavior
When I follow those steps, I see...
I was expecting to see...
## Isolating the problem
<!-- MARK COMPLETED ITEMS WITH AN [x] -->
- [ ] This bug happens with only WooCommerce plugin active
- [ ] This bug happens with a default WordPress theme active, or [Storefront](https://woocommerce.com/storefront/)
- [ ] I can reproduce this bug consistently using the steps above
## WordPress Environment
<details>
```
Copy and paste the system status report from **WooCommerce > System Status** in WordPress admin here.
```
</details>
**Issue Description:**

View File

@ -1,5 +1,5 @@
---
name: "\U0001F46E Security issue"
name: "\U0001F512 Security issue"
about: Please report security issues *only* via https://www.hackerone.com
title: ''
labels: ''

View File

@ -1,10 +1,18 @@
---
name: "\U0001F47D External issues"
about: Please report WooCommerce REST API or WooCommerce Gutenberg Products Blocks issues directly to their respective repositories.
about: Please report WooCommerce REST API, WooCommerce Admin or WooCommerce Gutenberg Products Blocks issues directly to their respective repositories.
title: ''
labels: ''
assignees: ''
---
Please report WooCommerce REST API (https://github.com/woocommerce/woocommerce-rest-api) or WooCommerce Gutenberg Products Blocks (https://github.com/woocommerce/woocommerce-gutenberg-products-block) issues directly to their respective repositories.
Please report issues for the following features directly to their respective repositories.
WooCommerce REST API: https://github.com/woocommerce/woocommerce-rest-api
WooCommerce Admin: https://github.com/woocommerce/woocommerce-admin
WooCommerce Gutenberg Products Blocks: https://github.com/woocommerce/woocommerce-gutenberg-products-block
Action Scheduler: https://github.com/woocommerce/action-scheduler

View File

@ -16,10 +16,13 @@ Usage docs can be found here: https://docs.woocommerce.com/
If you have a problem, you may want to start with the self help guide here: https://docs.woocommerce.com/document/woocommerce-self-service-guide/
**Technical support for premium extensions or if you're a WooCommerce.com customer**
from a human being - submit a ticket via the helpdesk
Contact WooCommerce support by opening a ticket.
https://woocommerce.com/contact-us/
**For help with custom code**
WooCommerce Slack Community: https://woocommerce.com/community-slack/ in the `#developers` channel.
**General usage and development questions**
- WooCommerce Slack Community: https://woocommerce.com/community-slack/
- WordPress.org Forums: https://wordpress.org/support/plugin/woocommerce
- The WooCommerce Help and Share Facebook group
- The Official WooCommerce Facebook Group https://www.facebook.com/groups/advanced.woocommerce/

View File

@ -8,11 +8,30 @@ assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is. Please be as descriptive as possible; issues lacking detail, or for any other reason than to report a bug, may be closed without action.
Please provide us with the information requested in this bug report. Without these details, we won't be able to fully evaluate this issue.
Bug reports lacking detail, or for any other reason than to report a bug, may be closed without action.
**To Reproduce**
Steps to reproduce the behavior:
<!-- This template is for confirmed bugs only. If you have a support request or custom code related question please see our docs or use our forums, helpdesk, or Slack Community! https://github.com/woocommerce/woocommerce/issues/new?assignees=&labels=&template=3-Support.md&title= -->
<!-- Make sure to look through the existing issues to see whether your bug has already been submitted. Feel free to contribute to any existing issues. -->
<!-- Search tip: You can filter our issues using our component labels https://github.com/woocommerce/woocommerce/labels?q=component -->
<!-- Search tip: Make use of GitHub's search syntax to refine your search https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests -->
**Prerequisites (mark completed items with an [x]):**
- [ ] I have have carried out troubleshooting steps and I believe I have found a bug.
- [ ] I have searched for similar bugs in both open and closed issues and cannot find a duplicate.
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
A clear and concise description of what actually happens. Please be as descriptive as possible;
**Steps to reproduce the bug (We need to be able to reproduce the bug in order to fix it.)**
Steps to reproduce the bug:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@ -21,8 +40,7 @@ Steps to reproduce the behavior:
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Expected behavior**
A clear and concise description of what you expected to happen.
<!-- Please try testing your site for theme and plugins conflict. To do that deactivate all plugins except for WooCommerce and switch to a default WordPress theme or [Storefront](https://en-gb.wordpress.org/themes/storefront/). Then test again. If the issue is resolved with the default theme and all plugins deactivated, it means that one of your plugins or a theme is causing the issue. You will then need to enable it one by one and test every time you do that in order to figure out which plugin is causing the issue. -->
**Isolating the problem (mark completed items with an [x]):**
- [ ] I have deactivated other plugins and confirmed this bug occurs when only WooCommerce plugin is active.
@ -30,8 +48,11 @@ A clear and concise description of what you expected to happen.
- [ ] I can reproduce this bug consistently using the steps above.
**WordPress Environment**
We use the [WooCommerce System Status Report](https://docs.woocommerce.com/document/understanding-the-woocommerce-system-status-report/) to help us evaluate the issue.
Without this report we won't be able to fully evaluate this issue.
<details>
```
Copy and paste the system status report from **WooCommerce > System Status** in WordPress admin.
The System Status Report is found in your WordPress admin under **WooCommerce > Status**.
Please select “Get system report”, then “Copy for support”, and then paste it here.
```
</details>

View File

@ -8,6 +8,10 @@ assignees: ''
---
<!-- Make sure to look through existing issues to see whether your idea is already being discussed. Feel free to contribute to any existing issues. -->
<!-- Search tip: You can filter issues using our enhancement label https://github.com/woocommerce/woocommerce/issues?q=is%3Aissue+label%3Aenhancement -->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

View File

@ -8,6 +8,10 @@ assignees: ''
---
<!-- Make sure to look through existing issues to see whether your idea is already being discussed. Feel free to contribute to any existing issues. -->
<!-- Search tip: You can filter issues using our enhancement label https://github.com/woocommerce/woocommerce/issues?q=is%3Aissue+label%3Aenhancement -->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

4
.gitignore vendored
View File

@ -13,7 +13,7 @@ project.properties
*.swp
# Grunt
/node_modules/
node_modules/
none
# Sass
@ -47,6 +47,8 @@ tests/cli/vendor
/tests/e2e/config/local.json
/tests/e2e/docker
/tests/e2e/env/docker/wp-cli/initialize.sh
/tests/e2e/env/build/
/tests/e2e/env/build-module/
# Logs
/logs

View File

@ -36,6 +36,7 @@ jobs:
script:
- composer require wp-cli/i18n-command
- npm run build
- npm run build:packages
- npm install jest --global
- npm run docker:up
- npm run test:e2e

View File

@ -1,5 +1,69 @@
== Changelog ==
= 4.2.0 - 2020-06-02 =
**WooCommerce**
* Enhancement - Added Ghanaian regions to the state dropdown. #26273
* Enhancement - Added Mozambique provinces to the state dropdown. #26162
* Enhancement - Added support for the new group descriptions available on WordPress privacy exporters as of WP 5.3. #25575
* Fix - Fixed false positives when checking if uploads directory is public. #26600
* Fix - Introduced a new admin body class for supporting styling issues in WP 5.3+. #26251
* Fix - Removed case conversion of meta keys from CSV imports. #25517
* Fix - Allow schedule coupons via CRUD. #26387
* Fix - Password visibility toggle when password strength check fails. #26132
* Fix - Cross-sell placement when product has no description. #26334
* Fix - Display of the rate limit warning during payment method creation. #26411
* Fix - Made the shipping zone matching query's `zone_id` field more specific. #26308
* Fix - Corrected the display of RTL languages on the WooCommerce.com addons page. #26080
* Fix - Removed the postcode field for Ghana. #26272
* Fix - Made the hiding of state fields more explicit for Germany, Denmark, and Sweden. #25598
* Fix - Ensured that global attribute prefixes are passed to the `woocommerce_attribute_label` filter. #26022
* Dev - Increased WordPress minimum version to 5.2 according to policy. #26550
* Dev - Added the customer as a third argument to the `woocommerce_matched_rates` filter. #26361
* Dev - Introduced `woocommerce_menu_order_count` filter. #26044
* Dev - Introduced `should_send_ajax_request.adding_to_cart `cart event to allow short-circuiting cart addition. #25760
* Dev - Made the jQuery selector for checkout form rows less specific. #25654
* Dev - Changed the `{site_address}` placeholder to `{site_url}` for clarity. #25630
* Dev - Deprecated `.wp-policy-help` and replaced with with the `.privacy-policy-tutorial` and `.wp-suggested-text` classes added in WP 5.1. #26072
* Dev - Updated `automattic/jetpack-autoloader` to 1.7.0. #26559
* Dev - Add a way to fetch basic object data. #26025
**REST API 1.0.8**
* Enhancement - Add support for trash status for products in V2 and V3 API. #184
* Dev - Updated minimum PHP requirement to 7.0 to keep up with WooCommerce Core.
* Dev - Fixed failing unit tests. #105
**WooCommerce Admin 1.2.3**
* Enhancement - Add onboarding payments note #4157
* Enhancement - Marketing Inbox Note #4030
* Performance - Use Route based code splitting to reduce bundle size #4094
* Performance - trim down inbox note API request. #3977
* Fix - Proper display of elements in wc-admin pages when in a RTL environment. #4051
* Fix - Update UX when knowledge base articles fail to retrieve #4133
* Fix - Updated messaging after last step in OBW. #4148
* Fix - Reset profiler when visiting old OBW URL #4166.
* Fix - Dashboard flash before OBW chunk loads #4259
* Tweak - Enable the default homepage template to be filtered #4072
* Tweak - Create admin note if Jetpack or WooCommerce Services plugin doesn't get installed due to an error during OBW #3888
* Tweak - Update Email Marketing note. #4167
* Tweak - Adjust "demo products" verbiage to "Sample Products" #4184
* Tweak - Don't reschedule imports on failed imports #4263
* Tweak - Remove obsolete inbox messages #4182
* Tweak - Updates to WooCommerce Payments in Setup Checklist #4293
* Dev - Make query selector for admin alerts more specific #4289
* Dev - Guard against null themes in OBW #4244
* Dev - Update wcadmin db version after db callback #4323
* Dev - Only migrate options on version change #4324
* Dev - Use `PAGE_ROOT` constant to reduce redundant strings #4238
* Dev - Decouple Plugins DataStore from onboarding feature #4048
* Dev - Move API out of Onboarding #4093
* Dev - Add Profiler Step View Tracks #4141
* Dev - Add React Testing Library #4221
* Dev - Add List and Link components to Storybook #4219
* Dev - Cast Shipping Total to float #4042
* Dev - Dynamic Currency with Context API #4027
* Dev - Remove Duplicate array entry #4049
= 4.1.1 - 2020-05-19 =
* Enhancement - Added notice about public uploads directory. #26207

View File

@ -1 +1 @@
FROM wordpress:5.4.1
FROM wordpress:5.4.2

View File

@ -43,7 +43,7 @@
// Init after gallery.
setTimeout( function() {
$form.trigger( 'check_variations' );
$form.trigger( 'wc_variation_form' );
$form.trigger( 'wc_variation_form', self );
self.loading = false;
}, 100 );
};
@ -160,12 +160,12 @@
/**
* Looks for matching variations for current selected attributes.
*/
VariationForm.prototype.onFindVariation = function( event ) {
VariationForm.prototype.onFindVariation = function( event, chosenAttributes ) {
var form = event.data.variationForm,
attributes = form.getChosenAttributes(),
attributes = 'undefined' !== typeof chosenAttributes ? chosenAttributes : form.getChosenAttributes(),
currentAttributes = attributes.data;
if ( attributes.count === attributes.chosenCount ) {
if ( attributes.count && attributes.count === attributes.chosenCount ) {
if ( form.useAjax ) {
if ( form.xhr ) {
form.xhr.abort();

View File

@ -105,7 +105,7 @@ jQuery( function( $ ) {
}
$( '.woocommerce-cart-form' ).replaceWith( $new_form );
$( '.woocommerce-cart-form' ).find( ':input[name="update_cart"]' ).prop( 'disabled', true );
$( '.woocommerce-cart-form' ).find( ':input[name="update_cart"]' ).prop( 'disabled', true ).attr( 'aria-disabled', true );
if ( $notices.length > 0 ) {
show_notice( $notices );
@ -304,14 +304,14 @@ jQuery( function( $ ) {
'.woocommerce-cart-form .cart_item :input',
this.input_changed );
$( '.woocommerce-cart-form :input[name="update_cart"]' ).prop( 'disabled', true );
$( '.woocommerce-cart-form :input[name="update_cart"]' ).prop( 'disabled', true ).attr( 'aria-disabled', true );
},
/**
* After an input is changed, enable the update cart button.
*/
input_changed: function() {
$( '.woocommerce-cart-form :input[name="update_cart"]' ).prop( 'disabled', false );
$( '.woocommerce-cart-form :input[name="update_cart"]' ).prop( 'disabled', false ).attr( 'aria-disabled', false );
},
/**

View File

@ -9,7 +9,7 @@ jQuery( function( $ ) {
$( 'body' )
// Tabs
.on( 'init', '.wc-tabs-wrapper, .woocommerce-tabs', function() {
$( '.wc-tab, .woocommerce-tabs .panel:not(.panel .panel)' ).hide();
$( this ).find( '.wc-tab, .woocommerce-tabs .panel:not(.panel .panel)' ).hide();
var hash = window.location.hash;
var url = window.location.href;

View File

@ -81,15 +81,17 @@ jQuery( function( $ ) {
// Show password visiblity hover icon on woocommerce forms
$( '.woocommerce form .woocommerce-Input[type="password"]' ).wrap( '<span class="password-input"></span>' );
$( '.password-input' ).prepend( '<span class="show-password-input"></span>' );
// Add 'password-input' class to the password wrapper in checkout page.
$( '.woocommerce form input' ).filter(':password').parent('span').addClass('password-input');
$( '.password-input' ).append( '<span class="show-password-input"></span>' );
$( '.show-password-input' ).click(
function() {
$( this ).toggleClass( 'display-password' );
if ( $( this ).hasClass( 'display-password' ) ) {
$( this ).siblings( ['input[name^="password"]', 'input[type="password"]'] ).prop( 'type', 'text' );
$( this ).siblings( ['input[type="password"]'] ).prop( 'type', 'text' );
} else {
$( this ).siblings( 'input[name^="password"]' ).prop( 'type', 'password' );
$( this ).siblings( 'input[type="text"]' ).prop( 'type', 'password' );
}
}
);

View File

@ -755,6 +755,12 @@ S2.define('select2/utils',[
});
};
Utils.entityDecode = function(html) {
var txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
// Append an array of jQuery nodes to a given element.
Utils.appendMany = function ($element, $nodes) {
// jQuery 1.7.x does not support $.fn.append() with an array
@ -1611,9 +1617,9 @@ S2.define('select2/selection/single',[
var selection = data[0];
var $rendered = this.$selection.find('.select2-selection__rendered');
var formatted = this.display(selection, $rendered);
var formatted = Utils.entityDecode(this.display(selection, $rendered));
$rendered.empty().append(formatted);
$rendered.empty().text(formatted);
$rendered.prop('title', selection.title || selection.text);
};
@ -1742,12 +1748,14 @@ S2.define('select2/selection/multiple',[
var selection = data[d];
var $selection = this.selectionContainer();
var removeItemTag = $selection.html();
var formatted = this.display(selection, $selection);
if ('string' === typeof formatted) {
formatted = formatted.trim();
formatted = Utils.entityDecode(formatted.trim());
}
$selection.append(formatted);
$selection.text(formatted);
$selection.prepend(removeItemTag);
$selection.prop('title', selection.title || selection.text);
$selection.data('data', selection);
@ -1786,7 +1794,7 @@ S2.define('select2/selection/placeholder',[
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
var $placeholder = this.selectionContainer();
$placeholder.html(this.display(placeholder));
$placeholder.text(Utils.entityDecode(this.display(placeholder)));
$placeholder.addClass('select2-selection__placeholder')
.removeClass('select2-selection__choice');

File diff suppressed because one or more lines are too long

View File

@ -755,6 +755,12 @@ S2.define('select2/utils',[
});
};
Utils.entityDecode = function(html) {
var txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
// Append an array of jQuery nodes to a given element.
Utils.appendMany = function ($element, $nodes) {
// jQuery 1.7.x does not support $.fn.append() with an array
@ -1611,9 +1617,9 @@ S2.define('select2/selection/single',[
var selection = data[0];
var $rendered = this.$selection.find('.select2-selection__rendered');
var formatted = this.display(selection, $rendered);
var formatted = Utils.entityDecode(this.display(selection, $rendered));
$rendered.empty().append(formatted);
$rendered.empty().text(formatted);
$rendered.prop('title', selection.title || selection.text);
};
@ -1742,12 +1748,14 @@ S2.define('select2/selection/multiple',[
var selection = data[d];
var $selection = this.selectionContainer();
var removeItemTag = $selection.html();
var formatted = this.display(selection, $selection);
if ('string' === typeof formatted) {
formatted = formatted.trim();
formatted = Utils.entityDecode(formatted.trim());
}
$selection.append(formatted);
$selection.text(formatted);
$selection.prepend(removeItemTag);
$selection.prop('title', selection.title || selection.text);
$selection.data('data', selection);
@ -1786,7 +1794,7 @@ S2.define('select2/selection/placeholder',[
Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
var $placeholder = this.selectionContainer();
$placeholder.html(this.display(placeholder));
$placeholder.text(Utils.entityDecode(this.display(placeholder)));
$placeholder.addClass('select2-selection__placeholder')
.removeClass('select2-selection__choice');

File diff suppressed because one or more lines are too long

View File

@ -34,6 +34,7 @@ output 2 "Done!"
output 3 "Updating package JS textdomains..."
find ./packages/woocommerce-blocks -iname '*.js' -exec sed -i.bak -e "s/'woo-gutenberg-products-block'/'woocommerce'/g" -e "s/\"woo-gutenberg-products-block\"/'woocommerce'/g" {} \;
find ./packages/woocommerce-blocks -iname '*.js' -exec sed -i.bak -e "s/'woocommerce-admin'/'woocommerce'/g" -e "s/\"woocommerce-admin\"/'woocommerce'/g" {} \;
find ./packages/woocommerce-admin -iname '*.js' -exec sed -i.bak -e "s/'woocommerce-admin'/'woocommerce'/g" -e "s/\"woocommerce-admin\"/'woocommerce'/g" {} \;
# Cleanup backup files

22
bin/pre-push.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
PROTECTED_BRANCH="master"
REMOTE_REF=$(echo "$HUSKY_GIT_STDIN" | cut -d " " -f 3)
if [ -n "$REMOTE_REF" ]; then
if [ -z "${REMOTE_REF##*$PROTECTED_BRANCH*}" ]; then
printf "%sYou're about to push to master, is that what you intended? [y/N]: %s" "$(tput setaf 3)" "$(tput sgr0)"
read -r PROCEED < /dev/tty
echo
if [ "$(echo "${PROCEED:-n}" | tr "[:upper:]" "[:lower:]")" = "y" ]; then
echo "$(tput setaf 2)Brace yourself! Pushing to the master branch...$(tput sgr0)"
echo
exit 0
fi
echo "$(tput setaf 2)Push to master cancelled!$(tput sgr0)"
echo
exit 1
fi
fi

View File

@ -14,9 +14,9 @@
"maxmind-db/reader": "1.6.0",
"pelago/emogrifier": "^3.1",
"woocommerce/action-scheduler": "3.1.6",
"woocommerce/woocommerce-admin": "1.2.3",
"woocommerce/woocommerce-blocks": "2.5.16",
"woocommerce/woocommerce-rest-api": "1.0.8"
"woocommerce/woocommerce-admin": "1.3.0-rc.1",
"woocommerce/woocommerce-blocks": "2.7.1",
"woocommerce/woocommerce-rest-api": "1.0.10"
},
"require-dev": {
"phpunit/phpunit": "7.5.20",
@ -86,8 +86,8 @@
"test": "Run unit tests",
"phpcs": "Analyze code against the WordPress coding standards with PHP_CodeSniffer",
"phpcbf": "Fix coding standards warnings/errors automatically with PHP Code Beautifier",
"makepot-audit": "Generate i18n/langauges/woocommerce.pot file and run audit",
"makepot": "Generate i18n/langauges/woocommerce.pot file"
"makepot-audit": "Generate i18n/languages/woocommerce.pot file and run audit",
"makepot": "Generate i18n/languages/woocommerce.pot file"
}
}
}

128
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "674dae676cb4905418f7930391e8dcf2",
"content-hash": "c35209de8f965f88aa5121e3ba76fc65",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@ -331,7 +331,7 @@
},
{
"name": "symfony/css-selector",
"version": "v3.4.40",
"version": "v3.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@ -380,20 +380,6 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-03-16T08:31:04+00:00"
},
{
@ -433,16 +419,16 @@
},
{
"name": "woocommerce/woocommerce-admin",
"version": "v1.2.3",
"version": "v1.3.0-rc.1",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "5560c9b6e31e1d794a55a0eb4ccf040fd2cee4c7"
"reference": "12bc8bf522298a099bb725990cd50bae944e667f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/5560c9b6e31e1d794a55a0eb4ccf040fd2cee4c7",
"reference": "5560c9b6e31e1d794a55a0eb4ccf040fd2cee4c7",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/12bc8bf522298a099bb725990cd50bae944e667f",
"reference": "12bc8bf522298a099bb725990cd50bae944e667f",
"shasum": ""
},
"require": {
@ -476,20 +462,20 @@
],
"description": "A modern, javascript-driven WooCommerce Admin experience.",
"homepage": "https://github.com/woocommerce/woocommerce-admin",
"time": "2020-05-22T18:45:16+00:00"
"time": "2020-06-23T02:57:05+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",
"version": "v2.5.16",
"version": "v2.7.1",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
"reference": "3bd91b669247000fd3f5277954701d0b148d3f1a"
"reference": "0025c5cda83892c6f566fffd05197006f230d16c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/3bd91b669247000fd3f5277954701d0b148d3f1a",
"reference": "3bd91b669247000fd3f5277954701d0b148d3f1a",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/0025c5cda83892c6f566fffd05197006f230d16c",
"reference": "0025c5cda83892c6f566fffd05197006f230d16c",
"shasum": ""
},
"require": {
@ -523,20 +509,20 @@
"gutenberg",
"woocommerce"
],
"time": "2020-04-07T11:47:19+00:00"
"time": "2020-06-16T13:34:29+00:00"
},
{
"name": "woocommerce/woocommerce-rest-api",
"version": "1.0.8",
"version": "1.0.10",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-rest-api.git",
"reference": "0756027c669bb5749554ee58b9416cbdcceaa752"
"reference": "fdcb116b4f5b699b942c01b46fd863c7da8b4b7c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-rest-api/zipball/0756027c669bb5749554ee58b9416cbdcceaa752",
"reference": "0756027c669bb5749554ee58b9416cbdcceaa752",
"url": "https://api.github.com/repos/woocommerce/woocommerce-rest-api/zipball/fdcb116b4f5b699b942c01b46fd863c7da8b4b7c",
"reference": "fdcb116b4f5b699b942c01b46fd863c7da8b4b7c",
"shasum": ""
},
"require": {
@ -563,7 +549,7 @@
],
"description": "The WooCommerce core REST API.",
"homepage": "https://github.com/woocommerce/woocommerce-rest-api",
"time": "2020-05-11T14:54:30+00:00"
"time": "2020-06-16T09:51:51+00:00"
}
],
"packages-dev": [
@ -635,20 +621,20 @@
},
{
"name": "doctrine/instantiator",
"version": "1.3.0",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "ae466f726242e637cebdd526a7d991b9433bacf1"
"reference": "f350df0268e904597e3bd9c4685c53e0e333feea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1",
"reference": "ae466f726242e637cebdd526a7d991b9433bacf1",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea",
"reference": "f350df0268e904597e3bd9c4685c53e0e333feea",
"shasum": ""
},
"require": {
"php": "^7.1"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^6.0",
@ -687,7 +673,7 @@
"constructor",
"instantiate"
],
"time": "2019-10-21T16:45:58+00:00"
"time": "2020-05-29T17:27:14+00:00"
},
{
"name": "gettext/gettext",
@ -814,16 +800,16 @@
},
{
"name": "mck89/peast",
"version": "v1.10.3",
"version": "v1.10.4",
"source": {
"type": "git",
"url": "https://github.com/mck89/peast.git",
"reference": "6d1100f39f684c9e004f808b27f6c824b083d8d8"
"reference": "e11664ef53ba2a4ca1d16d8bc73fcc317cd65d3d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mck89/peast/zipball/6d1100f39f684c9e004f808b27f6c824b083d8d8",
"reference": "6d1100f39f684c9e004f808b27f6c824b083d8d8",
"url": "https://api.github.com/repos/mck89/peast/zipball/e11664ef53ba2a4ca1d16d8bc73fcc317cd65d3d",
"reference": "e11664ef53ba2a4ca1d16d8bc73fcc317cd65d3d",
"shasum": ""
},
"require": {
@ -835,7 +821,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.10.3-dev"
"dev-master": "1.10.4-dev"
}
},
"autoload": {
@ -855,7 +841,7 @@
}
],
"description": "Peast is PHP library that generates AST for JavaScript code",
"time": "2020-04-03T09:06:20+00:00"
"time": "2020-06-21T17:16:08+00:00"
},
{
"name": "mustache/mustache",
@ -2428,7 +2414,7 @@
},
{
"name": "symfony/finder",
"version": "v3.4.40",
"version": "v3.4.42",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
@ -2477,16 +2463,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.16.0",
"version": "v1.17.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294"
"reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/1aab00e39cebaef4d8652497f46c15c1b7e45294",
"reference": "1aab00e39cebaef4d8652497f46c15c1b7e45294",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d",
"reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d",
"shasum": ""
},
"require": {
@ -2498,7 +2484,11 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.16-dev"
"dev-master": "1.17-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -2531,21 +2521,7 @@
"polyfill",
"portable"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-05-08T16:50:20+00:00"
"time": "2020-06-06T08:46:27+00:00"
},
{
"name": "theseer/tokenizer",
@ -2589,16 +2565,16 @@
},
{
"name": "webmozart/assert",
"version": "1.8.0",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6"
"reference": "9dc4f203e36f2b486149058bade43c851dd97451"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
"reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
"url": "https://api.github.com/repos/webmozart/assert/zipball/9dc4f203e36f2b486149058bade43c851dd97451",
"reference": "9dc4f203e36f2b486149058bade43c851dd97451",
"shasum": ""
},
"require": {
@ -2606,6 +2582,7 @@
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<3.9.1"
},
"require-dev": {
@ -2633,7 +2610,7 @@
"check",
"validate"
],
"time": "2020-04-18T12:12:48+00:00"
"time": "2020-06-16T10:16:42+00:00"
},
{
"name": "woocommerce/woocommerce-sniffs",
@ -2677,16 +2654,16 @@
},
{
"name": "wp-cli/i18n-command",
"version": "v2.2.2",
"version": "v2.2.3",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/i18n-command.git",
"reference": "2804c5246d9338da59951737b03c54d257be8e47"
"reference": "7a5d483d872dfec1b89d88d348666ecd59454d52"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/2804c5246d9338da59951737b03c54d257be8e47",
"reference": "2804c5246d9338da59951737b03c54d257be8e47",
"url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/7a5d483d872dfec1b89d88d348666ecd59454d52",
"reference": "7a5d483d872dfec1b89d88d348666ecd59454d52",
"shasum": ""
},
"require": {
@ -2730,7 +2707,7 @@
],
"description": "Provides internationalization tools for WordPress projects.",
"homepage": "https://github.com/wp-cli/i18n-command",
"time": "2019-12-13T09:00:43+00:00"
"time": "2020-06-04T07:07:10+00:00"
},
{
"name": "wp-cli/mustangostang-spyc",
@ -2949,6 +2926,5 @@
"platform-dev": [],
"platform-overrides": {
"php": "7.1"
},
"plugin-api-version": "1.1.0"
}
}

View File

@ -1,4 +1,4 @@
version: '3.7'
version: '3.3'
services:

View File

@ -715,6 +715,55 @@ return array(
'JP46' => __( 'Kagoshima', 'woocommerce' ),
'JP47' => __( 'Okinawa', 'woocommerce' ),
),
'KE' => array( // Kenya counties.
'KE01' => __( 'Baringo', 'woocommerce' ),
'KE02' => __( 'Bomet', 'woocommerce' ),
'KE03' => __( 'Bungoma', 'woocommerce' ),
'KE04' => __( 'Busia', 'woocommerce' ),
'KE05' => __( 'Elgeyo-Marakwet', 'woocommerce' ),
'KE06' => __( 'Embu', 'woocommerce' ),
'KE07' => __( 'Garissa', 'woocommerce' ),
'KE08' => __( 'Homa Bay', 'woocommerce' ),
'KE09' => __( 'Isiolo', 'woocommerce' ),
'KE10' => __( 'Kajiado', 'woocommerce' ),
'KE11' => __( 'Kakamega', 'woocommerce' ),
'KE12' => __( 'Kericho', 'woocommerce' ),
'KE13' => __( 'Kiambu', 'woocommerce' ),
'KE14' => __( 'Kilifi', 'woocommerce' ),
'KE15' => __( 'Kirinyaga', 'woocommerce' ),
'KE16' => __( 'Kisii', 'woocommerce' ),
'KE17' => __( 'Kisumu', 'woocommerce' ),
'KE18' => __( 'Kitui', 'woocommerce' ),
'KE19' => __( 'Kwale', 'woocommerce' ),
'KE20' => __( 'Laikipia', 'woocommerce' ),
'KE21' => __( 'Lamu', 'woocommerce' ),
'KE22' => __( 'Machakos', 'woocommerce' ),
'KE23' => __( 'Makueni', 'woocommerce' ),
'KE24' => __( 'Mandera', 'woocommerce' ),
'KE25' => __( 'Marsabit', 'woocommerce' ),
'KE26' => __( 'Meru', 'woocommerce' ),
'KE27' => __( 'Migori', 'woocommerce' ),
'KE28' => __( 'Mombasa', 'woocommerce' ),
'KE29' => __( 'Muranga', 'woocommerce' ),
'KE30' => __( 'Nairobi County', 'woocommerce' ),
'KE31' => __( 'Nakuru', 'woocommerce' ),
'KE32' => __( 'Nandi', 'woocommerce' ),
'KE33' => __( 'Narok', 'woocommerce' ),
'KE34' => __( 'Nyamira', 'woocommerce' ),
'KE35' => __( 'Nyandarua', 'woocommerce' ),
'KE36' => __( 'Nyeri', 'woocommerce' ),
'KE37' => __( 'Samburu', 'woocommerce' ),
'KE38' => __( 'Siaya', 'woocommerce' ),
'KE39' => __( 'Taita-Taveta', 'woocommerce' ),
'KE40' => __( 'Tana River', 'woocommerce' ),
'KE41' => __( 'Tharaka-Nithi', 'woocommerce' ),
'KE42' => __( 'Trans Nzoia', 'woocommerce' ),
'KE43' => __( 'Turkana', 'woocommerce' ),
'KE44' => __( 'Uasin Gishu', 'woocommerce' ),
'KE45' => __( 'Vihiga', 'woocommerce' ),
'KE46' => __( 'Wajir', 'woocommerce' ),
'KE47' => __( 'West Pokot', 'woocommerce' ),
),
'KR' => array(),
'KW' => array(),
'LA' => array(

View File

@ -437,12 +437,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
* @return float
*/
public function get_subtotal() {
$subtotal = 0;
foreach ( $this->get_items() as $item ) {
$subtotal += wc_remove_number_precision( self::round_item_subtotal( wc_add_number_precision( $item->get_subtotal() ) ) );
}
$subtotal = round( $this->get_cart_subtotal_for_order(), wc_get_price_decimals() );
return apply_filters( 'woocommerce_order_get_subtotal', (float) $subtotal, $this );
}
@ -1672,18 +1667,13 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
public function calculate_totals( $and_taxes = true ) {
do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
$cart_subtotal = 0;
$cart_total = 0;
$fees_total = 0;
$shipping_total = 0;
$cart_subtotal_tax = 0;
$cart_total_tax = 0;
// Sum line item costs without rounding.
foreach ( $this->get_items() as $item ) {
$cart_subtotal += $item->get_subtotal();
$cart_total += $item->get_total();
}
$cart_subtotal = $this->get_cart_subtotal_for_order();
$cart_total = $this->get_cart_total_for_order();
// Sum shipping costs.
foreach ( $this->get_shipping_methods() as $shipping ) {

View File

@ -708,7 +708,7 @@ abstract class WC_Settings_API {
<legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
<select class="select <?php echo esc_attr( $data['class'] ); ?>" name="<?php echo esc_attr( $field_key ); ?>" id="<?php echo esc_attr( $field_key ); ?>" style="<?php echo esc_attr( $data['css'] ); ?>" <?php disabled( $data['disabled'], true ); ?> <?php echo $this->get_custom_attribute_html( $data ); // WPCS: XSS ok. ?>>
<?php foreach ( (array) $data['options'] as $option_key => $option_value ) : ?>
<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( (string) $option_key, esc_attr( $this->get_option( $key ) ) ); ?>><?php echo esc_attr( $option_value ); ?></option>
<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( (string) $option_key, esc_attr( $this->get_option( $key ) ) ); ?>><?php echo esc_html( $option_value ); ?></option>
<?php endforeach; ?>
</select>
<?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
@ -761,11 +761,11 @@ abstract class WC_Settings_API {
<?php if ( is_array( $option_value ) ) : ?>
<optgroup label="<?php echo esc_attr( $option_key ); ?>">
<?php foreach ( $option_value as $option_key_inner => $option_value_inner ) : ?>
<option value="<?php echo esc_attr( $option_key_inner ); ?>" <?php selected( in_array( (string) $option_key_inner, $value, true ), true ); ?>><?php echo esc_attr( $option_value_inner ); ?></option>
<option value="<?php echo esc_attr( $option_key_inner ); ?>" <?php selected( in_array( (string) $option_key_inner, $value, true ), true ); ?>><?php echo esc_html( $option_value_inner ); ?></option>
<?php endforeach; ?>
</optgroup>
<?php else : ?>
<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( in_array( (string) $option_key, $value, true ), true ); ?>><?php echo esc_attr( $option_value ); ?></option>
<option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( in_array( (string) $option_key, $value, true ), true ); ?>><?php echo esc_html( $option_value ); ?></option>
<?php endif; ?>
<?php endforeach; ?>
</select>

View File

@ -40,6 +40,7 @@ class WC_Admin_Notices {
'maxmind_license_key' => 'maxmind_missing_license_key_notice',
'redirect_download_method' => 'redirect_download_method_notice',
'uploads_directory_is_unprotected' => 'uploads_directory_is_unprotected_notice',
'base_tables_missing' => 'base_tables_missing_notice',
);
/**
@ -506,6 +507,21 @@ class WC_Admin_Notices {
include dirname( __FILE__ ) . '/views/html-notice-uploads-directory-is-unprotected.php';
}
/**
* Notice about base tables missing.
*/
public static function base_tables_missing_notice() {
$notice_dismissed = apply_filters(
'woocommerce_hide_base_tables_missing_nag',
get_user_meta( get_current_user_id(), 'dismissed_base_tables_missing_notice', true )
);
if ( $notice_dismissed ) {
self::remove_notice( 'base_tables_missing' );
}
include dirname( __FILE__ ) . '/views/html-notice-base-table-missing.php';
}
/**
* Determine if the store is running SSL.
*

View File

@ -339,6 +339,33 @@ class WC_Admin_Status {
}
}
/**
* Prints table info if a base table is not present.
*/
private static function output_tables_info() {
$missing_tables = WC_Install::verify_base_tables( false );
if ( 0 === count( $missing_tables ) ) {
return;
}
?>
<br>
<strong style="color:#a00;">
<span class="dashicons dashicons-warning"></span>
<?php
echo esc_html(
sprintf(
// translators: Comma seperated list of missing tables.
__( 'Missing base tables: %s. Some WooCommerce functionality may not work as expected.', 'woocommerce' ),
implode( ', ', $missing_tables )
)
);
?>
</strong>
<?php
}
/**
* Prints the information about plugins for the system status report.
* Used for both active and inactive plugins sections.

View File

@ -351,7 +351,7 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table {
*/
protected function render_products_type_filter() {
$current_product_type = isset( $_REQUEST['product_type'] ) ? wc_clean( wp_unslash( $_REQUEST['product_type'] ) ) : false; // WPCS: input var ok, sanitization ok.
$output = '<select name="product_type" id="dropdown_product_type"><option value="">' . __( 'Filter by product type', 'woocommerce' ) . '</option>';
$output = '<select name="product_type" id="dropdown_product_type"><option value="">' . esc_html__( 'Filter by product type', 'woocommerce' ) . '</option>';
foreach ( wc_get_product_types() as $value => $label ) {
$output .= '<option value="' . esc_attr( $value ) . '" ';
@ -362,11 +362,11 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table {
$output .= '<option value="downloadable" ';
$output .= selected( 'downloadable', $current_product_type, false );
$output .= '> ' . ( is_rtl() ? '&larr;' : '&rarr;' ) . ' ' . __( 'Downloadable', 'woocommerce' ) . '</option>';
$output .= '> ' . ( is_rtl() ? '&larr;' : '&rarr;' ) . ' ' . esc_html__( 'Downloadable', 'woocommerce' ) . '</option>';
$output .= '<option value="virtual" ';
$output .= selected( 'virtual', $current_product_type, false );
$output .= '> ' . ( is_rtl() ? '&larr;' : '&rarr;' ) . ' ' . __( 'Virtual', 'woocommerce' ) . '</option>';
$output .= '> ' . ( is_rtl() ? '&larr;' : '&rarr;' ) . ' ' . esc_html__( 'Virtual', 'woocommerce' ) . '</option>';
}
}

View File

@ -194,7 +194,7 @@ class WC_Meta_Box_Coupon_Data {
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $product->get_formatted_name() ) . '</option>';
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . htmlspecialchars( wp_kses_post( $product->get_formatted_name() ) ) . '</option>';
}
}
?>
@ -212,7 +212,7 @@ class WC_Meta_Box_Coupon_Data {
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $product->get_formatted_name() ) . '</option>';
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . htmlspecialchars( wp_kses_post( $product->get_formatted_name() ) ) . '</option>';
}
}
?>

View File

@ -404,9 +404,9 @@ class WC_Meta_Box_Order_Data {
}
if ( ! $found_method && ! empty( $payment_method ) ) {
echo '<option value="' . esc_attr( $payment_method ) . '" selected="selected">' . __( 'Other', 'woocommerce' ) . '</option>';
echo '<option value="' . esc_attr( $payment_method ) . '" selected="selected">' . esc_html__( 'Other', 'woocommerce' ) . '</option>';
} else {
echo '<option value="other">' . __( 'Other', 'woocommerce' ) . '</option>';
echo '<option value="other">' . esc_html__( 'Other', 'woocommerce' ) . '</option>';
}
?>
</select>

View File

@ -244,7 +244,7 @@ class WC_Meta_Box_Product_Data {
continue;
}
$attribute_id = 0;
$attribute_name = wc_clean( $attribute_names[ $i ] );
$attribute_name = wc_clean( esc_html( $attribute_names[ $i ] ) );
if ( 'pa_' === substr( $attribute_name, 0, 3 ) ) {
$attribute_id = wc_attribute_taxonomy_id_by_name( $attribute_name );
@ -257,7 +257,7 @@ class WC_Meta_Box_Product_Data {
$options = wp_parse_id_list( $options );
} else {
// Terms or text sent in textarea.
$options = 0 < $attribute_id ? wc_sanitize_textarea( wc_sanitize_term_text_based( $options ) ) : wc_sanitize_textarea( $options );
$options = 0 < $attribute_id ? wc_sanitize_textarea( esc_html( wc_sanitize_term_text_based( $options ) ) ) : wc_sanitize_textarea( esc_html( $options ) );
$options = wc_get_text_attributes( $options );
}

View File

@ -49,7 +49,7 @@ if ( ! defined( 'ABSPATH' ) ) {
foreach ( $all_terms as $term ) {
$options = $attribute->get_options();
$options = ! empty( $options ) ? $options : array();
echo '<option value="' . esc_attr( $term->term_id ) . '"' . wc_selected( $term->term_id, $options ) . '>' . esc_attr( apply_filters( 'woocommerce_product_attribute_term_name', $term->name, $term ) ) . '</option>';
echo '<option value="' . esc_attr( $term->term_id ) . '"' . wc_selected( $term->term_id, $options ) . '>' . esc_html( apply_filters( 'woocommerce_product_attribute_term_name', $term->name, $term ) ) . '</option>';
}
}
?>

View File

@ -19,7 +19,7 @@ defined( 'ABSPATH' ) || exit;
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $product->get_formatted_name() ) . '</option>';
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . htmlspecialchars( wp_kses_post( $product->get_formatted_name() ) ) . '</option>';
}
}
?>
@ -37,7 +37,7 @@ defined( 'ABSPATH' ) || exit;
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $product->get_formatted_name() ) . '</option>';
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . htmlspecialchars( wp_kses_post( $product->get_formatted_name() ) ) . '</option>';
}
}
?>
@ -53,7 +53,7 @@ defined( 'ABSPATH' ) || exit;
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $product->get_formatted_name() ) . '</option>';
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . htmlspecialchars( wp_kses_post( $product->get_formatted_name() ) ) . '</option>';
}
}
?>

View File

@ -156,7 +156,6 @@ class WC_Notes_Run_Db_Update {
. sprintf( ' ' . esc_html__( 'The database update process runs in the background and may take a little while, so please be patient. Advanced users can alternatively update via %1$sWP CLI%2$s.', 'woocommerce' ), '<a href="https://github.com/woocommerce/woocommerce/wiki/Upgrading-the-database-using-WP-CLI">', '</a>' )
);
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_UPDATE );
$note->set_icon( 'info' );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-core' );

View File

@ -45,13 +45,6 @@ class WC_Plugin_Updates {
*/
protected $major_untested_plugins = array();
/**
* Array of plugins lacking testing with the minor version.
*
* @var array
*/
protected $minor_untested_plugins = array();
/**
* Common JS for initializing and managing thickbox-based modals.
*/
@ -103,29 +96,6 @@ class WC_Plugin_Updates {
| Methods for getting messages.
*/
/**
* Get the inline warning notice for minor version updates.
*
* @return string
*/
protected function get_extensions_inline_warning_minor() {
$upgrade_type = 'minor';
$plugins = ! empty( $this->major_untested_plugins ) ? array_diff_key( $this->minor_untested_plugins, $this->major_untested_plugins ) : $this->minor_untested_plugins;
$version_parts = explode( '.', $this->new_version );
$new_version = $version_parts[0] . '.' . $version_parts[1];
if ( empty( $plugins ) ) {
return;
}
/* translators: %s: version number */
$message = sprintf( __( "<strong>Heads up!</strong> The versions of the following plugins you're running haven't been tested with the latest version of WooCommerce (%s).", 'woocommerce' ), $new_version );
ob_start();
include 'views/html-notice-untested-extensions-inline.php';
return ob_get_clean();
}
/**
* Get the inline warning notice for major version updates.
*

View File

@ -45,7 +45,6 @@ class WC_Plugins_Screen_Updates extends WC_Plugin_Updates {
$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->minor_untested_plugins = $this->get_untested_plugins( $response->new_version, 'minor' );
$current_version_parts = explode( '.', Constants::get_constant( 'WC_VERSION' ) );
$new_version_parts = explode( '.', $this->new_version );
@ -59,10 +58,6 @@ class WC_Plugins_Screen_Updates extends WC_Plugin_Updates {
$this->upgrade_notice .= $this->get_extensions_inline_warning_major();
}
if ( ! empty( $this->minor_untested_plugins ) ) {
$this->upgrade_notice .= $this->get_extensions_inline_warning_minor();
}
if ( ! empty( $this->major_untested_plugins ) ) {
$this->upgrade_notice .= $this->get_extensions_modal_warning();
add_action( 'admin_print_footer_scripts', array( $this, 'plugin_screen_modal_js' ) );

View File

@ -32,12 +32,14 @@ class WC_Settings_Accounts extends WC_Settings_Page {
*/
public function get_settings() {
$erasure_text = esc_html__( 'account erasure request', 'woocommerce' );
$privacy_text = esc_html__( 'privacy page', 'woocommerce' );
if ( current_user_can( 'manage_privacy_options' ) ) {
if ( version_compare( get_bloginfo( 'version' ), '5.3', '<' ) ) {
$erasure_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'tools.php?page=remove_personal_data' ) ), $erasure_text );
} else {
$erasure_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'erase-personal-data.php' ) ), $erasure_text );
}
$privacy_text = sprintf( '<a href="%s">%s</a>', esc_url( admin_url( 'options-privacy.php' ) ), $privacy_text );
}
$account_settings = array(
@ -136,18 +138,8 @@ class WC_Settings_Accounts extends WC_Settings_Page {
'title' => __( 'Privacy policy', 'woocommerce' ),
'type' => 'title',
'id' => 'privacy_policy_options',
'desc' => __( 'This section controls the display of your website privacy policy. The privacy notices below will not show up unless a privacy page is first set.', 'woocommerce' ),
),
array(
'title' => __( 'Privacy page', 'woocommerce' ),
'desc' => __( 'Choose a page to act as your privacy policy.', 'woocommerce' ),
'id' => 'wp_page_for_privacy_policy',
'type' => 'single_select_page',
'default' => '',
'class' => 'wc-enhanced-select-nostd',
'css' => 'min-width:300px;',
'desc_tip' => true,
/* translators: %s: privacy page link. */
'desc' => sprintf( esc_html__( 'This section controls the display of your website privacy policy. The privacy notices below will not show up unless a %s is set.', 'woocommerce' ), $privacy_text ),
),
array(

View File

@ -42,6 +42,7 @@ class WC_Settings_Advanced extends WC_Settings_Page {
'webhooks' => __( 'Webhooks', 'woocommerce' ),
'legacy_api' => __( 'Legacy API', 'woocommerce' ),
'woocommerce_com' => __( 'WooCommerce.com', 'woocommerce' ),
'features' => __( 'Features', 'woocommerce' ),
);
return apply_filters( 'woocommerce_get_sections_' . $this->id, $sections );
@ -397,6 +398,28 @@ class WC_Settings_Advanced extends WC_Settings_Page {
),
)
);
} elseif ( 'features' === $current_section ) {
$settings = apply_filters(
'woocommerce_settings_features',
array(
array(
'title' => __( 'Features', 'woocommerce' ),
'type' => 'title',
'desc' => __( 'Start using new features that are being progressively rolled out to improve the store management experience.', 'woocommerce' ),
'id' => 'features_options',
),
array(
'title' => __( 'Home Screen', 'woocommerce' ),
'desc' => __( 'Displays analytical insights, inbox notifications, and handy shortcuts in a single screen', 'woocommerce' ),
'id' => 'woocommerce_homescreen_enabled',
'type' => 'checkbox',
),
array(
'type' => 'sectionend',
'id' => 'features_options',
),
)
);
}
return apply_filters( 'woocommerce_get_settings_' . $this->id, $settings, $current_section );

View File

@ -189,7 +189,7 @@ if ( ! defined( 'ABSPATH' ) ) {
if ( ! $method->supports( 'shipping-zones' ) ) {
continue;
}
echo '<option data-description="' . esc_attr( wp_kses_post( wpautop( $method->get_method_description() ) ) ) . '" value="' . esc_attr( $method->id ) . '">' . esc_attr( $method->get_method_title() ) . '</li>';
echo '<option data-description="' . esc_attr( wp_kses_post( wpautop( $method->get_method_description() ) ) ) . '" value="' . esc_attr( $method->id ) . '">' . esc_html( $method->get_method_title() ) . '</li>';
}
?>
</select>

View File

@ -108,7 +108,7 @@ if ( ! defined( 'ABSPATH' ) ) {
if ( ! $method->supports( 'shipping-zones' ) ) {
continue;
}
echo '<option data-description="' . esc_attr( wp_kses_post( wpautop( $method->get_method_description() ) ) ) . '" value="' . esc_attr( $method->id ) . '">' . esc_attr( $method->get_method_title() ) . '</li>';
echo '<option data-description="' . esc_attr( wp_kses_post( wpautop( $method->get_method_description() ) ) ) . '" value="' . esc_attr( $method->id ) . '">' . esc_html( $method->get_method_title() ) . '</li>';
}
?>
</select>

View File

@ -496,10 +496,17 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, 'min
?>
</tbody>
</table>
<table class="wc_status_table widefat" cellspacing="0">
<table id="status-database" class="wc_status_table widefat" cellspacing="0">
<thead>
<tr>
<th colspan="3" data-export-label="Database"><h2><?php esc_html_e( 'Database', 'woocommerce' ); ?></h2></th>
<th colspan="3" data-export-label="Database">
<h2>
<?php
esc_html_e( 'Database', 'woocommerce' );
self::output_tables_info();
?>
</h2>
</th>
</tr>
</thead>
<tbody>

View File

@ -29,7 +29,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'3' => __( 'Decrease existing price by (fixed amount or %):', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -54,7 +54,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'4' => __( 'Set to regular price decreased by (fixed amount or %):', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -78,7 +78,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'none' => _x( 'None', 'Tax status', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -104,7 +104,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -124,7 +124,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'1' => __( 'Change to:', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -148,7 +148,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'1' => __( 'Change to:', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -170,7 +170,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<option value="_no_shipping_class"><?php _e( 'No shipping class', 'woocommerce' ); ?></option>
<?php
foreach ( $shipping_class as $key => $value ) {
echo '<option value="' . esc_attr( $value->slug ) . '">' . $value->name . '</option>';
echo '<option value="' . esc_attr( $value->slug ) . '">' . esc_html( $value->name ) . '</option>';
}
?>
</select>
@ -190,7 +190,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'hidden' => __( 'Hidden', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -207,7 +207,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'no' => __( 'No', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -222,7 +222,7 @@ if ( ! defined( 'ABSPATH' ) ) {
echo '<option value="">' . esc_html__( '— No Change —', 'woocommerce' ) . '</option>';
foreach ( wc_get_product_stock_status_options() as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -241,7 +241,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'no' => __( 'No', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -261,7 +261,7 @@ if ( ! defined( 'ABSPATH' ) ) {
'3' => __( 'Decrease existing stock by:', 'woocommerce' ),
);
foreach ( $options as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>
@ -280,7 +280,7 @@ if ( ! defined( 'ABSPATH' ) ) {
echo '<option value="">' . esc_html__( '— No Change —', 'woocommerce' ) . '</option>';
foreach ( wc_get_product_backorder_options() as $key => $value ) {
echo '<option value="' . esc_attr( $key ) . '">' . $value . '</option>';
echo '<option value="' . esc_attr( $key ) . '">' . esc_html( $value ) . '</option>';
}
?>
</select>

View File

@ -0,0 +1,43 @@
<?php
/**
* Admin View: Notice - Base table missing.
*
* @package WooCommerce\Admin
*/
defined( 'ABSPATH' ) || exit;
?>
<div class="updated woocommerce-message">
<a class="woocommerce-message-close notice-dismiss" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'wc-hide-notice', 'base_tables_missing' ), 'woocommerce_hide_notices_nonce', '_wc_notice_nonce' ) ); ?>">
<?php esc_html_e( 'Dismiss', 'woocommerce' ); ?>
</a>
<p>
<strong><?php esc_html_e( 'Database tables missing', 'woocommerce' ); ?></strong>
</p>
<p>
<?php
$verify_db_tool_available = array_key_exists( 'verify_db_tables', WC_Admin_Status::get_tools() );
$missing_tables = get_option( 'woocommerce_schema_missing_tables' );
if ( $verify_db_tool_available ) {
echo wp_kses_post(
sprintf(
/* translators: %1%s: Missing tables (seperated by ",") %2$s: Link to check again */
__( 'One or more tables required for WooCommerce to function are missing, some features may not work as expected. Missing tables: %1$s. <a href="%2$s">Check again.</a>', 'woocommerce' ),
esc_html( implode( ', ', $missing_tables ) ),
wp_nonce_url( admin_url( 'admin.php?page=wc-status&tab=tools&action=verify_db_tables' ), 'debug_action' )
)
);
} else {
echo wp_kses_post(
sprintf(
/* translators: %1%s: Missing tables (seperated by ",") */
__( 'One or more tables required for WooCommerce to function are missing, some features may not work as expected. Missing tables: %1$s.', 'woocommerce' ),
esc_html( implode( ', ', $missing_tables ) )
)
);
}
?>
</p>
</div>

View File

@ -40,8 +40,15 @@ class WC_Cache_Helper {
* @since 3.6.0
*/
public static function additional_nocache_headers( $headers ) {
// no-transform: Opt-out of Google weblight if page is dynamic e.g. cart/checkout. https://support.google.com/webmasters/answer/6211428?hl=en.
$headers['Cache-Control'] = 'no-transform, no-cache, no-store, must-revalidate';
/**
* Allow CDN plugins to disable nocache headers.
*
* @param bool $enable_nocache_headers Flag indicating whether to add nocache headers. Default: true.
*/
if ( apply_filters( 'woocommerce_enable_nocache_headers', true ) ) {
// no-transform: Opt-out of Google weblight. https://support.google.com/webmasters/answer/6211428?hl=en.
$headers['Cache-Control'] = 'no-transform, no-cache, no-store, must-revalidate';
}
return $headers;
}

View File

@ -765,7 +765,6 @@ class WC_Cart extends WC_Legacy_Cart {
public function check_cart_item_stock() {
$error = new WP_Error();
$product_qty_in_cart = $this->get_cart_item_quantities();
$hold_stock_minutes = (int) get_option( 'woocommerce_hold_stock_minutes', 0 );
$current_session_order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0;
foreach ( $this->get_cart() as $cart_item_key => $values ) {
@ -784,7 +783,7 @@ class WC_Cart extends WC_Legacy_Cart {
}
// Check stock based on all items in the cart and consider any held stock within pending orders.
$held_stock = ( $hold_stock_minutes > 0 ) ? wc_get_held_stock_quantity( $product, $current_session_order_id ) : 0;
$held_stock = wc_get_held_stock_quantity( $product, $current_session_order_id );
$required_stock = $product_qty_in_cart[ $product->get_stock_managed_by_id() ];
if ( $product->get_stock_quantity() < ( $held_stock + $required_stock ) ) {

View File

@ -388,12 +388,30 @@ class WC_Checkout {
// Save the order.
$order_id = $order->save();
/**
* Action hook fired after an order is created used to add custom meta to the order.
*
* @since 3.0.0
*/
do_action( 'woocommerce_checkout_update_order_meta', $order_id, $data );
/**
* Action hook fired after an order is created.
*
* @since 4.3.0
*/
do_action( 'woocommerce_checkout_order_created', $order );
return $order_id;
} catch ( Exception $e ) {
if ( $order && $order instanceof WC_Order ) {
$order->get_data_store()->release_held_coupons( $order );
/**
* Action hook fired when an order is discarded due to Exception.
*
* @since 4.3.0
*/
do_action( 'woocommerce_checkout_order_exception', $order );
}
return new WP_Error( 'checkout-error', $e->getMessage() );
}

View File

@ -146,7 +146,7 @@ class WC_Comments {
*/
public static function check_comment_rating( $comment_data ) {
// If posting a comment (not trackback etc) and not logged in.
if ( ! is_admin() && isset( $_POST['comment_post_ID'], $_POST['rating'], $comment_data['comment_type'] ) && 'product' === get_post_type( absint( $_POST['comment_post_ID'] ) ) && empty( $_POST['rating'] ) && '' === $comment_data['comment_type'] && wc_review_ratings_enabled() && wc_review_ratings_required() ) { // WPCS: input var ok, CSRF ok.
if ( ! is_admin() && isset( $_POST['comment_post_ID'], $_POST['rating'], $comment_data['comment_type'] ) && 'product' === get_post_type( absint( $_POST['comment_post_ID'] ) ) && empty( $_POST['rating'] ) && self::is_default_comment_type( $comment_data['comment_type'] ) && wc_review_ratings_enabled() && wc_review_ratings_required() ) { // WPCS: input var ok, CSRF ok.
wp_die( esc_html__( 'Please rate the product.', 'woocommerce' ) );
exit;
}
@ -406,12 +406,26 @@ class WC_Comments {
* @return array
*/
public static function update_comment_type( $comment_data ) {
if ( ! is_admin() && isset( $_POST['comment_post_ID'], $comment_data['comment_type'] ) && '' === $comment_data['comment_type'] && 'product' === get_post_type( absint( $_POST['comment_post_ID'] ) ) ) { // WPCS: input var ok, CSRF ok.
if ( ! is_admin() && isset( $_POST['comment_post_ID'], $comment_data['comment_type'] ) && self::is_default_comment_type( $comment_data['comment_type'] ) && 'product' === get_post_type( absint( $_POST['comment_post_ID'] ) ) ) { // WPCS: input var ok, CSRF ok.
$comment_data['comment_type'] = 'review';
}
return $comment_data;
}
/**
* Determines if a comment is of the default type.
*
* Prior to WordPress 5.5, '' was the default comment type.
* As of 5.5, the default type is 'comment'.
*
* @since 4.3.0
* @param string $comment_type Comment type.
* @return bool
*/
private static function is_default_comment_type( $comment_type ) {
return ( '' === $comment_type || 'comment' === $comment_type );
}
}
WC_Comments::init();

View File

@ -474,7 +474,7 @@ class WC_Countries {
echo ' selected="selected"';
}
echo '>' . esc_html( $value ) . ' &mdash; ' . ( $escape ? esc_js( $state_value ) : $state_value ) . '</option>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '>' . esc_html( $value ) . ' &mdash; ' . ( $escape ? esc_html( $state_value ) : $state_value ) . '</option>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</optgroup>';
@ -483,7 +483,7 @@ class WC_Countries {
if ( $selected_country === $key && '*' === $selected_state ) {
echo ' selected="selected"';
}
echo ' value="' . esc_attr( $key ) . '">' . ( $escape ? esc_js( $value ) : $value ) . '</option>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo ' value="' . esc_attr( $key ) . '">' . ( $escape ? esc_html( $value ) : $value ) . '</option>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
}

View File

@ -869,11 +869,12 @@ class WC_Form_Handler {
*/
private static function add_to_cart_handler_variable( $product_id ) {
try {
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$missing_attributes = array();
$variations = array();
$adding_to_cart = wc_get_product( $product_id );
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$missing_attributes = array();
$variations = array();
$variation_attributes = array();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return false;
@ -881,6 +882,9 @@ class WC_Form_Handler {
// If the $product_id was in fact a variation ID, update the variables.
if ( $adding_to_cart->is_type( 'variation' ) ) {
$variation_attributes = $adding_to_cart->get_variation_attributes();
// Filter out 'any' variations, which are empty, as they need to be explicitly specified while adding to cart.
$variation_attributes = array_filter( $variation_attributes );
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
@ -911,6 +915,9 @@ class WC_Form_Handler {
}
}
// Merge variation attributes and posted attributes.
$posted_and_variation_attributes = array_merge( $variation_attributes, $posted_attributes );
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
@ -939,8 +946,8 @@ class WC_Form_Handler {
*
* If no attribute was posted, only error if the variation has an 'any' attribute which requires a value.
*/
if ( isset( $posted_attributes[ $attribute_key ] ) ) {
$value = $posted_attributes[ $attribute_key ];
if ( isset( $posted_and_variation_attributes[ $attribute_key ] ) ) {
$value = $posted_and_variation_attributes[ $attribute_key ];
// Allow if valid or show error.
if ( $valid_value === $value ) {

View File

@ -287,6 +287,7 @@ class WC_Install {
WC()->wpdb_table_fix();
self::remove_admin_notices();
self::create_tables();
self::verify_base_tables();
self::create_options();
self::create_roles();
self::setup_environment();
@ -296,6 +297,7 @@ class WC_Install {
self::maybe_enable_setup_wizard();
self::update_wc_version();
self::maybe_update_db_version();
self::maybe_enable_homescreen();
delete_transient( 'wc_installing' );
@ -303,6 +305,54 @@ class WC_Install {
do_action( 'woocommerce_installed' );
}
/**
* Check if all the base tables are present.
*
* @param bool $modify_notice Whether to modify notice based on if all tables are present.
* @param bool $execute Whether to execute get_schema queries as well.
*
* @return array List of querues.
*/
public static function verify_base_tables( $modify_notice = true, $execute = false ) {
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
if ( $execute ) {
self::create_tables();
}
$queries = dbDelta( self::get_schema(), false );
$missing_tables = array();
foreach ( $queries as $table_name => $result ) {
if ( "Created table $table_name" === $result ) {
$missing_tables[] = $table_name;
}
}
if ( 0 < count( $missing_tables ) ) {
if ( $modify_notice ) {
WC_Admin_Notices::add_notice( 'base_tables_missing' );
}
update_option( 'woocommerce_schema_missing_tables', $missing_tables );
} else {
if ( $modify_notice ) {
WC_Admin_Notices::remove_notice( 'base_tables_missing' );
}
update_option( 'woocommerce_schema_version', WC()->db_version );
delete_option( 'woocommerce_schema_missing_tables' );
}
return $missing_tables;
}
/**
* Check if the homepage should be enabled and set the appropriate option if thats the case.
*
* @since 4.3.0
*/
private static function maybe_enable_homescreen() {
if ( self::is_new_install() && ! get_option( 'woocommerce_homescreen_enabled' ) ) {
add_option( 'woocommerce_homescreen_enabled', 'yes' );
}
}
/**
* Reset any notices added to admin.
*
@ -621,6 +671,11 @@ class WC_Install {
/**
* Set up the database tables which the plugin needs to function.
* WARNING: If you are modifying this method, make sure that its safe to call regardless of the state of database.
*
* This is called from `install` method and is executed in-sync when WC is installed or updated. This can also be called optionally from `verify_base_tables`.
*
* TODO: Add all crucial tables that we have created from workers in the past.
*
* Tables:
* woocommerce_attribute_taxonomies - Table for storing attribute taxonomies - these are user defined
@ -946,6 +1001,14 @@ CREATE TABLE {$wpdb->prefix}wc_tax_rate_classes (
slug varchar(200) NOT NULL DEFAULT '',
PRIMARY KEY (tax_rate_class_id),
UNIQUE KEY slug (slug($max_index_length))
) $collate;
CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
`order_id` bigint(20) NOT NULL,
`product_id` bigint(20) NOT NULL,
`stock_quantity` double NOT NULL DEFAULT 0,
`timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`expires` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`order_id`, `product_id`)
) $collate;
";
@ -980,6 +1043,7 @@ CREATE TABLE {$wpdb->prefix}wc_tax_rate_classes (
"{$wpdb->prefix}woocommerce_shipping_zones",
"{$wpdb->prefix}woocommerce_tax_rate_locations",
"{$wpdb->prefix}woocommerce_tax_rates",
"{$wpdb->prefix}wc_reserved_stock",
);
/**

View File

@ -59,6 +59,7 @@ class WC_Post_Types {
'query_var' => is_admin(),
'rewrite' => false,
'public' => false,
'label' => _x( 'Product type', 'Taxonomy name', 'woocommerce' ),
)
)
);
@ -75,6 +76,7 @@ class WC_Post_Types {
'query_var' => is_admin(),
'rewrite' => false,
'public' => false,
'label' => _x( 'Product visibility', 'Taxonomy name', 'woocommerce' ),
)
)
);

View File

@ -22,6 +22,15 @@ final class WooCommerce {
*/
public $version = '4.3.0';
/**
* WooCommerce Schema version.
*
* @since 4.3 started with version string 430.
*
* @var string
*/
public $db_version = '430';
/**
* The single instance of the class.
*
@ -229,7 +238,7 @@ final class WooCommerce {
$this->define( 'WC_LOG_DIR', $upload_dir['basedir'] . '/wc-logs/' );
$this->define( 'WC_SESSION_CACHE_GROUP', 'wc_session_id' );
$this->define( 'WC_TEMPLATE_DEBUG_MODE', false );
$this->define( 'WC_NOTICE_MIN_PHP_VERSION', '7.0' );
$this->define( 'WC_NOTICE_MIN_PHP_VERSION', '7.2' );
$this->define( 'WC_NOTICE_MIN_WP_VERSION', '5.2' );
$this->define( 'WC_PHP_MIN_REQUIREMENTS_NOTICE', 'wp_php_min_requirements_' . WC_NOTICE_MIN_PHP_VERSION . '_' . WC_NOTICE_MIN_WP_VERSION );
}
@ -246,6 +255,7 @@ final class WooCommerce {
'order_itemmeta' => 'woocommerce_order_itemmeta',
'wc_product_meta_lookup' => 'wc_product_meta_lookup',
'wc_tax_rate_classes' => 'wc_tax_rate_classes',
'wc_reserved_stock' => 'wc_reserved_stock',
);
foreach ( $tables as $name => $table ) {

View File

@ -762,10 +762,16 @@ class WC_Shop_Customizer {
)
);
$choose_pages = array(
'wp_page_for_privacy_policy' => __( 'Privacy policy', 'woocommerce' ),
'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ),
);
if ( current_user_can( 'manage_privacy_options' ) ) {
$choose_pages = array(
'wp_page_for_privacy_policy' => __( 'Privacy policy', 'woocommerce' ),
'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ),
);
} else {
$choose_pages = array(
'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ),
);
}
$pages = get_pages(
array(
'post_type' => 'page',

View File

@ -881,8 +881,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$outofstock_where = ' AND exclude_join.object_id IS NULL';
}
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $wpdb->get_results(
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"
SELECT posts.ID as id, posts.post_parent as parent_id
FROM {$wpdb->posts} AS posts
@ -900,8 +900,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
)
GROUP BY posts.ID
"
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
/**
@ -1603,7 +1603,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
foreach ( $search_terms as $search_term ) {
$like = '%' . $wpdb->esc_like( $search_term ) . '%';
$term_group_query .= $wpdb->prepare( " {$searchand} ( ( posts.post_title LIKE %s) OR ( posts.post_excerpt LIKE %s) OR ( posts.post_content LIKE %s ) OR ( wc_product_meta_lookup.sku LIKE %s ) )", $like, $like, $like, $like ); // @codingStandardsIgnoreLine.
$term_group_query .= $wpdb->prepare( " {$searchand} ( ( posts.post_title LIKE %s) OR ( posts.post_excerpt LIKE %s) OR ( posts.post_content LIKE %s ) OR ( wc_product_meta_lookup.sku LIKE %s ) )", $like, $like, $like, $like ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$searchand = ' AND ';
}
@ -2062,4 +2062,23 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
}
return '';
}
/**
* Returns query statement for getting current `_stock` of a product.
*
* @internal MAX function below is used to make sure result is a scalar.
* @param int $product_id Product ID.
* @return string|void Query statement.
*/
public function get_query_for_stock( $product_id ) {
global $wpdb;
return $wpdb->prepare(
"
SELECT COALESCE ( MAX( meta_value ), 0 ) FROM $wpdb->postmeta as meta_table
WHERE meta_table.meta_key = '_stock'
AND meta_table.post_id = %d
",
$product_id
);
}
}

View File

@ -656,10 +656,10 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
if ( 0 === strpos( $attribute_name, 'pa_' ) ) {
$option_term = get_term_by( 'slug', $attribute, $attribute_name ); // @codingStandardsIgnoreLine.
$row[ 'attributes:value' . $i ] = $option_term && ! is_wp_error( $option_term ) ? str_replace( ',', '\\,', $option_term->name ) : $attribute;
$row[ 'attributes:value' . $i ] = $option_term && ! is_wp_error( $option_term ) ? str_replace( ',', '\\,', $option_term->name ) : str_replace( ',', '\\,', $attribute );
$row[ 'attributes:taxonomy' . $i ] = 1;
} else {
$row[ 'attributes:value' . $i ] = $attribute;
$row[ 'attributes:value' . $i ] = str_replace( ',', '\\,', $attribute );
$row[ 'attributes:taxonomy' . $i ] = 0;
}

View File

@ -400,21 +400,22 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
$total = count( $_terms );
foreach ( $_terms as $index => $_term ) {
// Check if category exists. Parent must be empty string or null if doesn't exists.
$term = term_exists( $_term, 'product_cat', $parent );
if ( is_array( $term ) ) {
$term_id = $term['term_id'];
// Don't allow users without capabilities to create new categories.
} elseif ( ! current_user_can( 'manage_product_terms' ) ) {
// Don't allow users without capabilities to create new categories.
if ( ! current_user_can( 'manage_product_terms' ) ) {
break;
} else {
$term = wp_insert_term( $_term, 'product_cat', array( 'parent' => intval( $parent ) ) );
}
if ( is_wp_error( $term ) ) {
break; // We cannot continue if the term cannot be inserted.
$term = wp_insert_term( $_term, 'product_cat', array( 'parent' => intval( $parent ) ) );
if ( is_wp_error( $term ) ) {
if ( $term->get_error_code() === 'term_exists' ) {
// When term exists, error data should contain existing term id.
$term_id = $term->get_error_data();
} else {
break; // We cannot continue on any other error.
}
} else {
// New term.
$term_id = $term['term_id'];
}

View File

@ -84,9 +84,8 @@ class WC_Shortcode_Checkout {
// Pay for existing order.
if ( isset( $_GET['pay_for_order'], $_GET['key'] ) && $order_id ) { // WPCS: input var ok, CSRF ok.
try {
$order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // WPCS: input var ok, CSRF ok.
$order = wc_get_order( $order_id );
$hold_stock_minutes = (int) get_option( 'woocommerce_hold_stock_minutes', 0 );
$order_key = isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : ''; // WPCS: input var ok, CSRF ok.
$order = wc_get_order( $order_id );
// Order or payment link is invalid.
if ( ! $order || $order->get_id() !== $order_id || ! hash_equals( $order->get_order_key(), $order_key ) ) {
@ -158,7 +157,7 @@ class WC_Shortcode_Checkout {
}
// Check stock based on all items in the cart and consider any held stock within pending orders.
$held_stock = ( $hold_stock_minutes > 0 ) ? wc_get_held_stock_quantity( $product, $order->get_id() ) : 0;
$held_stock = wc_get_held_stock_quantity( $product, $order->get_id() );
$required_stock = $quantities[ $product->get_stock_managed_by_id() ];
if ( ! apply_filters( 'woocommerce_pay_order_product_has_enough_stock', ( $product->get_stock_quantity() >= ( $held_stock + $required_stock ) ), $product, $order ) ) {

View File

@ -318,8 +318,13 @@ function wc_cart_totals_order_total_html() {
$taxable_address = WC()->customer->get_taxable_address();
/* translators: %s: country name */
$estimated_text = WC()->customer->is_customer_outside_base() && ! WC()->customer->has_calculated_shipping() ? sprintf( ' ' . __( 'estimated for %s', 'woocommerce' ), WC()->countries->estimated_for_prefix( $taxable_address[0] ) . WC()->countries->countries[ $taxable_address[0] ] ) : '';
/* translators: %s: tax information */
$value .= '<small class="includes_tax">' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) . $estimated_text ) . '</small>';
$value .= '<small class="includes_tax">('
/* translators: includes tax information */
. esc_html__( 'includes', 'woocommerce' )
. ' '
. wp_kses_post( implode( ', ', $tax_string_array ) )
. esc_html( $estimated_text )
. ')</small>';
}
}

View File

@ -150,6 +150,86 @@ function wc_update_order( $args ) {
return wc_create_order( $args );
}
/**
* Given a path, this will convert any of the subpaths into their corresponding tokens.
*
* @since 4.3.0
* @param string $path The absolute path to tokenize.
* @param array $path_tokens An array keyed with the token, containing paths that should be replaced.
* @return string The tokenized path.
*/
function wc_tokenize_path( $path, $path_tokens ) {
// Order most to least specific so that the token can encompass as much of the path as possible.
uasort(
$path_tokens,
function ( $a, $b ) {
$a = strlen( $a );
$b = strlen( $b );
if ( $a > $b ) {
return -1;
}
if ( $b > $a ) {
return 1;
}
return 0;
}
);
foreach ( $path_tokens as $token => $token_path ) {
if ( 0 !== strpos( $path, $token_path ) ) {
continue;
}
$path = str_replace( $token_path, '{{' . $token . '}}', $path );
}
return $path;
}
/**
* Given a tokenized path, this will expand the tokens to their full path.
*
* @since 4.3.0
* @param string $path The absolute path to expand.
* @param array $path_tokens An array keyed with the token, containing paths that should be expanded.
* @return string The absolute path.
*/
function wc_untokenize_path( $path, $path_tokens ) {
foreach ( $path_tokens as $token => $token_path ) {
$path = str_replace( '{{' . $token . '}}', $token_path, $path );
}
return $path;
}
/**
* Fetches an array containing all of the configurable path constants to be used in tokenization.
*
* @return array The key is the define and the path is the constant.
*/
function wc_get_path_define_tokens() {
$defines = array(
'ABSPATH',
'WP_CONTENT_DIR',
'WP_PLUGIN_DIR',
'WPMU_PLUGIN_DIR',
'PLUGINDIR',
'WP_THEME_DIR',
);
$path_tokens = array();
foreach ( $defines as $define ) {
if ( defined( $define ) ) {
$path_tokens[ $define ] = constant( $define );
}
}
return apply_filters( 'woocommerce_get_path_define_tokens', $path_tokens );
}
/**
* Get template part (for templates like the shop-loop).
*
@ -187,7 +267,13 @@ function wc_get_template_part( $slug, $name = '' ) {
);
}
wp_cache_set( $cache_key, $template, 'woocommerce' );
// Don't cache the absolute path so that it can be shared between web servers with different paths.
$cache_path = wc_tokenize_path( $template, wc_get_path_define_tokens() );
wc_set_template_cache( $cache_key, $cache_path );
} else {
// Make sure that the absolute path to the template is resolved.
$template = wc_untokenize_path( $template, wc_get_path_define_tokens() );
}
// Allow 3rd party plugins to filter template file from their plugin.
@ -212,7 +298,14 @@ function wc_get_template( $template_name, $args = array(), $template_path = '',
if ( ! $template ) {
$template = wc_locate_template( $template_name, $template_path, $default_path );
wp_cache_set( $cache_key, $template, 'woocommerce' );
// Don't cache the absolute path so that it can be shared between web servers with different paths.
$cache_path = wc_tokenize_path( $template, wc_get_path_define_tokens() );
wc_set_template_cache( $cache_key, $cache_path );
} else {
// Make sure that the absolute path to the template is resolved.
$template = wc_untokenize_path( $template, wc_get_path_define_tokens() );
}
// Allow 3rd party plugin filter template file from their plugin.
@ -310,6 +403,42 @@ function wc_locate_template( $template_name, $template_path = '', $default_path
return apply_filters( 'woocommerce_locate_template', $template, $template_name, $template_path );
}
/**
* Add a template to the template cache.
*
* @since 4.3.0
* @param string $cache_key Object cache key.
* @param string $template Located template.
*/
function wc_set_template_cache( $cache_key, $template ) {
wp_cache_set( $cache_key, $template, 'woocommerce' );
$cached_templates = wp_cache_get( 'cached_templates', 'woocommerce' );
if ( is_array( $cached_templates ) ) {
$cached_templates[] = $cache_key;
} else {
$cached_templates = array( $cache_key );
}
wp_cache_set( 'cached_templates', $cached_templates, 'woocommerce' );
}
/**
* Clear the template cache.
*
* @since 4.3.0
*/
function wc_clear_template_cache() {
$cached_templates = wp_cache_get( 'cached_templates', 'woocommerce' );
if ( is_array( $cached_templates ) ) {
foreach ( $cached_templates as $cache_key ) {
wp_cache_delete( $cache_key, 'woocommerce' );
}
wp_cache_delete( 'cached_templates', 'woocommerce' );
}
}
/**
* Get Base Currency Code.
*

View File

@ -299,33 +299,80 @@ function wc_increase_stock_levels( $order_id ) {
* @param integer $exclude_order_id Order ID to exclude.
* @return int
*/
function wc_get_held_stock_quantity( $product, $exclude_order_id = 0 ) {
global $wpdb;
function wc_get_held_stock_quantity( WC_Product $product, $exclude_order_id = 0 ) {
/**
* Filter: woocommerce_hold_stock_for_checkout
* Allows enable/disable hold stock functionality on checkout.
*
* @since 4.3.0
* @param bool $enabled Default to true if managing stock globally.
*/
if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) {
return 0;
}
return $wpdb->get_var(
$wpdb->prepare(
"
SELECT SUM( order_item_meta.meta_value ) AS held_qty
FROM {$wpdb->posts} AS posts
LEFT JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id
LEFT JOIN {$wpdb->prefix}woocommerce_order_items as order_items ON posts.ID = order_items.order_id
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta2 ON order_items.order_item_id = order_item_meta2.order_item_id
WHERE order_item_meta.meta_key = '_qty'
AND order_item_meta2.meta_key = %s
AND order_item_meta2.meta_value = %d
AND postmeta.meta_key = '_created_via'
AND postmeta.meta_value = 'checkout'
AND posts.post_type IN ( '" . implode( "','", wc_get_order_types() ) . "' )
AND posts.post_status = 'wc-pending'
AND posts.ID != %d;",
'product_variation' === get_post_type( $product->get_stock_managed_by_id() ) ? '_variation_id' : '_product_id',
$product->get_stock_managed_by_id(),
$exclude_order_id
)
); // WPCS: unprepared SQL ok.
return ( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->get_reserved_stock( $product, $exclude_order_id );
}
/**
* Hold stock for an order.
*
* @throws ReserveStockException If reserve stock fails.
*
* @since 4.1.0
* @param \WC_Order|int $order Order ID or instance.
*/
function wc_reserve_stock_for_order( $order ) {
/**
* Filter: woocommerce_hold_stock_for_checkout
* Allows enable/disable hold stock functionality on checkout.
*
* @since @since 4.1.0
* @param bool $enabled Default to true if managing stock globally.
*/
if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) {
return;
}
$order = $order instanceof WC_Order ? $order : wc_get_order( $order );
if ( $order ) {
( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->reserve_stock_for_order( $order );
}
}
add_action( 'woocommerce_checkout_order_created', 'wc_reserve_stock_for_order' );
/**
* Release held stock for an order.
*
* @since 4.3.0
* @param \WC_Order|int $order Order ID or instance.
*/
function wc_release_stock_for_order( $order ) {
/**
* Filter: woocommerce_hold_stock_for_checkout
* Allows enable/disable hold stock functionality on checkout.
*
* @since 4.3.0
* @param bool $enabled Default to true if managing stock globally.
*/
if ( ! apply_filters( 'woocommerce_hold_stock_for_checkout', wc_string_to_bool( get_option( 'woocommerce_manage_stock', 'yes' ) ) ) ) {
return;
}
$order = $order instanceof WC_Order ? $order : wc_get_order( $order );
if ( $order ) {
( new \Automattic\WooCommerce\Checkout\Helpers\ReserveStock() )->release_stock_for_order( $order );
}
}
add_action( 'woocommerce_checkout_order_exception', 'wc_release_stock_for_order' );
add_action( 'woocommerce_payment_complete', 'wc_release_stock_for_order', 11 );
add_action( 'woocommerce_order_status_cancelled', 'wc_release_stock_for_order', 11 );
add_action( 'woocommerce_order_status_completed', 'wc_release_stock_for_order', 11 );
add_action( 'woocommerce_order_status_processing', 'wc_release_stock_for_order', 11 );
add_action( 'woocommerce_order_status_on-hold', 'wc_release_stock_for_order', 11 );
/**
* Return low stock amount to determine if notification needs to be sent
*

View File

@ -2097,7 +2097,7 @@ if ( ! function_exists( 'woocommerce_widget_shopping_cart_subtotal' ) ) {
* @since 3.7.0
*/
function woocommerce_widget_shopping_cart_subtotal() {
echo '<strong>' . esc_html__( 'Subtotal', 'woocommerce' ) . ':</strong> ' . WC()->cart->get_cart_subtotal(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<strong>' . esc_html__( 'Subtotal:', 'woocommerce' ) . '</strong> ' . WC()->cart->get_cart_subtotal(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
@ -2706,7 +2706,7 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
$field = '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="country_to_state country_select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . '><option value="">' . esc_html__( 'Select a country / region&hellip;', 'woocommerce' ) . '</option>';
foreach ( $countries as $ckey => $cvalue ) {
$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . $cvalue . '</option>';
$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . esc_html( $cvalue ) . '</option>';
}
$field .= '</select>';
@ -2733,7 +2733,7 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
<option value="">' . esc_html__( 'Select an option&hellip;', 'woocommerce' ) . '</option>';
foreach ( $states as $ckey => $cvalue ) {
$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . $cvalue . '</option>';
$field .= '<option value="' . esc_attr( $ckey ) . '" ' . selected( $value, $ckey, false ) . '>' . esc_html( $cvalue ) . '</option>';
}
$field .= '</select>';
@ -2782,7 +2782,7 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
}
$custom_attributes[] = 'data-allow_clear="true"';
}
$options .= '<option value="' . esc_attr( $option_key ) . '" ' . selected( $value, $option_key, false ) . '>' . esc_attr( $option_text ) . '</option>';
$options .= '<option value="' . esc_attr( $option_key ) . '" ' . selected( $value, $option_key, false ) . '>' . esc_html( $option_text ) . '</option>';
}
$field .= '<select name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" class="select ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" ' . implode( ' ', $custom_attributes ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ) . '">
@ -2797,7 +2797,7 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
if ( ! empty( $args['options'] ) ) {
foreach ( $args['options'] as $option_key => $option_text ) {
$field .= '<input type="radio" class="input-radio ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" value="' . esc_attr( $option_key ) . '" name="' . esc_attr( $key ) . '" ' . implode( ' ', $custom_attributes ) . ' id="' . esc_attr( $args['id'] ) . '_' . esc_attr( $option_key ) . '"' . checked( $value, $option_key, false ) . ' />';
$field .= '<label for="' . esc_attr( $args['id'] ) . '_' . esc_attr( $option_key ) . '" class="radio ' . implode( ' ', $args['label_class'] ) . '">' . $option_text . '</label>';
$field .= '<label for="' . esc_attr( $args['id'] ) . '_' . esc_attr( $option_key ) . '" class="radio ' . implode( ' ', $args['label_class'] ) . '">' . esc_html( $option_text ) . '</label>';
}
}

10
lerna.json Normal file
View File

@ -0,0 +1,10 @@
{
"command": {
"publish": {
"message": "chore(release): publish"
}
},
"ignoreChanges": [ "**/CHANGELOG.md", "**/test/**" ],
"packages": [ "tests/e2e/*" ],
"version": "independent"
}

8114
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
"scripts": {
"build": "grunt && npm run makepot",
"build-watch": "grunt watch",
"build:packages": "node ./tests/e2e/bin/build.js",
"build:zip": "./bin/build-zip.sh",
"lint:js": "eslint assets/js --ext=js",
"docker:up": "npm explore @woocommerce/e2e-environment -- npm run docker:up",
@ -20,6 +21,7 @@
"test:e2e-dev": "npm explore @woocommerce/e2e-environment -- npm run test:e2e-dev",
"makepot": "composer run-script makepot",
"packages:fix:textdomain": "node ./bin/package-update-textdomain.js",
"publish-packages": "npm run build:packages && lerna publish from-package",
"git:update-hooks": "rm -r .git/hooks && mkdir -p .git/hooks && node ./node_modules/husky/husky.js install"
},
"devDependencies": {
@ -32,8 +34,9 @@
"@typescript-eslint/eslint-plugin": "3.1.0",
"@typescript-eslint/parser": "3.1.0",
"@woocommerce/e2e-environment": "file:tests/e2e/env",
"@wordpress/e2e-test-utils": "4.3.1",
"@wordpress/eslint-plugin": "6.1.0",
"@wordpress/babel-plugin-import-jsx-pragma": "1.1.3",
"@wordpress/babel-preset-default": "3.0.2",
"@wordpress/e2e-test-utils": "4.6.0",
"autoprefixer": "9.7.5",
"babel-eslint": "10.1.0",
"chai": "4.2.0",
@ -41,6 +44,7 @@
"commander": "4.1.1",
"config": "3.3.1",
"cross-env": "6.0.3",
"deasync": "0.1.19",
"eslint": "6.8.0",
"eslint-config-wpcalypso": "5.0.0",
"eslint-plugin-jest": "23.8.2",
@ -62,8 +66,9 @@
"istanbul": "1.0.0-alpha.2",
"jest": "25.1.0",
"jest-puppeteer": "4.4.0",
"lerna": "3.20.2",
"lint-staged": "9.5.0",
"mocha": "7.0.1",
"mocha": "7.2.0",
"node-sass": "4.13.0",
"prettier": "npm:wp-prettier@^2.0.5",
"puppeteer": "2.0.0",
@ -81,8 +86,9 @@
},
"husky": {
"hooks": {
"post-merge": "./bin/post-merge.sh",
"pre-commit": "lint-staged",
"post-merge": "./bin/post-merge.sh"
"pre-push": "./bin/pre-push.sh"
}
},
"lint-staged": {

View File

@ -0,0 +1,210 @@
<?php
/**
* Handle product stock reservation during checkout.
*
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\Checkout\Helpers;
defined( 'ABSPATH' ) || exit;
/**
* Stock Reservation class.
*/
final class ReserveStock {
/**
* Is stock reservation enabled?
*
* @var boolean
*/
private $enabled = true;
/**
* Constructor
*/
public function __construct() {
// Table needed for this feature are added in 4.3.
$this->enabled = get_option( 'woocommerce_schema_version', 0 ) >= 430;
}
/**
* Is stock reservation enabled?
*
* @return boolean
*/
protected function is_enabled() {
return $this->enabled;
}
/**
* Query for any existing holds on stock for this item.
*
* @param \WC_Product $product Product to get reserved stock for.
* @param integer $exclude_order_id Optional order to exclude from the results.
*
* @return integer Amount of stock already reserved.
*/
public function get_reserved_stock( \WC_Product $product, $exclude_order_id = 0 ) {
global $wpdb;
if ( ! $this->is_enabled() ) {
return 0;
}
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
return (int) $wpdb->get_var( $this->get_query_for_reserved_stock( $product->get_stock_managed_by_id(), $exclude_order_id ) );
}
/**
* Put a temporary hold on stock for an order if enough is available.
*
* @throws ReserveStockException If stock cannot be reserved.
*
* @param \WC_Order $order Order object.
* @param int $minutes How long to reserve stock in minutes. Defaults to woocommerce_hold_stock_minutes.
*/
public function reserve_stock_for_order( \WC_Order $order, $minutes = 0 ) {
$minutes = $minutes ? $minutes : (int) get_option( 'woocommerce_hold_stock_minutes', 60 );
if ( ! $minutes || ! $this->is_enabled() ) {
return;
}
try {
$items = array_filter(
$order->get_items(),
function( $item ) {
return $item->is_type( 'line_item' ) && $item->get_product() instanceof \WC_Product && $item->get_quantity() > 0;
}
);
$rows = array();
foreach ( $items as $item ) {
$product = $item->get_product();
if ( ! $product->is_in_stock() ) {
throw new ReserveStockException(
'woocommerce_product_out_of_stock',
sprintf(
/* translators: %s: product name */
__( '&quot;%s&quot; is out of stock and cannot be purchased.', 'woocommerce' ),
$product->get_name()
),
403
);
}
// If stock management is off, no need to reserve any stock here.
if ( ! $product->managing_stock() || $product->backorders_allowed() ) {
continue;
}
$managed_by_id = $product->get_stock_managed_by_id();
$rows[ $managed_by_id ] = isset( $rows[ $managed_by_id ] ) ? $rows[ $managed_by_id ] + $item->get_quantity() : $item->get_quantity();
}
if ( ! empty( $rows ) ) {
foreach ( $rows as $product_id => $quantity ) {
$this->reserve_stock_for_product( $product_id, $quantity, $order, $minutes );
}
}
} catch ( ReserveStockException $e ) {
$this->release_stock_for_order( $order );
throw $e;
}
}
/**
* Release a temporary hold on stock for an order.
*
* @param \WC_Order $order Order object.
*/
public function release_stock_for_order( \WC_Order $order ) {
global $wpdb;
if ( ! $this->is_enabled() ) {
return;
}
$wpdb->delete(
$wpdb->wc_reserved_stock,
array(
'order_id' => $order->get_id(),
)
);
}
/**
* Reserve stock for a product by inserting rows into the DB.
*
* @throws ReserveStockException If a row cannot be inserted.
*
* @param int $product_id Product ID which is having stock reserved.
* @param int $stock_quantity Stock amount to reserve.
* @param \WC_Order $order Order object which contains the product.
* @param int $minutes How long to reserve stock in minutes.
*/
private function reserve_stock_for_product( $product_id, $stock_quantity, \WC_Order $order, $minutes ) {
global $wpdb;
$product_data_store = \WC_Data_Store::load( 'product' );
$query_for_stock = $product_data_store->get_query_for_stock( $product_id );
$query_for_reserved_stock = $this->get_query_for_reserved_stock( $product_id, $order->get_id() );
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
$result = $wpdb->query(
$wpdb->prepare(
"
INSERT INTO {$wpdb->wc_reserved_stock} ( `order_id`, `product_id`, `stock_quantity`, `timestamp`, `expires` )
SELECT %d, %d, %d, NOW(), ( NOW() + INTERVAL %d MINUTE ) FROM DUAL
WHERE ( $query_for_stock FOR UPDATE ) - ( $query_for_reserved_stock FOR UPDATE ) >= %d
ON DUPLICATE KEY UPDATE `expires` = VALUES( `expires` ), `stock_quantity` = VALUES( `stock_quantity` )
",
$order->get_id(),
$product_id,
$stock_quantity,
$minutes,
$stock_quantity
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
if ( ! $result ) {
$product = wc_get_product( $product_id );
throw new ReserveStockException(
'woocommerce_product_not_enough_stock',
sprintf(
/* translators: %s: product name */
__( 'Not enough units of %s are available in stock to fulfil this order.', 'woocommerce' ),
$product ? $product->get_name() : '#' . $product_id
),
403
);
}
}
/**
* Returns query statement for getting reserved stock of a product.
*
* @param int $product_id Product ID.
* @param integer $exclude_order_id Optional order to exclude from the results.
* @return string|void Query statement.
*/
private function get_query_for_reserved_stock( $product_id, $exclude_order_id = 0 ) {
global $wpdb;
return $wpdb->prepare(
"
SELECT COALESCE( SUM( stock_table.`stock_quantity` ), 0 ) FROM $wpdb->wc_reserved_stock stock_table
LEFT JOIN $wpdb->posts posts ON stock_table.`order_id` = posts.ID
WHERE posts.post_status IN ( 'wc-checkout-draft', 'wc-pending' )
AND stock_table.`expires` > NOW()
AND stock_table.`product_id` = %d
AND stock_table.`order_id` != %d
",
$product_id,
$exclude_order_id
);
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Exceptions for stock reservation.
*
* @package Automattic/WooCommerce
*/
namespace Automattic\WooCommerce\Checkout\Helpers;
defined( 'ABSPATH' ) || exit;
/**
* ReserveStockException class.
*/
class ReserveStockException extends \Exception {
/**
* Sanitized error code.
*
* @var string
*/
protected $error_code;
/**
* Error extra data.
*
* @var array
*/
protected $error_data;
/**
* Setup exception.
*
* @param string $code Machine-readable error code, e.g `woocommerce_invalid_product_id`.
* @param string $message User-friendly translated error message, e.g. 'Product ID is invalid'.
* @param int $http_status_code Proper HTTP status code to respond with, e.g. 400.
* @param array $data Extra error data.
*/
public function __construct( $code, $message, $http_status_code = 400, $data = array() ) {
$this->error_code = $code;
$this->error_data = $data;
parent::__construct( $message, $http_status_code );
}
/**
* Returns the error code.
*
* @return string
*/
public function getErrorCode() {
return $this->error_code;
}
/**
* Returns error data.
*
* @return array
*/
public function getErrorData() {
return $this->error_data;
}
}

View File

@ -50,6 +50,7 @@ defined( 'ABSPATH' ) || exit;
printf( esc_html__( 'Logged in as %s', 'woocommerce' ), esc_html( $user->display_name ) );
?>
<a href="<?php echo esc_url( $logout_url ); ?>" class="wc-auth-logout"><?php esc_html_e( 'Logout', 'woocommerce' ); ?></a>
</p>
</div>
<p class="wc-auth-actions">

163
tests/e2e/bin/build.js Executable file
View File

@ -0,0 +1,163 @@
/**
* script to build packages into `build/` directory.
*
* Example:
* node ./bin/packages/build.js
*/
/**
* External dependencies
*/
const fs = require( 'fs' );
const path = require( 'path' );
const glob = require( 'glob' );
const babel = require( '@babel/core' );
const chalk = require( 'chalk' );
const mkdirp = require( 'mkdirp' );
const deasync = require( 'deasync' );
/**
* Internal dependencies
*/
const getPackages = require( './get-packages' );
const getBabelConfig = require( './get-babel-config' );
/**
* Module Constants
*/
const PACKAGES_DIR = path.resolve( __dirname, '../' );
const SRC_DIR = 'src';
const BUILD_DIR = {
main: 'build',
module: 'build-module',
};
const DONE = chalk.reset.inverse.bold.green( ' DONE ' );
/**
* Get the package name for a specified file
*
* @param {string} file File name
* @return {string} Package name
*/
function getPackageName( file ) {
return path.relative( PACKAGES_DIR, file ).split( path.sep )[ 0 ];
}
const isJsFile = ( filepath ) => {
return /.\.js$/.test( filepath );
};
/**
* Get Build Path for a specified file
*
* @param {string} file File to build
* @param {string} buildFolder Output folder
* @return {string} Build path
*/
function getBuildPath( file, buildFolder ) {
const pkgName = getPackageName( file );
const pkgSrcPath = path.resolve( PACKAGES_DIR, pkgName, SRC_DIR );
const pkgBuildPath = path.resolve( PACKAGES_DIR, pkgName, buildFolder );
const relativeToSrcPath = path.relative( pkgSrcPath, file );
return path.resolve( pkgBuildPath, relativeToSrcPath );
}
/**
* Given a list of scss and js filepaths, divide them into sets them and rebuild.
*
* @param {Array} files list of files to rebuild
*/
function buildFiles( files ) {
// Reduce files into a unique sets of javaScript files and scss packages.
const buildPaths = files.reduce(
( accumulator, filePath ) => {
if ( isJsFile( filePath ) ) {
accumulator.jsFiles.add( filePath );
}
return accumulator;
},
{ jsFiles: new Set() }
);
buildPaths.jsFiles.forEach( buildJsFile );
}
/**
* Build a javaScript file for the required environments (node and ES5)
*
* @param {string} file File path to build
* @param {boolean} silent Show logs
*/
function buildJsFile( file, silent ) {
buildJsFileFor( file, silent, 'main' );
buildJsFileFor( file, silent, 'module' );
}
/**
* Build a file for a specific environment
*
* @param {string} file File path to build
* @param {boolean} silent Show logs
* @param {string} environment Dist environment (node or es5)
*/
function buildJsFileFor( file, silent, environment ) {
const buildDir = BUILD_DIR[ environment ];
const destPath = getBuildPath( file, buildDir );
const babelOptions = getBabelConfig( environment );
babelOptions.sourceMaps = true;
babelOptions.sourceFileName = file;
mkdirp.sync( path.dirname( destPath ) );
const transformed = babel.transformFileSync( file, babelOptions );
fs.writeFileSync( destPath + '.map', JSON.stringify( transformed.map ) );
fs.writeFileSync(
destPath,
transformed.code +
'\n//# sourceMappingURL=' +
path.basename( destPath ) +
'.map'
);
if ( ! silent ) {
process.stdout.write(
chalk.green( ' \u2022 ' ) +
path.relative( PACKAGES_DIR, file ) +
chalk.green( ' \u21D2 ' ) +
path.relative( PACKAGES_DIR, destPath ) +
'\n'
);
}
}
/**
* Build the provided package path
*
* @param {string} packagePath absolute package path
*/
function buildPackage( packagePath ) {
const srcDir = path.resolve( packagePath, SRC_DIR );
const jsFiles = glob.sync( `${ srcDir }/**/*.js`, {
ignore: [
`${ srcDir }/**/test/**/*.js`,
`${ srcDir }/**/__mocks__/**/*.js`,
],
nodir: true,
} );
process.stdout.write( `${ path.basename( packagePath ) }\n` );
// Build js files individually.
jsFiles.forEach( ( file ) => buildJsFile( file, true ) );
process.stdout.write( `${ DONE }\n` );
}
const files = process.argv.slice( 2 );
if ( files.length ) {
buildFiles( files );
} else {
process.stdout.write( chalk.inverse( '>> Building packages \n' ) );
getPackages().forEach( buildPackage );
process.stdout.write( '\n' );
}

View File

@ -0,0 +1,71 @@
/**
* External dependencies
*/
const { get, map } = require( 'lodash' );
const babel = require( '@babel/core' );
/**
* WordPress dependencies
*/
const { options: babelDefaultConfig } = babel.loadPartialConfig( {
configFile: '@wordpress/babel-preset-default',
} );
const plugins = babelDefaultConfig.plugins;
if ( ! process.env.SKIP_JSX_PRAGMA_TRANSFORM ) {
plugins.push( [ '@wordpress/babel-plugin-import-jsx-pragma', {
scopeVariable: 'createElement',
source: '@wordpress/element',
isDefault: false,
} ] );
}
const overrideOptions = ( target, targetName, options ) => {
if ( get( target, [ 'file', 'request' ] ) === targetName ) {
return [ targetName, Object.assign(
{},
target.options,
options
) ];
}
return target;
};
const babelConfigs = {
main: Object.assign(
{},
babelDefaultConfig,
{
plugins,
presets: map(
babelDefaultConfig.presets,
( preset ) => overrideOptions( preset, '@babel/preset-env', {
modules: 'commonjs',
} )
),
}
),
module: Object.assign(
{},
babelDefaultConfig,
{
plugins: map(
plugins,
( plugin ) => overrideOptions( plugin, '@babel/plugin-transform-runtime', {
useESModules: true,
} )
),
presets: map(
babelDefaultConfig.presets,
( preset ) => overrideOptions( preset, '@babel/preset-env', {
modules: false,
} )
),
}
),
};
function getBabelConfig( environment ) {
return babelConfigs[ environment ];
}
module.exports = getBabelConfig;

View File

@ -0,0 +1,83 @@
/**
* External dependencies
*/
const fs = require( 'fs' );
const path = require( 'path' );
const { overEvery, compact, includes, negate } = require( 'lodash' );
/**
* Absolute path to packages directory.
*
* @type {string}
*/
const PACKAGES_DIR = path.resolve( __dirname, '../' );
const {
/**
* Comma-separated string of packages to include in build.
*
* @type {string}
*/
INCLUDE_PACKAGES,
/**
* Comma-separated string of packages to exclude from build.
*
* @type {string}
*/
EXCLUDE_PACKAGES,
} = process.env;
/**
* Given a comma-separated string, returns a filter function which returns true
* if the item is contained within as a comma-separated entry.
*
* @param {Function} filterFn Filter function to call with item to test.
* @param {string} list Comma-separated list of items.
*
* @return {Function} Filter function.
*/
const createCommaSeparatedFilter = ( filterFn, list ) => {
const listItems = list.split( ',' );
return ( item ) => filterFn( listItems, item );
};
/**
* Returns true if the given base file name for a file within the packages
* directory is itself a directory.
*
* @param {string} file Packages directory file.
*
* @return {boolean} Whether file is a directory.
*/
function isDirectory( file ) {
return fs.lstatSync( path.resolve( PACKAGES_DIR, file ) ).isDirectory();
}
/**
* Filter predicate, returning true if the given base file name is to be
* included in the build.
*
* @param {string} pkg File base name to test.
*
* @return {boolean} Whether to include file in build.
*/
const filterPackages = overEvery( compact( [
isDirectory,
INCLUDE_PACKAGES && createCommaSeparatedFilter( includes, INCLUDE_PACKAGES ),
EXCLUDE_PACKAGES && createCommaSeparatedFilter( negate( includes ), EXCLUDE_PACKAGES ),
] ) );
/**
* Returns the absolute path of all WordPress packages
*
* @return {Array} Package paths
*/
function getPackages() {
return fs
.readdirSync( PACKAGES_DIR )
.filter( filterPackages )
.map( ( file ) => path.resolve( PACKAGES_DIR, file ) );
}
module.exports = getPackages;

View File

@ -5,4 +5,3 @@ echo "Initializing WooCommerce E2E"
wp plugin install woocommerce --activate
wp theme install twentynineteen --activate
wp user create customer customer@woocommercecoree2etestsuite.com --user_pass=password --role=customer --path=/var/www/html
wp post create --post_type=page --post_status=publish --post_title='Ready' --post_content='E2E-tests.'

View File

@ -136,9 +136,15 @@ echo "Initializing WooCommerce E2E"
wp plugin install woocommerce --activate
wp theme install twentynineteen --activate
wp user create customer customer@woocommercecoree2etestsuite.com --user_pass=password --role=customer --path=/var/www/html
wp post create --post_type=page --post_status=publish --post_title='Ready' --post_content='E2E-tests.'
```
### Additional Packages
There are a few packages you may find useful in writing tests scripts. You can see these in use in the `tests/e2e/utils` and `tests/e2e/specs` folders.
- `@wordpress/e2e-test-utils`
- `config`
### Travis CI
Add the following to the appropriate sections of your `.travis.yml` config file.

View File

@ -13,12 +13,13 @@ program
.command( 'up', 'Start and build the Docker container' )
.command( 'down', 'Stop the Docker container and remove volumes' )
.action( ( cmd, options ) => {
if ( 'up' === options[ 0 ] ) {
arg = options.args ? options.args[ 0 ] : options[ 0 ];
if ( 'up' === arg ) {
command = 'up';
dockerArgs.push( 'up', '--build', '-d' );
}
if ( 'down' === options[ 0 ] ) {
if ( 'down' === arg ) {
command = 'down';
dockerArgs.push( 'down', '-v' );
}

View File

@ -27,7 +27,7 @@
"country": "United States (US)",
"addressfirstline": "addr 1",
"addresssecondline": "addr 2",
"countryandstate": "United States (US) -- California",
"countryandstate": "United States (US) California",
"city": "San Francisco",
"state": "CA",
"postcode": "94107"

View File

@ -7,7 +7,7 @@ module.exports = {
moduleNameMapper: {
'@woocommerce/e2e-tests/(.*)':
'<rootDir>/node_modules/woocommerce/tests/e2e/$1',
'<rootDir>/tests/e2e/$1',
},
preset: 'jest-puppeteer',
@ -16,7 +16,7 @@ module.exports = {
// A list of paths to modules that run some code to configure or set up the testing framework
// before each test
setupFilesAfterEnv: [
'<rootDir>/config/jest.setup.js',
'<rootDir>/build/setup/jest.setup.js',
'expect-puppeteer',
],

View File

@ -1,4 +1,4 @@
version: '3.7'
version: '3.3'
services:

View File

@ -33,6 +33,7 @@ if $(wp core is-installed);
then
echo "Wordpress is already installed..."
else
WORDPRESS_INSTALLING=1
declare -p WORDPRESS_TITLE >/dev/null
declare -p WORDPRESS_LOGIN >/dev/null
declare -p WORDPRESS_PASSWORD >/dev/null
@ -61,5 +62,14 @@ if ! [[ ${CURRENT_DOMAIN} == ${URL} ]]; then
wp search-replace ${CURRENT_DOMAIN} ${URL}
fi
if [[ $WORDPRESS_INSTALLING ]];
then
wp post create \
--post_type=page \
--post_status=publish \
--post_title='Ready' \
--post_content='E2E-tests.'
fi
echo "Visit $(wp option get siteurl)"
touch /var/www/html/.initialized

View File

@ -1,6 +1,6 @@
{
"name": "@woocommerce/e2e-environment",
"version": "0.1.0",
"version": "0.1.5",
"description": "WooCommerce End to End Testing Environment Configuration.",
"author": "Automattic",
"license": "GPL-3.0-or-later",
@ -19,19 +19,22 @@
"url": "https://github.com/woocommerce/woocommerce/issues"
},
"main": "index.js",
"module": "build-module/index.js",
"dependencies": {
"@wordpress/e2e-test-utils": "^4.6.0",
"@wordpress/jest-preset-default": "^5.4.0",
"app-root-path": "^3.0.0",
"jest": "^25.1.0",
"jest-puppeteer": "^4.4.0",
"puppeteer": "^2.1.1"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.4",
"@babel/polyfill": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@wordpress/e2e-test-utils": "^4.3.0",
"@wordpress/eslint-plugin": "^4.0.0",
"@wordpress/jest-preset-default": "^5.4.0",
"app-root-path": "^3.0.0",
"jest": "^25.1.0",
"jest-puppeteer": "^4.4.0",
"ndb": "^1.1.5",
"puppeteer": "^2.1.1",
"woocommerce": "git+https://github.com/woocommerce/woocommerce.git"
},
"publishConfig": {

View File

@ -6,20 +6,17 @@
* Internal dependencies
*/
import { StoreOwnerFlow } from '../../utils/flows';
import { completeOldSetupWizard, completeOnboardingWizard } from '../../utils/components';
import { completeOnboardingWizard } from '../../utils/components';
import {
permalinkSettingsPageSaveChanges,
setCheckbox,
settingsPageSaveChanges,
verifyCheckboxIsSet,
verifyCheckboxIsUnset, verifyValueOfInputField
verifyValueOfInputField
} from '../../utils';
const config = require( 'config' );
describe( 'Store owner can login and make sure WooCommerce is activated', () => {
it( 'can login', async () => {
beforeAll( async () => {
await StoreOwnerFlow.login();
} );
@ -56,25 +53,16 @@ describe( 'Store owner can go through setup Task List', () => {
it( 'can setup shipping', async () => {
// Query for all tasks on the list
const taskListItems = await page.$$( '.woocommerce-list__item-title' );
expect( taskListItems ).toHaveLength( 5 );
expect( taskListItems ).toHaveLength( 6 );
await Promise.all( [
// Click on "Set up shipping" task to move to the next step
taskListItems[2].click(),
taskListItems[3].click(),
// Wait for shipping setup section to load
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
] );
// Query for store location fields
const storeLocationFields = await page.$$( '.components-text-control__input' );
expect( storeLocationFields ).toHaveLength( 4 );
// Wait for "Continue" button to become active
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
// Click on "Continue" button to move to the shipping cost section
await page.click( 'button.is-primary' );
// Wait for "Proceed" button to become active
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
await page.waitFor( 3000 );

View File

@ -121,11 +121,11 @@ const completeOnboardingWizard = async () => {
}
// Wait for "Continue" button to become active
await page.waitForSelector( 'button.woocommerce-profile-wizard__continue:not(:disabled)' );
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
await Promise.all( [
// Click on "Continue" button to move to the next step
page.click( 'button.woocommerce-profile-wizard__continue' ),
page.click( 'button.is-primary' ),
// Wait for "Tell us about your business" section to load
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
@ -186,9 +186,9 @@ const completeOnboardingWizard = async () => {
await page.waitForSelector( '.woocommerce-profile-wizard__header-title' );
// Wait for "No thanks" button to become active
await page.waitForSelector( 'button.is-default:not(:disabled)' );
await page.waitForSelector( 'button.is-secondary:not(:disabled)' );
// Click on "No thanks" button to move to the next step
await page.click( 'button.is-default' );
await page.click( 'button.is-secondary' );
// End of onboarding wizard

View File

@ -91,7 +91,6 @@ class WC_Tests_Notes_Run_Db_Update extends WC_Unit_Test_Case {
$note->set_title( 'WooCommerce database update required' );
$note->set_content( 'To keep things running smoothly, we have to update your database to the newest version.' );
$note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_UPDATE );
$note->set_icon( 'info' );
$note->set_name( WC_Notes_Run_Db_Update::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-core' );

View File

@ -2055,6 +2055,157 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$this->assertEquals( 70.86, WC()->cart->get_total( 'edit' ) );
}
/**
* Test that adding a variation with URL parameter increases the quantity appropriately
* as described in issue 24000.
*/
public function test_add_variation_by_url() {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
WC()->session->set( 'wc_notices', null );
$product = WC_Helper_Product::create_variation_product();
$variations = $product->get_available_variations();
$variation = array_pop( $variations );
// Add variation with add_to_cart_action.
$_REQUEST['add-to-cart'] = $variation['variation_id'];
WC_Form_Handler::add_to_cart_action( false );
$notices = WC()->session->get( 'wc_notices', array() );
// Reset filter / REQUEST variables.
unset( $_REQUEST['add-to-cart'] );
remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
// Check if the item is in the cart.
$this->assertCount( 1, WC()->cart->get_cart_contents() );
$this->assertEquals( 1, WC()->cart->get_cart_contents_count() );
// Check that there are no error notices.
$this->assertArrayNotHasKey( 'error', $notices );
// Add variation using parent id.
WC()->cart->add_to_cart(
$product->get_id(),
1,
$variation['variation_id'],
array(
'attribute_pa_size' => 'huge',
'attribute_pa_colour' => 'red',
'attribute_pa_number' => '2',
)
);
$notices = WC()->session->get( 'wc_notices', array() );
// Check that the second add to cart call increases the quantity of the existing cart-item.
$this->assertCount( 1, WC()->cart->get_cart_contents() );
$this->assertEquals( 2, WC()->cart->get_cart_contents_count() );
// Check that there are no error notices.
$this->assertArrayNotHasKey( 'error', $notices );
}
/**
* Test that adding a variation via URL parameter fails when specifying a value for the attribute
* that differs from a value belonging to that variant.
*/
public function test_add_variation_by_url_with_invalid_attribute() {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
WC()->session->set( 'wc_notices', null );
$product = WC_Helper_Product::create_variation_product();
$variations = $product->get_available_variations();
$variation = array_pop( $variations );
// Attempt adding variation with add_to_cart_action, specifying a different colour.
$_REQUEST['add-to-cart'] = $variation['variation_id'];
$_REQUEST['attribute_pa_colour'] = 'green';
WC_Form_Handler::add_to_cart_action( false );
$notices = WC()->session->get( 'wc_notices', array() );
// Reset filter / REQUEST variables.
unset( $_REQUEST['add-to-cart'] );
unset( $_REQUEST['attribute_pa_colour'] );
remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
// Check that the notices contain an error message about an invalid colour.
$this->assertArrayHasKey( 'error', $notices );
$this->assertCount( 1, $notices['error'] );
$this->assertEquals( 'Invalid value posted for colour', $notices['error'][0]['notice'] );
}
/**
* Test that adding a variation via URL parameter succeeds when some attributes belong to the
* variation and others are specificed via URL parameter.
*/
public function test_add_variation_by_url_with_valid_attribute() {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
WC()->session->set( 'wc_notices', null );
$product = WC_Helper_Product::create_variation_product();
$variations = $product->get_available_variations();
$variation = array_shift( $variations );
// Attempt adding variation with add_to_cart_action, specifying attributes not defined in the variation.
$_REQUEST['add-to-cart'] = $variation['variation_id'];
$_REQUEST['attribute_pa_colour'] = 'red';
$_REQUEST['attribute_pa_number'] = '1';
WC_Form_Handler::add_to_cart_action( false );
$notices = WC()->session->get( 'wc_notices', array() );
// Reset filter / REQUEST variables.
unset( $_REQUEST['add-to-cart'] );
unset( $_REQUEST['attribute_pa_colour'] );
unset( $_REQUEST['attribute_pa_number'] );
remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
// Check if the item is in the cart.
$this->assertCount( 1, WC()->cart->get_cart_contents() );
$this->assertEquals( 1, WC()->cart->get_cart_contents_count() );
// Check that there are no error notices.
$this->assertArrayNotHasKey( 'error', $notices );
}
/**
* Test that adding a varition via URL parameter fails when an 'any' attribute is missing.
*/
public function test_add_variation_by_url_fails_with_missing_any_attribute() {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
WC()->session->set( 'wc_notices', null );
$product = WC_Helper_Product::create_variation_product();
$variations = $product->get_available_variations();
$variation = array_shift( $variations );
// Attempt adding variation with add_to_cart_action, without specifying attribute_pa_colour.
$_REQUEST['add-to-cart'] = $variation['variation_id'];
$_REQUEST['attribute_pa_number'] = '0';
WC_Form_Handler::add_to_cart_action( false );
$notices = WC()->session->get( 'wc_notices', array() );
// Reset filter / REQUEST variables.
unset( $_REQUEST['add-to-cart'] );
unset( $_REQUEST['attribute_pa_number'] );
remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
// Verify that there is nothing in the cart.
$this->assertCount( 0, WC()->cart->get_cart_contents() );
$this->assertEquals( 0, WC()->cart->get_cart_contents_count() );
// Check that the notices contain an error message about an invalid colour.
$this->assertArrayHasKey( 'error', $notices );
$this->assertCount( 1, $notices['error'] );
$this->assertEquals( 'colour is a required field', $notices['error'][0]['notice'] );
}
/**
* Helper function. Adds 1.5 taxable fees to cart.
*/

View File

@ -9,9 +9,8 @@
* Class WC_Checkout
*/
class WC_Tests_Checkout extends WC_Unit_Test_Case {
/**
* TearDown for tests.
* TearDown.
*/
public function tearDown() {
parent::tearDown();
@ -19,7 +18,7 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
}
/**
* Setup for tests.
* Setup.
*/
public function setUp() {
parent::setUp();
@ -32,24 +31,24 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
* @throws Exception When unable to create order.
*/
public function test_create_order_with_limited_coupon() {
$coupon_code = 'coupon4one';
$coupon_code = 'coupon4one';
$coupon_data_store = WC_Data_Store::load( 'coupon' );
$coupon = WC_Helper_Coupon::create_coupon(
$coupon = WC_Helper_Coupon::create_coupon(
$coupon_code,
array( 'usage_limit' => 1 )
);
$product = WC_Helper_Product::create_simple_product( true );
$product = WC_Helper_Product::create_simple_product( true );
WC()->cart->add_to_cart( $product->get_id(), 1 );
WC()->cart->add_discount( $coupon->get_code() );
$checkout = WC_Checkout::instance();
$order_id = $checkout->create_order(
array(
'billing_email' => 'a@b.com',
'billing_email' => 'a@b.com',
'payment_method' => 'dummy_payment_gateway',
)
);
$this->assertNotWPError( $order_id );
$order = new WC_Order( $order_id );
$order = new WC_Order( $order_id );
$coupon_held_key = $order->get_data_store()->get_coupon_held_keys( $order );
$this->assertEquals( count( $coupon_held_key ), 1 );
$this->assertEquals( array_keys( $coupon_held_key )[0], $coupon->get_id() );
@ -61,7 +60,7 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
WC()->cart->add_discount( $coupon->get_code() );
$order2_id = $checkout->create_order(
array(
'billing_email' => 'a@c.com',
'billing_email' => 'a@c.com',
'payment_method' => 'dummy_payment_gateway',
)
);
@ -75,8 +74,8 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
* @throws Exception When unable to create an order.
*/
public function test_create_order_with_multiple_limited_coupons() {
$coupon_code1 = 'coupon1';
$coupon_code2 = 'coupon2';
$coupon_code1 = 'coupon1';
$coupon_code2 = 'coupon2';
$coupon_data_store = WC_Data_Store::load( 'coupon' );
$coupon1 = WC_Helper_Coupon::create_coupon(
@ -91,10 +90,10 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product->get_id(), 1 );
WC()->cart->add_discount( $coupon_code1 );
WC()->cart->add_discount( $coupon_code2 );
$checkout = WC_Checkout::instance();
$checkout = WC_Checkout::instance();
$order_id1 = $checkout->create_order(
array(
'billing_email' => 'a@b.com',
'billing_email' => 'a@b.com',
'payment_method' => 'dummy_payment_gateway',
)
);
@ -110,7 +109,7 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
$order2_id = $checkout->create_order(
array(
'billing_email' => 'a@b.com',
'billing_email' => 'a@b.com',
'payment_method' => 'dummy_payment_gateway',
)
);
@ -188,4 +187,120 @@ class WC_Tests_Checkout extends WC_Unit_Test_Case {
return 0.01;
}
/**
* Helper method to create a managed product and a order for that product.
*
* @return array
* @throws Exception When unable to create an order .
*/
protected function create_order_for_managed_inventory_product() {
$product = WC_Helper_Product::create_simple_product();
$product->set_props( array( 'manage_stock' => true ) );
$product->set_stock_quantity( 10 );
$product->save();
WC()->cart->add_to_cart( $product->get_id(), 9 );
$this->assertEquals( true, WC()->cart->check_cart_items() );
$checkout = WC_Checkout::instance();
$order_id = $checkout->create_order(
array(
'payment_method' => 'cod',
'billing_email' => 'a@b.com',
)
);
// Assertions whether the order was created successfully.
$this->assertNotWPError( $order_id );
$order = wc_get_order( $order_id );
return array( $product, $order );
}
/**
* Test when order is out stock because it is held by an order in pending status.
*
* @throws Exception When unable to create order.
*/
public function test_create_order_when_out_of_stock() {
list( $product, $order ) = $this->create_order_for_managed_inventory_product();
$this->assertEquals( 9, $order->get_item_count() );
$this->assertEquals( 'pending', $order->get_status() );
$this->assertEquals( 9, wc_get_held_stock_quantity( $product ) );
WC()->cart->empty_cart();
WC()->cart->add_to_cart( $product->get_stock_managed_by_id(), 2 );
$this->assertEquals( false, WC()->cart->check_cart_items() );
}
/**
* Test if pending stock is cleared when order is cancelled.
*
* @throws Exception When unable to create order.
*/
public function test_pending_is_cleared_when_order_is_cancelled() {
list( $product, $order ) = $this->create_order_for_managed_inventory_product();
$this->assertEquals( 9, wc_get_held_stock_quantity( $product ) );
$order->set_status( 'cancelled' );
$order->save();
$this->assertEquals( 0, wc_get_held_stock_quantity( $product ) );
$this->assertEquals( 10, $product->get_stock_quantity() );
}
/**
* Test if pending stock is cleared when order is processing.
*
* @throws Exception When unable to create order.
*/
public function test_pending_is_cleared_when_order_processed() {
list( $product, $order ) = $this->create_order_for_managed_inventory_product();
$this->assertEquals( 9, wc_get_held_stock_quantity( $product ) );
$order->set_status( 'processing' );
$order->save();
$this->assertEquals( 0, wc_get_held_stock_quantity( $product ) );
}
/**
* Test creating order from managed stock for variable product.
*
* @throws Exception When unable to create an order.
*/
public function test_create_order_for_variation_product() {
$parent_product = WC_Helper_Product::create_variation_product();
$variation = $parent_product->get_available_variations()[0];
$variation = wc_get_product( $variation['variation_id'] );
$variation->set_manage_stock( true );
$variation->set_stock_quantity( 10 );
$variation->save();
WC()->cart->add_to_cart( $variation->get_id(), 9 );
$this->assertEquals( true, WC()->cart->check_cart_items() );
$checkout = WC_Checkout::instance();
$order_id = $checkout->create_order(
array(
'payment_method' => 'cod',
'billing_email' => 'a@b.com',
)
);
// Assertions whether the first order was created successfully.
$this->assertNotWPError( $order_id );
$order = wc_get_order( $order_id );
$this->assertEquals( 9, $order->get_item_count() );
$this->assertEquals( 'pending', $order->get_status() );
$this->assertEquals( 9, wc_get_held_stock_quantity( $variation ) );
WC()->cart->empty_cart();
WC()->cart->add_to_cart( $variation->get_stock_managed_by_id(), 2 );
$this->assertEquals( false, WC()->cart->check_cart_items() );
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* Template cache tests class.
*
* @package WooCommerce\Tests\Core
*/
/**
* WC_Template_Cache class.
*/
class WC_Template_Cache extends WC_Unit_Test_Case {
/**
* Test wc_get_template_part().
*/
public function test_wc_get_template_part() {
// Clear cache to start.
$this->clear_template_cache();
// Prevent template being loaded.
add_filter( 'wc_get_template_part', '__return_false' );
// Use content-* templates.
$templates = array();
foreach ( glob( dirname( WC_PLUGIN_FILE ) . '/templates/content*.php' ) as $template ) {
$name = preg_replace( '|content-(.*)\.php|', '$1', basename( $template ) );
$cache_key = sanitize_key(
implode(
'-',
array(
'template-part',
'content',
$name,
WC_VERSION,
)
)
);
$templates[ $cache_key ] = $template;
wc_get_template_part( 'content', $name );
}
remove_filter( 'wc_get_template_part', '__return_false' );
// Check cached template list.
$this->assertEquals( array_keys( $templates ), wp_cache_get( 'cached_templates', 'woocommerce' ) );
// Check individual templates.
foreach ( $templates as $cache_key => $template ) {
$this->assertEquals( $template, wp_cache_get( $cache_key, 'woocommerce' ) );
}
// Clear cache.
$this->clear_template_cache();
}
/**
* Clear the template cache.
*/
protected function clear_template_cache() {
$cached_templates = wp_cache_get( 'cached_templates', 'woocommerce' );
wc_clear_template_cache();
foreach ( (array) $cached_templates as $template ) {
$this->assertEmpty( wp_cache_get( $template, 'woocommerce' ) );
}
$this->assertEmpty( wp_cache_get( 'cached_templates', 'woocommerce' ) );
}
}

View File

@ -8,7 +8,7 @@
/**
* Class WC_Tests_Order.
*/
class WC_Tests_Order extends WC_Unit_Test_Case {
class WC_Tests_Orders extends WC_Unit_Test_Case {
/**
* Test for total when round at subtotal is enabled.

View File

@ -371,6 +371,7 @@ class WC_Tests_Order_Coupons extends WC_Unit_Test_Case {
public function test_inclusive_tax_rounding_on_totals() {
update_option( 'woocommerce_prices_include_tax', 'yes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
update_option( 'woocommerce_tax_round_at_subtotal', 'yes' );
WC_Tax::_insert_tax_rate(
array(
@ -440,12 +441,9 @@ class WC_Tests_Order_Coupons extends WC_Unit_Test_Case {
$order->apply_coupon( $coupon->get_code() );
$applied_coupons = $order->get_items( 'coupon' );
$applied_coupon = current( $applied_coupons );
$this->assertEquals( '16.95', $order->get_total() );
$this->assertEquals( '1.73', $order->get_total_tax() );
$this->assertEquals( '1.69', $order->get_discount_total() );
$this->assertEquals( '1.69', $applied_coupon->get_discount() );
}
}

View File

@ -283,7 +283,7 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
public function test_wc_get_log_file_path() {
$log_dir = trailingslashit( WC_LOG_DIR );
$hash_name = sanitize_file_name( wp_hash( 'unit-tests' ) );
$date_suffix = date( 'Y-m-d', time() );
$date_suffix = date( 'Y-m-d', time() ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$this->assertEquals( $log_dir . 'unit-tests-' . $date_suffix . '-' . $hash_name . '.log', wc_get_log_file_path( 'unit-tests' ) );
}
@ -569,6 +569,81 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertEmpty( wc_get_template_part( 'nothinghere' ) );
}
/**
* Tests the wc_tokenize_path function.
*/
public function test_wc_tokenize_path() {
$path = wc_tokenize_path( ABSPATH . 'test', array() );
$this->assertEquals( ABSPATH . 'test', $path );
$path = wc_tokenize_path(
ABSPATH . 'test',
array(
'ABSPATH' => ABSPATH,
)
);
$this->assertEquals( '{{ABSPATH}}test', $path );
$path = wc_tokenize_path(
ABSPATH . 'test',
array(
'WP_CONTENT_DIR' => WP_CONTENT_DIR,
)
);
$this->assertEquals( ABSPATH . 'test', $path );
$path = wc_tokenize_path(
WP_CONTENT_DIR . 'test',
array(
'ABSPATH' => ABSPATH,
'WP_CONTENT_DIR' => WP_CONTENT_DIR,
)
);
$this->assertEquals( '{{WP_CONTENT_DIR}}test', $path );
}
/**
* Tests the wc_untokenize_path function.
*/
public function test_wc_untokenize_path() {
$path = wc_untokenize_path( '{{ABSPATH}}test', array() );
$this->assertEquals( '{{ABSPATH}}test', $path );
$path = wc_untokenize_path(
'{{ABSPATH}}test',
array(
'ABSPATH' => ABSPATH,
)
);
$this->assertEquals( ABSPATH . 'test', $path );
$path = wc_untokenize_path(
'{{ABSPATH}}test',
array(
'WP_CONTENT_DIR' => WP_CONTENT_DIR,
)
);
$this->assertEquals( '{{ABSPATH}}test', $path );
$path = wc_untokenize_path(
'{{WP_CONTENT_DIR}}test',
array(
'WP_CONTENT_DIR' => WP_CONTENT_DIR,
'ABSPATH' => ABSPATH,
)
);
$this->assertEquals( WP_CONTENT_DIR . 'test', $path );
}
/**
* Tests the wc_get_path_define_tokens function.
*/
public function test_wc_get_path_define_tokens() {
$defines = wc_get_path_define_tokens();
$this->assertArrayHasKey( 'ABSPATH', $defines );
$this->assertEquals( ABSPATH, $defines['ABSPATH'] );
}
/**
* Test wc_get_template.
*
@ -606,6 +681,28 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertNotEmpty( $template );
}
/**
* This test ensures that the absolute path to template files is replaced with a token. We do this so
* that the path can be made relative to each installation, and the cache can be shared.
*/
public function test_wc_get_template_cleans_absolute_path() {
add_filter( 'woocommerce_locate_template', array( $this, 'force_template_path' ), 10, 2 );
ob_start();
try {
wc_get_template( 'global/wrapper-start.php' );
} catch ( \Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
// Since the file doesn't really exist this is going to throw an exception (which is fine for our test).
}
ob_end_clean();
remove_filter( 'woocommerce_locate_template', array( $this, 'force_template_path' ) );
$file_path = wp_cache_get( sanitize_key( 'template-global/wrapper-start.php---' . WC_VERSION ), 'woocommerce' );
$this->assertEquals( '{{ABSPATH}}global/wrapper-start.php', $file_path );
}
/**
* Test wc_get_image_size function.
*
@ -956,7 +1053,9 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertInstanceOf( 'WC_Customer', $this->wc->customer );
$this->assertInstanceOf( 'WC_Session', $this->wc->session );
$this->wc->cart = $this->wc->customer = $this->wc->session = null;
$this->wc->cart = null;
$this->wc->customer = null;
$this->wc->session = null;
$this->assertNull( $this->wc->cart );
$this->assertNull( $this->wc->customer );
$this->assertNull( $this->wc->session );
@ -967,4 +1066,16 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertInstanceOf( 'WC_Session', $this->wc->session );
}
/**
* Allows us to force the template path. Since the ABSPATH is to /tmp/wordpress in tests, we need to do this
* in order to keep the paths consistent for testing purposes.
*
* @param string $template The path to the template file.
* @param string $template_name The name of the template file.
* @return string The path to be used instead.
*/
public function force_template_path( $template, $template_name ) {
return ABSPATH . $template_name;
}
}

View File

@ -38,6 +38,7 @@ class WC_Tests_Install extends WC_Unit_Test_Case {
/**
* Test - install.
*/
/**
public function test_install() {
// clean existing install first.
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
@ -52,6 +53,8 @@ class WC_Tests_Install extends WC_Unit_Test_Case {
$this->assertEquals( WC()->version, get_option( 'woocommerce_version' ) );
}
*
**/
/**
* Test - create pages.

View File

@ -0,0 +1,92 @@
<?php
/**
* Class WCInstallTest file.
*
* @package WooCommerce|Tests|WCInstallTest.
*/
namespace Automattic\WooCommerce;
/**
* Class WC_Tests_WC_Helper.
*/
class WCInstallTest extends \WC_Unit_Test_Case {
/**
* Test if verify base table can detect missing table and adds/remove a notice.
*/
public function test_verify_base_tables_adds_and_remove_notice() {
global $wpdb;
// Remove drop filter because we do want to drop temp table if it exists.
// This filter was added to only allow dropping temporary tables which will then be rollbacked after the test.
remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
$original_table_name = "{$wpdb->prefix}wc_tax_rate_classes";
$changed_table_name = "{$wpdb->prefix}wc_tax_rate_classes_2";
$clear_query = 'DROP TABLE IF EXISTS %s;';
$rename_table_query = 'RENAME TABLE %s to %s;';
// Workaround to call a private function.
$schema = function () {
return static::get_schema();
};
// Rename a base table to simulate it as non-existing.
dbDelta( $schema->call( new \WC_Install() ) ); // Restore correct state.
$wpdb->query( sprintf( $clear_query, $changed_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( sprintf( $rename_table_query, $original_table_name, $changed_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$missing_tables = \WC_Install::verify_base_tables();
$wpdb->query( sprintf( $rename_table_query, $changed_table_name, $original_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
add_filter( 'query', array( $this, '_drop_temporary_tables' ) );
$this->assertContains( $original_table_name, $missing_tables );
$this->assertContains( 'base_tables_missing', \WC_Admin_Notices::get_notices() );
// Ideally, no missing table anymore because we have switched back table name.
$missing_tables = \WC_Install::verify_base_tables();
$this->assertNotContains( $original_table_name, $missing_tables );
$this->assertNotContains( 'base_tables_missing', \WC_Admin_Notices::get_notices() );
}
/**
* Test if verify base table can fix the table as well.
*/
public function test_verify_base_tables_fix_tables() {
global $wpdb;
// Remove drop filter because we do want to drop temp table if it exists.
// This filter was added to only allow dropping temporary tables which will then be rollbacked after the test.
remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
$original_table_name = "{$wpdb->prefix}wc_tax_rate_classes";
$changed_table_name = "{$wpdb->prefix}wc_tax_rate_classes_2";
$clear_query = 'DROP TABLE IF EXISTS %s;';
$rename_table_query = 'RENAME TABLE %s to %s;';
// Workaround to call a private function.
$schema = function () {
return static::get_schema();
};
// Rename a base table to simulate it as non-existing.
dbDelta( $schema->call( new \WC_Install() ) ); // Restore correct state.
$wpdb->query( sprintf( $clear_query, $changed_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( sprintf( $rename_table_query, $original_table_name, $changed_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$missing_tables = \WC_Install::verify_base_tables( true, true );
$wpdb->query( sprintf( $clear_query, $original_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( sprintf( $rename_table_query, $changed_table_name, $original_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
add_filter( 'query', array( $this, '_drop_temporary_tables' ) );
// Ideally, no missing table because verify base tables created the table as well.
$this->assertNotContains( $original_table_name, $missing_tables );
$this->assertNotContains( 'base_tables_missing', \WC_Admin_Notices::get_notices() );
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Class WC_Abstract_Order file.
*
* @package WooCommerce\Tests\Abstracts
*/
/**
* Class WC_Abstract_Order.
*/
class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
/**
* Test when rounding is different when doing per line and in subtotal.
*/
public function test_order_calculate_26582() {
update_option( 'woocommerce_prices_include_tax', 'yes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
$tax_rate = array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '15.0000',
'tax_rate_name' => 'tax',
'tax_rate_priority' => '1',
'tax_rate_order' => '1',
);
WC_Tax::_insert_tax_rate( $tax_rate );
$product1 = WC_Helper_Product::create_simple_product();
$product1->set_regular_price( 99.48 );
$product1->save();
$product2 = WC_Helper_Product::create_simple_product();
$product2->set_regular_price( 108.68 );
$product2->save();
$order = new WC_Order();
$order->add_product( $product1, 6 );
$order->add_product( $product2, 6 );
$order->save();
$this->order_calculate_rounding_line( $order );
$this->order_calculate_rounding_subtotal( $order );
}
/**
* Helper method to test rounding per line for `test_order_calculate_26582`.
*
* @param WC_Order $order Order object.
*/
private function order_calculate_rounding_line( $order ) {
update_option( 'woocommerce_tax_round_at_subtotal', 'no' );
$order->calculate_totals( true );
$this->assertEquals( 1086.06, $order->get_subtotal() );
$this->assertEquals( 162.90, $order->get_total_tax() );
$this->assertEquals( 1248.96, $order->get_total() );
}
/**
* Helper method to test rounding at subtotal for `test_order_calculate_26582`.
*
* @param WC_Order $order Order object.
*/
private function order_calculate_rounding_subtotal( $order ) {
update_option( 'woocommerce_tax_round_at_subtotal', 'yes' );
$order->calculate_totals( true );
$this->assertEquals( 1086.05, $order->get_subtotal() );
$this->assertEquals( 162.91, $order->get_total_tax() );
$this->assertEquals( 1248.96, $order->get_total() );
}
}