diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 677999b111b..3e3b424196e 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -41,9 +41,8 @@ If you have questions about the process to contribute code or want to discuss de
- Make sure to write good and detailed commit messages (see [this post](https://chris.beams.io/posts/git-commit/) for more on this) and follow all the applicable sections of the pull request template.
- Please avoid modifying the changelog directly or updating the .pot files. These will be updated by the WooCommerce team.
-If you are contributing code to the (Javascript-driven) WooCommerce Admin project or to Gutenberg blocks, note that these are developed in external packages.
+If you are contributing code to the (Javascript-driven) Gutenberg blocks, note that it's developed in an external package.
-- [WooCommerce Admin](https://github.com/woocommerce/woocommerce-admin)
- [Blocks](https://github.com/woocommerce/woocommerce-gutenberg-products-block)
## Feature Requests š
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index b5140fcd7f0..dd08989d3c3 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -25,13 +25,10 @@ Closes # .
* [ ] Have you added an explanation of what your changes do and why you'd like us to include them?
* [ ] Have you written new tests for your changes, as applicable?
* [ ] Have you successfully run tests with your changes locally?
+* [ ] Have you created a changelog file by running `pnpm nx affected --target=changelog`?
-### Changelog entry
-
-> Enter a summary of all changes on this Pull Request. This will appear in the changelog if accepted.
-
### FOR PR REVIEWER ONLY:
* [ ] I have reviewed that everything is sanitized/escaped appropriately for any SQL or XSS injection possibilities. I made sure Linting is not ignored or disabled.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2b54e225a1b..ac8ab5115ce 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -69,7 +69,7 @@ jobs:
- name: Build Admin feature config
working-directory: ./
- run: pnpm nx build:feature-config woocommerce-admin
+ run: pnpm nx build:feature-config woocommerce
- name: Add PHP8 Compatibility.
run: |
diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml
index 816398c1854..c32ef4a016a 100644
--- a/.github/workflows/pr-code-coverage.yml
+++ b/.github/workflows/pr-code-coverage.yml
@@ -61,7 +61,7 @@ jobs:
- name: Build Admin feature config
working-directory: ./
run: |
- pnpm nx build:feature-config woocommerce-admin
+ pnpm nx build:feature-config woocommerce
- name: Init DB and WP
run: pnpm nx install-unit-test-db woocommerce
diff --git a/.github/workflows/pr-lint-monorepo.yml b/.github/workflows/pr-lint-monorepo.yml
new file mode 100644
index 00000000000..7c952b3a641
--- /dev/null
+++ b/.github/workflows/pr-lint-monorepo.yml
@@ -0,0 +1,26 @@
+name: Run lint checks potentially affecting projects across the monorepo
+on: pull_request
+concurrency:
+ group: changelogger-${{ github.event_name }}-${{ github.ref }}
+ cancel-in-progress: true
+jobs:
+ changelogger_used:
+ name: Changelogger use
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '7.4'
+
+ - name: Check change files are touched for touched projects
+ env:
+ BASE: ${{ github.event.pull_request.base.sha }}
+ HEAD: ${{ github.event.pull_request.head.sha }}
+ run: php tools/monorepo/check-changelogger-use.php --debug "$BASE" "$HEAD"
diff --git a/.github/workflows/pr-unit-tests.yml b/.github/workflows/pr-unit-tests.yml
index ac5a5b8de58..9dcaedf3ee4 100644
--- a/.github/workflows/pr-unit-tests.yml
+++ b/.github/workflows/pr-unit-tests.yml
@@ -69,7 +69,7 @@ jobs:
- name: Build Admin feature config
working-directory: ./
run: |
- pnpm nx build:feature-config woocommerce-admin
+ pnpm nx build:feature-config woocommerce
- name: Add PHP8 Compatibility.
run: |
diff --git a/.github/workflows/scripts/add-post-merge-comment.php b/.github/workflows/scripts/add-post-merge-comment.php
index ba2e13baf2d..afd6ec803f4 100644
--- a/.github/workflows/scripts/add-post-merge-comment.php
+++ b/.github/workflows/scripts/add-post-merge-comment.php
@@ -57,7 +57,6 @@ echo "The pull request was merged by: $merger_user_name\n";
$comment_body = "Hi @$merger_user_name, thanks for merging this pull request. Please take a look at these follow-up tasks you may need to perform:
-- [ ] Add the `release: add changelog` label
- [ ] Add the `release: add testing instructions` label";
$add_comment_mutation = "
diff --git a/.husky/post-merge b/.husky/post-merge
new file mode 100755
index 00000000000..48bc4affda4
--- /dev/null
+++ b/.husky/post-merge
@@ -0,0 +1,5 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+pnpm install
+pnpm nx affected --target="composer-install" --base=ORIG_HEAD --head=HEAD
diff --git a/plugins/woocommerce-admin/.husky/pre-commit b/.husky/pre-commit
similarity index 100%
rename from plugins/woocommerce-admin/.husky/pre-commit
rename to .husky/pre-commit
diff --git a/plugins/woocommerce-admin/.husky/pre-push b/.husky/pre-push
similarity index 61%
rename from plugins/woocommerce-admin/.husky/pre-push
rename to .husky/pre-push
index db541032602..e52fd7c5635 100755
--- a/plugins/woocommerce-admin/.husky/pre-push
+++ b/.husky/pre-push
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
-node bin/pre-push-hook.js
+./bin/pre-push.sh
diff --git a/bin/pre-push.sh b/bin/pre-push.sh
new file mode 100755
index 00000000000..9b2346b136f
--- /dev/null
+++ b/bin/pre-push.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+PROTECTED_BRANCH="trunk"
+CURRENT_BRANCH=$(git branch --show-current)
+if [ $PROTECTED_BRANCH = $CURRENT_BRANCH ]; then
+ if [ "$TERM" = "dumb" ]; then
+ >&2 echo "Sorry, you are unable to push to $PROTECTED_BRANCH using a GUI client! Please use git CLI."
+ exit 1
+ fi
+
+ printf "%sYou're about to push to $PROTECTED_BRANCH, is that what you intended? [y/N]: %s" "$(tput setaf 3)" "$(tput sgr0)"
+ read -r PROCEED < /dev/tty
+ echo
+
+ if [ "$(echo "${PROCEED:-n}" | tr "[:upper:]" "[:lower:]")" = "y" ]; then
+ echo "$(tput setaf 2)Brace yourself! Pushing to the $PROTECTED_BRANCH branch...$(tput sgr0)"
+ echo
+ exit 0
+ fi
+
+ echo "$(tput setaf 2)Push to $PROTECTED_BRANCH cancelled!$(tput sgr0)"
+ echo
+ exit 1
+fi
diff --git a/changelog.txt b/changelog.txt
index 2d055307fe0..ee3248e0e05 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,103 @@
== Changelog ==
+= 6.4.0 2022-04-12 =
+
+**WooCommerce**
+
+- Add - Scaffolding for the custom orders table feature. ([#31692](https://github.com/woocommerce/woocommerce/pull/31692))
+- Add - Add DB table structure for custom order tables. ([#31811](https://github.com/woocommerce/woocommerce/pull/31811))
+- Add - Primary key for the product attributes lookup table. ([#32067](https://github.com/woocommerce/woocommerce/pull/32067))
+- Add - Tracks to the dashboard status widget and setup widget. ([#31857](https://github.com/woocommerce/woocommerce/pull/31857))
+- Add - Check around setup widget display when features are disabled. ([#31884](https://github.com/woocommerce/woocommerce/pull/31884))
+- Add - 'woocommerce_get_formatted_meta_data_include_all_meta_lines' filter hook. This can be used to control whether metadata lines are shown in the order meta box. ([#30948](https://github.com/woocommerce/woocommerce/pull/30948))
+- Enhancement - Introduce rate_limit_remaining column in the wc_rate_limits table. ([#32041](https://github.com/woocommerce/woocommerce/pull/32041))
+- Tweak - Update PayPal Standard JS used in the admin environment to avoid deprecated functionality. ([#32076](https://github.com/woocommerce/woocommerce/pull/32076))
+- Tweak - Change level of escaping used to render the CSV import error log. ([#32000](https://github.com/woocommerce/woocommerce/pull/32000))
+- Tweak - Make the payment_url field available via the REST API's orders endpoint. ([#31826](https://github.com/woocommerce/woocommerce/pull/31826))
+- Tweak - Rename WC_API_Exception code woocommerce_api_cannot_edit_product_catgory into woocommerce_api_cannot_edit_product_category ([#31785](https://github.com/woocommerce/woocommerce/pull/31785))
+- Tweak - Updated default email color to new Woo purple ([#30586](https://github.com/woocommerce/woocommerce/pull/30586))
+- Fix - Avoid depending on the presence of a theme header template to clear the cart after payment is made. ([#31877](https://github.com/woocommerce/woocommerce/pull/31877))
+- Fix - Payments tab tracking. ([#31844](https://github.com/woocommerce/woocommerce/pull/31844))
+- Fix - Remove unnecessary duplicate style in email-styles template. ([#31860](https://github.com/woocommerce/woocommerce/pull/31860))
+- Fix - incorrect position value for registering menu pages. ([#31779](https://github.com/woocommerce/woocommerce/pull/31779))
+- Fix - SZL currency symbol. Updated from 'L' to 'E'. ([#30602](https://github.com/woocommerce/woocommerce/pull/30602))
+- Fix - Removed execution of at least one hook ignoring the `woocommerce_load_webhooks_limit` filter value. ([#29002](https://github.com/woocommerce/woocommerce/pull/29002))
+- Dev - Added has_options() to REST API v3 product endpoint response. ([#32031](https://github.com/woocommerce/woocommerce/pull/32031))
+- Dev - Added woocommerce_admin_order_should_render_refunds hook to allow control over the refunds UI within the order editor. ([#31414](https://github.com/woocommerce/woocommerce/pull/31414))
+
+**WooCommerce Admin - 3.3.0 & 3.3.1 & 3.3.2**
+
+- Add - Add asynchronous plugin install and activation endpoints ([#8079](https://github.com/woocommerce/woocommerce-admin/pull/8079))
+- Performance - Avoid expensive get_notes() queries in CouponPageMoved admin_init actions by using new Notes::get_note_by_name() helper method. ([#8202](https://github.com/woocommerce/woocommerce-admin/pull/8202))
+- Enhancement - Add chart color filter for overriding default chart colors. ([#8258](https://github.com/woocommerce/woocommerce-admin/pull/8258))
+- Enhancement - Added Typescript type declarations to build for @woocommerce/components ([#8282](https://github.com/woocommerce/woocommerce-admin/pull/8282))
+- Enhancement - Increase color selection limit to ten and add additional colors. ([#8258](https://github.com/woocommerce/woocommerce-admin/pull/8258))
+- Enhancement - Made @woocommerce/components/Stepper a Typescript file ([#8286](https://github.com/woocommerce/woocommerce-admin/pull/8286))
+- Enhancement - Prompts a modal to save any unsaved changes when the users try to move to a different step ([#8278](https://github.com/woocommerce/woocommerce-admin/pull/8278))
+- Tweak - OBW: Override Country/Region label line-height style to avoid truncated descenders. ([#8186](https://github.com/woocommerce/woocommerce-admin/pull/8186))
+- Tweak - Show single success message for theme install and activation ([#8236](https://github.com/woocommerce/woocommerce-admin/pull/8236))
+- Tweak - Use WC_VERSION as cache buster for assets ([#8308](https://github.com/woocommerce/woocommerce-admin/pull/8308))
+- Update - Adjust time range and add an image for the Jetpack Backup note. ([#8293](https://github.com/woocommerce/woocommerce-admin/pull/8293))
+- Update - Implement MailChimp API request threshold for MailchimpScheduler. ([#8342](https://github.com/woocommerce/woocommerce-admin/pull/8342))
+- Update - Reintroduce CES on product add, product update, and order update. ([#8238](https://github.com/woocommerce/woocommerce-admin/pull/8238))
+- Update - Replace mysql image with mariadb ([#8220](https://github.com/woocommerce/woocommerce-admin/pull/8220))
+- Update - Update country support list for WooCommerce Payments Task. ([#8517](https://github.com/woocommerce/woocommerce-admin/pull/8517))
+- Fix - Fix handling of paid themes in purchase task. ([#8493](https://github.com/woocommerce/woocommerce-admin/pull/8493))
+- Fix - Make sure the paid extension task is also shown for themes. ([#8412](https://github.com/woocommerce/woocommerce-admin/pull/8412))
+- Fix - Reintroduce emphasis on inbox note action button. ([#8411](https://github.com/woocommerce/woocommerce-admin/pull/8411))
+- Fix - Remove class ExtendedPayments. ([#8461](https://github.com/woocommerce/woocommerce-admin/pull/8461))
+- Fix - Added random IDs to SVG checkmarks in stepper component ([#8222](https://github.com/woocommerce/woocommerce-admin/pull/8222))
+- Fix - Fix Google Listings plugin is always shown in free features despite already activated. ([#8330](https://github.com/woocommerce/woocommerce-admin/pull/8330))
+- Fix - Fix hidden notes in `admin/notes` endpoint when the user is not in the tasklist experiment. ([#8328](https://github.com/woocommerce/woocommerce-admin/pull/8328))
+- Fix - Fix missing product name in variation analytic page for the deleted products. ([#8255](https://github.com/woocommerce/woocommerce-admin/pull/8255))
+- Fix - Fix payments extensions displayed below the offline payments options. ([#8232](https://github.com/woocommerce/woocommerce-admin/pull/8232))
+- Fix - Fix setup wizard title and flash of content ([#8201](https://github.com/woocommerce/woocommerce-admin/pull/8201))
+- Fix - Fix too many pending run_remote_notifications actions. ([#8285](https://github.com/woocommerce/woocommerce-admin/pull/8285))
+- Fix - Fix view logic for Setup additional payment providers task. ([#8391](https://github.com/woocommerce/woocommerce-admin/pull/8391))
+- Fix - OBW: fix copy on Business Details when "WooCommerce Shipping" is not listed ([#8324](https://github.com/woocommerce/woocommerce-admin/pull/8324))
+- Fix - Only add product data on REST requests and task list ([#8235](https://github.com/woocommerce/woocommerce-admin/pull/8235))
+- Fix - Stop showing actioned inbox items ([#8394](https://github.com/woocommerce/woocommerce-admin/pull/8394))
+- Fix - WC Payments task is not visible after installing the plugin ([#8514](https://github.com/woocommerce/woocommerce-admin/pull/8514))
+- Fix - PHP warning when default param is missing in payments spec. ([#8519](https://github.com/woocommerce/woocommerce-admin/pull/8519))
+- Dev - Added a test for tracks event recording for PaymentGatewaySuggestions ([#8306](https://github.com/woocommerce/woocommerce-admin/pull/8306))
+- Dev - Add README to hook reference generation script ([#8004](https://github.com/woocommerce/woocommerce-admin/pull/8004))
+- Dev - Add reset WooCommerce functionality to E2E tests, so tests have a fresh state. ([#8219](https://github.com/woocommerce/woocommerce-admin/pull/8219))
+- Dev - Enabled optional typescript checking on ./client subfolder ([#8372](https://github.com/woocommerce/woocommerce-admin/pull/8372))
+- Dev - Fix formatting and add filter param for changelog types for the testing instructions script. ([#8256](https://github.com/woocommerce/woocommerce-admin/pull/8256))
+- Dev - Refactor MerchantEmailNotifications ([#8304](https://github.com/woocommerce/woocommerce-admin/pull/8304))
+- Dev - Remove preloaded countries from data endpoints and use data store instead. ([#8380](https://github.com/woocommerce/woocommerce-admin/pull/8380))
+- Dev - Remove unused pre loaded setting data displaying all the routes. ([#8379](https://github.com/woocommerce/woocommerce-admin/pull/8379))
+- Dev - Remove unused task styling classes ([#8234](https://github.com/woocommerce/woocommerce-admin/pull/8234))
+- Dev - Update dependencies to support react 17 and drop support for IE11. ([#8305](https://github.com/woocommerce/woocommerce-admin/pull/8305))
+- Dev - Update task list data structure to better handle new designs. ([#8332](https://github.com/woocommerce/woocommerce-admin/pull/8332))
+
+**WooCommerce Blocks - 7.2.0 & 7.2.1**
+
+- Enhancement - Add Global Styles support to the Product Price block. ([5950](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5950))
+- Enhancement - Add Global Styles support to the Add To Cart Button block. ([5816](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5816))
+- Enhancement - Store API - Introduced `wc/store/v1` namespace. ([5911](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5911))
+- Enhancement - Renamed WooCommerce block templates to more e-commerce related names. ([5935](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5935))
+- Enhancement - Featured Product block: Add the ability to reset to a previously set custom background image. ([5886](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5886))
+- Enhancement - Add a remove image button to the WooCommerce Feature Category block. ([5719](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5719))
+- Enhancement - Add support for the global style for the On-Sale Badge block. ([5565](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5565))
+- Enhancement - Add support for the global style for the Attribute Filter block. ([5557](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5557))
+- Enhancement - Category List block: Add support for global style. ([5516](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5516))
+- Fix - Fixed typo in `wooocommerce_store_api_validate_add_to_cart` and `wooocommerce_store_api_validate_cart_item` hook names. ([5926](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5926))
+- Fix - Fix loading WC core translations in locales where WC Blocks is not localized for some strings. ([5910](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5910))
+- Fix - Fixed an issue where clear customizations functionality was not working for WooCommerce templates. ([5746](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5746))
+- Fix - Fixed hover and focus states for button components. ([5712](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5712))
+- Fix - Add to Cart button on Products listing blocks will respect the "Redirect to the cart page after successful addition" setting. ([5708](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5708))
+- Fix - Fixes Twenty Twenty Two issues with sales price and added to cart "View Cart" call out styling in the "Products by Category" block. ([5684](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5684))
+- Fix - StoreAPI: Clear all wc notice types in the cart validation context [#5983](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5983)
+- Fix - Don't trigger class deprecations notices if headers are already sent [#6074](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/6074)
+- Various - Remove v1 string from Store Keys. ([5987](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5987))
+- Various - Introduce the `InvalidCartException` for handling cart validation. ([5904](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5904))
+- Various - Renamed Store API custom headers to remove `X-WC-Store-API` prefixes. [#5983](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5983)
+- Various - Normalised Store API error codes [#5992](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5992)
+- Various - Deprecated `woocommerce_blocks_checkout_order_processed` in favour of `woocommerce_store_api_checkout_order_processed`
+- Various - Deprecated `woocommerce_blocks_checkout_update_order_meta` in favour of `woocommerce_store_api_checkout_update_order_meta`
+- Various - Deprecated `woocommerce_blocks_checkout_update_order_from_request` in favour of `woocommerce_store_api_checkout_update_order_from_request`
+
= 6.3.1 2022-03-10 =
**WooCommerce**
diff --git a/package.json b/package.json
index 3f7f9789ea7..2ff6a3d8460 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,9 @@
"url": "https://github.com/woocommerce/woocommerce/issues"
},
"scripts": {
- "preinstall": "npx only-allow pnpm"
+ "preinstall": "npx only-allow pnpm",
+ "postinstall": "pnpm git:update-hooks",
+ "git:update-hooks": "rm -r .git/hooks && mkdir -p .git/hooks && husky install"
},
"devDependencies": {
"@automattic/nx-composer": "^0.1.0",
@@ -30,7 +32,9 @@
"@wordpress/prettier-config": "^1.1.1",
"chalk": "^4.1.2",
"glob": "^7.2.0",
+ "husky": "^7.0.4",
"jest": "^27.3.1",
+ "lint-staged": "^12.3.7",
"mkdirp": "^1.0.4",
"node-stream-zip": "^1.15.0",
"prettier": "npm:wp-prettier@^2.2.1-beta-1",
diff --git a/packages/js/README.md b/packages/js/README.md
index 25ca7a90007..5f82f22a152 100644
--- a/packages/js/README.md
+++ b/packages/js/README.md
@@ -36,13 +36,13 @@ To create a new package, add a new folder to `/packages`, containingā¦
"author": "Automattic",
"license": "GPL-2.0-or-later",
"keywords": [ "wordpress", "woocommerce" ],
- "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/[_YOUR_PACKAGE_]/README.md",
+ "homepage": "https://github.com/woocommerce/woocommerce/tree/main/packages/[_YOUR_PACKAGE_]/README.md",
"repository": {
"type": "git",
- "url": "https://github.com/woocommerce/woocommerce-admin.git"
+ "url": "https://github.com/woocommerce/woocommerce.git"
},
"bugs": {
- "url": "https://github.com/woocommerce/woocommerce-admin/issues"
+ "url": "https://github.com/woocommerce/woocommerce/issues"
},
"main": "build/index.js",
"module": "build-module/index.js",
diff --git a/packages/js/admin-e2e-tests/CHANGELOG.md b/packages/js/admin-e2e-tests/CHANGELOG.md
index d764841fc75..5836c843674 100644
--- a/packages/js/admin-e2e-tests/CHANGELOG.md
+++ b/packages/js/admin-e2e-tests/CHANGELOG.md
@@ -1,5 +1,11 @@
# Unreleased
+- Add E2E tests to disabled welcome modal #32505
+
+- Update test for payment task. #32467
+
+- Increase timeout threshold for payment task. #32605
+
# 1.0.0
- Add returned type annotations and remove unused vars. #8020
diff --git a/packages/js/admin-e2e-tests/package.json b/packages/js/admin-e2e-tests/package.json
index be6a8efe6d2..38c82638c0a 100644
--- a/packages/js/admin-e2e-tests/package.json
+++ b/packages/js/admin-e2e-tests/package.json
@@ -3,10 +3,10 @@
"version": "1.0.0",
"author": "Automattic",
"description": "E2E tests for the new WooCommerce interface.",
- "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/admin-e2e-tests/README.md",
+ "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/admin-e2e-tests/README.md",
"repository": {
"type": "git",
- "url": "https://github.com/woocommerce/woocommerce-admin.git"
+ "url": "https://github.com/woocommerce/woocommerce.git"
},
"keywords": [
"woocommerce",
@@ -56,5 +56,10 @@
"clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*",
"lint": "eslint src",
"prepack": "pnpm run clean && pnpm run build"
+ },
+ "lint-staged": {
+ "*.(t|j)s?(x)": [
+ "eslint --fix"
+ ]
}
}
diff --git a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts
index 5224a8825a6..ea711a63e85 100644
--- a/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts
+++ b/packages/js/admin-e2e-tests/src/pages/PaymentsSetup.ts
@@ -21,8 +21,20 @@ export class PaymentsSetup extends BasePage {
await waitForElementByText( 'h1', 'Set up payments' );
}
- async closeHelpModal(): Promise< void > {
- await this.clickButtonWithText( 'Got it' );
+ async possiblyCloseHelpModal(): Promise< void > {
+ try {
+ await this.clickButtonWithText( 'Got it' );
+ } catch ( e ) {}
+ }
+
+ async showOtherPaymentMethods(): Promise< void > {
+ const selector = '.woocommerce-task-payments button.toggle-button';
+ await this.page.waitForSelector( selector );
+ const toggleButton = await this.page.$(
+ `${ selector }[aria-expanded=false]`
+ );
+ await toggleButton?.click();
+ await waitForElementByText( 'h2', 'Offline payment methods' );
}
async goToPaymentMethodSetup(
@@ -41,14 +53,6 @@ export class PaymentsSetup extends BasePage {
}
}
- async methodHasBeenSetup( method: PaymentMethod ): Promise< void > {
- const selector = `.woocommerce-task-payment-${ method }`;
- await this.page.waitForSelector( selector );
- expect(
- await getElementByText( '*', 'Manage', selector )
- ).toBeDefined();
- }
-
async enableCashOnDelivery(): Promise< void > {
await this.page.waitForSelector( '.woocommerce-task-payment-cod' );
await this.clickButtonWithText( 'Enable' );
diff --git a/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts b/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts
index a79b6852e19..d1c6c90e06a 100644
--- a/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts
+++ b/packages/js/admin-e2e-tests/src/pages/WcHomescreen.ts
@@ -24,12 +24,7 @@ export class WcHomescreen extends BasePage {
}
async possiblyDismissWelcomeModal(): Promise< void > {
- const modalText = 'Welcome to your WooCommerce storeās online HQ!';
- const modal = await waitForElementByTextWithoutThrow(
- 'h2',
- modalText,
- 10
- );
+ const modal = await this.isWelcomeModalVisible();
if ( modal ) {
await this.clickButtonWithText( 'Next' );
@@ -41,6 +36,16 @@ export class WcHomescreen extends BasePage {
}
}
+ async isWelcomeModalVisible(): Promise< boolean > {
+ const modalText = 'Welcome to your WooCommerce storeās online HQ!';
+ const modal = await waitForElementByTextWithoutThrow(
+ 'h2',
+ modalText,
+ 10
+ );
+ return modal;
+ }
+
async getTaskList(): Promise< Array< string | null > > {
await page.waitForSelector(
'.woocommerce-task-card .woocommerce-task-list__item-title'
diff --git a/packages/js/admin-e2e-tests/src/pages/WcSettings.ts b/packages/js/admin-e2e-tests/src/pages/WcSettings.ts
index ebf95a46033..0b5f78cf0a7 100644
--- a/packages/js/admin-e2e-tests/src/pages/WcSettings.ts
+++ b/packages/js/admin-e2e-tests/src/pages/WcSettings.ts
@@ -42,8 +42,22 @@ export class WcSettings extends BasePage {
);
}
+ async paymentMethodIsEnabled( method = '' ): Promise< boolean > {
+ await this.navigate( 'checkout' );
+ await waitForElementByText( 'h2', 'Payment methods' );
+ const className = await getAttribute(
+ `tr[data-gateway_id=${ method }] .woocommerce-input-toggle`,
+ 'className'
+ );
+ return (
+ ( className as string ).indexOf(
+ 'woocommerce-input-toggle--disabled'
+ ) === -1
+ );
+ }
+
async cleanPaymentMethods(): Promise< void > {
- this.navigate( 'checkout' );
+ await this.navigate( 'checkout' );
await waitForElementByText( 'h2', 'Payment methods' );
const paymentMethods = await page.$$( 'span.woocommerce-input-toggle' );
for ( const method of paymentMethods ) {
diff --git a/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts b/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts
index f0772672b86..717cacf4186 100644
--- a/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts
+++ b/packages/js/admin-e2e-tests/src/specs/activate-and-setup/complete-onboarding-wizard.ts
@@ -597,10 +597,37 @@ const testBusinessDetailsForm = () => {
} );
};
+const testAdminHomescreen = () => {
+ describe( 'Homescreen', () => {
+ const profileWizard = new OnboardingWizard( page );
+ const homeScreen = new WcHomescreen( page );
+ const login = new Login( page );
+
+ beforeAll( async () => {
+ await login.login();
+ await resetWooCommerceState();
+ await profileWizard.navigate();
+ await profileWizard.skipStoreSetup();
+ } );
+
+ afterAll( async () => {
+ await login.logout();
+ } );
+
+ it( 'should not show welcome modal', async () => {
+ await homeScreen.isDisplayed();
+ await expect( homeScreen.isWelcomeModalVisible() ).resolves.toBe(
+ false
+ );
+ } );
+ } );
+};
+
module.exports = {
testAdminOnboardingWizard,
testSelectiveBundleWCPay,
testDifferentStoreCurrenciesWCPay,
testSubscriptionsInclusion,
testBusinessDetailsForm,
+ testAdminHomescreen,
};
diff --git a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts
index 0515dce47f3..1571fea5fcc 100644
--- a/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts
+++ b/packages/js/admin-e2e-tests/src/specs/tasks/payment.ts
@@ -48,11 +48,13 @@ const testAdminPaymentSetupTask = () => {
it( 'Can visit the payment setup task from the homescreen if the setup wizard has been skipped', async () => {
await homeScreen.clickOnTaskList( 'Set up payments' );
- await paymentsSetup.closeHelpModal();
+ await paymentsSetup.possiblyCloseHelpModal();
await paymentsSetup.isDisplayed();
} );
- it( 'Saving valid bank account transfer details enables the payment method', async () => {
+ it.skip( 'Saving valid bank account transfer details enables the payment method', async () => {
+ await paymentsSetup.showOtherPaymentMethods();
+ await waitForTimeout( 500 );
await paymentsSetup.goToPaymentMethodSetup( 'bacs' );
await bankTransferSetup.saveAccountDetails( {
accountNumber: '1234',
@@ -62,27 +64,28 @@ const testAdminPaymentSetupTask = () => {
iban: '12 3456 7890',
swiftCode: 'ABBA',
} );
-
- await homeScreen.isDisplayed();
- await waitForTimeout( 1000 );
- await homeScreen.clickOnTaskList( 'Set up payments' );
- await paymentsSetup.isDisplayed();
- await paymentsSetup.methodHasBeenSetup( 'bacs' );
+ await waitForTimeout( 1500 );
+ expect( await settings.paymentMethodIsEnabled( 'bacs' ) ).toBe(
+ true
+ );
+ await homeScreen.navigate();
} );
- it( 'Enabling cash on delivery enables the payment method', async () => {
+ it.skip( 'Enabling cash on delivery enables the payment method', async () => {
await settings.cleanPaymentMethods();
await homeScreen.navigate();
await homeScreen.isDisplayed();
await waitForTimeout( 1000 );
await homeScreen.clickOnTaskList( 'Set up payments' );
- await paymentsSetup.enableCashOnDelivery();
- await homeScreen.navigate();
- await homeScreen.isDisplayed();
- await waitForTimeout( 1000 );
- await homeScreen.clickOnTaskList( 'Set up payments' );
+ await paymentsSetup.possiblyCloseHelpModal();
await paymentsSetup.isDisplayed();
- await paymentsSetup.methodHasBeenSetup( 'cod' );
+ await paymentsSetup.showOtherPaymentMethods();
+ await waitForTimeout( 500 );
+ await paymentsSetup.enableCashOnDelivery();
+ await waitForTimeout( 1500 );
+ expect( await settings.paymentMethodIsEnabled( 'cod' ) ).toBe(
+ true
+ );
} );
} );
};
diff --git a/packages/js/api-core-tests/package.json b/packages/js/api-core-tests/package.json
index 4c4bf1fa0d3..f83420e77d3 100644
--- a/packages/js/api-core-tests/package.json
+++ b/packages/js/api-core-tests/package.json
@@ -33,5 +33,10 @@
},
"bin": {
"wc-api-tests": "bin/wc-api-tests.sh"
+ },
+ "lint-staged": {
+ "*.(t|j)s?(x)": [
+ "eslint --fix"
+ ]
}
}
diff --git a/packages/js/api/package.json b/packages/js/api/package.json
index af4790ba749..913125e6bcd 100644
--- a/packages/js/api/package.json
+++ b/packages/js/api/package.json
@@ -52,5 +52,10 @@
},
"publishConfig": {
"access": "public"
+ },
+ "lint-staged": {
+ "*.(t|j)s?(x)": [
+ "eslint --fix"
+ ]
}
}
diff --git a/packages/js/components/CHANGELOG.md b/packages/js/components/CHANGELOG.md
index 798bde85839..e7782c11e57 100644
--- a/packages/js/components/CHANGELOG.md
+++ b/packages/js/components/CHANGELOG.md
@@ -1,5 +1,10 @@
# Unreleased
+- Fix documentation for `TableCard` component
+- Update dependency `@wordpress/hooks` to ^3.5.0
+- Update dependency `@wordpress/icons` to ^8.1.0
+- Add `className` prop for Pill component. #32605
+
# 10.0.0
- Replace deprecated wp.compose.withState with wp.element.useState. #8338
- Add missing dependencies. #8349
diff --git a/packages/js/components/package.json b/packages/js/components/package.json
index b9ec712baa5..646fa4ffec3 100644
--- a/packages/js/components/package.json
+++ b/packages/js/components/package.json
@@ -9,13 +9,13 @@
"woocommerce",
"components"
],
- "homepage": "https://github.com/woocommerce/woocommerce-admin/tree/main/packages/components/README.md",
+ "homepage": "https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/components/README.md",
"repository": {
"type": "git",
- "url": "https://github.com/woocommerce/woocommerce-admin.git"
+ "url": "https://github.com/woocommerce/woocommerce.git"
},
"bugs": {
- "url": "https://github.com/woocommerce/woocommerce-admin/issues"
+ "url": "https://github.com/woocommerce/woocommerce/issues"
},
"main": "build/index.js",
"module": "build-module/index.js",
@@ -39,10 +39,10 @@
"@wordpress/deprecated": "^3.3.1",
"@wordpress/dom": "^3.3.2",
"@wordpress/element": "^4.1.1",
- "@wordpress/hooks": "^2.12.3",
+ "@wordpress/hooks": "^3.5.0",
"@wordpress/html-entities": "^3.3.1",
"@wordpress/i18n": "^4.3.1",
- "@wordpress/icons": "^6.3.0",
+ "@wordpress/icons": "^8.1.0",
"@wordpress/keycodes": "^3.3.1",
"@wordpress/url": "^3.4.1",
"@wordpress/viewport": "^4.1.2",
@@ -123,5 +123,11 @@
"test:nobuild": "jest --config ./jest.config.json",
"test:update-snapshots": "pnpm run test:nobuild -- --updateSnapshot",
"test-staged": "jest --bail --config ./jest.config.json --findRelatedTests"
+ },
+ "lint-staged": {
+ "*.(t|j)s?(x)": [
+ "eslint --fix",
+ "pnpm test-staged"
+ ]
}
}
diff --git a/packages/js/components/src/advanced-filters/README.md b/packages/js/components/src/advanced-filters/README.md
index e9cbc16bd8c..72da82a2994 100644
--- a/packages/js/components/src/advanced-filters/README.md
+++ b/packages/js/components/src/advanced-filters/README.md
@@ -124,7 +124,7 @@ const config = {
};
```
-`type`: A string Autocompleter type used by the [Search Component](https://github.com/woocommerce/woocommerce-admin/tree/main/packages/components/src/search).
+`type`: A string Autocompleter type used by the [Search Component](https://github.com/woocommerce/woocommerce/tree/trunk/packages/js/components/src/search).
`getLabels`: A function returning a Promise resolving to an array of objects with `id` and `label` properties.
### Date
diff --git a/packages/js/components/src/pill/pill.js b/packages/js/components/src/pill/pill.js
index 662bb11fa45..33f91cf9bdf 100644
--- a/packages/js/components/src/pill/pill.js
+++ b/packages/js/components/src/pill/pill.js
@@ -2,16 +2,17 @@
* External dependencies
*/
import { createElement } from '@wordpress/element';
+import classnames from 'classnames';
/**
* Internal dependencies
*/
import { Text } from '../experimental';
-export function Pill( { children } ) {
+export function Pill( { children, className } ) {
return (
- { sprintf(
- /* translators: 1: completed tasks, 2: total tasks */
- __(
- 'Follow these steps to start selling quickly. %1$d out of %2$d complete.',
- 'woocommerce'
- ),
- completedCount,
- tasksCount
- ) }
-
+ { sprintf(
+ /* translators: 1: completed tasks, 2: total tasks */
+ __(
+ 'Follow these steps to start selling quickly. %1$d out of %2$d complete.',
+ 'woocommerce'
+ ),
+ completedCount,
+ tasksCount
+ ) }
+
{ progressTitle }
- );
- expect( queryByRole( 'button' ) ).toHaveTextContent( 'Set up' );
+ expect( queryByRole( 'button' ) ).toHaveTextContent( 'Get started' );
} );
it( 'should display the SetupRequired component when appropriate', () => {
@@ -138,7 +138,7 @@ describe( 'PaymentGatewaySuggestions > List', () => {
expect( queryByText( 'Recommended' ) ).not.toBeInTheDocument();
} );
- it( 'should display Manage button if not enabled and does have setup', () => {
+ it( 'should display Manage button if enabled and does have setup', () => {
const props = {
...defaultProps,
paymentGateways: [
@@ -180,6 +180,7 @@ describe( 'PaymentGatewaySuggestions > List', () => {
...mockGateway,
plugins: [ 'nope' ],
needsSetup: false,
+ enabled: true,
},
],
};
diff --git a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js
index 528d62f63a3..b80430d58e9 100644
--- a/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js
+++ b/plugins/woocommerce-admin/client/tasks/fills/PaymentGatewaySuggestions/components/Setup/Configure.js
@@ -156,7 +156,7 @@ export const Configure = ( { markConfigured, paymentGateway } ) => {
{ description }
+
- composer install',
- '' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '
'
- );
- ?>
-
';
- printf(
- /* Translators: %1$s is referring to a php constant name, %2$s is referring to the wp-config.php file. */
- esc_html__( 'WooCommerce Admin development mode requires the %1$s constant to be defined and true in your %2$s file. Otherwise you are loading the admin package from WooCommerce core.', 'woocommerce-admin' ),
- 'JETPACK_AUTOLOAD_DEV
',
- 'wp-config.php
'
- );
- echo '
';
- printf(
- /* Translators: %1$s, %2$s, and %3$s are all build commands to be run in order. */
- esc_html__( 'You have installed a development version of WooCommerce Admin which requires files to be built. From the plugin directory, run %1$s and %2$s to install dependencies, then %3$s to build the files.', 'woocommerce-admin' ),
- 'composer install
',
- 'pnpm install
',
- 'pnpm run build
'
- );
- printf(
- /* translators: 1: URL of GitHub Repository build page */
- esc_html__( 'Or you can download a pre-built version of the plugin by visiting the releases page in the repository.', 'woocommerce-admin' ),
- 'https://github.com/woocommerce/woocommerce-admin/releases'
- );
- echo '
WooCommerce.com, where you\'ll find the most popular WooCommerce extensions.', 'woocommerce' diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-menus.php b/plugins/woocommerce/includes/admin/class-wc-admin-menus.php index 3528be7e071..1f6542918b8 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-menus.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-menus.php @@ -61,7 +61,7 @@ class WC_Admin_Menus { $menu[] = array( '', 'read', 'separator-woocommerce', '', 'wp-menu-separator woocommerce' ); // WPCS: override ok. } - add_menu_page( __( 'WooCommerce', 'woocommerce' ), __( 'WooCommerce', 'woocommerce' ), 'edit_others_shop_orders', 'woocommerce', null, $woocommerce_icon, 55 ); + add_menu_page( __( 'WooCommerce', 'woocommerce' ), __( 'WooCommerce', 'woocommerce' ), 'edit_others_shop_orders', 'woocommerce', null, $woocommerce_icon, '55.5' ); add_submenu_page( 'edit.php?post_type=product', __( 'Attributes', 'woocommerce' ), __( 'Attributes', 'woocommerce' ), 'manage_product_terms', 'product_attributes', array( $this, 'attributes_page' ) ); } @@ -73,7 +73,7 @@ class WC_Admin_Menus { if ( self::can_view_woocommerce_menu_item() ) { add_submenu_page( 'woocommerce', __( 'Reports', 'woocommerce' ), __( 'Reports', 'woocommerce' ), 'view_woocommerce_reports', 'wc-reports', array( $this, 'reports_page' ) ); } else { - add_menu_page( __( 'Sales reports', 'woocommerce' ), __( 'Sales reports', 'woocommerce' ), 'view_woocommerce_reports', 'wc-reports', array( $this, 'reports_page' ), 'dashicons-chart-bar', 56 ); + add_menu_page( __( 'Sales reports', 'woocommerce' ), __( 'Sales reports', 'woocommerce' ), 'view_woocommerce_reports', 'wc-reports', array( $this, 'reports_page' ), 'dashicons-chart-bar', '55.6' ); } } diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php index 2eaef024a14..2a5ca1281f0 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-post-types.php @@ -928,7 +928,7 @@ class WC_Admin_Post_Types { return false; } - $old_price = $product->{"get_{$price_type}_price"}(); + $old_price = (float) $product->{"get_{$price_type}_price"}(); $price_changed = false; $change_price = absint( $request_data[ "change_{$price_type}_price" ] ); diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-settings.php b/plugins/woocommerce/includes/admin/class-wc-admin-settings.php index 69506066749..a6a2499c7c1 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-settings.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-settings.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Utilities\ArrayUtil; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -273,6 +274,12 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) : } break; + case 'info': + echo '
' . basename( $download_file ) . '
'
+ )
+ );
+ }
+
if ( ! $this->is_allowed_filetype() ) {
throw new Exception(
sprintf(
- /* translators: %1$s: Downloadable file */
+ /* translators: 1: Downloadable file, 2: List of allowed filetypes. */
__( 'The downloadable file %1$s cannot be used as it does not have an allowed file type. Allowed types include: %2$s', 'woocommerce' ),
'' . basename( $download_file ) . '
',
'' . implode( ', ', array_keys( $this->get_allowed_mime_types() ) ) . '
'
@@ -121,7 +132,7 @@ class WC_Product_Download implements ArrayAccess {
if ( ! $this->file_exists() ) {
throw new Exception(
sprintf(
- /* translators: %s: Downloadable file */
+ /* translators: %s: Downloadable file */
__( 'The downloadable file %s cannot be used as it does not exist on the server.', 'woocommerce' ),
'' . $download_file . '
'
)
@@ -201,35 +212,42 @@ class WC_Product_Download implements ArrayAccess {
return;
}
- $download_file = $this->get_file();
+ $download_file = $this->get_file();
+
+ /**
+ * Controls whether shortcodes should be resolved and validated using the Approved Download Directory feature.
+ *
+ * @param bool $should_validate
+ */
+ if ( apply_filters( 'woocommerce_product_downloads_approved_directory_validation_for_shortcodes', true ) && 'shortcode' === $this->get_type_of_file_path() ) {
+ $download_file = do_shortcode( $download_file );
+ }
+
$is_site_administrator = is_multisite() ? current_user_can( 'manage_sites' ) : current_user_can( 'manage_options' );
$valid_storage_directory = $download_directories->is_valid_path( $download_file );
- if ( ! $valid_storage_directory && $auto_add_to_approved_directory_list ) {
+ if ( $valid_storage_directory ) {
+ return;
+ }
+
+ if ( $auto_add_to_approved_directory_list ) {
try {
// Add the parent URL to the approved directories list, but *do not enable it* unless the current user is a site admin.
$download_directories->add_approved_directory( ( new URL( $download_file ) )->get_parent_url(), $is_site_administrator );
- } catch ( Exception $e ) {
- /* translators: %s: Downloadable file */
- throw new Exception(
- sprintf(
- /* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */
- __( 'The downloadable file %1$s cannot be used: it is not located in an approved directory. Please contact a site administrator and request their approval. %2$sLearn more.%3$s', 'woocommerce' ),
- '' . $download_file . '
',
- '', // @todo update to working link (see https://github.com/Automattic/woocommerce/issues/181)
- ''
- )
- );
+ $valid_storage_directory = $download_directories->is_valid_path( $download_file );
+ } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
+ // At this point, $valid_storage_directory will be false. Fall-through so the appropriate exception is
+ // triggered (same as if the storage directory was invalid and $auto_add_to_approved_directory_list was false.
}
}
- if ( ! $valid_storage_directory && ! $is_site_administrator ) {
+ if ( ! $valid_storage_directory ) {
throw new Exception(
sprintf(
/* translators: %1$s is the downloadable file path, %2$s is an opening link tag, %3%s is a closing link tag. */
__( 'The downloadable file %1$s cannot be used: it is not located in an approved directory. Please contact a site administrator for help. %2$sLearn more.%3$s', 'woocommerce' ),
'' . $download_file . '
',
- '', // @todo update to working link (see https://github.com/Automattic/woocommerce/issues/181)
+ '',
''
)
);
@@ -292,6 +310,15 @@ class WC_Product_Download implements ArrayAccess {
}
}
+ /**
+ * Sets the status of the download to enabled (true) or disabled (false).
+ *
+ * @param bool $enabled True indicates the downloadable file is enabled, false indicates it is disabled.
+ */
+ public function set_enabled( bool $enabled = true ) {
+ $this->data['enabled'] = $enabled;
+ }
+
/*
|--------------------------------------------------------------------------
| Getters
@@ -336,6 +363,15 @@ class WC_Product_Download implements ArrayAccess {
return $this->data['file'];
}
+ /**
+ * Get status of the download.
+ *
+ * @return bool
+ */
+ public function get_enabled(): bool {
+ return $this->data['enabled'];
+ }
+
/*
|--------------------------------------------------------------------------
| ArrayAccess/Backwards compatibility.
diff --git a/plugins/woocommerce/includes/class-wc-tracker.php b/plugins/woocommerce/includes/class-wc-tracker.php
index a7e9c6fc082..bda59973044 100644
--- a/plugins/woocommerce/includes/class-wc-tracker.php
+++ b/plugins/woocommerce/includes/class-wc-tracker.php
@@ -181,15 +181,17 @@ class WC_Tracker {
* @return array
*/
public static function get_theme_info() {
- $theme_data = wp_get_theme();
- $theme_child_theme = wc_bool_to_string( is_child_theme() );
- $theme_wc_support = wc_bool_to_string( current_theme_supports( 'woocommerce' ) );
+ $theme_data = wp_get_theme();
+ $theme_child_theme = wc_bool_to_string( is_child_theme() );
+ $theme_wc_support = wc_bool_to_string( current_theme_supports( 'woocommerce' ) );
+ $theme_is_block_theme = wc_bool_to_string( wc_current_theme_is_fse_theme() );
return array(
'name' => $theme_data->Name, // @phpcs:ignore
'version' => $theme_data->Version, // @phpcs:ignore
'child_theme' => $theme_child_theme,
'wc_support' => $theme_wc_support,
+ 'block_theme' => $theme_is_block_theme,
);
}
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index 5970ed38b90..92a51a6bf57 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -29,7 +29,7 @@ final class WooCommerce {
*
* @var string
*/
- public $version = '6.4.0';
+ public $version = '6.5.0';
/**
* WooCommerce Schema version.
diff --git a/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php b/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php
index 368d284d3bf..c9f2a689192 100644
--- a/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php
+++ b/plugins/woocommerce/includes/react-admin/wc-admin-update-functions.php
@@ -7,11 +7,9 @@
* @package WooCommerce\Admin
*/
-use Automattic\WooCommerce\Internal\Admin\Install as Installer;
use \Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists;
use \Automattic\WooCommerce\Admin\Notes\Notes;
use \Automattic\WooCommerce\Internal\Admin\Notes\UnsecuredReportFiles;
-use \Automattic\WooCommerce\Internal\Admin\Notes\DeactivatePlugin;
use \Automattic\WooCommerce\Admin\ReportExporter;
/**
@@ -40,13 +38,6 @@ function wc_admin_update_0201_order_status_index() {
$wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->prefix}wc_order_stats ADD INDEX status (status(%d))", $max_index_length ) );
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_0201_db_version() {
- Installer::update_db_version( '0.20.1' );
-}
-
/**
* Rename "gross_total" to "total_sales".
* See: https://github.com/woocommerce/woocommerce-admin/issues/3175
@@ -60,13 +51,6 @@ function wc_admin_update_0230_rename_gross_total() {
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_order_stats CHANGE COLUMN `gross_total` `total_sales` double DEFAULT 0 NOT NULL" );
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_0230_db_version() {
- Installer::update_db_version( '0.23.0' );
-}
-
/**
* Remove the note unsnoozing scheduled action.
*/
@@ -75,13 +59,6 @@ function wc_admin_update_0251_remove_unsnooze_action() {
as_unschedule_action( Notes::UNSNOOZE_HOOK, null, 'wc-admin-notes' );
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_0251_db_version() {
- Installer::update_db_version( '0.25.1' );
-}
-
/**
* Remove Facebook Extension note.
*/
@@ -89,13 +66,6 @@ function wc_admin_update_110_remove_facebook_note() {
Notes::delete_notes_with_name( 'wc-admin-facebook-extension' );
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_110_db_version() {
- Installer::update_db_version( '1.1.0' );
-}
-
/**
* Remove Dismiss action from tracking opt-in admin note.
*/
@@ -106,21 +76,13 @@ function wc_admin_update_130_remove_dismiss_action_from_tracking_opt_in_note() {
}
/**
+
* Update DB Version.
*/
function wc_admin_update_130_db_version() {
Installer::update_db_version( '1.3.0' );
}
-/**
- * Change the deactivate plugin note type to 'info'.
- */
-function wc_admin_update_140_change_deactivate_plugin_note_type() {
- global $wpdb;
-
- $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->prefix}wc_admin_notes SET type = 'info' WHERE name = %s", DeactivatePlugin::NOTE_NAME ) );
-}
-
/**
* Update DB Version.
*/
@@ -135,13 +97,6 @@ function wc_admin_update_160_remove_facebook_note() {
Notes::delete_notes_with_name( 'wc-admin-facebook-marketing-expert' );
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_160_db_version() {
- Installer::update_db_version( '1.6.0' );
-}
-
/**
* Set "two column" homescreen layout as default for existing stores.
*/
@@ -149,13 +104,6 @@ function wc_admin_update_170_homescreen_layout() {
add_option( 'woocommerce_default_homepage_layout', 'two_columns', '', 'no' );
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_170_db_version() {
- Installer::update_db_version( '1.7.0' );
-}
-
/**
* Delete the preexisting export files.
*/
@@ -241,13 +189,6 @@ function wc_admin_update_270_delete_report_downloads() {
}
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_270_db_version() {
- Installer::update_db_version( '2.7.0' );
-}
-
/**
* Update the old task list options.
*/
@@ -267,13 +208,6 @@ function wc_admin_update_271_update_task_list_options() {
delete_option( 'woocommerce_extended_task_list_hidden' );
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_271_db_version() {
- Installer::update_db_version( '2.7.1' );
-}
-
/**
* Update order stats `status`.
*/
@@ -289,13 +223,6 @@ function wc_admin_update_280_order_status() {
);
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_280_db_version() {
- Installer::update_db_version( '2.8.0' );
-}
-
/**
* Update the old task list options.
*/
@@ -317,13 +244,6 @@ function wc_admin_update_290_delete_default_homepage_layout_option() {
delete_option( 'woocommerce_default_homepage_layout' );
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_290_db_version() {
- Installer::update_db_version( '2.9.0' );
-}
-
/**
* Use woocommerce_admin_activity_panel_inbox_last_read from the user meta to set wc_admin_notes.is_read col.
*/
@@ -349,15 +269,6 @@ function wc_admin_update_300_update_is_read_from_last_read() {
}
}
-/**
- * Update DB Version.
- */
-function wc_admin_update_300_db_version() {
- Installer::update_db_version( '3.0.0' );
-}
-
-
-
/**
* Delete "is_primary" column from the wc_admin_notes table.
*/
@@ -365,10 +276,3 @@ function wc_admin_update_340_remove_is_primary_from_note_action() {
global $wpdb;
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_admin_note_actions DROP COLUMN `is_primary`" );
}
-
-/**
- * Update DB Version.
- */
-function wc_admin_update_340_db_version() {
- Installer::update_db_version( '3.4.0' );
-}
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
index e560a127026..8bda913c60a 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
@@ -258,6 +258,25 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
// Expand meta_data to include user-friendly values.
$formatted_meta_data = $item->get_all_formatted_meta_data( null );
+
+ // Filter out product variations.
+ if ( isset( $product ) && 'true' === $this->request['order_item_display_meta'] ) {
+ $order_item_name = $data['name'];
+ $data['meta_data'] = array_filter(
+ $data['meta_data'],
+ function( $meta ) use ( $product, $order_item_name ) {
+ $display_value = wp_kses_post( rawurldecode( (string) $meta->value ) );
+
+ // Skip items with values already in the product details area of the product name.
+ if ( $product && $product->is_type( 'variation' ) && wc_is_attribute_in_product_name( $display_value, $order_item_name ) ) {
+ return false;
+ }
+
+ return true;
+ }
+ );
+ }
+
$data['meta_data'] = array_map(
array( $this, 'merge_meta_item_with_formatted_meta_display_attributes' ),
$data['meta_data'],
@@ -1879,6 +1898,13 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
);
+ $params['order_item_display_meta'] = array(
+ 'default' => false,
+ 'description' => __( 'Only show meta which is meant to be displayed for an order.', 'woocommerce' ),
+ 'type' => 'boolean',
+ 'sanitize_callback' => 'rest_sanitize_boolean',
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
return $params;
}
diff --git a/plugins/woocommerce/includes/tracks/class-wc-site-tracking.php b/plugins/woocommerce/includes/tracks/class-wc-site-tracking.php
index e924d250448..d2bb5297688 100644
--- a/plugins/woocommerce/includes/tracks/class-wc-site-tracking.php
+++ b/plugins/woocommerce/includes/tracks/class-wc-site-tracking.php
@@ -161,6 +161,7 @@ class WC_Site_Tracking {
include_once WC_ABSPATH . 'includes/tracks/events/class-wc-coupons-tracking.php';
include_once WC_ABSPATH . 'includes/tracks/events/class-wc-order-tracking.php';
include_once WC_ABSPATH . 'includes/tracks/events/class-wc-coupon-tracking.php';
+ include_once WC_ABSPATH . 'includes/tracks/events/class-wc-theme-tracking.php';
$tracking_classes = array(
'WC_Extensions_Tracking',
@@ -172,6 +173,7 @@ class WC_Site_Tracking {
'WC_Coupons_Tracking',
'WC_Order_Tracking',
'WC_Coupon_Tracking',
+ 'WC_Theme_Tracking',
);
foreach ( $tracking_classes as $tracking_class ) {
diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php
new file mode 100644
index 00000000000..51e23a7fceb
--- /dev/null
+++ b/plugins/woocommerce/includes/tracks/events/class-wc-theme-tracking.php
@@ -0,0 +1,56 @@
+track_initial_theme();
+ add_action( 'switch_theme', array( $this, 'track_activated_theme' ) );
+ }
+
+ /**
+ * Tracks the sites current theme the first time this code is run, and will only be run once.
+ */
+ public function track_initial_theme() {
+ $has_been_initially_tracked = get_option( 'wc_has_tracked_default_theme' );
+
+ if ( $has_been_initially_tracked ) {
+ return;
+ }
+
+ $this->track_activated_theme();
+ add_option( 'wc_has_tracked_default_theme', 1 );
+ }
+
+ /**
+ * Send a Tracks event when a theme is activated so that we can track active block themes.
+ */
+ public function track_activated_theme() {
+ $is_block_theme = false;
+ $theme_object = wp_get_theme();
+
+ if ( function_exists( 'wc_current_theme_is_fse_theme' ) ) {
+ $is_block_theme = wc_current_theme_is_fse_theme();
+ }
+
+ $properties = array(
+ 'block_theme' => $is_block_theme,
+ 'theme_name' => $theme_object->get( 'Name' ),
+ 'theme_version' => $theme_object->get( 'Version' ),
+ );
+
+ WC_Tracks::record_event( 'activated_theme', $properties );
+ }
+}
diff --git a/plugins/woocommerce/includes/wc-core-functions.php b/plugins/woocommerce/includes/wc-core-functions.php
index 83ffafe812e..177dbf2eff3 100644
--- a/plugins/woocommerce/includes/wc-core-functions.php
+++ b/plugins/woocommerce/includes/wc-core-functions.php
@@ -1775,19 +1775,8 @@ function wc_uasort_comparison( $a, $b ) {
* @return int
*/
function wc_ascii_uasort_comparison( $a, $b ) {
- // 'setlocale' is required for compatibility with PHP 8.
- // Without it, 'iconv' will return '?'s instead of transliterated characters.
- $prev_locale = setlocale( LC_CTYPE, 0 );
- setlocale( LC_ALL, 'C.UTF-8' );
-
- // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
- if ( function_exists( 'iconv' ) && defined( 'ICONV_IMPL' ) && @strcasecmp( ICONV_IMPL, 'unknown' ) !== 0 ) {
- $a = @iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $a );
- $b = @iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $b );
- }
- // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
-
- setlocale( LC_ALL, $prev_locale );
+ $a = remove_accents( $a );
+ $b = remove_accents( $b );
return strcmp( $a, $b );
}
diff --git a/plugins/woocommerce/includes/wc-user-functions.php b/plugins/woocommerce/includes/wc-user-functions.php
index 0c30c4f1aeb..a874c92a052 100644
--- a/plugins/woocommerce/includes/wc-user-functions.php
+++ b/plugins/woocommerce/includes/wc-user-functions.php
@@ -614,6 +614,11 @@ function wc_get_customer_available_downloads( $customer_id ) {
$download_file = $_product->get_file( $result->download_id );
+ // If the downloadable file has been disabled (it may be located in an untrusted location) then do not return it.
+ if ( ! $download_file->get_enabled() ) {
+ continue;
+ }
+
// Download name will be 'Product Name' for products with a single downloadable file, and 'Product Name - File X' for products with multiple files.
$download_name = apply_filters(
'woocommerce_downloadable_product_name',
diff --git a/plugins/woocommerce/legacy/css/admin.scss b/plugins/woocommerce/legacy/css/admin.scss
index be221579281..b5fd23b2ae4 100644
--- a/plugins/woocommerce/legacy/css/admin.scss
+++ b/plugins/woocommerce/legacy/css/admin.scss
@@ -5110,6 +5110,14 @@ img.help_tip {
margin: 1px 0;
}
+ &.file_url {
+ /* Reduce the size of this field to make space for a warning asterisk. */
+ input {
+ display: inline-block;
+ width: 96%;
+ }
+ }
+
.upload_file_button {
width: auto;
float: right;
@@ -5160,6 +5168,11 @@ img.help_tip {
color: #333;
}
}
+
+ /* Warning asterisk (indicates if there is a problem with a downloadable file). */
+ span.disabled {
+ color: var( --wc-red );
+ }
}
}
diff --git a/plugins/woocommerce/legacy/js/admin/meta-boxes-product-variation.js b/plugins/woocommerce/legacy/js/admin/meta-boxes-product-variation.js
index f21f41a3066..79d3e7b322b 100644
--- a/plugins/woocommerce/legacy/js/admin/meta-boxes-product-variation.js
+++ b/plugins/woocommerce/legacy/js/admin/meta-boxes-product-variation.js
@@ -350,7 +350,7 @@ jQuery( function( $ ) {
.on( 'click','.downloadable_files a.delete', this.input_changed );
$( document.body )
- .on( 'change', '#variable_product_options .woocommerce_variations :input', this.input_changed )
+ .on( 'change input', '#variable_product_options .woocommerce_variations :input', this.input_changed )
.on( 'change', '.variations-defaults select', this.defaults_changed );
var postForm = $( 'form#post' );
@@ -705,13 +705,18 @@ jQuery( function( $ ) {
/**
* Add new class when have changes in some input
*/
- input_changed: function() {
+ input_changed: function( event ) {
$( this )
.closest( '.woocommerce_variation' )
.addClass( 'variation-needs-update' );
$( 'button.cancel-variation-changes, button.save-variation-changes' ).prop( 'disabled', false );
+ // Do not trigger 'woocommerce_variations_input_changed' for 'input' events for backwards compat.
+ if ( 'input' === event.type && $( this ).is( ':text' ) ) {
+ return;
+ }
+
$( '#variable_product_options' ).trigger( 'woocommerce_variations_input_changed' );
},
diff --git a/plugins/woocommerce/legacy/js/admin/settings.js b/plugins/woocommerce/legacy/js/admin/settings.js
index 702b0ae1a19..8464191f0e3 100644
--- a/plugins/woocommerce/legacy/js/admin/settings.js
+++ b/plugins/woocommerce/legacy/js/admin/settings.js
@@ -1,183 +1,265 @@
/* global woocommerce_settings_params, wp */
-( function( $, params, wp ) {
- $( function() {
+( function ( $, params, wp ) {
+ $( function () {
// Sell Countries
- $( 'select#woocommerce_allowed_countries' ).on( 'change', function() {
- if ( 'specific' === $( this ).val() ) {
- $( this ).closest('tr').next( 'tr' ).hide();
- $( this ).closest('tr').next().next( 'tr' ).show();
- } else if ( 'all_except' === $( this ).val() ) {
- $( this ).closest('tr').next( 'tr' ).show();
- $( this ).closest('tr').next().next( 'tr' ).hide();
- } else {
- $( this ).closest('tr').next( 'tr' ).hide();
- $( this ).closest('tr').next().next( 'tr' ).hide();
- }
- }).trigger( 'change' );
+ $( 'select#woocommerce_allowed_countries' )
+ .on( 'change', function () {
+ if ( 'specific' === $( this ).val() ) {
+ $( this ).closest( 'tr' ).next( 'tr' ).hide();
+ $( this ).closest( 'tr' ).next().next( 'tr' ).show();
+ } else if ( 'all_except' === $( this ).val() ) {
+ $( this ).closest( 'tr' ).next( 'tr' ).show();
+ $( this ).closest( 'tr' ).next().next( 'tr' ).hide();
+ } else {
+ $( this ).closest( 'tr' ).next( 'tr' ).hide();
+ $( this ).closest( 'tr' ).next().next( 'tr' ).hide();
+ }
+ } )
+ .trigger( 'change' );
// Ship Countries
- $( 'select#woocommerce_ship_to_countries' ).on( 'change', function() {
- if ( 'specific' === $( this ).val() ) {
- $( this ).closest('tr').next( 'tr' ).show();
- } else {
- $( this ).closest('tr').next( 'tr' ).hide();
- }
- }).trigger( 'change' );
+ $( 'select#woocommerce_ship_to_countries' )
+ .on( 'change', function () {
+ if ( 'specific' === $( this ).val() ) {
+ $( this ).closest( 'tr' ).next( 'tr' ).show();
+ } else {
+ $( this ).closest( 'tr' ).next( 'tr' ).hide();
+ }
+ } )
+ .trigger( 'change' );
// Stock management
- $( 'input#woocommerce_manage_stock' ).on( 'change', function() {
- if ( $( this ).is(':checked') ) {
- $( this ).closest('tbody').find( '.manage_stock_field' ).closest( 'tr' ).show();
- } else {
- $( this ).closest('tbody').find( '.manage_stock_field' ).closest( 'tr' ).hide();
- }
- }).trigger( 'change' );
+ $( 'input#woocommerce_manage_stock' )
+ .on( 'change', function () {
+ if ( $( this ).is( ':checked' ) ) {
+ $( this )
+ .closest( 'tbody' )
+ .find( '.manage_stock_field' )
+ .closest( 'tr' )
+ .show();
+ } else {
+ $( this )
+ .closest( 'tbody' )
+ .find( '.manage_stock_field' )
+ .closest( 'tr' )
+ .hide();
+ }
+ } )
+ .trigger( 'change' );
// Color picker
$( '.colorpick' )
-
- .iris({
- change: function( event, ui ) {
- $( this ).parent().find( '.colorpickpreview' ).css({ backgroundColor: ui.color.toString() });
+ .iris( {
+ change: function ( event, ui ) {
+ $( this )
+ .parent()
+ .find( '.colorpickpreview' )
+ .css( { backgroundColor: ui.color.toString() } );
},
hide: true,
- border: true
- })
+ border: true,
+ } )
- .on( 'click focus', function( event ) {
+ .on( 'click focus', function ( event ) {
event.stopPropagation();
$( '.iris-picker' ).hide();
$( this ).closest( 'td' ).find( '.iris-picker' ).show();
$( this ).data( 'originalValue', $( this ).val() );
- })
+ } )
- .on( 'change', function() {
+ .on( 'change', function () {
if ( $( this ).is( '.iris-error' ) ) {
var original_value = $( this ).data( 'originalValue' );
- if ( original_value.match( /^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/ ) ) {
- $( this ).val( $( this ).data( 'originalValue' ) ).trigger( 'change' );
+ if (
+ original_value.match(
+ /^\#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
+ )
+ ) {
+ $( this )
+ .val( $( this ).data( 'originalValue' ) )
+ .trigger( 'change' );
} else {
$( this ).val( '' ).trigger( 'change' );
}
}
- });
+ } );
- $( 'body' ).on( 'click', function() {
+ $( 'body' ).on( 'click', function () {
$( '.iris-picker' ).hide();
- });
+ } );
// Edit prompt
- $( function() {
+ $( function () {
var changed = false;
+ let $check_column = $( '.wp-list-table .check-column' );
+
+ $( 'input, textarea, select, checkbox' ).on( 'change', function (
+ event
+ ) {
+ // Toggling WP List Table checkboxes should not trigger navigation warnings.
+ if (
+ $check_column.length &&
+ $check_column.has( event.target )
+ ) {
+ return;
+ }
- $( 'input, textarea, select, checkbox' ).on( 'change', function() {
if ( ! changed ) {
- window.onbeforeunload = function() {
+ window.onbeforeunload = function () {
return params.i18n_nav_warning;
};
changed = true;
}
- });
+ } );
- $( '.submit :input' ).on( 'click', function() {
- window.onbeforeunload = '';
- });
- });
+ $( '.submit :input, input#search-submit' ).on(
+ 'click',
+ function () {
+ window.onbeforeunload = '';
+ }
+ );
+ } );
// Sorting
- $( 'table.wc_gateways tbody, table.wc_shipping tbody' ).sortable({
+ $( 'table.wc_gateways tbody, table.wc_shipping tbody' ).sortable( {
items: 'tr',
cursor: 'move',
axis: 'y',
handle: 'td.sort',
scrollSensitivity: 40,
- helper: function( event, ui ) {
- ui.children().each( function() {
+ helper: function ( event, ui ) {
+ ui.children().each( function () {
$( this ).width( $( this ).width() );
- });
+ } );
ui.css( 'left', '0' );
return ui;
},
- start: function( event, ui ) {
+ start: function ( event, ui ) {
ui.item.css( 'background-color', '#f6f6f6' );
},
- stop: function( event, ui ) {
+ stop: function ( event, ui ) {
ui.item.removeAttr( 'style' );
ui.item.trigger( 'updateMoveButtons' );
- }
- });
+ },
+ } );
// Select all/none
- $( '.woocommerce' ).on( 'click', '.select_all', function() {
- $( this ).closest( 'td' ).find( 'select option' ).prop( 'selected', true );
+ $( '.woocommerce' ).on( 'click', '.select_all', function () {
+ $( this )
+ .closest( 'td' )
+ .find( 'select option' )
+ .prop( 'selected', true );
$( this ).closest( 'td' ).find( 'select' ).trigger( 'change' );
return false;
- });
+ } );
- $( '.woocommerce' ).on( 'click', '.select_none', function() {
- $( this ).closest( 'td' ).find( 'select option' ).prop( 'selected', false );
+ $( '.woocommerce' ).on( 'click', '.select_none', function () {
+ $( this )
+ .closest( 'td' )
+ .find( 'select option' )
+ .prop( 'selected', false );
$( this ).closest( 'td' ).find( 'select' ).trigger( 'change' );
return false;
- });
+ } );
// Re-order buttons.
- $( '.wc-item-reorder-nav').find( '.wc-move-up, .wc-move-down' ).on( 'click', function() {
- var moveBtn = $( this ),
- $row = moveBtn.closest( 'tr' );
+ $( '.wc-item-reorder-nav' )
+ .find( '.wc-move-up, .wc-move-down' )
+ .on( 'click', function () {
+ var moveBtn = $( this ),
+ $row = moveBtn.closest( 'tr' );
- moveBtn.trigger( 'focus' );
+ moveBtn.trigger( 'focus' );
- var isMoveUp = moveBtn.is( '.wc-move-up' ),
- isMoveDown = moveBtn.is( '.wc-move-down' );
+ var isMoveUp = moveBtn.is( '.wc-move-up' ),
+ isMoveDown = moveBtn.is( '.wc-move-down' );
- if ( isMoveUp ) {
- var $previewRow = $row.prev( 'tr' );
+ if ( isMoveUp ) {
+ var $previewRow = $row.prev( 'tr' );
- if ( $previewRow && $previewRow.length ) {
- $previewRow.before( $row );
- wp.a11y.speak( params.i18n_moved_up );
+ if ( $previewRow && $previewRow.length ) {
+ $previewRow.before( $row );
+ wp.a11y.speak( params.i18n_moved_up );
+ }
+ } else if ( isMoveDown ) {
+ var $nextRow = $row.next( 'tr' );
+
+ if ( $nextRow && $nextRow.length ) {
+ $nextRow.after( $row );
+ wp.a11y.speak( params.i18n_moved_down );
+ }
}
- } else if ( isMoveDown ) {
- var $nextRow = $row.next( 'tr' );
- if ( $nextRow && $nextRow.length ) {
- $nextRow.after( $row );
- wp.a11y.speak( params.i18n_moved_down );
- }
- }
+ moveBtn.trigger( 'focus' ); // Re-focus after the container was moved.
+ moveBtn.closest( 'table' ).trigger( 'updateMoveButtons' );
+ } );
- moveBtn.trigger( 'focus' ); // Re-focus after the container was moved.
- moveBtn.closest( 'table' ).trigger( 'updateMoveButtons' );
- } );
+ $( '.wc-item-reorder-nav' )
+ .closest( 'table' )
+ .on( 'updateMoveButtons', function () {
+ var table = $( this ),
+ lastRow = $( this ).find( 'tbody tr:last' ),
+ firstRow = $( this ).find( 'tbody tr:first' );
- $( '.wc-item-reorder-nav').closest( 'table' ).on( 'updateMoveButtons', function() {
- var table = $( this ),
- lastRow = $( this ).find( 'tbody tr:last' ),
- firstRow = $( this ).find( 'tbody tr:first' );
+ table
+ .find( '.wc-item-reorder-nav .wc-move-disabled' )
+ .removeClass( 'wc-move-disabled' )
+ .attr( { tabindex: '0', 'aria-hidden': 'false' } );
+ firstRow
+ .find( '.wc-item-reorder-nav .wc-move-up' )
+ .addClass( 'wc-move-disabled' )
+ .attr( { tabindex: '-1', 'aria-hidden': 'true' } );
+ lastRow
+ .find( '.wc-item-reorder-nav .wc-move-down' )
+ .addClass( 'wc-move-disabled' )
+ .attr( { tabindex: '-1', 'aria-hidden': 'true' } );
+ } );
- table.find( '.wc-item-reorder-nav .wc-move-disabled' ).removeClass( 'wc-move-disabled' )
- .attr( { 'tabindex': '0', 'aria-hidden': 'false' } );
- firstRow.find( '.wc-item-reorder-nav .wc-move-up' ).addClass( 'wc-move-disabled' )
- .attr( { 'tabindex': '-1', 'aria-hidden': 'true' } );
- lastRow.find( '.wc-item-reorder-nav .wc-move-down' ).addClass( 'wc-move-disabled' )
- .attr( { 'tabindex': '-1', 'aria-hidden': 'true' } );
- } );
+ $( '.wc-item-reorder-nav' )
+ .closest( 'table' )
+ .trigger( 'updateMoveButtons' );
- $( '.wc-item-reorder-nav').closest( 'table' ).trigger( 'updateMoveButtons' );
-
-
- $( '.submit button' ).on( 'click', function() {
+ $( '.submit button' ).on( 'click', function () {
if (
- $( 'select#woocommerce_allowed_countries' ).val() === 'specific' &&
+ $( 'select#woocommerce_allowed_countries' ).val() ===
+ 'specific' &&
! $( '[name="woocommerce_specific_allowed_countries[]"]' ).val()
) {
- if ( window.confirm( woocommerce_settings_params.i18n_no_specific_countries_selected ) ) {
+ if (
+ window.confirm(
+ woocommerce_settings_params.i18n_no_specific_countries_selected
+ )
+ ) {
return true;
}
return false;
}
} );
- });
-})( jQuery, woocommerce_settings_params, wp );
+ $( '#settings-other-payment-methods' ).on( 'click', function ( e ) {
+ if (
+ typeof window.wcTracks.recordEvent === 'undefined' &&
+ typeof window.wc.tracks.recordEvent === 'undefined'
+ ) {
+ return;
+ }
+
+ var recordEvent =
+ window.wc.tracks.recordEvent || window.wcTracks.recordEvent;
+
+ var payment_methods = $.map(
+ $(
+ 'td.wc_payment_gateways_wrapper tbody tr[data-gateway_id] '
+ ),
+ function ( tr ) {
+ return $( tr ).attr( 'data-gateway_id' );
+ }
+ );
+
+ recordEvent( 'settings_payments_recommendations_other_options', {
+ available_payment_methods: payment_methods,
+ } );
+ } );
+ } );
+} )( jQuery, woocommerce_settings_params, wp );
diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json
index 1f20ec694d6..ac076b7e508 100644
--- a/plugins/woocommerce/package.json
+++ b/plugins/woocommerce/package.json
@@ -1,7 +1,7 @@
{
"name": "woocommerce",
"title": "WooCommerce",
- "version": "6.4.0",
+ "version": "6.5.0",
"homepage": "https://woocommerce.com/",
"repository": {
"type": "git",
@@ -14,7 +14,8 @@
"scripts": {
"preinstall": "npx only-allow pnpm",
"build": "./bin/build-zip.sh",
- "build:core": "pnpm nx build woocommerce-admin && pnpm nx build woocommerce-legacy-assets && pnpm run makepot",
+ "build:feature-config": "php bin/generate-feature-config.php",
+ "build:core": "WC_ADMIN_PHASE=core pnpm run build:feature-config && pnpm nx build woocommerce-admin && pnpm nx build woocommerce-legacy-assets && pnpm run makepot",
"build:zip": "pnpm run build",
"lint:js": "eslint assets/js --ext=js",
"docker:down": "pnpx wc-e2e docker:down",
@@ -27,8 +28,7 @@
"test:e2e-dev": "pnpx wc-e2e test:e2e-dev",
"test:unit": "./vendor/bin/phpunit -c ./phpunit.xml",
"makepot": "composer run-script makepot",
- "packages:fix:textdomain": "node ./bin/package-update-textdomain.js",
- "git:update-hooks": "rm -r .git/hooks && mkdir -p .git/hooks && node ./node_modules/husky/husky.js install"
+ "packages:fix:textdomain": "node ./bin/package-update-textdomain.js"
},
"devDependencies": {
"@babel/cli": "7.12.8",
@@ -55,15 +55,13 @@
"chai-as-promised": "7.1.1",
"config": "3.3.3",
"cross-env": "6.0.3",
- "deasync": "0.1.21",
+ "deasync": "0.1.26",
"eslint": "6.8.0",
"eslint-config-wpcalypso": "5.0.0",
"eslint-plugin-jest": "23.20.0",
"github-contributors-list": "https://github.com/woocommerce/github-contributors-list/tarball/master",
- "husky": "4.3.0",
"istanbul": "1.0.0-alpha.2",
"jest": "^25.1.0",
- "lint-staged": "9.5.0",
"mocha": "7.2.0",
"prettier": "npm:wp-prettier@2.0.5",
"stylelint": "^13.8.0",
@@ -72,35 +70,19 @@
"webpack-cli": "3.3.12",
"wp-textdomain": "1.0.1"
},
- "engines": {
- "node": "^16.13.1",
- "pnpm": "^6.24.2"
- },
- "husky": {
- "hooks": {
- "post-merge": "./bin/post-merge.sh",
- "pre-commit": "lint-staged",
- "pre-push": "./bin/pre-push.sh"
- }
- },
"lint-staged": {
"*.php": [
"php -d display_errors=1 -l",
"composer run-script phpcs-pre-commit"
],
- "*.scss": [
- "stylelint --syntax=scss --fix",
- "git add"
- ],
- "*.js": [
- "eslint --fix",
- "git add"
- ],
- "*.ts": [
- "eslint --fix",
- "git add"
+ "!(*min).js": [
+ "eslint --fix"
]
},
+ "engines": {
+ "node": "^16.13.1",
+ "pnpm": "^6.24.2"
+ },
"browserslist": [
"> 0.1%",
"ie 8",
diff --git a/plugins/woocommerce/project.json b/plugins/woocommerce/project.json
index 9fbc742dca1..89df485f665 100644
--- a/plugins/woocommerce/project.json
+++ b/plugins/woocommerce/project.json
@@ -57,7 +57,7 @@
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
- "pnpm nx build:feature-config woocommerce-admin",
+ "pnpm nx build:feature-config woocommerce",
"pnpm nx watch-assets woocommerce"
]
}
@@ -142,12 +142,6 @@
"script": "packages:fix:textdomain"
}
},
- "git-update-hooks": {
- "executor": "@nrwl/workspace:run-script",
- "options": {
- "script": "git:update-hooks"
- }
- },
"make-collection": {
"executor": "@nrwl/workspace:run-script",
"options": {
diff --git a/plugins/woocommerce/readme.txt b/plugins/woocommerce/readme.txt
index 5fe323beb36..63cfa9ee1dc 100644
--- a/plugins/woocommerce/readme.txt
+++ b/plugins/woocommerce/readme.txt
@@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
Requires at least: 5.7
Tested up to: 5.9
Requires PHP: 7.0
-Stable tag: 6.3.0
+Stable tag: 6.4.0
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -160,6 +160,6 @@ WooCommerce comes with some sample data you can use to see how products look; im
== Changelog ==
-= 6.4.0 2022-XX-XX =
+= 6.5.0 2022-XX-XX =
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/changelog.txt).
diff --git a/plugins/woocommerce/src/Admin/API/Reports/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/DataStore.php
index aebda100324..bf2a95cf2ac 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/DataStore.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/DataStore.php
@@ -577,7 +577,7 @@ class DataStore extends SqlQuery {
*/
protected static function get_excluded_report_order_statuses() {
$excluded_statuses = \WC_Admin_Settings::get_option( 'woocommerce_excluded_report_order_statuses', array( 'pending', 'failed', 'cancelled' ) );
- $excluded_statuses = array_merge( array( 'trash' ), array_map( 'esc_sql', $excluded_statuses ) );
+ $excluded_statuses = array_merge( array( 'auto-draft', 'trash' ), array_map( 'esc_sql', $excluded_statuses ) );
return apply_filters( 'woocommerce_analytics_excluded_order_statuses', $excluded_statuses );
}
diff --git a/plugins/woocommerce/src/Admin/Composer/Package.php b/plugins/woocommerce/src/Admin/Composer/Package.php
index 9d12915973c..a4ed70bac7f 100644
--- a/plugins/woocommerce/src/Admin/Composer/Package.php
+++ b/plugins/woocommerce/src/Admin/Composer/Package.php
@@ -11,7 +11,6 @@ namespace Automattic\WooCommerce\Admin\Composer;
defined( 'ABSPATH' ) || exit;
-use Automattic\WooCommerce\Internal\Admin\Notes\DeactivatePlugin;
use Automattic\WooCommerce\Admin\Notes\Notes;
use Automattic\WooCommerce\Admin\Notes\NotesUnavailableException;
use Automattic\WooCommerce\Internal\Admin\FeaturePlugin;
@@ -51,21 +50,10 @@ class Package {
// Avoid double initialization when the feature plugin is in use.
if ( defined( 'WC_ADMIN_VERSION_NUMBER' ) ) {
self::$active_version = WC_ADMIN_VERSION_NUMBER;
-
- // Check version after WooCommerce is initialized.
- add_action( 'woocommerce_init', array( __CLASS__, 'check_outdated_wca_plugin' ) );
-
- // Register a deactivation hook for the feature plugin.
- register_deactivation_hook( WC_ADMIN_PLUGIN_FILE, array( __CLASS__, 'on_deactivation' ) );
-
return;
}
$feature_plugin_instance = FeaturePlugin::instance();
- $satisfied_dependencies = is_callable( array( $feature_plugin_instance, 'has_satisfied_dependencies' ) ) && $feature_plugin_instance->has_satisfied_dependencies();
- if ( ! $satisfied_dependencies ) {
- return;
- }
// Indicate to the feature plugin that the core package exists.
if ( ! defined( 'WC_ADMIN_PACKAGE_EXISTS' ) ) {
@@ -116,39 +104,6 @@ class Package {
return dirname( __DIR__ );
}
- /**
- * Add deactivation hook for versions of the plugin that don't have the deactivation note.
- */
- public static function on_deactivation() {
- if ( ! self::is_notes_initialized() ) {
- return;
- }
-
- $update_version = new DeactivatePlugin();
- $update_version::delete_note();
- }
-
- /**
- * Checks if embedded WCA version is newer than standalone WCA
- * and adds/removes DeactivatePlugin note as necessary.
- */
- public static function check_outdated_wca_plugin() {
-
- if ( ! self::is_notes_initialized() ) {
- return;
- }
-
- $update_version = new DeactivatePlugin();
-
- if ( version_compare( WC_ADMIN_VERSION_NUMBER, self::VERSION, '<' ) ) {
- if ( method_exists( $update_version, 'possibly_add_note' ) ) {
- $update_version::possibly_add_note();
- }
- } else {
- $update_version::delete_note();
- }
- }
-
/**
* Checks if notes have been initialized.
*/
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php
index bd5670edff2..60ffbadb185 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/DeprecatedOptions.php
@@ -6,7 +6,7 @@
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\TaskList;
-use Automattic\WooCommerce\Internal\Admin\Install;
+use WC_Install;
/**
* DeprecatedOptions class.
@@ -30,9 +30,9 @@ class DeprecatedOptions {
* @return string
*/
public static function get_deprecated_options( $pre_option, $option ) {
- if ( Install::is_installing() ) {
+ if ( defined( 'WC_INSTALLING' ) && WC_INSTALLING === true ) {
return $pre_option;
- };
+ }
$hidden = get_option( 'woocommerce_task_list_hidden_lists', array() );
switch ( $option ) {
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php
index f6300aa5ea1..6285b7f5d6b 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Task.php
@@ -358,10 +358,6 @@ abstract class Task {
* Track task completion if task is viewable.
*/
public function possibly_track_completion() {
- if ( ! $this->can_view() ) {
- return;
- }
-
if ( ! $this->is_complete() ) {
return;
}
@@ -407,6 +403,15 @@ abstract class Task {
return true;
}
+ /**
+ * Check if task is disabled.
+ *
+ * @return bool
+ */
+ public function is_disabled() {
+ return false;
+ }
+
/**
* Check if the task is complete.
*
@@ -455,6 +460,7 @@ abstract class Task {
'isSnoozed' => $this->is_snoozed(),
'isSnoozeable' => $this->is_snoozeable(),
'isVisited' => $this->is_visited(),
+ 'isDisabled' => $this->is_disabled(),
'snoozedUntil' => $this->get_snoozed_until(),
'additionalData' => self::convert_object_to_camelcase( $this->get_additional_data() ),
'eventPrefix' => $this->prefix_event( '' ),
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php
index ddfa282d70e..4053546fc9a 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskList.php
@@ -96,6 +96,20 @@ class TaskList {
*/
public $options = array();
+ /**
+ * Array of TaskListSection.
+ *
+ * @var array
+ */
+ private $sections = array();
+
+ /**
+ * Key value map of task class and id used for sections.
+ *
+ * @var array
+ */
+ public $task_class_id_map = array();
+
/**
* Constructor
*
@@ -112,6 +126,7 @@ class TaskList {
'options' => array(),
'visible' => true,
'display_progress_header' => false,
+ 'sections' => array(),
);
$data = wp_parse_args( $data, $defaults );
@@ -132,6 +147,12 @@ class TaskList {
}
$this->possibly_remove_reminder_bar();
+ $this->sections = array_map(
+ function( $section ) {
+ return new TaskListSection( $section, $this );
+ },
+ $data['sections']
+ );
}
/**
@@ -243,7 +264,9 @@ class TaskList {
return;
}
- $this->tasks[] = $task;
+ $task_class_name = substr( get_class( $task ), strrpos( get_class( $task ), '\\' ) + 1 );
+ $this->task_class_id_map[ $task_class_name ] = $task->get_id();
+ $this->tasks[] = $task;
}
/**
@@ -279,6 +302,15 @@ class TaskList {
);
}
+ /**
+ * Get task list sections.
+ *
+ * @return array
+ */
+ public function get_sections() {
+ return $this->sections;
+ }
+
/**
* Track list completion of viewable tasks.
*/
@@ -329,6 +361,15 @@ class TaskList {
return $this->get_list_id() . '_tasklist_' . $event_name;
}
+ /**
+ * Returns option to keep completed task list.
+ *
+ * @return string
+ */
+ public function get_keep_completed_task_list() {
+ return get_option( 'woocommerce_task_list_keep_completed', 'no' );
+ }
+
/**
* Remove reminder bar four weeks after store creation.
*/
@@ -350,20 +391,30 @@ class TaskList {
*/
public function get_json() {
$this->possibly_track_completion();
+ $tasks_json = array();
+ foreach ( $this->tasks as $task ) {
+ $json = $task->get_json();
+ if ( $json['canView'] ) {
+ $tasks_json[] = $json;
+ }
+ }
+
return array(
'id' => $this->get_list_id(),
'title' => $this->title,
'isHidden' => $this->is_hidden(),
'isVisible' => $this->is_visible(),
'isComplete' => $this->is_complete(),
- 'tasks' => array_map(
- function( $task ) {
- return $task->get_json();
- },
- $this->get_viewable_tasks()
- ),
+ 'tasks' => $tasks_json,
'eventPrefix' => $this->prefix_event( '' ),
'displayProgressHeader' => $this->display_progress_header,
+ 'keepCompletedTaskList' => $this->get_keep_completed_task_list(),
+ 'sections' => array_map(
+ function( $section ) {
+ return $section->get_json();
+ },
+ $this->sections
+ ),
);
}
}
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskListSection.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskListSection.php
new file mode 100644
index 00000000000..bc343781dd3
--- /dev/null
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskListSection.php
@@ -0,0 +1,122 @@
+ '',
+ 'title' => '',
+ 'description' => '',
+ 'image' => '',
+ 'tasks' => array(),
+ );
+
+ $data = wp_parse_args( $data, $defaults );
+
+ $this->task_list = $task_list;
+ $this->id = $data['id'];
+ $this->title = $data['title'];
+ $this->description = $data['description'];
+ $this->image = $data['image'];
+ $this->task_names = $data['task_names'];
+ }
+
+ /**
+ * Returns if section is complete.
+ *
+ * @return boolean;
+ */
+ private function is_complete() {
+ $complete = true;
+ foreach ( $this->task_names as $task_name ) {
+ if ( null !== $this->task_list && isset( $this->task_list->task_class_id_map[ $task_name ] ) ) {
+ $task = $this->task_list->get_task( $this->task_list->task_class_id_map[ $task_name ] );
+ if ( $task->can_view() && ! $task->is_complete() ) {
+ $complete = false;
+ break;
+ }
+ }
+ }
+ return $complete;
+ }
+
+ /**
+ * Get the list for use in JSON.
+ *
+ * @return array
+ */
+ public function get_json() {
+ return array(
+ 'id' => $this->id,
+ 'title' => $this->title,
+ 'description' => $this->description,
+ 'image' => $this->image,
+ 'tasks' => array_map(
+ function( $task_name ) {
+ if ( null !== $this->task_list && isset( $this->task_list->task_class_id_map[ $task_name ] ) ) {
+ return $this->task_list->task_class_id_map[ $task_name ];
+ }
+ return '';
+ },
+ $this->task_names
+ ),
+ 'isComplete' => $this->is_complete(),
+ );
+ }
+}
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php
index 436adcff3ff..b50b78455ac 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/TaskLists.php
@@ -70,6 +70,34 @@ class TaskLists {
self::init_default_lists();
add_action( 'admin_init', array( __CLASS__, 'set_active_task' ), 5 );
add_action( 'init', array( __CLASS__, 'init_tasks' ) );
+ add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'task_list_preloaded_settings' ), 20 );
+ }
+
+ /**
+ * Check if an experiment is the treatment or control.
+ *
+ * @param string $name Name prefix of experiment.
+ * @return bool
+ */
+ public static function is_experiment_treatment( $name ) {
+ $anon_id = isset( $_COOKIE['tk_ai'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['tk_ai'] ) ) : '';
+ $allow_tracking = 'yes' === get_option( 'woocommerce_allow_tracking' );
+ $abtest = new \WooCommerce\Admin\Experimental_Abtest(
+ $anon_id,
+ 'woocommerce',
+ $allow_tracking
+ );
+
+ $date = new \DateTime();
+ $date->setTimeZone( new \DateTimeZone( 'UTC' ) );
+
+ $experiment_name = sprintf(
+ '%s_%s_%s',
+ $name,
+ $date->format( 'Y' ),
+ $date->format( 'm' )
+ );
+ return $abtest->get_variation( $experiment_name ) === 'treatment';
}
/**
@@ -92,7 +120,8 @@ class TaskLists {
'Appearance',
),
'event_prefix' => 'tasklist_',
- 'visible' => ! Features::is_enabled( 'tasklist-setup-experiment-1' ),
+ 'visible' => ! self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_1' )
+ && ! self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_2' ),
)
);
@@ -116,7 +145,65 @@ class TaskLists {
'options' => array(
'use_completed_title' => true,
),
- 'visible' => Features::is_enabled( 'tasklist-setup-experiment-1' ),
+ 'visible' => self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_1' ),
+ )
+ );
+
+ self::add_list(
+ array(
+ 'id' => 'setup_experiment_2',
+ 'hidden_id' => 'setup',
+ 'title' => __( 'Get ready to start selling', 'woocommerce' ),
+ 'tasks' => array(
+ 'StoreCreation',
+ 'StoreDetails',
+ 'Products',
+ 'WooCommercePayments',
+ 'Payments',
+ 'Tax',
+ 'Shipping',
+ 'Marketing',
+ 'Appearance',
+ ),
+ 'event_prefix' => 'tasklist_',
+ 'visible' => self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_2' )
+ && ! self::is_experiment_treatment( 'woocommerce_tasklist_setup_experiment_1' ),
+ 'options' => array(
+ 'use_completed_title' => true,
+ ),
+ 'display_progress_header' => true,
+ 'sections' => array(
+ array(
+ 'id' => 'basics',
+ 'title' => __( 'Cover the basics', 'woocommerce' ),
+ 'description' => __( 'Make sure youāve got everything you need to start sellingāfrom business details to products.', 'woocommerce' ),
+ 'image' => plugins_url(
+ '/assets/images/task_list/basics-section-illustration.png',
+ WC_ADMIN_PLUGIN_FILE
+ ),
+ 'task_names' => array( 'StoreCreation', 'StoreDetails', 'Products', 'Payments', 'WooCommercePayments' ),
+ ),
+ array(
+ 'id' => 'sales',
+ 'title' => __( 'Get ready to sell', 'woocommerce' ),
+ 'description' => __( 'Easily set up the backbone of your storeās operations and get ready to accept first orders.', 'woocommerce' ),
+ 'image' => plugins_url(
+ '/assets/images/task_list/sales-section-illustration.png',
+ WC_ADMIN_PLUGIN_FILE
+ ),
+ 'task_names' => array( 'Shipping', 'Tax' ),
+ ),
+ array(
+ 'id' => 'expand',
+ 'title' => __( 'Customize & expand', 'woocommerce' ),
+ 'description' => __( 'Personalize your storeās design and grow your business by enabling new sales channels.', 'woocommerce' ),
+ 'image' => plugins_url(
+ '/assets/images/task_list/expand-section-illustration.png',
+ WC_ADMIN_PLUGIN_FILE
+ ),
+ 'task_names' => array( 'Appearance', 'Marketing' ),
+ ),
+ ),
)
);
@@ -321,7 +408,7 @@ class TaskLists {
return array_filter(
self::get_lists(),
function ( $task_list ) {
- return ! $task_list->is_hidden();
+ return $task_list->is_visible();
}
);
}
@@ -373,4 +460,17 @@ class TaskLists {
return null;
}
+
+ /**
+ * Add visible list ids to component settings.
+ *
+ * @param array $settings Component settings.
+ *
+ * @return array
+ */
+ public static function task_list_preloaded_settings( $settings ) {
+ $settings['visibleTaskListIds'] = array_keys( self::get_visible() );
+
+ return $settings;
+ }
}
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/AdditionalPayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/AdditionalPayments.php
index 6277a0acd55..e479c4292db 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/AdditionalPayments.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/AdditionalPayments.php
@@ -73,8 +73,11 @@ class AdditionalPayments extends Payments {
$woocommerce_payments = new WooCommercePayments();
- if ( ! $woocommerce_payments->is_requested() || ( $woocommerce_payments->is_supported() && ! $woocommerce_payments->is_connected() ) ) {
- // Hide task if WC Pay is installed via OBW, in supported country, but not connected.
+ if ( ! $woocommerce_payments->is_requested() || ! $woocommerce_payments->is_supported() || ! $woocommerce_payments->is_connected() ) {
+ // Hide task if WC Pay is not installed via OBW, or is not connected, or the store is located in a country that is not supported by WC Pay.
+ return false;
+ }
+ if ( $this->get_parent_id() === 'extended_two_column' && WooCommercePayments::is_connected() ) {
return false;
}
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php
index 21db5d4aa30..36f4fd1b752 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Appearance.php
@@ -39,6 +39,9 @@ class Appearance extends Task {
* @return string
*/
public function get_title() {
+ if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
+ return __( 'Make your store stand out with unique design', 'woocommerce' );
+ }
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
if ( $this->is_complete() ) {
return __( 'You personalized your store', 'woocommerce' );
@@ -54,6 +57,9 @@ class Appearance extends Task {
* @return string
*/
public function get_content() {
+ if ( count( $this->task_list->get_sections() ) > 0 ) {
+ return __( 'Upload your logo to adapt the store to your brandās personality.', 'woocommerce' );
+ }
return __(
'Add your logo, create a homepage, and start designing your store.',
'woocommerce'
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Marketing.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Marketing.php
index 59dc41f51c0..81a35205140 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Marketing.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Marketing.php
@@ -25,6 +25,9 @@ class Marketing extends Task {
* @return string
*/
public function get_title() {
+ if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
+ return __( 'Grow your business with marketing tools', 'woocommerce' );
+ }
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
if ( $this->is_complete() ) {
return __( 'You added sales channels', 'woocommerce' );
@@ -40,6 +43,9 @@ class Marketing extends Task {
* @return string
*/
public function get_content() {
+ if ( count( $this->task_list->get_sections() ) > 0 ) {
+ return __( 'Promote your store in other sales channels, like email, Google, and Facebook.', 'woocommerce' );
+ }
return __(
'Add recommended marketing tools to reach new customers and grow your business',
'woocommerce'
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Payments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Payments.php
index 9d7029f74f0..6cd523c6f63 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Payments.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Payments.php
@@ -25,6 +25,9 @@ class Payments extends Task {
* @return string
*/
public function get_title() {
+ if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
+ return __( 'Add a way to get paid', 'woocommerce' );
+ }
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
if ( $this->is_complete() ) {
return __( 'You set up payments', 'woocommerce' );
@@ -40,6 +43,9 @@ class Payments extends Task {
* @return string
*/
public function get_content() {
+ if ( count( $this->task_list->get_sections() ) > 0 ) {
+ return __( 'Let your customers pay the way they like.', 'woocommerce' );
+ }
return __(
'Choose payment providers and enable payment methods at checkout.',
'woocommerce'
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php
index f632fa01ce0..2394ef9b288 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Products.php
@@ -36,6 +36,9 @@ class Products extends Task {
* @return string
*/
public function get_title() {
+ if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
+ return __( 'Create or upload your first products', 'woocommerce' );
+ }
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
if ( $this->is_complete() ) {
return __( 'You added products', 'woocommerce' );
@@ -51,6 +54,9 @@ class Products extends Task {
* @return string
*/
public function get_content() {
+ if ( count( $this->task_list->get_sections() ) > 0 ) {
+ return __( 'Add products to sell and build your catalog.', 'woocommerce' );
+ }
return __(
'Start by adding the first product to your store. You can add your products manually, via CSV, or import them from another service.',
'woocommerce'
@@ -98,7 +104,7 @@ class Products extends Task {
return;
}
- if ( ! $this->is_active() || $this->is_complete() ) {
+ if ( ! $this->is_active() || ! $this->is_complete() ) {
return;
}
@@ -112,6 +118,9 @@ class Products extends Task {
WC_VERSION,
true
);
+
+ // Clear the active task transient to only show notice once per active session.
+ delete_transient( self::ACTIVE_TASK_TRANSIENT );
}
/**
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Shipping.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Shipping.php
index 1a3adb553a6..4af52c248b2 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Shipping.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/Shipping.php
@@ -24,6 +24,9 @@ class Shipping extends Task {
* @return string
*/
public function get_title() {
+ if ( count( $this->task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
+ return __( 'Select how to ship your products', 'woocommerce' );
+ }
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
if ( $this->is_complete() ) {
return __( 'You added shipping costs', 'woocommerce' );
@@ -39,6 +42,9 @@ class Shipping extends Task {
* @return string
*/
public function get_content() {
+ if ( count( $this->task_list->get_sections() ) > 0 ) {
+ return __( 'Set delivery costs and enable extra features, like shipping label printing.', 'woocommerce' );
+ }
return __(
"Set your store location and where you'll ship to.",
'woocommerce'
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/StoreCreation.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/StoreCreation.php
new file mode 100644
index 00000000000..03a178779a5
--- /dev/null
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/StoreCreation.php
@@ -0,0 +1,77 @@
+task_list->get_sections() ) > 0 && ! $this->is_complete() ) {
+ return __( 'Get taxes out of your mind', 'woocommerce' );
+ }
if ( true === $this->get_parent_option( 'use_completed_title' ) ) {
if ( $this->is_complete() ) {
return __( 'You added tax rates', 'woocommerce' );
@@ -80,6 +83,9 @@ class Tax extends Task {
* @return string
*/
public function get_content() {
+ if ( count( $this->task_list->get_sections() ) > 0 ) {
+ return __( 'Have sales tax calculated automatically, or add the rates manually.', 'woocommerce' );
+ }
return self::can_use_automated_taxes()
? __(
'Good news! WooCommerce Services and Jetpack can automate your sales tax calculations for you.',
diff --git a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php
index 55b30784880..051a64dbbaa 100644
--- a/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php
+++ b/plugins/woocommerce/src/Admin/Features/OnboardingTasks/Tasks/WooCommercePayments.php
@@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Automattic\WooCommerce\Admin\PluginsHelper;
+use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\Init as Suggestions;
/**
* WooCommercePayments Task
@@ -19,15 +20,6 @@ class WooCommercePayments extends Task {
return 'woocommerce-payments';
}
- /**
- * Parent ID.
- *
- * @return string
- */
- public function get_parent_id() {
- return 'setup';
- }
-
/**
* Title.
*
@@ -146,22 +138,19 @@ class WooCommercePayments extends Task {
* @return bool
*/
public static function is_supported() {
- return in_array(
- WC()->countries->get_base_country(),
- array(
- 'US',
- 'PR',
- 'AU',
- 'CA',
- 'DE',
- 'ES',
- 'FR',
- 'GB',
- 'IE',
- 'IT',
- 'NZ',
- ),
- true
+ $suggestions = Suggestions::get_suggestions();
+ $suggestion_plugins = array_merge(
+ ...array_filter(
+ array_column( $suggestions, 'plugins' ),
+ function( $plugins ) {
+ return is_array( $plugins );
+ }
+ )
);
+ $woocommerce_payments_ids = array_search( 'woocommerce-payments', $suggestion_plugins, true );
+ if ( false !== $woocommerce_payments_ids ) {
+ return true;
+ }
+ return false;
}
}
diff --git a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php
index 1b1a76e74e2..31b7556793e 100644
--- a/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php
+++ b/plugins/woocommerce/src/Admin/Features/PaymentGatewaySuggestions/DefaultPaymentGateways.php
@@ -25,71 +25,32 @@ class DefaultPaymentGateways {
'id' => 'payfast',
'title' => __( 'PayFast', 'woocommerce' ),
'content' => __( 'The PayFast extension for WooCommerce enables you to accept payments by Credit Card and EFT via one of South Africaās most popular payment gateways. No setup fees or monthly subscription costs. Selecting this extension will configure your store to use South African rands as the selected currency.', 'woocommerce' ),
- 'image' => WC()->plugin_url() . '/assets/images/payfast.png',
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payfast.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/payfast.png',
'plugins' => array( 'woocommerce-payfast-gateway' ),
'is_visible' => array(
- (object) array(
- 'type' => 'base_location_country',
- 'value' => 'ZA',
- 'operation' => '=',
- ),
+ self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array( 'ZA', 'GH', 'NG' ),
+ 'category_additional' => array(),
),
array(
'id' => 'stripe',
'title' => __( ' Stripe', 'woocommerce' ),
'content' => __( 'Accept debit and credit cards in 135+ currencies, methods such as Alipay, and one-touch checkout with Apple Pay.', 'woocommerce' ),
- 'image' => WC()->plugin_url() . '/assets/images/stripe.png',
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/stripe.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/stripe.png',
'plugins' => array( 'woocommerce-gateway-stripe' ),
'is_visible' => array(
// https://stripe.com/global.
self::get_rules_for_countries(
- array(
- 'AU',
- 'AT',
- 'BE',
- 'BG',
- 'BR',
- 'CA',
- 'CY',
- 'CZ',
- 'DK',
- 'EE',
- 'FI',
- 'FR',
- 'DE',
- 'GR',
- 'HK',
- 'IN',
- 'IE',
- 'IT',
- 'JP',
- 'LV',
- 'LT',
- 'LU',
- 'MY',
- 'MT',
- 'MX',
- 'NL',
- 'NZ',
- 'NO',
- 'PL',
- 'PT',
- 'RO',
- 'SG',
- 'SK',
- 'SI',
- 'ES',
- 'SE',
- 'CH',
- 'GB',
- 'US',
- 'PR',
- )
+ array( 'AU', 'AT', 'BE', 'BG', 'BR', 'CA', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HK', 'IN', 'IE', 'IT', 'JP', 'LV', 'LT', 'LU', 'MY', 'MT', 'MX', 'NL', 'NZ', 'NO', 'PL', 'PT', 'RO', 'SG', 'SK', 'SI', 'ES', 'SE', 'CH', 'GB', 'US', 'PR', 'HU', 'SL', 'ID', 'MY', 'SI', 'PR' )
),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array( 'AU', 'AT', 'BE', 'BG', 'BR', 'CA', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HK', 'IN', 'IE', 'IT', 'JP', 'LV', 'LT', 'LU', 'MY', 'MT', 'MX', 'NL', 'NZ', 'NO', 'PL', 'PT', 'RO', 'SG', 'SK', 'SI', 'ES', 'SE', 'CH', 'GB', 'US', 'PR', 'HU', 'SL', 'ID', 'MY', 'SI', 'PR' ),
+ 'category_additional' => array(),
'recommendation_priority' => 3,
),
array(
@@ -97,89 +58,81 @@ class DefaultPaymentGateways {
'title' => __( 'Paystack', 'woocommerce' ),
'content' => __( 'Paystack helps African merchants accept one-time and recurring payments online with a modern, safe, and secure payment gateway.', 'woocommerce' ),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/paystack.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/paystack.png',
'plugins' => array( 'woo-paystack' ),
'is_visible' => array(
self::get_rules_for_countries( array( 'ZA', 'GH', 'NG' ) ),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array( 'ZA', 'GH', 'NG' ),
+ 'category_additional' => array(),
),
array(
'id' => 'kco',
'title' => __( 'Klarna Checkout', 'woocommerce' ),
'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ),
- 'image' => WC()->plugin_url() . '/assets/images/klarna-black.png',
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/klarna-black.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/klarna.png',
'plugins' => array( 'klarna-checkout-for-woocommerce' ),
'is_visible' => array(
self::get_rules_for_countries( array( 'SE', 'FI', 'NO' ) ),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array( 'SE', 'FI', 'NO' ),
+ 'category_additional' => array(),
),
array(
'id' => 'klarna_payments',
'title' => __( 'Klarna Payments', 'woocommerce' ),
'content' => __( 'Choose the payment that you want, pay now, pay later or slice it. No credit card numbers, no passwords, no worries.', 'woocommerce' ),
- 'image' => WC()->plugin_url() . '/assets/images/klarna-black.png',
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/klarna-black.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/klarna.png',
'plugins' => array( 'klarna-payments-for-woocommerce' ),
'is_visible' => array(
self::get_rules_for_countries(
- array(
- 'DK',
- 'DE',
- 'AT',
- 'NL',
- 'CH',
- 'BE',
- 'SP',
- 'PL',
- 'FR',
- 'IT',
- 'GB',
- )
+ array( 'US', 'CA', 'DK', 'DE', 'AT', 'NL', 'CH', 'BE', 'SP', 'PL', 'FR', 'IT', 'GB', 'ES', 'FI', 'NO', 'SE', 'ES', 'FI', 'NO', 'SE' )
),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array(),
+ 'category_additional' => array( 'US', 'CA', 'DK', 'DE', 'AT', 'NL', 'CH', 'BE', 'SP', 'PL', 'FR', 'IT', 'GB', 'ES', 'FI', 'NO', 'SE', 'ES', 'FI', 'NO', 'SE' ),
),
array(
'id' => 'mollie_wc_gateway_banktransfer',
'title' => __( 'Mollie', 'woocommerce' ),
'content' => __( 'Effortless payments by Mollie: Offer global and local payment methods, get onboarded in minutes, and supported in your language.', 'woocommerce' ),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mollie.svg',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/mollie.png',
'plugins' => array( 'mollie-payments-for-woocommerce' ),
'is_visible' => array(
self::get_rules_for_countries(
- array(
- 'FR',
- 'DE',
- 'GB',
- 'AT',
- 'CH',
- 'ES',
- 'IT',
- 'PL',
- 'FI',
- 'NL',
- 'BE',
- )
+ array( 'FR', 'DE', 'GB', 'AT', 'CH', 'ES', 'IT', 'PL', 'FI', 'NL', 'BE' )
),
),
+ 'category_other' => array( 'FR', 'DE', 'GB', 'AT', 'CH', 'ES', 'IT', 'PL', 'FI', 'NL', 'BE' ),
+ 'category_additional' => array(),
),
array(
'id' => 'woo-mercado-pago-custom',
'title' => __( 'Mercado Pago Checkout Pro & Custom', 'woocommerce' ),
'content' => __( 'Accept credit and debit cards, offline (cash or bank transfer) and logged-in payments with money in Mercado Pago. Safe and secure payments with the leading payment processor in LATAM.', 'woocommerce' ),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/mercadopago.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/mercadopago.png',
'plugins' => array( 'woocommerce-mercadopago' ),
'is_visible' => array(
self::get_rules_for_countries( array( 'AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY' ) ),
),
'recommendation_priority' => 2,
'is_local_partner' => true,
+ 'category_other' => array( 'AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY' ),
+ 'category_additional' => array(),
),
array(
'id' => 'ppcp-gateway',
'title' => __( 'PayPal Payments', 'woocommerce' ),
'content' => __( "Safe and secure payments using credit cards or your customer's PayPal account.", 'woocommerce' ),
- 'image' => WC()->plugin_url() . '/assets/images/paypal.png',
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/paypal.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/paypal.png',
'plugins' => array( 'woocommerce-paypal-payments' ),
'is_visible' => array(
(object) array(
@@ -189,24 +142,30 @@ class DefaultPaymentGateways {
),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array( 'US', 'CA', 'AT', 'BE', 'BG', 'HR', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'MX', 'BR', 'AR', 'CL', 'CO', 'EC', 'PE', 'UY', 'VE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'CN', 'ID', 'ZA', 'NG', 'GH' ),
+ 'category_additional' => array( 'US', 'CA', 'AT', 'BE', 'BG', 'HR', 'CH', 'CY', 'CZ', 'DK', 'EE', 'ES', 'FI', 'FR', 'DE', 'GB', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SL', 'SE', 'MX', 'BR', 'AR', 'CL', 'CO', 'EC', 'PE', 'UY', 'VE', 'AU', 'NZ', 'HK', 'JP', 'SG', 'CN', 'ID', 'IN', 'ZA', 'NG', 'GH' ),
),
array(
'id' => 'cod',
'title' => __( 'Cash on delivery', 'woocommerce' ),
'content' => __( 'Take payments in cash upon delivery.', 'woocommerce' ),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/cod.svg',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/cod.png',
'is_visible' => array(
self::get_rules_for_cbd( false ),
),
+ 'is_offline' => true,
),
array(
'id' => 'bacs',
'title' => __( 'Direct bank transfer', 'woocommerce' ),
'content' => __( 'Take payments via bank transfer.', 'woocommerce' ),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/bacs.svg',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/bacs.png',
'is_visible' => array(
self::get_rules_for_cbd( false ),
),
+ 'is_offline' => true,
),
array(
'id' => 'woocommerce_payments',
@@ -216,6 +175,7 @@ class DefaultPaymentGateways {
'woocommerce'
),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg',
'plugins' => array( 'woocommerce-payments' ),
'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies. Track cash flow and manage recurring revenue directly from your storeās dashboard - with no setup costs or monthly fees.',
'is_visible' => array(
@@ -258,6 +218,7 @@ class DefaultPaymentGateways {
'woocommerce'
),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg',
'plugins' => array( 'woocommerce-payments' ),
'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies. Track cash flow and manage recurring revenue directly from your storeās dashboard - with no setup costs or monthly fees.',
'is_visible' => array(
@@ -292,6 +253,7 @@ class DefaultPaymentGateways {
'woocommerce'
),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/wcpay.svg',
'plugins' => array( 'woocommerce-payments' ),
'description' => 'With WooCommerce Payments, you can securely accept major cards, Apple Pay, and payments in over 100 currencies ā with no setup costs or monthly fees ā and you can now accept in-person payments with the Woo mobile app.',
'is_visible' => array(
@@ -323,6 +285,7 @@ class DefaultPaymentGateways {
'title' => __( 'Razorpay', 'woocommerce' ),
'content' => __( 'The official Razorpay extension for WooCommerce allows you to accept credit cards, debit cards, netbanking, wallet, and UPI payments.', 'woocommerce' ),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/razorpay.svg',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/razorpay.png',
'plugins' => array( 'woo-razorpay' ),
'is_visible' => array(
(object) array(
@@ -332,12 +295,15 @@ class DefaultPaymentGateways {
),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array( 'IN' ),
+ 'category_additional' => array(),
),
array(
'id' => 'payubiz',
'title' => __( 'PayU for WooCommerce', 'woocommerce' ),
'content' => __( 'Enable PayUās exclusive plugin for WooCommerce to start accepting payments in 100+ payment methods available in India including credit cards, debit cards, UPI, & more!', 'woocommerce' ),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/payu.svg',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/payu.png',
'plugins' => array( 'payu-india' ),
'is_visible' => array(
(object) array(
@@ -347,23 +313,29 @@ class DefaultPaymentGateways {
),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array( 'IN' ),
+ 'category_additional' => array(),
),
array(
'id' => 'eway',
'title' => __( 'Eway', 'woocommerce' ),
'content' => __( 'The Eway extension for WooCommerce allows you to take credit card payments directly on your store without redirecting your customers to a third party site to make payment.', 'woocommerce' ),
'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/onboarding/eway.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/eway.png',
'plugins' => array( 'woocommerce-gateway-eway' ),
'is_visible' => array(
self::get_rules_for_countries( array( 'AU', 'NZ' ) ),
self::get_rules_for_cbd( false ),
),
+ 'category_other' => array( 'AU', 'NZ' ),
+ 'category_additional' => array(),
),
array(
'id' => 'square_credit_card',
'title' => __( 'Square', 'woocommerce' ),
'content' => __( 'Securely accept credit and debit cards with one low rate, no surprise fees (custom rates available). Sell online and in store and track sales and inventory in one place.', 'woocommerce' ),
- 'image' => WC()->plugin_url() . '/assets/images/square-black.png',
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/square-black.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/square.png',
'plugins' => array( 'woocommerce-square' ),
'is_visible' => array(
(object) array(
@@ -374,12 +346,54 @@ class DefaultPaymentGateways {
self::get_rules_for_cbd( true ),
),
array(
- self::get_rules_for_countries( array( 'US', 'CA', 'JP', 'GB', 'AU', 'IE', 'FR', 'ES' ) ),
+ self::get_rules_for_countries( array( 'US', 'CA', 'JP', 'GB', 'AU', 'IE', 'FR', 'ES', 'FI' ) ),
self::get_rules_for_selling_venues( array( 'brick-mortar', 'brick-mortar-other' ) ),
),
),
),
),
+ 'category_other' => array( 'US', 'CA', 'JP', 'GB', 'AU', 'IE', 'FR', 'ES', 'FI' ),
+ 'category_additional' => array(),
+ ),
+ array(
+ 'id' => 'afterpay',
+ 'title' => __( 'Afterpay', 'woocommerce' ),
+ 'content' => __( 'Afterpay allows customers to receive products immediately and pay for purchases over four installments, always interest-free.', 'woocommerce' ),
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/afterpay.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/afterpay.png',
+ 'plugins' => array( 'afterpay-gateway-for-woocommerce' ),
+ 'is_visible' => array(
+ self::get_rules_for_countries( array( 'US', 'CA' ) ),
+ ),
+ 'category_other' => array(),
+ 'category_additional' => array( 'US', 'CA' ),
+ ),
+ array(
+ 'id' => 'amazon_payments_advanced',
+ 'title' => __( 'Amazon Pay', 'woocommerce' ),
+ 'content' => __( 'Enable a familiar, fast checkout for hundreds of millions of active Amazon customers globally.', 'woocommerce' ),
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/amazonpay.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/amazonpay.png',
+ 'plugins' => array( 'woocommerce-gateway-amazon-payments-advanced' ),
+ 'is_visible' => array(
+ self::get_rules_for_countries( array( 'US', 'CA' ) ),
+ ),
+ 'category_other' => array(),
+ 'category_additional' => array( 'US', 'CA' ),
+ ),
+ array(
+ 'id' => 'affirm',
+ 'title' => __( 'Affirm', 'woocommerce' ),
+ 'content' => __( 'Affirmās tailored Buy Now Pay Later programs remove price as a barrier, turning browsers into buyers, increasing average order value, and expanding your customer base.', 'woocommerce' ),
+ 'image' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/affirm.png',
+ 'image_72x72' => WC_ADMIN_IMAGES_FOLDER_URL . '/payment_methods/72x72/affirm.png',
+ 'plugins' => array(),
+ 'external_link' => 'https://woocommerce.com/products/woocommerce-gateway-affirm',
+ 'is_visible' => array(
+ self::get_rules_for_countries( array( 'US', 'CA' ) ),
+ ),
+ 'category_other' => array(),
+ 'category_additional' => array( 'US', 'CA' ),
),
);
}
diff --git a/plugins/woocommerce/src/Admin/Notes/DataStore.php b/plugins/woocommerce/src/Admin/Notes/DataStore.php
index 0a7abdf954b..49b14e500b3 100644
--- a/plugins/woocommerce/src/Admin/Notes/DataStore.php
+++ b/plugins/woocommerce/src/Admin/Notes/DataStore.php
@@ -11,6 +11,9 @@ defined( 'ABSPATH' ) || exit;
* WC Admin Note Data Store (Custom Tables)
*/
class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Interface {
+ // Extensions should define their own contexts and use them to avoid applying woocommerce_note_where_clauses when not needed.
+ const WC_ADMIN_NOTE_OPER_GLOBAL = 'global';
+
/**
* Method to create a new note in the database.
*
@@ -323,10 +326,11 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter
/**
* Return an ordered list of notes.
*
- * @param array $args Query arguments.
+ * @param array $args Query arguments.
+ * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed.
* @return array An array of objects containing a note id.
*/
- public function get_notes( $args = array() ) {
+ public function get_notes( $args = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) {
global $wpdb;
$defaults = array(
@@ -338,7 +342,7 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter
$args = wp_parse_args( $args, $defaults );
$offset = $args['per_page'] * ( $args['page'] - 1 );
- $where_clauses = $this->get_notes_where_clauses( $args );
+ $where_clauses = $this->get_notes_where_clauses( $args, $context );
$query = $wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
@@ -378,16 +382,18 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter
*
* @param string $type Comma separated list of note types.
* @param string $status Comma separated list of statuses.
+ * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed.
* @return array An array of objects containing a note id.
*/
- public function get_notes_count( $type = array(), $status = array() ) {
+ public function get_notes_count( $type = array(), $status = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) {
global $wpdb;
$where_clauses = $this->get_notes_where_clauses(
array(
'type' => $type,
'status' => $status,
- )
+ ),
+ $context
);
if ( ! empty( $where_clauses ) ) {
@@ -425,10 +431,11 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter
* Applies woocommerce_note_where_clauses filter.
*
* @uses args_to_where_clauses
- * @param array $args Array of args to pass.
+ * @param array $args Array of args to pass.
+ * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed.
* @return string Where clauses for the query.
*/
- public function get_notes_where_clauses( $args = array() ) {
+ public function get_notes_where_clauses( $args = array(), $context = self::WC_ADMIN_NOTE_OPER_GLOBAL ) {
$where_clauses = $this->args_to_where_clauses( $args );
/**
@@ -438,8 +445,9 @@ class DataStore extends \WC_Data_Store_WP implements \WC_Object_Data_Store_Inter
*
* @param string $where_clauses The generated WHERE clause.
* @param array $args The original arguments for the request.
+ * @param string $context Optional argument that the woocommerce_note_where_clauses filter can use to determine whether to apply extra conditions. Extensions should define their own contexts and use them to avoid adding to notes where clauses when not needed.
*/
- return apply_filters( 'woocommerce_note_where_clauses', $where_clauses, $args );
+ return apply_filters( 'woocommerce_note_where_clauses', $where_clauses, $args, $context );
}
/**
diff --git a/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php b/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php
index 972c60cce13..6eafd2debb3 100644
--- a/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php
+++ b/plugins/woocommerce/src/Admin/Notes/DeprecatedNotes.php
@@ -118,27 +118,6 @@ class WC_Admin_Notes_Customize_Store_With_Blocks extends DeprecatedClassFacade {
protected static $deprecated_in_version = '1.7.0';
}
-/**
- * WC_Admin_Notes_Deactivate_Plugin.
- *
- * @deprecated since 1.7.0, use DeactivatePlugin
- */
-class WC_Admin_Notes_Deactivate_Plugin extends DeprecatedClassFacade {
- /**
- * The name of the non-deprecated class that this facade covers.
- *
- * @var string
- */
- protected static $facade_over_classname = 'Automattic\WooCommerce\Internal\Admin\Notes\DeactivatePlugin';
-
- /**
- * The version that this class was deprecated in.
- *
- * @var string
- */
- protected static $deprecated_in_version = '1.7.0';
-}
-
/**
* WC_Admin_Notes_Edit_Products_On_The_Move.
*
diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php
index 57ad552174a..3ef30a9b97e 100644
--- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php
+++ b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/OptionRuleProcessor.php
@@ -42,7 +42,7 @@ class OptionRuleProcessor implements RuleProcessorInterface {
}
if ( isset( $rule->transformers ) && is_array( $rule->transformers ) ) {
- $option_value = TransformerService::apply( $option_value, $rule->transformers, $rule->default );
+ $option_value = TransformerService::apply( $option_value, $rule->transformers, $default );
}
return ComparisonOperation::compare(
diff --git a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php
index cecefeb26f9..04493617406 100644
--- a/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php
+++ b/plugins/woocommerce/src/Admin/RemoteInboxNotifications/RemoteInboxNotificationsEngine.php
@@ -40,7 +40,7 @@ class RemoteInboxNotificationsEngine {
// Hook into WCA updated. This is hooked up here rather than in
// on_admin_init because that runs too late to hook into the action.
add_action(
- 'woocommerce_admin_updated',
+ 'woocommerce_updated',
function() {
$next_hook = WC()->queue()->get_next(
'woocommerce_run_on_woocommerce_admin_updated',
diff --git a/plugins/woocommerce/src/Container.php b/plugins/woocommerce/src/Container.php
index 711a6e16408..300e72d4aa4 100644
--- a/plugins/woocommerce/src/Container.php
+++ b/plugins/woocommerce/src/Container.php
@@ -6,6 +6,7 @@
namespace Automattic\WooCommerce;
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
+use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\COTMigrationServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\AssignDefaultCategoryServiceProvider;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\OrdersDataStoreServiceProvider;
@@ -47,6 +48,7 @@ final class Container implements \Psr\Container\ContainerInterface {
ProxiesServiceProvider::class,
RestockRefundedItemsAdjusterServiceProvider::class,
UtilsClassesServiceProvider::class,
+ COTMigrationServiceProvider::class,
);
/**
diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php
new file mode 100644
index 00000000000..f69d9dd3e32
--- /dev/null
+++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToCustomTableMigrator.php
@@ -0,0 +1,586 @@
+schema_config = MigrationHelper::escape_schema_for_backtick( $this->get_schema_config() );
+ $this->meta_column_mapping = $this->get_meta_column_config();
+ $this->core_column_mapping = $this->get_core_column_mapping();
+ $this->errors = array();
+ }
+
+ /**
+ * Specify schema config the source and destination table.
+ *
+ * @return array Schema, must of the form:
+ * array(
+ 'source' => array(
+ 'entity' => array(
+ 'table_name' => $source_table_name,
+ 'meta_rel_column' => $column_meta, Name of column in source table which is referenced by meta table.
+ 'destination_rel_column' => $column_dest, Name of column in source table which is refenced by destination table,
+ 'primary_key' => $primary_key, Primary key of the source table
+ ),
+ 'meta' => array(
+ 'table' => $meta_table_name,
+ 'meta_key_column' => $meta_key_column_name,
+ 'meta_value_column' => $meta_value_column_name,
+ 'entity_id_column' => $entity_id_column, Name of the column having entity IDs.
+ ),
+ ),
+ 'destination' => array(
+ 'table_name' => $table_name, Name of destination table,
+ 'source_rel_column' => $column_source_id, Name of the column in destination table which is referenced by source table.
+ 'primary_key' => $table_primary_key,
+ 'primary_key_type' => $type bool|int|string|decimal
+ )
+ */
+ abstract public function get_schema_config();
+
+ /**
+ * Specify column config from the source table.
+ *
+ * @return array Config, must be of the form:
+ * array(
+ * '$source_column_name_1' => array( // $source_column_name_1 is column name in source table, or a select statement.
+ * 'type' => 'type of value, could be string/int/date/float.',
+ * 'destination' => 'name of the column in column name where this data should be inserted in.',
+ * ),
+ * '$source_column_name_2' => array(
+ * ......
+ * ),
+ * ....
+ * ).
+ */
+ abstract public function get_core_column_mapping();
+
+ /**
+ * Specify meta keys config from source meta table.
+ *
+ * @return array Config, must be of the form.
+ * array(
+ * '$meta_key_1' => array( // $meta_key_1 is the name of meta_key in source meta table.
+ * 'type' => 'type of value, could be string/int/date/float',
+ * 'destination' => 'name of the column in column name where this data should be inserted in.',
+ * ),
+ * '$meta_key_2' => array(
+ * ......
+ * ),
+ * ....
+ * ).
+ */
+ abstract public function get_meta_column_config();
+
+ /**
+ * Generate SQL for data insertion.
+ *
+ * @param array $batch Data to generate queries for. Will be 'data' array returned by `$this->fetch_data_for_migration_for_ids()` method.
+ *
+ * @return string Generated queries for insertion for this batch, would be of the form:
+ * INSERT IGNORE INTO $table_name ($columns) values
+ * ($value for row 1)
+ * ($value for row 2)
+ * ...
+ */
+ public function generate_insert_sql_for_batch( $batch ) {
+ $table = $this->schema_config['destination']['table_name'];
+
+ list( $value_sql, $column_sql ) = $this->generate_column_clauses( array_merge( $this->core_column_mapping, $this->meta_column_mapping ), $batch );
+
+ return "INSERT IGNORE INTO $table (`$column_sql`) VALUES $value_sql;"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, -- $insert_query is hardcoded, $value_sql is already escaped.
+ }
+
+ /**
+ * Generate SQL for data updating.
+ *
+ * @param array $batch Data to generate queries for. Will be `data` array returned by fetch_data_for_migration_for_ids() method.
+ *
+ * @param array $entity_row_mapping Maps rows to update data with their original IDs. Will be returned by `generate_update_sql_for_batch`.
+ *
+ * @return string Generated queries for batch update. Would be of the form:
+ * INSERT INTO $table ( $columns ) VALUES
+ * ($value for row 1)
+ * ($valye for row 2)
+ * ...
+ * ON DUPLICATE KEY UPDATE
+ * $column1 = VALUES($column1)
+ * $column2 = VALUES($column2)
+ * ...
+ */
+ public function generate_update_sql_for_batch( $batch, $entity_row_mapping ) {
+ $table = $this->schema_config['destination']['table_name'];
+
+ $destination_primary_id_schema = $this->get_destination_table_primary_id_schema();
+ foreach ( $batch as $entity_id => $row ) {
+ $batch[ $entity_id ][ $destination_primary_id_schema['destination_primary_key']['destination'] ] = $entity_row_mapping[ $entity_id ]->destination_id;
+ }
+
+ list( $value_sql, $column_sql, $columns ) = $this->generate_column_clauses(
+ array_merge( $destination_primary_id_schema, $this->core_column_mapping, $this->meta_column_mapping ),
+ $batch
+ );
+
+ $duplicate_update_key_statement = $this->generate_on_duplicate_statement_clause( $columns );
+
+ return "INSERT INTO $table (`$column_sql`) VALUES $value_sql $duplicate_update_key_statement;";
+ }
+
+ /**
+ * Generate schema for primary ID column of destination table.
+ *
+ * @return array[] Schema for primary ID column.
+ */
+ protected function get_destination_table_primary_id_schema() {
+ return array(
+ 'destination_primary_key' => array(
+ 'destination' => $this->schema_config['destination']['primary_key'],
+ 'type' => $this->schema_config['destination']['primary_key_type'],
+ ),
+ );
+ }
+
+ /**
+ * Generate values and columns clauses to be used in INSERT and INSERT..ON DUPLICATE KEY UPDATE statements.
+ *
+ * @param array $columns_schema Columns config for destination table.
+ * @param array $batch Actual data to migrate as returned by `data` in `fetch_data_for_migration_for_ids` method.
+ *
+ * @return array SQL clause for values, columns placeholders, and columns.
+ */
+ protected function generate_column_clauses( $columns_schema, $batch ) {
+ global $wpdb;
+
+ $columns = array();
+ $placeholders = array();
+ foreach ( $columns_schema as $prev_column => $schema ) {
+ $columns[] = $schema['destination'];
+ $placeholders[] = MigrationHelper::get_wpdb_placeholder_for_type( $schema['type'] );
+ }
+ $placeholders = "'" . implode( "', '", $placeholders ) . "'";
+
+ $values = array();
+ foreach ( array_values( $batch ) as $row ) {
+ $query_params = array();
+ foreach ( $columns as $column ) {
+ $query_params[] = $row[ $column ] ?? null;
+ }
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $placeholders can only contain combination of placeholders described in MigrationHelper::get_wpdb_placeholder_for_type
+ $value_string = '(' . $wpdb->prepare( $placeholders, $query_params ) . ')';
+ $values[] = $value_string;
+ }
+
+ $value_sql = implode( ',', $values );
+
+ $column_sql = implode( '`, `', $columns );
+
+ return array( $value_sql, $column_sql, $columns );
+ }
+
+ /**
+ * Generates ON DUPLICATE KEY UPDATE clause to be used in migration.
+ *
+ * @param array $columns List of column names.
+ *
+ * @return string SQL clause for INSERT...ON DUPLICATE KEY UPDATE
+ */
+ private function generate_on_duplicate_statement_clause( $columns ) {
+ $update_value_statements = array();
+ foreach ( $columns as $column ) {
+ $update_value_statements[] = "$column = VALUES( $column )";
+ }
+ $update_value_clause = implode( ', ', $update_value_statements );
+
+ return "ON DUPLICATE KEY UPDATE $update_value_clause";
+ }
+
+ /**
+ * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far.
+ *
+ * @param array $entity_ids List of entity IDs to perform migrations for.
+ *
+ * @return array List of errors happened during migration.
+ */
+ public function process_migration_batch_for_ids( $entity_ids ) {
+ $data = $this->fetch_data_for_migration_for_ids( $entity_ids );
+
+ foreach ( $data['errors'] as $entity_id => $error ) {
+ $this->errors[ $entity_id ] = "Error in importing post id $entity_id: " . $error->get_message();
+ }
+
+ if ( count( $data['data'] ) === 0 ) {
+ return array();
+ }
+
+ $entity_ids = array_keys( $data['data'] );
+ $already_migrated = $this->get_already_migrated_records( $entity_ids );
+
+ $to_insert = array_diff_key( $data['data'], $already_migrated );
+ $this->process_insert_batch( $to_insert );
+
+ $to_update = array_intersect_key( $data['data'], $already_migrated );
+ $this->process_update_batch( $to_update, $already_migrated );
+
+ return array(
+ 'errors' => $this->errors,
+ );
+ }
+
+ /**
+ * Process batch for insertion into destination table.
+ *
+ * @param array $batch Data to insert, will be of the form as returned by `data` in `fetch_data_for_migration_for_ids`.
+ */
+ protected function process_insert_batch( $batch ) {
+ global $wpdb;
+ if ( 0 === count( $batch ) ) {
+ return;
+ }
+ $queries = $this->generate_insert_sql_for_batch( $batch );
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Queries should already be prepared.
+ $result = $wpdb->query( $queries );
+ $wpdb->query( 'COMMIT;' ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration?
+ if ( count( $batch ) !== $result ) {
+ $this->errors[] = 'Error with batch: ' . $wpdb->last_error;
+ }
+ }
+
+ /**
+ * Process batch for update into destination table.
+ *
+ * @param array $batch Data to insert, will be of the form as returned by `data` in `fetch_data_for_migration_for_ids`.
+ * @param array $already_migrated Maps rows to update data with their original IDs.
+ */
+ protected function process_update_batch( $batch, $already_migrated ) {
+ global $wpdb;
+ if ( 0 === count( $batch ) ) {
+ return;
+ }
+ $queries = $this->generate_update_sql_for_batch( $batch, $already_migrated );
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Queries should already be prepared.
+ $result = $wpdb->query( $queries );
+ $wpdb->query( 'COMMIT;' ); // For some reason, this seems necessary on some hosts? Maybe a MySQL configuration?
+ if ( count( $batch ) !== $result ) {
+ $this->errors[] = 'Error with batch: ' . $wpdb->last_error;
+ }
+ }
+
+
+ /**
+ * Fetch data for migration.
+ *
+ * @param array $entity_ids Entity IDs to fetch data for.
+ *
+ * @return array[] Data along with errors (if any), will of the form:
+ * array(
+ * 'data' => array(
+ * 'id_1' => array( 'column1' => value1, 'column2' => value2, ...),
+ * ...,
+ * ),
+ * 'errors' => array(
+ * 'id_1' => array( 'column1' => error1, 'column2' => value2, ...),
+ * ...,
+ * )
+ */
+ public function fetch_data_for_migration_for_ids( $entity_ids ) {
+ global $wpdb;
+
+ if ( empty( $entity_ids ) ) {
+ return array(
+ 'data' => array(),
+ 'errors' => array(),
+ );
+ }
+
+ $entity_table_query = $this->build_entity_table_query( $entity_ids );
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_entity_table_query is already prepared.
+ $entity_data = $wpdb->get_results( $entity_table_query );
+ if ( empty( $entity_data ) ) {
+ return array(
+ 'data' => array(),
+ 'errors' => array(),
+ );
+ }
+ $entity_meta_rel_ids = array_column( $entity_data, 'entity_meta_rel_id' );
+
+ $meta_table_query = $this->build_meta_data_query( $entity_meta_rel_ids );
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Output of $this->build_meta_data_query is already prepared.
+ $meta_data = $wpdb->get_results( $meta_table_query );
+
+ return $this->process_and_sanitize_data( $entity_data, $meta_data );
+ }
+
+ /**
+ * Fetch id mappings for records that are already inserted, or can be considered duplicates.
+ *
+ * @param array $entity_ids List of entity IDs to verify.
+ *
+ * @return array Already migrated entities, would be of the form
+ * array(
+ * '$source_id1' => array(
+ * 'source_id' => $source_id1,
+ * 'destination_id' => $destination_id1,
+ * ),
+ * ...
+ * )
+ */
+ public function get_already_migrated_records( $entity_ids ) {
+ global $wpdb;
+ $source_table = $this->schema_config['source']['entity']['table_name'];
+ $source_destination_join_column = $this->schema_config['source']['entity']['destination_rel_column'];
+ $source_primary_key_column = $this->schema_config['source']['entity']['primary_key'];
+
+ $destination_table = $this->schema_config['destination']['table_name'];
+ $destination_source_join_column = $this->schema_config['destination']['source_rel_column'];
+ $destination_primary_key_column = $this->schema_config['destination']['primary_key'];
+
+ $entity_id_placeholder = implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) );
+
+ $already_migrated_entity_ids = $wpdb->get_results(
+ $wpdb->prepare(
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- All columns and table names are hardcoded.
+ "
+SELECT source.`$source_primary_key_column` as source_id, destination.`$destination_primary_key_column` as destination_id
+FROM `$destination_table` destination
+JOIN `$source_table` source ON source.`$source_destination_join_column` = destination.`$destination_source_join_column`
+WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder )
+ ",
+ $entity_ids
+ )
+ // phpcs:enable
+ );
+
+ return array_column( $already_migrated_entity_ids, null, 'source_id' );
+ }
+
+
+ /**
+ * Helper method to build query used to fetch data from core source table.
+ *
+ * @param array $entity_ids List of entity IDs to fetch.
+ *
+ * @return string Query that can be used to fetch data.
+ */
+ protected function build_entity_table_query( $entity_ids ) {
+ global $wpdb;
+ $source_entity_table = $this->schema_config['source']['entity']['table_name'];
+ $source_meta_rel_id_column = "`$source_entity_table`.`{$this->schema_config['source']['entity']['meta_rel_column']}`";
+ $source_primary_key_column = "`$source_entity_table`.`{$this->schema_config['source']['entity']['primary_key']}`";
+
+ $where_clause = "$source_primary_key_column IN (" . implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) ) . ')';
+ $entity_keys = array();
+ foreach ( $this->core_column_mapping as $column_name => $column_schema ) {
+ if ( isset( $column_schema['select_clause'] ) ) {
+ $select_clause = $column_schema['select_clause'];
+ $entity_keys[] = "$select_clause AS $column_name";
+ } else {
+ $entity_keys[] = "$source_entity_table.$column_name";
+ }
+ }
+ $entity_column_string = implode( ', ', $entity_keys );
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $source_meta_rel_id_column, $source_destination_rel_id_column etc is escaped for backticks. $where clause and $order_by should already be escaped.
+ $query = $wpdb->prepare(
+ "
+SELECT
+ $source_meta_rel_id_column as entity_meta_rel_id,
+ $entity_column_string
+FROM `$source_entity_table`
+WHERE $where_clause;
+",
+ $entity_ids
+ );
+
+ // phpcs:enable
+
+ return $query;
+ }
+
+ /**
+ * Helper method to build query that will be used to fetch data from source meta table.
+ *
+ * @param array $entity_ids List of IDs to fetch metadata for.
+ *
+ * @return string Query for fetching meta data.
+ */
+ protected function build_meta_data_query( $entity_ids ) {
+ global $wpdb;
+ $meta_table = $this->schema_config['source']['meta']['table_name'];
+ $meta_keys = array_keys( $this->meta_column_mapping );
+ $meta_key_column = $this->schema_config['source']['meta']['meta_key_column'];
+ $meta_value_column = $this->schema_config['source']['meta']['meta_value_column'];
+ $meta_table_relational_key = $this->schema_config['source']['meta']['entity_id_column'];
+
+ $meta_column_string = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) );
+ $entity_id_string = implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) );
+
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $meta_table_relational_key, $meta_key_column, $meta_value_column and $meta_table is escaped for backticks. $entity_id_string and $meta_column_string are placeholders.
+ $query = $wpdb->prepare(
+ "
+SELECT `$meta_table_relational_key` as entity_id, `$meta_key_column` as meta_key, `$meta_value_column` as meta_value
+FROM `$meta_table`
+WHERE
+ `$meta_table_relational_key` IN ( $entity_id_string )
+ AND `$meta_key_column` IN ( $meta_column_string );
+",
+ array_merge(
+ $entity_ids,
+ $meta_keys
+ )
+ );
+
+ // phpcs:enable
+
+ return $query;
+ }
+
+ /**
+ * Helper function to validate and combine data before we try to insert.
+ *
+ * @param array $entity_data Data from source table.
+ * @param array $meta_data Data from meta table.
+ *
+ * @return array[] Validated and combined data with errors.
+ */
+ private function process_and_sanitize_data( $entity_data, $meta_data ) {
+ $sanitized_entity_data = array();
+ $error_records = array();
+ $this->process_and_sanitize_entity_data( $sanitized_entity_data, $error_records, $entity_data );
+ $this->processs_and_sanitize_meta_data( $sanitized_entity_data, $error_records, $meta_data );
+
+ return array(
+ 'data' => $sanitized_entity_data,
+ 'errors' => $error_records,
+ );
+ }
+
+ /**
+ * Helper method to sanitize core source table.
+ *
+ * @param array $sanitized_entity_data Array containing sanitized data for insertion.
+ * @param array $error_records Error records.
+ * @param array $entity_data Original source data.
+ */
+ private function process_and_sanitize_entity_data( &$sanitized_entity_data, &$error_records, $entity_data ) {
+ foreach ( $entity_data as $entity ) {
+ $row_data = array();
+ foreach ( $this->core_column_mapping as $column_name => $schema ) {
+ $custom_table_column_name = $schema['destination'] ?? $column_name;
+ $value = $entity->$column_name;
+ $value = $this->validate_data( $value, $schema['type'] );
+ if ( is_wp_error( $value ) ) {
+ $error_records[ $entity->primary_key_id ][ $custom_table_column_name ] = $value->get_error_message();
+ } else {
+ $row_data[ $custom_table_column_name ] = $value;
+ }
+ }
+ $sanitized_entity_data[ $entity->entity_meta_rel_id ] = $row_data;
+ }
+ }
+
+ /**
+ * Helper method to sanitize soure meta data.
+ *
+ * @param array $sanitized_entity_data Array containing sanitized data for insertion.
+ * @param array $error_records Error records.
+ * @param array $meta_data Original source data.
+ */
+ private function processs_and_sanitize_meta_data( &$sanitized_entity_data, &$error_records, $meta_data ) {
+ foreach ( $meta_data as $datum ) {
+ $column_schema = $this->meta_column_mapping[ $datum->meta_key ];
+ $value = $this->validate_data( $datum->meta_value, $column_schema['type'] );
+ if ( is_wp_error( $value ) ) {
+ $error_records[ $datum->entity_id ][ $column_schema['destination'] ] = "{$value->get_error_code()}: {$value->get_error_message()}";
+ } else {
+ $sanitized_entity_data[ $datum->entity_id ][ $column_schema['destination'] ] = $value;
+ }
+ }
+ }
+
+ /**
+ * Validate and transform data so that we catch as many errors as possible before inserting.
+ *
+ * @param mixed $value Actual data value.
+ * @param string $type Type of data, could be decimal, int, date, string.
+ *
+ * @return float|int|mixed|string|\WP_Error
+ */
+ private function validate_data( $value, $type ) {
+ switch ( $type ) {
+ case 'decimal':
+ $value = (float) $value;
+ break;
+ case 'int':
+ $value = (int) $value;
+ break;
+ case 'bool':
+ $value = wc_string_to_bool( $value );
+ break;
+ case 'date':
+ try {
+ if ( '' === $value ) {
+ $value = null;
+ } else {
+ $value = ( new \DateTime( $value ) )->format( 'Y-m-d H:i:s' );
+ }
+ } catch ( \Exception $e ) {
+ return new \WP_Error( $e->getMessage() );
+ }
+ break;
+ case 'date_epoch':
+ try {
+ if ( '' === $value ) {
+ $value = null;
+ } else {
+ $value = ( new \DateTime( "@$value" ) )->format( 'Y-m-d H:i:s' );
+ }
+ } catch ( \Exception $e ) {
+ return new \WP_Error( $e->getMessage() );
+ }
+ break;
+ }
+
+ return $value;
+ }
+}
diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php
new file mode 100644
index 00000000000..f043b70ffe2
--- /dev/null
+++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/MetaToMetaTableMigrator.php
@@ -0,0 +1,161 @@
+schema_config = $schema_config;
+ }
+
+ /**
+ * Generate insert sql queries for batches.
+ *
+ * @param array $batch Data to generate queries for.
+ * @param string $insert_switch Insert switch to use.
+ *
+ * @return string
+ */
+ public function generate_insert_sql_for_batch( $batch, $insert_switch ) {
+ global $wpdb;
+
+ $insert_query = MigrationHelper::get_insert_switch( $insert_switch );
+
+ $meta_key_column = $this->schema_config['destination']['meta']['meta_key_column'];
+ $meta_value_column = $this->schema_config['destination']['meta']['meta_value_column'];
+ $entity_id_column = $this->schema_config['destination']['meta']['entity_id_column'];
+ $column_sql = "(`$entity_id_column`, `$meta_key_column`, `$meta_value_column`)";
+ $table = $this->schema_config['destination']['meta']['table_name'];
+
+ $entity_id_column_placeholder = MigrationHelper::get_wpdb_placeholder_for_type( $this->schema_config['destination']['meta']['entity_id_type'] );
+ $placeholder_string = "$entity_id_column_placeholder, %s, %s";
+ $values = array();
+ foreach ( array_values( $batch ) as $row ) {
+ $query_params = array(
+ $row->destination_entity_id,
+ $row->meta_key,
+ $row->meta_value,
+ );
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $placeholder_string is hardcoded.
+ $value_sql = $wpdb->prepare( "$placeholder_string", $query_params );
+ $values[] = $value_sql;
+ }
+
+ $values_sql = implode( '), (', $values );
+
+ return "$insert_query INTO $table $column_sql VALUES ($values_sql)";
+ }
+
+ /**
+ * Fetch data for migration.
+ *
+ * @param array $order_post_ids Array of IDs to fetch data for.
+ *
+ * @return array[] Data along with errors (if any), will of the form:
+ * array(
+ * 'data' => array(
+ * 'id_1' => array( 'column1' => value1, 'column2' => value2, ...),
+ * ...,
+ * ),
+ * 'errors' => array(
+ * 'id_1' => array( 'column1' => error1, 'column2' => value2, ...),
+ * ...,
+ * )
+ */
+ public function fetch_data_for_migration_for_ids( $order_post_ids ) {
+ global $wpdb;
+ if ( empty( $order_post_ids ) ) {
+ return array(
+ 'data' => array(),
+ 'errors' => array(),
+ );
+ }
+
+ $meta_query = $this->build_meta_table_query( $order_post_ids );
+
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Meta query has interpolated variables, but they should all be escaped for backticks.
+ $meta_data_rows = $wpdb->get_results( $meta_query );
+ if ( empty( $meta_data_rows ) ) {
+ return array(
+ 'data' => array(),
+ 'errors' => array(),
+ );
+ }
+
+ return array(
+ 'data' => $meta_data_rows,
+ 'errors' => array(),
+ );
+ }
+
+ /**
+ * Helper method to build query used to fetch data from source meta table.
+ *
+ * @param string $entity_ids List of entity IDs to build meta query for.
+ *
+ * @return string Query that can be used to fetch data.
+ */
+ private function build_meta_table_query( $entity_ids ) {
+ global $wpdb;
+ $source_meta_table = $this->schema_config['source']['meta']['table_name'];
+ $source_meta_key_column = $this->schema_config['source']['meta']['meta_key_column'];
+ $source_meta_value_column = $this->schema_config['source']['meta']['meta_value_column'];
+ $source_entity_id_column = $this->schema_config['source']['meta']['entity_id_column'];
+ $order_by = "$source_entity_id_column ASC";
+
+ $where_clause = "$source_entity_id_column IN (" . implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ) . ')';
+
+ $destination_entity_table = $this->schema_config['destination']['entity']['table_name'];
+ $destination_entity_id_column = $this->schema_config['destination']['entity']['id_column'];
+ $destination_source_id_mapping_column = $this->schema_config['destination']['entity']['source_id_column'];
+
+ if ( $this->schema_config['source']['excluded_keys'] ) {
+ $key_placeholder = implode( ',', array_fill( 0, count( $this->schema_config['source']['excluded_keys'] ), '%s' ) );
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $source_meta_key_column is escated for backticks, $key_placeholder is hardcoded.
+ $exclude_clause = $wpdb->prepare( "source.$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] );
+ $where_clause = "$where_clause AND $exclude_clause";
+ }
+
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ return $wpdb->prepare(
+ "
+SELECT
+ source.`$source_entity_id_column` as source_entity_id,
+ destination.`$destination_entity_id_column` as destination_entity_id,
+ source.`$source_meta_key_column` as meta_key,
+ source.`$source_meta_value_column` as meta_value
+FROM `$source_meta_table` source
+JOIN `$destination_entity_table` destination ON destination.`$destination_source_id_mapping_column` = source.`$source_entity_id_column`
+WHERE $where_clause ORDER BY $order_by
+",
+ $entity_ids
+ );
+ // phpcs:enable
+ }
+}
diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php
new file mode 100644
index 00000000000..6e94d9106e4
--- /dev/null
+++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToCOTMigrator.php
@@ -0,0 +1,232 @@
+order_table_migrator = new WPPostToOrderTableMigrator();
+ $this->billing_address_table_migrator = new WPPostToOrderAddressTableMigrator( 'billing' );
+ $this->shipping_address_table_migrator = new WPPostToOrderAddressTableMigrator( 'shipping' );
+ $this->operation_data_table_migrator = new WPPostToOrderOpTableMigrator();
+
+ $meta_data_config = $this->get_config_for_meta_table();
+
+ $this->meta_table_migrator = new MetaToMetaTableMigrator( $meta_data_config );
+
+ $this->error_logger = new MigrationErrorLogger();
+ }
+
+ /**
+ * Generate config for meta data migration.
+ *
+ * @return array Meta data migration config.
+ */
+ private function get_config_for_meta_table() {
+ global $wpdb;
+ // TODO: Remove hardcoding.
+ $this->table_names = array(
+ 'orders' => $wpdb->prefix . 'wc_orders',
+ 'addresses' => $wpdb->prefix . 'wc_order_addresses',
+ 'op_data' => $wpdb->prefix . 'wc_order_operational_data',
+ 'meta' => $wpdb->prefix . 'wc_orders_meta',
+ );
+
+ $excluded_columns = array_keys( $this->order_table_migrator->get_meta_column_config() );
+ $excluded_columns = array_merge( $excluded_columns, array_keys( $this->billing_address_table_migrator->get_meta_column_config() ) );
+ $excluded_columns = array_merge( $excluded_columns, array_keys( $this->shipping_address_table_migrator->get_meta_column_config() ) );
+ $excluded_columns = array_merge( $excluded_columns, array_keys( $this->operation_data_table_migrator->get_meta_column_config() ) );
+
+ return array(
+ 'source' => array(
+ 'meta' => array(
+ 'table_name' => $wpdb->postmeta,
+ 'entity_id_column' => 'post_id',
+ 'meta_key_column' => 'meta_key',
+ 'meta_value_column' => 'meta_value',
+ ),
+ 'excluded_keys' => $excluded_columns,
+ ),
+ 'destination' => array(
+ 'meta' => array(
+ 'table_name' => $this->table_names['meta'],
+ 'entity_id_column' => 'order_id',
+ 'meta_key_column' => 'meta_key',
+ 'meta_value_column' => 'meta_value',
+ 'entity_id_type' => 'int',
+ ),
+ 'entity' => array(
+ 'table_name' => $this->table_names['orders'],
+ 'source_id_column' => 'post_id',
+ 'id_column' => 'id',
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Process next migration batch, uses option `wc_cot_migration` to checkpoints of what have been processed so far.
+ *
+ * @param int $batch_size Batch size of records to migrate.
+ *
+ * @return bool True if migration is completed, false if there are still records to process.
+ */
+ public function process_next_migration_batch( $batch_size = 100 ) {
+ $order_post_ids = $this->get_next_batch_ids( $batch_size );
+ if ( 0 === count( $order_post_ids ) ) {
+ return true;
+ }
+ $this->process_migration_for_ids( $order_post_ids );
+ $last_post_migrated = max( $order_post_ids );
+ $this->update_checkpoint( $last_post_migrated );
+ return false;
+ }
+
+ /**
+ * Process migration for specific order post IDs.
+ *
+ * @param array $order_post_ids List of post IDs to migrate.
+ */
+ public function process_migration_for_ids( $order_post_ids ) {
+ $this->order_table_migrator->process_migration_batch_for_ids( $order_post_ids );
+ $this->billing_address_table_migrator->process_migration_batch_for_ids( $order_post_ids );
+ $this->shipping_address_table_migrator->process_migration_batch_for_ids( $order_post_ids );
+ $this->operation_data_table_migrator->process_migration_batch_for_ids( $order_post_ids );
+ // TODO: Add resilience for meta migrations.
+ // $this->process_meta_migration( $order_post_ids );
+ // TODO: Return merged error array.
+ }
+
+ /**
+ * Process migration for metadata for given post ids.
+ *
+ * @param array $order_post_ids Post IDs.
+ */
+ private function process_meta_migration( $order_post_ids ) {
+ global $wpdb;
+ $data_to_migrate = $this->meta_table_migrator->fetch_data_for_migration_for_ids( $order_post_ids );
+ $insert_queries = $this->meta_table_migrator->generate_insert_sql_for_batch( $data_to_migrate['data'], 'insert' );
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $insert_queries should already be escaped in the generating function.
+ $result = $wpdb->query( $insert_queries );
+ if ( count( $data_to_migrate['data'] ) !== $result ) {
+ // TODO: Find and log entity ids that were not inserted.
+ echo 'error';
+ }
+ }
+
+ /**
+ * Method to migrate single record.
+ *
+ * @param int $post_id Post ID of record to migrate.
+ */
+ public function process_single( $post_id ) {
+ $this->process_migration_for_ids( array( $post_id ) );
+ // TODO: Return error.
+ }
+
+ /**
+ * Helper function to get where clause to send to MetaToCustomTableMigrator instance.
+ *
+ * @param int $batch_size Number of orders in batch.
+ *
+ * @return array List of IDs in the current patch.
+ */
+ private function get_next_batch_ids( $batch_size ) {
+ global $wpdb;
+
+ $checkpoint = $this->get_checkpoint();
+ $post_ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = %s ORDER BY ID ASC LIMIT %d ",
+ $checkpoint['id'],
+ 'shop_order',
+ $batch_size
+ )
+ );
+
+ return $post_ids;
+ }
+
+ /**
+ * Current checkpoint status.
+ *
+ * @return false|mixed|void
+ */
+ private function get_checkpoint() {
+ return get_option( 'wc_cot_migration', array( 'id' => 0 ) );
+ }
+
+ /**
+ * Updates current checkpoint
+ *
+ * @param int $id Order ID.
+ */
+ public function update_checkpoint( $id ) {
+ return update_option( 'wc_cot_migration', array( 'id' => $id ), false );
+ }
+
+ /**
+ * Remove checkpoint.
+ *
+ * @return bool Whether checkpoint was removed.
+ */
+ public function delete_checkpoint() {
+ return delete_option( 'wp_cot_migration' );
+ }
+}
diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php
new file mode 100644
index 00000000000..bbb2be8dda2
--- /dev/null
+++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderAddressTableMigrator.php
@@ -0,0 +1,191 @@
+type = $type;
+ parent::__construct();
+ }
+
+ /**
+ * Get schema config for wp_posts and wc_order_address table.
+ *
+ * @return array Config.
+ */
+ public function get_schema_config() {
+ global $wpdb;
+ // TODO: Remove hardcoding.
+ $this->table_names = array(
+ 'orders' => $wpdb->prefix . 'wc_orders',
+ 'addresses' => $wpdb->prefix . 'wc_order_addresses',
+ 'op_data' => $wpdb->prefix . 'wc_order_operational_data',
+ 'meta' => $wpdb->prefix . 'wc_orders_meta',
+ );
+
+ return array(
+ 'source' => array(
+ 'entity' => array(
+ 'table_name' => $this->table_names['orders'],
+ 'meta_rel_column' => 'post_id',
+ 'destination_rel_column' => 'id',
+ 'primary_key' => 'post_id',
+ ),
+ 'meta' => array(
+ 'table_name' => $wpdb->postmeta,
+ 'meta_key_column' => 'meta_key',
+ 'meta_value_column' => 'meta_value',
+ 'entity_id_column' => 'post_id',
+ ),
+ ),
+ 'destination' => array(
+ 'table_name' => $this->table_names['addresses'],
+ 'source_rel_column' => 'order_id',
+ 'primary_key' => 'id',
+ 'primary_key_type' => 'int',
+ ),
+ );
+ }
+
+ /**
+ * Get columns config.
+ *
+ * @return \string[][] Config.
+ */
+ public function get_core_column_mapping() {
+ $type = $this->type;
+
+ return array(
+ 'id' => array(
+ 'type' => 'int',
+ 'destination' => 'order_id',
+ ),
+ 'type' => array(
+ 'type' => 'string',
+ 'destination' => 'address_type',
+ 'select_clause' => "'$type'",
+ ),
+ );
+ }
+
+ /**
+ * Get meta data config.
+ *
+ * @return \string[][] Config.
+ */
+ public function get_meta_column_config() {
+ $type = $this->type;
+
+ return array(
+ "_{$type}_first_name" => array(
+ 'type' => 'string',
+ 'destination' => 'first_name',
+ ),
+ "_{$type}_last_name" => array(
+ 'type' => 'string',
+ 'destination' => 'last_name',
+ ),
+ "_{$type}_company" => array(
+ 'type' => 'string',
+ 'destination' => 'company',
+ ),
+ "_{$type}_address_1" => array(
+ 'type' => 'string',
+ 'destination' => 'address_1',
+ ),
+ "_{$type}_address_2" => array(
+ 'type' => 'string',
+ 'destination' => 'address_2',
+ ),
+ "_{$type}_city" => array(
+ 'type' => 'string',
+ 'destination' => 'city',
+ ),
+ "_{$type}_state" => array(
+ 'type' => 'string',
+ 'destination' => 'state',
+ ),
+ "_{$type}_postcode" => array(
+ 'type' => 'string',
+ 'destination' => 'postcode',
+ ),
+ "_{$type}_country" => array(
+ 'type' => 'string',
+ 'destination' => 'country',
+ ),
+ "_{$type}_email" => array(
+ 'type' => 'string',
+ 'destination' => 'email',
+ ),
+ "_{$type}_phone" => array(
+ 'type' => 'string',
+ 'destination' => 'phone',
+ ),
+ );
+ }
+
+ /**
+ * We overwrite this method to add a subclause to only fetch address of current type.
+ *
+ * @param array $entity_ids List of entity IDs to verify.
+ *
+ * @return array Already migrated entities, would be of the form
+ * array(
+ * '$source_id1' => array(
+ * 'source_id' => $source_id1,
+ * 'destination_id' => $destination_id1,
+ * ),
+ * ...
+ * )
+ */
+ public function get_already_migrated_records( $entity_ids ) {
+ global $wpdb;
+ $source_table = $this->schema_config['source']['entity']['table_name'];
+ $source_destination_join_column = $this->schema_config['source']['entity']['destination_rel_column'];
+ $source_primary_key_column = $this->schema_config['source']['entity']['primary_key'];
+
+ $destination_table = $this->schema_config['destination']['table_name'];
+ $destination_source_join_column = $this->schema_config['destination']['source_rel_column'];
+ $destination_primary_key_column = $this->schema_config['destination']['primary_key'];
+
+ $address_type = $this->type;
+
+ $entity_id_placeholder = implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) );
+
+ $already_migrated_entity_ids = $wpdb->get_results(
+ $wpdb->prepare(
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- All columns and table names are hardcoded.
+ "
+SELECT source.`$source_primary_key_column` as source_id, destination.`$destination_primary_key_column` as destination_id
+FROM `$destination_table` destination
+JOIN `$source_table` source ON source.`$source_destination_join_column` = destination.`$destination_source_join_column`
+WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder ) AND destination.`address_type` = '$address_type'
+ ",
+ $entity_ids
+ )
+ // phpcs:enable
+ );
+
+ return array_column( $already_migrated_entity_ids, null, 'source_id' );
+ }
+}
diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php
new file mode 100644
index 00000000000..54217a421c4
--- /dev/null
+++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderOpTableMigrator.php
@@ -0,0 +1,139 @@
+table_names = array(
+ 'orders' => $wpdb->prefix . 'wc_orders',
+ 'addresses' => $wpdb->prefix . 'wc_order_addresses',
+ 'op_data' => $wpdb->prefix . 'wc_order_operational_data',
+ 'meta' => $wpdb->prefix . 'wc_orders_meta',
+ );
+
+ return array(
+ 'source' => array(
+ 'entity' => array(
+ 'table_name' => $this->table_names['orders'],
+ 'meta_rel_column' => 'post_id',
+ 'destination_rel_column' => 'id',
+ 'primary_key' => 'post_id',
+ ),
+ 'meta' => array(
+ 'table_name' => $wpdb->postmeta,
+ 'meta_key_column' => 'meta_key',
+ 'meta_value_column' => 'meta_value',
+ 'entity_id_column' => 'post_id',
+ ),
+ ),
+ 'destination' => array(
+ 'table_name' => $this->table_names['op_data'],
+ 'source_rel_column' => 'order_id',
+ 'primary_key' => 'id',
+ 'primary_key_type' => 'int',
+ ),
+ );
+ }
+
+
+ /**
+ * Get columns config.
+ *
+ * @return \string[][] Config.
+ */
+ public function get_core_column_mapping() {
+ return array(
+ 'id' => array(
+ 'type' => 'int',
+ 'destination' => 'order_id',
+ ),
+ );
+ }
+
+
+ /**
+ * Get meta data config.
+ *
+ * @return \string[][] Config.
+ */
+ public function get_meta_column_config() {
+ return array(
+ '_created_via' => array(
+ 'type' => 'string',
+ 'destination' => 'created_via',
+ ),
+ '_order_version' => array(
+ 'type' => 'string',
+ 'destination' => 'woocommerce_version',
+ ),
+ '_prices_include_tax' => array(
+ 'type' => 'bool',
+ 'destination' => 'prices_include_tax',
+ ),
+ '_recorded_coupon_usage_counts' => array(
+ 'type' => 'bool',
+ 'destination' => 'coupon_usages_are_counted',
+ ),
+ '_download_permissions_granted' => array(
+ 'type' => 'bool',
+ 'destination' => 'download_permission_granted',
+ ),
+ '_cart_hash' => array(
+ 'type' => 'string',
+ 'destination' => 'cart_hash',
+ ),
+ '_new_order_email_sent' => array(
+ 'type' => 'bool',
+ 'destination' => 'new_order_email_sent',
+ ),
+ '_order_key' => array(
+ 'type' => 'string',
+ 'destination' => 'order_key',
+ ),
+ '_order_stock_reduced' => array(
+ 'type' => 'bool',
+ 'destination' => 'order_stock_reduced',
+ ),
+ '_date_paid' => array(
+ 'type' => 'date_epoch',
+ 'destination' => 'date_paid_gmt',
+ ),
+ '_date_completed' => array(
+ 'type' => 'date_epoch',
+ 'destination' => 'date_completed_gmt',
+ ),
+ '_order_shipping_tax' => array(
+ 'type' => 'decimal',
+ 'destination' => 'shipping_tax_amount',
+ ),
+ '_order_shipping' => array(
+ 'type' => 'decimal',
+ 'destination' => 'shipping_total_amount',
+ ),
+ '_cart_discount_tax' => array(
+ 'type' => 'decimal',
+ 'destination' => 'discount_tax_amount',
+ ),
+ '_cart_discount' => array(
+ 'type' => 'decimal',
+ 'destination' => 'discount_total_amount',
+ ),
+ );
+ }
+}
diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php
new file mode 100644
index 00000000000..e18b3876dfe
--- /dev/null
+++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/WPPostToOrderTableMigrator.php
@@ -0,0 +1,132 @@
+table_names = array(
+ 'orders' => $wpdb->prefix . 'wc_orders',
+ 'addresses' => $wpdb->prefix . 'wc_order_addresses',
+ 'op_data' => $wpdb->prefix . 'wc_order_operational_data',
+ 'meta' => $wpdb->prefix . 'wc_orders_meta',
+ );
+
+ return array(
+ 'source' => array(
+ 'entity' => array(
+ 'table_name' => $wpdb->posts,
+ 'meta_rel_column' => 'ID',
+ 'destination_rel_column' => 'ID',
+ 'primary_key' => 'ID',
+ ),
+ 'meta' => array(
+ 'table_name' => $wpdb->postmeta,
+ 'meta_key_column' => 'meta_key',
+ 'meta_value_column' => 'meta_value',
+ 'entity_id_column' => 'post_id',
+ ),
+ ),
+ 'destination' => array(
+ 'table_name' => $this->table_names['orders'],
+ 'source_rel_column' => 'post_id',
+ 'primary_key' => 'id',
+ 'primary_key_type' => 'int',
+ ),
+ );
+ }
+
+ /**
+ * Get columns config.
+ *
+ * @return \string[][] Config.
+ */
+ public function get_core_column_mapping() {
+ return array(
+ 'ID' => array(
+ 'type' => 'int',
+ 'destination' => 'post_id',
+ ),
+ 'post_status' => array(
+ 'type' => 'string',
+ 'destination' => 'status',
+ ),
+ 'post_date_gmt' => array(
+ 'type' => 'date',
+ 'destination' => 'date_created_gmt',
+ ),
+ 'post_modified_gmt' => array(
+ 'type' => 'date',
+ 'destination' => 'date_updated_gmt',
+ ),
+ 'post_parent' => array(
+ 'type' => 'int',
+ 'destination' => 'parent_order_id',
+ ),
+ );
+ }
+
+ /**
+ * Get meta data config.
+ *
+ * @return \string[][] Config.
+ */
+ public function get_meta_column_config() {
+ return array(
+ '_order_currency' => array(
+ 'type' => 'string',
+ 'destination' => 'currency',
+ ),
+ '_order_tax' => array(
+ 'type' => 'decimal',
+ 'destination' => 'tax_amount',
+ ),
+ '_order_total' => array(
+ 'type' => 'decimal',
+ 'destination' => 'total_amount',
+ ),
+ '_customer_user' => array(
+ 'type' => 'int',
+ 'destination' => 'customer_id',
+ ),
+ '_billing_email' => array(
+ 'type' => 'string',
+ 'destination' => 'billing_email',
+ ),
+ '_payment_method' => array(
+ 'type' => 'string',
+ 'destination' => 'payment_method',
+ ),
+ '_payment_method_title' => array(
+ 'type' => 'string',
+ 'destination' => 'payment_method_title',
+ ),
+ '_customer_ip_address' => array(
+ 'type' => 'string',
+ 'destination' => 'ip_address',
+ ),
+ '_customer_user_agent' => array(
+ 'type' => 'string',
+ 'destination' => 'user_agent',
+ ),
+ '_transaction_id' => array(
+ 'type' => 'string',
+ 'destination' => 'transaction_id',
+ ),
+ );
+ }
+}
diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php b/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php
new file mode 100644
index 00000000000..191dce8963d
--- /dev/null
+++ b/plugins/woocommerce/src/Database/Migrations/MigrationErrorLogger.php
@@ -0,0 +1,17 @@
+ '%d',
+ 'decimal' => '%f',
+ 'string' => '%s',
+ 'date' => '%s',
+ 'date_epoch' => '%s',
+ 'bool' => '%d',
+ );
+
+ /**
+ * Get insert clause for appropriate switch.
+ *
+ * @param string $switch Name of the switch to use.
+ *
+ * @return string Insert clause.
+ */
+ public static function get_insert_switch( $switch ) {
+ switch ( $switch ) {
+ case 'insert_ignore':
+ $insert_query = 'INSERT IGNORE';
+ break;
+ case 'replace': // delete and then insert.
+ $insert_query = 'REPLACE';
+ break;
+ case 'update':
+ $insert_query = 'UPDATE';
+ break;
+ case 'insert':
+ default:
+ $insert_query = 'INSERT';
+ }
+
+ return $insert_query;
+ }
+
+ /**
+ * Helper method to escape backtick in various schema fields.
+ *
+ * @param array $schema_config Schema config.
+ *
+ * @return array Schema config escaped for backtick.
+ */
+ public static function escape_schema_for_backtick( $schema_config ) {
+ array_walk( $schema_config['source']['entity'], array( self::class, 'escape_and_add_backtick' ) );
+ array_walk( $schema_config['source']['meta'], array( self::class, 'escape_and_add_backtick' ) );
+ array_walk( $schema_config['destination'], array( self::class, 'escape_and_add_backtick' ) );
+ return $schema_config;
+ }
+
+ /**
+ * Helper method to escape backtick in column and table names.
+ * WP does not provide a method to escape table/columns names yet, but hopefully soon in @link https://core.trac.wordpress.org/ticket/52506
+ *
+ * @param string|array $identifier Column or table name.
+ *
+ * @return array|string|string[] Escaped identifier.
+ */
+ public static function escape_and_add_backtick( $identifier ) {
+ return '`' . str_replace( '`', '``', $identifier ) . '`';
+ }
+
+ /**
+ * Return $wpdb->prepare placeholder for data type.
+ *
+ * @param string $type Data type.
+ *
+ * @return string $wpdb placeholder.
+ */
+ public static function get_wpdb_placeholder_for_type( $type ) {
+ return self::$wpdb_placeholder_for_type[ $type ];
+ }
+
+}
diff --git a/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php b/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php
index 9bc8fbf153d..ee16d188c40 100644
--- a/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php
+++ b/plugins/woocommerce/src/Internal/Admin/CategoryLookup.php
@@ -5,8 +5,6 @@
namespace Automattic\WooCommerce\Internal\Admin;
-use Automattic\WooCommerce\Internal\Admin\Install;
-
defined( 'ABSPATH' ) || exit;
/**
@@ -64,8 +62,6 @@ class CategoryLookup {
public function regenerate() {
global $wpdb;
- // Delete existing data and ensure schema is current.
- Install::create_tables();
$wpdb->query( "TRUNCATE TABLE $wpdb->wc_category_lookup" );
$terms = get_terms(
diff --git a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php
index 66d8195a41d..49dac42a47b 100644
--- a/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php
+++ b/plugins/woocommerce/src/Internal/Admin/FeaturePlugin.php
@@ -8,7 +8,6 @@ namespace Automattic\WooCommerce\Internal\Admin;
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Admin\API;
-use Automattic\WooCommerce\Internal\Admin\Install;
use \Automattic\WooCommerce\Admin\Notes\Notes;
use \Automattic\WooCommerce\Internal\Admin\Notes\OrderMilestones;
use \Automattic\WooCommerce\Internal\Admin\Notes\WooSubscriptionsNotes;
@@ -78,8 +77,6 @@ class FeaturePlugin {
require_once WC_ADMIN_ABSPATH . '/includes/react-admin/wc-admin-update-functions.php';
require_once WC_ADMIN_ABSPATH . '/includes/react-admin/class-experimental-abtest.php';
- register_activation_hook( WC_ADMIN_PLUGIN_FILE, array( $this, 'on_activation' ) );
- register_deactivation_hook( WC_ADMIN_PLUGIN_FILE, array( $this, 'on_deactivation' ) );
if ( did_action( 'plugins_loaded' ) ) {
self::on_plugins_loaded();
} else {
@@ -91,55 +88,12 @@ class FeaturePlugin {
}
}
- /**
- * Install DB and create cron events when activated.
- *
- * @return void
- */
- public function on_activation() {
- Install::create_tables();
- Install::create_events();
- }
-
- /**
- * Remove WooCommerce Admin scheduled actions on deactivate.
- *
- * @return void
- */
- public function on_deactivation() {
- // Don't clean up if the WooCommerce Admin package is in core.
- // NOTE: Any future divergence from the core package will need to be accounted for here.
- if ( defined( 'WC_ADMIN_PACKAGE_EXISTS' ) && WC_ADMIN_PACKAGE_EXISTS ) {
- return;
- }
-
- // Check if we are deactivating due to dependencies not being satisfied.
- // If WooCommerce is disabled we can't include files that depend upon it.
- if ( ! $this->has_satisfied_dependencies() ) {
- return;
- }
-
- $this->includes();
- ReportsSync::clear_queued_actions();
- Notes::clear_queued_actions();
- wp_clear_scheduled_hook( 'wc_admin_daily' );
- wp_clear_scheduled_hook( 'generate_category_lookup_table' );
- }
-
/**
* Setup plugin once all other plugins are loaded.
*
* @return void
*/
public function on_plugins_loaded() {
- $this->load_plugin_textdomain();
-
- if ( ! $this->has_satisfied_dependencies() ) {
- add_action( 'admin_init', array( $this, 'deactivate_self' ) );
- add_action( 'admin_notices', array( $this, 'render_dependencies_notice' ) );
- return;
- }
-
$this->hooks();
$this->includes();
}
@@ -164,19 +118,11 @@ class FeaturePlugin {
define( 'WC_ADMIN_VERSION_NUMBER', '3.3.0' );
}
- /**
- * Load Localisation files.
- */
- protected function load_plugin_textdomain() {
- load_plugin_textdomain( 'woocommerce-admin', false, basename( dirname( __DIR__ ) ) . '/languages' );
- }
-
/**
* Include WC Admin classes.
*/
public function includes() {
// Initialize Database updates, option migrations, and Notes.
- Install::init();
Events::instance()->init();
Notes::init();
@@ -229,65 +175,6 @@ class FeaturePlugin {
WCAdminAssets::get_instance();
}
- /**
- * Get an array of dependency error messages.
- *
- * @return array
- */
- protected function get_dependency_errors() {
- $errors = array();
- $wordpress_version = get_bloginfo( 'version' );
- $minimum_wordpress_version = '5.4';
- $minimum_woocommerce_version = '4.8';
- $wordpress_minimum_met = version_compare( $wordpress_version, $minimum_wordpress_version, '>=' );
- $woocommerce_minimum_met = class_exists( 'WooCommerce' ) && version_compare( WC_VERSION, $minimum_woocommerce_version, '>=' );
-
- if ( ! $woocommerce_minimum_met ) {
- $errors[] = sprintf(
- /* translators: 1: URL of WooCommerce plugin, 2: The minimum WooCommerce version number */
- __( 'The WooCommerce Admin feature plugin requires WooCommerce %2$s or greater to be installed and active.', 'woocommerce' ),
- 'https://wordpress.org/plugins/woocommerce/',
- $minimum_woocommerce_version
- );
- }
-
- if ( ! $wordpress_minimum_met ) {
- $errors[] = sprintf(
- /* translators: 1: URL of WordPress.org, 2: The minimum WordPress version number */
- __( 'The WooCommerce Admin feature plugin requires WordPress %2$s or greater to be installed and active.', 'woocommerce' ),
- 'https://wordpress.org/',
- $minimum_wordpress_version
- );
- }
-
- return $errors;
- }
-
- /**
- * Returns true if all dependencies for the wc-admin plugin are loaded.
- *
- * @return bool
- */
- public function has_satisfied_dependencies() {
- $dependency_errors = $this->get_dependency_errors();
- return 0 === count( $dependency_errors );
- }
-
- /**
- * Deactivates this plugin.
- */
- public function deactivate_self() {
- deactivate_plugins( plugin_basename( WC_ADMIN_PLUGIN_FILE ) );
- unset( $_GET['activate'] ); // phpcs:ignore CSRF ok.
- }
-
- /**
- * Notify users of the plugin requirements.
- */
- public function render_dependencies_notice() {
- $message = $this->get_dependency_errors();
- printf( '%s