Merge branch 'master' into e2e-shopper-pay-order
This commit is contained in:
commit
57a798c0e9
|
@ -0,0 +1,21 @@
|
|||
name: Build zip for PR
|
||||
on:
|
||||
pull_request
|
||||
jobs:
|
||||
build:
|
||||
name: Build zip for PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Build
|
||||
id: build
|
||||
uses: woocommerce/action-build@v2
|
||||
- name: Upload PR zip
|
||||
uses: actions/upload-artifact@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: woocommerce.zip
|
||||
path: ${{ steps.build.outputs.zip_path }}
|
||||
retention-days: 7
|
|
@ -0,0 +1,70 @@
|
|||
name: Run unit tests on PR
|
||||
on:
|
||||
pull_request
|
||||
jobs:
|
||||
test:
|
||||
name: PHP ${{ matrix.php }} WP ${{ matrix.wp }}
|
||||
timeout-minutes: 15
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php: [ '7.0', '7.1', '7.2', '7.3', '7.4', '8.0' ]
|
||||
wp: [ "latest" ]
|
||||
include:
|
||||
- wp: nightly
|
||||
php: '7.4'
|
||||
- wp: '5.5'
|
||||
php: 7.2
|
||||
- wp: '5.4'
|
||||
php: 7.2
|
||||
services:
|
||||
database:
|
||||
image: mysql:5.6
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer
|
||||
extensions: mysql
|
||||
coverage: none
|
||||
|
||||
- name: Tool versions
|
||||
run: |
|
||||
php --version
|
||||
composer --version
|
||||
|
||||
- name: Get cached composer directories
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
./packages
|
||||
./vendor
|
||||
key: ${{ runner.os }}-${{ hashFiles('./composer.lock') }}
|
||||
|
||||
- name: Setup and install composer
|
||||
run: composer install
|
||||
|
||||
- name: Add PHP8 Compatibility.
|
||||
run: |
|
||||
if [ "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]; then
|
||||
curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip
|
||||
unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip
|
||||
composer bin phpunit config --unset platform
|
||||
composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}'
|
||||
composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs
|
||||
fi
|
||||
|
||||
- name: Init DB and WP
|
||||
run: ./tests/bin/install.sh woo_test root root 127.0.0.1 ${{ matrix.wp }}
|
||||
|
||||
- name: Run tests
|
||||
run: ./vendor/bin/phpunit -c ./phpunit.xml
|
|
@ -36,9 +36,9 @@ jobs:
|
|||
- npm install
|
||||
- composer install --no-dev
|
||||
script:
|
||||
- travis_retry npm run build:assets
|
||||
- travis_retry npm run docker:up
|
||||
- travis_retry npm run test:e2e
|
||||
- npm run build:assets
|
||||
- npm run docker:up
|
||||
- npm run test:e2e
|
||||
after_script:
|
||||
- npm run docker:down
|
||||
- name: "WP Nightly"
|
||||
|
|
|
@ -19,3 +19,20 @@ $highlightext: desaturate(lighten($highlight, 50%), 18%) !default; // Text
|
|||
|
||||
$contentbg: #fff !default; // Content BG - Tabs (active state)
|
||||
$subtext: #767676 !default; // small, breadcrumbs etc
|
||||
|
||||
// export vars as CSS vars
|
||||
:root {
|
||||
--woocommerce: $woocommerce;
|
||||
--wc-green: $green;
|
||||
--wc-red: $red;
|
||||
--wc-orange: $orange;
|
||||
--wc-blue: $blue;
|
||||
--wc-primary: $primary;
|
||||
--wc-primary-text: $primarytext;
|
||||
--wc-secondary: $secondary;
|
||||
--wc-secondary-text: $secondarytext;
|
||||
--wc-highlight: $highlight;
|
||||
--wc-highligh-text: $highlightext;
|
||||
--wc-content-bg: $contentbg;
|
||||
--wc-subtext: $subtext;
|
||||
}
|
||||
|
|
|
@ -126,12 +126,18 @@ a.button {
|
|||
}
|
||||
}
|
||||
|
||||
.woocommerce-breadcrumb {
|
||||
margin-bottom: 5rem;
|
||||
.site-main {
|
||||
.woocommerce-breadcrumb {
|
||||
margin-bottom: var(--global--spacing-vertical);
|
||||
font-size: 0.88889em;
|
||||
font-family: $headings;
|
||||
}
|
||||
.woocommerce-products-header {
|
||||
margin-top: var(--global--spacing-vertical);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.woocommerce-pagination {
|
||||
font-family: $headings;
|
||||
font-size: 0.88889em;
|
||||
|
@ -350,6 +356,10 @@ a.button {
|
|||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.product-thumbnail {
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -537,13 +547,18 @@ dl.variation,
|
|||
display: none;
|
||||
}
|
||||
|
||||
|
||||
&.singular { // Needed for higher specificity to target the entry title font size
|
||||
.entry-title {
|
||||
font-size: var(--global--font-size-xl);
|
||||
font-weight: normal;
|
||||
margin: 0 0 2.5rem;
|
||||
|
||||
&::before {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin-bottom: 8rem;
|
||||
|
@ -829,7 +844,7 @@ a.reset_variations {
|
|||
}
|
||||
|
||||
h2:first-of-type {
|
||||
font-size: 3rem;
|
||||
font-size: var(--global--font-size-lg);
|
||||
margin: 0 0 2rem !important;
|
||||
}
|
||||
}
|
||||
|
@ -1424,10 +1439,6 @@ a.reset_variations {
|
|||
|
||||
#main {
|
||||
|
||||
.entry-header {
|
||||
padding: 3vw 0 1.5vw;
|
||||
}
|
||||
|
||||
.woocommerce {
|
||||
max-width: var(--responsive--alignwide-width);
|
||||
margin: 0 auto;
|
||||
|
@ -1860,6 +1871,10 @@ a.reset_variations {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
tfoot {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-order-received {
|
||||
|
@ -2788,11 +2803,6 @@ a.reset_variations {
|
|||
|
||||
.woocommerce-account {
|
||||
|
||||
.entry-header {
|
||||
|
||||
padding-bottom: 20px !important;
|
||||
}
|
||||
|
||||
.woocommerce-MyAccount-content {
|
||||
|
||||
p:first-of-type {
|
||||
|
|
|
@ -71,10 +71,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -133,10 +129,6 @@
|
|||
"phpcs",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
|
||||
"source": "https://github.com/PHPCompatibility/PHPCompatibility"
|
||||
},
|
||||
"time": "2019-12-27T09:44:58+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -189,10 +181,6 @@
|
|||
"polyfill",
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
|
||||
"source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
|
||||
},
|
||||
"time": "2019-11-04T15:17:54+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -243,10 +231,6 @@
|
|||
"standards",
|
||||
"wordpress"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
|
||||
"source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
|
||||
},
|
||||
"time": "2019-08-28T14:22:28+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -343,10 +327,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -393,11 +373,6 @@
|
|||
"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"
|
||||
}
|
||||
],
|
||||
|
@ -411,5 +386,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "1.1.0"
|
||||
}
|
||||
|
|
|
@ -332,10 +332,6 @@
|
|||
}
|
||||
],
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -448,10 +444,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -612,10 +604,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1236,10 +1224,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1504,10 +1488,6 @@
|
|||
],
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1627,10 +1607,6 @@
|
|||
}
|
||||
],
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -1680,10 +1656,6 @@
|
|||
"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"
|
||||
}
|
||||
],
|
||||
|
@ -1697,5 +1669,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "1.1.0"
|
||||
}
|
||||
|
|
|
@ -133,24 +133,20 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
"name": "mck89/peast",
|
||||
"version": "v1.11.0",
|
||||
"version": "v1.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mck89/peast.git",
|
||||
"reference": "2a2bc6826114c46ff0bc1359208b7083a17f7a99"
|
||||
"reference": "833be7a294627a8c5b1c482cbf489f73bf9b8086"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mck89/peast/zipball/2a2bc6826114c46ff0bc1359208b7083a17f7a99",
|
||||
"reference": "2a2bc6826114c46ff0bc1359208b7083a17f7a99",
|
||||
"url": "https://api.github.com/repos/mck89/peast/zipball/833be7a294627a8c5b1c482cbf489f73bf9b8086",
|
||||
"reference": "833be7a294627a8c5b1c482cbf489f73bf9b8086",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -162,7 +158,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.11.0-dev"
|
||||
"dev-master": "1.12.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -182,11 +178,7 @@
|
|||
}
|
||||
],
|
||||
"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"
|
||||
"time": "2021-01-08T15:16:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mustache/mustache",
|
||||
|
@ -232,10 +224,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -285,10 +273,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -398,10 +382,6 @@
|
|||
],
|
||||
"description": "Provides internationalization tools for WordPress projects.",
|
||||
"homepage": "https://github.com/wp-cli/i18n-command",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -450,9 +430,6 @@
|
|||
],
|
||||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -503,10 +480,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -569,11 +542,6 @@
|
|||
"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"
|
||||
}
|
||||
],
|
||||
|
@ -587,5 +555,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "1.1.0"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
== Changelog ==
|
||||
|
||||
= 4.9.0 =
|
||||
= 4.9.2 2021-01-25 =
|
||||
|
||||
* Tweak - Disable untested plugin's notice from System Status and Plugin's page. #28840
|
||||
|
||||
= 4.9.1 2021-01-19 =
|
||||
|
||||
* Fix - Reverts #28204 to ensure compatibility with extensions using legacy do_action calls. #28835
|
||||
|
||||
= 4.9.0 2021-01-12 =
|
||||
|
||||
**WooCommerce**
|
||||
* Localization - Add 'Ladakh' to the list of Indian states. #28458
|
||||
|
@ -28,6 +36,9 @@
|
|||
* Fix - Better error messages when usage limit are reached. #28592
|
||||
* Fix - Adjust stock items only for statuses which reduces the stock. #28620
|
||||
* Fix - Named parameter to fix DB update routine on PHP8. #28537
|
||||
* Fix - Add protection around func_get_args_call for backwards compatibility. #28677
|
||||
* Fix - Restore stock_status in REST API V3 response. #28731
|
||||
* Fix - Revert some of the changes related to perf enhancements (27735) as it was breaking stock_status filter. #28745
|
||||
* Dev - Hook for intializing price slider in frontend. #28014
|
||||
* Dev - Add support for running a custom initialization script for tests. #28041
|
||||
* Dev - Use the coenjacobs/mozart package to renamespace vendor packages. #28147
|
||||
|
@ -70,6 +81,7 @@
|
|||
* Fix - Preventing desktop-sized navigation placeholder from appearing on mobile during load. #5616
|
||||
* Fix - Completed tasks tracking causing infinite loop #5941
|
||||
* Fix - Remove Navigation access #5940
|
||||
* Fix - Compile the debug module so it can be used in older browsers like IE11. #5968
|
||||
* Tweak - Fix inconsistent REST API parameter name for customer type filtering. #5823
|
||||
* Tweak - Improve styles of the tax task. #5709
|
||||
* Tweak - Do not show store setup link on the homescreen. #5801
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"pelago/emogrifier": "3.1.0",
|
||||
"psr/container": "1.0.0",
|
||||
"woocommerce/action-scheduler": "3.1.6",
|
||||
"woocommerce/woocommerce-admin": "1.8.3",
|
||||
"woocommerce/woocommerce-admin": "1.9.0-rc.3",
|
||||
"woocommerce/woocommerce-blocks": "4.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
@ -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": "758b097c13e89200b28bf3a3b5fc2752",
|
||||
"content-hash": "5fac5fbdbcff552de109cd95d469e975",
|
||||
"packages": [
|
||||
{
|
||||
"name": "automattic/jetpack-autoloader",
|
||||
|
@ -77,23 +77,20 @@
|
|||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "A wrapper for defining constants in a more testable way.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-constants/tree/v1.5.1"
|
||||
},
|
||||
"time": "2020-10-28T19:00:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/installers",
|
||||
"version": "v1.9.0",
|
||||
"version": "v1.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/installers.git",
|
||||
"reference": "b93bcf0fa1fccb0b7d176b0967d969691cd74cca"
|
||||
"reference": "1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/installers/zipball/b93bcf0fa1fccb0b7d176b0967d969691cd74cca",
|
||||
"reference": "b93bcf0fa1fccb0b7d176b0967d969691cd74cca",
|
||||
"url": "https://api.github.com/repos/composer/installers/zipball/1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d",
|
||||
"reference": "1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -104,17 +101,18 @@
|
|||
"shama/baton": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/composer": "1.6.* || 2.0.*@dev",
|
||||
"composer/semver": "1.0.* || 2.0.*@dev",
|
||||
"phpunit/phpunit": "^4.8.36",
|
||||
"sebastian/comparator": "^1.2.4",
|
||||
"composer/composer": "1.6.* || ^2.0",
|
||||
"composer/semver": "^1 || ^3",
|
||||
"phpstan/phpstan": "^0.12.55",
|
||||
"phpstan/phpstan-phpunit": "^0.12.16",
|
||||
"symfony/phpunit-bridge": "^4.2 || ^5",
|
||||
"symfony/process": "^2.3"
|
||||
},
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"class": "Composer\\Installers\\Plugin",
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
"dev-main": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -152,6 +150,7 @@
|
|||
"Porto",
|
||||
"RadPHP",
|
||||
"SMF",
|
||||
"Starbug",
|
||||
"Thelia",
|
||||
"Whmcs",
|
||||
"WolfCMS",
|
||||
|
@ -192,6 +191,7 @@
|
|||
"phpbb",
|
||||
"piwik",
|
||||
"ppi",
|
||||
"processwire",
|
||||
"puppet",
|
||||
"pxcms",
|
||||
"reindex",
|
||||
|
@ -207,21 +207,21 @@
|
|||
"zend",
|
||||
"zikula"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/installers/issues",
|
||||
"source": "https://github.com/composer/installers/tree/v1.9.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-04-07T06:57:05+00:00"
|
||||
"time": "2021-01-14T11:07:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
|
@ -281,10 +281,6 @@
|
|||
"geolocation",
|
||||
"maxmind"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
|
||||
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.6.0"
|
||||
},
|
||||
"time": "2019-12-19T22:59:03+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -359,10 +355,6 @@
|
|||
"email",
|
||||
"pre-processing"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MyIntervals/emogrifier/issues",
|
||||
"source": "https://github.com/MyIntervals/emogrifier"
|
||||
},
|
||||
"time": "2019-12-26T19:37:31+00:00"
|
||||
},
|
||||
{
|
||||
|
@ -412,10 +404,6 @@
|
|||
"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"
|
||||
},
|
||||
{
|
||||
|
@ -507,24 +495,20 @@
|
|||
],
|
||||
"description": "Action Scheduler for WordPress and WooCommerce",
|
||||
"homepage": "https://actionscheduler.org/",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/action-scheduler/issues",
|
||||
"source": "https://github.com/woocommerce/action-scheduler/tree/master"
|
||||
},
|
||||
"time": "2020-05-12T16:22:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-admin",
|
||||
"version": "1.8.3",
|
||||
"version": "1.9.0-rc.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/woocommerce-admin.git",
|
||||
"reference": "534442980c34369f711efdb8e85fdcb1363be712"
|
||||
"reference": "8c5b20cb6347959daf5403ee30e47061f335240b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/534442980c34369f711efdb8e85fdcb1363be712",
|
||||
"reference": "534442980c34369f711efdb8e85fdcb1363be712",
|
||||
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/8c5b20cb6347959daf5403ee30e47061f335240b",
|
||||
"reference": "8c5b20cb6347959daf5403ee30e47061f335240b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -556,11 +540,7 @@
|
|||
],
|
||||
"description": "A modern, javascript-driven WooCommerce Admin experience.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce-admin",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/woocommerce-admin/issues",
|
||||
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v1.8.3"
|
||||
},
|
||||
"time": "2021-01-06T00:00:42+00:00"
|
||||
"time": "2021-01-22T10:23:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-blocks",
|
||||
|
@ -659,10 +639,6 @@
|
|||
"isolation",
|
||||
"tool"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bamarni/composer-bin-plugin/issues",
|
||||
"source": "https://github.com/bamarni/composer-bin-plugin/tree/master"
|
||||
},
|
||||
"time": "2020-05-03T08:27:20+00:00"
|
||||
}
|
||||
],
|
||||
|
@ -678,5 +654,5 @@
|
|||
"platform-overrides": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"plugin-api-version": "2.0.0"
|
||||
"plugin-api-version": "1.1.0"
|
||||
}
|
||||
|
|
|
@ -306,6 +306,40 @@ return array(
|
|||
'CZ' => array(),
|
||||
'DE' => array(),
|
||||
'DK' => array(),
|
||||
'DO' => array( // Dominican Republic.
|
||||
'DO-01' => __( 'Distrito Nacional', 'woocommerce' ),
|
||||
'DO-02' => __( 'Azua', 'woocommerce' ),
|
||||
'DO-03' => __( 'Baoruco', 'woocommerce' ),
|
||||
'DO-04' => __( 'Barahona', 'woocommerce' ),
|
||||
'DO-05' => __( 'Dajabón', 'woocommerce' ),
|
||||
'DO-06' => __( 'Duarte', 'woocommerce' ),
|
||||
'DO-07' => __( 'Elías Piña', 'woocommerce' ),
|
||||
'DO-08' => __( 'El Seibo', 'woocommerce' ),
|
||||
'DO-09' => __( 'Espaillat', 'woocommerce' ),
|
||||
'DO-10' => __( 'Independencia', 'woocommerce' ),
|
||||
'DO-11' => __( 'La Altagracia', 'woocommerce' ),
|
||||
'DO-12' => __( 'La Romana', 'woocommerce' ),
|
||||
'DO-13' => __( 'La Vega', 'woocommerce' ),
|
||||
'DO-14' => __( 'María Trinidad Sánchez', 'woocommerce' ),
|
||||
'DO-15' => __( 'Monte Cristi', 'woocommerce' ),
|
||||
'DO-16' => __( 'Pedernales', 'woocommerce' ),
|
||||
'DO-17' => __( 'Peravia', 'woocommerce' ),
|
||||
'DO-18' => __( 'Puerto Plata', 'woocommerce' ),
|
||||
'DO-19' => __( 'Hermanas Mirabal', 'woocommerce' ),
|
||||
'DO-20' => __( 'Samaná', 'woocommerce' ),
|
||||
'DO-21' => __( 'San Cristóbal', 'woocommerce' ),
|
||||
'DO-22' => __( 'San Juan', 'woocommerce' ),
|
||||
'DO-23' => __( 'San Pedro de Macorís', 'woocommerce' ),
|
||||
'DO-24' => __( 'Sánchez Ramírez', 'woocommerce' ),
|
||||
'DO-25' => __( 'Santiago', 'woocommerce' ),
|
||||
'DO-26' => __( 'Santiago Rodríguez', 'woocommerce' ),
|
||||
'DO-27' => __( 'Valverde', 'woocommerce' ),
|
||||
'DO-28' => __( 'Monseñor Nouel', 'woocommerce' ),
|
||||
'DO-29' => __( 'Monte Plata', 'woocommerce' ),
|
||||
'DO-30' => __( 'Hato Mayor', 'woocommerce' ),
|
||||
'DO-31' => __( 'San José de Ocoa', 'woocommerce' ),
|
||||
'DO-32' => __( 'Santo Domingo', 'woocommerce' ),
|
||||
),
|
||||
'DZ' => array(
|
||||
'DZ-01' => __( 'Adrar', 'woocommerce' ),
|
||||
'DZ-02' => __( 'Chlef', 'woocommerce' ),
|
||||
|
@ -442,6 +476,7 @@ return array(
|
|||
),
|
||||
'FI' => array(),
|
||||
'FR' => array(),
|
||||
'GF' => array(),
|
||||
'GH' => array( // Ghanaian Regions.
|
||||
'AF' => __( 'Ahafo', 'woocommerce' ),
|
||||
'AH' => __( 'Ashanti', 'woocommerce' ),
|
||||
|
@ -477,7 +512,30 @@ return array(
|
|||
'L' => __( 'South Aegean', 'woocommerce' ),
|
||||
'M' => __( 'Crete', 'woocommerce' ),
|
||||
),
|
||||
'GF' => array(),
|
||||
'GT' => array( // Guatemalan states.
|
||||
'AV' => __( 'Alta Verapaz', 'woocommerce' ),
|
||||
'BV' => __( 'Baja Verapaz', 'woocommerce' ),
|
||||
'CM' => __( 'Chimaltenango', 'woocommerce' ),
|
||||
'CQ' => __( 'Chiquimula', 'woocommerce' ),
|
||||
'PR' => __( 'El Progreso', 'woocommerce' ),
|
||||
'ES' => __( 'Escuintla', 'woocommerce' ),
|
||||
'GU' => __( 'Guatemala', 'woocommerce' ),
|
||||
'HU' => __( 'Huehuetenango', 'woocommerce' ),
|
||||
'IZ' => __( 'Izabal', 'woocommerce' ),
|
||||
'JA' => __( 'Jalapa', 'woocommerce' ),
|
||||
'JU' => __( 'Jutiapa', 'woocommerce' ),
|
||||
'PE' => __( 'Petén', 'woocommerce' ),
|
||||
'QZ' => __( 'Quetzaltenango', 'woocommerce' ),
|
||||
'QC' => __( 'Quiché', 'woocommerce' ),
|
||||
'RE' => __( 'Retalhuleu', 'woocommerce' ),
|
||||
'SA' => __( 'Sacatepéquez', 'woocommerce' ),
|
||||
'SM' => __( 'San Marcos', 'woocommerce' ),
|
||||
'SR' => __( 'Santa Rosa', 'woocommerce' ),
|
||||
'SO' => __( 'Sololá', 'woocommerce' ),
|
||||
'SU' => __( 'Suchitepéquez', 'woocommerce' ),
|
||||
'TO' => __( 'Totonicapán', 'woocommerce' ),
|
||||
'ZA' => __( 'Zacapa', 'woocommerce' )
|
||||
),
|
||||
'HK' => array( // Hong Kong states.
|
||||
'HONG KONG' => __( 'Hong Kong Island', 'woocommerce' ),
|
||||
'KOWLOON' => __( 'Kowloon', 'woocommerce' ),
|
||||
|
@ -1304,7 +1362,6 @@ return array(
|
|||
'VS' => __( 'Vaslui', 'woocommerce' ),
|
||||
'VN' => __( 'Vrancea', 'woocommerce' ),
|
||||
),
|
||||
'RS' => array(),
|
||||
'SG' => array(),
|
||||
'SK' => array(),
|
||||
'SI' => array(),
|
||||
|
|
|
@ -64,6 +64,51 @@ class WC_Admin_Notices {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses query to create nonces when available.
|
||||
*
|
||||
* @param object $response The WP_REST_Response we're working with.
|
||||
* @return object $response The prepared WP_REST_Response object.
|
||||
*/
|
||||
public static function prepare_note_with_nonce( $response ) {
|
||||
if ( 'wc-update-db-reminder' !== $response->data['name'] || ! isset( $response->data['actions'] ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
foreach ( $response->data['actions'] as $action_key => $action ) {
|
||||
$url_parts = ! empty( $action->query ) ? wp_parse_url( $action->query ) : '';
|
||||
|
||||
if ( ! isset( $url_parts['query'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wp_parse_str( $url_parts['query'], $params );
|
||||
|
||||
if ( array_key_exists( '_nonce_action', $params ) && array_key_exists( '_nonce_name', $params ) ) {
|
||||
$org_params = $params;
|
||||
|
||||
// Check to make sure we're acting on the whitelisted nonce actions.
|
||||
if ( 'wc_db_update' !== $params['_nonce_action'] && 'woocommerce_hide_notices_nonce' !== $params['_nonce_action'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset( $org_params['_nonce_action'] );
|
||||
unset( $org_params['_nonce_name'] );
|
||||
|
||||
$url = $url_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path'];
|
||||
|
||||
$nonce = array( $params['_nonce_name'] => wp_create_nonce( $params['_nonce_action'] ) );
|
||||
$merged_params = array_merge( $nonce, $org_params );
|
||||
$parsed_query = add_query_arg( $merged_params, $url );
|
||||
|
||||
$response->data['actions'][ $action_key ]->query = $parsed_query;
|
||||
$response->data['actions'][ $action_key ]->url = $parsed_query;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store notices to DB
|
||||
*/
|
||||
|
|
|
@ -377,7 +377,7 @@ class WC_Admin_Status {
|
|||
private static function output_plugins_info( $plugins, $untested_plugins ) {
|
||||
$wc_version = Constants::get_constant( 'WC_VERSION' );
|
||||
|
||||
if ( 'major' === WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE ) {
|
||||
if ( 'major' === Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) ) {
|
||||
// Since we're only testing against major, we don't need to show minor and patch version.
|
||||
$wc_version = $wc_version[0] . '.0';
|
||||
}
|
||||
|
|
|
@ -118,7 +118,9 @@ class WC_Meta_Box_Order_Actions {
|
|||
|
||||
WC()->payment_gateways();
|
||||
WC()->shipping();
|
||||
WC()->mailer()->emails['WC_Email_New_Order']->trigger( $order->get_id(), $order );
|
||||
add_filter( 'woocommerce_new_order_email_allows_resend', '__return_true' );
|
||||
WC()->mailer()->emails['WC_Email_New_Order']->trigger( $order->get_id(), $order, true );
|
||||
remove_filter( 'woocommerce_new_order_email_allows_resend', '__return_true' );
|
||||
|
||||
do_action( 'woocommerce_after_resend_order_email', $order, 'new_order' );
|
||||
|
||||
|
|
|
@ -110,10 +110,13 @@ class WC_Notes_Run_Db_Update {
|
|||
*/
|
||||
private static function update_needed_notice( $note_id = null ) {
|
||||
$update_url = html_entity_decode(
|
||||
wp_nonce_url(
|
||||
add_query_arg( 'do_update_woocommerce', 'true', wc_get_current_admin_url() ? wc_get_current_admin_url() : admin_url( 'admin.php?page=wc-settings' ) ),
|
||||
'wc_db_update',
|
||||
'wc_db_update_nonce'
|
||||
add_query_arg(
|
||||
array(
|
||||
'do_update_woocommerce' => 'true',
|
||||
'_nonce_action' => 'wc_db_update',
|
||||
'_nonce_name' => 'wc_db_update_nonce',
|
||||
),
|
||||
wc_get_current_admin_url() ? wc_get_current_admin_url() : admin_url( 'admin.php?page=wc-settings' )
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -206,14 +209,13 @@ class WC_Notes_Run_Db_Update {
|
|||
*/
|
||||
private static function update_done_notice( $note_id ) {
|
||||
$hide_notices_url = html_entity_decode( // to convert &s to normal &, otherwise produces invalid link.
|
||||
wp_nonce_url(
|
||||
add_query_arg(
|
||||
'wc-hide-notice',
|
||||
'update',
|
||||
wc_get_current_admin_url() ? wc_get_current_admin_url() : admin_url( 'admin.php?page=wc-settings' )
|
||||
array(
|
||||
'wc-hide-notice' => 'update',
|
||||
'_nonce_action' => 'woocommerce_hide_notices_nonce',
|
||||
'_nonce_name' => '_wc_notice_nonce',
|
||||
),
|
||||
'woocommerce_hide_notices_nonce',
|
||||
'_wc_notice_nonce'
|
||||
wc_get_current_admin_url() ? remove_query_arg( 'do_update_woocommerce', wc_get_current_admin_url() ) : admin_url( 'admin.php?page=wc-settings' )
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -150,10 +150,15 @@ class WC_Plugin_Updates {
|
|||
* with the $new_version.
|
||||
*
|
||||
* @param string $new_version WooCommerce version to test against.
|
||||
* @param string $release 'major' or 'minor'.
|
||||
* @param string $release 'major', 'minor', or 'none'.
|
||||
* @return array of plugin info arrays
|
||||
*/
|
||||
public function get_untested_plugins( $new_version, $release ) {
|
||||
// Since 5.0 all versions are backwards compatible.
|
||||
if ( 'none' === $release ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$extensions = array_merge( $this->get_plugins_with_header( self::VERSION_TESTED_HEADER ), $this->get_plugins_for_woocommerce() );
|
||||
$untested = array();
|
||||
$new_version_parts = explode( '.', $new_version );
|
||||
|
|
|
@ -42,9 +42,14 @@ class WC_Plugins_Screen_Updates extends WC_Plugin_Updates {
|
|||
* @param stdClass $response Plugin update response.
|
||||
*/
|
||||
public function in_plugin_update_message( $args, $response ) {
|
||||
$version_type = Constants::get_constant( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' );
|
||||
if ( ! is_string( $version_type ) ) {
|
||||
$version_type = 'none';
|
||||
}
|
||||
|
||||
$this->new_version = $response->new_version;
|
||||
$this->upgrade_notice = $this->get_upgrade_notice( $response->new_version );
|
||||
$this->major_untested_plugins = $this->get_untested_plugins( $response->new_version, 'major' );
|
||||
$this->major_untested_plugins = $this->get_untested_plugins( $response->new_version, $version_type );
|
||||
|
||||
$current_version_parts = explode( '.', Constants::get_constant( 'WC_VERSION' ) );
|
||||
$new_version_parts = explode( '.', $this->new_version );
|
||||
|
|
|
@ -18,7 +18,7 @@ $untested_plugins_msg = sprintf(
|
|||
?>
|
||||
<div id="wc_untested_extensions_modal">
|
||||
<div class="wc_untested_extensions_modal--content">
|
||||
<h1><?php esc_html_e( "This is a major update, are you sure you're ready?", 'woocommerce' ); ?></h1>
|
||||
<h1><?php esc_html_e( "Are you sure you're ready?", 'woocommerce' ); ?></h1>
|
||||
<div class="wc_plugin_upgrade_notice extensions_warning">
|
||||
<p><?php echo esc_html( $untested_plugins_msg ); ?></p>
|
||||
|
||||
|
@ -41,7 +41,7 @@ $untested_plugins_msg = sprintf(
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<p><?php esc_html_e( 'As this is a major update, we strongly recommend creating a backup of your site before updating.', 'woocommerce' ); ?> <a href="https://woocommerce.com/2017/05/create-use-backups-woocommerce/" target="_blank"><?php esc_html_e( 'Learn more', 'woocommerce' ); ?></a></p>
|
||||
<p><?php esc_html_e( 'We strongly recommend creating a backup of your site before updating.', 'woocommerce' ); ?> <a href="https://woocommerce.com/2017/05/create-use-backups-woocommerce/" target="_blank"><?php esc_html_e( 'Learn more', 'woocommerce' ); ?></a></p>
|
||||
|
||||
<?php if ( current_user_can( 'update_plugins' ) ) : ?>
|
||||
<div class="actions">
|
||||
|
|
|
@ -191,6 +191,22 @@ class WC_Settings_Emails extends WC_Settings_Page {
|
|||
'id' => 'email_template_options',
|
||||
),
|
||||
|
||||
array(
|
||||
'title' => __( 'Store management insights', 'woocommerce' ),
|
||||
'type' => 'title',
|
||||
'id' => 'email_merchant_notes',
|
||||
),
|
||||
|
||||
array(
|
||||
'title' => __( 'Enable email insights', 'woocommerce' ),
|
||||
'desc' => __( 'Receive email notifications with additional guidance to complete the basic store setup and helpful insights', 'woocommerce' ),
|
||||
'id' => 'woocommerce_merchant_email_notifications',
|
||||
'type' => 'checkbox',
|
||||
'checkboxgroup' => 'start',
|
||||
'default' => 'yes',
|
||||
'autoload' => false,
|
||||
),
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -9,6 +9,16 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
|
||||
?>
|
||||
<div class="wrap woocommerce">
|
||||
<div id="message" class="error inline" style="margin-top:30px">
|
||||
<p>
|
||||
<strong>
|
||||
<?php
|
||||
/* translators: 1: Link URL */
|
||||
echo wp_kses_post( sprintf( __( 'With the release of WooCommerce 4.0, these reports are being replaced. There is a new and better Analytics section available for users running WordPress 5.3+. Head on over to the <a href="%1$s">WooCommerce Analytics</a> or learn more about the new experience in the <a href="https://docs.woocommerce.com/document/woocommerce-analytics/" target="_blank">WooCommerce Analytics documentation</a>.', 'woocommerce' ), esc_url( wc_admin_url( '&path=/analytics/overview' ) ) ) );
|
||||
?>
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
<nav class="nav-tab-wrapper woo-nav-tab-wrapper">
|
||||
<?php
|
||||
foreach ( $reports as $key => $report_group ) {
|
||||
|
|
|
@ -11,7 +11,8 @@ global $wpdb;
|
|||
|
||||
if ( ! defined( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE' ) ) {
|
||||
// Define if we're checking against major or minor versions.
|
||||
define( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE', 'major' );
|
||||
// Since 5.0 all versions are backwards compatible, so there's no more check.
|
||||
define( 'WC_SSR_PLUGIN_UPDATE_RELEASE_VERSION_TYPE', 'none' );
|
||||
}
|
||||
|
||||
$report = wc()->api->get_endpoint_data( '/wc/v3/system_status' );
|
||||
|
@ -844,10 +845,11 @@ if ( 0 < count( $dropins_mu_plugins['mu_plugins'] ) ) :
|
|||
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . wp_kses_post( sprintf( __( 'Page visibility should be <a href="%s" target="_blank">public</a>', 'woocommerce' ), 'https://wordpress.org/support/article/content-visibility/' ) ) . '</mark>';
|
||||
$found_error = true;
|
||||
} else {
|
||||
// Shortcode check.
|
||||
if ( $_page['shortcode_required'] ) {
|
||||
if ( ! $_page['shortcode_present'] ) {
|
||||
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . sprintf( esc_html__( 'Page does not contain the shortcode.', 'woocommerce' ), esc_html( $_page['shortcode'] ) ) . '</mark>';
|
||||
// Shortcode and block check.
|
||||
if ( $_page['shortcode_required'] || $_page['block_required'] ) {
|
||||
if ( ! $_page['shortcode_present'] && ! $_page['block_present'] ) {
|
||||
/* Translators: %1$s: shortcode text, %2$s: block slug. */
|
||||
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . ( $_page['block_required'] ? sprintf( esc_html__( 'Page does not contain the %1$s shortcode or the %2$s block.', 'woocommerce' ), esc_html( $_page['shortcode'] ), esc_html( $_page['block'] ) ) : sprintf( esc_html__( 'Page does not contain the %s shortcode.', 'woocommerce' ), esc_html( $_page['shortcode'] ) ) ) . '</mark>'; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */
|
||||
$found_error = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div id="poststuff" class="woocommerce-reports-wide">
|
||||
<div class="postbox">
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
/**
|
||||
* Blocks Utils
|
||||
*
|
||||
* Used by core components that need to work with blocks.
|
||||
*
|
||||
* @package WooCommerce\Blocks\Utils
|
||||
* @version 5.0.0
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Blocks Utility class.
|
||||
*/
|
||||
class WC_Blocks_Utils {
|
||||
|
||||
/**
|
||||
* Get blocks from a woocommerce page.
|
||||
*
|
||||
* @param string $woo_page_name A woocommerce page e.g. `checkout` or `cart`.
|
||||
* @return array Array of blocks as returned by parse_blocks().
|
||||
*/
|
||||
private static function get_all_blocks_from_page( $woo_page_name ) {
|
||||
$page_id = wc_get_page_id( $woo_page_name );
|
||||
|
||||
$page = get_post( $page_id );
|
||||
if ( ! $page ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$blocks = parse_blocks( $page->post_content );
|
||||
if ( ! $blocks ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all instances of the specified block on a specific woo page
|
||||
* (e.g. `cart` or `checkout` page).
|
||||
*
|
||||
* @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`.
|
||||
* @param string $woo_page_name The woo page to search, e.g. `cart`.
|
||||
* @return array Array of blocks as returned by parse_blocks().
|
||||
*/
|
||||
public static function get_blocks_from_page( $block_name, $woo_page_name ) {
|
||||
$page_blocks = self::get_all_blocks_from_page( $woo_page_name );
|
||||
|
||||
// Get any instances of the specified block.
|
||||
return array_values(
|
||||
array_filter(
|
||||
$page_blocks,
|
||||
function ( $block ) use ( $block_name ) {
|
||||
return ( $block_name === $block['blockName'] );
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given page contains a particular block.
|
||||
*
|
||||
* @param int|WP_Post $page Page post ID or post object.
|
||||
* @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`.
|
||||
* @return bool Boolean value if the page contains the block or not. Null in case the page does not exist.
|
||||
*/
|
||||
public static function has_block_in_page( $page, $block_name ) {
|
||||
$page_to_check = get_post( $page );
|
||||
if ( null === $page_to_check ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$blocks = parse_blocks( $page_to_check->post_content );
|
||||
foreach ( $blocks as $block ) {
|
||||
if ( $block_name === $block['blockName'] ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -698,11 +698,11 @@ final class WC_Cart_Totals {
|
|||
/**
|
||||
* Subtotals are costs before discounts.
|
||||
*
|
||||
* To prevent rounding issues we need to work with the inclusive price where possible.
|
||||
* otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would.
|
||||
* To prevent rounding issues we need to work with the inclusive price where possible
|
||||
* otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would
|
||||
* be 8.325 leading to totals being 1p off.
|
||||
*
|
||||
* Pre tax coupons come off the price the customer thinks they are paying - tax is calculated.
|
||||
* Pre tax coupons come off the price the customer thinks they are paying - tax is calculated
|
||||
* afterwards.
|
||||
*
|
||||
* e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that.
|
||||
|
|
|
@ -210,7 +210,7 @@ class WC_Cart extends WC_Legacy_Cart {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get subtotal.
|
||||
* Get subtotal_tax.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @return float
|
||||
|
|
|
@ -340,6 +340,49 @@ class WC_Comments {
|
|||
return $average;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for getting review counts for multiple products in one query. This is not cached.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param array $product_ids Array of product IDs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_review_counts_for_product_ids( $product_ids ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $product_ids ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$product_id_string_placeholder = substr( str_repeat( ',%s', count( $product_ids ) ), 1 );
|
||||
|
||||
$review_counts = $wpdb->get_results(
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Ignored for allowing interpolation in IN query.
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT comment_post_ID as product_id, COUNT( comment_post_ID ) as review_count
|
||||
FROM $wpdb->comments
|
||||
WHERE
|
||||
comment_parent = 0
|
||||
AND comment_post_ID IN ( $product_id_string_placeholder )
|
||||
AND comment_approved = '1'
|
||||
AND comment_type in ( 'review', '', 'comment' )
|
||||
GROUP BY product_id
|
||||
",
|
||||
$product_ids
|
||||
),
|
||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared.
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
// Convert to key value pairs.
|
||||
$counts = array_replace( array_fill_keys( $product_ids, 0 ), array_column( $review_counts, 'review_count', 'product_id' ) );
|
||||
|
||||
return $counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get product review count for a product (not replies). Please note this is not cached.
|
||||
*
|
||||
|
@ -348,22 +391,9 @@ class WC_Comments {
|
|||
* @return int
|
||||
*/
|
||||
public static function get_review_count_for_product( &$product ) {
|
||||
global $wpdb;
|
||||
$counts = self::get_review_counts_for_product_ids( array( $product->get_id() ) );
|
||||
|
||||
$count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT COUNT(*) FROM $wpdb->comments
|
||||
WHERE comment_parent = 0
|
||||
AND comment_post_ID = %d
|
||||
AND comment_approved = '1'
|
||||
AND comment_type in ( 'review', '', 'comment' )
|
||||
",
|
||||
$product->get_id()
|
||||
)
|
||||
);
|
||||
|
||||
return $count;
|
||||
return $counts[ $product->get_id() ];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -190,7 +190,7 @@ class WC_Emails {
|
|||
$this->init();
|
||||
|
||||
// Email Header, Footer and content hooks.
|
||||
add_action( 'woocommerce_email_header', array( $this, 'email_header' ), 10, 2 );
|
||||
add_action( 'woocommerce_email_header', array( $this, 'email_header' ) );
|
||||
add_action( 'woocommerce_email_footer', array( $this, 'email_footer' ) );
|
||||
add_action( 'woocommerce_email_order_details', array( $this, 'order_downloads' ), 10, 4 );
|
||||
add_action( 'woocommerce_email_order_details', array( $this, 'order_details' ), 10, 4 );
|
||||
|
@ -264,25 +264,16 @@ class WC_Emails {
|
|||
* Get the email header.
|
||||
*
|
||||
* @param mixed $email_heading Heading for the email.
|
||||
* @param WC_Email $email Email object for the email.
|
||||
*/
|
||||
public function email_header( $email_heading, $email ) {
|
||||
wc_get_template(
|
||||
'emails/email-header.php',
|
||||
array(
|
||||
'email_heading' => $email_heading,
|
||||
'email' => $email,
|
||||
)
|
||||
);
|
||||
public function email_header( $email_heading ) {
|
||||
wc_get_template( 'emails/email-header.php', array( 'email_heading' => $email_heading ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the email footer.
|
||||
*
|
||||
* @param WC_Email $email Email object for the email.
|
||||
*/
|
||||
public function email_footer( $email ) {
|
||||
wc_get_template( 'emails/email-footer.php', array( 'email' => $email ) );
|
||||
public function email_footer() {
|
||||
wc_get_template( 'emails/email-footer.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -157,6 +157,10 @@ class WC_Install {
|
|||
'wc_update_450_sanitize_coupons_code',
|
||||
'wc_update_450_db_version',
|
||||
),
|
||||
'5.0.0' => array(
|
||||
'wc_update_500_fix_product_review_count',
|
||||
'wc_update_500_db_version',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,7 +20,7 @@ class WC_Payment_Tokens {
|
|||
* Gets valid tokens from the database based on user defined criteria.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @param array $args Query argyments {
|
||||
* @param array $args Query arguments {
|
||||
* Array of query parameters.
|
||||
*
|
||||
* @type string $token_id Token ID.
|
||||
|
|
|
@ -132,7 +132,7 @@ class WC_Post_Data {
|
|||
/**
|
||||
* Filter to prevent variations from being deleted while switching from a variable product type to a variable product type.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param bool A boolean value of true will delete the variations.
|
||||
* @param WC_Product $product Product data.
|
||||
|
|
|
@ -352,30 +352,105 @@ class WC_Tracker {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get order counts
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_order_counts() {
|
||||
$order_count = array();
|
||||
$order_count_data = wp_count_posts( 'shop_order' );
|
||||
foreach ( wc_get_order_statuses() as $status_slug => $status_name ) {
|
||||
$order_count[ $status_slug ] = $order_count_data->{ $status_slug };
|
||||
}
|
||||
return $order_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine all order data.
|
||||
* Get all order data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_orders() {
|
||||
$order_dates = self::get_order_dates();
|
||||
$order_counts = self::get_order_counts();
|
||||
$order_totals = self::get_order_totals();
|
||||
$args = array(
|
||||
'type' => array( 'shop_order', 'shop_order_refund' ),
|
||||
'limit' => get_option( 'posts_per_page' ),
|
||||
'paged' => 1,
|
||||
);
|
||||
|
||||
return array_merge( $order_dates, $order_counts, $order_totals );
|
||||
$first = time();
|
||||
$last = 0;
|
||||
$processing_first = time();
|
||||
$processing_last = 0;
|
||||
|
||||
$orders = wc_get_orders( $args );
|
||||
$orders_count = count( $orders );
|
||||
|
||||
while ( $orders_count ) {
|
||||
|
||||
foreach ( $orders as $order ) {
|
||||
|
||||
$date_created = (int) $order->get_date_created()->getTimestamp();
|
||||
$type = $order->get_type();
|
||||
$status = $order->get_status();
|
||||
|
||||
if ( 'shop_order' == $type ) {
|
||||
|
||||
// Find the first and last order dates for completed and processing statuses.
|
||||
if ( 'completed' == $status && $date_created < $first ) {
|
||||
$first = $date_created;
|
||||
}
|
||||
if ( 'completed' == $status && $date_created > $last ) {
|
||||
$last = $date_created;
|
||||
}
|
||||
if ( 'processing' == $status && $date_created < $processing_first ) {
|
||||
$processing_first = $date_created;
|
||||
}
|
||||
if ( 'processing' == $status && $date_created > $processing_last ) {
|
||||
$processing_last = $date_created;
|
||||
}
|
||||
|
||||
// Get order counts by status.
|
||||
$status = 'wc-' . $status;
|
||||
|
||||
if ( ! isset( $order_data[ $status ] ) ) {
|
||||
$order_data[ $status ] = 1;
|
||||
} else {
|
||||
$order_data[ $status ] += 1;
|
||||
}
|
||||
|
||||
// Count number of orders by gateway used.
|
||||
$gateway = $order->get_payment_method();
|
||||
|
||||
if ( ! empty( $gateway ) && in_array( $status, array( 'wc-completed', 'wc-refunded', 'wc-processing' ) ) ) {
|
||||
$gateway = 'gateway_' . $gateway;
|
||||
|
||||
if ( ! isset( $order_data[ $gateway ] ) ) {
|
||||
$order_data[ $gateway ] = 1;
|
||||
} else {
|
||||
$order_data[ $gateway ] += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If it is a refunded order (shop_order_refunnd type), add the prefix as this prefix gets
|
||||
// added midway in the if clause.
|
||||
$status = 'wc-' . $status;
|
||||
}
|
||||
|
||||
// Calculate the gross total for 'completed' and 'processing' orders.
|
||||
$total = $order->get_total();
|
||||
|
||||
if ( in_array( $status, array( 'wc-completed', 'wc-refunded' ) ) ) {
|
||||
if ( ! isset( $order_data['gross'] ) ) {
|
||||
$order_data['gross'] = $total;
|
||||
} else {
|
||||
$order_data['gross'] += $total;
|
||||
}
|
||||
} elseif ( 'wc-processing' == $status ) {
|
||||
if ( ! isset( $order_data['processing_gross'] ) ) {
|
||||
$order_data['processing_gross'] = $total;
|
||||
} else {
|
||||
$order_data['processing_gross'] += $total;
|
||||
}
|
||||
}
|
||||
}
|
||||
$args['paged']++;
|
||||
|
||||
$orders = wc_get_orders( $args );
|
||||
$orders_count = count( $orders );
|
||||
}
|
||||
|
||||
$order_data['first'] = gmdate( 'Y-m-d H:i:s', $first );
|
||||
$order_data['last'] = gmdate( 'Y-m-d H:i:s', $last );
|
||||
$order_data['processing_first'] = gmdate( 'Y-m-d H:i:s', $processing_first );
|
||||
$order_data['processing_last'] = gmdate( 'Y-m-d H:i:s', $processing_last );
|
||||
|
||||
return $order_data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -543,94 +618,12 @@ class WC_Tracker {
|
|||
/**
|
||||
* Get order totals
|
||||
*
|
||||
* @deprecated 5.1.0 Logic moved to get_orders.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_order_totals() {
|
||||
global $wpdb;
|
||||
|
||||
$gross_total = $wpdb->get_var(
|
||||
"
|
||||
SELECT
|
||||
SUM( order_meta.meta_value ) AS 'gross_total'
|
||||
FROM {$wpdb->prefix}posts AS orders
|
||||
LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID
|
||||
WHERE order_meta.meta_key = '_order_total'
|
||||
AND orders.post_status in ( 'wc-completed', 'wc-refunded' )
|
||||
GROUP BY order_meta.meta_key
|
||||
"
|
||||
);
|
||||
|
||||
if ( is_null( $gross_total ) ) {
|
||||
$gross_total = 0;
|
||||
}
|
||||
|
||||
$processing_gross_total = $wpdb->get_var(
|
||||
"
|
||||
SELECT
|
||||
SUM( order_meta.meta_value ) AS 'gross_total'
|
||||
FROM {$wpdb->prefix}posts AS orders
|
||||
LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID
|
||||
WHERE order_meta.meta_key = '_order_total'
|
||||
AND orders.post_status = 'wc-processing'
|
||||
GROUP BY order_meta.meta_key
|
||||
"
|
||||
);
|
||||
|
||||
if ( is_null( $processing_gross_total ) ) {
|
||||
$processing_gross_total = 0;
|
||||
}
|
||||
|
||||
return array(
|
||||
'gross' => $gross_total,
|
||||
'processing_gross' => $processing_gross_total,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last order date
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function get_order_dates() {
|
||||
global $wpdb;
|
||||
|
||||
$min_max = $wpdb->get_row(
|
||||
"
|
||||
SELECT
|
||||
MIN( post_date_gmt ) as 'first', MAX( post_date_gmt ) as 'last'
|
||||
FROM {$wpdb->prefix}posts
|
||||
WHERE post_type = 'shop_order'
|
||||
AND post_status = 'wc-completed'
|
||||
",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( is_null( $min_max ) ) {
|
||||
$min_max = array(
|
||||
'first' => '-',
|
||||
'last' => '-',
|
||||
);
|
||||
}
|
||||
|
||||
$processing_min_max = $wpdb->get_row(
|
||||
"
|
||||
SELECT
|
||||
MIN( post_date_gmt ) as 'processing_first', MAX( post_date_gmt ) as 'processing_last'
|
||||
FROM {$wpdb->prefix}posts
|
||||
WHERE post_type = 'shop_order'
|
||||
AND post_status = 'wc-processing'
|
||||
",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( is_null( $processing_min_max ) ) {
|
||||
$processing_min_max = array(
|
||||
'processing_first' => '-',
|
||||
'processing_last' => '-',
|
||||
);
|
||||
}
|
||||
|
||||
return array_merge( $min_max, $processing_min_max );
|
||||
wc_deprecated_function( 'WC_Tracker::get_order_totals', '5.1.0', '' );
|
||||
return self::get_orders();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -660,49 +653,6 @@ class WC_Tracker {
|
|||
return ( '0' !== $result ) ? 'Yes' : 'No';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blocks from a woocommerce page.
|
||||
*
|
||||
* @param string $woo_page_name A woocommerce page e.g. `checkout` or `cart`.
|
||||
* @return array Array of blocks as returned by parse_blocks().
|
||||
*/
|
||||
private static function get_all_blocks_from_page( $woo_page_name ) {
|
||||
$page_id = wc_get_page_id( $woo_page_name );
|
||||
|
||||
$page = get_post( $page_id );
|
||||
if ( ! $page ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$blocks = parse_blocks( $page->post_content );
|
||||
if ( ! $blocks ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all instances of the specified block on a specific woo page
|
||||
* (e.g. `cart` or `checkout` page).
|
||||
*
|
||||
* @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`.
|
||||
* @param string $woo_page_name The woo page to search, e.g. `cart`.
|
||||
* @return array Array of blocks as returned by parse_blocks().
|
||||
*/
|
||||
private static function get_blocks_from_page( $block_name, $woo_page_name ) {
|
||||
$page_blocks = self::get_all_blocks_from_page( $woo_page_name );
|
||||
|
||||
// Get any instances of the specified block.
|
||||
return array_values(
|
||||
array_filter(
|
||||
$page_blocks,
|
||||
function ( $block ) use ( $block_name ) {
|
||||
return ( $block_name === $block['blockName'] );
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tracker data for a specific block type on a woocommerce page.
|
||||
|
@ -714,7 +664,7 @@ class WC_Tracker {
|
|||
* - block_attributes
|
||||
*/
|
||||
public static function get_block_tracker_data( $block_name, $woo_page_name ) {
|
||||
$blocks = self::get_blocks_from_page( $block_name, $woo_page_name );
|
||||
$blocks = WC_Blocks_Utils::get_blocks_from_page( $block_name, $woo_page_name );
|
||||
|
||||
$block_present = false;
|
||||
$attributes = array();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
|
||||
/**
|
||||
|
@ -22,7 +23,7 @@ final class WooCommerce {
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
public $version = '5.0.0';
|
||||
public $version = '5.1.0';
|
||||
|
||||
/**
|
||||
* WooCommerce Schema version.
|
||||
|
@ -202,6 +203,10 @@ final class WooCommerce {
|
|||
add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 );
|
||||
add_action( 'activated_plugin', array( $this, 'activated_plugin' ) );
|
||||
add_action( 'deactivated_plugin', array( $this, 'deactivated_plugin' ) );
|
||||
add_filter( 'woocommerce_rest_prepare_note', array( 'WC_Admin_Notices', 'prepare_note_with_nonce' ) );
|
||||
|
||||
// These classes set up hooks on instantiation.
|
||||
wc_get_container()->get( DownloadPermissionsAdjuster::class );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -421,6 +426,7 @@ final class WooCommerce {
|
|||
include_once WC_ABSPATH . 'includes/queue/class-wc-action-queue.php';
|
||||
include_once WC_ABSPATH . 'includes/queue/class-wc-queue.php';
|
||||
include_once WC_ABSPATH . 'includes/admin/marketplace-suggestions/class-wc-marketplace-updater.php';
|
||||
include_once WC_ABSPATH . 'includes/blocks/class-wc-blocks-utils.php';
|
||||
|
||||
/**
|
||||
* Data stores - used to store and retrieve CRUD object data from the database.
|
||||
|
@ -901,7 +907,7 @@ final class WooCommerce {
|
|||
'https://wordpress.org/plugins/woocommerce/',
|
||||
'https://github.com/woocommerce/woocommerce/releases'
|
||||
);
|
||||
printf( '<div class="error"><p>%s %s</p></div>', $message_one, $message_two ); /* WPCS: xss ok. */
|
||||
printf( '<div class="error"><p>%s %s</p></div>', $message_one, $message_two ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,38 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
*/
|
||||
class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store_Interface {
|
||||
|
||||
/**
|
||||
* Names of the database fields for the download permissions table.
|
||||
*/
|
||||
const DOWNLOAD_PERMISSION_DB_FIELDS = array(
|
||||
'download_id',
|
||||
'product_id',
|
||||
'user_id',
|
||||
'user_email',
|
||||
'order_id',
|
||||
'order_key',
|
||||
'downloads_remaining',
|
||||
'access_granted',
|
||||
'download_count',
|
||||
'access_expires',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create download permission for a user, from an array of data.
|
||||
*
|
||||
* @param array $data Data to create the permission for.
|
||||
* @returns int The database id of the created permission, or false if the permission creation failed.
|
||||
*/
|
||||
public function create_from_data( $data ) {
|
||||
$data = array_intersect_key( $data, array_flip( self::DOWNLOAD_PERMISSION_DB_FIELDS ) );
|
||||
|
||||
$id = $this->insert_new_download_permission( $data );
|
||||
|
||||
do_action( 'woocommerce_grant_product_download_access', $data );
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create download permission for a user.
|
||||
*
|
||||
|
@ -29,18 +61,41 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
|
|||
$download->set_access_granted( time() );
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'download_id' => $download->get_download_id( 'edit' ),
|
||||
'product_id' => $download->get_product_id( 'edit' ),
|
||||
'user_id' => $download->get_user_id( 'edit' ),
|
||||
'user_email' => $download->get_user_email( 'edit' ),
|
||||
'order_id' => $download->get_order_id( 'edit' ),
|
||||
'order_key' => $download->get_order_key( 'edit' ),
|
||||
'downloads_remaining' => $download->get_downloads_remaining( 'edit' ),
|
||||
'access_granted' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ),
|
||||
'download_count' => $download->get_download_count( 'edit' ),
|
||||
'access_expires' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null,
|
||||
);
|
||||
$data = array();
|
||||
foreach ( self::DOWNLOAD_PERMISSION_DB_FIELDS as $db_field_name ) {
|
||||
$value = call_user_func( array( $download, 'get_' . $db_field_name ), 'edit' );
|
||||
$data[ $db_field_name ] = $value;
|
||||
}
|
||||
|
||||
$inserted_id = $this->insert_new_download_permission( $data );
|
||||
if ( $inserted_id ) {
|
||||
$download->set_id( $inserted_id );
|
||||
$download->apply_changes();
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_grant_product_download_access', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create download permission for a user, from an array of data.
|
||||
* Assumes that all the keys in the passed data are valid.
|
||||
*
|
||||
* @param array $data Data to create the permission for.
|
||||
* @return int The database id of the created permission, or false if the permission creation failed.
|
||||
*/
|
||||
private function insert_new_download_permission( $data ) {
|
||||
global $wpdb;
|
||||
|
||||
// Always set a access granted date.
|
||||
if ( ! isset( $data['access_granted'] ) ) {
|
||||
$data['access_granted'] = time();
|
||||
}
|
||||
|
||||
$data['access_granted'] = $this->adjust_date_for_db( $data['access_granted'] );
|
||||
|
||||
if ( isset( $data['access_expires'] ) ) {
|
||||
$data['access_expires'] = $this->adjust_date_for_db( $data['access_expires'] );
|
||||
}
|
||||
|
||||
$format = array(
|
||||
'%s',
|
||||
|
@ -61,12 +116,29 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
|
|||
apply_filters( 'woocommerce_downloadable_file_permission_format', $format, $data )
|
||||
);
|
||||
|
||||
if ( $result ) {
|
||||
$download->set_id( $wpdb->insert_id );
|
||||
$download->apply_changes();
|
||||
return $result ? $wpdb->insert_id : false;
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_grant_product_download_access', $data );
|
||||
/**
|
||||
* Adjust a date value to be inserted in the database.
|
||||
*
|
||||
* @param mixed $date The date value. Can be a WC_DateTime, a timestamp, or anything else that "date" recognizes.
|
||||
* @return string The date converted to 'Y-m-d' format.
|
||||
* @throws Exception The passed value can't be converted to a date.
|
||||
*/
|
||||
private function adjust_date_for_db( $date ) {
|
||||
if ( 'WC_DateTime' === get_class( $date ) ) {
|
||||
$date = $date->getTimestamp();
|
||||
}
|
||||
|
||||
$adjusted_date = date( 'Y-m-d', $date ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
|
||||
if ( $adjusted_date ) {
|
||||
return $adjusted_date;
|
||||
}
|
||||
|
||||
$msg = sprintf( __( "I don't know how to get a date from a %s", 'woocommerce' ), is_object( $date ) ? get_class( $date ) : gettype( $date ) );
|
||||
throw new Exception( $msg );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,8 +200,10 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
|
|||
'order_id' => $download->get_order_id( 'edit' ),
|
||||
'order_key' => $download->get_order_key( 'edit' ),
|
||||
'downloads_remaining' => $download->get_downloads_remaining( 'edit' ),
|
||||
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
'access_granted' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ),
|
||||
'download_count' => $download->get_download_count( 'edit' ),
|
||||
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
'access_expires' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null,
|
||||
);
|
||||
|
||||
|
@ -412,7 +486,7 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store
|
|||
)
|
||||
ORDER BY permissions.order_id, permissions.product_id, permissions.permission_id;",
|
||||
$customer_id,
|
||||
date( 'Y-m-d', current_time( 'timestamp' ) )
|
||||
date( 'Y-m-d', current_time( 'timestamp' ) ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -906,7 +906,14 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
|
|||
$this->prime_order_item_caches_for_orders( $order_ids, $query_vars );
|
||||
|
||||
foreach ( $query->posts as $post ) {
|
||||
$orders[] = wc_get_order( $post );
|
||||
$order = wc_get_order( $post );
|
||||
|
||||
// If the order returns false, don't add it to the list.
|
||||
if ( false === $order ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orders[] = $order;
|
||||
}
|
||||
|
||||
return $orders;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
use Automattic\WooCommerce\Utilities\NumberUtil;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
@ -265,6 +266,10 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
|
|||
$this->handle_updated_props( $product );
|
||||
$this->clear_caches( $product );
|
||||
|
||||
wc_get_container()
|
||||
->get( DownloadPermissionsAdjuster::class )
|
||||
->maybe_schedule_adjust_download_permissions( $product );
|
||||
|
||||
$product->apply_changes();
|
||||
|
||||
do_action( 'woocommerce_update_product', $product->get_id(), $product );
|
||||
|
|
|
@ -74,33 +74,41 @@ class WC_Shipping_Zone_Data_Store extends WC_Data_Store_WP implements WC_Shippin
|
|||
public function read( &$zone ) {
|
||||
global $wpdb;
|
||||
|
||||
$zone_data = false;
|
||||
// Zone 0 is used as a default if no other zones fit.
|
||||
if ( 0 === $zone->get_id() || '0' === $zone->get_id() ) {
|
||||
$this->read_zone_locations( $zone );
|
||||
$zone->set_zone_name( __( 'Locations not covered by your other zones', 'woocommerce' ) );
|
||||
$zone->read_meta_data();
|
||||
$zone->set_object_read( true );
|
||||
|
||||
/**
|
||||
* Indicate that the WooCommerce shipping zone has been loaded.
|
||||
*
|
||||
* @param WC_Shipping_Zone $zone The shipping zone that has been loaded.
|
||||
*/
|
||||
do_action( 'woocommerce_shipping_zone_loaded', $zone );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 0 !== $zone->get_id() || '0' !== $zone->get_id() ) {
|
||||
$zone_data = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1",
|
||||
$zone->get_id()
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $zone_data ) {
|
||||
throw new Exception( __( 'Invalid data store.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( 0 === $zone->get_id() || '0' === $zone->get_id() ) {
|
||||
$this->read_zone_locations( $zone );
|
||||
$zone->set_zone_name( __( 'Locations not covered by your other zones', 'woocommerce' ) );
|
||||
$zone->read_meta_data();
|
||||
$zone->set_object_read( true );
|
||||
do_action( 'woocommerce_shipping_zone_loaded', $zone );
|
||||
} elseif ( $zone_data ) {
|
||||
$zone->set_zone_name( $zone_data->zone_name );
|
||||
$zone->set_zone_order( $zone_data->zone_order );
|
||||
$this->read_zone_locations( $zone );
|
||||
$zone->read_meta_data();
|
||||
$zone->set_object_read( true );
|
||||
|
||||
/** This action is documented in includes/datastores/class-wc-shipping-zone-data-store.php. */
|
||||
do_action( 'woocommerce_shipping_zone_loaded', $zone );
|
||||
} else {
|
||||
throw new Exception( __( 'Invalid data store.', 'woocommerce' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -92,10 +92,25 @@ if ( ! class_exists( 'WC_Email_New_Order' ) ) :
|
|||
$this->object = $order;
|
||||
$this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() );
|
||||
$this->placeholders['{order_number}'] = $this->object->get_order_number();
|
||||
|
||||
$email_already_sent = $order->get_meta( '_new_order_email_sent' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls if new order emails can be resend multiple times.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @param bool $allows Defaults to true.
|
||||
*/
|
||||
if ( 'true' === $email_already_sent && ! apply_filters( 'woocommerce_new_order_email_allows_resend', false ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_enabled() && $this->get_recipient() ) {
|
||||
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
|
||||
|
||||
$order->update_meta_data( '_new_order_email_sent', 'true' );
|
||||
$order->save();
|
||||
}
|
||||
|
||||
$this->restore_locale();
|
||||
|
|
|
@ -1162,7 +1162,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
|
|||
|
||||
/**
|
||||
* Returns a mini-report on WC pages and if they are configured correctly:
|
||||
* Present, visible, and including the correct shortcode.
|
||||
* Present, visible, and including the correct shortcode or block.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
@ -1172,22 +1172,27 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
|
|||
_x( 'Shop base', 'Page setting', 'woocommerce' ) => array(
|
||||
'option' => 'woocommerce_shop_page_id',
|
||||
'shortcode' => '',
|
||||
'block' => '',
|
||||
),
|
||||
_x( 'Cart', 'Page setting', 'woocommerce' ) => array(
|
||||
'option' => 'woocommerce_cart_page_id',
|
||||
'shortcode' => '[' . apply_filters( 'woocommerce_cart_shortcode_tag', 'woocommerce_cart' ) . ']',
|
||||
'block' => 'woocommerce/cart',
|
||||
),
|
||||
_x( 'Checkout', 'Page setting', 'woocommerce' ) => array(
|
||||
'option' => 'woocommerce_checkout_page_id',
|
||||
'shortcode' => '[' . apply_filters( 'woocommerce_checkout_shortcode_tag', 'woocommerce_checkout' ) . ']',
|
||||
'block' => 'woocommerce/checkout',
|
||||
),
|
||||
_x( 'My account', 'Page setting', 'woocommerce' ) => array(
|
||||
'option' => 'woocommerce_myaccount_page_id',
|
||||
'shortcode' => '[' . apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) . ']',
|
||||
'block' => '',
|
||||
),
|
||||
_x( 'Terms and conditions', 'Page setting', 'woocommerce' ) => array(
|
||||
'option' => 'woocommerce_terms_page_id',
|
||||
'shortcode' => '',
|
||||
'block' => '',
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -1199,6 +1204,8 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
|
|||
$page_visible = false;
|
||||
$shortcode_present = false;
|
||||
$shortcode_required = false;
|
||||
$block_present = false;
|
||||
$block_required = false;
|
||||
|
||||
// Page checks.
|
||||
if ( $page_id ) {
|
||||
|
@ -1220,6 +1227,12 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
|
|||
}
|
||||
}
|
||||
|
||||
// Block checks.
|
||||
if ( $values['block'] && get_post( $page_id ) ) {
|
||||
$block_required = true;
|
||||
$block_present = WC_Blocks_Utils::has_block_in_page( $page_id, $values['block'] );
|
||||
}
|
||||
|
||||
// Wrap up our findings into an output array.
|
||||
$pages_output[] = array(
|
||||
'page_name' => $page_name,
|
||||
|
@ -1228,8 +1241,11 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
|
|||
'page_exists' => $page_exists,
|
||||
'page_visible' => $page_visible,
|
||||
'shortcode' => $values['shortcode'],
|
||||
'block' => $values['block'],
|
||||
'shortcode_required' => $shortcode_required,
|
||||
'shortcode_present' => $shortcode_present,
|
||||
'block_present' => $block_present,
|
||||
'block_required' => $block_required,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -574,10 +574,33 @@ function wc_price( $price, $args = array() ) {
|
|||
)
|
||||
);
|
||||
|
||||
$original_price = $price;
|
||||
|
||||
// Convert to float to avoid issues on PHP 8.
|
||||
$price = (float) $price;
|
||||
|
||||
$unformatted_price = $price;
|
||||
$negative = $price < 0;
|
||||
$price = apply_filters( 'raw_woocommerce_price', floatval( $negative ? $price * -1 : $price ) );
|
||||
$price = apply_filters( 'formatted_woocommerce_price', number_format( $price, $args['decimals'], $args['decimal_separator'], $args['thousand_separator'] ), $price, $args['decimals'], $args['decimal_separator'], $args['thousand_separator'] );
|
||||
|
||||
/**
|
||||
* Filter raw price.
|
||||
*
|
||||
* @param float $raw_price Raw price.
|
||||
* @param float|string $original_price Original price as float, or empty string. Since 5.0.0.
|
||||
*/
|
||||
$price = apply_filters( 'raw_woocommerce_price', $negative ? $price * -1 : $price, $original_price );
|
||||
|
||||
/**
|
||||
* Filter formatted price.
|
||||
*
|
||||
* @param float $formatted_price Formatted price.
|
||||
* @param float $price Unformatted price.
|
||||
* @param int $decimals Number of decimals.
|
||||
* @param string $decimal_separator Decimal separator.
|
||||
* @param string $thousand_separator Thousand separator.
|
||||
* @param float|string $original_price Original price as float, or empty string. Since 5.0.0.
|
||||
*/
|
||||
$price = apply_filters( 'formatted_woocommerce_price', number_format( $price, $args['decimals'], $args['decimal_separator'], $args['thousand_separator'] ), $price, $args['decimals'], $args['decimal_separator'], $args['thousand_separator'], $original_price );
|
||||
|
||||
if ( apply_filters( 'woocommerce_price_trim_zeros', false ) && $args['decimals'] > 0 ) {
|
||||
$price = wc_trim_zeros( $price );
|
||||
|
@ -597,8 +620,9 @@ function wc_price( $price, $args = array() ) {
|
|||
* @param string $price Formatted price.
|
||||
* @param array $args Pass on the args.
|
||||
* @param float $unformatted_price Price as float to allow plugins custom formatting. Since 3.2.0.
|
||||
* @param float|string $original_price Original price as float, or empty string. Since 5.0.0.
|
||||
*/
|
||||
return apply_filters( 'wc_price', $return, $price, $args, $unformatted_price );
|
||||
return apply_filters( 'wc_price', $return, $price, $args, $unformatted_price, $original_price );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -651,7 +651,7 @@ function wc_get_product_id_by_sku( $sku ) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get attibutes/data for an individual variation from the database and maintain it's integrity.
|
||||
* Get attributes/data for an individual variation from the database and maintain it's integrity.
|
||||
*
|
||||
* @since 2.4.0
|
||||
* @param int $variation_id Variation ID.
|
||||
|
|
|
@ -3568,8 +3568,13 @@ function wc_empty_cart_message() {
|
|||
*/
|
||||
function wc_page_noindex() {
|
||||
if ( is_page( wc_get_page_id( 'cart' ) ) || is_page( wc_get_page_id( 'checkout' ) ) || is_page( wc_get_page_id( 'myaccount' ) ) ) {
|
||||
// Adds support for WP 5.7.
|
||||
if ( function_exists( 'wp_robots_no_robots' ) ) {
|
||||
wp_robots_no_robots();
|
||||
} else {
|
||||
wp_no_robots();
|
||||
}
|
||||
}
|
||||
}
|
||||
add_action( 'wp_head', 'wc_page_noindex' );
|
||||
|
||||
|
|
|
@ -2219,3 +2219,67 @@ function wc_update_450_sanitize_coupons_code() {
|
|||
delete_option( 'woocommerce_update_450_last_coupon_id' );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes product review count that might have been incorrect.
|
||||
*
|
||||
* See @link https://github.com/woocommerce/woocommerce/issues/27688.
|
||||
*/
|
||||
function wc_update_500_fix_product_review_count() {
|
||||
global $wpdb;
|
||||
|
||||
$product_id = 0;
|
||||
$last_product_id = get_option( 'woocommerce_update_500_last_product_id', '0' );
|
||||
|
||||
$products_data = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"
|
||||
SELECT post_id, meta_value
|
||||
FROM $wpdb->postmeta
|
||||
JOIN $wpdb->posts
|
||||
ON $wpdb->postmeta.post_id = $wpdb->posts.ID
|
||||
WHERE
|
||||
post_type = 'product'
|
||||
AND post_status = 'publish'
|
||||
AND post_id > %d
|
||||
AND meta_key = '_wc_review_count'
|
||||
ORDER BY post_id ASC
|
||||
LIMIT 10
|
||||
",
|
||||
$last_product_id
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( empty( $products_data ) ) {
|
||||
delete_option( 'woocommerce_update_500_last_product_id' );
|
||||
return false;
|
||||
}
|
||||
|
||||
$product_ids_to_check = array_column( $products_data, 'post_id' );
|
||||
$actual_review_counts = WC_Comments::get_review_counts_for_product_ids( $product_ids_to_check );
|
||||
|
||||
foreach ( $products_data as $product_data ) {
|
||||
$product_id = intval( $product_data['post_id'] );
|
||||
$current_review_count = intval( $product_data['meta_value'] );
|
||||
|
||||
if ( intval( $actual_review_counts[ $product_id ] ) !== $current_review_count ) {
|
||||
WC_Comments::clear_transients( $product_id );
|
||||
}
|
||||
}
|
||||
|
||||
// Start the run again.
|
||||
if ( $product_id ) {
|
||||
return update_option( 'woocommerce_update_500_last_product_id', $product_id );
|
||||
}
|
||||
|
||||
delete_option( 'woocommerce_update_500_last_product_id' );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update DB version to 5.0.0.
|
||||
*/
|
||||
function wc_update_500_db_version() {
|
||||
WC_Install::update_db_version( '5.0.0' );
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "woocommerce",
|
||||
"title": "WooCommerce",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.0",
|
||||
"homepage": "https://woocommerce.com/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
|
|||
Requires at least: 5.3
|
||||
Tested up to: 5.6
|
||||
Requires PHP: 7.0
|
||||
Stable tag: 4.8.0
|
||||
Stable tag: 4.9.2
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
@ -160,11 +160,6 @@ WooCommerce comes with some sample data you can use to see how products look; im
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 4.9.0 - 2021-01-xx =
|
||||
= 5.1.0 2021-03-xx =
|
||||
|
||||
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/changelog.txt).
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 4.0 =
|
||||
4.0 is a major update. Make a full site backup, update your theme and extensions, and [review update best practices](https://docs.woocommerce.com/document/how-to-update-your-site) before upgrading.
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
namespace Automattic\WooCommerce;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider;
|
||||
|
||||
/**
|
||||
* PSR11 compliant dependency injection container for WooCommerce.
|
||||
|
@ -33,6 +34,7 @@ final class Container implements \Psr\Container\ContainerInterface {
|
|||
*/
|
||||
private $service_providers = array(
|
||||
ProxiesServiceProvider::class,
|
||||
DownloadPermissionsAdjusterServiceProvider::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
/**
|
||||
* DownloadPermissionsAdjusterServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
|
||||
/**
|
||||
* Service provider for the DownloadPermissionsAdjuster class.
|
||||
*/
|
||||
class DownloadPermissionsAdjusterServiceProvider extends AbstractServiceProvider {
|
||||
|
||||
/**
|
||||
* The classes/interfaces that are serviced by this service provider.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $provides = array(
|
||||
DownloadPermissionsAdjuster::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Register the classes.
|
||||
*/
|
||||
public function register() {
|
||||
$this->share( DownloadPermissionsAdjuster::class );
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* Proxies class file.
|
||||
* ProxiesServiceProvider class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
/**
|
||||
* DownloadPermissionsAdjuster class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal;
|
||||
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* Class to adjust download permissions on product save.
|
||||
*/
|
||||
class DownloadPermissionsAdjuster {
|
||||
|
||||
/**
|
||||
* The downloads data store to use.
|
||||
*
|
||||
* @var WC_Data_Store
|
||||
*/
|
||||
private $downloads_data_store;
|
||||
|
||||
/**
|
||||
* Class initialization, to be executed when the class is resolved by the container.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final public function init() {
|
||||
$this->downloads_data_store = wc_get_container()->get( LegacyProxy::class )->get_instance_of( \WC_Data_Store::class, 'customer-download' );
|
||||
add_action( 'adjust_download_permissions', array( $this, 'adjust_download_permissions' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a download permissions adjustment for a product if necessary.
|
||||
* This should be executed whenever a product is saved.
|
||||
*
|
||||
* @param \WC_Product $product The product to schedule a download permission adjustments for.
|
||||
*/
|
||||
public function maybe_schedule_adjust_download_permissions( \WC_Product $product ) {
|
||||
$children_ids = $product->get_children();
|
||||
if ( ! $children_ids ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scheduled_action_args = array( $product->get_id() );
|
||||
|
||||
$already_scheduled_actions =
|
||||
WC()->call_function(
|
||||
'as_get_scheduled_actions',
|
||||
array(
|
||||
'hook' => 'adjust_download_permissions',
|
||||
'args' => $scheduled_action_args,
|
||||
'status' => \ActionScheduler_Store::STATUS_PENDING,
|
||||
),
|
||||
'ids'
|
||||
);
|
||||
|
||||
if ( empty( $already_scheduled_actions ) ) {
|
||||
WC()->call_function(
|
||||
'as_schedule_single_action',
|
||||
WC()->call_function( 'time' ) + 1,
|
||||
'adjust_download_permissions',
|
||||
$scheduled_action_args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create additional download permissions for variations if necessary.
|
||||
*
|
||||
* When a simple downloadable product is converted to a variable product,
|
||||
* existing download permissions are still present in the database but they don't apply anymore.
|
||||
* This method creates additional download permissions for the variations based on
|
||||
* the old existing ones for the main product.
|
||||
*
|
||||
* The procedure is as follows. For each existing download permission for the parent product,
|
||||
* check if there's any variation offering the same file for download (the file URL, not name, is checked).
|
||||
* If that is found, check if an equivalent permission exists (equivalent means for the same file and with
|
||||
* the same order id and customer id). If no equivalent permission exists, create it.
|
||||
*
|
||||
* @param int $product_id The id of the product to check permissions for.
|
||||
*/
|
||||
public function adjust_download_permissions( int $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
|
||||
$children_ids = $product->get_children();
|
||||
if ( ! $children_ids ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parent_downloads = $this->get_download_files_and_permissions( $product );
|
||||
if ( ! $parent_downloads ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$children_with_downloads = array();
|
||||
foreach ( $children_ids as $child_id ) {
|
||||
$child = wc_get_product( $child_id );
|
||||
$children_with_downloads[ $child_id ] = $this->get_download_files_and_permissions( $child );
|
||||
}
|
||||
|
||||
foreach ( $parent_downloads['permission_data_by_file_order_user'] as $parent_file_order_and_user => $parent_download_data ) {
|
||||
foreach ( $children_with_downloads as $child_id => $child_download_data ) {
|
||||
$file_url = $parent_download_data['file'];
|
||||
|
||||
$must_create_permission =
|
||||
// The variation offers the same file as the parent for download...
|
||||
in_array( $file_url, array_keys( $child_download_data['download_ids_by_file_url'] ), true ) &&
|
||||
// ...but no equivalent download permission (same file URL, order id and user id) exists.
|
||||
! array_key_exists( $parent_file_order_and_user, $child_download_data['permission_data_by_file_order_user'] );
|
||||
|
||||
if ( $must_create_permission ) {
|
||||
// The new child download permission is a copy of the parent's,
|
||||
// but with the product and download ids changed to match those of the variation.
|
||||
$new_download_data = $parent_download_data['data'];
|
||||
$new_download_data['product_id'] = $child_id;
|
||||
$new_download_data['download_id'] = $child_download_data['download_ids_by_file_url'][ $file_url ];
|
||||
$this->downloads_data_store->create_from_data( $new_download_data );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the existing downloadable files and download permissions for a given product.
|
||||
* The returned value is an array with two keys:
|
||||
*
|
||||
* - download_ids_by_file_url: an associative array of file url => download_id.
|
||||
* - permission_data_by_file_order_user: an associative array where key is "file_url:customer_id:order_id" and value is the full permission data set.
|
||||
*
|
||||
* @param \WC_Product $product The product to get the downloadable files and permissions for.
|
||||
* @return array[] Information about the downloadable files and permissions for the product.
|
||||
*/
|
||||
private function get_download_files_and_permissions( \WC_Product $product ) {
|
||||
$result = array(
|
||||
'permission_data_by_file_order_user' => array(),
|
||||
'download_ids_by_file_url' => array(),
|
||||
);
|
||||
$downloads = $product->get_downloads();
|
||||
foreach ( $downloads as $download ) {
|
||||
$result['download_ids_by_file_url'][ $download->get_file() ] = $download->get_id();
|
||||
}
|
||||
|
||||
$permissions = $this->downloads_data_store->get_downloads( array( 'product_id' => $product->get_id() ) );
|
||||
foreach ( $permissions as $permission ) {
|
||||
$permission_data = (array) $permission->data;
|
||||
if ( array_key_exists( $permission_data['download_id'], $downloads ) ) {
|
||||
$file = $downloads[ $permission_data['download_id'] ]->get_file();
|
||||
$data = array(
|
||||
'file' => $file,
|
||||
'data' => (array) $permission->data,
|
||||
);
|
||||
$result['permission_data_by_file_order_user'][ "${file}:${permission_data['user_id']}:${permission_data['order_id']}" ] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -72,10 +72,18 @@ class Packages {
|
|||
// Proxies "activated_plugin" hook for embedded packages listen on WC plugin activation
|
||||
// https://github.com/woocommerce/woocommerce/issues/28697.
|
||||
if ( is_admin() ) {
|
||||
$woocommerce_activated_plugin = get_transient( 'woocommerce_activated_plugin' );
|
||||
if ( $woocommerce_activated_plugin ) {
|
||||
$activated_plugin = get_transient( 'woocommerce_activated_plugin' );
|
||||
if ( $activated_plugin ) {
|
||||
delete_transient( 'woocommerce_activated_plugin' );
|
||||
do_action( 'woocommerce_activated_plugin', $woocommerce_activated_plugin );
|
||||
|
||||
/**
|
||||
* WooCommerce is activated hook.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @param bool $activated_plugin Activated plugin path,
|
||||
* generally woocommerce/woocommerce.php.
|
||||
*/
|
||||
do_action( 'woocommerce_activated_plugin', $activated_plugin );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,11 @@ class LegacyProxy {
|
|||
return $class_name::instance( ...$args );
|
||||
}
|
||||
|
||||
// If the class has a "load" method, use it.
|
||||
if ( method_exists( $class_name, 'load' ) ) {
|
||||
return $class_name::load( ...$args );
|
||||
}
|
||||
|
||||
// Fallback to simply creating a new instance of the class.
|
||||
return new $class_name( ...$args );
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Prevent anyone from accidentally adding code to these directories.
|
||||
# This will break any PRs that do, revealing ths mistake they made.
|
||||
README.md
|
||||
# This will break any PRs that do, revealing the mistake they made.
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
|
|
|
@ -30,7 +30,7 @@ $wrapper_classes = apply_filters(
|
|||
'woocommerce_single_product_image_gallery_classes',
|
||||
array(
|
||||
'woocommerce-product-gallery',
|
||||
'woocommerce-product-gallery--' . ( $product->get_image_id() ? 'with-images' : 'without-images' ),
|
||||
'woocommerce-product-gallery--' . ( $post_thumbnail_id ? 'with-images' : 'without-images' ),
|
||||
'woocommerce-product-gallery--columns-' . absint( $columns ),
|
||||
'images',
|
||||
)
|
||||
|
@ -39,7 +39,7 @@ $wrapper_classes = apply_filters(
|
|||
<div class="<?php echo esc_attr( implode( ' ', array_map( 'sanitize_html_class', $wrapper_classes ) ) ); ?>" data-columns="<?php echo esc_attr( $columns ); ?>" style="opacity: 0; transition: opacity .25s ease-in-out;">
|
||||
<figure class="woocommerce-product-gallery__wrapper">
|
||||
<?php
|
||||
if ( $product->get_image_id() ) {
|
||||
if ( $post_thumbnail_id ) {
|
||||
$html = wc_get_gallery_image_html( $post_thumbnail_id, true );
|
||||
} else {
|
||||
$html = '<div class="woocommerce-product-gallery__image--placeholder">';
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Unreleased
|
||||
|
||||
# 0.1.1
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- The `HTTPClientFactory` API was changed to make it easier to configure instances with
|
||||
|
@ -14,6 +16,11 @@
|
|||
|
||||
- Added a tranformation layer between API responses and internal models
|
||||
|
||||
## Fixed
|
||||
|
||||
- issues that caused the factory creation to fail for SimpleProduct types
|
||||
- a bug with OAuth signature generation when using query parameters
|
||||
|
||||
# 0.1.0
|
||||
|
||||
- Initial/beta release
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@woocommerce/api",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"author": "Automattic",
|
||||
"description": "A simple interface for interacting with a WooCommerce installation.",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/api/README.md",
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
const {
|
||||
switchUserToAdmin,
|
||||
visitAdminPage,
|
||||
switchUserToTest,
|
||||
clearLocalStorage,
|
||||
setBrowserViewport
|
||||
} = require( "@wordpress/e2e-test-utils" );
|
||||
|
||||
const { merchant } = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* Navigates to the post listing screen and bulk-trashes any posts which exist.
|
||||
*
|
||||
* @return {Promise} Promise resolving once posts have been trashed.
|
||||
*/
|
||||
async function trashExistingPosts() {
|
||||
await switchUserToAdmin();
|
||||
await merchant.login();
|
||||
// Visit `/wp-admin/edit.php` so we can see a list of posts and delete them.
|
||||
await visitAdminPage( 'edit.php' );
|
||||
|
||||
|
@ -41,7 +42,7 @@ async function trashExistingPosts() {
|
|||
* @return {Promise} Promise resolving once products have been trashed.
|
||||
*/
|
||||
async function trashExistingProducts() {
|
||||
await switchUserToAdmin();
|
||||
await merchant.login();
|
||||
// Visit `/wp-admin/edit.php?post_type=product` so we can see a list of products and delete them.
|
||||
await visitAdminPage( 'edit.php', 'post_type=product' );
|
||||
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
# Unreleased
|
||||
|
||||
# 0.1.1
|
||||
|
||||
## Added
|
||||
|
||||
- Registered Shopper Checkout tests
|
||||
- Merchant Order Status Filter tests
|
||||
- Merchant Order Refund tests
|
||||
- Merchant Apply Coupon tests
|
||||
- Added new config variable for Simple Product price to `tests/e2e/env/config/default.json`. Defaults to 9.99
|
||||
- Shopper My Account Pay Order
|
||||
|
||||
- Shopper Checkout Apply Coupon
|
||||
|
||||
|
||||
- Shopper Cart Apply Coupon
|
||||
|
||||
## Fixed
|
||||
|
||||
- Flaky Create Product, Coupon, and Order tests
|
||||
|
|
|
@ -37,32 +37,32 @@ The functions to access the core tests are:
|
|||
### Activation and setup
|
||||
|
||||
- `runSetupOnboardingTests` - Run all setup and onboarding tests
|
||||
- `runActivationTest` - Merchant can activate WooCommerce
|
||||
- `runOnboardingFlowTest` - Merchant can complete onboarding flow
|
||||
- `runTaskListTest` - Merchant can complete onboarding task list
|
||||
- `runInitialStoreSettingsTest` - Merchant can complete initial settings
|
||||
- `runActivationTest` - Merchant can activate WooCommerce
|
||||
- `runOnboardingFlowTest` - Merchant can complete onboarding flow
|
||||
- `runTaskListTest` - Merchant can complete onboarding task list
|
||||
- `runInitialStoreSettingsTest` - Merchant can complete initial settings
|
||||
|
||||
### Merchant
|
||||
|
||||
- `runMerchantTests` - Run all merchant tests
|
||||
- `runCreateCouponTest` - Merchant can create coupon
|
||||
- `runCreateOrderTest` - Merchant can create order
|
||||
- `runAddSimpleProductTest` - Merchant can create a simple product
|
||||
- `runAddVariableProductTest` - Merchant can create a variable product
|
||||
- `runUpdateGeneralSettingsTest` - Merchant can update general settings
|
||||
- `runProductSettingsTest` - Merchant can update product settings
|
||||
- `runTaxSettingsTest` - Merchant can update tax settings
|
||||
- `runOrderStatusFilterTest` - Merchant can filter orders by order status
|
||||
- `runOrderRefundTest` - Merchant can refund an order
|
||||
- `runOrderApplyCouponTest` - Merchant can apply a coupon to an order
|
||||
- `runCreateCouponTest` - Merchant can create coupon
|
||||
- `runCreateOrderTest` - Merchant can create order
|
||||
- `runAddSimpleProductTest` - Merchant can create a simple product
|
||||
- `runAddVariableProductTest` - Merchant can create a variable product
|
||||
- `runUpdateGeneralSettingsTest` - Merchant can update general settings
|
||||
- `runProductSettingsTest` - Merchant can update product settings
|
||||
- `runTaxSettingsTest` - Merchant can update tax settings
|
||||
- `runOrderStatusFilterTest` - Merchant can filter orders by order status
|
||||
- `runOrderRefundTest` - Merchant can refund an order
|
||||
- `runOrderApplyCouponTest` - Merchant can apply a coupon to an order
|
||||
|
||||
### Shopper
|
||||
|
||||
- `runShopperTests` - Run all shopper tests
|
||||
- `runCartPageTest` - Shopper can view and update cart
|
||||
- `runCheckoutPageTest` - Shopper can complete checkout
|
||||
- `runMyAccountPageTest` - Shopper can access my account page
|
||||
- `runSingleProductPageTest` - Shopper can view single product page
|
||||
- `runCartPageTest` - Shopper can view and update cart
|
||||
- `runCheckoutPageTest` - Shopper can complete checkout
|
||||
- `runMyAccountPageTest` - Shopper can access my account page
|
||||
- `runSingleProductPageTest` - Shopper can view single product page
|
||||
|
||||
## Contributing a new test
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@woocommerce/e2e-core-tests",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "End-To-End (E2E) tests for WooCommerce",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/core-tests/README.md",
|
||||
"repository": {
|
||||
|
@ -14,7 +14,7 @@
|
|||
"config": "3.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@woocommerce/e2e-utils": "^0.1.1"
|
||||
"@woocommerce/e2e-utils": "^0.1.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
|
|
@ -9,7 +9,9 @@ const { runOnboardingFlowTest, runTaskListTest } = require( './activate-and-setu
|
|||
const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' );
|
||||
|
||||
// Shopper tests
|
||||
const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test');
|
||||
const runCartPageTest = require( './shopper/front-end-cart.test' );
|
||||
const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test');
|
||||
const runCheckoutPageTest = require( './shopper/front-end-checkout.test' );
|
||||
const runMyAccountPayOrderTest = require( './shopper/front-end-my-account-pay-order.test' );
|
||||
const runMyAccountPageTest = require( './shopper/front-end-my-account.test' );
|
||||
|
@ -34,7 +36,9 @@ const runSetupOnboardingTests = () => {
|
|||
};
|
||||
|
||||
const runShopperTests = () => {
|
||||
runCartApplyCouponsTest();
|
||||
runCartPageTest();
|
||||
runCheckoutApplyCouponsTest();
|
||||
runCheckoutPageTest();
|
||||
runMyAccountPayOrderTest();
|
||||
runMyAccountPageTest();
|
||||
|
@ -60,7 +64,9 @@ module.exports = {
|
|||
runTaskListTest,
|
||||
runInitialStoreSettingsTest,
|
||||
runSetupOnboardingTests,
|
||||
runCartApplyCouponsTest,
|
||||
runCartPageTest,
|
||||
runCheckoutApplyCouponsTest,
|
||||
runCheckoutPageTest,
|
||||
runMyAccountPayOrderTest,
|
||||
runMyAccountPageTest,
|
||||
|
|
|
@ -10,6 +10,7 @@ const {
|
|||
createCoupon,
|
||||
uiUnblocked,
|
||||
addProductToOrder,
|
||||
evalAndClick,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
const config = require( 'config' );
|
||||
|
@ -55,7 +56,7 @@ const runOrderApplyCouponTest = () => {
|
|||
// 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 });
|
||||
await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() });
|
||||
|
||||
// Check that the coupon has been applied
|
||||
await expect(page).toMatchElement('.wc-order-item-discount', { text: '5.00' });
|
||||
|
@ -65,16 +66,13 @@ const runOrderApplyCouponTest = () => {
|
|||
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 expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() });
|
||||
await evalAndClick( 'a.remove-coupon' );
|
||||
|
||||
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_coupon_list li.code.editable', { text: couponCode.toLowerCase() });
|
||||
await expect(page).not.toMatchElement('.wc-order-item-discount', { text: '5.00' });
|
||||
await expect(page).not.toMatchElement('.line-cost .view .woocommerce-Price-amount', { text: discountedPrice });
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ const {
|
|||
verifyValueOfInputField,
|
||||
uiUnblocked,
|
||||
addProductToOrder,
|
||||
evalAndClick,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
const config = require( 'config' );
|
||||
|
@ -75,10 +76,7 @@ const runRefundOrderTest = () => {
|
|||
});
|
||||
|
||||
it('can delete an issued refund', async () => {
|
||||
// We need to use this here as `expect(page).toClick()` was unable to find the element
|
||||
// See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219
|
||||
page.$eval('a.delete_refund', elem => elem.click());
|
||||
|
||||
await evalAndClick( 'a.delete_refund' );
|
||||
await uiUnblocked();
|
||||
|
||||
// Verify the refunded row item is no longer showing
|
||||
|
|
|
@ -6,6 +6,7 @@ const {
|
|||
merchant,
|
||||
clickTab,
|
||||
uiUnblocked,
|
||||
evalAndClick,
|
||||
setCheckbox,
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
const {
|
||||
|
@ -120,9 +121,7 @@ const runAddVariableProductTest = () => {
|
|||
|
||||
// headless: false doesn't require this
|
||||
const firstDialog = await expect(page).toDisplayDialog(async () => {
|
||||
// Using this technique since toClick() isn't working.
|
||||
// See: https://github.com/GoogleChrome/puppeteer/issues/1805#issuecomment-464802876
|
||||
page.$eval('a.do_variation_action', elem => elem.click());
|
||||
await evalAndClick( 'a.do_variation_action' );
|
||||
});
|
||||
|
||||
await expect(firstDialog.message()).toMatch('Are you sure you want to link all variations?');
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
shopper,
|
||||
merchant,
|
||||
createCoupon,
|
||||
createSimpleProduct,
|
||||
uiUnblocked
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const runCartApplyCouponsTest = () => {
|
||||
describe('Cart applying coupons', () => {
|
||||
let couponFixedCart;
|
||||
let couponPercentage;
|
||||
let couponFixedProduct;
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
await createSimpleProduct();
|
||||
couponFixedCart = await createCoupon();
|
||||
couponPercentage = await createCoupon('50', 'Percentage discount');
|
||||
couponFixedProduct = await createCoupon('5', 'Fixed product discount');
|
||||
await merchant.logout();
|
||||
});
|
||||
|
||||
it('allows customer to apply coupons in the cart', async () => {
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage('Simple product');
|
||||
await shopper.goToCart();
|
||||
await shopper.productIsInCart('Simple product');
|
||||
|
||||
// Apply Fixed cart discount coupon
|
||||
await expect(page).toFill('#coupon_code', couponFixedCart);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Wait for page to expand total calculations to avoid flakyness
|
||||
await page.waitForSelector('.order-total');
|
||||
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$5.00'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$4.99'});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
|
||||
// Apply Percentage discount coupon
|
||||
await expect(page).toFill('#coupon_code', couponPercentage);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$4.99'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$5.00'});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
|
||||
// Apply Fixed product discount coupon
|
||||
await expect(page).toFill('#coupon_code', couponFixedProduct);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$5.00'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$4.99'});
|
||||
|
||||
// Try to apply the same coupon
|
||||
await expect(page).toFill('#coupon_code', couponFixedProduct);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' });
|
||||
|
||||
// Try to apply multiple coupons
|
||||
await expect(page).toFill('#coupon_code', couponFixedCart);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$0.00'});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
|
||||
// Verify the total amount after all coupons removal
|
||||
await page.waitForSelector('.order-total .amount', {text: '$9.99'});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runCartApplyCouponsTest;
|
|
@ -0,0 +1,118 @@
|
|||
/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect, jest/no-standalone-expect */
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const {
|
||||
shopper,
|
||||
merchant,
|
||||
createCoupon,
|
||||
createSimpleProduct,
|
||||
uiUnblocked
|
||||
} = require( '@woocommerce/e2e-utils' );
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const {
|
||||
it,
|
||||
describe,
|
||||
beforeAll,
|
||||
} = require( '@jest/globals' );
|
||||
|
||||
const runCheckoutApplyCouponsTest = () => {
|
||||
describe('Checkout applying coupons', () => {
|
||||
let couponFixedCart;
|
||||
let couponPercentage;
|
||||
let couponFixedProduct;
|
||||
beforeAll(async () => {
|
||||
await merchant.login();
|
||||
await createSimpleProduct();
|
||||
couponFixedCart = await createCoupon();
|
||||
couponPercentage = await createCoupon('50', 'Percentage discount');
|
||||
couponFixedProduct = await createCoupon('5', 'Fixed product discount');
|
||||
await merchant.logout();
|
||||
});
|
||||
|
||||
it('allows customer to apply coupons in the checkout', async () => {
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage('Simple product');
|
||||
await uiUnblocked();
|
||||
await shopper.goToCheckout();
|
||||
|
||||
// Apply Fixed cart discount coupon
|
||||
await expect(page).toClick('a', {text: 'Click here to enter your code'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toFill('#coupon_code', couponFixedCart);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
|
||||
// Wait for page to expand total calculations to avoid flakyness
|
||||
await page.waitForSelector('.order-total');
|
||||
|
||||
// Verify discount applied and order total
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$5.00'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$4.99'});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
|
||||
// Apply Percentage discount coupon
|
||||
await expect(page).toClick('a', {text: 'Click here to enter your code'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toFill('#coupon_code', couponPercentage);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$4.99'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$5.00'});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
|
||||
// Apply Fixed product discount coupon
|
||||
await expect(page).toClick('a', {text: 'Click here to enter your code'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toFill('#coupon_code', couponFixedProduct);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.cart-discount .amount', {text: '$5.00'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$4.99'});
|
||||
|
||||
// Try to apply the same coupon
|
||||
await expect(page).toClick('a', {text: 'Click here to enter your code'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toFill('#coupon_code', couponFixedProduct);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' });
|
||||
|
||||
// Try to apply multiple coupons
|
||||
await expect(page).toClick('a', {text: 'Click here to enter your code'});
|
||||
await uiUnblocked();
|
||||
await expect(page).toFill('#coupon_code', couponFixedCart);
|
||||
await expect(page).toClick('button', {text: 'Apply coupon'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'});
|
||||
await page.waitForSelector('.order-total .amount', {text: '$0.00'});
|
||||
|
||||
// Remove coupon
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'});
|
||||
await uiUnblocked();
|
||||
await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'});
|
||||
|
||||
// Verify the total amount after all coupons removal
|
||||
await page.waitForSelector('.order-total .amount', {text: '$9.99'});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = runCheckoutApplyCouponsTest;
|
|
@ -20,7 +20,8 @@ const threeProductPrice = singleProductPrice * 3;
|
|||
const fourProductPrice = singleProductPrice * 4;
|
||||
const fiveProductPrice = singleProductPrice * 5;
|
||||
|
||||
let orderId;
|
||||
let guestOrderId;
|
||||
let customerOrderId;
|
||||
|
||||
const runCheckoutPageTest = () => {
|
||||
describe('Checkout page', () => {
|
||||
|
@ -136,34 +137,36 @@ const runCheckoutPageTest = () => {
|
|||
// Get order ID from the order received html element on the page
|
||||
let orderReceivedHtmlElement = await page.$('.woocommerce-order-overview__order.order');
|
||||
let orderReceivedText = await page.evaluate(element => element.textContent, orderReceivedHtmlElement);
|
||||
return orderId = orderReceivedText.split(/(\s+)/)[6].toString();
|
||||
return guestOrderId = orderReceivedText.split(/(\s+)/)[6].toString();
|
||||
});
|
||||
|
||||
it('allows existing customer to place order', async () => {
|
||||
await shopper.login();
|
||||
await shopper.goToShop();
|
||||
await shopper.addToCartFromShopPage(simpleProductName);
|
||||
await shopper.goToCheckout();
|
||||
await shopper.productIsInCheckout(simpleProductName, `1`, singleProductPrice, singleProductPrice);
|
||||
await shopper.fillBillingDetails(config.get('addresses.customer.billing'));
|
||||
|
||||
await uiUnblocked();
|
||||
|
||||
await expect(page).toClick('.wc_payment_method label', {text: 'Cash on delivery'});
|
||||
await expect(page).toMatchElement('.payment_method_cod', {text: 'Pay with cash upon delivery.'});
|
||||
await uiUnblocked();
|
||||
await shopper.placeOrder();
|
||||
|
||||
await expect(page).toMatch('Order received');
|
||||
|
||||
// Get order ID from the order received html element on the page
|
||||
let orderReceivedHtmlElement = await page.$('.woocommerce-order-overview__order.order');
|
||||
let orderReceivedText = await page.evaluate(element => element.textContent, orderReceivedHtmlElement);
|
||||
return customerOrderId = orderReceivedText.split(/(\s+)/)[6].toString();
|
||||
});
|
||||
|
||||
it('store owner can confirm the order was received', async () => {
|
||||
await merchant.login();
|
||||
await merchant.openAllOrdersView();
|
||||
|
||||
// Click on the order which was placed in the previous step
|
||||
await Promise.all([
|
||||
page.click('#post-' + orderId),
|
||||
page.waitForNavigation({waitUntil: 'networkidle0'}),
|
||||
]);
|
||||
|
||||
// Verify that the order page is indeed of the order that was placed
|
||||
// Verify order number
|
||||
await expect(page).toMatchElement('.woocommerce-order-data__heading', {text: 'Order #' + orderId + ' details'});
|
||||
|
||||
// Verify product name
|
||||
await expect(page).toMatchElement('.wc-order-item-name', {text: simpleProductName});
|
||||
|
||||
// Verify product cost
|
||||
await expect(page).toMatchElement('.woocommerce-Price-amount.amount', {text: singleProductPrice});
|
||||
|
||||
// Verify product quantity
|
||||
await expect(page).toMatchElement('.quantity', {text: '5'});
|
||||
|
||||
// Verify total order amount without shipping
|
||||
await expect(page).toMatchElement('.line_cost', {text: fiveProductPrice});
|
||||
await merchant.verifyOrder(guestOrderId, simpleProductName, singleProductPrice, 5, fiveProductPrice);
|
||||
await merchant.verifyOrder(customerOrderId, simpleProductName, singleProductPrice, 1, singleProductPrice, true);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -9,6 +9,3 @@ WORDPRESS_DEBUG=1
|
|||
# WordPress CLI environment
|
||||
WORDPRESS_HOST=wordpress-www:80
|
||||
WORDPRESS_TITLE=WooCommerce Core E2E Test Suite
|
||||
WORDPRESS_LOGIN=admin
|
||||
WORDPRESS_PASSWORD=password
|
||||
WORDPRESS_EMAIL=admin@woocommercecoree2etestsuite.com
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# Unreleased
|
||||
|
||||
# 0.2.0
|
||||
|
||||
## Fixed
|
||||
|
||||
- Return jest exit code from `npx wc-e2e test:e2e*`
|
||||
|
||||
## Added
|
||||
|
||||
- support for custom container name
|
||||
|
@ -9,6 +15,7 @@
|
|||
## Fixed
|
||||
|
||||
- Remove redundant `puppeteer` dependency
|
||||
- Support for admin user configuration from `default.json`
|
||||
|
||||
# 0.1.6
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ const { spawnSync } = require( 'child_process' );
|
|||
const program = require( 'commander' );
|
||||
const path = require( 'path' );
|
||||
const fs = require( 'fs' );
|
||||
const { getAppBase, getAppRoot, getAppName, getTestConfig } = require( '../utils' );
|
||||
const { getAdminConfig, getAppBase, getAppRoot, getAppName, getTestConfig } = require( '../utils' );
|
||||
|
||||
const dockerArgs = [];
|
||||
let command = '';
|
||||
|
@ -29,7 +29,7 @@ program
|
|||
.parse( process.argv );
|
||||
|
||||
const appPath = getAppRoot();
|
||||
const envVars = {};
|
||||
const envVars = getAdminConfig();
|
||||
|
||||
if ( appPath ) {
|
||||
if ( 'up' === command ) {
|
||||
|
|
|
@ -25,6 +25,9 @@ fi
|
|||
# Store original path
|
||||
OLDPATH=$(pwd)
|
||||
|
||||
# Return value for CI test runs
|
||||
TESTRESULT=0
|
||||
|
||||
# Use the script symlink to find and change directory to the root of the package
|
||||
SCRIPTPATH=$(dirname "$0")
|
||||
REALPATH=$(readlink $0)
|
||||
|
@ -46,12 +49,15 @@ case $1 in
|
|||
;;
|
||||
'test:e2e')
|
||||
./bin/wait-for-build.sh && ./bin/e2e-test-integration.js $2
|
||||
TESTRESULT=$?
|
||||
;;
|
||||
'test:e2e-dev')
|
||||
./bin/wait-for-build.sh && ./bin/e2e-test-integration.js --dev $2
|
||||
TESTRESULT=$?
|
||||
;;
|
||||
'test:e2e-debug')
|
||||
./bin/wait-for-build.sh && ./bin/e2e-test-integration.js --dev --debug $2
|
||||
TESTRESULT=$?
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
|
@ -60,3 +66,5 @@ esac
|
|||
|
||||
# Restore working path
|
||||
cd $OLDPATH
|
||||
|
||||
exit $TESTRESULT
|
||||
|
|
|
@ -48,11 +48,6 @@ The built in container initialization needs to know the particulars of your test
|
|||
"username": "admin",
|
||||
"password": "password",
|
||||
"email": "admin@woocommercecoree2etestsuite.com"
|
||||
},
|
||||
"customer": {
|
||||
"username": "customer",
|
||||
"password": "password",
|
||||
"email": "customer@woocommercecoree2etestsuite.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@woocommerce/e2e-environment",
|
||||
"version": "0.1.6",
|
||||
"version": "0.2.0",
|
||||
"description": "WooCommerce End to End Testing Environment Configuration.",
|
||||
"author": "Automattic",
|
||||
"license": "GPL-3.0-or-later",
|
||||
|
|
|
@ -5,7 +5,7 @@ const path = require( 'path' );
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const getTestConfig = require( './test-config' );
|
||||
const { getTestConfig } = require( './test-config' );
|
||||
const getAppRoot = require( './app-root' );
|
||||
|
||||
const getAppName = () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Provide the base test URL to bash scripts.
|
||||
*/
|
||||
const getTestConfig = require( './test-config' );
|
||||
const { getTestConfig } = require( './test-config' );
|
||||
const testConfig = getTestConfig();
|
||||
|
||||
console.log( testConfig.baseUrl );
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
const getAppRoot = require( './app-root' );
|
||||
const { getAppName, getAppBase } = require( './app-name' );
|
||||
const getTestConfig = require( './test-config' );
|
||||
const { getTestConfig, getAdminConfig } = require( './test-config' );
|
||||
|
||||
module.exports = {
|
||||
getAppBase,
|
||||
getAppRoot,
|
||||
getAppName,
|
||||
getTestConfig,
|
||||
getAdminConfig,
|
||||
};
|
||||
|
|
|
@ -27,4 +27,21 @@ const getTestConfig = () => {
|
|||
return testConfig;
|
||||
};
|
||||
|
||||
module.exports = getTestConfig;
|
||||
/**
|
||||
* Get user account settings for Docker configuration.
|
||||
*/
|
||||
const getAdminConfig = () => {
|
||||
const testConfig = getTestConfig();
|
||||
const adminConfig = {
|
||||
'WORDPRESS_LOGIN': testConfig.users.admin.username ? testConfig.users.admin.username : 'admin',
|
||||
'WORDPRESS_PASSWORD': testConfig.users.admin.password ? testConfig.users.admin.password : 'password',
|
||||
'WORDPRESS_EMAIL': testConfig.users.admin.email ? testConfig.users.admin.email : 'admin@woocommercecoree2etestsuite.com',
|
||||
};
|
||||
|
||||
return adminConfig;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getTestConfig,
|
||||
getAdminConfig,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runCartApplyCouponsTest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runCartApplyCouponsTest();
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { runCheckoutApplyCouponsTest } = require( '@woocommerce/e2e-core-tests' );
|
||||
|
||||
runCheckoutApplyCouponsTest();
|
|
@ -1,5 +1,7 @@
|
|||
# Unreleased
|
||||
|
||||
# 0.1.2
|
||||
|
||||
## Fixed
|
||||
|
||||
- Missing `config` package dependency
|
||||
|
@ -12,11 +14,13 @@
|
|||
- `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.
|
||||
- `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element.
|
||||
|
||||
## Changes
|
||||
|
||||
- Deprecated `StoreOwnerFlow`, `CustomerFlow` in favour of `merchant`,`shopper`
|
||||
- `createSimpleOrder( status )` returns the ID of the order that was created
|
||||
- Updated `createCoupon( couponAmount )` component by adding a new parameter `discountType` which allows you to use any coupon discount type in tests
|
||||
|
||||
# 0.1.1
|
||||
|
||||
|
|
|
@ -14,21 +14,20 @@ npm install @woocommerce/e2e-utils --save
|
|||
Example:
|
||||
~~~js
|
||||
import {
|
||||
CustomerFlow,
|
||||
StoreOwnerFlow,
|
||||
createSimpleProduct,
|
||||
uiUnblocked
|
||||
shopper,
|
||||
merchant,
|
||||
createSimpleProduct
|
||||
} from '@woocommerce/e2e-utils';
|
||||
|
||||
describe( 'Cart page', () => {
|
||||
beforeAll( async () => {
|
||||
await StoreOwnerFlow.login();
|
||||
await merchant.login();
|
||||
await createSimpleProduct();
|
||||
await StoreOwnerFlow.logout();
|
||||
await merchant.logout();
|
||||
} );
|
||||
|
||||
it( 'should display no item in the cart', async () => {
|
||||
await CustomerFlow.goToCart();
|
||||
await shopper.goToCart();
|
||||
await expect( page ).toMatchElement( '.cart-empty', { text: 'Your cart is currently empty.' } );
|
||||
} );
|
||||
} );
|
||||
|
@ -36,10 +35,11 @@ describe( 'Cart page', () => {
|
|||
|
||||
## Test Function
|
||||
|
||||
### Merchant `StoreOwnerFlow`
|
||||
### Merchant `merchant`
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|-------------|------------|
|
||||
| `goToOrder` | `orderId` | Go to view a single order |
|
||||
| `login` | | Log in as merchant |
|
||||
| `logout` | | Log out of merchant account |
|
||||
| `openAllOrdersView` | | Go to the orders listing |
|
||||
|
@ -51,11 +51,9 @@ describe( 'Cart page', () => {
|
|||
| `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`
|
||||
### Shopper `shopper`
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|------------|-------------|
|
||||
|
@ -67,23 +65,28 @@ describe( 'Cart page', () => {
|
|||
| `goToAccountDetails` | | Go to My Account -> Details |
|
||||
| `goToCart` | | Go to the cart page |
|
||||
| `goToCheckout` | | Go to the checkout page |
|
||||
| `goToShop` | | Go to the shop page |
|
||||
| `goToProduct` | `productId` | Go to a single product in the shop |
|
||||
| `goToOrders` | | Go to My Account -> Orders |
|
||||
| `goToDownloads` | | Go to My Account -> Downloads |
|
||||
| `goToMyAccount` | | Go to the My Account page |
|
||||
| `goToOrders` | | Go to My Account -> Orders |
|
||||
| `goToProduct` | `productId` | Go to a single product in the shop |
|
||||
| `goToShop` | | Go to the shop page |
|
||||
| `login` | | Log in as the shopper |
|
||||
| `placeOrder` | | Place an order from the checkout page |
|
||||
| `productIsInCheckout` | `productTitle, quantity, total, cartSubtotal` | Verify product is in cart on checkout page |
|
||||
| `removeFromCart` | `productTitle` | Remove a product from the cart on the cart page |
|
||||
| `setCartQuantity` | `productTitle, quantityValue` | Change the quantity of a product on the cart page |
|
||||
|----------|------------|-------------|
|
||||
|
||||
### Page Utilities
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|------------|-------------|
|
||||
| `addProductToOrder` | `orderId, productName` | adds a product to an order using the product search |
|
||||
| `clearAndFillInput` | `selector, value` | Replace the contents of an input with the passed value |
|
||||
| `clickFilter` | `selector` | helper method that clicks on a list page filter |
|
||||
| `clickTab` | `tabName` | Click on a WooCommerce -> Settings tab |
|
||||
| `createCoupon` | `couponAmount` | creates a basic coupon. Default amount is 5. Returns the generated coupon code. |
|
||||
| `createSimpleOrder` | `status` | creates a basic order with the provided status string |
|
||||
| `moveAllItemsToTrash` | | helper method that checks every item in a list page and moves them to the trash |
|
||||
| `settingsPageSaveChanges` | | Save the current WooCommerce settings page |
|
||||
| `permalinkSettingsPageSaveChanges` | | Save the current Permalink settings |
|
||||
| `setCheckbox` | `selector` | Check a checkbox |
|
||||
|
@ -95,7 +98,6 @@ describe( 'Cart page', () => {
|
|||
| `verifyValueOfInputField` | `selector, value` | Verify an input contains the passed value |
|
||||
| `clickFilter` | `selector` | Click on a list page filter |
|
||||
| `moveAllItemsToTrash` | | Moves all items in a list view to the Trash |
|
||||
|----------|------------|-------------|
|
||||
|
||||
### Test Utilities
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@woocommerce/e2e-utils",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"description": "End-To-End (E2E) test utils for WooCommerce",
|
||||
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e-utils/README.md",
|
||||
"repository": {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { merchant } from './flows';
|
||||
import { clickTab, uiUnblocked, verifyCheckboxIsUnset } from './page-utils';
|
||||
import { clickTab, uiUnblocked, verifyCheckboxIsUnset, evalAndClick } from './page-utils';
|
||||
import factories from './factories';
|
||||
|
||||
const config = require( 'config' );
|
||||
|
@ -25,6 +25,20 @@ const verifyAndPublish = async () => {
|
|||
await expect( page ).toMatchElement( '.updated.notice', { text: 'Product published.' } );
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for primary button to be enabled and click.
|
||||
*
|
||||
* @param waitForNetworkIdle - Wait for network idle after click
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const waitAndClickPrimary = async ( waitForNetworkIdle = true ) => {
|
||||
// Wait for "Continue" button to become active
|
||||
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
|
||||
await page.click( 'button.is-primary' );
|
||||
if ( waitForNetworkIdle ) {
|
||||
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Complete onboarding wizard.
|
||||
*/
|
||||
|
@ -88,15 +102,7 @@ const completeOnboardingWizard = async () => {
|
|||
await expect( page ).toFill( '.components-text-control__input', config.get( 'onboardingwizard.industry' ) );
|
||||
|
||||
// Wait for "Continue" button to become active
|
||||
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
|
||||
|
||||
await Promise.all( [
|
||||
// Click on "Continue" button to move to the next step
|
||||
page.click( 'button.is-primary' ),
|
||||
|
||||
// Wait for "What type of products will be listed?" section to load
|
||||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
] );
|
||||
await waitAndClickPrimary();
|
||||
|
||||
// Product types section
|
||||
|
||||
|
@ -110,15 +116,7 @@ const completeOnboardingWizard = async () => {
|
|||
}
|
||||
|
||||
// Wait for "Continue" button to become active
|
||||
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
|
||||
|
||||
await Promise.all( [
|
||||
// Click on "Continue" button to move to the next step
|
||||
page.click( 'button.is-primary' ),
|
||||
|
||||
// Wait for "Tell us about your business" section to load
|
||||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
] );
|
||||
await waitAndClickPrimary();
|
||||
|
||||
// Business Details section
|
||||
|
||||
|
@ -136,48 +134,15 @@ const completeOnboardingWizard = async () => {
|
|||
await page.waitForSelector( '.woocommerce-select-control__control' );
|
||||
await expect( page ).toClick( '.woocommerce-select-control__option', { text: config.get( 'onboardingwizard.sellingelsewhere' ) } );
|
||||
|
||||
// Query for the extensions toggles
|
||||
const extensionsToggles = await page.$$( '.components-form-toggle__input' );
|
||||
expect( extensionsToggles ).toHaveLength( 4 );
|
||||
|
||||
// Disable download of the onboarding suggested extensions
|
||||
for ( let i = 0; i < extensionsToggles.length; i++ ) {
|
||||
await extensionsToggles[i].click();
|
||||
}
|
||||
|
||||
// Wait for "Continue" button to become active
|
||||
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
|
||||
await waitAndClickPrimary( false );
|
||||
|
||||
await Promise.all( [
|
||||
// Click on "Continue" button to move to the next step
|
||||
page.click( 'button.is-primary' ),
|
||||
|
||||
// Wait for "Theme" section to load
|
||||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
] );
|
||||
// Skip installing extensions
|
||||
await evalAndClick( '.components-checkbox-control__input' );
|
||||
await waitAndClickPrimary();
|
||||
|
||||
// Theme section
|
||||
|
||||
// Wait for "Continue with my active theme" button to become active
|
||||
await page.waitForSelector( 'button.is-primary:not(:disabled)' );
|
||||
|
||||
await Promise.all( [
|
||||
// Click on "Continue with my active theme" button to move to the next step
|
||||
page.click( 'button.is-primary' ),
|
||||
|
||||
// Wait for "Enhance your store with WooCommerce Services" section to load
|
||||
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
|
||||
] );
|
||||
|
||||
// Benefits section
|
||||
|
||||
// Wait for Benefits section to appear
|
||||
await page.waitForSelector( '.woocommerce-profile-wizard__benefits' );
|
||||
|
||||
// Wait for "No thanks" button to become active
|
||||
await page.waitForSelector( 'button.is-secondary:not(:disabled)' );
|
||||
// Click on "No thanks" button to move to the next step
|
||||
await page.click( 'button.is-secondary' );
|
||||
await waitAndClickPrimary();
|
||||
|
||||
// End of onboarding wizard
|
||||
|
||||
|
@ -403,17 +368,18 @@ const addProductToOrder = async ( orderId, productName ) => {
|
|||
* Creates a basic coupon with the provided coupon amount. Returns the coupon code.
|
||||
*
|
||||
* @param couponAmount Amount to be applied. Defaults to 5.
|
||||
* @param discountType Type of a coupon. Defaults to Fixed cart discount.
|
||||
*/
|
||||
const createCoupon = async ( couponAmount = '5' ) => {
|
||||
const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart discount' ) => {
|
||||
await merchant.openNewCoupon();
|
||||
|
||||
// Fill in coupon code
|
||||
let couponCode = 'code-' + new Date().getTime().toString();
|
||||
let couponCode = 'Code-' + discountType + 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).toSelect( '#discount_type', discountType );
|
||||
await expect(page).toFill( '#coupon_amount', couponAmount );
|
||||
|
||||
// Publish coupon
|
||||
|
|
|
@ -131,6 +131,31 @@ const merchant = {
|
|||
await page.waitForSelector( '#message' );
|
||||
await expect( page ).toMatchElement( '#message', { text: 'Order updated.' } );
|
||||
},
|
||||
|
||||
verifyOrder: async (orderId, productName, productPrice, quantity, orderTotal, ensureCustomerRegistered = false) => {
|
||||
await merchant.goToOrder(orderId);
|
||||
|
||||
// Verify that the order page is indeed of the order that was placed
|
||||
// Verify order number
|
||||
await expect(page).toMatchElement('.woocommerce-order-data__heading', {text: 'Order #' + orderId + ' details'});
|
||||
|
||||
// Verify product name
|
||||
await expect(page).toMatchElement('.wc-order-item-name', {text: productName});
|
||||
|
||||
// Verify product cost
|
||||
await expect(page).toMatchElement('.woocommerce-Price-amount.amount', {text: productPrice});
|
||||
|
||||
// Verify product quantity
|
||||
await expect(page).toMatchElement('.quantity', {text: quantity.toString()});
|
||||
|
||||
// Verify total order amount without shipping
|
||||
await expect(page).toMatchElement('.line_cost', {text: orderTotal});
|
||||
|
||||
if ( ensureCustomerRegistered ) {
|
||||
// Verify customer profile link is present to verify order was placed by a registered customer, not a guest
|
||||
await expect( page ).toMatchElement( 'label[for="customer_user"] a[href*=user-edit]', { text: 'Profile' } );
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = merchant;
|
||||
|
|
|
@ -160,7 +160,7 @@ const verifyValueOfInputField = async( selector, value ) => {
|
|||
*
|
||||
* @param {string} selector Selector of the filter link to be clicked.
|
||||
*/
|
||||
const clickFilter = async( selector ) => {
|
||||
const clickFilter = async ( selector ) => {
|
||||
await page.waitForSelector( selector );
|
||||
await page.focus( selector );
|
||||
await Promise.all( [
|
||||
|
@ -174,7 +174,7 @@ const clickFilter = async( selector ) => {
|
|||
*
|
||||
* If there's more than 20 items, it moves all 20 items on the current page.
|
||||
*/
|
||||
const moveAllItemsToTrash = async() => {
|
||||
const moveAllItemsToTrash = async () => {
|
||||
await setCheckbox( '#cb-select-all-1' );
|
||||
await expect( page ).toSelect( '#bulk-action-selector-top', 'Move to Trash' );
|
||||
await Promise.all( [
|
||||
|
@ -183,6 +183,19 @@ const moveAllItemsToTrash = async() => {
|
|||
] );
|
||||
};
|
||||
|
||||
/**
|
||||
* Use puppeteer page eval to click an element.
|
||||
*
|
||||
* Useful for clicking items that have been added to the DOM via ajax.
|
||||
*
|
||||
* @param {string} selector Selector of the filter link to be clicked.
|
||||
*/
|
||||
const evalAndClick = async ( selector ) => {
|
||||
// We use this when `expect(page).toClick()` is unable to find the element
|
||||
// See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219
|
||||
page.$eval( selector, elem => elem.click() );
|
||||
};
|
||||
|
||||
export {
|
||||
clearAndFillInput,
|
||||
clickTab,
|
||||
|
@ -197,4 +210,5 @@ export {
|
|||
verifyValueOfInputField,
|
||||
clickFilter,
|
||||
moveAllItemsToTrash,
|
||||
evalAndClick,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for the WC_Data class.
|
||||
*
|
||||
* @package WooCommerce\Tests\Blocks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WC_Test_Blocks_Utils
|
||||
*/
|
||||
class WC_Test_Blocks_Utils extends WC_Unit_Test_Case {
|
||||
/**
|
||||
* @group block-utils
|
||||
* Test: has_block_in_page.
|
||||
*
|
||||
*/
|
||||
public function test_has_block_in_page_on_page_with_single_block() {
|
||||
$page = array(
|
||||
'name' => 'blocks-page',
|
||||
'title' => 'Checkout',
|
||||
'content' => '<!-- wp:woocommerce/checkout {"showOrderNotes":false} --> <div class="wp-block-woocommerce-checkout is-loading"></div> <!-- /wp:woocommerce/checkout -->',
|
||||
);
|
||||
|
||||
$page_id = wc_create_page( $page['name'], '', $page['title'], $page['content'] );
|
||||
|
||||
$this->assertTrue( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/checkout' ) );
|
||||
$this->assertFalse( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/cart' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @group block-utils
|
||||
* Test: has_block_in_page.
|
||||
*
|
||||
*/
|
||||
public function test_has_block_in_page_on_page_with_no_blocks() {
|
||||
$page = array(
|
||||
'name' => 'shortcode-page',
|
||||
'title' => 'Checkout',
|
||||
'content' => '<!-- wp:shortcode --> [woocommerce_checkout] <!-- /wp:shortcode -->',
|
||||
);
|
||||
|
||||
$page_id = wc_create_page( $page['name'], '', $page['title'], $page['content'] );
|
||||
|
||||
$this->assertFalse( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/checkout' ) );
|
||||
$this->assertFalse( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/cart' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @group block-utils
|
||||
* Test: has_block_in_page.
|
||||
*
|
||||
*/
|
||||
public function test_has_block_in_page_on_page_with_multiple_blocks() {
|
||||
$page = array(
|
||||
'name' => 'shortcode-page',
|
||||
'title' => 'Checkout',
|
||||
'content' => '<!-- wp:woocommerce/featured-product {"editMode":false,"productId":17} -->
|
||||
<!-- wp:button {"align":"center"} -->
|
||||
<div class="wp-block-button aligncenter"><a class="wp-block-button__link" href="https://blocks.local/product/beanie/">Shop now</a></div>
|
||||
<!-- /wp:button -->
|
||||
<!-- /wp:woocommerce/featured-product -->
|
||||
|
||||
<!-- wp:heading -->
|
||||
<h2>test</h2>
|
||||
<!-- /wp:heading -->',
|
||||
);
|
||||
|
||||
$page_id = wc_create_page( $page['name'], '', $page['title'], $page['content'] );
|
||||
|
||||
$this->assertTrue( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/featured-product' ) );
|
||||
$this->assertTrue( WC_Blocks_Utils::has_block_in_page( $page_id, 'core/heading' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @group block-utils
|
||||
* Test: get_all_blocks_from_page.
|
||||
*
|
||||
*/
|
||||
public function test_get_all_blocks_from_page() {
|
||||
$page = array(
|
||||
'name' => 'cart',
|
||||
'title' => 'Checkout',
|
||||
'content' => '<!-- wp:heading --><h2>test1</h2><!-- /wp:heading --><!-- wp:heading --><h1>test2</h1><!-- /wp:heading -->',
|
||||
);
|
||||
|
||||
wc_create_page( $page['name'], 'woocommerce_cart_page_id', $page['title'], $page['content'] );
|
||||
|
||||
$expected = array(
|
||||
0 => array(
|
||||
'blockName' => 'core/heading',
|
||||
'attrs' => array(),
|
||||
'innerBlocks' => array(),
|
||||
'innerHTML' => '<h2>test1</h2>',
|
||||
'innerContent' => array(
|
||||
0 => '<h2>test1</h2>',
|
||||
),
|
||||
),
|
||||
1 => array(
|
||||
'blockName' => 'core/heading',
|
||||
'attrs' => array(),
|
||||
'innerBlocks' => array(),
|
||||
'innerHTML' => '<h1>test2</h1>',
|
||||
'innerContent' => array(
|
||||
0 => '<h1>test2</h1>',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$blocks = WC_Blocks_Utils::get_blocks_from_page( 'core/heading', 'cart' );
|
||||
|
||||
$this->assertEquals( $expected, $blocks );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/**
|
||||
* Tests for WC_Comments class.
|
||||
*/
|
||||
class WC_Comments_Tests extends \WC_Unit_Test_Case {
|
||||
/**
|
||||
* Test get_review_counts_for_product_ids().
|
||||
*/
|
||||
public function test_get_review_counts_for_product_ids() {
|
||||
$product1 = WC_Helper_Product::create_simple_product();
|
||||
$product2 = WC_Helper_Product::create_simple_product();
|
||||
$product3 = WC_Helper_Product::create_simple_product();
|
||||
|
||||
$expected_review_count = array(
|
||||
$product1->get_id() => 0,
|
||||
$product2->get_id() => 0,
|
||||
$product3->get_id() => 0,
|
||||
);
|
||||
$product_id_array = array_keys( $expected_review_count );
|
||||
|
||||
$this->assertEquals( $expected_review_count, WC_Comments::get_review_counts_for_product_ids( $product_id_array ) );
|
||||
|
||||
\Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_product_review( $product2->get_id() );
|
||||
|
||||
\Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_product_review( $product3->get_id() );
|
||||
\Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_product_review( $product3->get_id() );
|
||||
|
||||
$expected_review_count = array(
|
||||
$product1->get_id() => 0,
|
||||
$product2->get_id() => 1,
|
||||
$product3->get_id() => 2,
|
||||
);
|
||||
$this->assertEquals( $expected_review_count, WC_Comments::get_review_counts_for_product_ids( $product_id_array ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_review_count_for_product.
|
||||
*/
|
||||
public function test_get_review_count_for_product() {
|
||||
$product = WC_Helper_Product::create_simple_product();
|
||||
$this->assertEquals( 0, WC_Comments::get_review_count_for_product( $product ) );
|
||||
|
||||
\Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_product_review( $product->get_id() );
|
||||
$this->assertEquals( 1, WC_Comments::get_review_count_for_product( $product ) );
|
||||
|
||||
\Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_product_review( $product->get_id() );
|
||||
$this->assertEquals( 2, WC_Comments::get_review_count_for_product( $product ) );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Class WC_Emails_Tests.
|
||||
*/
|
||||
class WC_Emails_Tests extends \WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* Test that email_header hooks are compatible with do_action calls with only param.
|
||||
* This test should be dropped after all extensions are using compatible do_action calls.
|
||||
*/
|
||||
public function test_email_header_is_compatible_with_legacy_do_action() {
|
||||
$email_object = new WC_Emails();
|
||||
// 10 is expected priority of the hook.
|
||||
$this->assertEquals( 10, has_action( 'woocommerce_email_header', array( $email_object, 'email_header' ) ) );
|
||||
ob_start();
|
||||
do_action( 'woocommerce_email_header', 'header' );
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$this->assertFalse( empty( $content ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that email_footer hooks are compatible with do_action calls with only param.
|
||||
* This test should be dropped after all extensions are using compatible do_action calls.
|
||||
*/
|
||||
public function test_email_footer_is_compatible_with_legacy_do_action() {
|
||||
$email_object = new WC_Emails();
|
||||
// 10 is expected priority of the hook.
|
||||
$this->assertEquals( 10, has_action( 'woocommerce_email_footer', array( $email_object, 'email_footer' ) ) );
|
||||
ob_start();
|
||||
do_action( 'woocommerce_email_footer' );
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$this->assertFalse( empty( $content ) );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Class WC_Shipping_Zone_Data_Store_CPT_Test.
|
||||
*/
|
||||
class WC_Shipping_Zone_Data_Store_CPT_Test extends WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* @testdox read() sets properties for normal, non-zero shipping zones.
|
||||
*/
|
||||
public function test_read_for_normal_shipping_zones() {
|
||||
$zone = new WC_Shipping_Zone();
|
||||
$zone->set_zone_name( 'California' );
|
||||
$zone->set_zone_order( 3 );
|
||||
$zone->add_location( 'US:CA', 'state' );
|
||||
$zone->save();
|
||||
|
||||
$datastore = new WC_Shipping_Zone_Data_Store();
|
||||
$datastore->read( $zone );
|
||||
$this->assertSame( 'California', $zone->get_zone_name() );
|
||||
$this->assertSame( 3, $zone->get_zone_order() );
|
||||
$this->assertGreaterThan( 0, did_action( 'woocommerce_shipping_zone_loaded' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox read() sets default properties for shipping zone with ID 0.
|
||||
*/
|
||||
public function test_read_for_shipping_zone_zero() {
|
||||
$zone = new WC_Shipping_Zone( 0 );
|
||||
|
||||
$datastore = new WC_Shipping_Zone_Data_Store();
|
||||
$datastore->read( $zone );
|
||||
$this->assertSame( 0, $zone->get_zone_order() );
|
||||
$this->assertGreaterThan( 0, did_action( 'woocommerce_shipping_zone_loaded' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox read() throws an exception if the zone ID cannot be found.
|
||||
*/
|
||||
public function test_read_with_invalid_zone_id() {
|
||||
$this->expectException( \Exception::class );
|
||||
|
||||
$zone = new WC_Shipping_Zone( -1 );
|
||||
|
||||
$datastore = new WC_Shipping_Zone_Data_Store();
|
||||
$datastore->read( $zone );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
/**
|
||||
* ClassWithSingleton class file.
|
||||
*/
|
||||
|
||||
// This class is in the root namespace on purpose, since it simulates being a legacy class in the 'includes' directory.
|
||||
|
||||
/**
|
||||
* An example of a class that holds a singleton instance.
|
||||
*/
|
||||
class ClassWithLoadMethod {
|
||||
|
||||
/**
|
||||
* @var ClassWithLoadMethod The last instance of the class that has been loaded.
|
||||
*/
|
||||
public static $loaded;
|
||||
|
||||
/**
|
||||
* @var array The arguments supplied to 'load'.
|
||||
*/
|
||||
public static $loaded_args;
|
||||
|
||||
/**
|
||||
* Load an instance of the class.
|
||||
*
|
||||
* @param mixed ...$args Any arguments required by the method.
|
||||
*
|
||||
* @return ClassWithLoadMethod The singleton instance of the class.
|
||||
*/
|
||||
public static function load( ...$args ) {
|
||||
self::$loaded = new ClassWithLoadMethod();
|
||||
self::$loaded_args = $args;
|
||||
return self::$loaded;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
<?php
|
||||
/**
|
||||
* DownloadPermissionsAdjusterTest class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Tests\Internal;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
|
||||
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper;
|
||||
|
||||
/**
|
||||
* Tests for DownloadPermissionsAdjuster.
|
||||
*/
|
||||
class DownloadPermissionsAdjusterTest extends \WC_Unit_Test_Case {
|
||||
|
||||
/**
|
||||
* The system under test.
|
||||
*
|
||||
* @var DownloadPermissionsAdjuster
|
||||
*/
|
||||
private $sut;
|
||||
|
||||
/**
|
||||
* Runs before each test.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->sut = new DownloadPermissionsAdjuster();
|
||||
$this->sut->init();
|
||||
|
||||
// This is needed for "product->set_downloads" to work without actual files.
|
||||
add_filter(
|
||||
'woocommerce_downloadable_file_allowed_mime_types',
|
||||
function() {
|
||||
return array( 'foo' => 'nonsense/foo' );
|
||||
}
|
||||
);
|
||||
add_filter(
|
||||
'woocommerce_downloadable_file_exists',
|
||||
function( $exists, $filename ) {
|
||||
return true;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox DownloadPermissionsAdjuster class hooks on 'adjust_download_permissions' on initialization.
|
||||
*/
|
||||
public function test_class_hooks_on_adjust_download_permissions() {
|
||||
remove_all_actions( 'adjust_download_permissions' );
|
||||
$this->assertFalse( has_action( 'adjust_download_permissions' ) );
|
||||
$this->setUp();
|
||||
$this->assertTrue( has_action( 'adjust_download_permissions' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'maybe_schedule_adjust_download_permissions' does nothing if the product has no children.
|
||||
*/
|
||||
public function test_no_adjustment_is_scheduled_if_product_has_no_children() {
|
||||
$as_get_scheduled_actions_invoked = false;
|
||||
$as_schedule_single_action_invoked = false;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'as_get_scheduled_actions' => function( $args, $return_format ) use ( &$as_get_scheduled_actions_invoked ) {
|
||||
$as_get_scheduled_actions_invoked = true;
|
||||
},
|
||||
'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_invoked ) {
|
||||
$as_schedule_single_action_invoked = true;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_simple_product();
|
||||
$this->sut->maybe_schedule_adjust_download_permissions( $product );
|
||||
|
||||
$this->assertFalse( $as_get_scheduled_actions_invoked );
|
||||
$this->assertFalse( $as_schedule_single_action_invoked );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'maybe_schedule_adjust_download_permissions' does nothing if the an adjustment is already pending.
|
||||
*/
|
||||
public function test_no_adjustment_is_scheduled_if_already_scheduled() {
|
||||
$as_get_scheduled_actions_args = null;
|
||||
$as_schedule_single_action_invoked = false;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'as_get_scheduled_actions' => function( $args, $return_format ) use ( &$as_get_scheduled_actions_args ) {
|
||||
$as_get_scheduled_actions_args = $args;
|
||||
return array( 1 );
|
||||
},
|
||||
'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_invoked ) {
|
||||
$as_schedule_single_action_invoked = true;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$this->sut->maybe_schedule_adjust_download_permissions( $product );
|
||||
|
||||
$expected_get_scheduled_actions_args = array(
|
||||
'hook' => 'adjust_download_permissions',
|
||||
'args' => array( $product->get_id() ),
|
||||
'status' => \ActionScheduler_Store::STATUS_PENDING,
|
||||
);
|
||||
$this->assertEquals( $expected_get_scheduled_actions_args, $as_get_scheduled_actions_args );
|
||||
$this->assertFalse( $as_schedule_single_action_invoked );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'maybe_schedule_adjust_download_permissions' schedules an adjustment if not scheduled already.
|
||||
*/
|
||||
public function test_no_adjustment_is_scheduled_if_not_yet_scheduled() {
|
||||
$as_get_scheduled_actions_args = null;
|
||||
$as_schedule_single_action_args = null;
|
||||
|
||||
$this->register_legacy_proxy_function_mocks(
|
||||
array(
|
||||
'as_get_scheduled_actions' => function( $params, $return_format ) use ( &$as_get_scheduled_actions_args ) {
|
||||
$as_get_scheduled_actions_args = $params;
|
||||
return array();
|
||||
},
|
||||
'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_args ) {
|
||||
$as_schedule_single_action_args = array( $timestamp, $hook, $args );
|
||||
},
|
||||
'time' => function() {
|
||||
return 0; },
|
||||
)
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$this->sut->maybe_schedule_adjust_download_permissions( $product );
|
||||
|
||||
$expected_get_scheduled_actions_args = array(
|
||||
'hook' => 'adjust_download_permissions',
|
||||
'args' => array( $product->get_id() ),
|
||||
'status' => \ActionScheduler_Store::STATUS_PENDING,
|
||||
);
|
||||
$this->assertEquals( $expected_get_scheduled_actions_args, $as_get_scheduled_actions_args );
|
||||
|
||||
$expected_as_schedule_single_action_args = array(
|
||||
1,
|
||||
'adjust_download_permissions',
|
||||
array( $product->get_id() ),
|
||||
);
|
||||
$this->assertEquals( $expected_as_schedule_single_action_args, $as_schedule_single_action_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'adjust_download_permissions' creates child download permissions when they are missing (see method comment for details).
|
||||
*/
|
||||
public function test_adjust_download_permissions_creates_additional_permissions_if_not_exist() {
|
||||
$download = array(
|
||||
'name' => 'the_file',
|
||||
'file' => 'the_file.foo',
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$product->set_downloads( array( $download ) );
|
||||
$product->save();
|
||||
$parent_download_id = current( $product->get_downloads() )->get_id();
|
||||
|
||||
$child = wc_get_product( current( $product->get_children() ) );
|
||||
$child->set_downloads( array( $download ) );
|
||||
$child->save();
|
||||
$child_download_id = current( $child->get_downloads() )->get_id();
|
||||
|
||||
$data_for_data_store =
|
||||
array(
|
||||
$product->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $parent_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
'downloads_remaining' => 34,
|
||||
'access_granted' => '2000-01-01',
|
||||
'access_expires' => '2034-02-27',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$data_store = $this->create_mock_data_store( $data_for_data_store );
|
||||
|
||||
$this->setUp();
|
||||
$this->sut->adjust_download_permissions( $product->get_id() );
|
||||
|
||||
$expected_created_data = array(
|
||||
'download_id' => $child_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
'product_id' => $child->get_id(),
|
||||
'downloads_remaining' => 34,
|
||||
'access_granted' => '2000-01-01',
|
||||
'access_expires' => '2034-02-27',
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected_created_data, $data_store->created_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'adjust_download_permissions' doesn't create child download permissions that already exist.
|
||||
*/
|
||||
public function test_adjust_download_permissions_dont_create_additional_permissions_if_already_exists() {
|
||||
$download = array(
|
||||
'name' => 'the_file',
|
||||
'file' => 'the_file.foo',
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$product->set_downloads( array( $download ) );
|
||||
$product->save();
|
||||
$parent_download_id = current( $product->get_downloads() )->get_id();
|
||||
|
||||
$child = wc_get_product( current( $product->get_children() ) );
|
||||
$child->set_downloads( array( $download ) );
|
||||
$child->save();
|
||||
$child_download_id = current( $child->get_downloads() )->get_id();
|
||||
|
||||
$data_for_data_store =
|
||||
array(
|
||||
$product->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $parent_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
),
|
||||
),
|
||||
),
|
||||
$child->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $child_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$data_store = $this->create_mock_data_store( $data_for_data_store );
|
||||
|
||||
$this->setUp();
|
||||
$this->sut->adjust_download_permissions( $product->get_id() );
|
||||
|
||||
$this->assertEmpty( $data_store->created_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'adjust_download_permissions' creates child download permissions when one exists but for a different order or customer id.
|
||||
*
|
||||
* @testWith [9999, 5678]
|
||||
* [1234, 9999]
|
||||
* @param int $user_id User id the child download permission exists for.
|
||||
* @param int $order_id Order id the child download permission exists for.
|
||||
*/
|
||||
public function test_adjust_download_permissions_creates_additional_permissions_if_exists_but_not_matching( $user_id, $order_id ) {
|
||||
$download = array(
|
||||
'name' => 'the_file',
|
||||
'file' => 'the_file.foo',
|
||||
);
|
||||
|
||||
$product = ProductHelper::create_variation_product();
|
||||
$product->set_downloads( array( $download ) );
|
||||
$product->save();
|
||||
$parent_download_id = current( $product->get_downloads() )->get_id();
|
||||
|
||||
$child = wc_get_product( current( $product->get_children() ) );
|
||||
$child->set_downloads( array( $download ) );
|
||||
$child->save();
|
||||
$child_download_id = current( $child->get_downloads() )->get_id();
|
||||
|
||||
$data_for_data_store =
|
||||
array(
|
||||
$product->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $parent_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
),
|
||||
),
|
||||
),
|
||||
$child->get_id() =>
|
||||
array(
|
||||
(object) array(
|
||||
'data' => array(
|
||||
'download_id' => $child_download_id,
|
||||
'user_id' => $user_id,
|
||||
'order_id' => $order_id,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$data_store = $this->create_mock_data_store( $data_for_data_store );
|
||||
|
||||
$this->setUp();
|
||||
$this->sut->adjust_download_permissions( $product->get_id() );
|
||||
|
||||
$expected = array(
|
||||
'download_id' => $child_download_id,
|
||||
'user_id' => 1234,
|
||||
'order_id' => 5678,
|
||||
'product_id' => $child->get_id(),
|
||||
);
|
||||
|
||||
$this->assertEquals( $expected, $data_store->created_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and register a mock customer downloads data store.
|
||||
*
|
||||
* @param array $data An array where keys are product ids, and values are what 'get_downloads' will return for that input.
|
||||
* @return object An object that mocks the customer downloads data store.
|
||||
*/
|
||||
private function create_mock_data_store( $data ) {
|
||||
// phpcs:disable Squiz.Commenting
|
||||
$data_store = new class($data) {
|
||||
private $data;
|
||||
public $created_data = null;
|
||||
|
||||
public function __construct( $data ) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function get_downloads( $params ) {
|
||||
if ( array_key_exists( $params['product_id'], $this->data ) ) {
|
||||
return $this->data[ $params['product_id'] ];
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
public function create_from_data( $data ) {
|
||||
$this->created_data = $data;
|
||||
}
|
||||
};
|
||||
// phpcs:enable Squiz.Commenting
|
||||
|
||||
$this->register_legacy_proxy_class_mocks(
|
||||
array(
|
||||
'WC_Data_Store' => $data_store,
|
||||
)
|
||||
);
|
||||
|
||||
return $data_store;
|
||||
}
|
||||
}
|
|
@ -60,6 +60,18 @@ class LegacyProxyTest extends \WC_Unit_Test_Case {
|
|||
$this->assertEquals( array( 'foo', 'bar' ), \ClassWithSingleton::$instance_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_instance_of' uses the 'load' static method in classes that implement it, passing the supplied arguments.
|
||||
*/
|
||||
public function test_get_instance_of_class_with_load_method_gets_an_instance_of_the_appropriate_class() {
|
||||
// ClassWithLoadMethod is in the root namespace and thus can't be autoloaded.
|
||||
require_once dirname( __DIR__ ) . '/Internal/DependencyManagement/ExampleClasses/ClassWithLoadMethod.php';
|
||||
|
||||
$loaded = $this->sut->get_instance_of( \ClassWithLoadMethod::class, 'foo', 'bar' );
|
||||
$this->assertSame( \ClassWithLoadMethod::$loaded, $loaded );
|
||||
$this->assertEquals( array( 'foo', 'bar' ), \ClassWithLoadMethod::$loaded_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testdox 'get_instance_of' can be used to get an instance of a class implementing WC_Queue_Interface.
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Plugin Name: WooCommerce
|
||||
* Plugin URI: https://woocommerce.com/
|
||||
* Description: An eCommerce toolkit that helps you sell anything. Beautifully.
|
||||
* Version: 5.0.0-dev
|
||||
* Version: 5.1.0-dev
|
||||
* Author: Automattic
|
||||
* Author URI: https://woocommerce.com
|
||||
* Text Domain: woocommerce
|
||||
|
|
Loading…
Reference in New Issue