Merge branch 'master' into fix/27215

This commit is contained in:
Ron Rennick 2020-08-28 11:38:37 -03:00
commit 1ae1b3e4e6
132 changed files with 6780 additions and 14105 deletions

View File

@ -8,8 +8,9 @@
/node_modules/ /node_modules/
/tests/ /tests/
babel.config.js babel.config.js
CHANGELOG.txt changelog.txt
composer.* composer.*
tsconfig.*
contributors.html contributors.html
docker-compose.yaml docker-compose.yaml
Dockerfile Dockerfile

31
.gitattributes vendored
View File

@ -1,13 +1,20 @@
/.* export-ignore # Set the default behavior, in case people don't have `core.autocrlf` set.
bin export-ignore * text=auto
# Declare files that will always have LF line endings on checkout.
*.php text eol=lf
# Remove files for archives generated using `git archive`.
/.* export-ignore
bin export-ignore
CODE_OF_CONDUCT.md export-ignore CODE_OF_CONDUCT.md export-ignore
CHANGELOG.txt export-ignore changelog.txt export-ignore
composer.* export-ignore composer.* export-ignore
Gruntfile.js export-ignore Gruntfile.js export-ignore
package.json export-ignore package.json export-ignore
package-lock.json export-ignore package-lock.json export-ignore
phpcs.xml export-ignore phpcs.xml export-ignore
phpunit.* export-ignore phpunit.* export-ignore
README.md export-ignore README.md export-ignore
tests export-ignore renovate.json export-ignore
renovate.json export-ignore tests export-ignore

View File

@ -11,9 +11,7 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Build - name: Build
id: build id: build
uses: woocommerce/action-build@master uses: woocommerce/action-build@v2
with:
generate-zip: true
- name: Upload release asset - name: Upload release asset
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:

View File

@ -17,9 +17,7 @@ jobs:
ref: ${{ matrix.build }} ref: ${{ matrix.build }}
- name: Build - name: Build
id: build id: build
uses: woocommerce/action-build@master uses: woocommerce/action-build@v2
with:
generate-zip: true
- name: Deploy nightly build - name: Deploy nightly build
uses: WebFreak001/deploy-nightly@v1.0.3 uses: WebFreak001/deploy-nightly@v1.0.3
env: env:

2
.gitignore vendored
View File

@ -49,6 +49,8 @@ tests/cli/vendor
/tests/e2e/env/docker/wp-cli/initialize.sh /tests/e2e/env/docker/wp-cli/initialize.sh
/tests/e2e/env/build/ /tests/e2e/env/build/
/tests/e2e/env/build-module/ /tests/e2e/env/build-module/
/tests/e2e/utils/build/
/tests/e2e/utils/build-module/
# Logs # Logs
/logs /logs

View File

@ -33,8 +33,7 @@ jobs:
php: 7.4 php: 7.4
env: WP_VERSION=latest WP_MULTISITE=0 RUN_E2E=1 env: WP_VERSION=latest WP_MULTISITE=0 RUN_E2E=1
script: script:
- composer require wp-cli/i18n-command - npm run build:assets
- npm run build
- npm run build:packages - npm run build:packages
- npm install jest --global - npm install jest --global
- npm run docker:up - npm run docker:up

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 KiB

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 279 KiB

View File

@ -6163,6 +6163,18 @@ table.bar_chart {
} }
} }
.post-type-product {
#wp-pointer-2 .wp-pointer-arrow {
left: 240px;
}
#wp-pointer-3 .wp-pointer-arrow,
#wp-pointer-4 .wp-pointer-arrow {
left: 46%;
}
}
/** /**
* Small screen optimisation * Small screen optimisation
*/ */

View File

@ -1315,6 +1315,7 @@ jQuery( function ( $ ) {
}; };
$.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) { $.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) {
$( 'ul.order_notes .no-items' ).remove();
$( 'ul.order_notes' ).prepend( response ); $( 'ul.order_notes' ).prepend( response );
$( '#woocommerce-order-notes' ).unblock(); $( '#woocommerce-order-notes' ).unblock();
$( '#add_order_note' ).val( '' ); $( '#add_order_note' ).val( '' );

View File

@ -103,8 +103,10 @@ jQuery( function( $ ) {
} }
// Class changes. // Class changes.
field.removeClass( 'form-row-first form-row-last form-row-wide' ); if ( Array.isArray( fieldLocale.class ) ) {
field.addClass( fieldLocale.class.join( ' ' ) ); field.removeClass( 'form-row-first form-row-last form-row-wide' );
field.addClass( fieldLocale.class.join( ' ' ) );
}
}); });
var fieldsets = $( var fieldsets = $(

View File

@ -13,20 +13,19 @@ echo "Installing PHP and JS dependencies..."
npm install npm install
composer install || exit "$?" composer install || exit "$?"
echo "Running JS Build..." echo "Running JS Build..."
npm run build || exit "$?" npm run build:core || exit "$?"
echo "Cleaning up PHP dependencies..." echo "Cleaning up PHP dependencies..."
composer install --no-dev || exit "$?" composer install --no-dev || exit "$?"
echo "Syncing files..." echo "Syncing files..."
rsync -rc --exclude-from="$PROJECT_PATH/.distignore" "$PROJECT_PATH/" "$DEST_PATH/" --delete --delete-excluded rsync -rc --exclude-from="$PROJECT_PATH/.distignore" "$PROJECT_PATH/" "$DEST_PATH/" --delete --delete-excluded
echo "Restoring PHP dependencies..."
composer install || exit "$?"
npm run build || exit "$?"
echo "Generating zip file..." echo "Generating zip file..."
cd "$BUILD_PATH" || exit cd "$BUILD_PATH" || exit
zip -q -r "${PLUGIN_SLUG}.zip" "$PLUGIN_SLUG/" zip -q -r "${PLUGIN_SLUG}.zip" "$PLUGIN_SLUG/"
echo "$BUILD_PATH/${PLUGIN_SLUG}.zip file generated!"
cd "$PROJECT_PATH" || exit
mv "$BUILD_PATH/${PLUGIN_SLUG}.zip" "$PROJECT_PATH"
echo "${PLUGIN_SLUG}.zip file generated!"
echo "Build done!" echo "Build done!"

View File

@ -1,5 +1,49 @@
== Changelog == == Changelog ==
= 4.5.0 - 2020-09-08 =
**WooCommerce**
* Localization - Added postcode validation for Bosnia and Herzegovina. #27048
* Localization - Added the postcode validation for Liechtenstein. #27059
* Localization - Add i18n locale information for Liechtenstein, Switzerland and Austria. #27193
* Tweak - Increase priority of `admin_body_class` filter to avoid comflict with plugins that incorrectly remove all body classes from WP. #27426
* Tweak - Rename built-in PayPal payment method to PayPal Standard. #27468
* Fix - Remove whitespace within a link. #26897
* Fix - `get_review_count_for_product` return all comments count not only 'review' types #26928
* Fix - Hidden field type is now supported by `woocommerce_form_field`. #27023
* Fix - Remove state for country liechtenstein. #27057
* Fix - Fixed validation of variation attributes while adding products to the cart. #27115
* Fix - Coupon code inconsistent between admins and shop owners. #27140
* Fix - Fixed the logic behind "Hide shipping costs until an address is entered". #27143
* Fix - Searches for variations now will fallback to parent SKU if one is not entered. #27171
* Fix - Release coupon holds for cancelled orders previously in pending status. #27179
* Fix - Fixes Japan zip code format issue (dash is now optional). #27244
* Fix - Restore backward compatibility with WC 4.x and forward compatibility with WC 5.5. #27318
* Fix - Switch to site locale before translating refund reason. #27323
* Fix - Declare `WC_Post_Types::updated_term_messages` as a static method to remove PHP deprecation warning. #27436
* Fix - Allow HTML to be entered in product title for formatting purposes. #27465
* Dev - Added additional stock-based cart filters including `woocommerce_cart_product_cannot_add_another_message`, `woocommerce_cart_product_out_of_stock_message`, and `woocommerce_cart_product_not_enough_stock_message`. #26439
* Dev - Changed text domain to `woocommerce` for REST API files. #27248
* Dev - Added file path to the `woocommerce_file_download_method` filter. #27152
* Dev - Merge API Package into core. #27100
**WooCommerce Admin 1.5.0**
* Enhancement - Add eWAY to Payment Setup for AU/NZ Stores. #4947
* Fix - Use clipRule and fillRule props. #4889, part of #4864
* Dev - New notification: Don't forget to test your checkout. #4805
* Dev - Enable tax calculation before redirecting to standard tax rates page. #4878
* Dev - Added event recording to Orders, Stock, and Reviews panels. #4861
* Dev - Added personalization to purchase extension task. #4849
* Dev - Display modal with more info about the new homescreen. #4890
* Dev - Task list - add a shortcut back to store setup. #4853
* Dev - Update the colors of the illustrations in the welcome modal. #4945
= 4.4.1 - 2020-08-19 =
**WooCommerce**
* Fix - Add protection to run adjust methods only if product query. #27396
* Dev - Stripped the internals of the DI Container to address plugin dependency conflicts it caused. #27395
= 4.4.0 - 2020-08-18 = = 4.4.0 - 2020-08-18 =
**WooCommerce** **WooCommerce**
@ -58,8 +102,10 @@
* Fix - After clicking to update WooCommerce, the user will stay in the same page instead of being redirected to the "Settings" page. #27172 * Fix - After clicking to update WooCommerce, the user will stay in the same page instead of being redirected to the "Settings" page. #27172
* Fix - "Product type" dropdown missing from Product's data meta box on WP 5.5. #27170 * Fix - "Product type" dropdown missing from Product's data meta box on WP 5.5. #27170
* Fix - Removed the JETPACK_AUTOLOAD_DEV define. #27185 * Fix - Removed the JETPACK_AUTOLOAD_DEV define. #27185
* Dev - Update WooCommerce Admin version to v1.4.0-beta.3. #27214 * Fix - Fixed "virtual" and "downlodable" pointers on product walkthrough. #27145
* Dev - Upgraded to the 2.x Jetpack Autoloader. #27123 * Fix - Updated tested up to for WordPress 5.5. #27334
* Dev - Update WooCommerce Admin version to v1.4.0. #27378
* Dev - Upgraded to v2.2 of Jetpack Autoloader. #27358
* Dev - Update jest-preset-default version to ^6.2.0. #27090 * Dev - Update jest-preset-default version to ^6.2.0. #27090
* Dev - Added a second $existing_meta_keys parameter to the woocommerce_duplicate_product_exclude_meta filter. #27038 * Dev - Added a second $existing_meta_keys parameter to the woocommerce_duplicate_product_exclude_meta filter. #27038
* Dev - Remove leftover note for translators in customer-completed-order.php. #26989 * Dev - Remove leftover note for translators in customer-completed-order.php. #26989
@ -85,14 +131,18 @@
* Dev - Ensure wc_load_cart loads its own dependencies. #26219 * Dev - Ensure wc_load_cart loads its own dependencies. #26219
* Dev - Clean up deprecated documentation. #27054 * Dev - Clean up deprecated documentation. #27054
* Dev - Update WooCommerce Blocks version to 3.1.0. #27177 * Dev - Update WooCommerce Blocks version to 3.1.0. #27177
* Dev - Added woocommerce_order_item_quantity filter to ReserveStock::reserve_stock_for_order(). #27251
* Dev - Updated docs to make the type in docblock more specific. #27285
**REST API 1.0.11** **REST API 1.0.15**
* Enhancement - Introduced X-WP-Total header for product attributes GET endpoint listing the number of entries in the response. woocommerce/woocommerce-rest-api#171 * Enhancement - Introduced X-WP-Total header for product attributes GET endpoint listing the number of entries in the response. woocommerce/woocommerce-rest-api#171
* Enhancement - Introduced X-WP-TotalPages header for product attributes GET endpoint listing the number of pages that can be fetched. woocommerce/woocommerce-rest-api#171 * Enhancement - Introduced X-WP-TotalPages header for product attributes GET endpoint listing the number of pages that can be fetched. woocommerce/woocommerce-rest-api#171
* Enhancement - Introduced the modified option for orderby fetch requests in post based resources. woocommerce/woocommerce-rest-api#226 * Enhancement - Introduced the modified option for orderby fetch requests in post based resources. woocommerce/woocommerce-rest-api#226
* Enhancement - Compatibility fixes for WordPress 5.5. woocommerce/woocommerce-rest-api#232
* Fix - Ensured Action Scheduler transients are cleared by "Clear Transients" tool. woocommerce/woocommerce-rest-api#152 * Fix - Ensured Action Scheduler transients are cleared by "Clear Transients" tool. woocommerce/woocommerce-rest-api#152
* Fix - Corrected the schema datatype for coupon expiry_date, date_expires, and date_expires_gmt fields. woocommerce/woocommerce-rest-api#176 * Fix - Corrected the schema datatype for coupon expiry_date, date_expires, and date_expires_gmt fields. woocommerce/woocommerce-rest-api#176
* Fix - Query parameters are now passed correctly when using the batch product variation endpoints. woocommerce/woocommerce-rest-api#191 * Fix - Query parameters are now passed correctly when using the batch product variation endpoints. woocommerce/woocommerce-rest-api#191
* Fix - Fix regression and restore backward compatibility for date-time and mixed data types. woocommerce/woocommerce-rest-api#238
**WooCommerce Admin 1.4.0** **WooCommerce Admin 1.4.0**
* Enhancement - Move the WooCommerce > Coupons dashboard menu item to Marketing > Coupons. #4786 * Enhancement - Move the WooCommerce > Coupons dashboard menu item to Marketing > Coupons. #4786
@ -107,9 +157,15 @@
* Fix - Polyfill core-data saveUser() on WP 5.3.x. #4869 * Fix - Polyfill core-data saveUser() on WP 5.3.x. #4869
* Fix - Product types step bugs in onboarding wizard. #4900 * Fix - Product types step bugs in onboarding wizard. #4900
* Fix - Center all descriptive text on onboarding wizard steps. #4902 * Fix - Center all descriptive text on onboarding wizard steps. #4902
* Fix - Match the requires version to the exact WordPress version number in readme.txt. #4956
* Fix - Change account required text on biz step in onboarding wizard. #4909 * Fix - Change account required text on biz step in onboarding wizard. #4909
* Fix - Fix industry args type in REST API. #4974
* Fix - Update style on shipping banner. #4948
* Fix - CSS Fixes for Business Features Popover ( parts 1&2 ). #4994
* Dev - Add the experimental resolver to WCA data package. #4862 * Dev - Add the experimental resolver to WCA data package. #4862
* Dev - Fix linter errors. #4904 * Dev - Fix linter errors. #4904
* Dev - Fix usage of "package" tag in file headers. #4940
* Dev - Update Jetpack Autoloader to match Woo Core. #4993
**WooCommerce Blocks 3.0.0** **WooCommerce Blocks 3.0.0**
* Build - Updated the automattic/jetpack-autoloader package to the 2.0 branch. #2847 * Build - Updated the automattic/jetpack-autoloader package to the 2.0 branch. #2847
@ -130,6 +186,11 @@
* Fix - 'Product Summary' in All Products block is not pulling in the short description of the product. #2913 * Fix - 'Product Summary' in All Products block is not pulling in the short description of the product. #2913
* Dev - Add query filter when searching for a table. #2886 * Dev - Add query filter when searching for a table. #2886
= 4.3.3 - 2020-08-14 =
**WooCommerce REST API 1.0.10-pl-2**
* Fix - Fix regression and restore backward compatibility for date-time and mixed datatypes. #239
= 4.3.2 - 2020-08-10 = = 4.3.2 - 2020-08-10 =
**WooCommerce** **WooCommerce**
@ -811,7 +872,7 @@
* Tweak - Remove the left and right margin from the logo in emails. #23360 * Tweak - Remove the left and right margin from the logo in emails. #23360
* Tweak - Use the high res version of the WP spinner in the coupon Block UI. #23364 * Tweak - Use the high res version of the WP spinner in the coupon Block UI. #23364
* Tweak - Improve user registration validation messages. #23468 * Tweak - Improve user registration validation messages. #23468
* Tweak - Auto generate a new username when a username is blacklisted by WordPress. #23672 * Tweak - Auto generate a new username when a username is blocked by WordPress. #23672
* Tweak - Guest cart sessions now gets deleted when a user logs in, preventing duplicate cart sessions. #23687 * Tweak - Guest cart sessions now gets deleted when a user logs in, preventing duplicate cart sessions. #23687
* Tweak - Include the store's base postcode and city when calculating order taxes. #23695 * Tweak - Include the store's base postcode and city when calculating order taxes. #23695
* Tweak - Update the generate username setting description label to reflect how the username is actually generated. #23911 * Tweak - Update the generate username setting description label to reflect how the username is actually generated. #23911
@ -2973,7 +3034,7 @@
* Fix - Make save button clickable in tax rate table after using autocomplete field. * Fix - Make save button clickable in tax rate table after using autocomplete field.
* Fix - Fix passed image_size variable in email templates. * Fix - Fix passed image_size variable in email templates.
* Fix - Don't show purchase note to admin in emails. * Fix - Don't show purchase note to admin in emails.
* Fix - Fix 'hide empty' setting in category widget. * Fix - Fix 'hide empty' setting in category widget.
* Fix - Prevent notice in get_allowed_countries. * Fix - Prevent notice in get_allowed_countries.
* Fix - Prevent add-to-cart querystring in pagination links. * Fix - Prevent add-to-cart querystring in pagination links.
* Tweak - Allow propagation in variation script. * Tweak - Allow propagation in variation script.
@ -3005,7 +3066,7 @@
* Fix - Show the taxable country rather than base country in "estimated for" text during checkout. * Fix - Show the taxable country rather than base country in "estimated for" text during checkout.
* Fix - Prevent select2 gaining focus on IOS7 scroll. * Fix - Prevent select2 gaining focus on IOS7 scroll.
* Fix - API - Fix indexes on decimal and thousand values. * Fix - API - Fix indexes on decimal and thousand values.
* Tweak - Clear cron jobs on uninstall. * Tweak - Clear cron jobs on uninstall.
* Tweak - Don't disable place order button on checkout if a weak password is used. * Tweak - Don't disable place order button on checkout if a weak password is used.
* Tweak - Added password strength meter in lost password and edit accout pages. * Tweak - Added password strength meter in lost password and edit accout pages.
* Tweak - Pass $args to woocommerce_dropdown_variation_attribute_options_html hook. * Tweak - Pass $args to woocommerce_dropdown_variation_attribute_options_html hook.
@ -3075,10 +3136,10 @@
= 2.4.11 - 2015-12-7 = = 2.4.11 - 2015-12-7 =
* Fix - WordPress 4.4 support. * Fix - WordPress 4.4 support.
* Fix - Removes Switzerland from EU VAT definition. * Fix - Removes Switzerland from EU VAT definition.
* Fix - Fix auth endpoint urls. * Fix - Fix auth endpoint urls.
* Fix - To allow backslash in SKUs. * Fix - To allow backslash in SKUs.
* Fix - Sanity check for min/max quantity. * Fix - Sanity check for min/max quantity.
* Fix - 4.4 - Shipping class menu display. * Fix - 4.4 - Shipping class menu display.
* Fix - 4.4 - Admin menu icons and styling. * Fix - 4.4 - Admin menu icons and styling.
* Fix - API - Variable product backorders editing. * Fix - API - Variable product backorders editing.
@ -3115,9 +3176,9 @@
* Fix - Shipping priority for methods with colons in the name. * Fix - Shipping priority for methods with colons in the name.
* Fix - Saving of passwords with '&' inside. * Fix - Saving of passwords with '&' inside.
* Fix - Remove double escaping of coupon descriptions. * Fix - Remove double escaping of coupon descriptions.
* Fix - Settings API default value should not apply if value of option is 0. * Fix - Settings API default value should not apply if value of option is 0.
* Fix - Avoid potential PHP Fatals by avoiding premature script enqueues. * Fix - Avoid potential PHP Fatals by avoiding premature script enqueues.
* Fix - Pass mimes when checking file type. * Fix - Pass mimes when checking file type.
* Fix - Reset shipping totals before calculation to prevent totals being used incorrectly. * Fix - Reset shipping totals before calculation to prevent totals being used incorrectly.
* Fix - API - Corrected how attributes terms saves non-latin characters. * Fix - API - Corrected how attributes terms saves non-latin characters.
* Fix - API - Variations price sync. * Fix - API - Variations price sync.
@ -3147,7 +3208,7 @@
* Fix - Network activated plugins not showing up in system status report. * Fix - Network activated plugins not showing up in system status report.
* Fix - Tax fields showing on bulk/quick edit when disabled the tax system. * Fix - Tax fields showing on bulk/quick edit when disabled the tax system.
* Fix - Tax status and tax class values within bulk edit. * Fix - Tax status and tax class values within bulk edit.
* Tweak - Allow bulk edit price to 0. * Tweak - Allow bulk edit price to 0.
* Tweak - Add filters to control "shipped via" text. * Tweak - Add filters to control "shipped via" text.
* Tweak - Allow line breaks in non-variation attributes. * Tweak - Allow line breaks in non-variation attributes.
* Tweak - Renamed wc_var_prices transient to allow them to flush on product save. * Tweak - Renamed wc_var_prices transient to allow them to flush on product save.
@ -3907,7 +3968,7 @@
* Fix - Fix bulk editing variation sale price. * Fix - Fix bulk editing variation sale price.
* Fix - Remove comment exclusion in order notes meta box. * Fix - Remove comment exclusion in order notes meta box.
* Fix - Sync min and max prices for regular and sale prices so prices are displayed correctly when sale price is lower than a regular price of another variation. * Fix - Sync min and max prices for regular and sale prices so prices are displayed correctly when sale price is lower than a regular price of another variation.
* Fix - Expanding line item_meta causes conflicts if attributes are named with things like 'name', 'type' or 'qty'. Added blacklist to exclude unsafe values. * Fix - Expanding line item_meta causes conflicts if attributes are named with things like 'name', 'type' or 'qty'. Added blocklist to exclude unsafe values.
* Fix - Added support for clearing report transients when using object caching. * Fix - Added support for clearing report transients when using object caching.
* Fix - encoding issues with attribute values. * Fix - encoding issues with attribute values.
* Fix - Escape the contents of the changelog when displayed. * Fix - Escape the contents of the changelog when displayed.

View File

@ -8,19 +8,19 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"require": { "require": {
"php": ">=7.0", "php": ">=7.0",
"automattic/jetpack-autoloader": "2.0.2", "automattic/jetpack-autoloader": "2.2.0",
"automattic/jetpack-constants": "1.4.0", "automattic/jetpack-constants": "1.4.0",
"composer/installers": "1.7.0", "composer/installers": "1.7.0",
"league/container": "3.3.1",
"maxmind-db/reader": "1.6.0", "maxmind-db/reader": "1.6.0",
"pelago/emogrifier": "3.1.0", "pelago/emogrifier": "3.1.0",
"psr/container": "^1.0",
"woocommerce/action-scheduler": "3.1.6", "woocommerce/action-scheduler": "3.1.6",
"woocommerce/woocommerce-admin": "1.4.0-beta.3", "woocommerce/woocommerce-admin": "1.5.0-rc.2",
"woocommerce/woocommerce-blocks": "3.1.0" "woocommerce/woocommerce-blocks": "3.1.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "7.5.20", "phpunit/phpunit": "7.5.20",
"woocommerce/woocommerce-sniffs": "0.0.10", "woocommerce/woocommerce-sniffs": "^0.1.0",
"wp-cli/i18n-command": "^2.2" "wp-cli/i18n-command": "^2.2"
}, },
"config": { "config": {

197
composer.lock generated
View File

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "ae4abaa8d39e860cc6c379cb5f6a0c2f", "content-hash": "6f5bef4c75b0d62b2f3d9bc2458eff03",
"packages": [ "packages": [
{ {
"name": "automattic/jetpack-autoloader", "name": "automattic/jetpack-autoloader",
"version": "v2.0.2", "version": "v2.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Automattic/jetpack-autoloader.git", "url": "https://github.com/Automattic/jetpack-autoloader.git",
"reference": "4502da4b2443fc1b61389cacc94c34876aca2b3d" "reference": "66a5d150b3928be718d86696f85631a7f0b98a7b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/4502da4b2443fc1b61389cacc94c34876aca2b3d", "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/66a5d150b3928be718d86696f85631a7f0b98a7b",
"reference": "4502da4b2443fc1b61389cacc94c34876aca2b3d", "reference": "66a5d150b3928be718d86696f85631a7f0b98a7b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -40,7 +40,7 @@
"GPL-2.0-or-later" "GPL-2.0-or-later"
], ],
"description": "Creates a custom autoloader for a plugin or theme.", "description": "Creates a custom autoloader for a plugin or theme.",
"time": "2020-07-09T13:18:38+00:00" "time": "2020-08-14T20:34:36+00:00"
}, },
{ {
"name": "automattic/jetpack-constants", "name": "automattic/jetpack-constants",
@ -195,78 +195,6 @@
], ],
"time": "2019-08-12T15:00:31+00:00" "time": "2019-08-12T15:00:31+00:00"
}, },
{
"name": "league/container",
"version": "3.3.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/container.git",
"reference": "93238f74ff5964aee27a78508cdfbdba1cd338f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/container/zipball/93238f74ff5964aee27a78508cdfbdba1cd338f6",
"reference": "93238f74ff5964aee27a78508cdfbdba1cd338f6",
"shasum": ""
},
"require": {
"php": "^7.0",
"psr/container": "^1.0"
},
"provide": {
"psr/container-implementation": "^1.0"
},
"replace": {
"orno/di": "~2.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0",
"squizlabs/php_codesniffer": "^3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-3.x": "3.x-dev",
"dev-2.x": "2.x-dev",
"dev-1.x": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Container\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Phil Bennett",
"email": "philipobenito@gmail.com",
"homepage": "http://www.philipobenito.com",
"role": "Developer"
}
],
"description": "A fast and intuitive dependency injection container.",
"homepage": "https://github.com/thephpleague/container",
"keywords": [
"container",
"dependency",
"di",
"injection",
"league",
"provider",
"service"
],
"funding": [
{
"url": "https://github.com/philipobenito",
"type": "github"
}
],
"time": "2020-05-18T08:20:23+00:00"
},
{ {
"name": "maxmind-db/reader", "name": "maxmind-db/reader",
"version": "v1.6.0", "version": "v1.6.0",
@ -501,20 +429,6 @@
], ],
"description": "Symfony CssSelector Component", "description": "Symfony CssSelector Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-03-16T08:31:04+00:00" "time": "2020-03-16T08:31:04+00:00"
}, },
{ {
@ -554,16 +468,16 @@
}, },
{ {
"name": "woocommerce/woocommerce-admin", "name": "woocommerce/woocommerce-admin",
"version": "v1.4.0-beta.3", "version": "1.5.0-rc.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git", "url": "https://github.com/woocommerce/woocommerce-admin.git",
"reference": "df2af46a8552cdee15df0030fccbe4cd5a6d270d" "reference": "bb2fbb0e105e419478b09a15dc4b43c8f1426381"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/df2af46a8552cdee15df0030fccbe4cd5a6d270d", "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/bb2fbb0e105e419478b09a15dc4b43c8f1426381",
"reference": "df2af46a8552cdee15df0030fccbe4cd5a6d270d", "reference": "bb2fbb0e105e419478b09a15dc4b43c8f1426381",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -597,7 +511,7 @@
], ],
"description": "A modern, javascript-driven WooCommerce Admin experience.", "description": "A modern, javascript-driven WooCommerce Admin experience.",
"homepage": "https://github.com/woocommerce/woocommerce-admin", "homepage": "https://github.com/woocommerce/woocommerce-admin",
"time": "2020-08-04T02:21:47+00:00" "time": "2020-08-20T23:35:02+00:00"
}, },
{ {
"name": "woocommerce/woocommerce-blocks", "name": "woocommerce/woocommerce-blocks",
@ -650,22 +564,22 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "dealerdirect/phpcodesniffer-composer-installer", "name": "dealerdirect/phpcodesniffer-composer-installer",
"version": "v0.6.2", "version": "v0.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
"reference": "8001af8eb107fbfcedc31a8b51e20b07d85b457a" "reference": "e8d808670b8f882188368faaf1144448c169c0b7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/8001af8eb107fbfcedc31a8b51e20b07d85b457a", "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e8d808670b8f882188368faaf1144448c169c0b7",
"reference": "8001af8eb107fbfcedc31a8b51e20b07d85b457a", "reference": "e8d808670b8f882188368faaf1144448c169c0b7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"composer-plugin-api": "^1.0", "composer-plugin-api": "^1.0 || ^2.0",
"php": "^5.3|^7", "php": ">=5.3",
"squizlabs/php_codesniffer": "^2|^3" "squizlabs/php_codesniffer": "^2 || ^3 || 4.0.x-dev"
}, },
"require-dev": { "require-dev": {
"composer/composer": "*", "composer/composer": "*",
@ -712,7 +626,7 @@
"stylecheck", "stylecheck",
"tests" "tests"
], ],
"time": "2020-01-29T20:22:20+00:00" "time": "2020-06-25T14:57:39+00:00"
}, },
{ {
"name": "doctrine/instantiator", "name": "doctrine/instantiator",
@ -768,20 +682,6 @@
"constructor", "constructor",
"instantiate" "instantiate"
], ],
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
"type": "tidelift"
}
],
"time": "2020-05-29T17:27:14+00:00" "time": "2020-05-29T17:27:14+00:00"
}, },
{ {
@ -1044,12 +944,6 @@
"object", "object",
"object graph" "object graph"
], ],
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2020-06-29T13:22:24+00:00" "time": "2020-06-29T13:22:24+00:00"
}, },
{ {
@ -1775,6 +1669,7 @@
"keywords": [ "keywords": [
"tokenizer" "tokenizer"
], ],
"abandoned": true,
"time": "2019-09-17T06:23:10+00:00" "time": "2019-09-17T06:23:10+00:00"
}, },
{ {
@ -2478,16 +2373,16 @@
}, },
{ {
"name": "squizlabs/php_codesniffer", "name": "squizlabs/php_codesniffer",
"version": "3.5.5", "version": "3.5.6",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6" "reference": "e97627871a7eab2f70e59166072a6b767d5834e0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/73e2e7f57d958e7228fce50dc0c61f58f017f9f6", "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/e97627871a7eab2f70e59166072a6b767d5834e0",
"reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6", "reference": "e97627871a7eab2f70e59166072a6b767d5834e0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2525,7 +2420,7 @@
"phpcs", "phpcs",
"standards" "standards"
], ],
"time": "2020-04-17T01:09:41+00:00" "time": "2020-08-10T04:50:15+00:00"
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
@ -2574,20 +2469,6 @@
], ],
"description": "Symfony Finder Component", "description": "Symfony Finder Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-02-14T07:34:21+00:00" "time": "2020-02-14T07:34:21+00:00"
}, },
{ {
@ -2743,23 +2624,23 @@
}, },
{ {
"name": "woocommerce/woocommerce-sniffs", "name": "woocommerce/woocommerce-sniffs",
"version": "0.0.10", "version": "0.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/woocommerce/woocommerce-sniffs.git", "url": "https://github.com/woocommerce/woocommerce-sniffs.git",
"reference": "b0e3d69a53b3ffdbb97a0371bd1b43aa17092d65" "reference": "b72b7dd2e70aa6aed16f80cdae5b1e6cce2e4c79"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/woocommerce/woocommerce-sniffs/zipball/b0e3d69a53b3ffdbb97a0371bd1b43aa17092d65", "url": "https://api.github.com/repos/woocommerce/woocommerce-sniffs/zipball/b72b7dd2e70aa6aed16f80cdae5b1e6cce2e4c79",
"reference": "b0e3d69a53b3ffdbb97a0371bd1b43aa17092d65", "reference": "b72b7dd2e70aa6aed16f80cdae5b1e6cce2e4c79",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"dealerdirect/phpcodesniffer-composer-installer": "0.6.2", "dealerdirect/phpcodesniffer-composer-installer": "0.7.0",
"php": ">=7.0", "php": ">=7.0",
"phpcompatibility/phpcompatibility-wp": "2.1.0", "phpcompatibility/phpcompatibility-wp": "2.1.0",
"wp-coding-standards/wpcs": "2.2.1" "wp-coding-standards/wpcs": "2.3.0"
}, },
"type": "phpcodesniffer-standard", "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -2779,7 +2660,7 @@
"woocommerce", "woocommerce",
"wordpress" "wordpress"
], ],
"time": "2020-04-07T20:25:44+00:00" "time": "2020-08-06T18:23:45+00:00"
}, },
{ {
"name": "wp-cli/i18n-command", "name": "wp-cli/i18n-command",
@ -3000,16 +2881,16 @@
}, },
{ {
"name": "wp-coding-standards/wpcs", "name": "wp-coding-standards/wpcs",
"version": "2.2.1", "version": "2.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/WordPress/WordPress-Coding-Standards.git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
"reference": "b5a453203114cc2284b1a614c4953456fbe4f546" "reference": "7da1894633f168fe244afc6de00d141f27517b62"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b5a453203114cc2284b1a614c4953456fbe4f546", "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62",
"reference": "b5a453203114cc2284b1a614c4953456fbe4f546", "reference": "7da1894633f168fe244afc6de00d141f27517b62",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3019,6 +2900,7 @@
"require-dev": { "require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6", "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6",
"phpcompatibility/php-compatibility": "^9.0", "phpcompatibility/php-compatibility": "^9.0",
"phpcsstandards/phpcsdevtools": "^1.0",
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
}, },
"suggest": { "suggest": {
@ -3041,7 +2923,7 @@
"standards", "standards",
"wordpress" "wordpress"
], ],
"time": "2020-02-04T02:52:06+00:00" "time": "2020-05-13T23:57:56+00:00"
} }
], ],
"aliases": [], "aliases": [],
@ -3055,6 +2937,5 @@
"platform-dev": [], "platform-dev": [],
"platform-overrides": { "platform-overrides": {
"php": "7.1" "php": "7.1"
}, }
"plugin-api-version": "1.1.0"
} }

View File

@ -2,10 +2,8 @@
/** /**
* Setup customize items. * Setup customize items.
* *
* @author WooCommerce * @package WooCommerce\Admin\Customize
* @category Admin * @version 3.1.0
* @package WooCommerce\Admin\Customize
* @version 3.1.0
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {

View File

@ -2,10 +2,8 @@
/** /**
* Adds and controls pointers for contextual help/tutorials * Adds and controls pointers for contextual help/tutorials
* *
* @author WooThemes * @package WooCommerce\Admin\Pointers
* @category Admin * @version 2.4.0
* @package WooCommerce\Admin
* @version 2.4.0
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
@ -28,7 +26,9 @@ class WC_Admin_Pointers {
* Setup pointers for screen. * Setup pointers for screen.
*/ */
public function setup_pointers_for_screen() { public function setup_pointers_for_screen() {
if ( ! $screen = get_current_screen() ) { $screen = get_current_screen();
if ( ! $screen ) {
return; return;
} }
@ -43,9 +43,10 @@ class WC_Admin_Pointers {
* Pointers for creating a product. * Pointers for creating a product.
*/ */
public function create_product_tutorial() { public function create_product_tutorial() {
if ( ! isset( $_GET['tutorial'] ) || ! current_user_can( 'manage_options' ) ) { if ( ! isset( $_GET['tutorial'] ) || ! current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return; return;
} }
// These pointers will chain - they will not be shown at once. // These pointers will chain - they will not be shown at once.
$pointers = array( $pointers = array(
'pointers' => array( 'pointers' => array(
@ -218,7 +219,7 @@ class WC_Admin_Pointers {
/** /**
* Enqueue pointers and add script to page. * Enqueue pointers and add script to page.
* *
* @param array $pointers * @param array $pointers Pointers data.
*/ */
public function enqueue_pointers( $pointers ) { public function enqueue_pointers( $pointers ) {
$pointers = rawurlencode( wp_json_encode( $pointers ) ); $pointers = rawurlencode( wp_json_encode( $pointers ) );

View File

@ -35,7 +35,7 @@ class WC_Admin {
add_filter( 'action_scheduler_post_type_args', array( $this, 'disable_webhook_post_export' ) ); add_filter( 'action_scheduler_post_type_args', array( $this, 'disable_webhook_post_export' ) );
// Add body class for WP 5.3+ compatibility. // Add body class for WP 5.3+ compatibility.
add_filter( 'admin_body_class', array( $this, 'include_admin_body_class' ) ); add_filter( 'admin_body_class', array( $this, 'include_admin_body_class' ), 9999 );
} }
/** /**
@ -137,7 +137,7 @@ class WC_Admin {
} }
// phpcs:disable WordPress.Security.NonceVerification.Recommended // phpcs:disable WordPress.Security.NonceVerification.Recommended
// Nonced plugin install redirects (whitelisted). // Nonced plugin install redirects.
if ( ! empty( $_GET['wc-install-plugin-redirect'] ) ) { if ( ! empty( $_GET['wc-install-plugin-redirect'] ) ) {
$plugin_slug = wc_clean( wp_unslash( $_GET['wc-install-plugin-redirect'] ) ); $plugin_slug = wc_clean( wp_unslash( $_GET['wc-install-plugin-redirect'] ) );
@ -318,7 +318,7 @@ class WC_Admin {
* @return string * @return string
*/ */
public function include_admin_body_class( $classes ) { public function include_admin_body_class( $classes ) {
if ( false !== strpos( $classes, 'wc-wp-version-gte-53' ) ) { if ( in_array( array( 'wc-wp-version-gte-53', 'wc-wp-version-gte-55' ), explode( ' ', $classes ), true ) ) {
return $classes; return $classes;
} }

View File

@ -267,7 +267,7 @@ class WC_Meta_Box_Coupon_Data {
'id' => 'customer_email', 'id' => 'customer_email',
'label' => __( 'Allowed emails', 'woocommerce' ), 'label' => __( 'Allowed emails', 'woocommerce' ),
'placeholder' => __( 'No restrictions', 'woocommerce' ), 'placeholder' => __( 'No restrictions', 'woocommerce' ),
'description' => __( 'Whitelist of billing emails to check against when an order is placed. Separate email addresses with commas. You can also use an asterisk (*) to match parts of an email. For example "*@gmail.com" would match all gmail addresses.', 'woocommerce' ), 'description' => __( 'List of allowed billing emails to check against when an order is placed. Separate email addresses with commas. You can also use an asterisk (*) to match parts of an email. For example "*@gmail.com" would match all gmail addresses.', 'woocommerce' ),
'value' => implode( ', ', (array) $coupon->get_email_restrictions( 'edit' ) ), 'value' => implode( ', ', (array) $coupon->get_email_restrictions( 'edit' ) ),
'desc_tip' => true, 'desc_tip' => true,
'type' => 'email', 'type' => 'email',

View File

@ -41,7 +41,7 @@ defined( 'ABSPATH' ) || exit;
} }
} else { } else {
?> ?>
<li><?php esc_html_e( 'There are no notes yet.', 'woocommerce' ); ?></li> <li class="no-items"><?php esc_html_e( 'There are no notes yet.', 'woocommerce' ); ?></li>
<?php <?php
} }
?> ?>

View File

@ -1022,6 +1022,95 @@ class WC_Cart extends WC_Legacy_Cart {
return false; return false;
} }
if ( $product_data->is_type( 'variation' ) ) {
$missing_attributes = array();
$parent_data = wc_get_product( $product_data->get_parent_id() );
$variation_attributes = $product_data->get_variation_attributes();
// Filter out 'any' variations, which are empty, as they need to be explicitly specified while adding to cart.
$variation_attributes = array_filter( $variation_attributes );
// Gather posted attributes.
$posted_attributes = array();
foreach ( $parent_data->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
$attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
if ( isset( $variation[ $attribute_key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $attribute['is_taxonomy'] ) {
// Don't use wc_clean as it destroys sanitized characters.
$value = sanitize_title( wp_unslash( $variation[ $attribute_key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
} else {
$value = html_entity_decode( wc_clean( wp_unslash( $variation[ $attribute_key ] ) ), ENT_QUOTES, get_bloginfo( 'charset' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
// Don't include if it's empty.
if ( ! empty( $value ) ) {
$posted_attributes[ $attribute_key ] = $value;
}
}
}
// Merge variation attributes and posted attributes.
$posted_and_variation_attributes = array_merge( $variation_attributes, $posted_attributes );
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $parent_data, $posted_attributes );
}
// Do we have a variation ID?
if ( empty( $variation_id ) ) {
throw new Exception( __( 'Please choose product options&hellip;', 'woocommerce' ) );
}
// Check the data we have is valid.
$variation_data = wc_get_product_variation_attributes( $variation_id );
$attributes = array();
foreach ( $parent_data->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
// Get valid value from variation data.
$attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
$valid_value = isset( $variation_data[ $attribute_key ] ) ? $variation_data[ $attribute_key ] : '';
/**
* If the attribute value was posted, check if it's valid.
*
* If no attribute was posted, only error if the variation has an 'any' attribute which requires a value.
*/
if ( isset( $posted_and_variation_attributes[ $attribute_key ] ) ) {
$value = $posted_and_variation_attributes[ $attribute_key ];
// Allow if valid or show error.
if ( $valid_value === $value ) {
$attributes[ $attribute_key ] = $value;
} elseif ( '' === $valid_value && in_array( $value, $attribute->get_slugs(), true ) ) {
// If valid values are empty, this is an 'any' variation so get all possible values.
$attributes[ $attribute_key ] = $value;
} else {
/* translators: %s: Attribute name. */
throw new Exception( sprintf( __( 'Invalid value posted for %s', 'woocommerce' ), wc_attribute_label( $attribute['name'] ) ) );
}
} elseif ( '' === $valid_value ) {
$missing_attributes[] = wc_attribute_label( $attribute['name'] );
}
$variation = $attributes;
}
if ( ! empty( $missing_attributes ) ) {
/* translators: %s: Attribute name. */
throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', count( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
}
}
// Load cart item data - may be added by other plugins. // Load cart item data - may be added by other plugins.
$cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id, $quantity ); $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id, $quantity );
@ -1039,9 +1128,11 @@ class WC_Cart extends WC_Legacy_Cart {
if ( $found_in_cart ) { if ( $found_in_cart ) {
/* translators: %s: product name */ /* translators: %s: product name */
$message = sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_name() ); $message = sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_name() );
/** /**
* Filters message about more than 1 product being added to cart. * Filters message about more than 1 product being added to cart.
* *
* @since 4.5.0
* @param string $message Message. * @param string $message Message.
* @param WC_Product $product_data Product data. * @param WC_Product $product_data Product data.
*/ */
@ -1068,9 +1159,11 @@ class WC_Cart extends WC_Legacy_Cart {
if ( ! $product_data->is_in_stock() ) { if ( ! $product_data->is_in_stock() ) {
/* translators: %s: product name */ /* translators: %s: product name */
$message = sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_name() ); $message = sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_name() );
/** /**
* Filters message about product being out of stock. * Filters message about product being out of stock.
* *
* @since 4.5.0
* @param string $message Message. * @param string $message Message.
* @param WC_Product $product_data Product data. * @param WC_Product $product_data Product data.
*/ */
@ -1083,9 +1176,11 @@ class WC_Cart extends WC_Legacy_Cart {
/* translators: 1: product name 2: quantity in stock */ /* translators: 1: product name 2: quantity in stock */
$message = sprintf( __( 'You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_name(), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ) ); $message = sprintf( __( 'You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_name(), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ) );
/** /**
* Filters message about product not having enough stock. * Filters message about product not having enough stock.
* *
* @since 4.5.0
* @param string $message Message. * @param string $message Message.
* @param WC_Product $product_data Product data. * @param WC_Product $product_data Product data.
* @param int $stock_quantity Quantity remaining. * @param int $stock_quantity Quantity remaining.
@ -1425,10 +1520,8 @@ class WC_Cart extends WC_Legacy_Cart {
} }
if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) { if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) {
if ( ! $this->get_customer()->has_calculated_shipping() ) { if ( ! $this->get_customer()->get_shipping_country() || ! $this->get_customer()->get_shipping_state() || ! $this->get_customer()->get_shipping_postcode() ) {
if ( ! $this->get_customer()->get_shipping_country() || ( ! $this->get_customer()->get_shipping_state() && ! $this->get_customer()->get_shipping_postcode() ) ) { return false;
return false;
}
} }
} }
@ -1509,7 +1602,7 @@ class WC_Cart extends WC_Legacy_Cart {
if ( 0 < $coupon_usage_limit && 0 === get_current_user_id() ) { if ( 0 < $coupon_usage_limit && 0 === get_current_user_id() ) {
// For guest, usage per user has not been enforced yet. Enforce it now. // For guest, usage per user has not been enforced yet. Enforce it now.
$coupon_data_store = $coupon->get_data_store(); $coupon_data_store = $coupon->get_data_store();
$billing_email = strtolower( sanitize_email( $billing_email ) ); $billing_email = strtolower( sanitize_email( $billing_email ) );
if ( $coupon_data_store && $coupon_data_store->get_usage_by_email( $coupon, $billing_email ) >= $coupon_usage_limit ) { if ( $coupon_data_store && $coupon_data_store->get_usage_by_email( $coupon, $billing_email ) >= $coupon_usage_limit ) {
$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ); $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
} }

View File

@ -357,6 +357,7 @@ class WC_Comments {
WHERE comment_parent = 0 WHERE comment_parent = 0
AND comment_post_ID = %d AND comment_post_ID = %d
AND comment_approved = '1' AND comment_approved = '1'
AND comment_type = 'review'
", ",
$product->get_id() $product->get_id()
) )

View File

@ -215,10 +215,12 @@ class WC_Download_Handler {
$filename = current( explode( '?', $filename ) ); $filename = current( explode( '?', $filename ) );
} }
$filename = apply_filters( 'woocommerce_file_download_filename', $filename, $product_id ); $filename = apply_filters( 'woocommerce_file_download_filename', $filename, $product_id );
/** /**
* Filter download method. * Filter download method.
* *
* @since 4.5.0
* @param string $method Download method. * @param string $method Download method.
* @param int $product_id Product ID. * @param int $product_id Product ID.
* @param string $file_path URL to file. * @param string $file_path URL to file.

View File

@ -46,7 +46,7 @@ class WC_Form_Handler {
$user = get_user_by( 'login', sanitize_user( wp_unslash( $_GET['login'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $user = get_user_by( 'login', sanitize_user( wp_unslash( $_GET['login'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$user_id = $user ? $user->ID : 0; $user_id = $user ? $user->ID : 0;
} else { } else {
$user_id = absint( $_GET['id'] ); $user_id = absint( $_GET['id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
} }
$value = sprintf( '%d:%s', $user_id, wp_unslash( $_GET['key'] ) ); // phpcs:ignore $value = sprintf( '%d:%s', $user_id, wp_unslash( $_GET['key'] ) ); // phpcs:ignore
@ -638,7 +638,7 @@ class WC_Form_Handler {
if ( ( ! empty( $_POST['apply_coupon'] ) || ! empty( $_POST['update_cart'] ) || ! empty( $_POST['proceed'] ) ) && wp_verify_nonce( $nonce_value, 'woocommerce-cart' ) ) { if ( ( ! empty( $_POST['apply_coupon'] ) || ! empty( $_POST['update_cart'] ) || ! empty( $_POST['proceed'] ) ) && wp_verify_nonce( $nonce_value, 'woocommerce-cart' ) ) {
$cart_updated = false; $cart_updated = false;
$cart_totals = isset( $_POST['cart'] ) ? wp_unslash( $_POST['cart'] ) : ''; // PHPCS: input var ok, CSRF ok, sanitization ok. $cart_totals = isset( $_POST['cart'] ) ? wp_unslash( $_POST['cart'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( ! WC()->cart->is_empty() && is_array( $cart_totals ) ) { if ( ! WC()->cart->is_empty() && is_array( $cart_totals ) ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) { foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
@ -868,108 +868,16 @@ class WC_Form_Handler {
* @return bool success or not * @return bool success or not
*/ */
private static function add_to_cart_handler_variable( $product_id ) { private static function add_to_cart_handler_variable( $product_id ) {
try { $variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $variations = array();
$missing_attributes = array();
$variations = array();
$variation_attributes = array();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) { foreach ( $_REQUEST as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return false; if ( 'attribute_' !== substr( $key, 0, 10 ) ) {
continue;
} }
// If the $product_id was in fact a variation ID, update the variables. $variations[ sanitize_title( wp_unslash( $key ) ) ] = wp_unslash( $value );
if ( $adding_to_cart->is_type( 'variation' ) ) {
$variation_attributes = $adding_to_cart->get_variation_attributes();
// Filter out 'any' variations, which are empty, as they need to be explicitly specified while adding to cart.
$variation_attributes = array_filter( $variation_attributes );
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return false;
}
}
// Gather posted attributes.
$posted_attributes = array();
foreach ( $adding_to_cart->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
$attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
if ( isset( $_REQUEST[ $attribute_key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( $attribute['is_taxonomy'] ) {
// Don't use wc_clean as it destroys sanitized characters.
$value = sanitize_title( wp_unslash( $_REQUEST[ $attribute_key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
} else {
$value = html_entity_decode( wc_clean( wp_unslash( $_REQUEST[ $attribute_key ] ) ), ENT_QUOTES, get_bloginfo( 'charset' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
$posted_attributes[ $attribute_key ] = $value;
}
}
// Merge variation attributes and posted attributes.
$posted_and_variation_attributes = array_merge( $variation_attributes, $posted_attributes );
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
$variation_id = $data_store->find_matching_product_variation( $adding_to_cart, $posted_attributes );
}
// Do we have a variation ID?
if ( empty( $variation_id ) ) {
throw new Exception( __( 'Please choose product options&hellip;', 'woocommerce' ) );
}
// Check the data we have is valid.
$variation_data = wc_get_product_variation_attributes( $variation_id );
foreach ( $adding_to_cart->get_attributes() as $attribute ) {
if ( ! $attribute['is_variation'] ) {
continue;
}
// Get valid value from variation data.
$attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
$valid_value = isset( $variation_data[ $attribute_key ] ) ? $variation_data[ $attribute_key ] : '';
/**
* If the attribute value was posted, check if it's valid.
*
* If no attribute was posted, only error if the variation has an 'any' attribute which requires a value.
*/
if ( isset( $posted_and_variation_attributes[ $attribute_key ] ) ) {
$value = $posted_and_variation_attributes[ $attribute_key ];
// Allow if valid or show error.
if ( $valid_value === $value ) {
$variations[ $attribute_key ] = $value;
} elseif ( '' === $valid_value && in_array( $value, $attribute->get_slugs(), true ) ) {
// If valid values are empty, this is an 'any' variation so get all possible values.
$variations[ $attribute_key ] = $value;
} else {
/* translators: %s: Attribute name. */
throw new Exception( sprintf( __( 'Invalid value posted for %s', 'woocommerce' ), wc_attribute_label( $attribute['name'] ) ) );
}
} elseif ( '' === $valid_value ) {
$missing_attributes[] = wc_attribute_label( $attribute['name'] );
}
}
if ( ! empty( $missing_attributes ) ) {
/* translators: %s: Attribute name. */
throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', count( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
}
} catch ( Exception $e ) {
wc_add_notice( $e->getMessage(), 'error' );
return false;
} }
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variations ); $passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity, $variation_id, $variations );
@ -1083,7 +991,7 @@ class WC_Form_Handler {
return; return;
} }
if ( in_array( $field, array( 'password_1', 'password_2' ) ) ) { if ( in_array( $field, array( 'password_1', 'password_2' ), true ) ) {
// Don't unslash password fields // Don't unslash password fields
// @see https://github.com/woocommerce/woocommerce/issues/23922. // @see https://github.com/woocommerce/woocommerce/issues/23922.
$posted_fields[ $field ] = $_POST[ $field ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash $posted_fields[ $field ] = $_POST[ $field ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash

View File

@ -753,15 +753,8 @@ class WC_Install {
// Add constraint to download logs if the columns matches. // Add constraint to download logs if the columns matches.
if ( ! empty( $download_permissions_column_type ) && ! empty( $download_log_column_type ) && $download_permissions_column_type === $download_log_column_type ) { if ( ! empty( $download_permissions_column_type ) && ! empty( $download_log_column_type ) && $download_permissions_column_type === $download_log_column_type ) {
$fk_result = $wpdb->get_row( $fk_result = $wpdb->get_row( "SHOW CREATE TABLE {$wpdb->prefix}wc_download_log" ); // WPCS: unprepared SQL ok.
"SELECT COUNT(*) AS fk_count if ( false === strpos( $fk_result->{'Create Table'}, "fk_{$wpdb->prefix}wc_download_log_permission_id" ) ) {
FROM information_schema.TABLE_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = '{$wpdb->dbname}'
AND CONSTRAINT_NAME = 'fk_{$wpdb->prefix}wc_download_log_permission_id'
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
AND TABLE_NAME = '{$wpdb->prefix}wc_download_log'"
);
if ( 0 === (int) $fk_result->fk_count ) {
$wpdb->query( $wpdb->query(
"ALTER TABLE `{$wpdb->prefix}wc_download_log` "ALTER TABLE `{$wpdb->prefix}wc_download_log`
ADD CONSTRAINT `fk_{$wpdb->prefix}wc_download_log_permission_id` ADD CONSTRAINT `fk_{$wpdb->prefix}wc_download_log_permission_id`

View File

@ -489,7 +489,7 @@ class WC_Post_Types {
* @since 4.4.0 * @since 4.4.0
* @return bool * @return bool
*/ */
public function updated_term_messages( $messages ) { public static function updated_term_messages( $messages ) {
$messages['product_cat'] = array( $messages['product_cat'] = array(
0 => '', 0 => '',
1 => __( 'Category added.', 'woocommerce' ), 1 => __( 'Category added.', 'woocommerce' ),

View File

@ -44,8 +44,6 @@ class WC_Query {
add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 ); add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 );
add_action( 'parse_request', array( $this, 'parse_request' ), 0 ); add_action( 'parse_request', array( $this, 'parse_request' ), 0 );
add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) ); add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
add_filter( 'the_posts', array( $this, 'handle_get_posts' ) );
add_filter( 'found_posts', array( $this, 'adjust_posts_count' ), 10, 2 );
add_filter( 'get_pagenum_link', array( $this, 'remove_add_to_cart_pagination' ), 10, 1 ); add_filter( 'get_pagenum_link', array( $this, 'remove_add_to_cart_pagination' ), 10, 1 );
} }
$this->init_query_vars(); $this->init_query_vars();
@ -355,11 +353,15 @@ class WC_Query {
/** /**
* Handler for the 'the_posts' WP filter. * Handler for the 'the_posts' WP filter.
* *
* @param array $posts Posts from WP Query. * @param array $posts Posts from WP Query.
* @param WP_Query $query Current query.
* *
* @return array * @return array
*/ */
public function handle_get_posts( $posts ) { public function handle_get_posts( $posts, $query ) {
if ( 'product_query' !== $query->get( 'wc_query' ) ) {
return $posts;
}
$this->adjust_total_pages(); $this->adjust_total_pages();
$this->remove_product_query_filters( $posts ); $this->remove_product_query_filters( $posts );
return $posts; return $posts;
@ -511,7 +513,8 @@ class WC_Query {
// Additonal hooks to change WP Query. // Additonal hooks to change WP Query.
add_filter( 'posts_clauses', array( $this, 'price_filter_post_clauses' ), 10, 2 ); add_filter( 'posts_clauses', array( $this, 'price_filter_post_clauses' ), 10, 2 );
add_filter( 'the_posts', array( $this, 'handle_get_posts' ), 10, 2 );
add_filter( 'found_posts', array( $this, 'adjust_posts_count' ), 10, 2 );
do_action( 'woocommerce_product_query', $q, $this ); do_action( 'woocommerce_product_query', $q, $this );
} }

View File

@ -203,7 +203,7 @@ class WC_Regenerate_Images {
return $image; return $image;
} }
// Use a whitelist of sizes we want to resize. Ignore others. // List of sizes we want to resize. Ignore others.
if ( ! $image || ! in_array( $size, apply_filters( 'woocommerce_image_sizes_to_resize', array( 'woocommerce_thumbnail', 'woocommerce_gallery_thumbnail', 'woocommerce_single', 'shop_thumbnail', 'shop_catalog', 'shop_single' ) ), true ) ) { if ( ! $image || ! in_array( $size, apply_filters( 'woocommerce_image_sizes_to_resize', array( 'woocommerce_thumbnail', 'woocommerce_gallery_thumbnail', 'woocommerce_single', 'shop_thumbnail', 'shop_catalog', 'shop_single' ) ), true ) ) {
return $image; return $image;
} }

View File

@ -80,7 +80,7 @@ class WC_Validation {
$valid = (bool) preg_match( '/([AC-FHKNPRTV-Y]\d{2}|D6W)[0-9AC-FHKNPRTV-Y]{4}/', wc_normalize_postcode( $postcode ) ); $valid = (bool) preg_match( '/([AC-FHKNPRTV-Y]\d{2}|D6W)[0-9AC-FHKNPRTV-Y]{4}/', wc_normalize_postcode( $postcode ) );
break; break;
case 'JP': case 'JP':
$valid = (bool) preg_match( '/^([0-9]{3})([-])([0-9]{4})$/', $postcode ); $valid = (bool) preg_match( '/^([0-9]{3})([-]?)([0-9]{4})$/', $postcode );
break; break;
case 'PT': case 'PT':
$valid = (bool) preg_match( '/^([0-9]{4})([-])([0-9]{3})$/', $postcode ); $valid = (bool) preg_match( '/^([0-9]{4})([-])([0-9]{3})$/', $postcode );
@ -105,6 +105,9 @@ class WC_Validation {
case 'SI': case 'SI':
$valid = (bool) preg_match( '/^([1-9][0-9]{3})$/', $postcode ); $valid = (bool) preg_match( '/^([1-9][0-9]{3})$/', $postcode );
break; break;
case 'LI':
$valid = (bool) preg_match( '/^(94[8-9][0-9])$/', $postcode );
break;
default: default:
$valid = true; $valid = true;
break; break;

View File

@ -912,60 +912,4 @@ final class WooCommerce {
public function is_wc_admin_active() { public function is_wc_admin_active() {
return function_exists( 'wc_admin_url' ); return function_exists( 'wc_admin_url' );
} }
/**
* Call a user function. This should be used to execute any non-idempotent function, especially
* those in the `includes` directory or provided by WordPress.
*
* This method can be useful for unit tests, since functions called using this method
* can be easily mocked by using WC_Unit_Test_Case::register_legacy_proxy_function_mocks.
*
* @param string $function_name The function to execute.
* @param mixed ...$parameters The parameters to pass to the function.
*
* @return mixed The result from the function.
*
* @since 4.4
*/
public function call_function( $function_name, ...$parameters ) {
return wc_get_container()->get( LegacyProxy::class )->call_function( $function_name, ...$parameters );
}
/**
* Call a static method in a class. This should be used to execute any non-idempotent method in classes
* from the `includes` directory.
*
* This method can be useful for unit tests, since methods called using this method
* can be easily mocked by using WC_Unit_Test_Case::register_legacy_proxy_static_mocks.
*
* @param string $class_name The name of the class containing the method.
* @param string $method_name The name of the method.
* @param mixed ...$parameters The parameters to pass to the method.
*
* @return mixed The result from the method.
*
* @since 4.4
*/
public function call_static( $class_name, $method_name, ...$parameters ) {
return wc_get_container()->get( LegacyProxy::class )->call_static( $class_name, $method_name, ...$parameters );
}
/**
* Gets an instance of a given legacy class.
* This must not be used to get instances of classes in the `src` directory.
*
* This method can be useful for unit tests, since objects obtained using this method
* can be easily mocked by using WC_Unit_Test_Case::register_legacy_proxy_class_mocks.
*
* @param string $class_name The name of the class to get an instance for.
* @param mixed ...$args Parameters to be passed to the class constructor or to the appropriate internal 'get_instance_of_' method.
*
* @return object The instance of the class.
* @throws \Exception The requested class belongs to the `src` directory, or there was an error creating an instance of the class.
*
* @since 4.4
*/
public function get_instance_of( string $class_name, ...$args ) {
return wc_get_container()->get( LegacyProxy::class )->get_instance_of( $class_name, ...$args );
}
} }

View File

@ -1559,10 +1559,17 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
} }
$post_types = $include_variations ? array( 'product', 'product_variation' ) : array( 'product' ); $post_types = $include_variations ? array( 'product', 'product_variation' ) : array( 'product' );
$join_query = '';
$type_where = ''; $type_where = '';
$status_where = ''; $status_where = '';
$limit_query = ''; $limit_query = '';
// When searching variations we should include the parent's meta table for use in searches.
if ( $include_variations ) {
$join_query = " LEFT JOIN {$wpdb->wc_product_meta_lookup} parent_wc_product_meta_lookup
ON posts.post_type = 'product_variation' AND parent_wc_product_meta_lookup.product_id = posts.post_parent ";
}
/** /**
* Hook woocommerce_search_products_post_statuses. * Hook woocommerce_search_products_post_statuses.
* *
@ -1602,8 +1609,16 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$searchand = ''; $searchand = '';
foreach ( $search_terms as $search_term ) { foreach ( $search_terms as $search_term ) {
$like = '%' . $wpdb->esc_like( $search_term ) . '%'; $like = '%' . $wpdb->esc_like( $search_term ) . '%';
$term_group_query .= $wpdb->prepare( " {$searchand} ( ( posts.post_title LIKE %s) OR ( posts.post_excerpt LIKE %s) OR ( posts.post_content LIKE %s ) OR ( wc_product_meta_lookup.sku LIKE %s ) )", $like, $like, $like, $like ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// Variations should also search the parent's meta table for fallback fields.
if ( $include_variations ) {
$variation_query = $wpdb->prepare( ' OR ( wc_product_meta_lookup.sku = "" AND parent_wc_product_meta_lookup.sku LIKE %s ) ', $like );
} else {
$variation_query = '';
}
$term_group_query .= $wpdb->prepare( " {$searchand} ( ( posts.post_title LIKE %s) OR ( posts.post_excerpt LIKE %s) OR ( posts.post_content LIKE %s ) OR ( wc_product_meta_lookup.sku LIKE %s ) $variation_query)", $like, $like, $like, $like ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$searchand = ' AND '; $searchand = ' AND ';
} }
@ -1643,6 +1658,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
// phpcs:disable // phpcs:disable
"SELECT DISTINCT posts.ID as product_id, posts.post_parent as parent_id FROM {$wpdb->posts} posts "SELECT DISTINCT posts.ID as product_id, posts.post_parent as parent_id FROM {$wpdb->posts} posts
LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON posts.ID = wc_product_meta_lookup.product_id LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON posts.ID = wc_product_meta_lookup.product_id
$join_query
WHERE posts.post_type IN ('" . implode( "','", $post_types ) . "') WHERE posts.post_type IN ('" . implode( "','", $post_types ) . "')
$search_where $search_where
$status_where $status_where
@ -1692,7 +1708,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$product_type = 'variation'; $product_type = 'variation';
} elseif ( 'product' === $post_type ) { } elseif ( 'product' === $post_type ) {
$terms = get_the_terms( $product_id, 'product_type' ); $terms = get_the_terms( $product_id, 'product_type' );
$product_type = ! empty( $terms ) ? sanitize_title( current( $terms )->name ) : 'simple'; $product_type = ! empty( $terms ) && ! is_wp_error( $terms ) ? sanitize_title( current( $terms )->name ) : 'simple';
} else { } else {
$product_type = false; $product_type = false;
} }

View File

@ -42,7 +42,7 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
$this->id = 'paypal'; $this->id = 'paypal';
$this->has_fields = false; $this->has_fields = false;
$this->order_button_text = __( 'Proceed to PayPal', 'woocommerce' ); $this->order_button_text = __( 'Proceed to PayPal', 'woocommerce' );
$this->method_title = __( 'PayPal', 'woocommerce' ); $this->method_title = __( 'PayPal Standard', 'woocommerce' );
/* translators: %s: Link to WC system status page */ /* translators: %s: Link to WC system status page */
$this->method_description = __( 'PayPal Standard redirects customers to PayPal to enter their payment information.', 'woocommerce' ); $this->method_description = __( 'PayPal Standard redirects customers to PayPal to enter their payment information.', 'woocommerce' );
$this->supports = array( $this->supports = array(
@ -283,7 +283,7 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
?> ?>
<div class="inline error"> <div class="inline error">
<p> <p>
<strong><?php esc_html_e( 'Gateway disabled', 'woocommerce' ); ?></strong>: <?php esc_html_e( 'PayPal does not support your store currency.', 'woocommerce' ); ?> <strong><?php esc_html_e( 'Gateway disabled', 'woocommerce' ); ?></strong>: <?php esc_html_e( 'PayPal Standard does not support your store currency.', 'woocommerce' ); ?>
</p> </p>
</div> </div>
<?php <?php

View File

@ -1,6 +1,6 @@
<?php <?php
/** /**
* Settings for PayPal Gateway. * Settings for PayPal Standard Gateway.
* *
* @package WooCommerce\Classes\Payment * @package WooCommerce\Classes\Payment
*/ */

View File

@ -511,7 +511,7 @@ class WC_REST_Coupons_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),

View File

@ -350,7 +350,7 @@ class WC_REST_Customers_V2_Controller extends WC_REST_Customers_V1_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),

View File

@ -410,7 +410,7 @@ class WC_REST_Order_Refunds_V2_Controller extends WC_REST_Orders_V2_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),
@ -432,13 +432,13 @@ class WC_REST_Order_Refunds_V2_Controller extends WC_REST_Orders_V2_Controller {
), ),
'name' => array( 'name' => array(
'description' => __( 'Product name.', 'woocommerce' ), 'description' => __( 'Product name.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
'readonly' => true, 'readonly' => true,
), ),
'product_id' => array( 'product_id' => array(
'description' => __( 'Product ID.', 'woocommerce' ), 'description' => __( 'Product ID.', 'woocommerce' ),
'type' => array( 'integer', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
'readonly' => true, 'readonly' => true,
), ),
@ -535,7 +535,7 @@ class WC_REST_Order_Refunds_V2_Controller extends WC_REST_Orders_V2_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
'readonly' => true, 'readonly' => true,
), ),

View File

@ -1170,7 +1170,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),
@ -1191,12 +1191,12 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'name' => array( 'name' => array(
'description' => __( 'Product name.', 'woocommerce' ), 'description' => __( 'Product name.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'product_id' => array( 'product_id' => array(
'description' => __( 'Product ID.', 'woocommerce' ), 'description' => __( 'Product ID.', 'woocommerce' ),
'type' => array( 'integer' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'variation_id' => array( 'variation_id' => array(
@ -1282,7 +1282,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),
@ -1373,7 +1373,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),
@ -1397,12 +1397,12 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'method_title' => array( 'method_title' => array(
'description' => __( 'Shipping method name.', 'woocommerce' ), 'description' => __( 'Shipping method name.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'method_id' => array( 'method_id' => array(
'description' => __( 'Shipping method ID.', 'woocommerce' ), 'description' => __( 'Shipping method ID.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'instance_id' => array( 'instance_id' => array(
@ -1464,7 +1464,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),
@ -1488,7 +1488,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'name' => array( 'name' => array(
'description' => __( 'Fee name.', 'woocommerce' ), 'description' => __( 'Fee name.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'tax_class' => array( 'tax_class' => array(
@ -1562,7 +1562,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),
@ -1586,7 +1586,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'code' => array( 'code' => array(
'description' => __( 'Coupon code.', 'woocommerce' ), 'description' => __( 'Coupon code.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'discount' => array( 'discount' => array(
@ -1620,7 +1620,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),

View File

@ -799,7 +799,7 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr
), ),
'manage_stock' => array( 'manage_stock' => array(
'description' => __( 'Stock management at variation level.', 'woocommerce' ), 'description' => __( 'Stock management at variation level.', 'woocommerce' ),
'type' => array( 'boolean', 'null' ), 'type' => 'mixed',
'default' => false, 'default' => false,
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
@ -982,7 +982,7 @@ class WC_REST_Product_Variations_V2_Controller extends WC_REST_Products_V2_Contr
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),

View File

@ -2084,7 +2084,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),

View File

@ -530,18 +530,12 @@ class WC_REST_Setting_Options_V2_Controller extends WC_REST_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Setting value.', 'woocommerce' ), 'description' => __( 'Setting value.', 'woocommerce' ),
'type' => array( 'string', 'array', 'null' ), 'type' => 'mixed',
'items' => array(
'type' => array( 'string', 'null' ),
),
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'default' => array( 'default' => array(
'description' => __( 'Default value for the setting.', 'woocommerce' ), 'description' => __( 'Default value for the setting.', 'woocommerce' ),
'type' => array( 'string', 'array', 'null' ), 'type' => 'mixed',
'items' => array(
'type' => array( 'string', 'null' ),
),
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
'readonly' => true, 'readonly' => true,
), ),

View File

@ -93,16 +93,57 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
return $endpoint_args; return $endpoint_args;
} }
foreach ( $endpoint_args as $field_id => $params ) { $endpoint_args = $this->adjust_wp_5_5_datatype_compatibility( $endpoint_args );
/**
* Custom types are not supported as of WP 5.5, this translates type => 'date-time' to type => 'string' with format date-time. return $endpoint_args;
*/ }
if ( 'date-time' === $params['type'] ) {
$endpoint_args[ $field_id ]['type'] = 'string'; /**
$endpoint_args[ $field_id ]['format'] = 'date-time'; * Change datatypes `date-time` to string, and `mixed` to composite of all built in types. This is required for maintaining forward compatibility with WP 5.5 since custom post types are not supported anymore.
} *
* See @link https://core.trac.wordpress.org/changeset/48306
*
* We still use the 'mixed' type, since if we convert to composite type everywhere, it won't work in 5.4 anymore because they require to define the full schema.
*
* @param array $endpoint_args Schema with datatypes to convert.
* @return mixed Schema with converted datatype.
*/
protected function adjust_wp_5_5_datatype_compatibility( $endpoint_args ) {
if ( version_compare( get_bloginfo( 'version' ), '5.5', '<' ) ) {
return $endpoint_args;
} }
foreach ( $endpoint_args as $field_id => $params ) {
if ( ! isset( $params['type'] ) ) {
continue;
}
/**
* Custom types are not supported as of WP 5.5, this translates type => 'date-time' to type => 'string'.
*/
if ( 'date-time' === $params['type'] ) {
$params['type'] = array( 'null', 'string' );
}
/**
* WARNING: Order of fields here is important, types of fields are ordered from most specific to least specific as perceived by core's built-in type validation methods.
*/
if ( 'mixed' === $params['type'] ) {
$params['type'] = array( 'null', 'object', 'string', 'number', 'boolean', 'integer', 'array' );
}
if ( isset( $params['properties'] ) ) {
$params['properties'] = $this->adjust_wp_5_5_datatype_compatibility( $params['properties'] );
}
if ( isset( $params['items'] ) && isset( $params['items']['properties'] ) ) {
$params['items']['properties'] = $this->adjust_wp_5_5_datatype_compatibility( $params['items']['properties'] );
}
$endpoint_args[ $field_id ] = $params;
}
return $endpoint_args; return $endpoint_args;
} }

View File

@ -91,8 +91,6 @@ abstract class WC_REST_CRUD_Controller extends WC_REST_Posts_Controller {
return true; return true;
} }
/** /**
* Get object permalink. * Get object permalink.
* *

View File

@ -293,7 +293,7 @@ class WC_REST_Customers_Controller extends WC_REST_Customers_V2_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),

View File

@ -736,7 +736,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),

View File

@ -1288,7 +1288,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
), ),
'value' => array( 'value' => array(
'description' => __( 'Meta value.', 'woocommerce' ), 'description' => __( 'Meta value.', 'woocommerce' ),
'type' => array( 'string', 'null' ), 'type' => 'mixed',
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
), ),

View File

@ -199,18 +199,12 @@ class WC_REST_Setting_Options_Controller extends WC_REST_Setting_Options_V2_Cont
), ),
'value' => array( 'value' => array(
'description' => __( 'Setting value.', 'woocommerce' ), 'description' => __( 'Setting value.', 'woocommerce' ),
'type' => array( 'string', 'array', 'null' ), 'type' => 'mixed',
'items' => array(
'type' => array( 'string', 'null' ),
),
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
), ),
'default' => array( 'default' => array(
'description' => __( 'Default value for the setting.', 'woocommerce' ), 'description' => __( 'Default value for the setting.', 'woocommerce' ),
'type' => array( 'string', 'array', 'null' ), 'type' => 'mixed',
'items' => array(
'type' => array( 'string', 'null' ),
),
'context' => array( 'view', 'edit' ), 'context' => array( 'view', 'edit' ),
'readonly' => true, 'readonly' => true,
), ),

View File

@ -521,7 +521,7 @@ class WC_Shortcode_Products {
* Get wrapper classes. * Get wrapper classes.
* *
* @since 3.2.0 * @since 3.2.0
* @param array $columns Number of columns. * @param int $columns Number of columns.
* @return array * @return array
*/ */
protected function get_wrapper_classes( $columns ) { protected function get_wrapper_classes( $columns ) {

View File

@ -86,7 +86,7 @@ class WC_Tracks_Event {
$_event = (object) array_merge( (array) $event, $validated ); $_event = (object) array_merge( (array) $event, $validated );
// If you want to blacklist property names, do it here. // If you want to block property names, do it here.
// Make sure we have an event timestamp. // Make sure we have an event timestamp.
if ( ! isset( $_event->_ts ) ) { if ( ! isset( $_event->_ts ) ) {
$_event->_ts = WC_Tracks_Client::build_timestamp(); $_event->_ts = WC_Tracks_Client::build_timestamp();
@ -150,13 +150,13 @@ class WC_Tracks_Event {
return; return;
} }
$whitelisted_key_names = array( $allowed_key_names = array(
'anonId', 'anonId',
'Browser_Type', 'Browser_Type',
); );
foreach ( array_keys( (array) $event ) as $key ) { foreach ( array_keys( (array) $event ) as $key ) {
if ( in_array( $key, $whitelisted_key_names, true ) ) { if ( in_array( $key, $allowed_key_names, true ) ) {
continue; continue;
} }
if ( ! self::prop_name_is_valid( $key ) ) { if ( ! self::prop_name_is_valid( $key ) ) {

View File

@ -30,7 +30,7 @@ class WC_Admin_Setup_Wizard_Tracking {
add_action( 'shutdown', array( $this, 'track_skip_step' ), 1 ); add_action( 'shutdown', array( $this, 'track_skip_step' ), 1 );
add_action( 'add_option_woocommerce_allow_tracking', array( $this, 'track_start' ), 10, 2 ); add_action( 'add_option_woocommerce_allow_tracking', array( $this, 'track_start' ), 10, 2 );
add_action( 'admin_init', array( $this, 'track_ready_next_steps' ), 1 ); add_action( 'admin_init', array( $this, 'track_ready_next_steps' ), 1 );
add_action( 'wp_print_scripts', array( $this, 'dequeue_non_whitelisted_scripts' ) ); add_action( 'wp_print_scripts', array( $this, 'dequeue_non_allowed_scripts' ) );
$this->add_step_save_events(); $this->add_step_save_events();
add_action( 'woocommerce_setup_footer', array( $this, 'add_footer_scripts' ) ); add_action( 'woocommerce_setup_footer', array( $this, 'add_footer_scripts' ) );
} }
@ -56,12 +56,12 @@ class WC_Admin_Setup_Wizard_Tracking {
/** /**
* Dequeue unwanted scripts from OBW footer. * Dequeue unwanted scripts from OBW footer.
*/ */
public function dequeue_non_whitelisted_scripts() { public function dequeue_non_allowed_scripts() {
global $wp_scripts; global $wp_scripts;
$whitelist = array( 'woo-tracks' ); $allowed = array( 'woo-tracks' );
foreach ( $wp_scripts->queue as $script ) { foreach ( $wp_scripts->queue as $script ) {
if ( in_array( $script, $whitelist, true ) ) { if ( in_array( $script, $allowed, true ) ) {
continue; continue;
} }
wp_dequeue_script( $script ); wp_dequeue_script( $script );

View File

@ -11,12 +11,13 @@ defined( 'ABSPATH' ) || exit;
* This class adds actions to track usage of WooCommerce Settings. * This class adds actions to track usage of WooCommerce Settings.
*/ */
class WC_Settings_Tracking { class WC_Settings_Tracking {
/** /**
* Whitelisted WooCommerce settings to potentially track updates for. * List of allowed WooCommerce settings to potentially track updates for.
* *
* @var array * @var array
*/ */
protected $whitelist = array(); protected $allowed_options = array();
/** /**
* WooCommerce settings that have been updated (and will be tracked). * WooCommerce settings that have been updated (and will be tracked).
@ -30,22 +31,22 @@ class WC_Settings_Tracking {
*/ */
public function init() { public function init() {
add_action( 'woocommerce_settings_page_init', array( $this, 'track_settings_page_view' ) ); add_action( 'woocommerce_settings_page_init', array( $this, 'track_settings_page_view' ) );
add_action( 'woocommerce_update_option', array( $this, 'add_option_to_whitelist' ) ); add_action( 'woocommerce_update_option', array( $this, 'add_option_to_list' ) );
add_action( 'woocommerce_update_options', array( $this, 'send_settings_change_event' ) ); add_action( 'woocommerce_update_options', array( $this, 'send_settings_change_event' ) );
} }
/** /**
* Add a WooCommerce option name to our whitelist and attach * Add a WooCommerce option name to our allowed options list and attach
* the `update_option` hook. Rather than inspecting every updated * the `update_option` hook. Rather than inspecting every updated
* option and pattern matching for "woocommerce", just build a dynamic * option and pattern matching for "woocommerce", just build a dynamic
* whitelist for WooCommerce options that might get updated. * list for WooCommerce options that might get updated.
* *
* See `woocommerce_update_option` hook. * See `woocommerce_update_option` hook.
* *
* @param array $option WooCommerce option (config) that might get updated. * @param array $option WooCommerce option (config) that might get updated.
*/ */
public function add_option_to_whitelist( $option ) { public function add_option_to_list( $option ) {
$this->whitelist[] = $option['id']; $this->allowed_options[] = $option['id'];
// Delay attaching this action since it could get fired a lot. // Delay attaching this action since it could get fired a lot.
if ( false === has_action( 'update_option', array( $this, 'track_setting_change' ) ) ) { if ( false === has_action( 'update_option', array( $this, 'track_setting_change' ) ) ) {
@ -62,7 +63,7 @@ class WC_Settings_Tracking {
*/ */
public function track_setting_change( $option_name, $old_value, $new_value ) { public function track_setting_change( $option_name, $old_value, $new_value ) {
// Make sure this is a WooCommerce option. // Make sure this is a WooCommerce option.
if ( ! in_array( $option_name, $this->whitelist, true ) ) { if ( ! in_array( $option_name, $this->allowed_options, true ) ) {
return; return;
} }

View File

@ -1918,8 +1918,8 @@ add_action( 'woocommerce_cleanup_logs', 'wc_cleanup_logs' );
/** /**
* Prints human-readable information about a variable. * Prints human-readable information about a variable.
* *
* Some server environments blacklist some debugging functions. This function provides a safe way to * Some server environments block some debugging functions. This function provides a safe way to
* turn an expression into a printable, readable form without calling blacklisted functions. * turn an expression into a printable, readable form without calling blocked functions.
* *
* @since 3.0 * @since 3.0
* *

View File

@ -82,7 +82,7 @@ function wc_get_coupon_code_by_id( $id ) {
} }
/** /**
* Get coupon code by ID. * Get coupon ID by code.
* *
* @since 3.0.0 * @since 3.0.0
* @param string $code Coupon code. * @param string $code Coupon code.

View File

@ -778,6 +778,7 @@ function wc_order_fully_refunded( $order_id ) {
} }
// Create the refund object. // Create the refund object.
wc_switch_to_site_locale();
wc_create_refund( wc_create_refund(
array( array(
'amount' => $max_refund, 'amount' => $max_refund,
@ -786,6 +787,7 @@ function wc_order_fully_refunded( $order_id ) {
'line_items' => array(), 'line_items' => array(),
) )
); );
wc_restore_locale();
$order->add_order_note( __( 'Order status set to refunded. To return funds to the customer you will need to issue a refund through your payment gateway.', 'woocommerce' ) ); $order->add_order_note( __( 'Order status set to refunded. To return funds to the customer you will need to issue a refund through your payment gateway.', 'woocommerce' ) );
} }

View File

@ -2811,6 +2811,10 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) {
case 'tel': case 'tel':
$field .= '<input type="' . esc_attr( $args['type'] ) . '" class="input-text ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" placeholder="' . esc_attr( $args['placeholder'] ) . '" value="' . esc_attr( $value ) . '" ' . implode( ' ', $custom_attributes ) . ' />'; $field .= '<input type="' . esc_attr( $args['type'] ) . '" class="input-text ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" placeholder="' . esc_attr( $args['placeholder'] ) . '" value="' . esc_attr( $value ) . '" ' . implode( ' ', $custom_attributes ) . ' />';
break;
case 'hidden':
$field .= '<input type="' . esc_attr( $args['type'] ) . '" class="input-hidden ' . esc_attr( implode( ' ', $args['input_class'] ) ) . '" name="' . esc_attr( $key ) . '" id="' . esc_attr( $args['id'] ) . '" value="' . esc_attr( $value ) . '" ' . implode( ' ', $custom_attributes ) . ' />';
break; break;
case 'select': case 'select':
$field = ''; $field = '';

View File

@ -162,10 +162,10 @@ function wc_create_new_customer_username( $email, $new_user_args = array(), $suf
} }
/** /**
* WordPress 4.4 - filters the list of blacklisted usernames. * WordPress 4.4 - filters the list of blocked usernames.
* *
* @since 3.7.0 * @since 3.7.0
* @param array $usernames Array of blacklisted usernames. * @param array $usernames Array of blocked usernames.
*/ */
$illegal_logins = (array) apply_filters( 'illegal_user_logins', array() ); $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );

17010
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "woocommerce", "name": "woocommerce",
"title": "WooCommerce", "title": "WooCommerce",
"version": "4.4.0", "version": "4.5.0",
"homepage": "https://woocommerce.com/", "homepage": "https://woocommerce.com/",
"repository": { "repository": {
"type": "git", "type": "git",
@ -9,11 +9,17 @@
}, },
"license": "GPL-3.0+", "license": "GPL-3.0+",
"main": "Gruntfile.js", "main": "Gruntfile.js",
"config": {
"wp_org_slug": "woocommerce"
},
"scripts": { "scripts": {
"build": "grunt && npm run makepot && npm run build:packages", "build": "./bin/build-zip.sh",
"build:core": "grunt && npm run makepot",
"build:dev": "npm run build:core && npm run build:packages",
"build-watch": "grunt watch", "build-watch": "grunt watch",
"build:packages": "lerna run build", "build:packages": "lerna run build",
"build:zip": "./bin/build-zip.sh", "build:zip": "npm run build && composer install && npm run build:dev",
"build:assets": "grunt assets",
"lint:js": "eslint assets/js --ext=js", "lint:js": "eslint assets/js --ext=js",
"docker:up": "npm explore @woocommerce/e2e-environment -- npm run docker:up", "docker:up": "npm explore @woocommerce/e2e-environment -- npm run docker:up",
"docker:down": "npm explore @woocommerce/e2e-environment -- npm run docker:down", "docker:down": "npm explore @woocommerce/e2e-environment -- npm run docker:down",
@ -37,8 +43,8 @@
"@woocommerce/model-factories": "file:tests/e2e/factories", "@woocommerce/model-factories": "file:tests/e2e/factories",
"@wordpress/babel-plugin-import-jsx-pragma": "1.1.3", "@wordpress/babel-plugin-import-jsx-pragma": "1.1.3",
"@wordpress/babel-preset-default": "3.0.2", "@wordpress/babel-preset-default": "3.0.2",
"@wordpress/e2e-test-utils": "4.6.0",
"@wordpress/eslint-plugin": "7.1.0", "@wordpress/eslint-plugin": "7.1.0",
"@woocommerce/e2e-utils": "file:tests/e2e/utils",
"autoprefixer": "9.8.6", "autoprefixer": "9.8.6",
"babel-eslint": "10.1.0", "babel-eslint": "10.1.0",
"chai": "4.2.0", "chai": "4.2.0",

View File

@ -31,6 +31,14 @@
<!-- Rules --> <!-- Rules -->
<rule ref="WooCommerce-Core" /> <rule ref="WooCommerce-Core" />
<rule ref="WooCommerce.Functions.InternalInjectionMethod">
<include-pattern>src/</include-pattern>
<include-pattern>tests/php/src/</include-pattern>
<properties>
<property name="injectionMethod" value="init"/>
</properties>
</rule>
<rule ref="WordPress.WP.I18n"> <rule ref="WordPress.WP.I18n">
<properties> <properties>
<property name="text_domain" type="array" value="woocommerce" /> <property name="text_domain" type="array" value="woocommerce" />

View File

@ -1,146 +1,133 @@
=== WooCommerce === === WooCommerce ===
Contributors: automattic, mikejolley, jameskoster, claudiosanches, kloon, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony Contributors: automattic, mikejolley, jameskoster, claudiosanches, kloon, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony, konamiman, sadowski
Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, downloads, payments, paypal, storefront, stripe, woo commerce Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, downloads, payments, paypal, storefront, stripe, woo commerce
Requires at least: 5.2 Requires at least: 5.2
Tested up to: 5.4 Tested up to: 5.5
Requires PHP: 7.0 Requires PHP: 7.0
Stable tag: 4.3.1 Stable tag: 4.4.1
License: GPLv3 License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html License URI: https://www.gnu.org/licenses/gpl-3.0.html
WooCommerce is a flexible, open-source eCommerce solution built on WordPress. Sell anything, anywhere and make your way. WooCommerce is the worlds most popular open-source eCommerce solution.
== Description == == Description ==
WooCommerce is a flexible, open-source eCommerce solution built on WordPress. Whether you're launching a business, taking an existing brick and mortar store online, or designing sites for clients you can get started quickly and build exactly the store you want. WooCommerce is the worlds most popular open-source eCommerce solution.
Activate the free WooCommerce plugin on a new or existing WordPress site, follow the optional guided tour, and set up a new store in minutes with: Our core platform is free, flexible, and amplified by a global community. The freedom of open-source means you retain full ownership of your stores content and data forever.
- Product, Cart, and Checkout pages Whether youre launching a business, taking brick-and-mortar retail online, or developing sites for clients, use WooCommerce for a store that powerfully blends content and commerce.
- Secure payments by credit card and alternatives
- Configurable shipping options, including flat rates and [label printing](https://woocommerce.com/products/shipping/?utm_source=wp%20org%20repo%20listing&utm_content=3.6)
- Integrate content and commerce across your site via modular blocks
- [Automated tax calculations](https://woocommerce.com/products/tax/?utm_source=wp%20org%20repo%20listing&utm_content=3.6)
- [Google Analytics](https://woocommerce.com/products/woocommerce-google-analytics/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [MailChimp](https://woocommerce.com/products/mailchimp-for-woocommerce/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), and [Facebook](https://woocommerce.com/products/facebook/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) integration
- [Central store dashboard](https://woocommerce.com/posts/woocommerce-admin-a-new-central-dashboard-for-woocommerce/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) with key metrics, and more.
Beyond the basics, WooCommerce is fully customizable and extendable: - **Create beautiful, enticing storefronts** with [themes](https://woocommerce.com/product-category/themes/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) suited to your brand and industry.
- **Customize pages in minutes** using modular [product blocks](https://docs.woocommerce.com/document/woocommerce-blocks/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
- Showcase physical and digital goods, product variations, custom configurations, instant downloads, and affiliate items. [Bookings](https://woocommerce.com/products/woocommerce-bookings/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), [memberships](https://woocommerce.com/products/woocommerce-memberships/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), [subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), and [dynamic pricing](https://woocommerce.com/products/dynamic-pricing/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) rules are only an extension away.
- **Rise to the top of search results** by leveraging [WordPress SEO advantage](https://www.searchenginejournal.com/wordpress-best-cms-seo/).
- Choose a design to suit your brand and industry. Built-in tools and popular integrations help you efficiently manage your business operations. Many services are free to add with a single click via the optional [Setup Wizard](https://docs.woocommerce.com/document/woocommerce-setup-wizard/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
- Enhance your store with free and paid extensions: add features and integrate with local and global eCommerce web services.
- Inspect and modify any aspect of the core plugin code.
- Leverage hooks and filters to modify functions.
- Build on top of the REST API and webhooks.
Open-source freedoms mean full ownership of content and data forever plus the expertise of a friendly [global community](https://woocommerce.com/meetups/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). - **Choose how you want to get paid**. Conveniently manage payments from the comfort of your store with [WooCommerce Payments](https://woocommerce.com/payments/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) (U.S.-only). Securely accept cards, mobile wallets, bank transfers, and cash thanks to [100+ payment gateways](https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) including [Stripe](https://woocommerce.com/products/stripe/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), [PayPal](https://woocommerce.com/products/woocommerce-gateway-paypal-checkout/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), and [Square](https://woocommerce.com/products/square/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
- **Configure your shipping options**. Print USPS labels right from your dashboard and even schedule a pickup with [WooCommerce Shipping](https://woocommerce.com/products/shipping/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) (U.S.-only). Connect with [well-known carriers](https://woocommerce.com/product-category/woocommerce-extensions/shipping-methods/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) such as UPS, FedEx, and ShipStation plus a wide variety of delivery, inventory, and fulfillment solutions for your locale.
- **Simplify sales tax**. Add [WooCommerce Tax](https://woocommerce.com/products/tax/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) or [similar integrated services](https://woocommerce.com/product-category/woocommerce-extensions/tax?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) to make automated calculations a reality.
WooCommerce is built to allow store managers to run an eCommerce site themselves, no web developer needed. There is also a free WooCommerce [mobile app](https://woocommerce.com/mobile/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) (Android and iOS) for store management on the go. = Grow your business, add features, and monitor your store on the go =
WooCommerce is developed and supported by Automattic, the creators of Jetpack and WordPress.com, along with independent contributors. The [official extension marketplace](https://woocommerce.com/product-category/woocommerce-extensions/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) is on WooCommerce.com. WooCommerce means business. Keep tabs on the performance metrics most important to you with [WooCommerce Admin](https://wordpress.org/plugins/woocommerce-admin/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) a powerful, customizable central dashboard for your store.
= From subscriptions to gym classes to luxury cars = Expand your audience across marketing and social channels with [Google Ads](https://woocommerce.com/products/google-ads/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), [HubSpot](https://woocommerce.com/products/hubspot-for-woocommerce/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), [Mailchimp](https://woocommerce.com/products/mailchimp-for-woocommerce/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), and [Facebook](https://woocommerce.com/products/facebook/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) integrations. You can always check out the in-dashboard [Marketing Hub](https://docs.woocommerce.com/document/marketing-hub/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) for fresh ideas and tips to help you succeed.
With WooCommerce, you can sell both physical and digital goods in all shapes and sizes, offer product variations, complex configurations, and instant downloads to shoppers; and even sell affiliate goods from online marketplaces.
And those are just the out-of-the-box options. With paid extensions, you can extend your WooCommerce store to take bookings, offer memberships, set up recurring payments by subscription, create dynamic pricing rules, and much more. Enhance store functionality with hundreds of free and paid extensions from the [official WooCommerce Marketplace](https://woocommerce.com/products/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing). Our developers [vet each new extension](https://docs.woocommerce.com/document/marketplace-overview/#section-6?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) and regularly review existing inventory to maintain Marketplace quality standards. We are actively [looking for products that help store builders create successful stores](https://docs.woocommerce.com/document/marketplace-overview/#section-2?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
Start a monthly wine subscription box, offer a discount on yoga mats to members whove attended 10+ classes, offer configurable hampers or personalized jewelry its all possible with WooCommerce. Manage your store from anywhere with the free WooCommerce [mobile app](https://woocommerce.com/mobile/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) (Android and iOS). Spoiler alert: Keep an ear out for the slightly addictive "cha-ching" notification sound each time you make a new sale!
= Comprehensive payment options = = Own and control your store data forever =
WooCommerce comes bundled with the ability to accept major credit cards, alternative payment methods, BACS (bank transfers), and cash on delivery.
For additional options, WooCommerce also integrates with more than 140 region-specific gateways including popular choices like [Stripe](https://woocommerce.com/products/stripe/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [PayPal](https://woocommerce.com/products/woocommerce-gateway-paypal-checkout/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Square](https://woocommerce.com/products/square/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) and [Amazon Pay](https://woocommerce.com/products/pay-with-amazon/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). [Apple Pay](https://woocommerce.com/apple-pay/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) and Google Pay are also supported. With WooCommerce, your data belongs to you. Always.
Search for your payment service provider of choice on the [official marketplace](https://woocommerce.com/product-category/woocommerce-extensions/payment-gateways/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). If you opt to share [usage data](https://woocommerce.com/usage-tracking/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) with us, you can feel confident knowing that its anonymized and kept secure. Choose to opt-out at any time without impacting your store.
= Ship locally and globally = Unlike hosted eCommerce solutions, WooCommerce store data is future-proof; should you wish to migrate to a different platform, youre free to export all your content and take your site wherever you choose. No restrictions.
Shipping with WooCommerce is highly configurable. Adjust the built-in settings to offer free shipping or flat rate shipping, limit your shipments to specific countries by setting up shipping zones, or open your store up to the world.
Official extensions connect you with hundreds of local and international carriers including [Royal Mail](https://woocommerce.com/products/royal-mail/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [FedEx](https://woocommerce.com/products/fedex-shipping-module/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), and [Australia Post](https://woocommerce.com/products/australia-post-shipping-method/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) and let you integrate with [inventory management and fulfilment providers](https://woocommerce.com/product-category/woocommerce-extensions/shipping-methods/inventory-fulfillment/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). = Why developers choose (and love) WooCommerce =
There are also extensions available to add [delivery and shipping options](https://woocommerce.com/product-category/woocommerce-extensions/shipping-methods/delivery-shipping-options/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) and support strategies like buy-one-get-one free, free gifts, and add-ons. [WooCommerce Shipping](https://woocommerce.com/products/shipping/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) supports real-time calculations and printing labels at home. Developers can use WooCommerce to create, customize, and scale a store to meet a clients exact specifications, making enhancements through extensions or custom solutions.
= Design your store with themes and blocks = - Leverage [hooks and filters](https://docs.woocommerce.com/document/introduction-to-hooks-actions-and-filters/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) to modify or create functionality.
- Integrate virtually any service using a robust [REST API](https://docs.woocommerce.com/document/woocommerce-rest-api/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) and webhooks.
- Design and build custom content blocks with React.
- [Inspect and modify](https://docs.woocommerce.com/documentation/plugins/woocommerce/woocommerce-codex/extending/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) any aspect of the core plugin code.
- Speed up development with a lightning-fast [CLI](https://woocommerce.github.io/code-reference/classes/wc-cli-rest-command.html?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
WooCommerce store design starts with a theme of your choice. There are hundreds of free and paid themes available, including [Storefront](https://woocommerce.com/storefront/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) by Automattic -- it's free to all stores and you can choose to have it installed for you during the guided setup. The core platform is tested rigorously and often, supported by a dedicated development team working across time zones. Comprehensive documentation is updated with each release, empowering you to build exactly the store required.
Storefront offers deep WooCommerce integration and prioritizes speed and uptime. You can add your brand and define your style by customizing Storefront yourself or adding one of several industry-themed [Storefront child themes](https://woocommerce.com/product-category/themes/storefront-child-theme-themes/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). = Be part of our growing international community =
Personalize your store's design even more with WooCommerce Blocks (available in WooCommerce 3.6 and above) -- use them to add selections of or single products to any page, for a seamless blending of commerce into content. WooCommerce has a large, passionate community dedicated to helping merchants succeed, and its growing fast.
= Customize your store with extensions = There are [WooCommerce Meetups](https://woocommerce.com/meetups/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) in locations around the world that you can attend for free and even get involved in running. These events are a great way to learn from others, share your expertise, and connect with like-minded folks.
The easiest way to add features and functionality to a WooCommerce store is with an extension: WooCommerce also has a regular presence at WordCamps across the globe wed love to meet you.
- Sell anything - [Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Memberships](https://woocommerce.com/products/woocommerce-memberships/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Bookings](https://woocommerce.com/products/woocommerce-bookings/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Product Bundles](https://woocommerce.com/products/product-bundles/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), and [more](https://woocommerce.com/product-category/woocommerce-extensions/product-type/). = Contribute and translate =
- Make your store, your way - [Product Add-Ons](https://woocommerce.com/products/product-add-ons/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Checkout Field Editor](https://woocommerce.com/products/woocommerce-checkout-field-editor/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Additional Variation Images](https://woocommerce.com/products/woocommerce-additional-variation-images/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), and [more](https://woocommerce.com/customize-product-pages?utm_source=wp%20org%20repo%20listing&utm_content=3.6).
- Customize your shipping options - [Table Rate Shipping](https://woocommerce.com/products/table-rate-shipping/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Shipment Tracking](https://woocommerce.com/products/shipment-tracking/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), live rates from [top carriers](https://woocommerce.com/product-category/woocommerce-extensions/shipping-methods/shipping-carriers/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), and [more](https://woocommerce.com/product-category/woocommerce-extensions/shipping-methods/?utm_source=wp%20org%20repo%20listing&utm_content=3.6).
- Find your audience, market to them your way - [Google Product Feed](https://woocommerce.com/products/google-product-feed/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [LiveChat](https://woocommerce.com/products/livechat/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Amazon/eBay marketplace integration](https://woocommerce.com/products/amazon-ebay-integration/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), and [more](https://woocommerce.com/product-category/woocommerce-extensions/marketing-extensions/?utm_source=wp%20org%20repo%20listing&utm_content=3.6).
- Drive sales [Dynamic Pricing](https://woocommerce.com/products/dynamic-pricing/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Smart Coupons](https://woocommerce.com/products/smart-coupons/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), [Google Ads](https://woocommerce.com/products/google-ads/?utm_source=wp%20org%20repo%20listing&utm_content=3.6), and [more](https://woocommerce.com/product-category/woocommerce-extensions/marketing-extensions/promotions/?utm_source=wp%20org%20repo%20listing&utm_content=3.6).
There are hundreds of official extensions reviewed by WooCommerce developers available on the WooCommerce.com marketplace, and many in the WordPress.org repository and on the wider web. WooCommerce is developed and supported by Automattic, the creators of WordPress.com and Jetpack. We also have hundreds of independent contributors, and theres always room for more. Head to the [WooCommerce GitHub Repository](https://github.com/woocommerce/woocommerce?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) to find out how you can pitch in.
= Control your data forever = WooCommerce is translated into multiple languages, including Danish, Ukrainian, and Persian. Help localize WooCommerce even further by adding your locale visit [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/woocommerce/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
WooCommerce gives you complete control from taxes to stock levels to customer accounts. Add and remove extensions, change your stores design, and switch themes or hosts or payment service providers, all as you please.
In a world increasingly aware of the importance of data protection, WooCommerce gives you full ownership over what is tracked and stored. If you opt to share [usage data](https://woocommerce.com/usage-tracking/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) with us, your data is anonymized and kept secure. At any stage, you can opt out of all forms of tracking while still enjoying all of WooCommerce's capabilities.
With WooCommerce, your data belongs to you. One of the risks of using a hosted eCommerce platform the risk of losing your store if the provider closes. WooCommerce store data is future-proof -- youre are free to export all your content and take your site wherever you choose.
= Why developers like WooCommerce =
WooCommerce was originally created with developers in mind. Built with a REST API, it integrates with virtually any service. Store data can be accessed anywhere, anytime, 100% securely.
WooCommerce allows developers to easily create, modify, and scale a store that meets clients specifications, and to make enhancements either with extensions or with customs solution.
No matter the size of the store you want to build, WooCommerce has a robust framework that supports stores from basic to enterprise with content and commerce in a single, central location.
WooCommerce is audited by a dedicated team of developers who work across time zones to identify and patch any and all discovered bugs. There is comprehensive, easily-accessible documentation that is updated with each release. With our docs, youll learn how to create the site your client needs.
= Join our growing community =
WooCommerce is one of the fastest-growing eCommerce communities. Were proud that the helpfulness of the community and wealth of resources available online are frequently cited as reasons our users love it.
There are 80+ [WooCommerce Meetups](https://woocommerce.com/woocommerce/meetups/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) taking place in cities across the world that you can attend for free and even get involved in running. WooCommerce also has a regular presence at WordCamps across the globe and wed love to meet you.
If youre interested in contributing to WooCommerce weve got more than 350 contributors, and theres always room for more. Head to the [WooCommerce GitHub Repository](https://github.com/woocommerce/woocommerce?utm_source=wp%20org%20repo%20listing&utm_content=3.6) to find out how you can pitch in.
WooCommerce is currently 100% translated into 24 languages, including Danish, Ukrainian, and Persian. If youre interested in helping to localize WooCommerce by adding your local language, visit [translate.wordpress.org](https://translate.wordpress.org/projects/wp-plugins/woocommerce?utm_source=wp%20org%20repo%20listing&utm_content=3.6).
== Frequently Asked Questions == == Frequently Asked Questions ==
= Where can I find WooCommerce documentation and user guides? = = Where can I find WooCommerce documentation and user guides? =
For help setting up and configuring WooCommerce please refer to our [user guide](https://docs.woocommerce.com/documentation/plugins/woocommerce/getting-started/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) For help setting up and configuring WooCommerce, please refer to [Getting Started](https://docs.woocommerce.com/documentation/plugins/woocommerce/getting-started/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) and the [New WooCommerce Store Owner Guide](https://woocommerce.com/guides/new-store/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
For extending or theming WooCommerce, see our [codex](https://docs.woocommerce.com/documentation/plugins/woocommerce/woocommerce-codex/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). For extending or theming WooCommerce, see our [codex](https://docs.woocommerce.com/documentation/plugins/woocommerce/woocommerce-codex/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), as well as the [Plugin Developer Handbook](https://docs.woocommerce.com/document/create-a-plugin/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
= Where can I get support or talk to other users? = = Where can I get help or talk to other users about WooCommerce Core? =
If you get stuck, you can ask for help in the [WooCommerce Plugin Forum](https://wordpress.org/support/plugin/woocommerce). If you get stuck, you can ask for help in the [WooCommerce Support Forum](https://wordpress.org/support/plugin/woocommerce/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) by following [these guidelines](https://wordpress.org/support/topic/guide-to-the-woocommerce-forum/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), reach out via the [WooCommerce Community Slack](https://woocommerce.com/community-slack/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing), or post in the [WooCommerce Community group](https://www.facebook.com/groups/advanced.woocommerce?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) on Facebook.
For help with paid extensions from WooCommerce.com, use [our helpdesk](https://woocommerce.com/my-account/tickets/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). = Where can I get help for extensions I have purchased on WooCommerce.com? =
For assistance with paid extensions from the WooCommerce.com Marketplace: first, review our [self-service troubleshooting guide](https://docs.woocommerce.com/document/woocommerce-self-service-guide/). If the problem persists, kindly log a support ticket via [our helpdesk](https://woocommerce.com/my-account/create-a-ticket/). Our dedicated Happiness Engineers aim to respond within 24 hours.
= Im having trouble logging in to WooCommerce.com what now? =
First, troubleshoot common login issues using this helpful [step-by-step guide](https://docs.woocommerce.com/document/log-into-woocommerce-com-with-wordpress-com/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing). Still not working? [Get in touch with us](https://woocommerce.com/contact-us/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
= Will WooCommerce work with my theme? = = Will WooCommerce work with my theme? =
Yes! WooCommerce will work with any theme, but may require some styling. Please see our [codex](https://docs.woocommerce.com/documentation/plugins/woocommerce/woocommerce-codex/?utm_source=wp%20org%20repo%20listing&utm_content=3.6) for help. If you're looking for a theme with built in WooCommerce integration we recommend [Storefront](https://woocommerce.com/storefront/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). Yes! WooCommerce will work with any theme but may require some additional styling. If youre looking for a theme featuring deep WooCommerce integration, we recommend [Storefront](https://woocommerce.com/storefront/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
= Where can I request new features, eCommerce themes, and extensions? = = How do I update WooCommerce? =
You can vote on and request new features and extensions on our [WooIdeas board](http://ideas.woocommerce.com/forums/133476-woocommerce?utm_source=wp%20org%20repo%20listing&utm_content=3.6) We have a detailed guide on [How To Update WooCommerce](https://docs.woocommerce.com/document/how-to-update-woocommerce/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
= Where can I report bugs or contribute to the project? = = My site broke what do I do? =
Report bugs on the [WooCommerce GitHub repository](https://github.com/woocommerce/woocommerce/issues?utm_source=wp%20org%20repo%20listing&utm_content=3.6). You can also report them in our [support forum](https://wordpress.org/support/plugin/woocommerce). Start by diagnosing the issue using our helpful [troubleshooting guide](https://docs.woocommerce.com/documentation/get-help/troubleshooting-get-help/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
If you noticed the error after updating a theme or plugin, there might be compatibility issues between it and WooCommerce. If the issue appeared after updating WooCommerce, there could be a conflict between WooCommerce and an outdated theme or plugin.
= Where can I find the REST API documentation? = In both instances, we recommend running a conflict test using [Health Check](https://docs.woocommerce.com/document/troubleshooting-using-health-check/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) (which allows you to disable themes and plugins without affecting your visitors) or troubleshooting the issue using a [staging site](https://docs.woocommerce.com/document/how-to-test-for-conflicts/#section-3?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing).
You can find the documentation of our REST API on the [WooCommerce REST API Docs](https://woocommerce.github.io/woocommerce-rest-api-docs/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). = Where can I report bugs? =
Report bugs on the [WooCommerce GitHub repository](https://github.com/woocommerce/woocommerce/issues?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing). You can also notify us via our support forum be sure to search the forums to confirm that the error has not already been reported.
= Where can I request new features, themes, and extensions? =
Request new features and extensions and vote on existing suggestions on our official [ideas board](https://ideas.woocommerce.com/forums/133476-woocommerce?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing). Our Product teams regularly review requests and consider them valuable for product planning.
= WooCommerce is awesome! Can I contribute? = = WooCommerce is awesome! Can I contribute? =
Yes you can! Join in on our [GitHub repository](https://github.com/woocommerce/woocommerce/?utm_source=wp%20org%20repo%20listing&utm_content=3.6). Yes, you can! Join in on our [GitHub repository](https://github.com/woocommerce/woocommerce/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) and follow the [development blog](https://woocommerce.wordpress.com/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) to stay up-to-date with everything happening in the project.
= Where can I find REST API documentation? =
Extensive [WooCommerce REST API Documentation](https://woocommerce.github.io/woocommerce-rest-api-docs/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) is available on GitHub.
= My question is not listed here. Where can I find more answers? =
Check out [Frequently Asked Questions](https://docs.woocommerce.com/document/frequently-asked-questions/?utm_medium=referral&utm_source=wordpress.org&utm_campaign=wp_org_repo_listing) for more.
== Installation == == Installation ==
@ -179,137 +166,46 @@ INTERESTED IN DEVELOPMENT?
== Changelog == == Changelog ==
= 4.4.0 - 2020-08-18 =
= 4.5.0 - 2020-09-08 =
**WooCommerce** **WooCommerce**
* Accessibility: Adds alt attribute to photoswipe gallery images. #26945 * Localization - Added postcode validation for Bosnia and Herzegovina. #27048
* Enhancement - Remove the privacy page dropdown from the Accounts & Privacy page. #26809 * Localization - Added the postcode validation for Liechtenstein. #27059
* Enhancement - Added automatic language pack updates for WooCommerce.com extensions. #26750 * Localization - Add i18n locale information for Liechtenstein, Switzerland and Austria. #27193
* Enhancement - Improvements for the Hungarian address format. #26697 * Tweak - Increase priority of `admin_body_class` filter to avoid comflict with plugins that incorrectly remove all body classes from WP. #27426
* Enhancement - Dropdown arrow width was made smaller. #26202 * Tweak - Rename built-in PayPal payment method to PayPal Standard. #27468
* Enhancement - Add a "No change" option to the "Stock status" selector in quick edit, preselect it when the product being edited is a variable product. #26174 * Fix - Remove whitespace within a link. #26897
* Enhancement - Don't request language packs for empty locales list. #27148 * Fix - `get_review_count_for_product` return all comments count not only 'review' types #26928
* Localization - Added 14 Namibia regions. #26894 * Fix - Hidden field type is now supported by `woocommerce_form_field`. #27023
* Localization - Change default Greek states names to English. #26719 * Fix - Remove state for country liechtenstein. #27057
* Localization - Improved Puerto Rico addresses and improve address formatting. #26698 * Fix - Fixed validation of variation attributes while adding products to the cart. #27115
* Localization - Wrapped price and currency inside a BDI tag, in order to prevent the bidirectional algorithm to produce confusing results. #26462 * Fix - Coupon code inconsistent between admins and shop owners. #27140
* Localization - Added Algerian provinces. #25687 * Fix - Fixed the logic behind "Hide shipping costs until an address is entered". #27143
* Tweak - Added "order_total" to the wcadmin_orders_edit_status_change tracker event. #26935 * Fix - Searches for variations now will fallback to parent SKU if one is not entered. #27171
* Tweak - Fixed WooCommerce menu for users that can only manage orders on WooCommerce. #26877 * Fix - Release coupon holds for cancelled orders previously in pending status. #27179
* Tweak - Limit nocache headers to googleweblight by default. #26858 * Fix - Fixes Japan zip code format issue (dash is now optional). #27244
* Tweak - Preserve quantity input value when changing variations. #26805 * Fix - Restore backward compatibility with WC 4.x and forward compatibility with WC 5.5. #27318
* Tweak - Confirm before running any tool from the WooCommerce Status settings. #26660 * Fix - Switch to site locale before translating refund reason. #27323
* Tweak - Limit stock changes for order items to status methods for consistency. #26642 * Fix - Declare `WC_Post_Types::updated_term_messages` as a static method to remove PHP deprecation warning. #27436
* Tweak - Custom vendor taxonomy update messages. #26634 * Fix - Allow HTML to be entered in product title for formatting purposes. #27465
* Tweak - Remove HTML tags from plain text email template for Customer new account. #26613 * Dev - Added additional stock-based cart filters including `woocommerce_cart_product_cannot_add_another_message`, `woocommerce_cart_product_out_of_stock_message`, and `woocommerce_cart_product_not_enough_stock_message`. #26439
* Tweak - Conditionally change the text in My account to reflect if shipping is disabled. #26325 * Dev - Changed text domain to `woocommerce` for REST API files. #27248
* Tweak - Show CSV file name in result message when product import is complete. #25240 * Dev - Added file path to the `woocommerce_file_download_method` filter. #27152
* Tweak - Improve order details UI to highlight "Paid" and "Net Payment" sections. #27142 * Dev - Merge API Package into core. #27100
* Fix - Remove the dot after the generated password in new account emails. #27073
* Fix - Delayed the execution of all webhooks until after the request has completed. #27067
* Fix - [Importer/Exporter] Fixed the value display of "Published" for children of draft variable products. #27046
* Fix - Removed the extra id parameter added to CLI commands that shouldn't have one. #27017
* Fix - Added the missing instance_id to the REST CLI command so that shipping zone method commands will work. #27017
* Fix - Add rating_count to order by rating clause. #26964
* Fix - Don't show premium support forum link if the store is not connected to WooCommerce.com. #26932
* Fix - Incorrect capability used on add order note while creating an user note. #26920
* Fix - Preserve HTML entities from product names in the cart page. #26885
* Fix - Display warning hen leaving settings page without saving first. #26880
* Fix - Remove wc_round_tax_total from shipping tax because shipping prices never include tax so rounding down is not needed. #26850
* Fix - Make the "Please log in" message displayed to users with an existing account a hyperlink. #26837
* Fix - Typo in composer.json for makepot. #26829
* Fix - Layout issue on the checkout page when switching countries. #26697
* Fix - Missing closing select tag to the product exporter category select. #26680
* Fix - Possible PHP undefined index notice before WooCommerce has been configured. #26658
* Fix - A deferred product sync is now scheduled when a product having a parent (e.g. a variation product) is deleted, not only when it's saved. #26629
* Fix - Stock status of variable products that handle stock at the main product level is now appropriately updated when the product is saved. #26611
* Fix - Discounted prices are no longer underlined in Twenty Twenty. #26609
* Fix - Email link color clash. #26591
* Fix - Remove HTML from error message. #26589
* Fix - Fixed Tooltip flashing. #26558
* Fix - Correctly displays the instructional option as default in the select box for picking a Country / Region on the checkout page. #26554
* Fix - Default option "Select a country..." will now display accurately on Country select box in Cart shipping calculator. #26541
* Fix - Fixed user capability required to view the order count indicator. #26338
* Fix - The filtering widget now works as expected with variable products, displaying those products for which visible variations are available. #26260
* Fix - Added a z-index to the remove button (x) to set the z-order of the element. #26202
* Fix - Don't change the stock status of variations when bulk editing a variable product and leaving the "Stock status" selector as "No change". #26174
* Fix - Remove new WP 5.5 meta box arrows from "Order data" and "Order items" meta boxes. #27173
* Fix - After clicking to update WooCommerce, the user will stay in the same page instead of being redirected to the "Settings" page. #27172
* Fix - "Product type" dropdown missing from Product's data meta box on WP 5.5. #27170
* Fix - Removed the JETPACK_AUTOLOAD_DEV define. #27185
* Dev - Update WooCommerce Admin version to v1.4.0-beta.3. #27214
* Dev - Upgraded to the 2.x Jetpack Autoloader. #27123
* Dev - Update jest-preset-default version to ^6.2.0. #27090
* Dev - Added a second $existing_meta_keys parameter to the woocommerce_duplicate_product_exclude_meta filter. #27038
* Dev - Remove leftover note for translators in customer-completed-order.php. #26989
* Dev - Allow extend BACS accounts filter with order ID. #26961
* Dev - Add npm run build:packages to npm run build. #26906
* Dev - Add woocommerce_order_note_added action. #26846
* Dev - Add tests for template cache. #26840
* Dev - Add filter to allow disabling nocache headers. #26802
* Dev - Introduce a dependency injection framework for the code in the src directory. #26731
* Dev - Normalized parameters of woocommerce_product_importer_parsed_data filter. #26669
* Dev - Introduced new WC_Product_CSV_Importer::get_formatting_callback() fixing a typo in the method name. #26668
* Dev - Allow set "date_created" while creating orders via CRUD. #26567
* Dev - Allow set a custom as order key using wc_generate_order_key(). #26566
* Dev - Allow set order_key while creating an order via CRUD. #26565
* Dev - Introduced woocommerce_product_cross_sells_products_heading filter. #26545
* Dev - Added the removed_coupon_in_checkout event that is triggered on the Checkout page after a coupon is removed using .woocommerce-remove-coupon button. #26536
* Dev - Remove no longer used styles from TwentyTwenty. #26516
* Dev - Fix error message in wc_get_template() function. #26515
* Dev - Add npm publish script for @woocommerce/e2e-environment. #26432
* Dev - Make WC_Cart::display_prices_including_tax aware of tax display changes. #26400
* Dev - Deprecated WC_Legacy_Cart::tax_display_cart in favor of WC_Cart:: get_tax_price_display_mode(). #26400
* Dev - Add an optional $render_variations argument to in WC_Product_Variable::get_available_variation() in order to allow plugins to avoid performance bottlenecks. #26303
* Dev - Ensure wc_load_cart loads its own dependencies. #26219
* Dev - Clean up deprecated documentation. #27054
* Dev - Update WooCommerce Blocks version to 3.1.0. #27177
**REST API 1.0.11** **WooCommerce Admin 1.5.0**
* Enhancement - Introduced X-WP-Total header for product attributes GET endpoint listing the number of entries in the response. woocommerce/woocommerce-rest-api#171 * Enhancement - Add eWAY to Payment Setup for AU/NZ Stores. #4947
* Enhancement - Introduced X-WP-TotalPages header for product attributes GET endpoint listing the number of pages that can be fetched. woocommerce/woocommerce-rest-api#171 * Fix - Use clipRule and fillRule props. #4889, part of #4864
* Enhancement - Introduced the modified option for orderby fetch requests in post based resources. woocommerce/woocommerce-rest-api#226 * Dev - New notification: Don't forget to test your checkout. #4805
* Fix - Ensured Action Scheduler transients are cleared by "Clear Transients" tool. woocommerce/woocommerce-rest-api#152 * Dev - Enable tax calculation before redirecting to standard tax rates page. #4878
* Fix - Corrected the schema datatype for coupon expiry_date, date_expires, and date_expires_gmt fields. woocommerce/woocommerce-rest-api#176 * Dev - Added event recording to Orders, Stock, and Reviews panels. #4861
* Fix - Query parameters are now passed correctly when using the batch product variation endpoints. woocommerce/woocommerce-rest-api#191 * Dev - Added personalization to purchase extension task. #4849
* Dev - Display modal with more info about the new homescreen. #4890
**WooCommerce Admin 1.4.0** * Dev - Task list - add a shortcut back to store setup. #4853
* Enhancement - Move the WooCommerce > Coupons dashboard menu item to Marketing > Coupons. #4786 * Dev - Update the colors of the illustrations in the welcome modal. #4945
* Fix - Installation of child theme zip files from the store setup wizard. #4852 [See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/changelog.txt).
* Fix - Center the skip link on the theme selection step. #4847
* Fix - Removed item "profiler" from the menu. #4851
* Fix - PHP notices when hosts block certain WP scripts. #4856
* Fix - Remove new WP 5.5 meta box arrows in the shipping banner. #4914
* Fix - Allow revisiting of the payments task. #4918
* Fix - Use of Jetpack autoloader. #4920
* Fix - Only show WCPay task in US based stores. #4899
* Fix - Polyfill core-data saveUser() on WP 5.3.x. #4869
* Fix - Product types step bugs in onboarding wizard. #4900
* Fix - Center all descriptive text on onboarding wizard steps. #4902
* Fix - Change account required text on biz step in onboarding wizard. #4909
* Dev - Add the experimental resolver to WCA data package. #4862
* Dev - Fix linter errors. #4904
**WooCommerce Blocks 3.0.0**
* Build - Updated the automattic/jetpack-autoloader package to the 2.0 branch. #2847
* Enhancement - Add support for the Bank Transfer (BACS) payment method in the Checkout block. #2821
* Enhancement - Several improvements to make Credit Card input fields display more consistent across different themes and viewport sizes. #2869
* Enhancement - Cart and Checkout blocks show a notification for products on backorder. #2833
* Enhancement - Chip styles of the Filter Products by Attribute and Active Filters have been updated to give a more consistent experience. #2765
* Enhancement - Add protection for rogue filters on order queries when executing cleanup draft orders logic. #2874
* Enhancement - Extend payment gateway extension API so gateways (payment methods) can dynamically disable (hide), based on checkout or order data (such as cart items or shipping method). For example, Cash on Delivery can limit availability to specific shipping methods only. #2840 [DN]
* Enhancement - Support Cash on Delivery core payment gateway in the Checkout block. #2831
* Performance - Don't load shortcode Cart and Checkout scripts when using the blocks. #2842
* Performance - Scripts only relevant to the frontend side of blocks are no longer loaded in the editor. #2788
* Performance - Lazy Loading Atomic Components. #2777
* Pefactor - Remove dashicon classes. #2848
**WooCommerce Blocks 3.1.0**
* Fix - Missing permissions_callback arg in StoreApi route definitions. #2926
* Fix - 'Product Summary' in All Products block is not pulling in the short description of the product. #2913
* Dev - Add query filter when searching for a table. #2886
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce/master/CHANGELOG.txt).
== Upgrade Notice == == Upgrade Notice ==

View File

@ -39,8 +39,8 @@ final class ReserveStock {
/** /**
* Query for any existing holds on stock for this item. * Query for any existing holds on stock for this item.
* *
* @param \WC_Product|object $product Product to get reserved stock for. * @param \WC_Product $product Product to get reserved stock for.
* @param integer $exclude_order_id Optional order to exclude from the results. * @param integer $exclude_order_id Optional order to exclude from the results.
* *
* @return integer Amount of stock already reserved. * @return integer Amount of stock already reserved.
*/ */
@ -60,8 +60,8 @@ final class ReserveStock {
* *
* @throws ReserveStockException If stock cannot be reserved. * @throws ReserveStockException If stock cannot be reserved.
* *
* @param \WC_Order|object $order Order object. * @param \WC_Order $order Order object.
* @param int $minutes How long to reserve stock in minutes. Defaults to woocommerce_hold_stock_minutes. * @param int $minutes How long to reserve stock in minutes. Defaults to woocommerce_hold_stock_minutes.
*/ */
public function reserve_stock_for_order( $order, $minutes = 0 ) { public function reserve_stock_for_order( $order, $minutes = 0 ) {
$minutes = $minutes ? $minutes : (int) get_option( 'woocommerce_hold_stock_minutes', 60 ); $minutes = $minutes ? $minutes : (int) get_option( 'woocommerce_hold_stock_minutes', 60 );
@ -127,7 +127,7 @@ final class ReserveStock {
/** /**
* Release a temporary hold on stock for an order. * Release a temporary hold on stock for an order.
* *
* @param \WC_Order|object $order Order object. * @param \WC_Order $order Order object.
*/ */
public function release_stock_for_order( $order ) { public function release_stock_for_order( $order ) {
global $wpdb; global $wpdb;
@ -149,10 +149,10 @@ final class ReserveStock {
* *
* @throws ReserveStockException If a row cannot be inserted. * @throws ReserveStockException If a row cannot be inserted.
* *
* @param int $product_id Product ID which is having stock reserved. * @param int $product_id Product ID which is having stock reserved.
* @param int $stock_quantity Stock amount to reserve. * @param int $stock_quantity Stock amount to reserve.
* @param \WC_Order|object $order Order object which contains the product. * @param \WC_Order $order Order object which contains the product.
* @param int $minutes How long to reserve stock in minutes. * @param int $minutes How long to reserve stock in minutes.
*/ */
private function reserve_stock_for_product( $product_id, $stock_quantity, $order, $minutes ) { private function reserve_stock_for_product( $product_id, $stock_quantity, $order, $minutes ) {
global $wpdb; global $wpdb;
@ -219,7 +219,7 @@ final class ReserveStock {
* Filter: woocommerce_query_for_reserved_stock * Filter: woocommerce_query_for_reserved_stock
* Allows to filter the query for getting reserved stock of a product. * Allows to filter the query for getting reserved stock of a product.
* *
* @since 4.4.0 * @since 4.5.0
* @param string $query The query for getting reserved stock of a product. * @param string $query The query for getting reserved stock of a product.
* @param int $product_id Product ID. * @param int $product_id Product ID.
* @param int $exclude_order_id Order to exclude from the results. * @param int $exclude_order_id Order to exclude from the results.

View File

@ -5,8 +5,9 @@
namespace Automattic\WooCommerce; namespace Automattic\WooCommerce;
use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider; use Psr\Container\ContainerExceptionInterface;
use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer; use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
/** /**
* PSR11 compliant dependency injection container for WooCommerce. * PSR11 compliant dependency injection container for WooCommerce.
@ -21,61 +22,23 @@ use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer;
* Classes in the `includes` directory should use the `wc_get_container` function to get the instance of the container when * Classes in the `includes` directory should use the `wc_get_container` function to get the instance of the container when
* they need to get an instance of a class from the `src` directory. * they need to get an instance of a class from the `src` directory.
* *
* Class registration should be done via service providers that inherit from Automattic\WooCommerce\Tools\DependencyManagement * Class registration should be done via service providers that inherit from Automattic\WooCommerce\Internal\DependencyManagement
* and those should go in the `src\Tools\DependencyManagement\ServiceProviders` folder unless there's a good reason * and those should go in the `src\Internal\DependencyManagement\ServiceProviders` folder unless there's a good reason
* to put them elsewhere. All the service provider class names must be in the `SERVICE_PROVIDERS` constant. * to put them elsewhere. All the service provider class names must be in the `SERVICE_PROVIDERS` constant.
*/ */
final class Container implements \Psr\Container\ContainerInterface { final class Container implements ContainerInterface {
/**
* The root namespace of all WooCommerce classes in the `src` directory.
*/
const WOOCOMMERCE_ROOT_NAMESPACE = 'Automattic\\WooCommerce';
/**
* The list of service provider classes to register.
*
* @var string[]
*/
private $service_providers = array(
ProxiesServiceProvider::class,
);
/**
* The underlying container.
*
* @var \League\Container\Container
*/
private $container;
/**
* Class constructor.
*/
public function __construct() {
$this->container = new ExtendedContainer();
// Add ourselves as the shared instance of ContainerInterface,
// register everything else using service providers.
$this->container->share( \Psr\Container\ContainerInterface::class, $this );
foreach ( $this->service_providers as $service_provider_class ) {
$this->container->addServiceProvider( $service_provider_class );
}
}
/** /**
* Finds an entry of the container by its identifier and returns it. * Finds an entry of the container by its identifier and returns it.
* *
* @param string $id Identifier of the entry to look for. * @param string $id Identifier of the entry to look for.
* *
* @throws NotFoundExceptionInterface No entry was found for **this** identifier. * @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws Psr\Container\ContainerExceptionInterface Error while retrieving the entry. * @throws ContainerExceptionInterface Error while retrieving the entry.
* *
* @return mixed Entry. * @return mixed Entry.
*/ */
public function get( $id ) { public function get( $id ) {
return $this->container->get( $id ); return null;
} }
/** /**
@ -90,6 +53,6 @@ final class Container implements \Psr\Container\ContainerInterface {
* @return bool * @return bool
*/ */
public function has( $id ) { public function has( $id ) {
return $this->container->has( $id ); return false;
} }
} }

View File

@ -1,141 +0,0 @@
<?php
/**
* AbstractServiceProvider class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement;
use League\Container\Argument\RawArgument;
use League\Container\Definition\DefinitionInterface;
use League\Container\Definition\Definition;
/**
* Base class for the service providers used to register classes in the container.
*
* See the documentation of the original class this one is based on (https://container.thephpleague.com/3.x/service-providers)
* for basic usage details. What this class adds is:
*
* - The `add_with_auto_arguments` method that allows to register classes without having to specify the constructor arguments.
* - The `share_with_auto_arguments` method, sibling of the above.
* - Convenience `add` and `share` methods that are just proxies for the same methods in `$this->getContainer()`.
*/
abstract class AbstractServiceProvider extends \League\Container\ServiceProvider\AbstractServiceProvider {
/**
* Register a class in the container and use reflection to guess the constructor arguments.
*
* WARNING: this method uses reflection, so please have performance in mind when using it.
*
* @param string $class_name Class name to register.
* @param mixed $concrete The concrete to register. Can be a shared instance, a factory callback, or a class name.
* @param bool $shared Whether to register the class as shared (`get` always returns the same instance) or not.
*
* @return DefinitionInterface The generated container definition.
*
* @throws ContainerException Error when reflecting the class, or class constructor is not public, or an argument has no valid type hint.
*/
protected function add_with_auto_arguments( string $class_name, $concrete = null, bool $shared = false ) : DefinitionInterface {
$definition = new Definition( $class_name, $concrete );
$function = $this->reflect_class_or_callable( $class_name, $concrete );
if ( ! is_null( $function ) ) {
$arguments = $function->getParameters();
foreach ( $arguments as $argument ) {
if ( $argument->isDefaultValueAvailable() ) {
$default_value = $argument->getDefaultValue();
$definition->addArgument( new RawArgument( $default_value ) );
} else {
$argument_class = $argument->getClass();
if ( is_null( $argument_class ) ) {
throw new ContainerException( "AbstractServiceProvider::add_with_auto_arguments: constructor argument '{$argument->getName()}' of class '$class_name' doesn't have a type hint or has one that doesn't specify a class." );
}
$definition->addArgument( $argument_class->name );
}
}
}
// Register the definition only after being sure that no exception will be thrown.
$this->getContainer()->add( $definition->getAlias(), $definition, $shared );
return $definition;
}
/**
* Check if a combination of class name and concrete is valid for registration.
* Also return the class constructor if the concrete is either a class name or null (then use the supplied class name).
*
* @param string $class_name The class name to check.
* @param mixed $concrete The concrete to check.
*
* @return \ReflectionFunctionAbstract|null A reflection instance for the $class_name constructor or $concrete constructor or callable; null otherwise.
* @throws ContainerException Class has a private constructor, can't reflect class, or the concrete is invalid.
*/
private function reflect_class_or_callable( string $class_name, $concrete ) {
if ( ! isset( $concrete ) || is_string( $concrete ) && class_exists( $concrete ) ) {
try {
$class = $concrete ?? $class_name;
$reflector = new \ReflectionClass( $class );
$constructor = $reflector->getConstructor();
if ( isset( $constructor ) && ! $constructor->isPublic() ) {
throw new ContainerException( "AbstractServiceProvider::add_with_auto_arguments: constructor of class '$class' isn't public, instances can't be created." );
}
return $constructor;
} catch ( \ReflectionException $ex ) {
throw new ContainerException( "AbstractServiceProvider::add_with_auto_arguments: error when reflecting class '$class': {$ex->getMessage()}" );
}
} elseif ( is_callable( $concrete ) ) {
try {
return new \ReflectionFunction( $concrete );
} catch ( \ReflectionException $ex ) {
throw new ContainerException( "AbstractServiceProvider::add_with_auto_arguments: error when reflecting callable: {$ex->getMessage()}" );
}
}
return null;
}
/**
* Register a class in the container and use reflection to guess the constructor arguments.
* The class is registered as shared, so `get` on the container always returns the same instance.
*
* WARNING: this method uses reflection, so please have performance in mind when using it.
*
* @param string $class_name Class name to register.
* @param mixed $concrete The concrete to register. Can be a shared instance, a factory callback, or a class name.
*
* @return DefinitionInterface The generated container definition.
*
* @throws ContainerException Error when reflecting the class, or class constructor is not public, or an argument has no valid type hint.
*/
protected function share_with_auto_arguments( string $class_name, $concrete = null ) : DefinitionInterface {
return $this->add_with_auto_arguments( $class_name, $concrete, true );
}
/**
* Register an entry in the container.
*
* @param string $id Entry id (typically a class or interface name).
* @param mixed|null $concrete Concrete entity to register under that id, null for automatic creation.
* @param bool|null $shared Whether to register the class as shared (`get` always returns the same instance) or not.
*
* @return DefinitionInterface The generated container definition.
*/
protected function add( string $id, $concrete = null, bool $shared = null ) : DefinitionInterface {
return $this->getContainer()->add( $id, $concrete, $shared );
}
/**
* Register a shared entry in the container (`get` always returns the same instance).
*
* @param string $id Entry id (typically a class or interface name).
* @param mixed|null $concrete Concrete entity to register under that id, null for automatic creation.
*
* @return DefinitionInterface The generated container definition.
*/
protected function share( string $id, $concrete = null ) : DefinitionInterface {
return $this->add( $id, $concrete, true );
}
}

View File

@ -1,23 +0,0 @@
<?php
/**
* ExtendedContainer class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement;
/**
* Class ContainerException.
* Used to signal error conditions related to the dependency injection container.
*/
class ContainerException extends \Exception {
/**
* Create a new instance of the class.
*
* @param null $message The exception message to throw.
* @param int $code The error code.
* @param Exception|null $previous The previous throwable used for exception chaining.
*/
public function __construct( $message = null, $code = 0, Exception $previous = null ) {
parent::__construct( $message, $code, $previous );
}
}

View File

@ -1,106 +0,0 @@
<?php
/**
* ExtendedContainer class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement;
use Automattic\WooCommerce\Container;
use League\Container\Definition\DefinitionInterface;
/**
* This class extends the original League's Container object by adding some functionality
* that we need for WooCommerce.
*/
class ExtendedContainer extends \League\Container\Container {
/**
* Whitelist of classes that we can register using the container
* despite not belonging to the WooCommerce root namespace.
*
* In general we allow only the registration of classes in the
* WooCommerce root namespace to prevent registering 3rd party code
* (which doesn't really belong to this container) or old classes
* (which may be eventually deprecated, also the LegacyProxy
* should be used for those).
*
* @var string[]
*/
private $registration_whitelist = array(
\Psr\Container\ContainerInterface::class,
);
/**
* Register a class in the container.
*
* @param string $class_name Class name.
* @param mixed $concrete How to resolve the class with `get`: a factory callback, a concrete instance, another class name, or null to just create an instance of the class.
* @param bool|null $shared Whether the resolution should be performed only once and cached.
*
* @return DefinitionInterface The generated definition for the container.
* @throws ContainerException Invalid parameters.
*/
public function add( string $class_name, $concrete = null, bool $shared = null ) : DefinitionInterface {
if ( ! $this->class_is_in_root_namespace( $class_name ) && ! in_array( $class_name, $this->registration_whitelist, true ) ) {
throw new ContainerException( "Can't use the container to register '$class_name', only objects in the " . Container::WOOCOMMERCE_ROOT_NAMESPACE . ' namespace are allowed for registration.' );
}
return parent::add( $class_name, $concrete, $shared );
}
/**
* Does a class belong to the WooCommerce root namespace?
*
* @param string $class_name The class name to check.
*
* @return bool True if the class belongs to the WooCommerce root namespace.
*/
private function class_is_in_root_namespace( $class_name ) {
return substr( $class_name, 0, strlen( Container::WOOCOMMERCE_ROOT_NAMESPACE ) + 1 ) === Container::WOOCOMMERCE_ROOT_NAMESPACE . '\\';
}
/**
* Replace an existing registration with a different concrete.
*
* @param string $class_name The class name whose definition will be replaced.
* @param mixed $concrete The new concrete (same as "add").
*
* @return DefinitionInterface The modified definition.
* @throws ContainerException Invalid parameters.
*/
public function replace( string $class_name, $concrete ) {
if ( ! $this->has( $class_name ) ) {
throw new ContainerException( "ExtendedContainer::replace: The container doesn't have '$class_name' registered, please use 'add' instead of 'replace'." );
}
return $this->extend( $class_name )->setConcrete( $concrete );
}
/**
* Reset all the cached resolutions, so any further "get" for shared definitions will generate the instance again.
*/
public function reset_all_resolved() {
foreach ( $this->definitions->getIterator() as $definition ) {
// setConcrete causes the cached resolved value to be forgotten.
$concrete = $definition->getConcrete();
$definition->setConcrete( $concrete );
}
}
/**
* Get an instance of a registered class.
*
* @param string $id The class name.
* @param bool $new True to generate a new instance even if the class was registered as shared.
*
* @return object An instance of the requested class.
* @throws ContainerException Attempt to get an instance of a non-namespaced class.
*/
public function get( $id, bool $new = false ) {
if ( false === strpos( $id, '\\' ) ) {
throw new ContainerException( "Attempt to get an instance of the non-namespaced class '$id' from the container, did you forget to add a namespace import?" );
}
return parent::get( $id, $new );
}
}

View File

@ -1,34 +0,0 @@
<?php
/**
* Proxies class file.
*/
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Proxies\ActionsProxy;
/**
* Service provider for the classes in the Automattic\WooCommerce\Tools\Proxies namespace.
*/
class ProxiesServiceProvider extends AbstractServiceProvider {
/**
* The classes/interfaces that are serviced by this service provider.
*
* @var array
*/
protected $provides = array(
LegacyProxy::class,
ActionsProxy::class,
);
/**
* Register the classes.
*/
public function register() {
$this->share( ActionsProxy::class );
$this->share_with_auto_arguments( LegacyProxy::class );
}
}

View File

@ -5,7 +5,8 @@
namespace Automattic\WooCommerce\Proxies; namespace Automattic\WooCommerce\Proxies;
use \Psr\Container\ContainerInterface as Container; use Automattic\WooCommerce\Internal\DependencyManagement\Definition;
use \Psr\Container\ContainerInterface;
/** /**
* Proxy class to access legacy WooCommerce functionality. * Proxy class to access legacy WooCommerce functionality.
@ -34,7 +35,10 @@ class LegacyProxy {
*/ */
public function get_instance_of( string $class_name, ...$args ) { public function get_instance_of( string $class_name, ...$args ) {
if ( false !== strpos( $class_name, '\\' ) ) { if ( false !== strpos( $class_name, '\\' ) ) {
throw new \Exception( 'The LegacyProxy class is not intended for getting instances of classes in the src directory, please use constructor injection or the instance of \\Psr\\Container\\ContainerInterface for that.' ); throw new \Exception(
'The LegacyProxy class is not intended for getting instances of classes in the src directory, please use ' .
Definition::INJECTION_METHOD . ' method injection or the instance of ' . ContainerInterface::class . ' for that.'
);
} }
// If a class has a dedicated method to obtain a instance, use it. // If a class has a dedicated method to obtain a instance, use it.

View File

@ -1,5 +1,9 @@
# WooCommerce `src` files # WooCommerce `src` files
## Important note
The dependency injection container is disabled for now due to conflicts with plugins that use the same container package. Therefore all the content about registering and resolving classes, and interacting with legacy code, doesn't apply yet. It will be enabled at a later time.
## Table of contents ## Table of contents
* [Installing Composer](#installing-composer) * [Installing Composer](#installing-composer)
@ -69,7 +73,7 @@ _Resolving_ a class means asking the container to provide an instance of the cla
In principle, the container should be used to register and resolve all the classes in the `src` directory. The exception might be data-only classes that could be created the old way (using a plain `new` statement); but as a rule of thumb, the container should always be used. In principle, the container should be used to register and resolve all the classes in the `src` directory. The exception might be data-only classes that could be created the old way (using a plain `new` statement); but as a rule of thumb, the container should always be used.
There are two ways to resolve registered classes, depending on from where they are resolved: There are two ways to resolve registered classes, depending on from where they are resolved:
* Classes in the `src` directory specify their dependencies as constructor arguments, which are automatically supplied by the container when the class is resolved (this is called _constructor injection_). * Classes in the `src` directory specify their dependencies as `init` arguments, which are automatically supplied by the container when the class is resolved (this is called _dependency injection_).
* For code in the `includes` directory there's a `wc_get_container` function that will return the container, then its `get` method can be used to resolve any class. * For code in the `includes` directory there's a `wc_get_container` function that will return the container, then its `get` method can be used to resolve any class.
### Resolving classes ### Resolving classes
@ -78,7 +82,7 @@ There are two ways to resolve registered classes, depending on from where they n
#### 1. Other classes in the `src` directory #### 1. Other classes in the `src` directory
When a class in the `src` directory depends on other one classes from the same directory, it should use constructor injection. This means specifying these dependencies as constructor arguments with appropriate type hints, and storing these in private variables, ready to be used when needed: When a class in the `src` directory depends on other one classes from the same directory, it should use method injection. This means specifying these dependencies as arguments in a `init` method with appropriate type hints, and storing these in private variables, ready to be used when needed:
```php ```php
use TheService1Namespace\Service1; use TheService1Namespace\Service1;
@ -89,7 +93,7 @@ class TheClassWithDependencies {
private $service2; private $service2;
public function __construct( Service1Class $service1, Service2Class $service2 ) { public function init( Service1Class $service1, Service2Class $service2 ) {
$this->$service1 = $service1; $this->$service1 = $service1;
$this->$service2 = $service2; $this->$service2 = $service2;
} }
@ -100,9 +104,9 @@ class TheClassWithDependencies {
} }
``` ```
Whenever the container is about to resolve `TheClassWithDependencies` it will also resolve `Service1Class` and `Service2Class` and pass them as constructor arguments to the requested class. If these service classes have constructor arguments too then those will also be appropriately resolved recursively. Whenever the container is about to resolve `TheClassWithDependencies` it will also resolve `Service1Class` and `Service2Class` and pass them as method arguments to the requested class. If these service classes have method arguments too then those will also be appropriately resolved recursively.
A "lazy" approach is also possible if needed: you can specify the container itself as a constructor argument (using `\Psr\Container\ContainerInterface` as type hint), and use its `get` method to obtain the required instance at the appropriate time: A "lazy" approach is also possible if needed: you can specify the container itself as a method argument (using `\Psr\Container\ContainerInterface` as type hint), and use its `get` method to obtain the required instance at the appropriate time:
```php ```php
use TheService1Namespace\Service1; use TheService1Namespace\Service1;
@ -110,7 +114,7 @@ use TheService1Namespace\Service1;
class TheClassWithDependencies { class TheClassWithDependencies {
private $container; private $container;
public function __construct( \Psr\Container\ContainerInterface $container ) { public function init( \Psr\Container\ContainerInterface $container ) {
$this->$container = $container; $this->$container = $container;
} }
@ -120,7 +124,7 @@ class TheClassWithDependencies {
} }
``` ```
In general, however, constructor injection is preferred and the lazy approach should be used only when really necessary. In general, however, method injection is strongly preferred and the lazy approach should be used only when really necessary.
#### 2. Code in the `includes` directory #### 2. Code in the `includes` directory
@ -146,7 +150,7 @@ For a class to be resolvable using the container, it needs to have been previous
The `Container` class is "read-only", in that it has a `get` method to resolve classes but it doesn't have any method to register classes. Instead, class registration is done by using [service providers](https://container.thephpleague.com/3.x/service-providers/). That's how the whole process would go when creating a new class: The `Container` class is "read-only", in that it has a `get` method to resolve classes but it doesn't have any method to register classes. Instead, class registration is done by using [service providers](https://container.thephpleague.com/3.x/service-providers/). That's how the whole process would go when creating a new class:
First, create the class in the appropriate namespace (and thus in the matching folder), remember that the base namespace for the classes in the `src` directory is `Atuomattic\WooCommerce`. If the class depends on other classes from `src`, specify these dependencies as constructor arguments in detailed above. First, create the class in the appropriate namespace (and thus in the matching folder), remember that the base namespace for the classes in the `src` directory is `Atuomattic\WooCommerce`. If the class depends on other classes from `src`, specify these dependencies as `init` arguments in detailed above.
Example of such a class: Example of such a class:
@ -158,7 +162,7 @@ use Automattic\WooCommerce\TheDependencyNamespace\TheDependencyClass;
class TheClass { class TheClass {
private $the_dependency; private $the_dependency;
public function __construct( TheDependencyClass $dependency ) { public function init( TheDependencyClass $dependency ) {
$this->the_dependency = $dependency; $this->the_dependency = $dependency;
} }
@ -195,7 +199,7 @@ Worth noting:
* If you look at [the service provider documentation](https://container.thephpleague.com/3.x/service-providers/) you will see that classes are registered using `this->getContainer()->add`. WooCommerce's `AbstractServiceProvider` adds a utility `add` method itself that serves the same purpose. * If you look at [the service provider documentation](https://container.thephpleague.com/3.x/service-providers/) you will see that classes are registered using `this->getContainer()->add`. WooCommerce's `AbstractServiceProvider` adds a utility `add` method itself that serves the same purpose.
* You can use `share` instead of `add` to register single-instance classes (the class is instantiated only once and cached, so the same instance is returned every time the class is resolved). * You can use `share` instead of `add` to register single-instance classes (the class is instantiated only once and cached, so the same instance is returned every time the class is resolved).
If the class being registered has constructor arguments then the `add` (or `share`) method must be followed by as many `addArguments` calls as needed. WooCommerce's `AbstractServiceProvider` adds a utility `add_with_auto_arguments` method (and a sibling `share_with_auto_arguments` method) that uses reflection to figure out and register all the constructor arguments (which need to have type hints). Please have in mind the possible performance penalty incurred by the usage of reflection when using this helper method. If the class being registered has `init` arguments then the `add` (or `share`) method must be followed by as many `addArguments` calls as needed. WooCommerce's `AbstractServiceProvider` adds a utility `add_with_auto_arguments` method (and a sibling `share_with_auto_arguments` method) that uses reflection to figure out and register all the `init` arguments (which need to have type hints). Please have in mind the possible performance penalty incurred by the usage of reflection when using this helper method.
An alternative version of the service provider, which is used to register both the class and its dependency, and which takes advantage of `add_with_auto_arguments`, could be as follows: An alternative version of the service provider, which is used to register both the class and its dependency, and which takes advantage of `add_with_auto_arguments`, could be as follows:
@ -259,7 +263,7 @@ Note that if the closure is defined as a function with arguments, the supplied p
The container is intended for registering **only** classes in the `src` folder. There is a check in place to prevent classes outside the root `Automattic\Woocommerce` namespace from being registered. The container is intended for registering **only** classes in the `src` folder. There is a check in place to prevent classes outside the root `Automattic\Woocommerce` namespace from being registered.
This implies that classes outside `src` can't be constructor-injected, and thus must not be used as type hints in constructor arguments. There are mechanisms in place to interact with "outside" code (including code from the `includes` folder and third-party code) in a way that makes it easy to write unit tests. This implies that classes outside `src` can't be dependency-injected, and thus must not be used as type hints in `init` arguments. There are mechanisms in place to interact with "outside" code (including code from the `includes` folder and third-party code) in a way that makes it easy to write unit tests.
## The `Internal` namespace ## The `Internal` namespace
@ -298,7 +302,7 @@ But how does using `LegacyProxy` help in making the code testable? The trick is
### Using the legacy proxy ### Using the legacy proxy
`LegacyProxy` is a class that is registered in the container as any other class, so an instance can be obtained by using constructor injection: `LegacyProxy` is a class that is registered in the container as any other class, so an instance can be obtained by using dependency-injection:
```php ```php
use Automattic\WooCommerce\Proxies\LegacyProxy; use Automattic\WooCommerce\Proxies\LegacyProxy;
@ -306,7 +310,7 @@ use Automattic\WooCommerce\Proxies\LegacyProxy;
class TheClass { class TheClass {
private $legacy_proxy; private $legacy_proxy;
public function __construct( LegacyProxy $legacy_proxy ) { public function init( LegacyProxy $legacy_proxy ) {
$this->legacy_proxy = $legacy_proxy; $this->legacy_proxy = $legacy_proxy;
} }
@ -316,7 +320,7 @@ class TheClass {
} }
``` ```
However, the recommended way (especially when no other dependencies need to be constructor-injected) is to use the equivalent methods in the `WooCommerce` class via the `WC()` helper, like this: However, the recommended way (especially when no other dependencies need to be dependency-injected) is to use the equivalent methods in the `WooCommerce` class via the `WC()` helper, like this:
```php ```php
class TheClass { class TheClass {
@ -382,14 +386,14 @@ class ActionsProxy {
} }
``` ```
Note however that such a class would have to be explicitly constructor-injected (unless additional helper methods are defined in the `WooCommerce` class), and that you would need to create a pairing mock class (e.g. `MockableActionsProxy`) and replace the original registration using `wc_get_container()->replace( ActionsProxy::class, MockableActionsProxy::class )`. Note however that such a class would have to be explicitly dependency-injected (unless additional helper methods are defined in the `WooCommerce` class), and that you would need to create a pairing mock class (e.g. `MockableActionsProxy`) and replace the original registration using `wc_get_container()->replace( ActionsProxy::class, MockableActionsProxy::class )`.
## Defining new actions and filters ## Defining new actions and filters
WordPress' hooks (actions and filters) are a very powerful extensibility mechanism and it's the core tool that allows WooCommerce extensions to be developer. However it has been often (ab)used in the WooCommerce core codebase to drive internal logic, e.g. an action is triggered from within one class or function with the assumption that somewhere there's some other class or function that will handle it and continue whatever processing is supposed to happen. WordPress' hooks (actions and filters) are a very powerful extensibility mechanism and it's the core tool that allows WooCommerce extensions to be developer. However it has been often (ab)used in the WooCommerce core codebase to drive internal logic, e.g. an action is triggered from within one class or function with the assumption that somewhere there's some other class or function that will handle it and continue whatever processing is supposed to happen.
In order to keep the code as easy as reasonably possible to read and maintain, **hooks shouldn't be used to drive WooCommerce's internal logic and processes**. If you need the services of a given class or function, please call these directly (by using constructor injection or the legacy proxy as appropriate to get access to the desired service). **New hooks should be introduced only if they provide a valuable extension point for plugins**. In order to keep the code as easy as reasonably possible to read and maintain, **hooks shouldn't be used to drive WooCommerce's internal logic and processes**. If you need the services of a given class or function, please call these directly (by using dependency-injection or the legacy proxy as appropriate to get access to the desired service). **New hooks should be introduced only if they provide a valuable extension point for plugins**.
As usual, there might be reasonable exceptions to this; but please keep this rule in mind whenever you consider creating a new hook. As usual, there might be reasonable exceptions to this; but please keep this rule in mind whenever you consider creating a new hook.
@ -400,11 +404,11 @@ Unit tests are a fundamental tool to keep the code reliable and reasonably safe
**If you are a WooCommerce core team member or a contributor from other team at Automattic:** Please write unit tests to cover any code addition or modification that you make to the `src` directory (and ideally the same for the `includes` directory, by the way). There are always reasonable exceptions, but the rule of thumb is that all code should be covered by tests. **If you are a WooCommerce core team member or a contributor from other team at Automattic:** Please write unit tests to cover any code addition or modification that you make to the `src` directory (and ideally the same for the `includes` directory, by the way). There are always reasonable exceptions, but the rule of thumb is that all code should be covered by tests.
**If you are an external contributor:** When adding or changing code on the WooCommerce codebase, and especially in the `src` directory, adding unit tests is recommended but not mandatory: no contributions will be rejected solely for lacking unit tests. However, please try to at least make the code easily testable by honoring the container and constructor injection mechanism, and by using the legacy proxy to interact with legacy code when needed. If you do so, the WooCommerce team or other contributors will be able to add the missing tests. **If you are an external contributor:** When adding or changing code on the WooCommerce codebase, and especially in the `src` directory, adding unit tests is recommended but not mandatory: no contributions will be rejected solely for lacking unit tests. However, please try to at least make the code easily testable by honoring the container and dependency-injection mechanism, and by using the legacy proxy to interact with legacy code when needed. If you do so, the WooCommerce team or other contributors will be able to add the missing tests.
### Mocking dependencies ### Mocking dependencies
Since all the dependencies for classes in this directory are constructor-injected or retrieved lazily by directly accessing the container, it's easy to mock them by either manually creating a mock class with the same public surface or by using [PHPUnit's test doubles](https://phpunit.readthedocs.io/en/9.2/test-doubles.html): Since all the dependencies for classes in this directory are dependency-injected or retrieved lazily by directly accessing the container, it's easy to mock them by either manually creating a mock class with the same public surface or by using [PHPUnit's test doubles](https://phpunit.readthedocs.io/en/9.2/test-doubles.html):
```php ```php
$dependency_mock = somehow_create_mock(); $dependency_mock = somehow_create_mock();

View File

@ -0,0 +1,60 @@
<?php
/**
* A class of utilities for dealing with strings.
*/
namespace Automattic\WooCommerce\Utilities;
/**
* A class of utilities for dealing with strings.
*/
final class StringUtil {
/**
* Checks to see whether or not a string starts with another.
*
* @param string $string The string we want to check.
* @param string $starts_with The string we're looking for at the start of $string.
* @param bool $case_sensitive Indicates whether the comparison should be case-sensitive.
*
* @return bool True if the $string starts with $starts_with, false otherwise.
*/
public static function starts_with( string $string, string $starts_with, bool $case_sensitive = true ): bool {
$len = strlen( $starts_with );
if ( $len > strlen( $string ) ) {
return false;
}
$string = substr( $string, 0, $len );
if ( $case_sensitive ) {
return strcmp( $string, $starts_with ) === 0;
}
return strcasecmp( $string, $starts_with ) === 0;
}
/**
* Checks to see whether or not a string ends with another.
*
* @param string $string The string we want to check.
* @param string $ends_with The string we're looking for at the end of $string.
* @param bool $case_sensitive Indicates whether the comparison should be case-sensitive.
*
* @return bool True if the $string ends with $ends_with, false otherwise.
*/
public static function ends_with( string $string, string $ends_with, bool $case_sensitive = true ): bool {
$len = strlen( $ends_with );
if ( $len > strlen( $string ) ) {
return false;
}
$string = substr( $string, -$len );
if ( $case_sensitive ) {
return strcmp( $string, $ends_with ) === 0;
}
return strcasecmp( $string, $ends_with ) === 0;
}
}

View File

@ -11,8 +11,8 @@
* the readme will list any important changes. * the readme will list any important changes.
* *
* @see https://docs.woocommerce.com/document/template-structure/ * @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates * @package WooCommerce/Templates
* @version 4.4.0 * @version 3.8.0
*/ */
defined( 'ABSPATH' ) || exit; defined( 'ABSPATH' ) || exit;
@ -77,9 +77,9 @@ do_action( 'woocommerce_before_cart' ); ?>
<td class="product-name" data-title="<?php esc_attr_e( 'Product', 'woocommerce' ); ?>"> <td class="product-name" data-title="<?php esc_attr_e( 'Product', 'woocommerce' ); ?>">
<?php <?php
if ( ! $product_permalink ) { if ( ! $product_permalink ) {
echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', esc_html( $_product->get_name() ), $cart_item, $cart_item_key ) . '&nbsp;' ); echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', $_product->get_name(), $cart_item, $cart_item_key ) . '&nbsp;' );
} else { } else {
echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', sprintf( '<a href="%s">%s</a>', esc_url( $product_permalink ), esc_html( $_product->get_name() ) ), $cart_item, $cart_item_key ) ); echo wp_kses_post( apply_filters( 'woocommerce_cart_item_name', sprintf( '<a href="%s">%s</a>', esc_url( $product_permalink ), $_product->get_name() ), $cart_item, $cart_item_key ) );
} }
do_action( 'woocommerce_after_cart_item_name', $cart_item, $cart_item_key ); do_action( 'woocommerce_after_cart_item_name', $cart_item, $cart_item_key );

View File

@ -97,9 +97,7 @@ do_action( 'woocommerce_before_account_orders', $has_orders ); ?>
<?php else : ?> <?php else : ?>
<div class="woocommerce-message woocommerce-message--info woocommerce-Message woocommerce-Message--info woocommerce-info"> <div class="woocommerce-message woocommerce-message--info woocommerce-Message woocommerce-Message--info woocommerce-info">
<a class="woocommerce-Button button" href="<?php echo esc_url( apply_filters( 'woocommerce_return_to_shop_redirect', wc_get_page_permalink( 'shop' ) ) ); ?>"> <a class="woocommerce-Button button" href="<?php echo esc_url( apply_filters( 'woocommerce_return_to_shop_redirect', wc_get_page_permalink( 'shop' ) ) ); ?>"><?php esc_html_e( 'Browse products', 'woocommerce' ); ?></a>
<?php esc_html_e( 'Browse products', 'woocommerce' ); ?>
</a>
<?php esc_html_e( 'No order has been made yet.', 'woocommerce' ); ?> <?php esc_html_e( 'No order has been made yet.', 'woocommerce' ); ?>
</div> </div>
<?php endif; ?> <?php endif; ?>

View File

@ -10,16 +10,13 @@
* happen. When this occurs the version of the template file will be bumped and * happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes. * the readme will list any important changes.
* *
* @see https://docs.woocommerce.com/document/template-structure/ * @see https://docs.woocommerce.com/document/template-structure/
* @package WooCommerce\Templates * @package WooCommerce/Templates
* @version 4.4.0 * @version 1.6.4
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly. exit; // Exit if accessed directly.
} }
?> the_title( '<h1 class="product_title entry-title">', '</h1>' );
<h1 class="product_title entry-title">
<?php echo esc_html( get_the_title() ); ?>
</h1>

View File

@ -25,9 +25,6 @@ final class DependencyManagementTestHook implements BeforeTestHook {
* @param string $test "TestClass::TestMethod". * @param string $test "TestClass::TestMethod".
*/ */
public function executeBeforeTest( string $test ): void { public function executeBeforeTest( string $test ): void {
// Reset the instance of MockableLegacyProxy that was registered during bootstrap,
// in order to start the test in a clean state (without anything mocked).
wc_get_container()->get( LegacyProxy::class )->reset();
} }
} }

View File

@ -5,6 +5,11 @@ if [[ ${RUN_PHPCS} == 1 ]]; then
IGNORE="tests/cli/,includes/libraries/,includes/api/legacy/" IGNORE="tests/cli/,includes/libraries/,includes/api/legacy/"
if [ "$CHANGED_FILES" != "" ]; then if [ "$CHANGED_FILES" != "" ]; then
if [ ! -f "./vendor/bin/phpcs" ]; then
# Install wpcs globally
composer require woocommerce/woocommerce-sniffs --update-with-all-dependencies
fi
echo "Running Code Sniffer." echo "Running Code Sniffer."
./vendor/bin/phpcs --ignore=$IGNORE --encoding=utf-8 -s -n -p $CHANGED_FILES ./vendor/bin/phpcs --ignore=$IGNORE --encoding=utf-8 -s -n -p $CHANGED_FILES
fi fi

View File

@ -130,16 +130,16 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
private static function terminate_proc( $proc ) { private static function terminate_proc( $proc ) {
$status = proc_get_status( $proc ); $status = proc_get_status( $proc );
$master_pid = $status['pid']; $pid = $status['pid'];
$output = `ps -o ppid,pid,command | grep $master_pid`; $output = `ps -o ppid,pid,command | grep $pid`;
foreach ( explode( PHP_EOL, $output ) as $line ) { foreach ( explode( PHP_EOL, $output ) as $line ) {
if ( preg_match( '/^\s*(\d+)\s+(\d+)/', $line, $matches ) ) { if ( preg_match( '/^\s*(\d+)\s+(\d+)/', $line, $matches ) ) {
$parent = $matches[1]; $parent = $matches[1];
$child = $matches[2]; $child = $matches[2];
if ( $parent == $master_pid ) { if ( $parent == $pid ) {
if ( ! posix_kill( (int) $child, 9 ) ) { if ( ! posix_kill( (int) $child, 9 ) ) {
throw new RuntimeException( posix_strerror( posix_get_last_error() ) ); throw new RuntimeException( posix_strerror( posix_get_last_error() ) );
} }
@ -147,7 +147,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface {
} }
} }
if ( ! posix_kill( (int) $master_pid, 9 ) ) { if ( ! posix_kill( (int) $pid, 9 ) ) {
throw new RuntimeException( posix_strerror( posix_get_last_error() ) ); throw new RuntimeException( posix_strerror( posix_get_last_error() ) );
} }
} }

View File

@ -94,7 +94,7 @@ Setup Wizard e2e test (located in `activate-and-setup` directory) will run befor
- Run `composer install --no-dev` - Run `composer install --no-dev`
- Run `npm run build` - Run `npm run build:core`
- Run the following command to build the test site using Docker: `npm run docker:up` and watch the site being built. Note that it may take a few minutes the first time you do that. The process is considered completed when the messages letting you know that WordPress was installed, WooCommerce was activated and users created will be displayed: - Run the following command to build the test site using Docker: `npm run docker:up` and watch the site being built. Note that it may take a few minutes the first time you do that. The process is considered completed when the messages letting you know that WordPress was installed, WooCommerce was activated and users created will be displayed:

View File

@ -5,15 +5,16 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { StoreOwnerFlow } from '../../utils/flows';
import { completeOnboardingWizard } from '../../utils/components';
import { import {
StoreOwnerFlow,
completeOldSetupWizard,
completeOnboardingWizard,
permalinkSettingsPageSaveChanges, permalinkSettingsPageSaveChanges,
setCheckbox, setCheckbox,
settingsPageSaveChanges, settingsPageSaveChanges,
verifyCheckboxIsSet, verifyCheckboxIsSet,
verifyValueOfInputField verifyValueOfInputField
} from '../../utils'; } from '@woocommerce/e2e-utils';
describe( 'Store owner can login and make sure WooCommerce is activated', () => { describe( 'Store owner can login and make sure WooCommerce is activated', () => {
beforeAll( async () => { beforeAll( async () => {

View File

@ -5,9 +5,12 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { createSimpleProduct } from '../../utils/components'; import {
import { CustomerFlow, StoreOwnerFlow } from '../../utils/flows'; CustomerFlow,
import { uiUnblocked } from '../../utils'; StoreOwnerFlow,
createSimpleProduct,
uiUnblocked
} from '@woocommerce/e2e-utils';
describe( 'Cart page', () => { describe( 'Cart page', () => {
beforeAll( async () => { beforeAll( async () => {

View File

@ -5,9 +5,15 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { createSimpleProduct } from '../../utils/components'; import {
import { CustomerFlow, StoreOwnerFlow } from '../../utils/flows'; CustomerFlow,
import { setCheckbox, settingsPageSaveChanges, uiUnblocked, verifyCheckboxIsSet } from '../../utils'; StoreOwnerFlow,
createSimpleProduct,
setCheckbox,
settingsPageSaveChanges,
uiUnblocked,
verifyCheckboxIsSet
} from '@woocommerce/e2e-utils';
const config = require( 'config' ); const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' ); const simpleProductName = config.get( 'products.simple.name' );

View File

@ -5,7 +5,10 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { CustomerFlow, StoreOwnerFlow } from '../../utils/flows'; import {
CustomerFlow,
StoreOwnerFlow
} from '@woocommerce/e2e-utils';
describe( 'My account page', () => { describe( 'My account page', () => {
it( 'allows customer to login', async () => { it( 'allows customer to login', async () => {

View File

@ -5,9 +5,13 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { createSimpleProduct, createVariableProduct } from '../../utils/components'; import {
import { CustomerFlow, StoreOwnerFlow } from '../../utils/flows'; CustomerFlow,
import { uiUnblocked } from '../../utils'; StoreOwnerFlow,
createSimpleProduct,
createVariableProduct,
uiUnblocked
} from '@woocommerce/e2e-utils';
let simplePostIdValue; let simplePostIdValue;
let variablePostIdValue; let variablePostIdValue;

View File

@ -5,8 +5,11 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { StoreOwnerFlow } from '../../utils/flows'; import {
import { clickTab, verifyPublishAndTrash } from '../../utils'; StoreOwnerFlow,
clickTab,
verifyPublishAndTrash
} from '@woocommerce/e2e-utils';
describe( 'Add New Coupon Page', () => { describe( 'Add New Coupon Page', () => {
beforeAll( async () => { beforeAll( async () => {

View File

@ -5,8 +5,10 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { StoreOwnerFlow } from '../../utils/flows'; import {
import { verifyPublishAndTrash } from '../../utils'; StoreOwnerFlow,
verifyPublishAndTrash
} from '@woocommerce/e2e-utils';
describe( 'Add New Order Page', () => { describe( 'Add New Order Page', () => {
beforeAll( async () => { beforeAll( async () => {

View File

@ -5,8 +5,11 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { StoreOwnerFlow } from '../../utils/flows'; import {
import { clickTab, uiUnblocked } from '../../utils'; StoreOwnerFlow,
clickTab,
uiUnblocked
} from '@woocommerce/e2e-utils';
const config = require( 'config' ); const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' ); const simpleProductName = config.get( 'products.simple.name' );

View File

@ -5,8 +5,11 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { StoreOwnerFlow } from '../../utils/flows'; import {
import { settingsPageSaveChanges, verifyValueOfInputField } from '../../utils'; StoreOwnerFlow,
settingsPageSaveChanges,
verifyValueOfInputField
} from '@woocommerce/e2e-utils';
describe( 'WooCommerce General Settings', () => { describe( 'WooCommerce General Settings', () => {
beforeAll( async () => { beforeAll( async () => {

View File

@ -5,8 +5,14 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { StoreOwnerFlow } from '../../utils/flows'; import {
import { setCheckbox, settingsPageSaveChanges, unsetCheckbox, verifyCheckboxIsSet, verifyCheckboxIsUnset } from '../../utils'; StoreOwnerFlow,
setCheckbox,
settingsPageSaveChanges,
unsetCheckbox,
verifyCheckboxIsSet,
verifyCheckboxIsUnset
} from '@woocommerce/e2e-utils';
describe( 'WooCommerce Products > Downloadable Products Settings', () => { describe( 'WooCommerce Products > Downloadable Products Settings', () => {
beforeAll( async () => { beforeAll( async () => {

View File

@ -5,15 +5,15 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { StoreOwnerFlow } from '../../utils/flows';
import { import {
StoreOwnerFlow,
clearAndFillInput, clearAndFillInput,
setCheckbox, setCheckbox,
settingsPageSaveChanges, settingsPageSaveChanges,
uiUnblocked, uiUnblocked,
verifyCheckboxIsSet, verifyCheckboxIsSet,
verifyValueOfInputField verifyValueOfInputField
} from '../../utils'; } from '@woocommerce/e2e-utils';
describe( 'WooCommerce Tax Settings', () => { describe( 'WooCommerce Tax Settings', () => {
beforeAll( async () => { beforeAll( async () => {

35
tests/e2e/utils/README.md Normal file
View File

@ -0,0 +1,35 @@
# WooCommerce End to End Test Utilities
This package contains utilities to simplify writing e2e tests specific to WooCommmerce.
## Installation
```bash
npm install @woocommerce/e2e-utils --save
```
## Usage
Example:
~~~js
import {
CustomerFlow,
StoreOwnerFlow,
createSimpleProduct,
uiUnblocked
} from '@woocommerce/e2e-utils';
describe( 'Cart page', () => {
beforeAll( async () => {
await StoreOwnerFlow.login();
await createSimpleProduct();
await StoreOwnerFlow.logout();
} );
it( 'should display no item in the cart', async () => {
await CustomerFlow.goToCart();
await expect( page ).toMatchElement( '.cart-empty', { text: 'Your cart is currently empty.' } );
} );
} );
~~~

View File

@ -1,166 +1,14 @@
/** import { CustomerFlow, StoreOwnerFlow } from './src/flows';
* External dependencies
*/
import { pressKeyWithModifier } from '@wordpress/e2e-test-utils';
/** import {
* Internal dependencies completeOnboardingWizard,
*/ completeOldSetupWizard,
const flows = require( './flows' ); createSimpleProduct,
createVariableProduct,
verifyAndPublish,
} from './src/components';
/** import {
* Perform a "select all" and then fill a input.
*
* @param {string} selector
* @param {string} value
*/
const clearAndFillInput = async ( selector, value ) => {
await page.focus( selector );
await pressKeyWithModifier( 'primary', 'a' );
await page.type( selector, value );
};
/**
* Click a tab (on post type edit screen).
*
* @param {string} tabName Tab label
*/
const clickTab = async ( tabName ) => {
await expect( page ).toClick( '.wc-tabs > li > a', { text: tabName } );
};
/**
* Save changes on a WooCommerce settings page.
*/
const settingsPageSaveChanges = async () => {
await page.focus( 'button.woocommerce-save-button' );
await Promise.all( [
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
page.click( 'button.woocommerce-save-button' ),
] );
};
/**
* Save changes on Permalink settings page.
*/
const permalinkSettingsPageSaveChanges = async () => {
await page.focus( '.wp-core-ui .button-primary' );
await Promise.all( [
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
page.click( '.wp-core-ui .button-primary' ),
] );
};
/**
* Set checkbox.
*
* @param {string} selector
*/
const setCheckbox = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
if ( checkboxStatus !== true ) {
await page.click( selector );
}
};
/**
* Unset checkbox.
*
* @param {string} selector
*/
const unsetCheckbox = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
if ( checkboxStatus === true ) {
await page.click( selector );
}
};
/**
* Wait for UI blocking to end.
*/
const uiUnblocked = async () => {
await page.waitForFunction( () => ! Boolean( document.querySelector( '.blockUI' ) ) );
};
/**
* Publish, verify that item was published. Trash, verify that item was trashed.
*
* @param {string} button (Publish)
* @param {string} publishNotice
* @param {string} publishVerification
* @param {string} trashVerification
*/
const verifyPublishAndTrash = async ( button, publishNotice, publishVerification, trashVerification ) => {
// Wait for auto save
await page.waitFor( 2000 );
// Publish
await expect( page ).toClick( button );
await page.waitForSelector( publishNotice );
// Verify
await expect( page ).toMatchElement( publishNotice, { text: publishVerification } );
if ( button === '.order_actions li .save_order' ) {
await expect( page ).toMatchElement( '#select2-order_status-container', { text: 'Processing' } );
await expect( page ).toMatchElement(
'#woocommerce-order-notes .note_content',
{
text: 'Order status changed from Pending payment to Processing.',
}
);
}
// Trash
await expect( page ).toClick( 'a', { text: "Move to Trash" } );
await page.waitForSelector( '#message' );
// Verify
await expect( page ).toMatchElement( publishNotice, { text: trashVerification } );
};
/**
* Verify that checkbox is set.
*
* @param {string} selector Selector of the checkbox that needs to be verified.
*/
const verifyCheckboxIsSet = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
await expect( checkboxStatus ).toBe( true );
};
/**
* Verify that checkbox is unset.
*
* @param {string} selector Selector of the checkbox that needs to be verified.
*/
const verifyCheckboxIsUnset = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
await expect( checkboxStatus ).not.toBe( true );
};
/**
* Verify the value of input field once it was saved (can be used for radio buttons verification as well).
*
* @param {string} selector Selector of the input field that needs to be verified.
* @param {string} value Value of the input field that needs to be verified.
*/
const verifyValueOfInputField = async( selector, value ) => {
await page.focus( selector );
const field = await page.$( selector );
const fieldValue = ( await ( await field.getProperty( 'value' ) ).jsonValue() );
await expect( fieldValue ).toBe( value );
};
module.exports = {
...flows,
clearAndFillInput, clearAndFillInput,
clickTab, clickTab,
settingsPageSaveChanges, settingsPageSaveChanges,
@ -172,4 +20,25 @@ module.exports = {
verifyCheckboxIsSet, verifyCheckboxIsSet,
verifyCheckboxIsUnset, verifyCheckboxIsUnset,
verifyValueOfInputField, verifyValueOfInputField,
}; } from './src/page-utils';
module.exports = {
CustomerFlow,
StoreOwnerFlow,
completeOnboardingWizard,
completeOldSetupWizard,
createSimpleProduct,
createVariableProduct,
verifyAndPublish,
clearAndFillInput,
clickTab,
settingsPageSaveChanges,
permalinkSettingsPageSaveChanges,
setCheckbox,
unsetCheckbox,
uiUnblocked,
verifyPublishAndTrash,
verifyCheckboxIsSet,
verifyCheckboxIsUnset,
verifyValueOfInputField
}

View File

@ -0,0 +1,16 @@
{
"name": "@woocommerce/e2e-utils",
"version": "0.1.0",
"description": "End-To-End (E2E) test utils for WooCommerce",
"homepage": "https://github.com/woocommerce/woocommerce/tree/master/tests/e2e-utils/README.md",
"repository": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce.git"
},
"license": "GPL-3.0+",
"main": "build/index.js",
"module": "build-module/index.js",
"dependencies": {
"@wordpress/e2e-test-utils": "4.6.0"
}
}

View File

@ -6,9 +6,9 @@
* Internal dependencies * Internal dependencies
*/ */
import { StoreOwnerFlow } from './flows'; import { StoreOwnerFlow } from './flows';
import { clickTab, uiUnblocked, verifyCheckboxIsUnset } from './index'; import modelRegistry from '../../utils/factories';
import modelRegistry from './factories';
import { SimpleProduct } from '@woocommerce/model-factories'; import { SimpleProduct } from '@woocommerce/model-factories';
import { clickTab, uiUnblocked, verifyCheckboxIsUnset } from './page-utils';
const config = require( 'config' ); const config = require( 'config' );
const simpleProductName = config.get( 'products.simple.name' ); const simpleProductName = config.get( 'products.simple.name' );
@ -115,7 +115,7 @@ const completeOnboardingWizard = async () => {
// Query for the product types checkboxes // Query for the product types checkboxes
const productTypesCheckboxes = await page.$$( '.components-checkbox-control__input' ); const productTypesCheckboxes = await page.$$( '.components-checkbox-control__input' );
expect( productTypesCheckboxes ).toHaveLength( 8 ); expect( productTypesCheckboxes ).toHaveLength( 7 );
// Select Physical and Downloadable products // Select Physical and Downloadable products
for ( let i = 1; i < 2; i++ ) { for ( let i = 1; i < 2; i++ ) {
@ -189,16 +189,26 @@ const completeOnboardingWizard = async () => {
// End of onboarding wizard // End of onboarding wizard
// Wait for "Woo-hoo almost there" window to appear // Wait for homescreen welcome modal to appear
await page.waitForSelector( '.components-modal__header-heading' ); await page.waitForSelector( '.woocommerce__welcome-modal__page-content__header' );
await expect( page ).toMatchElement( await expect( page ).toMatchElement(
'.components-modal__header-heading', { text: 'Woo hoo - you\'re almost there!' } '.woocommerce__welcome-modal__page-content__header', { text: 'Welcome to your WooCommerce store\s online HQ!' }
); );
// Wait for "Continue" button to become active // Wait for "Next" button to become active
await page.waitForSelector( 'button.is-primary:not(:disabled)' ); await page.waitForSelector( 'button.components-guide__forward-button' );
// Click on "Continue" button to move to the next step // Click on "Next" button to move to the next step
await page.click( 'button.is-primary:not(:disabled)' ); await page.click( 'button.components-guide__forward-button' );
// Wait for "Next" button to become active
await page.waitForSelector( 'button.components-guide__forward-button' );
// Click on "Next" button to move to the next step
await page.click( 'button.components-guide__forward-button' );
// Wait for "Let's go" button to become active
await page.waitForSelector( 'button.components-guide__finish-button' );
// Click on "Let's go" button to move to the next step
await page.click( 'button.components-guide__finish-button' );
}; };
/** /**

View File

@ -10,7 +10,7 @@ import { pressKeyWithModifier } from '@wordpress/e2e-test-utils';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { clearAndFillInput } from './index'; import { clearAndFillInput } from './page-utils';
const config = require( 'config' ); const config = require( 'config' );
const baseUrl = config.get( 'url' ); const baseUrl = config.get( 'url' );

View File

@ -0,0 +1,169 @@
/**
* External dependencies
*/
import { pressKeyWithModifier } from '@wordpress/e2e-test-utils';
/**
* Perform a "select all" and then fill a input.
*
* @param {string} selector
* @param {string} value
*/
const clearAndFillInput = async ( selector, value ) => {
await page.focus( selector );
await pressKeyWithModifier( 'primary', 'a' );
await page.type( selector, value );
};
/**
* Click a tab (on post type edit screen).
*
* @param {string} tabName Tab label
*/
const clickTab = async ( tabName ) => {
await expect( page ).toClick( '.wc-tabs > li > a', { text: tabName } );
};
/**
* Save changes on a WooCommerce settings page.
*/
const settingsPageSaveChanges = async () => {
await page.focus( 'button.woocommerce-save-button' );
await Promise.all( [
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
page.click( 'button.woocommerce-save-button' ),
] );
};
/**
* Save changes on Permalink settings page.
*/
const permalinkSettingsPageSaveChanges = async () => {
await page.focus( '.wp-core-ui .button-primary' );
await Promise.all( [
page.waitForNavigation( { waitUntil: 'networkidle0' } ),
page.click( '.wp-core-ui .button-primary' ),
] );
};
/**
* Set checkbox.
*
* @param {string} selector
*/
const setCheckbox = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
if ( checkboxStatus !== true ) {
await page.click( selector );
}
};
/**
* Unset checkbox.
*
* @param {string} selector
*/
const unsetCheckbox = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
if ( checkboxStatus === true ) {
await page.click( selector );
}
};
/**
* Wait for UI blocking to end.
*/
const uiUnblocked = async () => {
await page.waitForFunction( () => ! Boolean( document.querySelector( '.blockUI' ) ) );
};
/**
* Publish, verify that item was published. Trash, verify that item was trashed.
*
* @param {string} button (Publish)
* @param {string} publishNotice
* @param {string} publishVerification
* @param {string} trashVerification
*/
const verifyPublishAndTrash = async ( button, publishNotice, publishVerification, trashVerification ) => {
// Wait for auto save
await page.waitFor( 2000 );
// Publish
await expect( page ).toClick( button );
await page.waitForSelector( publishNotice );
// Verify
await expect( page ).toMatchElement( publishNotice, { text: publishVerification } );
if ( button === '.order_actions li .save_order' ) {
await expect( page ).toMatchElement( '#select2-order_status-container', { text: 'Processing' } );
await expect( page ).toMatchElement(
'#woocommerce-order-notes .note_content',
{
text: 'Order status changed from Pending payment to Processing.',
}
);
}
// Trash
await expect( page ).toClick( 'a', { text: "Move to Trash" } );
await page.waitForSelector( '#message' );
// Verify
await expect( page ).toMatchElement( publishNotice, { text: trashVerification } );
};
/**
* Verify that checkbox is set.
*
* @param {string} selector Selector of the checkbox that needs to be verified.
*/
const verifyCheckboxIsSet = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
await expect( checkboxStatus ).toBe( true );
};
/**
* Verify that checkbox is unset.
*
* @param {string} selector Selector of the checkbox that needs to be verified.
*/
const verifyCheckboxIsUnset = async( selector ) => {
await page.focus( selector );
const checkbox = await page.$( selector );
const checkboxStatus = ( await ( await checkbox.getProperty( 'checked' ) ).jsonValue() );
await expect( checkboxStatus ).not.toBe( true );
};
/**
* Verify the value of input field once it was saved (can be used for radio buttons verification as well).
*
* @param {string} selector Selector of the input field that needs to be verified.
* @param {string} value Value of the input field that needs to be verified.
*/
const verifyValueOfInputField = async( selector, value ) => {
await page.focus( selector );
const field = await page.$( selector );
const fieldValue = ( await ( await field.getProperty( 'value' ) ).jsonValue() );
await expect( fieldValue ).toBe( value );
};
export {
clearAndFillInput,
clickTab,
settingsPageSaveChanges,
permalinkSettingsPageSaveChanges,
setCheckbox,
unsetCheckbox,
uiUnblocked,
verifyPublishAndTrash,
verifyCheckboxIsSet,
verifyCheckboxIsUnset,
verifyValueOfInputField,
};

Some files were not shown because too many files have changed in this diff Show More