Merge branch 'master' into fix/27673

This commit is contained in:
vedanshujain 2020-12-21 15:29:55 +05:30
commit 39c200bf2d
31 changed files with 1012 additions and 184 deletions

View File

@ -51,4 +51,4 @@ Feature requests can be [submitted to our issue tracker](https://github.com/wooc
Feature request issues will remain closed until we see sufficient interest via comments and [👍 reactions](https://help.github.com/articles/about-discussions-in-issues-and-pull-requests/) from the community.
You can see a [list of current feature requests which require votes here](https://github.com/woocommerce/woocommerce/issues?q=label%3A%22votes+needed%22+label%3Aenhancement+sort%3Areactions-%2B1-desc+is%3Aclosed).
You can see a [list of current feature requests which require votes here](https://github.com/woocommerce/woocommerce/issues?q=is%3Aclosed+label%3A%22type%3A+enhancement%22+label%3A%22votes+needed%22+sort%3Areactions-%2B1-desc).

View File

@ -35,9 +35,9 @@ jobs:
- npm install
- composer install --no-dev
script:
- npm run build:assets
- npm run docker:up
- npm run test:e2e
- travis_retry npm run build:assets
- travis_retry npm run docker:up
- travis_retry npm run test:e2e
after_script:
- npm run docker:down
- name: "WP Nightly"

View File

@ -3169,6 +3169,12 @@ table.wc_input_table {
}
}
table.wc_tax_rates {
td.country {
position: relative;
}
}
table.wc_gateways,
table.wc_emails,
table.wc_shipping {

View File

@ -32,6 +32,7 @@
"phpunit/phpunit": "^8.5",
"squizlabs/php_codesniffer": "^3.5"
},
"default-branch": true,
"bin": [
"bin/mozart"
],
@ -52,6 +53,10 @@
}
],
"description": "Composes all dependencies as a package inside a WordPress plugin",
"support": {
"issues": "https://github.com/coenjacobs/mozart/issues",
"source": "https://github.com/coenjacobs/mozart/tree/master"
},
"funding": [
{
"url": "https://github.com/coenjacobs",
@ -142,6 +147,10 @@
"sftp",
"storage"
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/1.0.70"
},
"funding": [
{
"url": "https://offset.earth/frankdejonge",
@ -197,6 +206,10 @@
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/master"
},
"time": "2017-02-14T16:28:37+00:00"
},
{
@ -269,6 +282,9 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/console/tree/v4.4.17"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@ -327,6 +343,9 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v4.4.17"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@ -404,6 +423,9 @@
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@ -480,6 +502,9 @@
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@ -560,6 +585,9 @@
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.20.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@ -636,6 +664,9 @@
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v1.1.9"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@ -665,5 +696,5 @@
"platform-overrides": {
"php": "7.2"
},
"plugin-api-version": "1.1.0"
"plugin-api-version": "2.0.0"
}

View File

@ -71,6 +71,10 @@
"stylecheck",
"tests"
],
"support": {
"issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues",
"source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer"
},
"time": "2020-06-25T14:57:39+00:00"
},
{
@ -129,6 +133,10 @@
"phpcs",
"standards"
],
"support": {
"issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
"source": "https://github.com/PHPCompatibility/PHPCompatibility"
},
"time": "2019-12-27T09:44:58+00:00"
},
{
@ -181,6 +189,10 @@
"polyfill",
"standards"
],
"support": {
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
"source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
},
"time": "2019-11-04T15:17:54+00:00"
},
{
@ -231,6 +243,10 @@
"standards",
"wordpress"
],
"support": {
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
"source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
},
"time": "2019-08-28T14:22:28+00:00"
},
{
@ -282,6 +298,11 @@
"phpcs",
"standards"
],
"support": {
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
},
"time": "2020-10-23T02:01:07+00:00"
},
{
@ -322,6 +343,10 @@
"woocommerce",
"wordpress"
],
"support": {
"issues": "https://github.com/woocommerce/woocommerce-sniffs/issues",
"source": "https://github.com/woocommerce/woocommerce-sniffs/tree/master"
},
"time": "2020-08-06T18:23:45+00:00"
},
{
@ -368,6 +393,11 @@
"standards",
"wordpress"
],
"support": {
"issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
"source": "https://github.com/WordPress/WordPress-Coding-Standards",
"wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
},
"time": "2020-05-13T23:57:56+00:00"
}
],
@ -381,5 +411,5 @@
"platform-overrides": {
"php": "7.0"
},
"plugin-api-version": "1.1.0"
"plugin-api-version": "2.0.0"
}

View File

@ -59,6 +59,10 @@
"constructor",
"instantiate"
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/master"
},
"time": "2015-06-14T21:17:01+00:00"
},
{
@ -104,6 +108,10 @@
"object",
"object graph"
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.x"
},
"time": "2017-10-19T19:58:43+00:00"
},
{
@ -159,6 +167,10 @@
}
],
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
"source": "https://github.com/phar-io/manifest/tree/master"
},
"time": "2017-03-05T18:14:27+00:00"
},
{
@ -206,6 +218,10 @@
}
],
"description": "Library for handling version information and constraints",
"support": {
"issues": "https://github.com/phar-io/version/issues",
"source": "https://github.com/phar-io/version/tree/master"
},
"time": "2017-03-05T17:38:23+00:00"
},
{
@ -260,6 +276,10 @@
"reflection",
"static analysis"
],
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master"
},
"time": "2017-09-11T18:02:19+00:00"
},
{
@ -312,6 +332,10 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/4.x"
},
"time": "2019-12-28T18:55:12+00:00"
},
{
@ -357,6 +381,10 @@
"email": "me@mikevanriel.com"
}
],
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/master"
},
"time": "2017-12-30T13:23:38+00:00"
},
{
@ -420,6 +448,10 @@
"spy",
"stub"
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
"source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
},
"time": "2020-03-05T15:02:03+00:00"
},
{
@ -483,6 +515,10 @@
"testing",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/5.3"
},
"time": "2018-04-06T15:36:58+00:00"
},
{
@ -530,6 +566,11 @@
"filesystem",
"iterator"
],
"support": {
"irc": "irc://irc.freenode.net/phpunit",
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5"
},
"time": "2017-11-27T13:52:08+00:00"
},
{
@ -571,6 +612,10 @@
"keywords": [
"template"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
"source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
},
"time": "2015-06-21T13:50:34+00:00"
},
{
@ -620,6 +665,10 @@
"keywords": [
"timer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
"source": "https://github.com/sebastianbergmann/php-timer/tree/master"
},
"time": "2017-02-26T11:10:40+00:00"
},
{
@ -669,6 +718,10 @@
"keywords": [
"tokenizer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
"source": "https://github.com/sebastianbergmann/php-token-stream/tree/master"
},
"abandoned": true,
"time": "2017-11-27T05:48:46+00:00"
},
@ -754,6 +807,10 @@
"testing",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/6.5.14"
},
"time": "2019-02-01T05:22:47+00:00"
},
{
@ -813,6 +870,10 @@
"mock",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues",
"source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/5.0.10"
},
"abandoned": true,
"time": "2018-08-09T05:50:03+00:00"
},
@ -859,6 +920,10 @@
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
"source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
@ -929,6 +994,10 @@
"compare",
"equality"
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"source": "https://github.com/sebastianbergmann/comparator/tree/master"
},
"time": "2018-02-01T13:46:46+00:00"
},
{
@ -981,6 +1050,10 @@
"keywords": [
"diff"
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/master"
},
"time": "2017-08-03T08:09:46+00:00"
},
{
@ -1031,6 +1104,10 @@
"environment",
"hhvm"
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"source": "https://github.com/sebastianbergmann/environment/tree/master"
},
"time": "2017-07-01T08:51:00+00:00"
},
{
@ -1098,6 +1175,10 @@
"export",
"exporter"
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
@ -1155,6 +1236,10 @@
"keywords": [
"global state"
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0"
},
"time": "2017-04-27T15:39:26+00:00"
},
{
@ -1202,6 +1287,10 @@
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
"source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
@ -1253,6 +1342,10 @@
],
"description": "Allows reflection of object attributes, including inherited and non-public ones",
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
"source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
@ -1312,6 +1405,10 @@
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
@ -1360,6 +1457,10 @@
],
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
"issues": "https://github.com/sebastianbergmann/resource-operations/issues",
"source": "https://github.com/sebastianbergmann/resource-operations/tree/master"
},
"time": "2015-07-28T20:34:47+00:00"
},
{
@ -1403,6 +1504,10 @@
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"support": {
"issues": "https://github.com/sebastianbergmann/version/issues",
"source": "https://github.com/sebastianbergmann/version/tree/master"
},
"time": "2016-10-03T07:35:21+00:00"
},
{
@ -1465,6 +1570,9 @@
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
@ -1519,6 +1627,10 @@
}
],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/master"
},
"time": "2019-06-13T22:48:21+00:00"
},
{
@ -1568,6 +1680,10 @@
"check",
"validate"
],
"support": {
"issues": "https://github.com/webmozart/assert/issues",
"source": "https://github.com/webmozart/assert/tree/master"
},
"time": "2020-07-08T17:02:28+00:00"
}
],
@ -1581,5 +1697,5 @@
"platform-overrides": {
"php": "7.0"
},
"plugin-api-version": "1.1.0"
"plugin-api-version": "2.0.0"
}

View File

@ -67,6 +67,11 @@
"po",
"translation"
],
"support": {
"email": "oom@oscarotero.com",
"issues": "https://github.com/oscarotero/Gettext/issues",
"source": "https://github.com/php-gettext/Gettext/tree/v4.8.3"
},
"time": "2020-11-18T22:35:49+00:00"
},
{
@ -128,6 +133,10 @@
"translations",
"unicode"
],
"support": {
"issues": "https://github.com/php-gettext/Languages/issues",
"source": "https://github.com/php-gettext/Languages/tree/2.6.0"
},
"time": "2019-11-13T10:30:21+00:00"
},
{
@ -173,6 +182,10 @@
}
],
"description": "Peast is PHP library that generates AST for JavaScript code",
"support": {
"issues": "https://github.com/mck89/peast/issues",
"source": "https://github.com/mck89/peast/tree/v1.11.0"
},
"time": "2020-10-09T15:12:13+00:00"
},
{
@ -219,6 +232,10 @@
"mustache",
"templating"
],
"support": {
"issues": "https://github.com/bobthecow/mustache.php/issues",
"source": "https://github.com/bobthecow/mustache.php/tree/master"
},
"time": "2019-11-23T21:40:31+00:00"
},
{
@ -268,6 +285,10 @@
"iri",
"sockets"
],
"support": {
"issues": "https://github.com/rmccue/Requests/issues",
"source": "https://github.com/rmccue/Requests/tree/master"
},
"time": "2016-10-13T00:11:37+00:00"
},
{
@ -317,20 +338,23 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/3.3"
},
"time": "2017-06-01T21:01:25+00:00"
},
{
"name": "wp-cli/i18n-command",
"version": "v2.2.5",
"version": "v2.2.6",
"source": {
"type": "git",
"url": "https://github.com/wp-cli/i18n-command.git",
"reference": "b02ecdc9a57f9633740c254d19749118b7411f0f"
"reference": "a66da3f09f6a728832381012848c3074bf1635c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/b02ecdc9a57f9633740c254d19749118b7411f0f",
"reference": "b02ecdc9a57f9633740c254d19749118b7411f0f",
"url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/a66da3f09f6a728832381012848c3074bf1635c8",
"reference": "a66da3f09f6a728832381012848c3074bf1635c8",
"shasum": ""
},
"require": {
@ -374,7 +398,11 @@
],
"description": "Provides internationalization tools for WordPress projects.",
"homepage": "https://github.com/wp-cli/i18n-command",
"time": "2020-07-08T15:20:38+00:00"
"support": {
"issues": "https://github.com/wp-cli/i18n-command/issues",
"source": "https://github.com/wp-cli/i18n-command/tree/v2.2.6"
},
"time": "2020-12-07T19:28:27+00:00"
},
{
"name": "wp-cli/mustangostang-spyc",
@ -422,6 +450,9 @@
],
"description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)",
"homepage": "https://github.com/mustangostang/spyc/",
"support": {
"source": "https://github.com/wp-cli/spyc/tree/autoload"
},
"time": "2017-04-25T11:26:20+00:00"
},
{
@ -472,6 +503,10 @@
"cli",
"console"
],
"support": {
"issues": "https://github.com/wp-cli/php-cli-tools/issues",
"source": "https://github.com/wp-cli/php-cli-tools/tree/master"
},
"time": "2018-09-04T13:28:00+00:00"
},
{
@ -534,6 +569,11 @@
"cli",
"wordpress"
],
"support": {
"docs": "https://make.wordpress.org/cli/handbook/",
"issues": "https://github.com/wp-cli/wp-cli/issues",
"source": "https://github.com/wp-cli/wp-cli"
},
"time": "2020-02-18T08:15:37+00:00"
}
],
@ -547,5 +587,5 @@
"platform-overrides": {
"php": "7.0"
},
"plugin-api-version": "1.1.0"
"plugin-api-version": "2.0.0"
}

View File

@ -21,8 +21,8 @@
"pelago/emogrifier": "3.1.0",
"psr/container": "1.0.0",
"woocommerce/action-scheduler": "3.1.6",
"woocommerce/woocommerce-admin": "1.7.3",
"woocommerce/woocommerce-blocks": "3.8.1"
"woocommerce/woocommerce-admin": "1.8.1",
"woocommerce/woocommerce-blocks": "4.0.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4"

32
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": "77e54b85eedb5be94edbd73cf5436900",
"content-hash": "8b83d26cd9edae1a33b5dde164c44216",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@ -486,16 +486,16 @@
},
{
"name": "woocommerce/woocommerce-admin",
"version": "1.7.3",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "16de972f319e5e6fc8dbebf5024dd263234f39e0"
"reference": "7cbf3db2dba0fa80e66761a8955ca4cc86863877"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/16de972f319e5e6fc8dbebf5024dd263234f39e0",
"reference": "16de972f319e5e6fc8dbebf5024dd263234f39e0",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/7cbf3db2dba0fa80e66761a8955ca4cc86863877",
"reference": "7cbf3db2dba0fa80e66761a8955ca4cc86863877",
"shasum": ""
},
"require": {
@ -527,20 +527,24 @@
],
"description": "A modern, javascript-driven WooCommerce Admin experience.",
"homepage": "https://github.com/woocommerce/woocommerce-admin",
"time": "2020-12-03T21:12:01+00:00"
"support": {
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v1.8.1"
},
"time": "2020-12-15T01:30:12+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",
"version": "v3.8.1",
"version": "v4.0.0",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
"reference": "e5aef9eddd13c5511ba673eb70ed8cb3e80d828c"
"reference": "f5b2485254f36f0b85fd0f30c28e17bdf44a8d1e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/e5aef9eddd13c5511ba673eb70ed8cb3e80d828c",
"reference": "e5aef9eddd13c5511ba673eb70ed8cb3e80d828c",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/f5b2485254f36f0b85fd0f30c28e17bdf44a8d1e",
"reference": "f5b2485254f36f0b85fd0f30c28e17bdf44a8d1e",
"shasum": ""
},
"require": {
@ -574,7 +578,11 @@
"gutenberg",
"woocommerce"
],
"time": "2020-11-23T20:48:39+00:00"
"support": {
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v4.0.0"
},
"time": "2020-12-08T13:17:01+00:00"
}
],
"packages-dev": [
@ -637,5 +645,5 @@
"platform-overrides": {
"php": "7.0"
},
"plugin-api-version": "1.1.0"
"plugin-api-version": "2.0.0"
}

View File

@ -358,9 +358,6 @@ class WC_Countries {
if ( 'eu_vat' === $type ) {
$countries[] = 'MC';
$countries[] = 'IM';
// The UK is still part of the EU VAT zone.
$countries[] = 'GB';
}
return apply_filters( 'woocommerce_european_union_countries', $countries, $type );

View File

@ -251,16 +251,62 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
/**
* Get formatted item data.
*
* @since 3.0.0
* @param WC_Data $object WC_Data instance.
* @since 3.0.0
* @param WC_Order $order WC_Data instance.
*
* @return array
*/
protected function get_formatted_item_data( $object ) {
$data = $object->get_data();
protected function get_formatted_item_data( $order ) {
$extra_fields = array( 'meta_data', 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines', 'refunds' );
$format_decimal = array( 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'shipping_total', 'shipping_tax', 'cart_tax', 'total', 'total_tax' );
$format_date = array( 'date_created', 'date_modified', 'date_completed', 'date_paid' );
$format_line_items = array( 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines' );
// Only fetch fields that we need.
$request = func_get_arg( 1 );
if ( $request ) {
$fields = $this->get_fields_for_response( $request );
$extra_fields = array_intersect( $extra_fields, $fields );
$format_decimal = array_intersect( $format_decimal, $fields );
$format_date = array_intersect( $format_date, $fields );
$format_line_items = array_intersect( $format_line_items, $fields );
}
$data = $order->get_base_data();
// Add extra data as necessary.
foreach ( $extra_fields as $field ) {
switch ( $field ) {
case 'meta_data':
$data['meta_data'] = $order->get_meta_data();
break;
case 'line_items':
$data['line_items'] = $order->get_items( 'line_item');
break;
case 'tax_lines':
$data['tax_lines'] = $order->get_items( 'tax' );
break;
case 'shipping_lines':
$data['shipping_lines'] = $order->get_items( 'shipping' );
break;
case 'fee_lines':
$data['fee_lines'] = $order->get_items( 'fee' );
break;
case 'coupon_lines':
$data['coupon_lines'] = $order->get_items( 'coupon' );
break;
case 'refunds':
foreach ( $order->get_refunds() as $refund ) {
$data['refunds'][] = array(
'id' => $refund->get_id(),
'reason' => $refund->get_reason() ? $refund->get_reason() : '',
'total' => '-' . wc_format_decimal( $refund->get_amount(), $this->request['dp'] ),
);
}
break;
}
}
// Format decimal values.
foreach ( $format_decimal as $key ) {
$data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
@ -281,59 +327,53 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
$data[ $key ] = array_values( array_map( array( $this, 'get_order_item_data' ), $data[ $key ] ) );
}
// Refunds.
$data['refunds'] = array();
foreach ( $object->get_refunds() as $refund ) {
$data['refunds'][] = array(
'id' => $refund->get_id(),
'reason' => $refund->get_reason() ? $refund->get_reason() : '',
'total' => '-' . wc_format_decimal( $refund->get_amount(), $this->request['dp'] ),
);
}
return array(
'id' => $object->get_id(),
'parent_id' => $data['parent_id'],
'number' => $data['number'],
'order_key' => $data['order_key'],
'created_via' => $data['created_via'],
'version' => $data['version'],
'status' => $data['status'],
'currency' => $data['currency'],
'date_created' => $data['date_created'],
'date_created_gmt' => $data['date_created_gmt'],
'date_modified' => $data['date_modified'],
'date_modified_gmt' => $data['date_modified_gmt'],
'discount_total' => $data['discount_total'],
'discount_tax' => $data['discount_tax'],
'shipping_total' => $data['shipping_total'],
'shipping_tax' => $data['shipping_tax'],
'cart_tax' => $data['cart_tax'],
'total' => $data['total'],
'total_tax' => $data['total_tax'],
'prices_include_tax' => $data['prices_include_tax'],
'customer_id' => $data['customer_id'],
'customer_ip_address' => $data['customer_ip_address'],
'customer_user_agent' => $data['customer_user_agent'],
'customer_note' => $data['customer_note'],
'billing' => $data['billing'],
'shipping' => $data['shipping'],
'payment_method' => $data['payment_method'],
'payment_method_title' => $data['payment_method_title'],
'transaction_id' => $data['transaction_id'],
'date_paid' => $data['date_paid'],
'date_paid_gmt' => $data['date_paid_gmt'],
'date_completed' => $data['date_completed'],
'date_completed_gmt' => $data['date_completed_gmt'],
'cart_hash' => $data['cart_hash'],
'meta_data' => $data['meta_data'],
'line_items' => $data['line_items'],
'tax_lines' => $data['tax_lines'],
'shipping_lines' => $data['shipping_lines'],
'fee_lines' => $data['fee_lines'],
'coupon_lines' => $data['coupon_lines'],
'refunds' => $data['refunds'],
$allowed_fields = array(
'id',
'parent_id',
'number',
'order_key',
'created_via',
'version',
'status',
'currency',
'date_created',
'date_created_gmt',
'date_modified',
'date_modified_gmt',
'discount_total',
'discount_tax',
'shipping_total',
'shipping_tax',
'cart_tax',
'total',
'total_tax',
'prices_include_tax',
'customer_id',
'customer_ip_address',
'customer_user_agent',
'customer_note',
'billing',
'shipping',
'payment_method',
'payment_method_title',
'transaction_id',
'date_paid',
'date_paid_gmt',
'date_completed',
'date_completed_gmt',
'cart_hash',
'meta_data',
'line_items',
'tax_lines',
'shipping_lines',
'fee_lines',
'coupon_lines',
'refunds',
);
$data = array_intersect_key( $data, array_flip( $allowed_fields ) );
return $data;
}
/**
@ -347,7 +387,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
public function prepare_object_for_response( $object, $request ) {
$this->request = $request;
$this->request['dp'] = is_null( $this->request['dp'] ) ? wc_get_price_decimals() : absint( $this->request['dp'] );
$data = $this->get_formatted_item_data( $object );
$data = $this->get_formatted_item_data( $object, $request );
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );

View File

@ -159,7 +159,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
*/
public function prepare_object_for_response( $object, $request ) {
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->get_product_data( $object, $context );
$data = $this->get_product_data( $object, $context, $request );
// Add variations to variable products.
if ( $object->is_type( 'variable' ) && $object->has_child() ) {
@ -589,86 +589,254 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
return $attributes;
}
/**
* Fetch price HTML.
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
* @return string
*/
protected function api_get_price_html( $product, $context ) {
return $product->get_price_html();
}
/**
* Fetch related IDs.
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
* @return array
*/
protected function api_get_related_ids( $product, $context ) {
return array_map( 'absint', array_values( wc_get_related_products( $product->get_id() ) ) );
}
/**
* Fetch meta data.
* @param WC_Product $product Product object.
* @param string $context Context of request, can be `view` or `edit`.
*
* @return array
*/
protected function api_get_meta_data( $product, $context ) {
return $product->get_meta_data();
}
/**
* Get product data.
*
* @param WC_Product $product Product instance.
* @param string $context Request context.
* Options: 'view' and 'edit'.
* @param WC_Product $product Product instance.
* @param string $context Request context. Options: 'view' and 'edit'.
* @param WP_REST_Request $request Current request object. For backward compatibility, we pass this argument silently.
*
* @return array
*/
protected function get_product_data( $product, $context = 'view' ) {
$data = array(
'id' => $product->get_id(),
'name' => $product->get_name( $context ),
'slug' => $product->get_slug( $context ),
'permalink' => $product->get_permalink(),
'date_created' => wc_rest_prepare_date_response( $product->get_date_created( $context ), false ),
'date_created_gmt' => wc_rest_prepare_date_response( $product->get_date_created( $context ) ),
'date_modified' => wc_rest_prepare_date_response( $product->get_date_modified( $context ), false ),
'date_modified_gmt' => wc_rest_prepare_date_response( $product->get_date_modified( $context ) ),
'type' => $product->get_type(),
'status' => $product->get_status( $context ),
'featured' => $product->is_featured(),
'catalog_visibility' => $product->get_catalog_visibility( $context ),
'description' => 'view' === $context ? wpautop( do_shortcode( $product->get_description() ) ) : $product->get_description( $context ),
'short_description' => 'view' === $context ? apply_filters( 'woocommerce_short_description', $product->get_short_description() ) : $product->get_short_description( $context ),
'sku' => $product->get_sku( $context ),
'price' => $product->get_price( $context ),
'regular_price' => $product->get_regular_price( $context ),
'sale_price' => $product->get_sale_price( $context ) ? $product->get_sale_price( $context ) : '',
'date_on_sale_from' => wc_rest_prepare_date_response( $product->get_date_on_sale_from( $context ), false ),
'date_on_sale_from_gmt' => wc_rest_prepare_date_response( $product->get_date_on_sale_from( $context ) ),
'date_on_sale_to' => wc_rest_prepare_date_response( $product->get_date_on_sale_to( $context ), false ),
'date_on_sale_to_gmt' => wc_rest_prepare_date_response( $product->get_date_on_sale_to( $context ) ),
'price_html' => $product->get_price_html(),
'on_sale' => $product->is_on_sale( $context ),
'purchasable' => $product->is_purchasable(),
'total_sales' => $product->get_total_sales( $context ),
'virtual' => $product->is_virtual(),
'downloadable' => $product->is_downloadable(),
'downloads' => $this->get_downloads( $product ),
'download_limit' => $product->get_download_limit( $context ),
'download_expiry' => $product->get_download_expiry( $context ),
'external_url' => $product->is_type( 'external' ) ? $product->get_product_url( $context ) : '',
'button_text' => $product->is_type( 'external' ) ? $product->get_button_text( $context ) : '',
'tax_status' => $product->get_tax_status( $context ),
'tax_class' => $product->get_tax_class( $context ),
'manage_stock' => $product->managing_stock(),
'stock_quantity' => $product->get_stock_quantity( $context ),
'in_stock' => $product->is_in_stock(),
'backorders' => $product->get_backorders( $context ),
'backorders_allowed' => $product->backorders_allowed(),
'backordered' => $product->is_on_backorder(),
'sold_individually' => $product->is_sold_individually(),
'weight' => $product->get_weight( $context ),
'dimensions' => array(
'length' => $product->get_length( $context ),
'width' => $product->get_width( $context ),
'height' => $product->get_height( $context ),
),
'shipping_required' => $product->needs_shipping(),
'shipping_taxable' => $product->is_shipping_taxable(),
'shipping_class' => $product->get_shipping_class(),
'shipping_class_id' => $product->get_shipping_class_id( $context ),
'reviews_allowed' => $product->get_reviews_allowed( $context ),
'average_rating' => 'view' === $context ? wc_format_decimal( $product->get_average_rating(), 2 ) : $product->get_average_rating( $context ),
'rating_count' => $product->get_rating_count(),
'related_ids' => array_map( 'absint', array_values( wc_get_related_products( $product->get_id() ) ) ),
'upsell_ids' => array_map( 'absint', $product->get_upsell_ids( $context ) ),
'cross_sell_ids' => array_map( 'absint', $product->get_cross_sell_ids( $context ) ),
'parent_id' => $product->get_parent_id( $context ),
'purchase_note' => 'view' === $context ? wpautop( do_shortcode( wp_kses_post( $product->get_purchase_note() ) ) ) : $product->get_purchase_note( $context ),
'categories' => $this->get_taxonomy_terms( $product ),
'tags' => $this->get_taxonomy_terms( $product, 'tag' ),
'images' => $this->get_images( $product ),
'attributes' => $this->get_attributes( $product ),
'default_attributes' => $this->get_default_attributes( $product ),
'variations' => array(),
'grouped_products' => array(),
'menu_order' => $product->get_menu_order( $context ),
'meta_data' => $product->get_meta_data(),
$fields = array();
$request = func_get_arg( 2 );
if ( $request instanceof WP_REST_Request ) {
$fields = $this->get_fields_for_response( $request );
}
$base_data = array();
foreach ( $fields as $field ) {
switch ( $field ) {
case 'id':
$base_data['id'] = $product->get_id();
break;
case 'name':
$base_data['name'] = $product->get_name( $context );
break;
case 'slug':
$base_data['slug'] = $product->get_slug( $context );
break;
case 'permalink':
$base_data['permalink'] = $product->get_permalink();
break;
case 'date_created':
$base_data['date_created'] = wc_rest_prepare_date_response( $product->get_date_created( $context ), false );
break;
case 'date_created_gmt':
$base_data['date_created_gmt'] = wc_rest_prepare_date_response( $product->get_date_created( $context ) );
break;
case 'date_modified':
$base_data['date_modified'] = wc_rest_prepare_date_response( $product->get_date_modified( $context ), false );
break;
case 'date_modified_gmt':
$base_data['date_modified_gmt'] = wc_rest_prepare_date_response( $product->get_date_modified( $context ) );
break;
case 'type':
$base_data['type'] = $product->get_type();
break;
case 'status':
$base_data['status'] = $product->get_status( $context );
break;
case 'featured':
$base_data['featured'] = $product->is_featured();
break;
case 'catalog_visibility':
$base_data['catalog_visibility'] = $product->get_catalog_visibility( $context );
break;
case 'description':
$base_data['description'] = 'view' === $context ? wpautop( do_shortcode( $product->get_description() ) ) : $product->get_description( $context );
break;
case 'short_description':
$base_data['short_description'] = 'view' === $context ? apply_filters( 'woocommerce_short_description', $product->get_short_description() ) : $product->get_short_description( $context );
break;
case 'sku':
$base_data['sku'] = $product->get_sku( $context );
break;
case 'price':
$base_data['price'] = $product->get_price( $context );
break;
case 'regular_price':
$base_data['regular_price'] = $product->get_regular_price( $context );
break;
case 'sale_price':
$base_data['sale_price'] = $product->get_sale_price( $context ) ? $product->get_sale_price( $context ) : '';
break;
case 'date_on_sale_from':
$base_data['date_on_sale_from'] = wc_rest_prepare_date_response( $product->get_date_on_sale_from( $context ), false );
break;
case 'date_on_sale_from_gmt':
$base_data['date_on_sale_from_gmt'] = wc_rest_prepare_date_response( $product->get_date_on_sale_from( $context ) );
break;
case 'date_on_sale_to':
$base_data['date_on_sale_to'] = wc_rest_prepare_date_response( $product->get_date_on_sale_to( $context ), false );
break;
case 'date_on_sale_to_gmt':
$base_data['date_on_sale_to_gmt'] = wc_rest_prepare_date_response( $product->get_date_on_sale_to( $context ) );
break;
case 'on_sale':
$base_data['on_sale'] = $product->is_on_sale( $context );
break;
case 'purchasable':
$base_data['purchasable'] = $product->is_purchasable();
break;
case 'total_sales':
$base_data['total_sales'] = $product->get_total_sales( $context );
break;
case 'virtual':
$base_data['virtual'] = $product->is_virtual();
break;
case 'downloadable':
$base_data['downloadable'] = $product->is_downloadable();
break;
case 'downloads':
$base_data['downloads'] = $this->get_downloads( $product );
break;
case 'download_limit':
$base_data['download_limit'] = $product->get_download_limit( $context );
break;
case 'download_expiry':
$base_data['download_expiry'] = $product->get_download_expiry( $context );
break;
case 'external_url':
$base_data['external_url'] = $product->is_type( 'external' ) ? $product->get_product_url( $context ) : '';
break;
case 'button_text':
$base_data['button_text'] = $product->is_type( 'external' ) ? $product->get_button_text( $context ) : '';
break;
case 'tax_status':
$base_data['tax_status'] = $product->get_tax_status( $context );
break;
case 'tax_class':
$base_data['tax_class'] = $product->get_tax_class( $context );
break;
case 'manage_stock':
$base_data['manage_stock'] = $product->managing_stock();
break;
case 'stock_quantity':
$base_data['stock_quantity'] = $product->get_stock_quantity( $context );
break;
case 'in_stock':
$base_data['in_stock'] = $product->is_in_stock();
break;
case 'backorders':
$base_data['backorders'] = $product->get_backorders( $context );
break;
case 'backorders_allowed':
$base_data['backorders_allowed'] = $product->backorders_allowed();
break;
case 'backordered':
$base_data['backordered'] = $product->is_on_backorder();
break;
case 'sold_individually':
$base_data['sold_individually'] = $product->is_sold_individually();
break;
case 'weight':
$base_data['weight'] = $product->get_weight( $context );
break;
case 'dimensions':
$base_data['dimensions'] = array(
'length' => $product->get_length( $context ),
'width' => $product->get_width( $context ),
'height' => $product->get_height( $context ),
);
break;
case 'shipping_required':
$base_data['shipping_required'] = $product->needs_shipping();
break;
case 'shipping_taxable':
$base_data['shipping_taxable'] = $product->is_shipping_taxable();
break;
case 'shipping_class':
$base_data['shipping_class'] = $product->get_shipping_class();
break;
case 'shipping_class_id':
$base_data['shipping_class_id'] = $product->get_shipping_class_id( $context );
break;
case 'reviews_allowed':
$base_data['reviews_allowed'] = $product->get_reviews_allowed( $context );
break;
case 'average_rating':
$base_data['average_rating'] = 'view' === $context ? wc_format_decimal( $product->get_average_rating(), 2 ) : $product->get_average_rating( $context );
break;
case 'rating_count':
$base_data['rating_count'] = $product->get_rating_count();
break;
case 'upsell_ids':
$base_data['upsell_ids'] = array_map( 'absint', $product->get_upsell_ids( $context ) );
break;
case 'cross_sell_ids':
$base_data['cross_sell_ids'] = array_map( 'absint', $product->get_cross_sell_ids( $context ) );
break;
case 'parent_id':
$base_data['parent_id'] = $product->get_parent_id( $context );
break;
case 'purchase_note':
$base_data['purchase_note'] = 'view' === $context ? wpautop( do_shortcode( wp_kses_post( $product->get_purchase_note() ) ) ) : $product->get_purchase_note( $context );
break;
case 'categories':
$base_data['categories'] = $this->get_taxonomy_terms( $product );
break;
case 'tags':
$base_data['tags'] = $this->get_taxonomy_terms( $product, 'tag' );
break;
case 'images':
$base_data['images'] = $this->get_images( $product );
break;
case 'attributes':
$base_data['attributes'] = $this->get_attributes( $product );
break;
case 'default_attributes':
$base_data['default_attributes'] = $this->get_default_attributes( $product );
break;
case 'variations':
$base_data['variations'] = array();
break;
case 'grouped_products':
$base_data['grouped_products'] = array();
break;
case 'menu_order':
$base_data['menu_order'] = $product->get_menu_order( $context );
break;
}
}
$data = array_merge(
$base_data,
$this->fetch_fields_using_getters( $product, $context, $fields )
);
return $data;

View File

@ -43,6 +43,20 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
*/
protected $rest_base = '';
/**
* Used to cache computed return fields.
*
* @var null|array
*/
private $_fields = null;
/**
* Used to verify if cached fields are for correct request object.
*
* @var null|WP_REST_Request
*/
private $_request = null;
/**
* Add the schema from additional fields to an schema array.
*
@ -513,10 +527,19 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
* @return array Fields to be included in the response.
*/
public function get_fields_for_response( $request ) {
// From xdebug profiling, this method could take upto 25% of request time in index calls.
// Cache it and make sure _fields was cached on current request object!
// TODO: Submit this caching behavior in core.
if ( isset( $this->_fields ) && is_array( $this->_fields ) && $request === $this->_request ) {
return $this->_fields;
}
$this->_request = $request;
$schema = $this->get_item_schema();
$properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
$additional_fields = $this->get_additional_fields();
foreach ( $additional_fields as $field_name => $field_options ) {
// For back-compat, include any field with an empty schema
// because it won't be present in $this->get_item_schema().
@ -538,10 +561,12 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
$fields = array_keys( $properties );
if ( ! isset( $request['_fields'] ) ) {
$this->_fields = $fields;
return $fields;
}
$requested_fields = wp_parse_list( $request['_fields'] );
if ( 0 === count( $requested_fields ) ) {
$this->_fields = $fields;
return $fields;
}
// Trim off outside whitespace from the comma delimited list.
@ -551,7 +576,7 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
$requested_fields[] = 'id';
}
// Return the list of all requested fields which appear in the schema.
return array_reduce(
$this->_fields = array_reduce(
$requested_fields,
function( $response_fields, $field ) use ( $fields ) {
if ( in_array( $field, $fields, true ) ) {
@ -560,8 +585,8 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
}
// Check for nested fields if $field is not a direct match.
$nested_fields = explode( '.', $field );
// A nested field is included so long as its top-level property is
// present in the schema.
// A nested field is included so long as its top-level property
// is present in the schema.
if ( in_array( $nested_fields[0], $fields, true ) ) {
$response_fields[] = $field;
}
@ -569,5 +594,6 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
},
array()
);
return $this->_fields;
}
}

View File

@ -479,6 +479,24 @@ abstract class WC_REST_CRUD_Controller extends WC_REST_Posts_Controller {
return $response;
}
/**
* Get fields for an object if getter is defined.
*
* @param object $object Object we are fetching response for.
* @param string $context Context of the request. Can be `view` or `edit`.
* @param array $fields List of fields to fetch.
* @return array Data fetched from getters.
*/
public function fetch_fields_using_getters( $object, $context, $fields ) {
$data = array();
foreach ( $fields as $field ) {
if ( method_exists( $this, "api_get_$field" ) ) {
$data[ $field ] = $this->{"api_get_$field"}( $object, $context );
}
}
return $data;
}
/**
* Prepare links for the request.
*

View File

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

View File

@ -217,6 +217,25 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
return $args;
}
/**
* Get products.
*
* @param array $query_args Query args.
*
* @return array Products.
*/
protected function get_objects( $query_args ) {
$query_args['paginate'] = true;
$query_args['return'] = 'objects';
$results = wc_get_products( $query_args );
return array(
'objects' => $results->products,
'total' => $results->total,
'pages' => $results->max_num_pages,
);
}
/**
* Set product images.
*
@ -1326,16 +1345,22 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
* @param WC_Product $product Product instance.
* @param string $context Request context.
* Options: 'view' and 'edit'.
* @param array $fields List of fields to fetch. If empty, then all fields will be returned.
* For backward compatibility, we pass this argument silently.
* @return array
*/
protected function get_product_data( $product, $context = 'view' ) {
$data = parent::get_product_data( $product, $context );
$request = func_get_arg( 2 );
$data = parent::get_product_data( $product, $context, $request );
// Replace in_stock with stock_status.
$pos = array_search( 'in_stock', array_keys( $data ), true );
$array_section_1 = array_slice( $data, 0, $pos, true );
$array_section_2 = array_slice( $data, $pos + 1, null, true );
if ( false !== $pos ) {
$array_section_1 = array_slice( $data, 0, $pos, true );
$array_section_2 = array_slice( $data, $pos + 1, null, true );
$data = $array_section_1 + array( 'stock_status' => $product->get_stock_status( $context ) ) + $array_section_2;
}
return $array_section_1 + array( 'stock_status' => $product->get_stock_status( $context ) ) + $array_section_2;
return $data;
}
}

View File

@ -2748,7 +2748,7 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
} else {
$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="default">' . esc_html__( 'Select a country / region&hellip;', 'woocommerce' ) . '</option>';
$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 ) . ' data-placeholder="' . esc_attr( $args['placeholder'] ? $args['placeholder'] : esc_attr__( 'Select a country / region&hellip;', 'woocommerce' ) ) . '"><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 ) . '>' . esc_html( $cvalue ) . '</option>';

View File

@ -2,6 +2,8 @@
## Added
- Merchant Order Status Filter tests
- Merchant Order Refund tests
- Merchant Apply Coupon tests
## Fixed

View File

@ -53,6 +53,8 @@ The functions to access the core tests are:
- `runProductSettingsTest` - Merchant can update product settings
- `runTaxSettingsTest` - Merchant can update tax settings
- `runOrderStatusFilterTest` - Merchant can filter orders by order status
- `runOrderRefundTest` - Merchant can refund an order
- `runOrderApplyCouponTest` - Merchant can apply a coupon to an order
### Shopper

View File

@ -35,9 +35,10 @@ const runTaskListTest = () => {
const taskListItems = await page.$$('.woocommerce-list__item-title');
expect(taskListItems).toHaveLength(6);
const [ setupTaskListItem ] = await page.$x( '//div[contains(text(),"Set up shipping")]' );
await Promise.all([
// Click on "Set up shipping" task to move to the next step
taskListItems[3].click(),
setupTaskListItem.click(),
// Wait for shipping setup section to load
page.waitForNavigation({waitUntil: 'networkidle0'}),

View File

@ -22,6 +22,8 @@ const runUpdateGeneralSettingsTest = require( './merchant/wp-admin-settings-gene
const runProductSettingsTest = require( './merchant/wp-admin-settings-product.test' );
const runTaxSettingsTest = require( './merchant/wp-admin-settings-tax.test' );
const runOrderStatusFiltersTest = require( './merchant/wp-admin-order-status-filters.test' );
const runOrderRefundTest = require( './merchant/wp-admin-order-refund.test' );
const runOrderApplyCouponTest = require( './merchant/wp-admin-order-apply-coupon.test' );
const runSetupOnboardingTests = () => {
runActivationTest();
@ -46,6 +48,8 @@ const runMerchantTests = () => {
runProductSettingsTest();
runTaxSettingsTest();
runOrderStatusFiltersTest();
runOrderRefundTest();
runOrderApplyCouponTest();
}
module.exports = {
@ -67,5 +71,7 @@ module.exports = {
runProductSettingsTest,
runTaxSettingsTest,
runOrderStatusFiltersTest,
runOrderRefundTest,
runOrderApplyCouponTest,
runMerchantTests,
};

View File

@ -0,0 +1,84 @@
/* eslint-disable jest/no-export */
/**
* Internal dependencies
*/
const {
StoreOwnerFlow,
createSimpleProduct,
createSimpleOrder,
createCoupon,
uiUnblocked,
addProductToOrder,
} = require( '@woocommerce/e2e-utils' );
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
const couponDialogMessage = 'Enter a coupon code to apply. Discounts are applied to line totals, before taxes.';
let couponCode;
let orderId;
const runOrderApplyCouponTest = () => {
describe('WooCommerce Orders > Apply coupon', () => {
beforeAll(async () => {
await StoreOwnerFlow.login();
await Promise.all([
await createSimpleProduct(),
couponCode = await createCoupon(),
orderId = await createSimpleOrder('Pending payment', simpleProductName),
await addProductToOrder(orderId, simpleProductName),
// We need to remove any listeners on the `dialog` event otherwise we can't catch the dialog below
page.removeAllListeners('dialog'),
]);
} );
it('can apply a coupon', async () => {
const couponDialog = await expect(page).toDisplayDialog(async () => {
await expect(page).toClick('button.add-coupon');
});
expect(couponDialog.message()).toMatch(couponDialogMessage);
// Accept the dialog with the coupon code
await couponDialog.accept(couponCode);
await uiUnblocked();
// Verify the coupon list is showing
await page.waitForSelector('.wc-used-coupons');
await expect(page).toMatchElement('.wc_coupon_list', { text: 'Coupon(s)' });
await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode });
// Check that the coupon has been applied
await expect(page).toMatchElement('.wc-order-item-discount', { text: '5.00' });
await expect(page).toMatchElement('.line_cost > .view > .woocommerce-Price-amount', { text: '4.99' });
});
it('can remove a coupon', async () => {
// Make sure we have a coupon on the page to use
await page.waitForSelector('.wc-used-coupons');
await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode });
// We need to use this here as `expect(page).toClick()` was unable to find the element
// See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219
page.$eval('a.remove-coupon', elem => elem.click());
await uiUnblocked();
// Verify the coupon pricing has been removed
await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode });
await expect(page).not.toMatchElement('.wc-order-item-discount', { text: '5.00' });
await expect(page).not.toMatchElement('.line-cost .view .woocommerce-Price-amount', { text: '4.99' });
// Verify the original price is the order total
await expect(page).toMatchElement('.line_cost > .view > .woocommerce-Price-amount', { text: '9.99' });
});
});
};
module.exports = runOrderApplyCouponTest;

View File

@ -0,0 +1,100 @@
/* eslint-disable jest/no-export, jest/no-disabled-tests, */
/**
* Internal dependencies
*/
const {
StoreOwnerFlow,
createSimpleProduct,
createSimpleOrder,
verifyCheckboxIsSet,
verifyValueOfInputField,
uiUnblocked,
addProductToOrder,
} = require( '@woocommerce/e2e-utils' );
const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' );
let orderId;
let currencySymbol;
const runRefundOrderTest = () => {
describe('WooCommerce Orders > Refund an order', () => {
beforeAll(async () => {
await StoreOwnerFlow.login();
await createSimpleProduct();
orderId = await createSimpleOrder();
await addProductToOrder(orderId, simpleProductName);
// Get the currency symbol for the store's selected currency
await page.waitForSelector('.woocommerce-Price-currencySymbol');
let currencyElement = await page.$('.woocommerce-Price-currencySymbol');
currencySymbol = await page.evaluate(el => el.textContent, currencyElement);
// Update order status to `Completed` so we can issue a refund
await StoreOwnerFlow.updateOrderStatus(orderId, 'Completed');
});
it('can issue a refund by quantity', async () => {
// Click the Refund button
await expect(page).toClick('button.refund-items');
// Verify the refund section shows
await page.waitForSelector('div.wc-order-refund-items', { visible: true });
await verifyCheckboxIsSet('#restock_refunded_items');
// Initiate a refund
await expect(page).toFill('.refund_order_item_qty', '1');
await expect(page).toFill('#refund_reason', 'No longer wanted');
await Promise.all([
verifyValueOfInputField('.refund_line_total', '9.99'),
verifyValueOfInputField('#refund_amount', '9.99'),
expect(page).toMatchElement('.do-manual-refund', { text: `Refund ${currencySymbol}9.99 manually` }),
]);
await expect(page).toClick('.do-manual-refund');
await uiUnblocked();
await Promise.all([
// Verify the product line item shows the refunded quantity and amount
expect(page).toMatchElement('.quantity .refunded', { text: '-1' }),
expect(page).toMatchElement('.line_cost .refunded', { text: `-${currencySymbol}9.99` }),
// Verify the refund shows in the list with the amount
expect(page).toMatchElement('.refund .description', { text: 'No longer wanted' }),
expect(page).toMatchElement('.refund > .line_cost', { text: `-${currencySymbol}9.99` }),
// Verify system note was added
expect(page).toMatchElement('.system-note', { text: 'Order status changed from Completed to Refunded.' }),
]);
});
it('can delete an issued refund', async () => {
// We need to use this here as `expect(page).toClick()` was unable to find the element
// See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219
page.$eval('a.delete_refund', elem => elem.click());
await uiUnblocked();
// Verify the refunded row item is no longer showing
await page.waitForSelector('tr.refund', { visible: false });
await Promise.all([
// Verify the product line item shows the refunded quantity and amount
expect(page).not.toMatchElement('.quantity .refunded', { text: '-1' }),
expect(page).not.toMatchElement('.line_cost .refunded', { text: `-${currencySymbol}9.99` }),
// Verify the refund shows in the list with the amount
expect(page).not.toMatchElement('.refund .description', { text: 'No longer wanted' }),
expect(page).not.toMatchElement('.refund > .line_cost', { text: `-${currencySymbol}9.99` }),
]);
});
});
};
module.exports = runRefundOrderTest;

View File

@ -1,5 +1,9 @@
# Unreleased
## Added
- Insert a 12 hour delay in using new docker image tags
## Fixed
- Remove redundant `puppeteer` dependency

View File

@ -36,12 +36,22 @@ async function fetchLatestTagFromPage( image, nameSearch, page ) {
if ( ! data.count ) {
reject( "No image '" + image + '" found' );
} else {
// Implement a 12 hour delay on pulling newly released docker tags.
const delayMilliseconds = 12 * 3600 * 1000;
const currentTime = Date.now();
let latestTag = null;
let lastUpdated = null;
for ( let tag of data.results ) {
tag.semver = tag.name.match( /^\d+\.\d+(.\d+)*$/ );
if ( ! tag.semver ) {
continue;
}
lastUpdated = Date.parse( tag.last_updated );
if ( currentTime - lastUpdated < delayMilliseconds ) {
continue;
}
tag.semver = semver.coerce( tag.semver[0] );
if ( ! latestTag || semver.gt( tag.semver, latestTag.semver ) ) {
latestTag = tag;

View File

@ -0,0 +1,6 @@
/*
* Internal dependencies
*/
const { runOrderApplyCouponTest } = require( '@woocommerce/e2e-core-tests' );
runOrderApplyCouponTest();

View File

@ -0,0 +1,6 @@
/*
* Internal dependencies
*/
const { runOrderRefundTest } = require( '@woocommerce/e2e-core-tests' );
runOrderRefundTest();

View File

@ -9,6 +9,12 @@
- `clickFilter()` util helper method that clicks on a list page filter
- `moveAllItemsToTrash()` util helper method that checks every item in a list page and moves them to the trash
- `createSimpleOrder( status )` component which accepts an order status string and creates a basic order with that status
- `addProductToOrder( orderId, productName )` component which adds the provided productName to the passed in orderId
- `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code.
## Changes
- `createSimpleOrder( status )` returns the ID of the order that was created
# 0.1.1

View File

@ -38,20 +38,22 @@ describe( 'Cart page', () => {
### Merchant `StoreOwnerFlow`
| Function | Description |
|----------|-------------|
| `login` | Log in as merchant |
| `logout` | log out of merchant account |
| `openAllOrdersView` | Go to the orders listing |
| `openDashboard` | Go to the WordPress dashboard |
| `openNewCoupon` | Go to the new coupon editor |
| `openNewOrder` | Go to the new order editor |
| `openNewProduct` | Go to the new product editor |
| `openPermalinkSettings` | Go to Settings -> Permalinks |
| `openPlugins` | Go to the Plugins screen |
| `openSettings` | Go to WooCommerce -> Settings |
| `runSetupWizard` | Open the onboarding profiler |
|----------|-------------|
| Function | Parameters | Description |
|----------|-------------|------------|
| `login` | | Log in as merchant |
| `logout` | | Log out of merchant account |
| `openAllOrdersView` | | Go to the orders listing |
| `openDashboard` | | Go to the WordPress dashboard |
| `openNewCoupon` | | Go to the new coupon editor |
| `openNewOrder` | | Go to the new order editor |
| `openNewProduct` | | Go to the new product editor |
| `openPermalinkSettings` | | Go to Settings -> Permalinks |
| `openPlugins` | | Go to the Plugins screen |
| `openSettings` | | Go to WooCommerce -> Settings |
| `runSetupWizard` | | Open the onboarding profiler |
| `goToOrder` | `orderId` | Go to view a single order |
| `updateOrderStatus` | `orderId, status` | Update the status of an order |
|----------|-------------|-------------|
### Shopper `CustomerFlow`

View File

@ -368,6 +368,61 @@ const createSimpleOrder = async ( orderStatus = 'Pending payment' ) => {
// Verify
await expect( page ).toMatchElement( '#message', { text: 'Order updated.' } );
const variablePostId = await page.$( '#post_ID' );
let variablePostIdValue = ( await ( await variablePostId.getProperty( 'value' ) ).jsonValue() );
return variablePostIdValue;
};
/**
* Adds a product to an order in the StoreOwnerFlow.
*
* @param orderId ID of the order to add the product to.
* @param productName Name of the product being added to the order.
*/
const addProductToOrder = async ( orderId, productName ) => {
await StoreOwnerFlow.goToOrder( orderId );
// Add a product to the order
await expect( page ).toClick( 'button.add-line-item' );
await expect( page ).toClick( 'button.add-order-item' );
await page.waitForSelector( '.wc-backbone-modal-header' );
await expect( page ).toClick( '.wc-backbone-modal-content .wc-product-search' );
await expect( page ).toFill( '#wc-backbone-modal-dialog + .select2-container .select2-search__field', productName );
await expect( page ).toClick( 'li[aria-selected="true"]' );
await page.click( '.wc-backbone-modal-content #btn-ok' );
await uiUnblocked();
// Verify the product we added shows as a line item now
await expect( page ).toMatchElement( '.wc-order-item-name', { text: productName } );
}
/**
* Creates a basic coupon with the provided coupon amount. Returns the coupon code.
*
* @param couponAmount Amount to be applied. Defaults to 5.
*/
const createCoupon = async ( couponAmount = '5' ) => {
await StoreOwnerFlow.openNewCoupon();
// Fill in coupon code
let couponCode = 'code-' + new Date().getTime().toString();
await expect(page).toFill( '#title', couponCode );
// Set general coupon data
await clickTab( 'General' );
await expect(page).toSelect( '#discount_type', 'Fixed cart discount' );
await expect(page).toFill( '#coupon_amount', couponAmount );
// Publish coupon
await expect( page ).toClick( '#publish' );
await page.waitForSelector( '.updated.notice' );
// Verify
await expect( page ).toMatchElement( '.updated.notice', { text: 'Coupon updated.' } );
return couponCode;
};
export {
@ -376,4 +431,6 @@ export {
createVariableProduct,
createSimpleOrder,
verifyAndPublish,
addProductToOrder,
createCoupon,
};

View File

@ -25,6 +25,7 @@ const WP_ADMIN_NEW_ORDER = baseUrl + 'wp-admin/post-new.php?post_type=shop_order
const WP_ADMIN_NEW_PRODUCT = baseUrl + 'wp-admin/post-new.php?post_type=product';
const WP_ADMIN_WC_SETTINGS = baseUrl + 'wp-admin/admin.php?page=wc-settings&tab=';
const WP_ADMIN_PERMALINK_SETTINGS = baseUrl + 'wp-admin/options-permalink.php';
const WP_ADMIN_SINGLE_CPT_VIEW = ( postId ) => baseUrl + `wp-admin/post.php?post=${ postId }&action=edit`;
const SHOP_PAGE = baseUrl + 'shop';
const SHOP_PRODUCT_PAGE = baseUrl + '?p=';
@ -310,6 +311,23 @@ const StoreOwnerFlow = {
waitUntil: 'networkidle0',
} );
},
goToOrder: async ( orderId ) => {
await page.goto( WP_ADMIN_SINGLE_CPT_VIEW( orderId ), {
waitUntil: 'networkidle0',
} );
},
updateOrderStatus: async ( orderId, status ) => {
await page.goto( WP_ADMIN_SINGLE_CPT_VIEW( orderId ), {
waitUntil: 'networkidle0',
} );
await expect( page ).toSelect( '#order_status', status );
await page.waitFor( 2000 );
await expect( page ).toClick( 'button.save_order' );
await page.waitForSelector( '#message' );
await expect( page ).toMatchElement( '#message', { text: 'Order updated.' } );
}
};
export { CustomerFlow, StoreOwnerFlow };