Merge branch 'trunk' into add/e2e-merchant-batch-create-orders

This commit is contained in:
rodelgc 2021-07-29 19:48:26 +08:00 committed by GitHub
commit 0ea8637094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
96 changed files with 8268 additions and 1347 deletions

View File

@ -1,8 +1,6 @@
/** @format */ /** @format */
const { useE2EEsLintConfig } = require( './tests/e2e/env/config/use-config' ); module.exports = {
module.exports = useE2EEsLintConfig( {
root: true, root: true,
env: { env: {
browser: true, browser: true,
@ -30,4 +28,4 @@ module.exports = useE2EEsLintConfig( {
jsx: true jsx: true
} }
}, },
} ); };

View File

@ -18,7 +18,7 @@ Bug reports lacking detail, or for any other reason than to report a bug, may be
<!-- Search tip: Make use of GitHub's search syntax to refine your search https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests --> <!-- Search tip: Make use of GitHub's search syntax to refine your search https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests -->
**Prerequisites (mark completed items with an [x]):** **Prerequisites (mark completed items with an [x]):**
- [ ] I have have carried out troubleshooting steps and I believe I have found a bug. - [ ] I have carried out troubleshooting steps and I believe I have found a bug.
- [ ] I have searched for similar bugs in both open and closed issues and cannot find a duplicate. - [ ] I have searched for similar bugs in both open and closed issues and cannot find a duplicate.
**Describe the bug** **Describe the bug**

View File

@ -99,7 +99,7 @@ if ( getenv( 'DRY_RUN' ) ) {
* Assign the milestone to the pull request. * Assign the milestone to the pull request.
*/ */
echo "Assigning the milestone to the pull request...\n"; echo 'Assigning the milestone to the pull request... ';
$milestone_id = $chosen_milestone['id']; $milestone_id = $chosen_milestone['id'];
$mutation = " $mutation = "
@ -108,14 +108,25 @@ $mutation = "
} }
"; ";
do_graphql_api_request( $mutation, true ); $result = do_graphql_api_request( $mutation, true );
if ( is_array( $result ) ) {
if ( empty( $result['errors'] ) ) {
echo "Ok!\n";
} else {
echo "\n*** Errors found while assigning the milestone:\n";
echo var_dump( $result['errors'] );
}
} else {
echo "\n*** Error found while assigning the milestone: file_get_contents returned the following:\n";
echo var_dump( $result );
}
/** /**
* Function to query the GitHub GraphQL API. * Function to query the GitHub GraphQL API.
* *
* @param string $body The GraphQL-formatted request body, without "query" or "mutation" wrapper. * @param string $body The GraphQL-formatted request body, without "query" or "mutation" wrapper.
* @param bool $is_mutation True if the request is a mutation, false if it's a query. * @param bool $is_mutation True if the request is a mutation, false if it's a query.
* @return array The json-decoded response. * @return mixed The json-decoded response if a response is received, 'false' (or whatever file_get_contents returns) otherwise.
*/ */
function do_graphql_api_request( $body, $is_mutation = false ) { function do_graphql_api_request( $body, $is_mutation = false ) {
global $github_token, $graphql_api_url; global $github_token, $graphql_api_url;
@ -138,7 +149,7 @@ function do_graphql_api_request( $body, $is_mutation = false ) {
); );
$result = file_get_contents( $graphql_api_url, false, $context ); $result = file_get_contents( $graphql_api_url, false, $context );
return json_decode( $result, true ); return is_string( $result ) ? json_decode( $result, true ) : $result;
} }
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions

View File

@ -38,5 +38,7 @@ jobs:
E2E_RETEST: 1 E2E_RETEST: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }} E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
UPDATE_WC: 1
run: | run: |
npx wc-e2e test:e2e ./tests/e2e/specs/smoke-tests/update-woocommerce.js
npx wc-e2e test:e2e npx wc-e2e test:e2e

4
.gitignore vendored
View File

@ -52,6 +52,7 @@ tests/cli/vendor
/tests/e2e/env/build/ /tests/e2e/env/build/
/tests/e2e/env/build-module/ /tests/e2e/env/build-module/
/tests/e2e/screenshots /tests/e2e/screenshots
/tests/e2e/plugins
/tests/e2e/utils/build/ /tests/e2e/utils/build/
/tests/e2e/utils/build-module/ /tests/e2e/utils/build-module/
@ -65,6 +66,9 @@ tests/cli/vendor
contributors.md contributors.md
contributors.html contributors.html
# Yarn
yarn.lock
# Packages # Packages
/packages/* /packages/*
!/packages/README.md !/packages/README.md

View File

@ -84,7 +84,7 @@
var meter = wrapper.find( '.woocommerce-password-strength' ), var meter = wrapper.find( '.woocommerce-password-strength' ),
hint = wrapper.find( '.woocommerce-password-hint' ), hint = wrapper.find( '.woocommerce-password-hint' ),
hint_html = '<small class="woocommerce-password-hint">' + wc_password_strength_meter_params.i18n_password_hint + '</small>', hint_html = '<small class="woocommerce-password-hint">' + wc_password_strength_meter_params.i18n_password_hint + '</small>',
strength = wp.passwordStrength.meter( field.val(), wp.passwordStrength.userInputBlacklist() ), strength = wp.passwordStrength.meter( field.val(), wp.passwordStrength.userInputDisallowedList() ),
error = ''; error = '';
// Reset. // Reset.

View File

@ -266,16 +266,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v5.3.0", "version": "v5.3.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "058553870f7809087fa80fa734704a21b9bcaeb2" "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/058553870f7809087fa80fa734704a21b9bcaeb2", "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1",
"reference": "058553870f7809087fa80fa734704a21b9bcaeb2", "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -344,7 +344,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v5.3.0" "source": "https://github.com/symfony/console/tree/v5.3.2"
}, },
"funding": [ "funding": [
{ {
@ -360,7 +360,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-05-26T17:43:10+00:00" "time": "2021-06-12T09:42:48+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
@ -1057,16 +1057,16 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v5.3.0", "version": "v5.3.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "a9a0f8b6aafc5d2d1c116dcccd1573a95153515b" "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/a9a0f8b6aafc5d2d1c116dcccd1573a95153515b", "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
"reference": "a9a0f8b6aafc5d2d1c116dcccd1573a95153515b", "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1120,7 +1120,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v5.3.0" "source": "https://github.com/symfony/string/tree/v5.3.3"
}, },
"funding": [ "funding": [
{ {
@ -1136,7 +1136,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-05-26T17:43:10+00:00" "time": "2021-06-27T11:44:38+00:00"
} }
], ],
"aliases": [], "aliases": [],
@ -1151,5 +1151,5 @@
"platform-overrides": { "platform-overrides": {
"php": "7.3" "php": "7.3"
}, },
"plugin-api-version": "2.1.0" "plugin-api-version": "2.0.0"
} }

View File

@ -9,16 +9,16 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "gettext/gettext", "name": "gettext/gettext",
"version": "v4.8.4", "version": "v4.8.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-gettext/Gettext.git", "url": "https://github.com/php-gettext/Gettext.git",
"reference": "58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1" "reference": "ef2e312dff383fc0e4cd62dd39042e1157f137d4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-gettext/Gettext/zipball/58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1", "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/ef2e312dff383fc0e4cd62dd39042e1157f137d4",
"reference": "58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1", "reference": "ef2e312dff383fc0e4cd62dd39042e1157f137d4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -26,7 +26,7 @@
"php": ">=5.4.0" "php": ">=5.4.0"
}, },
"require-dev": { "require-dev": {
"illuminate/view": "*", "illuminate/view": "^5.0.x-dev",
"phpunit/phpunit": "^4.8|^5.7|^6.5", "phpunit/phpunit": "^4.8|^5.7|^6.5",
"squizlabs/php_codesniffer": "^3.0", "squizlabs/php_codesniffer": "^3.0",
"symfony/yaml": "~2", "symfony/yaml": "~2",
@ -70,7 +70,7 @@
"support": { "support": {
"email": "oom@oscarotero.com", "email": "oom@oscarotero.com",
"issues": "https://github.com/oscarotero/Gettext/issues", "issues": "https://github.com/oscarotero/Gettext/issues",
"source": "https://github.com/php-gettext/Gettext/tree/v4.8.4" "source": "https://github.com/php-gettext/Gettext/tree/v4.8.5"
}, },
"funding": [ "funding": [
{ {
@ -86,27 +86,26 @@
"type": "patreon" "type": "patreon"
} }
], ],
"time": "2021-03-10T19:35:49+00:00" "time": "2021-07-13T16:45:53+00:00"
}, },
{ {
"name": "gettext/languages", "name": "gettext/languages",
"version": "2.6.0", "version": "2.8.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-gettext/Languages.git", "url": "https://github.com/php-gettext/Languages.git",
"reference": "38ea0482f649e0802e475f0ed19fa993bcb7a618" "reference": "4ad818b6341e177b7c508ec4c37e18932a7b788a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-gettext/Languages/zipball/38ea0482f649e0802e475f0ed19fa993bcb7a618", "url": "https://api.github.com/repos/php-gettext/Languages/zipball/4ad818b6341e177b7c508ec4c37e18932a7b788a",
"reference": "38ea0482f649e0802e475f0ed19fa993bcb7a618", "reference": "4ad818b6341e177b7c508ec4c37e18932a7b788a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3" "php": ">=5.3"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "^2.16.0",
"phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4"
}, },
"bin": [ "bin": [
@ -149,22 +148,32 @@
], ],
"support": { "support": {
"issues": "https://github.com/php-gettext/Languages/issues", "issues": "https://github.com/php-gettext/Languages/issues",
"source": "https://github.com/php-gettext/Languages/tree/2.6.0" "source": "https://github.com/php-gettext/Languages/tree/2.8.1"
}, },
"time": "2019-11-13T10:30:21+00:00" "funding": [
{
"url": "https://paypal.me/mlocati",
"type": "custom"
},
{
"url": "https://github.com/mlocati",
"type": "github"
}
],
"time": "2021-07-14T15:03:58+00:00"
}, },
{ {
"name": "mck89/peast", "name": "mck89/peast",
"version": "v1.13.0", "version": "v1.13.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/mck89/peast.git", "url": "https://github.com/mck89/peast.git",
"reference": "db38b1524f5bda921cbda2385e440c2bb71d18b4" "reference": "ad912d4cf6ac682974058b6d49df4c2bf93d424d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/mck89/peast/zipball/db38b1524f5bda921cbda2385e440c2bb71d18b4", "url": "https://api.github.com/repos/mck89/peast/zipball/ad912d4cf6ac682974058b6d49df4c2bf93d424d",
"reference": "db38b1524f5bda921cbda2385e440c2bb71d18b4", "reference": "ad912d4cf6ac682974058b6d49df4c2bf93d424d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -176,7 +185,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.13.0-dev" "dev-master": "1.13.2-dev"
} }
}, },
"autoload": { "autoload": {
@ -198,9 +207,9 @@
"description": "Peast is PHP library that generates AST for JavaScript code", "description": "Peast is PHP library that generates AST for JavaScript code",
"support": { "support": {
"issues": "https://github.com/mck89/peast/issues", "issues": "https://github.com/mck89/peast/issues",
"source": "https://github.com/mck89/peast/tree/v1.13.0" "source": "https://github.com/mck89/peast/tree/v1.13.2"
}, },
"time": "2021-05-22T16:06:09+00:00" "time": "2021-07-14T09:31:25+00:00"
}, },
{ {
"name": "mustache/mustache", "name": "mustache/mustache",
@ -306,6 +315,10 @@
"iri", "iri",
"sockets" "sockets"
], ],
"support": {
"issues": "https://github.com/WordPress/Requests/issues",
"source": "https://github.com/WordPress/Requests/tree/v1.8.1"
},
"time": "2021-06-04T09:56:25+00:00" "time": "2021-06-04T09:56:25+00:00"
}, },
{ {
@ -362,27 +375,30 @@
}, },
{ {
"name": "wp-cli/i18n-command", "name": "wp-cli/i18n-command",
"version": "v2.2.8", "version": "v2.2.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/wp-cli/i18n-command.git", "url": "https://github.com/wp-cli/i18n-command.git",
"reference": "8bc234617edc533590ac0f41080164a8d85ec9ce" "reference": "26e171c5708060b6d7cede9af934b946f5ec3a59"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/8bc234617edc533590ac0f41080164a8d85ec9ce", "url": "https://api.github.com/repos/wp-cli/i18n-command/zipball/26e171c5708060b6d7cede9af934b946f5ec3a59",
"reference": "8bc234617edc533590ac0f41080164a8d85ec9ce", "reference": "26e171c5708060b6d7cede9af934b946f5ec3a59",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"gettext/gettext": "^4.8", "gettext/gettext": "^4.8",
"mck89/peast": "^1.8", "mck89/peast": "^1.13",
"wp-cli/wp-cli": "^2.5" "wp-cli/wp-cli": "^2.5"
}, },
"require-dev": { "require-dev": {
"wp-cli/scaffold-command": "^1.2 || ^2", "wp-cli/scaffold-command": "^1.2 || ^2",
"wp-cli/wp-cli-tests": "^3.0.11" "wp-cli/wp-cli-tests": "^3.0.11"
}, },
"suggest": {
"ext-mbstring": "Used for calculating include/exclude matches in code extraction"
},
"type": "wp-cli-package", "type": "wp-cli-package",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
@ -417,9 +433,9 @@
"homepage": "https://github.com/wp-cli/i18n-command", "homepage": "https://github.com/wp-cli/i18n-command",
"support": { "support": {
"issues": "https://github.com/wp-cli/i18n-command/issues", "issues": "https://github.com/wp-cli/i18n-command/issues",
"source": "https://github.com/wp-cli/i18n-command/tree/v2.2.8" "source": "https://github.com/wp-cli/i18n-command/tree/v2.2.9"
}, },
"time": "2021-05-10T10:24:16+00:00" "time": "2021-07-20T21:25:54+00:00"
}, },
{ {
"name": "wp-cli/mustangostang-spyc", "name": "wp-cli/mustangostang-spyc",
@ -474,16 +490,16 @@
}, },
{ {
"name": "wp-cli/php-cli-tools", "name": "wp-cli/php-cli-tools",
"version": "v0.11.12", "version": "v0.11.13",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/wp-cli/php-cli-tools.git", "url": "https://github.com/wp-cli/php-cli-tools.git",
"reference": "e472e08489f7504d9e8c5c5a057e1419cd1b2b3e" "reference": "a2866855ac1abc53005c102e901553ad5772dc04"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/e472e08489f7504d9e8c5c5a057e1419cd1b2b3e", "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/a2866855ac1abc53005c102e901553ad5772dc04",
"reference": "e472e08489f7504d9e8c5c5a057e1419cd1b2b3e", "reference": "a2866855ac1abc53005c102e901553ad5772dc04",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -522,9 +538,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/wp-cli/php-cli-tools/issues", "issues": "https://github.com/wp-cli/php-cli-tools/issues",
"source": "https://github.com/wp-cli/php-cli-tools/tree/v0.11.12" "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.11.13"
}, },
"time": "2021-03-03T12:43:49+00:00" "time": "2021-07-01T15:08:16+00:00"
}, },
{ {
"name": "wp-cli/wp-cli", "name": "wp-cli/wp-cli",
@ -608,5 +624,5 @@
"platform-overrides": { "platform-overrides": {
"php": "7.0" "php": "7.0"
}, },
"plugin-api-version": "1.1.0" "plugin-api-version": "2.0.0"
} }

View File

@ -1,6 +1,119 @@
== Changelog == == Changelog ==
= 5.5.0-beta.1 2021-06-22 = = 5.6.0 2021-08-17 =
**WooCommerce**
* Enhancement - Product attributes lookup table synchronization when the table exists. #30041
* Tweak - Copy changes on WCS extension banner to include DHL Express. #30081
* Tweak - Remove Canada Post from WCS extensions banner. #30082
* Tweak - For 2021 theme, use theme font, and allow font-family customization. #30111
* Tweak - Allow the `api_restock` parameter to be specified via the refunds API, so that it's possible to refund without restocking refunded items. . #30179
* Tweak - Add option for checkout login reminder to the tracker. #30334
* Fix - Bulk export fix to overcome memory limitations. #29749
* Fix - Bulk export fix to overcome `file_put_contents` missing LOCK in distributed filesystems. #29749
* Fix - Restored behavior that allows downloadable product to have permissions set in any order. #29901
* Fix - Script error in enhanced select re-ordering that prevented saving new order. #30108
* Fix - PHP 8 error when cropping image values are not numeric. #30165
* Fix - "Search product" block not displaying textbox in shop page. #30287
* Fix - Replace hardcoded frontend JS script versions with WC version to bust cached/staled JS scripts. #30301
* Fix - Variable product showing HTML content while granting access for downloadable product in orders. #30305
* Fix - Replaced wp.passwordStrength deprecated method. #30191
* Dev - Apply `woocommerce_logout_default_redirect_url` filter to logout for custom endpoint. #29967
* Dev - Added new `woocommerce_email_sent` hook. #30123
* Dev - Add Refund and Returns Policy sample page. #30194
**WooCommerce Admin - 2.5.0**
- Add - Add a delete option to completed tasks #7300
- Add - Add unit tests around extended payment gateway controller #7133
- Add - Add payment gateway suggestion unit tests #7142
- Add - Add TableSummaryPlaceholder to support skeleton loading #7294
- Add - Feature toggle to disable Analytics UI #7168
- Add - Hook reference slotFill support #6833
- Add - Adding tests for PaymentGatewaySuggestions > List component #7201
- Add - Remote Inbox feature setting toggle #7298
- Dev - Add `woocommerce_admin_export_id` filter for customizing the export file name #7178
- Dev - Allow packages to be build independently, fix commonjs module builds. #7286
- Dev - Point the changelog linter to updated changelog entry location #7318
- Dev - Remove old payment gateway task components #7224
- Fix - Attribute filter bug with "any X" variations. #7046
- Fix - Currency display on Orders activity card on homescreen #7181
- Fix - Fix obsolete key property in gateway defaults #7229
- Fix - Fixing button state logic for remote payment gateways #7200
- Fix - Recommended gateway suggestions not displayed properly #7231
- Fix - Include onboarding settings on the analytic pages #7109
- Fix - Load Analytics API only when feature is turned on #7193
- Fix - Localize string for description #7219
- Fix - Filters: On update respect all other queries, not just persistedQueries #7155
- Fix - Use saved form values if available when switching tabs #7226
- Fix - Skip schedule customer data deletion on site deletion #7214
- Fix - WCPay not working in local payments task #7151
- Fix - Report export filtering bug. #7165
- Fix - Add padding on table header button #7213
- Fix - Use tab char for the CSV injection prevention. #7154
- Fix - Add height auto on autocomplete popover button #7225
- Fix - Make WooCommerce-admin full-screen minimum height 100vh important #7230
- Fix - Cache product/variation revenue query results. #7067
- Fix - Transient overlapping adjacent content. #7302
- Fix - Unused feature preloaded options #7299
- Fix - Fix missing translation strings for CES #7270
- Fix - Add missing translation strings in the business features section #7268
- Fix - Fix inbox note dismiss dropdown not closing on Safari #7278
- Fix - Fixed OBW - Business details style #7353
- Fix - Fix links on the dismiss dropdown are not clickable #7342
- Fix - Fix undefined method error when setting up WC Tax #7344
- Fix - Invalidate task status when enabling a payment gateway #7330
- Fix - Redirect to homescreen after payment gateway setup #7332
- Fix - Create workable defaults for Reports that dont have AdvancedFilters #7186
- Fix - Set default value for performanceIndicators variable #7343
- Fix - Sync the category lookup table when a new category gets created #7290
- Tweak - Remove performance indicators when Analytics Flag disabled #7234
- Tweak - Change event name when installing Google Listings and Ads. #7276
- Tweak - Removed unused feature flags #7233 and #7273
- Tweak - Render a spinner while woocommerce_setup_jetpack_opted_in is being loaded #7269
- Tweak - Repurpose disable wc-admin filter to remove optional features #7232
- Update - Notes to use a date range. #7222
- Update - Remove facebook extension from onboarding extensions fallback list #7287
- Performance - Add cache-control header to low stock REST API response #7364
- Performance - Add lazy loading by checking panel open status #7379
**WooCommerce Blocks - 5.4.1 & 5.5.1**
- Enhancement - Add screen reader text to price ranges. #4367
- Enhancement - Allow HTML in All Products Block Product Titles. #4363
- Enhancement - Made script and style handles consistent. #4324
- Enhancement - Show loading state in the express payments area whilst payment is processing or the page is redirecting. #4228
- Fix - Ensure product grids display as intended in the editor. #4424
- Fix - Wrap components in the Cart and Checkout sidebar in a TotalsWrapper. This will ensure consistent spacing and borders are applied to items in the sidebar. #4415
- Fix - Remove `couponName` filter and replace it with `coupons` filter. #4312
- Fix - Fix filtering by product type on Store API. #4422
- Fix - Fix a warning shown when fees are included in the order. #4360
- Fix - Prevent PHP notice for variable products without enabled variations. #4317
- Tweak - Add documentation for the IntegrationInterface which extension developers can use to register scripts, styles, and data with WooCommerce Blocks. #4394
- Tweak - Allow products to be added by SKU in the Hand-Picked Products block. #4366
- Tweak - Add Slot in the Discounts section of the Checkout sidebar to allow third party extensions to render their own components there. #4310
= 5.5.2 2021-07-22 =
* Fix - Add a new option allowing product downloads to be served using redirects as a last resort. #30288
* Fix - Remove unnecessary seacrh related 'where' clause added in the 'post_clauses' hook handling. #30335
* Fix - Check before calling $screen method to make sure its not null. #30277
**WooCommerce Admin - 2.4.4 & 2.4.3 & 2.4.2 **
* Fix - Fix homepage stock panel regression in 2.4.3. #7389
* Fix - Add a new low stock products endpoint to improve the performance. #7377
* Fix - Add lazy loading by checking panel open status. #7376
* Fix - Add cache-control header to low stock REST API response. #7364
= 5.5.1 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 5.5.0 2021-07-13 =
**WooCommerce** **WooCommerce**
@ -13,6 +126,7 @@
* Tweak - Rename Products, Products by Rating, and Recent Viewed Products widgets to Products list, Products by Rating list, and Recently Viewed Products list. #29941 * Tweak - Rename Products, Products by Rating, and Recent Viewed Products widgets to Products list, Products by Rating list, and Recently Viewed Products list. #29941
* Tweak - By default the postcode field will no longer be used, and the state field will become optional, for Curaçao. #29848 * Tweak - By default the postcode field will no longer be used, and the state field will become optional, for Curaçao. #29848
* Tweak - Handle WP_Error while creating placeholder image during install. #29783 * Tweak - Handle WP_Error while creating placeholder image during install. #29783
* Tweak - Exclude block templates from showing up in product edit page. #30138
* Fix - Allow block templates for WooCommerce pages. #30013 * Fix - Allow block templates for WooCommerce pages. #30013
* Fix - Download IDs are included in export CSV and imported when updating existing products to maintain download permissions. #29970 * Fix - Download IDs are included in export CSV and imported when updating existing products to maintain download permissions. #29970
* Fix - Fees added to an order from wp-admin are now calculated correctly the first time. #29945 * Fix - Fees added to an order from wp-admin are now calculated correctly the first time. #29945
@ -20,6 +134,7 @@
* Fix - Invoice emails now contain payment link if the order needs payment, not just when the order is "pending". #29833 * Fix - Invoice emails now contain payment link if the order needs payment, not just when the order is "pending". #29833
* Fix - Introduce meta to track stocks that refunded and restocked to properly handle stock recalculation. #29762 * Fix - Introduce meta to track stocks that refunded and restocked to properly handle stock recalculation. #29762
* Fix - Resolved a console error that could occur when clicking Add Shipping Zone. #30015 * Fix - Resolved a console error that could occur when clicking Add Shipping Zone. #30015
* Fix - Issue with Product Add-ons where multiple choice (images) setting would show false when hovering over image. #30096
* Dev - Added an if condition block to check for new install before creating Zero and Reduced rate tax classes in class-wc-install.php. #29938 * Dev - Added an if condition block to check for new install before creating Zero and Reduced rate tax classes in class-wc-install.php. #29938
* Dev - Product attributes lookup table usage when enabled. #29896 * Dev - Product attributes lookup table usage when enabled. #29896
* Dev - Set $woocommerce_loop name propriety to widget "Products". #29847 * Dev - Set $woocommerce_loop name propriety to widget "Products". #29847
@ -113,6 +228,12 @@
* Fix - Add target to the button to open it in a new tab #7110 * Fix - Add target to the button to open it in a new tab #7110
* Fix - Make `Search` accept synchronous `autocompleter.options`. #6884 * Fix - Make `Search` accept synchronous `autocompleter.options`. #6884
* Fix - Set autoload to false for all remote inbox notifications options. #7060 * Fix - Set autoload to false for all remote inbox notifications options. #7060
* Fix - Fix and refactor explat polling to use setTimeout. #7274
* Fix - Update the wordpress/babel-preset to avoid crashes in WP5.8 beta2 #7202
* Fix - Add fallback for the select/dispatch data-controls for older WP versions #7204
* Fix - The use of gridicons for Analytics section controls. #7237
* Fix - WordPress 5.8 compatibility UI fixes #7255
* Fix - CurrencyFactory constructor to use proper function #7261
* Tweak - Setup checklist copy revert. #7015 * Tweak - Setup checklist copy revert. #7015
* Tweak - Revert Card component removal #7167 * Tweak - Revert Card component removal #7167
* Update - Task list component with new Experimental Task list. #6849 * Update - Task list component with new Experimental Task list. #6849
@ -124,7 +245,7 @@
* Update - Remove original business step flow #7103 * Update - Remove original business step flow #7103
* Update - WooCommerce Shipping copy on onboarding steps #7148 * Update - WooCommerce Shipping copy on onboarding steps #7148
** WooCommerce Blocks Package - 5.2.0 & 5.3.0 & 5.3.1 ** ** WooCommerce Blocks Package - 5.2.0 & 5.3.0 & 5.3.1 & 5.3.2(dev only) **
* Enhancement - Hide legacy widgets with a feature-complete block equivalent from the widget area block inserter. #4237 * Enhancement - Hide legacy widgets with a feature-complete block equivalent from the widget area block inserter. #4237
* Enhancement - Provide block transforms for legacy widgets with a feature-complete block equivalent. #4292 * Enhancement - Provide block transforms for legacy widgets with a feature-complete block equivalent. #4292
@ -137,7 +258,7 @@
* Fix - Make links in the Product Categories List block unclickable in the editor #4339. * Fix - Make links in the Product Categories List block unclickable in the editor #4339.
* Fix - Fix rating stars not being shown in the Site Editor #4345. * Fix - Fix rating stars not being shown in the Site Editor #4345.
** WooCommerce Blocks Feature Plugin - 5.2.0 & 5.3.0 & 5.3.1 ** ** WooCommerce Blocks Feature Plugin - 5.2.0 & 5.3.0 & 5.3.1 & 5.3.2 **
* Enhancement - Added a key prop to each CartTotalItem within usePaymentMethodInterface. (4240) * Enhancement - Added a key prop to each CartTotalItem within usePaymentMethodInterface. (4240)
* Enhancement - Sync customer data during checkout with draft orders. (4197) * Enhancement - Sync customer data during checkout with draft orders. (4197)
@ -150,10 +271,10 @@
* Fix - Stopped a warning being shown when using WooCommerce Force Sells and adding a product with a Synced Force Sell to the cart. (4182) * Fix - Stopped a warning being shown when using WooCommerce Force Sells and adding a product with a Synced Force Sell to the cart. (4182)
* Fix - Fix some missing translations from the Cart and Checkout blocks. (4295) * Fix - Fix some missing translations from the Cart and Checkout blocks. (4295)
* Fix - Fix the flickering of the Proceed to Checkout button on quantity update in the Cart Block. (4293) * Fix - Fix the flickering of the Proceed to Checkout button on quantity update in the Cart Block. (4293)
* Fix - Remove the ability to filter snackbar notices. #4398
* Fix - Fix a display issue when itemized taxes are enabled, but no products in the cart are taxable. (4284) * Fix - Fix a display issue when itemized taxes are enabled, but no products in the cart are taxable. (4284)
* Compatibility - Add the ability for extensions to register callbacks to be executed by Blocks when the cart/extensions endpoint is hit. Extensions can now tell Blocks they need to do some server-side processing which will update the cart. (4298) * Compatibility - Add the ability for extensions to register callbacks to be executed by Blocks when the cart/extensions endpoint is hit. Extensions can now tell Blocks they need to do some server-side processing which will update the cart. (4298)
* Tweak - Add couponName filter to allow extensions to modify how coupons are displayed in the Cart and Checkout summary. (4166) * Tweak - Add couponName filter to allow extensions to modify how coupons are displayed in the Cart and Checkout summary. (4166)
* Tweak - Move Button and Label components to @woocommerce/blocks-checkout package. (4222)
* Tweak - Add Slot in the Discounts section of the cart sidebar to allow third party extensions to render their own components there. (4248) * Tweak - Add Slot in the Discounts section of the cart sidebar to allow third party extensions to render their own components there. (4248)
** ActionScheduler 3.2.0 & 3.2.1 ** ** ActionScheduler 3.2.0 & 3.2.1 **
@ -176,6 +297,12 @@
* Fix - Add extra safety/account for different versions of AS and different loading patterns. #714 * Fix - Add extra safety/account for different versions of AS and different loading patterns. #714
* Fix - Handle hidden columns (Tools → Scheduled Actions) | #600. * Fix - Handle hidden columns (Tools → Scheduled Actions) | #600.
= 5.4.2 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 5.4.1 2021-06-10 = = 5.4.1 2021-06-10 =
**WooCommerce** **WooCommerce**
@ -269,6 +396,12 @@
* Fix - Prevent parts of old addresses being displayed in the shipping calculator when changing countries. #4038 * Fix - Prevent parts of old addresses being displayed in the shipping calculator when changing countries. #4038
* Fix - issue in which email and phone fields are cleared when using a separate billing address. #4162 * Fix - issue in which email and phone fields are cleared when using a separate billing address. #4162
= 5.3.1 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 5.3.0 2021-05-11 = = 5.3.0 2021-05-11 =
**WooCommerce** **WooCommerce**
@ -400,6 +533,12 @@
* Tweak - Store profiler - Changed MailPoet's title and description #6886 * Tweak - Store profiler - Changed MailPoet's title and description #6886
* Tweak - Update PayU logo #6829 * Tweak - Update PayU logo #6829
= 5.2.3 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 5.2.2 2021-04-15 = = 5.2.2 2021-04-15 =
**WooCommerce** **WooCommerce**
@ -563,6 +702,12 @@
* Fix - Ensure sale badges have a uniform height in the Cart block. ([3897](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3897)) * Fix - Ensure sale badges have a uniform height in the Cart block. ([3897](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3897))
* Note - Internally, this release has modified how `AbstractBlock` (the base class for all of our blocks) functions, and how it loads assets. `AbstractBlock` is internal to this project and does not seem like something that would ever need to be extended by 3rd parties, but note if you are doing so for whatever reason, your implementation would need to be updated to match. ([3829](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3829)) * Note - Internally, this release has modified how `AbstractBlock` (the base class for all of our blocks) functions, and how it loads assets. `AbstractBlock` is internal to this project and does not seem like something that would ever need to be extended by 3rd parties, but note if you are doing so for whatever reason, your implementation would need to be updated to match. ([3829](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3829))
= 5.1.1 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 5.1.0 2021-03-09 = = 5.1.0 2021-03-09 =
**WooCommerce** **WooCommerce**
@ -671,6 +816,12 @@
* Dev - Added formatting classes to the Store API for extensions to consume. * Dev - Added formatting classes to the Store API for extensions to consume.
* Dev - Refactored and reordered Store API checkout processing to handle various edge cases and better support future extensibility. ([3454](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3454)) * Dev - Refactored and reordered Store API checkout processing to handle various edge cases and better support future extensibility. ([3454](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3454))
= 5.0.1 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 5.0.0 - 2021-02-09 = = 5.0.0 - 2021-02-09 =
**WooCommerce** **WooCommerce**
@ -738,6 +889,12 @@
* Enhancement - Add an "unread" indicator to inbox messages. #6047 * Enhancement - Add an "unread" indicator to inbox messages. #6047
* Add - Manage activity from home screen inbox message. #6072 * Add - Manage activity from home screen inbox message. #6072
= 4.9.3 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.9.2 2021-01-25 = = 4.9.2 2021-01-25 =
* Tweak - Disable untested plugin's notice from System Status and Plugin's page. #28840 * Tweak - Disable untested plugin's notice from System Status and Plugin's page. #28840
@ -862,6 +1019,12 @@
* Dev - Expose store/cart via ExtendRestApi to extensions. ([3445](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3445)) * Dev - Expose store/cart via ExtendRestApi to extensions. ([3445](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3445))
* Dev - Added formatting classes to the Store API for extensions to consume. * Dev - Added formatting classes to the Store API for extensions to consume.
= 4.8.1 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.8.0 - 2020-12-08 = = 4.8.0 - 2020-12-08 =
**WooCommerce** **WooCommerce**
@ -975,6 +1138,12 @@
* Fix - Twenty Twenty One Button and Placeholder Styling. #3443 * Fix - Twenty Twenty One Button and Placeholder Styling. #3443
* Fix - checkbox and textarea styles in Twenty Twenty One when it has dark controls active. #3450 * Fix - checkbox and textarea styles in Twenty Twenty One when it has dark controls active. #3450
= 4.7.2 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.7.1 - 2020-11-24 = = 4.7.1 - 2020-11-24 =
**WooCommerce** **WooCommerce**
@ -1037,6 +1206,12 @@
* Tweak: Add BR and IN to list of stripe countries [#5377](https://github.com/woocommerce/woocommerce-admin/pull/5377) * Tweak: Add BR and IN to list of stripe countries [#5377](https://github.com/woocommerce/woocommerce-admin/pull/5377)
* Fix: Redirect instead of stalling on WCPay Inbox note action [#5413](https://github.com/woocommerce/woocommerce-admin/pull/5413) * Fix: Redirect instead of stalling on WCPay Inbox note action [#5413](https://github.com/woocommerce/woocommerce-admin/pull/5413)
= 4.6.3 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.6.2 - 2020-11-05 = = 4.6.2 - 2020-11-05 =
**WooCommerce** **WooCommerce**
@ -1159,6 +1334,12 @@
- Create DebouncedValidatedTextInput component. ([3108](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3108)) - Create DebouncedValidatedTextInput component. ([3108](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3108))
- Merge ProductPrice atomic block and component. ([3065](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3065)) - Merge ProductPrice atomic block and component. ([3065](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3065))
= 4.5.3 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.5.2 - 2020-09-14 = = 4.5.2 - 2020-09-14 =
* Fix - Revert the changes in filtering by attribute that were introduced in WooCommerce 4.4. #27625 * Fix - Revert the changes in filtering by attribute that were introduced in WooCommerce 4.4. #27625
* Fix - Adjusted validation to allow for variations with "0" as an attribute value. #27633 * Fix - Adjusted validation to allow for variations with "0" as an attribute value. #27633
@ -1207,6 +1388,12 @@
* Dev - Task list - add a shortcut back to store setup. #4853 * Dev - Task list - add a shortcut back to store setup. #4853
* Dev - Update the colors of the illustrations in the welcome modal. #4945 * Dev - Update the colors of the illustrations in the welcome modal. #4945
= 4.4.2 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.4.1 - 2020-08-19 = = 4.4.1 - 2020-08-19 =
**WooCommerce** **WooCommerce**
@ -1355,6 +1542,12 @@
* Fix - 'Product Summary' in All Products block is not pulling in the short description of the product. #2913 * Fix - 'Product Summary' in All Products block is not pulling in the short description of the product. #2913
* Dev - Add query filter when searching for a table. #2886 * Dev - Add query filter when searching for a table. #2886
= 4.3.4 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.3.3 - 2020-08-14 = = 4.3.3 - 2020-08-14 =
**WooCommerce REST API 1.0.10-pl-2** **WooCommerce REST API 1.0.10-pl-2**
@ -1536,6 +1729,12 @@
* Dev - Table creation validation for install routine #2287 * Dev - Table creation validation for install routine #2287
* Dev - Update the icons used in the blocks. #1644 * Dev - Update the icons used in the blocks. #1644
= 4.2.3 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.2.2 - 2020-06-23 = = 4.2.2 - 2020-06-23 =
**WooCommerce** **WooCommerce**
@ -1614,6 +1813,12 @@
* Dev - Dynamic Currency with Context API #4027 * Dev - Dynamic Currency with Context API #4027
* Dev - Remove Duplicate array entry #4049 * Dev - Remove Duplicate array entry #4049
= 4.1.2 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.1.1 - 2020-05-19 = = 4.1.1 - 2020-05-19 =
* Enhancement - Added notice about public uploads directory. #26207 * Enhancement - Added notice about public uploads directory. #26207
@ -1673,6 +1878,12 @@
* Dev - Adds usage data for the of cart & checkout blocks (currently in development in WooCommmerce Blocks plugin) to the WC Tracker snapshot. #26084 * Dev - Adds usage data for the of cart & checkout blocks (currently in development in WooCommmerce Blocks plugin) to the WC Tracker snapshot. #26084
* Dev - Implement some additional tracks for coupons, orders, and products. #26085 * Dev - Implement some additional tracks for coupons, orders, and products. #26085
= 4.0.2 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 4.0.1 - 2020-03-18 = = 4.0.1 - 2020-03-18 =
**WooCommerce** **WooCommerce**
@ -1791,6 +2002,12 @@
* Dev - Applies woocommerce_maxmind_geolocation_database_path in MaxMind database migration. #25681 * Dev - Applies woocommerce_maxmind_geolocation_database_path in MaxMind database migration. #25681
* Dev - Support both .data() and .dataset for formdata in add to cart requests. #25726 * Dev - Support both .data() and .dataset for formdata in add to cart requests. #25726
= 3.9.4 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 3.9.3 - 2020-02-27 = = 3.9.3 - 2020-02-27 =
* Fix - Replaced deprecated Jetpack::is_staging_site call. #25670 * Fix - Replaced deprecated Jetpack::is_staging_site call. #25670
* Fix - Corrected the cache invalidation behavior of order item CRUD actions. #25734 * Fix - Corrected the cache invalidation behavior of order item CRUD actions. #25734
@ -1900,6 +2117,12 @@
* Localization - Fixed translatable string comments for translators. #24928 * Localization - Fixed translatable string comments for translators. #24928
* Localization - Add postcode validation for Slovenia. #25174 * Localization - Add postcode validation for Slovenia. #25174
= 3.8.2 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 3.8.0 - 2019-11-05 = = 3.8.0 - 2019-11-05 =
* Enhancement - Show error message in "My Account - view order" if order does not exist. #24435 * Enhancement - Show error message in "My Account - view order" if order does not exist. #24435
* Enhancement - Add support to allow connect and install for in-app purchase flow. #24451 * Enhancement - Add support to allow connect and install for in-app purchase flow. #24451
@ -2016,6 +2239,12 @@
* Security - Add an exit after the redirect when checking author archive capabilities for customers. * Security - Add an exit after the redirect when checking author archive capabilities for customers.
* Security - Ensure 404 pages with single product urls cannot be exploited using Open Redirect. * Security - Ensure 404 pages with single product urls cannot be exploited using Open Redirect.
= 3.7.2 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 3.7.1 - 2019-10-09 = 3.7.1 - 2019-10-09
* Security - Add an exit after the redirect when checking author archive capabilities for customers. * Security - Add an exit after the redirect when checking author archive capabilities for customers.
* Security - Ensure 404 pages with single product urls cannot be exploited using Open Redirect. * Security - Ensure 404 pages with single product urls cannot be exploited using Open Redirect.
@ -2122,6 +2351,12 @@
* Localization - Add new currency for São Tomé, Príncipe dobra and Mauritanian ouguiya. #23950 * Localization - Add new currency for São Tomé, Príncipe dobra and Mauritanian ouguiya. #23950
* Localization - Change Canada poscode label to `Postal code`. #23740 * Localization - Change Canada poscode label to `Postal code`. #23740
= 3.6.6 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 3.6.5 - 2019-07-02 = = 3.6.5 - 2019-07-02 =
* Security - Introduce file type check for tax rate importer. * Security - Introduce file type check for tax rate importer.
* Security - Added nonce check to CSV importer actions. * Security - Added nonce check to CSV importer actions.
@ -2445,6 +2680,12 @@
* Localization - Update CA address format. #22692 * Localization - Update CA address format. #22692
* Localization - Updated JP field order. #22774 * Localization - Updated JP field order. #22774
= 3.5.9 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 3.5.8 - 2019-04-16 = = 3.5.8 - 2019-04-16 =
* Security - Added escaping for states on the user profile screen. * Security - Added escaping for states on the user profile screen.
* Security - Added escaping for PhotoSwipe captions. * Security - Added escaping for PhotoSwipe captions.
@ -2780,6 +3021,12 @@
* Localization - Make Romania state selection mandatory. #21180 * Localization - Make Romania state selection mandatory. #21180
* Localization - Make city field optional and hidden for Singapore addresses. #21016 * Localization - Make city field optional and hidden for Singapore addresses. #21016
= 3.4.8 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 3.4.6 - 2018-10-11 = = 3.4.6 - 2018-10-11 =
* Fix - Security issues * Fix - Security issues
* Fix - Allow percent coupons with sale restrictions to apply to carts with sale items in them. #21241 * Fix - Allow percent coupons with sale restrictions to apply to carts with sale items in them. #21241
@ -3084,6 +3331,12 @@
* Localization - Various spelling, grammar fixes, and phrasing improvements. * Localization - Various spelling, grammar fixes, and phrasing improvements.
* Localization - Fix missing Bahrain country code. #20061 * Localization - Fix missing Bahrain country code. #20061
= 3.3.6 2021-07-14 =
**WooCommerce**
* Fix - Patched security vulnerability. https://woocommerce.com/posts/critical-vulnerability-detected-july-2021/
= 3.3.5 - 2018-04-10 = = 3.3.5 - 2018-04-10 =
* Fix - Shop page notice should not appear when editing the "Hello World!" page. * Fix - Shop page notice should not appear when editing the "Hello World!" page.
* Fix - Inconsistent order item refund sign. * Fix - Inconsistent order item refund sign.

View File

@ -21,8 +21,8 @@
"pelago/emogrifier": "3.1.0", "pelago/emogrifier": "3.1.0",
"psr/container": "1.0.0", "psr/container": "1.0.0",
"woocommerce/action-scheduler": "3.2.1", "woocommerce/action-scheduler": "3.2.1",
"woocommerce/woocommerce-admin": "2.4.0-rc.2", "woocommerce/woocommerce-admin": "2.5.0-beta.2",
"woocommerce/woocommerce-blocks": "5.3.1" "woocommerce/woocommerce-blocks": "5.5.1"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.4" "bamarni/composer-bin-plugin": "^1.4"

44
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "6fb169d13104940f13185353a857a799", "content-hash": "4c8530b8fbe190ef3cd009b7be769677",
"packages": [ "packages": [
{ {
"name": "automattic/jetpack-autoloader", "name": "automattic/jetpack-autoloader",
@ -532,16 +532,16 @@
}, },
{ {
"name": "woocommerce/woocommerce-admin", "name": "woocommerce/woocommerce-admin",
"version": "2.4.0-rc.2", "version": "2.5.0-beta.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git", "url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "ed72985cd459831c555dff2ff5f75111b6e210c1" "reference": "12d7339e0d298e0a1fd21be5731a84fbf0141fca"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/ed72985cd459831c555dff2ff5f75111b6e210c1", "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/12d7339e0d298e0a1fd21be5731a84fbf0141fca",
"reference": "ed72985cd459831c555dff2ff5f75111b6e210c1", "reference": "12d7339e0d298e0a1fd21be5731a84fbf0141fca",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -550,6 +550,7 @@
"php": ">=7.0" "php": ">=7.0"
}, },
"require-dev": { "require-dev": {
"automattic/jetpack-changelogger": "^1.1",
"bamarni/composer-bin-plugin": "^1.4", "bamarni/composer-bin-plugin": "^1.4",
"suin/phpcs-psr4-sniff": "^2.2", "suin/phpcs-psr4-sniff": "^2.2",
"woocommerce/woocommerce-sniffs": "0.1.0" "woocommerce/woocommerce-sniffs": "0.1.0"
@ -563,6 +564,23 @@
}, },
"bamarni-bin": { "bamarni-bin": {
"target-directory": "bin/composer" "target-directory": "bin/composer"
},
"changelogger": {
"changelog": "./changelog.txt",
"formatter": {
"filename": "bin/changelogger/WCAdminFormatter.php"
},
"versioning": "semver",
"changes-dir": "./changelogs",
"types": [
"Fix",
"Add",
"Update",
"Dev",
"Tweak",
"Performance",
"Enhancement"
]
} }
}, },
"autoload": { "autoload": {
@ -578,22 +596,22 @@
"homepage": "https://github.com/woocommerce/woocommerce-admin", "homepage": "https://github.com/woocommerce/woocommerce-admin",
"support": { "support": {
"issues": "https://github.com/woocommerce/woocommerce-admin/issues", "issues": "https://github.com/woocommerce/woocommerce-admin/issues",
"source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.4.0-rc.2" "source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.5.0-beta.2"
}, },
"time": "2021-06-18T05:58:25+00:00" "time": "2021-07-19T18:07:02+00:00"
}, },
{ {
"name": "woocommerce/woocommerce-blocks", "name": "woocommerce/woocommerce-blocks",
"version": "v5.3.1", "version": "v5.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git", "url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git",
"reference": "28c7c4f9b5cace9098fb2246ff93abe110a26bca" "reference": "f3d8dbadb745cbb2544e86dfb3864e870146d197"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/28c7c4f9b5cace9098fb2246ff93abe110a26bca", "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/f3d8dbadb745cbb2544e86dfb3864e870146d197",
"reference": "28c7c4f9b5cace9098fb2246ff93abe110a26bca", "reference": "f3d8dbadb745cbb2544e86dfb3864e870146d197",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -629,9 +647,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues", "issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues",
"source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v5.3.1" "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v5.5.1"
}, },
"time": "2021-06-15T09:12:48+00:00" "time": "2021-07-14T20:59:04+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [

952
i18n/currency-info.php Normal file
View File

@ -0,0 +1,952 @@
<?php
/**
* Currency formatting information
*
* @package WooCommerce\i18n
* @version 5.7.0
*/
defined( 'ABSPATH' ) || exit;
$global_formats = array(
'ls_comma_dot_ltr' => array(
'thousand_sep' => '.',
'decimal_sep' => ',',
'direction' => 'ltr',
'currency_pos' => 'left_space',
),
'ls_comma_dot_rtl' => array(
'thousand_sep' => '.',
'decimal_sep' => ',',
'direction' => 'rtl',
'currency_pos' => 'left_space',
),
'ls_comma_space_ltr' => array(
'thousand_sep' => ' ',
'decimal_sep' => ',',
'direction' => 'ltr',
'currency_pos' => 'left_space',
),
'ls_dot_apos_ltr' => array(
'thousand_sep' => '\'',
'decimal_sep' => '.',
'direction' => 'ltr',
'currency_pos' => 'left_space',
),
'ls_dot_comma_ltr' => array(
'thousand_sep' => ',',
'decimal_sep' => '.',
'direction' => 'ltr',
'currency_pos' => 'left_space',
),
'ls_dot_comma_rtl' => array(
'thousand_sep' => ',',
'decimal_sep' => '.',
'direction' => 'rtl',
'currency_pos' => 'left_space',
),
'lx_comma_dot_ltr' => array(
'thousand_sep' => '.',
'decimal_sep' => ',',
'direction' => 'ltr',
'currency_pos' => 'left',
),
'lx_comma_dot_rtl' => array(
'thousand_sep' => '.',
'decimal_sep' => ',',
'direction' => 'rtl',
'currency_pos' => 'left',
),
'lx_comma_space_ltr' => array(
'thousand_sep' => ' ',
'decimal_sep' => ',',
'direction' => 'ltr',
'currency_pos' => 'left',
),
'lx_dot_comma_ltr' => array(
'thousand_sep' => ',',
'decimal_sep' => '.',
'direction' => 'ltr',
'currency_pos' => 'left',
),
'lx_dot_space_ltr' => array(
'thousand_sep' => ' ',
'decimal_sep' => '.',
'direction' => 'ltr',
'currency_pos' => 'left',
),
'rs_comma_dot_ltr' => array(
'thousand_sep' => '.',
'decimal_sep' => ',',
'direction' => 'ltr',
'currency_pos' => 'right_space',
),
'rs_comma_dot_rtl' => array(
'thousand_sep' => '.',
'decimal_sep' => ',',
'direction' => 'rtl',
'currency_pos' => 'right_space',
),
'rs_comma_space_ltr' => array(
'thousand_sep' => ' ',
'decimal_sep' => ',',
'direction' => 'ltr',
'currency_pos' => 'right_space',
),
'rs_dot_apos_ltr' => array(
'thousand_sep' => '\'',
'decimal_sep' => '.',
'direction' => 'ltr',
'currency_pos' => 'right_space',
),
'rs_dot_comma_ltr' => array(
'thousand_sep' => ',',
'decimal_sep' => '.',
'direction' => 'ltr',
'currency_pos' => 'right_space',
),
'rs_dot_comma_rtl' => array(
'thousand_sep' => ',',
'decimal_sep' => '.',
'direction' => 'rtl',
'currency_pos' => 'right_space',
),
'rx_comma_dot_ltr' => array(
'thousand_sep' => '.',
'decimal_sep' => ',',
'direction' => 'ltr',
'currency_pos' => 'right',
),
'rx_dot_comma_ltr' => array(
'thousand_sep' => ',',
'decimal_sep' => '.',
'direction' => 'ltr',
'currency_pos' => 'right',
),
);
return array(
'AED' => array(
'ar_AE' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'AFN' => array(
'fa_AF' => $global_formats['ls_comma_dot_rtl'],
'default' => $global_formats['ls_comma_dot_rtl'],
'ps_AF' => $global_formats['rs_comma_dot_rtl'],
'uz_AF' => $global_formats['rs_comma_space_ltr'],
),
'ALL' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'sq_AL' => $global_formats['rs_comma_space_ltr'],
),
'AMD' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'hy_AM' => $global_formats['rs_comma_space_ltr'],
),
'ANG' => array(
'en_SX' => $global_formats['lx_dot_comma_ltr'],
'nl_CW' => $global_formats['ls_comma_dot_ltr'],
'nl_SX' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['ls_comma_dot_ltr'],
),
'AOA' => array(
'pt_AO' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
),
'ARS' => array(
'es_AR' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['ls_comma_dot_ltr'],
),
'AUD' => array(
'en_AU' => $global_formats['lx_dot_comma_ltr'],
'en_CC' => $global_formats['lx_dot_comma_ltr'],
'en_CX' => $global_formats['lx_dot_comma_ltr'],
'en_KI' => $global_formats['lx_dot_comma_ltr'],
'en_NF' => $global_formats['lx_dot_comma_ltr'],
'en_NR' => $global_formats['lx_dot_comma_ltr'],
'en_TV' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'AWG' => array(
'nl_AW' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['ls_comma_dot_ltr'],
),
'AZN' => array(
'default' => $global_formats['rs_comma_dot_ltr'],
'az_AZ' => $global_formats['rs_comma_dot_ltr'],
),
'BAM' => array(
'hr_BA' => $global_formats['rs_comma_dot_ltr'],
'sr_Latn_BA' => $global_formats['rs_comma_dot_ltr'],
'default' => $global_formats['rs_comma_dot_ltr'],
'bs_BA' => $global_formats['rs_comma_dot_ltr'],
),
'BBD' => array(
'en_BB' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'BDT' => array(
'default' => $global_formats['rx_dot_comma_ltr'],
'bn_BD' => $global_formats['rx_dot_comma_ltr'],
),
'BGN' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'bg_BG' => $global_formats['rs_comma_space_ltr'],
),
'BHD' => array(
'ar_BH' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'BIF' => array(
'en_BI' => $global_formats['lx_dot_comma_ltr'],
'fr_BI' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'rn_BI' => $global_formats['rx_comma_dot_ltr'],
),
'BMD' => array(
'en_BM' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'BND' => array(
'ms_BN' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['ls_comma_dot_ltr'],
),
'BOB' => array(
'es_BO' => $global_formats['lx_comma_dot_ltr'],
'qu_BO' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['lx_comma_dot_ltr'],
),
'BRL' => array(
'default' => $global_formats['ls_comma_dot_ltr'],
'pt_BR' => $global_formats['ls_comma_dot_ltr'],
),
'BSD' => array(
'en_BS' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'BTN' => array(
'default' => $global_formats['lx_dot_comma_ltr'],
'dz_BT' => $global_formats['lx_dot_comma_ltr'],
),
'BWP' => array(
'en_BW' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'BYN' => array(
'ru_BY' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
'be_BY' => $global_formats['rs_comma_space_ltr'],
),
'BZD' => array(
'en_BZ' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'CAD' => array(
'en_CA' => $global_formats['lx_dot_comma_ltr'],
'fr_CA' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'CDF' => array(
'fr_CD' => $global_formats['rs_comma_space_ltr'],
'sw_CD' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
'ln_CD' => $global_formats['rs_comma_dot_ltr'],
),
'CHF' => array(
'de_CH' => $global_formats['ls_dot_apos_ltr'],
'de_LI' => $global_formats['ls_dot_apos_ltr'],
'fr_CH' => $global_formats['rs_comma_space_ltr'],
'gsw_LI' => $global_formats['rs_dot_apos_ltr'],
'it_CH' => $global_formats['ls_dot_apos_ltr'],
'default' => $global_formats['ls_dot_apos_ltr'],
'gsw_CH' => $global_formats['rs_dot_apos_ltr'],
'rm_CH' => $global_formats['rs_dot_apos_ltr'],
),
'CLP' => array(
'es_CL' => $global_formats['lx_comma_dot_ltr'],
'default' => $global_formats['lx_comma_dot_ltr'],
),
'CNY' => array(
'default' => $global_formats['lx_dot_comma_ltr'],
'bo_CN' => $global_formats['ls_dot_comma_ltr'],
'ug_CN' => $global_formats['lx_dot_comma_ltr'],
'zh_CN' => $global_formats['lx_dot_comma_ltr'],
),
'COP' => array(
'es_CO' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['ls_comma_dot_ltr'],
),
'CRC' => array(
'es_CR' => $global_formats['lx_comma_space_ltr'],
'default' => $global_formats['lx_comma_space_ltr'],
),
'CUC' => array(
'es_CU' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'CVE' => array(
'pt_CV' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
),
'CZK' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'cs_CZ' => $global_formats['rs_comma_space_ltr'],
),
'DJF' => array(
'ar_DJ' => $global_formats['rs_comma_dot_rtl'],
'fr_DJ' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'DKK' => array(
'default' => $global_formats['rs_comma_dot_ltr'],
'da_DK' => $global_formats['rs_comma_dot_ltr'],
'fo_FO' => $global_formats['rs_comma_dot_ltr'],
'kl_GL' => $global_formats['lx_comma_dot_ltr'],
),
'DOP' => array(
'es_DO' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'DZD' => array(
'ar_DZ' => $global_formats['ls_comma_dot_rtl'],
'fr_DZ' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['ls_comma_dot_rtl'],
),
'EGP' => array(
'ar_EG' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'ERN' => array(
'ar_ER' => $global_formats['rs_comma_dot_rtl'],
'en_ER' => $global_formats['lx_dot_comma_ltr'],
'ti_ER' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'ETB' => array(
'default' => $global_formats['lx_dot_comma_ltr'],
'am_ET' => $global_formats['lx_dot_comma_ltr'],
),
'EUR' => array(
'ca_AD' => $global_formats['rs_comma_dot_ltr'],
'de_AT' => $global_formats['ls_comma_space_ltr'],
'de_BE' => $global_formats['rs_comma_dot_ltr'],
'de_LU' => $global_formats['rs_comma_dot_ltr'],
'el_CY' => $global_formats['rs_comma_dot_ltr'],
'en_IE' => $global_formats['lx_dot_comma_ltr'],
'en_MT' => $global_formats['lx_dot_comma_ltr'],
'es_EA' => $global_formats['rs_comma_dot_ltr'],
'es_IC' => $global_formats['rs_comma_dot_ltr'],
'fr_BE' => $global_formats['rs_comma_space_ltr'],
'fr_BL' => $global_formats['rs_comma_space_ltr'],
'fr_GF' => $global_formats['rs_comma_space_ltr'],
'fr_GP' => $global_formats['rs_comma_space_ltr'],
'fr_LU' => $global_formats['rs_comma_dot_ltr'],
'fr_MC' => $global_formats['rs_comma_space_ltr'],
'fr_MF' => $global_formats['rs_comma_space_ltr'],
'fr_MQ' => $global_formats['rs_comma_space_ltr'],
'fr_PM' => $global_formats['rs_comma_space_ltr'],
'fr_RE' => $global_formats['rs_comma_space_ltr'],
'fr_YT' => $global_formats['rs_comma_space_ltr'],
'it_SM' => $global_formats['rs_comma_dot_ltr'],
'it_VA' => $global_formats['rs_comma_dot_ltr'],
'nl_BE' => $global_formats['ls_comma_dot_ltr'],
'pt_PT' => $global_formats['rs_comma_space_ltr'],
'sq_XK' => $global_formats['rs_comma_space_ltr'],
'sr_Latn_ME' => $global_formats['rs_comma_dot_ltr'],
'sr_Latn_XK' => $global_formats['rs_comma_dot_ltr'],
'sv_AX' => $global_formats['rs_comma_space_ltr'],
'sv_FI' => $global_formats['rs_comma_space_ltr'],
'tr_CY' => $global_formats['lx_comma_dot_ltr'],
'default' => $global_formats['rs_comma_dot_ltr'],
'ast_ES' => $global_formats['rs_comma_dot_ltr'],
'ca_ES' => $global_formats['rs_comma_dot_ltr'],
'de_DE' => $global_formats['rs_comma_dot_ltr'],
'el_GR' => $global_formats['rs_comma_dot_ltr'],
'es_ES' => $global_formats['rs_comma_dot_ltr'],
'et_EE' => $global_formats['rs_comma_space_ltr'],
'eu_ES' => $global_formats['rs_comma_dot_ltr'],
'fi_FI' => $global_formats['rs_comma_space_ltr'],
'fr_FR' => $global_formats['rs_comma_space_ltr'],
'fy_NL' => $global_formats['ls_comma_dot_ltr'],
'ga_IE' => $global_formats['lx_dot_comma_ltr'],
'gl_ES' => $global_formats['rs_comma_dot_ltr'],
'it_IT' => $global_formats['rs_comma_dot_ltr'],
'lb_LU' => $global_formats['rs_comma_dot_ltr'],
'lt_LT' => $global_formats['rs_comma_space_ltr'],
'lv_LV' => $global_formats['rs_comma_space_ltr'],
'mt_MT' => $global_formats['lx_dot_comma_ltr'],
'nl_NL' => $global_formats['ls_comma_dot_ltr'],
'sk_SK' => $global_formats['rs_comma_space_ltr'],
'sl_SI' => $global_formats['rs_comma_dot_ltr'],
),
'FJD' => array(
'en_FJ' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'FKP' => array(
'en_FK' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'GBP' => array(
'en_GB' => $global_formats['lx_dot_comma_ltr'],
'en_GG' => $global_formats['lx_dot_comma_ltr'],
'en_IM' => $global_formats['lx_dot_comma_ltr'],
'en_JE' => $global_formats['lx_dot_comma_ltr'],
'ga_GB' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'cy_GB' => $global_formats['lx_dot_comma_ltr'],
'gd_GB' => $global_formats['lx_dot_comma_ltr'],
'gv_IM' => $global_formats['lx_dot_comma_ltr'],
),
'GEL' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'ka_GE' => $global_formats['rs_comma_space_ltr'],
'os_GE' => $global_formats['ls_comma_space_ltr'],
),
'GHS' => array(
'en_GH' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'ak_GH' => $global_formats['lx_dot_comma_ltr'],
'ee_GH' => $global_formats['lx_dot_comma_ltr'],
),
'GIP' => array(
'en_GI' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'GMD' => array(
'en_GM' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'GNF' => array(
'fr_GN' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
),
'GTQ' => array(
'es_GT' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'GYD' => array(
'en_GY' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'HKD' => array(
'en_HK' => $global_formats['lx_dot_comma_ltr'],
'zh_Hant_HK' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'HNL' => array(
'es_HN' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'HRK' => array(
'default' => $global_formats['rs_comma_dot_ltr'],
'hr_HR' => $global_formats['rs_comma_dot_ltr'],
),
'HUF' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'hu_HU' => $global_formats['rs_comma_space_ltr'],
),
'IDR' => array(
'default' => $global_formats['lx_comma_dot_ltr'],
'id_ID' => $global_formats['lx_comma_dot_ltr'],
),
'ILS' => array(
'ar_IL' => $global_formats['rs_comma_dot_rtl'],
'ar_PS' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
'he_IL' => $global_formats['rs_dot_comma_rtl'],
),
'INR' => array(
'bn_IN' => $global_formats['rx_dot_comma_ltr'],
'en_IN' => $global_formats['lx_dot_comma_ltr'],
'ne_IN' => $global_formats['ls_dot_comma_ltr'],
'ur_IN' => $global_formats['ls_comma_dot_rtl'],
'default' => $global_formats['lx_dot_comma_ltr'],
'as_IN' => $global_formats['ls_dot_comma_ltr'],
'dz_BT' => $global_formats['lx_dot_comma_ltr'],
'gu_IN' => $global_formats['lx_dot_comma_ltr'],
'hi_IN' => $global_formats['lx_dot_comma_ltr'],
'kn_IN' => $global_formats['lx_dot_comma_ltr'],
'kok_IN' => $global_formats['ls_dot_comma_ltr'],
'mai_IN' => $global_formats['ls_dot_comma_ltr'],
'ml_IN' => $global_formats['lx_dot_comma_ltr'],
'mr_IN' => $global_formats['lx_dot_comma_ltr'],
'or_IN' => $global_formats['lx_dot_comma_ltr'],
'sa_IN' => $global_formats['lx_dot_comma_ltr'],
'sd_PK' => $global_formats['rs_comma_dot_ltr'],
'ta_IN' => $global_formats['ls_dot_comma_ltr'],
'te_IN' => $global_formats['lx_dot_comma_ltr'],
),
'IQD' => array(
'ar_IQ' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
'ckb_IQ' => $global_formats['rs_comma_dot_rtl'],
),
'IRR' => array(
'default' => $global_formats['lx_comma_dot_rtl'],
'fa_IR' => $global_formats['lx_comma_dot_rtl'],
),
'ISK' => array(
'default' => $global_formats['rs_comma_dot_ltr'],
'is_IS' => $global_formats['rs_comma_dot_ltr'],
),
'JMD' => array(
'en_JM' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'JOD' => array(
'ar_JO' => $global_formats['rs_comma_dot_rtl'],
'ar_PS' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'JPY' => array(
'default' => $global_formats['lx_dot_comma_ltr'],
'ja_JP' => $global_formats['lx_dot_comma_ltr'],
),
'KES' => array(
'en_KE' => $global_formats['lx_dot_comma_ltr'],
'sw_KE' => $global_formats['ls_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'KGS' => array(
'ru_KG' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
'ky_KG' => $global_formats['rs_comma_space_ltr'],
),
'KHR' => array(
'default' => $global_formats['rx_comma_dot_ltr'],
'km_KH' => $global_formats['rx_comma_dot_ltr'],
),
'KMF' => array(
'ar_KM' => $global_formats['rs_comma_dot_rtl'],
'fr_KM' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'KPW' => array(
'ko_KP' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'KRW' => array(
'default' => $global_formats['lx_dot_comma_ltr'],
'ko_KR' => $global_formats['lx_dot_comma_ltr'],
),
'KWD' => array(
'ar_KW' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'KYD' => array(
'en_KY' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'KZT' => array(
'ru_KZ' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
'kk_KZ' => $global_formats['rs_comma_space_ltr'],
),
'LAK' => array(
'default' => $global_formats['lx_comma_dot_ltr'],
'lo_LA' => $global_formats['lx_comma_dot_ltr'],
),
'LBP' => array(
'ar_LB' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'LKR' => array(
'ta_LK' => $global_formats['ls_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'si_LK' => $global_formats['lx_dot_comma_ltr'],
),
'LRD' => array(
'en_LR' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'LSL' => array(
'en_LS' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'LYD' => array(
'ar_LY' => $global_formats['ls_comma_dot_rtl'],
'default' => $global_formats['ls_comma_dot_rtl'],
),
'MAD' => array(
'ar_EH' => $global_formats['ls_dot_comma_rtl'],
'ar_MA' => $global_formats['ls_comma_dot_rtl'],
'fr_MA' => $global_formats['rs_comma_dot_ltr'],
'default' => $global_formats['ls_dot_comma_rtl'],
'tzm_MA' => $global_formats['rs_comma_space_ltr'],
),
'MDL' => array(
'ro_MD' => $global_formats['rs_comma_dot_ltr'],
'default' => $global_formats['rs_comma_dot_ltr'],
),
'MGA' => array(
'en_MG' => $global_formats['lx_dot_comma_ltr'],
'fr_MG' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'mg_MG' => $global_formats['ls_dot_comma_ltr'],
),
'MKD' => array(
'sq_MK' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_dot_ltr'],
'mk_MK' => $global_formats['rs_comma_dot_ltr'],
),
'MMK' => array(
'default' => $global_formats['rs_dot_comma_ltr'],
'my_MM' => $global_formats['rs_dot_comma_ltr'],
),
'MNT' => array(
'default' => $global_formats['ls_dot_comma_ltr'],
'mn_MN' => $global_formats['ls_dot_comma_ltr'],
),
'MOP' => array(
'pt_MO' => $global_formats['rs_comma_space_ltr'],
'zh_Hant_MO' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
),
'MRU' => array(
'ar_MR' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'MUR' => array(
'en_MU' => $global_formats['lx_dot_comma_ltr'],
'fr_MU' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'MVR' => array(
'default' => array(),
),
'MWK' => array(
'en_MW' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'MXN' => array(
'es_MX' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'MYR' => array(
'default' => $global_formats['lx_dot_comma_ltr'],
'ms_MY' => $global_formats['lx_dot_comma_ltr'],
),
'MZN' => array(
'pt_MZ' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
),
'NAD' => array(
'en_NA' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'NGN' => array(
'en_NG' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'yo_NG' => $global_formats['lx_dot_comma_ltr'],
),
'NIO' => array(
'es_NI' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'NOK' => array(
'nb_SJ' => $global_formats['ls_comma_space_ltr'],
'default' => $global_formats['ls_comma_space_ltr'],
'nb_NO' => $global_formats['ls_comma_space_ltr'],
'nn_NO' => $global_formats['rs_comma_space_ltr'],
'se_NO' => $global_formats['rs_comma_space_ltr'],
),
'NPR' => array(
'default' => $global_formats['ls_dot_comma_ltr'],
'ne_NP' => $global_formats['ls_dot_comma_ltr'],
),
'NZD' => array(
'en_CK' => $global_formats['lx_dot_comma_ltr'],
'en_NU' => $global_formats['lx_dot_comma_ltr'],
'en_NZ' => $global_formats['lx_dot_comma_ltr'],
'en_PN' => $global_formats['lx_dot_comma_ltr'],
'en_TK' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'mi_NZ' => $global_formats['ls_dot_comma_ltr'],
),
'OMR' => array(
'ar_OM' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'PEN' => array(
'es_PE' => $global_formats['ls_dot_comma_ltr'],
'default' => $global_formats['ls_dot_comma_ltr'],
'qu_PE' => $global_formats['ls_dot_comma_ltr'],
),
'PGK' => array(
'en_PG' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'PHP' => array(
'en_PH' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'ceb_PH' => $global_formats['lx_dot_comma_ltr'],
'fil_PH' => $global_formats['lx_dot_comma_ltr'],
),
'PKR' => array(
'en_PK' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'ur_PK' => $global_formats['ls_dot_comma_rtl'],
),
'PLN' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'pl_PL' => $global_formats['rs_comma_space_ltr'],
),
'PYG' => array(
'es_PY' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['ls_comma_dot_ltr'],
),
'QAR' => array(
'ar_QA' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'RON' => array(
'default' => $global_formats['rs_comma_dot_ltr'],
'ro_RO' => $global_formats['rs_comma_dot_ltr'],
),
'RSD' => array(
'default' => $global_formats['rs_comma_dot_ltr'],
'sr_RS' => $global_formats['rs_comma_dot_ltr'],
),
'RUB' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'ce_RU' => $global_formats['rs_dot_comma_ltr'],
'ru_RU' => $global_formats['rs_comma_space_ltr'],
'sah_RU' => $global_formats['rs_comma_space_ltr'],
'tt_RU' => $global_formats['rs_comma_space_ltr'],
),
'RWF' => array(
'en_RW' => $global_formats['lx_dot_comma_ltr'],
'fr_RW' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'rw_RW' => $global_formats['ls_comma_dot_ltr'],
),
'SAR' => array(
'ar_SA' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'SBD' => array(
'en_SB' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'SCR' => array(
'en_SC' => $global_formats['lx_dot_comma_ltr'],
'fr_SC' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'SDG' => array(
'ar_SD' => $global_formats['rs_comma_dot_rtl'],
'en_SD' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'SEK' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'sv_SE' => $global_formats['rs_comma_space_ltr'],
),
'SGD' => array(
'en_SG' => $global_formats['lx_dot_comma_ltr'],
'ms_SG' => $global_formats['lx_dot_comma_ltr'],
'ta_SG' => $global_formats['ls_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'SHP' => array(
'en_SH' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'SLL' => array(
'en_SL' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'SOS' => array(
'ar_SO' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
'so_SO' => $global_formats['lx_dot_comma_ltr'],
),
'SRD' => array(
'nl_SR' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['ls_comma_dot_ltr'],
),
'SSP' => array(
'en_SS' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'STN' => array(
'pt_ST' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
),
'SYP' => array(
'ar_SY' => $global_formats['rs_comma_dot_rtl'],
'fr_SY' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'SZL' => array(
'en_SZ' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'THB' => array(
'default' => $global_formats['lx_dot_comma_ltr'],
'th_TH' => $global_formats['lx_dot_comma_ltr'],
),
'TJS' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'tg_TJ' => $global_formats['rs_comma_space_ltr'],
),
'TMT' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'tk_TM' => $global_formats['rs_comma_space_ltr'],
),
'TND' => array(
'ar_TN' => $global_formats['ls_comma_dot_rtl'],
'fr_TN' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['ls_comma_dot_rtl'],
),
'TOP' => array(
'en_TO' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'to_TO' => $global_formats['ls_dot_comma_ltr'],
),
'TRY' => array(
'default' => $global_formats['lx_comma_dot_ltr'],
'tr_TR' => $global_formats['lx_comma_dot_ltr'],
),
'TTD' => array(
'en_TT' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'TWD' => array(
'zh_Hant' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'TZS' => array(
'en_TZ' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'sw_TZ' => $global_formats['ls_dot_comma_ltr'],
),
'UAH' => array(
'ru_UA' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
'uk_UA' => $global_formats['rs_comma_space_ltr'],
),
'UGX' => array(
'en_UG' => $global_formats['lx_dot_comma_ltr'],
'sw_UG' => $global_formats['ls_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'USD' => array(
'en_AS' => $global_formats['lx_dot_comma_ltr'],
'en_DG' => $global_formats['lx_dot_comma_ltr'],
'en_FM' => $global_formats['lx_dot_comma_ltr'],
'en_GU' => $global_formats['lx_dot_comma_ltr'],
'en_IO' => $global_formats['lx_dot_comma_ltr'],
'en_MH' => $global_formats['lx_dot_comma_ltr'],
'en_MP' => $global_formats['lx_dot_comma_ltr'],
'en_PR' => $global_formats['lx_dot_comma_ltr'],
'en_PW' => $global_formats['lx_dot_comma_ltr'],
'en_TC' => $global_formats['lx_dot_comma_ltr'],
'en_UM' => $global_formats['lx_dot_comma_ltr'],
'en_VG' => $global_formats['lx_dot_comma_ltr'],
'en_VI' => $global_formats['lx_dot_comma_ltr'],
'en_ZW' => $global_formats['lx_dot_comma_ltr'],
'es_EC' => $global_formats['lx_comma_dot_ltr'],
'es_PA' => $global_formats['lx_dot_comma_ltr'],
'es_PR' => $global_formats['lx_dot_comma_ltr'],
'es_SV' => $global_formats['lx_dot_comma_ltr'],
'es_US' => $global_formats['lx_dot_comma_ltr'],
'fr_HT' => $global_formats['rs_comma_space_ltr'],
'nl_BQ' => $global_formats['ls_comma_dot_ltr'],
'pt_TL' => $global_formats['rs_comma_space_ltr'],
'qu_EC' => $global_formats['ls_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'en_US' => $global_formats['lx_dot_comma_ltr'],
'haw_US' => $global_formats['lx_dot_comma_ltr'],
'nd_ZW' => $global_formats['lx_dot_comma_ltr'],
'sn_ZW' => $global_formats['ls_dot_comma_ltr'],
),
'UYU' => array(
'es_UY' => $global_formats['ls_comma_dot_ltr'],
'default' => $global_formats['ls_comma_dot_ltr'],
),
'UZS' => array(
'default' => $global_formats['rs_comma_space_ltr'],
'uz_AF' => $global_formats['rs_comma_space_ltr'],
),
'VES' => array(
'es_VE' => $global_formats['lx_comma_dot_ltr'],
'default' => $global_formats['lx_comma_dot_ltr'],
),
'VND' => array(
'default' => $global_formats['rs_comma_dot_ltr'],
'vi_VN' => $global_formats['rs_comma_dot_ltr'],
),
'VUV' => array(
'en_VU' => $global_formats['lx_dot_comma_ltr'],
'fr_VU' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'WST' => array(
'en_WS' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'XAF' => array(
'ar_TD' => $global_formats['rs_comma_dot_rtl'],
'en_CM' => $global_formats['lx_dot_comma_ltr'],
'es_GQ' => $global_formats['lx_comma_dot_ltr'],
'fr_CF' => $global_formats['rs_comma_space_ltr'],
'fr_CG' => $global_formats['rs_comma_space_ltr'],
'fr_CM' => $global_formats['rs_comma_space_ltr'],
'fr_GA' => $global_formats['rs_comma_space_ltr'],
'fr_GQ' => $global_formats['rs_comma_space_ltr'],
'fr_TD' => $global_formats['rs_comma_space_ltr'],
'pt_GQ' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
'sg_CF' => $global_formats['lx_comma_dot_ltr'],
),
'XCD' => array(
'en_AG' => $global_formats['lx_dot_comma_ltr'],
'en_AI' => $global_formats['lx_dot_comma_ltr'],
'en_DM' => $global_formats['lx_dot_comma_ltr'],
'en_GD' => $global_formats['lx_dot_comma_ltr'],
'en_KN' => $global_formats['lx_dot_comma_ltr'],
'en_LC' => $global_formats['lx_dot_comma_ltr'],
'en_MS' => $global_formats['lx_dot_comma_ltr'],
'en_VC' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
'XOF' => array(
'fr_BF' => $global_formats['rs_comma_space_ltr'],
'fr_BJ' => $global_formats['rs_comma_space_ltr'],
'fr_CI' => $global_formats['rs_comma_space_ltr'],
'fr_ML' => $global_formats['rs_comma_space_ltr'],
'fr_NE' => $global_formats['rs_comma_space_ltr'],
'fr_SN' => $global_formats['rs_comma_space_ltr'],
'fr_TG' => $global_formats['rs_comma_space_ltr'],
'pt_GW' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
'dyo_SN' => $global_formats['rs_comma_space_ltr'],
'wo_SN' => $global_formats['ls_comma_dot_ltr'],
),
'XPF' => array(
'fr_NC' => $global_formats['rs_comma_space_ltr'],
'fr_PF' => $global_formats['rs_comma_space_ltr'],
'fr_WF' => $global_formats['rs_comma_space_ltr'],
'default' => $global_formats['rs_comma_space_ltr'],
),
'YER' => array(
'ar_YE' => $global_formats['rs_comma_dot_rtl'],
'default' => $global_formats['rs_comma_dot_rtl'],
),
'ZAR' => array(
'en_LS' => $global_formats['lx_dot_comma_ltr'],
'en_NA' => $global_formats['lx_dot_comma_ltr'],
'en_ZA' => $global_formats['lx_comma_space_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
'af_ZA' => $global_formats['lx_comma_space_ltr'],
'xh_ZA' => $global_formats['lx_dot_space_ltr'],
'zu_ZA' => $global_formats['lx_dot_comma_ltr'],
),
'ZMW' => array(
'en_ZM' => $global_formats['lx_dot_comma_ltr'],
'default' => $global_formats['lx_dot_comma_ltr'],
),
);

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,8 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore as ProductAttributesLookupDataStore;
/** /**
* Legacy product contains all deprecated methods for this class and can be * Legacy product contains all deprecated methods for this class and can be
* removed in the future. * removed in the future.
@ -1374,13 +1376,22 @@ class WC_Product extends WC_Abstract_Legacy_Product {
*/ */
do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store ); do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
$state = $this->before_data_store_save_or_update();
if ( $this->get_id() ) { if ( $this->get_id() ) {
$changeset = $this->get_changes();
$this->data_store->update( $this ); $this->data_store->update( $this );
} else { } else {
$changeset = null;
$this->data_store->create( $this ); $this->data_store->create( $this );
} }
$this->maybe_defer_product_sync(); $this->after_data_store_save_or_update( $state );
// Update attributes lookup table if the product is new OR it's not but there are actually any changes.
if ( is_null( $changeset ) || ! empty( $changeset ) ) {
wc_get_container()->get( ProductAttributesLookupDataStore::class )->on_product_changed( $this, $changeset );
}
/** /**
* Trigger action after saving to the DB. * Trigger action after saving to the DB.
@ -1393,6 +1404,25 @@ class WC_Product extends WC_Abstract_Legacy_Product {
return $this->get_id(); return $this->get_id();
} }
/**
* Do any extra processing needed before the actual product save
* (but after triggering the 'woocommerce_before_..._object_save' action)
*
* @return mixed A state value that will be passed to after_data_store_save_or_update.
*/
protected function before_data_store_save_or_update() {
}
/**
* Do any extra processing needed after the actual product save
* (but before triggering the 'woocommerce_after_..._object_save' action)
*
* @param mixed $state The state object that was returned by before_data_store_save_or_update.
*/
protected function after_data_store_save_or_update( $state ) {
$this->maybe_defer_product_sync();
}
/** /**
* Delete the product, set its ID to 0, and return result. * Delete the product, set its ID to 0, and return result.
* *
@ -1400,10 +1430,12 @@ class WC_Product extends WC_Abstract_Legacy_Product {
* @return bool result * @return bool result
*/ */
public function delete( $force_delete = false ) { public function delete( $force_delete = false ) {
$product_id = $this->get_id();
$deleted = parent::delete( $force_delete ); $deleted = parent::delete( $force_delete );
if ( $deleted ) { if ( $deleted ) {
$this->maybe_defer_product_sync(); $this->maybe_defer_product_sync();
wc_get_container()->get( ProductAttributesLookupDataStore::class )->on_product_deleted( $product_id );
} }
return $deleted; return $deleted;

View File

@ -54,7 +54,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
wp_style_add_data( 'woocommerce_admin_marketplace_styles', 'rtl', 'replace' ); wp_style_add_data( 'woocommerce_admin_marketplace_styles', 'rtl', 'replace' );
wp_style_add_data( 'woocommerce_admin_privacy_styles', 'rtl', 'replace' ); wp_style_add_data( 'woocommerce_admin_privacy_styles', 'rtl', 'replace' );
if ( $screen->is_block_editor() ) { if ( $screen && $screen->is_block_editor() ) {
wp_register_style( 'woocommerce-general', WC()->plugin_url() . '/assets/css/woocommerce.css', array(), $version ); wp_register_style( 'woocommerce-general', WC()->plugin_url() . '/assets/css/woocommerce.css', array(), $version );
wp_style_add_data( 'woocommerce-general', 'rtl', 'replace' ); wp_style_add_data( 'woocommerce-general', 'rtl', 'replace' );
} }

View File

@ -142,6 +142,10 @@ if ( ! class_exists( 'WC_Admin_Profile', false ) ) :
'description' => __( 'State / County or state code', 'woocommerce' ), 'description' => __( 'State / County or state code', 'woocommerce' ),
'class' => 'js_field-state', 'class' => 'js_field-state',
), ),
'shipping_phone' => array(
'label' => __( 'Phone', 'woocommerce' ),
'description' => '',
),
), ),
), ),
) )

View File

@ -133,6 +133,9 @@ class WC_Meta_Box_Order_Data {
'class' => 'js_field-state select short', 'class' => 'js_field-state select short',
'show' => false, 'show' => false,
), ),
'phone' => array(
'label' => __( 'Phone', 'woocommerce' ),
),
) )
); );
} }
@ -460,6 +463,10 @@ class WC_Meta_Box_Order_Data {
$field_value = $order->get_meta( '_' . $field_name ); $field_value = $order->get_meta( '_' . $field_name );
} }
if ( 'shipping_phone' === $field_name ) {
$field_value = wc_make_phone_clickable( $field_value );
}
if ( $field_value ) { if ( $field_value ) {
echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>'; echo '<p><strong>' . esc_html( $field['label'] ) . ':</strong> ' . wp_kses_post( $field_value ) . '</p>';
} }

View File

@ -280,7 +280,7 @@ if ( wc_tax_enabled() ) {
<button type="button" class="button add-coupon"><?php esc_html_e( 'Apply coupon', 'woocommerce' ); ?></button> <button type="button" class="button add-coupon"><?php esc_html_e( 'Apply coupon', 'woocommerce' ); ?></button>
<?php endif; ?> <?php endif; ?>
<?php else : ?> <?php else : ?>
<span class="description"><?php echo wc_help_tip( __( 'To edit this order change the status back to "Pending"', 'woocommerce' ) ); ?> <?php esc_html_e( 'This order is no longer editable.', 'woocommerce' ); ?></span> <span class="description"><?php echo wc_help_tip( __( 'To edit this order change the status back to "Pending payment"', 'woocommerce' ) ); ?> <?php esc_html_e( 'This order is no longer editable.', 'woocommerce' ); ?></span>
<?php endif; ?> <?php endif; ?>
<?php if ( 0 < $order->get_total() - $order->get_total_refunded() || 0 < absint( $order->get_item_count() - $order->get_item_count_refunded() ) ) : ?> <?php if ( 0 < $order->get_total() - $order->get_total_refunded() || 0 < absint( $order->get_item_count() - $order->get_item_count_refunded() ) ) : ?>
<button type="button" class="button refund-items"><?php esc_html_e( 'Refund', 'woocommerce' ); ?></button> <button type="button" class="button refund-items"><?php esc_html_e( 'Refund', 'woocommerce' ); ?></button>

View File

@ -86,15 +86,15 @@ defined( 'ABSPATH' ) || exit;
?> ?>
<p class="form-row form-row-full options"> <p class="form-row form-row-full options">
<label> <label>
<?php esc_html_e( 'Enabled', 'woocommerce' ); ?>: <?php esc_html_e( 'Enabled', 'woocommerce' ); ?>
<input type="checkbox" class="checkbox" name="variable_enabled[<?php echo esc_attr( $loop ); ?>]" <?php checked( in_array( $variation_object->get_status( 'edit' ), array( 'publish', false ), true ), true ); ?> /> <input type="checkbox" class="checkbox" name="variable_enabled[<?php echo esc_attr( $loop ); ?>]" <?php checked( in_array( $variation_object->get_status( 'edit' ), array( 'publish', false ), true ), true ); ?> />
</label> </label>
<label class="tips" data-tip="<?php esc_attr_e( 'Enable this option if access is given to a downloadable file upon purchase of a product', 'woocommerce' ); ?>"> <label class="tips" data-tip="<?php esc_attr_e( 'Enable this option if access is given to a downloadable file upon purchase of a product', 'woocommerce' ); ?>">
<?php esc_html_e( 'Downloadable', 'woocommerce' ); ?>: <?php esc_html_e( 'Downloadable', 'woocommerce' ); ?>
<input type="checkbox" class="checkbox variable_is_downloadable" name="variable_is_downloadable[<?php echo esc_attr( $loop ); ?>]" <?php checked( $variation_object->get_downloadable( 'edit' ), true ); ?> /> <input type="checkbox" class="checkbox variable_is_downloadable" name="variable_is_downloadable[<?php echo esc_attr( $loop ); ?>]" <?php checked( $variation_object->get_downloadable( 'edit' ), true ); ?> />
</label> </label>
<label class="tips" data-tip="<?php esc_attr_e( 'Enable this option if a product is not shipped or there is no shipping cost', 'woocommerce' ); ?>"> <label class="tips" data-tip="<?php esc_attr_e( 'Enable this option if a product is not shipped or there is no shipping cost', 'woocommerce' ); ?>">
<?php esc_html_e( 'Virtual', 'woocommerce' ); ?>: <?php esc_html_e( 'Virtual', 'woocommerce' ); ?>
<input type="checkbox" class="checkbox variable_is_virtual" name="variable_is_virtual[<?php echo esc_attr( $loop ); ?>]" <?php checked( $variation_object->get_virtual( 'edit' ), true ); ?> /> <input type="checkbox" class="checkbox variable_is_virtual" name="variable_is_virtual[<?php echo esc_attr( $loop ); ?>]" <?php checked( $variation_object->get_virtual( 'edit' ), true ); ?> />
</label> </label>

View File

@ -0,0 +1,72 @@
<?php
/**
* Refund and Returns Policy Page Note Provider.
*
* Adds notes to the merchant's inbox concerning the created page.
*
* @package WooCommerce
*/
defined( 'ABSPATH' ) || exit;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Admin\Notes\Note;
/**
* WC_Notes_Refund_Returns.
*/
class WC_Notes_Refund_Returns {
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'wc-refund-returns-page';
/**
* Maybe add a note to the inbox.
*
* @param int $page_id The ID of the page.
*/
public static function possibly_add_note( $page_id ) {
$data_store = \WC_Data_Store::load( 'admin-note' );
// Do we already have this note?
$note_id = $data_store->get_notes_with_name( self::NOTE_NAME );
if ( ! empty( $note_id ) ) {
$note = new Note( $note_id );
if ( false !== $note || $note::E_WC_ADMIN_NOTE_ACTIONED === $note->get_status() ) {
// note actioned -> don't show it.
return;
}
}
// Add note.
$note = self::get_note( $page_id );
$note->save();
delete_option( 'woocommerce_refund_returns_page_created' );
}
/**
* Get the note.
*
* @param int $page_id The ID of the page.
* @return object $note The note object.
*/
public static function get_note( $page_id ) {
$note = new Note();
$note->set_title( __( 'Setup a Refund and Returns Policy page to boost your store\'s credibility.', 'woocommerce' ) );
$note->set_content( __( 'We have created a sample draft Refund and Returns Policy page for you. Please have a look and update it to fit your store.', 'woocommerce' ) );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_name( self::NOTE_NAME );
$note->set_content_data( (object) array() );
$note->set_source( 'woocommerce-core' );
$note->add_action(
'notify-refund-returns-page',
__( 'Edit page', 'woocommerce' ),
admin_url( sprintf( 'post.php?post=%d&action=edit', (int) $page_id ) )
);
return $note;
}
}

View File

@ -386,6 +386,20 @@ class WC_Settings_Products extends WC_Settings_Page {
'autoload' => false, 'autoload' => false,
), ),
array(
'desc' => __( 'Allow using redirect mode (insecure) as a last resort', 'woocommerce' ),
'id' => 'woocommerce_downloads_redirect_fallback_allowed',
'type' => 'checkbox',
'default' => 'no',
'desc_tip' => sprintf(
/* translators: %1$s is a link to the WooCommerce documentation. */
__( 'If the "Force Downloads" or "X-Accel-Redirect/X-Sendfile" download method is selected but does not work, the system will use the "Redirect" method as a last resort. <a href="%1$s">See this guide</a> for more details.', 'woocommerce' ),
'https://docs.woocommerce.com/document/digital-downloadable-product-handling/'
),
'checkboxgroup' => 'start',
'autoload' => false,
),
array( array(
'title' => __( 'Access restriction', 'woocommerce' ), 'title' => __( 'Access restriction', 'woocommerce' ),
'desc' => __( 'Downloads require login', 'woocommerce' ), 'desc' => __( 'Downloads require login', 'woocommerce' ),

View File

@ -26,8 +26,13 @@ if ( ! defined( 'ABSPATH' ) ) {
<?php foreach ( $logs as $log_key => $log_file ) : ?> <?php foreach ( $logs as $log_key => $log_file ) : ?>
<?php <?php
$timestamp = filemtime( WC_LOG_DIR . $log_file ); $timestamp = filemtime( WC_LOG_DIR . $log_file );
/* translators: 1: last access date 2: last access time */ $date = sprintf(
$date = sprintf( __( '%1$s at %2$s', 'woocommerce' ), date_i18n( wc_date_format(), $timestamp ), date_i18n( wc_time_format(), $timestamp ) ); /* translators: 1: last access date 2: last access time 3: last access timezone abbreviation */
__( '%1$s at %2$s %3$s', 'woocommerce' ),
wp_date( wc_date_format(), $timestamp ),
wp_date( wc_time_format(), $timestamp ),
wp_date( 'T', $timestamp )
);
?> ?>
<option value="<?php echo esc_attr( $log_key ); ?>" <?php selected( sanitize_title( $viewed_log ), $log_key ); ?>><?php echo esc_html( $log_file ); ?> (<?php echo esc_html( $date ); ?>)</option> <option value="<?php echo esc_attr( $log_key ); ?>" <?php selected( sanitize_title( $viewed_log ), $log_key ); ?>><?php echo esc_html( $log_file ); ?> (<?php echo esc_html( $date ); ?>)</option>
<?php endforeach; ?> <?php endforeach; ?>

View File

@ -11,22 +11,44 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
foreach ( $tools as $action_name => $tool ) {
?> ?>
<form method="post" action="options.php"> <form id="<?php echo esc_attr( 'form_' . $action_name ); ?>" method="GET" action="<?php echo esc_attr( esc_url( admin_url( 'admin.php?foo=bar' ) ) ); ?>">
<?php settings_fields( 'woocommerce_status_settings_fields' ); ?> <?php wp_nonce_field( 'debug_action', '_wpnonce', false ); ?>
<input type="hidden" name="page" value="wc-status"/>
<input type="hidden" name="tab" value="tools"/>
<input type="hidden" name="action" value="<?php echo esc_attr( $action_name ); ?>"/>
</form>
<?php
}
?>
<table class="wc_status_table wc_status_table--tools widefat" cellspacing="0"> <table class="wc_status_table wc_status_table--tools widefat" cellspacing="0">
<tbody class="tools"> <tbody class="tools">
<?php foreach ( $tools as $action_name => $tool ) : ?> <?php foreach ( $tools as $action_name => $tool ) : ?>
<tr class="<?php echo sanitize_html_class( $action_name ); ?>"> <tr class="<?php echo sanitize_html_class( $action_name ); ?>">
<th> <th>
<strong class="name"><?php echo esc_html( $tool['name'] ); ?></strong> <strong class="name"><?php echo esc_html( $tool['name'] ); ?></strong>
<p class="description"><?php echo wp_kses_post( $tool['desc'] ); ?></p> <p class="description">
<?php
echo wp_kses_post( $tool['desc'] );
if ( ! is_null( ArrayUtil::get_value_or_default( $tool, 'selector' ) ) ) {
$selector = $tool['selector'];
if ( isset( $selector['description'] ) ) {
echo '</p><p class="description">';
echo wp_kses_post( $selector['description'] );
}
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo "&nbsp;&nbsp;<select style='width: 300px;' form='form_$action_name' id='selector_$action_name' data-allow_clear='true' class='${selector['class']}' name='${selector['name']}' data-placeholder='${selector['placeholder']}' data-action='${selector['search_action']}'></select>";
}
?>
</p>
</th> </th>
<td class="run-tool"> <td class="run-tool">
<a <?php echo ArrayUtil::is_truthy( $tool, 'disabled' ) ? 'disabled' : ''; ?> href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=wc-status&tab=tools&action=' . $action_name ), 'debug_action' ) ); ?>" class="button button-large <?php echo esc_attr( $action_name ); ?>"><?php echo esc_html( $tool['button'] ); ?></a> <?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<input <?php echo ArrayUtil::is_truthy( $tool, 'disabled' ) ? 'disabled' : ''; ?> type="submit" form="<?php echo 'form_' . $action_name; ?>" class="button button-large" value="<?php echo esc_attr( $tool['button'] ); ?>" />
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</form>

View File

@ -15,7 +15,7 @@ defined( 'ABSPATH' ) || exit;
echo wp_kses_post( echo wp_kses_post(
sprintf( sprintf(
/* translators: %s: Link to settings page. */ /* translators: %s: Link to settings page. */
__( 'Your store is configured to serve digital products using "Redirect only" method. This method is deprecated, <a href="%s">please switch to a different method instead.</a><br><em>If you use a remote server for downloadable files (such as Google Drive, Dropbox, Amazon S3), the right method will automatically be used, so select any of the other options to make this notice go away.</em>', 'woocommerce' ), __( 'Your store is configured to serve digital products using "Redirect only" method. This method is deprecated, <a href="%s">please switch to a different method instead.</a><br><em>If you use a remote server for downloadable files (such as Google Drive, Dropbox, Amazon S3), you may optionally wish to "allow using redirects as a last resort". Enabling that and/or selecting any of the other options will make this notice go away.</em>', 'woocommerce' ),
add_query_arg( add_query_arg(
array( array(
'page' => 'wc-settings', 'page' => 'wc-settings',

View File

@ -63,9 +63,10 @@ function wc_get_screen_ids() {
* @param string $page_title (default: '') Title for the new page. * @param string $page_title (default: '') Title for the new page.
* @param string $page_content (default: '') Content for the new page. * @param string $page_content (default: '') Content for the new page.
* @param int $post_parent (default: 0) Parent for the new page. * @param int $post_parent (default: 0) Parent for the new page.
* @param string $post_status (default: publish) The post status of the new page.
* @return int page ID. * @return int page ID.
*/ */
function wc_create_page( $slug, $option = '', $page_title = '', $page_content = '', $post_parent = 0 ) { function wc_create_page( $slug, $option = '', $page_title = '', $page_content = '', $post_parent = 0, $post_status = 'publish' ) {
global $wpdb; global $wpdb;
$option_value = get_option( $option ); $option_value = get_option( $option );
@ -110,12 +111,12 @@ function wc_create_page( $slug, $option = '', $page_title = '', $page_content =
$page_id = $trashed_page_found; $page_id = $trashed_page_found;
$page_data = array( $page_data = array(
'ID' => $page_id, 'ID' => $page_id,
'post_status' => 'publish', 'post_status' => $post_status,
); );
wp_update_post( $page_data ); wp_update_post( $page_data );
} else { } else {
$page_data = array( $page_data = array(
'post_status' => 'publish', 'post_status' => $post_status,
'post_type' => 'page', 'post_type' => 'page',
'post_author' => 1, 'post_author' => 1,
'post_name' => $slug, 'post_name' => $slug,
@ -125,6 +126,8 @@ function wc_create_page( $slug, $option = '', $page_title = '', $page_content =
'comment_status' => 'closed', 'comment_status' => 'closed',
); );
$page_id = wp_insert_post( $page_data ); $page_id = wp_insert_post( $page_data );
do_action( 'woocommerce_page_created', $page_id, $page_data );
} }
if ( $option ) { if ( $option ) {

View File

@ -795,22 +795,43 @@ class WC_AJAX {
$loop = intval( $_POST['loop'] ); $loop = intval( $_POST['loop'] );
$file_counter = 0; $file_counter = 0;
$order = wc_get_order( $order_id ); $order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $item ) {
$product = $item->get_product();
if ( ! in_array( $product->get_id(), $product_ids, true ) ) {
continue;
}
$files = $product->get_downloads();
if ( ! $order->get_billing_email() ) { if ( ! $order->get_billing_email() ) {
wp_die(); wp_die();
} }
if ( ! empty( $files ) ) { $data = array();
foreach ( $files as $download_id => $file ) { $items = $order->get_items();
$inserted_id = wc_downloadable_file_permission( $download_id, $product->get_id(), $order, $item->get_quantity(), $item );
// Check against order items first.
foreach ( $items as $item ) {
$product = $item->get_product();
if ( $product && $product->exists() && in_array( $product->get_id(), $product_ids, true ) && $product->is_downloadable() ) {
$data[ $product->get_id() ] = array(
'files' => $product->get_downloads(),
'quantity' => $item->get_quantity(),
'order_item' => $item,
);
}
}
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( isset( $data[ $product->get_id() ] ) ) {
$download_data = $data[ $product->get_id() ];
} else {
$download_data = array(
'files' => $product->get_downloads(),
'quantity' => 1,
'order_item' => null,
);
}
if ( ! empty( $download_data['files'] ) ) {
foreach ( $download_data['files'] as $download_id => $file ) {
$inserted_id = wc_downloadable_file_permission( $download_id, $product->get_id(), $order, $download_data['quantity'], $download_data['order_item'] );
if ( $inserted_id ) { if ( $inserted_id ) {
$download = new WC_Customer_Download( $inserted_id ); $download = new WC_Customer_Download( $inserted_id );
$loop ++; $loop ++;
@ -1652,7 +1673,7 @@ class WC_AJAX {
$products = array(); $products = array();
foreach ( $product_objects as $product_object ) { foreach ( $product_objects as $product_object ) {
$products[ $product_object->get_id() ] = rawurldecode( $product_object->get_formatted_name() ); $products[ $product_object->get_id() ] = rawurldecode( wp_strip_all_tags( $product_object->get_formatted_name() ) );
} }
wp_send_json( $products ); wp_send_json( $products );

View File

@ -1099,7 +1099,7 @@ class WC_Countries {
), ),
'IN' => array( 'IN' => array(
'postcode' => array( 'postcode' => array(
'label' => __( 'Pin code', 'woocommerce' ), 'label' => __( 'PIN', 'woocommerce' ),
), ),
'state' => array( 'state' => array(
'label' => __( 'State', 'woocommerce' ), 'label' => __( 'State', 'woocommerce' ),
@ -1379,7 +1379,7 @@ class WC_Countries {
), ),
'US' => array( 'US' => array(
'postcode' => array( 'postcode' => array(
'label' => __( 'ZIP', 'woocommerce' ), 'label' => __( 'ZIP Code', 'woocommerce' ),
), ),
'state' => array( 'state' => array(
'label' => __( 'State', 'woocommerce' ), 'label' => __( 'State', 'woocommerce' ),

View File

@ -52,6 +52,7 @@ class WC_Customer extends WC_Legacy_Customer {
'postcode' => '', 'postcode' => '',
'country' => '', 'country' => '',
'state' => '', 'state' => '',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
); );
@ -728,6 +729,17 @@ class WC_Customer extends WC_Legacy_Customer {
return $this->get_address_prop( 'country', 'shipping', $context ); return $this->get_address_prop( 'country', 'shipping', $context );
} }
/**
* Get shipping phone.
*
* @since 5.6.0
* @param string $context What the value is for. Valid values are 'view' and 'edit'.
* @return string
*/
public function get_shipping_phone( $context = 'view' ) {
return $this->get_address_prop( 'phone', 'shipping', $context );
}
/** /**
* Is the user a paying customer? * Is the user a paying customer?
* *
@ -1117,6 +1129,16 @@ class WC_Customer extends WC_Legacy_Customer {
$this->set_address_prop( 'country', 'shipping', $value ); $this->set_address_prop( 'country', 'shipping', $value );
} }
/**
* Set shipping phone.
*
* @since 5.6.0
* @param string $value Shipping phone.
*/
public function set_shipping_phone( $value ) {
$this->set_address_prop( 'phone', 'shipping', $value );
}
/** /**
* Set if the user a paying customer. * Set if the user a paying customer.
* *

View File

@ -340,6 +340,13 @@ class WC_Download_Handler {
} }
// Fallback. // Fallback.
wc_get_logger()->warning(
sprintf(
/* translators: %1$s contains the filepath of the digital asset. */
__( '%1$s could not be served using the X-Accel-Redirect/X-Sendfile method. A Force Download will be used instead.', 'woocommerce' ),
$file_path
)
);
self::download_file_force( $file_path, $filename ); self::download_file_force( $file_path, $filename );
} }
@ -435,8 +442,19 @@ class WC_Download_Handler {
$start = isset( $download_range['start'] ) ? $download_range['start'] : 0; $start = isset( $download_range['start'] ) ? $download_range['start'] : 0;
$length = isset( $download_range['length'] ) ? $download_range['length'] : 0; $length = isset( $download_range['length'] ) ? $download_range['length'] : 0;
if ( ! self::readfile_chunked( $parsed_file_path['file_path'], $start, $length ) ) { if ( ! self::readfile_chunked( $parsed_file_path['file_path'], $start, $length ) ) {
if ( $parsed_file_path['remote_file'] && 'yes' === get_option( 'woocommerce_downloads_redirect_fallback_allowed' ) ) {
wc_get_logger()->warning(
sprintf(
/* translators: %1$s contains the filepath of the digital asset. */
__( '%1$s could not be served using the Force Download method. A redirect will be used instead.', 'woocommerce' ),
$file_path
)
);
self::download_file_redirect( $file_path );
} else {
self::download_error( __( 'File not found', 'woocommerce' ) ); self::download_error( __( 'File not found', 'woocommerce' ) );
} }
}
exit; exit;
} }

View File

@ -174,42 +174,42 @@ class WC_Frontend_Scripts {
'flexslider' => array( 'flexslider' => array(
'src' => self::get_asset_url( 'assets/js/flexslider/jquery.flexslider' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/flexslider/jquery.flexslider' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '2.7.2', 'version' => '2.7.2-wc.' . $version,
), ),
'js-cookie' => array( 'js-cookie' => array(
'src' => self::get_asset_url( 'assets/js/js-cookie/js.cookie' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/js-cookie/js.cookie' . $suffix . '.js' ),
'deps' => array(), 'deps' => array(),
'version' => '2.1.4', 'version' => '2.1.4-wc.' . $version,
), ),
'jquery-blockui' => array( 'jquery-blockui' => array(
'src' => self::get_asset_url( 'assets/js/jquery-blockui/jquery.blockUI' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/jquery-blockui/jquery.blockUI' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '2.70', 'version' => '2.7.0-wc.' . $version,
), ),
'jquery-cookie' => array( // deprecated. 'jquery-cookie' => array( // deprecated.
'src' => self::get_asset_url( 'assets/js/jquery-cookie/jquery.cookie' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/jquery-cookie/jquery.cookie' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '1.4.1', 'version' => '1.4.1-wc.' . $version,
), ),
'jquery-payment' => array( 'jquery-payment' => array(
'src' => self::get_asset_url( 'assets/js/jquery-payment/jquery.payment' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/jquery-payment/jquery.payment' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '3.0.0', 'version' => '3.0.0-wc.' . $version,
), ),
'photoswipe' => array( 'photoswipe' => array(
'src' => self::get_asset_url( 'assets/js/photoswipe/photoswipe' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/photoswipe/photoswipe' . $suffix . '.js' ),
'deps' => array(), 'deps' => array(),
'version' => '4.1.1', 'version' => '4.1.1-wc.' . $version,
), ),
'photoswipe-ui-default' => array( 'photoswipe-ui-default' => array(
'src' => self::get_asset_url( 'assets/js/photoswipe/photoswipe-ui-default' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/photoswipe/photoswipe-ui-default' . $suffix . '.js' ),
'deps' => array( 'photoswipe' ), 'deps' => array( 'photoswipe' ),
'version' => '4.1.1', 'version' => '4.1.1-wc.' . $version,
), ),
'prettyPhoto' => array( // deprecated. 'prettyPhoto' => array( // deprecated.
'src' => self::get_asset_url( 'assets/js/prettyPhoto/jquery.prettyPhoto' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/prettyPhoto/jquery.prettyPhoto' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '3.1.6', 'version' => '3.1.6-wc.' . $version,
), ),
'prettyPhoto-init' => array( // deprecated. 'prettyPhoto-init' => array( // deprecated.
'src' => self::get_asset_url( 'assets/js/prettyPhoto/jquery.prettyPhoto.init' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/prettyPhoto/jquery.prettyPhoto.init' . $suffix . '.js' ),
@ -219,12 +219,12 @@ class WC_Frontend_Scripts {
'select2' => array( 'select2' => array(
'src' => self::get_asset_url( 'assets/js/select2/select2.full' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/select2/select2.full' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '4.0.3', 'version' => '4.0.3-wc.' . $version,
), ),
'selectWoo' => array( 'selectWoo' => array(
'src' => self::get_asset_url( 'assets/js/selectWoo/selectWoo.full' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/selectWoo/selectWoo.full' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '1.0.9', 'version' => '1.0.9-wc.' . $version,
), ),
'wc-address-i18n' => array( 'wc-address-i18n' => array(
'src' => self::get_asset_url( 'assets/js/frontend/address-i18n' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/frontend/address-i18n' . $suffix . '.js' ),
@ -299,7 +299,7 @@ class WC_Frontend_Scripts {
'zoom' => array( 'zoom' => array(
'src' => self::get_asset_url( 'assets/js/zoom/jquery.zoom' . $suffix . '.js' ), 'src' => self::get_asset_url( 'assets/js/zoom/jquery.zoom' . $suffix . '.js' ),
'deps' => array( 'jquery' ), 'deps' => array( 'jquery' ),
'version' => '1.7.21', 'version' => '1.7.21-wc.' . $version,
), ),
); );
foreach ( $register_scripts as $name => $props ) { foreach ( $register_scripts as $name => $props ) {

View File

@ -161,6 +161,10 @@ class WC_Install {
'wc_update_500_fix_product_review_count', 'wc_update_500_fix_product_review_count',
'wc_update_500_db_version', 'wc_update_500_db_version',
), ),
'5.6.0' => array(
'wc_update_560_create_refund_returns_page',
'wc_update_560_db_version',
),
); );
/** /**
@ -170,8 +174,10 @@ class WC_Install {
add_action( 'init', array( __CLASS__, 'check_version' ), 5 ); add_action( 'init', array( __CLASS__, 'check_version' ), 5 );
add_action( 'init', array( __CLASS__, 'manual_database_update' ), 20 ); add_action( 'init', array( __CLASS__, 'manual_database_update' ), 20 );
add_action( 'admin_init', array( __CLASS__, 'wc_admin_db_update_notice' ) ); add_action( 'admin_init', array( __CLASS__, 'wc_admin_db_update_notice' ) );
add_action( 'admin_init', array( __CLASS__, 'add_admin_note_after_page_created' ) );
add_action( 'woocommerce_run_update_callback', array( __CLASS__, 'run_update_callback' ) ); add_action( 'woocommerce_run_update_callback', array( __CLASS__, 'run_update_callback' ) );
add_action( 'admin_init', array( __CLASS__, 'install_actions' ) ); add_action( 'admin_init', array( __CLASS__, 'install_actions' ) );
add_action( 'woocommerce_page_created', array( __CLASS__, 'page_created' ), 10, 2 );
add_filter( 'plugin_action_links_' . WC_PLUGIN_BASENAME, array( __CLASS__, 'plugin_action_links' ) ); add_filter( 'plugin_action_links_' . WC_PLUGIN_BASENAME, array( __CLASS__, 'plugin_action_links' ) );
add_filter( 'plugin_row_meta', array( __CLASS__, 'plugin_row_meta' ), 10, 2 ); add_filter( 'plugin_row_meta', array( __CLASS__, 'plugin_row_meta' ), 10, 2 );
add_filter( 'wpmu_drop_tables', array( __CLASS__, 'wpmu_drop_tables' ) ); add_filter( 'wpmu_drop_tables', array( __CLASS__, 'wpmu_drop_tables' ) );
@ -579,11 +585,24 @@ class WC_Install {
'title' => _x( 'My account', 'Page title', 'woocommerce' ), 'title' => _x( 'My account', 'Page title', 'woocommerce' ),
'content' => '<!-- wp:shortcode -->[' . apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) . ']<!-- /wp:shortcode -->', 'content' => '<!-- wp:shortcode -->[' . apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) . ']<!-- /wp:shortcode -->',
), ),
'refund_returns' => array(
'name' => _x( 'refund_returns', 'Page slug', 'woocommerce' ),
'title' => _x( 'Refund and Returns Policy', 'Page title', 'woocommerce' ),
'content' => self::get_refunds_return_policy_page_content(),
'post_status' => 'draft',
),
) )
); );
foreach ( $pages as $key => $page ) { foreach ( $pages as $key => $page ) {
wc_create_page( esc_sql( $page['name'] ), 'woocommerce_' . $key . '_page_id', $page['title'], $page['content'], ! empty( $page['parent'] ) ? wc_get_page_id( $page['parent'] ) : '' ); wc_create_page(
esc_sql( $page['name'] ),
'woocommerce_' . $key . '_page_id',
$page['title'],
$page['content'],
! empty( $page['parent'] ) ? wc_get_page_id( $page['parent'] ) : '',
! empty( $page['post_status'] ) ? $page['post_status'] : 'publish'
);
} }
} }
@ -1648,6 +1667,196 @@ CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
( new WC_Gateway_Paypal() )->should_load(); ( new WC_Gateway_Paypal() )->should_load();
} }
} }
/**
* Gets the content of the sample refunds and return policy page.
*
* @since 5.6.0
* @return HTML The content for the page
*/
private static function get_refunds_return_policy_page_content() {
return <<<EOT
<!-- wp:paragraph -->
<p><b>This is a sample page.</b></p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<h3>Overview</h3>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Our refund and returns policy lasts 30 days. If 30 days have passed since your purchase, we cant offer you a full refund or exchange.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>To be eligible for a return, your item must be unused and in the same condition that you received it. It must also be in the original packaging.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Several types of goods are exempt from being returned. Perishable goods such as food, flowers, newspapers or magazines cannot be returned. We also do not accept products that are intimate or sanitary goods, hazardous materials, or flammable liquids or gases.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Additional non-returnable items:</p>
<!-- /wp:paragraph -->
<!-- wp:list -->
<ul>
<li>Gift cards</li>
<li>Downloadable software products</li>
<li>Some health and personal care items</li>
</ul>
<!-- /wp:list -->
<!-- wp:paragraph -->
<p>To complete your return, we require a receipt or proof of purchase.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Please do not send your purchase back to the manufacturer.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>There are certain situations where only partial refunds are granted:</p>
<!-- /wp:paragraph -->
<!-- wp:list -->
<ul>
<li>Book with obvious signs of use</li>
<li>CD, DVD, VHS tape, software, video game, cassette tape, or vinyl record that has been opened.</li>
<li>Any item not in its original condition, is damaged or missing parts for reasons not due to our error.</li>
<li>Any item that is returned more than 30 days after delivery</li>
</ul>
<!-- /wp:list -->
<!-- wp:paragraph -->
<h2>Refunds</h2>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Once your return is received and inspected, we will send you an email to notify you that we have received your returned item. We will also notify you of the approval or rejection of your refund.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>If you are approved, then your refund will be processed, and a credit will automatically be applied to your credit card or original method of payment, within a certain amount of days.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<b>Late or missing refunds</b>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>If you havent received a refund yet, first check your bank account again.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Then contact your credit card company, it may take some time before your refund is officially posted.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Next contact your bank. There is often some processing time before a refund is posted.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>If youve done all of this and you still have not received your refund yet, please contact us at {email address}.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<b>Sale items</b>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Only regular priced items may be refunded. Sale items cannot be refunded.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<h2>Exchanges</h2>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>We only replace items if they are defective or damaged. If you need to exchange it for the same item, send us an email at {email address} and send your item to: {physical address}.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<h2>Gifts</h2>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>If the item was marked as a gift when purchased and shipped directly to you, youll receive a gift credit for the value of your return. Once the returned item is received, a gift certificate will be mailed to you.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>If the item wasnt marked as a gift when purchased, or the gift giver had the order shipped to themselves to give to you later, we will send a refund to the gift giver and they will find out about your return.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<h2>Shipping returns</h2>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>To return your product, you should mail your product to: {physical address}.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>You will be responsible for paying for your own shipping costs for returning your item. Shipping costs are non-refundable. If you receive a refund, the cost of return shipping will be deducted from your refund.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Depending on where you live, the time it may take for your exchanged product to reach you may vary.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>If you are returning more expensive items, you may consider using a trackable shipping service or purchasing shipping insurance. We dont guarantee that we will receive your returned item.</p>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<h2>Need help?</h2>
<!-- /wp:paragraph -->
<!-- wp:paragraph -->
<p>Contact us at {email} for questions related to refunds and returns.</p>
<!-- /wp:paragraph -->
EOT;
}
/**
* Adds an admin inbox note after a page has been created to notify
* user. For example to take action to edit the page such as the
* Refund and returns page.
*
* @since 5.6.0
* @return void
*/
public static function add_admin_note_after_page_created() {
if ( ! WC()->is_wc_admin_active() ) {
return;
}
$page_id = get_option( 'woocommerce_refund_returns_page_created', null );
if ( null === $page_id ) {
return;
}
WC_Notes_Refund_Returns::possibly_add_note( $page_id );
}
/**
* When pages are created, we might want to take some action.
* In this case we want to set an option when refund and returns
* page is created.
*
* @since 5.6.0
* @param int $page_id ID of the page.
* @param array $page_data The data of the page created.
* @return void
*/
public static function page_created( $page_id, $page_data ) {
if ( 'refund_returns' === $page_data['post_name'] ) {
delete_option( 'woocommerce_refund_returns_page_created' );
add_option( 'woocommerce_refund_returns_page_created', $page_id, '', false );
}
}
} }
WC_Install::init(); WC_Install::init();

View File

@ -63,6 +63,7 @@ class WC_Order_Query extends WC_Object_Query {
'shipping_state' => '', 'shipping_state' => '',
'shipping_postcode' => '', 'shipping_postcode' => '',
'shipping_country' => '', 'shipping_country' => '',
'shipping_phone' => '',
'payment_method' => '', 'payment_method' => '',
'payment_method_title' => '', 'payment_method_title' => '',
'transaction_id' => '', 'transaction_id' => '',

View File

@ -71,6 +71,7 @@ class WC_Order extends WC_Abstract_Order {
'state' => '', 'state' => '',
'postcode' => '', 'postcode' => '',
'country' => '', 'country' => '',
'phone' => '',
), ),
'payment_method' => '', 'payment_method' => '',
'payment_method_title' => '', 'payment_method_title' => '',
@ -742,6 +743,17 @@ class WC_Order extends WC_Abstract_Order {
return $this->get_address_prop( 'country', 'shipping', $context ); return $this->get_address_prop( 'country', 'shipping', $context );
} }
/**
* Get shipping phone.
*
* @since 5.6.0
* @param string $context What the value is for. Valid values are view and edit.
* @return string
*/
public function get_shipping_phone( $context = 'view' ) {
return $this->get_address_prop( 'phone', 'shipping', $context );
}
/** /**
* Get the payment method. * Get the payment method.
* *
@ -869,7 +881,7 @@ class WC_Order extends WC_Abstract_Order {
$address = $this->get_address( 'shipping' ); $address = $this->get_address( 'shipping' );
// Remove name and company before generate the Google Maps URL. // Remove name and company before generate the Google Maps URL.
unset( $address['first_name'], $address['last_name'], $address['company'] ); unset( $address['first_name'], $address['last_name'], $address['company'], $address['phone'] );
$address = apply_filters( 'woocommerce_shipping_address_map_url_parts', $address, $this ); $address = apply_filters( 'woocommerce_shipping_address_map_url_parts', $address, $this );
@ -1232,6 +1244,17 @@ class WC_Order extends WC_Abstract_Order {
$this->set_address_prop( 'country', 'shipping', $value ); $this->set_address_prop( 'country', 'shipping', $value );
} }
/**
* Set shipping phone.
*
* @since 5.6.0
* @param string $value Shipping phone.
* @throws WC_Data_Exception Throws exception when invalid data is found.
*/
public function set_shipping_phone( $value ) {
$this->set_address_prop( 'phone', 'shipping', $value );
}
/** /**
* Set the payment method. * Set the payment method.
* *

View File

@ -8,6 +8,9 @@
* @version 2.2.0 * @version 2.2.0
*/ */
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore as ProductAttributesLookupDataStore;
use Automattic\WooCommerce\Proxies\LegacyProxy;
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
/** /**
@ -296,27 +299,31 @@ class WC_Post_Data {
* @param mixed $id ID of post being deleted. * @param mixed $id ID of post being deleted.
*/ */
public static function delete_post( $id ) { public static function delete_post( $id ) {
if ( ! current_user_can( 'delete_posts' ) || ! $id ) { $container = wc_get_container();
if ( ! $container->get( LegacyProxy::class )->call_function( 'current_user_can', 'delete_posts' ) || ! $id ) {
return; return;
} }
$post_type = get_post_type( $id ); $post_type = self::get_post_type( $id );
switch ( $post_type ) { switch ( $post_type ) {
case 'product': case 'product':
$data_store = WC_Data_Store::load( 'product-variable' ); $data_store = WC_Data_Store::load( 'product-variable' );
$data_store->delete_variations( $id, true ); $data_store->delete_variations( $id, true );
$data_store->delete_from_lookup_table( $id, 'wc_product_meta_lookup' ); $data_store->delete_from_lookup_table( $id, 'wc_product_meta_lookup' );
$parent_id = wp_get_post_parent_id( $id ); $container->get( ProductAttributesLookupDataStore::class )->on_product_deleted( $id );
$parent_id = wp_get_post_parent_id( $id );
if ( $parent_id ) { if ( $parent_id ) {
wc_delete_product_transients( $parent_id ); wc_delete_product_transients( $parent_id );
} }
break; break;
case 'product_variation': case 'product_variation':
$data_store = WC_Data_Store::load( 'product' ); $data_store = WC_Data_Store::load( 'product' );
$data_store->delete_from_lookup_table( $id, 'wc_product_meta_lookup' ); $data_store->delete_from_lookup_table( $id, 'wc_product_meta_lookup' );
wc_delete_product_transients( wp_get_post_parent_id( $id ) ); wc_delete_product_transients( wp_get_post_parent_id( $id ) );
$container->get( ProductAttributesLookupDataStore::class )->on_product_deleted( $id );
break; break;
case 'shop_order': case 'shop_order':
global $wpdb; global $wpdb;
@ -342,7 +349,7 @@ class WC_Post_Data {
return; return;
} }
$post_type = get_post_type( $id ); $post_type = self::get_post_type( $id );
// If this is an order, trash any refunds too. // If this is an order, trash any refunds too.
if ( in_array( $post_type, wc_get_order_types( 'order-count' ), true ) ) { if ( in_array( $post_type, wc_get_order_types( 'order-count' ), true ) ) {
@ -360,6 +367,9 @@ class WC_Post_Data {
} elseif ( 'product' === $post_type ) { } elseif ( 'product' === $post_type ) {
$data_store = WC_Data_Store::load( 'product-variable' ); $data_store = WC_Data_Store::load( 'product-variable' );
$data_store->delete_variations( $id, false ); $data_store->delete_variations( $id, false );
wc_get_container()->get( ProductAttributesLookupDataStore::class )->on_product_deleted( $id );
} elseif ( 'product_variation' === $post_type ) {
wc_get_container()->get( ProductAttributesLookupDataStore::class )->on_product_deleted( $id );
} }
} }
@ -373,7 +383,7 @@ class WC_Post_Data {
return; return;
} }
$post_type = get_post_type( $id ); $post_type = self::get_post_type( $id );
if ( in_array( $post_type, wc_get_order_types( 'order-count' ), true ) ) { if ( in_array( $post_type, wc_get_order_types( 'order-count' ), true ) ) {
global $wpdb; global $wpdb;
@ -391,9 +401,23 @@ class WC_Post_Data {
$data_store->untrash_variations( $id ); $data_store->untrash_variations( $id );
wc_product_force_unique_sku( $id ); wc_product_force_unique_sku( $id );
wc_get_container()->get( ProductAttributesLookupDataStore::class )->on_product_changed( $id );
} elseif ( 'product_variation' === $post_type ) {
wc_get_container()->get( ProductAttributesLookupDataStore::class )->on_product_changed( $id );
} }
} }
/**
* Get the post type for a given post.
*
* @param int $id The post id.
* @return string The post type.
*/
private static function get_post_type( $id ) {
return wc_get_container()->get( LegacyProxy::class )->call_function( 'get_post_type', $id );
}
/** /**
* Before deleting an order, do some cleanup. * Before deleting an order, do some cleanup.
* *

View File

@ -52,7 +52,7 @@ class WC_Privacy_Erasers {
'billing_postcode' => __( 'Billing Postal/Zip Code', 'woocommerce' ), 'billing_postcode' => __( 'Billing Postal/Zip Code', 'woocommerce' ),
'billing_state' => __( 'Billing State', 'woocommerce' ), 'billing_state' => __( 'Billing State', 'woocommerce' ),
'billing_country' => __( 'Billing Country / Region', 'woocommerce' ), 'billing_country' => __( 'Billing Country / Region', 'woocommerce' ),
'billing_phone' => __( 'Phone Number', 'woocommerce' ), 'billing_phone' => __( 'Billing Phone Number', 'woocommerce' ),
'billing_email' => __( 'Email Address', 'woocommerce' ), 'billing_email' => __( 'Email Address', 'woocommerce' ),
'shipping_first_name' => __( 'Shipping First Name', 'woocommerce' ), 'shipping_first_name' => __( 'Shipping First Name', 'woocommerce' ),
'shipping_last_name' => __( 'Shipping Last Name', 'woocommerce' ), 'shipping_last_name' => __( 'Shipping Last Name', 'woocommerce' ),
@ -63,6 +63,7 @@ class WC_Privacy_Erasers {
'shipping_postcode' => __( 'Shipping Postal/Zip Code', 'woocommerce' ), 'shipping_postcode' => __( 'Shipping Postal/Zip Code', 'woocommerce' ),
'shipping_state' => __( 'Shipping State', 'woocommerce' ), 'shipping_state' => __( 'Shipping State', 'woocommerce' ),
'shipping_country' => __( 'Shipping Country / Region', 'woocommerce' ), 'shipping_country' => __( 'Shipping Country / Region', 'woocommerce' ),
'shipping_phone' => __( 'Shipping Phone Number', 'woocommerce' ),
), ),
$customer $customer
); );
@ -257,6 +258,7 @@ class WC_Privacy_Erasers {
'shipping_postcode' => 'text', 'shipping_postcode' => 'text',
'shipping_state' => 'address_state', 'shipping_state' => 'address_state',
'shipping_country' => 'address_country', 'shipping_country' => 'address_country',
'shipping_phone' => 'phone',
'customer_id' => 'numeric_id', 'customer_id' => 'numeric_id',
'transaction_id' => 'numeric_id', 'transaction_id' => 'numeric_id',
), ),

View File

@ -191,7 +191,7 @@ class WC_Privacy_Exporters {
'billing_postcode' => __( 'Billing Postal/Zip Code', 'woocommerce' ), 'billing_postcode' => __( 'Billing Postal/Zip Code', 'woocommerce' ),
'billing_state' => __( 'Billing State', 'woocommerce' ), 'billing_state' => __( 'Billing State', 'woocommerce' ),
'billing_country' => __( 'Billing Country / Region', 'woocommerce' ), 'billing_country' => __( 'Billing Country / Region', 'woocommerce' ),
'billing_phone' => __( 'Phone Number', 'woocommerce' ), 'billing_phone' => __( 'Billing Phone Number', 'woocommerce' ),
'billing_email' => __( 'Email Address', 'woocommerce' ), 'billing_email' => __( 'Email Address', 'woocommerce' ),
'shipping_first_name' => __( 'Shipping First Name', 'woocommerce' ), 'shipping_first_name' => __( 'Shipping First Name', 'woocommerce' ),
'shipping_last_name' => __( 'Shipping Last Name', 'woocommerce' ), 'shipping_last_name' => __( 'Shipping Last Name', 'woocommerce' ),
@ -202,6 +202,7 @@ class WC_Privacy_Exporters {
'shipping_postcode' => __( 'Shipping Postal/Zip Code', 'woocommerce' ), 'shipping_postcode' => __( 'Shipping Postal/Zip Code', 'woocommerce' ),
'shipping_state' => __( 'Shipping State', 'woocommerce' ), 'shipping_state' => __( 'Shipping State', 'woocommerce' ),
'shipping_country' => __( 'Shipping Country / Region', 'woocommerce' ), 'shipping_country' => __( 'Shipping Country / Region', 'woocommerce' ),
'shipping_phone' => __( 'Shipping Phone Number', 'woocommerce' ),
), ),
$customer $customer
); );
@ -257,6 +258,7 @@ class WC_Privacy_Exporters {
'formatted_shipping_address' => __( 'Shipping Address', 'woocommerce' ), 'formatted_shipping_address' => __( 'Shipping Address', 'woocommerce' ),
'billing_phone' => __( 'Phone Number', 'woocommerce' ), 'billing_phone' => __( 'Phone Number', 'woocommerce' ),
'billing_email' => __( 'Email Address', 'woocommerce' ), 'billing_email' => __( 'Email Address', 'woocommerce' ),
'shipping_phone' => __( 'Shipping Phone Number', 'woocommerce' ),
), ),
$order $order
); );
@ -367,11 +369,11 @@ class WC_Privacy_Exporters {
), ),
array( array(
'name' => __( 'Access granted', 'woocommerce' ), 'name' => __( 'Access granted', 'woocommerce' ),
'value' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ), 'value' => gmdate( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ),
), ),
array( array(
'name' => __( 'Access expires', 'woocommerce' ), 'name' => __( 'Access expires', 'woocommerce' ),
'value' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null, 'value' => ! is_null( $download->get_access_expires( 'edit' ) ) ? gmdate( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null,
), ),
); );

View File

@ -449,47 +449,31 @@ class WC_Product_Variable extends WC_Product {
} }
/** /**
* Save data (either create or update depending on if we are working on an existing product). * Do any extra processing needed before the actual product save
* (but after triggering the 'woocommerce_before_..._object_save' action)
* *
* @since 3.0.0 * @return mixed A state value that will be passed to after_data_store_save_or_update.
*/ */
public function save() { protected function before_data_store_save_or_update() {
$this->validate_props();
if ( ! $this->data_store ) {
return $this->get_id();
}
/**
* Trigger action before saving to the DB. Allows you to adjust object props before save.
*
* @param WC_Data $this The object being saved.
* @param WC_Data_Store_WP $data_store The data store persisting the data.
*/
do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
// Get names before save. // Get names before save.
$previous_name = $this->data['name']; $previous_name = $this->data['name'];
$new_name = $this->get_name( 'edit' ); $new_name = $this->get_name( 'edit' );
if ( $this->get_id() ) { return array(
$this->data_store->update( $this ); 'previous_name' => $previous_name,
} else { 'new_name' => $new_name,
$this->data_store->create( $this ); );
} }
$this->data_store->sync_variation_names( $this, $previous_name, $new_name );
$this->data_store->sync_managed_variation_stock_status( $this );
/** /**
* Trigger action after saving to the DB. * Do any extra processing needed after the actual product save
* (but before triggering the 'woocommerce_after_..._object_save' action)
* *
* @param WC_Data $this The object being saved. * @param mixed $state The state object that was returned by before_data_store_save_or_update.
* @param WC_Data_Store_WP $data_store The data store persisting the data.
*/ */
do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store ); protected function after_data_store_save_or_update( $state ) {
$this->data_store->sync_variation_names( $this, $state['previous_name'], $state['new_name'] );
return $this->get_id(); $this->data_store->sync_managed_variation_stock_status( $this );
} }
/* /*

View File

@ -583,21 +583,4 @@ class WC_Product_Variation extends WC_Product_Simple {
return $valid_classes; return $valid_classes;
} }
/**
* Delete variation, set the ID to 0, and return result.
*
* @since 4.4.0
* @param bool $force_delete Should the variation be deleted permanently.
* @return bool result
*/
public function delete( $force_delete = false ) {
$variation_id = $this->get_id();
if ( ! parent::delete( $force_delete ) ) {
return false;
}
return true;
}
} }

View File

@ -529,11 +529,6 @@ class WC_Query {
$args = $this->price_filter_post_clauses( $args, $wp_query ); $args = $this->price_filter_post_clauses( $args, $wp_query );
$args = $this->filterer->filter_by_attribute_post_clauses( $args, $wp_query, $this->get_layered_nav_chosen_attributes() ); $args = $this->filterer->filter_by_attribute_post_clauses( $args, $wp_query, $this->get_layered_nav_chosen_attributes() );
$search = $this->get_main_search_query_sql();
if ( $search ) {
$args['where'] .= ' AND ' . $search;
}
return $args; return $args;
} }

View File

@ -628,6 +628,7 @@ class WC_Tracker {
'calc_taxes' => get_option( 'woocommerce_calc_taxes' ), 'calc_taxes' => get_option( 'woocommerce_calc_taxes' ),
'coupons_enabled' => get_option( 'woocommerce_enable_coupons' ), 'coupons_enabled' => get_option( 'woocommerce_enable_coupons' ),
'guest_checkout' => get_option( 'woocommerce_enable_guest_checkout' ), 'guest_checkout' => get_option( 'woocommerce_enable_guest_checkout' ),
'checkout_login_reminder' => get_option( 'woocommerce_enable_checkout_login_reminder' ),
'secure_checkout' => get_option( 'woocommerce_force_ssl_checkout' ), 'secure_checkout' => get_option( 'woocommerce_force_ssl_checkout' ),
'enable_signup_and_login_from_checkout' => get_option( 'woocommerce_enable_signup_and_login_from_checkout' ), 'enable_signup_and_login_from_checkout' => get_option( 'woocommerce_enable_signup_and_login_from_checkout' ),
'enable_myaccount_registration' => get_option( 'woocommerce_enable_myaccount_registration' ), 'enable_myaccount_registration' => get_option( 'woocommerce_enable_myaccount_registration' ),

View File

@ -8,10 +8,11 @@
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
use Automattic\WooCommerce\Internal\RestockRefundedItemsAdjuster;
use Automattic\WooCommerce\Internal\AssignDefaultCategory; use Automattic\WooCommerce\Internal\AssignDefaultCategory;
use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator; use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
use Automattic\WooCommerce\Internal\RestockRefundedItemsAdjuster;
use Automattic\WooCommerce\Proxies\LegacyProxy; use Automattic\WooCommerce\Proxies\LegacyProxy;
/** /**
@ -213,6 +214,7 @@ final class WooCommerce {
wc_get_container()->get( DownloadPermissionsAdjuster::class ); wc_get_container()->get( DownloadPermissionsAdjuster::class );
wc_get_container()->get( AssignDefaultCategory::class ); wc_get_container()->get( AssignDefaultCategory::class );
wc_get_container()->get( DataRegenerator::class ); wc_get_container()->get( DataRegenerator::class );
wc_get_container()->get( LookupDataStore::class );
wc_get_container()->get( RestockRefundedItemsAdjuster::class ); wc_get_container()->get( RestockRefundedItemsAdjuster::class );
} }

View File

@ -48,6 +48,7 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
'shipping_first_name', 'shipping_first_name',
'shipping_last_name', 'shipping_last_name',
'shipping_company', 'shipping_company',
'shipping_phone',
); );
/** /**

View File

@ -59,6 +59,7 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
'shipping_first_name', 'shipping_first_name',
'shipping_last_name', 'shipping_last_name',
'shipping_company', 'shipping_company',
'shipping_phone',
'wptests_capabilities', 'wptests_capabilities',
'wptests_user_level', 'wptests_user_level',
'syntax_highlighting', 'syntax_highlighting',
@ -301,6 +302,7 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
'shipping_state' => 'shipping_state', 'shipping_state' => 'shipping_state',
'shipping_postcode' => 'shipping_postcode', 'shipping_postcode' => 'shipping_postcode',
'shipping_country' => 'shipping_country', 'shipping_country' => 'shipping_country',
'shipping_phone' => 'shipping_phone',
); );
foreach ( $shipping_address_props as $meta_key => $prop ) { foreach ( $shipping_address_props as $meta_key => $prop ) {

View File

@ -46,6 +46,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
'_shipping_state', '_shipping_state',
'_shipping_postcode', '_shipping_postcode',
'_shipping_country', '_shipping_country',
'_shipping_phone',
'_completed_date', '_completed_date',
'_paid_date', '_paid_date',
'_edit_lock', '_edit_lock',
@ -133,6 +134,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
'shipping_state' => get_post_meta( $id, '_shipping_state', true ), 'shipping_state' => get_post_meta( $id, '_shipping_state', true ),
'shipping_postcode' => get_post_meta( $id, '_shipping_postcode', true ), 'shipping_postcode' => get_post_meta( $id, '_shipping_postcode', true ),
'shipping_country' => get_post_meta( $id, '_shipping_country', true ), 'shipping_country' => get_post_meta( $id, '_shipping_country', true ),
'shipping_phone' => get_post_meta( $id, '_shipping_phone', true ),
'payment_method' => get_post_meta( $id, '_payment_method', true ), 'payment_method' => get_post_meta( $id, '_payment_method', true ),
'payment_method_title' => get_post_meta( $id, '_payment_method_title', true ), 'payment_method_title' => get_post_meta( $id, '_payment_method_title', true ),
'transaction_id' => get_post_meta( $id, '_transaction_id', true ), 'transaction_id' => get_post_meta( $id, '_transaction_id', true ),
@ -240,6 +242,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement
'_shipping_state' => 'shipping_state', '_shipping_state' => 'shipping_state',
'_shipping_postcode' => 'shipping_postcode', '_shipping_postcode' => 'shipping_postcode',
'_shipping_country' => 'shipping_country', '_shipping_country' => 'shipping_country',
'_shipping_phone' => 'shipping_phone',
), ),
); );

View File

@ -1118,7 +1118,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$product->get_id() $product->get_id()
); );
$query .= ' AND postmeta.meta_key IN ( "' . implode( '","', array_map( 'esc_sql', $meta_attribute_names ) ) . '" )'; $query .= " AND postmeta.meta_key IN ( '" . implode( "','", array_map( 'esc_sql', $meta_attribute_names ) ) . "' )";
$query .= ' ORDER BY posts.menu_order ASC, postmeta.post_id ASC;'; $query .= ' ORDER BY posts.menu_order ASC, postmeta.post_id ASC;';

View File

@ -277,7 +277,7 @@ class WC_Webhook_Data_Store implements WC_Webhook_Data_Store_Interface {
$limit = -1 < $args['limit'] ? $wpdb->prepare( 'LIMIT %d', $args['limit'] ) : ''; $limit = -1 < $args['limit'] ? $wpdb->prepare( 'LIMIT %d', $args['limit'] ) : '';
$offset = 0 < $args['offset'] ? $wpdb->prepare( 'OFFSET %d', $args['offset'] ) : ''; $offset = 0 < $args['offset'] ? $wpdb->prepare( 'OFFSET %d', $args['offset'] ) : '';
$status = ! empty( $args['status'] ) ? $wpdb->prepare( 'AND `status` = %s', isset( $statuses[ $args['status'] ] ) ? $statuses[ $args['status'] ] : $args['status'] ) : ''; $status = ! empty( $args['status'] ) ? $wpdb->prepare( 'AND `status` = %s', isset( $statuses[ $args['status'] ] ) ? $statuses[ $args['status'] ] : $args['status'] ) : '';
$search = ! empty( $args['search'] ) ? "AND `name` LIKE '%" . $wpdb->esc_like( sanitize_text_field( $args['search'] ) ) . "%'" : ''; $search = ! empty( $args['search'] ) ? $wpdb->prepare( 'AND `name` LIKE %s', '%' . $wpdb->esc_like( sanitize_text_field( $args['search'] ) ) . '%' ) : '';
$include = ''; $include = '';
$exclude = ''; $exclude = '';
$date_created = ''; $date_created = '';

View File

@ -259,6 +259,11 @@ class WC_REST_Customers_Controller extends WC_REST_Customers_V2_Controller {
'type' => 'string', 'type' => 'string',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'phone' => array(
'description' => __( 'Phone number.', 'woocommerce' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
), ),
), ),
'is_paying_customer' => array( 'is_paying_customer' => array(

View File

@ -56,7 +56,7 @@ class WC_REST_Order_Refunds_Controller extends WC_REST_Order_Refunds_V2_Controll
'reason' => $request['reason'], 'reason' => $request['reason'],
'line_items' => $request['line_items'], 'line_items' => $request['line_items'],
'refund_payment' => $request['api_refund'], 'refund_payment' => $request['api_refund'],
'restock_items' => true, 'restock_items' => $request['api_restock'],
) )
); );
@ -110,6 +110,13 @@ class WC_REST_Order_Refunds_Controller extends WC_REST_Order_Refunds_V2_Controll
'readonly' => true, 'readonly' => true,
); );
$schema['properties']['api_restock'] = array(
'description' => __( 'When true, refunded items are restocked.', 'woocommerce' ),
'type' => 'boolean',
'context' => array( 'edit' ),
'default' => true,
);
return $schema; return $schema;
} }
} }

View File

@ -278,7 +278,7 @@ function wc_format_refund_total( $amount ) {
/** /**
* Format decimal numbers ready for DB storage. * Format decimal numbers ready for DB storage.
* *
* Sanitize, remove decimals, and optionally round + trim off zeros. * Sanitize, optionally remove decimals, and optionally round + trim off zeros.
* *
* This function does not remove thousands - this should be done before passing a value to the function. * This function does not remove thousands - this should be done before passing a value to the function.
* *

View File

@ -1259,7 +1259,54 @@ if ( ! function_exists( 'woocommerce_product_archive_description' ) ) {
if ( is_post_type_archive( 'product' ) && in_array( absint( get_query_var( 'paged' ) ), array( 0, 1 ), true ) ) { if ( is_post_type_archive( 'product' ) && in_array( absint( get_query_var( 'paged' ) ), array( 0, 1 ), true ) ) {
$shop_page = get_post( wc_get_page_id( 'shop' ) ); $shop_page = get_post( wc_get_page_id( 'shop' ) );
if ( $shop_page ) { if ( $shop_page ) {
$description = wc_format_content( wp_kses_post( $shop_page->post_content ) );
$allowed_html = wp_kses_allowed_html( 'post' );
// This is needed for the search product block to work.
$allowed_html = array_merge(
$allowed_html,
array(
'form' => array(
'action' => true,
'accept' => true,
'accept-charset' => true,
'enctype' => true,
'method' => true,
'name' => true,
'target' => true,
),
'input' => array(
'type' => true,
'id' => true,
'class' => true,
'placeholder' => true,
'name' => true,
'value' => true,
),
'button' => array(
'type' => true,
'class' => true,
'label' => true,
),
'svg' => array(
'hidden' => true,
'role' => true,
'focusable' => true,
'xmlns' => true,
'width' => true,
'height' => true,
'viewbox' => true,
),
'path' => array(
'd' => true,
),
)
);
$description = wc_format_content( wp_kses( $shop_page->post_content, $allowed_html ) );
if ( $description ) { if ( $description ) {
echo '<div class="page-description">' . $description . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '<div class="page-description">' . $description . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} }

View File

@ -2266,3 +2266,19 @@ function wc_update_500_fix_product_review_count() {
function wc_update_500_db_version() { function wc_update_500_db_version() {
WC_Install::update_db_version( '5.0.0' ); WC_Install::update_db_version( '5.0.0' );
} }
/**
* Creates the refund and returns policy page.
*
* See @link https://github.com/woocommerce/woocommerce/issues/29235.
*/
function wc_update_560_create_refund_returns_page() {
WC_Install::create_pages();
}
/**
* Update DB version to 5.6.0.
*/
function wc_update_560_db_version() {
WC_Install::update_db_version( '5.6.0' );
}

1078
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "woocommerce", "name": "woocommerce",
"title": "WooCommerce", "title": "WooCommerce",
"version": "5.6.0", "version": "5.7.0",
"homepage": "https://woocommerce.com/", "homepage": "https://woocommerce.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@ -88,8 +88,8 @@
"mocha": "7.2.0", "mocha": "7.2.0",
"node-sass": "4.14.1", "node-sass": "4.14.1",
"prettier": "npm:wp-prettier@2.0.5", "prettier": "npm:wp-prettier@2.0.5",
"stylelint": "12.0.1", "stylelint": "^13.8.0",
"stylelint-config-wordpress": "16.0.0", "stylelint-config-wordpress": "17.0.0",
"typescript": "3.9.7", "typescript": "3.9.7",
"webpack": "4.44.2", "webpack": "4.44.2",
"webpack-cli": "3.3.12", "webpack-cli": "3.3.12",

View File

@ -2,9 +2,9 @@
Contributors: automattic, mikejolley, jameskoster, claudiosanches, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, wpmuguru, royho, barryhughes-1 Contributors: automattic, mikejolley, jameskoster, claudiosanches, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski, wpmuguru, royho, barryhughes-1
Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, downloads, payments, paypal, storefront, stripe, woo commerce Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, downloads, payments, paypal, storefront, stripe, woo commerce
Requires at least: 5.5 Requires at least: 5.5
Tested up to: 5.7 Tested up to: 5.8
Requires PHP: 7.0 Requires PHP: 7.0
Stable tag: 5.4.1 Stable tag: 5.5.2
License: GPLv3 License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -159,180 +159,7 @@ If you encounter issues with the shop/category pages after an update, flush the
WooCommerce comes with some sample data you can use to see how products look; import sample_products.xml via the [WordPress importer](https://wordpress.org/plugins/wordpress-importer/). You can also use the core [CSV importer](https://docs.woocommerce.com/document/product-csv-importer-exporter/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) or our [CSV Import Suite extension](https://woocommerce.com/products/product-csv-import-suite/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) to import sample_products.csv WooCommerce comes with some sample data you can use to see how products look; import sample_products.xml via the [WordPress importer](https://wordpress.org/plugins/wordpress-importer/). You can also use the core [CSV importer](https://docs.woocommerce.com/document/product-csv-importer-exporter/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) or our [CSV Import Suite extension](https://woocommerce.com/products/product-csv-import-suite/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) to import sample_products.csv
== Changelog == == Changelog ==
= 5.5.0-beta.1 2021-06-22 =
**WooCommerce** = 5.7.0 2021-09-xx =
* Performance - Set Geolocation fallback transients to expire in one day instead of one week. #29987
* Enhancement - [Transparency] CLI command for viewing tracking data for your store. #30010
* Enhancement - All settings pages can now be extended consistently with new sections and settings. Also, unit tests have been added. #27684
* Enhancement - Set checkout fields value with the default defined value where form is not presented to the user. #29820
* Tweak - Show legacy widget instance in Rest API. #30012
* Tweak - No longer load PayPal Standard by default on new installs. #29971
* Tweak - Rename Products, Products by Rating, and Recent Viewed Products widgets to Products list, Products by Rating list, and Recently Viewed Products list. #29941
* Tweak - By default the postcode field will no longer be used, and the state field will become optional, for Curaçao. #29848
* Tweak - Handle WP_Error while creating placeholder image during install. #29783
* Fix - Allow block templates for WooCommerce pages. #30013
* Fix - Download IDs are included in export CSV and imported when updating existing products to maintain download permissions. #29970
* Fix - Fees added to an order from wp-admin are now calculated correctly the first time. #29945
* Fix - Prevent caching of cart/checkout page when using Chrome browser. #29912
* Fix - Invoice emails now contain payment link if the order needs payment, not just when the order is "pending". #29833
* Fix - Introduce meta to track stocks that refunded and restocked to properly handle stock recalculation. #29762
* Fix - Resolved a console error that could occur when clicking Add Shipping Zone. #30015
* Dev - Added an if condition block to check for new install before creating Zero and Reduced rate tax classes in class-wc-install.php. #29938
* Dev - Product attributes lookup table usage when enabled. #29896
* Dev - Set $woocommerce_loop name propriety to widget "Products". #29847
* Dev - Reduce the potential for errors when plugins implement REST API endpoints based on WooCommerce's own products controller. #29835
* Dev - Remove ABSPATH check in interfaces. #30124
* Dev - Add ability to bulk update order status to cancelled. #30116
* Dev - Register woocommerce.css in editor screens so it can be enqueued in the editor. #30093
* Dev - Add Customize WooCommerce link for block-based themes. #30044
**WooCommerce Admin - 2.4.0 **
* Add - SlotFill to Abbreviated Notification panel #7091
* Add - Consume remote payment methods on frontend #6867
* Add - Extend payment gateways REST endpoint #6919
* Add - Add remote payment gateway recommendations initial docs #6962
* Add - Add loading placeholders for payment gateways task #7123
* Add - Note date range logic for GivingFeedback, and InsightFirstSale note. #6969
* Add - Add transient notices feature #6809
* Add - Add transformers in remote inbox notifications #6948
* Add - Add Mercado Pago as default fallback payment gateway #7043
* Add - Add in Razorpay as default fallback payment gateway #7096
* Add - Get post install scripts from gateway and enqueue in client #6967
* Add - Add eWAY as default fallback gateway #7108
* Add - Free extension list powered by remote config #6952
* Add - Add PayPal to fallback payment gateways #7001
* Add - Add a data store for WC Payments REST APIs #6918
* Add - Progressive setup checklist copy and call to action buttons. #6956
* Add - Add Paystack as fallback gateway #7025
* Add - Add Square as default fallback gateway #7107
* Add - Add COD method to default payment gateway recommendations #7057
* Add - Add BACS as default fallback payment gateway #7073
* Add - A/B test of progressive checklist features. #7089
* Add - Add payment gateway return URL and action #7095
* Add - Add Mollie to the default payment gateways. #7092
* Add - Show task and activity notifications in the Inbox panel #7017
* Add - Adding WCPay payment configuration defaults. #7097
* Add - Create onboarding package to house refactored WCPay card and relevant components #7058
* Dev - Add Jetpack Backup admin note #6738
* Dev - Reduce the specificity and complexity of the ReportError component #6846
* Dev - Converting <SettingsForm /> component to TypeScript. #6981
* Dev - Update package-lock to fix versioning of local packages. #6843
* Dev - Use rule processing for remote payment methods #6830
* Dev - Update E2E jest config, so it correctly creates screenshots on failure. #6858
* Dev - Fixed storybook build script #6875
* Dev - Removed allowed keys list for adding woocommerce_meta data. #6889 🎉 @xristos3490
* Dev - Delete all products when running product import tests, unskip previously skipped test. #6905
* Dev - Add payment method selector to onboarding store #6921
* Dev - Add disabled prop to SelectControl #6902
* Dev - Add filter variation to tracks data in products analytics. #6913
* Dev - Offload remote inbox notifications engine run using action-scheduler. #6995
* Dev - Add source param support for notes query. #6979
* Dev - Remove the use of Dashicons and replace with @wordpress/icons or gridicons. #7020
* Dev - Refactor inbox panel components and moved to experimental package. #7006
* Dev - Business features uncheck creative mail by default #7139
* Dev - Remove support for IE11. #7112
* Dev - Drop styling support for IE11. #7137
* Dev - Remove react-docgen docs in favor of Storybook #7055
* Enhancement - Add expand/collapse to extendable task list. #6910
* Enhancement - Add task hierarchy support to extended task list. #6916
* Enhancement - Add remind me later option to task list. #6923
* Enhancement - Enable Remote Free Extensions List #7144
* Enhancement - Adding Slotfills for remote payments and SettingsForm component. #6932
* Fix - Update the wordpress/babel-preset to avoid crashes in WP5.8 beta2 #7202
* Fix - Add fallback for the select/dispatch data-controls for older WP versions #7204
* Fix - RemoteFreeExtension hide bundle when all of its plugins are not visible #7182
* Fix - Issue where summary stats were not showing in Analytics > Stock. #7161
* Fix - Rule Processing Transformer to handle dotNotation default value #7009
* Fix - Remove Navigation's uneeded SlotFill context #6832
* Fix - Report filters expecting specific ordering. #6847
* Fix - Render bug with report comparison mode selections. #6862
* Fix - Throw exception if the data store cannot be loaded when trying to use notes. #6771
* Fix - Autocompleter for custom Search in FilterPicker #6880
* Fix - Get currency from CurrencyContext #6723
* Fix - Correct the left position of transient notices when the new nav is used. #6914
* Fix - Exclude WC Shipping for store that are only offering downloadable products #6917
* Fix - SelectControl focus and de-focus bug #6906
* Fix - Multiple preload tag output bug. #6998
* Fix - Call existing filters for leaderboards in analytics. #6626
* Fix - Set target to blank for the external links #6999
* Fix style regression with the Chart header. #7002
* Fix styling of the advanced filter operator selection. #7005
* Fix - Deprecated warnings from select control @wordpress/data-controls. #7007
* Fix - Bug with Orders Report coupon exclusion filter. #7021
* Fix - Show Google Listing and Ads in installed marketing extensions section. #7029
* Fix - Notices not dissapearing. #7077
* Fix - Keyboard accessibility on the free features tab. #7149
* Fix - Fix error handling when remote free extension API returns empty array. #7147
* Fix - Transformer casing is incorrect and creates an error on case-sensitive systems #7104
* Fix - Preventing redundant notices when installing plugins via payments task list. #7026
* Fix - Autocompleter for custom Search in CompareFilter #6911
* Fix - Add target to the button to open it in a new tab #7110
* Fix - Make `Search` accept synchronous `autocompleter.options`. #6884
* Fix - Set autoload to false for all remote inbox notifications options. #7060
* Tweak - Setup checklist copy revert. #7015
* Tweak - Revert Card component removal #7167
* Update - Task list component with new Experimental Task list. #6849
* Update - Optimize payment gateway resolution #7124
* Update - Experimental task list import to the experimental package. #6950
* Update - Redirect to WC Home after setting up a payment method #6891
* Update - Hook up payments gateway data store #7038
* Update - Update remote payment docs gateway methods #7079
* Update - Remove original business step flow #7103
* Update - WooCommerce Shipping copy on onboarding steps #7148
** WooCommerce Blocks Package - 5.2.0 & 5.3.0 & 5.3.1 **
* Enhancement - Hide legacy widgets with a feature-complete block equivalent from the widget area block inserter. #4237
* Enhancement - Provide block transforms for legacy widgets with a feature-complete block equivalent. #4292
* Enhancement - Hide the All Products Block from the Customizer Widget Areas until full support is achieved. #4225
* Enhancement - Improved accessibility and styling of the controls of several of ours blocks. #4100
* Enhancement - Fix duplicate react keys in ProductDetails component. #4187
* Fix - Fix a bug in which Cart Widget didnt update when adding items from the All Products block. #4291
* Fix - Fix an issue where an attempt to add an out-of-stock product to the cart was made when clicking the “Read more” button. #4265
* Fix - Fix Product Categories List block display in Site Editor #4335.
* Fix - Make links in the Product Categories List block unclickable in the editor #4339.
* Fix - Fix rating stars not being shown in the Site Editor #4345.
** WooCommerce Blocks Feature Plugin - 5.2.0 & 5.3.0 & 5.3.1 **
* Enhancement - Added a key prop to each CartTotalItem within usePaymentMethodInterface. (4240)
* Enhancement - Sync customer data during checkout with draft orders. (4197)
* Enhancement - Update the display of the sidebar/order summary in the Cart and Checkout blocks. (4180)
* Enhancement - Hide the Cart and Checkout blocks from the new block-based widget editor. (4303)
* Fix - Hide tax breakdown if the total amount of tax to be paid is 0. (4262)
* Fix - Prevent Coupon code panel from appearing in stores were coupons are disabled. (4202)
* Fix - For payment methods, only use canMakePayment in the frontend (not the editor) context. (4188)
* Fix - Fix sending of confirmation emails for orders when no payment is needed. (4186)
* Fix - Stopped a warning being shown when using WooCommerce Force Sells and adding a product with a Synced Force Sell to the cart. (4182)
* Fix - Fix some missing translations from the Cart and Checkout blocks. (4295)
* Fix - Fix the flickering of the Proceed to Checkout button on quantity update in the Cart Block. (4293)
* Fix - Fix a display issue when itemized taxes are enabled, but no products in the cart are taxable. (4284)
* Compatibility - Add the ability for extensions to register callbacks to be executed by Blocks when the cart/extensions endpoint is hit. Extensions can now tell Blocks they need to do some server-side processing which will update the cart. (4298)
* Tweak - Add couponName filter to allow extensions to modify how coupons are displayed in the Cart and Checkout summary. (4166)
* Tweak - Move Button and Label components to @woocommerce/blocks-checkout package. (4222)
* Tweak - Add Slot in the Discounts section of the cart sidebar to allow third party extensions to render their own components there. (4248)
** ActionScheduler 3.2.0 **
* Fix - Add "no ordering" option to as_next_scheduled_action().
* Fix - Add secondary scheduled date checks when claiming actions (DBStore) | #634.
* Fix - Add secondary scheduled date checks when claiming actions (wpPostStore) | #634.
* Fix - Adds a new index to the action table, reducing the potential for deadlocks (props: @glagonikas).
* Fix - Fix unit tests infrastructure and adapt tests to PHP 8.
* Fix - Identify in-use data store.
* Fix - Improve test_migration_is_scheduled.
* Fix - PHP notice on list table.
* Fix - Speed up clean up and batch selects.
* Fix - Update pending dependencies.
* Fix - [PHP 8.0] Only pass action arg values through to do_action_ref_array().
* Fix - [PHP 8] Set the PHP version to 7.1 in composer.json for PHP 8 compatibility.
* Fix - add is_initialized() to docs.
* Fix - fix file permissions.
* Fix - fixes #664 by replacing __ with esc_html__.
* Fix - Add extra safety/account for different versions of AS and different loading patterns. #714
* Fix - Handle hidden columns (Tools → Scheduled Actions) | #600.
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt). [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt).

View File

@ -108,20 +108,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[24]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -279,20 +279,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1.5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[10]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[8]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -456,20 +456,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[10]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[6]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -621,20 +621,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.8]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[8]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[6]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -786,20 +786,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[4]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -950,20 +950,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1.2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[12]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1.5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -1116,20 +1116,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.6]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[8]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[6.5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[4]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -1281,20 +1281,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[4]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1.4]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -1449,20 +1449,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[3]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[10]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[8]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -1614,20 +1614,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[8]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[6]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -1779,20 +1779,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[7]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -1944,20 +1944,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.8]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[6]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -3489,20 +3489,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[10]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[12]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.5]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>
@ -3654,20 +3654,20 @@
<wp:meta_value><![CDATA[no]]></wp:meta_value> <wp:meta_value><![CDATA[no]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_weight</wp:meta_key> <wp:meta_key><![CDATA[_weight]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[0.2]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_length</wp:meta_key> <wp:meta_key><![CDATA[_length]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[6]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_width</wp:meta_key> <wp:meta_key><![CDATA[_width]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[4]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_height</wp:meta_key> <wp:meta_key><![CDATA[_height]]></wp:meta_key>
<wp:meta_value><![CDATA[]]></wp:meta_value> <wp:meta_value><![CDATA[1]]></wp:meta_value>
</wp:postmeta> </wp:postmeta>
<wp:postmeta> <wp:postmeta>
<wp:meta_key>_upsell_ids</wp:meta_key> <wp:meta_key>_upsell_ids</wp:meta_key>

View File

@ -9,7 +9,6 @@ use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator; use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer; use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore; use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
use Automattic\WooCommerce\Proxies\LegacyProxy;
/** /**
* Service provider for the ProductAttributesLookupServiceProvider namespace. * Service provider for the ProductAttributesLookupServiceProvider namespace.
@ -32,7 +31,7 @@ class ProductAttributesLookupServiceProvider extends AbstractServiceProvider {
*/ */
public function register() { public function register() {
$this->share( DataRegenerator::class )->addArgument( LookupDataStore::class ); $this->share( DataRegenerator::class )->addArgument( LookupDataStore::class );
$this->share( Filterer::class )->addArgument( LookupDataStore::class )->addArgument( LegacyProxy::class ); $this->share( Filterer::class )->addArgument( LookupDataStore::class );
$this->share( LookupDataStore::class ); $this->share( LookupDataStore::class );
} }
} }

View File

@ -6,6 +6,7 @@
namespace Automattic\WooCommerce\Internal\ProductAttributesLookup; namespace Automattic\WooCommerce\Internal\ProductAttributesLookup;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore; use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
use Automattic\WooCommerce\Utilities\ArrayUtil;
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
@ -61,7 +62,7 @@ class DataRegenerator {
); );
add_action( add_action(
'woocommerce_run_product_attribute_lookup_update_callback', 'woocommerce_run_product_attribute_lookup_regeneration_callback',
function () { function () {
$this->run_regeneration_step_callback(); $this->run_regeneration_step_callback();
} }
@ -93,6 +94,8 @@ class DataRegenerator {
* (Note how we are returning "false" since the class handles the step scheduling by itself). * (Note how we are returning "false" since the class handles the step scheduling by itself).
*/ */
public function initiate_regeneration() { public function initiate_regeneration() {
$this->enable_or_disable_lookup_table_usage( false );
$this->delete_all_attributes_lookup_data(); $this->delete_all_attributes_lookup_data();
$products_exist = $this->initialize_table_and_data(); $products_exist = $this->initialize_table_and_data();
if ( $products_exist ) { if ( $products_exist ) {
@ -102,15 +105,6 @@ class DataRegenerator {
} }
} }
/**
* Tells if a regeneration is already in progress.
*
* @return bool True if a regeneration is already in progress.
*/
public function regeneration_is_in_progress() {
return ! is_null( get_option( 'woocommerce_attribute_lookup__last_products_page_processed', null ) );
}
/** /**
* Delete all the existing data related to the lookup table, including the table itself. * Delete all the existing data related to the lookup table, including the table itself.
* *
@ -124,6 +118,7 @@ class DataRegenerator {
delete_option( 'woocommerce_attribute_lookup__enabled' ); delete_option( 'woocommerce_attribute_lookup__enabled' );
delete_option( 'woocommerce_attribute_lookup__last_product_id_to_process' ); delete_option( 'woocommerce_attribute_lookup__last_product_id_to_process' );
delete_option( 'woocommerce_attribute_lookup__last_products_page_processed' ); delete_option( 'woocommerce_attribute_lookup__last_products_page_processed' );
$this->data_store->unset_regeneration_in_progress_flag();
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( 'DROP TABLE IF EXISTS ' . $this->lookup_table_name ); $wpdb->query( 'DROP TABLE IF EXISTS ' . $this->lookup_table_name );
@ -170,6 +165,7 @@ CREATE TABLE ' . $this->lookup_table_name . '(
return false; return false;
} }
$this->data_store->set_regeneration_in_progress_flag();
update_option( 'woocommerce_attribute_lookup__last_product_id_to_process', current( $last_existing_product_id ) ); update_option( 'woocommerce_attribute_lookup__last_product_id_to_process', current( $last_existing_product_id ) );
update_option( 'woocommerce_attribute_lookup__last_products_page_processed', 0 ); update_option( 'woocommerce_attribute_lookup__last_products_page_processed', 0 );
@ -181,7 +177,7 @@ CREATE TABLE ' . $this->lookup_table_name . '(
* schedules the next step if necessary. * schedules the next step if necessary.
*/ */
private function run_regeneration_step_callback() { private function run_regeneration_step_callback() {
if ( ! $this->regeneration_is_in_progress() ) { if ( ! $this->data_store->regeneration_is_in_progress() ) {
return; return;
} }
@ -200,7 +196,7 @@ CREATE TABLE ' . $this->lookup_table_name . '(
$queue = WC()->get_instance_of( \WC_Queue::class ); $queue = WC()->get_instance_of( \WC_Queue::class );
$queue->schedule_single( $queue->schedule_single(
WC()->call_function( 'time' ) + 1, WC()->call_function( 'time' ) + 1,
'woocommerce_run_product_attribute_lookup_update_callback', 'woocommerce_run_product_attribute_lookup_regeneration_callback',
array(), array(),
'woocommerce-db-updates' 'woocommerce-db-updates'
); );
@ -233,7 +229,7 @@ CREATE TABLE ' . $this->lookup_table_name . '(
} }
foreach ( $product_ids as $id ) { foreach ( $product_ids as $id ) {
$this->data_store->update_data_for_product( $id ); $this->data_store->create_data_for_product( $id );
} }
update_option( 'woocommerce_attribute_lookup__last_products_page_processed', $current_products_page ); update_option( 'woocommerce_attribute_lookup__last_products_page_processed', $current_products_page );
@ -249,19 +245,7 @@ CREATE TABLE ' . $this->lookup_table_name . '(
delete_option( 'woocommerce_attribute_lookup__last_product_id_to_process' ); delete_option( 'woocommerce_attribute_lookup__last_product_id_to_process' );
delete_option( 'woocommerce_attribute_lookup__last_products_page_processed' ); delete_option( 'woocommerce_attribute_lookup__last_products_page_processed' );
update_option( 'woocommerce_attribute_lookup__enabled', 'no' ); update_option( 'woocommerce_attribute_lookup__enabled', 'no' );
} $this->data_store->unset_regeneration_in_progress_flag();
/**
* Check if the lookup table exists in the database.
*
* @return bool True if the lookup table exists in the database.
*/
private function lookup_table_exists() {
global $wpdb;
$query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $this->lookup_table_name ) );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return $this->lookup_table_name === $wpdb->get_var( $query );
} }
/** /**
@ -275,14 +259,14 @@ CREATE TABLE ' . $this->lookup_table_name . '(
return $tools_array; return $tools_array;
} }
$lookup_table_exists = $this->lookup_table_exists(); $lookup_table_exists = $this->data_store->check_lookup_table_exists();
$generation_is_in_progress = $this->regeneration_is_in_progress(); $generation_is_in_progress = $this->data_store->regeneration_is_in_progress();
// Regenerate table. // Regenerate table.
if ( $lookup_table_exists ) { if ( $lookup_table_exists ) {
$generate_item_name = __( 'Regenerate the product attributes lookup table', 'woocommerce' ); $generate_item_name = __( 'Regenerate the product attributes lookup table', 'woocommerce' );
$generate_item_desc = __( 'This tool will regenerate the product attributes lookup table data from existing products data. This process may take a while.', 'woocommerce' ); $generate_item_desc = __( 'This tool will regenerate the product attributes lookup table data from existing product(s) data. This process may take a while.', 'woocommerce' );
$generate_item_return = __( 'Product attributes lookup table data is regenerating', 'woocommerce' ); $generate_item_return = __( 'Product attributes lookup table data is regenerating', 'woocommerce' );
$generate_item_button = __( 'Regenerate', 'woocommerce' ); $generate_item_button = __( 'Regenerate', 'woocommerce' );
} else { } else {
@ -302,6 +286,16 @@ CREATE TABLE ' . $this->lookup_table_name . '(
}, },
); );
if ( $lookup_table_exists ) {
$entry['selector'] = array(
'description' => __( 'Select a product to regenerate the data for, or leave empty for a full table regeneration:', 'woocommerce' ),
'class' => 'wc-product-search',
'search_action' => 'woocommerce_json_search_products',
'name' => 'regenerate_product_attribute_lookup_data_product_id',
'placeholder' => esc_attr__( 'Search for a product&hellip;', 'woocommerce' ),
);
}
if ( $generation_is_in_progress ) { if ( $generation_is_in_progress ) {
$entry['button'] = sprintf( $entry['button'] = sprintf(
/* translators: %d: How many products have been processed so far. */ /* translators: %d: How many products have been processed so far. */
@ -335,35 +329,6 @@ CREATE TABLE ' . $this->lookup_table_name . '(
); );
} }
if ( $lookup_table_exists && ! $generation_is_in_progress ) {
// Enable or disable table usage.
if ( 'yes' === get_option( 'woocommerce_attribute_lookup__enabled' ) ) {
$tools_array['disable_product_attributes_lookup_table_usage'] = array(
'name' => __( 'Disable the product attributes lookup table usage', 'woocommerce' ),
'desc' => __( 'The product attributes lookup table usage is currently enabled, use this tool to disable it.', 'woocommerce' ),
'button' => __( 'Disable', 'woocommerce' ),
'requires_refresh' => true,
'callback' => function () {
$this->enable_or_disable_lookup_table_usage( false );
return __( 'Product attributes lookup table usage has been disabled.', 'woocommerce' );
},
);
} else {
$tools_array['enable_product_attributes_lookup_table_usage'] = array(
'name' => __( 'Enable the product attributes lookup table usage', 'woocommerce' ),
'desc' => __( 'The product attributes lookup table usage is currently disabled, use this tool to enable it.', 'woocommerce' ),
'button' => __( 'Enable', 'woocommerce' ),
'requires_refresh' => true,
'callback' => function () {
$this->enable_or_disable_lookup_table_usage( true );
return __( 'Product attributes lookup table usage has been enabled.', 'woocommerce' );
},
);
}
}
return $tools_array; return $tools_array;
} }
@ -373,12 +338,20 @@ CREATE TABLE ' . $this->lookup_table_name . '(
* @throws \Exception The regeneration is already in progress. * @throws \Exception The regeneration is already in progress.
*/ */
private function initiate_regeneration_from_tools_page() { private function initiate_regeneration_from_tools_page() {
if ( $this->regeneration_is_in_progress() ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
throw new \Exception( 'Product attributes lookup table is already regenerating.' ); if ( ! isset( $_REQUEST['_wpnonce'] ) || false === wp_verify_nonce( $_REQUEST['_wpnonce'], 'debug_action' ) ) {
throw new \Exception( 'Invalid nonce' );
} }
if ( isset( $_REQUEST['regenerate_product_attribute_lookup_data_product_id'] ) ) {
$product_id = (int) $_REQUEST['regenerate_product_attribute_lookup_data_product_id'];
$this->check_can_do_lookup_table_regeneration( $product_id );
$this->data_store->create_data_for_product( $product_id );
} else {
$this->check_can_do_lookup_table_regeneration();
$this->initiate_regeneration(); $this->initiate_regeneration();
} }
}
/** /**
* Enable or disable the actual lookup table usage. * Enable or disable the actual lookup table usage.
@ -387,10 +360,32 @@ CREATE TABLE ' . $this->lookup_table_name . '(
* @throws \Exception A lookup table regeneration is currently in progress. * @throws \Exception A lookup table regeneration is currently in progress.
*/ */
private function enable_or_disable_lookup_table_usage( $enable ) { private function enable_or_disable_lookup_table_usage( $enable ) {
if ( $this->regeneration_is_in_progress() ) { if ( $this->data_store->regeneration_is_in_progress() ) {
throw new \Exception( "Can't enable or disable the attributes lookup table usage while it's regenerating." ); throw new \Exception( "Can't enable or disable the attributes lookup table usage while it's regenerating." );
} }
update_option( 'woocommerce_attribute_lookup__enabled', $enable ? 'yes' : 'no' ); update_option( 'woocommerce_attribute_lookup__enabled', $enable ? 'yes' : 'no' );
} }
/**
* Check if everything is good to go to perform a complete or per product lookup table data regeneration
* and throw an exception if not.
*
* @param mixed $product_id The product id to check the regeneration viability for, or null to check if a complete regeneration is possible.
* @throws \Exception Something prevents the regeneration from starting.
*/
private function check_can_do_lookup_table_regeneration( $product_id = null ) {
if ( ! $this->data_store->is_feature_visible() ) {
throw new \Exception( "Can't do product attribute lookup data regeneration: feature is not visible" );
}
if ( $product_id && ! $this->data_store->check_lookup_table_exists() ) {
throw new \Exception( "Can't do product attribute lookup data regeneration: lookup table doesn't exist" );
}
if ( $this->data_store->regeneration_is_in_progress() ) {
throw new \Exception( "Can't do product attribute lookup data regeneration: regeneration is already in progress" );
}
if ( $product_id && ! wc_get_product( $product_id ) ) {
throw new \Exception( "Can't do product attribute lookup data regeneration: product doesn't exist" );
}
}
} }

View File

@ -196,7 +196,9 @@ class Filterer {
$query['select'] = 'SELECT COUNT(DISTINCT product_or_parent_id) as term_count, term_id as term_count_id'; $query['select'] = 'SELECT COUNT(DISTINCT product_or_parent_id) as term_count, term_id as term_count_id';
$query['from'] = "FROM {$this->lookup_table_name}"; $query['from'] = "FROM {$this->lookup_table_name}";
$query['join'] = "INNER JOIN {$wpdb->posts} ON {$wpdb->posts}.ID = {$this->lookup_table_name}.product_or_parent_id"; $query['join'] = "
{$tax_query_sql['join']} {$meta_query_sql['join']}
INNER JOIN {$wpdb->posts} ON {$wpdb->posts}.ID = {$this->lookup_table_name}.product_or_parent_id";
$term_ids_sql = $this->get_term_ids_sql( $term_ids ); $term_ids_sql = $this->get_term_ids_sql( $term_ids );
$query['where'] = " $query['where'] = "
@ -211,41 +213,50 @@ class Filterer {
$attributes_to_filter_by = \WC_Query::get_layered_nav_chosen_attributes(); $attributes_to_filter_by = \WC_Query::get_layered_nav_chosen_attributes();
if ( ! empty( $attributes_to_filter_by ) ) { if ( ! empty( $attributes_to_filter_by ) ) {
$all_terms_to_filter_by = array(); $and_term_ids = array();
$or_term_ids = array();
foreach ( $attributes_to_filter_by as $taxonomy => $data ) { foreach ( $attributes_to_filter_by as $taxonomy => $data ) {
$all_terms = get_terms( $taxonomy, array( 'hide_empty' => false ) ); $all_terms = get_terms( $taxonomy, array( 'hide_empty' => false ) );
$term_ids_by_slug = wp_list_pluck( $all_terms, 'term_id', 'slug' ); $term_ids_by_slug = wp_list_pluck( $all_terms, 'term_id', 'slug' );
$term_ids_to_filter_by = array_values( array_intersect_key( $term_ids_by_slug, array_flip( $data['terms'] ) ) ); $term_ids_to_filter_by = array_values( array_intersect_key( $term_ids_by_slug, array_flip( $data['terms'] ) ) );
$all_terms_to_filter_by = array_merge( $all_terms_to_filter_by, $term_ids_to_filter_by ); if ( 'and' === $data['query_type'] ) {
$term_ids_to_filter_by_list = '(' . join( ',', $term_ids_to_filter_by ) . ')'; $and_term_ids = array_merge( $and_term_ids, $term_ids_to_filter_by );
} else {
$or_term_ids = array_merge( $or_term_ids, $term_ids_to_filter_by );
}
}
$count = count( $term_ids_to_filter_by ); if ( ! empty( $and_term_ids ) ) {
if ( 0 !== $count ) { $terms_count = count( $and_term_ids );
$query['where'] .= ' AND product_or_parent_id IN ('; $term_ids_list = '(' . join( ',', $and_term_ids ) . ')';
if ( 'and' === $attributes_to_filter_by[ $taxonomy ]['query_type'] ) {
$query['where'] .= " $query['where'] .= "
AND product_or_parent_id IN (
SELECT product_or_parent_id SELECT product_or_parent_id
FROM {$this->lookup_table_name} lt FROM {$this->lookup_table_name} lt
WHERE is_variation_attribute=0 WHERE is_variation_attribute=0
{$in_stock_clause} {$in_stock_clause}
AND term_id in {$term_ids_to_filter_by_list} AND term_id in {$term_ids_list}
GROUP BY product_id GROUP BY product_id
HAVING COUNT(product_id)={$count} HAVING COUNT(product_id)={$terms_count}
UNION UNION
SELECT product_or_parent_id SELECT product_or_parent_id
FROM {$this->lookup_table_name} lt FROM {$this->lookup_table_name} lt
WHERE is_variation_attribute=1 WHERE is_variation_attribute=1
{$in_stock_clause} {$in_stock_clause}
AND term_id in {$term_ids_to_filter_by_list} AND term_id in {$term_ids_list}
)"; )";
} else { }
if ( ! empty( $or_term_ids ) ) {
$term_ids_list = '(' . join( ',', $or_term_ids ) . ')';
$query['where'] .= " $query['where'] .= "
AND product_or_parent_id IN (
SELECT product_or_parent_id FROM {$this->lookup_table_name} SELECT product_or_parent_id FROM {$this->lookup_table_name}
WHERE term_id in {$term_ids_to_filter_by_list} WHERE term_id in {$term_ids_list}
{$in_stock_clause} {$in_stock_clause}
)"; )";
}
}
} }
} else { } else {
$query['where'] .= $in_stock_clause; $query['where'] .= $in_stock_clause;

View File

@ -14,6 +14,15 @@ defined( 'ABSPATH' ) || exit;
*/ */
class LookupDataStore { class LookupDataStore {
/**
* Types of updates to perform depending on the current changest
*/
const ACTION_NONE = 0;
const ACTION_INSERT = 1;
const ACTION_UPDATE_STOCK = 2;
const ACTION_DELETE = 3;
/** /**
* The lookup table name. * The lookup table name.
* *
@ -36,6 +45,101 @@ class LookupDataStore {
$this->lookup_table_name = $wpdb->prefix . 'wc_product_attributes_lookup'; $this->lookup_table_name = $wpdb->prefix . 'wc_product_attributes_lookup';
$this->is_feature_visible = false; $this->is_feature_visible = false;
$this->init_hooks();
}
/**
* Initialize the hooks used by the class.
*/
private function init_hooks() {
add_action(
'woocommerce_run_product_attribute_lookup_update_callback',
function ( $product_id, $action ) {
$this->run_update_callback( $product_id, $action );
},
10,
2
);
add_filter(
'woocommerce_get_sections_products',
function ( $products ) {
if ( $this->is_feature_visible() && $this->check_lookup_table_exists() ) {
$products['advanced'] = __( 'Advanced', 'woocommerce' );
}
return $products;
},
100,
1
);
add_filter(
'woocommerce_get_settings_products',
function ( $settings, $section_id ) {
if ( 'advanced' === $section_id && $this->is_feature_visible() && $this->check_lookup_table_exists() ) {
$title_item = array(
'title' => __( 'Product attributes lookup table', 'woocommerce' ),
'type' => 'title',
);
$regeneration_is_in_progress = $this->regeneration_is_in_progress();
if ( $regeneration_is_in_progress ) {
$title_item['desc'] = __( 'These settings are not available while the lookup table regeneration is in progress.', 'woocommerce' );
}
$settings[] = $title_item;
if ( ! $regeneration_is_in_progress ) {
$settings[] = array(
'title' => __( 'Enable table usage', 'woocommerce' ),
'desc' => __( 'Use the product attributes lookup table for catalog filtering.', 'woocommerce' ),
'id' => 'woocommerce_attribute_lookup__enabled',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'start',
);
$settings[] = array(
'title' => __( 'Direct updates', 'woocommerce' ),
'desc' => __( 'Update the table directly upon product changes, instead of scheduling a deferred update.', 'woocommerce' ),
'id' => 'woocommerce_attribute_lookup__direct_updates',
'default' => 'no',
'type' => 'checkbox',
'checkboxgroup' => 'start',
);
}
$settings[] = array( 'type' => 'sectionend' );
}
return $settings;
},
100,
2
);
}
/**
* Check if the lookup table exists in the database.
*
* TODO: Remove this method and references to it once the lookup table is created via data migration.
*
* @return bool
*/
public function check_lookup_table_exists() {
global $wpdb;
$query = $wpdb->prepare(
'SELECT count(*)
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = %s;',
$this->lookup_table_name
);
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
return (bool) $wpdb->get_var( $query );
} }
/** /**
@ -71,45 +175,229 @@ class LookupDataStore {
} }
/** /**
* Insert or update the lookup data for a given product or variation. * Insert/update the appropriate lookup table entries for a new or modified product or variation.
* If a variable product is passed the information is updated for all of its variations. * This must be invoked after a product or a variation is created (including untrashing and duplication)
* or modified.
* *
* @param int|WC_Product $product Product object or id. * @param int|\WC_Product $product Product object or product id.
* @throws \Exception A variation object is passed. * @param null|array $changeset Changes as provided by 'get_changes' method in the product object, null if it's being created.
*/ */
public function update_data_for_product( $product ) { public function on_product_changed( $product, $changeset = null ) {
// TODO: For now data is always deleted and fully regenerated, existing data should be updated instead. if ( ! $this->check_lookup_table_exists() ) {
return;
}
if ( ! is_a( $product, \WC_Product::class ) ) { if ( ! is_a( $product, \WC_Product::class ) ) {
$product = WC()->call_function( 'wc_get_product', $product ); $product = WC()->call_function( 'wc_get_product', $product );
} }
if ( $this->is_variation( $product ) ) { $action = $this->get_update_action( $changeset );
throw new \Exception( "LookupDataStore::update_data_for_product can't be called for variations." ); if ( self::ACTION_NONE !== $action ) {
} $this->maybe_schedule_update( $product->get_id(), $action );
$this->delete_lookup_table_entries_for( $product->get_id() );
if ( $this->is_variable_product( $product ) ) {
$this->create_lookup_table_entries_for_variable_product( $product );
} else {
$this->create_lookup_table_entries_for_simple_product( $product );
} }
} }
/** /**
* Delete all the lookup table entries for a given product * Schedule an update of the product attributes lookup table for a given product.
* (entries are identified by the "parent_or_product_id" field) * If an update for the same action is already scheduled, nothing is done.
*
* If the 'woocommerce_attribute_lookup__direct_update' option is set to 'yes',
* the update is done directly, without scheduling.
*
* @param int $product_id The product id to schedule the update for.
* @param int $action The action to perform, one of the ACTION_ constants.
*/
private function maybe_schedule_update( int $product_id, int $action ) {
if ( 'yes' === get_option( 'woocommerce_attribute_lookup__direct_updates' ) ) {
$this->run_update_callback( $product_id, $action );
return;
}
$args = array( $product_id, $action );
$queue = WC()->get_instance_of( \WC_Queue::class );
$already_scheduled = $queue->search(
array(
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback',
'args' => $args,
'status' => \ActionScheduler_Store::STATUS_PENDING,
),
'ids'
);
if ( empty( $already_scheduled ) ) {
$queue->schedule_single(
WC()->call_function( 'time' ) + 1,
'woocommerce_run_product_attribute_lookup_update_callback',
$args,
'woocommerce-db-updates'
);
}
}
/**
* Perform an update of the lookup table for a specific product.
*
* @param int $product_id The product id to perform the update for.
* @param int $action The action to perform, one of the ACTION_ constants.
*/
private function run_update_callback( int $product_id, int $action ) {
if ( ! $this->check_lookup_table_exists() ) {
return;
}
$product = WC()->call_function( 'wc_get_product', $product_id );
if ( ! $product ) {
$action = self::ACTION_DELETE;
}
switch ( $action ) {
case self::ACTION_INSERT:
$this->delete_data_for( $product_id );
$this->create_data_for( $product );
break;
case self::ACTION_UPDATE_STOCK:
$this->update_stock_status_for( $product );
break;
case self::ACTION_DELETE:
$this->delete_data_for( $product_id );
break;
}
}
/**
* Determine the type of action to perform depending on the received changeset.
*
* @param array|null $changeset The changeset received by on_product_changed.
* @return int One of the ACTION_ constants.
*/
private function get_update_action( $changeset ) {
if ( is_null( $changeset ) ) {
// No changeset at all means that the product is new.
return self::ACTION_INSERT;
}
$keys = array_keys( $changeset );
// Order matters:
// - The change with the most precedence is a change in catalog visibility
// (which will result in all data being regenerated or deleted).
// - Then a change in attributes (all data will be regenerated).
// - And finally a change in stock status (existing data will be updated).
// Thus these conditions must be checked in that same order.
if ( in_array( 'catalog_visibility', $keys, true ) ) {
$new_visibility = $changeset['catalog_visibility'];
if ( 'visible' === $new_visibility || 'catalog' === $new_visibility ) {
return self::ACTION_INSERT;
} else {
return self::ACTION_DELETE;
}
}
if ( in_array( 'attributes', $keys, true ) ) {
return self::ACTION_INSERT;
}
if ( array_intersect( $keys, array( 'stock_quantity', 'stock_status', 'manage_stock' ) ) ) {
return self::ACTION_UPDATE_STOCK;
}
return self::ACTION_NONE;
}
/**
* Update the stock status of the lookup table entries for a given product.
*
* @param \WC_Product $product The product to update the entries for.
*/
private function update_stock_status_for( \WC_Product $product ) {
global $wpdb;
$in_stock = $product->is_in_stock();
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query(
$wpdb->prepare(
'UPDATE ' . $this->lookup_table_name . ' SET in_stock = %d WHERE product_id = %d',
$in_stock ? 1 : 0,
$product->get_id()
)
);
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
}
/**
* Delete the lookup table contents related to a given product or variation,
* if it's a variable product it deletes the information for variations too.
* This must be invoked after a product or a variation is trashed or deleted.
*
* @param int|\WC_Product $product Product object or product id.
*/
public function on_product_deleted( $product ) {
if ( ! $this->check_lookup_table_exists() ) {
return;
}
if ( is_a( $product, \WC_Product::class ) ) {
$product_id = $product->get_id();
} else {
$product_id = $product;
}
$this->maybe_schedule_update( $product_id, self::ACTION_DELETE );
}
/**
* Create the lookup data for a given product, if a variable product is passed
* the information is created for all of its variations.
* This method is intended to be called from the data regenerator.
*
* @param int|WC_Product $product Product object or id.
* @throws \Exception A variation object is passed.
*/
public function create_data_for_product( $product ) {
if ( ! is_a( $product, \WC_Product::class ) ) {
$product = WC()->call_function( 'wc_get_product', $product );
}
if ( $this->is_variation( $product ) ) {
throw new \Exception( "LookupDataStore::create_data_for_product can't be called for variations." );
}
$this->delete_data_for( $product->get_id() );
$this->create_data_for( $product );
}
/**
* Create lookup table data for a given product.
*
* @param \WC_Product $product The product to create the data for.
*/
private function create_data_for( \WC_Product $product ) {
if ( $this->is_variation( $product ) ) {
$this->create_data_for_variation( $product );
} elseif ( $this->is_variable_product( $product ) ) {
$this->create_data_for_variable_product( $product );
} else {
$this->create_data_for_simple_product( $product );
}
}
/**
* Delete all the lookup table entries for a given product,
* if it's a variable product information for variations is deleted too.
* *
* @param int $product_id Simple product id, or main/parent product id for variable products. * @param int $product_id Simple product id, or main/parent product id for variable products.
*/ */
private function delete_lookup_table_entries_for( int $product_id ) { private function delete_data_for( int $product_id ) {
global $wpdb; global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->query(
$wpdb->prepare( $wpdb->prepare(
'DELETE FROM ' . $this->lookup_table_name . ' WHERE product_or_parent_id = %d', 'DELETE FROM ' . $this->lookup_table_name . ' WHERE product_id = %d OR product_or_parent_id = %d',
$product_id,
$product_id $product_id
) )
); );
@ -122,7 +410,7 @@ class LookupDataStore {
* *
* @param \WC_Product $product The product to create the entries for. * @param \WC_Product $product The product to create the entries for.
*/ */
private function create_lookup_table_entries_for_simple_product( \WC_Product $product ) { private function create_data_for_simple_product( \WC_Product $product ) {
$product_attributes_data = $this->get_attribute_taxonomies( $product ); $product_attributes_data = $this->get_attribute_taxonomies( $product );
$has_stock = $product->is_in_stock(); $has_stock = $product->is_in_stock();
$product_id = $product->get_id(); $product_id = $product->get_id();
@ -140,7 +428,7 @@ class LookupDataStore {
* *
* @param \WC_Product_Variable $product The product to create the entries for. * @param \WC_Product_Variable $product The product to create the entries for.
*/ */
private function create_lookup_table_entries_for_variable_product( \WC_Product_Variable $product ) { private function create_data_for_variable_product( \WC_Product_Variable $product ) {
$product_attributes_data = $this->get_attribute_taxonomies( $product ); $product_attributes_data = $this->get_attribute_taxonomies( $product );
$variation_attributes_data = array_filter( $variation_attributes_data = array_filter(
$product_attributes_data, $product_attributes_data,
@ -170,20 +458,56 @@ class LookupDataStore {
foreach ( $variation_attributes_data as $taxonomy => $data ) { foreach ( $variation_attributes_data as $taxonomy => $data ) {
foreach ( $variations as $variation ) { foreach ( $variations as $variation ) {
$this->insert_lookup_table_data_for_variation( $variation, $taxonomy, $main_product_id, $data['term_ids'], $term_ids_by_slug_cache );
}
}
}
/**
* Create all the necessary lookup data for a given variation.
*
* @param \WC_Product_Variation $variation The variation to create entries for.
*/
private function create_data_for_variation( \WC_Product_Variation $variation ) {
$main_product = WC()->call_function( 'wc_get_product', $variation->get_parent_id() );
$product_attributes_data = $this->get_attribute_taxonomies( $main_product );
$variation_attributes_data = array_filter(
$product_attributes_data,
function( $item ) {
return $item['used_for_variations'];
}
);
$term_ids_by_slug_cache = $this->get_term_ids_by_slug_cache( array_keys( $variation_attributes_data ) );
foreach ( $variation_attributes_data as $taxonomy => $data ) {
$this->insert_lookup_table_data_for_variation( $variation, $taxonomy, $main_product->get_id(), $data['term_ids'], $term_ids_by_slug_cache );
}
}
/**
* Create lookup table entries for a given variation, corresponding to a given taxonomy and a set of term ids.
*
* @param \WC_Product_Variation $variation The variation to create entries for.
* @param string $taxonomy The taxonomy to create the entries for.
* @param int $main_product_id The parent product id.
* @param array $term_ids The term ids to create entries for.
* @param array $term_ids_by_slug_cache A dictionary of term ids by term slug, as returned by 'get_term_ids_by_slug_cache'.
*/
private function insert_lookup_table_data_for_variation( \WC_Product_Variation $variation, string $taxonomy, int $main_product_id, array $term_ids, array $term_ids_by_slug_cache ) {
$variation_id = $variation->get_id(); $variation_id = $variation->get_id();
$variation_has_stock = $variation->is_in_stock(); $variation_has_stock = $variation->is_in_stock();
$variation_definition_term_id = $this->get_variation_definition_term_id( $variation, $taxonomy, $term_ids_by_slug_cache ); $variation_definition_term_id = $this->get_variation_definition_term_id( $variation, $taxonomy, $term_ids_by_slug_cache );
if ( $variation_definition_term_id ) { if ( $variation_definition_term_id ) {
$this->insert_lookup_table_data( $variation_id, $main_product_id, $taxonomy, $variation_definition_term_id, true, $variation_has_stock ); $this->insert_lookup_table_data( $variation_id, $main_product_id, $taxonomy, $variation_definition_term_id, true, $variation_has_stock );
} else { } else {
$term_ids_for_taxonomy = $data['term_ids']; $term_ids_for_taxonomy = $term_ids;
foreach ( $term_ids_for_taxonomy as $term_id ) { foreach ( $term_ids_for_taxonomy as $term_id ) {
$this->insert_lookup_table_data( $variation_id, $main_product_id, $taxonomy, $term_id, true, $variation_has_stock ); $this->insert_lookup_table_data( $variation_id, $main_product_id, $taxonomy, $term_id, true, $variation_has_stock );
} }
} }
} }
}
}
/** /**
* Get a cache of term ids by slug for a set of taxonomies, with this format: * Get a cache of term ids by slug for a set of taxonomies, with this format:
@ -338,4 +662,27 @@ class LookupDataStore {
); );
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
} }
/**
* Tells if a lookup table regeneration is currently in progress.
*
* @return bool True if a lookup table regeneration is already in progress.
*/
public function regeneration_is_in_progress() {
return 'yes' === get_option( 'woocommerce_attribute_lookup__regeneration_in_progress', null );
}
/**
* Set a permanent flag (via option) indicating that the lookup table regeneration is in process.
*/
public function set_regeneration_in_progress_flag() {
update_option( 'woocommerce_attribute_lookup__regeneration_in_progress', 'yes' );
}
/**
* Remove the flag indicating that the lookup table regeneration is in process.
*/
public function unset_regeneration_in_progress_flag() {
delete_option( 'woocommerce_attribute_lookup__regeneration_in_progress' );
}
} }

View File

@ -20,6 +20,7 @@ class RestApiUtil {
* [ * [
* "reason" => "", * "reason" => "",
* "api_refund" => "x", * "api_refund" => "x",
* "api_restock" => "x",
* "line_items" => [ * "line_items" => [
* "id" => "111", * "id" => "111",
* "quantity" => 222, * "quantity" => 222,
@ -37,6 +38,7 @@ class RestApiUtil {
* [ * [
* "reason" => null, (if it's missing or any empty value, set as null) * "reason" => null, (if it's missing or any empty value, set as null)
* "api_refund" => true, (if it's missing or non-bool, set as "true") * "api_refund" => true, (if it's missing or non-bool, set as "true")
* "api_restock" => true, (if it's missing or non-bool, set as "true")
* "line_items" => [ (convert sequential array to associative based on "id") * "line_items" => [ (convert sequential array to associative based on "id")
* "111" => [ * "111" => [
* "qty" => 222, (rename "quantity" to "qty") * "qty" => 222, (rename "quantity" to "qty")
@ -66,6 +68,10 @@ class RestApiUtil {
$request['api_refund'] = true; $request['api_refund'] = true;
} }
if ( ! is_bool( $request['api_restock'] ) ) {
$request['api_restock'] = true;
}
if ( empty( $request['line_items'] ) ) { if ( empty( $request['line_items'] ) ) {
$request['line_items'] = array(); $request['line_items'] = array();
} else { } else {

View File

@ -12,7 +12,7 @@
* *
* @see https://docs.woocommerce.com/document/template-structure/ * @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates\Emails * @package WooCommerce\Templates\Emails
* @version 3.9.0 * @version 5.6.0
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
@ -42,7 +42,12 @@ $shipping = $order->get_formatted_shipping_address();
<td style="text-align:<?php echo esc_attr( $text_align ); ?>; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; padding:0;" valign="top" width="50%"> <td style="text-align:<?php echo esc_attr( $text_align ); ?>; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; padding:0;" valign="top" width="50%">
<h2><?php esc_html_e( 'Shipping address', 'woocommerce' ); ?></h2> <h2><?php esc_html_e( 'Shipping address', 'woocommerce' ); ?></h2>
<address class="address"><?php echo wp_kses_post( $shipping ); ?></address> <address class="address">
<?php echo wp_kses_post( $shipping ); ?>
<?php if ( $order->get_shipping_phone() ) : ?>
<br /><?php echo wc_make_phone_clickable( $order->get_shipping_phone() ); ?>
<?php endif; ?>
</address>
</td> </td>
<?php endif; ?> <?php endif; ?>
</tr> </tr>

View File

@ -12,20 +12,20 @@
* *
* @see https://docs.woocommerce.com/document/template-structure/ * @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates\Emails\Plain * @package WooCommerce\Templates\Emails\Plain
* @version 3.4.0 * @version 5.6.0
*/ */
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
echo "\n" . esc_html( wc_strtoupper( esc_html__( 'Billing address', 'woocommerce' ) ) ) . "\n\n"; echo "\n" . esc_html( wc_strtoupper( esc_html__( 'Billing address', 'woocommerce' ) ) ) . "\n\n";
echo preg_replace( '#<br\s*/?>#i', "\n", $order->get_formatted_billing_address() ) . "\n"; // WPCS: XSS ok. echo preg_replace( '#<br\s*/?>#i', "\n", $order->get_formatted_billing_address() ) . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( $order->get_billing_phone() ) { if ( $order->get_billing_phone() ) {
echo $order->get_billing_phone() . "\n"; // WPCS: XSS ok. echo $order->get_billing_phone() . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} }
if ( $order->get_billing_email() ) { if ( $order->get_billing_email() ) {
echo $order->get_billing_email() . "\n"; // WPCS: XSS ok. echo $order->get_billing_email() . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} }
if ( ! wc_ship_to_billing_address_only() && $order->needs_shipping_address() ) { if ( ! wc_ship_to_billing_address_only() && $order->needs_shipping_address() ) {
@ -33,6 +33,10 @@ if ( ! wc_ship_to_billing_address_only() && $order->needs_shipping_address() ) {
if ( $shipping ) { if ( $shipping ) {
echo "\n" . esc_html( wc_strtoupper( esc_html__( 'Shipping address', 'woocommerce' ) ) ) . "\n\n"; echo "\n" . esc_html( wc_strtoupper( esc_html__( 'Shipping address', 'woocommerce' ) ) ) . "\n\n";
echo preg_replace( '#<br\s*/?>#i', "\n", $shipping ) . "\n"; // WPCS: XSS ok. echo preg_replace( '#<br\s*/?>#i', "\n", $shipping ) . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ( $order->get_shipping_phone() ) {
echo $order->get_shipping_phone() . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
} }
} }

View File

@ -12,7 +12,7 @@
* *
* @see https://docs.woocommerce.com/document/template-structure/ * @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates * @package WooCommerce\Templates
* @version 3.4.4 * @version 5.6.0
*/ */
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
@ -50,6 +50,10 @@ $show_shipping = ! wc_ship_to_billing_address_only() && $order->needs_shipping_a
<h2 class="woocommerce-column__title"><?php esc_html_e( 'Shipping address', 'woocommerce' ); ?></h2> <h2 class="woocommerce-column__title"><?php esc_html_e( 'Shipping address', 'woocommerce' ); ?></h2>
<address> <address>
<?php echo wp_kses_post( $order->get_formatted_shipping_address( esc_html__( 'N/A', 'woocommerce' ) ) ); ?> <?php echo wp_kses_post( $order->get_formatted_shipping_address( esc_html__( 'N/A', 'woocommerce' ) ) ); ?>
<?php if ( $order->get_shipping_phone() ) : ?>
<p class="woocommerce-customer-details--phone"><?php echo esc_html( $order->get_shipping_phone() ); ?></p>
<?php endif; ?>
</address> </address>
</div><!-- /.col-2 --> </div><!-- /.col-2 -->

View File

@ -31,7 +31,7 @@ class FakeQueue implements \WC_Queue_Interface {
* *
* @var array * @var array
*/ */
public $methods_called = array(); private $methods_called = array();
// phpcs:disable Squiz.Commenting.FunctionComment.Missing // phpcs:disable Squiz.Commenting.FunctionComment.Missing
@ -72,7 +72,13 @@ class FakeQueue implements \WC_Queue_Interface {
} }
public function search( $args = array(), $return_format = OBJECT ) { public function search( $args = array(), $return_format = OBJECT ) {
// TODO: Implement search() method. $result = array();
foreach ( $this->methods_called as $method_called ) {
if ( $method_called['args'] === $args['args'] && $method_called['hook'] === $args['hook'] ) {
$result[] = $method_called;
}
}
return $result;
} }
// phpcs:enable Squiz.Commenting.FunctionComment.Missing // phpcs:enable Squiz.Commenting.FunctionComment.Missing
@ -94,4 +100,21 @@ class FakeQueue implements \WC_Queue_Interface {
$this->methods_called[] = array_merge( $value, $extra_args ); $this->methods_called[] = array_merge( $value, $extra_args );
} }
/**
* Get the data about the methods called so far.
*
* @return array The current value of $methods_called.
*/
public function get_methods_called() {
return $this->methods_called;
}
/**
* Clears the collection of the methods called so far.
*/
public function clear_methods_called() {
$this->methods_called = array();
}
} }

View File

@ -7,7 +7,7 @@ import {
const config = require('config'); const config = require('config');
const { HTTPClientFactory } = require('@woocommerce/api'); const { HTTPClientFactory } = require('@woocommerce/api');
const { addConsoleSuppression } = require( '@woocommerce/e2e-environment' ); const { addConsoleSuppression, updateReadyPageStatus } = require( '@woocommerce/e2e-environment' );
// @todo: remove this once https://github.com/woocommerce/woocommerce-admin/issues/6992 has been addressed // @todo: remove this once https://github.com/woocommerce/woocommerce-admin/issues/6992 has been addressed
addConsoleSuppression( 'woocommerce_shared_settings' ); addConsoleSuppression( 'woocommerce_shared_settings' );
@ -32,13 +32,15 @@ async function trashExistingPosts() {
for (const post of posts) { for (const post of posts) {
await client.delete(`${wpPostsEndpoint}/${post.id}`); await client.delete(`${wpPostsEndpoint}/${post.id}`);
} }
} }
// Before every test suite run, delete all content created by the test. This ensures // Before every test suite run, delete all content created by the test. This ensures
// other posts/comments/etc. aren't dirtying tests and tests don't depend on // other posts/comments/etc. aren't dirtying tests and tests don't depend on
// each other's side-effects. // each other's side-effects.
beforeAll(async () => { beforeAll(async () => {
// Update the ready page to prevent concurrent test runs
await updateReadyPageStatus('draft');
await trashExistingPosts(); await trashExistingPosts();
await withRestApi.deleteAllProducts(); await withRestApi.deleteAllProducts();
await withRestApi.deleteAllCoupons(); await withRestApi.deleteAllCoupons();
@ -50,6 +52,9 @@ beforeAll(async () => {
// Clear browser cookies and cache using DevTools. // Clear browser cookies and cache using DevTools.
// This is to ensure that each test ends with no user logged in. // This is to ensure that each test ends with no user logged in.
afterAll(async () => { afterAll(async () => {
// Reset the ready page to published to allow future test runs
await updateReadyPageStatus('publish');
const client = await page.target().createCDPSession(); const client = await page.target().createCDPSession();
await client.send('Network.clearBrowserCookies'); await client.send('Network.clearBrowserCookies');
await client.send('Network.clearBrowserCache'); await client.send('Network.clearBrowserCache');

View File

@ -28,6 +28,7 @@ const runProductSettingsTest = () => {
await expect(page).toSelect('#woocommerce_file_download_method', 'Redirect only (Insecure)'); await expect(page).toSelect('#woocommerce_file_download_method', 'Redirect only (Insecure)');
await setCheckbox('#woocommerce_downloads_require_login'); await setCheckbox('#woocommerce_downloads_require_login');
await setCheckbox('#woocommerce_downloads_grant_access_after_payment'); await setCheckbox('#woocommerce_downloads_grant_access_after_payment');
await setCheckbox('#woocommerce_downloads_redirect_fallback_allowed');
await settingsPageSaveChanges(); await settingsPageSaveChanges();
// Verify that settings have been saved // Verify that settings have been saved
@ -36,11 +37,14 @@ const runProductSettingsTest = () => {
expect(page).toMatchElement('#woocommerce_file_download_method', {text: 'Redirect only (Insecure)'}), expect(page).toMatchElement('#woocommerce_file_download_method', {text: 'Redirect only (Insecure)'}),
verifyCheckboxIsSet('#woocommerce_downloads_require_login'), verifyCheckboxIsSet('#woocommerce_downloads_require_login'),
verifyCheckboxIsSet('#woocommerce_downloads_grant_access_after_payment'), verifyCheckboxIsSet('#woocommerce_downloads_grant_access_after_payment'),
verifyCheckboxIsSet('#woocommerce_downloads_redirect_fallback_allowed'),
]); ]);
await page.reload();
await expect(page).toSelect('#woocommerce_file_download_method', 'Force downloads'); await expect(page).toSelect('#woocommerce_file_download_method', 'Force downloads');
await unsetCheckbox('#woocommerce_downloads_require_login'); await unsetCheckbox('#woocommerce_downloads_require_login');
await unsetCheckbox('#woocommerce_downloads_grant_access_after_payment'); await unsetCheckbox('#woocommerce_downloads_grant_access_after_payment');
await unsetCheckbox('#woocommerce_downloads_redirect_fallback_allowed');
await settingsPageSaveChanges(); await settingsPageSaveChanges();
// Verify that settings have been saved // Verify that settings have been saved
@ -49,6 +53,7 @@ const runProductSettingsTest = () => {
expect(page).toMatchElement('#woocommerce_file_download_method', {text: 'Force downloads'}), expect(page).toMatchElement('#woocommerce_file_download_method', {text: 'Force downloads'}),
verifyCheckboxIsUnset('#woocommerce_downloads_require_login'), verifyCheckboxIsUnset('#woocommerce_downloads_require_login'),
verifyCheckboxIsUnset('#woocommerce_downloads_grant_access_after_payment'), verifyCheckboxIsUnset('#woocommerce_downloads_grant_access_after_payment'),
verifyCheckboxIsUnset('#woocommerce_downloads_redirect_fallback_allowed'),
]); ]);
}); });
}); });

View File

@ -13,6 +13,7 @@ wp plugin install https://github.com/WP-API/Basic-Auth/archive/master.zip --acti
wp plugin install wp-mail-logging --activate wp plugin install wp-mail-logging --activate
echo "Updating to WordPress Nightly Point Release" echo "Updating to WordPress Nightly Point Release"
wp core update https://wordpress.org/nightly-builds/wordpress-latest.zip
wp plugin install wordpress-beta-tester --activate echo "Updating the database"
wp core check-update wp core update-db

View File

@ -1,5 +1,12 @@
# Unreleased # Unreleased
- `updateReadyPageStatus` utility to update the status of the ready page
- Added plugin upload functionality util that provides a method to pull a plugin zip from a remote location
- `getRemotePluginZip( fileUrl )` to get the remote zip. Returns the filepath of the zip location.
- Added plugin zip utility functions:
- `checkNestedZip( zipFilePath, savePath )` checks a plugin zip file for any nested zip files. If one is found, it is extracted. Returns the path where the zip file is located.
- `downloadZip( fileUrl, downloadPath )` downloads a plugin zip file from a remote location to the provided path.
# 0.2.2 # 0.2.2
## Added ## Added

View File

@ -180,6 +180,22 @@ To implement the Slackbot in your CI:
To test your setup, create a pull request that triggers an error in the E2E tests. To test your setup, create a pull request that triggers an error in the E2E tests.
## Plugin functions
Depending on the testing scenario, you may wish to upload a plugin that can be used in the tests from a remote location.
To download a zip file, you can use `getRemotePluginZip( fileUrl )` to get the remote zip. This returns the filepath of the location where the zip file was downloaded to. For example, you could use this method to download the latest nightly version of WooCommerce:
```javascript
const pluginZipUrl = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip';
await getRemotePluginZip( pluginZipUrl );
```
The above method also makes use of the following utility methods which can also be used:
- `checkNestedZip( zipFilePath, savePath )` used to check a plugin zip file for any nested zip files. If one is found, it is extracted. Returns the path where the zip file is located.
- `downloadZip( fileUrl, downloadPath )` can be used to directly download a plugin zip file from a remote location to the provided path.
## Additional information ## Additional information
Refer to [`tests/e2e/core-tests`](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/core-tests) for some test examples, and [`tests/e2e`](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e) for general information on e2e tests. Refer to [`tests/e2e/core-tests`](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e/core-tests) for some test examples, and [`tests/e2e`](https://github.com/woocommerce/woocommerce/tree/trunk/tests/e2e) for general information on e2e tests.

View File

@ -1,4 +1,4 @@
FROM wordpress:cli-php7.4 FROM wordpress:cli-2.5.0
USER root USER root

View File

@ -24,12 +24,15 @@
"@automattic/puppeteer-utils": "github:Automattic/puppeteer-utils#0f3ec50", "@automattic/puppeteer-utils": "github:Automattic/puppeteer-utils#0f3ec50",
"@jest/test-sequencer": "^25.5.4", "@jest/test-sequencer": "^25.5.4",
"@slack/web-api": "^6.1.0", "@slack/web-api": "^6.1.0",
"@woocommerce/api": "^0.2.0",
"@wordpress/e2e-test-utils": "^4.15.0", "@wordpress/e2e-test-utils": "^4.15.0",
"@wordpress/jest-preset-default": "^6.4.0", "@wordpress/jest-preset-default": "^6.4.0",
"app-root-path": "^3.0.0", "app-root-path": "^3.0.0",
"jest": "^25.1.0", "jest": "^25.1.0",
"jest-each": "25.5.0", "jest-each": "25.5.0",
"jest-puppeteer": "^4.4.0" "jest-puppeteer": "^4.4.0",
"request": "^2.88.2",
"node-stream-zip": "^1.13.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "7.12.0", "@babel/cli": "7.12.0",

85
tests/e2e/env/utils/get-plugin-zip.js vendored Normal file
View File

@ -0,0 +1,85 @@
const path = require( 'path' );
const getAppRoot = require( './app-root' );
const fs = require('fs');
const mkdirp = require( 'mkdirp' );
const request = require('request');
const StreamZip = require('node-stream-zip');
/**
* Upload a plugin zip from a remote location, such as a GitHub URL or other hosted location.
*
* @param {string} fileUrl The URL where the zip file is located.
* @returns {string} The path where the zip file is located.
*/
const getRemotePluginZip = async ( fileUrl ) => {
const appPath = getAppRoot();
const savePath = path.resolve( appPath, 'tests/e2e/plugins' );
mkdirp.sync( savePath );
// Pull the filename from the end of the URL
let fileName = fileUrl.split('/').pop();
let filePath = path.join( savePath, fileName );
// First, download the zip file
await downloadZip( fileUrl, filePath );
// Check for a nested zip and update the filepath
filePath = await checkNestedZip( filePath, savePath );
return filePath;
};
/**
* Check the zip file for any nested zips. If one is found, extract it.
*
* @param {string} zipFilePath The location of the zip file.
* @param {string} savePath The location where to save a nested zip if found.
* @returns {string} The path where the zip file is located.
*/
const checkNestedZip = async ( zipFilePath, savePath ) => {
const zip = new StreamZip.async( { file: zipFilePath } );
const entries = await zip.entries();
for (const entry of Object.values( entries )) {
if ( entry.name.match( /.zip/ )) {
await zip.extract( null, savePath );
await zip.close();
return path.join( savePath, entry.name );
}
}
return zipFilePath;
}
/**
* Download the zip file from a remote location.
*
* @param {string} fileUrl The URL where the zip file is located.
* @param {string} downloadPath The location where to download the zip to.
* @returns {Promise<void>}
*/
const downloadZip = async ( fileUrl, downloadPath ) => {
const options = {
url: fileUrl,
method: 'GET',
encoding: null,
};
// Wrap in a promise to make the request async
return new Promise( function( resolve, reject ) {
request.get(options, function( err, resp, body ) {
if ( err ) {
reject( err );
} else {
resolve( body );
}
})
.pipe( fs.createWriteStream( downloadPath ) );
});
};
module.exports = {
getRemotePluginZip,
checkNestedZip,
downloadZip,
};

View File

@ -1,7 +1,9 @@
const getAppRoot = require( './app-root' ); const getAppRoot = require( './app-root' );
const { getAppName, getAppBase } = require( './app-name' ); const { getAppName, getAppBase } = require( './app-name' );
const { getTestConfig, getAdminConfig } = require( './test-config' ); const { getTestConfig, getAdminConfig } = require( './test-config' );
const { getRemotePluginZip } = require('./get-plugin-zip');
const takeScreenshotFor = require( './take-screenshot' ); const takeScreenshotFor = require( './take-screenshot' );
const updateReadyPageStatus = require('./update-ready-page');
const consoleUtils = require( './filter-console' ); const consoleUtils = require( './filter-console' );
module.exports = { module.exports = {
@ -10,6 +12,8 @@ module.exports = {
getAppName, getAppName,
getTestConfig, getTestConfig,
getAdminConfig, getAdminConfig,
getRemotePluginZip,
takeScreenshotFor, takeScreenshotFor,
updateReadyPageStatus,
...consoleUtils, ...consoleUtils,
}; };

View File

@ -0,0 +1,36 @@
const { getTestConfig } = require( './test-config' );
const { HTTPClientFactory } = require('@woocommerce/api');
/**
* Uses the WordPress API to update the Ready page's status.
*
* @param {string} status | Status to update the page to. One of: publish, future, draft, pending, private
*/
const updateReadyPageStatus = async ( status ) => {
const testConfig = getTestConfig();
const apiUrl = testConfig.url;
const wpPagesEndpoint = '/wp/v2/pages';
const adminUsername = testConfig.users.admin.username ? testConfig.users.admin.username : 'admin';
const adminPassword = testConfig.users.admin.password ? testConfig.users.admin.password : 'password';
const client = HTTPClientFactory.build(apiUrl)
.withBasicAuth(adminUsername, adminPassword)
.create();
// As the default status filter in the API is `publish`, we need to
// filter based on the supplied status otherwise no results are returned.
let statusFilter = 'publish';
if ( 'publish' === status ) {
// The page will be in a draft state, so we need to filter on that status
statusFilter = 'draft';
}
const getPostsResponse = await client.get(`${wpPagesEndpoint}?search=ready&status=${statusFilter}`);
if ( getPostsResponse.data && getPostsResponse.data.length > 0) {
const pageId = getPostsResponse.data[0].id;
// Update the page to the new status
await client.post(`${wpPagesEndpoint}/${pageId}`, { 'status': status });
}
}
module.exports = updateReadyPageStatus;

View File

@ -0,0 +1,37 @@
/**
* Internal dependencies
*/
const { merchant, utils } = require( '@woocommerce/e2e-utils' );
const { getRemotePluginZip } = require( '@woocommerce/e2e-environment' );
/**
* External dependencies
*/
const {
it,
beforeAll,
} = require( '@jest/globals' );
const { UPDATE_WC } = process.env;
const nightlyZip = 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip';
const pluginName = 'WooCommerce';
let pluginPath;
utils.describeIf( UPDATE_WC )( 'WooCommerce plugin can be uploaded and activated', () => {
beforeAll( async () => {
pluginPath = await getRemotePluginZip( nightlyZip );
await merchant.login();
});
afterAll( async () => {
await merchant.logout();
});
it( 'can upload and activate the WooCommerce plugin', async () => {
await merchant.uploadAndActivatePlugin( pluginPath, pluginName );
});
});

View File

@ -4,6 +4,15 @@
- Factories for variable product, variation, and grouped product - Factories for variable product, variation, and grouped product
- New function to create orders by batch using the orders API - New function to create orders by batch using the orders API
- Added new constant for WordPress update page `WP_ADMIN_WP_UPDATES`
- Added new merchant flow for `openWordPressUpdatesPage()`
- Added new merchant flows:
- `openWordPressUpdatesPage()`
- `installAllUpdates()`
- Added `getSlug()` helper to return the slug string for a provided string
- Added `describeIf()` to conditionally run a test suite
- Added `itIf()` to conditionally run a test case.
- Added merchant workflows around plugins: `uploadAndActivatePlugin()`, `activatePlugin()`, `deactivatePlugin()`, `deletePlugin()`
# 0.1.5 # 0.1.5

View File

@ -44,6 +44,7 @@ This package provides support for enabling retries in tests:
- `WP_ADMIN_LOGIN` - WordPress login - `WP_ADMIN_LOGIN` - WordPress login
- `WP_ADMIN_DASHBOARD` - WordPress dashboard - `WP_ADMIN_DASHBOARD` - WordPress dashboard
- `WP_ADMIN_WP_UPDATES` - WordPress updates
- `WP_ADMIN_PLUGINS` - Plugin list - `WP_ADMIN_PLUGINS` - Plugin list
- `WP_ADMIN_PERMALINK_SETTINGS` - Permalink settings - `WP_ADMIN_PERMALINK_SETTINGS` - Permalink settings
- `WP_ADMIN_ALL_USERS_VIEW` - WordPress user list - `WP_ADMIN_ALL_USERS_VIEW` - WordPress user list
@ -63,6 +64,7 @@ This package provides support for enabling retries in tests:
- `WP_ADMIN_WC_SETTINGS` - WooCommerce settings page root - `WP_ADMIN_WC_SETTINGS` - WooCommerce settings page root
- `WP_ADMIN_NEW_SHIPPING_ZONE` - WooCommerce new shipping zone - `WP_ADMIN_NEW_SHIPPING_ZONE` - WooCommerce new shipping zone
- `WP_ADMIN_WC_EXTENSIONS` - WooCommerce extensions page - `WP_ADMIN_WC_EXTENSIONS` - WooCommerce extensions page
- `WP_ADMIN_PLUGIN_INSTALL` - WordPress plugin install page
#### Front end #### Front end
@ -102,6 +104,11 @@ This package provides support for enabling retries in tests:
| `openAllUsersView` | | Open the All Users page | | `openAllUsersView` | | Open the All Users page |
| `openImportProducts` | | Open the Import Products page | | `openImportProducts` | | Open the Import Products page |
| `openExtensions` | | Go to WooCommerce -> Extensions | | `openExtensions` | | Go to WooCommerce -> Extensions |
| `openWordPressUpdatesPage` | | Go to Dashboard -> Updates |
| `installAllUpdates` | | Install all pending updates on Dashboard -> Updates|
| `updateWordPress` | | Install pending WordPress updates on Dashboard -> Updates|
| `updatePlugins` | | Install all pending plugin updates on Dashboard -> Updates|
| `updateThemes` | | Install all pending theme updates on Dashboard -> Updates|
### Shopper `shopper` ### Shopper `shopper`

View File

@ -10,9 +10,12 @@ const baseUrl = config.get( 'url' );
*/ */
export const WP_ADMIN_LOGIN = baseUrl + 'wp-login.php'; export const WP_ADMIN_LOGIN = baseUrl + 'wp-login.php';
export const WP_ADMIN_DASHBOARD = baseUrl + 'wp-admin/'; export const WP_ADMIN_DASHBOARD = baseUrl + 'wp-admin/';
export const WP_ADMIN_WP_UPDATES = WP_ADMIN_DASHBOARD + 'update-core.php';
export const WP_ADMIN_PLUGINS = WP_ADMIN_DASHBOARD + 'plugins.php'; export const WP_ADMIN_PLUGINS = WP_ADMIN_DASHBOARD + 'plugins.php';
export const WP_ADMIN_PLUGIN_INSTALL = WP_ADMIN_DASHBOARD + 'plugin-install.php';
export const WP_ADMIN_PERMALINK_SETTINGS = WP_ADMIN_DASHBOARD + 'options-permalink.php'; export const WP_ADMIN_PERMALINK_SETTINGS = WP_ADMIN_DASHBOARD + 'options-permalink.php';
export const WP_ADMIN_ALL_USERS_VIEW = WP_ADMIN_DASHBOARD + 'users.php'; export const WP_ADMIN_ALL_USERS_VIEW = WP_ADMIN_DASHBOARD + 'users.php';
/** /**
* WooCommerce core post type pages. * WooCommerce core post type pages.
* @type {string} * @type {string}
@ -26,6 +29,7 @@ export const WP_ADMIN_NEW_ORDER = WP_ADMIN_NEW_POST_TYPE + 'shop_order';
export const WP_ADMIN_ALL_PRODUCTS_VIEW = WP_ADMIN_POST_TYPE + 'product'; export const WP_ADMIN_ALL_PRODUCTS_VIEW = WP_ADMIN_POST_TYPE + 'product';
export const WP_ADMIN_NEW_PRODUCT = WP_ADMIN_NEW_POST_TYPE + 'product'; export const WP_ADMIN_NEW_PRODUCT = WP_ADMIN_NEW_POST_TYPE + 'product';
export const WP_ADMIN_IMPORT_PRODUCTS = WP_ADMIN_ALL_PRODUCTS_VIEW + '&page=product_importer'; export const WP_ADMIN_IMPORT_PRODUCTS = WP_ADMIN_ALL_PRODUCTS_VIEW + '&page=product_importer';
/** /**
* WooCommerce settings pages. * WooCommerce settings pages.
* @type {string} * @type {string}
@ -37,6 +41,7 @@ export const WP_ADMIN_ANALYTICS_PAGES = WP_ADMIN_WC_HOME + '&path=%2Fanalytics%2
export const WP_ADMIN_WC_SETTINGS = WP_ADMIN_PLUGIN_PAGE + 'wc-settings&tab='; export const WP_ADMIN_WC_SETTINGS = WP_ADMIN_PLUGIN_PAGE + 'wc-settings&tab=';
export const WP_ADMIN_NEW_SHIPPING_ZONE = WP_ADMIN_WC_SETTINGS + 'shipping&zone_id=new'; export const WP_ADMIN_NEW_SHIPPING_ZONE = WP_ADMIN_WC_SETTINGS + 'shipping&zone_id=new';
export const WP_ADMIN_WC_EXTENSIONS = WP_ADMIN_PLUGIN_PAGE + 'wc-addons'; export const WP_ADMIN_WC_EXTENSIONS = WP_ADMIN_PLUGIN_PAGE + 'wc-addons';
/** /**
* Shop pages. * Shop pages.
* @type {string} * @type {string}
@ -46,6 +51,7 @@ export const SHOP_PRODUCT_PAGE = baseUrl + '?p=';
export const SHOP_CART_PAGE = baseUrl + 'cart'; export const SHOP_CART_PAGE = baseUrl + 'cart';
export const SHOP_CHECKOUT_PAGE = baseUrl + 'checkout/'; export const SHOP_CHECKOUT_PAGE = baseUrl + 'checkout/';
export const SHOP_MY_ACCOUNT_PAGE = baseUrl + 'my-account/'; export const SHOP_MY_ACCOUNT_PAGE = baseUrl + 'my-account/';
/** /**
* Customer account pages. * Customer account pages.
* @type {string} * @type {string}

View File

@ -6,6 +6,7 @@ const flowExpressions = require( './expressions' );
const merchant = require( './merchant' ); const merchant = require( './merchant' );
const shopper = require( './shopper' ); const shopper = require( './shopper' );
const { withRestApi } = require( './with-rest-api' ); const { withRestApi } = require( './with-rest-api' );
const utils = require( './utils' );
module.exports = { module.exports = {
...flowConstants, ...flowConstants,
@ -13,4 +14,5 @@ module.exports = {
merchant, merchant,
shopper, shopper,
withRestApi, withRestApi,
utils,
}; };

View File

@ -6,7 +6,7 @@ const config = require( 'config' );
/** /**
* Internal dependencies * Internal dependencies
*/ */
const { clearAndFillInput } = require( '../page-utils' ); const { clearAndFillInput, setCheckbox } = require( '../page-utils' );
const { const {
WP_ADMIN_ALL_ORDERS_VIEW, WP_ADMIN_ALL_ORDERS_VIEW,
WP_ADMIN_ALL_PRODUCTS_VIEW, WP_ADMIN_ALL_PRODUCTS_VIEW,
@ -25,9 +25,13 @@ const {
WP_ADMIN_ANALYTICS_PAGES, WP_ADMIN_ANALYTICS_PAGES,
WP_ADMIN_ALL_USERS_VIEW, WP_ADMIN_ALL_USERS_VIEW,
WP_ADMIN_IMPORT_PRODUCTS, WP_ADMIN_IMPORT_PRODUCTS,
WP_ADMIN_PLUGIN_INSTALL,
WP_ADMIN_WP_UPDATES,
IS_RETEST_MODE, IS_RETEST_MODE,
} = require( './constants' ); } = require( './constants' );
const { getSlug } = require('./utils');
const baseUrl = config.get( 'url' ); const baseUrl = config.get( 'url' );
const WP_ADMIN_SINGLE_CPT_VIEW = ( postId ) => baseUrl + `wp-admin/post.php?post=${ postId }&action=edit`; const WP_ADMIN_SINGLE_CPT_VIEW = ( postId ) => baseUrl + `wp-admin/post.php?post=${ postId }&action=edit`;
@ -215,6 +219,161 @@ const merchant = {
waitUntil: 'networkidle0', waitUntil: 'networkidle0',
} ); } );
}, },
/**
* Opens the WordPress updates page at Dashboard > Updates.
*/
openWordPressUpdatesPage: async () => {
await page.goto( WP_ADMIN_WP_UPDATES, {
waitUntil: 'networkidle0',
} );
},
/**
* Installs all pending updates on the Dashboard > Updates page, including WordPress, plugins, and themes.
*/
installAllUpdates: async () => {
await merchant.updateWordPress();
await merchant.updatePlugins();
await merchant.updateThemes();
},
/**
* Updates WordPress if there are any updates available.
*/
updateWordPress: async () => {
await merchant.openWordPressUpdatesPage();
if ( null !== await page.$( 'form[action="update-core.php?action=do-core-upgrade"][name="upgrade"]' ) ) {
await Promise.all([
expect( page ).toClick( 'input.button-primary' ),
// The WordPress update can take some time, so setting a longer timeout here
page.waitForNavigation( { waitUntil: 'networkidle0', timeout: 1000000 } ),
]);
}
},
/**
* Updates all installed plugins if there are updates available.
*/
updatePlugins: async () => {
await merchant.openWordPressUpdatesPage();
if ( null !== await page.$( 'form[action="update-core.php?action=do-plugin-upgrade"][name="upgrade-plugins"]' ) ) {
await setCheckbox( '#plugins-select-all' );
await Promise.all([
expect( page ).toClick( '#upgrade-plugins' ),
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
]);
}
},
/**
* Updates all installed themes if there are updates available.
*/
updateThemes: async () => {
await merchant.openWordPressUpdatesPage();
if ( null !== await page.$( 'form[action="update-core.php?action=do-theme-upgrade"][name="upgrade-themes"]' )) {
await setCheckbox( '#themes-select-all' );
await Promise.all([
expect( page ).toClick( '#upgrade-themes' ),
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
]);
}
},
/* Uploads and activates a plugin located at the provided file path. This will also deactivate and delete the plugin if it exists.
*
* @param {string} pluginFilePath The location of the plugin zip file to upload.
* @param {string} pluginName The name of the plugin. For example, `WooCommerce`.
*/
uploadAndActivatePlugin: async ( pluginFilePath, pluginName ) => {
await merchant.openPlugins();
// Deactivate and delete the plugin if it exists
let pluginSlug = getSlug( pluginName );
if ( await page.$( `a#deactivate-${pluginSlug}` ) !== null ) {
await merchant.deactivatePlugin( pluginName, true );
}
// Open the plugin install page
await page.goto( WP_ADMIN_PLUGIN_INSTALL, {
waitUntil: 'networkidle0',
} );
// Upload the plugin zip
await page.click( 'a.upload-view-toggle' );
await expect( page ).toMatchElement(
'p.install-help',
{
text: 'If you have a plugin in a .zip format, you may install or update it by uploading it here.'
}
);
const uploader = await page.$( 'input[type=file]' );
await uploader.uploadFile( pluginFilePath );
// Manually update the button to `enabled` so we can submit the file
await page.evaluate(() => {
document.getElementById( 'install-plugin-submit' ).disabled = false;
});
// Click to upload the file
await page.click( '#install-plugin-submit' );
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
// Click to activate the plugin
await page.click( '.button-primary' );
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
},
/**
* Activate a given plugin by the plugin's name.
*
* @param {string} pluginName The name of the plugin to activate. For example, `WooCommerce`.
*/
activatePlugin: async ( pluginName ) => {
let pluginSlug = getSlug( pluginName );
await expect( page ).toClick( `a#activate-${pluginSlug}` );
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
},
/**
* Deactivate a plugin by the plugin's name with the option to delete the plugin as well.
*
* @param {string} pluginName The name of the plugin to deactivate. For example, `WooCommerce`.
* @param {Boolean} deletePlugin Pass in `true` to delete the plugin. Defaults to `false`.
*/
deactivatePlugin: async ( pluginName, deletePlugin = false ) => {
let pluginSlug = getSlug( pluginName );
await expect( page ).toClick( `a#deactivate-${pluginSlug}` );
await page.waitForNavigation( { waitUntil: 'networkidle0' } );
if ( deletePlugin ) {
await merchant.deletePlugin( pluginName );
}
},
/**
* Delete a plugin by the plugin's name.
*
* @param {string} pluginName The name of the plugin to delete. For example, `WooCommerce`.
*/
deletePlugin: async ( pluginName ) => {
let pluginSlug = getSlug( pluginName );
await expect( page ).toClick( `a#delete-${pluginSlug}` );
// Wait for Ajax calls to finish
await page.waitForResponse( response => response.status() === 200 );
},
}; };
module.exports = merchant; module.exports = merchant;

View File

@ -167,8 +167,10 @@ const shopper = {
}, },
searchForProduct: async ( prouductName ) => { searchForProduct: async ( prouductName ) => {
await expect(page).toFill('.search-field', prouductName); const searchFieldSelector = 'input.wp-block-search__input';
await expect(page).toClick('.search-submit'); await page.waitForSelector(searchFieldSelector, { timeout: 100000 });
await expect(page).toFill(searchFieldSelector, prouductName);
await expect(page).toClick('.wp-block-search__button');
await page.waitForSelector('h2.entry-title'); await page.waitForSelector('h2.entry-title');
await expect(page).toMatchElement('h2.entry-title', {text: prouductName}); await expect(page).toMatchElement('h2.entry-title', {text: prouductName});
await expect(page).toClick('h2.entry-title', {text: prouductName}); await expect(page).toClick('h2.entry-title', {text: prouductName});

View File

@ -0,0 +1,33 @@
/**
* Take a string name and generate the slug for it.
* Example: 'My plugin' => 'my-plugin'
*
* Sourced from: https://gist.github.com/spyesx/561b1d65d4afb595f295
**/
export const getSlug = ( text ) => {
text = text.trim().toLowerCase();
// remove accents, swap ñ for n, etc
const from = 'åàáãäâèéëêìíïîòóöôùúüûñç·/_,:;';
const to = 'aaaaaaeeeeiiiioooouuuunc------';
for (let i = 0, l = from.length; i < l; i++) {
text = text.replace(new RegExp(from.charAt(i), "g"), to.charAt(i));
}
return text
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-') // collapse dashes
.replace(/^-+/, '') // trim - from start of text
.replace(/-+$/, '') // trim - from end of text
.replace(/-/g, '-');
};
// Conditionally determine whether or not to skip a test suite
export const describeIf = ( condition ) =>
condition ? describe : describe.skip;
// Conditionally determine whether or not to skip a test case
export const itIf = ( condition ) =>
condition ? it : it.skip;

View File

@ -177,12 +177,12 @@ class WC_Unit_Tests_Bootstrap {
define( 'WC_REMOVE_ALL_DATA', true ); define( 'WC_REMOVE_ALL_DATA', true );
include $this->plugin_dir . '/uninstall.php'; include $this->plugin_dir . '/uninstall.php';
WC_Install::install();
// Initialize the WC API extensions. // Initialize the WC API extensions.
\Automattic\WooCommerce\Admin\Install::create_tables(); \Automattic\WooCommerce\Admin\Install::create_tables();
\Automattic\WooCommerce\Admin\Install::create_events(); \Automattic\WooCommerce\Admin\Install::create_events();
WC_Install::install();
// Reload capabilities after install, see https://core.trac.wordpress.org/ticket/28374. // Reload capabilities after install, see https://core.trac.wordpress.org/ticket/28374.
if ( version_compare( $GLOBALS['wp_version'], '4.7', '<' ) ) { if ( version_compare( $GLOBALS['wp_version'], '4.7', '<' ) ) {
$GLOBALS['wp_roles']->reinit(); $GLOBALS['wp_roles']->reinit();

View File

@ -1231,6 +1231,16 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$this->assertEquals( $set_to, $object->get_shipping_country() ); $this->assertEquals( $set_to, $object->get_shipping_country() );
} }
/**
* Test: get_shipping_phone
*/
public function test_get_shipping_phone() {
$object = new WC_Order();
$set_to = '123456678';
$object->set_shipping_phone( $set_to );
$this->assertEquals( $set_to, $object->get_shipping_phone() );
}
/** /**
* Test get_formatted_billing_address and has_billing_address. * Test get_formatted_billing_address and has_billing_address.
* *
@ -1421,6 +1431,7 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
'state' => 'Boulder', 'state' => 'Boulder',
'postcode' => '00001', 'postcode' => '00001',
'country' => 'US', 'country' => 'US',
'phone' => '',
); );
$object->set_billing_first_name( 'Fred' ); $object->set_billing_first_name( 'Fred' );

View File

@ -6,6 +6,12 @@
* @since 3.0.0 * @since 3.0.0
*/ */
/**
* Tests for the Customers REST API.
*
* @package WooCommerce\Tests\API
* @extends WC_REST_Unit_Test_Case
*/
class Customers_V2 extends WC_REST_Unit_Test_Case { class Customers_V2 extends WC_REST_Unit_Test_Case {
/** /**
@ -87,6 +93,7 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '94110', 'postcode' => '94110',
'country' => 'US', 'country' => 'US',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'orders_count' => 0, 'orders_count' => 0,
@ -177,6 +184,7 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'state' => '', 'state' => '',
'postcode' => '', 'postcode' => '',
'country' => '', 'country' => '',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'meta_data' => array(), 'meta_data' => array(),
@ -187,7 +195,7 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
$data $data
); );
// Test extra data // Test extra data.
$request = new WP_REST_Request( 'POST', '/wc/v2/customers' ); $request = new WP_REST_Request( 'POST', '/wc/v2/customers' );
$request->set_body_params( $request->set_body_params(
array( array(
@ -245,6 +253,7 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '', 'postcode' => '',
'country' => 'US', 'country' => 'US',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'meta_data' => array(), 'meta_data' => array(),
@ -255,7 +264,7 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
$data $data
); );
// Test without required field // Test without required field.
$request = new WP_REST_Request( 'POST', '/wc/v2/customers' ); $request = new WP_REST_Request( 'POST', '/wc/v2/customers' );
$request->set_body_params( $request->set_body_params(
array( array(
@ -332,6 +341,7 @@ class Customers_V2 extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '94110', 'postcode' => '94110',
'country' => 'US', 'country' => 'US',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'meta_data' => array(), 'meta_data' => array(),

View File

@ -63,6 +63,12 @@ class Payment_Gateways_V2 extends WC_REST_Unit_Test_Case {
'description' => false, 'description' => false,
) )
), ),
'needs_setup' => false,
'post_install_scripts' => array(),
'settings_url' => 'http://example.org/wp-admin/admin.php?page=wc-settings&tab=checkout&section=cheque',
'connection_url' => '',
'setup_help_text' => '',
'required_settings_keys' => array(),
'_links' => array( '_links' => array(
'self' => array( 'self' => array(
array( array(
@ -119,6 +125,12 @@ class Payment_Gateways_V2 extends WC_REST_Unit_Test_Case {
'description' => false, 'description' => false,
) )
), ),
'needs_setup' => false,
'post_install_scripts' => array(),
'settings_url' => 'http://example.org/wp-admin/admin.php?page=wc-settings&tab=checkout&section=paypal',
'connection_url' => null,
'setup_help_text' => null,
'required_settings_keys' => array(),
), ),
$paypal $paypal
); );

View File

@ -54,7 +54,7 @@ class Customers extends WC_REST_Unit_Test_Case {
); );
$response = $this->server->dispatch( $request ); $response = $this->server->dispatch( $request );
$customers = $response->get_data(); $customers = $response->get_data();
$date_created = get_date_from_gmt( date( 'Y-m-d H:i:s', strtotime( $customer_1->get_date_created() ) ) ); $date_created = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', strtotime( $customer_1->get_date_created() ) ) );
$this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 2, count( $customers ) ); $this->assertEquals( 2, count( $customers ) );
@ -94,6 +94,7 @@ class Customers extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '94110', 'postcode' => '94110',
'country' => 'US', 'country' => 'US',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'avatar_url' => $customer_1->get_avatar_url(), 'avatar_url' => $customer_1->get_avatar_url(),
@ -125,7 +126,7 @@ class Customers extends WC_REST_Unit_Test_Case {
); );
$response = $this->server->dispatch( $request ); $response = $this->server->dispatch( $request );
$customers = $response->get_data(); $customers = $response->get_data();
$date_created = get_date_from_gmt( date( 'Y-m-d H:i:s', strtotime( $customer_3->get_date_created() ) ) ); $date_created = get_date_from_gmt( gmdate( 'Y-m-d H:i:s', strtotime( $customer_3->get_date_created() ) ) );
$this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 200, $response->get_status() );
@ -164,6 +165,7 @@ class Customers extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '94110', 'postcode' => '94110',
'country' => 'US', 'country' => 'US',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'avatar_url' => $customer_3->get_avatar_url(), 'avatar_url' => $customer_3->get_avatar_url(),
@ -253,6 +255,7 @@ class Customers extends WC_REST_Unit_Test_Case {
'state' => '', 'state' => '',
'postcode' => '', 'postcode' => '',
'country' => '', 'country' => '',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'meta_data' => array(), 'meta_data' => array(),
@ -319,6 +322,7 @@ class Customers extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '', 'postcode' => '',
'country' => 'US', 'country' => 'US',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'meta_data' => array(), 'meta_data' => array(),
@ -404,6 +408,7 @@ class Customers extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '94110', 'postcode' => '94110',
'country' => 'US', 'country' => 'US',
'phone' => '',
), ),
'is_paying_customer' => false, 'is_paying_customer' => false,
'meta_data' => array(), 'meta_data' => array(),
@ -630,5 +635,6 @@ class Customers extends WC_REST_Unit_Test_Case {
$this->assertArrayHasKey( 'state', $properties['shipping']['properties'] ); $this->assertArrayHasKey( 'state', $properties['shipping']['properties'] );
$this->assertArrayHasKey( 'postcode', $properties['shipping']['properties'] ); $this->assertArrayHasKey( 'postcode', $properties['shipping']['properties'] );
$this->assertArrayHasKey( 'country', $properties['shipping']['properties'] ); $this->assertArrayHasKey( 'country', $properties['shipping']['properties'] );
$this->assertArrayHasKey( 'phone', $properties['shipping']['properties'] );
} }
} }

View File

@ -295,6 +295,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '94103', 'postcode' => '94103',
'country' => 'US', 'country' => 'US',
'phone' => '(555) 555-5555',
), ),
'line_items' => array( 'line_items' => array(
array( array(
@ -352,6 +353,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
$this->assertEquals( $order->get_shipping_state(), $data['shipping']['state'] ); $this->assertEquals( $order->get_shipping_state(), $data['shipping']['state'] );
$this->assertEquals( $order->get_shipping_postcode(), $data['shipping']['postcode'] ); $this->assertEquals( $order->get_shipping_postcode(), $data['shipping']['postcode'] );
$this->assertEquals( $order->get_shipping_country(), $data['shipping']['country'] ); $this->assertEquals( $order->get_shipping_country(), $data['shipping']['country'] );
$this->assertEquals( $order->get_shipping_phone(), $data['shipping']['phone'] );
$this->assertEquals( 1, count( $data['line_items'] ) ); $this->assertEquals( 1, count( $data['line_items'] ) );
$this->assertEquals( 1, count( $data['shipping_lines'] ) ); $this->assertEquals( 1, count( $data['shipping_lines'] ) );
@ -417,6 +419,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '94103', 'postcode' => '94103',
'country' => 'US', 'country' => 'US',
'phone' => '(555) 555-5555',
), ),
'line_items' => array( 'line_items' => array(
array( array(
@ -493,6 +496,7 @@ class WC_Tests_API_Orders extends WC_REST_Unit_Test_Case {
'state' => 'CA', 'state' => 'CA',
'postcode' => '94103', 'postcode' => '94103',
'country' => 'US', 'country' => 'US',
'phone' => '(555) 555-5555',
), ),
'line_items' => array( 'line_items' => array(
array( array(

View File

@ -66,6 +66,12 @@ class Payment_Gateways extends WC_REST_Unit_Test_Case {
'description' => false, 'description' => false,
) )
), ),
'needs_setup' => false,
'post_install_scripts' => array(),
'settings_url' => 'http://example.org/wp-admin/admin.php?page=wc-settings&tab=checkout&section=cheque',
'connection_url' => '',
'setup_help_text' => '',
'required_settings_keys' => array(),
'_links' => array( '_links' => array(
'self' => array( 'self' => array(
array( array(
@ -126,6 +132,12 @@ class Payment_Gateways extends WC_REST_Unit_Test_Case {
'description' => false, 'description' => false,
) )
), ),
'needs_setup' => false,
'post_install_scripts' => array(),
'settings_url' => 'http://example.org/wp-admin/admin.php?page=wc-settings&tab=checkout&section=paypal',
'connection_url' => null,
'setup_help_text' => null,
'required_settings_keys' => array(),
), ),
$paypal $paypal
); );

View File

@ -63,6 +63,7 @@ class WC_Tests_Install extends WC_Unit_Test_Case {
delete_option( 'woocommerce_cart_page_id' ); delete_option( 'woocommerce_cart_page_id' );
delete_option( 'woocommerce_checkout_page_id' ); delete_option( 'woocommerce_checkout_page_id' );
delete_option( 'woocommerce_myaccount_page_id' ); delete_option( 'woocommerce_myaccount_page_id' );
delete_option( 'woocommerce_refund_returns_page_id' );
WC_Install::create_pages(); WC_Install::create_pages();
@ -70,18 +71,21 @@ class WC_Tests_Install extends WC_Unit_Test_Case {
$this->assertGreaterThan( 0, get_option( 'woocommerce_cart_page_id' ) ); $this->assertGreaterThan( 0, get_option( 'woocommerce_cart_page_id' ) );
$this->assertGreaterThan( 0, get_option( 'woocommerce_checkout_page_id' ) ); $this->assertGreaterThan( 0, get_option( 'woocommerce_checkout_page_id' ) );
$this->assertGreaterThan( 0, get_option( 'woocommerce_myaccount_page_id' ) ); $this->assertGreaterThan( 0, get_option( 'woocommerce_myaccount_page_id' ) );
$this->assertGreaterThan( 0, get_option( 'woocommerce_refund_returns_page_id' ) );
// Delete pages. // Delete pages.
wp_delete_post( get_option( 'woocommerce_shop_page_id' ), true ); wp_delete_post( get_option( 'woocommerce_shop_page_id' ), true );
wp_delete_post( get_option( 'woocommerce_cart_page_id' ), true ); wp_delete_post( get_option( 'woocommerce_cart_page_id' ), true );
wp_delete_post( get_option( 'woocommerce_checkout_page_id' ), true ); wp_delete_post( get_option( 'woocommerce_checkout_page_id' ), true );
wp_delete_post( get_option( 'woocommerce_myaccount_page_id' ), true ); wp_delete_post( get_option( 'woocommerce_myaccount_page_id' ), true );
wp_delete_post( get_option( 'woocommerce_refund_returns_page_id' ), true );
// Clear options. // Clear options.
delete_option( 'woocommerce_shop_page_id' ); delete_option( 'woocommerce_shop_page_id' );
delete_option( 'woocommerce_cart_page_id' ); delete_option( 'woocommerce_cart_page_id' );
delete_option( 'woocommerce_checkout_page_id' ); delete_option( 'woocommerce_checkout_page_id' );
delete_option( 'woocommerce_myaccount_page_id' ); delete_option( 'woocommerce_myaccount_page_id' );
delete_option( 'woocommerce_refund_returns_page_id' );
WC_Install::create_pages(); WC_Install::create_pages();
@ -89,6 +93,7 @@ class WC_Tests_Install extends WC_Unit_Test_Case {
$this->assertGreaterThan( 0, get_option( 'woocommerce_cart_page_id' ) ); $this->assertGreaterThan( 0, get_option( 'woocommerce_cart_page_id' ) );
$this->assertGreaterThan( 0, get_option( 'woocommerce_checkout_page_id' ) ); $this->assertGreaterThan( 0, get_option( 'woocommerce_checkout_page_id' ) );
$this->assertGreaterThan( 0, get_option( 'woocommerce_myaccount_page_id' ) ); $this->assertGreaterThan( 0, get_option( 'woocommerce_myaccount_page_id' ) );
$this->assertGreaterThan( 0, get_option( 'woocommerce_refund_returns_page_id' ) );
} }
/** /**

View File

@ -29,6 +29,7 @@ class WC_Settings_Advanced_Test extends WC_Settings_Unit_Test_Case {
'webhooks', 'webhooks',
'legacy_api', 'legacy_api',
'woocommerce_com', 'woocommerce_com',
'features',
); );
$this->assertEquals( $expected, $section_names ); $this->assertEquals( $expected, $section_names );

View File

@ -136,6 +136,7 @@ class WC_Settings_Products_Test extends WC_Settings_Unit_Test_Case {
$expected = array( $expected = array(
'digital_download_options' => array( 'title', 'sectionend' ), 'digital_download_options' => array( 'title', 'sectionend' ),
'woocommerce_file_download_method' => 'select', 'woocommerce_file_download_method' => 'select',
'woocommerce_downloads_redirect_fallback_allowed' => 'checkbox',
'woocommerce_downloads_require_login' => 'checkbox', 'woocommerce_downloads_require_login' => 'checkbox',
'woocommerce_downloads_grant_access_after_payment' => 'checkbox', 'woocommerce_downloads_grant_access_after_payment' => 'checkbox',
'woocommerce_downloads_add_hash_to_filename' => 'checkbox', 'woocommerce_downloads_add_hash_to_filename' => 'checkbox',

View File

@ -51,14 +51,14 @@ class DataRegeneratorTest extends \WC_Unit_Test_Case {
$this->lookup_data_store = new class() extends LookupDataStore { $this->lookup_data_store = new class() extends LookupDataStore {
public $passed_products = array(); public $passed_products = array();
public function update_data_for_product( $product ) { public function create_data_for_product( $product ) {
$this->passed_products[] = $product; $this->passed_products[] = $product;
} }
}; };
// phpcs:enable Squiz.Commenting // phpcs:enable Squiz.Commenting
// This is needed to prevent the hook to act on the already registered LookupDataStore class. // This is needed to prevent the hook to act on the already registered LookupDataStore class.
remove_all_actions( 'woocommerce_run_product_attribute_lookup_update_callback' ); remove_all_actions( 'woocommerce_run_product_attribute_lookup_regeneration_callback' );
$container = wc_get_container(); $container = wc_get_container();
$container->reset_all_resolved(); $container->reset_all_resolved();
@ -128,10 +128,10 @@ class DataRegeneratorTest extends \WC_Unit_Test_Case {
'method' => 'schedule_single', 'method' => 'schedule_single',
'args' => array(), 'args' => array(),
'timestamp' => 1001, 'timestamp' => 1001,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback', 'hook' => 'woocommerce_run_product_attribute_lookup_regeneration_callback',
'group' => 'woocommerce-db-updates', 'group' => 'woocommerce-db-updates',
); );
$actual_enqueued = current( $this->queue->methods_called ); $actual_enqueued = current( $this->queue->get_methods_called() );
$this->assertEquals( sort( $expected_enqueued ), sort( $actual_enqueued ) ); $this->assertEquals( sort( $expected_enqueued ), sort( $actual_enqueued ) );
} }
@ -158,7 +158,7 @@ class DataRegeneratorTest extends \WC_Unit_Test_Case {
$this->assertFalse( get_option( 'woocommerce_attribute_lookup__last_product_id_to_process' ) ); $this->assertFalse( get_option( 'woocommerce_attribute_lookup__last_product_id_to_process' ) );
$this->assertFalse( get_option( 'woocommerce_attribute_lookup__last_products_page_processed' ) ); $this->assertFalse( get_option( 'woocommerce_attribute_lookup__last_products_page_processed' ) );
$this->assertEquals( 'no', get_option( 'woocommerce_attribute_lookup__enabled' ) ); $this->assertEquals( 'no', get_option( 'woocommerce_attribute_lookup__enabled' ) );
$this->assertEmpty( $this->queue->methods_called ); $this->assertEmpty( $this->queue->get_methods_called() );
} }
/** /**
@ -184,11 +184,11 @@ class DataRegeneratorTest extends \WC_Unit_Test_Case {
); );
$this->sut->initiate_regeneration(); $this->sut->initiate_regeneration();
$this->queue->methods_called = array(); $this->queue->clear_methods_called();
update_option( 'woocommerce_attribute_lookup__last_products_page_processed', 7 ); update_option( 'woocommerce_attribute_lookup__last_products_page_processed', 7 );
do_action( 'woocommerce_run_product_attribute_lookup_update_callback' ); do_action( 'woocommerce_run_product_attribute_lookup_regeneration_callback' );
$this->assertEquals( array( 1, 2, 3 ), $this->lookup_data_store->passed_products ); $this->assertEquals( array( 1, 2, 3 ), $this->lookup_data_store->passed_products );
$this->assertEquals( array( 8 ), $requested_products_pages ); $this->assertEquals( array( 8 ), $requested_products_pages );
@ -198,10 +198,10 @@ class DataRegeneratorTest extends \WC_Unit_Test_Case {
'method' => 'schedule_single', 'method' => 'schedule_single',
'args' => array(), 'args' => array(),
'timestamp' => 1001, 'timestamp' => 1001,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback', 'hook' => 'woocommerce_run_product_attribute_lookup_regeneration_callback',
'group' => 'woocommerce-db-updates', 'group' => 'woocommerce-db-updates',
); );
$actual_enqueued = current( $this->queue->methods_called ); $actual_enqueued = current( $this->queue->get_methods_called() );
$this->assertEquals( sort( $expected_enqueued ), sort( $actual_enqueued ) ); $this->assertEquals( sort( $expected_enqueued ), sort( $actual_enqueued ) );
} }
@ -231,14 +231,14 @@ class DataRegeneratorTest extends \WC_Unit_Test_Case {
); );
$this->sut->initiate_regeneration(); $this->sut->initiate_regeneration();
$this->queue->methods_called = array(); $this->queue->clear_methods_called();
do_action( 'woocommerce_run_product_attribute_lookup_update_callback' ); do_action( 'woocommerce_run_product_attribute_lookup_regeneration_callback' );
$this->assertEquals( $product_ids, $this->lookup_data_store->passed_products ); $this->assertEquals( $product_ids, $this->lookup_data_store->passed_products );
$this->assertFalse( get_option( 'woocommerce_attribute_lookup__last_product_id_to_process' ) ); $this->assertFalse( get_option( 'woocommerce_attribute_lookup__last_product_id_to_process' ) );
$this->assertFalse( get_option( 'woocommerce_attribute_lookup__last_products_page_processed' ) ); $this->assertFalse( get_option( 'woocommerce_attribute_lookup__last_products_page_processed' ) );
$this->assertEquals( 'no', get_option( 'woocommerce_attribute_lookup__enabled' ) ); $this->assertEquals( 'no', get_option( 'woocommerce_attribute_lookup__enabled' ) );
$this->assertEmpty( $this->queue->methods_called ); $this->assertEmpty( $this->queue->get_methods_called() );
} }
} }

View File

@ -74,7 +74,7 @@ class FiltererTest extends \WC_Unit_Test_Case {
$child->delete( true ); $child->delete( true );
} else { } else {
$child->set_parent_id( 0 ); $child->set_parent_id( 0 );
$child->save(); $this->save( $child );
} }
} }
@ -86,6 +86,27 @@ class FiltererTest extends \WC_Unit_Test_Case {
\WC_Query::reset_chosen_attributes(); \WC_Query::reset_chosen_attributes();
} }
/**
* Save a product and delete any lookup table data that may have been automatically inserted
* (for the purposes of unit testing we want to insert this data manually)
*
* @param \WC_Product $product The product to save and delete lookup table data for.
*/
private function save( \WC_Product $product ) {
global $wpdb;
$product->save();
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->prefix}wc_product_attributes_lookup WHERE product_id = %d",
$product->get_id()
)
);
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
}
/** /**
* Core function to create a product. * Core function to create a product.
* *
@ -165,7 +186,7 @@ class FiltererTest extends \WC_Unit_Test_Case {
$product->set_stock_status( $in_stock ? 'instock' : 'outofstock' ); $product->set_stock_status( $in_stock ? 'instock' : 'outofstock' );
$product->save(); $this->save( $product );
if ( empty( $attribute_terms_by_name ) ) { if ( empty( $attribute_terms_by_name ) ) {
return $product; return $product;
@ -234,7 +255,7 @@ class FiltererTest extends \WC_Unit_Test_Case {
) )
); );
$product->save(); $this->save( $product );
$product_id = $product->get_id(); $product_id = $product->get_id();
@ -259,7 +280,7 @@ class FiltererTest extends \WC_Unit_Test_Case {
} }
$variation->set_attributes( $attributes ); $variation->set_attributes( $attributes );
$variation->set_stock_status( $variation_data['in_stock'] ? 'instock' : 'outofstock' ); $variation->set_stock_status( $variation_data['in_stock'] ? 'instock' : 'outofstock' );
$variation->save(); $this->save( $variation );
$variation_ids[] = $variation->get_id(); $variation_ids[] = $variation->get_id();
} }

View File

@ -9,6 +9,7 @@ use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore; use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
use Automattic\WooCommerce\Testing\Tools\FakeQueue; use Automattic\WooCommerce\Testing\Tools\FakeQueue;
use Automattic\WooCommerce\Utilities\ArrayUtil; use Automattic\WooCommerce\Utilities\ArrayUtil;
use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\FunctionsMockerHack;
/** /**
* Tests for the LookupDataStore class. * Tests for the LookupDataStore class.
@ -23,45 +24,62 @@ class LookupDataStoreTest extends \WC_Unit_Test_Case {
*/ */
private $sut; private $sut;
/**
* The lookup table name.
*
* @var string
*/
private $lookup_table_name;
/**
* Runs after all the tests in the class.
*/
public static function tearDownAfterClass() {
parent::tearDownAfterClass();
wc_get_container()->get( DataRegenerator::class )->delete_all_attributes_lookup_data();
}
/** /**
* Runs before each test. * Runs before each test.
*/ */
public function setUp() { public function setUp() {
global $wpdb; global $wpdb;
$this->lookup_table_name = $wpdb->prefix . 'wc_product_attributes_lookup';
$this->sut = new LookupDataStore(); $this->sut = new LookupDataStore();
// Initiating regeneration with a fake queue will just create the lookup table in the database. $this->reset_legacy_proxy_mocks();
add_filter( $this->register_legacy_proxy_class_mocks(
'woocommerce_queue_class', array(
function() { \WC_Queue::class => new FakeQueue(),
return FakeQueue::class; )
}
); );
// Initiating regeneration with a fake queue will just create the lookup table in the database.
$this->get_instance_of( DataRegenerator::class )->initiate_regeneration(); $this->get_instance_of( DataRegenerator::class )->initiate_regeneration();
} }
/** /**
* @testdox `test_update_data_for_product` throws an exception if a variation is passed. * @testdox `create_data_for_product` throws an exception if a variation is passed.
*/ */
public function test_update_data_for_product_throws_if_variation_is_passed() { public function test_create_data_for_product_throws_if_variation_is_passed() {
$product = new \WC_Product_Variation(); $product = new \WC_Product_Variation();
$this->expectException( \Exception::class ); $this->expectException( \Exception::class );
$this->expectExceptionMessage( "LookupDataStore::update_data_for_product can't be called for variations." ); $this->expectExceptionMessage( "LookupDataStore::create_data_for_product can't be called for variations." );
$this->sut->update_data_for_product( $product ); $this->sut->create_data_for_product( $product );
} }
/** /**
* @testdox `test_update_data_for_product` creates the appropriate entries for simple products, skipping custom product attributes. * @testdox `create_data_for_product` creates the appropriate entries for simple products, skipping custom product attributes.
* *
* @testWith [true] * @testWith [true]
* [false] * [false]
* *
* @param bool $in_stock 'true' if the product is supposed to be in stock. * @param bool $in_stock 'true' if the product is supposed to be in stock.
*/ */
public function test_update_data_for_simple_product( $in_stock ) { public function test_create_data_for_simple_product( $in_stock ) {
$product = new \WC_Product_Simple(); $product = new \WC_Product_Simple();
$product->set_id( 10 ); $product->set_id( 10 );
$this->set_product_attributes( $this->set_product_attributes(
@ -90,7 +108,7 @@ class LookupDataStoreTest extends \WC_Unit_Test_Case {
$expected_in_stock = 0; $expected_in_stock = 0;
} }
$this->sut->update_data_for_product( $product ); $this->sut->create_data_for_product( $product );
$expected = array( $expected = array(
array( array(
@ -133,7 +151,7 @@ class LookupDataStoreTest extends \WC_Unit_Test_Case {
} }
/** /**
* @testdox `test_update_data_for_product` creates the appropriate entries for variable products. * @testdox `create_data_for_product` creates the appropriate entries for variable products.
*/ */
public function test_update_data_for_variable_product() { public function test_update_data_for_variable_product() {
$products = array(); $products = array();
@ -239,7 +257,7 @@ class LookupDataStoreTest extends \WC_Unit_Test_Case {
$products[1001] = $variation_1; $products[1001] = $variation_1;
$products[1002] = $variation_2; $products[1002] = $variation_2;
$this->sut->update_data_for_product( $product ); $this->sut->create_data_for_product( $product );
$expected = array( $expected = array(
// Main product: one entry for each of the regular attribute values, // Main product: one entry for each of the regular attribute values,
@ -331,6 +349,748 @@ class LookupDataStoreTest extends \WC_Unit_Test_Case {
$this->assertEquals( sort( $expected ), sort( $actual ) ); $this->assertEquals( sort( $expected ), sort( $actual ) );
} }
/**
* @testdox Deleting a simple product schedules deletion of lookup table entries when the "direct updates" option is off.
*
* @testWith ["wp_trash_post"]
* ["delete_post"]
* ["delete_method_in_product"]
* ["force_delete_method_in_product"]
*
* @param string $deletion_mechanism The mechanism used for deletion, one of: 'wp_trash_post', 'delete_post', 'delete_method_in_product', 'force_delete_method_in_product'.
*/
public function test_deleting_simple_product_schedules_deletion( $deletion_mechanism ) {
$this->set_direct_update_option( false );
$product = new \WC_Product_Simple();
$product_id = 10;
$product->set_id( $product_id );
$this->save( $product );
$this->register_legacy_proxy_function_mocks(
array(
'get_post_type' => function( $id ) use ( $product ) {
if ( $id === $product->get_id() || $id === $product ) {
return 'product';
} else {
return get_post_type( $id );
}
},
'time' => function() {
return 100;
},
'current_user_can' => function( $capability, ...$args ) {
if ( 'delete_posts' === $capability ) {
return true;
} else {
return current_user_can( $capability, $args );
}
},
)
);
$this->delete_product( $product, $deletion_mechanism );
$queue_calls = WC()->get_instance_of( \WC_Queue::class )->get_methods_called();
$this->assertEquals( 1, count( $queue_calls ) );
$expected = array(
'method' => 'schedule_single',
'args' =>
array(
$product_id,
LookupDataStore::ACTION_DELETE,
),
'group' => 'woocommerce-db-updates',
'timestamp' => 101,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback',
);
$this->assertEquals( $expected, $queue_calls[0] );
}
/**
* Delete a product or variation.
*
* @param \WC_Product $product The product to delete.
* @param string $deletion_mechanism The mechanism used for deletion, one of: 'wp_trash_post', 'delete_post', 'delete_method_in_product', 'force_delete_method_in_product'.
*/
private function delete_product( \WC_Product $product, string $deletion_mechanism ) {
// We can't use the 'wp_trash_post' and 'delete_post' functions directly
// because these invoke 'get_post', which fails because tests runs within an
// uncommitted database transaction. Being WP core functions they can't be mocked or hacked.
// So instead, we trigger the actions that the tested functionality captures.
switch ( $deletion_mechanism ) {
case 'wp_trash_post':
do_action( 'wp_trash_post', $product );
break;
case 'delete_post':
do_action( 'delete_post', $product->get_id() );
break;
case 'delete_method_in_product':
$product->delete( false );
break;
case 'force_delete_method_in_product':
$product->delete( true );
break;
}
}
/**
* @testdox Deleting a variable product schedules deletion of lookup table entries when the "direct updates" option is off.
*
* @testWith ["wp_trash_post"]
* ["delete_post"]
* ["delete_method_in_product"]
* ["force_delete_method_in_product"]
*
* @param string $deletion_mechanism The mechanism used for deletion, one of: 'wp_trash_post', 'delete_post', 'delete_method_in_product', 'force_delete_method_in_product'.
*/
public function test_deleting_variable_product_schedules_deletion( $deletion_mechanism ) {
$this->set_direct_update_option( false );
$product = new \WC_Product_Variable();
$product->set_id( 1000 );
$variation = new \WC_Product_Variation();
$variation->set_id( 1001 );
$product->set_children( array( 1001 ) );
$this->save( $product );
$product_id = $product->get_id();
$this->register_legacy_proxy_function_mocks(
array(
'get_post_type' => function( $id ) use ( $product, $variation ) {
if ( $id === $product->get_id() || $id === $product ) {
return 'product';
} elseif ( $id === $variation->get_id() || $id === $variation ) {
return 'product_variation';
} else {
return get_post_type( $id );
}
},
'time' => function() {
return 100;
},
'current_user_can' => function( $capability, ...$args ) {
if ( 'delete_posts' === $capability ) {
return true;
} else {
return current_user_can( $capability, $args );
}
},
)
);
$this->delete_product( $product, $deletion_mechanism );
$queue_calls = WC()->get_instance_of( \WC_Queue::class )->get_methods_called();
$this->assertEquals( 1, count( $queue_calls ) );
$expected = array(
'method' => 'schedule_single',
'args' =>
array(
$product_id,
LookupDataStore::ACTION_DELETE,
),
'group' => 'woocommerce-db-updates',
'timestamp' => 101,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback',
);
$this->assertEquals( $expected, $queue_calls[0] );
}
/**
* @testdox Deleting a variation schedules deletion of lookup table entries when the "direct updates" option is off.
*
* @testWith ["wp_trash_post"]
* ["delete_post"]
* ["delete_method_in_product"]
* ["force_delete_method_in_product"]
*
* @param string $deletion_mechanism The mechanism used for deletion, one of: 'wp_trash_post', 'delete_post', 'delete_method_in_product', 'force_delete_method_in_product'.
*/
public function test_deleting_variation_schedules_deletion( $deletion_mechanism ) {
$this->set_direct_update_option( false );
$product = new \WC_Product_Variable();
$product->set_id( 1000 );
$variation = new \WC_Product_Variation();
$variation->set_id( 1001 );
$product->set_children( array( 1001 ) );
$this->save( $product );
$variation_id = $product->get_id();
$this->register_legacy_proxy_function_mocks(
array(
'get_post_type' => function( $id ) use ( $product, $variation ) {
if ( $id === $product->get_id() || $id === $product ) {
return 'product';
} elseif ( $id === $variation->get_id() || $id === $variation ) {
return 'product_variation';
} else {
return get_post_type( $id );
}
},
'time' => function() {
return 100;
},
'current_user_can' => function( $capability, ...$args ) {
if ( 'delete_posts' === $capability ) {
return true;
} else {
return current_user_can( $capability, $args );
}
},
)
);
$this->delete_product( $product, $deletion_mechanism );
$queue_calls = WC()->get_instance_of( \WC_Queue::class )->get_methods_called();
$this->assertEquals( 1, count( $queue_calls ) );
$expected = array(
'method' => 'schedule_single',
'args' =>
array(
$variation_id,
LookupDataStore::ACTION_DELETE,
),
'group' => 'woocommerce-db-updates',
'timestamp' => 101,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback',
);
$this->assertEquals( $expected, $queue_calls[0] );
}
/**
* @testdox 'on_product_deleted' doesn't schedule duplicate deletions (for the same product).
*/
public function test_no_duplicate_deletions_are_scheduled() {
$this->set_direct_update_option( false );
$this->register_legacy_proxy_function_mocks(
array(
'time' => function() {
return 100;
},
)
);
$this->sut->on_product_deleted( 1 );
$this->sut->on_product_deleted( 1 );
$this->sut->on_product_deleted( 2 );
$queue_calls = WC()->get_instance_of( \WC_Queue::class )->get_methods_called();
$this->assertEquals( 2, count( $queue_calls ) );
$expected = array(
array(
'method' => 'schedule_single',
'args' =>
array(
1,
LookupDataStore::ACTION_DELETE,
),
'group' => 'woocommerce-db-updates',
'timestamp' => 101,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback',
),
array(
'method' => 'schedule_single',
'args' =>
array(
2,
LookupDataStore::ACTION_DELETE,
),
'group' => 'woocommerce-db-updates',
'timestamp' => 101,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback',
),
);
$this->assertEquals( $expected, $queue_calls );
}
/**
* @testdox 'on_product_deleted' deletes the data for a variation when the "direct updates" option is on.
*/
public function test_direct_deletion_of_variation() {
global $wpdb;
$this->set_direct_update_option( true );
$variation = new \WC_Product_Variation();
$variation->set_id( 2 );
$this->save( $variation );
$this->insert_lookup_table_data( 1, 1, 'pa_foo', 10, false, true );
$this->insert_lookup_table_data( 2, 1, 'pa_bar', 20, true, true );
$this->sut->on_product_deleted( $variation );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$rows = $wpdb->get_results( 'SELECT DISTINCT product_id FROM ' . $this->lookup_table_name, ARRAY_N );
$this->assertEquals( array( 1 ), $rows[0] );
}
/**
* @testdox 'on_product_deleted' deletes the data for a product and its variations when the "direct updates" option is on.
*/
public function test_direct_deletion_of_product() {
global $wpdb;
$this->set_direct_update_option( true );
$product = new \WC_Product();
$product->set_id( 1 );
$this->save( $product );
$this->insert_lookup_table_data( 1, 1, 'pa_foo', 10, false, true );
$this->insert_lookup_table_data( 2, 1, 'pa_bar', 20, true, true );
$this->insert_lookup_table_data( 3, 3, 'pa_foo', 10, false, true );
$this->sut->on_product_deleted( $product );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$rows = $wpdb->get_results( 'SELECT DISTINCT product_id FROM ' . $this->lookup_table_name, ARRAY_N );
$this->assertEquals( array( 3 ), $rows[0] );
}
/**
* @testdox Changing the stock status of a simple product schedules update of lookup table entries when the "direct updates" option is off.
*
* @testWith ["instock", "outofstock"]
* ["outofstock", "instock"]
*
* @param string $old_status Original status of the product.
* @param string $new_status New status of the product.
*/
public function test_changing_simple_product_stock_schedules_update( string $old_status, string $new_status ) {
$this->set_direct_update_option( false );
$product = new \WC_Product_Simple();
$product_id = 10;
$product->set_id( $product_id );
$product->set_stock_status( $old_status );
$this->save( $product );
$this->register_legacy_proxy_function_mocks(
array(
'time' => function() {
return 100;
},
)
);
$product->set_stock_status( $new_status );
$product->save();
$queue_calls = WC()->get_instance_of( \WC_Queue::class )->get_methods_called();
$this->assertEquals( 1, count( $queue_calls ) );
$expected = array(
'method' => 'schedule_single',
'args' =>
array(
$product_id,
LookupDataStore::ACTION_UPDATE_STOCK,
),
'group' => 'woocommerce-db-updates',
'timestamp' => 101,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback',
);
$this->assertEquals( $expected, $queue_calls[0] );
}
/**
* @testdox Changing the stock status of a variable product or a variation schedules update of lookup table entries when the "direct updates" option is off.
*
* @testWith ["instock", "outofstock", true]
* ["outofstock", "instock", true]
* ["instock", "outofstock", false]
* ["outofstock", "instock", false]
*
* @param string $old_status Original status of the product.
* @param string $new_status New status of the product.
* @param bool $change_variation_stock True if the stock of the variation changes.
*/
public function test_changing_variable_product_or_variation_stock_schedules_update( string $old_status, string $new_status, bool $change_variation_stock ) {
$this->set_direct_update_option( false );
$product = new \WC_Product_Variable();
$product_id = 1000;
$product->set_id( $product_id );
$variation = new \WC_Product_Variation();
$variation_id = 1001;
$variation->set_id( $variation_id );
$variation->set_stock_status( $old_status );
$variation->save();
$product->set_children( array( 1001 ) );
$product->set_stock_status( $old_status );
$this->save( $product );
$this->register_legacy_proxy_function_mocks(
array(
'time' => function () {
return 100;
},
)
);
if ( $change_variation_stock ) {
$variation->set_stock_status( $new_status );
$variation->save();
} else {
$product->set_stock_status( $new_status );
$product->save();
}
$queue_calls = WC()->get_instance_of( \WC_Queue::class )->get_methods_called();
$this->assertEquals( 1, count( $queue_calls ) );
$expected = array(
'method' => 'schedule_single',
'args' =>
array(
$change_variation_stock ? $variation_id : $product_id,
LookupDataStore::ACTION_UPDATE_STOCK,
),
'group' => 'woocommerce-db-updates',
'timestamp' => 101,
'hook' => 'woocommerce_run_product_attribute_lookup_update_callback',
);
$this->assertEquals( $expected, $queue_calls[0] );
}
/**
* Data provider for on_product_changed tests with direct update option set.
*
* @return array[]
*/
public function data_provider_for_test_on_product_changed_with_direct_updates() {
return array(
array(
null,
'creation',
),
array(
array( 'attributes' => array() ),
'creation',
),
array(
array( 'stock_quantity' => 1 ),
'update',
),
array(
array( 'stock_status' => 'instock' ),
'update',
),
array(
array( 'manage_stock' => true ),
'update',
),
array(
array( 'catalog_visibility' => 'visible' ),
'creation',
),
array(
array( 'catalog_visibility' => 'catalog' ),
'creation',
),
array(
array( 'catalog_visibility' => 'search' ),
'deletion',
),
array(
array( 'catalog_visibility' => 'hidden' ),
'deletion',
),
array(
array( 'foo' => 'bar' ),
'none',
),
);
}
/**
* @testdox 'on_product_changed' creates, updates deletes the data for a simple product depending on the changeset when the "direct updates" option is on.
*
* @dataProvider data_provider_for_test_on_product_changed_with_direct_updates
*
* @param array $changeset The changeset to test.
* @param string $expected_action The expected performed action, one of 'none', 'creation', 'update' or 'deletion'.
*/
public function test_on_product_changed_for_simple_product_with_direct_updates( $changeset, $expected_action ) {
global $wpdb;
$this->set_direct_update_option( true );
$product = new \WC_Product_Simple();
$product->set_id( 2 );
$product->set_stock_status( 'instock' );
$this->set_product_attributes(
$product,
array(
'pa_bar' => array(
'id' => 100,
'options' => array( 20 ),
),
)
);
$this->register_legacy_proxy_function_mocks(
array(
'wc_get_product' => function( $id ) use ( $product ) {
if ( $id === $product->get_id() || $id === $product ) {
return $product;
} else {
return wc_get_product( $id );
}
},
)
);
$this->insert_lookup_table_data( 1, 1, 'pa_foo', 10, false, true );
if ( 'creation' !== $expected_action ) {
$this->insert_lookup_table_data( 2, 2, 'pa_bar', 20, false, false );
}
$this->sut->on_product_changed( $product, $changeset );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$rows = $wpdb->get_results( 'SELECT * FROM ' . $this->lookup_table_name, ARRAY_N );
$expected = array( array( '1', '1', 'pa_foo', '10', '0', '1' ) );
// Differences:
// Creation or update: the product is stored as having stock.
// None: the product remains stored as not having stock.
if ( 'creation' === $expected_action || 'update' === $expected_action ) {
$expected[] = array( '2', '2', 'pa_bar', '20', '0', '1' );
} elseif ( 'none' === $expected_action ) {
$expected[] = array( '2', '2', 'pa_bar', '20', '0', '0' );
}
$this->assertEquals( $expected, $rows );
}
/**
* @testdox 'on_product_changed' creates, updates deletes the data for a variable product and if needed its variations depending on the changeset when the "direct updates" option is on.
*
* @dataProvider data_provider_for_test_on_product_changed_with_direct_updates
*
* @param array $changeset The changeset to test.
* @param string $expected_action The expected performed action, one of 'none', 'creation', 'update' or 'deletion'.
*/
public function test_on_variable_product_changed_for_variable_product_with_direct_updates( $changeset, $expected_action ) {
global $wpdb;
$this->set_direct_update_option( true );
$product = new \WC_Product_Variable();
$product->set_id( 2 );
$this->set_product_attributes(
$product,
array(
'non-variation-attribute' => array(
'id' => 100,
'options' => array( 10 ),
),
'variation-attribute' => array(
'id' => 200,
'options' => array( 20 ),
'variation' => true,
),
)
);
$product->set_stock_status( 'instock' );
$variation = new \WC_Product_Variation();
$variation->set_id( 3 );
$variation->set_attributes(
array(
'variation-attribute' => 'term_20',
)
);
$variation->set_stock_status( 'instock' );
$variation->set_parent_id( 2 );
$product->set_children( array( 3 ) );
$this->register_legacy_proxy_function_mocks(
array(
'get_terms' => function( $args ) {
switch ( $args['taxonomy'] ) {
case 'non-variation-attribute':
return array(
10 => 'term_10',
);
case 'variation-attribute':
return array(
20 => 'term_20',
);
default:
throw new \Exception( "Unexpected call to 'get_terms'" );
}
},
'wc_get_product' => function( $id ) use ( $product, $variation ) {
if ( $id === $product->get_id() || $id === $product ) {
return $product;
} elseif ( $id === $variation->get_id() || $id === $variation ) {
return $variation;
} else {
return wc_get_product( $id );
}
},
)
);
$this->insert_lookup_table_data( 1, 1, 'pa_foo', 10, false, true );
if ( 'creation' !== $expected_action ) {
$this->insert_lookup_table_data( 2, 2, 'non-variation-attribute', 10, false, false );
$this->insert_lookup_table_data( 3, 2, 'variation-attribute', 20, true, false );
}
$this->sut->on_product_changed( $product, $changeset );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$rows = $wpdb->get_results( 'SELECT * FROM ' . $this->lookup_table_name, ARRAY_N );
$expected = array( array( '1', '1', 'pa_foo', '10', '0', '1' ) );
// Differences:
// Creation: both main product and variation are stored as having stock.
// Update: main product only is updated as having stock (variation is supposed to get a separate update).
// None: both main product and variation are still stored as not having stock.
if ( 'creation' === $expected_action ) {
$expected[] = array( '2', '2', 'non-variation-attribute', '10', '0', '1' );
$expected[] = array( '3', '2', 'variation-attribute', '20', '1', '1' );
} elseif ( 'update' === $expected_action ) {
$expected[] = array( '2', '2', 'non-variation-attribute', '10', '0', '1' );
$expected[] = array( '3', '2', 'variation-attribute', '20', '1', '0' );
} elseif ( 'none' === $expected_action ) {
$expected[] = array( '2', '2', 'non-variation-attribute', '10', '0', '0' );
$expected[] = array( '3', '2', 'variation-attribute', '20', '1', '0' );
}
$this->assertEquals( $expected, $rows );
}
/**
* @testdox 'on_product_changed' creates, updates deletes the data for a variation depending on the changeset when the "direct updates" option is on.
*
* @dataProvider data_provider_for_test_on_product_changed_with_direct_updates
*
* @param array $changeset The changeset to test.
* @param string $expected_action The expected performed action, one of 'none', 'creation', 'update' or 'deletion'.
*/
public function test_on_variation_changed_for_variable_product_with_direct_updates( $changeset, $expected_action ) {
global $wpdb;
$this->set_direct_update_option( true );
$product = new \WC_Product_Variable();
$product->set_id( 2 );
$this->set_product_attributes(
$product,
array(
'non-variation-attribute' => array(
'id' => 100,
'options' => array( 10 ),
),
'variation-attribute' => array(
'id' => 200,
'options' => array( 20 ),
'variation' => true,
),
)
);
$product->set_stock_status( 'instock' );
$variation = new \WC_Product_Variation();
$variation->set_id( 3 );
$variation->set_attributes(
array(
'variation-attribute' => 'term_20',
)
);
$variation->set_stock_status( 'instock' );
$variation->set_parent_id( 2 );
$product->set_children( array( 3 ) );
$this->register_legacy_proxy_function_mocks(
array(
'get_terms' => function( $args ) {
switch ( $args['taxonomy'] ) {
case 'non-variation-attribute':
return array(
10 => 'term_10',
);
case 'variation-attribute':
return array(
20 => 'term_20',
);
default:
throw new \Exception( "Unexpected call to 'get_terms'" );
}
},
'wc_get_product' => function( $id ) use ( $product, $variation ) {
if ( $id === $product->get_id() || $id === $product ) {
return $product;
} elseif ( $id === $variation->get_id() || $id === $variation ) {
return $variation;
} else {
return wc_get_product( $id );
}
},
)
);
$this->insert_lookup_table_data( 1, 1, 'pa_foo', 10, false, true );
if ( 'creation' !== $expected_action ) {
$this->insert_lookup_table_data( 3, 2, 'variation-attribute', 20, true, false );
}
$this->sut->on_product_changed( $variation, $changeset );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$rows = $wpdb->get_results( 'SELECT * FROM ' . $this->lookup_table_name, ARRAY_N );
$expected = array( array( '1', '1', 'pa_foo', '10', '0', '1' ) );
// Differences:
// Creation or update: the variation is stored as having stock.
// None: the variation is still stored as not having stock.
if ( 'creation' === $expected_action || 'update' === $expected_action ) {
$expected[] = array( '3', '2', 'variation-attribute', '20', '1', '1' );
} elseif ( 'none' === $expected_action ) {
$expected[] = array( '3', '2', 'variation-attribute', '20', '1', '0' );
}
$this->assertEquals( $expected, $rows );
}
/** /**
* Set the product attributes from an array with this format: * Set the product attributes from an array with this format:
* *
@ -380,4 +1140,73 @@ class LookupDataStoreTest extends \WC_Unit_Test_Case {
return $result; return $result;
} }
/**
* Set the value of the option for direct lookup table updates.
*
* @param bool $value True to set the option to 'yes', false for 'no'.
*/
private function set_direct_update_option( bool $value ) {
update_option( 'woocommerce_attribute_lookup__direct_updates', $value ? 'yes' : 'no' );
}
/**
* Save a product and delete any lookup table data that may have been automatically inserted
* (for the purposes of unit testing we want to insert this data manually)
*
* @param \WC_Product $product The product to save and delete lookup table data for.
*/
private function save( \WC_Product $product ) {
global $wpdb;
$product->save();
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->prefix}wc_product_attributes_lookup WHERE product_id = %d",
$product->get_id()
)
);
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
$queue = WC()->get_instance_of( \WC_Queue::class );
$queue->clear_methods_called();
}
/**
* Insert one entry in the lookup table.
*
* @param int $product_id The product id.
* @param int $product_or_parent_id The product id for non-variable products, the main/parent product id for variations.
* @param string $taxonomy Taxonomy name.
* @param int $term_id Term id.
* @param bool $is_variation_attribute True if the taxonomy corresponds to an attribute used to define variations.
* @param bool $has_stock True if the product is in stock.
*/
private function insert_lookup_table_data( int $product_id, int $product_or_parent_id, string $taxonomy, int $term_id, bool $is_variation_attribute, bool $has_stock ) {
global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query(
$wpdb->prepare(
'INSERT INTO ' . $this->lookup_table_name . ' (
product_id,
product_or_parent_id,
taxonomy,
term_id,
is_variation_attribute,
in_stock)
VALUES
( %d, %d, %s, %d, %d, %d )',
$product_id,
$product_or_parent_id,
$taxonomy,
$term_id,
$is_variation_attribute ? 1 : 0,
$has_stock ? 1 : 0
)
);
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
}
} }

View File

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce * Plugin Name: WooCommerce
* Plugin URI: https://woocommerce.com/ * Plugin URI: https://woocommerce.com/
* Description: An eCommerce toolkit that helps you sell anything. Beautifully. * Description: An eCommerce toolkit that helps you sell anything. Beautifully.
* Version: 5.6.0-dev * Version: 5.7.0-dev
* Author: Automattic * Author: Automattic
* Author URI: https://woocommerce.com * Author URI: https://woocommerce.com
* Text Domain: woocommerce * Text Domain: woocommerce