diff --git a/.github/workflows/build-release-zip-file.yml b/.github/workflows/build-release-zip-file.yml new file mode 100644 index 00000000000..2f6dd819e20 --- /dev/null +++ b/.github/workflows/build-release-zip-file.yml @@ -0,0 +1,30 @@ +name: Build release zip file +on: + workflow_dispatch: + inputs: + ref: + description: 'By default the zip file is generated from the branch the workflow runs from, but you can specify an explicit reference to use instead here (e.g. refs/tags/tag_name). The resulting file will be available as an artifact on the workflow run.' + required: false + default: '' +jobs: + build: + name: Build release zip file + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.ref || github.ref }} + - name: Build the zip file + id: build + uses: woocommerce/action-build@v2 + - name: Unzip the file (prevents double zip problem) + run: unzip ${{ steps.build.outputs.zip_path }} -d zipfile + - name: Upload the zip file as an artifact + uses: actions/upload-artifact@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: woocommerce + path: zipfile + retention-days: 7 diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index e5f93efa41c..152860063a4 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -21,3 +21,17 @@ jobs: asset_path: ${{ steps.build.outputs.zip_path }} asset_name: woocommerce.zip asset_content_type: application/zip + update-code-reference: + if: github.event.release.prerelease == false && github.event.release.draft == false && github.repository_owner == 'woocommerce' + name: Update Code Reference + needs: build + runs-on: ubuntu-latest + steps: + - name: Invoke Code Reference build and deploy workflow + uses: aurelien-baudet/workflow-dispatch@v2 + with: + workflow: GitHub Pages deploy + repo: woocommerce/code-reference + token: ${{ secrets.CUSTOM_GH_TOKEN }} + ref: refs/heads/trunk + inputs: '{ "version": "${{ github.event.release.tag_name }}" }' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000000..ddf9249f8df --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: Run CI +on: + push: + branches: + - trunk + - 'release/**' +jobs: + test: + name: PHP ${{ matrix.php }} WP ${{ matrix.wp }} + timeout-minutes: 15 + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: [ '7.0', '7.1', '7.2', '7.3', '7.4', '8.0' ] + wp: [ 'latest' ] + include: + - wp: nightly + php: '7.4' + - wp: '5.5' + php: 7.2 + - wp: '5.4' + php: 7.2 + services: + database: + image: mysql:5.6 + env: + MYSQL_ROOT_PASSWORD: root + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer + extensions: mysql + coverage: none + + - name: Tool versions + run: | + php --version + composer --version + + - name: Get cached composer directories + uses: actions/cache@v2 + with: + path: | + ./packages + ./vendor + key: ${{ runner.os }}-${{ hashFiles('./composer.lock') }} + + - name: Setup and install composer + run: composer install + + - name: Add PHP8 Compatibility. + run: | + if [ "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]; then + curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip + unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip + composer bin phpunit config --unset platform + composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}' + composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs + fi + + - name: Init DB and WP + run: ./tests/bin/install.sh woo_test root root 127.0.0.1 ${{ matrix.wp }} + + - name: Run tests + run: ./vendor/bin/phpunit -c ./phpunit.xml diff --git a/.github/workflows/nightly-builds.yml b/.github/workflows/nightly-builds.yml index fb2a2874cb0..ac25e0770b7 100644 --- a/.github/workflows/nightly-builds.yml +++ b/.github/workflows/nightly-builds.yml @@ -4,6 +4,7 @@ on: - cron: '0 0 * * *' # Run at 12 AM UTC. jobs: build: + if: github.repository_owner == 'woocommerce' name: Nightly builds strategy: fail-fast: false diff --git a/.github/workflows/pr-build-and-e2e-tests.yml b/.github/workflows/pr-build-and-e2e-tests.yml index bb66e43bc44..2b643c7ef49 100644 --- a/.github/workflows/pr-build-and-e2e-tests.yml +++ b/.github/workflows/pr-build-and-e2e-tests.yml @@ -21,38 +21,11 @@ jobs: name: woocommerce path: ${{ steps.build.outputs.zip_path }} retention-days: 7 - - e2e-tests-cache: - name: Set e2e caches for running tests - runs-on: ubuntu-latest - steps: - - name: Checkout code. - uses: actions/checkout@v2 - - - name: Load Node.js. - uses: actions/setup-node@v2 - with: - node-version: '12' - - # From https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows#using-the-cache-action - - name: Cache node modules - uses: actions/cache@v2 - id: cache_node_modules - env: - cache-name: cache-node-modules - with: - path: ./node_modules - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - - name: Run npm install, and cache if they aren't. - run: npm install - e2e-tests-run: name: Runs E2E tests. - runs-on: ubuntu-latest - needs: [ build, e2e-tests-cache ] + runs-on: ubuntu-18.04 + needs: [ build ] steps: - name: Create dirs. @@ -66,20 +39,6 @@ jobs: uses: actions/checkout@v2 with: path: package/woocommerce - - # From https://docs.github.com/en/actions/guides/caching-dependencies-to-speed-up-workflows#using-the-cache-action - - name: Cache node modules - uses: actions/cache@v2 - id: cache_node_modules - env: - cache-name: cache-node-modules - with: - path: ./node_modules - key: ${{ runner.os }}-build-${{ hashFiles('package-lock.json') }} - restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}- - - - name: Restore node modules from cache, if available. - run: mv ./node_modules package/woocommerce/node_modules - name: Run npm install. working-directory: package/woocommerce @@ -106,4 +65,8 @@ jobs: - name: Run tests command. working-directory: code/woocommerce + env: + WC_E2E_SCREENSHOTS: 1 + E2E_SLACK_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }} + E2E_SLACK_CHANNEL: ${{ secrets.E2E_SLACK_CHANNEL }} run: npx wc-e2e test:e2e diff --git a/.github/workflows/pr-code-coverage.yml b/.github/workflows/pr-code-coverage.yml index 0b12def78ac..bd01d32b521 100644 --- a/.github/workflows/pr-code-coverage.yml +++ b/.github/workflows/pr-code-coverage.yml @@ -17,6 +17,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + with: + fetch-depth: 100 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -49,3 +51,7 @@ jobs: run: | RUN_CODE_COVERAGE=1 bash ./tests/bin/phpunit.sh exit 0 + + - name: Send code coverage to Codecov. + run: | + bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/pr-code-sniff.yml b/.github/workflows/pr-code-sniff.yml index 427760afd13..67964501c0b 100644 --- a/.github/workflows/pr-code-sniff.yml +++ b/.github/workflows/pr-code-sniff.yml @@ -6,25 +6,17 @@ jobs: name: Code sniff (PHP 7.4, WP Latest) timeout-minutes: 15 runs-on: ubuntu-latest - services: - database: - image: mysql:5.6 - env: - MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 steps: - name: Checkout code uses: actions/checkout@v2 + with: + fetch-depth: 100 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 7.4 - tools: composer - extensions: mysql - coverage: none + tools: composer, cs2pr - name: Tool versions run: | @@ -42,8 +34,9 @@ jobs: - name: Setup and install composer run: composer install - - name: Init DB and WP - run: ./tests/bin/install.sh woo_test root root 127.0.0.1 latest - - name: Run code sniff - run: RUN_PHPCS=1 bash ./tests/bin/phpcs.sh + continue-on-error: true + run: ./tests/bin/phpcs.sh "${{ github.event.pull_request.base.sha }}" "${{ github.event.after }}" + + - name: Show PHPCS results in PR + run: cs2pr ./phpcs-report.xml diff --git a/.github/workflows/pull-request-post-merge-processing.yml b/.github/workflows/pull-request-post-merge-processing.yml new file mode 100644 index 00000000000..1347ed5e5e1 --- /dev/null +++ b/.github/workflows/pull-request-post-merge-processing.yml @@ -0,0 +1,32 @@ +name: "Pull request post-merge processing" +on: + pull_request: + types: [closed] + +jobs: + assign-milestone: + name: "Assign milestone to merged pull request" + if: github.event.pull_request.merged == true && ! github.event.pull_request.milestone + runs-on: ubuntu-latest + steps: + - name: "Get the milestone changer script" + run: | + curl \ + --silent \ + --fail \ + --header 'Authorization: bearer ${{ secrets.GITHUB_TOKEN }}' \ + --header 'User-Agent: GitHub action to set the milestone for a pull request' \ + --header 'Accept: application/vnd.github.v3.raw' \ + --remote-name \ + --location $GITHUB_API_URL/repos/${{ github.repository }}/contents/.github/workflows/scripts/assign-milestone-to-merged-pr.php + env: + GITHUB_API_URL: ${{ env.GITHUB_API_URL }} + - name: "Install PHP" + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + - name: "Run the milestone changer script" + run: php assign-milestone-to-merged-pr.php + env: + PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scripts/assign-milestone-to-merged-pr.php b/.github/workflows/scripts/assign-milestone-to-merged-pr.php new file mode 100644 index 00000000000..0258813e209 --- /dev/null +++ b/.github/workflows/scripts/assign-milestone-to-merged-pr.php @@ -0,0 +1,144 @@ + "$keyword { $body }" ); + $context = stream_context_create( + array( + 'http' => array( + 'method' => 'POST', + 'header' => array( + 'Accept: application/json', + 'Content-Type: application/json', + 'User-Agent: GitHub action to set the milestone for a pull request', + 'Authorization: bearer ' . $github_token, + ), + 'content' => json_encode( $data ), + ), + ) + ); + + $result = file_get_contents( $graphql_api_url, false, $context ); + return json_decode( $result, true ); +} + +// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions diff --git a/.github/workflows/smoke-test-daily.yml b/.github/workflows/smoke-test-daily.yml new file mode 100644 index 00000000000..d168350f512 --- /dev/null +++ b/.github/workflows/smoke-test-daily.yml @@ -0,0 +1,43 @@ +name: Smoke test daily +on: + schedule: + - cron: '25 3 * * *' +jobs: + login-run: + name: Daily smoke test on trunk. + runs-on: ubuntu-18.04 + steps: + + - name: Create dirs. + run: | + mkdir -p code/woocommerce + mkdir -p package/woocommerce + mkdir -p tmp/woocommerce + mkdir -p node_modules + + - name: Checkout code. + uses: actions/checkout@v2 + with: + ref: trunk + + - name: Install prerequisites. + run: | + npm install + composer install --no-dev + npm run build:assets + npm install jest + + - name: Run smoke test. + env: + SMOKE_TEST_URL: ${{ secrets.SMOKE_TEST_URL }} + SMOKE_TEST_ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }} + SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }} + SMOKE_TEST_CUSTOMER_USER: ${{ secrets.SMOKE_TEST_CUSTOMER_USER }} + SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_CUSTOMER_PASSWORD }} + WC_E2E_SCREENSHOTS: 1 + E2E_RETEST: 1 + E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }} + E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }} + run: | + npx wc-e2e docker:up + npx wc-e2e test:e2e diff --git a/.github/workflows/stalebot.yml b/.github/workflows/stalebot.yml new file mode 100644 index 00000000000..23c1a0eec6e --- /dev/null +++ b/.github/workflows/stalebot.yml @@ -0,0 +1,20 @@ +name: 'Close stale needs-feedback issues' +on: + schedule: + - cron: '21 0 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "As a part of this repository’s maintenance, this issue is being marked as stale due to inactivity. Please feel free to comment on it in case we missed something.\n\n###### After 7 days with no activity this issue will be automatically be closed." + close-issue-message: 'This issue was closed because it has been 14 days with no activity.' + days-before-issue-stale: 7 + days-before-issue-close: 7 + days-before-pr-close: -1 + only-issue-labels: 'needs feedback' + close-issue-label: "category: can't reproduce" + ascending: true diff --git a/.github/workflows/update-feedback-labels.yml b/.github/workflows/update-feedback-labels.yml new file mode 100644 index 00000000000..b353c28032d --- /dev/null +++ b/.github/workflows/update-feedback-labels.yml @@ -0,0 +1,22 @@ +name: 'Update contributor feedback labels on comment' +on: 'issue_comment' + +jobs: + feedback: + if: | + github.actor != 'github-actions' && + github.event.issue && + github.event.issue.state == 'open' && + contains(github.event.issue.labels.*.name, 'needs feedback') + runs-on: ubuntu-latest + steps: + - name: Add has feedback + uses: actions-ecosystem/action-add-labels@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: 'has feedback' + - name: remove needs feedback + uses: actions-ecosystem/action-remove-labels@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: 'needs feedback' diff --git a/.gitignore b/.gitignore index c86c350cc91..106818c04f8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ project.properties .settings* .idea .vscode +.eslintcache *.sublime-project *.sublime-workspace .sublimelinterrc @@ -50,6 +51,7 @@ tests/cli/vendor /tests/e2e/env/docker/wp-cli/initialize.sh /tests/e2e/env/build/ /tests/e2e/env/build-module/ +/tests/e2e/screenshots /tests/e2e/utils/build/ /tests/e2e/utils/build-module/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2f7c3afcc8a..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,110 +0,0 @@ -version: ~> 1.0 - -# Specifies that Travis should create builds for trunk and release branches and also tags. -branches: - only: - - trunk - - /^\d+\.\d+(\.\d+)?(-\S*)?$/ - - /^release\// - -language: php -os: - - linux -dist: xenial - -# Test main supported versions of PHP against latest WP. -php: - - "7.0" - - "7.1" - - "7.2" - - "7.3" - - "7.4" - - "8.0" - -env: - - WP_VERSION=latest WP_MULTISITE=0 - -# Additional tests against stable PHP (min version is 7.0) -# and code coverage report. -jobs: - fast_finish: true - include: - - name: "Core E2E Tests" - env: WP_VERSION=latest WP_MULTISITE=0 RUN_E2E=1 - install: - - nvm install - - npm install - - composer install --no-dev - script: - - npm run build:assets - - npm run docker:up - - npm run test:e2e - after_script: - - npm run docker:down - - name: "WP Nightly" - php: "7.4" - env: WP_VERSION=nightly WP_MULTISITE=0 - - name: "WP Latest - 1" - php: "7.2" - env: WP_VERSION=5.5 WP_MULTISITE=0 - - name: "WP Latest - 2" - php: "7.2" - env: WP_VERSION=5.4 WP_MULTISITE=0 - - name: "Code Standards" - php: "7.4" - env: WP_VERSION=latest WP_MULTISITE=0 RUN_PHPCS=1 - - name: "Code Coverage" - php: "7.4" - env: WP_VERSION=latest WP_MULTISITE=0 RUN_CODE_COVERAGE=1 - allow_failures: - - php: "7.4" - env: WP_VERSION=latest WP_MULTISITE=0 RUN_CODE_COVERAGE=1 - -# Git clone depth -# By default Travis CI clones repositories to a depth of 50 commits. Using a depth of 1 makes this step a bit faster. -git: - depth: 1 - -# Since Xenial services are not started by default, we need to instruct it below to start. -services: - - mysql - - docker - -cache: - directories: - - $HOME/.composer/cache - -# Composer 2.0.7 introduced a change that broke the jetpack autoloader in PHP 7.0 - 7.3. -before_install: - - composer self-update 2.0.6 - -install: - - export PATH="$HOME/.composer/vendor/bin:$PATH" - - | - # Remove Xdebug for a huge performance increase: - if [ -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then - phpenv config-rm xdebug.ini - else - echo "xdebug.ini does not exist" - fi - - composer install - - | - if [ "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]; then - curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip - unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip - composer bin phpunit config --unset platform - composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}' - composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs - fi - - | - # Install WP Test suite: - if [[ ! -z "$WP_VERSION" ]]; then - bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION - fi - -script: - - bash tests/bin/phpunit.sh - - bash tests/bin/phpcs.sh - -after_script: - - bash tests/bin/travis.sh after diff --git a/README.md b/README.md index b24fb96bff1..4ff98b84e02 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Latest Stable Version WordPress.org downloads WordPress.org rating -Build Status +Build Status codecov

@@ -35,3 +35,9 @@ Support requests in issues on this repository will be closed on sight. ## Contributing to WooCommerce If you have a patch or have stumbled upon an issue with WooCommerce core, you can contribute this back to the code. Please read our [contributor guidelines](https://github.com/woocommerce/woocommerce/blob/trunk/.github/CONTRIBUTING.md) for more information how you can do this. + +

+

+ Made with đź’ś by WooCommerce.
+ We're hiring! Come work with us! +

diff --git a/assets/css/twenty-twenty-one.scss b/assets/css/twenty-twenty-one.scss index 1ddfb83a5dc..3091ae71eb2 100644 --- a/assets/css/twenty-twenty-one.scss +++ b/assets/css/twenty-twenty-one.scss @@ -348,6 +348,12 @@ a.button { .woocommerce, .woocommerce-page { + &.is-dark-theme { + .select2-dropdown { + color: var(--global--color-dark-gray); + } + } + table.shop_table { td, @@ -385,6 +391,7 @@ ul.products { .woocommerce-loop-product__link { display: block; text-decoration: none; + position: relative; } .woocommerce-loop-product__title { @@ -1311,6 +1318,30 @@ a.reset_variations { } } + &.woocommerce-lost-password { + .woocommerce { + + max-width: var(--responsive--alignwide-width) !important; + padding: 0 !important; + flex-wrap: wrap; + + .woocommerce-notices-wrapper { + flex: 1 0 100%; + } + + .woocommerce-ResetPassword { + + .woocommerce-form-row--first { + float: none; + } + + #user_login { + margin-bottom: 10px; + } + } + } + } + table.account-orders-table { margin-top: 0; border: 0; @@ -1432,6 +1463,11 @@ a.reset_variations { } .woocommerce-cart { + table.woocommerce-cart-form__contents { + thead, tfoot { + text-align: left; + } + } .post-inner { padding-top: 0; @@ -2122,6 +2158,10 @@ a.reset_variations { .woocommerce-table--order-details { margin-bottom: 2rem; + + thead, tfoot { + text-align: left; + } } /** diff --git a/assets/css/twenty-twenty.scss b/assets/css/twenty-twenty.scss index ad8cdab03be..3610d94c0d4 100644 --- a/assets/css/twenty-twenty.scss +++ b/assets/css/twenty-twenty.scss @@ -2474,6 +2474,12 @@ a.reset_variations { margin: 1.5rem 0; } } + + .woocommerce-ResetPassword { + .woocommerce-form-row--first { + float: none; + } + } } /** diff --git a/assets/images/mercadopago.png b/assets/images/mercadopago.png new file mode 100644 index 00000000000..39a790e5bf4 Binary files /dev/null and b/assets/images/mercadopago.png differ diff --git a/assets/js/admin/meta-boxes-product-variation.js b/assets/js/admin/meta-boxes-product-variation.js index 55ac9327559..551ad33a114 100644 --- a/assets/js/admin/meta-boxes-product-variation.js +++ b/assets/js/admin/meta-boxes-product-variation.js @@ -763,6 +763,7 @@ jQuery( function( $ ) { case 'variable_regular_price' : case 'variable_sale_price' : case 'variable_stock' : + case 'variable_low_stock_amount' : case 'variable_weight' : case 'variable_length' : case 'variable_width' : diff --git a/assets/js/admin/wc-enhanced-select.js b/assets/js/admin/wc-enhanced-select.js index 4eac44f6371..843ce80640c 100644 --- a/assets/js/admin/wc-enhanced-select.js +++ b/assets/js/admin/wc-enhanced-select.js @@ -72,6 +72,49 @@ jQuery( function( $ ) { $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); }); + function display_result( self, select2_args ) { + select2_args = $.extend( select2_args, getEnhancedSelectFormatString() ); + + $( self ).selectWoo( select2_args ).addClass( 'enhanced' ); + + if ( $( self ).data( 'sortable' ) ) { + var $select = $(self); + var $list = $( self ).next( '.select2-container' ).find( 'ul.select2-selection__rendered' ); + + $list.sortable({ + placeholder : 'ui-state-highlight select2-selection__choice', + forcePlaceholderSize: true, + items : 'li:not(.select2-search__field)', + tolerance : 'pointer', + stop: function() { + $( $list.find( '.select2-selection__choice' ).get().reverse() ).each( function() { + var id = $( self ).data( 'data' ).id; + var option = $select.find( 'option[value="' + id + '"]' )[0]; + $select.prepend( option ); + } ); + } + }); + // Keep multiselects ordered alphabetically if they are not sortable. + } else if ( $( self ).prop( 'multiple' ) ) { + $( self ).on( 'change', function(){ + var $children = $( self ).children(); + $children.sort(function(a, b){ + var atext = a.text.toLowerCase(); + var btext = b.text.toLowerCase(); + + if ( atext > btext ) { + return 1; + } + if ( atext < btext ) { + return -1; + } + return 0; + }); + $( self ).html( $children ); + }); + } + } + // Ajax product search box $( ':input.wc-product-search' ).filter( ':not(.enhanced)' ).each( function() { var select2_args = { @@ -112,46 +155,48 @@ jQuery( function( $ ) { } }; - select2_args = $.extend( select2_args, getEnhancedSelectFormatString() ); + display_result( this, select2_args ); + }); + + // Ajax Page Search. + $( ':input.wc-page-search' ).filter( ':not(.enhanced)' ).each( function() { + var select2_args = { + allowClear: $( this ).data( 'allow_clear' ) ? true : false, + placeholder: $( this ).data( 'placeholder' ), + minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '3', + escapeMarkup: function( m ) { + return m; + }, + ajax: { + url: wc_enhanced_select_params.ajax_url, + dataType: 'json', + delay: 250, + data: function( params ) { + return { + term : params.term, + action : $( this ).data( 'action' ) || 'woocommerce_json_search_pages', + security : wc_enhanced_select_params.search_pages_nonce, + exclude : $( this ).data( 'exclude' ), + post_status : $( this ).data( 'post_status' ), + limit : $( this ).data( 'limit' ), + }; + }, + processResults: function( data ) { + var terms = []; + if ( data ) { + $.each( data, function( id, text ) { + terms.push( { id: id, text: text } ); + } ); + } + return { + results: terms + }; + }, + cache: true + } + }; $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); - - if ( $( this ).data( 'sortable' ) ) { - var $select = $(this); - var $list = $( this ).next( '.select2-container' ).find( 'ul.select2-selection__rendered' ); - - $list.sortable({ - placeholder : 'ui-state-highlight select2-selection__choice', - forcePlaceholderSize: true, - items : 'li:not(.select2-search__field)', - tolerance : 'pointer', - stop: function() { - $( $list.find( '.select2-selection__choice' ).get().reverse() ).each( function() { - var id = $( this ).data( 'data' ).id; - var option = $select.find( 'option[value="' + id + '"]' )[0]; - $select.prepend( option ); - } ); - } - }); - // Keep multiselects ordered alphabetically if they are not sortable. - } else if ( $( this ).prop( 'multiple' ) ) { - $( this ).on( 'change', function(){ - var $children = $( this ).children(); - $children.sort(function(a, b){ - var atext = a.text.toLowerCase(); - var btext = b.text.toLowerCase(); - - if ( atext > btext ) { - return 1; - } - if ( atext < btext ) { - return -1; - } - return 0; - }); - $( this ).html( $children ); - }); - } }); // Ajax customer search boxes diff --git a/assets/js/admin/wc-shipping-zone-methods.js b/assets/js/admin/wc-shipping-zone-methods.js index 08e4d097d0c..45d17f19d21 100644 --- a/assets/js/admin/wc-shipping-zone-methods.js +++ b/assets/js/admin/wc-shipping-zone-methods.js @@ -62,6 +62,9 @@ shippingMethod.trigger( 'change:methods' ); shippingMethod.changes = {}; shippingMethod.trigger( 'saved:methods' ); + + // Overrides the onbeforeunload callback added by settings.js. + window.onbeforeunload = null; } else { window.alert( data.strings.save_failed ); } diff --git a/assets/js/frontend/cart.js b/assets/js/frontend/cart.js index 622c8eb1437..cdee33f5d79 100644 --- a/assets/js/frontend/cart.js +++ b/assets/js/frontend/cart.js @@ -57,6 +57,28 @@ jQuery( function( $ ) { $node.removeClass( 'processing' ).unblock(); }; + /** + * Removes duplicate notices. + * + * @param {JQuery Object} notices + */ + var remove_duplicate_notices = function( notices ) { + var seen = []; + var new_notices = notices; + + notices.each( function( index ) { + var text = $( this ).text(); + + if ( 'undefined' === typeof seen[ text ] ) { + seen[ text ] = true; + } else { + new_notices.splice( index, 1 ); + } + } ); + + return new_notices; + }; + /** * Update the .woocommerce div with a string of html. * @@ -67,7 +89,7 @@ jQuery( function( $ ) { var $html = $.parseHTML( html_str ); var $new_form = $( '.woocommerce-cart-form', $html ); var $new_totals = $( '.cart_totals', $html ); - var $notices = $( '.woocommerce-error, .woocommerce-message, .woocommerce-info', $html ); + var $notices = remove_duplicate_notices( $( '.woocommerce-error, .woocommerce-message, .woocommerce-info', $html ) ); // No form, cannot do this. if ( $( '.woocommerce-cart-form' ).length === 0 ) { @@ -180,6 +202,7 @@ jQuery( function( $ ) { */ toggle_shipping: function() { $( '.shipping-calculator-form' ).slideToggle( 'slow' ); + $( 'select.country_to_state, input.country_to_state' ).trigger( 'change' ); $( document.body ).trigger( 'country_to_state_changed' ); // Trigger select2 to load. return false; }, diff --git a/assets/js/frontend/checkout.js b/assets/js/frontend/checkout.js index 597ef13e4f0..c7e5960df7f 100644 --- a/assets/js/frontend/checkout.js +++ b/assets/js/frontend/checkout.js @@ -525,7 +525,7 @@ jQuery( function( $ ) { wc_checkout_form.detachUnloadEventsOnSubmit(); try { - if ( 'success' === result.result && $form.triggerHandler( 'checkout_place_order_success' ) !== false ) { + if ( 'success' === result.result && $form.triggerHandler( 'checkout_place_order_success', result ) !== false ) { if ( -1 === result.redirect.indexOf( 'https://' ) || -1 === result.redirect.indexOf( 'http://' ) ) { window.location = result.redirect; } else { diff --git a/assets/js/frontend/country-select.js b/assets/js/frontend/country-select.js index 0c003dd9e41..00e9d902682 100644 --- a/assets/js/frontend/country-select.js +++ b/assets/js/frontend/country-select.js @@ -55,8 +55,11 @@ jQuery( function( $ ) { var wc_country_select_select2 = function() { $( 'select.country_select:visible, select.state_select:visible' ).each( function() { + var $this = $( this ); + var select2_args = $.extend({ - placeholder: $( this ).attr( 'data-placeholder' ) || $( this ).attr( 'placeholder' ) || '', + placeholder: $this.attr( 'data-placeholder' ) || $this.attr( 'placeholder' ) || '', + label: $this.attr( 'data-label' ) || null, width: '100%' }, getEnhancedSelectFormatString() ); diff --git a/assets/js/select2/select2.full.js b/assets/js/select2/select2.full.js index e750834ef5d..49cb11ee692 100644 --- a/assets/js/select2/select2.full.js +++ b/assets/js/select2/select2.full.js @@ -1,27 +1,41 @@ /*! - * Select2 4.0.3 - * https://select2.github.io + * SelectWoo 1.0.9 + * https://github.com/woocommerce/selectWoo * * Released under the MIT license - * https://github.com/select2/select2/blob/master/LICENSE.md + * https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); - } else if (typeof exports === 'object') { + } else if (typeof module === 'object' && module.exports) { // Node/CommonJS - factory(require('jquery')); + module.exports = function (root, jQuery) { + if (jQuery === undefined) { + // require('jQuery') returns a factory that requires window to + // build a jQuery instance, we normalize how we use modules + // that require this pattern but the window provided is a noop + // if it's defined (how jquery works) + if (typeof window !== 'undefined') { + jQuery = require('jquery'); + } + else { + jQuery = require('jquery')(root); + } + } + factory(jQuery); + return jQuery; + }; } else { // Browser globals factory(jQuery); } -}(function (jQuery) { +} (function (jQuery) { // This is needed so we can catch the AMD loader configuration and use it // The inner file should be wrapped (by `banner.start.js`) in a function that // returns the AMD loader references. - var S2 = -(function () { + var S2 =(function () { // Restore the Select2 AMD loader so it can be used // Needed mostly in the language files, where the loader is not inserted if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { @@ -30,13 +44,11 @@ var S2;(function () { if (!S2 || !S2.requirejs) { if (!S2) { S2 = {}; } else { require = S2; } /** - * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/jrburke/almond for details + * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. + * Released under MIT license, http://github.com/requirejs/almond/LICENSE */ //Going sloppy to avoid 'use strict' string cost, but strict practices should //be followed. -/*jslint sloppy: true */ /*global setTimeout: false */ var requirejs, require, define; @@ -64,60 +76,58 @@ var requirejs, require, define; */ function normalize(name, baseName) { var nameParts, nameSegment, mapValue, foundMap, lastIndex, - foundI, foundStarMap, starI, i, j, part, + foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, baseParts = baseName && baseName.split("/"), map = config.map, starMap = (map && map['*']) || {}; //Adjust any relative paths. - if (name && name.charAt(0) === ".") { - //If have a base name, try to normalize against it, - //otherwise, assume it is a top-level require that will - //be relative to baseUrl in the end. - if (baseName) { - name = name.split('/'); - lastIndex = name.length - 1; + if (name) { + name = name.split('/'); + lastIndex = name.length - 1; - // Node .js allowance: - if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { - name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); - } + // If wanting node ID compatibility, strip .js from end + // of IDs. Have to do this here, and not in nameToUrl + // because node allows either .js or non .js to map + // to same file. + if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { + name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); + } - //Lop off the last part of baseParts, so that . matches the - //"directory" and not name of the baseName's module. For instance, - //baseName of "one/two/three", maps to "one/two/three.js", but we - //want the directory, "one/two" for this normalization. - name = baseParts.slice(0, baseParts.length - 1).concat(name); + // Starts with a '.' so need the baseName + if (name[0].charAt(0) === '.' && baseParts) { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + name = normalizedBaseParts.concat(name); + } - //start trimDots - for (i = 0; i < name.length; i += 1) { - part = name[i]; - if (part === ".") { - name.splice(i, 1); - i -= 1; - } else if (part === "..") { - if (i === 1 && (name[2] === '..' || name[0] === '..')) { - //End of the line. Keep at least one non-dot - //path segment at the front so it can be mapped - //correctly to disk. Otherwise, there is likely - //no path mapping for a path starting with '..'. - //This can still fail, but catches the most reasonable - //uses of .. - break; - } else if (i > 0) { - name.splice(i - 1, 2); - i -= 2; - } + //start trimDots + for (i = 0; i < name.length; i++) { + part = name[i]; + if (part === '.') { + name.splice(i, 1); + i -= 1; + } else if (part === '..') { + // If at the start, or previous value is still .., + // keep them so that when converted to a path it may + // still work when converted to a path, even though + // as an ID it is less than ideal. In larger point + // releases, may be better to just kick out an error. + if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') { + continue; + } else if (i > 0) { + name.splice(i - 1, 2); + i -= 2; } } - //end trimDots - - name = name.join("/"); - } else if (name.indexOf('./') === 0) { - // No baseName, so this is ID is resolved relative - // to baseUrl, pull off the leading dot. - name = name.substring(2); } + //end trimDots + + name = name.join('/'); } //Apply map config if available. @@ -230,32 +240,39 @@ var requirejs, require, define; return [prefix, name]; } + //Creates a parts array for a relName where first part is plugin ID, + //second part is resource ID. Assumes relName has already been normalized. + function makeRelParts(relName) { + return relName ? splitPrefix(relName) : []; + } + /** * Makes a name map, normalizing the name, and using a plugin * for normalization if necessary. Grabs a ref to plugin * too, as an optimization. */ - makeMap = function (name, relName) { + makeMap = function (name, relParts) { var plugin, parts = splitPrefix(name), - prefix = parts[0]; + prefix = parts[0], + relResourceName = relParts[1]; name = parts[1]; if (prefix) { - prefix = normalize(prefix, relName); + prefix = normalize(prefix, relResourceName); plugin = callDep(prefix); } //Normalize according if (prefix) { if (plugin && plugin.normalize) { - name = plugin.normalize(name, makeNormalize(relName)); + name = plugin.normalize(name, makeNormalize(relResourceName)); } else { - name = normalize(name, relName); + name = normalize(name, relResourceName); } } else { - name = normalize(name, relName); + name = normalize(name, relResourceName); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; @@ -302,13 +319,14 @@ var requirejs, require, define; }; main = function (name, deps, callback, relName) { - var cjsModule, depName, ret, map, i, + var cjsModule, depName, ret, map, i, relParts, args = [], callbackType = typeof callback, usingExports; //Use name if no relName relName = relName || name; + relParts = makeRelParts(relName); //Call the callback to define the module, if necessary. if (callbackType === 'undefined' || callbackType === 'function') { @@ -317,7 +335,7 @@ var requirejs, require, define; //Default to [require, exports, module] if no deps deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; for (i = 0; i < deps.length; i += 1) { - map = makeMap(deps[i], relName); + map = makeMap(deps[i], relParts); depName = map.f; //Fast path CommonJS standard dependencies. @@ -373,7 +391,7 @@ var requirejs, require, define; //deps arg is the module name, and second arg (if passed) //is just the relName. //Normalize module name, if it contains . or .. - return callDep(makeMap(deps, callback).f); + return callDep(makeMap(deps, makeRelParts(callback)).f); } else if (!deps.splice) { //deps is a config object, not an array. config = deps; @@ -737,6 +755,12 @@ S2.define('select2/utils',[ }); }; + Utils.entityDecode = function (html) { + var txt = document.createElement('textarea'); + txt.innerHTML = html; + return txt.value; + } + // Append an array of jQuery nodes to a given element. Utils.appendMany = function ($element, $nodes) { // jQuery 1.7.x does not support $.fn.append() with an array @@ -754,6 +778,14 @@ S2.define('select2/utils',[ $element.append($nodes); }; + // Determine whether the browser is on a touchscreen device. + Utils.isTouchscreen = function() { + if ('undefined' === typeof Utils._isTouchscreenCache) { + Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement; + } + return Utils._isTouchscreenCache; + } + return Utils; }); @@ -773,7 +805,7 @@ S2.define('select2/results',[ Results.prototype.render = function () { var $results = $( - '' + '' ); if (this.options.get('multiple')) { @@ -796,7 +828,7 @@ S2.define('select2/results',[ this.hideLoading(); var $message = $( - '
  • ' ); @@ -858,9 +890,9 @@ S2.define('select2/results',[ Results.prototype.highlightFirstItem = function () { var $options = this.$results - .find('.select2-results__option[aria-selected]'); + .find('.select2-results__option[data-selected]'); - var $selected = $options.filter('[aria-selected=true]'); + var $selected = $options.filter('[data-selected=true]'); // Check if there are any selected options if ($selected.length > 0) { @@ -884,7 +916,7 @@ S2.define('select2/results',[ }); var $options = self.$results - .find('.select2-results__option[aria-selected]'); + .find('.select2-results__option[data-selected]'); $options.each(function () { var $option = $(this); @@ -896,9 +928,9 @@ S2.define('select2/results',[ if ((item.element != null && item.element.selected) || (item.element == null && $.inArray(id, selectedIds) > -1)) { - $option.attr('aria-selected', 'true'); + $option.attr('data-selected', 'true'); } else { - $option.attr('aria-selected', 'false'); + $option.attr('data-selected', 'false'); } }); @@ -930,17 +962,18 @@ S2.define('select2/results',[ option.className = 'select2-results__option'; var attrs = { - 'role': 'treeitem', - 'aria-selected': 'false' + 'role': 'option', + 'data-selected': 'false', + 'tabindex': -1 }; if (data.disabled) { - delete attrs['aria-selected']; + delete attrs['data-selected']; attrs['aria-disabled'] = 'true'; } if (data.id == null) { - delete attrs['aria-selected']; + delete attrs['data-selected']; } if (data._resultId != null) { @@ -952,9 +985,8 @@ S2.define('select2/results',[ } if (data.children) { - attrs.role = 'group'; attrs['aria-label'] = data.text; - delete attrs['aria-selected']; + delete attrs['data-selected']; } for (var attr in attrs) { @@ -971,6 +1003,7 @@ S2.define('select2/results',[ var $label = $(label); this.template(data, label); + $label.attr('role', 'presentation'); var $children = []; @@ -983,10 +1016,11 @@ S2.define('select2/results',[ } var $childrenContainer = $('', { - 'class': 'select2-results__options select2-results__options--nested' + 'class': 'select2-results__options select2-results__options--nested', + 'role': 'listbox' }); - $childrenContainer.append($children); + $option.attr('role', 'list'); $option.append(label); $option.append($childrenContainer); @@ -1082,7 +1116,7 @@ S2.define('select2/results',[ var data = $highlighted.data('data'); - if ($highlighted.attr('aria-selected') == 'true') { + if ($highlighted.attr('data-selected') == 'true') { self.trigger('close', {}); } else { self.trigger('select', { @@ -1094,7 +1128,7 @@ S2.define('select2/results',[ container.on('results:previous', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[aria-selected]'); + var $options = self.$results.find('[data-selected]'); var currentIndex = $options.index($highlighted); @@ -1128,7 +1162,7 @@ S2.define('select2/results',[ container.on('results:next', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[aria-selected]'); + var $options = self.$results.find('[data-selected]'); var currentIndex = $options.index($highlighted); @@ -1156,7 +1190,8 @@ S2.define('select2/results',[ }); container.on('results:focus', function (params) { - params.element.addClass('select2-results__option--highlighted'); + params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true'); + self.$results.attr('aria-activedescendant', params.element.attr('id')); }); container.on('results:message', function (params) { @@ -1188,13 +1223,13 @@ S2.define('select2/results',[ }); } - this.$results.on('mouseup', '.select2-results__option[aria-selected]', + this.$results.on('mouseup', '.select2-results__option[data-selected]', function (evt) { var $this = $(this); var data = $this.data('data'); - if ($this.attr('aria-selected') === 'true') { + if ($this.attr('data-selected') === 'true') { if (self.options.get('multiple')) { self.trigger('unselect', { originalEvent: evt, @@ -1213,12 +1248,13 @@ S2.define('select2/results',[ }); }); - this.$results.on('mouseenter', '.select2-results__option[aria-selected]', + this.$results.on('mouseenter', '.select2-results__option[data-selected]', function (evt) { var data = $(this).data('data'); self.getHighlightedResults() - .removeClass('select2-results__option--highlighted'); + .removeClass('select2-results__option--highlighted') + .attr('aria-selected', 'false'); self.trigger('results:focus', { data: data, @@ -1245,7 +1281,7 @@ S2.define('select2/results',[ return; } - var $options = this.$results.find('[aria-selected]'); + var $options = this.$results.find('[data-selected]'); var currentIndex = $options.index($highlighted); @@ -1323,7 +1359,7 @@ S2.define('select2/selection/base',[ BaseSelection.prototype.render = function () { var $selection = $( - '' ); @@ -1349,6 +1385,7 @@ S2.define('select2/selection/base',[ var id = container.id + '-container'; var resultsId = container.id + '-results'; + var searchHidden = this.options.get('minimumResultsForSearch') === Infinity; this.container = container; @@ -1390,7 +1427,11 @@ S2.define('select2/selection/base',[ self.$selection.removeAttr('aria-activedescendant'); self.$selection.removeAttr('aria-owns'); - self.$selection.focus(); + // This needs to be delayed as the active element is the body when the + // key is pressed. + window.setTimeout(function () { + self.$selection.focus(); + }, 1); self._detachCloseHandler(container); }); @@ -1440,8 +1481,14 @@ S2.define('select2/selection/base',[ } var $element = $this.data('element'); - $element.select2('close'); + + // Remove any focus when dropdown is closed by clicking outside the select area. + // Timeout of 1 required for close to finish wrapping up. + setTimeout(function(){ + $this.find('*:focus').blur(); + $target.focus(); + }, 1); }); }); }; @@ -1500,8 +1547,21 @@ S2.define('select2/selection/single',[ var id = container.id + '-container'; - this.$selection.find('.select2-selection__rendered').attr('id', id); - this.$selection.attr('aria-labelledby', id); + this.$selection.find('.select2-selection__rendered') + .attr('id', id) + .attr('role', 'textbox') + .attr('aria-readonly', 'true'); + + var label = this.options.get( 'label' ); + + if ( typeof( label ) === 'string' ) { + this.$selection.attr( 'aria-label', label ); + } else { + this.$selection.attr( 'aria-labelledby', id ); + } + + // This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove. + this.$selection.attr('role', 'combobox'); this.$selection.on('mousedown', function (evt) { // Only respond to left clicks @@ -1518,6 +1578,13 @@ S2.define('select2/selection/single',[ // User focuses on the container }); + this.$selection.on('keydown', function (evt) { + // If user starts typing an alphanumeric key on the keyboard, open if not opened. + if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) { + container.open(); + } + }); + this.$selection.on('blur', function (evt) { // User exits the container }); @@ -1557,9 +1624,9 @@ S2.define('select2/selection/single',[ var selection = data[0]; var $rendered = this.$selection.find('.select2-selection__rendered'); - var formatted = this.display(selection, $rendered); + var formatted = Utils.entityDecode(this.display(selection, $rendered)); - $rendered.empty().append(formatted); + $rendered.empty().text(formatted); $rendered.prop('title', selection.title || selection.text); }; @@ -1583,7 +1650,7 @@ S2.define('select2/selection/multiple',[ $selection.addClass('select2-selection--multiple'); $selection.html( - '' + '' ); return $selection; @@ -1620,6 +1687,18 @@ S2.define('select2/selection/multiple',[ }); } ); + + this.$selection.on('keydown', function (evt) { + // If user starts typing an alphanumeric key on the keyboard, open if not opened. + if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) { + container.open(); + } + }); + + // Focus on the search field when the container is focused instead of the main container. + container.on( 'focus', function(){ + self.focusOnSearch(); + }); }; MultipleSelection.prototype.clear = function () { @@ -1636,7 +1715,7 @@ S2.define('select2/selection/multiple',[ MultipleSelection.prototype.selectionContainer = function () { var $container = $( '
  • ' + - '' + + '' + '
  • ' @@ -1645,6 +1724,24 @@ S2.define('select2/selection/multiple',[ return $container; }; + /** + * Focus on the search field instead of the main multiselect container. + */ + MultipleSelection.prototype.focusOnSearch = function() { + var self = this; + + if ('undefined' !== typeof self.$search) { + // Needs 1 ms delay because of other 1 ms setTimeouts when rendering. + setTimeout(function(){ + // Prevent the dropdown opening again when focused from this. + // This gets reset automatically when focus is triggered. + self._keyUpPrevented = true; + + self.$search.focus(); + }, 1); + } + } + MultipleSelection.prototype.update = function (data) { this.clear(); @@ -1658,9 +1755,14 @@ S2.define('select2/selection/multiple',[ var selection = data[d]; var $selection = this.selectionContainer(); + var removeItemTag = $selection.html(); var formatted = this.display(selection, $selection); + if ('string' === typeof formatted) { + formatted = Utils.entityDecode(formatted.trim()); + } - $selection.append(formatted); + $selection.text(formatted); + $selection.prepend(removeItemTag); $selection.prop('title', selection.title || selection.text); $selection.data('data', selection); @@ -1699,7 +1801,7 @@ S2.define('select2/selection/placeholder',[ Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { var $placeholder = this.selectionContainer(); - $placeholder.html(this.display(placeholder)); + $placeholder.text(Utils.entityDecode(this.display(placeholder))); $placeholder.addClass('select2-selection__placeholder') .removeClass('select2-selection__choice'); @@ -1836,8 +1938,8 @@ S2.define('select2/selection/search',[ Search.prototype.render = function (decorated) { var $search = $( '' ); @@ -1854,16 +1956,19 @@ S2.define('select2/selection/search',[ Search.prototype.bind = function (decorated, container, $container) { var self = this; + var resultsId = container.id + '-results'; decorated.call(this, container, $container); container.on('open', function () { + self.$search.attr('aria-owns', resultsId); self.$search.trigger('focus'); }); container.on('close', function () { self.$search.val(''); self.$search.removeAttr('aria-activedescendant'); + self.$search.removeAttr('aria-owns'); self.$search.trigger('focus'); }); @@ -1882,7 +1987,7 @@ S2.define('select2/selection/search',[ }); container.on('results:focus', function (params) { - self.$search.attr('aria-activedescendant', params.id); + self.$search.attr('aria-activedescendant', params.data._resultId); }); this.$selection.on('focusin', '.select2-search--inline', function (evt) { @@ -1913,6 +2018,9 @@ S2.define('select2/selection/search',[ evt.preventDefault(); } + } else if (evt.which === KEYS.ENTER) { + container.open(); + evt.preventDefault(); } }); @@ -3004,8 +3112,15 @@ S2.define('select2/data/base',[ }; BaseAdapter.prototype.generateResultId = function (container, data) { - var id = container.id + '-result-'; + var id = ''; + if (container != null) { + id += container.id + } else { + id += Utils.generateChars(4); + } + + id += '-result-'; id += Utils.generateChars(4); if (data.id != null) { @@ -3191,7 +3306,7 @@ S2.define('select2/data/select',[ } } - if (data.id) { + if (data.id !== undefined) { option.value = data.id; } @@ -3289,7 +3404,7 @@ S2.define('select2/data/select',[ item.text = item.text.toString(); } - if (item._resultId == null && item.id && this.container != null) { + if (item._resultId == null && item.id) { item._resultId = this.generateResultId(this.container, item); } @@ -3466,6 +3581,7 @@ S2.define('select2/data/ajax',[ } callback(results); + self.container.focusOnActiveElement(); }, function () { // Attempt to detect if a request was aborted // Only works if the transport exposes a status property @@ -3550,7 +3666,10 @@ S2.define('select2/data/tags',[ }, true) ); - var checkText = option.text === params.term; + var optionText = (option.text || '').toUpperCase(); + var paramsTerm = (params.term || '').toUpperCase(); + + var checkText = optionText === paramsTerm; if (checkText || checkChildren) { if (child) { @@ -3887,9 +4006,9 @@ S2.define('select2/dropdown/search',[ var $search = $( '' + - '' + + '' + '' ); @@ -3903,6 +4022,7 @@ S2.define('select2/dropdown/search',[ Search.prototype.bind = function (decorated, container, $container) { var self = this; + var resultsId = container.id + '-results'; decorated.call(this, container, $container); @@ -3926,7 +4046,7 @@ S2.define('select2/dropdown/search',[ container.on('open', function () { self.$search.attr('tabindex', 0); - + self.$search.attr('aria-owns', resultsId); self.$search.focus(); window.setTimeout(function () { @@ -3936,12 +4056,13 @@ S2.define('select2/dropdown/search',[ container.on('close', function () { self.$search.attr('tabindex', -1); - + self.$search.removeAttr('aria-activedescendant'); + self.$search.removeAttr('aria-owns'); self.$search.val(''); }); container.on('focus', function () { - if (container.isOpen()) { + if (!container.isOpen()) { self.$search.focus(); } }); @@ -3957,6 +4078,10 @@ S2.define('select2/dropdown/search',[ } } }); + + container.on('results:focus', function (params) { + self.$search.attr('aria-activedescendant', params.data._resultId); + }); }; Search.prototype.handleSearch = function (evt) { @@ -4098,7 +4223,7 @@ S2.define('select2/dropdown/infiniteScroll',[ var $option = $( '
  • ' + 'role="option" aria-disabled="true">' ); var message = this.options.get('translations').get('loadingMore'); @@ -5344,16 +5469,22 @@ S2.define('select2/core',[ }); }); - this.on('keypress', function (evt) { - var key = evt.which; + this.on('open', function(){ + // Focus on the active element when opening dropdown. + // Needs 1 ms delay because of other 1 ms setTimeouts when rendering. + setTimeout(function(){ + self.focusOnActiveElement(); + }, 1); + }); + $(document).on('keydown', function (evt) { + var key = evt.which; if (self.isOpen()) { - if (key === KEYS.ESC || key === KEYS.TAB || - (key === KEYS.UP && evt.altKey)) { + if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) { self.close(); evt.preventDefault(); - } else if (key === KEYS.ENTER) { + } else if (key === KEYS.ENTER || key === KEYS.TAB) { self.trigger('results:select', {}); evt.preventDefault(); @@ -5370,17 +5501,42 @@ S2.define('select2/core',[ evt.preventDefault(); } - } else { - if (key === KEYS.ENTER || key === KEYS.SPACE || - (key === KEYS.DOWN && evt.altKey)) { - self.open(); + var $searchField = self.$dropdown.find('.select2-search__field'); + if (! $searchField.length) { + $searchField = self.$container.find('.select2-search__field'); + } + + // Move the focus to the selected element on keyboard navigation. + // Required for screen readers to work properly. + if (key === KEYS.DOWN || key === KEYS.UP) { + self.focusOnActiveElement(); + } else { + // Focus on the search if user starts typing. + $searchField.focus(); + // Focus back to active selection when finished typing. + // Small delay so typed character can be read by screen reader. + setTimeout(function(){ + self.focusOnActiveElement(); + }, 1000); + } + } else if (self.hasFocus()) { + if (key === KEYS.ENTER || key === KEYS.SPACE || + key === KEYS.DOWN) { + self.open(); evt.preventDefault(); } } }); }; + Select2.prototype.focusOnActiveElement = function () { + // Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards. + if (this.isOpen() && ! Utils.isTouchscreen()) { + this.$results.find('li.select2-results__option--highlighted').focus(); + } + }; + Select2.prototype._syncAttributes = function () { this.options.set('disabled', this.$element.prop('disabled')); @@ -6364,11 +6520,11 @@ S2.define('jquery.select2',[ './select2/core', './select2/defaults' ], function ($, _, Select2, Defaults) { - if ($.fn.select2 == null) { + if ($.fn.selectWoo == null) { // All methods that should return the element var thisMethods = ['open', 'close', 'destroy']; - $.fn.select2 = function (options) { + $.fn.selectWoo = function (options) { options = options || {}; if (typeof options === 'object') { @@ -6408,10 +6564,17 @@ S2.define('jquery.select2',[ }; } - if ($.fn.select2.defaults == null) { - $.fn.select2.defaults = Defaults; + if ($.fn.select2 != null && $.fn.select2.defaults != null) { + $.fn.selectWoo.defaults = $.fn.select2.defaults; } + if ($.fn.selectWoo.defaults == null) { + $.fn.selectWoo.defaults = Defaults; + } + + // Also register selectWoo under select2 if select2 is not already present. + $.fn.select2 = $.fn.select2 || $.fn.selectWoo; + return Select2; }); @@ -6430,6 +6593,7 @@ S2.define('jquery.select2',[ // This allows Select2 to use the internal loader outside of this file, such // as in the language files. jQuery.fn.select2.amd = S2; + jQuery.fn.selectWoo.amd = S2; // Return the Select2 instance for anyone who is importing it. return select2; diff --git a/assets/js/select2/select2.js b/assets/js/select2/select2.js index 13b84fadff6..3f27bbd4032 100644 --- a/assets/js/select2/select2.js +++ b/assets/js/select2/select2.js @@ -1,27 +1,41 @@ /*! - * Select2 4.0.3 - * https://select2.github.io + * SelectWoo 1.0.9 + * https://github.com/woocommerce/selectWoo * * Released under the MIT license - * https://github.com/select2/select2/blob/master/LICENSE.md + * https://github.com/woocommerce/selectWoo/blob/master/LICENSE.md */ (function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); - } else if (typeof exports === 'object') { + } else if (typeof module === 'object' && module.exports) { // Node/CommonJS - factory(require('jquery')); + module.exports = function (root, jQuery) { + if (jQuery === undefined) { + // require('jQuery') returns a factory that requires window to + // build a jQuery instance, we normalize how we use modules + // that require this pattern but the window provided is a noop + // if it's defined (how jquery works) + if (typeof window !== 'undefined') { + jQuery = require('jquery'); + } + else { + jQuery = require('jquery')(root); + } + } + factory(jQuery); + return jQuery; + }; } else { // Browser globals factory(jQuery); } -}(function (jQuery) { +} (function (jQuery) { // This is needed so we can catch the AMD loader configuration and use it // The inner file should be wrapped (by `banner.start.js`) in a function that // returns the AMD loader references. - var S2 = -(function () { + var S2 =(function () { // Restore the Select2 AMD loader so it can be used // Needed mostly in the language files, where the loader is not inserted if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { @@ -30,13 +44,11 @@ var S2;(function () { if (!S2 || !S2.requirejs) { if (!S2) { S2 = {}; } else { require = S2; } /** - * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/jrburke/almond for details + * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. + * Released under MIT license, http://github.com/requirejs/almond/LICENSE */ //Going sloppy to avoid 'use strict' string cost, but strict practices should //be followed. -/*jslint sloppy: true */ /*global setTimeout: false */ var requirejs, require, define; @@ -64,60 +76,58 @@ var requirejs, require, define; */ function normalize(name, baseName) { var nameParts, nameSegment, mapValue, foundMap, lastIndex, - foundI, foundStarMap, starI, i, j, part, + foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, baseParts = baseName && baseName.split("/"), map = config.map, starMap = (map && map['*']) || {}; //Adjust any relative paths. - if (name && name.charAt(0) === ".") { - //If have a base name, try to normalize against it, - //otherwise, assume it is a top-level require that will - //be relative to baseUrl in the end. - if (baseName) { - name = name.split('/'); - lastIndex = name.length - 1; + if (name) { + name = name.split('/'); + lastIndex = name.length - 1; - // Node .js allowance: - if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { - name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); - } + // If wanting node ID compatibility, strip .js from end + // of IDs. Have to do this here, and not in nameToUrl + // because node allows either .js or non .js to map + // to same file. + if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { + name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); + } - //Lop off the last part of baseParts, so that . matches the - //"directory" and not name of the baseName's module. For instance, - //baseName of "one/two/three", maps to "one/two/three.js", but we - //want the directory, "one/two" for this normalization. - name = baseParts.slice(0, baseParts.length - 1).concat(name); + // Starts with a '.' so need the baseName + if (name[0].charAt(0) === '.' && baseParts) { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + name = normalizedBaseParts.concat(name); + } - //start trimDots - for (i = 0; i < name.length; i += 1) { - part = name[i]; - if (part === ".") { - name.splice(i, 1); - i -= 1; - } else if (part === "..") { - if (i === 1 && (name[2] === '..' || name[0] === '..')) { - //End of the line. Keep at least one non-dot - //path segment at the front so it can be mapped - //correctly to disk. Otherwise, there is likely - //no path mapping for a path starting with '..'. - //This can still fail, but catches the most reasonable - //uses of .. - break; - } else if (i > 0) { - name.splice(i - 1, 2); - i -= 2; - } + //start trimDots + for (i = 0; i < name.length; i++) { + part = name[i]; + if (part === '.') { + name.splice(i, 1); + i -= 1; + } else if (part === '..') { + // If at the start, or previous value is still .., + // keep them so that when converted to a path it may + // still work when converted to a path, even though + // as an ID it is less than ideal. In larger point + // releases, may be better to just kick out an error. + if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') { + continue; + } else if (i > 0) { + name.splice(i - 1, 2); + i -= 2; } } - //end trimDots - - name = name.join("/"); - } else if (name.indexOf('./') === 0) { - // No baseName, so this is ID is resolved relative - // to baseUrl, pull off the leading dot. - name = name.substring(2); } + //end trimDots + + name = name.join('/'); } //Apply map config if available. @@ -230,32 +240,39 @@ var requirejs, require, define; return [prefix, name]; } + //Creates a parts array for a relName where first part is plugin ID, + //second part is resource ID. Assumes relName has already been normalized. + function makeRelParts(relName) { + return relName ? splitPrefix(relName) : []; + } + /** * Makes a name map, normalizing the name, and using a plugin * for normalization if necessary. Grabs a ref to plugin * too, as an optimization. */ - makeMap = function (name, relName) { + makeMap = function (name, relParts) { var plugin, parts = splitPrefix(name), - prefix = parts[0]; + prefix = parts[0], + relResourceName = relParts[1]; name = parts[1]; if (prefix) { - prefix = normalize(prefix, relName); + prefix = normalize(prefix, relResourceName); plugin = callDep(prefix); } //Normalize according if (prefix) { if (plugin && plugin.normalize) { - name = plugin.normalize(name, makeNormalize(relName)); + name = plugin.normalize(name, makeNormalize(relResourceName)); } else { - name = normalize(name, relName); + name = normalize(name, relResourceName); } } else { - name = normalize(name, relName); + name = normalize(name, relResourceName); parts = splitPrefix(name); prefix = parts[0]; name = parts[1]; @@ -302,13 +319,14 @@ var requirejs, require, define; }; main = function (name, deps, callback, relName) { - var cjsModule, depName, ret, map, i, + var cjsModule, depName, ret, map, i, relParts, args = [], callbackType = typeof callback, usingExports; //Use name if no relName relName = relName || name; + relParts = makeRelParts(relName); //Call the callback to define the module, if necessary. if (callbackType === 'undefined' || callbackType === 'function') { @@ -317,7 +335,7 @@ var requirejs, require, define; //Default to [require, exports, module] if no deps deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; for (i = 0; i < deps.length; i += 1) { - map = makeMap(deps[i], relName); + map = makeMap(deps[i], relParts); depName = map.f; //Fast path CommonJS standard dependencies. @@ -373,7 +391,7 @@ var requirejs, require, define; //deps arg is the module name, and second arg (if passed) //is just the relName. //Normalize module name, if it contains . or .. - return callDep(makeMap(deps, callback).f); + return callDep(makeMap(deps, makeRelParts(callback)).f); } else if (!deps.splice) { //deps is a config object, not an array. config = deps; @@ -737,6 +755,12 @@ S2.define('select2/utils',[ }); }; + Utils.entityDecode = function (html) { + var txt = document.createElement('textarea'); + txt.innerHTML = html; + return txt.value; + } + // Append an array of jQuery nodes to a given element. Utils.appendMany = function ($element, $nodes) { // jQuery 1.7.x does not support $.fn.append() with an array @@ -754,6 +778,14 @@ S2.define('select2/utils',[ $element.append($nodes); }; + // Determine whether the browser is on a touchscreen device. + Utils.isTouchscreen = function() { + if ('undefined' === typeof Utils._isTouchscreenCache) { + Utils._isTouchscreenCache = 'ontouchstart' in document.documentElement; + } + return Utils._isTouchscreenCache; + } + return Utils; }); @@ -773,7 +805,7 @@ S2.define('select2/results',[ Results.prototype.render = function () { var $results = $( - '' + '' ); if (this.options.get('multiple')) { @@ -796,7 +828,7 @@ S2.define('select2/results',[ this.hideLoading(); var $message = $( - '
  • ' ); @@ -858,9 +890,9 @@ S2.define('select2/results',[ Results.prototype.highlightFirstItem = function () { var $options = this.$results - .find('.select2-results__option[aria-selected]'); + .find('.select2-results__option[data-selected]'); - var $selected = $options.filter('[aria-selected=true]'); + var $selected = $options.filter('[data-selected=true]'); // Check if there are any selected options if ($selected.length > 0) { @@ -884,7 +916,7 @@ S2.define('select2/results',[ }); var $options = self.$results - .find('.select2-results__option[aria-selected]'); + .find('.select2-results__option[data-selected]'); $options.each(function () { var $option = $(this); @@ -896,9 +928,9 @@ S2.define('select2/results',[ if ((item.element != null && item.element.selected) || (item.element == null && $.inArray(id, selectedIds) > -1)) { - $option.attr('aria-selected', 'true'); + $option.attr('data-selected', 'true'); } else { - $option.attr('aria-selected', 'false'); + $option.attr('data-selected', 'false'); } }); @@ -930,17 +962,18 @@ S2.define('select2/results',[ option.className = 'select2-results__option'; var attrs = { - 'role': 'treeitem', - 'aria-selected': 'false' + 'role': 'option', + 'data-selected': 'false', + 'tabindex': -1 }; if (data.disabled) { - delete attrs['aria-selected']; + delete attrs['data-selected']; attrs['aria-disabled'] = 'true'; } if (data.id == null) { - delete attrs['aria-selected']; + delete attrs['data-selected']; } if (data._resultId != null) { @@ -952,9 +985,8 @@ S2.define('select2/results',[ } if (data.children) { - attrs.role = 'group'; attrs['aria-label'] = data.text; - delete attrs['aria-selected']; + delete attrs['data-selected']; } for (var attr in attrs) { @@ -971,6 +1003,7 @@ S2.define('select2/results',[ var $label = $(label); this.template(data, label); + $label.attr('role', 'presentation'); var $children = []; @@ -983,10 +1016,11 @@ S2.define('select2/results',[ } var $childrenContainer = $('', { - 'class': 'select2-results__options select2-results__options--nested' + 'class': 'select2-results__options select2-results__options--nested', + 'role': 'listbox' }); - $childrenContainer.append($children); + $option.attr('role', 'list'); $option.append(label); $option.append($childrenContainer); @@ -1082,7 +1116,7 @@ S2.define('select2/results',[ var data = $highlighted.data('data'); - if ($highlighted.attr('aria-selected') == 'true') { + if ($highlighted.attr('data-selected') == 'true') { self.trigger('close', {}); } else { self.trigger('select', { @@ -1094,7 +1128,7 @@ S2.define('select2/results',[ container.on('results:previous', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[aria-selected]'); + var $options = self.$results.find('[data-selected]'); var currentIndex = $options.index($highlighted); @@ -1128,7 +1162,7 @@ S2.define('select2/results',[ container.on('results:next', function () { var $highlighted = self.getHighlightedResults(); - var $options = self.$results.find('[aria-selected]'); + var $options = self.$results.find('[data-selected]'); var currentIndex = $options.index($highlighted); @@ -1156,7 +1190,8 @@ S2.define('select2/results',[ }); container.on('results:focus', function (params) { - params.element.addClass('select2-results__option--highlighted'); + params.element.addClass('select2-results__option--highlighted').attr('aria-selected', 'true'); + self.$results.attr('aria-activedescendant', params.element.attr('id')); }); container.on('results:message', function (params) { @@ -1188,13 +1223,13 @@ S2.define('select2/results',[ }); } - this.$results.on('mouseup', '.select2-results__option[aria-selected]', + this.$results.on('mouseup', '.select2-results__option[data-selected]', function (evt) { var $this = $(this); var data = $this.data('data'); - if ($this.attr('aria-selected') === 'true') { + if ($this.attr('data-selected') === 'true') { if (self.options.get('multiple')) { self.trigger('unselect', { originalEvent: evt, @@ -1213,12 +1248,13 @@ S2.define('select2/results',[ }); }); - this.$results.on('mouseenter', '.select2-results__option[aria-selected]', + this.$results.on('mouseenter', '.select2-results__option[data-selected]', function (evt) { var data = $(this).data('data'); self.getHighlightedResults() - .removeClass('select2-results__option--highlighted'); + .removeClass('select2-results__option--highlighted') + .attr('aria-selected', 'false'); self.trigger('results:focus', { data: data, @@ -1245,7 +1281,7 @@ S2.define('select2/results',[ return; } - var $options = this.$results.find('[aria-selected]'); + var $options = this.$results.find('[data-selected]'); var currentIndex = $options.index($highlighted); @@ -1323,7 +1359,7 @@ S2.define('select2/selection/base',[ BaseSelection.prototype.render = function () { var $selection = $( - '' ); @@ -1349,6 +1385,7 @@ S2.define('select2/selection/base',[ var id = container.id + '-container'; var resultsId = container.id + '-results'; + var searchHidden = this.options.get('minimumResultsForSearch') === Infinity; this.container = container; @@ -1390,7 +1427,11 @@ S2.define('select2/selection/base',[ self.$selection.removeAttr('aria-activedescendant'); self.$selection.removeAttr('aria-owns'); - self.$selection.focus(); + // This needs to be delayed as the active element is the body when the + // key is pressed. + window.setTimeout(function () { + self.$selection.focus(); + }, 1); self._detachCloseHandler(container); }); @@ -1440,8 +1481,14 @@ S2.define('select2/selection/base',[ } var $element = $this.data('element'); - $element.select2('close'); + + // Remove any focus when dropdown is closed by clicking outside the select area. + // Timeout of 1 required for close to finish wrapping up. + setTimeout(function(){ + $this.find('*:focus').blur(); + $target.focus(); + }, 1); }); }); }; @@ -1500,8 +1547,21 @@ S2.define('select2/selection/single',[ var id = container.id + '-container'; - this.$selection.find('.select2-selection__rendered').attr('id', id); - this.$selection.attr('aria-labelledby', id); + this.$selection.find('.select2-selection__rendered') + .attr('id', id) + .attr('role', 'textbox') + .attr('aria-readonly', 'true'); + + var label = this.options.get( 'label' ); + + if ( typeof( label ) === 'string' ) { + this.$selection.attr( 'aria-label', label ); + } else { + this.$selection.attr( 'aria-labelledby', id ); + } + + // This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove. + this.$selection.attr('role', 'combobox'); this.$selection.on('mousedown', function (evt) { // Only respond to left clicks @@ -1518,6 +1578,13 @@ S2.define('select2/selection/single',[ // User focuses on the container }); + this.$selection.on('keydown', function (evt) { + // If user starts typing an alphanumeric key on the keyboard, open if not opened. + if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) { + container.open(); + } + }); + this.$selection.on('blur', function (evt) { // User exits the container }); @@ -1557,9 +1624,9 @@ S2.define('select2/selection/single',[ var selection = data[0]; var $rendered = this.$selection.find('.select2-selection__rendered'); - var formatted = this.display(selection, $rendered); + var formatted = Utils.entityDecode(this.display(selection, $rendered)); - $rendered.empty().append(formatted); + $rendered.empty().text(formatted); $rendered.prop('title', selection.title || selection.text); }; @@ -1583,7 +1650,7 @@ S2.define('select2/selection/multiple',[ $selection.addClass('select2-selection--multiple'); $selection.html( - '' + '' ); return $selection; @@ -1620,6 +1687,18 @@ S2.define('select2/selection/multiple',[ }); } ); + + this.$selection.on('keydown', function (evt) { + // If user starts typing an alphanumeric key on the keyboard, open if not opened. + if (!container.isOpen() && evt.which >= 48 && evt.which <= 90) { + container.open(); + } + }); + + // Focus on the search field when the container is focused instead of the main container. + container.on( 'focus', function(){ + self.focusOnSearch(); + }); }; MultipleSelection.prototype.clear = function () { @@ -1636,7 +1715,7 @@ S2.define('select2/selection/multiple',[ MultipleSelection.prototype.selectionContainer = function () { var $container = $( '
  • ' + - '' + + '' + '
  • ' @@ -1645,6 +1724,24 @@ S2.define('select2/selection/multiple',[ return $container; }; + /** + * Focus on the search field instead of the main multiselect container. + */ + MultipleSelection.prototype.focusOnSearch = function() { + var self = this; + + if ('undefined' !== typeof self.$search) { + // Needs 1 ms delay because of other 1 ms setTimeouts when rendering. + setTimeout(function(){ + // Prevent the dropdown opening again when focused from this. + // This gets reset automatically when focus is triggered. + self._keyUpPrevented = true; + + self.$search.focus(); + }, 1); + } + } + MultipleSelection.prototype.update = function (data) { this.clear(); @@ -1658,9 +1755,14 @@ S2.define('select2/selection/multiple',[ var selection = data[d]; var $selection = this.selectionContainer(); + var removeItemTag = $selection.html(); var formatted = this.display(selection, $selection); + if ('string' === typeof formatted) { + formatted = Utils.entityDecode(formatted.trim()); + } - $selection.append(formatted); + $selection.text(formatted); + $selection.prepend(removeItemTag); $selection.prop('title', selection.title || selection.text); $selection.data('data', selection); @@ -1699,7 +1801,7 @@ S2.define('select2/selection/placeholder',[ Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { var $placeholder = this.selectionContainer(); - $placeholder.html(this.display(placeholder)); + $placeholder.text(Utils.entityDecode(this.display(placeholder))); $placeholder.addClass('select2-selection__placeholder') .removeClass('select2-selection__choice'); @@ -1836,8 +1938,8 @@ S2.define('select2/selection/search',[ Search.prototype.render = function (decorated) { var $search = $( '' ); @@ -1854,16 +1956,19 @@ S2.define('select2/selection/search',[ Search.prototype.bind = function (decorated, container, $container) { var self = this; + var resultsId = container.id + '-results'; decorated.call(this, container, $container); container.on('open', function () { + self.$search.attr('aria-owns', resultsId); self.$search.trigger('focus'); }); container.on('close', function () { self.$search.val(''); self.$search.removeAttr('aria-activedescendant'); + self.$search.removeAttr('aria-owns'); self.$search.trigger('focus'); }); @@ -1882,7 +1987,7 @@ S2.define('select2/selection/search',[ }); container.on('results:focus', function (params) { - self.$search.attr('aria-activedescendant', params.id); + self.$search.attr('aria-activedescendant', params.data._resultId); }); this.$selection.on('focusin', '.select2-search--inline', function (evt) { @@ -1913,6 +2018,9 @@ S2.define('select2/selection/search',[ evt.preventDefault(); } + } else if (evt.which === KEYS.ENTER) { + container.open(); + evt.preventDefault(); } }); @@ -3004,8 +3112,15 @@ S2.define('select2/data/base',[ }; BaseAdapter.prototype.generateResultId = function (container, data) { - var id = container.id + '-result-'; + var id = ''; + if (container != null) { + id += container.id + } else { + id += Utils.generateChars(4); + } + + id += '-result-'; id += Utils.generateChars(4); if (data.id != null) { @@ -3191,7 +3306,7 @@ S2.define('select2/data/select',[ } } - if (data.id) { + if (data.id !== undefined) { option.value = data.id; } @@ -3289,7 +3404,7 @@ S2.define('select2/data/select',[ item.text = item.text.toString(); } - if (item._resultId == null && item.id && this.container != null) { + if (item._resultId == null && item.id) { item._resultId = this.generateResultId(this.container, item); } @@ -3466,6 +3581,7 @@ S2.define('select2/data/ajax',[ } callback(results); + self.container.focusOnActiveElement(); }, function () { // Attempt to detect if a request was aborted // Only works if the transport exposes a status property @@ -3550,7 +3666,10 @@ S2.define('select2/data/tags',[ }, true) ); - var checkText = option.text === params.term; + var optionText = (option.text || '').toUpperCase(); + var paramsTerm = (params.term || '').toUpperCase(); + + var checkText = optionText === paramsTerm; if (checkText || checkChildren) { if (child) { @@ -3887,9 +4006,9 @@ S2.define('select2/dropdown/search',[ var $search = $( '' + - '' + + '' + '' ); @@ -3903,6 +4022,7 @@ S2.define('select2/dropdown/search',[ Search.prototype.bind = function (decorated, container, $container) { var self = this; + var resultsId = container.id + '-results'; decorated.call(this, container, $container); @@ -3926,7 +4046,7 @@ S2.define('select2/dropdown/search',[ container.on('open', function () { self.$search.attr('tabindex', 0); - + self.$search.attr('aria-owns', resultsId); self.$search.focus(); window.setTimeout(function () { @@ -3936,12 +4056,13 @@ S2.define('select2/dropdown/search',[ container.on('close', function () { self.$search.attr('tabindex', -1); - + self.$search.removeAttr('aria-activedescendant'); + self.$search.removeAttr('aria-owns'); self.$search.val(''); }); container.on('focus', function () { - if (container.isOpen()) { + if (!container.isOpen()) { self.$search.focus(); } }); @@ -3957,6 +4078,10 @@ S2.define('select2/dropdown/search',[ } } }); + + container.on('results:focus', function (params) { + self.$search.attr('aria-activedescendant', params.data._resultId); + }); }; Search.prototype.handleSearch = function (evt) { @@ -4098,7 +4223,7 @@ S2.define('select2/dropdown/infiniteScroll',[ var $option = $( '
  • ' + 'role="option" aria-disabled="true">' ); var message = this.options.get('translations').get('loadingMore'); @@ -5344,16 +5469,22 @@ S2.define('select2/core',[ }); }); - this.on('keypress', function (evt) { - var key = evt.which; + this.on('open', function(){ + // Focus on the active element when opening dropdown. + // Needs 1 ms delay because of other 1 ms setTimeouts when rendering. + setTimeout(function(){ + self.focusOnActiveElement(); + }, 1); + }); + $(document).on('keydown', function (evt) { + var key = evt.which; if (self.isOpen()) { - if (key === KEYS.ESC || key === KEYS.TAB || - (key === KEYS.UP && evt.altKey)) { + if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) { self.close(); evt.preventDefault(); - } else if (key === KEYS.ENTER) { + } else if (key === KEYS.ENTER || key === KEYS.TAB) { self.trigger('results:select', {}); evt.preventDefault(); @@ -5370,17 +5501,42 @@ S2.define('select2/core',[ evt.preventDefault(); } - } else { - if (key === KEYS.ENTER || key === KEYS.SPACE || - (key === KEYS.DOWN && evt.altKey)) { - self.open(); + var $searchField = self.$dropdown.find('.select2-search__field'); + if (! $searchField.length) { + $searchField = self.$container.find('.select2-search__field'); + } + + // Move the focus to the selected element on keyboard navigation. + // Required for screen readers to work properly. + if (key === KEYS.DOWN || key === KEYS.UP) { + self.focusOnActiveElement(); + } else { + // Focus on the search if user starts typing. + $searchField.focus(); + // Focus back to active selection when finished typing. + // Small delay so typed character can be read by screen reader. + setTimeout(function(){ + self.focusOnActiveElement(); + }, 1000); + } + } else if (self.hasFocus()) { + if (key === KEYS.ENTER || key === KEYS.SPACE || + key === KEYS.DOWN) { + self.open(); evt.preventDefault(); } } }); }; + Select2.prototype.focusOnActiveElement = function () { + // Don't mess with the focus on touchscreens because it causes havoc with on-screen keyboards. + if (this.isOpen() && ! Utils.isTouchscreen()) { + this.$results.find('li.select2-results__option--highlighted').focus(); + } + }; + Select2.prototype._syncAttributes = function () { this.options.set('disabled', this.$element.prop('disabled')); @@ -5653,11 +5809,11 @@ S2.define('jquery.select2',[ './select2/core', './select2/defaults' ], function ($, _, Select2, Defaults) { - if ($.fn.select2 == null) { + if ($.fn.selectWoo == null) { // All methods that should return the element var thisMethods = ['open', 'close', 'destroy']; - $.fn.select2 = function (options) { + $.fn.selectWoo = function (options) { options = options || {}; if (typeof options === 'object') { @@ -5697,10 +5853,17 @@ S2.define('jquery.select2',[ }; } - if ($.fn.select2.defaults == null) { - $.fn.select2.defaults = Defaults; + if ($.fn.select2 != null && $.fn.select2.defaults != null) { + $.fn.selectWoo.defaults = $.fn.select2.defaults; } + if ($.fn.selectWoo.defaults == null) { + $.fn.selectWoo.defaults = Defaults; + } + + // Also register selectWoo under select2 if select2 is not already present. + $.fn.select2 = $.fn.select2 || $.fn.selectWoo; + return Select2; }); @@ -5719,6 +5882,7 @@ S2.define('jquery.select2',[ // This allows Select2 to use the internal loader outside of this file, such // as in the language files. jQuery.fn.select2.amd = S2; + jQuery.fn.selectWoo.amd = S2; // Return the Select2 instance for anyone who is importing it. return select2; diff --git a/assets/js/selectWoo/selectWoo.full.js b/assets/js/selectWoo/selectWoo.full.js index 23548fce727..49cb11ee692 100644 --- a/assets/js/selectWoo/selectWoo.full.js +++ b/assets/js/selectWoo/selectWoo.full.js @@ -1,5 +1,5 @@ /*! - * SelectWoo 1.0.6 + * SelectWoo 1.0.9 * https://github.com/woocommerce/selectWoo * * Released under the MIT license @@ -755,8 +755,8 @@ S2.define('select2/utils',[ }); }; - Utils.entityDecode = function(html) { - var txt = document.createElement("textarea"); + Utils.entityDecode = function (html) { + var txt = document.createElement('textarea'); txt.innerHTML = html; return txt.value; } @@ -1551,7 +1551,14 @@ S2.define('select2/selection/single',[ .attr('id', id) .attr('role', 'textbox') .attr('aria-readonly', 'true'); - this.$selection.attr('aria-labelledby', id); + + var label = this.options.get( 'label' ); + + if ( typeof( label ) === 'string' ) { + this.$selection.attr( 'aria-label', label ); + } else { + this.$selection.attr( 'aria-labelledby', id ); + } // This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove. this.$selection.attr('role', 'combobox'); diff --git a/assets/js/selectWoo/selectWoo.js b/assets/js/selectWoo/selectWoo.js index f4dc2bda2ca..3f27bbd4032 100644 --- a/assets/js/selectWoo/selectWoo.js +++ b/assets/js/selectWoo/selectWoo.js @@ -1,5 +1,5 @@ /*! - * SelectWoo 1.0.6 + * SelectWoo 1.0.9 * https://github.com/woocommerce/selectWoo * * Released under the MIT license @@ -755,8 +755,8 @@ S2.define('select2/utils',[ }); }; - Utils.entityDecode = function(html) { - var txt = document.createElement("textarea"); + Utils.entityDecode = function (html) { + var txt = document.createElement('textarea'); txt.innerHTML = html; return txt.value; } @@ -1551,7 +1551,14 @@ S2.define('select2/selection/single',[ .attr('id', id) .attr('role', 'textbox') .attr('aria-readonly', 'true'); - this.$selection.attr('aria-labelledby', id); + + var label = this.options.get( 'label' ); + + if ( typeof( label ) === 'string' ) { + this.$selection.attr( 'aria-label', label ); + } else { + this.$selection.attr( 'aria-labelledby', id ); + } // This makes single non-search selects work in screen readers. If it causes problems elsewhere, remove. this.$selection.attr('role', 'combobox'); diff --git a/bin/composer/mozart/composer.lock b/bin/composer/mozart/composer.lock index 1a3ce3075fe..6e9a52a1179 100644 --- a/bin/composer/mozart/composer.lock +++ b/bin/composer/mozart/composer.lock @@ -33,6 +33,7 @@ "squizlabs/php_codesniffer": "^3.5", "vimeo/psalm": "^4.4" }, + "default-branch": true, "bin": [ "bin/mozart" ], @@ -53,6 +54,10 @@ } ], "description": "Composes all dependencies as a package inside a WordPress plugin", + "support": { + "issues": "https://github.com/coenjacobs/mozart/issues", + "source": "https://github.com/coenjacobs/mozart/tree/master" + }, "funding": [ { "url": "https://github.com/coenjacobs", @@ -144,6 +149,10 @@ "sftp", "storage" ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.x" + }, "funding": [ { "url": "https://offset.earth/frankdejonge", @@ -192,6 +201,10 @@ } ], "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0" + }, "funding": [ { "url": "https://github.com/frankdejonge", @@ -206,27 +219,22 @@ }, { "name": "psr/container", - "version": "1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -239,7 +247,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", @@ -253,22 +261,22 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/master" + "source": "https://github.com/php-fig/container/tree/1.1.1" }, - "time": "2017-02-14T16:28:37+00:00" + "time": "2021-03-05T17:36:06+00:00" }, { "name": "symfony/console", - "version": "v5.2.3", + "version": "v5.2.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a" + "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a", - "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "url": "https://api.github.com/repos/symfony/console/zipball/35f039df40a3b335ebf310f244cb242b3a83ac8d", + "reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d", "shasum": "" }, "require": { @@ -335,6 +343,9 @@ "console", "terminal" ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.2.6" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -349,20 +360,20 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-03-28T09:42:18+00:00" }, { "name": "symfony/finder", - "version": "v5.2.3", + "version": "v5.2.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4adc8d172d602008c204c2e16956f99257248e03" + "reference": "0d639a0943822626290d169965804f79400e6a04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4adc8d172d602008c204c2e16956f99257248e03", - "reference": "4adc8d172d602008c204c2e16956f99257248e03", + "url": "https://api.github.com/repos/symfony/finder/zipball/0d639a0943822626290d169965804f79400e6a04", + "reference": "0d639a0943822626290d169965804f79400e6a04", "shasum": "" }, "require": { @@ -393,6 +404,9 @@ ], "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.2.4" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -407,11 +421,11 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-02-15T18:55:04+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -469,6 +483,9 @@ "polyfill", "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -487,16 +504,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "267a9adeb8ecb8071040a740930e077cdfb987af" + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/267a9adeb8ecb8071040a740930e077cdfb987af", - "reference": "267a9adeb8ecb8071040a740930e077cdfb987af", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", "shasum": "" }, "require": { @@ -547,6 +564,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -561,20 +581,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", "shasum": "" }, "require": { @@ -628,6 +648,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -642,20 +665,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T17:09:11+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13" + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", - "reference": "f377a3dd1fde44d37b9831d68dc8dea3ffd28e13", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", "shasum": "" }, "require": { @@ -705,6 +728,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -719,11 +745,11 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-01-22T09:19:47+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -781,6 +807,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -799,7 +828,7 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.22.0", + "version": "v1.22.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -861,6 +890,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -937,6 +969,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/master" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -955,16 +990,16 @@ }, { "name": "symfony/string", - "version": "v5.2.3", + "version": "v5.2.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "c95468897f408dd0aca2ff582074423dd0455122" + "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", - "reference": "c95468897f408dd0aca2ff582074423dd0455122", + "url": "https://api.github.com/repos/symfony/string/zipball/ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", + "reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572", "shasum": "" }, "require": { @@ -1017,6 +1052,9 @@ "utf-8", "utf8" ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.2.6" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1031,7 +1069,7 @@ "type": "tidelift" } ], - "time": "2021-01-25T15:14:59+00:00" + "time": "2021-03-17T17:12:15+00:00" } ], "aliases": [], @@ -1046,5 +1084,5 @@ "platform-overrides": { "php": "7.3" }, - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/bin/composer/phpcs/composer.lock b/bin/composer/phpcs/composer.lock index 5a70d30ff7e..d784a60a0a0 100644 --- a/bin/composer/phpcs/composer.lock +++ b/bin/composer/phpcs/composer.lock @@ -71,6 +71,10 @@ "stylecheck", "tests" ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, "time": "2020-06-25T14:57:39+00:00" }, { @@ -129,32 +133,36 @@ "phpcs", "standards" ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibility" + }, "time": "2019-12-27T09:44:58+00:00" }, { "name": "phpcompatibility/phpcompatibility-paragonie", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", - "reference": "b862bc32f7e860d0b164b199bd995e690b4b191c" + "reference": "ddabec839cc003651f2ce695c938686d1086cf43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/b862bc32f7e860d0b164b199bd995e690b4b191c", - "reference": "b862bc32f7e860d0b164b199bd995e690b4b191c", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/ddabec839cc003651f2ce695c938686d1086cf43", + "reference": "ddabec839cc003651f2ce695c938686d1086cf43", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", "paragonie/random_compat": "dev-master", "paragonie/sodium_compat": "dev-master" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -181,7 +189,11 @@ "polyfill", "standards" ], - "time": "2019-11-04T15:17:54+00:00" + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" + }, + "time": "2021-02-15T10:24:51+00:00" }, { "name": "phpcompatibility/phpcompatibility-wp", @@ -231,6 +243,10 @@ "standards", "wordpress" ], + "support": { + "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" + }, "time": "2019-08-28T14:22:28+00:00" }, { @@ -327,6 +343,10 @@ "woocommerce", "wordpress" ], + "support": { + "issues": "https://github.com/woocommerce/woocommerce-sniffs/issues", + "source": "https://github.com/woocommerce/woocommerce-sniffs/tree/master" + }, "time": "2020-08-06T18:23:45+00:00" }, { @@ -373,6 +393,11 @@ "standards", "wordpress" ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, "time": "2020-05-13T23:57:56+00:00" } ], @@ -386,5 +411,5 @@ "platform-overrides": { "php": "7.0" }, - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/bin/composer/phpunit/composer.lock b/bin/composer/phpunit/composer.lock index d1c92edd290..b32d4d77b22 100644 --- a/bin/composer/phpunit/composer.lock +++ b/bin/composer/phpunit/composer.lock @@ -332,6 +332,10 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/4.x" + }, "time": "2019-12-28T18:55:12+00:00" }, { @@ -444,6 +448,10 @@ "spy", "stub" ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + }, "time": "2020-03-05T15:02:03+00:00" }, { @@ -604,6 +612,10 @@ "keywords": [ "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, "time": "2015-06-21T13:50:34+00:00" }, { @@ -1224,6 +1236,10 @@ "keywords": [ "global state" ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0" + }, "time": "2017-04-27T15:39:26+00:00" }, { @@ -1488,6 +1504,10 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, "time": "2016-10-03T07:35:21+00:00" }, { @@ -1607,6 +1627,10 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/master" + }, "time": "2019-06-13T22:48:21+00:00" }, { @@ -1656,6 +1680,10 @@ "check", "validate" ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" + }, "time": "2020-07-08T17:02:28+00:00" } ], @@ -1669,5 +1697,5 @@ "platform-overrides": { "php": "7.0" }, - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/bin/composer/wp/composer.lock b/bin/composer/wp/composer.lock index 24fdbbf28cb..8fb33475e13 100644 --- a/bin/composer/wp/composer.lock +++ b/bin/composer/wp/composer.lock @@ -9,16 +9,16 @@ "packages-dev": [ { "name": "gettext/gettext", - "version": "v4.8.3", + "version": "v4.8.4", "source": { "type": "git", "url": "https://github.com/php-gettext/Gettext.git", - "reference": "57ff4fb16647e78e80a5909fe3c190f1c3110321" + "reference": "58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/57ff4fb16647e78e80a5909fe3c190f1c3110321", - "reference": "57ff4fb16647e78e80a5909fe3c190f1c3110321", + "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1", + "reference": "58bc0f7f37e78efb0f9758f93d4a0f669f0f84a1", "shasum": "" }, "require": { @@ -70,9 +70,23 @@ "support": { "email": "oom@oscarotero.com", "issues": "https://github.com/oscarotero/Gettext/issues", - "source": "https://github.com/php-gettext/Gettext/tree/v4.8.3" + "source": "https://github.com/php-gettext/Gettext/tree/v4.8.4" }, - "time": "2020-11-18T22:35:49+00:00" + "funding": [ + { + "url": "https://paypal.me/oscarotero", + "type": "custom" + }, + { + "url": "https://github.com/oscarotero", + "type": "github" + }, + { + "url": "https://www.patreon.com/misteroom", + "type": "patreon" + } + ], + "time": "2021-03-10T19:35:49+00:00" }, { "name": "gettext/languages", @@ -133,6 +147,10 @@ "translations", "unicode" ], + "support": { + "issues": "https://github.com/php-gettext/Languages/issues", + "source": "https://github.com/php-gettext/Languages/tree/2.6.0" + }, "time": "2019-11-13T10:30:21+00:00" }, { @@ -178,6 +196,10 @@ } ], "description": "Peast is PHP library that generates AST for JavaScript code", + "support": { + "issues": "https://github.com/mck89/peast/issues", + "source": "https://github.com/mck89/peast/tree/v1.12.0" + }, "time": "2021-01-08T15:16:19+00:00" }, { @@ -224,6 +246,10 @@ "mustache", "templating" ], + "support": { + "issues": "https://github.com/bobthecow/mustache.php/issues", + "source": "https://github.com/bobthecow/mustache.php/tree/master" + }, "time": "2019-11-23T21:40:31+00:00" }, { @@ -273,6 +299,10 @@ "iri", "sockets" ], + "support": { + "issues": "https://github.com/rmccue/Requests/issues", + "source": "https://github.com/rmccue/Requests/tree/master" + }, "time": "2016-10-13T00:11:37+00:00" }, { @@ -382,6 +412,10 @@ ], "description": "Provides internationalization tools for WordPress projects.", "homepage": "https://github.com/wp-cli/i18n-command", + "support": { + "issues": "https://github.com/wp-cli/i18n-command/issues", + "source": "https://github.com/wp-cli/i18n-command/tree/v2.2.6" + }, "time": "2020-12-07T19:28:27+00:00" }, { @@ -430,20 +464,23 @@ ], "description": "A simple YAML loader/dumper class for PHP (WP-CLI fork)", "homepage": "https://github.com/mustangostang/spyc/", + "support": { + "source": "https://github.com/wp-cli/spyc/tree/autoload" + }, "time": "2017-04-25T11:26:20+00:00" }, { "name": "wp-cli/php-cli-tools", - "version": "v0.11.11", + "version": "v0.11.12", "source": { "type": "git", "url": "https://github.com/wp-cli/php-cli-tools.git", - "reference": "fe9c7c44a9e1bf2196ec51dc38da0593dbf2993f" + "reference": "e472e08489f7504d9e8c5c5a057e1419cd1b2b3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/fe9c7c44a9e1bf2196ec51dc38da0593dbf2993f", - "reference": "fe9c7c44a9e1bf2196ec51dc38da0593dbf2993f", + "url": "https://api.github.com/repos/wp-cli/php-cli-tools/zipball/e472e08489f7504d9e8c5c5a057e1419cd1b2b3e", + "reference": "e472e08489f7504d9e8c5c5a057e1419cd1b2b3e", "shasum": "" }, "require": { @@ -463,15 +500,15 @@ "MIT" ], "authors": [ - { - "name": "James Logsdon", - "email": "jlogsdon@php.net", - "role": "Developer" - }, { "name": "Daniel Bachhuber", "email": "daniel@handbuilt.co", "role": "Maintainer" + }, + { + "name": "James Logsdon", + "email": "jlogsdon@php.net", + "role": "Developer" } ], "description": "Console utilities for PHP", @@ -480,7 +517,11 @@ "cli", "console" ], - "time": "2018-09-04T13:28:00+00:00" + "support": { + "issues": "https://github.com/wp-cli/php-cli-tools/issues", + "source": "https://github.com/wp-cli/php-cli-tools/tree/v0.11.12" + }, + "time": "2021-03-03T12:43:49+00:00" }, { "name": "wp-cli/wp-cli", @@ -542,6 +583,11 @@ "cli", "wordpress" ], + "support": { + "docs": "https://make.wordpress.org/cli/handbook/", + "issues": "https://github.com/wp-cli/wp-cli/issues", + "source": "https://github.com/wp-cli/wp-cli" + }, "time": "2020-02-18T08:15:37+00:00" } ], @@ -555,5 +601,5 @@ "platform-overrides": { "php": "7.0" }, - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.0.0" } diff --git a/changelog.txt b/changelog.txt index a0491ef9f77..4b636018665 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,276 @@ == Changelog == += 5.2.2 2021-04-15 = + +**WooCommerce** + +* Fix - Can't grant permission for download from order details page. #29691 + += 5.2.1 2021-04-14 = + +**WooCommerce** + +* Update - WooCommerce Blocks package 4.7.2. #29660 + +**WooCommerce Blocks - 4.7.2** + +* Fix - Check if Cart and Checkout are registered before removing payment methods. ([4056](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4056)) + += 5.2.0 2021-04-13 = + +**WooCommerce** + +* Add - Filter woocommerce_product_recount_terms to allow/prevent recounting of product terms. #29281 +* Add - Result return array to the checkout_place_order_success callback to allow 3rd party to manipulate results. #29232 +* Dev - Fix miscellaneous typos in docblocks. #29285 +* Dev - Added woocommerce_ajax_order_items_removed hook. #29241 +* Dev - Fix usage of docker-compose (wc-e2e) commands in e2e tests when running them within WSL2. #29207 +* Dev - Fixes to documentation of WC_Shipping_Rate. #29117 +* Dev - Add tracks event when a user clicks on the links of the WooCommerce Status widget. #29109 +* Dev - Track the number of installations of WooCommerce Payments via that extensions banner for stores that have opted in to tracking. #29052 +* Dev - Added woocommerce_ajax_order_items_removed hook. #28936 +* Dev - Add tracking of woocommerce_admin_disabled usage. #28535 +* Enhancement - Add support for the low stock threshold for variations. #29345 +* Enhancement - Clean up of major version update compatibility warnings. #29200 +* Enhancement - Add a new dashboard widget to promote users to finish onboarding tasks. #29174 +* Enhancement - Update the Woo widget net sales link and logic to use the new analytics page and data. #29149 +* Enhancement - Added the body class woocommerce-shop to the shop page, so that it can be targeted via CSS. (#28724). #29051 +* Enhancement - Make sure downloadable file paths are properly recognized for strengthened security. #28699 +* Enhancement - Delay the registration of data exporters and erasers to avoid multiple language files from being loaded. #28078 +* Fix - Offsets not calculated correctly sometimes on select2 dropdowns causing usability issues. #29397 +* Fix - Handle errors in fault installations of PHP Intl. #29391 +* Fix - Cart page calculate shipping fields not showing correct fields based on location. #29282 +* Fix - Product categories widget item count not always showing the correct number. #29281 +* Fix - Updated include/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php to include a item schema function which appends support for order and class type values. #29218 +* Fix - Check if variation_id if belongs to the parent product while adding products to the cart. #29208 +* Fix - Reduce the number of ajax calls used when Geolocation (with Page Caching Enabled) mode is enabled. #29182 +* Fix - Don't display the coupon form on checkouts requiring the customer to be logged in to checkout. #29151 +* Fix - If coupon_lines are specified within a REST API order update, return an error if coupon item IDs are also specified. #29146 +* Fix - Avoids duplicating the word '(optional)' in the context of the Billing Address 2 field. #29136 +* Fix - PHP notice when checking out. #29133 +* Fix - Remove duplicate containers from the single and archive product pages. #29121 +* Fix - Wrong taxonomy caching in term and product attributes controllers. #29115 +* Fix - Make the parameters of the refund creation REST API behave as documented. #29099 +* Fix - Shipping methods with similar names could cause shipping method not selectable in order page. #29049 +* Fix - WC_Countries::get_formatted_address() not returning full name in correct order in some languages. #29008 +* Fix - add validation of the posted country codes on checkout. #28849 +* Fix - Correctly display pagination arrows on RTL languages. #28523 +* Fix - Invalid refund amount error on $0 refund when number of decimals is equal to 0. #27277 +* Fix - "Sale" badge misaligned on products when displaying 1 item per row. #29425 +* Fix - Revert a replacement of wp_redirect to wp_safe_redirect in WC_Checkout::process_order_payment that caused issues in the default PayPal interface. #29459 +* Fix - Don't remove existing coupons from order when an invalid REST API request for updating coupons is submitted. #29474 +* Fix - Wrong logic for including or excluding the payments step in the list of completed tasks in the onboarding wizard. #29518 +* Fix - Error when loading the admin dashboard while the admin package was disabled. #29613 +* Fix - "'' is not a valid country code" error when no billing/shipping country specified (e.g. when using PayPal checkout). #29606 +* Fix - Sanitize tax class and display errors in admin while creating tax classes. +* Fix - Check if a verified product owner is required before placing a review. +* Fix - Make product name escaping consistent in the front-end. +* Tweak - Added the Mercado Pago logo into the assets/images folder in order to use it in the payments setup task. #29365 +* Tweak - Update the contributor guidelines. #29150 +* Tweak - Introduced phone number input validation. #27242 +* Tweak - Escape short description. +* Update - WooCommerce Admin package 2.1.5. #29577 +* Update - WooCommerce Blocks package 4.7.0. #29406 + +**WooCommerce Admin - 2.1.0 & 2.1.1 & 2.1.2 & 2.1.3 & 2.1.4 & 2.1.5** + +* Add - Add navigation intro modal. #6367 +* Add - CES track settings tab on updating settings #6368 +* Add - Core settings redirection to new settings pages #6091 +* Add - Favorites tooltip to the navigation #6312 +* Add - Favoriting extensions client UI #6287 +* Add - Remove CES actions for adding and editing a product and editing an order #6355 +* Add - Settings client pages #6092 +* Add - Settings feature and pages #6089 +* Dev - Add filter to allow enabling the WP toolbar within the new navigation. #6371 +* Dev - Add navigation favorites data store #6275 +* Dev - Add unit tests to Navigation's Container component. #6344 +* Dev - Allow highlight tooltip to use body tag as parent. #6309 +* Dev - Change `siteUrl` to `homeUrl` on navigation site title #6240 +* Dev - Fix the react state update error on homescreen. #6320 +* Dev - Refactor head and body heights #6247 +* Dev - Remove Google fonts and material icons. #6343 +* Dev - Use box sizing and padding to fix nav and admin menu styling #6335 +* Enhancement - Move capability checks to client #6365 +* Enhancement - Move favorited menu items to primary menu #6290 +* Enhancement - Navigation: Add test to container component #6344 +* Enhancement - override wpbody styles when nav present #6354 +* Feature - Increase target audience for business feature step. #6508 +* Fix - Add check for navigating being enabled. #6462 +* Fix - Add customer name column to CSV export #6556 +* Fix - Add guard to "Deactivate Plugin" note handlers to prevent fatal error. #6532 +* Fix - Adding New Zealand and Ireland to selective bundle option, previously missed. #6649 +* Fix - Broken link anchors to online documentation. #6455 +* Fix - Check if tax was successfully added before displaying notice #6229 +* Fix - Correct a bug where the JP connection flow would not happen when installing JP in the OBW. #6521 +* Fix - Crash of Analytics > Settings page when Gutenberg is installed. #6540 +* Fix - Display" option fails to collapse upon invoking "Help" option #6233 +* Fix - Email notes now are turned off by default #6324 +* Fix - Empty nav menu #6366 +* Fix - Enqueue scripts called incorrectly in php unit tests #6358 +* Fix - Hide tooltip in welcome modal #6142 +* Fix - Recommended Payment Banner missing in Safari #6375 +* Fix - Removal of core settings pages #6328 +* Fix - Removed @woocommerce/components/card from OBW #6374 +* Fix - Reset Navigation submenu before making Flyout #6396 +* Fix - Restore missing Correct the Klarna slug #6440 +* Fix - Top bar slightly overlaps wp-admin navigation on mobile #6292 +* Fix - update single column home screen width to 680px #6297 +* Fix - Update timing of InboxPanel state changes for the unread indicator #6246 +* Tweak - Enqueue beta features scripts on enqueue_scripts action instead of filter #6358 +* Tweak - Move admin menu manipulation from admin_head to admin_menu #6310 +* Tweak - Navigation: Migrate methods to `admin_menu` hook #6319 +* Tweak - New Settings: Turn off in dev mode #6348 +* Tweak - Order and styles updates to nav footer #6373 +* Tweak - Remove categories without menu items #6329 +* Tweak - Set international country feature flag off +* Tweak - Set `is_deleted` from the database when instantiating a `Note` #6322 +* Tweak - Update inline documentation for navigation Screen class #6173 +* Tweak - Updates to copy and punctuation to be more conversational and consistent. #6298 + +**WooCommerce Blocks - 4.5.0 & 4.6.0 & 4.7.0 & 4.7.1** + +* Enhancement - Login links on the checkout should use the account page. ([3844](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3844)) +* Enhancement - Prevent checkout linking to trashed terms and policy pages. ([3843](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3843)) +* Enhancement - Improved nonce logic by moving nonces to cart routes only. ([3812](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3812)) +* Enhancement - If coupons become invalid between applying to a cart and checking out, show the user a notice when the order is placed. ([3810](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3810)) +* Enhancement - Improve design of cart and checkout sidebars. ([3797](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3797)) +* Enhancement - Improve error displayed to customers when an item's stock status changes during checkout. ([3703](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3703)) +* Enhancement - Dev - Block Checkout will now respect custom address locales and custom country states via core filter hooks. ([3662](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3662)) +* Enhancement - Update checkout block payment methods UI. ([3439](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3439)) +* Enhancement - StoreAPI: Inject Order and Cart Controllers into Routes. ([3871](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3871)) +* Enhancement - Update Panel component class names to follow guidelines. More info can be found in our theming docs: https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/18dd54f07262b4d1dcf15561624617f824fcdc22/docs/theming/class-names-update-460.md. ([3860](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3860)) +* Enhancement - Refactor block type registration to support 3rd party integrations. +* Enhancement - A new configuration property is available to registered payment methods for additional logic handling of saved payment method tokens. ([3961](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3961)) +* Enhancement - Provided billing data to payment method extensions so they can decide if payment is possible. ([3922](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3922)) +* Enhancement - Prevent errant payment methods from keeping Cart and Checkout blocks from loading. ([3920](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3920)) +* Fix block elements that don't play well with dark backgrounds. ([3887](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3887)) +* Fix - JS warning if two cart products share the same name. ([3814](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3814)) +* Fix - Align place order button to the right of the block. ([3803](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3803)) +* Fix - Ensure special characters are displayed properly in the Cart sidebar. ([3721](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3721)) +* Fix - Bug where the total price of items did not include tax in the cart and checkout blocks. ([3851](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3851)) +* Fix - Handle out-of-stock product visibility setting in All Products block. ([3859](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3859)) +* Fix - Show cart item subtotal instead of total in Cart and Checkout blocks ([#3905](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3905)) +* Fix - Fix button styles in Twenty Nineteen theme. ([3862](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3862)) +* Fix - Return correct sale/regular prices for variable products in the Store API. ([3854](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3854)) +* Fix - Remove shadows from text buttons and gradient background from selects in some themes. ([3846](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3846)) +* Fix - Hide Browse Shop link in cart block empty state when there is no shop page. ([3845](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3845)) +* Fix - Remove extra padding from payment methods with no description. ([3952](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3952)) +* Fix - "save payment" checkbox not showing for payment methods. ([3950](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3950)) +* Fix - Cart preview when shipping rates are set to be hidden until an address is entered. ([3946](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3946)) +* Fix - Sync cart item quantity if its Implicitly changed. ([3907](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3907)) +* Fix - FSE not being visible when WC Blocks was enabled. ([3898](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3898)) +* Fix - Ensure sale badges have a uniform height in the Cart block. ([3897](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3897)) +* Note - Internally, this release has modified how `AbstractBlock` (the base class for all of our blocks) functions, and how it loads assets. `AbstractBlock` is internal to this project and does not seem like something that would ever need to be extended by 3rd parties, but note if you are doing so for whatever reason, your implementation would need to be updated to match. ([3829](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3829)) + += 5.1.0 2021-03-09 = + +**WooCommerce** + +* Update - WooCommerce Admin package 2.0.2. #29229 +* Update - WooCommerce Blocks package 4.4.3. #29016 +* Fix - Error in notice message of reports when WC Admin is disabled via a filter. #29095 +* Fix - Error when calculating orders with tax option rounding at subtotal level in PHP 8. #29089 +* Fix - price filtering not working properly with variable products whose variations have different prices. #29043 +* Fix - Removed extra closing brace from the Zone regions help text. #29036 +* Fix - Tax name/label is not being updated in the order when it is changed. #28983 +* Fix - Additional protection after wc_get_product to account for invalid ID. #28962 +* Fix - orders list from returning false values if orders are missing. #28927 +* Fix - Terms and Policy checkbox wording settings not shown in customizer. #28735 +* Fix - Admin notices sometimes were persisting even after dismissing. #28500 +* Fix - Calculate discount based on order location in the admin view. #26983 +* Fix - SASS variables not being compile correctly due to recent SASS version. #29120 +* Fix - Placeholder image height in cart. #29139 +* Dev - Updated admin bar icons to use SVG and Dashicons instead of custom font. #29094 +* Dev - Admin menu modification has been moved from admin_head hooks to admin_menu hooks. #29088 +* Dev - status report generation time to the Status Report. #28980 +* Dev - Add the 'woocommerce_exporter_product_types' filter to allow third-parties to filter the product types which can be imported and exported. #28950 +* Dev - Filter added to allow 'woocommerce_hold_stock_minutes' to be customized. #28933 +* Dev - Add optional semicolon to JS code for better compatibility. #28880 +* Dev - Added Guatemala states. #28755 +* Dev - jQuery 3 deprecated functions update. #28753 +* Dev - Add Woo Version as global prop in track events. #28627 +* Dev - Added orders count by payment method to Tracker data and replaced direct DB calls with CRUD. #28584 +* Dev - WC_Tax::get_tax_rate_classes() is now public. #27671 +* Dev - "Store management insights" option now is turned off by default. #29105 +* Dev - Update the woo widget stock links to the new analytics page. #29093 +* Tweak - Updated WooCommerce logo color. #29054 +* Tweak - Correctly aligns content in the checkout with Twenty Twenty-One. #28951 +* Tweak - Use assigned variable for $post_thumbnail_id instead of calling function more than once. #28919 +* Tweak - Ensure that all tracker values collected for orders are of string type. #28893 +* Tweak - Adjust CSS font size and spacing for Twenty Twenty One. #28827 + +**WooCommerce Admin - 2.0.0 & 2.0.1 & 2.0.2** + +* Tweak - Bump minimum supported version of PHP to 7.0. #6046 +* Tweak - update the content and timing of the NeedSomeInspiration note. #6076 +* Tweak - Adjust the Marketing note not to show until store is at least 5 days. #6083 +* Tweak - Refactored extended task list. #6081 +* Fix - Add support for a floating-point number as a SummaryNumber's delta. #5926 +* Fix - allow for more terms to be shown for product attributes in the Analytics orders report. #5868 +* Fix - Fixed the Add First Product email note checks. #6260 +* Fix - Onboarding - Fixed "Business Details" error. #6271 +* Fix - Show management links when only main task list is hidden. #6291 +* Fix - Correct the Klarna slug. #6440 +* Add - new inbox message - Getting started in Ecommerce - watch this webinar. #6086 +* Add - Remote inbox notifications contains comparison and fix product rule. #6073 +* Add - Task list payments - include Mollie as an option. #6257 +* Update - store deprecation welcome modal support doc link #6094 +* Update - Homescreen layout, moving Inbox panel for better interaction. #6122 +* Enhancement - Allowing users to create products by selecting a template. #5892 +* Enhancement - Use the new Paypal payments plugin for onboarding. #6261 +* Dev - Add wait script for mysql to be ready for phpunit tests in docker. #6185 +* Dev - Remove old debug code for connecting to Calypso / Wordpress.com. #6097 +* Dev - Allow highlight tooltip to use body tag as parent. #6309 + +**WooCommerce Blocks - 4.1.0 & 4.2.0 & 4.3.0 & 4.4.0 & 4.4.1 & 4.4.2 & 4.4.3** + +* Update - Jetpack Autoloader to 2.9.1. +* Update - Update package for WooCommerce core inclusion. +* Enhancements - Design tweaks to the cart page which move the quantity picker below each cart item and improve usability on mobile. ([3734](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3734)) +* Enhancements - Store API - Fix selected rate in cart shipping rates response. ([3680](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3680)) +* Enhancements - Create get_item_responses_from_schema abstraction. ([3679](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3679)) +* Enhancements - Show itemized fee rows in the cart/checkout blocks. ([3678](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3678)) +* Enhancements - Extensibility: Show item data in Cart and Checkout blocks and update the variation data styles. ([3665](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3665)) +* Enhancements - Introduce SlotFill for Sidebar. ([3361](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3361)) +* Enhancements - Add the ability to directly upload an image in Featured Category and Featured Product blocks. ([3579](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3579)) +* Enhancements - Fix coupon code button height not adapting to the font size. ([3575](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3575)) +* Enhancements - Fixed Coupon Code panel not expanding/contracting in some themes. ([3569](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3569)) +* Enhancements - Fix: Added fallback styling for screen reader text. ([3557](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3557)) +* Fix - Ensure empty categories are correctly hidden in the product categories block. ([3765](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3765)) +* Fix - Added missing wrapper div within FeaturedCategory and FeatureProduct blocks. ([3746](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3746)) +* Fix - Set correct text color in BlockErrorBoundry notices. ([3738](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3738)) +* Fix - Hidden cart item meta data will not be rendered in the Cart and Checkout blocks. ([3732](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3732)) +* Fix - Improved accessibility of product image links in the products block by using correct aria tags and hiding empty image placeholders. ([3722](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3722)) +* Fix - Add missing aria-label for stars image in the review-list-item component. ([3706](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3706)) +* Fix - Prevent "X-WC-Store-API-Nonce is invalid" error when going back to a page with the products block using the browser back button. ([3770](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3770)) +* Fix - Adds a default "features" array for payment methods which do not define supported features. Fixes conflicts with 3rd Party payment method integrations. +* Fix - Fix an error that was blocking checkout with some user saved payment methods. ([3627](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3627)) +* Fix - Fix nonce issues when adding product to cart from All Products. ([3598](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3598)) +* Fix - Fix bug inside Product Search in the editor. ([3578](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3578)) +* Fix - Fix console warnings in WordPress 5.6. ([3577](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3577)) +* Fix - Fixed text visibility in select inputs when using Twenty Twenty-One theme's dark mode. ([3554](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3554)) +* Fix - Fix product list images skewed in Widgets editor. ([3553](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3553)) +* Add address validation to values posted to the Checkout via StoreApi. ([3552](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3552)) +* Fix - Fix Fees not visible in Cart & Checkout blocks when order doesn't need shipping. ([3521](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3521)) +* Fix - Fix All Products block edit screen. ([3547](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3547)) +* Fix - Checkout block: Prevent `Create an account` from creating up a user account if the order fails coupon validation. ([3423](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3423)) +* Fix - Make sure cart is initialized before the CartItems route is used in the Store API. ([3488](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3488)) +* Fix - Fix notice close button color in Twenty Twenty One dark mode. ([3472](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3472)) +* Fix - Remove held stock for a draft order if an item is removed from the cart. ([3468](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3468)) +* Fix - Ensure correct alignment of checkout notice's dismiss button. ([3455](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3455)) +* Fix - Fixed a bug in Checkout block (Store API) causing checkout to fail when using an invalid coupon and creating an account. +* Fix - Checkout block: Correctly handle cases where the order fails with an error (e.g. invalid coupon) and a new user account is created. ([3429](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3429)) +* Update - Hide the All Products Block from the new Gutenberg Widget Areas until full support is achieved. ([3737](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3737)) +* Update - Legacy `star-rating` class name has been removed from Product rating block (inside All Products block). That element is still selectable with the `.wc-block-components-product-rating` class name. ([3717](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3717)) +* Update - Update input colors and alignment. ([3597](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3597)) +* Update - Removed compatibility with packages in WordPress 5.3. ([3541](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3541)) +* Update - Bumped the minimum WP required version to 5.4. ([3537](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3537)) +* Dev - Change register_endpoint_data to use an array of params instead of individual params. ([3478](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3478)) +* Dev - Expose store/cart via ExtendRestApi to extensions. ([3445](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3445)) +* Dev - Added formatting classes to the Store API for extensions to consume. +* Dev - Refactored and reordered Store API checkout processing to handle various edge cases and better support future extensibility. ([3454](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3454)) + = 5.0.0 - 2021-02-09 = **WooCommerce** @@ -1099,6 +1370,7 @@ * Fix - Don't show duplicated update notifications on Woo Screens. #25828 * Fix - Escape MaxMind database URL. #25682 * Fix - System status report should correctly identify inactive package. #25830 +* Fix - "Sale" badge misaligned on products when displaying 1 item per row. #29425 * Dev - Added support for placeholder attribute in quantity inputs. #25418 * Dev - Added `tax_status` and `tax_class` columns to the product meta data lookup table. #25428 * Dev - Introduced `woocommerce_top_rated_widget_args` filter. #25320 diff --git a/composer.json b/composer.json index 33cecfc6758..10c9be56a53 100644 --- a/composer.json +++ b/composer.json @@ -14,15 +14,15 @@ ], "require": { "php": ">=7.0", - "automattic/jetpack-autoloader": "2.9.1", + "automattic/jetpack-autoloader": "2.10.1", "automattic/jetpack-constants": "1.5.1", "composer/installers": "~1.7", "maxmind-db/reader": "1.6.0", "pelago/emogrifier": "3.1.0", "psr/container": "1.0.0", "woocommerce/action-scheduler": "3.1.6", - "woocommerce/woocommerce-admin": "2.0.2", - "woocommerce/woocommerce-blocks": "4.4.3" + "woocommerce/woocommerce-admin": "2.2.2-rc.1", + "woocommerce/woocommerce-blocks": "4.9.1" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4" diff --git a/composer.lock b/composer.lock index 24b5bc27a02..e74cb27df92 100644 --- a/composer.lock +++ b/composer.lock @@ -4,32 +4,39 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f24a600ea103061d766dd7b06c13e8f2", + "content-hash": "be33d948ed1d2ee3a7a23ef657f3148d", "packages": [ { "name": "automattic/jetpack-autoloader", - "version": "v2.9.1", + "version": "2.10.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-autoloader.git", - "reference": "d6ca2cc26ad6963e1be19b3338a9e98f40d9bd88" + "reference": "20393c4677765c3e737dcb5aee7a3f7b90dce4b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/d6ca2cc26ad6963e1be19b3338a9e98f40d9bd88", - "reference": "d6ca2cc26ad6963e1be19b3338a9e98f40d9bd88", + "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/20393c4677765c3e737dcb5aee7a3f7b90dce4b3", + "reference": "20393c4677765c3e737dcb5aee7a3f7b90dce4b3", "shasum": "" }, "require": { "composer-plugin-api": "^1.1 || ^2.0" }, "require-dev": { + "automattic/jetpack-changelogger": "^1.1", "yoast/phpunit-polyfills": "0.2.0" }, "type": "composer-plugin", "extra": { "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin", - "mirror-repo": "Automattic/jetpack-autoloader" + "mirror-repo": "Automattic/jetpack-autoloader", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" + }, + "branch-alias": { + "dev-master": "2.10.x-dev" + } }, "autoload": { "classmap": [ @@ -44,10 +51,7 @@ "GPL-2.0-or-later" ], "description": "Creates a custom autoloader for a plugin or theme.", - "support": { - "source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.9.1" - }, - "time": "2021-02-05T19:07:06+00:00" + "time": "2021-03-30T15:15:59+00:00" }, { "name": "automattic/jetpack-constants", @@ -78,23 +82,20 @@ "GPL-2.0-or-later" ], "description": "A wrapper for defining constants in a more testable way.", - "support": { - "source": "https://github.com/Automattic/jetpack-constants/tree/v1.5.1" - }, "time": "2020-10-28T19:00:31+00:00" }, { "name": "composer/installers", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d" + "reference": "ae03311f45dfe194412081526be2e003960df74b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d", - "reference": "1a0357fccad9d1cc1ea0c9a05b8847fbccccb78d", + "url": "https://api.github.com/repos/composer/installers/zipball/ae03311f45dfe194412081526be2e003960df74b", + "reference": "ae03311f45dfe194412081526be2e003960df74b", "shasum": "" }, "require": { @@ -188,6 +189,7 @@ "majima", "mako", "mediawiki", + "miaoxing", "modulework", "modx", "moodle", @@ -205,16 +207,13 @@ "sydes", "sylius", "symfony", + "tastyigniter", "typo3", "wordpress", "yawik", "zend", "zikula" ], - "support": { - "issues": "https://github.com/composer/installers/issues", - "source": "https://github.com/composer/installers/tree/v1.10.0" - }, "funding": [ { "url": "https://packagist.com", @@ -229,7 +228,7 @@ "type": "tidelift" } ], - "time": "2021-01-14T11:07:16+00:00" + "time": "2021-04-28T06:42:17+00:00" }, { "name": "maxmind-db/reader", @@ -289,10 +288,6 @@ "geolocation", "maxmind" ], - "support": { - "issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues", - "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.6.0" - }, "time": "2019-12-19T22:59:03+00:00" }, { @@ -367,10 +362,6 @@ "email", "pre-processing" ], - "support": { - "issues": "https://github.com/MyIntervals/emogrifier/issues", - "source": "https://github.com/MyIntervals/emogrifier" - }, "time": "2019-12-26T19:37:31+00:00" }, { @@ -420,10 +411,6 @@ "container-interop", "psr" ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/master" - }, "time": "2017-02-14T16:28:37+00:00" }, { @@ -477,9 +464,6 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/css-selector/tree/master" - }, "time": "2017-05-01T15:01:29+00:00" }, { @@ -515,24 +499,20 @@ ], "description": "Action Scheduler for WordPress and WooCommerce", "homepage": "https://actionscheduler.org/", - "support": { - "issues": "https://github.com/woocommerce/action-scheduler/issues", - "source": "https://github.com/woocommerce/action-scheduler/tree/master" - }, "time": "2020-05-12T16:22:33+00:00" }, { "name": "woocommerce/woocommerce-admin", - "version": "2.0.2", + "version": "2.2.2-rc.1", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-admin.git", - "reference": "c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4" + "reference": "0d305d1716481a0cc2010ec7b0a608a2d3c8ebe4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4", - "reference": "c4ffd90ebc72652f9d1bc8943a56d7723acc5bf4", + "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/0d305d1716481a0cc2010ec7b0a608a2d3c8ebe4", + "reference": "0d305d1716481a0cc2010ec7b0a608a2d3c8ebe4", "shasum": "" }, "require": { @@ -564,24 +544,20 @@ ], "description": "A modern, javascript-driven WooCommerce Admin experience.", "homepage": "https://github.com/woocommerce/woocommerce-admin", - "support": { - "issues": "https://github.com/woocommerce/woocommerce-admin/issues", - "source": "https://github.com/woocommerce/woocommerce-admin/tree/v2.0.2" - }, - "time": "2021-02-25T07:29:24+00:00" + "time": "2021-04-28T19:39:33+00:00" }, { "name": "woocommerce/woocommerce-blocks", - "version": "v4.4.3", + "version": "v4.9.1", "source": { "type": "git", "url": "https://github.com/woocommerce/woocommerce-gutenberg-products-block.git", - "reference": "1eade21846e81d5aaf9bf40cdd1be60778849244" + "reference": "62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/1eade21846e81d5aaf9bf40cdd1be60778849244", - "reference": "1eade21846e81d5aaf9bf40cdd1be60778849244", + "url": "https://api.github.com/repos/woocommerce/woocommerce-gutenberg-products-block/zipball/62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce", + "reference": "62f32bfb45dfcb2ba3ca349a6ed0a8cf48ddefce", "shasum": "" }, "require": { @@ -615,11 +591,7 @@ "gutenberg", "woocommerce" ], - "support": { - "issues": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues", - "source": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/v4.4.3" - }, - "time": "2021-02-11T18:07:48+00:00" + "time": "2021-04-13T16:11:16+00:00" } ], "packages-dev": [ @@ -667,10 +639,6 @@ "isolation", "tool" ], - "support": { - "issues": "https://github.com/bamarni/composer-bin-plugin/issues", - "source": "https://github.com/bamarni/composer-bin-plugin/tree/master" - }, "time": "2020-05-03T08:27:20+00:00" } ], @@ -686,5 +654,5 @@ "platform-overrides": { "php": "7.0" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "1.1.0" } diff --git a/includes/abstracts/abstract-wc-order.php b/includes/abstracts/abstract-wc-order.php index 4c282a8cc03..e07153da6e7 100644 --- a/includes/abstracts/abstract-wc-order.php +++ b/includes/abstracts/abstract-wc-order.php @@ -1633,6 +1633,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { continue; } $saved_rate_ids[] = $tax->get_rate_id(); + $tax->set_rate( $tax->get_rate_id() ); $tax->set_tax_total( isset( $cart_taxes[ $tax->get_rate_id() ] ) ? $cart_taxes[ $tax->get_rate_id() ] : 0 ); $tax->set_label( WC_Tax::get_rate_label( $tax->get_rate_id() ) ); $tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax->get_rate_id() ] ) ? $shipping_taxes[ $tax->get_rate_id() ] : 0 ); diff --git a/includes/abstracts/abstract-wc-payment-gateway.php b/includes/abstracts/abstract-wc-payment-gateway.php index fe29740e569..3dcc281464d 100644 --- a/includes/abstracts/abstract-wc-payment-gateway.php +++ b/includes/abstracts/abstract-wc-payment-gateway.php @@ -262,7 +262,9 @@ abstract class WC_Payment_Gateway extends WC_Settings_API { // Gets order total from "pay for order" page. if ( 0 < $order_id ) { $order = wc_get_order( $order_id ); - $total = (float) $order->get_total(); + if ( $order ) { + $total = (float) $order->get_total(); + } // Gets order total from cart/checkout. } elseif ( 0 < WC()->cart->total ) { diff --git a/includes/abstracts/abstract-wc-settings-api.php b/includes/abstracts/abstract-wc-settings-api.php index f33a62365fc..35bdbe522b7 100644 --- a/includes/abstracts/abstract-wc-settings-api.php +++ b/includes/abstracts/abstract-wc-settings-api.php @@ -696,6 +696,7 @@ abstract class WC_Settings_API { ); $data = wp_parse_args( $data, $defaults ); + $value = $this->get_option( $key ); ob_start(); ?> @@ -708,7 +709,15 @@ abstract class WC_Settings_API { get_description_html( $data ); // WPCS: XSS ok. ?> diff --git a/includes/admin/class-wc-admin-assets.php b/includes/admin/class-wc-admin-assets.php index 567e2efe7ca..01e62199ba5 100644 --- a/includes/admin/class-wc-admin-assets.php +++ b/includes/admin/class-wc-admin-assets.php @@ -145,6 +145,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) : 'search_products_nonce' => wp_create_nonce( 'search-products' ), 'search_customers_nonce' => wp_create_nonce( 'search-customers' ), 'search_categories_nonce' => wp_create_nonce( 'search-categories' ), + 'search_pages_nonce' => wp_create_nonce( 'search-pages' ), ) ); diff --git a/includes/admin/class-wc-admin-dashboard-setup.php b/includes/admin/class-wc-admin-dashboard-setup.php index 8fdbc1e94de..4360241b37f 100644 --- a/includes/admin/class-wc-admin-dashboard-setup.php +++ b/includes/admin/class-wc-admin-dashboard-setup.php @@ -162,7 +162,9 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) : * @return bool */ private function should_display_widget() { - return 'yes' !== get_option( 'woocommerce_task_list_complete' ) && 'yes' !== get_option( 'woocommerce_task_list_hidden' ); + return WC()->is_wc_admin_active() && + 'yes' !== get_option( 'woocommerce_task_list_complete' ) && + 'yes' !== get_option( 'woocommerce_task_list_hidden' ); } /** @@ -170,7 +172,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) : */ private function populate_payment_tasks() { $is_woo_payment_installed = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' ); - $country = explode( ':', get_option( 'woocommerce_default_country', '' ) )[0]; + $country = explode( ':', get_option( 'woocommerce_default_country', 'US:CA' ) )[0]; // woocommerce-payments requires its plugin activated and country must be US. if ( ! $is_woo_payment_installed || 'US' !== $country ) { @@ -178,7 +180,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard_Setup', false ) ) : } // payments can't be used when woocommerce-payments exists and country is US. - if ( $is_woo_payment_installed || 'US' === $country ) { + if ( $is_woo_payment_installed && 'US' === $country ) { unset( $this->tasks['payments'] ); } diff --git a/includes/admin/class-wc-admin-dashboard.php b/includes/admin/class-wc-admin-dashboard.php index 498eeee78ae..6357b46e8bd 100644 --- a/includes/admin/class-wc-admin-dashboard.php +++ b/includes/admin/class-wc-admin-dashboard.php @@ -63,6 +63,10 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : * @return bool */ private function should_display_widget() { + if ( ! WC()->is_wc_admin_active() ) { + return false; + } + $has_permission = current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ); $task_completed_or_hidden = 'yes' === get_option( 'woocommerce_task_list_complete' ) || 'yes' === get_option( 'woocommerce_task_list_hidden' ); return $task_completed_or_hidden && $has_permission; @@ -127,11 +131,11 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : $reports = new WC_Admin_Report(); - $net_sales_link = 'admin.php?page=wc-reports&tab=orders&range=month'; + $net_sales_link = 'admin.php?page=wc-reports&tab=orders&range=month'; $top_seller_link = 'admin.php?page=wc-reports&tab=orders&report=sales_by_product&range=month&product_ids='; - $report_data = $is_wc_admin_disabled ? $this->get_sales_report_data() : $this->get_wc_admin_performance_data(); + $report_data = $is_wc_admin_disabled ? $this->get_sales_report_data() : $this->get_wc_admin_performance_data(); if ( ! $is_wc_admin_disabled ) { - $net_sales_link = 'admin.php?page=wc-admin&path=%2Fanalytics%2Frevenue&chart=net_revenue&orderby=net_revenue&period=month&compare=previous_period'; + $net_sales_link = 'admin.php?page=wc-admin&path=%2Fanalytics%2Frevenue&chart=net_revenue&orderby=net_revenue&period=month&compare=previous_period'; $top_seller_link = 'admin.php?page=wc-admin&filter=single_product&path=%2Fanalytics%2Fproducts&products='; } diff --git a/includes/admin/class-wc-admin-post-types.php b/includes/admin/class-wc-admin-post-types.php index e2d145206a1..2eaef024a14 100644 --- a/includes/admin/class-wc-admin-post-types.php +++ b/includes/admin/class-wc-admin-post-types.php @@ -430,7 +430,10 @@ class WC_Admin_Post_Types { // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $product->set_manage_stock( $manage_stock ); - $product->set_backorders( $backorders ); + + if ( 'external' !== $product->get_type() ) { + $product->set_backorders( $backorders ); + } if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { $stock_amount = 'yes' === $manage_stock && isset( $request_data['_stock'] ) && is_numeric( wp_unslash( $request_data['_stock'] ) ) ? wc_stock_amount( wp_unslash( $request_data['_stock'] ) ) : ''; @@ -550,7 +553,10 @@ class WC_Admin_Post_Types { $stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity(); $product->set_manage_stock( $manage_stock ); - $product->set_backorders( $backorders ); + + if ( 'external' !== $product->get_type() ) { + $product->set_backorders( $backorders ); + } if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { $change_stock = absint( $request_data['change_stock'] ); diff --git a/includes/admin/class-wc-admin-settings.php b/includes/admin/class-wc-admin-settings.php index b36d9a889d6..69506066749 100644 --- a/includes/admin/class-wc-admin-settings.php +++ b/includes/admin/class-wc-admin-settings.php @@ -579,6 +579,47 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) : post_title, + $option_value + ); + } + ?> + + + + + + + + + countries->get_base_state(); $country = WC()->countries->get_base_country(); $postcode = WC()->countries->get_base_postcode(); - $currency = get_option( 'woocommerce_currency', 'GBP' ); + $currency = get_option( 'woocommerce_currency', 'USD' ); $product_type = get_option( 'woocommerce_product_type', 'both' ); $sell_in_person = get_option( 'woocommerce_sell_in_person', 'none_selected' ); diff --git a/includes/admin/class-wc-admin-taxonomies.php b/includes/admin/class-wc-admin-taxonomies.php index 82651dbc65d..fd6d297dfa1 100644 --- a/includes/admin/class-wc-admin-taxonomies.php +++ b/includes/admin/class-wc-admin-taxonomies.php @@ -11,6 +11,8 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } +use Automattic\WooCommerce\Internal\AssignDefaultCategory; + /** * WC_Admin_Taxonomies class. */ @@ -49,6 +51,12 @@ class WC_Admin_Taxonomies { // Category/term ordering. add_action( 'create_term', array( $this, 'create_term' ), 5, 3 ); + add_action( + 'delete_product_cat', + function() { + wc_get_container()->get( AssignDefaultCategory::class )->schedule_action(); + } + ); // Add form. add_action( 'product_cat_add_form_fields', array( $this, 'add_category_fields' ) ); @@ -91,7 +99,7 @@ class WC_Admin_Taxonomies { * @param string $taxonomy Taxonomy slug. */ public function create_term( $term_id, $tt_id = '', $taxonomy = '' ) { - if ( 'product_cat' != $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) { + if ( 'product_cat' !== $taxonomy && ! taxonomy_is_product_attribute( $taxonomy ) ) { return; } diff --git a/includes/admin/list-tables/class-wc-admin-list-table-orders.php b/includes/admin/list-tables/class-wc-admin-list-table-orders.php index 949f484814c..b2a0e511886 100644 --- a/includes/admin/list-tables/class-wc-admin-list-table-orders.php +++ b/includes/admin/list-tables/class-wc-admin-list-table-orders.php @@ -15,7 +15,7 @@ if ( class_exists( 'WC_Admin_List_Table_Orders', false ) ) { } if ( ! class_exists( 'WC_Admin_List_Table', false ) ) { - include_once __DIR__ . '/abstract-class-wc-admin-list-table.php'; + include_once __DIR__ . '/abstract-class-wc-admin-list-table.php'; } /** @@ -765,7 +765,7 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table { } ?> object->get_id() ) ) . '">' . $this->object->get_image( 'thumbnail' ) . ''; // WPCS: XSS ok. @@ -203,21 +203,21 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table { } /** - * Render columm: sku. + * Render column: sku. */ protected function render_sku_column() { echo $this->object->get_sku() ? esc_html( $this->object->get_sku() ) : ''; } /** - * Render columm: price. + * Render column: price. */ protected function render_price_column() { echo $this->object->get_price_html() ? wp_kses_post( $this->object->get_price_html() ) : ''; } /** - * Render columm: product_cat. + * Render column: product_cat. */ protected function render_product_cat_column() { $terms = get_the_terms( $this->object->get_id(), 'product_cat' ); @@ -234,7 +234,7 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table { } /** - * Render columm: product_tag. + * Render column: product_tag. */ protected function render_product_tag_column() { $terms = get_the_terms( $this->object->get_id(), 'product_tag' ); @@ -251,7 +251,7 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table { } /** - * Render columm: featured. + * Render column: featured. */ protected function render_featured_column() { $url = wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_feature_product&product_id=' . $this->object->get_id() ), 'woocommerce-feature-product' ); @@ -265,7 +265,7 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table { } /** - * Render columm: is_in_stock. + * Render column: is_in_stock. */ protected function render_is_in_stock_column() { if ( $this->object->is_on_backorder() ) { @@ -337,7 +337,7 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table { ?> isset( $_POST['variable_manage_stock'][ $i ] ), 'stock_quantity' => $stock, + 'low_stock_amount' => isset( $_POST['variable_low_stock_amount'][ $i ] ) && '' !== $_POST['variable_low_stock_amount'][ $i ] ? wc_stock_amount( wp_unslash( $_POST['variable_low_stock_amount'][ $i ] ) ) : '', 'backorders' => isset( $_POST['variable_backorders'], $_POST['variable_backorders'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_backorders'][ $i ] ) ) : null, 'stock_status' => isset( $_POST['variable_stock_status'][ $i ] ) ? wc_clean( wp_unslash( $_POST['variable_stock_status'][ $i ] ) ) : null, 'image_id' => isset( $_POST['upload_image_id'][ $i ] ) ? wc_clean( wp_unslash( $_POST['upload_image_id'][ $i ] ) ) : null, diff --git a/includes/admin/meta-boxes/views/html-order-shipping.php b/includes/admin/meta-boxes/views/html-order-shipping.php index 86633b06ca9..e972e204b18 100644 --- a/includes/admin/meta-boxes/views/html-order-shipping.php +++ b/includes/admin/meta-boxes/views/html-order-shipping.php @@ -31,11 +31,11 @@ if ( ! defined( 'ABSPATH' ) ) { $found_method = false; foreach ( $shipping_methods as $method ) { - $current_method = ( 0 === strpos( $item->get_method_id(), $method->id ) ) ? $item->get_method_id() : $method->id; + $is_active = $item->get_method_id() === $method->id; - echo ''; + echo ''; - if ( $item->get_method_id() === $current_method ) { + if ( $is_active ) { $found_method = true; } } diff --git a/includes/admin/meta-boxes/views/html-product-data-inventory.php b/includes/admin/meta-boxes/views/html-product-data-inventory.php index cb5d406b231..4bab3e0bd0e 100644 --- a/includes/admin/meta-boxes/views/html-product-data-inventory.php +++ b/includes/admin/meta-boxes/views/html-product-data-inventory.php @@ -75,10 +75,14 @@ if ( ! defined( 'ABSPATH' ) ) { array( 'id' => '_low_stock_amount', 'value' => $product_object->get_low_stock_amount( 'edit' ), - 'placeholder' => get_option( 'woocommerce_notify_low_stock_amount' ), + 'placeholder' => sprintf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Store-wide threshold (%d)', 'woocommerce' ), + esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) ) + ), 'label' => __( 'Low stock threshold', 'woocommerce' ), 'desc_tip' => true, - 'description' => __( 'When product stock reaches this amount you will be notified by email', 'woocommerce' ), + 'description' => __( 'When product stock reaches this amount you will be notified by email. It is possible to define different values for each variation individually. The shop default value can be set in Settings > Products > Inventory.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array( 'step' => 'any', diff --git a/includes/admin/meta-boxes/views/html-product-data-variations.php b/includes/admin/meta-boxes/views/html-product-data-variations.php index 15af978f892..bc35103b2e6 100644 --- a/includes/admin/meta-boxes/views/html-product-data-variations.php +++ b/includes/admin/meta-boxes/views/html-product-data-variations.php @@ -75,6 +75,7 @@ if ( ! defined( 'ABSPATH' ) ) { + diff --git a/includes/admin/meta-boxes/views/html-variation-admin.php b/includes/admin/meta-boxes/views/html-variation-admin.php index 01a0a20e7eb..8570dc9ad3d 100644 --- a/includes/admin/meta-boxes/views/html-variation-admin.php +++ b/includes/admin/meta-boxes/views/html-variation-admin.php @@ -210,6 +210,35 @@ defined( 'ABSPATH' ) || exit; ) ); + $low_stock_placeholder = ( $product_object->get_manage_stock() && '' !== $product_object->get_low_stock_amount() ) + ? sprintf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Parent product\'s threshold (%d)', 'woocommerce' ), + esc_attr( $product_object->get_low_stock_amount() ) + ) + : sprintf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Store-wide threshold (%d)', 'woocommerce' ), + esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) ) + ); + + woocommerce_wp_text_input( + array( + 'id' => "variable_low_stock_amount{$loop}", + 'name' => "variable_low_stock_amount[{$loop}]", + 'value' => $variation_object->get_low_stock_amount( 'edit' ), + 'placeholder' => $low_stock_placeholder, + 'label' => __( 'Low stock threshold', 'woocommerce' ), + 'desc_tip' => true, + 'description' => __( 'When variation stock reaches this amount you will be notified by email. The default value for all variations can be set in the product Inventory tab. The shop default value can be set in Settings > Products > Inventory.', 'woocommerce' ), + 'type' => 'number', + 'custom_attributes' => array( + 'step' => 'any', + ), + 'wrapper_class' => 'form-row', + ) + ); + /** * Variation options inventory action. * diff --git a/includes/admin/settings/class-wc-settings-advanced.php b/includes/admin/settings/class-wc-settings-advanced.php index 2c8c34a550d..516d52ee090 100644 --- a/includes/admin/settings/class-wc-settings-advanced.php +++ b/includes/admin/settings/class-wc-settings-advanced.php @@ -74,9 +74,9 @@ class WC_Settings_Advanced extends WC_Settings_Page { /* Translators: %s Page contents. */ 'desc' => sprintf( __( 'Page contents: [%s]', 'woocommerce' ), apply_filters( 'woocommerce_cart_shortcode_tag', 'woocommerce_cart' ) ), 'id' => 'woocommerce_cart_page_id', - 'type' => 'single_select_page', + 'type' => 'single_select_page_with_search', 'default' => '', - 'class' => 'wc-enhanced-select-nostd', + 'class' => 'wc-page-search', 'css' => 'min-width:300px;', 'args' => array( 'exclude' => @@ -94,9 +94,9 @@ class WC_Settings_Advanced extends WC_Settings_Page { /* Translators: %s Page contents. */ 'desc' => sprintf( __( 'Page contents: [%s]', 'woocommerce' ), apply_filters( 'woocommerce_checkout_shortcode_tag', 'woocommerce_checkout' ) ), 'id' => 'woocommerce_checkout_page_id', - 'type' => 'single_select_page', - 'default' => '', - 'class' => 'wc-enhanced-select-nostd', + 'type' => 'single_select_page_with_search', + 'default' => wc_get_page_id( 'checkout' ), + 'class' => 'wc-page-search', 'css' => 'min-width:300px;', 'args' => array( 'exclude' => @@ -114,9 +114,9 @@ class WC_Settings_Advanced extends WC_Settings_Page { /* Translators: %s Page contents. */ 'desc' => sprintf( __( 'Page contents: [%s]', 'woocommerce' ), apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) ), 'id' => 'woocommerce_myaccount_page_id', - 'type' => 'single_select_page', + 'type' => 'single_select_page_with_search', 'default' => '', - 'class' => 'wc-enhanced-select-nostd', + 'class' => 'wc-page-search', 'css' => 'min-width:300px;', 'args' => array( 'exclude' => @@ -134,9 +134,9 @@ class WC_Settings_Advanced extends WC_Settings_Page { 'desc' => __( 'If you define a "Terms" page the customer will be asked if they accept them when checking out.', 'woocommerce' ), 'id' => 'woocommerce_terms_page_id', 'default' => '', - 'class' => 'wc-enhanced-select-nostd', + 'class' => 'wc-page-search', 'css' => 'min-width:300px;', - 'type' => 'single_select_page', + 'type' => 'single_select_page_with_search', 'args' => array( 'exclude' => wc_get_page_id( 'checkout' ) ), 'desc_tip' => true, 'autoload' => false, diff --git a/includes/admin/settings/class-wc-settings-emails.php b/includes/admin/settings/class-wc-settings-emails.php index 361771ecc0e..0543e42631e 100644 --- a/includes/admin/settings/class-wc-settings-emails.php +++ b/includes/admin/settings/class-wc-settings-emails.php @@ -46,12 +46,19 @@ class WC_Settings_Emails extends WC_Settings_Page { * @return array */ public function get_settings() { + $desc_help_text = sprintf( + /* translators: %1$s: Link to WP Mail Logging plugin, %2$s: Link to Email FAQ support page. */ + __( 'To ensure your store’s notifications arrive in your and your customers’ inboxes, we recommend connecting your email address to your domain and setting up a dedicated SMTP server. If something doesn’t seem to be sending correctly, install the WP Mail Logging Plugin or check the Email FAQ page.', 'woocommerce' ), + 'https://wordpress.org/plugins/wp-mail-logging/', + 'https://docs.woocommerce.com/document/email-faq' + ); $settings = apply_filters( 'woocommerce_email_settings', array( array( 'title' => __( 'Email notifications', 'woocommerce' ), - 'desc' => __( 'Email notifications sent from WooCommerce are listed below. Click on an email to configure it.', 'woocommerce' ), + /* translators: %s: help description with link to WP Mail logging and support page. */ + 'desc' => sprintf( __( 'Email notifications sent from WooCommerce are listed below. Click on an email to configure it.
    %s', 'woocommerce' ), $desc_help_text ), 'type' => 'title', 'id' => 'email_notification_settings', ), @@ -207,6 +214,10 @@ class WC_Settings_Emails extends WC_Settings_Page { 'autoload' => false, ), + array( + 'type' => 'sectionend', + 'id' => 'email_merchant_notes', + ), ) ); diff --git a/includes/admin/settings/class-wc-settings-general.php b/includes/admin/settings/class-wc-settings-general.php index 8535dd7f956..1476c428c0c 100644 --- a/includes/admin/settings/class-wc-settings-general.php +++ b/includes/admin/settings/class-wc-settings-general.php @@ -81,7 +81,7 @@ class WC_Settings_General extends WC_Settings_Page { 'title' => __( 'Country / State', 'woocommerce' ), 'desc' => __( 'The country and state or province, if any, in which your business is located.', 'woocommerce' ), 'id' => 'woocommerce_default_country', - 'default' => 'GB', + 'default' => 'US:CA', 'type' => 'single_select_country', 'desc_tip' => true, ), @@ -229,7 +229,7 @@ class WC_Settings_General extends WC_Settings_Page { 'title' => __( 'Currency', 'woocommerce' ), 'desc' => __( 'This controls what currency prices are listed at in the catalog and which currency gateways will take payments in.', 'woocommerce' ), 'id' => 'woocommerce_currency', - 'default' => 'GBP', + 'default' => 'USD', 'type' => 'select', 'class' => 'wc-enhanced-select', 'desc_tip' => true, diff --git a/includes/admin/settings/class-wc-settings-integrations.php b/includes/admin/settings/class-wc-settings-integrations.php index b5859b6d155..12408d80185 100644 --- a/includes/admin/settings/class-wc-settings-integrations.php +++ b/includes/admin/settings/class-wc-settings-integrations.php @@ -2,17 +2,13 @@ /** * WooCommerce Integration Settings * - * @author WooThemes - * @category Admin - * @package WooCommerce\Admin - * @version 2.1.0 + * @package WooCommerce\Admin + * @version 2.1.0 */ use Automattic\Jetpack\Constants; -if ( ! defined( 'ABSPATH' ) ) { - exit; // Exit if accessed directly -} +defined( 'ABSPATH' ) || exit; if ( ! class_exists( 'WC_Settings_Integrations', false ) ) : @@ -50,7 +46,7 @@ if ( ! class_exists( 'WC_Settings_Integrations', false ) ) : $current_section = current( $integrations )->id; } - if ( sizeof( $integrations ) > 1 ) { + if ( count( $integrations ) > 1 ) { foreach ( $integrations as $integration ) { $title = empty( $integration->method_title ) ? ucfirst( $integration->id ) : $integration->method_title; $sections[ strtolower( $integration->id ) ] = esc_html( $title ); diff --git a/includes/admin/settings/class-wc-settings-page.php b/includes/admin/settings/class-wc-settings-page.php index b652182da23..ca359acfffb 100644 --- a/includes/admin/settings/class-wc-settings-page.php +++ b/includes/admin/settings/class-wc-settings-page.php @@ -2,14 +2,12 @@ /** * WooCommerce Settings Page/Tab * - * @author WooThemes - * @category Admin * @package WooCommerce\Admin * @version 2.1.0 */ if ( ! defined( 'ABSPATH' ) ) { - exit; // Exit if accessed directly + exit; // Exit if accessed directly. } if ( ! class_exists( 'WC_Settings_Page', false ) ) : @@ -66,7 +64,7 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) : /** * Add this page to settings. * - * @param array $pages + * @param array $pages The pages array to add this page to. * * @return mixed */ @@ -102,7 +100,7 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) : $sections = $this->get_sections(); - if ( empty( $sections ) || 1 === sizeof( $sections ) ) { + if ( empty( $sections ) || 1 === count( $sections ) ) { return; } @@ -111,7 +109,8 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) : $array_keys = array_keys( $sections ); foreach ( $sections as $id => $label ) { - echo '
  • ' . $label . ' ' . ( end( $array_keys ) == $id ? '' : '|' ) . '
  • '; + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo '
  • ' . esc_html( $label ) . ' ' . ( end( $array_keys ) === $id ? '' : '|' ) . '
  • '; } echo '
    '; diff --git a/includes/admin/settings/class-wc-settings-products.php b/includes/admin/settings/class-wc-settings-products.php index 8346e63c3ec..5bf11bde2dd 100644 --- a/includes/admin/settings/class-wc-settings-products.php +++ b/includes/admin/settings/class-wc-settings-products.php @@ -64,6 +64,12 @@ class WC_Settings_Products extends WC_Settings_Page { $settings = $this->get_settings( $current_section ); WC_Admin_Settings::save_fields( $settings ); + /* + * Product->Inventory has a setting `Out of stock visibility`. + * Because of this, we need to recount the terms to keep them in-sync. + */ + wc_recount_all_terms(); + if ( $current_section ) { do_action( 'woocommerce_update_options_' . $this->id . '_' . $current_section ); } diff --git a/includes/admin/settings/class-wc-settings-tax.php b/includes/admin/settings/class-wc-settings-tax.php index a80f2664cfb..0c45245ab51 100644 --- a/includes/admin/settings/class-wc-settings-tax.php +++ b/includes/admin/settings/class-wc-settings-tax.php @@ -2,8 +2,6 @@ /** * WooCommerce Tax Settings * - * @author WooThemes - * @category Admin * @package WooCommerce\Admin * @version 2.1.0 */ @@ -66,6 +64,7 @@ class WC_Settings_Tax extends WC_Settings_Page { $tax_classes = WC_Tax::get_tax_classes(); foreach ( $tax_classes as $class ) { + /* translators: $s tax rate section name */ $sections[ sanitize_title( $class ) ] = sprintf( __( '%s rates', 'woocommerce' ), $class ); } @@ -95,7 +94,7 @@ class WC_Settings_Tax extends WC_Settings_Page { $tax_classes = WC_Tax::get_tax_class_slugs(); - if ( 'standard' === $current_section || in_array( $current_section, $tax_classes, true ) ) { + if ( 'standard' === $current_section || in_array( $current_section, array_filter( $tax_classes ), true ) ) { $this->output_tax_rates(); } else { $settings = $this->get_settings(); @@ -149,7 +148,19 @@ class WC_Settings_Tax extends WC_Settings_Page { } foreach ( $added as $name ) { - WC_Tax::create_tax_class( $name ); + $tax_class = WC_Tax::create_tax_class( $name ); + + // Display any error that could be triggered while creating tax classes. + if ( is_wp_error( $tax_class ) ) { + WC_Admin_Settings::add_error( + sprintf( + /* translators: 1: tax class name 2: error message */ + esc_html__( 'Additional tax class "%1$s" couldn\'t be saved. %2$s.', 'woocommerce' ), + esc_html( $name ), + $tax_class->get_error_message() + ) + ); + } } return null; @@ -201,6 +212,7 @@ class WC_Settings_Tax extends WC_Settings_Page { 'wc_tax_nonce' => wp_create_nonce( 'wc_tax_nonce-class:' . $current_class ), 'base_url' => $base_url, 'rates' => array_values( WC_Tax::get_rates_for_tax_class( $current_class ) ), + // phpcs:ignore WordPress.Security.NonceVerification.Recommended 'page' => ! empty( $_GET['p'] ) ? absint( $_GET['p'] ) : 1, 'limit' => 100, 'countries' => $countries, @@ -278,6 +290,7 @@ class WC_Settings_Tax extends WC_Settings_Page { 'tax_rate_priority', ); + // phpcs:disable WordPress.Security.NonceVerification.Missing foreach ( $tax_rate_keys as $tax_rate_key ) { if ( isset( $_POST[ $tax_rate_key ], $_POST[ $tax_rate_key ][ $key ] ) ) { $tax_rate[ $tax_rate_key ] = wc_clean( wp_unslash( $_POST[ $tax_rate_key ][ $key ] ) ); @@ -288,6 +301,7 @@ class WC_Settings_Tax extends WC_Settings_Page { $tax_rate['tax_rate_shipping'] = isset( $_POST['tax_rate_shipping'][ $key ] ) ? 1 : 0; $tax_rate['tax_rate_order'] = $order; $tax_rate['tax_rate_class'] = $class; + // phpcs:enable WordPress.Security.NonceVerification.Missing return $tax_rate; } @@ -298,7 +312,8 @@ class WC_Settings_Tax extends WC_Settings_Page { public function save_tax_rates() { global $wpdb; - $current_class = sanitize_title( $this->get_current_tax_class() ); + $current_class = sanitize_title( $this->get_current_tax_class() ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.NonceVerification.Missing $posted_countries = wc_clean( wp_unslash( $_POST['tax_rate_country'] ) ); // get the tax rate id of the first submited row. @@ -310,13 +325,14 @@ class WC_Settings_Tax extends WC_Settings_Page { $index = isset( $tax_rate_order ) ? $tax_rate_order : 0; // Loop posted fields. + // phpcs:disable WordPress.Security.NonceVerification.Missing foreach ( $posted_countries as $key => $value ) { $mode = ( 0 === strpos( $key, 'new-' ) ) ? 'insert' : 'update'; $tax_rate = $this->get_posted_tax_rate( $key, $index ++, $current_class ); if ( 'insert' === $mode ) { $tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate ); - } elseif ( 1 === absint( $_POST['remove_tax_rate'][ $key ] ) ) { + } elseif ( isset( $_POST['remove_tax_rate'][ $key ] ) && 1 === absint( $_POST['remove_tax_rate'][ $key ] ) ) { $tax_rate_id = absint( $key ); WC_Tax::_delete_tax_rate( $tax_rate_id ); continue; @@ -332,6 +348,7 @@ class WC_Settings_Tax extends WC_Settings_Page { WC_Tax::_update_tax_rate_cities( $tax_rate_id, wc_clean( wp_unslash( $_POST['tax_rate_city'][ $key ] ) ) ); } } + // phpcs:enable WordPress.Security.NonceVerification.Missing } } diff --git a/includes/admin/views/html-admin-page-status-report.php b/includes/admin/views/html-admin-page-status-report.php index 421040eb023..1743566555d 100644 --- a/includes/admin/views/html-admin-page-status-report.php +++ b/includes/admin/views/html-admin-page-status-report.php @@ -514,7 +514,7 @@ $untested_plugins = $plugin_updates->get_untested_plugins( WC()->version, Cons : - + diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index 964c0e2a0bb..b35d97d6590 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -155,6 +155,7 @@ class WC_AJAX { 'json_search_downloadable_products_and_variations', 'json_search_customers', 'json_search_categories', + 'json_search_pages', 'term_ordering', 'product_ordering', 'refund_line_items', @@ -794,10 +795,14 @@ class WC_AJAX { $loop = intval( $_POST['loop'] ); $file_counter = 0; $order = wc_get_order( $order_id ); + $items = $order->get_items(); - foreach ( $product_ids as $product_id ) { - $product = wc_get_product( $product_id ); - $files = $product->get_downloads(); + foreach ( $items as $item ) { + $product = $item->get_product(); + if ( ! in_array( $product->get_id(), $product_ids, true ) ) { + continue; + } + $files = $product->get_downloads(); if ( ! $order->get_billing_email() ) { wp_die(); @@ -805,7 +810,7 @@ class WC_AJAX { if ( ! empty( $files ) ) { foreach ( $files as $download_id => $file ) { - $inserted_id = wc_downloadable_file_permission( $download_id, $product_id, $order ); + $inserted_id = wc_downloadable_file_permission( $download_id, $product->get_id(), $order, $item->get_quantity(), $item ); if ( $inserted_id ) { $download = new WC_Customer_Download( $inserted_id ); $loop ++; @@ -1766,6 +1771,47 @@ class WC_AJAX { wp_send_json( apply_filters( 'woocommerce_json_search_found_categories', $found_categories ) ); } + /** + * Ajax request handling for page searching. + */ + public static function json_search_pages() { + ob_start(); + + check_ajax_referer( 'search-pages', 'security' ); + + if ( ! current_user_can( 'manage_woocommerce' ) ) { + wp_die( -1 ); + } + + $search_text = isset( $_GET['term'] ) ? wc_clean( wp_unslash( $_GET['term'] ) ) : ''; + $limit = isset( $_GET['limit'] ) ? absint( wp_unslash( $_GET['limit'] ) ) : -1; + $exclude_ids = ! empty( $_GET['exclude'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) : array(); + + $args = array( + 'no_found_rows' => true, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'posts_per_page' => $limit, + 'post_type' => 'page', + 'post_status' => array( 'publish', 'private', 'draft' ), + 's' => $search_text, + 'post__not_in' => $exclude_ids, + ); + $search_results_query = new WP_Query( $args ); + + $pages_results = array(); + foreach ( $search_results_query->get_posts() as $post ) { + $pages_results[ $post->ID ] = sprintf( + /* translators: 1: page name 2: page ID */ + __( '%1$s (ID: %2$s)', 'woocommerce' ), + get_the_title( $post ), + $post->ID + ); + } + + wp_send_json( apply_filters( 'woocommerce_json_search_found_pages', $pages_results ) ); + } + /** * Ajax request handling for categories ordering. */ @@ -2301,6 +2347,32 @@ class WC_AJAX { } } + /** + * Bulk action - Set Low Stock Amount. + * + * @param array $variations List of variations. + * @param array $data Data to set. + * + * @used-by bulk_edit_variations + */ + private static function variation_bulk_action_variable_low_stock_amount( $variations, $data ) { + if ( ! isset( $data['value'] ) ) { + return; + } + + $low_stock_amount = wc_stock_amount( wc_clean( $data['value'] ) ); + + foreach ( $variations as $variation_id ) { + $variation = wc_get_product( $variation_id ); + if ( $variation->managing_stock() ) { + $variation->set_low_stock_amount( $low_stock_amount ); + } else { + $variation->set_low_stock_amount( '' ); + } + $variation->save(); + } + } + /** * Bulk action - Set Weight. * @@ -2547,6 +2619,7 @@ class WC_AJAX { * @uses WC_AJAX::variation_bulk_action_toggle_virtual() * @uses WC_AJAX::variation_bulk_action_toggle_downloadable() * @uses WC_AJAX::variation_bulk_action_toggle_enabled + * @uses WC_AJAX::variation_bulk_action_variable_low_stock_amount() */ public static function bulk_edit_variations() { ob_start(); diff --git a/includes/class-wc-cart-session.php b/includes/class-wc-cart-session.php index d8eb30ca8e9..50f2291a68c 100644 --- a/includes/class-wc-cart-session.php +++ b/includes/class-wc-cart-session.php @@ -175,6 +175,10 @@ final class WC_Cart_Session { if ( $update_cart_session || is_null( WC()->session->get( 'cart_totals', null ) ) ) { WC()->session->set( 'cart', $this->get_cart_for_session() ); $this->cart->calculate_totals(); + + if ( $merge_saved_cart ) { + $this->persistent_cart_update(); + } } // If this is a re-order, redirect to the cart page to get rid of the `order_again` query string. diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 1103ee053e5..594bb904670 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -1218,15 +1218,30 @@ class WC_Cart extends WC_Legacy_Cart { $products_qty_in_cart = $this->get_cart_item_quantities(); if ( isset( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] ) && ! $product_data->has_enough_stock( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] + $quantity ) ) { - throw new Exception( - sprintf( - '%s %s', - wc_get_cart_url(), - __( 'View cart', 'woocommerce' ), - /* translators: 1: quantity in stock 2: current quantity */ - sprintf( __( 'You cannot add that amount to the cart — we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $product_data->get_stock_quantity(), $product_data ), wc_format_stock_quantity_for_display( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ], $product_data ) ) - ) + $stock_quantity = $product_data->get_stock_quantity(); + $stock_quantity_in_cart = $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ]; + + $message = sprintf( + '%s %s', + wc_get_cart_url(), + __( 'View cart', 'woocommerce' ), + /* translators: 1: quantity in stock 2: current quantity */ + sprintf( __( 'You cannot add that amount to the cart — we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ), wc_format_stock_quantity_for_display( $stock_quantity_in_cart, $product_data ) ) ); + + /** + * Filters message about product not having enough stock accounting for what's already in the cart. + * + * @param string $message Message. + * @param WC_Product $product_data Product data. + * @param int $stock_quantity Quantity remaining. + * @param int $stock_quantity_in_cart + * + * @since 5.3.0 + */ + $message = apply_filters( 'woocommerce_cart_product_not_enough_stock_already_in_cart_message', $message, $product_data, $stock_quantity, $stock_quantity_in_cart ); + + throw new Exception( $message ); } } diff --git a/includes/class-wc-checkout.php b/includes/class-wc-checkout.php index 686846abe78..036c4ba612c 100644 --- a/includes/class-wc-checkout.php +++ b/includes/class-wc-checkout.php @@ -747,7 +747,7 @@ class WC_Checkout { $field_label = isset( $field['label'] ) ? $field['label'] : ''; if ( $validate_fieldset && - ( isset( $field['type'] ) && 'country' === $field['type'] ) && + ( isset( $field['type'] ) && 'country' === $field['type'] && '' !== $data[ $key ] ) && ! WC()->countries->country_exists( $data[ $key ] ) ) { /* translators: ISO 3166-1 alpha-2 country code */ $errors->add( $key . '_validation', sprintf( __( "'%s' is not a valid country code.", 'woocommerce' ), $data[ $key ] ) ); @@ -972,10 +972,13 @@ class WC_Checkout { // Redirect to success/confirmation/payment page. if ( isset( $result['result'] ) && 'success' === $result['result'] ) { + $result['order_id'] = $order_id; + $result = apply_filters( 'woocommerce_payment_successful_result', $result, $order_id ); if ( ! is_ajax() ) { - wp_safe_redirect( $result['redirect'] ); + // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect + wp_redirect( $result['redirect'] ); exit; } diff --git a/includes/class-wc-comments.php b/includes/class-wc-comments.php index a36be39d9c1..f83c30b2b8f 100644 --- a/includes/class-wc-comments.php +++ b/includes/class-wc-comments.php @@ -51,6 +51,9 @@ class WC_Comments { // Set comment type. add_action( 'preprocess_comment', array( __CLASS__, 'update_comment_type' ), 1 ); + + // Validate product reviews if requires verified owners. + add_action( 'pre_comment_on_post', array( __CLASS__, 'validate_product_review_verified_owners' ) ); } /** @@ -444,6 +447,36 @@ class WC_Comments { return $comment_data; } + /** + * Validate product reviews if requires a verified owner. + * + * @param int $comment_post_id Post ID. + */ + public static function validate_product_review_verified_owners( $comment_post_id ) { + // Only validate if option is enabled. + if ( 'yes' !== get_option( 'woocommerce_review_rating_verification_required' ) ) { + return; + } + + // Validate only products. + if ( 'product' !== get_post_type( $comment_post_id ) ) { + return; + } + + // Skip if is a verified owner. + if ( wc_customer_bought_product( '', get_current_user_id(), $comment_post_id ) ) { + return; + } + + wp_die( + esc_html__( 'Only logged in customers who have purchased this product may leave a review.', 'woocommerce' ), + esc_html__( 'Reviews can only be left by "verified owners"', 'woocommerce' ), + array( + 'code' => 403, + ) + ); + } + /** * Determines if a comment is of the default type. * diff --git a/includes/class-wc-customer.php b/includes/class-wc-customer.php index 3c5c53079f3..da549f99907 100644 --- a/includes/class-wc-customer.php +++ b/includes/class-wc-customer.php @@ -242,6 +242,27 @@ class WC_Customer extends WC_Legacy_Customer { return $this->get_calculated_shipping(); } + /** + * Indicates if the customer has a non-empty shipping address. + * + * Note that this does not indicate if the customer's shipping address + * is complete, only that one or more fields are populated. + * + * @since 5.3.0 + * + * @return bool + */ + public function has_shipping_address() { + foreach ( $this->get_shipping() as $address_field ) { + // Trim guards against a case where a subset of saved shipping address fields contain whitespace. + if ( strlen( trim( $address_field ) ) > 0 ) { + return true; + } + } + + return false; + } + /** * Get if customer is VAT exempt? * @@ -449,7 +470,19 @@ class WC_Customer extends WC_Legacy_Customer { * @return array */ public function get_billing( $context = 'view' ) { - return $this->get_prop( 'billing', $context ); + $value = null; + $prop = 'billing'; + + if ( array_key_exists( $prop, $this->data ) ) { + $changes = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : array(); + $value = array_merge( $this->data[ $prop ], $changes ); + + if ( 'view' === $context ) { + $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this ); + } + } + + return $value; } /** @@ -580,7 +613,19 @@ class WC_Customer extends WC_Legacy_Customer { * @return array */ public function get_shipping( $context = 'view' ) { - return $this->get_prop( 'shipping', $context ); + $value = null; + $prop = 'shipping'; + + if ( array_key_exists( $prop, $this->data ) ) { + $changes = array_key_exists( $prop, $this->changes ) ? $this->changes[ $prop ] : array(); + $value = array_merge( $this->data[ $prop ], $changes ); + + if ( 'view' === $context ) { + $value = apply_filters( $this->get_hook_prefix() . $prop, $value, $this ); + } + } + + return $value; } /** diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php index 374668efcf8..b182f6a6de0 100644 --- a/includes/class-wc-form-handler.php +++ b/includes/class-wc-form-handler.php @@ -440,6 +440,8 @@ class WC_Form_Handler { // Redirect to success/confirmation/payment page. if ( isset( $result['result'] ) && 'success' === $result['result'] ) { + $result['order_id'] = $order_id; + $result = apply_filters( 'woocommerce_payment_successful_result', $result, $order_id ); wp_redirect( $result['redirect'] ); //phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect diff --git a/includes/class-wc-frontend-scripts.php b/includes/class-wc-frontend-scripts.php index c84b60c094c..68ad242910f 100644 --- a/includes/class-wc-frontend-scripts.php +++ b/includes/class-wc-frontend-scripts.php @@ -224,7 +224,7 @@ class WC_Frontend_Scripts { 'selectWoo' => array( 'src' => self::get_asset_url( 'assets/js/selectWoo/selectWoo.full' . $suffix . '.js' ), 'deps' => array( 'jquery' ), - 'version' => '1.0.6', + 'version' => '1.0.9', ), 'wc-address-i18n' => array( 'src' => self::get_asset_url( 'assets/js/frontend/address-i18n' . $suffix . '.js' ), diff --git a/includes/class-wc-logger.php b/includes/class-wc-logger.php index 040dd580529..fed86dea651 100644 --- a/includes/class-wc-logger.php +++ b/includes/class-wc-logger.php @@ -139,11 +139,23 @@ class WC_Logger implements WC_Logger_Interface { } if ( $this->should_handle( $level ) ) { - $timestamp = current_time( 'timestamp', 1 ); - $message = apply_filters( 'woocommerce_logger_log_message', $message, $level, $context ); + $timestamp = time(); foreach ( $this->handlers as $handler ) { - $handler->handle( $timestamp, $level, $message, $context ); + /** + * Filter the logging message. Returning null will prevent logging from occuring since 5.3. + * + * @since 3.1 + * @param string $message Log message. + * @param string $level One of: emergency, alert, critical, error, warning, notice, info, or debug. + * @param array $context Additional information for log handlers. + * @param object $handler The handler object, such as WC_Log_Handler_File. Available since 5.3. + */ + $message = apply_filters( 'woocommerce_logger_log_message', $message, $level, $context, $handler ); + + if ( null !== $message ) { + $handler->handle( $timestamp, $level, $message, $context ); + } } } } diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index 516f905b10b..87bbc0b8835 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -179,7 +179,7 @@ class WC_Order extends WC_Abstract_Order { } if ( $total_refunded && $display_refunded ) { - $formatted_total = '' . wp_strip_all_tags( $formatted_total ) . ' ' . wc_price( $order_total - $total_refunded, array( 'currency' => $this->get_currency() ) ) . $tax_string . ''; + $formatted_total = ' ' . wc_price( $order_total - $total_refunded, array( 'currency' => $this->get_currency() ) ) . $tax_string . ''; } else { $formatted_total .= $tax_string; } diff --git a/includes/class-wc-session-handler.php b/includes/class-wc-session-handler.php index cacda9e59a2..0c6dda12484 100644 --- a/includes/class-wc-session-handler.php +++ b/includes/class-wc-session-handler.php @@ -75,7 +75,7 @@ class WC_Session_Handler extends WC_Session { add_action( 'wp_logout', array( $this, 'destroy_session' ) ); if ( ! is_user_logged_in() ) { - add_filter( 'nonce_user_logged_out', array( $this, 'nonce_user_logged_out' ) ); + add_filter( 'nonce_user_logged_out', array( $this, 'maybe_update_nonce_user_logged_out' ), 10, 2 ); } } @@ -187,6 +187,25 @@ class WC_Session_Handler extends WC_Session { return $customer_id; } + /** + * Get session unique ID for requests if session is initialized or user ID if logged in. + * Introduced to help with unit tests. + * + * @since 5.3.0 + * @return string + */ + public function get_customer_unique_id() { + $customer_id = ''; + + if ( $this->has_session() && $this->_customer_id ) { + $customer_id = $this->_customer_id; + } elseif ( is_user_logged_in() ) { + $customer_id = (string) get_current_user_id(); + } + + return $customer_id; + } + /** * Get the session cookie, if set. Otherwise return false. * @@ -288,13 +307,33 @@ class WC_Session_Handler extends WC_Session { /** * When a user is logged out, ensure they have a unique nonce by using the customer/session ID. * + * @deprecated 5.3.0 * @param int $uid User ID. - * @return string + * @return int|string */ public function nonce_user_logged_out( $uid ) { + wc_deprecated_function( 'WC_Session_Handler::nonce_user_logged_out', '5.3', 'WC_Session_Handler::maybe_update_nonce_user_logged_out' ); + return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid; } + /** + * When a user is logged out, ensure they have a unique nonce to manage cart and more using the customer/session ID. + * This filter runs everything `wp_verify_nonce()` and `wp_create_nonce()` gets called. + * + * @since 5.3.0 + * @param int $uid User ID. + * @param string $action The nonce action. + * @return int|string + */ + public function maybe_update_nonce_user_logged_out( $uid, $action ) { + if ( Automattic\WooCommerce\Utilities\StringUtil::starts_with( $action, 'woocommerce' ) ) { + return $this->has_session() && $this->_customer_id ? $this->_customer_id : $uid; + } + + return $uid; + } + /** * Cleanup session data from the database and clear caches. */ diff --git a/includes/class-wc-structured-data.php b/includes/class-wc-structured-data.php index 070bea646b0..1d3778864e7 100644 --- a/includes/class-wc-structured-data.php +++ b/includes/class-wc-structured-data.php @@ -198,7 +198,7 @@ class WC_Structured_Data { $markup = array( '@type' => 'Product', '@id' => $permalink . '#product', // Append '#product' to differentiate between this @id and the @id generated for the Breadcrumblist. - 'name' => $product->get_name(), + 'name' => wp_kses_post( $product->get_name() ), 'url' => $permalink, 'description' => wp_strip_all_tags( do_shortcode( $product->get_short_description() ? $product->get_short_description() : $product->get_description() ) ), ); @@ -477,7 +477,7 @@ class WC_Structured_Data { ), 'itemOffered' => array( '@type' => 'Product', - 'name' => apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, $is_visible ), + 'name' => wp_kses_post( apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, $is_visible ) ), 'sku' => $product_exists ? $product->get_sku() : '', 'image' => $product_exists ? wp_get_attachment_image_url( $product->get_image_id() ) : '', 'url' => $is_visible ? get_permalink( $product->get_id() ) : get_home_url(), diff --git a/includes/class-wc-tax.php b/includes/class-wc-tax.php index 65a4978cc2b..a59f60cc4de 100644 --- a/includes/class-wc-tax.php +++ b/includes/class-wc-tax.php @@ -815,6 +815,7 @@ class WC_Tax { $existing = self::get_tax_classes(); $existing_slugs = self::get_tax_class_slugs(); + $name = wc_clean( $name ); if ( in_array( $name, $existing, true ) ) { return new WP_Error( 'tax_class_exists', __( 'Tax class already exists', 'woocommerce' ) ); @@ -824,6 +825,11 @@ class WC_Tax { $slug = sanitize_title( $name ); } + // Stop if there's no slug. + if ( ! $slug ) { + return new WP_Error( 'tax_class_slug_invalid', __( 'Tax class slug is invalid', 'woocommerce' ) ); + } + if ( in_array( $slug, $existing_slugs, true ) ) { return new WP_Error( 'tax_class_slug_exists', __( 'Tax class slug already exists', 'woocommerce' ) ); } diff --git a/includes/class-wc-template-loader.php b/includes/class-wc-template-loader.php index 5a3233756fa..bdb728da65d 100644 --- a/includes/class-wc-template-loader.php +++ b/includes/class-wc-template-loader.php @@ -139,7 +139,7 @@ class WC_Template_Loader { if ( 0 === $validated_file ) { $templates[] = $page_template; } else { - error_log( "WooCommerce: Unable to validate template path: \"$page_template\". Error Code: $validated_file." ); + error_log( "WooCommerce: Unable to validate template path: \"$page_template\". Error Code: $validated_file." ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } } } @@ -294,8 +294,8 @@ class WC_Template_Loader { } // Description handling. - if ( ! empty( $queried_object->description ) && ( empty( $_GET['product-page'] ) || 1 === absint( $_GET['product-page'] ) ) ) { // WPCS: input var ok, CSRF ok. - $prefix = '
    ' . wc_format_content( $queried_object->description ) . '
    '; // WPCS: XSS ok. + if ( ! empty( $queried_object->description ) && ( empty( $_GET['product-page'] ) || 1 === absint( $_GET['product-page'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $prefix = '
    ' . wc_format_content( wp_kses_post( $queried_object->description ) ) . '
    '; } else { $prefix = ''; } diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 85a7c5bad71..8c74c11fd5b 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -9,6 +9,7 @@ defined( 'ABSPATH' ) || exit; use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster; +use Automattic\WooCommerce\Internal\AssignDefaultCategory; use Automattic\WooCommerce\Proxies\LegacyProxy; /** @@ -23,7 +24,7 @@ final class WooCommerce { * * @var string */ - public $version = '5.1.0'; + public $version = '5.3.0'; /** * WooCommerce Schema version. @@ -207,6 +208,7 @@ final class WooCommerce { // These classes set up hooks on instantiation. wc_get_container()->get( DownloadPermissionsAdjuster::class ); + wc_get_container()->get( AssignDefaultCategory::class ); } /** diff --git a/includes/customizer/class-wc-shop-customizer.php b/includes/customizer/class-wc-shop-customizer.php index 2d633188612..f17cac7dfea 100644 --- a/includes/customizer/class-wc-shop-customizer.php +++ b/includes/customizer/class-wc-shop-customizer.php @@ -6,6 +6,8 @@ * @package WooCommerce */ +use Automattic\WooCommerce\Internal\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -13,10 +15,19 @@ defined( 'ABSPATH' ) || exit; */ class WC_Shop_Customizer { + /** + * Holds the instance of ThemeSupport to use. + * + * @var ThemeSupport $theme_support The instance of ThemeSupport to use. + */ + private $theme_support; + /** * Constructor. */ public function __construct() { + $this->theme_support = wc_get_container()->get( ThemeSupport::class ); + add_action( 'customize_register', array( $this, 'add_sections' ) ); add_action( 'customize_controls_print_styles', array( $this, 'add_styles' ) ); add_action( 'customize_controls_print_scripts', array( $this, 'add_scripts' ), 30 ); @@ -545,11 +556,11 @@ class WC_Shop_Customizer { ) ); - if ( ! wc_get_theme_support( 'single_image_width' ) ) { + if ( ! $this->theme_support->has_option( 'single_image_width', false ) ) { $wp_customize->add_setting( 'woocommerce_single_image_width', array( - 'default' => 600, + 'default' => $this->theme_support->get_option( 'single_image_width', 600 ), 'type' => 'option', 'capability' => 'manage_woocommerce', 'sanitize_callback' => 'absint', @@ -573,11 +584,11 @@ class WC_Shop_Customizer { ); } - if ( ! wc_get_theme_support( 'thumbnail_image_width' ) ) { + if ( ! $this->theme_support->has_option( 'thumbnail_image_width', false ) ) { $wp_customize->add_setting( 'woocommerce_thumbnail_image_width', array( - 'default' => 300, + 'default' => $this->theme_support->get_option( 'thumbnail_image_width', 300 ), 'type' => 'option', 'capability' => 'manage_woocommerce', 'sanitize_callback' => 'absint', @@ -769,7 +780,7 @@ class WC_Shop_Customizer { ); } else { $choose_pages = array( - 'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ), + 'woocommerce_terms_page_id' => __( 'Terms and conditions', 'woocommerce' ), ); } $pages = get_pages( diff --git a/includes/data-stores/class-wc-customer-data-store-session.php b/includes/data-stores/class-wc-customer-data-store-session.php index 4a2585d20ca..9b5d66ad4f9 100644 --- a/includes/data-stores/class-wc-customer-data-store-session.php +++ b/includes/data-stores/class-wc-customer-data-store-session.php @@ -126,12 +126,13 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust protected function set_defaults( &$customer ) { try { $default = wc_get_customer_default_location(); + $has_shipping_address = $customer->has_shipping_address(); if ( ! $customer->get_billing_country() ) { $customer->set_billing_country( $default['country'] ); } - if ( ! $customer->get_shipping_country() ) { + if ( ! $customer->get_shipping_country() && ! $has_shipping_address ) { $customer->set_shipping_country( $customer->get_billing_country() ); } @@ -139,7 +140,7 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust $customer->set_billing_state( $default['state'] ); } - if ( ! $customer->get_shipping_state() ) { + if ( ! $customer->get_shipping_state() && ! $has_shipping_address ) { $customer->set_shipping_state( $customer->get_billing_state() ); } diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index ed626f9a9b3..8a71ed6de96 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -710,6 +710,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da if ( $force || array_key_exists( 'shipping_class_id', $changes ) ) { wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false ); } + + _wc_recount_terms_by_product( $product->get_id() ); } /** diff --git a/includes/data-stores/class-wc-product-variation-data-store-cpt.php b/includes/data-stores/class-wc-product-variation-data-store-cpt.php index 534ba382ef8..cf481afc41a 100644 --- a/includes/data-stores/class-wc-product-variation-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-variation-data-store-cpt.php @@ -357,6 +357,7 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl 'date_on_sale_to' => get_post_meta( $id, '_sale_price_dates_to', true ), 'manage_stock' => get_post_meta( $id, '_manage_stock', true ), 'stock_status' => get_post_meta( $id, '_stock_status', true ), + 'low_stock_amount' => get_post_meta( $id, '_low_stock_amount', true ), 'shipping_class_id' => current( $this->get_term_ids( $id, 'product_shipping_class' ) ), 'virtual' => get_post_meta( $id, '_virtual', true ), 'downloadable' => get_post_meta( $id, '_downloadable', true ), @@ -404,7 +405,6 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl 'sku' => get_post_meta( $product->get_parent_id(), '_sku', true ), 'manage_stock' => get_post_meta( $product->get_parent_id(), '_manage_stock', true ), 'backorders' => get_post_meta( $product->get_parent_id(), '_backorders', true ), - 'low_stock_amount' => get_post_meta( $product->get_parent_id(), '_low_stock_amount', true ), 'stock_quantity' => wc_stock_amount( get_post_meta( $product->get_parent_id(), '_stock', true ) ), 'weight' => get_post_meta( $product->get_parent_id(), '_weight', true ), 'length' => get_post_meta( $product->get_parent_id(), '_length', true ), diff --git a/includes/emails/class-wc-email-new-order.php b/includes/emails/class-wc-email-new-order.php index 9cc757495e3..f4b2f383d5f 100644 --- a/includes/emails/class-wc-email-new-order.php +++ b/includes/emails/class-wc-email-new-order.php @@ -100,7 +100,7 @@ if ( ! class_exists( 'WC_Email_New_Order' ) ) : * Controls if new order emails can be resend multiple times. * * @since 5.0.0 - * @param bool $allows Defaults to true. + * @param bool $allows Defaults to false. */ if ( 'true' === $email_already_sent && ! apply_filters( 'woocommerce_new_order_email_allows_resend', false ) ) { return; diff --git a/includes/gateways/bacs/class-wc-gateway-bacs.php b/includes/gateways/bacs/class-wc-gateway-bacs.php index 3ec175dfd59..b93dd40316f 100644 --- a/includes/gateways/bacs/class-wc-gateway-bacs.php +++ b/includes/gateways/bacs/class-wc-gateway-bacs.php @@ -37,7 +37,7 @@ class WC_Gateway_BACS extends WC_Payment_Gateway { $this->icon = apply_filters( 'woocommerce_bacs_icon', '' ); $this->has_fields = false; $this->method_title = __( 'Direct bank transfer', 'woocommerce' ); - $this->method_description = __( 'Take payments in person via BACS. More commonly known as direct bank/wire transfer', 'woocommerce' ); + $this->method_description = __( 'Take payments in person via BACS. More commonly known as direct bank/wire transfer.', 'woocommerce' ); // Load the settings. $this->init_form_fields(); diff --git a/includes/gateways/cod/class-wc-gateway-cod.php b/includes/gateways/cod/class-wc-gateway-cod.php index 33c17643bf0..f2bb24aa48e 100644 --- a/includes/gateways/cod/class-wc-gateway-cod.php +++ b/includes/gateways/cod/class-wc-gateway-cod.php @@ -132,7 +132,7 @@ class WC_Gateway_COD extends WC_Payment_Gateway { $order = wc_get_order( $order_id ); // Test if order needs shipping. - if ( 0 < count( $order->get_items() ) ) { + if ( $order && 0 < count( $order->get_items() ) ) { foreach ( $order->get_items() as $item ) { $_product = $item->get_product(); if ( $_product && $_product->needs_shipping() ) { diff --git a/includes/log-handlers/class-wc-log-handler-file.php b/includes/log-handlers/class-wc-log-handler-file.php index 0fc59a36a2d..49347b5a259 100644 --- a/includes/log-handlers/class-wc-log-handler-file.php +++ b/includes/log-handlers/class-wc-log-handler-file.php @@ -146,10 +146,12 @@ class WC_Log_Handler_File extends WC_Log_Handler { if ( $file ) { if ( ! file_exists( $file ) ) { $temphandle = @fopen( $file, 'w+' ); // @codingStandardsIgnoreLine. - @fclose( $temphandle ); // @codingStandardsIgnoreLine. + if ( is_resource( $temphandle ) ) { + @fclose( $temphandle ); // @codingStandardsIgnoreLine. - if ( Constants::is_defined( 'FS_CHMOD_FILE' ) ) { - @chmod( $file, FS_CHMOD_FILE ); // @codingStandardsIgnoreLine. + if ( Constants::is_defined( 'FS_CHMOD_FILE' ) ) { + @chmod( $file, FS_CHMOD_FILE ); // @codingStandardsIgnoreLine. + } } } diff --git a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php index add78763cf2..19ba9b67ff5 100644 --- a/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php +++ b/includes/rest-api/Controllers/Version1/class-wc-rest-taxes-v1-controller.php @@ -4,8 +4,6 @@ * * Handles requests to the /taxes endpoint. * - * @author WooThemes - * @category API * @package WooCommerce\RestApi * @since 3.0.0 */ @@ -40,67 +38,79 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { * Register the routes for taxes. */ public function register_routes() { - register_rest_route( $this->namespace, '/' . $this->rest_base, array( + register_rest_route( + $this->namespace, + '/' . $this->rest_base, array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - 'args' => $this->get_collection_params(), - ), - array( - 'methods' => WP_REST_Server::CREATABLE, - 'callback' => array( $this, 'create_item' ), - 'permission_callback' => array( $this, 'create_item_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) ); + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); - register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)', array( - 'args' => array( - 'id' => array( - 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), - 'type' => 'integer', - ), - ), + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), - 'args' => array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), - ), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'update_item' ), - 'permission_callback' => array( $this, 'update_item_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ), - array( - 'methods' => WP_REST_Server::DELETABLE, - 'callback' => array( $this, 'delete_item' ), - 'permission_callback' => array( $this, 'delete_item_permissions_check' ), - 'args' => array( - 'force' => array( - 'default' => false, - 'type' => 'boolean', - 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ), + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), + 'type' => 'integer', ), ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) ); + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'args' => array( + 'force' => array( + 'default' => false, + 'type' => 'boolean', + 'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ), + ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); - register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array( + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/batch', array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'batch_items' ), - 'permission_callback' => array( $this, 'batch_items_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ), - 'schema' => array( $this, 'get_public_batch_schema' ), - ) ); + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'batch_items' ), + 'permission_callback' => array( $this, 'batch_items_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + 'schema' => array( $this, 'get_public_batch_schema' ), + ) + ); } /** @@ -200,7 +210,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { public function get_items( $request ) { global $wpdb; - $prepared_args = array(); + $prepared_args = array(); $prepared_args['order'] = $request['order']; $prepared_args['number'] = $request['per_page']; if ( ! empty( $request['offset'] ) ) { @@ -208,9 +218,10 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { } else { $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; } - $orderby_possibles = array( - 'id' => 'tax_rate_id', - 'order' => 'tax_rate_order', + $orderby_possibles = array( + 'id' => 'tax_rate_id', + 'order' => 'tax_rate_order', + 'priority' => 'tax_rate_priority', ); $prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ]; $prepared_args['class'] = $request['class']; @@ -223,30 +234,42 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { */ $prepared_args = apply_filters( 'woocommerce_rest_tax_query', $prepared_args, $request ); - $query = " + $orderby = sanitize_key( $prepared_args['orderby'] ) . ' ' . sanitize_key( $prepared_args['order'] ); + $query = " SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates - WHERE 1 = 1 + %s + ORDER BY {$orderby} + LIMIT %%d, %%d "; + $wpdb_prepare_args = array( + $prepared_args['offset'], + $prepared_args['number'], + ); + // Filter by tax class. - if ( ! empty( $prepared_args['class'] ) ) { + if ( empty( $prepared_args['class'] ) ) { + $query = sprintf( $query, '' ); + } else { $class = 'standard' !== $prepared_args['class'] ? sanitize_title( $prepared_args['class'] ) : ''; - $query .= " AND tax_rate_class = '$class'"; + array_unshift( $wpdb_prepare_args, $class ); + $query = sprintf( $query, 'WHERE tax_rate_class = %s' ); } - // Order tax rates. - $order_by = sprintf( ' ORDER BY %s', sanitize_key( $prepared_args['orderby'] ) ); - - // Pagination. - $pagination = sprintf( ' LIMIT %d, %d', $prepared_args['offset'], $prepared_args['number'] ); - // Query taxes. - $results = $wpdb->get_results( $query . $order_by . $pagination ); + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared + $results = $wpdb->get_results( + $wpdb->prepare( + $query, + $wpdb_prepare_args + ) + ); + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared $taxes = array(); foreach ( $results as $tax ) { - $data = $this->prepare_item_for_response( $tax, $request ); + $data = $this->prepare_item_for_response( $tax, $request ); $taxes[] = $this->prepare_response_for_collection( $data ); } @@ -254,10 +277,18 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { // Store pagination values for headers then unset for count query. $per_page = (int) $prepared_args['number']; - $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); + $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); // Query only for ids. - $wpdb->get_results( str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ) ); + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared + $query = str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ); + $wpdb->get_results( + $wpdb->prepare( + $query, + $wpdb_prepare_args + ) + ); + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared // Calculate totals. $total_taxes = (int) $wpdb->num_rows; @@ -287,13 +318,13 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { * Take tax data from the request and return the updated or newly created rate. * * @param WP_REST_Request $request Full details about the request. - * @param stdClass|null $current Existing tax object. + * @param stdClass|null $current Existing tax object. * @return object */ protected function create_or_update_tax( $request, $current = null ) { - $id = absint( isset( $request['id'] ) ? $request['id'] : 0 ); - $data = array(); - $fields = array( + $id = absint( isset( $request['id'] ) ? $request['id'] : 0 ); + $data = array(); + $fields = array( 'tax_rate_country', 'tax_rate_state', 'tax_rate', @@ -321,25 +352,25 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { // Add to data array. switch ( $key ) { - case 'tax_rate_priority' : - case 'tax_rate_compound' : - case 'tax_rate_shipping' : - case 'tax_rate_order' : + case 'tax_rate_priority': + case 'tax_rate_compound': + case 'tax_rate_shipping': + case 'tax_rate_order': $data[ $field ] = absint( $request[ $key ] ); break; - case 'tax_rate_class' : + case 'tax_rate_class': $data[ $field ] = 'standard' !== $request['tax_rate_class'] ? $request['tax_rate_class'] : ''; break; - default : + default: $data[ $field ] = wc_clean( $request[ $key ] ); break; } } - if ( $id ) { - WC_Tax::_update_tax_rate( $id, $data ); - } else { + if ( ! $id ) { $id = WC_Tax::_insert_tax_rate( $data ); + } elseif ( $data ) { + WC_Tax::_update_tax_rate( $id, $data ); } // Add locales. @@ -487,13 +518,12 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { /** * Prepare a single tax output for response. * - * @param stdClass $tax Tax object. + * @param stdClass $tax Tax object. * @param WP_REST_Request $request Request object. + * * @return WP_REST_Response $response Response data. */ public function prepare_item_for_response( $tax, $request ) { - global $wpdb; - $id = (int) $tax->tax_rate_id; $data = array( 'id' => $id, @@ -510,18 +540,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { 'class' => $tax->tax_rate_class ? $tax->tax_rate_class : 'standard', ); - // Get locales from a tax rate. - $locales = $wpdb->get_results( $wpdb->prepare( " - SELECT location_code, location_type - FROM {$wpdb->prefix}woocommerce_tax_rate_locations - WHERE tax_rate_id = %d - ", $id ) ); - - if ( ! is_wp_error( $tax ) && ! is_null( $tax ) ) { - foreach ( $locales as $locale ) { - $data[ $locale->location_type ] = $locale->location_code; - } - } + $data = $this->add_tax_rate_locales( $data, $tax ); $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); @@ -550,7 +569,7 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { */ protected function prepare_links( $tax ) { $links = array( - 'self' => array( + 'self' => array( 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $tax->tax_rate_id ) ), ), 'collection' => array( @@ -561,6 +580,38 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { return $links; } + /** + * Add tax rate locales to the response array. + * + * @param array $data Response data. + * @param stdClass $tax Tax object. + * + * @return array + */ + protected function add_tax_rate_locales( $data, $tax ) { + global $wpdb; + + // Get locales from a tax rate. + $locales = $wpdb->get_results( + $wpdb->prepare( + " + SELECT location_code, location_type + FROM {$wpdb->prefix}woocommerce_tax_rate_locations + WHERE tax_rate_id = %d + ", + $tax->tax_rate_id + ) + ); + + if ( ! is_wp_error( $tax ) && ! is_null( $tax ) ) { + foreach ( $locales as $locale ) { + $data[ $locale->location_type ] = $locale->location_code; + } + } + + return $data; + } + /** * Get the Taxes schema, conforming to JSON Schema. * @@ -572,18 +623,18 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { 'title' => 'tax', 'type' => 'object', 'properties' => array( - 'id' => array( + 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'country' => array( + 'country' => array( 'description' => __( 'Country ISO 3166 code.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'state' => array( + 'state' => array( 'description' => __( 'State code.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), @@ -593,17 +644,17 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'city' => array( + 'city' => array( 'description' => __( 'City name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'rate' => array( + 'rate' => array( 'description' => __( 'Tax rate.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), - 'name' => array( + 'name' => array( 'description' => __( 'Tax rate name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), @@ -626,12 +677,12 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { 'default' => true, 'context' => array( 'view', 'edit' ), ), - 'order' => array( + 'order' => array( 'description' => __( 'Indicates the order that will appear in queries.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ), - 'class' => array( + 'class' => array( 'description' => __( 'Tax class.', 'woocommerce' ), 'type' => 'string', 'default' => 'standard', @@ -654,54 +705,55 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { $params['context'] = $this->get_context_param(); $params['context']['default'] = 'view'; - $params['page'] = array( - 'description' => __( 'Current page of the collection.', 'woocommerce' ), - 'type' => 'integer', - 'default' => 1, - 'sanitize_callback' => 'absint', - 'validate_callback' => 'rest_validate_request_arg', - 'minimum' => 1, + $params['page'] = array( + 'description' => __( 'Current page of the collection.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 1, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + 'minimum' => 1, ); $params['per_page'] = array( - 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), - 'type' => 'integer', - 'default' => 10, - 'minimum' => 1, - 'maximum' => 100, - 'sanitize_callback' => 'absint', - 'validate_callback' => 'rest_validate_request_arg', + 'description' => __( 'Maximum number of items to be returned in result set.', 'woocommerce' ), + 'type' => 'integer', + 'default' => 10, + 'minimum' => 1, + 'maximum' => 100, + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', ); - $params['offset'] = array( - 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), - 'type' => 'integer', - 'sanitize_callback' => 'absint', - 'validate_callback' => 'rest_validate_request_arg', + $params['offset'] = array( + 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', ); - $params['order'] = array( - 'default' => 'asc', - 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), - 'enum' => array( 'asc', 'desc' ), - 'sanitize_callback' => 'sanitize_key', - 'type' => 'string', - 'validate_callback' => 'rest_validate_request_arg', + $params['order'] = array( + 'default' => 'asc', + 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), + 'enum' => array( 'asc', 'desc' ), + 'sanitize_callback' => 'sanitize_key', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', ); - $params['orderby'] = array( - 'default' => 'order', - 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), - 'enum' => array( + $params['orderby'] = array( + 'default' => 'order', + 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), + 'enum' => array( 'id', 'order', + 'priority', ), - 'sanitize_callback' => 'sanitize_key', - 'type' => 'string', - 'validate_callback' => 'rest_validate_request_arg', + 'sanitize_callback' => 'sanitize_key', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', ); - $params['class'] = array( - 'description' => __( 'Sort by tax class.', 'woocommerce' ), - 'enum' => array_merge( array( 'standard' ), WC_Tax::get_tax_class_slugs() ), - 'sanitize_callback' => 'sanitize_title', - 'type' => 'string', - 'validate_callback' => 'rest_validate_request_arg', + $params['class'] = array( + 'description' => __( 'Sort by tax class.', 'woocommerce' ), + 'enum' => array_merge( array( 'standard' ), WC_Tax::get_tax_class_slugs() ), + 'sanitize_callback' => 'sanitize_title', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', ); return $params; diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php index 7d06b2573d2..2e80879f194 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php @@ -766,6 +766,9 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller { case 'backordered': $base_data['backordered'] = $product->is_on_backorder(); break; + case 'low_stock_amount': + $base_data['low_stock_amount'] = '' === $product->get_low_stock_amount() ? null : $product->get_low_stock_amount(); + break; case 'sold_individually': $base_data['sold_individually'] = $product->is_sold_individually(); break; diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php index 06838eb5469..19f212da2b6 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-tools-v2-controller.php @@ -504,22 +504,7 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller { break; case 'recount_terms': - $product_cats = get_terms( - 'product_cat', - array( - 'hide_empty' => false, - 'fields' => 'id=>parent', - ) - ); - _wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), true, false ); - $product_tags = get_terms( - 'product_tag', - array( - 'hide_empty' => false, - 'fields' => 'id=>parent', - ) - ); - _wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), true, false ); + wc_recount_all_terms(); $message = __( 'Terms successfully recounted', 'woocommerce' ); break; diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php index 0517a6a18ab..d61a52cdcb3 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php @@ -34,31 +34,58 @@ class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller { * @return bool */ protected function calculate_coupons( $request, $order ) { - if ( ! isset( $request['coupon_lines'] ) || ! is_array( $request['coupon_lines'] ) ) { + if ( ! isset( $request['coupon_lines'] ) ) { return false; } - // Remove all coupons first to ensure calculation is correct. - foreach ( $order->get_items( 'coupon' ) as $coupon ) { - $order->remove_coupon( $coupon->get_code() ); - } + // Validate input and at the same time store the processed coupon codes to apply. + + $coupon_codes = array(); + $discounts = new WC_Discounts( $order ); + + $current_order_coupons = array_values( $order->get_coupons() ); + $current_order_coupon_codes = array_map( + function( $coupon ) { + return $coupon->get_code(); + }, + $current_order_coupons + ); foreach ( $request['coupon_lines'] as $item ) { - if ( is_array( $item ) ) { - if ( ! empty( $item['id'] ) ) { - throw new WC_REST_Exception( 'woocommerce_rest_coupon_item_id_readonly', __( 'Coupon item ID is readonly.', 'woocommerce' ), 400 ); - } + if ( ! empty( $item['id'] ) ) { + throw new WC_REST_Exception( 'woocommerce_rest_coupon_item_id_readonly', __( 'Coupon item ID is readonly.', 'woocommerce' ), 400 ); + } - if ( empty( $item['code'] ) ) { - throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 ); - } + if ( empty( $item['code'] ) ) { + throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 ); + } - $results = $order->apply_coupon( wc_clean( $item['code'] ) ); + $coupon_code = wc_format_coupon_code( wc_clean( $item['code'] ) ); + $coupon = new WC_Coupon( $coupon_code ); - if ( is_wp_error( $results ) ) { - throw new WC_REST_Exception( 'woocommerce_rest_' . $results->get_error_code(), $results->get_error_message(), 400 ); + // Skip check if the coupon is already applied to the order, as this could wrongly throw an error for single-use coupons. + if ( ! in_array( $coupon_code, $current_order_coupon_codes, true ) ) { + $check_result = $discounts->is_coupon_valid( $coupon ); + if ( is_wp_error( $check_result ) ) { + throw new WC_REST_Exception( 'woocommerce_rest_' . $check_result->get_error_code(), $check_result->get_error_message(), 400 ); } } + + $coupon_codes[] = $coupon_code; + } + + // Remove all coupons first to ensure calculation is correct. + foreach ( $order->get_items( 'coupon' ) as $existing_coupon ) { + $order->remove_coupon( $existing_coupon->get_code() ); + } + + // Apply the coupons. + foreach ( $coupon_codes as $new_coupon ) { + $results = $order->apply_coupon( $new_coupon ); + + if ( is_wp_error( $results ) ) { + throw new WC_REST_Exception( 'woocommerce_rest_' . $results->get_error_code(), $results->get_error_message(), 400 ); + } } return true; diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php index 417804ff383..9d2000b23eb 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-product-variations-controller.php @@ -65,6 +65,7 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V 'backorders' => $object->get_backorders(), 'backorders_allowed' => $object->backorders_allowed(), 'backordered' => $object->is_on_backorder(), + 'low_stock_amount' => '' === $object->get_low_stock_amount() ? null : $object->get_low_stock_amount(), 'weight' => $object->get_weight(), 'dimensions' => array( 'length' => $object->get_length(), @@ -185,9 +186,18 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V $stock_quantity += wc_stock_amount( $request['inventory_delta'] ); $variation->set_stock_quantity( $stock_quantity ); } + // isset() returns false for value null, thus we need to check whether the value has been sent by the request. + if ( array_key_exists( 'low_stock_amount', $request->get_params() ) ) { + if ( null === $request['low_stock_amount'] ) { + $variation->set_low_stock_amount( '' ); + } else { + $variation->set_low_stock_amount( wc_stock_amount( $request['low_stock_amount'] ) ); + } + } } else { $variation->set_backorders( 'no' ); $variation->set_stock_quantity( '' ); + $variation->set_low_stock_amount( '' ); } // Regular Price. @@ -597,6 +607,11 @@ class WC_REST_Product_Variations_Controller extends WC_REST_Product_Variations_V 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'low_stock_amount' => array( + 'description' => __( 'Low Stock amount for the variation.', 'woocommerce' ), + 'type' => array( 'integer', 'null' ), + 'context' => array( 'view', 'edit' ), + ), 'weight' => array( /* translators: %s: weight unit */ 'description' => sprintf( __( 'Variation weight (%s).', 'woocommerce' ), $weight_unit ), diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php index bad0b9bca14..00966cedc30 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php @@ -551,11 +551,22 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller { $stock_quantity += wc_stock_amount( $request['inventory_delta'] ); $product->set_stock_quantity( wc_stock_amount( $stock_quantity ) ); } + + // Low stock amount. + // isset() returns false for value null, thus we need to check whether the value has been sent by the request. + if ( array_key_exists( 'low_stock_amount', $request->get_params() ) ) { + if ( null === $request['low_stock_amount'] ) { + $product->set_low_stock_amount( '' ); + } else { + $product->set_low_stock_amount( wc_stock_amount( $request['low_stock_amount'] ) ); + } + } } else { // Don't manage stock. $product->set_manage_stock( 'no' ); $product->set_stock_quantity( '' ); $product->set_stock_status( $stock_status ); + $product->set_low_stock_amount( '' ); } } elseif ( ! $product->is_type( 'variable' ) ) { $product->set_stock_status( $stock_status ); @@ -985,6 +996,11 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller { 'context' => array( 'view', 'edit' ), 'readonly' => true, ), + 'low_stock_amount' => array( + 'description' => __( 'Low Stock amount for the product.', 'woocommerce' ), + 'type' => array( 'integer', 'null' ), + 'context' => array( 'view', 'edit' ), + ), 'sold_individually' => array( 'description' => __( 'Allow one item to be bought in a single order.', 'woocommerce' ), 'type' => 'boolean', diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php index 32b59d0771f..f03234e0633 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-shipping-zone-methods-controller.php @@ -24,4 +24,20 @@ class WC_REST_Shipping_Zone_Methods_Controller extends WC_REST_Shipping_Zone_Met * @var string */ protected $namespace = 'wc/v3'; + + /** + * Get the settings schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + // Get parent schema to append additional supported settings types for shipping zone method. + $schema = parent::get_item_schema(); + + // Append additional settings supported types (class, order). + $schema['properties']['settings']['properties']['type']['enum'][] = 'class'; + $schema['properties']['settings']['properties']['type']['enum'][] = 'order'; + + return $this->add_additional_fields_schema( $schema ); + } } diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php index 516aee8dfb7..6fef3703eb6 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-taxes-controller.php @@ -24,4 +24,118 @@ class WC_REST_Taxes_Controller extends WC_REST_Taxes_V2_Controller { * @var string */ protected $namespace = 'wc/v3'; + + /** + * Add tax rate locales to the response array. + * + * @param array $data Response data. + * @param stdClass $tax Tax object. + * + * @return array + */ + protected function add_tax_rate_locales( $data, $tax ) { + global $wpdb; + + $data = parent::add_tax_rate_locales( $data, $tax ); + $data['postcodes'] = array(); + $data['cities'] = array(); + + // Get locales from a tax rate. + $locales = $wpdb->get_results( + $wpdb->prepare( + " + SELECT location_code, location_type + FROM {$wpdb->prefix}woocommerce_tax_rate_locations + WHERE tax_rate_id = %d + ", + $tax->tax_rate_id + ) + ); + + if ( ! is_wp_error( $tax ) && ! is_null( $tax ) ) { + foreach ( $locales as $locale ) { + if ( 'postcode' === $locale->location_type ) { + $data['postcodes'][] = $locale->location_code; + } elseif ( 'city' === $locale->location_type ) { + $data['cities'][] = $locale->location_code; + } + } + } + + return $data; + } + + /** + * Get the taxes schema, conforming to JSON Schema. + * + * @return array + */ + public function get_item_schema() { + $schema = parent::get_item_schema(); + + $schema['properties']['postcodes'] = array( + 'description' => __( 'List of postcodes / ZIPs. Introduced in WooCommerce 5.3.', 'woocommerce' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + 'context' => array( 'view', 'edit' ), + ); + + $schema['properties']['cities'] = array( + 'description' => __( 'List of city names. Introduced in WooCommerce 5.3.', 'woocommerce' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + 'context' => array( 'view', 'edit' ), + ); + + $schema['properties']['postcode']['description'] = + __( "Postcode/ZIP, it doesn't support multiple values. Deprecated as of WooCommerce 5.3, 'postcodes' should be used instead.", 'woocommerce' ); + + $schema['properties']['city']['description'] = + __( "City name, it doesn't support multiple values. Deprecated as of WooCommerce 5.3, 'cities' should be used instead.", 'woocommerce' ); + + return $schema; + } + + /** + * Create a single tax. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response The response, or an error. + */ + public function create_item( $request ) { + $this->adjust_cities_and_postcodes( $request ); + + return parent::create_item( $request ); + } + + /** + * Update a single tax. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response The response, or an error. + */ + public function update_item( $request ) { + $this->adjust_cities_and_postcodes( $request ); + + return parent::update_item( $request ); + } + + /** + * Convert array "cities" and "postcodes" parameters + * into semicolon-separated strings "city" and "postcode". + * + * @param WP_REST_Request $request The request to adjust. + */ + private function adjust_cities_and_postcodes( &$request ) { + if ( isset( $request['cities'] ) ) { + $request['city'] = join( ';', $request['cities'] ); + } + if ( isset( $request['postcodes'] ) ) { + $request['postcode'] = join( ';', $request['postcodes'] ); + } + } } diff --git a/includes/rest-api/Controllers/Version3/class-wc-rest-terms-controller.php b/includes/rest-api/Controllers/Version3/class-wc-rest-terms-controller.php index f434ef60b00..40423decb89 100644 --- a/includes/rest-api/Controllers/Version3/class-wc-rest-terms-controller.php +++ b/includes/rest-api/Controllers/Version3/class-wc-rest-terms-controller.php @@ -10,6 +10,8 @@ if ( ! defined( 'ABSPATH' ) ) { exit; } +use Automattic\WooCommerce\Internal\AssignDefaultCategory; + /** * Terms controller class. */ @@ -563,6 +565,9 @@ abstract class WC_REST_Terms_Controller extends WC_REST_Controller { return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) ); } + // Schedule action to assign default category. + wc_get_container()->get( AssignDefaultCategory::class )->schedule_action(); + /** * Fires after a single term is deleted via the REST API. * diff --git a/includes/shortcodes/class-wc-shortcode-products.php b/includes/shortcodes/class-wc-shortcode-products.php index d0d089f905a..25ac8223eda 100644 --- a/includes/shortcodes/class-wc-shortcode-products.php +++ b/includes/shortcodes/class-wc-shortcode-products.php @@ -84,7 +84,7 @@ class WC_Shortcode_Products { * Get shortcode type. * * @since 3.2.0 - * @return array + * @return string */ public function get_type() { return $this->type; diff --git a/includes/theme-support/class-wc-twenty-eleven.php b/includes/theme-support/class-wc-twenty-eleven.php index fb06a7a84dc..db598f78907 100644 --- a/includes/theme-support/class-wc-twenty-eleven.php +++ b/includes/theme-support/class-wc-twenty-eleven.php @@ -6,6 +6,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\Internal\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -29,8 +31,7 @@ class WC_Twenty_Eleven { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 150, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-fifteen.php b/includes/theme-support/class-wc-twenty-fifteen.php index 83e1930cf93..a3a194c7579 100644 --- a/includes/theme-support/class-wc-twenty-fifteen.php +++ b/includes/theme-support/class-wc-twenty-fifteen.php @@ -7,6 +7,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\Internal\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -30,8 +32,7 @@ class WC_Twenty_Fifteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 200, 'single_image_width' => 350, diff --git a/includes/theme-support/class-wc-twenty-fourteen.php b/includes/theme-support/class-wc-twenty-fourteen.php index ce04395299d..b8f5cf09a6b 100644 --- a/includes/theme-support/class-wc-twenty-fourteen.php +++ b/includes/theme-support/class-wc-twenty-fourteen.php @@ -7,6 +7,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\Internal\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -30,8 +32,7 @@ class WC_Twenty_Fourteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 150, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-nineteen.php b/includes/theme-support/class-wc-twenty-nineteen.php index 47c1aff660c..5419ade5b0a 100644 --- a/includes/theme-support/class-wc-twenty-nineteen.php +++ b/includes/theme-support/class-wc-twenty-nineteen.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Internal\ThemeSupport; defined( 'ABSPATH' ) || exit; @@ -37,8 +38,7 @@ class WC_Twenty_Nineteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 300, 'single_image_width' => 450, @@ -48,7 +48,7 @@ class WC_Twenty_Nineteen { // Tweak Twenty Nineteen features. add_action( 'wp', array( __CLASS__, 'tweak_theme_features' ) ); - // Color scheme CSS + // Color scheme CSS. add_filter( 'twentynineteen_custom_colors_css', array( __CLASS__, 'custom_colors_css' ), 10, 3 ); } diff --git a/includes/theme-support/class-wc-twenty-seventeen.php b/includes/theme-support/class-wc-twenty-seventeen.php index 2093d2200e7..661121063a5 100644 --- a/includes/theme-support/class-wc-twenty-seventeen.php +++ b/includes/theme-support/class-wc-twenty-seventeen.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Internal\ThemeSupport; defined( 'ABSPATH' ) || exit; @@ -30,8 +31,7 @@ class WC_Twenty_Seventeen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 250, 'single_image_width' => 350, diff --git a/includes/theme-support/class-wc-twenty-sixteen.php b/includes/theme-support/class-wc-twenty-sixteen.php index c9681fa5e98..f78fbb634f5 100644 --- a/includes/theme-support/class-wc-twenty-sixteen.php +++ b/includes/theme-support/class-wc-twenty-sixteen.php @@ -6,6 +6,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\Internal\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -29,8 +31,7 @@ class WC_Twenty_Sixteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 250, 'single_image_width' => 400, diff --git a/includes/theme-support/class-wc-twenty-ten.php b/includes/theme-support/class-wc-twenty-ten.php index 8a9262e6191..4988aea16a4 100644 --- a/includes/theme-support/class-wc-twenty-ten.php +++ b/includes/theme-support/class-wc-twenty-ten.php @@ -6,6 +6,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\Internal\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -29,8 +31,7 @@ class WC_Twenty_Ten { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 200, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-thirteen.php b/includes/theme-support/class-wc-twenty-thirteen.php index 4e80b3e27c6..527fa176ee0 100644 --- a/includes/theme-support/class-wc-twenty-thirteen.php +++ b/includes/theme-support/class-wc-twenty-thirteen.php @@ -7,6 +7,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\Internal\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -30,8 +32,7 @@ class WC_Twenty_Thirteen { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 200, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-twelve.php b/includes/theme-support/class-wc-twenty-twelve.php index 116dabea432..3815ac4c26d 100644 --- a/includes/theme-support/class-wc-twenty-twelve.php +++ b/includes/theme-support/class-wc-twenty-twelve.php @@ -7,6 +7,8 @@ * @package WooCommerce\Classes */ +use Automattic\WooCommerce\Internal\ThemeSupport; + defined( 'ABSPATH' ) || exit; /** @@ -30,8 +32,7 @@ class WC_Twenty_Twelve { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 200, 'single_image_width' => 300, diff --git a/includes/theme-support/class-wc-twenty-twenty-one.php b/includes/theme-support/class-wc-twenty-twenty-one.php index 6b568ae0e2d..7707c05ecd5 100644 --- a/includes/theme-support/class-wc-twenty-twenty-one.php +++ b/includes/theme-support/class-wc-twenty-twenty-one.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Internal\ThemeSupport; defined( 'ABSPATH' ) || exit; @@ -37,8 +38,7 @@ class WC_Twenty_Twenty_One { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 450, 'single_image_width' => 600, diff --git a/includes/theme-support/class-wc-twenty-twenty.php b/includes/theme-support/class-wc-twenty-twenty.php index 47296f639ab..862376f9eda 100644 --- a/includes/theme-support/class-wc-twenty-twenty.php +++ b/includes/theme-support/class-wc-twenty-twenty.php @@ -7,6 +7,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Internal\ThemeSupport; defined( 'ABSPATH' ) || exit; @@ -37,8 +38,7 @@ class WC_Twenty_Twenty { add_theme_support( 'wc-product-gallery-zoom' ); add_theme_support( 'wc-product-gallery-lightbox' ); add_theme_support( 'wc-product-gallery-slider' ); - add_theme_support( - 'woocommerce', + wc_get_container()->get( ThemeSupport::class )->add_default_options( array( 'thumbnail_image_width' => 450, 'single_image_width' => 600, diff --git a/includes/tracks/class-wc-site-tracking.php b/includes/tracks/class-wc-site-tracking.php index 74e06215985..e924d250448 100644 --- a/includes/tracks/class-wc-site-tracking.php +++ b/includes/tracks/class-wc-site-tracking.php @@ -104,7 +104,7 @@ class WC_Site_Tracking { ?>