diff --git a/.github/workflows/install-testing.yml b/.github/workflows/install-testing.yml index f820c214df16d..d35b25c08bd8b 100644 --- a/.github/workflows/install-testing.yml +++ b/.github/workflows/install-testing.yml @@ -141,7 +141,7 @@ jobs: steps: - name: Set up PHP ${{ matrix.php }} - uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d # v2.30.0 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 with: php-version: '${{ matrix.php }}' coverage: none diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index b40237f82802d..663b93bba48a9 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -36,7 +36,7 @@ jobs: # test-with-mysql: name: PHP ${{ matrix.php }} - uses: WordPress/wordpress-develop/.github/workflows/reusable-phpunit-tests.yml@trunk + uses: WordPress/wordpress-develop/.github/workflows/reusable-phpunit-tests-v3.yml@trunk permissions: contents: read secrets: inherit @@ -108,7 +108,7 @@ jobs: # test-with-mariadb: name: PHP ${{ matrix.php }} - uses: WordPress/wordpress-develop/.github/workflows/reusable-phpunit-tests.yml@trunk + uses: WordPress/wordpress-develop/.github/workflows/reusable-phpunit-tests-v3.yml@trunk permissions: contents: read secrets: inherit diff --git a/.github/workflows/props-bot.yml b/.github/workflows/props-bot.yml index 5b0ba5c3c4515..548f9023a92e2 100644 --- a/.github/workflows/props-bot.yml +++ b/.github/workflows/props-bot.yml @@ -18,7 +18,7 @@ on: # You cannot filter this event for PR comments only. # However, the logic below does short-circuit the workflow for issues. issue_comment: - type: + types: - created # This event will run everytime a new PR review is initially submitted. pull_request_review: diff --git a/.github/workflows/pull-request-comments.yml b/.github/workflows/pull-request-comments.yml index 189eb1c1737d0..59c73db4e4131 100644 --- a/.github/workflows/pull-request-comments.yml +++ b/.github/workflows/pull-request-comments.yml @@ -195,7 +195,7 @@ jobs: const prBody = pr.body ?? ''; const prTitle = pr.title ?? ''; - const tracTicketRegex = new RegExp( 'https?://core.trac.wordpress.org/ticket/([0-9]+)', 'g' ); + const tracTicketRegex = new RegExp( '(https?://core.trac.wordpress.org/ticket/|Core-|ticket:)([0-9]+)', 'g' ); const tracTicketMatches = prBody.match( tracTicketRegex ) || prTitle.match( tracTicketRegex ); if ( ! tracTicketMatches ) { diff --git a/.github/workflows/reusable-coding-standards-javascript.yml b/.github/workflows/reusable-coding-standards-javascript.yml index a424f80630887..e36e2f592081c 100644 --- a/.github/workflows/reusable-coding-standards-javascript.yml +++ b/.github/workflows/reusable-coding-standards-javascript.yml @@ -35,7 +35,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm diff --git a/.github/workflows/reusable-coding-standards-php.yml b/.github/workflows/reusable-coding-standards-php.yml index 3f8520c2f74d7..5aea737c5eb83 100644 --- a/.github/workflows/reusable-coding-standards-php.yml +++ b/.github/workflows/reusable-coding-standards-php.yml @@ -11,6 +11,11 @@ on: required: false type: 'string' default: 'latest' + old-branch: + description: 'Whether this is an old branch that runs phpcbf instead of phpcs' + required: false + type: 'boolean' + default: false jobs: # Runs the PHP coding standards checks. @@ -42,7 +47,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d # v2.30.0 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 with: php-version: ${{ inputs.php-version }} coverage: none @@ -55,7 +60,7 @@ jobs: run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT - name: Cache PHPCS scan cache - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: | .cache/phpcs-src.json @@ -74,6 +79,7 @@ jobs: - name: Run PHPCS on all Core files id: phpcs-core + if: ${{ ! inputs.old-branch }} run: phpcs -n --report-full --cache=./.cache/phpcs-src.json --report-checkstyle=./.cache/phpcs-report.xml - name: Show PHPCS results in PR @@ -82,11 +88,16 @@ jobs: - name: Check test suite files for warnings id: phpcs-tests + if: ${{ ! inputs.old-branch }} run: phpcs tests --report-full --cache=./.cache/phpcs-tests.json --report-checkstyle=./.cache/phpcs-tests-report.xml - name: Show test suite scan results in PR if: ${{ always() && steps.phpcs-tests.outcome == 'failure' }} run: cs2pr ./.cache/phpcs-tests-report.xml + - name: Run PHPCBF on all Core files (old branches) + if: ${{ inputs.old-branch }} + run: phpcbf + - name: Ensure version-controlled files are not modified during the tests run: git diff --exit-code diff --git a/.github/workflows/reusable-end-to-end-tests.yml b/.github/workflows/reusable-end-to-end-tests.yml index 7aa23fbb1aa9e..1d980551b48c6 100644 --- a/.github/workflows/reusable-end-to-end-tests.yml +++ b/.github/workflows/reusable-end-to-end-tests.yml @@ -45,6 +45,7 @@ jobs: # - Logs Docker debug information (about both the Docker installation within the runner and the WordPress container). # - Install WordPress within the Docker container. # - Install Gutenberg. + # - Install additional languages. # - Run the E2E tests. # - Uploads screenshots and HTML snapshots as an artifact. # - Ensures version-controlled files are not modified or deleted. @@ -67,7 +68,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm @@ -114,11 +115,17 @@ jobs: if: ${{ inputs.install-gutenberg }} run: npm run env:cli -- plugin install gutenberg --path=/var/www/${{ env.LOCAL_DIR }} + - name: Install additional languages + run: | + npm run env:cli -- language core install de_DE --path=/var/www/${{ env.LOCAL_DIR }} + npm run env:cli -- language plugin install de_DE --all --path=/var/www/${{ env.LOCAL_DIR }} + npm run env:cli -- language theme install de_DE --all --path=/var/www/${{ env.LOCAL_DIR }} + - name: Run E2E tests run: npm run test:e2e - name: Archive debug artifacts (screenshots, HTML snapshots) - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 if: always() with: name: failures-artifacts${{ inputs.LOCAL_SCRIPT_DEBUG && '-SCRIPT_DEBUG' || '' }}-${{ github.run_id }} diff --git a/.github/workflows/reusable-javascript-tests.yml b/.github/workflows/reusable-javascript-tests.yml index 0d7b674fb932a..7adee1161c840 100644 --- a/.github/workflows/reusable-javascript-tests.yml +++ b/.github/workflows/reusable-javascript-tests.yml @@ -30,7 +30,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm diff --git a/.github/workflows/reusable-performance.yml b/.github/workflows/reusable-performance.yml index 630287dab6f9b..bf6b31fc17a90 100644 --- a/.github/workflows/reusable-performance.yml +++ b/.github/workflows/reusable-performance.yml @@ -54,6 +54,7 @@ jobs: # Performs the following steps: # - Configure environment variables. # - Checkout repository. + # - Determine the target SHA value (on `workflow_dispatch` only). # - Set up Node.js. # - Log debug information. # - Install npm dependencies. @@ -109,9 +110,15 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + fetch-depth: ${{ github.event_name == 'workflow_dispatch' && '2' || '1' }} + + # The `workflow_dispatch` event is the only one missing the needed SHA to target. + - name: Retrieve previous commit SHA (if necessary) + if: ${{ github.event_name == 'workflow_dispatch' }} + run: echo "TARGET_SHA=$(git rev-parse HEAD^1)" >> $GITHUB_ENV - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm @@ -274,7 +281,7 @@ jobs: run: npm run test:performance - name: Archive artifacts - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 if: always() with: name: performance-artifacts${{ inputs.memcached && '-memcached' || '' }}-${{ github.run_id }} diff --git a/.github/workflows/reusable-php-compatibility.yml b/.github/workflows/reusable-php-compatibility.yml index 1a6a6b128747b..6171c3467e8e4 100644 --- a/.github/workflows/reusable-php-compatibility.yml +++ b/.github/workflows/reusable-php-compatibility.yml @@ -41,7 +41,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up PHP - uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d # v2.30.0 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 with: php-version: ${{ inputs.php-version }} coverage: none @@ -58,7 +58,7 @@ jobs: run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT - name: Cache PHP compatibility scan cache - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: .cache/phpcompat.json key: ${{ runner.os }}-date-${{ steps.get-date.outputs.date }}-php-${{ inputs.php-version }}-phpcompat-cache-${{ hashFiles('**/composer.json', 'phpcompat.xml.dist') }} diff --git a/.github/workflows/reusable-phpunit-tests-v1.yml b/.github/workflows/reusable-phpunit-tests-v1.yml new file mode 100644 index 0000000000000..621e9eb045b74 --- /dev/null +++ b/.github/workflows/reusable-phpunit-tests-v1.yml @@ -0,0 +1,184 @@ +## +# DEPRECATED +# +# A reusable workflow that runs the PHPUnit test suite with the specified configuration. +# +# This workflow is used by branches 4.1 through 5.1. +## +name: Run PHPUnit tests + +on: + workflow_call: + inputs: + os: + description: 'Operating system to run tests on' + required: false + type: 'string' + default: 'ubuntu-latest' + php: + description: 'The version of PHP to use, in the format of X.Y' + required: true + type: 'string' + phpunit: + description: 'The PHPUnit version to use when running tests. See .env for details about valid values.' + required: false + type: 'string' + default: ${{ inputs.php }}-fpm + multisite: + description: 'Whether to run tests as multisite' + required: false + type: 'boolean' + default: false + split_slow: + description: 'Whether to run slow tests group.' + required: false + type: 'boolean' + default: false + memcached: + description: 'Whether to test with memcached enabled' + required: false + type: 'boolean' + default: false + phpunit-config: + description: 'The PHPUnit configuration file to use' + required: false + type: 'string' + default: 'phpunit.xml.dist' + allow-errors: + description: 'Whether to continue when test errors occur.' + required: false + type: boolean + default: false +env: + LOCAL_PHP: ${{ inputs.php }}-fpm + LOCAL_PHPUNIT: ${{ inputs.phpunit && inputs.phpunit || inputs.php }}-fpm + LOCAL_PHP_MEMCACHED: ${{ inputs.memcached }} + PHPUNIT_CONFIG: ${{ inputs.phpunit-config }} + PHPUNIT_SCRIPT: php + PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + SLOW_TESTS: 'external-http,media' + +jobs: + # Runs the PHPUnit tests for WordPress. + # + # Performs the following steps: + # - Sets environment variables. + # - Sets up the environment variables needed for testing with memcached (if desired). + # - Installs NodeJS. + # - Build WordPress + # _ Installs npm dependencies. + # - Configures caching for Composer. + # _ Installs Composer dependencies (if desired). + # - Logs Docker debug information (about the Docker installation within the runner). + # - Starts the WordPress Docker container. + # - Starts the Memcached server after the Docker network has been created (if desired). + # - Logs general debug information about the runner. + # - Logs the running Docker containers. + # - Logs debug information from inside the WordPress Docker container. + # - Logs debug information about what's installed within the WordPress Docker containers. + # - Install WordPress within the Docker container. + # - Run the PHPUnit tests. + test-php: + name: PHP ${{ inputs.php }} / ${{ inputs.multisite && ' Multisite' || 'Single site' }}${{ inputs.split_slow && ' slow tests' || '' }}${{ inputs.memcached && ' with memcached' || '' }} + runs-on: ${{ inputs.os }} + timeout-minutes: 20 + + steps: + - name: Configure environment variables + run: | + echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV + echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Set up Node.js + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Install Dependencies + run: npm ci + + - name: Build WordPress + run: npm run build + + - name: Cache Composer dependencies + if: ${{ env.COMPOSER_INSTALL == true }} + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + env: + cache-name: cache-composer-dependencies + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-php-${{ inputs.php }}-composer-${{ hashFiles('**/composer.lock') }} + + - name: Install Composer dependencies + if: ${{ env.COMPOSER_INSTALL == true }} + run: | + docker compose run --rm php composer --version + docker compose run --rm php composer install + + - name: Docker debug information + run: | + docker -v + docker compose -v + + - name: Start Docker environment + run: | + npm run env:start + + # The memcached server needs to start after the Docker network has been set up with `npm run env:start`. + - name: Start the Memcached server. + if: ${{ inputs.memcached }} + run: | + cp tests/phpunit/includes/object-cache.php build/wp-content/object-cache.php + docker run --name memcached --net $(basename "$PWD")_wpdevnet -d memcached + + - name: General debug information + run: | + npm --version + node --version + curl --version + git --version + svn --version + + - name: Log running Docker containers + run: docker ps -a + + - name: WordPress Docker container debug information + run: | + docker compose run --rm mysql mysql --version + docker compose run --rm php php --version + docker compose run --rm php php -m + docker compose run --rm php php -i + docker compose run --rm php locale -a + + - name: Install WordPress + run: npm run env:install + + - name: Run slow PHPUnit tests + if: ${{ inputs.split_slow }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --group ${{ env.SLOW_TESTS }} + + - name: Run PHPUnit tests for single site excluding slow tests + if: ${{ inputs.php < '7.0' && ! inputs.split_slow && ! inputs.multisite }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --exclude-group ${{ env.SLOW_TESTS }},ajax,ms-files,ms-required + + - name: Run PHPUnit tests for Multisite excluding slow tests + if: ${{ inputs.php < '7.0' && ! inputs.split_slow && inputs.multisite }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --exclude-group ${{ env.SLOW_TESTS }},ajax,ms-files,ms-excluded,oembed-headers + + - name: Run PHPUnit tests + if: ${{ inputs.php >= '7.0' }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} + + - name: Run AJAX tests + if: ${{ ! inputs.multisite && ! inputs.split_slow }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --group ajax + + - name: Run external HTTP tests + if: ${{ ! inputs.multisite && ! inputs.split_slow }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c phpunit.xml.dist --group external-http diff --git a/.github/workflows/reusable-phpunit-tests-v2.yml b/.github/workflows/reusable-phpunit-tests-v2.yml new file mode 100644 index 0000000000000..a9cb221664c14 --- /dev/null +++ b/.github/workflows/reusable-phpunit-tests-v2.yml @@ -0,0 +1,205 @@ +## +# DEPRECATED +# +# A reusable workflow that runs the PHPUnit test suite with the specified configuration. +# +# This workflow is used by branches 5.2 through 5.8. +## +name: Run PHPUnit tests + +on: + workflow_call: + inputs: + os: + description: 'Operating system to run tests on' + required: false + type: 'string' + default: 'ubuntu-latest' + php: + description: 'The version of PHP to use, in the format of X.Y' + required: true + type: 'string' + multisite: + description: 'Whether to run tests as multisite' + required: false + type: 'boolean' + default: false + split_slow: + description: 'Whether to run slow tests group.' + required: false + type: 'boolean' + default: false + test_ajax: + description: 'Whether to run AJAX tests.' + required: false + type: 'boolean' + default: true + memcached: + description: 'Whether to test with memcached enabled' + required: false + type: 'boolean' + default: false + phpunit-config: + description: 'The PHPUnit configuration file to use' + required: false + type: 'string' + default: 'phpunit.xml.dist' + report: + description: 'Whether to report results to WordPress.org Hosting Tests' + required: false + type: 'boolean' + default: false + allow-errors: + description: 'Whether to continue when test errors occur.' + required: false + type: boolean + default: false +env: + LOCAL_PHP: ${{ inputs.php }}-fpm + LOCAL_PHP_MEMCACHED: ${{ inputs.memcached }} + PHPUNIT_CONFIG: ${{ inputs.phpunit-config }} + PUPPETEER_SKIP_DOWNLOAD: ${{ true }} + # Controls which npm script to use for running PHPUnit tests. Options ar `php` and `php-composer`. + PHPUNIT_SCRIPT: php + SLOW_TESTS: 'external-http,media' + +jobs: + # Runs the PHPUnit tests for WordPress. + # + # Performs the following steps: + # - Sets environment variables. + # - Checks out the repository. + # - Installs Node.js. + # - Installs npm dependencies + # - Configures caching for Composer. + # - Installs Composer dependencies. + # - Logs Docker debug information (about the Docker installation within the runner). + # - Starts the WordPress Docker container. + # - Logs general debug information about the runner. + # - Logs the running Docker containers. + # - Logs debug information from inside the WordPress Docker container. + # - Install WordPress within the Docker container. + # - Run the PHPUnit tests. + # - Ensures version-controlled files are not modified or deleted. + test-php: + name: PHP ${{ inputs.php }} / ${{ inputs.multisite && ' Multisite' || 'Single Site' }}${{ inputs.split_slow && ' slow tests' || '' }}${{ inputs.memcached && ' with memcached' || '' }} + runs-on: ${{ inputs.os }} + timeout-minutes: 20 + + steps: + - name: Configure environment variables + run: | + echo "PHP_FPM_UID=$(id -u)" >> $GITHUB_ENV + echo "PHP_FPM_GID=$(id -g)" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + + - name: Install Node.js + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version-file: '.nvmrc' + cache: npm + + - name: Install npm dependencies + run: npm ci + + - name: Get composer cache directory + id: composer-cache + run: echo "composer_dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + env: + cache-name: cache-composer-dependencies + with: + path: ${{ steps.composer-cache.outputs.composer_dir }} + key: ${{ runner.os }}-php-${{ inputs.php }}-composer-${{ hashFiles('**/composer.lock') }} + + - name: Install Composer dependencies + run: | + docker compose run --rm php composer --version + + # The PHPUnit 7.x phar is not compatible with PHP 8 and won't be updated, + # as PHPUnit 7 is no longer supported. The Composer-installed PHPUnit should be + # used for PHP 8 testing instead. + if [ ${{ env.LOCAL_PHP }} == '8.0-fpm' ]; then + docker compose run --rm php composer install --ignore-platform-reqs + echo "PHPUNIT_SCRIPT=php-composer" >> $GITHUB_ENV + elif [ ${{ env.LOCAL_PHP }} == '7.1-fpm' ]; then + docker compose run --rm php composer update + git checkout -- composer.lock + elif [[ ${{ env.LOCAL_PHP }} == '5.6-fpm' || ${{ env.LOCAL_PHP }} == '7.0-fpm' ]]; then + docker compose run --rm php composer require --dev phpunit/phpunit:"^5.7" --update-with-dependencies + git checkout -- composer.lock composer.json + else + docker compose run --rm php composer install + fi + + - name: Docker debug information + run: | + docker -v + docker compose -v + + - name: Start Docker environment + run: | + npm run env:start + + - name: General debug information + run: | + npm --version + node --version + curl --version + git --version + + - name: Log running Docker containers + run: docker ps -a + + - name: WordPress Docker container debug information + run: | + docker compose run --rm mysql mysql --version + docker compose run --rm php php --version + docker compose run --rm php php -m + docker compose run --rm php php -i + docker compose run --rm php locale -a + + - name: Install WordPress + run: npm run env:install + + - name: Run slow PHPUnit tests + if: ${{ inputs.split_slow }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --group ${{ env.SLOW_TESTS }} + + - name: Run PHPUnit tests for single site excluding slow tests + if: ${{ inputs.php < '7.0' && ! inputs.split_slow && ! inputs.multisite }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --exclude-group ${{ env.SLOW_TESTS }},ajax,ms-files,ms-required + + - name: Run PHPUnit tests for Multisite excluding slow tests + if: ${{ inputs.php < '7.0' && ! inputs.split_slow && inputs.multisite }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --exclude-group ${{ env.SLOW_TESTS }},ajax,ms-files,ms-excluded,oembed-headers + + - name: Run PHPUnit tests + if: ${{ inputs.php >= '7.0' }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} + + - name: Run AJAX tests + if: ${{ ! inputs.split_slow&& inputs.test_ajax }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --group ajax + + - name: Run ms-files tests as a multisite install + if: ${{ inputs.multisite && ! inputs.split_slow }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c ${{ env.PHPUNIT_CONFIG }} --group ms-files + + - name: Run external HTTP tests + if: ${{ ! inputs.multisite && ! inputs.split_slow }} + run: npm run test:${{ env.PHPUNIT_SCRIPT }} -- --verbose -c phpunit.xml.dist --group external-http + + # __fakegroup__ is excluded to force PHPUnit to ignore the settings in phpunit.xml.dist. + - name: Run (xDebug) tests + if: ${{ ! inputs.split_slow }} + run: LOCAL_PHP_XDEBUG=true npm run test:${{ env.PHPUNIT_SCRIPT }} -- -v --group xdebug --exclude-group __fakegroup__ + + - name: Ensure version-controlled files are not modified or deleted + run: git diff --exit-code diff --git a/.github/workflows/reusable-phpunit-tests.yml b/.github/workflows/reusable-phpunit-tests-v3.yml similarity index 97% rename from .github/workflows/reusable-phpunit-tests.yml rename to .github/workflows/reusable-phpunit-tests-v3.yml index d5302b83b2a07..c9387d06a8451 100644 --- a/.github/workflows/reusable-phpunit-tests.yml +++ b/.github/workflows/reusable-phpunit-tests-v3.yml @@ -1,5 +1,7 @@ ## # A reusable workflow that runs the PHPUnit test suite with the specified configuration. +# +# This workflow is used by `trunk` and branches >= 5.9. ## name: Run PHPUnit tests @@ -101,7 +103,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm @@ -114,7 +116,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d # v2.30.0 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 with: php-version: '${{ inputs.php }}' coverage: none diff --git a/.github/workflows/reusable-test-core-build-process.yml b/.github/workflows/reusable-test-core-build-process.yml index 0df177b7d786f..6d1c39ce37584 100644 --- a/.github/workflows/reusable-test-core-build-process.yml +++ b/.github/workflows/reusable-test-core-build-process.yml @@ -63,7 +63,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm @@ -101,7 +101,7 @@ jobs: run: git diff --exit-code - name: Upload ZIP as a GitHub Actions artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 if: ${{ inputs.save-build || inputs.prepare-playground }} with: name: wordpress-build-${{ github.event_name == 'pull_request' && github.event.number || github.sha }} @@ -117,7 +117,7 @@ jobs: # Uploads the PR number as an artifact for the Pull Request Commenting workflow to download and then # leave a comment detailing how to test the PR within WordPress Playground. - name: Upload PR number as artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 if: ${{ inputs.prepare-playground && github.repository == 'WordPress/wordpress-develop' && github.event_name == 'pull_request' }} with: name: pr-number diff --git a/.github/workflows/reusable-test-gutenberg-build-process.yml b/.github/workflows/reusable-test-gutenberg-build-process.yml index a72e616bc9446..7765453f6157e 100644 --- a/.github/workflows/reusable-test-gutenberg-build-process.yml +++ b/.github/workflows/reusable-test-gutenberg-build-process.yml @@ -55,7 +55,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm diff --git a/.github/workflows/reusable-upgrade-testing.yml b/.github/workflows/reusable-upgrade-testing.yml index 8e467c20ccbc9..6b285d190c6a3 100644 --- a/.github/workflows/reusable-upgrade-testing.yml +++ b/.github/workflows/reusable-upgrade-testing.yml @@ -62,7 +62,7 @@ jobs: steps: - name: Set up PHP ${{ inputs.php }} - uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d # v2.30.0 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 with: php-version: '${{ inputs.php }}' coverage: none diff --git a/.github/workflows/slack-notifications.yml b/.github/workflows/slack-notifications.yml index aab3a85147bc0..4ae4e52df569d 100644 --- a/.github/workflows/slack-notifications.yml +++ b/.github/workflows/slack-notifications.yml @@ -167,7 +167,7 @@ jobs: steps: - name: Post failure notifications to Slack - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: ${{ needs.prepare.outputs.payload }} env: @@ -183,7 +183,7 @@ jobs: steps: - name: Post failure notifications to Slack - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: ${{ needs.prepare.outputs.payload }} env: @@ -199,7 +199,7 @@ jobs: steps: - name: Post success notifications to Slack - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: ${{ needs.prepare.outputs.payload }} env: @@ -215,7 +215,7 @@ jobs: steps: - name: Post cancelled notifications to Slack - uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: ${{ needs.prepare.outputs.payload }} env: diff --git a/.github/workflows/test-and-zip-default-themes.yml b/.github/workflows/test-and-zip-default-themes.yml index 31e4800cfa44d..74800a4ace8bf 100644 --- a/.github/workflows/test-and-zip-default-themes.yml +++ b/.github/workflows/test-and-zip-default-themes.yml @@ -131,7 +131,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm @@ -187,7 +187,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Upload theme ZIP as an artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: if-no-files-found: error name: ${{ matrix.theme }} diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 042ab8d6065df..8c8655b9a9fc1 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -81,7 +81,7 @@ jobs: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - name: Set up Node.js - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version-file: '.nvmrc' cache: npm @@ -94,7 +94,7 @@ jobs: # dependency versions are installed and cached. ## - name: Set up PHP - uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d # v2.30.0 + uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1 with: php-version: '7.4' coverage: none @@ -151,7 +151,7 @@ jobs: - name: Upload single site report to Codecov if: ${{ ! matrix.multisite && matrix.format == 'clover' && github.event_name != 'pull_request' }} - uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} file: wp-code-coverage-single-${{ github.sha }}${{ 'clover' == matrix.format && '.xml' || '' }} @@ -160,7 +160,7 @@ jobs: - name: Upload single site HTML report as artifact if: ${{ ! matrix.multisite && matrix.format == 'html' }} - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: wp-code-coverage-single-${{ github.sha }} path: wp-code-coverage-single-${{ github.sha }} @@ -175,7 +175,7 @@ jobs: - name: Upload multisite report to Codecov if: ${{ matrix.multisite && matrix.format == 'clover' && github.event_name != 'pull_request' }} - uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} file: wp-code-coverage-multisite-${{ github.sha }}${{ 'clover' == matrix.format && '.xml' || '' }} @@ -184,7 +184,7 @@ jobs: - name: Upload multisite HTML report as artifact if: ${{ matrix.multisite && matrix.format == 'html' }} - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: wp-code-coverage-multisite-${{ github.sha }} path: wp-code-coverage-multisite-${{ github.sha }} diff --git a/.github/workflows/test-old-branches.yml b/.github/workflows/test-old-branches.yml index 777ed9e2c01a9..5ac2b7c03a78a 100644 --- a/.github/workflows/test-old-branches.yml +++ b/.github/workflows/test-old-branches.yml @@ -7,6 +7,8 @@ on: - trunk paths: - '.github/workflows/test-old-branches.yml' + - '.github/workflows/reusable-phpunit-tests-v1.yml' + - '.github/workflows/reusable-phpunit-tests-v2.yml' # Run twice a month on the 1st and 15th at 00:00 UTC. schedule: - cron: '0 0 1 * *' @@ -17,7 +19,7 @@ on: permissions: {} env: - CURRENTLY_SUPPORTED_BRANCH: '6.5' + CURRENTLY_SUPPORTED_BRANCH: '6.6' jobs: dispatch-workflows-for-old-branches: @@ -34,15 +36,17 @@ jobs: 'coding-standards.yml', 'javascript-tests.yml', 'phpunit-tests.yml', - 'test-npm.yml' + 'test-build-processes.yml' ] branch: [ - '6.5', '6.4', '6.3', '6.2', '6.1','6.0', + '6.6', '6.5', '6.4', '6.3', '6.2', '6.1','6.0', '5.9', '5.8', '5.7', '5.6', '5.5', '5.4', '5.3', '5.2', '5.1', '5.0', '4.9', '4.8', '4.7', '4.6', '4.5', '4.4', '4.3', '4.2', '4.1' ] include: # PHP Compatibility testing was introduced in 5.5. + - branch: '6.6' + workflow: 'php-compatibility.yml' - branch: '6.5' workflow: 'php-compatibility.yml' - branch: '6.4' @@ -69,6 +73,8 @@ jobs: # End-to-end testing was introduced in 5.3 but was later removed as there were no meaningful assertions. # Starting in 5.8 with #52905, some additional tests with real assertions were introduced. # Branches 5.8 and newer should be tested to confirm no regressions are introduced. + - branch: '6.6' + workflow: 'end-to-end-tests.yml' - branch: '6.5' workflow: 'end-to-end-tests.yml' - branch: '6.4' @@ -86,19 +92,15 @@ jobs: - branch: '5.8' workflow: 'end-to-end-tests.yml' - # Performance testing was introduced in 6.2. + # Performance testing was introduced in 6.2 using Puppeteer but was overhauled to use Playwright instead in 6.4. + # Since the workflow frequently failed for 6.2 and 6.3 due to the flaky nature of the Puppeteer tests, + # the workflow was removed from those two branches. + - branch: '6.6' + workflow: 'performance.yml' - branch: '6.5' workflow: 'performance.yml' - branch: '6.4' workflow: 'performance.yml' - - branch: '6.3' - workflow: 'performance.yml' - - branch: '6.2' - workflow: 'performance.yml' - - # Build Process testing was introduced in 6.5. - - branch: '6.5' - workflow: 'test-build-processes.yml' # Run all branches monthly, but only the currently supported one twice per month. steps: diff --git a/.github/workflows/upgrade-testing.yml b/.github/workflows/upgrade-testing.yml index 1ffe4bfc585ac..25ba7e97080ba 100644 --- a/.github/workflows/upgrade-testing.yml +++ b/.github/workflows/upgrade-testing.yml @@ -46,7 +46,7 @@ jobs: php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] db-type: [ 'mysql' ] db-version: [ '5.7', '8.0' ] - wp: [ '6.0', '6.1', '6.2', '6.3', '6.4', '6.5' ] + wp: [ '6.0', '6.1', '6.2', '6.3', '6.4', '6.5', '6.6' ] multisite: [ false, true ] with: os: ${{ matrix.os }} diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000000..bbcf17b3ec9d4 --- /dev/null +++ b/.mailmap @@ -0,0 +1,129 @@ +# Aliases names and emails inside of commits. +# See https://git-scm.com/docs/gitmailmap +# +# Some entries appear as duplicates, but are both required to correct +# identities with just the wrong name as well as those with the wrong +# name and also the wrong email address. + +# Accounts with a display name. +Aaron D. Campbell +Aaron Jorbin +Adam Silverstein +Adam Zieliński +Adam Zieliński +Alex King +Alex Shiels +André +André +Andrea Fercia +Andrew Duthie +Andrew Nacin +Andrew Ozz +Anthony Burchell +Anton Timmermans +Bernie Reiter +Bernie Reiter +Boone Gorges +Carlos Bravo +Colin Stewart +Daniel Bachhuber +Daniel Richards +Daryl Koopersmith +David A. Kennedy +David Baumwald +Dennis Snell +Dion Hulse +Dominik Schilling +Dominik Schilling +Donncha O Caoimh +Dougal Campbell +Drew Jaynes +Drew Jaynes +Drew Jaynes +Ella +Ella +Ella +Ella +Eric Andrew Lewis +Felix Arntz +Gary Pendergast +Greg Ziółkowski +Greg Ziółkowski +Helen Hou-Sandi +Ian Belanger +Ian Dunn +Ian Stewart +Isabel Brison +Jake Spurlock +James Nylen +Jb Audras +Jeff Ong +Jeremy Felt +Joe Dolson +Joe Hoyle +Joe McGill +John Blackbourn +John James Jacoby +Jon Cave +Jonathan Desrosiers +Jonny Harris +Jorge Costa +Joseph Scott +Juliette Reinders Folmer +Juliette Reinders Folmer +K. Adam White +Kelly Choyce-Dwan +Kelly Choyce-Dwan +Kira Schroder +Kira Schroder +Konstantin Kovshenin +Konstantin Obenland +Konstantin Obenland +Lance Willett +Marius L. J +Mark Jaquith +Matias Ventura +Matt Mullenweg +Matt Thomas +Mel Choyce +Michael Adams +Michael Adams +Michael Arestad +Michal Czaplinski +Miguel Fonseca +Mike Little +Nikolay Bachiyski +Omar Reiss +Pascal Birchler +Pete Mall +Peter Westwood +Peter Wilson +Rachel Baker +Riad Benguella +Robert Anderson +Ron Rennick +Ryan Boren +Ryan McCue +Scott Taylor +Sergey Biryukov +Sergey Biryukov +Tammie Lister +Tammie Lister +Timothy Jacobs +Timothy Jacobs +Tonya Mork +Tonya Mork +Weston Ruter + +# Accounts without a corresponding display name. +allancole +bumpbot +jverber +laurelfulford +luisherranz +michelvaldrighi +potbot +ramonopoly +rob1n +scribu +zieladam diff --git a/.version-support-mysql.json b/.version-support-mysql.json index 9bbe6bbd08788..bd2bfac03233e 100644 --- a/.version-support-mysql.json +++ b/.version-support-mysql.json @@ -1,4 +1,13 @@ { + "6-7": [ + "8.3", + "8.2", + "8.1", + "8.0", + "5.7", + "5.6", + "5.5" + ], "6-6": [ "8.3", "8.2", diff --git a/.version-support-php.json b/.version-support-php.json index 789d52636e96f..6d9f3708a5a46 100644 --- a/.version-support-php.json +++ b/.version-support-php.json @@ -1,4 +1,13 @@ { + "6-7": [ + "7.2", + "7.3", + "7.4", + "8.0", + "8.1", + "8.2", + "8.3" + ], "6-6": [ "7.2", "7.3", diff --git a/SECURITY.md b/SECURITY.md index 40fd5039e5ba2..32e8273dcbb4c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,6 +10,7 @@ Full details of the WordPress Security Policy and the list of covered projects a | Version | Supported | |---------| --------- | +| 6.6.x | Yes | | 6.5.x | Yes | | 6.4.x | Yes | | 6.3.x | Yes | diff --git a/package-lock.json b/package-lock.json index f7553fcb86837..82abc3952a860 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,90 +1,89 @@ { "name": "WordPress", - "version": "6.6.0", + "version": "6.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "WordPress", - "version": "6.6.0", + "version": "6.7.0", "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/a11y": "4.0.1", - "@wordpress/annotations": "3.0.1", + "@wordpress/annotations": "3.0.2", "@wordpress/api-fetch": "7.0.1", "@wordpress/autop": "4.0.1", "@wordpress/blob": "4.0.1", - "@wordpress/block-directory": "5.0.1", - "@wordpress/block-editor": "13.0.1", - "@wordpress/block-library": "9.0.1", + "@wordpress/block-directory": "5.0.8", + "@wordpress/block-editor": "13.0.6", + "@wordpress/block-library": "9.0.7", "@wordpress/block-serialization-default-parser": "5.0.1", - "@wordpress/blocks": "13.0.1", - "@wordpress/commands": "1.0.1", - "@wordpress/components": "28.0.1", + "@wordpress/blocks": "13.0.3", + "@wordpress/commands": "1.0.3", + "@wordpress/components": "28.0.3", "@wordpress/compose": "7.0.1", - "@wordpress/core-commands": "1.0.1", - "@wordpress/core-data": "7.0.1", - "@wordpress/customize-widgets": "5.0.1", - "@wordpress/data": "10.0.1", - "@wordpress/data-controls": "4.0.1", - "@wordpress/dataviews": "2.0.1", + "@wordpress/core-commands": "1.0.6", + "@wordpress/core-data": "7.0.6", + "@wordpress/customize-widgets": "5.0.7", + "@wordpress/data": "10.0.2", + "@wordpress/data-controls": "4.0.2", + "@wordpress/dataviews": "2.0.4", "@wordpress/date": "5.0.1", "@wordpress/deprecated": "4.0.1", "@wordpress/dom": "4.0.1", "@wordpress/dom-ready": "4.0.1", - "@wordpress/edit-post": "8.0.1", - "@wordpress/edit-site": "6.0.1", - "@wordpress/edit-widgets": "6.0.1", - "@wordpress/editor": "14.0.1", + "@wordpress/edit-post": "8.0.8", + "@wordpress/edit-site": "6.0.8", + "@wordpress/edit-widgets": "6.0.7", + "@wordpress/editor": "14.0.7", "@wordpress/element": "6.0.1", "@wordpress/escape-html": "3.0.1", - "@wordpress/format-library": "5.0.1", + "@wordpress/format-library": "5.0.6", "@wordpress/hooks": "4.0.1", "@wordpress/html-entities": "4.0.1", "@wordpress/i18n": "5.0.1", - "@wordpress/icons": "10.0.1", - "@wordpress/interactivity": "6.0.1", - "@wordpress/interactivity-router": "2.0.1", - "@wordpress/interface": "6.0.1", + "@wordpress/icons": "10.0.2", + "@wordpress/interactivity": "6.0.2", + "@wordpress/interactivity-router": "2.0.2", + "@wordpress/interface": "6.0.3", "@wordpress/is-shallow-equal": "5.0.1", - "@wordpress/keyboard-shortcuts": "5.0.1", + "@wordpress/keyboard-shortcuts": "5.0.2", "@wordpress/keycodes": "4.0.1", - "@wordpress/list-reusable-blocks": "5.0.1", + "@wordpress/list-reusable-blocks": "5.0.3", "@wordpress/media-utils": "5.0.1", - "@wordpress/notices": "5.0.1", - "@wordpress/nux": "9.0.1", - "@wordpress/patterns": "2.0.1", - "@wordpress/plugins": "7.0.1", - "@wordpress/preferences": "4.0.1", + "@wordpress/notices": "5.0.2", + "@wordpress/nux": "9.0.3", + "@wordpress/patterns": "2.0.6", + "@wordpress/plugins": "7.0.3", + "@wordpress/preferences": "4.0.3", "@wordpress/preferences-persistence": "2.0.1", "@wordpress/primitives": "4.0.1", "@wordpress/priority-queue": "3.0.1", - "@wordpress/private-apis": "1.0.1", + "@wordpress/private-apis": "1.0.2", "@wordpress/redux-routine": "5.0.1", - "@wordpress/reusable-blocks": "5.0.1", - "@wordpress/rich-text": "7.0.1", - "@wordpress/router": "1.0.1", - "@wordpress/server-side-render": "5.0.1", + "@wordpress/reusable-blocks": "5.0.6", + "@wordpress/rich-text": "7.0.2", + "@wordpress/router": "1.0.2", + "@wordpress/server-side-render": "5.0.3", "@wordpress/shortcode": "4.0.1", - "@wordpress/style-engine": "2.0.1", + "@wordpress/style-engine": "2.0.2", "@wordpress/sync": "1.0.1", "@wordpress/token-list": "3.0.1", "@wordpress/undo-manager": "1.0.1", "@wordpress/url": "4.0.1", - "@wordpress/viewport": "6.0.1", + "@wordpress/viewport": "6.0.2", "@wordpress/warning": "3.0.1", - "@wordpress/widgets": "4.0.1", + "@wordpress/widgets": "4.0.6", "@wordpress/wordcount": "4.0.1", - "backbone": "1.5.0", + "backbone": "1.6.0", "clipboard": "2.0.11", "core-js-url-browser": "3.6.4", "element-closest": "^3.0.2", - "es-module-shims": "1.8.2", "formdata-polyfill": "4.0.10", "hoverintent": "2.2.1", "imagesloaded": "5.0.0", "jquery": "3.7.1", - "jquery-color": "2.2.0", + "jquery-color": "3.0.0", "jquery-form": "4.3.0", "jquery-hoverintent": "1.10.2", "json2php": "^0.0.7", @@ -96,28 +95,28 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-is": "18.3.1", - "regenerator-runtime": "0.14.0", - "underscore": "1.13.6", - "whatwg-fetch": "3.6.17", + "regenerator-runtime": "0.14.1", + "underscore": "1.13.7", + "whatwg-fetch": "3.6.20", "wicg-inert": "3.1.2" }, "devDependencies": { "@lodder/grunt-postcss": "^3.1.1", - "@playwright/test": "1.32.0", - "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", + "@playwright/test": "1.45.0", + "@pmmmwh/react-refresh-webpack-plugin": "0.5.15", "@wordpress/babel-preset-default": "8.0.1", - "@wordpress/dependency-extraction-webpack-plugin": "6.0.1", + "@wordpress/dependency-extraction-webpack-plugin": "6.0.2", "@wordpress/e2e-test-utils": "11.0.1", "@wordpress/e2e-test-utils-playwright": "1.0.1", "@wordpress/prettier-config": "4.0.1", - "@wordpress/scripts": "28.0.1", - "autoprefixer": "10.4.17", + "@wordpress/scripts": "28.0.2", + "autoprefixer": "10.4.19", "chalk": "5.3.0", "check-node-version": "4.2.1", "copy-webpack-plugin": "12.0.2", - "cssnano": "6.0.3", - "dotenv": "16.4.4", - "dotenv-expand": "11.0.3", + "cssnano": "7.0.3", + "dotenv": "16.4.5", + "dotenv-expand": "11.0.6", "grunt": "1.6.1", "grunt-banner": "^0.6.0", "grunt-contrib-clean": "~2.0.1", @@ -126,7 +125,7 @@ "grunt-contrib-cssmin": "~5.0.0", "grunt-contrib-imagemin": "~4.0.0", "grunt-contrib-jshint": "3.2.0", - "grunt-contrib-qunit": "~7.0.1", + "grunt-contrib-qunit": "~10.0.0", "grunt-contrib-uglify": "~5.2.2", "grunt-contrib-watch": "~1.1.0", "grunt-file-append": "0.0.7", @@ -140,13 +139,13 @@ "ink-docstrap": "1.3.2", "install-changed": "1.1.0", "matchdep": "~2.0.0", - "postcss": "8.4.35", + "postcss": "8.4.38", "prettier": "npm:wp-prettier@2.6.2", - "qunit": "~2.20.0", + "qunit": "~2.21.0", "react-refresh": "0.14.0", - "sass": "1.70.0", + "sass": "1.77.6", "sinon": "16.1.3", - "sinon-test": "~3.1.5", + "sinon-test": "~3.1.6", "source-map-loader": "5.0.0", "terser-webpack-plugin": "5.3.10", "uglify-js": "^3.17.4", @@ -2795,12 +2794,12 @@ } }, "node_modules/@jest/core/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2841,9 +2840,9 @@ "dev": true }, "node_modules/@jest/core/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -3269,12 +3268,12 @@ } }, "node_modules/@jest/transform/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3321,9 +3320,9 @@ "dev": true }, "node_modules/@jest/transform/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -3648,38 +3647,32 @@ } }, "node_modules/@playwright/test": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz", - "integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", + "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", "dev": true, "dependencies": { - "@types/node": "*", - "playwright-core": "1.32.0" + "playwright": "1.45.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "fsevents": "2.3.2" + "node": ">=18" } }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", - "integrity": "sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", + "integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==", "dev": true, "dependencies": { - "ansi-html-community": "^0.0.8", - "common-path-prefix": "^3.0.0", + "ansi-html": "^0.0.9", "core-js-pure": "^3.23.3", "error-stack-parser": "^2.0.6", - "find-up": "^5.0.0", "html-entities": "^2.1.0", "loader-utils": "^2.0.4", - "schema-utils": "^3.0.0", + "schema-utils": "^4.2.0", "source-map": "^0.7.3" }, "engines": { @@ -3691,7 +3684,7 @@ "sockjs-client": "^1.4.0", "type-fest": ">=0.17.0 <5.0.0", "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x || 4.x", + "webpack-dev-server": "3.x || 4.x || 5.x", "webpack-hot-middleware": "2.x", "webpack-plugin-serve": "0.x || 1.x" }, @@ -3716,74 +3709,57 @@ } } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" + "fast-deep-equal": "^3.1.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, "dependencies": { - "p-limit": "^3.0.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">=10" + "node": ">= 12.13.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { @@ -3817,42 +3793,34 @@ } }, "node_modules/@preact/signals-core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.6.0.tgz", - "integrity": "sha512-O/XGxwP85h1F7+ouqTMOIZ3+V1whfaV9ToIVcuyGriD4JkSD00cQo54BKdqjvBJxbenvp7ynfqRHEwI6e+NIhw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.6.1.tgz", + "integrity": "sha512-KXEEmJoKDlo0Igju/cj9YvKIgyaWFDgnprShQjzimUd5VynAAdTWMshawEOjUVeKbsI0aR58V6WOQp+DNcKApw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, "node_modules/@puppeteer/browsers": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", - "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.3.tgz", + "integrity": "sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==", "dev": true, "dependencies": { "debug": "4.3.4", "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "yargs": "17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=14.1.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, "node_modules/@puppeteer/browsers/node_modules/ansi-regex": { @@ -3952,6 +3920,31 @@ "node": ">=8" } }, + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/@puppeteer/browsers/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3979,9 +3972,9 @@ } }, "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -6075,15 +6068,15 @@ } }, "node_modules/@wordpress/annotations": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/annotations/-/annotations-3.0.1.tgz", - "integrity": "sha512-vJE5zzoakwJZVoBhsI0ePeKrA2xbg6EjyZs4wtbJR1tRUkGjY+J/c8X/y2613ZeyWTSvemubEA5Zz2TrJXGXMw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/annotations/-/annotations-3.0.2.tgz", + "integrity": "sha512-+Z1WGJ0Yy7uh76WjxlQL1JF0veiYMmdWQ4z39B32VZOUNGLcTzD/JfeY1r9LHMi/OD4NBCWB9/e1F5vANdSH7Q==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/hooks": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/rich-text": "^7.0.2", "uuid": "^9.0.1" }, "engines": { @@ -6166,29 +6159,29 @@ } }, "node_modules/@wordpress/block-directory": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/block-directory/-/block-directory-5.0.1.tgz", - "integrity": "sha512-LIGNILjoVL0Ohjh35SGjpQpEd76QZIpRNqkGdYkIKi8mIZfEz9YaP2xiJkdGTN/QE7/6ntL2v9mc8d8EAlH3VQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@wordpress/block-directory/-/block-directory-5.0.8.tgz", + "integrity": "sha512-rHU/X3OZrE/IBtA1ayRqfsqw72IYonb8jiVWYBod+NI2bMeOEyXJ3PP/o7N856YFDVEka8b3sBNcr+A15xGysw==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", - "@wordpress/edit-post": "^8.0.1", - "@wordpress/editor": "^14.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", + "@wordpress/edit-post": "^8.0.8", + "@wordpress/editor": "^14.0.7", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/notices": "^5.0.2", + "@wordpress/plugins": "^7.0.3", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1", "change-case": "^4.1.2" }, @@ -6202,9 +6195,9 @@ } }, "node_modules/@wordpress/block-editor": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-13.0.1.tgz", - "integrity": "sha512-nactAtC/Tu0VN7r//uKbv7S2oPgzPH82JIHcdOgNVvQVdutufh7cUBfFCxCq6KLDirZ1hQWSE9Yzid/7E37jmw==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-13.0.6.tgz", + "integrity": "sha512-j1+wfHK5qIi/LBdUyaT1ipIGfkcBnLZBlFglaCliwTO7jOMH0+Kyc8vwKFW4waPZVesGt1+w+s1YZrtgkA+vdQ==", "dependencies": { "@babel/runtime": "^7.16.0", "@emotion/react": "^11.7.1", @@ -6213,11 +6206,11 @@ "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/commands": "^1.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/blocks": "^13.0.3", + "@wordpress/commands": "^1.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/date": "^5.0.1", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", @@ -6226,15 +6219,15 @@ "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", + "@wordpress/icons": "^10.0.2", "@wordpress/is-shallow-equal": "^5.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", - "@wordpress/style-engine": "^2.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", + "@wordpress/style-engine": "^2.0.2", "@wordpress/token-list": "^3.0.1", "@wordpress/url": "^4.0.1", "@wordpress/warning": "^3.0.1", @@ -6263,21 +6256,21 @@ } }, "node_modules/@wordpress/block-library": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-9.0.1.tgz", - "integrity": "sha512-RqvXQhELagIEQSRgdAO66ih9rqwG+6/8jAylCRwdr+NSm0rqce59SNEJddb3K32cTuNuPy7BG4fm2mqSSnQDIw==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-9.0.7.tgz", + "integrity": "sha512-n4ON/8AqcXdJmvW15d4cTYxxljgjPsUzWH7JvQreQdZDoOTnaDaF8rdil0gYa7YSaDCVMsniDxAItyUFxUbijg==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", "@wordpress/autop": "^4.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/date": "^5.0.1", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", @@ -6286,20 +6279,20 @@ "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/interactivity": "^6.0.1", - "@wordpress/interactivity-router": "^2.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/interactivity": "^6.0.2", + "@wordpress/interactivity-router": "^2.0.2", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/patterns": "^2.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/patterns": "^2.0.6", "@wordpress/primitives": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/reusable-blocks": "^5.0.1", - "@wordpress/rich-text": "^7.0.1", - "@wordpress/server-side-render": "^5.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/reusable-blocks": "^5.0.6", + "@wordpress/rich-text": "^7.0.2", + "@wordpress/server-side-render": "^5.0.3", "@wordpress/url": "^4.0.1", - "@wordpress/viewport": "^6.0.1", + "@wordpress/viewport": "^6.0.2", "@wordpress/wordcount": "^4.0.1", "change-case": "^4.1.2", "clsx": "^2.1.1", @@ -6333,15 +6326,15 @@ } }, "node_modules/@wordpress/blocks": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-13.0.1.tgz", - "integrity": "sha512-ByZtkOrnbPuKxsdRJC77ydH7SGombYCfHK0NLUJhxeGPUAYC3m/Z8uU2w+m+Ff3XmmUF5v3QaTIwkGeLsJMgKQ==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-13.0.3.tgz", + "integrity": "sha512-HukM204sR8KpaQB/Mt6m8GdcYFBzdbss8SxKRgIDQOp9wttqKEMe4s9RmDqclIeQOFmHsFpHFvPEANjzsUwbSg==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/autop": "^4.0.1", "@wordpress/blob": "^4.0.1", "@wordpress/block-serialization-default-parser": "^5.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", "@wordpress/element": "^6.0.1", @@ -6349,8 +6342,8 @@ "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", "@wordpress/is-shallow-equal": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", "@wordpress/shortcode": "^4.0.1", "change-case": "^4.1.2", "colord": "^2.7.0", @@ -6383,18 +6376,18 @@ } }, "node_modules/@wordpress/commands": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-1.0.1.tgz", - "integrity": "sha512-s3Yxq5pPO6w+TrizzySzIVO8PF0MOB6bO24bZdiTPM1f4K+hblPkTpnmoBYpJtHLwxiJfAmcTYWkoBxDXLRtjA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-1.0.3.tgz", + "integrity": "sha512-GwWu3hDoIzWatsMTeyzP+9rzSYUBcjbnKV6Q91SvMW9WnURznmwiHB2/mA80z1Glg+FJ7q+1ybuJLZ8IxxtCkw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^28.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/components": "^28.0.3", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/keyboard-shortcuts": "^5.0.2", + "@wordpress/private-apis": "^1.0.2", "clsx": "^2.1.1", "cmdk": "^0.2.0" }, @@ -6408,9 +6401,9 @@ } }, "node_modules/@wordpress/components": { - "version": "28.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-28.0.1.tgz", - "integrity": "sha512-RRpX2wZKORmzidusM92IZ7i8Y3Oo8iWPZM3DSwGBZVuUIF+ao0EyMpN7l/y6Lm4buVGDLQL5b1sH8wcN74prpw==", + "version": "28.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-28.0.3.tgz", + "integrity": "sha512-94OCwm21IlsZ94UCNrm9VSNVAbauddWmc+8/ytffd2Kgrjw0nNfIF+SgB1fUj3XVwKcqJ/CyEHZ8sEPDL31wEw==", "dependencies": { "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", @@ -6434,12 +6427,12 @@ "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", + "@wordpress/icons": "^10.0.2", "@wordpress/is-shallow-equal": "^5.0.1", "@wordpress/keycodes": "^4.0.1", "@wordpress/primitives": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", "@wordpress/warning": "^3.0.1", "change-case": "^4.1.2", "clsx": "^2.1.1", @@ -6497,21 +6490,21 @@ } }, "node_modules/@wordpress/core-commands": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-1.0.1.tgz", - "integrity": "sha512-RcY49Y4LVENvVXexYPjnr9R96BYjZDskAL2Jpj1rrnmMwyuw1O2pWe+TvbcIrCcLJ882OsxbVB5XNomr8POFjg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-1.0.6.tgz", + "integrity": "sha512-KkZ93Q79XlpxwL9tAsw/MSmrJ1T0Q9DFa48p2U4xcnZGrKh5p4vFAwJV3MOpZDbVXkoT/1msBNe6+FHmRdaREQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/commands": "^1.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/commands": "^1.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/router": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/router": "^1.0.2", "@wordpress/url": "^4.0.1" }, "engines": { @@ -6524,23 +6517,23 @@ } }, "node_modules/@wordpress/core-data": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-7.0.1.tgz", - "integrity": "sha512-VFRXFbWHXh5fvPnMTD2MZQcEnvIY5C8+Ipl7Y8b4oPZ7IrMp99TKdN7AGG8FzK2bbm22Rse4VblWCseyIioGBQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-7.0.6.tgz", + "integrity": "sha512-hjLoLBmRFvueEK5HWQMVWQYL985zq5gPouWYvpSDpjTRUkRctThmilB8xrdX17Zjgs52xOX/DpLAvFbWGlCsYw==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", "@wordpress/is-shallow-equal": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", "@wordpress/sync": "^1.0.1", "@wordpress/undo-manager": "^1.0.1", "@wordpress/url": "^4.0.1", @@ -6560,31 +6553,31 @@ } }, "node_modules/@wordpress/customize-widgets": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/customize-widgets/-/customize-widgets-5.0.1.tgz", - "integrity": "sha512-FXI4dWhctNvBR7xJAFqovSnIOb3qlW/6wsaIz7bAjbdvbMSLNPx4/aeEi1hN1ZwlRo1B9Yq5/I4JHGVvD2X60g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@wordpress/customize-widgets/-/customize-widgets-5.0.7.tgz", + "integrity": "sha512-rGpa8ylei6tUN55QHLo79JcglySC263SDc+TPR8CXXwUiGG9h03ZgrMTo/Ho50KSp4WGhZELWccGAk4uPxh8Bw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/block-library": "^9.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/block-library": "^9.0.7", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/dom": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/interface": "^6.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/interface": "^6.0.3", "@wordpress/is-shallow-equal": "^5.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", "@wordpress/media-utils": "^5.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/widgets": "^4.0.1", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/widgets": "^4.0.6", "clsx": "^2.1.1", "fast-deep-equal": "^3.1.3" }, @@ -6598,9 +6591,9 @@ } }, "node_modules/@wordpress/data": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.0.1.tgz", - "integrity": "sha512-qPPsqBp0beYzzpLr6P2YIUcqUNV8EPFTcVQu6z+IOdCVvmEQmHe1dc6ZC7SxYuep0eTcoPh311mymGQxUMHeAA==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.0.2.tgz", + "integrity": "sha512-2ntfc1hag1jhK3Zrli0NDDT4HPY3k0Fkdf9VdDJVGEE8yQIrtRHbwfTg39CDNIiNG1Ey17Gklv0vXjpgvZVwZg==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/compose": "^7.0.1", @@ -6608,7 +6601,7 @@ "@wordpress/element": "^6.0.1", "@wordpress/is-shallow-equal": "^5.0.1", "@wordpress/priority-queue": "^3.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/private-apis": "^1.0.2", "@wordpress/redux-routine": "^5.0.1", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", @@ -6627,13 +6620,13 @@ } }, "node_modules/@wordpress/data-controls": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-4.0.1.tgz", - "integrity": "sha512-++P5YUnnjgFY8aPry4J+hqsu/gZ0j5ptlv/So1U6wAzJTcjdaGpDCOL/35dYdPB2JmOxf3XcO+D/1LLQmsnFkg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-4.0.2.tgz", + "integrity": "sha512-MfFRJGwwmvLB+7/Fqs4h/66tAbDhpn6QfYUfeF+fELSaWZpWN0cwjjFcCGBLcPvn046fPeNOJlCsz5aDYYoMrA==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1" }, "engines": { @@ -6645,19 +6638,19 @@ } }, "node_modules/@wordpress/dataviews": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/dataviews/-/dataviews-2.0.1.tgz", - "integrity": "sha512-rBIItAuYvGKpOIgyMk0FY5w/EwFX4nkNwgPbuUfmBykIGy43nWVOrIMUOnH7IGP9m/vBlTRnRNtNbMK2f9CdeA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@wordpress/dataviews/-/dataviews-2.0.4.tgz", + "integrity": "sha512-4FpLyvmiO0su4Fx3cLctqTIE+Bp1HCC1fHRFZw5JJgvkNXuAKHJRdpFCo1O11ZMH3ibxpWVshXZcfu7Z7HBWlw==", "dependencies": { "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", + "@wordpress/icons": "^10.0.2", "@wordpress/primitives": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/private-apis": "^1.0.2", "clsx": "^2.1.1", "remove-accents": "^0.5.0" }, @@ -6685,9 +6678,9 @@ } }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.0.1.tgz", - "integrity": "sha512-xfsDY9kr9J7Lec899t8BC/QaSfqa5w9ONnuj1AplwdVToIXrLO1LQNHaP6/Yw1qjRwA8iE7USMnW5huUTnDoUA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.0.2.tgz", + "integrity": "sha512-XvIEKvA4YB1mDyfiiOddhWDU18ICuDS0wf+et52EXu4ad5YYZzpKpnqKsLh9HoCHBbVCzgiWOesya3eugdqKKw==", "dev": true, "dependencies": { "json2php": "^0.0.7" @@ -6826,39 +6819,40 @@ } }, "node_modules/@wordpress/edit-post": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-8.0.1.tgz", - "integrity": "sha512-FIqy8DbpwAyarLUTHNdznBoTRkc3fIouD/u9MptR3GPjflZfARA1azkSwYy+XIruqcdfWdnxLloX9rcVsbukbw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-8.0.8.tgz", + "integrity": "sha512-zB4k+1NbD1J9dhxq+c+Nqih+Zm6yOgMNs4TRNF6YYBOGSuAPxcNSA0qtfALC8PtI19MpUv0JCh3HckaJQs+fHQ==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/block-library": "^9.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/commands": "^1.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/block-library": "^9.0.7", + "@wordpress/blocks": "^13.0.3", + "@wordpress/commands": "^1.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-commands": "^1.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-commands": "^1.0.6", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", - "@wordpress/editor": "^14.0.1", + "@wordpress/editor": "^14.0.7", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", + "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1", - "@wordpress/viewport": "^6.0.1", + "@wordpress/viewport": "^6.0.2", "@wordpress/warning": "^3.0.1", - "@wordpress/widgets": "^4.0.1", + "@wordpress/widgets": "^4.0.6", "clsx": "^2.1.1", "memize": "^2.1.0" }, @@ -6872,50 +6866,50 @@ } }, "node_modules/@wordpress/edit-site": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-6.0.1.tgz", - "integrity": "sha512-kl5lQUdTN7Ju/dcydApN4OcbAs+MZTTEW654hq7dHrD7BHvvkWiKwxACTGUbXnb3EguCnX/fRib4QCRtmMnQfw==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-6.0.8.tgz", + "integrity": "sha512-HJZmmpV3UDPylfa0+3MNXNzfgWt+eGoYdmLG4z2JpNPEyOZ+w0yY3nx8oSsx85sHtT5bRT0drWsj0gqr4qZynw==", "dependencies": { "@babel/runtime": "^7.16.0", "@react-spring/web": "^9.4.5", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/block-library": "^9.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/commands": "^1.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/block-library": "^9.0.7", + "@wordpress/blocks": "^13.0.3", + "@wordpress/commands": "^1.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-commands": "^1.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", - "@wordpress/dataviews": "^2.0.1", + "@wordpress/core-commands": "^1.0.6", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", + "@wordpress/dataviews": "^2.0.4", "@wordpress/date": "^5.0.1", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", - "@wordpress/editor": "^14.0.1", + "@wordpress/editor": "^14.0.7", "@wordpress/element": "^6.0.1", "@wordpress/escape-html": "^3.0.1", "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/patterns": "^2.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/patterns": "^2.0.6", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", "@wordpress/primitives": "^4.0.1", "@wordpress/priority-queue": "^3.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/reusable-blocks": "^5.0.1", - "@wordpress/router": "^1.0.1", - "@wordpress/style-engine": "^2.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/reusable-blocks": "^5.0.6", + "@wordpress/router": "^1.0.2", + "@wordpress/style-engine": "^2.0.2", "@wordpress/url": "^4.0.1", - "@wordpress/viewport": "^6.0.1", - "@wordpress/widgets": "^4.0.1", + "@wordpress/viewport": "^6.0.2", + "@wordpress/widgets": "^4.0.6", "@wordpress/wordcount": "^4.0.1", "change-case": "^4.1.2", "clsx": "^2.1.1", @@ -6934,37 +6928,37 @@ } }, "node_modules/@wordpress/edit-widgets": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-6.0.1.tgz", - "integrity": "sha512-KXIa9UC7hFZbgYg83CPPaE5q8HPjczqi99UY/oTCNbVWA/uM0YRYxWUe/tSoAgDEWsZ+J12eMHoKtLpMWcj8wA==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-6.0.7.tgz", + "integrity": "sha512-KUas6yd7Lmt623F5lQNd/n2S9VWFfmtjckemlwlP7niXaU8XrdEhRFxDAY52W6Jk90KXVxLsG20qnx8nYah3NQ==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/block-library": "^9.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/block-library": "^9.0.7", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/interface": "^6.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/interface": "^6.0.3", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", "@wordpress/media-utils": "^5.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/patterns": "^2.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/reusable-blocks": "^5.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/patterns": "^2.0.6", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/reusable-blocks": "^5.0.6", "@wordpress/url": "^4.0.1", - "@wordpress/widgets": "^4.0.1", + "@wordpress/widgets": "^4.0.6", "clsx": "^2.1.1" }, "engines": { @@ -6977,21 +6971,21 @@ } }, "node_modules/@wordpress/editor": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-14.0.1.tgz", - "integrity": "sha512-TDTQJLAU20TFoxz05nqNpHF6arXMmb2GONKTdlXnvIdGIYHworVuYPPQSwQuDj0PSnG9V810qULbCEtjB+tRgQ==", + "version": "14.0.7", + "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-14.0.7.tgz", + "integrity": "sha512-Y3eHgVbf8h+T3Ssn1hLFpAGBwNxbP2ZeWQIwopIZ2V5d/qcHWzRW2Aoss3Wy1K+NHQJTkXJSsfrnC2zyd7dZBQ==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/commands": "^1.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/commands": "^1.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/date": "^5.0.1", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", @@ -6999,19 +6993,19 @@ "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/interface": "^6.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/interface": "^6.0.3", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", "@wordpress/media-utils": "^5.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/patterns": "^2.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/reusable-blocks": "^5.0.1", - "@wordpress/rich-text": "^7.0.1", - "@wordpress/server-side-render": "^5.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/patterns": "^2.0.6", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/reusable-blocks": "^5.0.6", + "@wordpress/rich-text": "^7.0.2", + "@wordpress/server-side-render": "^5.0.3", "@wordpress/url": "^4.0.1", "@wordpress/warning": "^3.0.1", "@wordpress/wordcount": "^4.0.1", @@ -7125,22 +7119,22 @@ } }, "node_modules/@wordpress/format-library": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/format-library/-/format-library-5.0.1.tgz", - "integrity": "sha512-HWL8RFJZj8yuGyTDcCiRKkkoNnvmfeAgI6/8NexFovOTHagZGdhFNs7QrQd7JXk+rSwhpdUXowBxOGgz561mKw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/format-library/-/format-library-5.0.6.tgz", + "integrity": "sha512-91LTfbjfgPqVnI8uuBEtA6ZBzJCmFyUJn1amGcvlpK9gX1BRvCZmmbOwTfAs4Wce7NIJswUr//5I765pz7P8Tg==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", "@wordpress/url": "^4.0.1" }, "engines": { @@ -7197,9 +7191,9 @@ } }, "node_modules/@wordpress/icons": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.0.1.tgz", - "integrity": "sha512-CyemntOjhTzYXismZFDcX5ZETxBd8ltokF0Bdo7d2wExxIYiPlSfKcjTxTIOuwKJUZ+3owWhhFu0wxpiOQs9BA==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.0.2.tgz", + "integrity": "sha512-YocOYpnB/zRW28ApIBIICnV28HaaCX1ayjm/tRUp76q1J/c2pK4meCU4keR6IDpuEg5dtkzGLWxR/fK+KtAt6w==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/element": "^6.0.1", @@ -7211,9 +7205,9 @@ } }, "node_modules/@wordpress/interactivity": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-6.0.1.tgz", - "integrity": "sha512-bqWFgqNIA2QMSQRh/yGP6xWEvDBjLZeOi97KLqcResUYrx6GL0Y6bD5RdsoGiDXDdH9bsHkuOepEaBHHVFFu8w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-6.0.2.tgz", + "integrity": "sha512-QsY29re3NP+21tSQ5TaJHmgZlzKHSTWdVukAk1lgzkzTBrE4URIZoCjk4KzCWKd5X7FPqEEz8vKoWM1E3ZbKFw==", "dependencies": { "@preact/signals": "^1.2.2", "deepsignal": "^1.4.0", @@ -7225,11 +7219,11 @@ } }, "node_modules/@wordpress/interactivity-router": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-2.0.1.tgz", - "integrity": "sha512-n7qP78FwLJ/zE3m3JfE0LSMZbZF83jwM9ifh73NLLf3fv3hwlIEwjp+OPuS0hfTDYBYhAx8BqAOLGMTTW1ibCQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-2.0.2.tgz", + "integrity": "sha512-igySPnGkQxiRVIPkE/mwR/N4ThFeewUdtpiMSpWstUGEDnOwIks6+vh1f6qpZKwZW1qMyZMheyuXz+H4ABZwYQ==", "dependencies": { - "@wordpress/interactivity": "^6.0.1" + "@wordpress/interactivity": "^6.0.2" }, "engines": { "node": ">=18.12.0", @@ -7237,23 +7231,23 @@ } }, "node_modules/@wordpress/interface": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-6.0.1.tgz", - "integrity": "sha512-5rgTxC9SkOE22XLraP5AfpGEWisD62VfDfnnthORWKVMta605+9MjqZt+KlxIpULpI0F511szu6llb9ITQJFjw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-6.0.3.tgz", + "integrity": "sha512-jtgXweeWIzhMt6v2wTXRFAmFYttpE1N6eWmruObvXMZlJAMhMf/nwAHVDZr65O/03shdG+1E+4fyVRgOgUz+Ww==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/viewport": "^6.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/viewport": "^6.0.2", "clsx": "^2.1.1" }, "engines": { @@ -7313,12 +7307,12 @@ } }, "node_modules/@wordpress/keyboard-shortcuts": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-5.0.1.tgz", - "integrity": "sha512-NGY+xX0GtnrGIV3E0U7EhcYt03G+9HA3i8tj7YKYIxlGhdEdytPB3HSd8WsWi91VaCFnizYaZzRYyv2rRsXe0A==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-5.0.2.tgz", + "integrity": "sha512-eNCpGJ+/PheT1ph9ldzK97lL6Wsrnlf2+keKTHvFX5kzUMjBfsaVHNNnOhsbk80uVNtYaE39V6Eg1iPc+yZtwA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/keycodes": "^4.0.1" }, @@ -7344,14 +7338,14 @@ } }, "node_modules/@wordpress/list-reusable-blocks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/list-reusable-blocks/-/list-reusable-blocks-5.0.1.tgz", - "integrity": "sha512-W9FpQ/wlsb8tOXYslRI3sP5dfkVCnEpJW84qsEHvHNW1EGUTBLlRGkAKM1J3NImDPKkHaMbh6LWdW4HudG7KXg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/list-reusable-blocks/-/list-reusable-blocks-5.0.3.tgz", + "integrity": "sha512-m1+SvihoFIxv8D6PoukFeFkHX89l+S0xb3G3qWod3t2cDvfrqLmDPua97Pi3EiOX6tSPA7M+4DvysYdBkjikCw==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", @@ -7383,13 +7377,13 @@ } }, "node_modules/@wordpress/notices": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-5.0.1.tgz", - "integrity": "sha512-SQsLyYTAhfKAbNkLx50redcMDcNeZ8vwK2wFOGMSsUR6QoiT9lcTJqDe85jV9XFeZ8+GfQJxXvayhYzXA25g5g==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-5.0.2.tgz", + "integrity": "sha512-ihlXgxfce+h+ztyj9btb4s6fNhUU7cNGTF+VjjKHRdeRBw62bc86xI7fyIrtm6utVrZiWtSSQUtIR/Cbvao7TQ==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/data": "^10.0.1" + "@wordpress/data": "^10.0.2" }, "engines": { "node": ">=18.12.0", @@ -7413,18 +7407,18 @@ } }, "node_modules/@wordpress/nux": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/nux/-/nux-9.0.1.tgz", - "integrity": "sha512-rkCVhE1cu5GufYptdHxSxNoxsq7Exi72E2SLBxA4LisNm2qfLh4PWEWn2ulWc3IBw0Y6cmiM4SfqqA4o/wicpQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/nux/-/nux-9.0.3.tgz", + "integrity": "sha512-3W5sQivCFmu8f7qgieHvtVog6orYCQFTt53/4uYp/spZohxZG+4EUTziSItR9yZpjVQL9cX6oFk7aTsxwFh+Hg==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1" + "@wordpress/icons": "^10.0.2" }, "engines": { "node": ">=18.12.0", @@ -7436,24 +7430,24 @@ } }, "node_modules/@wordpress/patterns": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-2.0.1.tgz", - "integrity": "sha512-F7HHPn0CavyUnGyUJwCEkwbPD39I2jR3Fg/CZSvSyzFk7hBCOZBGbdnSh1g1JLCgznmfUkvUMWgq3Bo1F+E7iw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-2.0.6.tgz", + "integrity": "sha512-b1ne2lxq4sdl4/HvIFIVcbnGfDL5BWpo3x+uf22N/bMXVxSNBWlGlI9TDtISnkHzpXFlrjcPiAmCBz//hEJa/g==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/notices": "^5.0.2", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1" }, "engines": { @@ -7466,16 +7460,16 @@ } }, "node_modules/@wordpress/plugins": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-7.0.1.tgz", - "integrity": "sha512-uaj4KHAPodJIaz19oZGK0BGXXTQpgp/fGfWZceQyqgAPZBIMJqWgxhFzFkcLjvkqoKEG0XhEKX7ZcWGVpIhnWg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-7.0.3.tgz", + "integrity": "sha512-snU0QE1jyonhu7fM/3uH812nIIfwPF5UwRBFZzaUWc2dwyRHmYcvuwxrlQXUCrEU+cj9BXkmuJHTE6KWvveehQ==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", - "@wordpress/icons": "^10.0.1", + "@wordpress/icons": "^10.0.2", "@wordpress/is-shallow-equal": "^5.0.1", "memize": "^2.0.1" }, @@ -7506,20 +7500,20 @@ } }, "node_modules/@wordpress/preferences": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-4.0.1.tgz", - "integrity": "sha512-/Y1/eWLDGDFsrUkdulMpayYQyK4HB+GnG4gLfyeh7y4uD77WsNApbvMyU6qbAKHROVRJs25UNbP56Ah8SM1BUw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-4.0.3.tgz", + "integrity": "sha512-H255doYLwI5/03LiUhRcQyKHD7ZjF4S7pfYTicuM0GH/vYWHPtwsnWUEu2MXxIraoEuAnescMyczjHJ7bZRXug==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/private-apis": "^1.0.2", "clsx": "^2.1.1" }, "engines": { @@ -7585,9 +7579,9 @@ } }, "node_modules/@wordpress/private-apis": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.0.1.tgz", - "integrity": "sha512-g6ftWDyCXG3cWN2NgdJsgIC1yYwBlzCRfcKlEq0r4fVQ71YulEW1el1L8a9LyFhEC5X2IzFN9FFJ1nca4/pMtA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.0.2.tgz", + "integrity": "sha512-epLzW+hxekcZwO0C4u8wVL0j9Ko+5Go20zQ2IN9WBSXXb9xOTI8Nje+pww70ukrkUk2UqBvXP17NvSdYV5LTwA==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -7615,21 +7609,21 @@ } }, "node_modules/@wordpress/reusable-blocks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-5.0.1.tgz", - "integrity": "sha512-Xqs027qSscDQHck9U10oQOnwqlC+/c0VDOhmtoA2+5hjkcq5w0UMXHlZsQcrcnmlzCn2lN/DUF+zyL4ujLeHqg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-5.0.6.tgz", + "integrity": "sha512-a/ZeUFGkAr7Ki/a0pQ0SsSODrY3q1ff+qe2E2+I0ORk5FSX71yli56bmlqbsFr/KCTRcITtYRsev4oalL6QzDg==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/notices": "^5.0.2", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1" }, "engines": { @@ -7642,14 +7636,14 @@ } }, "node_modules/@wordpress/rich-text": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.0.1.tgz", - "integrity": "sha512-E1E1tBur7Lv7ALXsawXpA/47N6/3T39m7t5elgZGz+t3H9fbH3zMoO1lNFYhQnAfcl/crutCiG1ESuyrPhzujg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.0.2.tgz", + "integrity": "sha512-zj7+ekI9X73OrUq8C5OMX9KDBgNP9SJO6lA/aiKE4YgQBxrpMDPu7Ser+gr4CJtJtKYiJWQxsWGsVASjNbt5+g==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/escape-html": "^3.0.1", @@ -7666,13 +7660,13 @@ } }, "node_modules/@wordpress/router": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-1.0.1.tgz", - "integrity": "sha512-nhZCj1cvCiu5vAyGWQtiSX4tTEFhaun9RL+McY5bihHLfSn0bnOp1Ov0sAH/hLntc6NNAOOVavDI+iRJiqjb3Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-1.0.2.tgz", + "integrity": "sha512-KgREOYycD9z5P+1LtukoPLYw4pOaWkaUS0K86wIfKBsHCSUtDqqHA4sLQMb2fESPH306OhzMHVz4xqNqKrUZYQ==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/element": "^6.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1", "history": "^5.3.0" }, @@ -7685,9 +7679,9 @@ } }, "node_modules/@wordpress/scripts": { - "version": "28.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-28.0.1.tgz", - "integrity": "sha512-ZvPFcGz+9Rj6OrvePOfNjigUlSDyL2FbZWlj2KyR4TM5H9S4XHd7q4NyjrnT1RFhj/D0ZyJETdBWjO6yTJtlVw==", + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-28.0.2.tgz", + "integrity": "sha512-TawbVRncjSjDDsjdoBdwMIvJaFobTL9Dq5Euh3o+1iEjT5gAsb8nn+ICQpW5xSnZTBCoUps8EAksVpytwxOCIg==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", @@ -7695,7 +7689,7 @@ "@svgr/webpack": "^8.0.1", "@wordpress/babel-preset-default": "^8.0.1", "@wordpress/browserslist-config": "^6.0.1", - "@wordpress/dependency-extraction-webpack-plugin": "^6.0.1", + "@wordpress/dependency-extraction-webpack-plugin": "^6.0.2", "@wordpress/e2e-test-utils-playwright": "^1.0.1", "@wordpress/eslint-plugin": "^19.0.1", "@wordpress/jest-preset-default": "^12.0.1", @@ -7851,6 +7845,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/@wordpress/scripts/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@wordpress/scripts/node_modules/copy-webpack-plugin": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", @@ -7875,6 +7878,208 @@ "webpack": "^5.1.0" } }, + "node_modules/@wordpress/scripts/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/@wordpress/scripts/node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/@wordpress/scripts/node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/@wordpress/scripts/node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dev": true, + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@wordpress/scripts/node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@wordpress/scripts/node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, + "node_modules/@wordpress/scripts/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/@wordpress/scripts/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/@wordpress/scripts/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/@wordpress/scripts/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/@wordpress/scripts/node_modules/filenamify": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", @@ -7964,6 +8169,24 @@ "node": ">=8" } }, + "node_modules/@wordpress/scripts/node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, + "node_modules/@wordpress/scripts/node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/@wordpress/scripts/node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -7985,6 +8208,413 @@ "node": ">=8" } }, + "node_modules/@wordpress/scripts/node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "dev": true, + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dev": true, + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/@wordpress/scripts/node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/@wordpress/scripts/node_modules/prettier": { "name": "wp-prettier", "version": "3.0.3", @@ -8070,6 +8700,22 @@ "webpack": "^5.0.0" } }, + "node_modules/@wordpress/scripts/node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, "node_modules/@wordpress/scripts/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8082,6 +8728,31 @@ "node": ">=8" } }, + "node_modules/@wordpress/scripts/node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, "node_modules/@wordpress/scripts/node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -8092,16 +8763,16 @@ } }, "node_modules/@wordpress/server-side-render": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-5.0.1.tgz", - "integrity": "sha512-nH+KZ9HwnDcsjkqwQZBvY4gBCDXr0z8F1ly7cWJPdqLXroaIB91yunj8oED08BpUHyTad/srqYl2PF9fzfz6Eg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-5.0.3.tgz", + "integrity": "sha512-/8bP+uTqX/9lU7fvRuq5D0RmYPu48mN4vdEdA7HNifrm+2v7lLHA66aq+1gCfwhxxOowuvaw+ZGnpGXB0wRx1g==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", @@ -8131,9 +8802,9 @@ } }, "node_modules/@wordpress/style-engine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-2.0.1.tgz", - "integrity": "sha512-c4da79SjZvtqMrCHkAT9cCuv5diHnp6nU5DAZpUYKmynFJlxA1/0fqdHMwdt3GlxiXM/WgkiFI3P/w0/Ce+UGQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-2.0.2.tgz", + "integrity": "sha512-Jq1853v68orVnNsxDiB2LGeNlmQoSowk3ypuKCaz2YTWX4etKWMbIdFvmY99OKKuzNDRCG8XklXMEIR+tsiGIQ==", "dependencies": { "@babel/runtime": "^7.16.0", "change-case": "^4.1.2" @@ -8220,13 +8891,13 @@ } }, "node_modules/@wordpress/viewport": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-6.0.1.tgz", - "integrity": "sha512-IUMr48mza9b3qZe+Jc0uCbz94Y9XA7idzj4IPQd0WnRXE58/xJQ+3bNW3R5LQJpzzOaQCaCqrvfD0jjIrbwVzQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-6.0.2.tgz", + "integrity": "sha512-6/3EI6mWKj6t25ZW70FPchvmXpeSFm/qE3nmOEezOKxcj8UXpOVkJksIyTICK2lztLQ70ErGdSCnXby+SMJksA==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1" }, "engines": { @@ -8247,22 +8918,22 @@ } }, "node_modules/@wordpress/widgets": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-4.0.1.tgz", - "integrity": "sha512-oqnr6Dtb51jNVMJy67pCJMz1eIw4E0O7deh6HKl0oaB2WPLShC4Sg+xrMmL2SGdhPNNm7Kk7/rnCR21LfNKcdw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-4.0.6.tgz", + "integrity": "sha512-PPhSX9NuQKpBVdF+bw2Ai134fOBACinUjksN49PWdSbBpTH5uCr5QEOisMsRsKuqr8k6hphMNnKuRxq0qfulBw==", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/notices": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/notices": "^5.0.2", "clsx": "^2.1.1" }, "engines": { @@ -8502,6 +9173,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -8978,9 +9661,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.17", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", - "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "dev": true, "funding": [ { @@ -8997,8 +9680,8 @@ } ], "dependencies": { - "browserslist": "^4.22.2", - "caniuse-lite": "^1.0.30001578", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -9383,9 +10066,9 @@ "dev": true }, "node_modules/backbone": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.5.0.tgz", - "integrity": "sha512-RPKlstw5NW+rD2X4PnEnvgLhslRnXOugXw2iBloHkPMgOxvakP1/A+tZIGM3qCm8uvZeEf8zMm0uvcK1JwL+IA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.6.0.tgz", + "integrity": "sha512-13PUjmsgw/49EowNcQvfG4gmczz1ximTMhUktj0Jfrjth0MVaTxehpU+qYYX4MxnuIuhmvBLC6/ayxuAGnOhbA==", "dependencies": { "underscore": ">=1.8.3" } @@ -9396,6 +10079,52 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, "node_modules/base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", @@ -9909,13 +10638,13 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -9923,7 +10652,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -10053,9 +10782,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "funding": [ { @@ -10072,10 +10801,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -10383,9 +11112,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001587", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", - "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", "dev": true, "funding": [ { @@ -10629,21 +11358,21 @@ } }, "node_modules/chokidar/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/chokidar/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -10719,17 +11448,25 @@ } }, "node_modules/chromium-bidi": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz", - "integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==", + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.24.tgz", + "integrity": "sha512-5xQNN2SVBdZv4TxeMLaI+PelrnZsHDhn8h2JtyriLr+0qHcZS8BMuo93qN6J1VmtmrgYP+rmcLHcbpnA8QJh+w==", "dev": true, "dependencies": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" } }, + "node_modules/chromium-bidi/node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, "node_modules/ci-info": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", @@ -11068,12 +11805,6 @@ "node": ">= 12.0.0" } }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -11252,9 +11983,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -11632,9 +12363,9 @@ "dev": true }, "node_modules/css-declaration-sorter": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz", - "integrity": "sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -11748,16 +12479,16 @@ } }, "node_modules/cssnano": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz", - "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.3.tgz", + "integrity": "sha512-lsekJctOTqdCn4cNrtrSwsuMR/fHC+oiVMHkp/OugBWtwjH8XJag1/OtGaYJGtz0un1fQcRy4ryfYTQsfh+KSQ==", "dev": true, "dependencies": { - "cssnano-preset-default": "^6.0.3", - "lilconfig": "^3.0.0" + "cssnano-preset-default": "^7.0.3", + "lilconfig": "^3.1.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "funding": { "type": "opencollective", @@ -11768,55 +12499,56 @@ } }, "node_modules/cssnano-preset-default": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz", - "integrity": "sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==", - "dev": true, - "dependencies": { - "css-declaration-sorter": "^7.1.1", - "cssnano-utils": "^4.0.1", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.0.2", - "postcss-convert-values": "^6.0.2", - "postcss-discard-comments": "^6.0.1", - "postcss-discard-duplicates": "^6.0.1", - "postcss-discard-empty": "^6.0.1", - "postcss-discard-overridden": "^6.0.1", - "postcss-merge-longhand": "^6.0.2", - "postcss-merge-rules": "^6.0.3", - "postcss-minify-font-values": "^6.0.1", - "postcss-minify-gradients": "^6.0.1", - "postcss-minify-params": "^6.0.2", - "postcss-minify-selectors": "^6.0.2", - "postcss-normalize-charset": "^6.0.1", - "postcss-normalize-display-values": "^6.0.1", - "postcss-normalize-positions": "^6.0.1", - "postcss-normalize-repeat-style": "^6.0.1", - "postcss-normalize-string": "^6.0.1", - "postcss-normalize-timing-functions": "^6.0.1", - "postcss-normalize-unicode": "^6.0.2", - "postcss-normalize-url": "^6.0.1", - "postcss-normalize-whitespace": "^6.0.1", - "postcss-ordered-values": "^6.0.1", - "postcss-reduce-initial": "^6.0.2", - "postcss-reduce-transforms": "^6.0.1", - "postcss-svgo": "^6.0.2", - "postcss-unique-selectors": "^6.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.3.tgz", + "integrity": "sha512-dQ3Ba1p/oewICp/szF1XjFFgql8OlOBrI2YNBUUwhHQnJNoMOcQTa+Bi7jSJN8r/eM1egW0Ud1se/S7qlduWKA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.0", + "postcss-calc": "^10.0.0", + "postcss-colormin": "^7.0.1", + "postcss-convert-values": "^7.0.1", + "postcss-discard-comments": "^7.0.1", + "postcss-discard-duplicates": "^7.0.0", + "postcss-discard-empty": "^7.0.0", + "postcss-discard-overridden": "^7.0.0", + "postcss-merge-longhand": "^7.0.2", + "postcss-merge-rules": "^7.0.2", + "postcss-minify-font-values": "^7.0.0", + "postcss-minify-gradients": "^7.0.0", + "postcss-minify-params": "^7.0.1", + "postcss-minify-selectors": "^7.0.2", + "postcss-normalize-charset": "^7.0.0", + "postcss-normalize-display-values": "^7.0.0", + "postcss-normalize-positions": "^7.0.0", + "postcss-normalize-repeat-style": "^7.0.0", + "postcss-normalize-string": "^7.0.0", + "postcss-normalize-timing-functions": "^7.0.0", + "postcss-normalize-unicode": "^7.0.1", + "postcss-normalize-url": "^7.0.0", + "postcss-normalize-whitespace": "^7.0.0", + "postcss-ordered-values": "^7.0.1", + "postcss-reduce-initial": "^7.0.1", + "postcss-reduce-transforms": "^7.0.0", + "postcss-svgo": "^7.0.1", + "postcss-unique-selectors": "^7.0.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/cssnano-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz", - "integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz", + "integrity": "sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -12950,9 +13682,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", - "integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true, "engines": { "node": ">=12" @@ -12962,12 +13694,12 @@ } }, "node_modules/dotenv-expand": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.3.tgz", - "integrity": "sha512-qkK+MLTvZ86oq4sjMqGpUN/38SQ/J37mny88CsEUFFjb2MBVz06a809ri0QeVDXpxkvZkXzqjGUb0M1R6n3OGw==", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", + "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", "dev": true, "dependencies": { - "dotenv": "^16.4.1" + "dotenv": "^16.4.4" }, "engines": { "node": ">=12" @@ -13092,9 +13824,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.665", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.665.tgz", - "integrity": "sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw==", + "version": "1.4.811", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.811.tgz", + "integrity": "sha512-CDyzcJ5XW78SHzsIOdn27z8J4ist8eaFLhdto2hSMSJQgsiwvbv2fbizcKUICryw1Wii1TI/FEkvzvJsR3awrA==", "dev": true }, "node_modules/element-closest": { @@ -13213,6 +13945,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", @@ -13373,11 +14114,6 @@ "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", "dev": true }, - "node_modules/es-module-shims": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-1.8.2.tgz", - "integrity": "sha512-7vIYVzpOhXtpc3Yn03itB+GSgVZFW7oL4kdydA+iL+IEi7HiSLBUxM05QFw4SxTl6e++pMpGqZPo2+vdNs3TbA==" - }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -13431,9 +14167,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -14742,17 +15478,17 @@ "dev": true }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -15076,21 +15812,21 @@ } }, "node_modules/fast-glob/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/fast-glob/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -15549,12 +16285,12 @@ } }, "node_modules/findup-sync/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -15573,9 +16309,9 @@ } }, "node_modules/findup-sync/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -15774,9 +16510,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -17017,17 +17753,16 @@ } }, "node_modules/grunt-contrib-qunit": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-qunit/-/grunt-contrib-qunit-7.0.1.tgz", - "integrity": "sha512-+5eL4qv2H8q6he+2HGDkqbKwAulRUrtMaX5NoY2AwwvbA4d4OqsI1YGiUZ0L/O9oL7nUQ1cxGKeOp+TcE/AYUg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-qunit/-/grunt-contrib-qunit-10.0.0.tgz", + "integrity": "sha512-CUGMTiJVKR69jUoo6vETlZ0XXZXZPxxqtyD5e2kisE3WKX3RQn1axaof7DRZaZOiX+nl7yzyI1jiSGRJKsT40A==", "dev": true, "dependencies": { "eventemitter2": "^6.4.9", - "p-each-series": "^2.2.0", - "puppeteer": "^19.7.0" + "puppeteer": "^22.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/grunt-contrib-qunit/node_modules/eventemitter2": { @@ -18007,21 +18742,21 @@ } }, "node_modules/http-proxy-middleware/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/http-proxy-middleware/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -20180,12 +20915,12 @@ } }, "node_modules/jest-config/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -20226,9 +20961,9 @@ "dev": true }, "node_modules/jest-config/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -20642,21 +21377,21 @@ } }, "node_modules/jest-haste-map/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/jest-haste-map/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -20872,12 +21607,12 @@ } }, "node_modules/jest-message-util/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -20918,9 +21653,9 @@ "dev": true }, "node_modules/jest-message-util/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -21835,11 +22570,11 @@ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, "node_modules/jquery-color": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jquery-color/-/jquery-color-2.2.0.tgz", - "integrity": "sha512-4VoxsLMw860EQGNT/TmP3Lbr7/1OCQlBPS4ILj7bxRApJrPQfpqzdIOTY8Ll9nGY7UHtWqDuzR7cUcS1lcWjVw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jquery-color/-/jquery-color-3.0.0.tgz", + "integrity": "sha512-TbyqpkxEfVq9K/eUn4jW14YnJn19dORJrplJ3vo9jgLrqnuOyH6gyv8yDZdYcERlrVVDub4HV5r2t5KGjcvbKg==", "peerDependencies": { - "jquery": ">=1.11.0" + "jquery": ">=1.12.0" } }, "node_modules/jquery-form": { @@ -22084,9 +22819,9 @@ } }, "node_modules/jsdom/node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -22487,12 +23222,12 @@ } }, "node_modules/liftup/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -22511,9 +23246,9 @@ } }, "node_modules/liftup/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -23115,12 +23850,15 @@ } }, "node_modules/lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "dev": true, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/line-height": { @@ -23749,7 +24487,7 @@ "node_modules/matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", "dev": true, "dependencies": { "findup-sync": "^2.0.0", @@ -23776,7 +24514,7 @@ "node_modules/matchdep/node_modules/findup-sync": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", "dev": true, "dependencies": { "detect-file": "^1.0.0", @@ -25633,18 +26371,6 @@ "node": ">=4" } }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-event": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", @@ -26071,9 +26797,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -26184,16 +26910,34 @@ "node": ">=8" } }, - "node_modules/playwright-core": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz", - "integrity": "sha512-Z9Ij17X5Z3bjpp6XKujGBp9Gv4eViESac9aDmwgQFUEJBW0K80T21m/Z+XJQlu4cNsvPygw33b6V1Va6Bda5zQ==", + "node_modules/playwright": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", + "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", "dev": true, + "dependencies": { + "playwright-core": "1.45.0" + }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", + "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/plur": { @@ -26271,9 +27015,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -26291,105 +27035,108 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.0.tgz", + "integrity": "sha512-OmjhudoNTP0QleZCwl1i6NeBwN+5MZbY5ersLZz69mjJiDVv/p57RjRuKDkHeDWr4T+S97wQfsqRTNoDHB2e3g==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^6.0.16", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12 || ^20.9 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4.38" } }, "node_modules/postcss-colormin": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz", - "integrity": "sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.1.tgz", + "integrity": "sha512-uszdT0dULt3FQs47G5UHCduYK+FnkLYlpu1HpWu061eGsKZ7setoG7kA+WC9NQLsOJf69D5TxGHgnAdRgylnFQ==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "caniuse-api": "^3.0.0", - "colord": "^2.9.1", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-convert-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz", - "integrity": "sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.1.tgz", + "integrity": "sha512-9x2ofb+hYPwHWMlWAzyWys2yMDZYGfkX9LodbaVTmLdlupmtH2AGvj8Up95wzzNPRDEzPIxQIkUaPJew3bT6xA==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-discard-comments": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz", - "integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.1.tgz", + "integrity": "sha512-GVrQxUOhmle1W6jX2SvNLt4kmN+JYhV7mzI6BMnkAWR9DtVvg8e67rrV0NfdWhn7x1zxvzdWkMBPdBDCls+uwQ==", "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.1.0" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz", - "integrity": "sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.0.tgz", + "integrity": "sha512-bAnSuBop5LpAIUmmOSsuvtKAAKREB6BBIYStWUTGq8oG5q9fClDMMuY8i4UPI/cEcDx2TN+7PMnXYIId20UVDw==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-discard-empty": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz", - "integrity": "sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz", + "integrity": "sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz", - "integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz", + "integrity": "sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -26424,98 +27171,99 @@ "dev": true }, "node_modules/postcss-merge-longhand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz", - "integrity": "sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.2.tgz", + "integrity": "sha512-06vrW6ZWi9qeP7KMS9fsa9QW56+tIMW55KYqF7X3Ccn+NI2pIgPV6gFfvXTMQ05H90Y5DvnCDPZ2IuHa30PMUg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.2" + "stylehacks": "^7.0.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-merge-rules": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz", - "integrity": "sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.2.tgz", + "integrity": "sha512-VAR47UNvRsdrTHLe7TV1CeEtF9SJYR5ukIB9U4GZyZOptgtsS20xSxy+k5wMrI3udST6O1XuIn7cjQkg7sDAAw==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.1", - "postcss-selector-parser": "^6.0.15" + "cssnano-utils": "^5.0.0", + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-minify-font-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz", - "integrity": "sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz", + "integrity": "sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz", - "integrity": "sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz", + "integrity": "sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==", "dev": true, "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^4.0.1", + "colord": "^2.9.3", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-minify-params": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz", - "integrity": "sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.1.tgz", + "integrity": "sha512-e+Xt8xErSRPgSRFxHeBCSxMiO8B8xng7lh8E0A5ep1VfwYhY8FXhu4Q3APMjgx9YDDbSp53IBGENrzygbUvgUQ==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", - "cssnano-utils": "^4.0.1", + "browserslist": "^4.23.1", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz", - "integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.2.tgz", + "integrity": "sha512-dCzm04wqW1uqLmDZ41XYNBJfjgps3ZugDpogAmJXoCb5oCiTzIX4oPXXKxDpTvWOnKxQKR4EbV4ZawJBLcdXXA==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.15" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -26581,149 +27329,149 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", - "integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz", + "integrity": "sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-display-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz", - "integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz", + "integrity": "sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz", - "integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz", + "integrity": "sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz", - "integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz", + "integrity": "sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-string": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz", - "integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz", + "integrity": "sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz", - "integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz", + "integrity": "sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-unicode": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz", - "integrity": "sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.1.tgz", + "integrity": "sha512-PTPGdY9xAkTw+8ZZ71DUePb7M/Vtgkbbq+EoI33EuyQEzbKemEQMhe5QSr0VP5UfZlreANDPxSfcdSprENcbsg==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz", - "integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz", + "integrity": "sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz", - "integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz", + "integrity": "sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-ordered-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz", - "integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz", + "integrity": "sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==", "dev": true, "dependencies": { - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -26738,31 +27486,31 @@ } }, "node_modules/postcss-reduce-initial": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz", - "integrity": "sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.1.tgz", + "integrity": "sha512-0JDUSV4bGB5FGM5g8MkS+rvqKukJZ7OTHw/lcKn7xPNqeaqJyQbUO8/dJpvyTpaVwPsd3Uc33+CfNzdVowp2WA==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz", - "integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz", + "integrity": "sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -26817,9 +27565,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -26830,16 +27578,16 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz", - "integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz", + "integrity": "sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^3.3.2" }, "engines": { - "node": "^14 || ^16 || >= 18" + "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { "postcss": "^8.4.31" @@ -26999,9 +27747,9 @@ } }, "node_modules/postcss-svgo/node_modules/svgo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "dev": true, "dependencies": { "@trysound/sax": "0.2.0", @@ -27024,15 +27772,15 @@ } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz", - "integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.1.tgz", + "integrity": "sha512-MH7QE/eKUftTB5ta40xcHLl7hkZjgDFydpfTK+QWXeHxghVt3VoPqYL5/G+zYZPPIs+8GuqFXSTgxBSoB1RZtQ==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.15" + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -27215,6 +27963,72 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -27265,18 +28079,22 @@ } }, "node_modules/puppeteer": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.11.1.tgz", - "integrity": "sha512-39olGaX2djYUdhaQQHDZ0T0GwEp+5f9UB9HmEP0qHfdQHIq0xGQZuAZ5TLnJIc/88SrPLpEflPC+xUqOTv3c5g==", + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.12.0.tgz", + "integrity": "sha512-kyUYI12SyJIjf9UGTnHfhNMYv4oVK321Jb9QZDBiGVNx5453SplvbdKI7UrF+S//3RtCneuUFCyHxnvQXQjpxg==", "dev": true, "hasInstallScript": true, "dependencies": { - "@puppeteer/browsers": "0.5.0", - "cosmiconfig": "8.1.3", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "puppeteer-core": "19.11.1" + "@puppeteer/browsers": "2.2.3", + "cosmiconfig": "9.0.0", + "devtools-protocol": "0.0.1299070", + "puppeteer-core": "22.12.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/puppeteer-core": { @@ -27345,27 +28163,52 @@ "dev": true }, "node_modules/puppeteer/node_modules/cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "dependencies": { - "import-fresh": "^3.2.1", + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/puppeteer/node_modules/devtools-protocol": { - "version": "0.0.1107588", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz", - "integrity": "sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==", + "version": "0.0.1299070", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", + "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==", "dev": true }, "node_modules/puppeteer/node_modules/js-yaml": { @@ -27380,40 +28223,32 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/puppeteer/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/puppeteer/node_modules/puppeteer-core": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", - "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.12.0.tgz", + "integrity": "sha512-9gY+JwBW/Fp3/x9+cOGK7ZcwqjvtvY2xjqRqsAA0B3ZFMzBauVTSZ26iWTmvOQX2sk78TN/rd5rnetxVxmK5CQ==", "dev": true, "dependencies": { - "@puppeteer/browsers": "0.5.0", - "chromium-bidi": "0.4.7", - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1107588", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.13.0" + "@puppeteer/browsers": "2.2.3", + "chromium-bidi": "0.5.24", + "debug": "4.3.5", + "devtools-protocol": "0.0.1299070", + "ws": "8.17.1" }, "engines": { - "node": ">=14.14.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, "node_modules/puppeteer/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -27514,9 +28349,9 @@ } }, "node_modules/qunit": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.20.0.tgz", - "integrity": "sha512-N8Fp1J55waE+QG1KwX2LOyqulZUToRrrPBqDOfYfuAMkEglFL15uwvmH1P4Tq/omQ/mGbBI8PEB3PhIfvUb+jg==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.21.0.tgz", + "integrity": "sha512-kJJ+uzx5xDWk0oRrbOZ3zsm+imPULE58ZMIrNl+3POZl4a1k6VXj2E4OiqTmZ9j6hh9egE3kNgnAti9Q+BG6Yw==", "dev": true, "dependencies": { "commander": "7.2.0", @@ -27557,9 +28392,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -27927,9 +28762,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.1", @@ -28808,9 +29643,9 @@ } }, "node_modules/sass": { - "version": "1.70.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", - "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -28945,9 +29780,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -29413,9 +30248,9 @@ } }, "node_modules/sinon-test": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/sinon-test/-/sinon-test-3.1.5.tgz", - "integrity": "sha512-uKwKebXEX1yMQ9vz25Q/uDAFwoTsO/AQc+2b+6ndOUoxj7MZWoptz38lFw9QEgfxDPkN6NN0m+Kbah60tn0ZZA==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/sinon-test/-/sinon-test-3.1.6.tgz", + "integrity": "sha512-3jBJGf61sS2EN3M+YuIiIbeutKrubP6SFolceTcJrubG+4s+zq3rey/y0huSEwU2ECKOcyCs7EkzANnwqHWPjA==", "dev": true, "peerDependencies": { "sinon": ">= 2.x" @@ -29808,9 +30643,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -30202,13 +31037,17 @@ } }, "node_modules/streamx": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", - "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/strict-uri-encode": { @@ -30475,16 +31314,16 @@ "dev": true }, "node_modules/stylehacks": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz", - "integrity": "sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.2.tgz", + "integrity": "sha512-HdkWZS9b4gbgYTdMg4gJLmm7biAUug1qTqXjS+u8X+/pUd+9Px1E+520GnOW3rST9MNsVOVpsJG+mPHNosxjOQ==", "dev": true, "dependencies": { - "browserslist": "^4.22.2", - "postcss-selector-parser": "^6.0.15" + "browserslist": "^4.23.1", + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -30600,12 +31439,12 @@ "dev": true }, "node_modules/stylelint/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -30635,9 +31474,9 @@ "dev": true }, "node_modules/stylelint/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -31384,6 +32223,15 @@ "node": ">=8" } }, + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -31874,9 +32722,9 @@ } }, "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" }, "node_modules/underscore.string": { "version": "3.3.5", @@ -32044,9 +32892,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -32063,8 +32911,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -32090,9 +32938,9 @@ } }, "node_modules/uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "dependencies": { "punycode": "^2.1.0" @@ -32174,6 +33022,12 @@ "node": ">= 4" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -32674,9 +33528,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -32877,9 +33731,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -33027,9 +33881,9 @@ } }, "node_modules/whatwg-fetch": { - "version": "3.6.17", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz", - "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==" + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" }, "node_modules/whatwg-mimetype": { "version": "3.0.0", @@ -33199,9 +34053,9 @@ } }, "node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "engines": { "node": ">=8.3.0" @@ -33341,9 +34195,9 @@ } }, "node_modules/y-webrtc/node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "optional": true, "engines": { "node": ">=10.0.0" @@ -33443,6 +34297,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { @@ -35349,12 +36212,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "chalk": { @@ -35383,9 +36246,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -35710,12 +36573,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "chalk": { @@ -35750,9 +36613,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -36003,76 +36866,68 @@ "dev": true }, "@playwright/test": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz", - "integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", + "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", "dev": true, "requires": { - "@types/node": "*", - "fsevents": "2.3.2", - "playwright-core": "1.32.0" + "playwright": "1.45.0" } }, "@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", - "integrity": "sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", + "integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==", "dev": true, "requires": { - "ansi-html-community": "^0.0.8", - "common-path-prefix": "^3.0.0", + "ansi-html": "^0.0.9", "core-js-pure": "^3.23.3", "error-stack-parser": "^2.0.6", - "find-up": "^5.0.0", "html-entities": "^2.1.0", "loader-utils": "^2.0.4", - "schema-utils": "^3.0.0", + "schema-utils": "^4.2.0", "source-map": "^0.7.3" }, "dependencies": { - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" } }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "requires": { - "p-locate": "^5.0.0" + "fast-deep-equal": "^3.1.3" } }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, "requires": { - "p-limit": "^3.0.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -36096,24 +36951,24 @@ } }, "@preact/signals-core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.6.0.tgz", - "integrity": "sha512-O/XGxwP85h1F7+ouqTMOIZ3+V1whfaV9ToIVcuyGriD4JkSD00cQo54BKdqjvBJxbenvp7ynfqRHEwI6e+NIhw==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.6.1.tgz", + "integrity": "sha512-KXEEmJoKDlo0Igju/cj9YvKIgyaWFDgnprShQjzimUd5VynAAdTWMshawEOjUVeKbsI0aR58V6WOQp+DNcKApw==" }, "@puppeteer/browsers": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", - "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.3.tgz", + "integrity": "sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==", "dev": true, "requires": { "debug": "4.3.4", "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "yargs": "17.7.2" }, "dependencies": { "ansi-regex": { @@ -36189,6 +37044,29 @@ "ansi-regex": "^5.0.1" } }, + "tar-fs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", + "dev": true, + "requires": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -36207,9 +37085,9 @@ "dev": true }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -37841,15 +38719,15 @@ } }, "@wordpress/annotations": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/annotations/-/annotations-3.0.1.tgz", - "integrity": "sha512-vJE5zzoakwJZVoBhsI0ePeKrA2xbg6EjyZs4wtbJR1tRUkGjY+J/c8X/y2613ZeyWTSvemubEA5Zz2TrJXGXMw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/annotations/-/annotations-3.0.2.tgz", + "integrity": "sha512-+Z1WGJ0Yy7uh76WjxlQL1JF0veiYMmdWQ4z39B32VZOUNGLcTzD/JfeY1r9LHMi/OD4NBCWB9/e1F5vANdSH7Q==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/hooks": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/rich-text": "^7.0.2", "uuid": "^9.0.1" } }, @@ -37905,37 +38783,37 @@ } }, "@wordpress/block-directory": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/block-directory/-/block-directory-5.0.1.tgz", - "integrity": "sha512-LIGNILjoVL0Ohjh35SGjpQpEd76QZIpRNqkGdYkIKi8mIZfEz9YaP2xiJkdGTN/QE7/6ntL2v9mc8d8EAlH3VQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@wordpress/block-directory/-/block-directory-5.0.8.tgz", + "integrity": "sha512-rHU/X3OZrE/IBtA1ayRqfsqw72IYonb8jiVWYBod+NI2bMeOEyXJ3PP/o7N856YFDVEka8b3sBNcr+A15xGysw==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", - "@wordpress/edit-post": "^8.0.1", - "@wordpress/editor": "^14.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", + "@wordpress/edit-post": "^8.0.8", + "@wordpress/editor": "^14.0.7", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/notices": "^5.0.2", + "@wordpress/plugins": "^7.0.3", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1", "change-case": "^4.1.2" } }, "@wordpress/block-editor": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-13.0.1.tgz", - "integrity": "sha512-nactAtC/Tu0VN7r//uKbv7S2oPgzPH82JIHcdOgNVvQVdutufh7cUBfFCxCq6KLDirZ1hQWSE9Yzid/7E37jmw==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-13.0.6.tgz", + "integrity": "sha512-j1+wfHK5qIi/LBdUyaT1ipIGfkcBnLZBlFglaCliwTO7jOMH0+Kyc8vwKFW4waPZVesGt1+w+s1YZrtgkA+vdQ==", "requires": { "@babel/runtime": "^7.16.0", "@emotion/react": "^11.7.1", @@ -37944,11 +38822,11 @@ "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/commands": "^1.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/blocks": "^13.0.3", + "@wordpress/commands": "^1.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/date": "^5.0.1", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", @@ -37957,15 +38835,15 @@ "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", + "@wordpress/icons": "^10.0.2", "@wordpress/is-shallow-equal": "^5.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", - "@wordpress/style-engine": "^2.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", + "@wordpress/style-engine": "^2.0.2", "@wordpress/token-list": "^3.0.1", "@wordpress/url": "^4.0.1", "@wordpress/warning": "^3.0.1", @@ -37986,21 +38864,21 @@ } }, "@wordpress/block-library": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-9.0.1.tgz", - "integrity": "sha512-RqvXQhELagIEQSRgdAO66ih9rqwG+6/8jAylCRwdr+NSm0rqce59SNEJddb3K32cTuNuPy7BG4fm2mqSSnQDIw==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@wordpress/block-library/-/block-library-9.0.7.tgz", + "integrity": "sha512-n4ON/8AqcXdJmvW15d4cTYxxljgjPsUzWH7JvQreQdZDoOTnaDaF8rdil0gYa7YSaDCVMsniDxAItyUFxUbijg==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", "@wordpress/autop": "^4.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/date": "^5.0.1", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", @@ -38009,20 +38887,20 @@ "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/interactivity": "^6.0.1", - "@wordpress/interactivity-router": "^2.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/interactivity": "^6.0.2", + "@wordpress/interactivity-router": "^2.0.2", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/patterns": "^2.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/patterns": "^2.0.6", "@wordpress/primitives": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/reusable-blocks": "^5.0.1", - "@wordpress/rich-text": "^7.0.1", - "@wordpress/server-side-render": "^5.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/reusable-blocks": "^5.0.6", + "@wordpress/rich-text": "^7.0.2", + "@wordpress/server-side-render": "^5.0.3", "@wordpress/url": "^4.0.1", - "@wordpress/viewport": "^6.0.1", + "@wordpress/viewport": "^6.0.2", "@wordpress/wordcount": "^4.0.1", "change-case": "^4.1.2", "clsx": "^2.1.1", @@ -38044,15 +38922,15 @@ } }, "@wordpress/blocks": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-13.0.1.tgz", - "integrity": "sha512-ByZtkOrnbPuKxsdRJC77ydH7SGombYCfHK0NLUJhxeGPUAYC3m/Z8uU2w+m+Ff3XmmUF5v3QaTIwkGeLsJMgKQ==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-13.0.3.tgz", + "integrity": "sha512-HukM204sR8KpaQB/Mt6m8GdcYFBzdbss8SxKRgIDQOp9wttqKEMe4s9RmDqclIeQOFmHsFpHFvPEANjzsUwbSg==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/autop": "^4.0.1", "@wordpress/blob": "^4.0.1", "@wordpress/block-serialization-default-parser": "^5.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", "@wordpress/element": "^6.0.1", @@ -38060,8 +38938,8 @@ "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", "@wordpress/is-shallow-equal": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", "@wordpress/shortcode": "^4.0.1", "change-case": "^4.1.2", "colord": "^2.7.0", @@ -38083,26 +38961,26 @@ "dev": true }, "@wordpress/commands": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-1.0.1.tgz", - "integrity": "sha512-s3Yxq5pPO6w+TrizzySzIVO8PF0MOB6bO24bZdiTPM1f4K+hblPkTpnmoBYpJtHLwxiJfAmcTYWkoBxDXLRtjA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/commands/-/commands-1.0.3.tgz", + "integrity": "sha512-GwWu3hDoIzWatsMTeyzP+9rzSYUBcjbnKV6Q91SvMW9WnURznmwiHB2/mA80z1Glg+FJ7q+1ybuJLZ8IxxtCkw==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^28.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/components": "^28.0.3", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/keyboard-shortcuts": "^5.0.2", + "@wordpress/private-apis": "^1.0.2", "clsx": "^2.1.1", "cmdk": "^0.2.0" } }, "@wordpress/components": { - "version": "28.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-28.0.1.tgz", - "integrity": "sha512-RRpX2wZKORmzidusM92IZ7i8Y3Oo8iWPZM3DSwGBZVuUIF+ao0EyMpN7l/y6Lm4buVGDLQL5b1sH8wcN74prpw==", + "version": "28.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/components/-/components-28.0.3.tgz", + "integrity": "sha512-94OCwm21IlsZ94UCNrm9VSNVAbauddWmc+8/ytffd2Kgrjw0nNfIF+SgB1fUj3XVwKcqJ/CyEHZ8sEPDL31wEw==", "requires": { "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", @@ -38126,12 +39004,12 @@ "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", + "@wordpress/icons": "^10.0.2", "@wordpress/is-shallow-equal": "^5.0.1", "@wordpress/keycodes": "^4.0.1", "@wordpress/primitives": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", "@wordpress/warning": "^3.0.1", "change-case": "^4.1.2", "clsx": "^2.1.1", @@ -38174,42 +39052,42 @@ } }, "@wordpress/core-commands": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-1.0.1.tgz", - "integrity": "sha512-RcY49Y4LVENvVXexYPjnr9R96BYjZDskAL2Jpj1rrnmMwyuw1O2pWe+TvbcIrCcLJ882OsxbVB5XNomr8POFjg==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/core-commands/-/core-commands-1.0.6.tgz", + "integrity": "sha512-KkZ93Q79XlpxwL9tAsw/MSmrJ1T0Q9DFa48p2U4xcnZGrKh5p4vFAwJV3MOpZDbVXkoT/1msBNe6+FHmRdaREQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/commands": "^1.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/commands": "^1.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/router": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/router": "^1.0.2", "@wordpress/url": "^4.0.1" } }, "@wordpress/core-data": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-7.0.1.tgz", - "integrity": "sha512-VFRXFbWHXh5fvPnMTD2MZQcEnvIY5C8+Ipl7Y8b4oPZ7IrMp99TKdN7AGG8FzK2bbm22Rse4VblWCseyIioGBQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/core-data/-/core-data-7.0.6.tgz", + "integrity": "sha512-hjLoLBmRFvueEK5HWQMVWQYL985zq5gPouWYvpSDpjTRUkRctThmilB8xrdX17Zjgs52xOX/DpLAvFbWGlCsYw==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", "@wordpress/is-shallow-equal": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", "@wordpress/sync": "^1.0.1", "@wordpress/undo-manager": "^1.0.1", "@wordpress/url": "^4.0.1", @@ -38221,39 +39099,39 @@ } }, "@wordpress/customize-widgets": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/customize-widgets/-/customize-widgets-5.0.1.tgz", - "integrity": "sha512-FXI4dWhctNvBR7xJAFqovSnIOb3qlW/6wsaIz7bAjbdvbMSLNPx4/aeEi1hN1ZwlRo1B9Yq5/I4JHGVvD2X60g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@wordpress/customize-widgets/-/customize-widgets-5.0.7.tgz", + "integrity": "sha512-rGpa8ylei6tUN55QHLo79JcglySC263SDc+TPR8CXXwUiGG9h03ZgrMTo/Ho50KSp4WGhZELWccGAk4uPxh8Bw==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/block-library": "^9.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/block-library": "^9.0.7", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/dom": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/interface": "^6.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/interface": "^6.0.3", "@wordpress/is-shallow-equal": "^5.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", "@wordpress/media-utils": "^5.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/widgets": "^4.0.1", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/widgets": "^4.0.6", "clsx": "^2.1.1", "fast-deep-equal": "^3.1.3" } }, "@wordpress/data": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.0.1.tgz", - "integrity": "sha512-qPPsqBp0beYzzpLr6P2YIUcqUNV8EPFTcVQu6z+IOdCVvmEQmHe1dc6ZC7SxYuep0eTcoPh311mymGQxUMHeAA==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/data/-/data-10.0.2.tgz", + "integrity": "sha512-2ntfc1hag1jhK3Zrli0NDDT4HPY3k0Fkdf9VdDJVGEE8yQIrtRHbwfTg39CDNIiNG1Ey17Gklv0vXjpgvZVwZg==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/compose": "^7.0.1", @@ -38261,7 +39139,7 @@ "@wordpress/element": "^6.0.1", "@wordpress/is-shallow-equal": "^5.0.1", "@wordpress/priority-queue": "^3.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/private-apis": "^1.0.2", "@wordpress/redux-routine": "^5.0.1", "deepmerge": "^4.3.0", "equivalent-key-map": "^0.2.2", @@ -38273,30 +39151,30 @@ } }, "@wordpress/data-controls": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-4.0.1.tgz", - "integrity": "sha512-++P5YUnnjgFY8aPry4J+hqsu/gZ0j5ptlv/So1U6wAzJTcjdaGpDCOL/35dYdPB2JmOxf3XcO+D/1LLQmsnFkg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/data-controls/-/data-controls-4.0.2.tgz", + "integrity": "sha512-MfFRJGwwmvLB+7/Fqs4h/66tAbDhpn6QfYUfeF+fELSaWZpWN0cwjjFcCGBLcPvn046fPeNOJlCsz5aDYYoMrA==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1" } }, "@wordpress/dataviews": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/dataviews/-/dataviews-2.0.1.tgz", - "integrity": "sha512-rBIItAuYvGKpOIgyMk0FY5w/EwFX4nkNwgPbuUfmBykIGy43nWVOrIMUOnH7IGP9m/vBlTRnRNtNbMK2f9CdeA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@wordpress/dataviews/-/dataviews-2.0.4.tgz", + "integrity": "sha512-4FpLyvmiO0su4Fx3cLctqTIE+Bp1HCC1fHRFZw5JJgvkNXuAKHJRdpFCo1O11ZMH3ibxpWVshXZcfu7Z7HBWlw==", "requires": { "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", + "@wordpress/icons": "^10.0.2", "@wordpress/primitives": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/private-apis": "^1.0.2", "clsx": "^2.1.1", "remove-accents": "^0.5.0" } @@ -38313,9 +39191,9 @@ } }, "@wordpress/dependency-extraction-webpack-plugin": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.0.1.tgz", - "integrity": "sha512-xfsDY9kr9J7Lec899t8BC/QaSfqa5w9ONnuj1AplwdVToIXrLO1LQNHaP6/Yw1qjRwA8iE7USMnW5huUTnDoUA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.0.2.tgz", + "integrity": "sha512-XvIEKvA4YB1mDyfiiOddhWDU18ICuDS0wf+et52EXu4ad5YYZzpKpnqKsLh9HoCHBbVCzgiWOesya3eugdqKKw==", "dev": true, "requires": { "json2php": "^0.0.7" @@ -38412,88 +39290,89 @@ } }, "@wordpress/edit-post": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-8.0.1.tgz", - "integrity": "sha512-FIqy8DbpwAyarLUTHNdznBoTRkc3fIouD/u9MptR3GPjflZfARA1azkSwYy+XIruqcdfWdnxLloX9rcVsbukbw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@wordpress/edit-post/-/edit-post-8.0.8.tgz", + "integrity": "sha512-zB4k+1NbD1J9dhxq+c+Nqih+Zm6yOgMNs4TRNF6YYBOGSuAPxcNSA0qtfALC8PtI19MpUv0JCh3HckaJQs+fHQ==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/block-library": "^9.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/commands": "^1.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/block-library": "^9.0.7", + "@wordpress/blocks": "^13.0.3", + "@wordpress/commands": "^1.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-commands": "^1.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-commands": "^1.0.6", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", - "@wordpress/editor": "^14.0.1", + "@wordpress/editor": "^14.0.7", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", + "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1", - "@wordpress/viewport": "^6.0.1", + "@wordpress/viewport": "^6.0.2", "@wordpress/warning": "^3.0.1", - "@wordpress/widgets": "^4.0.1", + "@wordpress/widgets": "^4.0.6", "clsx": "^2.1.1", "memize": "^2.1.0" } }, "@wordpress/edit-site": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-6.0.1.tgz", - "integrity": "sha512-kl5lQUdTN7Ju/dcydApN4OcbAs+MZTTEW654hq7dHrD7BHvvkWiKwxACTGUbXnb3EguCnX/fRib4QCRtmMnQfw==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@wordpress/edit-site/-/edit-site-6.0.8.tgz", + "integrity": "sha512-HJZmmpV3UDPylfa0+3MNXNzfgWt+eGoYdmLG4z2JpNPEyOZ+w0yY3nx8oSsx85sHtT5bRT0drWsj0gqr4qZynw==", "requires": { "@babel/runtime": "^7.16.0", "@react-spring/web": "^9.4.5", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/block-library": "^9.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/commands": "^1.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/block-library": "^9.0.7", + "@wordpress/blocks": "^13.0.3", + "@wordpress/commands": "^1.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-commands": "^1.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", - "@wordpress/dataviews": "^2.0.1", + "@wordpress/core-commands": "^1.0.6", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", + "@wordpress/dataviews": "^2.0.4", "@wordpress/date": "^5.0.1", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", - "@wordpress/editor": "^14.0.1", + "@wordpress/editor": "^14.0.7", "@wordpress/element": "^6.0.1", "@wordpress/escape-html": "^3.0.1", "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/patterns": "^2.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/patterns": "^2.0.6", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", "@wordpress/primitives": "^4.0.1", "@wordpress/priority-queue": "^3.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/reusable-blocks": "^5.0.1", - "@wordpress/router": "^1.0.1", - "@wordpress/style-engine": "^2.0.1", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/reusable-blocks": "^5.0.6", + "@wordpress/router": "^1.0.2", + "@wordpress/style-engine": "^2.0.2", "@wordpress/url": "^4.0.1", - "@wordpress/viewport": "^6.0.1", - "@wordpress/widgets": "^4.0.1", + "@wordpress/viewport": "^6.0.2", + "@wordpress/widgets": "^4.0.6", "@wordpress/wordcount": "^4.0.1", "change-case": "^4.1.2", "clsx": "^2.1.1", @@ -38504,56 +39383,56 @@ } }, "@wordpress/edit-widgets": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-6.0.1.tgz", - "integrity": "sha512-KXIa9UC7hFZbgYg83CPPaE5q8HPjczqi99UY/oTCNbVWA/uM0YRYxWUe/tSoAgDEWsZ+J12eMHoKtLpMWcj8wA==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@wordpress/edit-widgets/-/edit-widgets-6.0.7.tgz", + "integrity": "sha512-KUas6yd7Lmt623F5lQNd/n2S9VWFfmtjckemlwlP7niXaU8XrdEhRFxDAY52W6Jk90KXVxLsG20qnx8nYah3NQ==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/block-library": "^9.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/block-library": "^9.0.7", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/interface": "^6.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/interface": "^6.0.3", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", "@wordpress/media-utils": "^5.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/patterns": "^2.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/reusable-blocks": "^5.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/patterns": "^2.0.6", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/reusable-blocks": "^5.0.6", "@wordpress/url": "^4.0.1", - "@wordpress/widgets": "^4.0.1", + "@wordpress/widgets": "^4.0.6", "clsx": "^2.1.1" } }, "@wordpress/editor": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-14.0.1.tgz", - "integrity": "sha512-TDTQJLAU20TFoxz05nqNpHF6arXMmb2GONKTdlXnvIdGIYHworVuYPPQSwQuDj0PSnG9V810qULbCEtjB+tRgQ==", + "version": "14.0.7", + "resolved": "https://registry.npmjs.org/@wordpress/editor/-/editor-14.0.7.tgz", + "integrity": "sha512-Y3eHgVbf8h+T3Ssn1hLFpAGBwNxbP2ZeWQIwopIZ2V5d/qcHWzRW2Aoss3Wy1K+NHQJTkXJSsfrnC2zyd7dZBQ==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/api-fetch": "^7.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/commands": "^1.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/commands": "^1.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/date": "^5.0.1", "@wordpress/deprecated": "^4.0.1", "@wordpress/dom": "^4.0.1", @@ -38561,19 +39440,19 @@ "@wordpress/hooks": "^4.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/interface": "^6.0.1", - "@wordpress/keyboard-shortcuts": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/interface": "^6.0.3", + "@wordpress/keyboard-shortcuts": "^5.0.2", "@wordpress/keycodes": "^4.0.1", "@wordpress/media-utils": "^5.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/patterns": "^2.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/reusable-blocks": "^5.0.1", - "@wordpress/rich-text": "^7.0.1", - "@wordpress/server-side-render": "^5.0.1", + "@wordpress/notices": "^5.0.2", + "@wordpress/patterns": "^2.0.6", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/reusable-blocks": "^5.0.6", + "@wordpress/rich-text": "^7.0.2", + "@wordpress/server-side-render": "^5.0.3", "@wordpress/url": "^4.0.1", "@wordpress/warning": "^3.0.1", "@wordpress/wordcount": "^4.0.1", @@ -38649,22 +39528,22 @@ } }, "@wordpress/format-library": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/format-library/-/format-library-5.0.1.tgz", - "integrity": "sha512-HWL8RFJZj8yuGyTDcCiRKkkoNnvmfeAgI6/8NexFovOTHagZGdhFNs7QrQd7JXk+rSwhpdUXowBxOGgz561mKw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/format-library/-/format-library-5.0.6.tgz", + "integrity": "sha512-91LTfbjfgPqVnI8uuBEtA6ZBzJCmFyUJn1amGcvlpK9gX1BRvCZmmbOwTfAs4Wce7NIJswUr//5I765pz7P8Tg==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/rich-text": "^7.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/rich-text": "^7.0.2", "@wordpress/url": "^4.0.1" } }, @@ -38698,9 +39577,9 @@ } }, "@wordpress/icons": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.0.1.tgz", - "integrity": "sha512-CyemntOjhTzYXismZFDcX5ZETxBd8ltokF0Bdo7d2wExxIYiPlSfKcjTxTIOuwKJUZ+3owWhhFu0wxpiOQs9BA==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-10.0.2.tgz", + "integrity": "sha512-YocOYpnB/zRW28ApIBIICnV28HaaCX1ayjm/tRUp76q1J/c2pK4meCU4keR6IDpuEg5dtkzGLWxR/fK+KtAt6w==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/element": "^6.0.1", @@ -38708,9 +39587,9 @@ } }, "@wordpress/interactivity": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-6.0.1.tgz", - "integrity": "sha512-bqWFgqNIA2QMSQRh/yGP6xWEvDBjLZeOi97KLqcResUYrx6GL0Y6bD5RdsoGiDXDdH9bsHkuOepEaBHHVFFu8w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity/-/interactivity-6.0.2.tgz", + "integrity": "sha512-QsY29re3NP+21tSQ5TaJHmgZlzKHSTWdVukAk1lgzkzTBrE4URIZoCjk4KzCWKd5X7FPqEEz8vKoWM1E3ZbKFw==", "requires": { "@preact/signals": "^1.2.2", "deepsignal": "^1.4.0", @@ -38718,31 +39597,31 @@ } }, "@wordpress/interactivity-router": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-2.0.1.tgz", - "integrity": "sha512-n7qP78FwLJ/zE3m3JfE0LSMZbZF83jwM9ifh73NLLf3fv3hwlIEwjp+OPuS0hfTDYBYhAx8BqAOLGMTTW1ibCQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/interactivity-router/-/interactivity-router-2.0.2.tgz", + "integrity": "sha512-igySPnGkQxiRVIPkE/mwR/N4ThFeewUdtpiMSpWstUGEDnOwIks6+vh1f6qpZKwZW1qMyZMheyuXz+H4ABZwYQ==", "requires": { - "@wordpress/interactivity": "^6.0.1" + "@wordpress/interactivity": "^6.0.2" } }, "@wordpress/interface": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-6.0.1.tgz", - "integrity": "sha512-5rgTxC9SkOE22XLraP5AfpGEWisD62VfDfnnthORWKVMta605+9MjqZt+KlxIpULpI0F511szu6llb9ITQJFjw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-6.0.3.tgz", + "integrity": "sha512-jtgXweeWIzhMt6v2wTXRFAmFYttpE1N6eWmruObvXMZlJAMhMf/nwAHVDZr65O/03shdG+1E+4fyVRgOgUz+Ww==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/plugins": "^7.0.1", - "@wordpress/preferences": "^4.0.1", - "@wordpress/private-apis": "^1.0.1", - "@wordpress/viewport": "^6.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/plugins": "^7.0.3", + "@wordpress/preferences": "^4.0.3", + "@wordpress/private-apis": "^1.0.2", + "@wordpress/viewport": "^6.0.2", "clsx": "^2.1.1" } }, @@ -38775,12 +39654,12 @@ } }, "@wordpress/keyboard-shortcuts": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-5.0.1.tgz", - "integrity": "sha512-NGY+xX0GtnrGIV3E0U7EhcYt03G+9HA3i8tj7YKYIxlGhdEdytPB3HSd8WsWi91VaCFnizYaZzRYyv2rRsXe0A==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/keyboard-shortcuts/-/keyboard-shortcuts-5.0.2.tgz", + "integrity": "sha512-eNCpGJ+/PheT1ph9ldzK97lL6Wsrnlf2+keKTHvFX5kzUMjBfsaVHNNnOhsbk80uVNtYaE39V6Eg1iPc+yZtwA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/keycodes": "^4.0.1" } @@ -38795,14 +39674,14 @@ } }, "@wordpress/list-reusable-blocks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/list-reusable-blocks/-/list-reusable-blocks-5.0.1.tgz", - "integrity": "sha512-W9FpQ/wlsb8tOXYslRI3sP5dfkVCnEpJW84qsEHvHNW1EGUTBLlRGkAKM1J3NImDPKkHaMbh6LWdW4HudG7KXg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/list-reusable-blocks/-/list-reusable-blocks-5.0.3.tgz", + "integrity": "sha512-m1+SvihoFIxv8D6PoukFeFkHX89l+S0xb3G3qWod3t2cDvfrqLmDPua97Pi3EiOX6tSPA7M+4DvysYdBkjikCw==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", "@wordpress/blob": "^4.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", @@ -38822,13 +39701,13 @@ } }, "@wordpress/notices": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-5.0.1.tgz", - "integrity": "sha512-SQsLyYTAhfKAbNkLx50redcMDcNeZ8vwK2wFOGMSsUR6QoiT9lcTJqDe85jV9XFeZ8+GfQJxXvayhYzXA25g5g==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/notices/-/notices-5.0.2.tgz", + "integrity": "sha512-ihlXgxfce+h+ztyj9btb4s6fNhUU7cNGTF+VjjKHRdeRBw62bc86xI7fyIrtm6utVrZiWtSSQUtIR/Cbvao7TQ==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/data": "^10.0.1" + "@wordpress/data": "^10.0.2" } }, "@wordpress/npm-package-json-lint-config": { @@ -38838,53 +39717,53 @@ "dev": true }, "@wordpress/nux": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/nux/-/nux-9.0.1.tgz", - "integrity": "sha512-rkCVhE1cu5GufYptdHxSxNoxsq7Exi72E2SLBxA4LisNm2qfLh4PWEWn2ulWc3IBw0Y6cmiM4SfqqA4o/wicpQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/nux/-/nux-9.0.3.tgz", + "integrity": "sha512-3W5sQivCFmu8f7qgieHvtVog6orYCQFTt53/4uYp/spZohxZG+4EUTziSItR9yZpjVQL9cX6oFk7aTsxwFh+Hg==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1" + "@wordpress/icons": "^10.0.2" } }, "@wordpress/patterns": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-2.0.1.tgz", - "integrity": "sha512-F7HHPn0CavyUnGyUJwCEkwbPD39I2jR3Fg/CZSvSyzFk7hBCOZBGbdnSh1g1JLCgznmfUkvUMWgq3Bo1F+E7iw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/patterns/-/patterns-2.0.6.tgz", + "integrity": "sha512-b1ne2lxq4sdl4/HvIFIVcbnGfDL5BWpo3x+uf22N/bMXVxSNBWlGlI9TDtISnkHzpXFlrjcPiAmCBz//hEJa/g==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/html-entities": "^4.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/notices": "^5.0.2", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1" } }, "@wordpress/plugins": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-7.0.1.tgz", - "integrity": "sha512-uaj4KHAPodJIaz19oZGK0BGXXTQpgp/fGfWZceQyqgAPZBIMJqWgxhFzFkcLjvkqoKEG0XhEKX7ZcWGVpIhnWg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/plugins/-/plugins-7.0.3.tgz", + "integrity": "sha512-snU0QE1jyonhu7fM/3uH812nIIfwPF5UwRBFZzaUWc2dwyRHmYcvuwxrlQXUCrEU+cj9BXkmuJHTE6KWvveehQ==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", "@wordpress/element": "^6.0.1", "@wordpress/hooks": "^4.0.1", - "@wordpress/icons": "^10.0.1", + "@wordpress/icons": "^10.0.2", "@wordpress/is-shallow-equal": "^5.0.1", "memize": "^2.0.1" } @@ -38900,20 +39779,20 @@ } }, "@wordpress/preferences": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-4.0.1.tgz", - "integrity": "sha512-/Y1/eWLDGDFsrUkdulMpayYQyK4HB+GnG4gLfyeh7y4uD77WsNApbvMyU6qbAKHROVRJs25UNbP56Ah8SM1BUw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/preferences/-/preferences-4.0.3.tgz", + "integrity": "sha512-H255doYLwI5/03LiUhRcQyKHD7ZjF4S7pfYTicuM0GH/vYWHPtwsnWUEu2MXxIraoEuAnescMyczjHJ7bZRXug==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/private-apis": "^1.0.2", "clsx": "^2.1.1" } }, @@ -38952,9 +39831,9 @@ } }, "@wordpress/private-apis": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.0.1.tgz", - "integrity": "sha512-g6ftWDyCXG3cWN2NgdJsgIC1yYwBlzCRfcKlEq0r4fVQ71YulEW1el1L8a9LyFhEC5X2IzFN9FFJ1nca4/pMtA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.0.2.tgz", + "integrity": "sha512-epLzW+hxekcZwO0C4u8wVL0j9Ko+5Go20zQ2IN9WBSXXb9xOTI8Nje+pww70ukrkUk2UqBvXP17NvSdYV5LTwA==", "requires": { "@babel/runtime": "^7.16.0" } @@ -38971,33 +39850,33 @@ } }, "@wordpress/reusable-blocks": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-5.0.1.tgz", - "integrity": "sha512-Xqs027qSscDQHck9U10oQOnwqlC+/c0VDOhmtoA2+5hjkcq5w0UMXHlZsQcrcnmlzCn2lN/DUF+zyL4ujLeHqg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/reusable-blocks/-/reusable-blocks-5.0.6.tgz", + "integrity": "sha512-a/ZeUFGkAr7Ki/a0pQ0SsSODrY3q1ff+qe2E2+I0ORk5FSX71yli56bmlqbsFr/KCTRcITtYRsev4oalL6QzDg==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/notices": "^5.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/notices": "^5.0.2", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1" } }, "@wordpress/rich-text": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.0.1.tgz", - "integrity": "sha512-E1E1tBur7Lv7ALXsawXpA/47N6/3T39m7t5elgZGz+t3H9fbH3zMoO1lNFYhQnAfcl/crutCiG1ESuyrPhzujg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-7.0.2.tgz", + "integrity": "sha512-zj7+ekI9X73OrUq8C5OMX9KDBgNP9SJO6lA/aiKE4YgQBxrpMDPu7Ser+gr4CJtJtKYiJWQxsWGsVASjNbt5+g==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/a11y": "^4.0.1", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/escape-html": "^3.0.1", @@ -39007,21 +39886,21 @@ } }, "@wordpress/router": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-1.0.1.tgz", - "integrity": "sha512-nhZCj1cvCiu5vAyGWQtiSX4tTEFhaun9RL+McY5bihHLfSn0bnOp1Ov0sAH/hLntc6NNAOOVavDI+iRJiqjb3Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/router/-/router-1.0.2.tgz", + "integrity": "sha512-KgREOYycD9z5P+1LtukoPLYw4pOaWkaUS0K86wIfKBsHCSUtDqqHA4sLQMb2fESPH306OhzMHVz4xqNqKrUZYQ==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/element": "^6.0.1", - "@wordpress/private-apis": "^1.0.1", + "@wordpress/private-apis": "^1.0.2", "@wordpress/url": "^4.0.1", "history": "^5.3.0" } }, "@wordpress/scripts": { - "version": "28.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-28.0.1.tgz", - "integrity": "sha512-ZvPFcGz+9Rj6OrvePOfNjigUlSDyL2FbZWlj2KyR4TM5H9S4XHd7q4NyjrnT1RFhj/D0ZyJETdBWjO6yTJtlVw==", + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-28.0.2.tgz", + "integrity": "sha512-TawbVRncjSjDDsjdoBdwMIvJaFobTL9Dq5Euh3o+1iEjT5gAsb8nn+ICQpW5xSnZTBCoUps8EAksVpytwxOCIg==", "dev": true, "requires": { "@babel/core": "^7.16.0", @@ -39029,7 +39908,7 @@ "@svgr/webpack": "^8.0.1", "@wordpress/babel-preset-default": "^8.0.1", "@wordpress/browserslist-config": "^6.0.1", - "@wordpress/dependency-extraction-webpack-plugin": "^6.0.1", + "@wordpress/dependency-extraction-webpack-plugin": "^6.0.2", "@wordpress/e2e-test-utils-playwright": "^1.0.1", "@wordpress/eslint-plugin": "^19.0.1", "@wordpress/jest-preset-default": "^12.0.1", @@ -39145,6 +40024,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, "copy-webpack-plugin": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", @@ -39159,6 +40044,150 @@ "serialize-javascript": "^6.0.0" } }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "requires": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dev": true, + "requires": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + } + }, + "cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dev": true, + "requires": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + } + }, + "cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "dev": true + }, + "csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "requires": { + "css-tree": "~2.2.0" + }, + "dependencies": { + "css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "requires": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + } + }, + "mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + } + } + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, "filenamify": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", @@ -39224,6 +40253,21 @@ "p-locate": "^4.1.0" } }, + "mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -39239,6 +40283,251 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, + "postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dev": true, + "requires": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "dev": true, + "requires": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "dev": true + }, + "postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "dev": true + }, + "postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "dev": true + }, + "postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "dev": true + }, + "postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + } + }, + "postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "dev": true, + "requires": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + } + }, + "postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "dev": true, + "requires": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "dev": true, + "requires": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.16" + } + }, + "postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "dev": true + }, + "postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dev": true, + "requires": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dev": true, + "requires": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "dev": true, + "requires": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + } + }, + "postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.16" + } + }, "prettier": { "version": "npm:wp-prettier@3.0.3", "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-3.0.3.tgz", @@ -39285,6 +40574,16 @@ "source-map-js": "^1.0.1" } }, + "stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "dev": true, + "requires": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -39294,6 +40593,21 @@ "has-flag": "^4.0.0" } }, + "svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + } + }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -39303,16 +40617,16 @@ } }, "@wordpress/server-side-render": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-5.0.1.tgz", - "integrity": "sha512-nH+KZ9HwnDcsjkqwQZBvY4gBCDXr0z8F1ly7cWJPdqLXroaIB91yunj8oED08BpUHyTad/srqYl2PF9fzfz6Eg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@wordpress/server-side-render/-/server-side-render-5.0.3.tgz", + "integrity": "sha512-/8bP+uTqX/9lU7fvRuq5D0RmYPu48mN4vdEdA7HNifrm+2v7lLHA66aq+1gCfwhxxOowuvaw+ZGnpGXB0wRx1g==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/deprecated": "^4.0.1", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", @@ -39330,9 +40644,9 @@ } }, "@wordpress/style-engine": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-2.0.1.tgz", - "integrity": "sha512-c4da79SjZvtqMrCHkAT9cCuv5diHnp6nU5DAZpUYKmynFJlxA1/0fqdHMwdt3GlxiXM/WgkiFI3P/w0/Ce+UGQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/style-engine/-/style-engine-2.0.2.tgz", + "integrity": "sha512-Jq1853v68orVnNsxDiB2LGeNlmQoSowk3ypuKCaz2YTWX4etKWMbIdFvmY99OKKuzNDRCG8XklXMEIR+tsiGIQ==", "requires": { "@babel/runtime": "^7.16.0", "change-case": "^4.1.2" @@ -39392,13 +40706,13 @@ } }, "@wordpress/viewport": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-6.0.1.tgz", - "integrity": "sha512-IUMr48mza9b3qZe+Jc0uCbz94Y9XA7idzj4IPQd0WnRXE58/xJQ+3bNW3R5LQJpzzOaQCaCqrvfD0jjIrbwVzQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/viewport/-/viewport-6.0.2.tgz", + "integrity": "sha512-6/3EI6mWKj6t25ZW70FPchvmXpeSFm/qE3nmOEezOKxcj8UXpOVkJksIyTICK2lztLQ70ErGdSCnXby+SMJksA==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/compose": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1" } }, @@ -39408,22 +40722,22 @@ "integrity": "sha512-xSVH/zMAg4ABeNOWo6mlkF+TDBDQNaWVdMNzi+yvGoSDImhaM6Bqrhr1e/65AS29iajnqQt6dlu7E56o5FZlcg==" }, "@wordpress/widgets": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-4.0.1.tgz", - "integrity": "sha512-oqnr6Dtb51jNVMJy67pCJMz1eIw4E0O7deh6HKl0oaB2WPLShC4Sg+xrMmL2SGdhPNNm7Kk7/rnCR21LfNKcdw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@wordpress/widgets/-/widgets-4.0.6.tgz", + "integrity": "sha512-PPhSX9NuQKpBVdF+bw2Ai134fOBACinUjksN49PWdSbBpTH5uCr5QEOisMsRsKuqr8k6hphMNnKuRxq0qfulBw==", "requires": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^7.0.1", - "@wordpress/block-editor": "^13.0.1", - "@wordpress/blocks": "^13.0.1", - "@wordpress/components": "^28.0.1", + "@wordpress/block-editor": "^13.0.6", + "@wordpress/blocks": "^13.0.3", + "@wordpress/components": "^28.0.3", "@wordpress/compose": "^7.0.1", - "@wordpress/core-data": "^7.0.1", - "@wordpress/data": "^10.0.1", + "@wordpress/core-data": "^7.0.6", + "@wordpress/data": "^10.0.2", "@wordpress/element": "^6.0.1", "@wordpress/i18n": "^5.0.1", - "@wordpress/icons": "^10.0.1", - "@wordpress/notices": "^5.0.1", + "@wordpress/icons": "^10.0.2", + "@wordpress/notices": "^5.0.2", "clsx": "^2.1.1" } }, @@ -39594,6 +40908,12 @@ } } }, + "ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "dev": true + }, "ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -39940,13 +41260,13 @@ "dev": true }, "autoprefixer": { - "version": "10.4.17", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", - "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", "dev": true, "requires": { - "browserslist": "^4.22.2", - "caniuse-lite": "^1.0.30001578", + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -40243,9 +41563,9 @@ } }, "backbone": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.5.0.tgz", - "integrity": "sha512-RPKlstw5NW+rD2X4PnEnvgLhslRnXOugXw2iBloHkPMgOxvakP1/A+tZIGM3qCm8uvZeEf8zMm0uvcK1JwL+IA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.6.0.tgz", + "integrity": "sha512-13PUjmsgw/49EowNcQvfG4gmczz1ximTMhUktj0Jfrjth0MVaTxehpU+qYYX4MxnuIuhmvBLC6/ayxuAGnOhbA==", "requires": { "underscore": ">=1.8.3" } @@ -40256,6 +41576,52 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "optional": true + }, + "bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "optional": true, + "requires": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "dev": true, + "optional": true + }, + "bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "requires": { + "bare-os": "^2.1.0" + } + }, + "bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "optional": true, + "requires": { + "streamx": "^2.18.0" + } + }, "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", @@ -40695,13 +42061,13 @@ } }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -40709,7 +42075,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -40802,15 +42168,15 @@ } }, "browserslist": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", - "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.0.16" } }, "bser": { @@ -41051,9 +42417,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001587", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz", - "integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==", + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", "dev": true }, "capital-case": { @@ -41229,18 +42595,18 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -41296,12 +42662,22 @@ "dev": true }, "chromium-bidi": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz", - "integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==", + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.24.tgz", + "integrity": "sha512-5xQNN2SVBdZv4TxeMLaI+PelrnZsHDhn8h2JtyriLr+0qHcZS8BMuo93qN6J1VmtmrgYP+rmcLHcbpnA8QJh+w==", "dev": true, "requires": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "dependencies": { + "mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + } } }, "ci-info": { @@ -41574,12 +42950,6 @@ "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true }, - "common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true - }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -41744,9 +43114,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true }, "cookie-signature": { @@ -42029,9 +43399,9 @@ "dev": true }, "css-declaration-sorter": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz", - "integrity": "sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true }, "css-functions-list": { @@ -42110,56 +43480,57 @@ "dev": true }, "cssnano": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz", - "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.3.tgz", + "integrity": "sha512-lsekJctOTqdCn4cNrtrSwsuMR/fHC+oiVMHkp/OugBWtwjH8XJag1/OtGaYJGtz0un1fQcRy4ryfYTQsfh+KSQ==", "dev": true, "requires": { - "cssnano-preset-default": "^6.0.3", - "lilconfig": "^3.0.0" + "cssnano-preset-default": "^7.0.3", + "lilconfig": "^3.1.2" } }, "cssnano-preset-default": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz", - "integrity": "sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA==", - "dev": true, - "requires": { - "css-declaration-sorter": "^7.1.1", - "cssnano-utils": "^4.0.1", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.0.2", - "postcss-convert-values": "^6.0.2", - "postcss-discard-comments": "^6.0.1", - "postcss-discard-duplicates": "^6.0.1", - "postcss-discard-empty": "^6.0.1", - "postcss-discard-overridden": "^6.0.1", - "postcss-merge-longhand": "^6.0.2", - "postcss-merge-rules": "^6.0.3", - "postcss-minify-font-values": "^6.0.1", - "postcss-minify-gradients": "^6.0.1", - "postcss-minify-params": "^6.0.2", - "postcss-minify-selectors": "^6.0.2", - "postcss-normalize-charset": "^6.0.1", - "postcss-normalize-display-values": "^6.0.1", - "postcss-normalize-positions": "^6.0.1", - "postcss-normalize-repeat-style": "^6.0.1", - "postcss-normalize-string": "^6.0.1", - "postcss-normalize-timing-functions": "^6.0.1", - "postcss-normalize-unicode": "^6.0.2", - "postcss-normalize-url": "^6.0.1", - "postcss-normalize-whitespace": "^6.0.1", - "postcss-ordered-values": "^6.0.1", - "postcss-reduce-initial": "^6.0.2", - "postcss-reduce-transforms": "^6.0.1", - "postcss-svgo": "^6.0.2", - "postcss-unique-selectors": "^6.0.2" + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.3.tgz", + "integrity": "sha512-dQ3Ba1p/oewICp/szF1XjFFgql8OlOBrI2YNBUUwhHQnJNoMOcQTa+Bi7jSJN8r/eM1egW0Ud1se/S7qlduWKA==", + "dev": true, + "requires": { + "browserslist": "^4.23.1", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.0", + "postcss-calc": "^10.0.0", + "postcss-colormin": "^7.0.1", + "postcss-convert-values": "^7.0.1", + "postcss-discard-comments": "^7.0.1", + "postcss-discard-duplicates": "^7.0.0", + "postcss-discard-empty": "^7.0.0", + "postcss-discard-overridden": "^7.0.0", + "postcss-merge-longhand": "^7.0.2", + "postcss-merge-rules": "^7.0.2", + "postcss-minify-font-values": "^7.0.0", + "postcss-minify-gradients": "^7.0.0", + "postcss-minify-params": "^7.0.1", + "postcss-minify-selectors": "^7.0.2", + "postcss-normalize-charset": "^7.0.0", + "postcss-normalize-display-values": "^7.0.0", + "postcss-normalize-positions": "^7.0.0", + "postcss-normalize-repeat-style": "^7.0.0", + "postcss-normalize-string": "^7.0.0", + "postcss-normalize-timing-functions": "^7.0.0", + "postcss-normalize-unicode": "^7.0.1", + "postcss-normalize-url": "^7.0.0", + "postcss-normalize-whitespace": "^7.0.0", + "postcss-ordered-values": "^7.0.1", + "postcss-reduce-initial": "^7.0.1", + "postcss-reduce-transforms": "^7.0.0", + "postcss-svgo": "^7.0.1", + "postcss-unique-selectors": "^7.0.1" } }, "cssnano-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz", - "integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz", + "integrity": "sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==", "dev": true }, "csso": { @@ -43022,18 +44393,18 @@ } }, "dotenv": { - "version": "16.4.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", - "integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true }, "dotenv-expand": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.3.tgz", - "integrity": "sha512-qkK+MLTvZ86oq4sjMqGpUN/38SQ/J37mny88CsEUFFjb2MBVz06a809ri0QeVDXpxkvZkXzqjGUb0M1R6n3OGw==", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", + "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", "dev": true, "requires": { - "dotenv": "^16.4.1" + "dotenv": "^16.4.4" } }, "download": { @@ -43138,9 +44509,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.665", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.665.tgz", - "integrity": "sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw==", + "version": "1.4.811", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.811.tgz", + "integrity": "sha512-CDyzcJ5XW78SHzsIOdn27z8J4ist8eaFLhdto2hSMSJQgsiwvbv2fbizcKUICryw1Wii1TI/FEkvzvJsR3awrA==", "dev": true }, "element-closest": { @@ -43231,6 +44602,12 @@ "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", "dev": true }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, "envinfo": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", @@ -43370,11 +44747,6 @@ "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", "dev": true }, - "es-module-shims": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/es-module-shims/-/es-module-shims-1.8.2.tgz", - "integrity": "sha512-7vIYVzpOhXtpc3Yn03itB+GSgVZFW7oL4kdydA+iL+IEi7HiSLBUxM05QFw4SxTl6e++pMpGqZPo2+vdNs3TbA==" - }, "es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -43416,9 +44788,9 @@ } }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true }, "escape-html": { @@ -44359,17 +45731,17 @@ "dev": true }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -44635,18 +46007,18 @@ }, "dependencies": { "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -44997,12 +46369,12 @@ }, "dependencies": { "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "expand-tilde": { @@ -45015,9 +46387,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -45174,9 +46546,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true }, "for-each": { @@ -46138,14 +47510,13 @@ } }, "grunt-contrib-qunit": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-qunit/-/grunt-contrib-qunit-7.0.1.tgz", - "integrity": "sha512-+5eL4qv2H8q6he+2HGDkqbKwAulRUrtMaX5NoY2AwwvbA4d4OqsI1YGiUZ0L/O9oL7nUQ1cxGKeOp+TcE/AYUg==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-qunit/-/grunt-contrib-qunit-10.0.0.tgz", + "integrity": "sha512-CUGMTiJVKR69jUoo6vETlZ0XXZXZPxxqtyD5e2kisE3WKX3RQn1axaof7DRZaZOiX+nl7yzyI1jiSGRJKsT40A==", "dev": true, "requires": { "eventemitter2": "^6.4.9", - "p-each-series": "^2.2.0", - "puppeteer": "^19.7.0" + "puppeteer": "^22.0.0" }, "dependencies": { "eventemitter2": { @@ -46850,18 +48221,18 @@ }, "dependencies": { "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -48425,12 +49796,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "chalk": { @@ -48459,9 +49830,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -48768,18 +50139,18 @@ }, "dependencies": { "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -48939,12 +50310,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "chalk": { @@ -48973,9 +50344,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -49664,9 +51035,9 @@ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, "jquery-color": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jquery-color/-/jquery-color-2.2.0.tgz", - "integrity": "sha512-4VoxsLMw860EQGNT/TmP3Lbr7/1OCQlBPS4ILj7bxRApJrPQfpqzdIOTY8Ll9nGY7UHtWqDuzR7cUcS1lcWjVw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jquery-color/-/jquery-color-3.0.0.tgz", + "integrity": "sha512-TbyqpkxEfVq9K/eUn4jW14YnJn19dORJrplJ3vo9jgLrqnuOyH6gyv8yDZdYcERlrVVDub4HV5r2t5KGjcvbKg==" }, "jquery-form": { "version": "4.3.0", @@ -49852,9 +51223,9 @@ } }, "ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true } } @@ -50180,12 +51551,12 @@ }, "dependencies": { "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "expand-tilde": { @@ -50198,9 +51569,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -50657,9 +52028,9 @@ "dev": true }, "lilconfig": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", - "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "dev": true }, "line-height": { @@ -51166,7 +52537,7 @@ "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", "dev": true, "requires": { "findup-sync": "^2.0.0", @@ -51187,7 +52558,7 @@ "findup-sync": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", "dev": true, "requires": { "detect-file": "^1.0.0", @@ -52587,12 +53958,6 @@ "dev": true, "optional": true }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, "p-event": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", @@ -52921,9 +54286,9 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "picomatch": { "version": "2.3.1", @@ -53003,10 +54368,20 @@ } } }, + "playwright": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", + "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.45.0" + } + }, "playwright-core": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.32.0.tgz", - "integrity": "sha512-Z9Ij17X5Z3bjpp6XKujGBp9Gv4eViESac9aDmwgQFUEJBW0K80T21m/Z+XJQlu4cNsvPygw33b6V1Va6Bda5zQ==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", + "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", "dev": true }, "plur": { @@ -53068,69 +54443,72 @@ "dev": true }, "postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.0.tgz", + "integrity": "sha512-OmjhudoNTP0QleZCwl1i6NeBwN+5MZbY5ersLZz69mjJiDVv/p57RjRuKDkHeDWr4T+S97wQfsqRTNoDHB2e3g==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^6.0.16", "postcss-value-parser": "^4.2.0" } }, "postcss-colormin": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz", - "integrity": "sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.1.tgz", + "integrity": "sha512-uszdT0dULt3FQs47G5UHCduYK+FnkLYlpu1HpWu061eGsKZ7setoG7kA+WC9NQLsOJf69D5TxGHgnAdRgylnFQ==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "caniuse-api": "^3.0.0", - "colord": "^2.9.1", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" } }, "postcss-convert-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz", - "integrity": "sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.1.tgz", + "integrity": "sha512-9x2ofb+hYPwHWMlWAzyWys2yMDZYGfkX9LodbaVTmLdlupmtH2AGvj8Up95wzzNPRDEzPIxQIkUaPJew3bT6xA==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "postcss-value-parser": "^4.2.0" } }, "postcss-discard-comments": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz", - "integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==", - "dev": true + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.1.tgz", + "integrity": "sha512-GVrQxUOhmle1W6jX2SvNLt4kmN+JYhV7mzI6BMnkAWR9DtVvg8e67rrV0NfdWhn7x1zxvzdWkMBPdBDCls+uwQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.1.0" + } }, "postcss-discard-duplicates": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz", - "integrity": "sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.0.tgz", + "integrity": "sha512-bAnSuBop5LpAIUmmOSsuvtKAAKREB6BBIYStWUTGq8oG5q9fClDMMuY8i4UPI/cEcDx2TN+7PMnXYIId20UVDw==", "dev": true }, "postcss-discard-empty": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz", - "integrity": "sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz", + "integrity": "sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==", "dev": true }, "postcss-discard-overridden": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz", - "integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz", + "integrity": "sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==", "dev": true }, "postcss-loader": { @@ -53151,65 +54529,66 @@ "dev": true }, "postcss-merge-longhand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz", - "integrity": "sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.2.tgz", + "integrity": "sha512-06vrW6ZWi9qeP7KMS9fsa9QW56+tIMW55KYqF7X3Ccn+NI2pIgPV6gFfvXTMQ05H90Y5DvnCDPZ2IuHa30PMUg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.2" + "stylehacks": "^7.0.2" } }, "postcss-merge-rules": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz", - "integrity": "sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.2.tgz", + "integrity": "sha512-VAR47UNvRsdrTHLe7TV1CeEtF9SJYR5ukIB9U4GZyZOptgtsS20xSxy+k5wMrI3udST6O1XuIn7cjQkg7sDAAw==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.1", - "postcss-selector-parser": "^6.0.15" + "cssnano-utils": "^5.0.0", + "postcss-selector-parser": "^6.1.0" } }, "postcss-minify-font-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz", - "integrity": "sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz", + "integrity": "sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-minify-gradients": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz", - "integrity": "sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz", + "integrity": "sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==", "dev": true, "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^4.0.1", + "colord": "^2.9.3", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-params": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz", - "integrity": "sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.1.tgz", + "integrity": "sha512-e+Xt8xErSRPgSRFxHeBCSxMiO8B8xng7lh8E0A5ep1VfwYhY8FXhu4Q3APMjgx9YDDbSp53IBGENrzygbUvgUQ==", "dev": true, "requires": { - "browserslist": "^4.22.2", - "cssnano-utils": "^4.0.1", + "browserslist": "^4.23.1", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz", - "integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.2.tgz", + "integrity": "sha512-dCzm04wqW1uqLmDZ41XYNBJfjgps3ZugDpogAmJXoCb5oCiTzIX4oPXXKxDpTvWOnKxQKR4EbV4ZawJBLcdXXA==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.15" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^6.1.0" } }, "postcss-modules-extract-imports": { @@ -53248,91 +54627,91 @@ } }, "postcss-normalize-charset": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", - "integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz", + "integrity": "sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==", "dev": true }, "postcss-normalize-display-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz", - "integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz", + "integrity": "sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-positions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz", - "integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz", + "integrity": "sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-repeat-style": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz", - "integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz", + "integrity": "sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-string": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz", - "integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz", + "integrity": "sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-timing-functions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz", - "integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz", + "integrity": "sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-unicode": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz", - "integrity": "sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.1.tgz", + "integrity": "sha512-PTPGdY9xAkTw+8ZZ71DUePb7M/Vtgkbbq+EoI33EuyQEzbKemEQMhe5QSr0VP5UfZlreANDPxSfcdSprENcbsg==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz", - "integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz", + "integrity": "sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-whitespace": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz", - "integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz", + "integrity": "sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-ordered-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz", - "integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz", + "integrity": "sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==", "dev": true, "requires": { - "cssnano-utils": "^4.0.1", + "cssnano-utils": "^5.0.0", "postcss-value-parser": "^4.2.0" } }, @@ -53342,19 +54721,19 @@ "integrity": "sha512-h9MJGaIvT5hnzFc7Vuo+2ulBw6ecmmfcd8SKKH2TziUzcIA04gUoXIbptuM+tR+htmsQIKNEluiQlmCQ2p5a2g==" }, "postcss-reduce-initial": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz", - "integrity": "sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.1.tgz", + "integrity": "sha512-0JDUSV4bGB5FGM5g8MkS+rvqKukJZ7OTHw/lcKn7xPNqeaqJyQbUO8/dJpvyTpaVwPsd3Uc33+CfNzdVowp2WA==", "dev": true, "requires": { - "browserslist": "^4.22.2", + "browserslist": "^4.23.1", "caniuse-api": "^3.0.0" } }, "postcss-reduce-transforms": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz", - "integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz", + "integrity": "sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" @@ -53379,9 +54758,9 @@ "dev": true }, "postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -53389,13 +54768,13 @@ } }, "postcss-svgo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz", - "integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz", + "integrity": "sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^3.3.2" }, "dependencies": { "commander": { @@ -53510,9 +54889,9 @@ } }, "svgo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "dev": true, "requires": { "@trysound/sax": "0.2.0", @@ -53527,12 +54906,12 @@ } }, "postcss-unique-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz", - "integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.1.tgz", + "integrity": "sha512-MH7QE/eKUftTB5ta40xcHLl7hkZjgDFydpfTK+QWXeHxghVt3VoPqYL5/G+zYZPPIs+8GuqFXSTgxBSoB1RZtQ==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.15" + "postcss-selector-parser": "^6.1.0" } }, "postcss-urlrebase": { @@ -53674,6 +55053,59 @@ } } }, + "proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "dependencies": { + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -53715,17 +55147,15 @@ "dev": true }, "puppeteer": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.11.1.tgz", - "integrity": "sha512-39olGaX2djYUdhaQQHDZ0T0GwEp+5f9UB9HmEP0qHfdQHIq0xGQZuAZ5TLnJIc/88SrPLpEflPC+xUqOTv3c5g==", + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.12.0.tgz", + "integrity": "sha512-kyUYI12SyJIjf9UGTnHfhNMYv4oVK321Jb9QZDBiGVNx5453SplvbdKI7UrF+S//3RtCneuUFCyHxnvQXQjpxg==", "dev": true, "requires": { - "@puppeteer/browsers": "0.5.0", - "cosmiconfig": "8.1.3", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "puppeteer-core": "19.11.1" + "@puppeteer/browsers": "2.2.3", + "cosmiconfig": "9.0.0", + "devtools-protocol": "0.0.1299070", + "puppeteer-core": "22.12.0" }, "dependencies": { "argparse": { @@ -53735,21 +55165,30 @@ "dev": true }, "cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "requires": { - "import-fresh": "^3.2.1", + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" + } + }, + "debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "requires": { + "ms": "2.1.2" } }, "devtools-protocol": { - "version": "0.0.1107588", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz", - "integrity": "sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==", + "version": "0.0.1299070", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", + "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==", "dev": true }, "js-yaml": { @@ -53761,29 +55200,29 @@ "argparse": "^2.0.1" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "puppeteer-core": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", - "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.12.0.tgz", + "integrity": "sha512-9gY+JwBW/Fp3/x9+cOGK7ZcwqjvtvY2xjqRqsAA0B3ZFMzBauVTSZ26iWTmvOQX2sk78TN/rd5rnetxVxmK5CQ==", "dev": true, "requires": { - "@puppeteer/browsers": "0.5.0", - "chromium-bidi": "0.4.7", - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1107588", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.13.0" + "@puppeteer/browsers": "2.2.3", + "chromium-bidi": "0.5.24", + "debug": "4.3.5", + "devtools-protocol": "0.0.1299070", + "ws": "8.17.1" } }, "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true } } @@ -53871,9 +55310,9 @@ "dev": true }, "qunit": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.20.0.tgz", - "integrity": "sha512-N8Fp1J55waE+QG1KwX2LOyqulZUToRrrPBqDOfYfuAMkEglFL15uwvmH1P4Tq/omQ/mGbBI8PEB3PhIfvUb+jg==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.21.0.tgz", + "integrity": "sha512-kJJ+uzx5xDWk0oRrbOZ3zsm+imPULE58ZMIrNl+3POZl4a1k6VXj2E4OiqTmZ9j6hh9egE3kNgnAti9Q+BG6Yw==", "dev": true, "requires": { "commander": "7.2.0", @@ -53904,9 +55343,9 @@ "dev": true }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "requires": { "bytes": "3.1.2", @@ -54182,9 +55621,9 @@ } }, "regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "regenerator-transform": { "version": "0.15.1", @@ -54847,9 +56286,9 @@ } }, "sass": { - "version": "1.70.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", - "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", + "version": "1.77.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", + "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -54933,9 +56372,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "requires": { "lru-cache": "^6.0.0" } @@ -55328,9 +56767,9 @@ } }, "sinon-test": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/sinon-test/-/sinon-test-3.1.5.tgz", - "integrity": "sha512-uKwKebXEX1yMQ9vz25Q/uDAFwoTsO/AQc+2b+6ndOUoxj7MZWoptz38lFw9QEgfxDPkN6NN0m+Kbah60tn0ZZA==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/sinon-test/-/sinon-test-3.1.6.tgz", + "integrity": "sha512-3jBJGf61sS2EN3M+YuIiIbeutKrubP6SFolceTcJrubG+4s+zq3rey/y0huSEwU2ECKOcyCs7EkzANnwqHWPjA==", "dev": true }, "sirv": { @@ -55617,9 +57056,9 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" }, "source-map-loader": { "version": "5.0.0", @@ -55936,13 +57375,15 @@ } }, "streamx": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", - "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, "requires": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" } }, "strict-uri-encode": { @@ -56149,13 +57590,13 @@ "dev": true }, "stylehacks": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz", - "integrity": "sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.2.tgz", + "integrity": "sha512-HdkWZS9b4gbgYTdMg4gJLmm7biAUug1qTqXjS+u8X+/pUd+9Px1E+520GnOW3rST9MNsVOVpsJG+mPHNosxjOQ==", "dev": true, "requires": { - "browserslist": "^4.22.2", - "postcss-selector-parser": "^6.0.15" + "browserslist": "^4.23.1", + "postcss-selector-parser": "^6.1.0" } }, "stylelint": { @@ -56217,12 +57658,12 @@ "dev": true }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "camelcase-keys": { @@ -56243,9 +57684,9 @@ "dev": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -56836,6 +58277,15 @@ "minimatch": "^3.0.4" } }, + "text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "requires": { + "b4a": "^1.6.4" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -57227,9 +58677,9 @@ "dev": true }, "underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" }, "underscore.string": { "version": "3.3.5", @@ -57356,13 +58806,13 @@ } }, "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" } }, "upper-case": { @@ -57382,9 +58832,9 @@ } }, "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -57440,6 +58890,12 @@ "dev": true, "optional": true }, + "urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -57804,9 +59260,9 @@ } }, "webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "requires": { "colorette": "^2.0.10", @@ -57944,9 +59400,9 @@ } }, "ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true } } @@ -58044,9 +59500,9 @@ } }, "whatwg-fetch": { - "version": "3.6.17", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz", - "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==" + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" }, "whatwg-mimetype": { "version": "3.0.0", @@ -58182,9 +59638,9 @@ } }, "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true }, "xdg-basedir": { @@ -58256,9 +59712,9 @@ }, "dependencies": { "ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "optional": true } } @@ -58328,6 +59784,12 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true } } } diff --git a/package.json b/package.json index b1c71c7cff422..a5a4dc945903c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "WordPress", - "version": "6.6.0", + "version": "6.7.0", "description": "WordPress is open source software you can use to create a beautiful website, blog, or app.", "repository": { "type": "svn", @@ -25,21 +25,21 @@ ], "devDependencies": { "@lodder/grunt-postcss": "^3.1.1", - "@playwright/test": "1.32.0", - "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", + "@playwright/test": "1.45.0", + "@pmmmwh/react-refresh-webpack-plugin": "0.5.15", "@wordpress/babel-preset-default": "8.0.1", - "@wordpress/dependency-extraction-webpack-plugin": "6.0.1", + "@wordpress/dependency-extraction-webpack-plugin": "6.0.2", "@wordpress/e2e-test-utils": "11.0.1", "@wordpress/e2e-test-utils-playwright": "1.0.1", "@wordpress/prettier-config": "4.0.1", - "@wordpress/scripts": "28.0.1", - "autoprefixer": "10.4.17", + "@wordpress/scripts": "28.0.2", + "autoprefixer": "10.4.19", "chalk": "5.3.0", "check-node-version": "4.2.1", "copy-webpack-plugin": "12.0.2", - "cssnano": "6.0.3", - "dotenv": "16.4.4", - "dotenv-expand": "11.0.3", + "cssnano": "7.0.3", + "dotenv": "16.4.5", + "dotenv-expand": "11.0.6", "grunt": "1.6.1", "grunt-banner": "^0.6.0", "grunt-contrib-clean": "~2.0.1", @@ -48,7 +48,7 @@ "grunt-contrib-cssmin": "~5.0.0", "grunt-contrib-imagemin": "~4.0.0", "grunt-contrib-jshint": "3.2.0", - "grunt-contrib-qunit": "~7.0.1", + "grunt-contrib-qunit": "~10.0.0", "grunt-contrib-uglify": "~5.2.2", "grunt-contrib-watch": "~1.1.0", "grunt-file-append": "0.0.7", @@ -62,13 +62,13 @@ "ink-docstrap": "1.3.2", "install-changed": "1.1.0", "matchdep": "~2.0.0", - "postcss": "8.4.35", + "postcss": "8.4.38", "prettier": "npm:wp-prettier@2.6.2", - "qunit": "~2.20.0", + "qunit": "~2.21.0", "react-refresh": "0.14.0", - "sass": "1.70.0", + "sass": "1.77.6", "sinon": "16.1.3", - "sinon-test": "~3.1.5", + "sinon-test": "~3.1.6", "source-map-loader": "5.0.0", "terser-webpack-plugin": "5.3.10", "uglify-js": "^3.17.4", @@ -79,81 +79,80 @@ }, "dependencies": { "@wordpress/a11y": "4.0.1", - "@wordpress/annotations": "3.0.1", + "@wordpress/annotations": "3.0.2", "@wordpress/api-fetch": "7.0.1", "@wordpress/autop": "4.0.1", "@wordpress/blob": "4.0.1", - "@wordpress/block-directory": "5.0.1", - "@wordpress/block-editor": "13.0.1", - "@wordpress/block-library": "9.0.1", + "@wordpress/block-directory": "5.0.8", + "@wordpress/block-editor": "13.0.6", + "@wordpress/block-library": "9.0.7", "@wordpress/block-serialization-default-parser": "5.0.1", - "@wordpress/blocks": "13.0.1", - "@wordpress/commands": "1.0.1", - "@wordpress/components": "28.0.1", + "@wordpress/blocks": "13.0.3", + "@wordpress/commands": "1.0.3", + "@wordpress/components": "28.0.3", "@wordpress/compose": "7.0.1", - "@wordpress/core-commands": "1.0.1", - "@wordpress/core-data": "7.0.1", - "@wordpress/customize-widgets": "5.0.1", - "@wordpress/data": "10.0.1", - "@wordpress/data-controls": "4.0.1", - "@wordpress/dataviews": "2.0.1", + "@wordpress/core-commands": "1.0.6", + "@wordpress/core-data": "7.0.6", + "@wordpress/customize-widgets": "5.0.7", + "@wordpress/data": "10.0.2", + "@wordpress/data-controls": "4.0.2", + "@wordpress/dataviews": "2.0.4", "@wordpress/date": "5.0.1", "@wordpress/deprecated": "4.0.1", "@wordpress/dom": "4.0.1", "@wordpress/dom-ready": "4.0.1", - "@wordpress/edit-post": "8.0.1", - "@wordpress/edit-site": "6.0.1", - "@wordpress/edit-widgets": "6.0.1", - "@wordpress/editor": "14.0.1", + "@wordpress/edit-post": "8.0.8", + "@wordpress/edit-site": "6.0.8", + "@wordpress/edit-widgets": "6.0.7", + "@wordpress/editor": "14.0.7", "@wordpress/element": "6.0.1", "@wordpress/escape-html": "3.0.1", - "@wordpress/format-library": "5.0.1", + "@wordpress/format-library": "5.0.6", "@wordpress/hooks": "4.0.1", "@wordpress/html-entities": "4.0.1", "@wordpress/i18n": "5.0.1", - "@wordpress/icons": "10.0.1", - "@wordpress/interactivity": "6.0.1", - "@wordpress/interactivity-router": "2.0.1", - "@wordpress/interface": "6.0.1", + "@wordpress/icons": "10.0.2", + "@wordpress/interactivity": "6.0.2", + "@wordpress/interactivity-router": "2.0.2", + "@wordpress/interface": "6.0.3", "@wordpress/is-shallow-equal": "5.0.1", - "@wordpress/keyboard-shortcuts": "5.0.1", + "@wordpress/keyboard-shortcuts": "5.0.2", "@wordpress/keycodes": "4.0.1", - "@wordpress/list-reusable-blocks": "5.0.1", + "@wordpress/list-reusable-blocks": "5.0.3", "@wordpress/media-utils": "5.0.1", - "@wordpress/notices": "5.0.1", - "@wordpress/nux": "9.0.1", - "@wordpress/patterns": "2.0.1", - "@wordpress/plugins": "7.0.1", - "@wordpress/preferences": "4.0.1", + "@wordpress/notices": "5.0.2", + "@wordpress/nux": "9.0.3", + "@wordpress/patterns": "2.0.6", + "@wordpress/plugins": "7.0.3", + "@wordpress/preferences": "4.0.3", "@wordpress/preferences-persistence": "2.0.1", "@wordpress/primitives": "4.0.1", "@wordpress/priority-queue": "3.0.1", - "@wordpress/private-apis": "1.0.1", + "@wordpress/private-apis": "1.0.2", "@wordpress/redux-routine": "5.0.1", - "@wordpress/reusable-blocks": "5.0.1", - "@wordpress/rich-text": "7.0.1", - "@wordpress/router": "1.0.1", - "@wordpress/server-side-render": "5.0.1", + "@wordpress/reusable-blocks": "5.0.6", + "@wordpress/rich-text": "7.0.2", + "@wordpress/router": "1.0.2", + "@wordpress/server-side-render": "5.0.3", "@wordpress/shortcode": "4.0.1", - "@wordpress/style-engine": "2.0.1", + "@wordpress/style-engine": "2.0.2", "@wordpress/sync": "1.0.1", "@wordpress/token-list": "3.0.1", "@wordpress/undo-manager": "1.0.1", "@wordpress/url": "4.0.1", - "@wordpress/viewport": "6.0.1", + "@wordpress/viewport": "6.0.2", "@wordpress/warning": "3.0.1", - "@wordpress/widgets": "4.0.1", + "@wordpress/widgets": "4.0.6", "@wordpress/wordcount": "4.0.1", - "backbone": "1.5.0", + "backbone": "1.6.0", "clipboard": "2.0.11", "core-js-url-browser": "3.6.4", "element-closest": "^3.0.2", - "es-module-shims": "1.8.2", "formdata-polyfill": "4.0.10", "hoverintent": "2.2.1", "imagesloaded": "5.0.0", "jquery": "3.7.1", - "jquery-color": "2.2.0", + "jquery-color": "3.0.0", "jquery-form": "4.3.0", "jquery-hoverintent": "1.10.2", "json2php": "^0.0.7", @@ -165,9 +164,9 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-is": "18.3.1", - "regenerator-runtime": "0.14.0", - "underscore": "1.13.6", - "whatwg-fetch": "3.6.17", + "regenerator-runtime": "0.14.1", + "underscore": "1.13.7", + "whatwg-fetch": "3.6.20", "wicg-inert": "3.1.2" }, "scripts": { diff --git a/phpcs.xml.dist b/phpcs.xml.dist index ece1c8238613d..636580a4040d9 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -262,6 +262,7 @@ in the parsing, and distance the code from its standard. --> /wp-includes/html-api/class-wp-html-processor\.php + /wp-includes/html-api/class-wp-html-doctype-info\.php - - + + diff --git a/src/wp-content/themes/twentytwentythree/templates/home.html b/src/wp-content/themes/twentytwentythree/templates/home.html index 9b84d2b3b81f2..23fc5cf415c8c 100644 --- a/src/wp-content/themes/twentytwentythree/templates/home.html +++ b/src/wp-content/themes/twentytwentythree/templates/home.html @@ -20,8 +20,8 @@

Mindb - - + + diff --git a/src/wp-content/themes/twentytwentythree/templates/search.html b/src/wp-content/themes/twentytwentythree/templates/search.html index c80325d77e5eb..53ec6badc13da 100644 --- a/src/wp-content/themes/twentytwentythree/templates/search.html +++ b/src/wp-content/themes/twentytwentythree/templates/search.html @@ -18,8 +18,8 @@ - - + + diff --git a/src/wp-content/themes/twentytwentytwo/readme.txt b/src/wp-content/themes/twentytwentytwo/readme.txt index 61bbb8f828ff1..d34e08cc401c4 100644 --- a/src/wp-content/themes/twentytwentytwo/readme.txt +++ b/src/wp-content/themes/twentytwentytwo/readme.txt @@ -1,9 +1,9 @@ === Twenty Twenty-Two === Contributors: wordpressdotorg Requires at least: 5.9 -Tested up to: 6.5 +Tested up to: 6.6 Requires PHP: 5.6 -Stable tag: 1.7 +Stable tag: 1.8 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -17,6 +17,11 @@ Whether you’re building a single-page website, a blog, a business website, or == Changelog == += 1.8 = +* Released: July 16, 2024 + +https://wordpress.org/documentation/article/twenty-twenty-two-changelog/#Version_1.8 + = 1.7 = * Released: April 2, 2024 @@ -59,7 +64,7 @@ https://wordpress.org/documentation/article/twenty-twenty-two-changelog/#Version == Copyright == -Twenty Twenty-Two WordPress Theme, 2021-2024 WordPress.org +Twenty Twenty-Two WordPress Theme, 2021-2024 WordPress.org and contributors. Twenty Twenty-Two is distributed under the terms of the GNU GPL. This program is free software: you can redistribute it and/or modify diff --git a/src/wp-content/themes/twentytwentytwo/style.css b/src/wp-content/themes/twentytwentytwo/style.css index e1e26e985cb00..4d36abfab2f9b 100644 --- a/src/wp-content/themes/twentytwentytwo/style.css +++ b/src/wp-content/themes/twentytwentytwo/style.css @@ -5,9 +5,9 @@ Author: the WordPress team Author URI: https://wordpress.org/ Description: Built on a solidly designed foundation, Twenty Twenty-Two embraces the idea that everyone deserves a truly unique website. The theme’s subtle styles are inspired by the diversity and versatility of birds: its typography is lightweight yet strong, its color palette is drawn from nature, and its layout elements sit gently on the page. The true richness of Twenty Twenty-Two lies in its opportunity for customization. The theme is built to take advantage of the Site Editor features introduced in WordPress 5.9, which means that colors, typography, and the layout of every single page on your site can be customized to suit your vision. It also includes dozens of block patterns, opening the door to a wide range of professionally designed layouts in just a few clicks. Whether you’re building a single-page website, a blog, a business website, or a portfolio, Twenty Twenty-Two will help you create a site that is uniquely yours. Requires at least: 5.9 -Tested up to: 6.5 +Tested up to: 6.6 Requires PHP: 5.6 -Version: 1.7 +Version: 1.8 License: GNU General Public License v2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Text Domain: twentytwentytwo @@ -65,7 +65,7 @@ a:active { .wp-block-file .wp-block-file__button { background-color: var(--wp--preset--color--primary); border-radius: 0; - border: none; + border: 0 solid; color: var(--wp--preset--color--background); font-size: var(--wp--preset--font-size--medium); padding: calc(.667em + 2px) calc(1.333em + 2px); diff --git a/src/wp-content/themes/twentytwentytwo/styles/blue.json b/src/wp-content/themes/twentytwentytwo/styles/blue.json index 8ebedef019453..29338c5a1ace1 100644 --- a/src/wp-content/themes/twentytwentytwo/styles/blue.json +++ b/src/wp-content/themes/twentytwentytwo/styles/blue.json @@ -1,4 +1,5 @@ { + "$schema": "https://schemas.wp.org/wp/6.5/theme.json", "version": 2, "title": "Blue", "settings": { diff --git a/src/wp-content/themes/twentytwentytwo/styles/pink.json b/src/wp-content/themes/twentytwentytwo/styles/pink.json index 5c9be91a4e3c6..8646dcc6beb34 100644 --- a/src/wp-content/themes/twentytwentytwo/styles/pink.json +++ b/src/wp-content/themes/twentytwentytwo/styles/pink.json @@ -1,4 +1,5 @@ { + "$schema": "https://schemas.wp.org/wp/6.5/theme.json", "version": 2, "title": "Pink", "settings": { diff --git a/src/wp-content/themes/twentytwentytwo/styles/swiss.json b/src/wp-content/themes/twentytwentytwo/styles/swiss.json index 483467ccc0b83..226df86b4dc84 100644 --- a/src/wp-content/themes/twentytwentytwo/styles/swiss.json +++ b/src/wp-content/themes/twentytwentytwo/styles/swiss.json @@ -1,4 +1,5 @@ { + "$schema": "https://schemas.wp.org/wp/6.5/theme.json", "version": 2, "title": "Swiss", "settings": { diff --git a/src/wp-cron.php b/src/wp-cron.php index 161478ac49469..417dcce375849 100644 --- a/src/wp-cron.php +++ b/src/wp-cron.php @@ -24,7 +24,7 @@ } // Don't run cron until the request finishes, if possible. -if ( PHP_VERSION_ID >= 70016 && function_exists( 'fastcgi_finish_request' ) ) { +if ( function_exists( 'fastcgi_finish_request' ) ) { fastcgi_finish_request(); } elseif ( function_exists( 'litespeed_finish_request' ) ) { litespeed_finish_request(); @@ -141,7 +141,7 @@ function _get_cron_lock() { ); /** - * Fires when an error happens rescheduling a cron event. + * Fires if an error happens when rescheduling a cron event. * * @since 6.1.0 * @@ -168,7 +168,7 @@ function _get_cron_lock() { ); /** - * Fires when an error happens unscheduling a cron event. + * Fires if an error happens when unscheduling a cron event. * * @since 6.1.0 * diff --git a/src/wp-includes/assets/script-loader-packages.min.php b/src/wp-includes/assets/script-loader-packages.min.php index 34160b70d70d4..c39418af7632e 100644 --- a/src/wp-includes/assets/script-loader-packages.min.php +++ b/src/wp-includes/assets/script-loader-packages.min.php @@ -1 +1 @@ - array('dependencies' => array('wp-dom-ready', 'wp-i18n'), 'version' => 'd90eebea464f6c09bfd5'), 'annotations.min.js' => array('dependencies' => array('wp-data', 'wp-hooks', 'wp-i18n', 'wp-rich-text'), 'version' => '238360e96c76d37a2468'), 'api-fetch.min.js' => array('dependencies' => array('wp-i18n', 'wp-url'), 'version' => '4c185334c5ec26e149cc'), 'autop.min.js' => array('dependencies' => array(), 'version' => '9fb50649848277dd318d'), 'blob.min.js' => array('dependencies' => array(), 'version' => '9113eed771d446f4a556'), 'block-directory.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => '968ba40a656b83a9dc56'), 'block-editor.min.js' => array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-style-engine', 'wp-token-list', 'wp-url', 'wp-warning', 'wp-wordcount'), 'version' => '98e0e2146606bb15dd7f'), 'block-library.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-autop', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-patterns', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-viewport', 'wp-wordcount'), 'version' => 'e6c4fb4b09a3e3c43793'), 'block-serialization-default-parser.min.js' => array('dependencies' => array(), 'version' => '14d44daebf663d05d330'), 'blocks.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-autop', 'wp-blob', 'wp-block-serialization-default-parser', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-private-apis', 'wp-rich-text', 'wp-shortcode'), 'version' => '163a3237ce977e2fc461'), 'commands.min.js' => array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-primitives', 'wp-private-apis'), 'version' => 'bb30b880583b7afd7638'), 'components.min.js' => array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-compose', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-warning'), 'version' => 'deb675713dc5eae0dcd9'), 'compose.min.js' => array('dependencies' => array('react', 'react-jsx-runtime', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-priority-queue'), 'version' => 'b8d54449305350b51869'), 'core-commands.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-commands', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-private-apis', 'wp-router', 'wp-url'), 'version' => 'f784f7e0adecdb72f465'), 'core-data.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-private-apis', 'wp-rich-text', 'wp-url'), 'version' => '3bef343176e7686795bd'), 'customize-widgets.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-widgets'), 'version' => '3622c40e4bb681460856'), 'data.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-compose', 'wp-deprecated', 'wp-element', 'wp-is-shallow-equal', 'wp-priority-queue', 'wp-private-apis', 'wp-redux-routine'), 'version' => '75d37014bc62064a6c51'), 'data-controls.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-data', 'wp-deprecated'), 'version' => '49f5587e8b90f9e7cc7e'), 'date.min.js' => array('dependencies' => array('moment', 'wp-deprecated'), 'version' => 'aaca6387d1cf924acc51'), 'deprecated.min.js' => array('dependencies' => array('wp-hooks'), 'version' => 'e1f84915c5e8ae38964c'), 'dom.min.js' => array('dependencies' => array('wp-deprecated'), 'version' => '4ecffbffba91b10c5c7a'), 'dom-ready.min.js' => array('dependencies' => array(), 'version' => 'f77871ff7694fffea381'), 'edit-post.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-commands', 'wp-core-data', 'wp-data', 'wp-deprecated', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-widgets'), 'version' => '1a60a3ec392148b4b28e'), 'edit-site.min.js' => array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-commands', 'wp-core-data', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-patterns', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-priority-queue', 'wp-private-apis', 'wp-router', 'wp-url', 'wp-widgets'), 'version' => '13c93549d7c8812282da'), 'edit-widgets.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-patterns', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-viewport', 'wp-widgets'), 'version' => 'd68ea63efce95680c83e'), 'editor.min.js' => array('dependencies' => array('react', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-patterns', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-viewport', 'wp-warning', 'wp-wordcount'), 'version' => 'b61f6f05397a7eed3674'), 'element.min.js' => array('dependencies' => array('react', 'react-dom', 'wp-escape-html'), 'version' => 'cb762d190aebbec25b27'), 'escape-html.min.js' => array('dependencies' => array(), 'version' => '6561a406d2d232a6fbd2'), 'format-library.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-url'), 'version' => '4ff1382e9cf0dcb9e0ba'), 'hooks.min.js' => array('dependencies' => array(), 'version' => '2810c76e705dd1a53b18'), 'html-entities.min.js' => array('dependencies' => array(), 'version' => '2cd3358363e0675638fb'), 'i18n.min.js' => array('dependencies' => array('wp-hooks'), 'version' => '5e580eb46a90c2b997e6'), 'is-shallow-equal.min.js' => array('dependencies' => array(), 'version' => 'e0f9f1d78d83f5196979'), 'keyboard-shortcuts.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-data', 'wp-element', 'wp-keycodes'), 'version' => '32686e58e84193ce808b'), 'keycodes.min.js' => array('dependencies' => array('wp-i18n'), 'version' => '034ff647a54b018581d3'), 'list-reusable-blocks.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-blob', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n'), 'version' => '0eaedbf902a3af0eee37'), 'media-utils.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-blob', 'wp-element', 'wp-i18n'), 'version' => '1cf582d3c080c8694c8c'), 'notices.min.js' => array('dependencies' => array('wp-data'), 'version' => '673a68a7ac2f556ed50b'), 'nux.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'b8f02a77b1489668fb50'), 'patterns.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => 'd6e3dd761975c003f81c'), 'plugins.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-compose', 'wp-element', 'wp-hooks', 'wp-is-shallow-equal', 'wp-primitives'), 'version' => 'ef6da4a9b2747b62c09c'), 'preferences.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-private-apis'), 'version' => '4520395f0a0025020cf3'), 'preferences-persistence.min.js' => array('dependencies' => array('wp-api-fetch'), 'version' => '9307a8c9e3254140a223'), 'primitives.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-element'), 'version' => 'aef2543ab60c8c9bb609'), 'priority-queue.min.js' => array('dependencies' => array(), 'version' => '9c21c957c7e50ffdbf48'), 'private-apis.min.js' => array('dependencies' => array(), 'version' => '5e7fdf55d04b8c2aadef'), 'redux-routine.min.js' => array('dependencies' => array(), 'version' => 'a0a172871afaeb261566'), 'reusable-blocks.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => '771ceeb986898cba2423'), 'rich-text.min.js' => array('dependencies' => array('wp-a11y', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-keycodes'), 'version' => '4021b9e4e9ef4d3cd868'), 'router.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-element', 'wp-private-apis', 'wp-url'), 'version' => '01e28626c63ba9505bb6'), 'server-side-render.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '1e0f25c205ebeb30bcd2'), 'shortcode.min.js' => array('dependencies' => array(), 'version' => 'b7747eee0efafd2f0c3b'), 'style-engine.min.js' => array('dependencies' => array(), 'version' => '86ba6721a03e5b921dfe'), 'token-list.min.js' => array('dependencies' => array(), 'version' => '05f8a6df6258f0081718'), 'undo-manager.min.js' => array('dependencies' => array('wp-is-shallow-equal'), 'version' => 'f0698003cb0f0a7bd794'), 'url.min.js' => array('dependencies' => array(), 'version' => '36ae0e4dd9043bb8749b'), 'viewport.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-compose', 'wp-data'), 'version' => '829c9a30d366e1e5054c'), 'warning.min.js' => array('dependencies' => array(), 'version' => 'ed7c8b0940914f4fe44b'), 'widgets.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives'), 'version' => '53f9d5d5df6f21e39834'), 'wordcount.min.js' => array('dependencies' => array(), 'version' => '55d8c2bf3dc99e7ea5ec')); + array('dependencies' => array('wp-dom-ready', 'wp-i18n'), 'version' => 'd90eebea464f6c09bfd5'), 'annotations.min.js' => array('dependencies' => array('wp-data', 'wp-hooks', 'wp-i18n', 'wp-rich-text'), 'version' => '238360e96c76d37a2468'), 'api-fetch.min.js' => array('dependencies' => array('wp-i18n', 'wp-url'), 'version' => '4c185334c5ec26e149cc'), 'autop.min.js' => array('dependencies' => array(), 'version' => '9fb50649848277dd318d'), 'blob.min.js' => array('dependencies' => array(), 'version' => '9113eed771d446f4a556'), 'block-directory.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => '286a70e45f3a8522a72a'), 'block-editor.min.js' => array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-style-engine', 'wp-token-list', 'wp-url', 'wp-warning', 'wp-wordcount'), 'version' => 'f989eae66982c6c90d6e'), 'block-library.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-autop', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-patterns', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-viewport', 'wp-wordcount'), 'version' => '96b614e020e4ab48838d'), 'block-serialization-default-parser.min.js' => array('dependencies' => array(), 'version' => '14d44daebf663d05d330'), 'blocks.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-autop', 'wp-blob', 'wp-block-serialization-default-parser', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-private-apis', 'wp-rich-text', 'wp-shortcode'), 'version' => '0d232d232463200f5cfd'), 'commands.min.js' => array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-primitives', 'wp-private-apis'), 'version' => '73d702f6367f60b06d89'), 'components.min.js' => array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-compose', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-warning'), 'version' => '36b97398bf090476214e'), 'compose.min.js' => array('dependencies' => array('react', 'react-jsx-runtime', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-is-shallow-equal', 'wp-keycodes', 'wp-priority-queue'), 'version' => 'b8d54449305350b51869'), 'core-commands.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-commands', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-private-apis', 'wp-router', 'wp-url'), 'version' => 'c81574a3c95e631df2a9'), 'core-data.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-is-shallow-equal', 'wp-private-apis', 'wp-rich-text', 'wp-url'), 'version' => '07feee0ca98b13ab617d'), 'customize-widgets.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-widgets'), 'version' => '9b7db92a95af0a39ae98'), 'data.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-compose', 'wp-deprecated', 'wp-element', 'wp-is-shallow-equal', 'wp-priority-queue', 'wp-private-apis', 'wp-redux-routine'), 'version' => '7c62e39de0308c73d50c'), 'data-controls.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-data', 'wp-deprecated'), 'version' => '49f5587e8b90f9e7cc7e'), 'date.min.js' => array('dependencies' => array('moment', 'wp-deprecated'), 'version' => 'aaca6387d1cf924acc51'), 'deprecated.min.js' => array('dependencies' => array('wp-hooks'), 'version' => 'e1f84915c5e8ae38964c'), 'dom.min.js' => array('dependencies' => array('wp-deprecated'), 'version' => '4ecffbffba91b10c5c7a'), 'dom-ready.min.js' => array('dependencies' => array(), 'version' => 'f77871ff7694fffea381'), 'edit-post.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-commands', 'wp-core-data', 'wp-data', 'wp-deprecated', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-widgets'), 'version' => 'bf7b57a061aad9bf9020'), 'edit-site.min.js' => array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-commands', 'wp-core-data', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-patterns', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-priority-queue', 'wp-private-apis', 'wp-router', 'wp-url', 'wp-widgets'), 'version' => '908b9738a38cdf931130'), 'edit-widgets.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-patterns', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-url', 'wp-viewport', 'wp-widgets'), 'version' => '974884730aac247c3a3e'), 'editor.min.js' => array('dependencies' => array('react', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-media-utils', 'wp-notices', 'wp-patterns', 'wp-plugins', 'wp-preferences', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-server-side-render', 'wp-url', 'wp-viewport', 'wp-warning', 'wp-wordcount'), 'version' => '8607251058f984a77c8f'), 'element.min.js' => array('dependencies' => array('react', 'react-dom', 'wp-escape-html'), 'version' => 'cb762d190aebbec25b27'), 'escape-html.min.js' => array('dependencies' => array(), 'version' => '6561a406d2d232a6fbd2'), 'format-library.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-block-editor', 'wp-components', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-primitives', 'wp-private-apis', 'wp-rich-text', 'wp-url'), 'version' => 'e3618e6d17a1146f03e4'), 'hooks.min.js' => array('dependencies' => array(), 'version' => '2810c76e705dd1a53b18'), 'html-entities.min.js' => array('dependencies' => array(), 'version' => '2cd3358363e0675638fb'), 'i18n.min.js' => array('dependencies' => array('wp-hooks'), 'version' => '5e580eb46a90c2b997e6'), 'is-shallow-equal.min.js' => array('dependencies' => array(), 'version' => 'e0f9f1d78d83f5196979'), 'keyboard-shortcuts.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-data', 'wp-element', 'wp-keycodes'), 'version' => '32686e58e84193ce808b'), 'keycodes.min.js' => array('dependencies' => array('wp-i18n'), 'version' => '034ff647a54b018581d3'), 'list-reusable-blocks.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-blob', 'wp-components', 'wp-compose', 'wp-element', 'wp-i18n'), 'version' => '0eaedbf902a3af0eee37'), 'media-utils.min.js' => array('dependencies' => array('wp-api-fetch', 'wp-blob', 'wp-element', 'wp-i18n'), 'version' => '1cf582d3c080c8694c8c'), 'notices.min.js' => array('dependencies' => array('wp-data'), 'version' => '673a68a7ac2f556ed50b'), 'nux.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'b8f02a77b1489668fb50'), 'patterns.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => '74acf014a3907af88267'), 'plugins.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-compose', 'wp-element', 'wp-hooks', 'wp-is-shallow-equal', 'wp-primitives'), 'version' => 'ef6da4a9b2747b62c09c'), 'preferences.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-a11y', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-private-apis'), 'version' => 'e7b06b8f8bdd714600e9'), 'preferences-persistence.min.js' => array('dependencies' => array('wp-api-fetch'), 'version' => '9307a8c9e3254140a223'), 'primitives.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-element'), 'version' => 'aef2543ab60c8c9bb609'), 'priority-queue.min.js' => array('dependencies' => array(), 'version' => '9c21c957c7e50ffdbf48'), 'private-apis.min.js' => array('dependencies' => array(), 'version' => '17a2e640b653d742da6e'), 'redux-routine.min.js' => array('dependencies' => array(), 'version' => 'a0a172871afaeb261566'), 'reusable-blocks.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-url'), 'version' => '5c420cb83017d586169a'), 'rich-text.min.js' => array('dependencies' => array('wp-a11y', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-element', 'wp-escape-html', 'wp-i18n', 'wp-keycodes'), 'version' => '4021b9e4e9ef4d3cd868'), 'router.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-element', 'wp-private-apis', 'wp-url'), 'version' => 'e4887fecc16ef03e908f'), 'server-side-render.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '1e0f25c205ebeb30bcd2'), 'shortcode.min.js' => array('dependencies' => array(), 'version' => 'b7747eee0efafd2f0c3b'), 'style-engine.min.js' => array('dependencies' => array(), 'version' => '86ba6721a03e5b921dfe'), 'token-list.min.js' => array('dependencies' => array(), 'version' => '05f8a6df6258f0081718'), 'undo-manager.min.js' => array('dependencies' => array('wp-is-shallow-equal'), 'version' => 'f0698003cb0f0a7bd794'), 'url.min.js' => array('dependencies' => array(), 'version' => '36ae0e4dd9043bb8749b'), 'viewport.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-compose', 'wp-data'), 'version' => '829c9a30d366e1e5054c'), 'warning.min.js' => array('dependencies' => array(), 'version' => 'ed7c8b0940914f4fe44b'), 'widgets.min.js' => array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives'), 'version' => '53f9d5d5df6f21e39834'), 'wordcount.min.js' => array('dependencies' => array(), 'version' => '55d8c2bf3dc99e7ea5ec')); diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index fdc4846f69f80..e8900d1ccdc1a 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -532,7 +532,7 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex * entered by users does not break other global styles. */ $global_styles[] = array( - 'css' => wp_get_global_styles_custom_css(), + 'css' => wp_get_global_stylesheet( array( 'custom-css' ) ), '__unstableType' => 'user', 'isGlobalStyles' => true, ); @@ -648,6 +648,23 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex $editor_settings['postContentAttributes'] = $post_content_block_attributes; } + // Expose block bindings sources in the editor settings. + $registered_block_bindings_sources = get_all_registered_block_bindings_sources(); + if ( ! empty( $registered_block_bindings_sources ) ) { + // Initialize array. + $editor_settings['blockBindingsSources'] = array(); + foreach ( $registered_block_bindings_sources as $source_name => $source_properties ) { + // Add source with the label to editor settings. + $editor_settings['blockBindingsSources'][ $source_name ] = array( + 'label' => $source_properties->label, + ); + // Add `usesContext` property if exists. + if ( ! empty( $source_properties->uses_context ) ) { + $editor_settings['blockBindingsSources'][ $source_name ]['usesContext'] = $source_properties->uses_context; + } + } + } + /** * Filters the settings to pass to the block editor for all editor type. * diff --git a/src/wp-includes/block-supports/background.php b/src/wp-includes/block-supports/background.php index ff50be3206837..6ef9bbb9a6c21 100644 --- a/src/wp-includes/block-supports/background.php +++ b/src/wp-includes/block-supports/background.php @@ -42,6 +42,7 @@ function wp_register_background_support( $block_type ) { * @since 6.4.0 * @since 6.5.0 Added support for `backgroundPosition` and `backgroundRepeat` output. * @since 6.6.0 Removed requirement for `backgroundImage.source`. A file/url is the default. + * @since 6.7.0 Added support for `backgroundAttachment` output. * * @access private * @@ -62,17 +63,19 @@ function wp_render_background_support( $block_content, $block ) { return $block_content; } - $background_styles = array(); - $background_styles['backgroundImage'] = isset( $block_attributes['style']['background']['backgroundImage'] ) ? $block_attributes['style']['background']['backgroundImage'] : array(); + $background_styles = array(); + $background_styles['backgroundImage'] = $block_attributes['style']['background']['backgroundImage'] ?? null; + $background_styles['backgroundSize'] = $block_attributes['style']['background']['backgroundSize'] ?? null; + $background_styles['backgroundPosition'] = $block_attributes['style']['background']['backgroundPosition'] ?? null; + $background_styles['backgroundRepeat'] = $block_attributes['style']['background']['backgroundRepeat'] ?? null; + $background_styles['backgroundAttachment'] = $block_attributes['style']['background']['backgroundAttachment'] ?? null; if ( ! empty( $background_styles['backgroundImage'] ) ) { - $background_styles['backgroundSize'] = isset( $block_attributes['style']['background']['backgroundSize'] ) ? $block_attributes['style']['background']['backgroundSize'] : 'cover'; - $background_styles['backgroundPosition'] = isset( $block_attributes['style']['background']['backgroundPosition'] ) ? $block_attributes['style']['background']['backgroundPosition'] : null; - $background_styles['backgroundRepeat'] = isset( $block_attributes['style']['background']['backgroundRepeat'] ) ? $block_attributes['style']['background']['backgroundRepeat'] : null; + $background_styles['backgroundSize'] = $background_styles['backgroundSize'] ?? 'cover'; // If the background size is set to `contain` and no position is set, set the position to `center`. if ( 'contain' === $background_styles['backgroundSize'] && ! $background_styles['backgroundPosition'] ) { - $background_styles['backgroundPosition'] = 'center'; + $background_styles['backgroundPosition'] = '50% 50%'; } } diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index 8e84292ac0eb4..4397a34af9701 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -7,21 +7,6 @@ * @since 6.6.0 */ -/** - * Generate block style variation instance name. - * - * @since 6.6.0 - * @access private - * - * @param array $block Block object. - * @param string $variation Slug for the block style variation. - * - * @return string The unique variation name. - */ -function wp_create_block_style_variation_instance_name( $block, $variation ) { - return $variation . '--' . md5( serialize( $block ) ); -} - /** * Determines the block style variation names within a CSS class string. * @@ -40,6 +25,42 @@ function wp_get_block_style_variation_name_from_class( $class_string ) { return $matches[1] ?? null; } +/** + * Recursively resolves any `ref` values within a block style variation's data. + * + * @since 6.6.0 + * @access private + * + * @param array $variation_data Reference to the variation data being processed. + * @param array $theme_json Theme.json data to retrieve referenced values from. + */ +function wp_resolve_block_style_variation_ref_values( &$variation_data, $theme_json ) { + foreach ( $variation_data as $key => &$value ) { + // Only need to potentially process arrays. + if ( is_array( $value ) ) { + // If ref value is set, attempt to find its matching value and update it. + if ( array_key_exists( 'ref', $value ) ) { + // Clean up any invalid ref value. + if ( empty( $value['ref'] ) || ! is_string( $value['ref'] ) ) { + unset( $variation_data[ $key ] ); + } + + $value_path = explode( '.', $value['ref'] ?? '' ); + $ref_value = _wp_array_get( $theme_json, $value_path ); + + // Only update the current value if the referenced path matched a value. + if ( null === $ref_value ) { + unset( $variation_data[ $key ] ); + } else { + $value = $ref_value; + } + } else { + // Recursively look for ref instances. + wp_resolve_block_style_variation_ref_values( $value, $theme_json ); + } + } + } +} /** * Render the block style variation's styles. * @@ -82,7 +103,13 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { return $parsed_block; } - $variation_instance = wp_create_block_style_variation_instance_name( $parsed_block, $variation ); + /* + * Recursively resolve any ref values with the appropriate value within the + * theme_json data. + */ + wp_resolve_block_style_variation_ref_values( $variation_data, $theme_json ); + + $variation_instance = wp_unique_id( $variation . '--' ); $class_name = "is-style-$variation_instance"; $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; @@ -136,8 +163,9 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { array( 'styles' ), array( 'custom' ), array( - 'skip_root_layout_styles' => true, - 'scope' => ".$class_name", + 'include_block_style_variations' => true, + 'skip_root_layout_styles' => true, + 'scope' => ".$class_name", ) ); @@ -153,7 +181,7 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { return $parsed_block; } - wp_register_style( 'block-style-variation-styles', false, array( 'global-styles', 'wp-block-library' ) ); + wp_register_style( 'block-style-variation-styles', false, array( 'wp-block-library', 'global-styles' ) ); wp_add_inline_style( 'block-style-variation-styles', $variation_styles ); /* @@ -187,11 +215,9 @@ function wp_render_block_style_variation_class_name( $block_content, $block ) { /* * Matches a class prefixed by `is-style`, followed by the - * variation slug, then `--`, and finally a hash. - * - * See `wp_create_block_style_variation_instance_name` for class generation. + * variation slug, then `--`, and finally an instance number. */ - preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches ); + preg_match( '/\bis-style-(\S+?--\d+)\b/', $block['attrs']['className'], $matches ); if ( empty( $matches ) ) { return $block_content; @@ -211,176 +237,6 @@ function wp_render_block_style_variation_class_name( $block_content, $block ) { return $tags->get_updated_html(); } -/** - * Collects block style variation data for merging with theme.json data. - * - * @since 6.6.0 - * @access private - * - * @param array $variations Shared block style variations. - * - * @return array Block variations data to be merged under `styles.blocks`. - */ -function wp_resolve_block_style_variations( $variations ) { - $variations_data = array(); - - if ( empty( $variations ) ) { - return $variations_data; - } - - $have_named_variations = ! wp_is_numeric_array( $variations ); - - foreach ( $variations as $key => $variation ) { - $supported_blocks = $variation['blockTypes'] ?? array(); - - /* - * Standalone theme.json partial files for block style variations - * will have their styles under a top-level property by the same name. - * Variations defined within an existing theme.json or theme style - * variation will themselves already be the required styles data. - */ - $variation_data = $variation['styles'] ?? $variation; - - if ( empty( $variation_data ) ) { - continue; - } - - /* - * Block style variations read in via standalone theme.json partials - * need to have their name set to the kebab case version of their title. - */ - $variation_name = $have_named_variations ? $key : ( $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ) ); - - foreach ( $supported_blocks as $block_type ) { - // Add block style variation data under current block type. - $path = array( $block_type, 'variations', $variation_name ); - _wp_array_set( $variations_data, $path, $variation_data ); - } - } - - return $variations_data; -} - -/** - * Merges variations data with existing theme.json data ensuring that the - * current theme.json data values take precedence. - * - * @since 6.6.0 - * @access private - * - * @param array $variations_data Block style variations data keyed by block type. - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * @param string $origin Origin for the theme.json data. - * - * @return WP_Theme_JSON The merged theme.json data. - */ -function wp_merge_block_style_variations_data( $variations_data, $theme_json, $origin = 'theme' ) { - if ( empty( $variations_data ) ) { - return $theme_json; - } - - $variations_theme_json_data = array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, - 'styles' => array( 'blocks' => $variations_data ), - ); - - $variations_theme_json = new WP_Theme_JSON_Data( $variations_theme_json_data, $origin ); - - /* - * Merge the current theme.json data over shared variation data so that - * any explicit per block variation values take precedence. - */ - return $variations_theme_json->update_with( $theme_json->get_data() ); -} - -/** - * Merges any shared block style variation definitions from a theme style - * variation into their appropriate block type within theme json styles. Any - * custom user selections already made will take precedence over the shared - * style variation value. - * - * @since 6.6.0 - * @access private - * - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * - * @return WP_Theme_JSON_Data - */ -function wp_resolve_block_style_variations_from_theme_style_variation( $theme_json ) { - $theme_json_data = $theme_json->get_data(); - $shared_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); - $variations_data = wp_resolve_block_style_variations( $shared_variations ); - - return wp_merge_block_style_variations_data( $variations_data, $theme_json, 'user' ); -} - -/** - * Merges block style variation data sourced from standalone partial - * theme.json files. - * - * @since 6.6.0 - * @access private - * - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * - * @return WP_Theme_JSON_Data - */ -function wp_resolve_block_style_variations_from_theme_json_partials( $theme_json ) { - $block_style_variations = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); - $variations_data = wp_resolve_block_style_variations( $block_style_variations ); - - return wp_merge_block_style_variations_data( $variations_data, $theme_json ); -} - -/** - * Merges shared block style variations registered within the - * `styles.blocks.variations` property of the primary theme.json file. - * - * @since 6.6.0 - * @access private - * - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * - * @return WP_Theme_JSON_Data - */ -function wp_resolve_block_style_variations_from_primary_theme_json( $theme_json ) { - $theme_json_data = $theme_json->get_data(); - $block_style_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); - $variations_data = wp_resolve_block_style_variations( $block_style_variations ); - - return wp_merge_block_style_variations_data( $variations_data, $theme_json ); -} - -/** - * Merges block style variations registered via the block styles registry with a - * style object, under their appropriate block types within theme.json styles. - * Any variation values defined within the theme.json specific to a block type - * will take precedence over these shared definitions. - * - * @since 6.6.0 - * @access private - * - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * - * @return WP_Theme_JSON_Data - */ -function wp_resolve_block_style_variations_from_styles_registry( $theme_json ) { - $registry = WP_Block_Styles_Registry::get_instance(); - $styles = $registry->get_all_registered(); - $variations_data = array(); - - foreach ( $styles as $block_type => $variations ) { - foreach ( $variations as $variation_name => $variation ) { - if ( ! empty( $variation['style_data'] ) ) { - $path = array( $block_type, 'variations', $variation_name ); - _wp_array_set( $variations_data, $path, $variation['style_data'] ); - } - } - } - - return wp_merge_block_style_variations_data( $variations_data, $theme_json ); -} - /** * Enqueues styles for block style variations. * @@ -398,53 +254,30 @@ function wp_enqueue_block_style_variation_styles() { add_filter( 'render_block', 'wp_render_block_style_variation_class_name', 10, 2 ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles', 1 ); -// Resolve block style variations from all their potential sources. The order here is deliberate. -add_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_primary_theme_json', 10, 1 ); -add_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_theme_json_partials', 10, 1 ); -add_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_styles_registry', 10, 1 ); - -add_filter( 'wp_theme_json_data_user', 'wp_resolve_block_style_variations_from_theme_style_variation', 10, 1 ); - /** - * Registers any block style variations contained within the provided - * theme.json data. + * Registers block style variations read in from theme.json partials. * * @since 6.6.0 * @access private * * @param array $variations Shared block style variations. */ -function wp_register_block_style_variations_from_theme_json_data( $variations ) { +function wp_register_block_style_variations_from_theme_json_partials( $variations ) { if ( empty( $variations ) ) { - return $variations; + return; } - $registry = WP_Block_Styles_Registry::get_instance(); - $have_named_variations = ! wp_is_numeric_array( $variations ); - - foreach ( $variations as $key => $variation ) { - $supported_blocks = $variation['blockTypes'] ?? array(); + $registry = WP_Block_Styles_Registry::get_instance(); - /* - * Standalone theme.json partial files for block style variations - * will have their styles under a top-level property by the same name. - * Variations defined within an existing theme.json or theme style - * variation will themselves already be the required styles data. - */ - $variation_data = $variation['styles'] ?? $variation; - - if ( empty( $variation_data ) ) { + foreach ( $variations as $variation ) { + if ( empty( $variation['blockTypes'] ) || empty( $variation['styles'] ) ) { continue; } - /* - * Block style variations read in via standalone theme.json partials - * need to have their name set to the kebab case version of their title. - */ - $variation_name = $have_named_variations ? $key : ( $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ) ); + $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ); $variation_label = $variation['title'] ?? $variation_name; - foreach ( $supported_blocks as $block_type ) { + foreach ( $variation['blockTypes'] as $block_type ) { $registered_styles = $registry->get_registered_styles_for_block( $block_type ); // Register block style variation if it hasn't already been registered. @@ -460,41 +293,3 @@ function wp_register_block_style_variations_from_theme_json_data( $variations ) } } } - -/** - * Register shared block style variations defined by the theme. - * - * These can come in three forms: - * - the theme's theme.json - * - the theme's partials (standalone files in `/styles` that only define block style variations) - * - the user's theme.json (for example, theme style variations the user selected) - * - * @since 6.6.0 - * @access private - */ -function wp_register_block_style_variations_from_theme() { - // Partials from `/styles`. - $variations_partials = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); - wp_register_block_style_variations_from_theme_json_data( $variations_partials ); - - /* - * Pull the data from the specific origin instead of the merged data. - * This is because, for 6.6, we only support registering block style variations - * for the 'theme' and 'custom' origins but not for 'default' (core theme.json) - * or 'custom' (theme.json in a block). - * - * When/If we add support for every origin, we should switch to using the public API - * instead, e.g.: wp_get_global_styles( array( 'blocks', 'variations' ) ). - */ - - // theme.json of the theme. - $theme_json_theme = WP_Theme_JSON_Resolver::get_theme_data(); - $variations_theme = $theme_json_theme->get_data()['styles']['blocks']['variations'] ?? array(); - wp_register_block_style_variations_from_theme_json_data( $variations_theme ); - - // User data linked for this theme. - $theme_json_user = WP_Theme_JSON_Resolver::get_user_data(); - $variations_user = $theme_json_user->get_data()['styles']['blocks']['variations'] ?? array(); - wp_register_block_style_variations_from_theme_json_data( $variations_user ); -} -add_action( 'init', 'wp_register_block_style_variations_from_theme' ); diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php index e1894cdb72743..d3718930d5062 100644 --- a/src/wp-includes/block-supports/layout.php +++ b/src/wp-includes/block-supports/layout.php @@ -322,14 +322,22 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false * They're added separately because padding might only be set on one side. */ if ( isset( $block_spacing_values['declarations']['padding-right'] ) ) { - $padding_right = $block_spacing_values['declarations']['padding-right']; + $padding_right = $block_spacing_values['declarations']['padding-right']; + // Add unit if 0. + if ( '0' === $padding_right ) { + $padding_right = '0px'; + } $layout_styles[] = array( 'selector' => "$selector > .alignfull", 'declarations' => array( 'margin-right' => "calc($padding_right * -1)" ), ); } if ( isset( $block_spacing_values['declarations']['padding-left'] ) ) { - $padding_left = $block_spacing_values['declarations']['padding-left']; + $padding_left = $block_spacing_values['declarations']['padding-left']; + // Add unit if 0. + if ( '0' === $padding_left ) { + $padding_left = '0px'; + } $layout_styles[] = array( 'selector' => "$selector > .alignfull", 'declarations' => array( 'margin-left' => "calc($padding_left * -1)" ), @@ -951,6 +959,7 @@ function wp_add_parent_layout_to_parsed_block( $parsed_block, $source_block, $pa * to avoid breaking styles relying on that div. * * @since 5.8.0 + * @since 6.6.1 Removed inner container from Grid variations. * @access private * * @param string $block_content Rendered block content. @@ -967,7 +976,7 @@ function wp_restore_group_inner_container( $block_content, $block ) { if ( wp_theme_has_theme_json() || 1 === preg_match( $group_with_inner_container_regex, $block_content ) || - ( isset( $block['attrs']['layout']['type'] ) && 'flex' === $block['attrs']['layout']['type'] ) + ( isset( $block['attrs']['layout']['type'] ) && ( 'flex' === $block['attrs']['layout']['type'] || 'grid' === $block['attrs']['layout']['type'] ) ) ) { return $block_content; } diff --git a/src/wp-includes/block-supports/typography.php b/src/wp-includes/block-supports/typography.php index 334c01962d6cc..0770400e0000d 100644 --- a/src/wp-includes/block-supports/typography.php +++ b/src/wp-includes/block-supports/typography.php @@ -518,6 +518,7 @@ function wp_get_computed_fluid_typography_value( $args = array() ) { * @since 6.3.0 Using layout.wideSize as max viewport width, and logarithmic scale factor to calculate minimum font scale. * @since 6.4.0 Added configurable min and max viewport width values to the typography.fluid theme.json schema. * @since 6.6.0 Deprecated bool argument $should_use_fluid_typography. + * @since 6.7.0 Font size presets can enable fluid typography individually, even if it’s disabled globally. * * @param array $preset { * Required. fontSizes preset value as seen in theme.json. @@ -538,10 +539,11 @@ function wp_get_typography_font_size_value( $preset, $settings = array() ) { } /* - * Catches empty values and 0/'0'. - * Fluid calculations cannot be performed on 0. + * Catches falsy values and 0/'0'. Fluid calculations cannot be performed on `0`. + * Also returns early when a preset font size explicitly disables fluid typography with `false`. */ - if ( empty( $preset['size'] ) ) { + $fluid_font_size_settings = $preset['fluid'] ?? null; + if ( false === $fluid_font_size_settings || empty( $preset['size'] ) ) { return $preset['size']; } @@ -564,15 +566,20 @@ function wp_get_typography_font_size_value( $preset, $settings = array() ) { $global_settings ); - $typography_settings = isset( $settings['typography'] ) ? $settings['typography'] : array(); - $should_use_fluid_typography = ! empty( $typography_settings['fluid'] ); + $typography_settings = $settings['typography'] ?? array(); - if ( ! $should_use_fluid_typography ) { + /* + * Return early when fluid typography is disabled in the settings, and there + * are no local settings to enable it for the individual preset. + * + * If this condition isn't met, either the settings or individual preset settings + * have enabled fluid typography. + */ + if ( empty( $typography_settings['fluid'] ) && empty( $fluid_font_size_settings ) ) { return $preset['size']; } - // $typography_settings['fluid'] can be a bool or an array. Normalize to array. - $fluid_settings = is_array( $typography_settings['fluid'] ) ? $typography_settings['fluid'] : array(); + $fluid_settings = isset( $typography_settings['fluid'] ) ? $typography_settings['fluid'] : array(); $layout_settings = isset( $settings['layout'] ) ? $settings['layout'] : array(); // Defaults. @@ -592,14 +599,6 @@ function wp_get_typography_font_size_value( $preset, $settings = array() ) { $has_min_font_size = isset( $fluid_settings['minFontSize'] ) && ! empty( wp_get_typography_value_and_unit( $fluid_settings['minFontSize'] ) ); $minimum_font_size_limit = $has_min_font_size ? $fluid_settings['minFontSize'] : $default_minimum_font_size_limit; - // Font sizes. - $fluid_font_size_settings = isset( $preset['fluid'] ) ? $preset['fluid'] : null; - - // A font size has explicitly bypassed fluid calculations. - if ( false === $fluid_font_size_settings ) { - return $preset['size']; - } - // Try to grab explicit min and max fluid font sizes. $minimum_font_size_raw = isset( $fluid_font_size_settings['min'] ) ? $fluid_font_size_settings['min'] : null; $maximum_font_size_raw = isset( $fluid_font_size_settings['max'] ) ? $fluid_font_size_settings['max'] : null; diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 2681acb5686b3..3c6850bd3292a 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -355,7 +355,7 @@ function _get_block_template_file( $template_type, $slug ) { * @type string $post_type Post type to get the templates for. * } * - * @return array Template + * @return array|null Template files on success, null if `$template_type` is not matched. */ function _get_block_templates_files( $template_type, $query = array() ) { if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) { @@ -606,15 +606,35 @@ function _build_block_template_result_from_file( $template_file, $template_type $template->area = $template_file['area']; } + $hooked_blocks = get_hooked_blocks(); + $has_hooked_blocks = ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ); $before_block_visitor = '_inject_theme_attribute_in_template_part_block'; $after_block_visitor = null; - $hooked_blocks = get_hooked_blocks(); - if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { + + if ( $has_hooked_blocks ) { $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' ); $after_block_visitor = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' ); } - $blocks = parse_blocks( $template->content ); - $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + + if ( 'wp_template_part' === $template->type && $has_hooked_blocks ) { + /* + * In order for hooked blocks to be inserted at positions first_child and last_child in a template part, + * we need to wrap its content a mock template part block and traverse it. + */ + $content = get_comment_delimited_block_content( + 'core/template-part', + array(), + $template->content + ); + $content = traverse_and_serialize_blocks( parse_blocks( $content ), $before_block_visitor, $after_block_visitor ); + $template->content = remove_serialized_parent_block( $content ); + } else { + $template->content = traverse_and_serialize_blocks( + parse_blocks( $template->content ), + $before_block_visitor, + $after_block_visitor + ); + } return $template; } @@ -998,8 +1018,28 @@ function _build_block_template_result_from_post( $post ) { if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' ); $after_block_visitor = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' ); - $blocks = parse_blocks( $template->content ); - $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + if ( 'wp_template_part' === $template->type ) { + $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); + $attributes = ! empty( $existing_ignored_hooked_blocks ) ? array( 'metadata' => array( 'ignoredHookedBlocks' => json_decode( $existing_ignored_hooked_blocks, true ) ) ) : array(); + + /* + * In order for hooked blocks to be inserted at positions first_child and last_child in a template part, + * we need to wrap its content a mock template part block and traverse it. + */ + $content = get_comment_delimited_block_content( + 'core/template-part', + $attributes, + $template->content + ); + $content = traverse_and_serialize_blocks( parse_blocks( $content ), $before_block_visitor, $after_block_visitor ); + $template->content = remove_serialized_parent_block( $content ); + } else { + $template->content = traverse_and_serialize_blocks( + parse_blocks( $template->content ), + $before_block_visitor, + $after_block_visitor + ); + } } return $template; @@ -1562,6 +1602,10 @@ function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated _deprecated_argument( __FUNCTION__, '6.5.3' ); } + if ( ! isset( $changes->post_content ) ) { + return $changes; + } + $hooked_blocks = get_hooked_blocks(); if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) { return $changes; @@ -1611,7 +1655,36 @@ function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated return $template; } - $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' ); + if ( 'wp_template_part' === $post->post_type ) { + $attributes = array(); + $existing_ignored_hooked_blocks = isset( $post->ID ) ? get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ) : ''; + + if ( ! empty( $existing_ignored_hooked_blocks ) ) { + $attributes['metadata'] = array( + 'ignoredHookedBlocks' => json_decode( $existing_ignored_hooked_blocks, true ), + ); + } + + $content = get_comment_delimited_block_content( + 'core/template-part', + $attributes, + $changes->post_content + ); + $content = apply_block_hooks_to_content( $content, $template, 'set_ignored_hooked_blocks_metadata' ); + $changes->post_content = remove_serialized_parent_block( $content ); + + $wrapper_block_markup = extract_serialized_parent_block( $content ); + $wrapper_block = parse_blocks( $wrapper_block_markup )[0]; + $ignored_hooked_blocks = $wrapper_block['attrs']['metadata']['ignoredHookedBlocks'] ?? array(); + if ( ! empty( $ignored_hooked_blocks ) ) { + if ( ! isset( $changes->meta_input ) ) { + $changes->meta_input = array(); + } + $changes->meta_input['_wp_ignored_hooked_blocks'] = wp_json_encode( $ignored_hooked_blocks ); + } + } else { + $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' ); + } return $changes; } diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index decc4896c378a..9b24b989d4024 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -385,6 +385,7 @@ function get_block_metadata_i18n_schema() { * @since 6.3.0 Added `selectors` field. * @since 6.4.0 Added support for `blockHooks` field. * @since 6.5.0 Added support for `allowedBlocks`, `viewScriptModule`, and `viewStyle` fields. + * @since 6.7.0 Allow PHP filename as `variations` argument. * * @param string $file_or_folder Path to the JSON file with metadata definition for * the block or path to the folder where the `block.json` file is located. @@ -522,6 +523,34 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { } } + // If `variations` is a string, it's the name of a PHP file that + // generates the variations. + if ( ! empty( $metadata['variations'] ) && is_string( $metadata['variations'] ) ) { + $variations_path = wp_normalize_path( + realpath( + dirname( $metadata['file'] ) . '/' . + remove_block_asset_path_prefix( $metadata['variations'] ) + ) + ); + if ( $variations_path ) { + /** + * Generates the list of block variations. + * + * @since 6.7.0 + * + * @return string Returns the list of block variations. + */ + $settings['variation_callback'] = static function () use ( $variations_path ) { + $variations = require $variations_path; + return $variations; + }; + // The block instance's `variations` field is only allowed to be an array + // (of known block variations). We unset it so that the block instance will + // provide a getter that returns the result of the `variation_callback` instead. + unset( $settings['variations'] ); + } + } + $settings = array_merge( $settings, $args ); $script_fields = array( @@ -1045,6 +1074,23 @@ function remove_serialized_parent_block( $serialized_block ) { return substr( $serialized_block, $start, $end - $start ); } +/** + * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the wrapper block. + * + * @since 6.7.0 + * @access private + * + * @see remove_serialized_parent_block() + * + * @param string $serialized_block The serialized markup of a block and its inner blocks. + * @return string The serialized markup of the wrapper block. + */ +function extract_serialized_parent_block( $serialized_block ) { + $start = strpos( $serialized_block, '-->' ) + strlen( '-->' ); + $end = strrpos( $serialized_block, '\n"; + $ie_conditional_prefix = "\n"; } $before_script = $this->get_inline_script_tag( $handle, 'before' ); $after_script = $this->get_inline_script_tag( $handle, 'after' ); if ( $before_script || $after_script ) { - $inline_script_tag = $cond_before . $before_script . $after_script . $cond_after; + $inline_script_tag = $ie_conditional_prefix . $before_script . $after_script . $ie_conditional_suffix; } else { $inline_script_tag = ''; } @@ -353,10 +353,10 @@ public function do_item( $handle, $group = false ) { * @param string $src Script loader source path. * @param string $handle Script handle. */ - $srce = apply_filters( 'script_loader_src', $src, $handle ); + $filtered_src = apply_filters( 'script_loader_src', $src, $handle ); if ( - $this->in_default_dir( $srce ) + $this->in_default_dir( $filtered_src ) && ( $before_script || $after_script || $translations_stop_concat || $this->is_delayed_strategy( $strategy ) ) ) { $this->do_concat = false; @@ -364,7 +364,7 @@ public function do_item( $handle, $group = false ) { // Have to print the so-far concatenated scripts right away to maintain the right order. _print_scripts(); $this->reset(); - } elseif ( $this->in_default_dir( $srce ) && ! $conditional ) { + } elseif ( $this->in_default_dir( $filtered_src ) && ! $conditional ) { $this->print_code .= $this->print_extra_script( $handle, false ); $this->concat .= "$handle,"; $this->concat_version .= "$handle$ver"; @@ -378,13 +378,13 @@ public function do_item( $handle, $group = false ) { $has_conditional_data = $conditional && $this->get_data( $handle, 'data' ); if ( $has_conditional_data ) { - echo $cond_before; + echo $ie_conditional_prefix; } $this->print_extra_script( $handle ); if ( $has_conditional_data ) { - echo $cond_after; + echo $ie_conditional_suffix; } // A single item may alias a set of items, by having dependencies, but no source. @@ -425,9 +425,9 @@ public function do_item( $handle, $group = false ) { if ( $intended_strategy ) { $attr['data-wp-strategy'] = $intended_strategy; } - $tag = $translations . $cond_before . $before_script; + $tag = $translations . $ie_conditional_prefix . $before_script; $tag .= wp_get_script_tag( $attr ); - $tag .= $after_script . $cond_after; + $tag .= $after_script . $ie_conditional_suffix; /** * Filters the HTML script tag of an enqueued script. @@ -626,16 +626,16 @@ public function localize( $handle, $object_name, $l10n ) { */ public function set_group( $handle, $recursion, $group = false ) { if ( isset( $this->registered[ $handle ]->args ) && 1 === $this->registered[ $handle ]->args ) { - $grp = 1; + $calculated_group = 1; } else { - $grp = (int) $this->get_data( $handle, 'group' ); + $calculated_group = (int) $this->get_data( $handle, 'group' ); } - if ( false !== $group && $grp > $group ) { - $grp = $group; + if ( false !== $group && $calculated_group > $group ) { + $calculated_group = $group; } - return parent::set_group( $handle, $recursion, $grp ); + return parent::set_group( $handle, $recursion, $calculated_group ); } /** @@ -723,7 +723,7 @@ public function print_translations( $handle, $display = true ) { * @return bool True on success, false on failure. */ public function all_deps( $handles, $recursion = false, $group = false ) { - $r = parent::all_deps( $handles, $recursion, $group ); + $result = parent::all_deps( $handles, $recursion, $group ); if ( ! $recursion ) { /** * Filters the list of script dependencies left to print. @@ -734,7 +734,7 @@ public function all_deps( $handles, $recursion = false, $group = false ) { */ $this->to_do = apply_filters( 'print_scripts_array', $this->to_do ); } - return $r; + return $result; } /** @@ -889,10 +889,10 @@ private function is_delayed_strategy( $strategy ) { * @return string The best eligible loading strategy. */ private function get_eligible_loading_strategy( $handle ) { - $intended = (string) $this->get_data( $handle, 'strategy' ); + $intended_strategy = (string) $this->get_data( $handle, 'strategy' ); // Bail early if there is no intended strategy. - if ( ! $intended ) { + if ( ! $intended_strategy ) { return ''; } @@ -900,16 +900,16 @@ private function get_eligible_loading_strategy( $handle ) { * If the intended strategy is 'defer', limit the initial list of eligible * strategies, since 'async' can fallback to 'defer', but not vice-versa. */ - $initial = ( 'defer' === $intended ) ? array( 'defer' ) : null; + $initial_strategy = ( 'defer' === $intended_strategy ) ? array( 'defer' ) : null; - $eligible = $this->filter_eligible_strategies( $handle, $initial ); + $eligible_strategies = $this->filter_eligible_strategies( $handle, $initial_strategy ); // Return early once we know the eligible strategy is blocking. - if ( empty( $eligible ) ) { + if ( empty( $eligible_strategies ) ) { return ''; } - return in_array( 'async', $eligible, true ) ? 'async' : 'defer'; + return in_array( 'async', $eligible_strategies, true ) ? 'async' : 'defer'; } /** @@ -917,20 +917,20 @@ private function get_eligible_loading_strategy( $handle ) { * * @since 6.3.0 * - * @param string $handle The script handle. - * @param string[]|null $eligible Optional. The list of strategies to filter. Default null. - * @param array $checked Optional. An array of already checked script handles, used to avoid recursive loops. + * @param string $handle The script handle. + * @param string[]|null $eligible_strategies Optional. The list of strategies to filter. Default null. + * @param array $checked Optional. An array of already checked script handles, used to avoid recursive loops. * @return string[] A list of eligible loading strategies that could be used. */ - private function filter_eligible_strategies( $handle, $eligible = null, $checked = array() ) { + private function filter_eligible_strategies( $handle, $eligible_strategies = null, $checked = array() ) { // If no strategies are being passed, all strategies are eligible. - if ( null === $eligible ) { - $eligible = $this->delayed_strategies; + if ( null === $eligible_strategies ) { + $eligible_strategies = $this->delayed_strategies; } // If this handle was already checked, return early. if ( isset( $checked[ $handle ] ) ) { - return $eligible; + return $eligible_strategies; } // Mark this handle as checked. @@ -938,12 +938,12 @@ private function filter_eligible_strategies( $handle, $eligible = null, $checked // If this handle isn't registered, don't filter anything and return. if ( ! isset( $this->registered[ $handle ] ) ) { - return $eligible; + return $eligible_strategies; } // If the handle is not enqueued, don't filter anything and return. if ( ! $this->query( $handle, 'enqueued' ) ) { - return $eligible; + return $eligible_strategies; } $is_alias = (bool) ! $this->registered[ $handle ]->src; @@ -961,7 +961,7 @@ private function filter_eligible_strategies( $handle, $eligible = null, $checked // If the intended strategy is 'defer', filter out 'async'. if ( 'defer' === $intended_strategy ) { - $eligible = array( 'defer' ); + $eligible_strategies = array( 'defer' ); } $dependents = $this->get_dependents( $handle ); @@ -969,14 +969,14 @@ private function filter_eligible_strategies( $handle, $eligible = null, $checked // Recursively filter eligible strategies for dependents. foreach ( $dependents as $dependent ) { // Bail early once we know the eligible strategy is blocking. - if ( empty( $eligible ) ) { + if ( empty( $eligible_strategies ) ) { return array(); } - $eligible = $this->filter_eligible_strategies( $dependent, $eligible, $checked ); + $eligible_strategies = $this->filter_eligible_strategies( $dependent, $eligible_strategies, $checked ); } - return $eligible; + return $eligible_strategies; } /** diff --git a/src/wp-includes/class-wp-site-query.php b/src/wp-includes/class-wp-site-query.php index fb3a6d22fe0f2..8c097fc17e7fc 100644 --- a/src/wp-includes/class-wp-site-query.php +++ b/src/wp-includes/class-wp-site-query.php @@ -660,7 +660,16 @@ protected function get_site_ids() { * * @since 4.6.0 * - * @param string[] $clauses An associative array of site query clauses. + * @param string[] $clauses { + * Associative array of the clauses for the query. + * + * @type string $fields The SELECT clause of the query. + * @type string $join The JOIN clause of the query. + * @type string $where The WHERE clause of the query. + * @type string $orderby The ORDER BY clause of the query. + * @type string $limits The LIMIT clause of the query. + * @type string $groupby The GROUP BY clause of the query. + * } * @param WP_Site_Query $query Current instance of WP_Site_Query (passed by reference). */ $clauses = apply_filters_ref_array( 'sites_clauses', array( compact( $pieces ), &$this ) ); diff --git a/src/wp-includes/class-wp-styles.php b/src/wp-includes/class-wp-styles.php index 76883b54ca98a..e64378be5fc8d 100644 --- a/src/wp-includes/class-wp-styles.php +++ b/src/wp-includes/class-wp-styles.php @@ -165,14 +165,14 @@ public function do_item( $handle, $group = false ) { $ver = $ver ? $ver . '&' . $this->args[ $handle ] : $this->args[ $handle ]; } - $src = $obj->src; - $cond_before = ''; - $cond_after = ''; - $conditional = isset( $obj->extra['conditional'] ) ? $obj->extra['conditional'] : ''; + $src = $obj->src; + $ie_conditional_prefix = ''; + $ie_conditional_suffix = ''; + $conditional = isset( $obj->extra['conditional'] ) ? $obj->extra['conditional'] : ''; if ( $conditional ) { - $cond_before = "\n"; + $ie_conditional_prefix = "\n"; } $inline_style = $this->print_inline_style( $handle, false ); @@ -279,17 +279,17 @@ public function do_item( $handle, $group = false ) { } if ( $this->do_concat ) { - $this->print_html .= $cond_before; + $this->print_html .= $ie_conditional_prefix; $this->print_html .= $tag; if ( $inline_style_tag ) { $this->print_html .= $inline_style_tag; } - $this->print_html .= $cond_after; + $this->print_html .= $ie_conditional_suffix; } else { - echo $cond_before; + echo $ie_conditional_prefix; echo $tag; $this->print_inline_style( $handle ); - echo $cond_after; + echo $ie_conditional_suffix; } return true; @@ -368,7 +368,7 @@ public function print_inline_style( $handle, $display = true ) { * @return bool True on success, false on failure. */ public function all_deps( $handles, $recursion = false, $group = false ) { - $r = parent::all_deps( $handles, $recursion, $group ); + $result = parent::all_deps( $handles, $recursion, $group ); if ( ! $recursion ) { /** * Filters the array of enqueued styles before processing for output. @@ -379,7 +379,7 @@ public function all_deps( $handles, $recursion = false, $group = false ) { */ $this->to_do = apply_filters( 'print_styles_array', $this->to_do ); } - return $r; + return $result; } /** diff --git a/src/wp-includes/class-wp-textdomain-registry.php b/src/wp-includes/class-wp-textdomain-registry.php index 7037b2bc076fa..e5aeb82e5cda5 100644 --- a/src/wp-includes/class-wp-textdomain-registry.php +++ b/src/wp-includes/class-wp-textdomain-registry.php @@ -182,8 +182,8 @@ public function get_language_files_from_path( $path ) { * @since 6.5.0 * * @param null|array $files List of translation files. Default null. - * @param string $path The path from which translation files are being fetched. - **/ + * @param string $path The path from which translation files are being fetched. + */ $files = apply_filters( 'pre_get_language_files_from_path', null, $path ); if ( null !== $files ) { diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index fca687cfb9f1b..d63a84353cd46 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -172,8 +172,18 @@ public static function get_core_data() { * * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ - $theme_json = apply_filters( 'wp_theme_json_data_default', new WP_Theme_JSON_Data( $config, 'default' ) ); - static::$core = $theme_json->get_theme_json(); + $theme_json = apply_filters( 'wp_theme_json_data_default', new WP_Theme_JSON_Data( $config, 'default' ) ); + + /* + * Backward compatibility for extenders returning a WP_Theme_JSON_Data + * compatible class that is not a WP_Theme_JSON_Data object. + */ + if ( $theme_json instanceof WP_Theme_JSON_Data ) { + static::$core = $theme_json->get_theme_json(); + } else { + $config = $theme_json->get_data(); + static::$core = new WP_Theme_JSON( $config, 'default' ); + } return static::$core; } @@ -221,6 +231,7 @@ protected static function has_same_registered_blocks( $origin ) { * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. * @since 6.6.0 Add support for 'default-font-sizes' and 'default-spacing-sizes' theme supports. + * Added registration and merging of block style variations from partial theme.json files and the block styles registry. * * @param array $deprecated Deprecated. Not used. * @param array $options { @@ -247,6 +258,30 @@ public static function get_theme_data( $deprecated = array(), $options = array() $theme_json_data = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); } + /* + * Register variations defined by theme partials (theme.json files in the styles directory). + * This is required so the variations pass sanitization of theme.json data. + */ + $variations = static::get_style_variations( 'block' ); + wp_register_block_style_variations_from_theme_json_partials( $variations ); + + /* + * Source variations from the block registry and block style variation files. Then, merge them into the existing theme.json data. + * + * In case the same style properties are defined in several sources, this is how we should resolve the values, + * from higher to lower priority: + * + * - styles.blocks.blockType.variations from theme.json + * - styles.variations from theme.json + * - variations from block style variation files + * - variations from block styles registry + * + * See test_add_registered_block_styles_to_theme_data and test_unwraps_block_style_variations. + * + */ + $theme_json_data = static::inject_variations_from_block_style_variation_files( $theme_json_data, $variations ); + $theme_json_data = static::inject_variations_from_block_styles_registry( $theme_json_data ); + /** * Filters the data provided by the theme for global styles and settings. * @@ -254,8 +289,18 @@ public static function get_theme_data( $deprecated = array(), $options = array() * * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ - $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $theme_json_data, 'theme' ) ); - static::$theme = $theme_json->get_theme_json(); + $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $theme_json_data, 'theme' ) ); + + /* + * Backward compatibility for extenders returning a WP_Theme_JSON_Data + * compatible class that is not a WP_Theme_JSON_Data object. + */ + if ( $theme_json instanceof WP_Theme_JSON_Data ) { + static::$theme = $theme_json->get_theme_json(); + } else { + $config = $theme_json->get_data(); + static::$theme = new WP_Theme_JSON( $config ); + } if ( $wp_theme->parent() ) { // Get parent theme.json. @@ -377,8 +422,19 @@ public static function get_block_data() { * * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ - $theme_json = apply_filters( 'wp_theme_json_data_blocks', new WP_Theme_JSON_Data( $config, 'blocks' ) ); - static::$blocks = $theme_json->get_theme_json(); + $theme_json = apply_filters( 'wp_theme_json_data_blocks', new WP_Theme_JSON_Data( $config, 'blocks' ) ); + + /* + * Backward compatibility for extenders returning a WP_Theme_JSON_Data + * compatible class that is not a WP_Theme_JSON_Data object. + */ + if ( $theme_json instanceof WP_Theme_JSON_Data ) { + static::$blocks = $theme_json->get_theme_json(); + } else { + $config = $theme_json->get_data(); + static::$blocks = new WP_Theme_JSON( $config, 'blocks' ); + } + return static::$blocks; } @@ -488,6 +544,7 @@ public static function get_user_data_from_wp_global_styles( $theme, $create_post * * @since 5.9.0 * @since 6.6.0 The 'isGlobalStylesUserThemeJSON' flag is left on the user data. + * Register the block style variations coming from the user data. * * @return WP_Theme_JSON Entity that holds styles for user data. */ @@ -513,7 +570,17 @@ public static function get_user_data() { * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); - return $theme_json->get_theme_json(); + + /* + * Backward compatibility for extenders returning a WP_Theme_JSON_Data + * compatible class that is not a WP_Theme_JSON_Data object. + */ + if ( $theme_json instanceof WP_Theme_JSON_Data ) { + return $theme_json->get_theme_json(); + } else { + $config = $theme_json->get_data(); + return new WP_Theme_JSON( $config, 'custom' ); + } } /* @@ -531,8 +598,18 @@ public static function get_user_data() { } /** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */ - $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); - static::$user = $theme_json->get_theme_json(); + $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); + + /* + * Backward compatibility for extenders returning a WP_Theme_JSON_Data + * compatible class that is not a WP_Theme_JSON_Data object. + */ + if ( $theme_json instanceof WP_Theme_JSON_Data ) { + static::$user = $theme_json->get_theme_json(); + } else { + $config = $theme_json->get_data(); + static::$user = new WP_Theme_JSON( $config, 'custom' ); + } return static::$user; } @@ -725,6 +802,7 @@ private static function style_variation_has_scope( $variation, $scope ) { * @since 6.6.0 Added configurable scope parameter to allow filtering * theme.json partial files by the scope to which they * can be applied e.g. theme vs block etc. + * Added basic caching for read theme.json partial files. * * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc. * @return array @@ -751,7 +829,7 @@ public static function get_style_variations( $scope = 'theme' ) { } ksort( $variation_files ); foreach ( $variation_files as $path => $file ) { - $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); + $decoded_file = self::read_json_file( $path ); if ( is_array( $decoded_file ) && static::style_variation_has_scope( $decoded_file, $scope ) ) { $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); $variation = ( new WP_Theme_JSON( $translated ) )->get_raw_data(); @@ -770,6 +848,7 @@ public static function get_style_variations( $scope = 'theme' ) { * as the value of `_link` object in REST API responses. * * @since 6.6.0 + * @since 6.7.0 Resolve relative paths in block styles. * * @param WP_Theme_JSON $theme_json A theme json instance. * @return array An array of resolved paths. @@ -782,15 +861,14 @@ public static function get_resolved_theme_uris( $theme_json ) { } $theme_json_data = $theme_json->get_raw_data(); - - // Top level styles. - $background_image_url = isset( $theme_json_data['styles']['background']['backgroundImage']['url'] ) ? $theme_json_data['styles']['background']['backgroundImage']['url'] : null; - /* * The same file convention when registering web fonts. * See: WP_Font_Face_Resolver::to_theme_file_uri. */ $placeholder = 'file:./'; + + // Top level styles. + $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; if ( isset( $background_image_url ) && is_string( $background_image_url ) && @@ -810,6 +888,33 @@ public static function get_resolved_theme_uris( $theme_json ) { $resolved_theme_uris[] = $resolved_theme_uri; } + // Block styles. + if ( ! empty( $theme_json_data['styles']['blocks'] ) ) { + foreach ( $theme_json_data['styles']['blocks'] as $block_name => $block_styles ) { + if ( ! isset( $block_styles['background']['backgroundImage']['url'] ) ) { + continue; + } + $background_image_url = $block_styles['background']['backgroundImage']['url']; + if ( + is_string( $background_image_url ) && + // Skip if the src doesn't start with the placeholder, as there's nothing to replace. + str_starts_with( $background_image_url, $placeholder ) + ) { + $file_type = wp_check_filetype( $background_image_url ); + $src_url = str_replace( $placeholder, '', $background_image_url ); + $resolved_theme_uri = array( + 'name' => $background_image_url, + 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), + 'target' => "styles.blocks.{$block_name}.background.backgroundImage.url", + ); + if ( isset( $file_type['type'] ) ) { + $resolved_theme_uri['type'] = $file_type['type']; + } + $resolved_theme_uris[] = $resolved_theme_uri; + } + } + } + return $resolved_theme_uris; } @@ -819,7 +924,7 @@ public static function get_resolved_theme_uris( $theme_json ) { * * @since 6.6.0 * - * @param WP_Theme_JSON $theme_json A theme json instance. + * @param WP_Theme_JSON $theme_json A theme json instance. * @return WP_Theme_JSON Theme merged with resolved paths, if any found. */ public static function resolve_theme_file_uris( $theme_json ) { @@ -841,4 +946,84 @@ public static function resolve_theme_file_uris( $theme_json ) { return $theme_json; } + + /** + * Adds variations sourced from block style variations files to the supplied theme.json data. + * + * @since 6.6.0 + * + * @param array $data Array following the theme.json specification. + * @param array $variations Shared block style variations. + * @return array Theme json data including shared block style variation definitions. + */ + private static function inject_variations_from_block_style_variation_files( $data, $variations ) { + if ( empty( $variations ) ) { + return $data; + } + + foreach ( $variations as $variation ) { + if ( empty( $variation['styles'] ) || empty( $variation['blockTypes'] ) ) { + continue; + } + + $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ); + + foreach ( $variation['blockTypes'] as $block_type ) { + // First, override partial styles with any top-level styles. + $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); + if ( ! empty( $top_level_data ) ) { + $variation['styles'] = array_replace_recursive( $variation['styles'], $top_level_data ); + } + + // Then, override styles so far with any block-level styles. + $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); + if ( ! empty( $block_level_data ) ) { + $variation['styles'] = array_replace_recursive( $variation['styles'], $block_level_data ); + } + + $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); + _wp_array_set( $data, $path, $variation['styles'] ); + } + } + + return $data; + } + + /** + * Adds variations sourced from the block styles registry to the supplied theme.json data. + * + * @since 6.6.0 + * + * @param array $data Array following the theme.json specification. + * @return array Theme json data including shared block style variation definitions. + */ + private static function inject_variations_from_block_styles_registry( $data ) { + $registry = WP_Block_Styles_Registry::get_instance(); + $styles = $registry->get_all_registered(); + + foreach ( $styles as $block_type => $variations ) { + foreach ( $variations as $variation_name => $variation ) { + if ( empty( $variation['style_data'] ) ) { + continue; + } + + // First, override registry styles with any top-level styles. + $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); + if ( ! empty( $top_level_data ) ) { + $variation['style_data'] = array_replace_recursive( $variation['style_data'], $top_level_data ); + } + + // Then, override styles so far with any block-level styles. + $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); + if ( ! empty( $block_level_data ) ) { + $variation['style_data'] = array_replace_recursive( $variation['style_data'], $block_level_data ); + } + + $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); + _wp_array_set( $data, $path, $variation['style_data'] ); + } + } + + return $data; + } } diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 05f5e7b9e7629..cbe266bfad0cc 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -226,6 +226,7 @@ class WP_Theme_JSON { * @since 6.4.0 Added `writing-mode` property. * @since 6.5.0 Added `aspect-ratio` property. * @since 6.6.0 Added `background-[image|position|repeat|size]` properties. + * @since 6.7.0 Added `background-attachment` property. * * @var array */ @@ -237,6 +238,7 @@ class WP_Theme_JSON { 'background-position' => array( 'background', 'backgroundPosition' ), 'background-repeat' => array( 'background', 'backgroundRepeat' ), 'background-size' => array( 'background', 'backgroundSize' ), + 'background-attachment' => array( 'background', 'backgroundAttachment' ), 'border-radius' => array( 'border', 'radius' ), 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), @@ -520,10 +522,11 @@ class WP_Theme_JSON { */ const VALID_STYLES = array( 'background' => array( - 'backgroundImage' => 'top', - 'backgroundPosition' => 'top', - 'backgroundRepeat' => 'top', - 'backgroundSize' => 'top', + 'backgroundImage' => null, + 'backgroundPosition' => null, + 'backgroundRepeat' => null, + 'backgroundSize' => null, + 'backgroundAttachment' => null, ), 'border' => array( 'color' => null, @@ -743,14 +746,14 @@ public static function get_element_class_name( $element ) { * Constructor. * * @since 5.8.0 - * @since 6.6.0 Key spacingScale by origin, and Pre-generate the - * spacingSizes from spacingScale. + * @since 6.6.0 Key spacingScale by origin, and Pre-generate the spacingSizes from spacingScale. + * Added unwrapping of shared block style variations into block type variations if registered. * * @param array $theme_json A structure that follows the theme.json schema. * @param string $origin Optional. What source of data this object represents. * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. */ - public function __construct( $theme_json = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ), $origin = 'theme' ) { + public function __construct( $theme_json = array( 'version' => self::LATEST_SCHEMA ), $origin = 'theme' ) { if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { $origin = 'theme'; } @@ -759,6 +762,7 @@ public function __construct( $theme_json = array( 'version' => WP_Theme_JSON::LA $valid_block_names = array_keys( static::get_blocks_metadata() ); $valid_element_names = array_keys( static::ELEMENTS ); $valid_variations = static::get_valid_block_style_variations(); + $this->theme_json = static::unwrap_shared_block_style_variations( $this->theme_json, $valid_variations ); $this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); $this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json ); @@ -802,6 +806,73 @@ public function __construct( $theme_json = array( 'version' => WP_Theme_JSON::LA } } + /** + * Unwraps shared block style variations. + * + * It takes the shared variations (styles.variations.variationName) and + * applies them to all the blocks that have the given variation registered + * (styles.blocks.blockType.variations.variationName). + * + * For example, given the `core/paragraph` and `core/group` blocks have + * registered the `section-a` style variation, and given the following input: + * + * { + * "styles": { + * "variations": { + * "section-a": { "color": { "background": "backgroundColor" } } + * } + * } + * } + * + * It returns the following output: + * + * { + * "styles": { + * "blocks": { + * "core/paragraph": { + * "variations": { + * "section-a": { "color": { "background": "backgroundColor" } } + * }, + * }, + * "core/group": { + * "variations": { + * "section-a": { "color": { "background": "backgroundColor" } } + * } + * } + * } + * } + * } + * + * @since 6.6.0 + * + * @param array $theme_json A structure that follows the theme.json schema. + * @param array $valid_variations Valid block style variations. + * @return array Theme json data with shared variation definitions unwrapped under appropriate block types. + */ + private static function unwrap_shared_block_style_variations( $theme_json, $valid_variations ) { + if ( empty( $theme_json['styles']['variations'] ) || empty( $valid_variations ) ) { + return $theme_json; + } + + $new_theme_json = $theme_json; + $variations = $new_theme_json['styles']['variations']; + + foreach ( $valid_variations as $block_type => $registered_variations ) { + foreach ( $registered_variations as $variation_name ) { + $block_level_data = $new_theme_json['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); + $top_level_data = $variations[ $variation_name ] ?? array(); + $merged_data = array_replace_recursive( $top_level_data, $block_level_data ); + if ( ! empty( $merged_data ) ) { + _wp_array_set( $new_theme_json, array( 'styles', 'blocks', $block_type, 'variations', $variation_name ), $merged_data ); + } + } + } + + unset( $new_theme_json['styles']['variations'] ); + + return $new_theme_json; + } + /** * Enables some opt-in settings if theme declared support. * @@ -967,12 +1038,6 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema['settings']['blocks'] = $schema_settings_blocks; $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA ); - /* - * Shared block style variations can be registered from the theme.json data so we can't - * validate them against pre-registered block style variations. - */ - $schema['styles']['blocks']['variations'] = null; - // Remove anything that's not present in the schema. foreach ( array( 'styles', 'settings' ) as $subtree ) { if ( ! isset( $input[ $subtree ] ) ) { @@ -1247,7 +1312,8 @@ public function get_settings() { * @since 5.8.0 * @since 5.9.0 Removed the `$type` parameter, added the `$types` and `$origins` parameters. * @since 6.3.0 Add fallback layout styles for Post Template when block gap support isn't available. - * @since 6.6.0 Added `skip_root_layout_styles` option to omit layout styles if desired. + * @since 6.6.0 Added boolean `skip_root_layout_styles` and `include_block_style_variations` options + * to control styles output as desired. * * @param string[] $types Types of styles to load. Will load all by default. It accepts: * - `variables`: only the CSS Custom Properties for presets & custom ones. @@ -1257,9 +1323,10 @@ public function get_settings() { * @param array $options { * Optional. An array of options for now used for internal purposes only (may change without notice). * - * @type string $scope Makes sure all style are scoped to a given selector - * @type string $root_selector Overwrites and forces a given selector to be used on the root node - * @type bool $skip_root_layout_styles Omits root layout styles from the generated stylesheet. Default false. + * @type string $scope Makes sure all style are scoped to a given selector + * @type string $root_selector Overwrites and forces a given selector to be used on the root node + * @type bool $skip_root_layout_styles Omits root layout styles from the generated stylesheet. Default false. + * @type bool $include_block_style_variations Includes styles for block style variations in the generated stylesheet. Default false. * } * @return string The resulting stylesheet. */ @@ -1281,7 +1348,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' } $blocks_metadata = static::get_blocks_metadata(); - $style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata ); + $style_nodes = static::get_style_nodes( $this->theme_json, $blocks_metadata, $options ); $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata ); $root_style_key = array_search( static::ROOT_BLOCK_SELECTOR, array_column( $style_nodes, 'selector' ), true ); @@ -1359,6 +1426,12 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); } + // Load the custom CSS last so it has the highest specificity. + if ( in_array( 'custom-css', $types, true ) ) { + // Add the global styles root CSS. + $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) ); + } + return $stylesheet; } @@ -1375,9 +1448,16 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' protected function process_blocks_custom_css( $css, $selector ) { $processed_css = ''; + if ( empty( $css ) ) { + return $processed_css; + } + // Split CSS nested rules. $parts = explode( '&', $css ); foreach ( $parts as $part ) { + if ( empty( $part ) ) { + continue; + } $is_root_css = ( ! str_contains( $part, '{' ) ); if ( $is_root_css ) { // If the part doesn't contain braces, it applies to the root level. @@ -1390,11 +1470,25 @@ protected function process_blocks_custom_css( $css, $selector ) { } $nested_selector = $part[0]; $css_value = $part[1]; - $part_selector = str_starts_with( $nested_selector, ' ' ) + + /* + * Handle pseudo elements such as ::before, ::after etc. Regex will also + * capture any leading combinator such as >, +, or ~, as well as spaces. + * This allows pseudo elements as descendants e.g. `.parent ::before`. + */ + $matches = array(); + $has_pseudo_element = preg_match( '/([>+~\s]*::[a-zA-Z-]+)/', $nested_selector, $matches ); + $pseudo_part = $has_pseudo_element ? $matches[1] : ''; + $nested_selector = $has_pseudo_element ? str_replace( $pseudo_part, '', $nested_selector ) : $nested_selector; + + // Finalize selector and re-append pseudo element if required. + $part_selector = str_starts_with( $nested_selector, ' ' ) ? static::scope_selector( $selector, $nested_selector ) : static::append_to_selector( $selector, $nested_selector ); - $final_selector = ":root :where($part_selector)"; - $processed_css .= $final_selector . '{' . trim( $css_value ) . '}';} + $final_selector = ":root :where($part_selector)$pseudo_part"; + + $processed_css .= $final_selector . '{' . trim( $css_value ) . '}'; + } } return $processed_css; } @@ -1403,10 +1497,12 @@ protected function process_blocks_custom_css( $css, $selector ) { * Returns the global styles custom CSS. * * @since 6.2.0 + * @deprecated 6.7.0 Use {@see 'get_stylesheet'} instead. * * @return string The global styles custom CSS. */ public function get_custom_css() { + _deprecated_function( __METHOD__, '6.7.0', 'get_stylesheet' ); // Add the global styles root CSS. $stylesheet = isset( $this->theme_json['styles']['css'] ) ? $this->theme_json['styles']['css'] : ''; @@ -1617,7 +1713,7 @@ protected function get_layout_styles( $block_metadata, $types = array() ) { $spacing_rule['selector'] ); } else { - $format = static::ROOT_BLOCK_SELECTOR === $selector ? '.%2$s %3$s' : '%1$s-%2$s %3$s'; + $format = static::ROOT_BLOCK_SELECTOR === $selector ? ':root :where(.%2$s)%3$s' : ':root :where(%1$s-%2$s)%3$s'; $layout_selector = sprintf( $format, $selector, @@ -2199,7 +2295,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { * * array( * 'name' => 'property_name', - * 'value' => 'property_value, + * 'value' => 'property_value', * ) * * @since 5.8.0 @@ -2207,6 +2303,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { * @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters. * @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set. * @since 6.6.0 Pass current theme JSON settings to wp_get_typography_font_size_value(), and process background properties. + * @since 6.7.0 `ref` resolution of background properties, and assigning custom default values. * * @param array $styles Styles to process. * @param array $settings Theme settings. @@ -2260,10 +2357,27 @@ protected static function compute_style_properties( $styles, $settings = array() } } - // Processes background styles. - if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) { - $background_styles = wp_style_engine_get_styles( array( 'background' => $styles['background'] ) ); - $value = isset( $background_styles['declarations'][ $css_property ] ) ? $background_styles['declarations'][ $css_property ] : $value; + /* + * Processes background image styles. + * If the value is a URL, it will be converted to a CSS `url()` value. + * For uploaded image (images with a database ID), apply size and position defaults, + * equal to those applied in block supports in lib/background.php. + */ + if ( 'background-image' === $css_property && ! empty( $value ) ) { + $background_styles = wp_style_engine_get_styles( + array( 'background' => array( 'backgroundImage' => $value ) ) + ); + $value = $background_styles['declarations'][ $css_property ]; + } + if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) { + if ( 'background-size' === $css_property ) { + $value = 'cover'; + } + // If the background size is set to `contain` and no position is set, set the position to `center`. + if ( 'background-position' === $css_property ) { + $background_size = $styles['background']['backgroundSize'] ?? null; + $value = 'contain' === $background_size ? '50% 50%' : null; + } } // Skip if empty and not "0" or value represents array of longhand values. @@ -2325,6 +2439,7 @@ protected static function compute_style_properties( $styles, $settings = array() * to the standard form "--wp--preset--color--secondary". * This is already done by the sanitize method, * so every property will be in the standard form. + * @since 6.7.0 Added support for background image refs. * * @param array $styles Styles subtree. * @param array $path Which property to process. @@ -2341,14 +2456,18 @@ protected static function get_property_value( $styles, $path, $theme_json = null /* * This converts references to a path to the value at that path - * where the values is an array with a "ref" key, pointing to a path. + * where the value is an array with a "ref" key, pointing to a path. * For example: { "ref": "style.color.background" } => "#fff". + * In the case of backgroundImage, if both a ref and a URL are present in the value, + * the URL takes precedence and the ref is ignored. */ if ( is_array( $value ) && isset( $value['ref'] ) ) { $value_path = explode( '.', $value['ref'] ); $ref_value = _wp_array_get( $theme_json, $value_path ); + // Background Image refs can refer to a string or an array containing a URL string. + $ref_value_url = $ref_value['url'] ?? null; // Only use the ref value if we find anything. - if ( ! empty( $ref_value ) && is_string( $ref_value ) ) { + if ( ! empty( $ref_value ) && ( is_string( $ref_value ) || is_string( $ref_value_url ) ) ) { $value = $ref_value; } @@ -2446,12 +2565,18 @@ protected static function get_setting_nodes( $theme_json, $selectors = array() ) * ] * * @since 5.8.0 + * @since 6.6.0 Added options array for modifying generated nodes. * * @param array $theme_json The tree to extract style nodes from. * @param array $selectors List of selectors per block. + * @param array $options { + * Optional. An array of options for now used for internal purposes only (may change without notice). + * + * @type bool $include_block_style_variations Includes style nodes for block style variations. Default false. + * } * @return array An array of style nodes metadata. */ - protected static function get_style_nodes( $theme_json, $selectors = array() ) { + protected static function get_style_nodes( $theme_json, $selectors = array(), $options = array() ) { $nodes = array(); if ( ! isset( $theme_json['styles'] ) ) { return $nodes; @@ -2493,7 +2618,7 @@ protected static function get_style_nodes( $theme_json, $selectors = array() ) { return $nodes; } - $block_nodes = static::get_block_nodes( $theme_json ); + $block_nodes = static::get_block_nodes( $theme_json, $selectors, $options ); foreach ( $block_nodes as $block_node ) { $nodes[] = $block_node; } @@ -2564,12 +2689,19 @@ private static function update_separator_declarations( $declarations ) { * * @since 6.1.0 * @since 6.3.0 Refactored and stabilized selectors API. + * @since 6.6.0 Added optional selectors and options for generating block nodes. * * @param array $theme_json The theme.json converted to an array. + * @param array $selectors Optional list of selectors per block. + * @param array $options { + * Optional. An array of options for now used for internal purposes only (may change without notice). + * + * @type bool $include_block_style_variations Includes nodes for block style variations. Default false. + * } * @return array The block nodes in theme.json. */ - private static function get_block_nodes( $theme_json ) { - $selectors = static::get_blocks_metadata(); + private static function get_block_nodes( $theme_json, $selectors = array(), $options = array() ) { + $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors; $nodes = array(); if ( ! isset( $theme_json['styles'] ) ) { return $nodes; @@ -2597,7 +2729,8 @@ private static function get_block_nodes( $theme_json ) { } $variation_selectors = array(); - if ( isset( $node['variations'] ) ) { + $include_variations = $options['include_block_style_variations'] ?? false; + if ( $include_variations && isset( $node['variations'] ) ) { foreach ( $node['variations'] as $variation => $node ) { $variation_selectors[] = array( 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), @@ -2614,6 +2747,7 @@ private static function get_block_nodes( $theme_json ) { 'duotone' => $duotone_selector, 'features' => $feature_selectors, 'variations' => $variation_selectors, + 'css' => $selector, ); if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { @@ -2647,6 +2781,7 @@ private static function get_block_nodes( $theme_json ) { * @since 6.1.0 * @since 6.6.0 Setting a min-height of HTML when root styles have a background gradient or image. * Updated general global styles specificity to 0-1-0. + * Fixed custom CSS output in block style variations. * * @param array $block_metadata Metadata about the block to get styles for. * @@ -2662,6 +2797,7 @@ public function get_styles_for_block( $block_metadata ) { // If there are style variations, generate the declarations for them, including any feature selectors the block may have. $style_variation_declarations = array(); + $style_variation_custom_css = array(); if ( ! empty( $block_metadata['variations'] ) ) { foreach ( $block_metadata['variations'] as $style_variation ) { $style_variation_node = _wp_array_get( $this->theme_json, $style_variation['path'], array() ); @@ -2691,6 +2827,10 @@ static function ( $split_selector ) use ( $clean_style_variation_selector ) { // Compute declarations for remaining styles not covered by feature level selectors. $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); + // Store custom CSS for the style variation. + if ( isset( $style_variation_node['css'] ) ) { + $style_variation_custom_css[ $style_variation['selector'] ] = $this->process_blocks_custom_css( $style_variation_node['css'], $style_variation['selector'] ); + } } } /* @@ -2795,8 +2935,28 @@ static function ( $pseudo_selector ) use ( $selector ) { $declarations = static::update_separator_declarations( $declarations ); } + /* + * Root selector (body) styles should not be wrapped in `:root where()` to keep + * specificity at (0,0,1) and maintain backwards compatibility. + * + * Top-level element styles using element-only specificity selectors should + * not get wrapped in `:root :where()` to maintain backwards compatibility. + * + * Pseudo classes, e.g. :hover, :focus etc., are a class-level selector so + * still need to be wrapped in `:root :where` to cap specificity for nested + * variations etc. Pseudo selectors won't match the ELEMENTS selector exactly. + */ + $element_only_selector = $is_root_selector || ( + $current_element && + isset( static::ELEMENTS[ $current_element ] ) && + // buttons, captions etc. still need `:root :where()` as they are class based selectors. + ! isset( static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $current_element ] ) && + static::ELEMENTS[ $current_element ] === $selector + ); + // 2. Generate and append the rules that use the general selector. - $block_rules .= static::to_ruleset( ":root :where($selector)", $declarations ); + $general_selector = $element_only_selector ? $selector : ":root :where($selector)"; + $block_rules .= static::to_ruleset( $general_selector, $declarations ); // 3. Generate and append the rules that use the duotone selector. if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { @@ -2819,6 +2979,14 @@ static function ( $pseudo_selector ) use ( $selector ) { // 6. Generate and append the style variation rulesets. foreach ( $style_variation_declarations as $style_variation_selector => $individual_style_variation_declarations ) { $block_rules .= static::to_ruleset( ":root :where($style_variation_selector)", $individual_style_variation_declarations ); + if ( isset( $style_variation_custom_css[ $style_variation_selector ] ) ) { + $block_rules .= $style_variation_custom_css[ $style_variation_selector ]; + } + } + + // 7. Generate and append any custom CSS rules. + if ( isset( $node['css'] ) && ! $is_root_selector ) { + $block_rules .= $this->process_blocks_custom_css( $node['css'], $selector ); } return $block_rules; @@ -2870,10 +3038,10 @@ public function get_root_layout_rules( $selector, $block_metadata ) { $css .= '.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }'; // Alignfull children of the container with left and right padding have negative margins so they can still be full width. $css .= '.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }'; - // Nested children of the container with left and right padding that are not wide or full aligned do not get padding. - $css .= '.has-global-padding :where(.has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) { padding-right: 0; padding-left: 0; }'; - // Nested children of the container with left and right padding that are not wide or full aligned do not get negative margin applied. - $css .= '.has-global-padding :where(.has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) > .alignfull { margin-left: 0; margin-right: 0; }'; + // Nested children of the container with left and right padding that are not full aligned do not get padding, unless they are direct children of an alignfull flow container. + $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }'; + // Alignfull direct children of the containers that are targeted by the rule above do not need negative margins. + $css .= '.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; }'; } $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; @@ -2938,6 +3106,7 @@ protected static function get_metadata_boolean( $data, $path, $default_value = f * * @since 5.8.0 * @since 5.9.0 Duotone preset also has origins. + * @since 6.7.0 Replace background image objects during merge. * * @param WP_Theme_JSON $incoming Data to merge. */ @@ -3061,6 +3230,25 @@ public function merge( $incoming ) { } } } + + /* + * Style values are merged at the leaf level, however + * some values provide exceptions, namely style values that are + * objects and represent unique definitions for the style. + */ + $style_nodes = static::get_styles_block_nodes(); + foreach ( $style_nodes as $style_node ) { + $path = $style_node['path']; + /* + * Background image styles should be replaced, not merged, + * as they themselves are specific object definitions for the style. + */ + $background_image_path = array_merge( $path, static::PROPERTIES_METADATA['background-image'] ); + $content = _wp_array_get( $incoming_data, $background_image_path, null ); + if ( isset( $content ) ) { + _wp_array_set( $this->theme_json, $background_image_path, $content ); + } + } } /** @@ -3266,7 +3454,8 @@ public static function remove_insecure_properties( $theme_json, $origin = 'theme $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations ); $blocks_metadata = static::get_blocks_metadata(); - $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata ); + $style_options = array( 'include_block_style_variations' => true ); // Allow variations data. + $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata, $style_options ); foreach ( $style_nodes as $metadata ) { $input = _wp_array_get( $theme_json, $metadata['path'], array() ); diff --git a/src/wp-includes/class-wp-token-map.php b/src/wp-includes/class-wp-token-map.php index a932f4cae4d27..09a0b9303b452 100644 --- a/src/wp-includes/class-wp-token-map.php +++ b/src/wp-includes/class-wp-token-map.php @@ -280,7 +280,7 @@ class WP_Token_Map { * * @return WP_Token_Map|null Token map, unless unable to create it. */ - public static function from_array( $mappings, $key_length = 2 ) { + public static function from_array( array $mappings, int $key_length = 2 ): ?WP_Token_Map { $map = new WP_Token_Map(); $map->key_length = $key_length; @@ -328,7 +328,7 @@ public static function from_array( $mappings, $key_length = 2 ) { foreach ( $groups as $group_key => $group ) { usort( $groups[ $group_key ], - static function ( $a, $b ) { + static function ( array $a, array $b ): int { return self::longest_first_then_alphabetical( $a[0], $b[0] ); } ); @@ -385,7 +385,7 @@ static function ( $a, $b ) { * * @return WP_Token_Map Map with precomputed data loaded. */ - public static function from_precomputed_table( $state ) { + public static function from_precomputed_table( $state ): ?WP_Token_Map { $has_necessary_state = isset( $state['storage_version'], $state['key_length'], @@ -439,7 +439,7 @@ public static function from_precomputed_table( $state ) { * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. * @return bool Whether there's an entry for the given word in the map. */ - public function contains( $word, $case_sensitivity = 'case-sensitive' ) { + public function contains( string $word, string $case_sensitivity = 'case-sensitive' ): bool { $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; if ( $this->key_length >= strlen( $word ) ) { @@ -520,13 +520,14 @@ public function contains( $word, $case_sensitivity = 'case-sensitive' ) { * * @since 6.6.0 * - * @param string $text String in which to search for a lookup key. - * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. - * @param ?int &$matched_token_byte_length Optional. Holds byte-length of found token matched, otherwise not set. Default null. - * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. + * @param string $text String in which to search for a lookup key. + * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. + * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found token matched, otherwise not set. Default null. + * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. + * * @return string|null Mapped value of lookup key if found, otherwise `null`. */ - public function read_token( $text, $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ) { + public function read_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; $text_length = strlen( $text ); @@ -570,15 +571,16 @@ public function read_token( $text, $offset = 0, &$matched_token_byte_length = nu /** * Finds a match for a short word at the index. * - * @since 6.6.0. + * @since 6.6.0 + * + * @param string $text String in which to search for a lookup key. + * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. + * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found lookup key if matched, otherwise not set. Default null. + * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. * - * @param string $text String in which to search for a lookup key. - * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. - * @param ?int &$matched_token_byte_length Optional. Holds byte-length of found lookup key if matched, otherwise not set. Default null. - * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. * @return string|null Mapped value of lookup key if found, otherwise `null`. */ - private function read_small_token( $text, $offset, &$matched_token_byte_length, $case_sensitivity = 'case-sensitive' ) { + private function read_small_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { $ignore_case = 'ascii-case-insensitive' === $case_sensitivity; $small_length = strlen( $this->small_words ); $search_text = substr( $text, $offset, $this->key_length ); @@ -633,7 +635,7 @@ private function read_small_token( $text, $offset, &$matched_token_byte_length, * * @return array The lookup key/substitution values as an associate array. */ - public function to_array() { + public function to_array(): array { $tokens = array(); $at = 0; @@ -695,7 +697,7 @@ public function to_array() { * @param string $indent Optional. Use this string for indentation, or rely on the default horizontal tab character. Default "\t". * @return string Value which can be pasted into a PHP source file for quick loading of table. */ - public function precomputed_php_source_table( $indent = "\t" ) { + public function precomputed_php_source_table( string $indent = "\t" ): string { $i1 = $indent; $i2 = $i1 . $indent; $i3 = $i2 . $indent; @@ -800,7 +802,7 @@ static function ( $match_result ) { * @param string $b Second string to compare. * @return int -1 or lower if `$a` is less than `$b`; 1 or greater if `$a` is greater than `$b`, and 0 if they are equal. */ - private static function longest_first_then_alphabetical( $a, $b ) { + private static function longest_first_then_alphabetical( string $a, string $b ): int { if ( $a === $b ) { return 0; } diff --git a/src/wp-includes/class-wp-walker.php b/src/wp-includes/class-wp-walker.php index ff5eac2f50040..df67921c2746c 100644 --- a/src/wp-includes/class-wp-walker.php +++ b/src/wp-includes/class-wp-walker.php @@ -135,6 +135,9 @@ public function display_element( $element, &$children_elements, $max_depth, $dep return; } + $max_depth = (int) $max_depth; + $depth = (int) $depth; + $id_field = $this->db_fields['id']; $id = $element->$id_field; @@ -191,6 +194,8 @@ public function display_element( $element, &$children_elements, $max_depth, $dep public function walk( $elements, $max_depth, ...$args ) { $output = ''; + $max_depth = (int) $max_depth; + // Invalid parameter or nothing to walk. if ( $max_depth < -1 || empty( $elements ) ) { return $output; @@ -285,12 +290,14 @@ public function walk( $elements, $max_depth, ...$args ) { * @return string XHTML of the specified page of elements. */ public function paged_walk( $elements, $max_depth, $page_num, $per_page, ...$args ) { + $output = ''; + + $max_depth = (int) $max_depth; + if ( empty( $elements ) || $max_depth < -1 ) { - return ''; + return $output; } - $output = ''; - $parent_field = $this->db_fields['parent']; $count = -1; diff --git a/src/wp-includes/comment-template.php b/src/wp-includes/comment-template.php index 8c560973339b8..ccc57ed8ee085 100644 --- a/src/wp-includes/comment-template.php +++ b/src/wp-includes/comment-template.php @@ -24,7 +24,13 @@ function get_comment_author( $comment_id = 0 ) { $comment = get_comment( $comment_id ); - $comment_id = ! empty( $comment->comment_ID ) ? $comment->comment_ID : (string) $comment_id; + if ( ! empty( $comment->comment_ID ) ) { + $comment_id = $comment->comment_ID; + } elseif ( is_scalar( $comment_id ) ) { + $comment_id = (string) $comment_id; + } else { + $comment_id = '0'; + } if ( empty( $comment->comment_author ) ) { $user = ! empty( $comment->user_id ) ? get_userdata( $comment->user_id ) : false; @@ -227,7 +233,13 @@ function get_comment_author_email_link( $link_text = '', $before = '', $after = function get_comment_author_link( $comment_id = 0 ) { $comment = get_comment( $comment_id ); - $comment_id = ! empty( $comment->comment_ID ) ? $comment->comment_ID : (string) $comment_id; + if ( ! empty( $comment->comment_ID ) ) { + $comment_id = $comment->comment_ID; + } elseif ( is_scalar( $comment_id ) ) { + $comment_id = (string) $comment_id; + } else { + $comment_id = '0'; + } $comment_author_url = get_comment_author_url( $comment ); $comment_author = get_comment_author( $comment ); diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 748dd6161b222..e4cc901df943c 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -479,7 +479,8 @@ function delete_comment_meta( $comment_id, $meta_key, $meta_value = '' ) { * @return mixed An array of values if `$single` is false. * The value of meta data field if `$single` is true. * False for an invalid `$comment_id` (non-numeric, zero, or negative value). - * An empty string if a valid but non-existing comment ID is passed. + * An empty array if a valid but non-existing comment ID is passed and `$single` is false. + * An empty string if a valid but non-existing comment ID is passed and `$single` is true. */ function get_comment_meta( $comment_id, $key = '', $single = false ) { return get_metadata( 'comment', $comment_id, $key, $single ); diff --git a/src/wp-includes/compat.php b/src/wp-includes/compat.php index c50fc69a047e1..030a288d823ca 100644 --- a/src/wp-includes/compat.php +++ b/src/wp-includes/compat.php @@ -40,6 +40,43 @@ function _wp_can_use_pcre_u( $set = null ) { return $utf8_pcre; } +/** + * Indicates if a given slug for a character set represents the UTF-8 text encoding. + * + * A charset is considered to represent UTF-8 if it is a case-insensitive match + * of "UTF-8" with or without the hyphen. + * + * Example: + * + * true === _is_utf8_charset( 'UTF-8' ); + * true === _is_utf8_charset( 'utf8' ); + * false === _is_utf8_charset( 'latin1' ); + * false === _is_utf8_charset( 'UTF 8' ); + * + * // Only strings match. + * false === _is_utf8_charset( [ 'charset' => 'utf-8' ] ); + * + * `is_utf8_charset` should be used outside of this file. + * + * @ignore + * @since 6.6.1 + * + * @param string $charset_slug Slug representing a text character encoding, or "charset". + * E.g. "UTF-8", "Windows-1252", "ISO-8859-1", "SJIS". + * + * @return bool Whether the slug represents the UTF-8 encoding. + */ +function _is_utf8_charset( $charset_slug ) { + if ( ! is_string( $charset_slug ) ) { + return false; + } + + return ( + 0 === strcasecmp( 'UTF-8', $charset_slug ) || + 0 === strcasecmp( 'UTF8', $charset_slug ) + ); +} + if ( ! function_exists( 'mb_substr' ) ) : /** * Compat function to mimic mb_substr(). @@ -91,7 +128,7 @@ function _mb_substr( $str, $start, $length = null, $encoding = null ) { * The solution below works only for UTF-8, so in case of a different * charset just use built-in substr(). */ - if ( ! is_utf8_charset( $encoding ) ) { + if ( ! _is_utf8_charset( $encoding ) ) { return is_null( $length ) ? substr( $str, $start ) : substr( $str, $start, $length ); } @@ -176,7 +213,7 @@ function _mb_strlen( $str, $encoding = null ) { * The solution below works only for UTF-8, so in case of a different charset * just use built-in strlen(). */ - if ( ! is_utf8_charset( $encoding ) ) { + if ( ! _is_utf8_charset( $encoding ) ) { return strlen( $str ); } @@ -512,3 +549,8 @@ function str_ends_with( $haystack, $needle ) { if ( ! defined( 'IMG_AVIF' ) ) { define( 'IMG_AVIF', IMAGETYPE_AVIF ); } + +// IMAGETYPE_HEIC constant is not yet defined in PHP as of PHP 8.3. +if ( ! defined( 'IMAGETYPE_HEIC' ) ) { + define( 'IMAGETYPE_HEIC', 99 ); +} diff --git a/src/wp-includes/css/admin-bar.css b/src/wp-includes/css/admin-bar.css index 07699977f9bd3..c4e8ba3afaa6e 100644 --- a/src/wp-includes/css/admin-bar.css +++ b/src/wp-includes/css/admin-bar.css @@ -701,7 +701,6 @@ html:lang(he-il) .rtl #wpadminbar * { #wpadminbar .screen-reader-text span { border: 0; clip: rect(1px, 1px, 1px, 1px); - -webkit-clip-path: inset(50%); clip-path: inset(50%); height: 1px; margin: -1px; @@ -795,7 +794,6 @@ html:lang(he-il) .rtl #wpadminbar * { #wpadminbar .ab-label { border: 0; clip: rect(1px, 1px, 1px, 1px); - -webkit-clip-path: inset(50%); clip-path: inset(50%); height: 1px; margin: -1px; diff --git a/src/wp-includes/css/media-views.css b/src/wp-includes/css/media-views.css index de700d61e4079..81f6611e7f2ee 100644 --- a/src/wp-includes/css/media-views.css +++ b/src/wp-includes/css/media-views.css @@ -709,6 +709,7 @@ color: #043959; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; + z-index: 1; } .media-router .active, @@ -2488,7 +2489,6 @@ /* Visually hide the menu heading keeping it available to assistive technologies. */ .media-frame-menu-heading { clip: rect(1px, 1px, 1px, 1px); - -webkit-clip-path: inset(50%); clip-path: inset(50%); height: 1px; overflow: hidden; @@ -2532,7 +2532,8 @@ width: 230px; } - .options-general-php .crop-content.site-icon { + .options-general-php .crop-content.site-icon, + .wp-customizer:not(.mobile) .media-frame-content .crop-content.site-icon { margin-right: 262px; } @@ -2832,7 +2833,8 @@ position: fixed; } - .options-general-php .crop-content.site-icon { + .options-general-php .crop-content.site-icon, + .wp-customizer:not(.mobile) .media-frame-content .crop-content.site-icon { margin-right: 0; } diff --git a/src/wp-includes/css/wp-embed-template.css b/src/wp-includes/css/wp-embed-template.css index 63a00632165ea..d6e4a7cf51118 100644 --- a/src/wp-includes/css/wp-embed-template.css +++ b/src/wp-includes/css/wp-embed-template.css @@ -11,7 +11,6 @@ body { .screen-reader-text { border: 0; clip: rect(1px, 1px, 1px, 1px); - -webkit-clip-path: inset(50%); clip-path: inset(50%); height: 1px; margin: -1px; diff --git a/src/wp-includes/customize/class-wp-customize-site-icon-control.php b/src/wp-includes/customize/class-wp-customize-site-icon-control.php index 33694b2a60600..fe05678321cc2 100644 --- a/src/wp-includes/customize/class-wp-customize-site-icon-control.php +++ b/src/wp-includes/customize/class-wp-customize-site-icon-control.php @@ -54,59 +54,66 @@ public function content_template() { <# if ( data.label ) { #> {{ data.label }} <# } #> - <# if ( data.description ) { #> - {{{ data.description }}} - <# } #> <# if ( data.attachment && data.attachment.id ) { #>
<# if ( data.attachment.sizes ) { #> -
-
- - -
- {{
-									data.attachment.alt ?
-										wp.i18n.sprintf(
-											<?php
-											/* translators: %s: The selected image alt text. */
-											echo wp_json_encode( __( 'Browser icon preview: Current image: %s' ) );
-											?>
-											,
-											data.attachment.alt
-										) :
-										wp.i18n.sprintf(
-											<?php
-											/* translators: %s: The selected image filename. */
-											echo wp_json_encode( __( 'Browser icon preview: The current image has no alternative text. The file name is: %s' ) );
-											?>
-											,
-											data.attachment.filename
-										)
-								}} + +
+
+ {{
+								data.attachment.alt ?
+									wp.i18n.sprintf(
+										<?php
+										/* translators: %s: The selected image alt text. */
+										echo wp_json_encode( __( 'App icon preview: Current image: %s' ) )
+										?>
+										,
+										data.attachment.alt
+									) :
+									wp.i18n.sprintf(
+										<?php
+										/* translators: %s: The selected image filename. */
+										echo wp_json_encode( __( 'App icon preview: The current image has no alternative text. The file name is: %s' ) );
+										?>
+										,
+										data.attachment.filename
+									)
+							}} +
+ +
+ {{
+										data.attachment.alt ?
+											wp.i18n.sprintf(
+												<?php
+												/* translators: %s: The selected image alt text. */
+												echo wp_json_encode( __( 'Browser icon preview: Current image: %s' ) );
+												?>
+												,
+												data.attachment.alt
+											) :
+											wp.i18n.sprintf(
+												<?php
+												/* translators: %s: The selected image filename. */
+												echo wp_json_encode( __( 'Browser icon preview: The current image has no alternative text. The file name is: %s' ) );
+												?>
+												,
+												data.attachment.filename
+											)
+									}} + + +
+
-
- {{
-							data.attachment.alt ?
-								wp.i18n.sprintf(
-									<?php
-									/* translators: %s: The selected image alt text. */
-									echo wp_json_encode( __( 'App icon preview: Current image: %s' ) )
-									?>
-									,
-									data.attachment.alt
-								) :
-								wp.i18n.sprintf(
-									<?php
-									/* translators: %s: The selected image filename. */
-									echo wp_json_encode( __( 'App icon preview: The current image has no alternative text. The file name is: %s' ) );
-									?>
-									,
-									data.attachment.filename
-								)
-						}}
<# } #>
@@ -128,6 +135,9 @@ public function content_template() {
<# } #> + <# if ( data.description ) { #> + {{{ data.description }}} + <# } #> 268435456 /* = 256M */ ) { + } elseif ( -1 === $current_limit_int || $current_limit_int > 256 * MB_IN_BYTES ) { define( 'WP_MAX_MEMORY_LIMIT', $current_limit ); + } elseif ( wp_convert_hr_to_bytes( WP_MEMORY_LIMIT ) > 256 * MB_IN_BYTES ) { + define( 'WP_MAX_MEMORY_LIMIT', WP_MEMORY_LIMIT ); } else { define( 'WP_MAX_MEMORY_LIMIT', '256M' ); } diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 2c182747a5bc0..dfa8cab48cc6f 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -3,6 +3,12 @@ * Sets up the default filters and actions for most * of the WordPress hooks. * + * This file is loaded very early in the bootstrap which + * means many functions are not yet available and site + * information such as if this is multisite is unknown. + * Before using functions besides `add_filter` and + * `add_action`, verify things will work as expected. + * * If you need to remove a default hook, this file will * give you the priority to use for removing the hook. * @@ -600,9 +606,6 @@ add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ); add_action( 'wp_footer', 'wp_enqueue_global_styles', 1 ); -// Global styles custom CSS. -add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles_custom_css' ); - // Block supports, and other styles parsed and stored in the Style Engine. add_action( 'wp_enqueue_scripts', 'wp_enqueue_stored_styles' ); add_action( 'wp_footer', 'wp_enqueue_stored_styles', 1 ); diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 8084cdd96cfac..be92ba6796c18 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -6310,3 +6310,98 @@ function wp_interactivity_process_directives_of_interactive_blocks( array $parse _deprecated_function( __FUNCTION__, '6.6.0' ); return $parsed_block; } + +/** + * Gets the global styles custom CSS from theme.json. + * + * @since 6.2.0 + * @deprecated 6.7.0 Use {@see 'wp_get_global_stylesheet'} instead. + * + * @return string The global styles custom CSS. + */ +function wp_get_global_styles_custom_css() { + _deprecated_function( __FUNCTION__, '6.7.0', 'wp_get_global_stylesheet' ); + if ( ! wp_theme_has_theme_json() ) { + return ''; + } + /* + * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme + * developer's workflow. + */ + $can_use_cached = ! wp_is_development_mode( 'theme' ); + + /* + * By using the 'theme_json' group, this data is marked to be non-persistent across requests. + * @see `wp_cache_add_non_persistent_groups()`. + * + * The rationale for this is to make sure derived data from theme.json + * is always fresh from the potential modifications done via hooks + * that can use dynamic data (modify the stylesheet depending on some option, + * settings depending on user permissions, etc.). + * See some of the existing hooks to modify theme.json behavior: + * @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/ + * + * A different alternative considered was to invalidate the cache upon certain + * events such as options add/update/delete, user meta, etc. + * It was judged not enough, hence this approach. + * @see https://github.com/WordPress/gutenberg/pull/45372 + */ + $cache_key = 'wp_get_global_styles_custom_css'; + $cache_group = 'theme_json'; + if ( $can_use_cached ) { + $cached = wp_cache_get( $cache_key, $cache_group ); + if ( $cached ) { + return $cached; + } + } + + $tree = WP_Theme_JSON_Resolver::get_merged_data(); + $stylesheet = $tree->get_custom_css(); + + if ( $can_use_cached ) { + wp_cache_set( $cache_key, $stylesheet, $cache_group ); + } + + return $stylesheet; +} + +/** + * Enqueues the global styles custom css defined via theme.json. + * + * @since 6.2.0 + * @deprecated 6.7.0 Use {@see 'wp_enqueue_global_styles'} instead. + */ +function wp_enqueue_global_styles_custom_css() { + _deprecated_function( __FUNCTION__, '6.7.0', 'wp_enqueue_global_styles' ); + if ( ! wp_is_block_theme() ) { + return; + } + + // Don't enqueue Customizer's custom CSS separately. + remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); + + $custom_css = wp_get_custom_css(); + $custom_css .= wp_get_global_styles_custom_css(); + + if ( ! empty( $custom_css ) ) { + wp_add_inline_style( 'global-styles', $custom_css ); + } +} + +/** + * Generate block style variation instance name. + * + * @since 6.6.0 + * @deprecated 6.7.0 Use `wp_unique_id( $variation . '--' )` instead. + * + * @access private + * + * @param array $block Block object. + * @param string $variation Slug for the block style variation. + * + * @return string The unique variation name. + */ +function wp_create_block_style_variation_instance_name( $block, $variation ) { + _deprecated_function( __FUNCTION__, '6.7.0', 'wp_unique_id' ); + return $variation . '--' . md5( serialize( $block ) ); +} diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 2068e5f3a70fb..e618536f41a20 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -762,8 +762,8 @@ function wp_replace_in_html_tags( $haystack, $replace_pairs ) { // Optimize when searching for one item. if ( 1 === count( $replace_pairs ) ) { // Extract $needle and $replace. - foreach ( $replace_pairs as $needle => $replace ) { - } + $needle = array_key_first( $replace_pairs ); + $replace = $replace_pairs[ $needle ]; // Loop through delimiters (elements) only. for ( $i = 1, $c = count( $textarr ); $i < $c; $i += 2 ) { @@ -3856,7 +3856,7 @@ function sanitize_email( $email ) { * Determines the difference between two timestamps. * * The difference is returned in a human-readable format such as "1 hour", - * "5 mins", "2 days". + * "5 minutes", "2 days". * * @since 1.5.0 * @since 5.3.0 Added support for showing a difference in seconds. @@ -3884,8 +3884,8 @@ function human_time_diff( $from, $to = 0 ) { if ( $mins <= 1 ) { $mins = 1; } - /* translators: Time difference between two dates, in minutes (min=minute). %s: Number of minutes. */ - $since = sprintf( _n( '%s min', '%s mins', $mins ), $mins ); + /* translators: Time difference between two dates, in minutes. %s: Number of minutes. */ + $since = sprintf( _n( '%s minute', '%s minutes', $mins ), $mins ); } elseif ( $diff < DAY_IN_SECONDS && $diff >= HOUR_IN_SECONDS ) { $hours = round( $diff / HOUR_IN_SECONDS ); if ( $hours <= 1 ) { @@ -4790,12 +4790,13 @@ static function ( $matches ) { * Escapes an HTML tag name. * * @since 2.5.0 + * @since 6.5.5 Allow hyphens in tag names (i.e. custom elements). * * @param string $tag_name * @return string */ function tag_escape( $tag_name ) { - $safe_tag = strtolower( preg_replace( '/[^a-zA-Z0-9_:]/', '', $tag_name ) ); + $safe_tag = strtolower( preg_replace( '/[^a-zA-Z0-9-_:]/', '', $tag_name ) ); /** * Filters a string cleaned and escaped for output as an HTML tag. * @@ -5498,11 +5499,11 @@ function normalize_whitespace( $str ) { } /** - * Properly strips all HTML tags including script and style + * Properly strips all HTML tags including 'script' and 'style'. * * This differs from strip_tags() because it removes the contents of * the `' )` - * will return 'something'. wp_strip_all_tags will return '' + * will return 'something'. wp_strip_all_tags() will return an empty string. * * @since 2.9.0 * diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 9c1090f02c104..6ddd11f0715a1 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -73,7 +73,7 @@ function mysql2date( $format, $date, $translate = true ) { function current_time( $type, $gmt = 0 ) { // Don't use non-GMT timestamp, unless you know the difference and really need to. if ( 'timestamp' === $type || 'U' === $type ) { - return $gmt ? time() : time() + (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); + return $gmt ? time() : time() + (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); } if ( 'mysql' === $type ) { @@ -2257,11 +2257,11 @@ function get_temp_dir() { * @return bool Whether the path is writable. */ function wp_is_writable( $path ) { - if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ) { + if ( 'Windows' === PHP_OS_FAMILY ) { return win_is_writable( $path ); - } else { - return @is_writable( $path ); } + + return @is_writable( $path ); } /** @@ -2706,8 +2706,7 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null ) * when regenerated. If yes, ensure the new file name will be unique and will produce unique sub-sizes. */ if ( $is_image ) { - /** This filter is documented in wp-includes/class-wp-image-editor.php */ - $output_formats = apply_filters( 'image_editor_output_format', array(), $_dir . $filename, $mime_type ); + $output_formats = wp_get_image_editor_output_format( $_dir . $filename, $mime_type ); $alt_types = array(); if ( ! empty( $output_formats[ $mime_type ] ) ) { @@ -3120,6 +3119,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { 'image/tiff' => 'tif', 'image/webp' => 'webp', 'image/avif' => 'avif', + 'image/heic' => 'heic', ) ); @@ -3299,6 +3299,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { * @since 4.7.1 * @since 5.8.0 Added support for WebP images. * @since 6.5.0 Added support for AVIF images. + * @since 6.7.0 Added support for HEIC images. * * @param string $file Full path to the file. * @return string|false The actual mime type or false if the type cannot be determined. @@ -3372,6 +3373,15 @@ function wp_get_image_mime( $file ) { ) { $mime = 'image/avif'; } + + if ( + isset( $magic[1] ) && + isset( $magic[2] ) && + 'ftyp' === hex2bin( $magic[1] ) && + ( 'heic' === hex2bin( $magic[2] ) || 'heif' === hex2bin( $magic[2] ) ) + ) { + $mime = 'image/heic'; + } } catch ( Exception $e ) { $mime = false; } @@ -3862,6 +3872,9 @@ function _default_wp_die_handler( $message, $title = '', $args = array() ) { @@ -6196,6 +6209,11 @@ function validate_file( $file, $allowed_files = array() ) { return 0; } + // Normalize path for Windows servers. + $file = wp_normalize_path( $file ); + // Normalize path for $allowed_files as well so it's an apples to apples comparison. + $allowed_files = array_map( 'wp_normalize_path', $allowed_files ); + // `../` on its own is not allowed: if ( '../' === $file ) { return 1; @@ -7488,26 +7506,17 @@ function get_tag_regex( $tag ) { * $is_utf8 = is_utf8_charset(); * * @since 6.6.0 + * @since 6.6.1 A wrapper for _is_utf8_charset + * + * @see _is_utf8_charset * - * @param ?string $blog_charset Slug representing a text character encoding, or "charset". - * E.g. "UTF-8", "Windows-1252", "ISO-8859-1", "SJIS". + * @param string|null $blog_charset Optional. Slug representing a text character encoding, or "charset". + * E.g. "UTF-8", "Windows-1252", "ISO-8859-1", "SJIS". + * Default value is to infer from "blog_charset" option. * @return bool Whether the slug represents the UTF-8 encoding. */ function is_utf8_charset( $blog_charset = null ) { - $charset_to_examine = $blog_charset ?? get_option( 'blog_charset' ); - - /* - * Only valid string values count: the absence of a charset - * does not imply any charset, let alone UTF-8. - */ - if ( ! is_string( $charset_to_examine ) ) { - return false; - } - - return ( - 0 === strcasecmp( 'UTF-8', $charset_to_examine ) || - 0 === strcasecmp( 'UTF8', $charset_to_examine ) - ); + return _is_utf8_charset( $blog_charset ?? get_option( 'blog_charset' ) ); } /** @@ -7636,8 +7645,10 @@ function wp_validate_boolean( $value ) { * Deletes a file. * * @since 4.2.0 + * @since 6.7.0 A return value was added. * * @param string $file The path to the file to delete. + * @return bool True on success, false on failure. */ function wp_delete_file( $file ) { /** @@ -7648,9 +7659,12 @@ function wp_delete_file( $file ) { * @param string $file Path to the file to delete. */ $delete = apply_filters( 'wp_delete_file', $file ); + if ( ! empty( $delete ) ) { - @unlink( $delete ); + return @unlink( $delete ); } + + return false; } /** @@ -7683,9 +7697,7 @@ function wp_delete_file_from_directory( $file, $directory ) { return false; } - wp_delete_file( $file ); - - return true; + return wp_delete_file( $file ); } /** @@ -8805,18 +8817,43 @@ function clean_dirsize_cache( $path ) { set_transient( 'dirsize_cache', $directory_cache, $expiration ); } +/** + * Returns the current WordPress version. + * + * Returns an unmodified value of `$wp_version`. Some plugins modify the global + * in an attempt to improve security through obscurity. This practice can cause + * errors in WordPress, so the ability to get an unmodified version is needed. + * + * @since 6.7.0 + * + * @return string The current WordPress version. + */ +function wp_get_wp_version() { + require ABSPATH . WPINC . '/version.php'; + + return $wp_version; +} + /** * Checks compatibility with the current WordPress version. * * @since 5.2.0 * - * @global string $wp_version The WordPress version string. + * @global string $_wp_tests_wp_version The WordPress version string. Used only in Core tests. * * @param string $required Minimum required WordPress version. * @return bool True if required version is compatible or empty, false if not. */ function is_wp_version_compatible( $required ) { - global $wp_version; + if ( + defined( 'WP_RUN_CORE_TESTS' ) + && WP_RUN_CORE_TESTS + && isset( $GLOBALS['_wp_tests_wp_version'] ) + ) { + $wp_version = $GLOBALS['_wp_tests_wp_version']; + } else { + $wp_version = wp_get_wp_version(); + } // Strip off any -alpha, -RC, -beta, -src suffixes. list( $version ) = explode( '-', $wp_version ); diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index bf70defd38701..f199000b5cd53 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -3120,6 +3120,15 @@ function feed_links( $args = array() ) { $args = wp_parse_args( $args, $defaults ); + /** + * Filters the feed links arguments. + * + * @since 6.7.0 + * + * @param array $args An array of feed links arguments. + */ + $args = apply_filters( 'feed_links_args', $args ); + /** * Filters whether to display the posts feed link. * @@ -3182,6 +3191,15 @@ function feed_links_extra( $args = array() ) { $args = wp_parse_args( $args, $defaults ); + /** + * Filters the extra feed links arguments. + * + * @since 6.7.0 + * + * @param array $args An array of extra feed links arguments. + */ + $args = apply_filters( 'feed_links_extra_args', $args ); + if ( is_singular() ) { $id = 0; $post = get_post( $id ); diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index b413273a64974..f40d4a5e9863e 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -243,62 +243,11 @@ function wp_get_global_stylesheet( $types = array() ) { return $stylesheet; } -/** - * Gets the global styles custom CSS from theme.json. - * - * @since 6.2.0 - * - * @return string The global styles custom CSS. - */ -function wp_get_global_styles_custom_css() { - if ( ! wp_theme_has_theme_json() ) { - return ''; - } - /* - * Ignore cache when the development mode is set to 'theme', so it doesn't interfere with the theme - * developer's workflow. - */ - $can_use_cached = ! wp_is_development_mode( 'theme' ); - - /* - * By using the 'theme_json' group, this data is marked to be non-persistent across requests. - * @see `wp_cache_add_non_persistent_groups()`. - * - * The rationale for this is to make sure derived data from theme.json - * is always fresh from the potential modifications done via hooks - * that can use dynamic data (modify the stylesheet depending on some option, - * settings depending on user permissions, etc.). - * See some of the existing hooks to modify theme.json behavior: - * @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/ - * - * A different alternative considered was to invalidate the cache upon certain - * events such as options add/update/delete, user meta, etc. - * It was judged not enough, hence this approach. - * @see https://github.com/WordPress/gutenberg/pull/45372 - */ - $cache_key = 'wp_get_global_styles_custom_css'; - $cache_group = 'theme_json'; - if ( $can_use_cached ) { - $cached = wp_cache_get( $cache_key, $cache_group ); - if ( $cached ) { - return $cached; - } - } - - $tree = WP_Theme_JSON_Resolver::get_merged_data(); - $stylesheet = $tree->get_custom_css(); - - if ( $can_use_cached ) { - wp_cache_set( $cache_key, $stylesheet, $cache_group ); - } - - return $stylesheet; -} - /** * Adds global style rules to the inline style for each block. * * @since 6.1.0 + * @since 6.7.0 Resolve relative paths in block styles. * * @global WP_Styles $wp_styles */ @@ -306,45 +255,10 @@ function wp_add_global_styles_for_blocks() { global $wp_styles; $tree = WP_Theme_JSON_Resolver::get_merged_data(); + $tree = WP_Theme_JSON_Resolver::resolve_theme_file_uris( $tree ); $block_nodes = $tree->get_styles_block_nodes(); - - $can_use_cached = ! wp_is_development_mode( 'theme' ); - if ( $can_use_cached ) { - // Hash global settings and block nodes together to optimize performance of key generation. - $hash = md5( - wp_json_encode( - array( - 'global_setting' => wp_get_global_settings(), - 'block_nodes' => $block_nodes, - ) - ) - ); - - $cache_key = "wp_styles_for_blocks:$hash"; - $cached = get_site_transient( $cache_key ); - if ( ! is_array( $cached ) ) { - $cached = array(); - } - } - - $update_cache = false; - foreach ( $block_nodes as $metadata ) { - - if ( $can_use_cached ) { - // Use the block name as the key for cached CSS data. Otherwise, use a hash of the metadata. - $cache_node_key = isset( $metadata['name'] ) ? $metadata['name'] : md5( wp_json_encode( $metadata ) ); - - if ( isset( $cached[ $cache_node_key ] ) ) { - $block_css = $cached[ $cache_node_key ]; - } else { - $block_css = $tree->get_styles_for_block( $metadata ); - $cached[ $cache_node_key ] = $block_css; - $update_cache = true; - } - } else { - $block_css = $tree->get_styles_for_block( $metadata ); - } + $block_css = $tree->get_styles_for_block( $metadata ); if ( ! wp_should_load_separate_core_block_assets() ) { wp_add_inline_style( 'global-styles', $block_css ); @@ -390,10 +304,6 @@ function wp_add_global_styles_for_blocks() { } } } - - if ( $update_cache ) { - set_site_transient( $cache_key, $cached, HOUR_IN_SECONDS ); - } } /** diff --git a/src/wp-includes/html-api/class-wp-html-active-formatting-elements.php b/src/wp-includes/html-api/class-wp-html-active-formatting-elements.php index 9f7fee9076243..2f51482eee052 100644 --- a/src/wp-includes/html-api/class-wp-html-active-formatting-elements.php +++ b/src/wp-includes/html-api/class-wp-html-active-formatting-elements.php @@ -51,7 +51,7 @@ class WP_HTML_Active_Formatting_Elements { * @param WP_HTML_Token $token Look for this node in the stack. * @return bool Whether the referenced node is in the stack of active formatting elements. */ - public function contains_node( $token ) { + public function contains_node( WP_HTML_Token $token ) { foreach ( $this->walk_up() as $item ) { if ( $token->bookmark_name === $item->bookmark_name ) { return true; @@ -86,6 +86,22 @@ public function current_node() { return $current_node ? $current_node : null; } + /** + * Inserts a "marker" at the end of the list of active formatting elements. + * + * > The markers are inserted when entering applet, object, marquee, + * > template, td, th, and caption elements, and are used to prevent + * > formatting from "leaking" into applet, object, marquee, template, + * > td, th, and caption elements. + * + * @see https://html.spec.whatwg.org/#concept-parser-marker + * + * @since 6.7.0 + */ + public function insert_marker(): void { + $this->push( new WP_HTML_Token( null, 'marker', false ) ); + } + /** * Pushes a node onto the stack of active formatting elements. * @@ -95,7 +111,7 @@ public function current_node() { * * @param WP_HTML_Token $token Push this node onto the stack. */ - public function push( $token ) { + public function push( WP_HTML_Token $token ) { /* * > If there are already three elements in the list of active formatting elements after the last marker, * > if any, or anywhere in the list if there are no markers, that have the same tag name, namespace, and @@ -119,7 +135,7 @@ public function push( $token ) { * @param WP_HTML_Token $token Remove this node from the stack, if it's there already. * @return bool Whether the node was found and removed from the stack of active formatting elements. */ - public function remove_node( $token ) { + public function remove_node( WP_HTML_Token $token ) { foreach ( $this->walk_up() as $position_from_end => $item ) { if ( $token->bookmark_name !== $item->bookmark_name ) { continue; @@ -184,4 +200,30 @@ public function walk_up() { yield $this->stack[ $i ]; } } + + /** + * Clears the list of active formatting elements up to the last marker. + * + * > When the steps below require the UA to clear the list of active formatting elements up to + * > the last marker, the UA must perform the following steps: + * > + * > 1. Let entry be the last (most recently added) entry in the list of active + * > formatting elements. + * > 2. Remove entry from the list of active formatting elements. + * > 3. If entry was a marker, then stop the algorithm at this point. + * > The list has been cleared up to the last marker. + * > 4. Go to step 1. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-list-of-active-formatting-elements-up-to-the-last-marker + * + * @since 6.7.0 + */ + public function clear_up_to_last_marker(): void { + foreach ( $this->walk_up() as $item ) { + array_pop( $this->stack ); + if ( 'marker' === $item->node_name ) { + break; + } + } + } } diff --git a/src/wp-includes/html-api/class-wp-html-decoder.php b/src/wp-includes/html-api/class-wp-html-decoder.php index 78976002b4a93..6c1404beddcf1 100644 --- a/src/wp-includes/html-api/class-wp-html-decoder.php +++ b/src/wp-includes/html-api/class-wp-html-decoder.php @@ -31,7 +31,7 @@ class WP_HTML_Decoder { * Default 'case-sensitive'. * @return bool Whether the attribute value starts with the given string. */ - public static function attribute_starts_with( $haystack, $search_text, $case_sensitivity = 'case-sensitive' ) { + public static function attribute_starts_with( $haystack, $search_text, $case_sensitivity = 'case-sensitive' ): bool { $search_length = strlen( $search_text ); $loose_case = 'ascii-case-insensitive' === $case_sensitivity; $haystack_end = strlen( $haystack ); @@ -90,7 +90,7 @@ public static function attribute_starts_with( $haystack, $search_text, $case_sen * @param string $text Text containing raw and non-decoded text node to decode. * @return string Decoded UTF-8 value of given text node. */ - public static function decode_text_node( $text ) { + public static function decode_text_node( $text ): string { return static::decode( 'data', $text ); } @@ -110,7 +110,7 @@ public static function decode_text_node( $text ) { * @param string $text Text containing raw and non-decoded attribute value to decode. * @return string Decoded UTF-8 value of given attribute value. */ - public static function decode_attribute( $text ) { + public static function decode_attribute( $text ): string { return static::decode( 'attribute', $text ); } @@ -133,7 +133,7 @@ public static function decode_attribute( $text ) { * @param string $text Text document containing span of text to decode. * @return string Decoded UTF-8 string. */ - public static function decode( $context, $text ) { + public static function decode( $context, $text ): string { $decoded = ''; $end = strlen( $text ); $at = 0; @@ -141,7 +141,7 @@ public static function decode( $context, $text ) { while ( $at < $end ) { $next_character_reference_at = strpos( $text, '&', $at ); - if ( false === $next_character_reference_at || $next_character_reference_at >= $end ) { + if ( false === $next_character_reference_at ) { break; } @@ -196,6 +196,8 @@ public static function decode( $context, $text ) { * * @since 6.6.0 * + * @global WP_Token_Map $html5_named_character_references Mappings for HTML5 named character references. + * * @param string $context `attribute` for decoding attribute values, `data` otherwise. * @param string $text Text document containing span of text to decode. * @param int $at Optional. Byte offset into text where span begins, defaults to the beginning (0). @@ -421,7 +423,7 @@ public static function read_character_reference( $context, $text, $at = 0, &$mat * @param int $code_point Which code point to convert. * @return string Converted code point, or `�` if invalid. */ - public static function code_point_to_utf8_bytes( $code_point ) { + public static function code_point_to_utf8_bytes( $code_point ): string { // Pre-check to ensure a valid code point. if ( $code_point <= 0 || @@ -436,26 +438,26 @@ public static function code_point_to_utf8_bytes( $code_point ) { } if ( $code_point <= 0x7FF ) { - $byte1 = ( $code_point >> 6 ) | 0xC0; - $byte2 = $code_point & 0x3F | 0x80; + $byte1 = chr( ( $code_point >> 6 ) | 0xC0 ); + $byte2 = chr( $code_point & 0x3F | 0x80 ); - return pack( 'CC', $byte1, $byte2 ); + return "{$byte1}{$byte2}"; } if ( $code_point <= 0xFFFF ) { - $byte1 = ( $code_point >> 12 ) | 0xE0; - $byte2 = ( $code_point >> 6 ) & 0x3F | 0x80; - $byte3 = $code_point & 0x3F | 0x80; + $byte1 = chr( ( $code_point >> 12 ) | 0xE0 ); + $byte2 = chr( ( $code_point >> 6 ) & 0x3F | 0x80 ); + $byte3 = chr( $code_point & 0x3F | 0x80 ); - return pack( 'CCC', $byte1, $byte2, $byte3 ); + return "{$byte1}{$byte2}{$byte3}"; } // Any values above U+10FFFF are eliminated above in the pre-check. - $byte1 = ( $code_point >> 18 ) | 0xF0; - $byte2 = ( $code_point >> 12 ) & 0x3F | 0x80; - $byte3 = ( $code_point >> 6 ) & 0x3F | 0x80; - $byte4 = $code_point & 0x3F | 0x80; + $byte1 = chr( ( $code_point >> 18 ) | 0xF0 ); + $byte2 = chr( ( $code_point >> 12 ) & 0x3F | 0x80 ); + $byte3 = chr( ( $code_point >> 6 ) & 0x3F | 0x80 ); + $byte4 = chr( $code_point & 0x3F | 0x80 ); - return pack( 'CCCC', $byte1, $byte2, $byte3, $byte4 ); + return "{$byte1}{$byte2}{$byte3}{$byte4}"; } } diff --git a/src/wp-includes/html-api/class-wp-html-doctype-info.php b/src/wp-includes/html-api/class-wp-html-doctype-info.php new file mode 100644 index 0000000000000..e0396f7d7d603 --- /dev/null +++ b/src/wp-includes/html-api/class-wp-html-doctype-info.php @@ -0,0 +1,616 @@ +`. + * + * > DOCTYPEs are required for legacy reasons. When omitted, browsers tend to use a different + * > rendering mode that is incompatible with some specifications. Including the DOCTYPE in a + * > document ensures that the browser makes a best-effort attempt at following the + * > relevant specifications. + * + * @see https://html.spec.whatwg.org/#the-doctype + * + * DOCTYPE declarations comprise four properties: a name, public identifier, system identifier, + * and an indication of which document compatability mode they would imply if an HTML parser + * hadn't already determined it from other information. + * + * @see https://html.spec.whatwg.org/#the-initial-insertion-mode + * + * Historically, the DOCTYPE declaration was used in SGML documents to instruct a parser how + * to interpret the various tags and entities within a document. Its role in HTML diverged + * from how it was used in SGML and no meaning should be back-read into HTML based on how it + * is used in SGML, XML, or XHTML documents. + * + * @see https://www.iso.org/standard/16387.html + * + * @since 6.7.0 + * + * @see WP_HTML_Processor + */ +class WP_HTML_Doctype_Info { + /** + * Name of the DOCTYPE: should be "html" for HTML documents. + * + * This value should be considered "read only" and not modified. + * + * Historically the DOCTYPE name indicates name of the document's root element. + * + * + * ╰──┴── name is "html". + * + * @see https://html.spec.whatwg.org/#tokenization + * + * @since 6.7.0 + * + * @var string|null + */ + public $name = null; + + /** + * Public identifier of the DOCTYPE. + * + * This value should be considered "read only" and not modified. + * + * The public identifier is optional and should not appear in HTML documents. + * A `null` value indicates that no public identifier was present in the DOCTYPE. + * + * Historically the presence of the public identifier indicated that a document + * was meant to be shared between computer systems and the value indicated to a + * knowledgeable parser how to find the relevant document type definition (DTD). + * + * + * │ │ ╰─── public identifier ─────╯ + * ╰──┴── name is "html". + * + * @see https://html.spec.whatwg.org/#tokenization + * + * @since 6.7.0 + * + * @var string|null + */ + public $public_identifier = null; + + /** + * System identifier of the DOCTYPE. + * + * This value should be considered "read only" and not modified. + * + * The system identifier is optional and should not appear in HTML documents. + * A `null` value indicates that no system identifier was present in the DOCTYPE. + * + * Historically the system identifier specified where a relevant document type + * declaration for the given document is stored and may be retrieved. + * + * + * │ │ ╰──── system identifier ────╯ + * ╰──┴── name is "html". + * + * If a public identifier were provided it would indicate to a knowledgeable + * parser how to interpret the system identifier. + * + * + * │ │ ╰─── public identifier ─────╯ ╰──── system identifier ────╯ + * ╰──┴── name is "html". + * + * @see https://html.spec.whatwg.org/#tokenization + * + * @since 6.7.0 + * + * @var string|null + */ + public $system_identifier = null; + + /** + * Which document compatability mode this DOCTYPE declaration indicates. + * + * This value should be considered "read only" and not modified. + * + * When an HTML parser has not already set the document compatability mode, + * (e.g. "quirks" or "no-quirks" mode), it will infer if from the properties + * of the appropriate DOCTYPE declaration, if one exists. The DOCTYPE can + * indicate one of three possible document compatability modes: + * + * - "no-quirks" and "limited-quirks" modes (also called "standards" mode). + * - "quirks" mode (also called `CSS1Compat` mode). + * + * An appropriate DOCTYPE is one encountered in the "initial" insertion mode, + * before the HTML element has been opened and before finding any other + * DOCTYPE declaration tokens. + * + * @see https://html.spec.whatwg.org/#the-initial-insertion-mode + * + * @since 6.7.0 + * + * @var string One of "no-quirks", "limited-quirks", or "quirks". + */ + public $indicated_compatability_mode; + + /** + * Constructor. + * + * This class should not be instantiated directly. + * Use the static {@see self::from_doctype_token} method instead. + * + * The arguments to this constructor correspond to the "DOCTYPE token" + * as defined in the HTML specification. + * + * > DOCTYPE tokens have a name, a public identifier, a system identifier, + * > and a force-quirks flag. When a DOCTYPE token is created, its name, public identifier, + * > and system identifier must be marked as missing (which is a distinct state from the + * > empty string), and the force-quirks flag must be set to off (its other state is on). + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#tokenization + * + * @since 6.7.0 + * + * @param string|null $name Name of the DOCTYPE. + * @param string|null $public_identifier Public identifier of the DOCTYPE. + * @param string|null $system_identifier System identifier of the DOCTYPE. + * @param bool $force_quirks_flag Whether the force-quirks flag is set for the token. + */ + private function __construct( + ?string $name, + ?string $public_identifier, + ?string $system_identifier, + bool $force_quirks_flag + ) { + $this->name = $name; + $this->public_identifier = $public_identifier; + $this->system_identifier = $system_identifier; + + /* + * > If the DOCTYPE token matches one of the conditions in the following list, + * > then set the Document to quirks mode: + */ + + /* + * > The force-quirks flag is set to on. + */ + if ( $force_quirks_flag ) { + $this->indicated_compatability_mode = 'quirks'; + return; + } + + /* + * Normative documents will contain the literal `` with no + * public or system identifiers; short-circuit to avoid extra parsing. + */ + if ( 'html' === $name && null === $public_identifier && null === $system_identifier ) { + $this->indicated_compatability_mode = 'no-quirks'; + return; + } + + /* + * > The name is not "html". + * + * The tokenizer must report the name in lower case even if provided in + * the document in upper case; thus no conversion is required here. + */ + if ( 'html' !== $name ) { + $this->indicated_compatability_mode = 'quirks'; + return; + } + + /* + * Set up some variables to handle the rest of the conditions. + * + * > set...the public identifier...to...the empty string if the public identifier was missing. + * > set...the system identifier...to...the empty string if the system identifier was missing. + * > + * > The system identifier and public identifier strings must be compared... + * > in an ASCII case-insensitive manner. + * > + * > A system identifier whose value is the empty string is not considered missing + * > for the purposes of the conditions above. + */ + $system_identifier_is_missing = null === $system_identifier; + $public_identifier = null === $public_identifier ? '' : strtolower( $public_identifier ); + $system_identifier = null === $system_identifier ? '' : strtolower( $system_identifier ); + + /* + * > The public identifier is set to… + */ + if ( + '-//w3o//dtd w3 html strict 3.0//en//' === $public_identifier || + '-/w3c/dtd html 4.0 transitional/en' === $public_identifier || + 'html' === $public_identifier + ) { + $this->indicated_compatability_mode = 'quirks'; + return; + } + + /* + * > The system identifier is set to… + */ + if ( 'http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd' === $system_identifier ) { + $this->indicated_compatability_mode = 'quirks'; + return; + } + + /* + * All of the following conditions depend on matching the public identifier. + * If the public identifier is empty, none of the following conditions will match. + */ + if ( '' === $public_identifier ) { + $this->indicated_compatability_mode = 'no-quirks'; + return; + } + + /* + * > The public identifier starts with… + * + * @todo Optimize this matching. It shouldn't be a large overall performance issue, + * however, as only a single DOCTYPE declaration token should ever be parsed, + * and normative documents will have exited before reaching this condition. + */ + if ( + str_starts_with( $public_identifier, '+//silmaril//dtd html pro v0r11 19970101//' ) || + str_starts_with( $public_identifier, '-//as//dtd html 3.0 aswedit + extensions//' ) || + str_starts_with( $public_identifier, '-//advasoft ltd//dtd html 3.0 aswedit + extensions//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 level 1//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 level 2//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 strict level 1//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 strict level 2//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 2.0 strict//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 2.0//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 2.1e//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 3.0//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 3.2 final//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 3.2//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html 3//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html level 0//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html level 1//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html level 2//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html level 3//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html strict level 0//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html strict level 1//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html strict level 2//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html strict level 3//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html strict//' ) || + str_starts_with( $public_identifier, '-//ietf//dtd html//' ) || + str_starts_with( $public_identifier, '-//metrius//dtd metrius presentational//' ) || + str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 2.0 html strict//' ) || + str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 2.0 html//' ) || + str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 2.0 tables//' ) || + str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 3.0 html strict//' ) || + str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 3.0 html//' ) || + str_starts_with( $public_identifier, '-//microsoft//dtd internet explorer 3.0 tables//' ) || + str_starts_with( $public_identifier, '-//netscape comm. corp.//dtd html//' ) || + str_starts_with( $public_identifier, '-//netscape comm. corp.//dtd strict html//' ) || + str_starts_with( $public_identifier, "-//o'reilly and associates//dtd html 2.0//" ) || + str_starts_with( $public_identifier, "-//o'reilly and associates//dtd html extended 1.0//" ) || + str_starts_with( $public_identifier, "-//o'reilly and associates//dtd html extended relaxed 1.0//" ) || + str_starts_with( $public_identifier, '-//sq//dtd html 2.0 hotmetal + extensions//' ) || + str_starts_with( $public_identifier, '-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//' ) || + str_starts_with( $public_identifier, '-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//' ) || + str_starts_with( $public_identifier, '-//spyglass//dtd html 2.0 extended//' ) || + str_starts_with( $public_identifier, '-//sun microsystems corp.//dtd hotjava html//' ) || + str_starts_with( $public_identifier, '-//sun microsystems corp.//dtd hotjava strict html//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 3 1995-03-24//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 3.2 draft//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 3.2 final//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 3.2//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 3.2s draft//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 4.0 frameset//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 4.0 transitional//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html experimental 19960712//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html experimental 970421//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd w3 html//' ) || + str_starts_with( $public_identifier, '-//w3o//dtd w3 html 3.0//' ) || + str_starts_with( $public_identifier, '-//webtechs//dtd mozilla html 2.0//' ) || + str_starts_with( $public_identifier, '-//webtechs//dtd mozilla html//' ) + ) { + $this->indicated_compatability_mode = 'quirks'; + return; + } + + /* + * > The system identifier is missing and the public identifier starts with… + */ + if ( + $system_identifier_is_missing && ( + str_starts_with( $public_identifier, '-//w3c//dtd html 4.01 frameset//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 4.01 transitional//' ) + ) + ) { + $this->indicated_compatability_mode = 'quirks'; + return; + } + + /* + * > Otherwise, if the DOCTYPE token matches one of the conditions in + * > the following list, then set the Document to limited-quirks mode. + */ + + /* + * > The public identifier starts with… + */ + if ( + str_starts_with( $public_identifier, '-//w3c//dtd xhtml 1.0 frameset//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd xhtml 1.0 transitional//' ) + ) { + $this->indicated_compatability_mode = 'limited-quirks'; + return; + } + + /* + * > The system identifier is not missing and the public identifier starts with… + */ + if ( + ! $system_identifier_is_missing && ( + str_starts_with( $public_identifier, '-//w3c//dtd html 4.01 frameset//' ) || + str_starts_with( $public_identifier, '-//w3c//dtd html 4.01 transitional//' ) + ) + ) { + $this->indicated_compatability_mode = 'limited-quirks'; + return; + } + + $this->indicated_compatability_mode = 'no-quirks'; + } + + /** + * Creates a WP_HTML_Doctype_Info instance by parsing a raw DOCTYPE declaration token. + * + * Use this method to parse a DOCTYPE declaration token and get access to its properties + * via the returned WP_HTML_Doctype_Info class instance. The provided input must parse + * properly as a DOCTYPE declaration, though it must not represent a valid DOCTYPE. + * + * Example: + * + * // Normative HTML DOCTYPE declaration. + * $doctype = WP_HTML_Doctype_Info::from_doctype_token( '' ); + * 'no-quirks' === $doctype->indicated_compatability_mode; + * + * // A nonsensical DOCTYPE is still valid, and will indicate "quirks" mode. + * $doctype = WP_HTML_Doctype_Info::from_doctype_token( '' ); + * 'quirks' === $doctype->indicated_compatability_mode; + * + * // Textual quirks present in raw HTML are handled appropriately. + * $doctype = WP_HTML_Doctype_Info::from_doctype_token( "" ); + * 'no-quirks' === $doctype->indicated_compatability_mode; + * + * // Anything other than a proper DOCTYPE declaration token fails to parse. + * null === WP_HTML_Doctype_Info::from_doctype_token( ' ' ); + * null === WP_HTML_Doctype_Info::from_doctype_token( '

' ); + * null === WP_HTML_Doctype_Info::from_doctype_token( '' ); + * null === WP_HTML_Doctype_Info::from_doctype_token( 'html' ); + * null === WP_HTML_Doctype_Info::from_doctype_token( '' ); + * + * @since 6.7.0 + * + * @param string $doctype_html The complete raw DOCTYPE HTML string, e.g. ``. + * + * @return WP_HTML_Doctype_Info|null A WP_HTML_Doctype_Info instance will be returned if the + * provided DOCTYPE HTML is a valid DOCTYPE. Otherwise, null. + */ + public static function from_doctype_token( string $doctype_html ): ?self { + $doctype_name = null; + $doctype_public_id = null; + $doctype_system_id = null; + + $end = strlen( $doctype_html ) - 1; + + /* + * This parser combines the rules for parsing DOCTYPE tokens found in the HTML + * specification for the DOCTYPE related tokenizer states. + * + * @see https://html.spec.whatwg.org/#doctype-state + */ + + /* + * - Valid DOCTYPE HTML token must be at least `` assuming a complete token not + * ending in end-of-file. + * - It must start with an ASCII case-insensitive match for `` must be the final byte in the HTML string. + */ + if ( + $end < 9 || + 0 !== substr_compare( $doctype_html, '`? + if ( '>' !== $doctype_html[ $end ] || ( strcspn( $doctype_html, '>', $at ) + $at ) < $end ) { + return null; + } + + /* + * Perform newline normalization and ensure the $end value is correct after normalization. + * + * @see https://html.spec.whatwg.org/#preprocessing-the-input-stream + * @see https://infra.spec.whatwg.org/#normalize-newlines + */ + $doctype_html = str_replace( "\r\n", "\n", $doctype_html ); + $doctype_html = str_replace( "\r", "\n", $doctype_html ); + $end = strlen( $doctype_html ) - 1; + + /* + * In this state, the doctype token has been found and its "content" optionally including the + * name, public identifier, and system identifier is between the current position and the end. + * + * "" + * ╰─ $at ╰─ $end + * + * It's also possible that the declaration part is empty. + * + * ╭─ $at + * "" + * ╰─ $end + * + * Rules for parsing ">" which terminates the DOCTYPE do not need to be considered as they + * have been handled above in the condition that the provided DOCTYPE HTML must contain + * exactly one ">" character in the final position. + */ + + /* + * + * Parsing effectively begins in "Before DOCTYPE name state". Ignore whitespace and + * proceed to the next state. + * + * @see https://html.spec.whatwg.org/#before-doctype-name-state + */ + $at += strspn( $doctype_html, " \t\n\f\r", $at ); + + if ( $at >= $end ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + } + + $name_length = strcspn( $doctype_html, " \t\n\f\r", $at, $end - $at ); + $doctype_name = str_replace( "\0", "\u{FFFD}", strtolower( substr( $doctype_html, $at, $name_length ) ) ); + + $at += $name_length; + $at += strspn( $doctype_html, " \t\n\f\r", $at, $end - $at ); + if ( $at >= $end ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, false ); + } + + /* + * "After DOCTYPE name state" + * + * Find a case-insensitive match for "PUBLIC" or "SYSTEM" at this point. + * Otherwise, set force-quirks and enter bogus DOCTYPE state (skip the rest of the doctype). + * + * @see https://html.spec.whatwg.org/#after-doctype-name-state + */ + if ( $at + 6 >= $end ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + } + + /* + * > If the six characters starting from the current input character are an ASCII + * > case-insensitive match for the word "PUBLIC", then consume those characters + * > and switch to the after DOCTYPE public keyword state. + */ + if ( 0 === substr_compare( $doctype_html, 'PUBLIC', $at, 6, true ) ) { + $at += 6; + $at += strspn( $doctype_html, " \t\n\f\r", $at, $end - $at ); + if ( $at >= $end ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + } + goto parse_doctype_public_identifier; + } + + /* + * > Otherwise, if the six characters starting from the current input character are an ASCII + * > case-insensitive match for the word "SYSTEM", then consume those characters and switch + * > to the after DOCTYPE system keyword state. + */ + if ( 0 === substr_compare( $doctype_html, 'SYSTEM', $at, 6, true ) ) { + $at += 6; + $at += strspn( $doctype_html, " \t\n\f\r", $at, $end - $at ); + if ( $at >= $end ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + } + goto parse_doctype_system_identifier; + } + + /* + * > Otherwise, this is an invalid-character-sequence-after-doctype-name parse error. + * > Set the current DOCTYPE token's force-quirks flag to on. Reconsume in the bogus + * > DOCTYPE state. + */ + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + + parse_doctype_public_identifier: + /* + * The parser should enter "DOCTYPE public identifier (double-quoted) state" or + * "DOCTYPE public identifier (single-quoted) state" by finding one of the valid quotes. + * Anything else forces quirks mode and ignores the rest of the contents. + * + * @see https://html.spec.whatwg.org/#doctype-public-identifier-(double-quoted)-state + * @see https://html.spec.whatwg.org/#doctype-public-identifier-(single-quoted)-state + */ + $closer_quote = $doctype_html[ $at ]; + + /* + * > This is a missing-quote-before-doctype-public-identifier parse error. Set the + * > current DOCTYPE token's force-quirks flag to on. Reconsume in the bogus DOCTYPE state. + */ + if ( '"' !== $closer_quote && "'" !== $closer_quote ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + } + + ++$at; + + $identifier_length = strcspn( $doctype_html, $closer_quote, $at, $end - $at ); + $doctype_public_id = str_replace( "\0", "\u{FFFD}", substr( $doctype_html, $at, $identifier_length ) ); + + $at += $identifier_length; + if ( $at >= $end || $closer_quote !== $doctype_html[ $at ] ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + } + + ++$at; + + /* + * "Between DOCTYPE public and system identifiers state" + * + * Advance through whitespace between public and system identifiers. + * + * @see https://html.spec.whatwg.org/#between-doctype-public-and-system-identifiers-state + */ + $at += strspn( $doctype_html, " \t\n\f\r", $at, $end - $at ); + if ( $at >= $end ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, false ); + } + + parse_doctype_system_identifier: + /* + * The parser should enter "DOCTYPE system identifier (double-quoted) state" or + * "DOCTYPE system identifier (single-quoted) state" by finding one of the valid quotes. + * Anything else forces quirks mode and ignores the rest of the contents. + * + * @see https://html.spec.whatwg.org/#doctype-system-identifier-(double-quoted)-state + * @see https://html.spec.whatwg.org/#doctype-system-identifier-(single-quoted)-state + */ + $closer_quote = $doctype_html[ $at ]; + + /* + * > This is a missing-quote-before-doctype-system-identifier parse error. Set the + * > current DOCTYPE token's force-quirks flag to on. Reconsume in the bogus DOCTYPE state. + */ + if ( '"' !== $closer_quote && "'" !== $closer_quote ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + } + + ++$at; + + $identifier_length = strcspn( $doctype_html, $closer_quote, $at, $end - $at ); + $doctype_system_id = str_replace( "\0", "\u{FFFD}", substr( $doctype_html, $at, $identifier_length ) ); + + $at += $identifier_length; + if ( $at >= $end || $closer_quote !== $doctype_html[ $at ] ) { + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, true ); + } + + return new self( $doctype_name, $doctype_public_id, $doctype_system_id, false ); + } +} diff --git a/src/wp-includes/html-api/class-wp-html-open-elements.php b/src/wp-includes/html-api/class-wp-html-open-elements.php index 505c1595f4685..5ce1f8feb552c 100644 --- a/src/wp-includes/html-api/class-wp-html-open-elements.php +++ b/src/wp-includes/html-api/class-wp-html-open-elements.php @@ -58,7 +58,7 @@ class WP_HTML_Open_Elements { * * @since 6.6.0 * - * @var Closure + * @var Closure|null */ private $pop_handler = null; @@ -69,7 +69,7 @@ class WP_HTML_Open_Elements { * * @since 6.6.0 * - * @var Closure + * @var Closure|null */ private $push_handler = null; @@ -83,7 +83,7 @@ class WP_HTML_Open_Elements { * * @param Closure $handler The handler function. */ - public function set_pop_handler( Closure $handler ) { + public function set_pop_handler( Closure $handler ): void { $this->pop_handler = $handler; } @@ -97,10 +97,53 @@ public function set_pop_handler( Closure $handler ) { * * @param Closure $handler The handler function. */ - public function set_push_handler( Closure $handler ) { + public function set_push_handler( Closure $handler ): void { $this->push_handler = $handler; } + /** + * Returns the name of the node at the nth position on the stack + * of open elements, or `null` if no such position exists. + * + * Note that this uses a 1-based index, which represents the + * "nth item" on the stack, counting from the top, where the + * top-most element is the 1st, the second is the 2nd, etc... + * + * @since 6.7.0 + * + * @param int $nth Retrieve the nth item on the stack, with 1 being + * the top element, 2 being the second, etc... + * @return WP_HTML_Token|null Name of the node on the stack at the given location, + * or `null` if the location isn't on the stack. + */ + public function at( int $nth ): ?WP_HTML_Token { + foreach ( $this->walk_down() as $item ) { + if ( 0 === --$nth ) { + return $item; + } + } + + return null; + } + + /** + * Reports if a node of a given name is in the stack of open elements. + * + * @since 6.7.0 + * + * @param string $node_name Name of node for which to check. + * @return bool Whether a node of the given name is in the stack of open elements. + */ + public function contains( string $node_name ): bool { + foreach ( $this->walk_up() as $item ) { + if ( $node_name === $item->node_name ) { + return true; + } + } + + return false; + } + /** * Reports if a specific node is in the stack of open elements. * @@ -109,9 +152,9 @@ public function set_push_handler( Closure $handler ) { * @param WP_HTML_Token $token Look for this node in the stack. * @return bool Whether the referenced node is in the stack of open elements. */ - public function contains_node( $token ) { + public function contains_node( WP_HTML_Token $token ): bool { foreach ( $this->walk_up() as $item ) { - if ( $token->bookmark_name === $item->bookmark_name ) { + if ( $token === $item ) { return true; } } @@ -126,7 +169,7 @@ public function contains_node( $token ) { * * @return int How many node are in the stack of open elements. */ - public function count() { + public function count(): int { return count( $this->stack ); } @@ -138,19 +181,56 @@ public function count() { * * @return WP_HTML_Token|null Last node in the stack of open elements, if one exists, otherwise null. */ - public function current_node() { + public function current_node(): ?WP_HTML_Token { $current_node = end( $this->stack ); return $current_node ? $current_node : null; } /** - * Returns whether an element is in a specific scope. + * Indicates if the current node is of a given type or name. + * + * It's possible to pass either a node type or a node name to this function. + * In the case there is no current element it will always return `false`. + * + * Example: + * + * // Is the current node a text node? + * $stack->current_node_is( '#text' ); * - * ## HTML Support + * // Is the current node a DIV element? + * $stack->current_node_is( 'DIV' ); * - * This function skips checking for the termination list because there - * are no supported elements which appear in the termination list. + * // Is the current node any element/tag? + * $stack->current_node_is( '#tag' ); + * + * @see WP_HTML_Tag_Processor::get_token_type + * @see WP_HTML_Tag_Processor::get_token_name + * + * @since 6.7.0 + * + * @access private + * + * @param string $identity Check if the current node has this name or type (depending on what is provided). + * @return bool Whether there is a current element that matches the given identity, whether a token name or type. + */ + public function current_node_is( string $identity ): bool { + $current_node = end( $this->stack ); + if ( false === $current_node ) { + return false; + } + + $current_node_name = $current_node->node_name; + + return ( + $current_node_name === $identity || + ( '#doctype' === $identity && 'html' === $current_node_name ) || + ( '#tag' === $identity && ctype_upper( $current_node_name ) ) + ); + } + + /** + * Returns whether an element is in a specific scope. * * @since 6.4.0 * @@ -160,25 +240,24 @@ public function current_node() { * @param string[] $termination_list List of elements that terminate the search. * @return bool Whether the element was found in a specific scope. */ - public function has_element_in_specific_scope( $tag_name, $termination_list ) { + public function has_element_in_specific_scope( string $tag_name, $termination_list ): bool { foreach ( $this->walk_up() as $node ) { - if ( $node->node_name === $tag_name ) { + $namespaced_name = 'html' === $node->namespace + ? $node->node_name + : "{$node->namespace} {$node->node_name}"; + + if ( $namespaced_name === $tag_name ) { return true; } if ( '(internal: H1 through H6 - do not use)' === $tag_name && - in_array( $node->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) + in_array( $namespaced_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true ) ) { return true; } - switch ( $node->node_name ) { - case 'HTML': - return false; - } - - if ( in_array( $node->node_name, $termination_list, true ) ) { + if ( in_array( $namespaced_name, $termination_list, true ) ) { return false; } } @@ -189,25 +268,61 @@ public function has_element_in_specific_scope( $tag_name, $termination_list ) { /** * Returns whether a particular element is in scope. * + * > The stack of open elements is said to have a particular element in + * > scope when it has that element in the specific scope consisting of + * > the following element types: + * > + * > - applet + * > - caption + * > - html + * > - table + * > - td + * > - th + * > - marquee + * > - object + * > - template + * > - MathML mi + * > - MathML mo + * > - MathML mn + * > - MathML ms + * > - MathML mtext + * > - MathML annotation-xml + * > - SVG foreignObject + * > - SVG desc + * > - SVG title + * * @since 6.4.0 + * @since 6.7.0 Full support. * * @see https://html.spec.whatwg.org/#has-an-element-in-scope * * @param string $tag_name Name of tag to check. * @return bool Whether given element is in scope. */ - public function has_element_in_scope( $tag_name ) { + public function has_element_in_scope( string $tag_name ): bool { return $this->has_element_in_specific_scope( $tag_name, array( - - /* - * Because it's not currently possible to encounter - * one of the termination elements, they don't need - * to be listed here. If they were, they would be - * unreachable and only waste CPU cycles while - * scanning through HTML. - */ + 'APPLET', + 'CAPTION', + 'HTML', + 'TABLE', + 'TD', + 'TH', + 'MARQUEE', + 'OBJECT', + 'TEMPLATE', + + 'math MI', + 'math MO', + 'math MN', + 'math MS', + 'math MTEXT', + 'math ANNOTATION-XML', + + 'svg FOREIGNOBJECT', + 'svg DESC', + 'svg TITLE', ) ); } @@ -215,21 +330,50 @@ public function has_element_in_scope( $tag_name ) { /** * Returns whether a particular element is in list item scope. * + * > The stack of open elements is said to have a particular element + * > in list item scope when it has that element in the specific scope + * > consisting of the following element types: + * > + * > - All the element types listed above for the has an element in scope algorithm. + * > - ol in the HTML namespace + * > - ul in the HTML namespace + * * @since 6.4.0 * @since 6.5.0 Implemented: no longer throws on every invocation. + * @since 6.7.0 Supports all required HTML elements. * * @see https://html.spec.whatwg.org/#has-an-element-in-list-item-scope * * @param string $tag_name Name of tag to check. * @return bool Whether given element is in scope. */ - public function has_element_in_list_item_scope( $tag_name ) { + public function has_element_in_list_item_scope( string $tag_name ): bool { return $this->has_element_in_specific_scope( $tag_name, array( - // There are more elements that belong here which aren't currently supported. + 'APPLET', + 'BUTTON', + 'CAPTION', + 'HTML', + 'TABLE', + 'TD', + 'TH', + 'MARQUEE', + 'OBJECT', 'OL', + 'TEMPLATE', 'UL', + + 'math MI', + 'math MO', + 'math MN', + 'math MS', + 'math MTEXT', + 'math ANNOTATION-XML', + + 'svg FOREIGNOBJECT', + 'svg DESC', + 'svg TITLE', ) ); } @@ -237,51 +381,115 @@ public function has_element_in_list_item_scope( $tag_name ) { /** * Returns whether a particular element is in button scope. * + * > The stack of open elements is said to have a particular element + * > in button scope when it has that element in the specific scope + * > consisting of the following element types: + * > + * > - All the element types listed above for the has an element in scope algorithm. + * > - button in the HTML namespace + * * @since 6.4.0 + * @since 6.7.0 Supports all required HTML elements. * * @see https://html.spec.whatwg.org/#has-an-element-in-button-scope * * @param string $tag_name Name of tag to check. * @return bool Whether given element is in scope. */ - public function has_element_in_button_scope( $tag_name ) { - return $this->has_element_in_specific_scope( $tag_name, array( 'BUTTON' ) ); + public function has_element_in_button_scope( string $tag_name ): bool { + return $this->has_element_in_specific_scope( + $tag_name, + array( + 'APPLET', + 'BUTTON', + 'CAPTION', + 'HTML', + 'TABLE', + 'TD', + 'TH', + 'MARQUEE', + 'OBJECT', + 'TEMPLATE', + + 'math MI', + 'math MO', + 'math MN', + 'math MS', + 'math MTEXT', + 'math ANNOTATION-XML', + + 'svg FOREIGNOBJECT', + 'svg DESC', + 'svg TITLE', + ) + ); } /** * Returns whether a particular element is in table scope. * + * > The stack of open elements is said to have a particular element + * > in table scope when it has that element in the specific scope + * > consisting of the following element types: + * > + * > - html in the HTML namespace + * > - table in the HTML namespace + * > - template in the HTML namespace + * * @since 6.4.0 + * @since 6.7.0 Full implementation. * * @see https://html.spec.whatwg.org/#has-an-element-in-table-scope * - * @throws WP_HTML_Unsupported_Exception Always until this function is implemented. - * * @param string $tag_name Name of tag to check. * @return bool Whether given element is in scope. */ - public function has_element_in_table_scope( $tag_name ) { - throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on table scope.' ); - - return false; // The linter requires this unreachable code until the function is implemented and can return. + public function has_element_in_table_scope( string $tag_name ): bool { + return $this->has_element_in_specific_scope( + $tag_name, + array( + 'HTML', + 'TABLE', + 'TEMPLATE', + ) + ); } /** * Returns whether a particular element is in select scope. * - * @since 6.4.0 + * This test differs from the others like it, in that its rules are inverted. + * Instead of arriving at a match when one of any tag in a termination group + * is reached, this one terminates if any other tag is reached. * - * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope + * > The stack of open elements is said to have a particular element in select scope when it has + * > that element in the specific scope consisting of all element types except the following: + * > - optgroup in the HTML namespace + * > - option in the HTML namespace * - * @throws WP_HTML_Unsupported_Exception Always until this function is implemented. + * @since 6.4.0 Stub implementation (throws). + * @since 6.7.0 Full implementation. + * + * @see https://html.spec.whatwg.org/#has-an-element-in-select-scope * * @param string $tag_name Name of tag to check. - * @return bool Whether given element is in scope. + * @return bool Whether the given element is in SELECT scope. */ - public function has_element_in_select_scope( $tag_name ) { - throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on select scope.' ); + public function has_element_in_select_scope( string $tag_name ): bool { + foreach ( $this->walk_up() as $node ) { + if ( $node->node_name === $tag_name ) { + return true; + } - return false; // The linter requires this unreachable code until the function is implemented and can return. + if ( + 'OPTION' !== $node->node_name && + 'OPTGROUP' !== $node->node_name + ) { + return false; + } + } + + return false; } /** @@ -293,7 +501,7 @@ public function has_element_in_select_scope( $tag_name ) { * * @return bool Whether a P is in BUTTON scope. */ - public function has_p_in_button_scope() { + public function has_p_in_button_scope(): bool { return $this->has_p_in_button_scope; } @@ -306,13 +514,17 @@ public function has_p_in_button_scope() { * * @return bool Whether a node was popped off of the stack. */ - public function pop() { + public function pop(): bool { $item = array_pop( $this->stack ); - if ( null === $item ) { return false; } + if ( 'context-node' === $item->bookmark_name ) { + $this->stack[] = $item; + return false; + } + $this->after_element_pop( $item ); return true; } @@ -327,8 +539,12 @@ public function pop() { * @param string $tag_name Name of tag that needs to be popped off of the stack of open elements. * @return bool Whether a tag of the given name was found and popped off of the stack of open elements. */ - public function pop_until( $tag_name ) { + public function pop_until( string $tag_name ): bool { foreach ( $this->walk_up() as $item ) { + if ( 'context-node' === $item->bookmark_name ) { + return true; + } + $this->pop(); if ( @@ -355,7 +571,7 @@ public function pop_until( $tag_name ) { * * @param WP_HTML_Token $stack_item Item to add onto stack. */ - public function push( $stack_item ) { + public function push( WP_HTML_Token $stack_item ): void { $this->stack[] = $stack_item; $this->after_element_push( $stack_item ); } @@ -368,7 +584,11 @@ public function push( $stack_item ) { * @param WP_HTML_Token $token The node to remove from the stack of open elements. * @return bool Whether the node was found and removed from the stack of open elements. */ - public function remove_node( $token ) { + public function remove_node( WP_HTML_Token $token ): bool { + if ( 'context-node' === $token->bookmark_name ) { + return false; + } + foreach ( $this->walk_up() as $position_from_end => $item ) { if ( $token->bookmark_name !== $item->bookmark_name ) { continue; @@ -431,9 +651,10 @@ public function walk_down() { * @since 6.4.0 * @since 6.5.0 Accepts $above_this_node to start traversal above a given node, if it exists. * - * @param ?WP_HTML_Token $above_this_node Start traversing above this node, if provided and if the node exists. + * @param WP_HTML_Token|null $above_this_node Optional. Start traversing above this node, + * if provided and if the node exists. */ - public function walk_up( $above_this_node = null ) { + public function walk_up( ?WP_HTML_Token $above_this_node = null ) { $has_found_node = null === $above_this_node; for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) { @@ -465,13 +686,35 @@ public function walk_up( $above_this_node = null ) { * * @param WP_HTML_Token $item Element that was added to the stack of open elements. */ - public function after_element_push( $item ) { + public function after_element_push( WP_HTML_Token $item ): void { + $namespaced_name = 'html' === $item->namespace + ? $item->node_name + : "{$item->namespace} {$item->node_name}"; + /* * When adding support for new elements, expand this switch to trap * cases where the precalculated value needs to change. */ - switch ( $item->node_name ) { + switch ( $namespaced_name ) { + case 'APPLET': case 'BUTTON': + case 'CAPTION': + case 'HTML': + case 'TABLE': + case 'TD': + case 'TH': + case 'MARQUEE': + case 'OBJECT': + case 'TEMPLATE': + case 'math MI': + case 'math MO': + case 'math MN': + case 'math MS': + case 'math MTEXT': + case 'math ANNOTATION-XML': + case 'svg FOREIGNOBJECT': + case 'svg DESC': + case 'svg TITLE': $this->has_p_in_button_scope = false; break; @@ -498,17 +741,32 @@ public function after_element_push( $item ) { * * @param WP_HTML_Token $item Element that was removed from the stack of open elements. */ - public function after_element_pop( $item ) { + public function after_element_pop( WP_HTML_Token $item ): void { /* * When adding support for new elements, expand this switch to trap * cases where the precalculated value needs to change. */ switch ( $item->node_name ) { + case 'APPLET': case 'BUTTON': - $this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' ); - break; - + case 'CAPTION': + case 'HTML': case 'P': + case 'TABLE': + case 'TD': + case 'TH': + case 'MARQUEE': + case 'OBJECT': + case 'TEMPLATE': + case 'math MI': + case 'math MO': + case 'math MN': + case 'math MS': + case 'math MTEXT': + case 'math ANNOTATION-XML': + case 'svg FOREIGNOBJECT': + case 'svg DESC': + case 'svg TITLE': $this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' ); break; } @@ -517,4 +775,87 @@ public function after_element_pop( $item ) { ( $this->pop_handler )( $item ); } } + + /** + * Clear the stack back to a table context. + * + * > When the steps above require the UA to clear the stack back to a table context, it means + * > that the UA must, while the current node is not a table, template, or html element, pop + * > elements from the stack of open elements. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-context + * + * @since 6.7.0 + */ + public function clear_to_table_context(): void { + foreach ( $this->walk_up() as $item ) { + if ( + 'TABLE' === $item->node_name || + 'TEMPLATE' === $item->node_name || + 'HTML' === $item->node_name + ) { + break; + } + $this->pop(); + } + } + + /** + * Clear the stack back to a table body context. + * + * > When the steps above require the UA to clear the stack back to a table body context, it + * > means that the UA must, while the current node is not a tbody, tfoot, thead, template, or + * > html element, pop elements from the stack of open elements. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-body-context + * + * @since 6.7.0 + */ + public function clear_to_table_body_context(): void { + foreach ( $this->walk_up() as $item ) { + if ( + 'TBODY' === $item->node_name || + 'TFOOT' === $item->node_name || + 'THEAD' === $item->node_name || + 'TEMPLATE' === $item->node_name || + 'HTML' === $item->node_name + ) { + break; + } + $this->pop(); + } + } + + /** + * Clear the stack back to a table row context. + * + * > When the steps above require the UA to clear the stack back to a table row context, it + * > means that the UA must, while the current node is not a tr, template, or html element, pop + * > elements from the stack of open elements. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#clear-the-stack-back-to-a-table-row-context + * + * @since 6.7.0 + */ + public function clear_to_table_row_context(): void { + foreach ( $this->walk_up() as $item ) { + if ( + 'TR' === $item->node_name || + 'TEMPLATE' === $item->node_name || + 'HTML' === $item->node_name + ) { + break; + } + $this->pop(); + } + } + + /** + * Wakeup magic method. + * + * @since 6.6.0 + */ + public function __wakeup() { + throw new \LogicException( __CLASS__ . ' should never be unserialized' ); + } } diff --git a/src/wp-includes/html-api/class-wp-html-processor-state.php b/src/wp-includes/html-api/class-wp-html-processor-state.php index 9cf10c344107a..16875c4ac1b2b 100644 --- a/src/wp-includes/html-api/class-wp-html-processor-state.php +++ b/src/wp-includes/html-api/class-wp-html-processor-state.php @@ -47,6 +47,66 @@ class WP_HTML_Processor_State { */ const INSERTION_MODE_INITIAL = 'insertion-mode-initial'; + /** + * Before HTML insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#the-before-html-insertion-mode + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_BEFORE_HTML = 'insertion-mode-before-html'; + + /** + * Before head insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-beforehead + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_BEFORE_HEAD = 'insertion-mode-before-head'; + + /** + * In head insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-inhead + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_HEAD = 'insertion-mode-in-head'; + + /** + * In head noscript insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-inheadnoscript + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_HEAD_NOSCRIPT = 'insertion-mode-in-head-noscript'; + + /** + * After head insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-afterhead + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_AFTER_HEAD = 'insertion-mode-after-head'; + /** * In body insertion mode for full HTML parser. * @@ -59,6 +119,222 @@ class WP_HTML_Processor_State { */ const INSERTION_MODE_IN_BODY = 'insertion-mode-in-body'; + /** + * In table insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-intable + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_TABLE = 'insertion-mode-in-table'; + + /** + * In table text insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-intabletext + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_TABLE_TEXT = 'insertion-mode-in-table-text'; + + /** + * In caption insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-incaption + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_CAPTION = 'insertion-mode-in-caption'; + + /** + * In column group insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-incolumngroup + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_COLUMN_GROUP = 'insertion-mode-in-column-group'; + + /** + * In table body insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-intablebody + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_TABLE_BODY = 'insertion-mode-in-table-body'; + + /** + * In row insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-inrow + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_ROW = 'insertion-mode-in-row'; + + /** + * In cell insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-incell + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_CELL = 'insertion-mode-in-cell'; + + /** + * In select insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-inselect + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_SELECT = 'insertion-mode-in-select'; + + /** + * In select in table insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-inselectintable + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_SELECT_IN_TABLE = 'insertion-mode-in-select-in-table'; + + /** + * In template insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-intemplate + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_TEMPLATE = 'insertion-mode-in-template'; + + /** + * After body insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-afterbody + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_AFTER_BODY = 'insertion-mode-after-body'; + + /** + * In frameset insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-inframeset + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_IN_FRAMESET = 'insertion-mode-in-frameset'; + + /** + * After frameset insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#parsing-main-afterframeset + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_AFTER_FRAMESET = 'insertion-mode-after-frameset'; + + /** + * After after body insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#the-after-after-body-insertion-mode + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_AFTER_AFTER_BODY = 'insertion-mode-after-after-body'; + + /** + * After after frameset insertion mode for full HTML parser. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#the-after-after-frameset-insertion-mode + * @see WP_HTML_Processor_State::$insertion_mode + * + * @var string + */ + const INSERTION_MODE_AFTER_AFTER_FRAMESET = 'insertion-mode-after-after-frameset'; + + /** + * No-quirks mode document compatability mode. + * + * > In no-quirks mode, the behavior is (hopefully) the desired behavior + * > described by the modern HTML and CSS specifications. + * + * @since 6.7.0 + * + * @var string + */ + const NO_QUIRKS_MODE = 'no-quirks-mode'; + + /** + * Quirks mode document compatability mode. + * + * > In quirks mode, layout emulates behavior in Navigator 4 and Internet + * > Explorer 5. This is essential in order to support websites that were + * > built before the widespread adoption of web standards. + * + * @since 6.7.0 + * + * @var string + */ + const QUIRKS_MODE = 'quirks-mode'; + + /** + * The stack of template insertion modes. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#the-insertion-mode:stack-of-template-insertion-modes + * + * @var array + */ + public $stack_of_template_insertion_modes = array(); + /** * Tracks open elements while scanning HTML. * @@ -70,7 +346,7 @@ class WP_HTML_Processor_State { * * @var WP_HTML_Open_Elements */ - public $stack_of_open_elements = null; + public $stack_of_open_elements; /** * Tracks open formatting elements, used to handle mis-nested formatting element tags. @@ -83,7 +359,7 @@ class WP_HTML_Processor_State { * * @var WP_HTML_Active_Formatting_Elements */ - public $active_formatting_elements = null; + public $active_formatting_elements; /** * Refers to the currently-matched tag, if any. @@ -105,6 +381,30 @@ class WP_HTML_Processor_State { */ public $insertion_mode = self::INSERTION_MODE_INITIAL; + /** + * Indicates if the document is in quirks mode or no-quirks mode. + * + * Impact on HTML parsing: + * + * - In `NO_QUIRKS_MODE` CSS class and ID selectors match in a byte-for-byte + * manner, otherwise for backwards compatability, class selectors are to + * match in an ASCII case-insensitive manner. + * + * - When not in `QUIRKS_MODE`, a TABLE start tag implicitly closes an open P tag + * if one is in scope and open, otherwise the TABLE becomes a child of the P. + * + * `QUIRKS_MODE` impacts many styling-related aspects of an HTML document, but + * none of the other changes modifies how the HTML is parsed or selected. + * + * @see self::QUIRKS_MODE + * @see self::NO_QUIRKS_MODE + * + * @since 6.7.0 + * + * @var string + */ + public $document_mode = self::NO_QUIRKS_MODE; + /** * Context node initializing fragment parser, if created as a fragment parser. * @@ -116,6 +416,67 @@ class WP_HTML_Processor_State { */ public $context_node = null; + /** + * The recognized encoding of the input byte stream. + * + * > The stream of code points that comprises the input to the tokenization + * > stage will be initially seen by the user agent as a stream of bytes + * > (typically coming over the network or from the local file system). + * > The bytes encode the actual characters according to a particular character + * > encoding, which the user agent uses to decode the bytes into characters. + * + * @since 6.7.0 + * + * @var string|null + */ + public $encoding = null; + + /** + * The parser's confidence in the input encoding. + * + * > When the HTML parser is decoding an input byte stream, it uses a character + * > encoding and a confidence. The confidence is either tentative, certain, or + * > irrelevant. The encoding used, and whether the confidence in that encoding + * > is tentative or certain, is used during the parsing to determine whether to + * > change the encoding. If no encoding is necessary, e.g. because the parser is + * > operating on a Unicode stream and doesn't have to use a character encoding + * > at all, then the confidence is irrelevant. + * + * @since 6.7.0 + * + * @var string + */ + public $encoding_confidence = 'tentative'; + + /** + * HEAD element pointer. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#head-element-pointer + * + * @var WP_HTML_Token|null + */ + public $head_element = null; + + /** + * FORM element pointer. + * + * > points to the last form element that was opened and whose end tag has + * > not yet been seen. It is used to make form controls associate with + * > forms in the face of dramatically bad markup, for historical reasons. + * > It is ignored inside template elements. + * + * @todo This may be invalidated by a seek operation. + * + * @see https://html.spec.whatwg.org/#form-element-pointer + * + * @since 6.7.0 + * + * @var WP_HTML_Token|null + */ + public $form_element = null; + /** * The frameset-ok flag indicates if a `FRAMESET` element is allowed in the current state. * diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 528c222335b75..d924c9be5dd5b 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -97,22 +97,11 @@ * will abort early and stop all processing. This draconian measure ensures * that the HTML Processor won't break any HTML it doesn't fully understand. * - * The following list specifies the HTML tags that _are_ supported: + * The HTML Processor supports all elements other than a specific set: * - * - Containers: ADDRESS, BLOCKQUOTE, DETAILS, DIALOG, DIV, FOOTER, HEADER, MAIN, MENU, SPAN, SUMMARY. - * - Custom elements: All custom elements are supported. :) - * - Form elements: BUTTON, DATALIST, FIELDSET, INPUT, LABEL, LEGEND, METER, PROGRESS, SEARCH. - * - Formatting elements: B, BIG, CODE, EM, FONT, I, PRE, SMALL, STRIKE, STRONG, TT, U, WBR. - * - Heading elements: H1, H2, H3, H4, H5, H6, HGROUP. - * - Links: A. - * - Lists: DD, DL, DT, LI, OL, UL. - * - Media elements: AUDIO, CANVAS, EMBED, FIGCAPTION, FIGURE, IMG, MAP, PICTURE, SOURCE, TRACK, VIDEO. - * - Paragraph: BR, P. - * - Phrasing elements: ABBR, AREA, BDI, BDO, CITE, DATA, DEL, DFN, INS, MARK, OUTPUT, Q, SAMP, SUB, SUP, TIME, VAR. - * - Sectioning elements: ARTICLE, ASIDE, HR, NAV, SECTION. - * - Templating elements: SLOT. - * - Text decoration: RUBY. - * - Deprecated elements: ACRONYM, BLINK, CENTER, DIR, ISINDEX, KEYGEN, LISTING, MULTICOL, NEXTID, PARAM, SPACER. + * - Any element inside a TABLE. + * - Any element inside foreign content, including SVG and MATH. + * - Any element outside the IN BODY insertion mode, e.g. doctype declarations, meta, links. * * ### Supported markup * @@ -121,15 +110,30 @@ * may in fact belong _before_ the table in the DOM. If the HTML Processor encounters * such a case it will stop processing. * - * The following list specifies HTML markup that _is_ supported: + * The following list illustrates some common examples of unexpected HTML inputs that + * the HTML Processor properly parses and represents: * - * - Markup involving only those tags listed above. - * - Fully-balanced and non-overlapping tags. - * - HTML with unexpected tag closers. - * - Some unbalanced or overlapping tags. - * - P tags after unclosed P tags. - * - BUTTON tags after unclosed BUTTON tags. - * - A tags after unclosed A tags that don't involve any active formatting elements. + * - HTML with optional tags omitted, e.g. `

one

two`. + * - HTML with unexpected tag closers, e.g. `

one more

`. + * - Non-void tags with self-closing flag, e.g. `
the DIV is still open.
`. + * - Heading elements which close open heading elements of another level, e.g. `

Closed by

`. + * - Elements containing text that looks like other tags but isn't, e.g. `The <img> is plaintext`. + * - SCRIPT and STYLE tags containing text that looks like HTML but isn't, e.g. ``. + * - SCRIPT content which has been escaped, e.g. ``. + * + * ### Unsupported Features + * + * This parser does not report parse errors. + * + * Normally, when additional HTML or BODY tags are encountered in a document, if there + * are any additional attributes on them that aren't found on the previous elements, + * the existing HTML and BODY elements adopt those missing attribute values. This + * parser does not add those additional attributes. + * + * In certain situations, elements are moved to a different part of the document in + * a process called "adoption" and "fostering." Because the nodes move to a location + * in the document that the parser had already processed, this parser does not support + * these situations and will bail. * * @since 6.4.0 * @@ -159,7 +163,7 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { * * @var WP_HTML_Processor_State */ - private $state = null; + private $state; /** * Used to create unique bookmark names. @@ -188,6 +192,17 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { */ private $last_error = null; + /** + * Stores context for why the parser bailed on unsupported HTML, if it did. + * + * @see self::get_unsupported_exception + * + * @since 6.7.0 + * + * @var WP_HTML_Unsupported_Exception|null + */ + private $unsupported_exception = null; + /** * Releases a bookmark when PHP garbage-collects its wrapping WP_HTML_Token instance. * @@ -197,7 +212,7 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { * * @since 6.4.0 * - * @var closure + * @var Closure|null */ private $release_internal_bookmark_on_destruct = null; @@ -211,6 +226,15 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { */ private $element_queue = array(); + /** + * Stores the current breadcrumbs. + * + * @since 6.7.0 + * + * @var string[] + */ + private $breadcrumbs = array(); + /** * Current stack event, if set, representing a matched token. * @@ -221,32 +245,17 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor { * * @since 6.6.0 * - * @var ?WP_HTML_Stack_Event + * @var WP_HTML_Stack_Event|null */ private $current_element = null; /** * Context node if created as a fragment parser. * - * @var ?WP_HTML_Token + * @var WP_HTML_Token|null */ private $context_node = null; - /** - * Whether the parser has yet processed the context node, - * if created as a fragment parser. - * - * The context node will be initially pushed onto the stack of open elements, - * but when created as a fragment parser, this context element (and the implicit - * HTML document node above it) should not be exposed as a matched token or node. - * - * This boolean indicates whether the processor should skip over the current - * node in its initial search for the first node created from the input HTML. - * - * @var bool - */ - private $has_seen_context_node = false; - /* * Public Interface Functions */ @@ -288,30 +297,60 @@ public static function create_fragment( $html, $context = '', $encoding = return null; } - $processor = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE ); - $processor->state->context_node = array( 'BODY', array() ); - $processor->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + $processor = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE ); + $processor->state->context_node = array( 'BODY', array() ); + $processor->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + $processor->state->encoding = $encoding; + $processor->state->encoding_confidence = 'certain'; // @todo Create "fake" bookmarks for non-existent but implied nodes. $processor->bookmarks['root-node'] = new WP_HTML_Span( 0, 0 ); $processor->bookmarks['context-node'] = new WP_HTML_Span( 0, 0 ); - $processor->state->stack_of_open_elements->push( - new WP_HTML_Token( - 'root-node', - 'HTML', - false - ) + $root_node = new WP_HTML_Token( + 'root-node', + 'HTML', + false ); + $processor->state->stack_of_open_elements->push( $root_node ); + $context_node = new WP_HTML_Token( 'context-node', $processor->state->context_node[0], false ); - $processor->state->stack_of_open_elements->push( $context_node ); $processor->context_node = $context_node; + $processor->breadcrumbs = array( 'HTML', $context_node->node_name ); + + return $processor; + } + + /** + * Creates an HTML processor in the full parsing mode. + * + * It's likely that a fragment parser is more appropriate, unless sending an + * entire HTML document from start to finish. Consider a fragment parser with + * a context node of ``. + * + * Since UTF-8 is the only currently-accepted charset, if working with a + * document that isn't UTF-8, it's important to convert the document before + * creating the processor: pass in the converted HTML. + * + * @param string $html Input HTML document to process. + * @param string|null $known_definite_encoding Optional. If provided, specifies the charset used + * in the input byte stream. Currently must be UTF-8. + * @return static|null The created processor if successful, otherwise null. + */ + public static function create_full_parser( $html, $known_definite_encoding = 'UTF-8' ) { + if ( 'UTF-8' !== $known_definite_encoding ) { + return null; + } + + $processor = new static( $html, self::CONSTRUCTOR_UNLOCK_CODE ); + $processor->state->encoding = $known_definite_encoding; + $processor->state->encoding_confidence = 'certain'; return $processor; } @@ -348,14 +387,28 @@ public function __construct( $html, $use_the_static_create_methods_instead = nul $this->state = new WP_HTML_Processor_State(); $this->state->stack_of_open_elements->set_push_handler( - function ( WP_HTML_Token $token ) { - $this->element_queue[] = new WP_HTML_Stack_Event( $token, WP_HTML_Stack_Event::PUSH ); + function ( WP_HTML_Token $token ): void { + $is_virtual = ! isset( $this->state->current_token ) || $this->is_tag_closer(); + $same_node = isset( $this->state->current_token ) && $token->node_name === $this->state->current_token->node_name; + $provenance = ( ! $same_node || $is_virtual ) ? 'virtual' : 'real'; + $this->element_queue[] = new WP_HTML_Stack_Event( $token, WP_HTML_Stack_Event::PUSH, $provenance ); + + $this->change_parsing_namespace( $token->namespace ); } ); $this->state->stack_of_open_elements->set_pop_handler( - function ( WP_HTML_Token $token ) { - $this->element_queue[] = new WP_HTML_Stack_Event( $token, WP_HTML_Stack_Event::POP ); + function ( WP_HTML_Token $token ): void { + $is_virtual = ! isset( $this->state->current_token ) || ! $this->is_tag_closer(); + $same_node = isset( $this->state->current_token ) && $token->node_name === $this->state->current_token->node_name; + $provenance = ( ! $same_node || $is_virtual ) ? 'virtual' : 'real'; + $this->element_queue[] = new WP_HTML_Stack_Event( $token, WP_HTML_Stack_Event::POP, $provenance ); + $adjusted_current_node = $this->get_adjusted_current_node(); + $this->change_parsing_namespace( + $adjusted_current_node + ? $adjusted_current_node->namespace + : 'html' + ); } ); @@ -364,11 +417,48 @@ function ( WP_HTML_Token $token ) { * a private method into WP_HTML_Token classes without * exposing it to any public API. */ - $this->release_internal_bookmark_on_destruct = function ( $name ) { + $this->release_internal_bookmark_on_destruct = function ( string $name ): void { parent::release_bookmark( $name ); }; } + /** + * Stops the parser and terminates its execution when encountering unsupported markup. + * + * @throws WP_HTML_Unsupported_Exception Halts execution of the parser. + * + * @since 6.7.0 + * + * @param string $message Explains support is missing in order to parse the current node. + */ + private function bail( string $message ) { + $here = $this->bookmarks[ $this->state->current_token->bookmark_name ]; + $token = substr( $this->html, $here->start, $here->length ); + + $open_elements = array(); + foreach ( $this->state->stack_of_open_elements->stack as $item ) { + $open_elements[] = $item->node_name; + } + + $active_formats = array(); + foreach ( $this->state->active_formatting_elements->walk_down() as $item ) { + $active_formats[] = $item->node_name; + } + + $this->last_error = self::ERROR_UNSUPPORTED; + + $this->unsupported_exception = new WP_HTML_Unsupported_Exception( + $message, + $this->state->current_token->node_name, + $here->start, + $token, + $open_elements, + $active_formats + ); + + throw $this->unsupported_exception; + } + /** * Returns the last error, if any. * @@ -392,10 +482,25 @@ function ( WP_HTML_Token $token ) { * * @return string|null The last error, if one exists, otherwise null. */ - public function get_last_error() { + public function get_last_error(): ?string { return $this->last_error; } + /** + * Returns context for why the parser aborted due to unsupported HTML, if it did. + * + * This is meant for debugging purposes, not for production use. + * + * @since 6.7.0 + * + * @see self::$unsupported_exception + * + * @return WP_HTML_Unsupported_Exception|null + */ + public function get_unsupported_exception() { + return $this->unsupported_exception; + } + /** * Finds the next tag matching the $query. * @@ -420,7 +525,7 @@ public function get_last_error() { * } * @return bool Whether a tag was matched. */ - public function next_tag( $query = null ) { + public function next_tag( $query = null ): bool { $visit_closers = isset( $query['tag_closers'] ) && 'visit' === $query['tag_closers']; if ( null === $query ) { @@ -460,6 +565,10 @@ public function next_tag( $query = null ) { continue; } + if ( isset( $query['tag_name'] ) && $query['tag_name'] !== $this->get_token_name() ) { + continue; + } + if ( isset( $needs_class ) && ! $this->has_class( $needs_class ) ) { continue; } @@ -506,52 +615,64 @@ public function next_tag( $query = null ) { * * @return bool */ - public function next_token() { + public function next_token(): bool { $this->current_element = null; if ( isset( $this->last_error ) ) { return false; } - if ( 0 === count( $this->element_queue ) && ! $this->step() ) { - while ( $this->state->stack_of_open_elements->pop() ) { - continue; - } + /* + * Prime the events if there are none. + * + * @todo In some cases, probably related to the adoption agency + * algorithm, this call to step() doesn't create any new + * events. Calling it again creates them. Figure out why + * this is and if it's inherent or if it's a bug. Looping + * until there are events or until there are no more + * tokens works in the meantime and isn't obviously wrong. + */ + if ( empty( $this->element_queue ) && $this->step() ) { + return $this->next_token(); } + // Process the next event on the queue. $this->current_element = array_shift( $this->element_queue ); - while ( isset( $this->context_node ) && ! $this->has_seen_context_node ) { - if ( isset( $this->current_element ) ) { - if ( $this->context_node === $this->current_element->token && WP_HTML_Stack_Event::PUSH === $this->current_element->operation ) { - $this->has_seen_context_node = true; - return $this->next_token(); - } + if ( ! isset( $this->current_element ) ) { + // There are no tokens left, so close all remaining open elements. + while ( $this->state->stack_of_open_elements->pop() ) { + continue; } - $this->current_element = array_shift( $this->element_queue ); + + return empty( $this->element_queue ) ? false : $this->next_token(); } - if ( ! isset( $this->current_element ) ) { + $is_pop = WP_HTML_Stack_Event::POP === $this->current_element->operation; + + /* + * The root node only exists in the fragment parser, and closing it + * indicates that the parse is complete. Stop before popping it from + * the breadcrumbs. + */ + if ( 'root-node' === $this->current_element->token->bookmark_name ) { return $this->next_token(); } - if ( isset( $this->context_node ) && WP_HTML_Stack_Event::POP === $this->current_element->operation && $this->context_node === $this->current_element->token ) { - $this->element_queue = array(); - $this->current_element = null; - return false; + // Adjust the breadcrumbs for this event. + if ( $is_pop ) { + array_pop( $this->breadcrumbs ); + } else { + $this->breadcrumbs[] = $this->current_element->token->node_name; } // Avoid sending close events for elements which don't expect a closing. - if ( - WP_HTML_Stack_Event::POP === $this->current_element->operation && - ! static::expects_closer( $this->current_element->token ) - ) { + if ( $is_pop && ! $this->expects_closer( $this->current_element->token ) ) { return $this->next_token(); } return true; } - /** * Indicates if the current tag token is a tag closer. * @@ -568,12 +689,27 @@ public function next_token() { * * @return bool Whether the current tag is a tag closer. */ - public function is_tag_closer() { - return isset( $this->current_element ) - ? ( WP_HTML_Stack_Event::POP === $this->current_element->operation ) + public function is_tag_closer(): bool { + return $this->is_virtual() + ? ( WP_HTML_Stack_Event::POP === $this->current_element->operation && '#tag' === $this->get_token_type() ) : parent::is_tag_closer(); } + /** + * Indicates if the currently-matched token is virtual, created by a stack operation + * while processing HTML, rather than a token found in the HTML text itself. + * + * @since 6.6.0 + * + * @return bool Whether the current token is virtual. + */ + private function is_virtual(): bool { + return ( + isset( $this->current_element->provenance ) && + 'virtual' === $this->current_element->provenance + ); + } + /** * Indicates if the currently-matched tag matches the given breadcrumbs. * @@ -599,7 +735,7 @@ public function is_tag_closer() { * May also contain the wildcard `*` which matches a single element, e.g. `array( 'SECTION', '*' )`. * @return bool Whether the currently-matched tag is found at the given nested structure. */ - public function matches_breadcrumbs( $breadcrumbs ) { + public function matches_breadcrumbs( $breadcrumbs ): bool { // Everything matches when there are zero constraints. if ( 0 === count( $breadcrumbs ) ) { return true; @@ -612,10 +748,11 @@ public function matches_breadcrumbs( $breadcrumbs ) { return false; } - foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { + for ( $i = count( $this->breadcrumbs ) - 1; $i >= 0; $i-- ) { + $node = $this->breadcrumbs[ $i ]; $crumb = strtoupper( current( $breadcrumbs ) ); - if ( '*' !== $crumb && $node->node_name !== $crumb ) { + if ( '*' !== $crumb && $node !== $crumb ) { return false; } @@ -640,20 +777,21 @@ public function matches_breadcrumbs( $breadcrumbs ) { * * @since 6.6.0 * - * @todo When adding support for foreign content, ensure that - * this returns false for self-closing elements in the - * SVG and MathML namespace. - * - * @param ?WP_HTML_Token $node Node to examine instead of current node, if provided. - * @return bool Whether to expect a closer for the currently-matched node, - * or `null` if not matched on any token. + * @param WP_HTML_Token|null $node Optional. Node to examine, if provided. + * Default is to examine current node. + * @return bool|null Whether to expect a closer for the currently-matched node, + * or `null` if not matched on any token. */ - public function expects_closer( $node = null ) { + public function expects_closer( WP_HTML_Token $node = null ): ?bool { $token_name = $node->node_name ?? $this->get_token_name(); + if ( ! isset( $token_name ) ) { return null; } + $token_namespace = $node->namespace ?? $this->get_namespace(); + $token_has_self_closing = $node->has_self_closing_flag ?? $this->has_self_closing_flag(); + return ! ( // Comments, text nodes, and other atomic tokens. '#' === $token_name[0] || @@ -662,7 +800,9 @@ public function expects_closer( $node = null ) { // Void elements. self::is_void( $token_name ) || // Special atomic elements. - in_array( $token_name, array( 'IFRAME', 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP' ), true ) + ( 'html' === $token_namespace && in_array( $token_name, array( 'IFRAME', 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP' ), true ) ) || + // Self-closing elements in foreign content. + ( 'html' !== $token_namespace && $token_has_self_closing ) ); } @@ -679,7 +819,7 @@ public function expects_closer( $node = null ) { * @param string $node_to_process Whether to parse the next node or reprocess the current node. * @return bool Whether a tag was matched. */ - public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { + public function step( $node_to_process = self::PROCESS_NEXT_NODE ): bool { // Refuse to proceed if there was a previous error. if ( null !== $this->last_error ) { return false; @@ -694,20 +834,21 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { * * When moving on to the next node, therefore, if the bottom-most element * on the stack is a void element, it must be closed. - * - * @todo Once self-closing foreign elements and BGSOUND are supported, - * they must also be implicitly closed here too. BGSOUND is - * special since it's only self-closing if the self-closing flag - * is provided in the opening tag, otherwise it expects a tag closer. */ $top_node = $this->state->stack_of_open_elements->current_node(); - if ( isset( $top_node ) && ! static::expects_closer( $top_node ) ) { + if ( isset( $top_node ) && ! $this->expects_closer( $top_node ) ) { $this->state->stack_of_open_elements->pop(); } } if ( self::PROCESS_NEXT_NODE === $node_to_process ) { parent::next_token(); + if ( + WP_HTML_Tag_Processor::STATE_TEXT_NODE === $this->parser_state || + WP_HTML_Tag_Processor::STATE_CDATA_NODE === $this->parser_state + ) { + parent::subdivide_text_appropriately(); + } } // Finish stepping when there are no more tokens in the document. @@ -718,21 +859,116 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { return false; } - $this->state->current_token = new WP_HTML_Token( - $this->bookmark_token(), - $this->get_token_name(), - $this->has_self_closing_flag(), - $this->release_internal_bookmark_on_destruct + $adjusted_current_node = $this->get_adjusted_current_node(); + $is_closer = $this->is_tag_closer(); + $is_start_tag = WP_HTML_Tag_Processor::STATE_MATCHED_TAG === $this->parser_state && ! $is_closer; + $token_name = $this->get_token_name(); + + if ( self::REPROCESS_CURRENT_NODE !== $node_to_process ) { + $this->state->current_token = new WP_HTML_Token( + $this->bookmark_token(), + $token_name, + $this->has_self_closing_flag(), + $this->release_internal_bookmark_on_destruct + ); + } + + $parse_in_current_insertion_mode = ( + 0 === $this->state->stack_of_open_elements->count() || + 'html' === $adjusted_current_node->namespace || + ( + 'math' === $adjusted_current_node->integration_node_type && + ( + ( $is_start_tag && ! in_array( $token_name, array( 'MGLYPH', 'MALIGNMARK' ), true ) ) || + '#text' === $token_name + ) + ) || + ( + 'math' === $adjusted_current_node->namespace && + 'ANNOTATION-XML' === $adjusted_current_node->node_name && + $is_start_tag && 'SVG' === $token_name + ) || + ( + 'html' === $adjusted_current_node->integration_node_type && + ( $is_start_tag || '#text' === $token_name ) + ) ); try { + if ( ! $parse_in_current_insertion_mode ) { + return $this->step_in_foreign_content(); + } + switch ( $this->state->insertion_mode ) { + case WP_HTML_Processor_State::INSERTION_MODE_INITIAL: + return $this->step_initial(); + + case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML: + return $this->step_before_html(); + + case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD: + return $this->step_before_head(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD: + return $this->step_in_head(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD_NOSCRIPT: + return $this->step_in_head_noscript(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD: + return $this->step_after_head(); + case WP_HTML_Processor_State::INSERTION_MODE_IN_BODY: return $this->step_in_body(); + case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE: + return $this->step_in_table(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_TEXT: + return $this->step_in_table_text(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION: + return $this->step_in_caption(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP: + return $this->step_in_column_group(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY: + return $this->step_in_table_body(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_ROW: + return $this->step_in_row(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL: + return $this->step_in_cell(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT: + return $this->step_in_select(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE: + return $this->step_in_select_in_table(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE: + return $this->step_in_template(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY: + return $this->step_after_body(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET: + return $this->step_in_frameset(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_FRAMESET: + return $this->step_after_frameset(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_BODY: + return $this->step_after_after_body(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_FRAMESET: + return $this->step_after_after_frameset(); + + // This should be unreachable but PHP doesn't have total type checking on switch. default: - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( "No support for parsing in the '{$this->state->insertion_mode}' state." ); + $this->bail( "Unaware of the requested parsing mode: '{$this->state->insertion_mode}'." ); } } catch ( WP_HTML_Unsupported_Exception $e ) { /* @@ -761,17 +997,10 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ) { * * @since 6.4.0 * - * @todo make aware of queue of elements, because stack operations have already been done by now. - * * @return string[]|null Array of tag names representing path to matched node, if matched, otherwise NULL. */ - public function get_breadcrumbs() { - $breadcrumbs = array(); - foreach ( $this->state->stack_of_open_elements->walk_down() as $stack_item ) { - $breadcrumbs[] = $stack_item->node_name; - } - - return $breadcrumbs; + public function get_breadcrumbs(): ?array { + return $this->breadcrumbs; } /** @@ -799,590 +1028,3607 @@ public function get_breadcrumbs() { * * @return int Nesting-depth of current location in the document. */ - public function get_current_depth() { - return $this->state->stack_of_open_elements->count(); + public function get_current_depth(): int { + return count( $this->breadcrumbs ); } /** - * Parses next element in the 'in body' insertion mode. + * Parses next element in the 'initial' insertion mode. * - * This internal function performs the 'in body' insertion mode + * This internal function performs the 'initial' insertion mode * logic for the generalized WP_HTML_Processor::step() function. * - * @since 6.4.0 + * @since 6.7.0 * * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. * - * @see https://html.spec.whatwg.org/#parsing-main-inbody + * @see https://html.spec.whatwg.org/#the-initial-insertion-mode * @see WP_HTML_Processor::step * * @return bool Whether an element was found. */ - private function step_in_body() { + private function step_initial(): bool { $token_name = $this->get_token_name(); $token_type = $this->get_token_type(); $op_sigil = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : ''; $op = "{$op_sigil}{$token_name}"; switch ( $op ) { + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, + * > U+000A LINE FEED (LF), U+000C FORM FEED (FF), + * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * + * Parse error: ignore the token. + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + return $this->step(); + } + goto initial_anything_else; + break; + + /* + * > A comment token + */ case '#comment': case '#funky-comment': case '#presumptuous-tag': $this->insert_html_element( $this->state->current_token ); return true; - case '#text': - $this->reconstruct_active_formatting_elements(); - - $current_token = $this->bookmarks[ $this->state->current_token->bookmark_name ]; - - /* - * > A character token that is U+0000 NULL - * - * Any successive sequence of NULL bytes is ignored and won't - * trigger active format reconstruction. Therefore, if the text - * only comprises NULL bytes then the token should be ignored - * here, but if there are any other characters in the stream - * the active formats should be reconstructed. - */ - if ( - 1 <= $current_token->length && - "\x00" === $this->html[ $current_token->start ] && - strspn( $this->html, "\x00", $current_token->start, $current_token->length ) === $current_token->length - ) { - // Parse error: ignore the token. - return $this->step(); + /* + * > A DOCTYPE token + */ + case 'html': + $doctype = $this->get_doctype_info(); + if ( null !== $doctype && 'quirks' === $doctype->indicated_compatability_mode ) { + $this->state->document_mode = WP_HTML_Processor_State::QUIRKS_MODE; } /* - * Whitespace-only text does not affect the frameset-ok flag. - * It is probably inter-element whitespace, but it may also - * contain character references which decode only to whitespace. + * > Then, switch the insertion mode to "before html". */ - $text = $this->get_modifiable_text(); - if ( strlen( $text ) !== strspn( $text, " \t\n\f\r" ) ) { - $this->state->frameset_ok = false; - } - + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML; $this->insert_html_element( $this->state->current_token ); return true; + } + + /* + * > Anything else + */ + initial_anything_else: + $this->state->document_mode = WP_HTML_Processor_State::QUIRKS_MODE; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'before html' insertion mode. + * + * This internal function performs the 'before html' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-before-html-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_before_html(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $is_closer = parent::is_tag_closer(); + $op_sigil = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + switch ( $op ) { + /* + * > A DOCTYPE token + */ case 'html': - /* - * > A DOCTYPE token - * > Parse error. Ignore the token. - */ + // Parse error: ignore the token. return $this->step(); /* - * > A start tag whose tag name is "button" + * > A comment token */ - case '+BUTTON': - if ( $this->state->stack_of_open_elements->has_element_in_scope( 'BUTTON' ) ) { - // @todo Indicate a parse error once it's possible. This error does not impact the logic here. - $this->generate_implied_end_tags(); - $this->state->stack_of_open_elements->pop_until( 'BUTTON' ); - } - - $this->reconstruct_active_formatting_elements(); + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': $this->insert_html_element( $this->state->current_token ); - $this->state->frameset_ok = false; - return true; /* - * > A start tag whose tag name is one of: "address", "article", "aside", - * > "blockquote", "center", "details", "dialog", "dir", "div", "dl", - * > "fieldset", "figcaption", "figure", "footer", "header", "hgroup", - * > "main", "menu", "nav", "ol", "p", "search", "section", "summary", "ul" + * > A character token that is one of U+0009 CHARACTER TABULATION, + * > U+000A LINE FEED (LF), U+000C FORM FEED (FF), + * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * + * Parse error: ignore the token. */ - case '+ADDRESS': - case '+ARTICLE': - case '+ASIDE': - case '+BLOCKQUOTE': - case '+CENTER': - case '+DETAILS': - case '+DIALOG': - case '+DIR': - case '+DIV': - case '+DL': - case '+FIELDSET': - case '+FIGCAPTION': - case '+FIGURE': - case '+FOOTER': - case '+HEADER': - case '+HGROUP': - case '+MAIN': - case '+MENU': - case '+NAV': - case '+OL': - case '+P': - case '+SEARCH': - case '+SECTION': - case '+SUMMARY': - case '+UL': - if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { - $this->close_a_p_element(); + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + return $this->step(); } + goto before_html_anything_else; + break; + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': $this->insert_html_element( $this->state->current_token ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD; return true; /* - * > An end tag whose tag name is one of: "address", "article", "aside", "blockquote", - * > "button", "center", "details", "dialog", "dir", "div", "dl", "fieldset", - * > "figcaption", "figure", "footer", "header", "hgroup", "listing", "main", - * > "menu", "nav", "ol", "pre", "search", "section", "summary", "ul" + * > An end tag whose tag name is one of: "head", "body", "html", "br" + * + * Closing BR tags are always reported by the Tag Processor as opening tags. */ - case '-ADDRESS': - case '-ARTICLE': - case '-ASIDE': - case '-BLOCKQUOTE': - case '-BUTTON': - case '-CENTER': - case '-DETAILS': - case '-DIALOG': - case '-DIR': - case '-DIV': - case '-DL': - case '-FIELDSET': - case '-FIGCAPTION': - case '-FIGURE': - case '-FOOTER': - case '-HEADER': - case '-HGROUP': - case '-LISTING': - case '-MAIN': - case '-MENU': - case '-NAV': - case '-OL': - case '-PRE': - case '-SEARCH': - case '-SECTION': - case '-SUMMARY': - case '-UL': - if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) ) { - // @todo Report parse error. - // Ignore the token. + case '-HEAD': + case '-BODY': + case '-HTML': + /* + * > Act as described in the "anything else" entry below. + */ + goto before_html_anything_else; + break; + } + + /* + * > Any other end tag + */ + if ( $is_closer ) { + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Anything else. + * + * > Create an html element whose node document is the Document object. + * > Append it to the Document object. Put this element in the stack of open elements. + * > Switch the insertion mode to "before head", then reprocess the token. + */ + before_html_anything_else: + $this->insert_virtual_node( 'HTML' ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'before head' insertion mode. + * + * This internal function performs the 'before head' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-before-head-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_before_head(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $is_closer = parent::is_tag_closer(); + $op_sigil = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, + * > U+000A LINE FEED (LF), U+000C FORM FEED (FF), + * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * + * Parse error: ignore the token. + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { return $this->step(); } + goto before_head_anything_else; + break; - $this->generate_implied_end_tags(); - if ( $this->state->stack_of_open_elements->current_node()->node_name !== $token_name ) { - // @todo Record parse error: this error doesn't impact parsing. - } - $this->state->stack_of_open_elements->pop_until( $token_name ); + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); return true; /* - * > A start tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" + * > A DOCTYPE token */ - case '+H1': - case '+H2': - case '+H3': - case '+H4': - case '+H5': - case '+H6': - if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { - $this->close_a_p_element(); - } + case 'html': + // Parse error: ignore the token. + return $this->step(); - if ( - in_array( - $this->state->stack_of_open_elements->current_node()->node_name, - array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), - true - ) - ) { - // @todo Indicate a parse error once it's possible. - $this->state->stack_of_open_elements->pop(); - } + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + /* + * > A start tag whose tag name is "head" + */ + case '+HEAD': $this->insert_html_element( $this->state->current_token ); + $this->state->head_element = $this->state->current_token; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD; return true; /* - * > A start tag whose tag name is one of: "pre", "listing" + * > An end tag whose tag name is one of: "head", "body", "html", "br" + * > Act as described in the "anything else" entry below. + * + * Closing BR tags are always reported by the Tag Processor as opening tags. */ - case '+PRE': - case '+LISTING': - if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { - $this->close_a_p_element(); + case '-HEAD': + case '-BODY': + case '-HTML': + goto before_head_anything_else; + break; + } + + if ( $is_closer ) { + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Anything else + * + * > Insert an HTML element for a "head" start tag token with no attributes. + */ + before_head_anything_else: + $this->state->head_element = $this->insert_virtual_node( 'HEAD' ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'in head' insertion mode. + * + * This internal function performs the 'in head' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_head(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $is_closer = parent::is_tag_closer(); + $op_sigil = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + case '#text': + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, + * > U+000A LINE FEED (LF), U+000C FORM FEED (FF), + * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + */ + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + // Insert the character. + $this->insert_html_element( $this->state->current_token ); + return true; } + + goto in_head_anything_else; + break; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': $this->insert_html_element( $this->state->current_token ); - $this->state->frameset_ok = false; return true; /* - * > An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" + * > A DOCTYPE token */ - case '-H1': - case '-H2': - case '-H3': - case '-H4': - case '-H5': - case '-H6': - if ( ! $this->state->stack_of_open_elements->has_element_in_scope( '(internal: H1 through H6 - do not use)' ) ) { - /* - * This is a parse error; ignore the token. - * - * @todo Indicate a parse error once it's possible. - */ - return $this->step(); - } - - $this->generate_implied_end_tags(); + case 'html': + // Parse error: ignore the token. + return $this->step(); - if ( $this->state->stack_of_open_elements->current_node()->node_name !== $token_name ) { - // @todo Record parse error: this error doesn't impact parsing. - } + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); - $this->state->stack_of_open_elements->pop_until( '(internal: H1 through H6 - do not use)' ); + /* + * > A start tag whose tag name is one of: "base", "basefont", "bgsound", "link" + */ + case '+BASE': + case '+BASEFONT': + case '+BGSOUND': + case '+LINK': + $this->insert_html_element( $this->state->current_token ); return true; /* - * > A start tag whose tag name is "li" - * > A start tag whose tag name is one of: "dd", "dt" + * > A start tag whose tag name is "meta" */ - case '+DD': - case '+DT': - case '+LI': - $this->state->frameset_ok = false; - $node = $this->state->stack_of_open_elements->current_node(); - $is_li = 'LI' === $token_name; + case '+META': + $this->insert_html_element( $this->state->current_token ); - in_body_list_loop: /* - * The logic for LI and DT/DD is the same except for one point: LI elements _only_ - * close other LI elements, but a DT or DD element closes _any_ open DT or DD element. + * > If the active speculative HTML parser is null, then: + * > - If the element has a charset attribute, and getting an encoding from + * > its value results in an encoding, and the confidence is currently + * > tentative, then change the encoding to the resulting encoding. */ - if ( $is_li ? 'LI' === $node->node_name : ( 'DD' === $node->node_name || 'DT' === $node->node_name ) ) { - $node_name = $is_li ? 'LI' : $node->node_name; - $this->generate_implied_end_tags( $node_name ); - if ( $node_name !== $this->state->stack_of_open_elements->current_node()->node_name ) { - // @todo Indicate a parse error once it's possible. This error does not impact the logic here. - } - - $this->state->stack_of_open_elements->pop_until( $node_name ); - goto in_body_list_done; + $charset = $this->get_attribute( 'charset' ); + if ( is_string( $charset ) && 'tentative' === $this->state->encoding_confidence ) { + $this->bail( 'Cannot yet process META tags with charset to determine encoding.' ); } + /* + * > - Otherwise, if the element has an http-equiv attribute whose value is + * > an ASCII case-insensitive match for the string "Content-Type", and + * > the element has a content attribute, and applying the algorithm for + * > extracting a character encoding from a meta element to that attribute's + * > value returns an encoding, and the confidence is currently tentative, + * > then change the encoding to the extracted encoding. + */ + $http_equiv = $this->get_attribute( 'http-equiv' ); + $content = $this->get_attribute( 'content' ); if ( - 'ADDRESS' !== $node->node_name && - 'DIV' !== $node->node_name && - 'P' !== $node->node_name && - $this->is_special( $node->node_name ) + is_string( $http_equiv ) && + is_string( $content ) && + 0 === strcasecmp( $http_equiv, 'Content-Type' ) && + 'tentative' === $this->state->encoding_confidence ) { - /* - * > If node is in the special category, but is not an address, div, - * > or p element, then jump to the step labeled done below. - */ - goto in_body_list_done; - } else { - /* - * > Otherwise, set node to the previous entry in the stack of open elements - * > and return to the step labeled loop. - */ - foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) { - $node = $item; - break; - } - goto in_body_list_loop; + $this->bail( 'Cannot yet process META tags with http-equiv Content-Type to determine encoding.' ); } - in_body_list_done: - if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { - $this->close_a_p_element(); - } + return true; + /* + * > A start tag whose tag name is "title" + */ + case '+TITLE': $this->insert_html_element( $this->state->current_token ); return true; /* - * > An end tag whose tag name is "li" - * > An end tag whose tag name is one of: "dd", "dt" + * > A start tag whose tag name is "noscript", if the scripting flag is enabled + * > A start tag whose tag name is one of: "noframes", "style" + * + * The scripting flag is never enabled in this parser. */ - case '-DD': - case '-DT': - case '-LI': - if ( - /* - * An end tag whose tag name is "li": - * If the stack of open elements does not have an li element in list item scope, - * then this is a parse error; ignore the token. - */ - ( - 'LI' === $token_name && - ! $this->state->stack_of_open_elements->has_element_in_list_item_scope( 'LI' ) - ) || - /* - * An end tag whose tag name is one of: "dd", "dt": - * If the stack of open elements does not have an element in scope that is an - * HTML element with the same tag name as that of the token, then this is a - * parse error; ignore the token. - */ - ( - 'LI' !== $token_name && - ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) - ) - ) { - /* - * This is a parse error, ignore the token. - * - * @todo Indicate a parse error once it's possible. - */ - return $this->step(); - } + case '+NOFRAMES': + case '+STYLE': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "noscript", if the scripting flag is disabled + */ + case '+NOSCRIPT': + $this->insert_html_element( $this->state->current_token ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD_NOSCRIPT; + return true; + + /* + * > A start tag whose tag name is "script" + * + * @todo Could the adjusted insertion location be anything other than the current location? + */ + case '+SCRIPT': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > An end tag whose tag name is "head" + */ + case '-HEAD': + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD; + return true; + + /* + * > An end tag whose tag name is one of: "body", "html", "br" + * + * BR tags are always reported by the Tag Processor as opening tags. + */ + case '-BODY': + case '-HTML': + /* + * > Act as described in the "anything else" entry below. + */ + goto in_head_anything_else; + break; + + /* + * > A start tag whose tag name is "template" + * + * @todo Could the adjusted insertion location be anything other than the current location? + */ + case '+TEMPLATE': + $this->state->active_formatting_elements->insert_marker(); + $this->state->frameset_ok = false; + + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE; + $this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE; + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > An end tag whose tag name is "template" + */ + case '-TEMPLATE': + if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) { + // @todo Indicate a parse error once it's possible. + return $this->step(); + } + + $this->generate_implied_end_tags_thoroughly(); + if ( ! $this->state->stack_of_open_elements->current_node_is( 'TEMPLATE' ) ) { + // @todo Indicate a parse error once it's possible. + } + + $this->state->stack_of_open_elements->pop_until( 'TEMPLATE' ); + $this->state->active_formatting_elements->clear_up_to_last_marker(); + array_pop( $this->state->stack_of_template_insertion_modes ); + $this->reset_insertion_mode_appropriately(); + return true; + } + + /* + * > A start tag whose tag name is "head" + * > Any other end tag + */ + if ( '+HEAD' === $op || $is_closer ) { + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Anything else + */ + in_head_anything_else: + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'in head noscript' insertion mode. + * + * This internal function performs the 'in head noscript' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inheadnoscript + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_head_noscript(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $is_closer = parent::is_tag_closer(); + $op_sigil = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, + * > U+000A LINE FEED (LF), U+000C FORM FEED (FF), + * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * + * Parse error: ignore the token. + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + return $this->step_in_head(); + } + + goto in_head_noscript_anything_else; + break; + + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + + /* + * > An end tag whose tag name is "noscript" + */ + case '-NOSCRIPT': + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD; + return true; + + /* + * > A comment token + * > + * > A start tag whose tag name is one of: "basefont", "bgsound", + * > "link", "meta", "noframes", "style" + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + case '+BASEFONT': + case '+BGSOUND': + case '+LINK': + case '+META': + case '+NOFRAMES': + case '+STYLE': + return $this->step_in_head(); + + /* + * > An end tag whose tag name is "br" + * + * This should never happen, as the Tag Processor prevents showing a BR closing tag. + */ + } + + /* + * > A start tag whose tag name is one of: "head", "noscript" + * > Any other end tag + */ + if ( '+HEAD' === $op || '+NOSCRIPT' === $op || $is_closer ) { + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Anything else + * + * Anything here is a parse error. + */ + in_head_noscript_anything_else: + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'after head' insertion mode. + * + * This internal function performs the 'after head' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-after-head-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_head(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $is_closer = parent::is_tag_closer(); + $op_sigil = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, + * > U+000A LINE FEED (LF), U+000C FORM FEED (FF), + * > U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + // Insert the character. + $this->insert_html_element( $this->state->current_token ); + return true; + } + goto after_head_anything_else; + break; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + + /* + * > A start tag whose tag name is "body" + */ + case '+BODY': + $this->insert_html_element( $this->state->current_token ); + $this->state->frameset_ok = false; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + return true; + + /* + * > A start tag whose tag name is "frameset" + */ + case '+FRAMESET': + $this->insert_html_element( $this->state->current_token ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET; + return true; + + /* + * > A start tag whose tag name is one of: "base", "basefont", "bgsound", + * > "link", "meta", "noframes", "script", "style", "template", "title" + * + * Anything here is a parse error. + */ + case '+BASE': + case '+BASEFONT': + case '+BGSOUND': + case '+LINK': + case '+META': + case '+NOFRAMES': + case '+SCRIPT': + case '+STYLE': + case '+TEMPLATE': + case '+TITLE': + /* + * > Push the node pointed to by the head element pointer onto the stack of open elements. + * > Process the token using the rules for the "in head" insertion mode. + * > Remove the node pointed to by the head element pointer from the stack of open elements. (It might not be the current node at this point.) + */ + $this->bail( 'Cannot process elements after HEAD which reopen the HEAD element.' ); + /* + * Do not leave this break in when adding support; it's here to prevent + * WPCS from getting confused at the switch structure without a return, + * because it doesn't know that `bail()` always throws. + */ + break; + + /* + * > An end tag whose tag name is "template" + */ + case '-TEMPLATE': + return $this->step_in_head(); + + /* + * > An end tag whose tag name is one of: "body", "html", "br" + * + * Closing BR tags are always reported by the Tag Processor as opening tags. + */ + case '-BODY': + case '-HTML': + /* + * > Act as described in the "anything else" entry below. + */ + goto after_head_anything_else; + break; + } + + /* + * > A start tag whose tag name is "head" + * > Any other end tag + */ + if ( '+HEAD' === $op || $is_closer ) { + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Anything else + * > Insert an HTML element for a "body" start tag token with no attributes. + */ + after_head_anything_else: + $this->insert_virtual_node( 'BODY' ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'in body' insertion mode. + * + * This internal function performs the 'in body' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.4.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inbody + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_body(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + case '#text': + /* + * > A character token that is U+0000 NULL + * + * Any successive sequence of NULL bytes is ignored and won't + * trigger active format reconstruction. Therefore, if the text + * only comprises NULL bytes then the token should be ignored + * here, but if there are any other characters in the stream + * the active formats should be reconstructed. + */ + if ( parent::TEXT_IS_NULL_SEQUENCE === $this->text_node_classification ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->reconstruct_active_formatting_elements(); + + /* + * Whitespace-only text does not affect the frameset-ok flag. + * It is probably inter-element whitespace, but it may also + * contain character references which decode only to whitespace. + */ + if ( parent::TEXT_IS_GENERIC === $this->text_node_classification ) { + $this->state->frameset_ok = false; + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A DOCTYPE token + * > Parse error. Ignore the token. + */ + case 'html': + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) { + /* + * > Otherwise, for each attribute on the token, check to see if the attribute + * > is already present on the top element of the stack of open elements. If + * > it is not, add the attribute and its corresponding value to that element. + * + * This parser does not currently support this behavior: ignore the token. + */ + } + + // Ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is one of: "base", "basefont", "bgsound", "link", + * > "meta", "noframes", "script", "style", "template", "title" + * > + * > An end tag whose tag name is "template" + */ + case '+BASE': + case '+BASEFONT': + case '+BGSOUND': + case '+LINK': + case '+META': + case '+NOFRAMES': + case '+SCRIPT': + case '+STYLE': + case '+TEMPLATE': + case '+TITLE': + case '-TEMPLATE': + return $this->step_in_head(); + + /* + * > A start tag whose tag name is "body" + * + * This tag in the IN BODY insertion mode is a parse error. + */ + case '+BODY': + if ( + 1 === $this->state->stack_of_open_elements->count() || + 'BODY' !== ( $this->state->stack_of_open_elements->at( 2 )->node_name ?? null ) || + $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) + ) { + // Ignore the token. + return $this->step(); + } + + /* + * > Otherwise, set the frameset-ok flag to "not ok"; then, for each attribute + * > on the token, check to see if the attribute is already present on the body + * > element (the second element) on the stack of open elements, and if it is + * > not, add the attribute and its corresponding value to that element. + * + * This parser does not currently support this behavior: ignore the token. + */ + $this->state->frameset_ok = false; + return $this->step(); + + /* + * > A start tag whose tag name is "frameset" + * + * This tag in the IN BODY insertion mode is a parse error. + */ + case '+FRAMESET': + if ( + 1 === $this->state->stack_of_open_elements->count() || + 'BODY' !== ( $this->state->stack_of_open_elements->at( 2 )->node_name ?? null ) || + false === $this->state->frameset_ok + ) { + // Ignore the token. + return $this->step(); + } + + /* + * > Otherwise, run the following steps: + */ + $this->bail( 'Cannot process non-ignored FRAMESET tags.' ); + break; + + /* + * > An end tag whose tag name is "body" + */ + case '-BODY': + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'BODY' ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Otherwise, if there is a node in the stack of open elements that is not either a + * > dd element, a dt element, an li element, an optgroup element, an option element, + * > a p element, an rb element, an rp element, an rt element, an rtc element, a tbody + * > element, a td element, a tfoot element, a th element, a thread element, a tr + * > element, the body element, or the html element, then this is a parse error. + * + * There is nothing to do for this parse error, so don't check for it. + */ + + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY; + return true; + + /* + * > An end tag whose tag name is "html" + */ + case '-HTML': + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'BODY' ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Otherwise, if there is a node in the stack of open elements that is not either a + * > dd element, a dt element, an li element, an optgroup element, an option element, + * > a p element, an rb element, an rp element, an rt element, an rtc element, a tbody + * > element, a td element, a tfoot element, a th element, a thread element, a tr + * > element, the body element, or the html element, then this is a parse error. + * + * There is nothing to do for this parse error, so don't check for it. + */ + + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > A start tag whose tag name is one of: "address", "article", "aside", + * > "blockquote", "center", "details", "dialog", "dir", "div", "dl", + * > "fieldset", "figcaption", "figure", "footer", "header", "hgroup", + * > "main", "menu", "nav", "ol", "p", "search", "section", "summary", "ul" + */ + case '+ADDRESS': + case '+ARTICLE': + case '+ASIDE': + case '+BLOCKQUOTE': + case '+CENTER': + case '+DETAILS': + case '+DIALOG': + case '+DIR': + case '+DIV': + case '+DL': + case '+FIELDSET': + case '+FIGCAPTION': + case '+FIGURE': + case '+FOOTER': + case '+HEADER': + case '+HGROUP': + case '+MAIN': + case '+MENU': + case '+NAV': + case '+OL': + case '+P': + case '+SEARCH': + case '+SECTION': + case '+SUMMARY': + case '+UL': + if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->close_a_p_element(); + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" + */ + case '+H1': + case '+H2': + case '+H3': + case '+H4': + case '+H5': + case '+H6': + if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->close_a_p_element(); + } + + if ( + in_array( + $this->state->stack_of_open_elements->current_node()->node_name, + array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), + true + ) + ) { + // @todo Indicate a parse error once it's possible. + $this->state->stack_of_open_elements->pop(); + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is one of: "pre", "listing" + */ + case '+PRE': + case '+LISTING': + if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->close_a_p_element(); + } + + /* + * > If the next token is a U+000A LINE FEED (LF) character token, + * > then ignore that token and move on to the next one. (Newlines + * > at the start of pre blocks are ignored as an authoring convenience.) + * + * This is handled in `get_modifiable_text()`. + */ + + $this->insert_html_element( $this->state->current_token ); + $this->state->frameset_ok = false; + return true; + + /* + * > A start tag whose tag name is "form" + */ + case '+FORM': + $stack_contains_template = $this->state->stack_of_open_elements->contains( 'TEMPLATE' ); + + if ( isset( $this->state->form_element ) && ! $stack_contains_template ) { + // Parse error: ignore the token. + return $this->step(); + } + + if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->close_a_p_element(); + } + + $this->insert_html_element( $this->state->current_token ); + if ( ! $stack_contains_template ) { + $this->state->form_element = $this->state->current_token; + } + + return true; + + /* + * > A start tag whose tag name is "li" + * > A start tag whose tag name is one of: "dd", "dt" + */ + case '+DD': + case '+DT': + case '+LI': + $this->state->frameset_ok = false; + $node = $this->state->stack_of_open_elements->current_node(); + $is_li = 'LI' === $token_name; + + in_body_list_loop: + /* + * The logic for LI and DT/DD is the same except for one point: LI elements _only_ + * close other LI elements, but a DT or DD element closes _any_ open DT or DD element. + */ + if ( $is_li ? 'LI' === $node->node_name : ( 'DD' === $node->node_name || 'DT' === $node->node_name ) ) { + $node_name = $is_li ? 'LI' : $node->node_name; + $this->generate_implied_end_tags( $node_name ); + if ( ! $this->state->stack_of_open_elements->current_node_is( $node_name ) ) { + // @todo Indicate a parse error once it's possible. This error does not impact the logic here. + } + + $this->state->stack_of_open_elements->pop_until( $node_name ); + goto in_body_list_done; + } + + if ( + 'ADDRESS' !== $node->node_name && + 'DIV' !== $node->node_name && + 'P' !== $node->node_name && + self::is_special( $node ) + ) { + /* + * > If node is in the special category, but is not an address, div, + * > or p element, then jump to the step labeled done below. + */ + goto in_body_list_done; + } else { + /* + * > Otherwise, set node to the previous entry in the stack of open elements + * > and return to the step labeled loop. + */ + foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) { + $node = $item; + break; + } + goto in_body_list_loop; + } + + in_body_list_done: + if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->close_a_p_element(); + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + case '+PLAINTEXT': + if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->close_a_p_element(); + } + + /* + * @todo This may need to be handled in the Tag Processor and turn into + * a single self-contained tag like TEXTAREA, whose modifiable text + * is the rest of the input document as plaintext. + */ + $this->bail( 'Cannot process PLAINTEXT elements.' ); + break; + + /* + * > A start tag whose tag name is "button" + */ + case '+BUTTON': + if ( $this->state->stack_of_open_elements->has_element_in_scope( 'BUTTON' ) ) { + // @todo Indicate a parse error once it's possible. This error does not impact the logic here. + $this->generate_implied_end_tags(); + $this->state->stack_of_open_elements->pop_until( 'BUTTON' ); + } + + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + $this->state->frameset_ok = false; + + return true; + + /* + * > An end tag whose tag name is one of: "address", "article", "aside", "blockquote", + * > "button", "center", "details", "dialog", "dir", "div", "dl", "fieldset", + * > "figcaption", "figure", "footer", "header", "hgroup", "listing", "main", + * > "menu", "nav", "ol", "pre", "search", "section", "summary", "ul" + */ + case '-ADDRESS': + case '-ARTICLE': + case '-ASIDE': + case '-BLOCKQUOTE': + case '-BUTTON': + case '-CENTER': + case '-DETAILS': + case '-DIALOG': + case '-DIR': + case '-DIV': + case '-DL': + case '-FIELDSET': + case '-FIGCAPTION': + case '-FIGURE': + case '-FOOTER': + case '-HEADER': + case '-HGROUP': + case '-LISTING': + case '-MAIN': + case '-MENU': + case '-NAV': + case '-OL': + case '-PRE': + case '-SEARCH': + case '-SECTION': + case '-SUMMARY': + case '-UL': + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) ) { + // @todo Report parse error. + // Ignore the token. + return $this->step(); + } + + $this->generate_implied_end_tags(); + if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) { + // @todo Record parse error: this error doesn't impact parsing. + } + $this->state->stack_of_open_elements->pop_until( $token_name ); + return true; + + /* + * > An end tag whose tag name is "form" + */ + case '-FORM': + if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) { + $node = $this->state->form_element; + $this->state->form_element = null; + + /* + * > If node is null or if the stack of open elements does not have node + * > in scope, then this is a parse error; return and ignore the token. + * + * @todo It's necessary to check if the form token itself is in scope, not + * simply whether any FORM is in scope. + */ + if ( + null === $node || + ! $this->state->stack_of_open_elements->has_element_in_scope( 'FORM' ) + ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->generate_implied_end_tags(); + if ( $node !== $this->state->stack_of_open_elements->current_node() ) { + // @todo Indicate a parse error once it's possible. This error does not impact the logic here. + $this->bail( 'Cannot close a FORM when other elements remain open as this would throw off the breadcrumbs for the following tokens.' ); + } + + $this->state->stack_of_open_elements->remove_node( $node ); + } else { + /* + * > If the stack of open elements does not have a form element in scope, + * > then this is a parse error; return and ignore the token. + * + * Note that unlike in the clause above, this is checking for any FORM in scope. + */ + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( 'FORM' ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->generate_implied_end_tags(); + + if ( ! $this->state->stack_of_open_elements->current_node_is( 'FORM' ) ) { + // @todo Indicate a parse error once it's possible. This error does not impact the logic here. + } + + $this->state->stack_of_open_elements->pop_until( 'FORM' ); + return true; + } + break; + + /* + * > An end tag whose tag name is "p" + */ + case '-P': + if ( ! $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->insert_html_element( $this->state->current_token ); + } + + $this->close_a_p_element(); + return true; + + /* + * > An end tag whose tag name is "li" + * > An end tag whose tag name is one of: "dd", "dt" + */ + case '-DD': + case '-DT': + case '-LI': + if ( + /* + * An end tag whose tag name is "li": + * If the stack of open elements does not have an li element in list item scope, + * then this is a parse error; ignore the token. + */ + ( + 'LI' === $token_name && + ! $this->state->stack_of_open_elements->has_element_in_list_item_scope( 'LI' ) + ) || + /* + * An end tag whose tag name is one of: "dd", "dt": + * If the stack of open elements does not have an element in scope that is an + * HTML element with the same tag name as that of the token, then this is a + * parse error; ignore the token. + */ + ( + 'LI' !== $token_name && + ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) + ) + ) { + /* + * This is a parse error, ignore the token. + * + * @todo Indicate a parse error once it's possible. + */ + return $this->step(); + } + + $this->generate_implied_end_tags( $token_name ); + + if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) { + // @todo Indicate a parse error once it's possible. This error does not impact the logic here. + } + + $this->state->stack_of_open_elements->pop_until( $token_name ); + return true; + + /* + * > An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" + */ + case '-H1': + case '-H2': + case '-H3': + case '-H4': + case '-H5': + case '-H6': + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( '(internal: H1 through H6 - do not use)' ) ) { + /* + * This is a parse error; ignore the token. + * + * @todo Indicate a parse error once it's possible. + */ + return $this->step(); + } + + $this->generate_implied_end_tags(); + + if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) { + // @todo Record parse error: this error doesn't impact parsing. + } + + $this->state->stack_of_open_elements->pop_until( '(internal: H1 through H6 - do not use)' ); + return true; + + /* + * > A start tag whose tag name is "a" + */ + case '+A': + foreach ( $this->state->active_formatting_elements->walk_up() as $item ) { + switch ( $item->node_name ) { + case 'marker': + break 2; + + case 'A': + $this->run_adoption_agency_algorithm(); + $this->state->active_formatting_elements->remove_node( $item ); + $this->state->stack_of_open_elements->remove_node( $item ); + break 2; + } + } + + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + $this->state->active_formatting_elements->push( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is one of: "b", "big", "code", "em", "font", "i", + * > "s", "small", "strike", "strong", "tt", "u" + */ + case '+B': + case '+BIG': + case '+CODE': + case '+EM': + case '+FONT': + case '+I': + case '+S': + case '+SMALL': + case '+STRIKE': + case '+STRONG': + case '+TT': + case '+U': + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + $this->state->active_formatting_elements->push( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "nobr" + */ + case '+NOBR': + $this->reconstruct_active_formatting_elements(); + + if ( $this->state->stack_of_open_elements->has_element_in_scope( 'NOBR' ) ) { + // Parse error. + $this->run_adoption_agency_algorithm(); + $this->reconstruct_active_formatting_elements(); + } + + $this->insert_html_element( $this->state->current_token ); + $this->state->active_formatting_elements->push( $this->state->current_token ); + return true; + + /* + * > An end tag whose tag name is one of: "a", "b", "big", "code", "em", "font", "i", + * > "nobr", "s", "small", "strike", "strong", "tt", "u" + */ + case '-A': + case '-B': + case '-BIG': + case '-CODE': + case '-EM': + case '-FONT': + case '-I': + case '-NOBR': + case '-S': + case '-SMALL': + case '-STRIKE': + case '-STRONG': + case '-TT': + case '-U': + $this->run_adoption_agency_algorithm(); + return true; + + /* + * > A start tag whose tag name is one of: "applet", "marquee", "object" + */ + case '+APPLET': + case '+MARQUEE': + case '+OBJECT': + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + $this->state->active_formatting_elements->insert_marker(); + $this->state->frameset_ok = false; + return true; + + /* + * > A end tag token whose tag name is one of: "applet", "marquee", "object" + */ + case '-APPLET': + case '-MARQUEE': + case '-OBJECT': + if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $token_name ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->generate_implied_end_tags(); + if ( ! $this->state->stack_of_open_elements->current_node_is( $token_name ) ) { + // This is a parse error. + } + + $this->state->stack_of_open_elements->pop_until( $token_name ); + $this->state->active_formatting_elements->clear_up_to_last_marker(); + return true; + + /* + * > A start tag whose tag name is "table" + */ + case '+TABLE': + /* + * > If the Document is not set to quirks mode, and the stack of open elements + * > has a p element in button scope, then close a p element. + */ + if ( + WP_HTML_Processor_State::QUIRKS_MODE !== $this->state->document_mode && + $this->state->stack_of_open_elements->has_p_in_button_scope() + ) { + $this->close_a_p_element(); + } + + $this->insert_html_element( $this->state->current_token ); + $this->state->frameset_ok = false; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + return true; + + /* + * > An end tag whose tag name is "br" + * + * This is prevented from happening because the Tag Processor + * reports all closing BR tags as if they were opening tags. + */ + + /* + * > A start tag whose tag name is one of: "area", "br", "embed", "img", "keygen", "wbr" + */ + case '+AREA': + case '+BR': + case '+EMBED': + case '+IMG': + case '+KEYGEN': + case '+WBR': + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + $this->state->frameset_ok = false; + return true; + + /* + * > A start tag whose tag name is "input" + */ + case '+INPUT': + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + + /* + * > If the token does not have an attribute with the name "type", or if it does, + * > but that attribute's value is not an ASCII case-insensitive match for the + * > string "hidden", then: set the frameset-ok flag to "not ok". + */ + $type_attribute = $this->get_attribute( 'type' ); + if ( ! is_string( $type_attribute ) || 'hidden' !== strtolower( $type_attribute ) ) { + $this->state->frameset_ok = false; + } + + return true; + + /* + * > A start tag whose tag name is one of: "param", "source", "track" + */ + case '+PARAM': + case '+SOURCE': + case '+TRACK': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "hr" + */ + case '+HR': + if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->close_a_p_element(); + } + $this->insert_html_element( $this->state->current_token ); + $this->state->frameset_ok = false; + return true; + + /* + * > A start tag whose tag name is "image" + */ + case '+IMAGE': + /* + * > Parse error. Change the token's tag name to "img" and reprocess it. (Don't ask.) + * + * Note that this is handled elsewhere, so it should not be possible to reach this code. + */ + $this->bail( "Cannot process an IMAGE tag. (Don't ask.)" ); + break; + + /* + * > A start tag whose tag name is "textarea" + */ + case '+TEXTAREA': + $this->insert_html_element( $this->state->current_token ); + + /* + * > If the next token is a U+000A LINE FEED (LF) character token, then ignore + * > that token and move on to the next one. (Newlines at the start of + * > textarea elements are ignored as an authoring convenience.) + * + * This is handled in `get_modifiable_text()`. + */ + + $this->state->frameset_ok = false; + + /* + * > Switch the insertion mode to "text". + * + * As a self-contained node, this behavior is handled in the Tag Processor. + */ + return true; + + /* + * > A start tag whose tag name is "xmp" + */ + case '+XMP': + if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { + $this->close_a_p_element(); + } + + $this->reconstruct_active_formatting_elements(); + $this->state->frameset_ok = false; + + /* + * > Follow the generic raw text element parsing algorithm. + * + * As a self-contained node, this behavior is handled in the Tag Processor. + */ + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * A start tag whose tag name is "iframe" + */ + case '+IFRAME': + $this->state->frameset_ok = false; + + /* + * > Follow the generic raw text element parsing algorithm. + * + * As a self-contained node, this behavior is handled in the Tag Processor. + */ + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "noembed" + * > A start tag whose tag name is "noscript", if the scripting flag is enabled + * + * The scripting flag is never enabled in this parser. + */ + case '+NOEMBED': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "select" + */ + case '+SELECT': + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + $this->state->frameset_ok = false; + + switch ( $this->state->insertion_mode ) { + /* + * > If the insertion mode is one of "in table", "in caption", "in table body", "in row", + * > or "in cell", then switch the insertion mode to "in select in table". + */ + case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE: + case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION: + case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY: + case WP_HTML_Processor_State::INSERTION_MODE_IN_ROW: + case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL: + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE; + break; + + /* + * > Otherwise, switch the insertion mode to "in select". + */ + default: + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT; + break; + } + return true; + + /* + * > A start tag whose tag name is one of: "optgroup", "option" + */ + case '+OPTGROUP': + case '+OPTION': + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) { + $this->state->stack_of_open_elements->pop(); + } + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is one of: "rb", "rtc" + */ + case '+RB': + case '+RTC': + if ( $this->state->stack_of_open_elements->has_element_in_scope( 'RUBY' ) ) { + $this->generate_implied_end_tags(); + + if ( $this->state->stack_of_open_elements->current_node_is( 'RUBY' ) ) { + // @todo Indicate a parse error once it's possible. + } + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is one of: "rp", "rt" + */ + case '+RP': + case '+RT': + if ( $this->state->stack_of_open_elements->has_element_in_scope( 'RUBY' ) ) { + $this->generate_implied_end_tags( 'RTC' ); + + $current_node_name = $this->state->stack_of_open_elements->current_node()->node_name; + if ( 'RTC' === $current_node_name || 'RUBY' === $current_node_name ) { + // @todo Indicate a parse error once it's possible. + } + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "math" + */ + case '+MATH': + $this->reconstruct_active_formatting_elements(); + + /* + * @todo Adjust MathML attributes for the token. (This fixes the case of MathML attributes that are not all lowercase.) + * @todo Adjust foreign attributes for the token. (This fixes the use of namespaced attributes, in particular XLink.) + * + * These ought to be handled in the attribute methods. + */ + $this->state->current_token->namespace = 'math'; + $this->insert_html_element( $this->state->current_token ); + if ( $this->state->current_token->has_self_closing_flag ) { + $this->state->stack_of_open_elements->pop(); + } + return true; + + /* + * > A start tag whose tag name is "svg" + */ + case '+SVG': + $this->reconstruct_active_formatting_elements(); + + /* + * @todo Adjust SVG attributes for the token. (This fixes the case of SVG attributes that are not all lowercase.) + * @todo Adjust foreign attributes for the token. (This fixes the use of namespaced attributes, in particular XLink in SVG.) + * + * These ought to be handled in the attribute methods. + */ + $this->state->current_token->namespace = 'svg'; + $this->insert_html_element( $this->state->current_token ); + if ( $this->state->current_token->has_self_closing_flag ) { + $this->state->stack_of_open_elements->pop(); + } + return true; + + /* + * > A start tag whose tag name is one of: "caption", "col", "colgroup", + * > "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr" + */ + case '+CAPTION': + case '+COL': + case '+COLGROUP': + case '+FRAME': + case '+HEAD': + case '+TBODY': + case '+TD': + case '+TFOOT': + case '+TH': + case '+THEAD': + case '+TR': + // Parse error. Ignore the token. + return $this->step(); + } + + if ( ! parent::is_tag_closer() ) { + /* + * > Any other start tag + */ + $this->reconstruct_active_formatting_elements(); + $this->insert_html_element( $this->state->current_token ); + return true; + } else { + /* + * > Any other end tag + */ + + /* + * Find the corresponding tag opener in the stack of open elements, if + * it exists before reaching a special element, which provides a kind + * of boundary in the stack. For example, a `` should not + * close anything beyond its containing `P` or `DIV` element. + */ + foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { + if ( 'html' === $node->namespace && $token_name === $node->node_name ) { + break; + } + + if ( self::is_special( $node ) ) { + // This is a parse error, ignore the token. + return $this->step(); + } + } + + $this->generate_implied_end_tags( $token_name ); + if ( $node !== $this->state->stack_of_open_elements->current_node() ) { + // @todo Record parse error: this error doesn't impact parsing. + } + + foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { + $this->state->stack_of_open_elements->pop(); + if ( $node === $item ) { + return true; + } + } + } + } + + /** + * Parses next element in the 'in table' insertion mode. + * + * This internal function performs the 'in table' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intable + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_table(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > A character token, if the current node is table, + * > tbody, template, tfoot, thead, or tr element + */ + case '#text': + $current_node = $this->state->stack_of_open_elements->current_node(); + $current_node_name = $current_node ? $current_node->node_name : null; + if ( + $current_node_name && ( + 'TABLE' === $current_node_name || + 'TBODY' === $current_node_name || + 'TEMPLATE' === $current_node_name || + 'TFOOT' === $current_node_name || + 'THEAD' === $current_node_name || + 'TR' === $current_node_name + ) + ) { + /* + * If the text is empty after processing HTML entities and stripping + * U+0000 NULL bytes then ignore the token. + */ + if ( parent::TEXT_IS_NULL_SEQUENCE === $this->text_node_classification ) { + return $this->step(); + } + + /* + * This follows the rules for "in table text" insertion mode. + * + * Whitespace-only text nodes are inserted in-place. Otherwise + * foster parenting is enabled and the nodes would be + * inserted out-of-place. + * + * > If any of the tokens in the pending table character tokens + * > list are character tokens that are not ASCII whitespace, + * > then this is a parse error: reprocess the character tokens + * > in the pending table character tokens list using the rules + * > given in the "anything else" entry in the "in table" + * > insertion mode. + * > + * > Otherwise, insert the characters given by the pending table + * > character tokens list. + * + * @see https://html.spec.whatwg.org/#parsing-main-intabletext + */ + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + $this->insert_html_element( $this->state->current_token ); + return true; + } + + // Non-whitespace would trigger fostering, unsupported at this time. + $this->bail( 'Foster parenting is not supported.' ); + break; + } + break; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "caption" + */ + case '+CAPTION': + $this->state->stack_of_open_elements->clear_to_table_context(); + $this->state->active_formatting_elements->insert_marker(); + $this->insert_html_element( $this->state->current_token ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION; + return true; + + /* + * > A start tag whose tag name is "colgroup" + */ + case '+COLGROUP': + $this->state->stack_of_open_elements->clear_to_table_context(); + $this->insert_html_element( $this->state->current_token ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP; + return true; + + /* + * > A start tag whose tag name is "col" + */ + case '+COL': + $this->state->stack_of_open_elements->clear_to_table_context(); + + /* + * > Insert an HTML element for a "colgroup" start tag token with no attributes, + * > then switch the insertion mode to "in column group". + */ + $this->insert_virtual_node( 'COLGROUP' ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > A start tag whose tag name is one of: "tbody", "tfoot", "thead" + */ + case '+TBODY': + case '+TFOOT': + case '+THEAD': + $this->state->stack_of_open_elements->clear_to_table_context(); + $this->insert_html_element( $this->state->current_token ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY; + return true; + + /* + * > A start tag whose tag name is one of: "td", "th", "tr" + */ + case '+TD': + case '+TH': + case '+TR': + $this->state->stack_of_open_elements->clear_to_table_context(); + /* + * > Insert an HTML element for a "tbody" start tag token with no attributes, + * > then switch the insertion mode to "in table body". + */ + $this->insert_virtual_node( 'TBODY' ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > A start tag whose tag name is "table" + * + * This tag in the IN TABLE insertion mode is a parse error. + */ + case '+TABLE': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TABLE' ) ) { + return $this->step(); + } + + $this->state->stack_of_open_elements->pop_until( 'TABLE' ); + $this->reset_insertion_mode_appropriately(); + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > An end tag whose tag name is "table" + */ + case '-TABLE': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TABLE' ) ) { + // @todo Indicate a parse error once it's possible. + return $this->step(); + } + + $this->state->stack_of_open_elements->pop_until( 'TABLE' ); + $this->reset_insertion_mode_appropriately(); + return true; + + /* + * > An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" + */ + case '-BODY': + case '-CAPTION': + case '-COL': + case '-COLGROUP': + case '-HTML': + case '-TBODY': + case '-TD': + case '-TFOOT': + case '-TH': + case '-THEAD': + case '-TR': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is one of: "style", "script", "template" + * > An end tag whose tag name is "template" + */ + case '+STYLE': + case '+SCRIPT': + case '+TEMPLATE': + case '-TEMPLATE': + /* + * > Process the token using the rules for the "in head" insertion mode. + */ + return $this->step_in_head(); + + /* + * > A start tag whose tag name is "input" + * + * > If the token does not have an attribute with the name "type", or if it does, but + * > that attribute's value is not an ASCII case-insensitive match for the string + * > "hidden", then: act as described in the "anything else" entry below. + */ + case '+INPUT': + $type_attribute = $this->get_attribute( 'type' ); + if ( ! is_string( $type_attribute ) || 'hidden' !== strtolower( $type_attribute ) ) { + goto anything_else; + } + // @todo Indicate a parse error once it's possible. + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "form" + * + * This tag in the IN TABLE insertion mode is a parse error. + */ + case '+FORM': + if ( + $this->state->stack_of_open_elements->has_element_in_scope( 'TEMPLATE' ) || + isset( $this->state->form_element ) + ) { + return $this->step(); + } + + // This FORM is special because it immediately closes and cannot have other children. + $this->insert_html_element( $this->state->current_token ); + $this->state->form_element = $this->state->current_token; + $this->state->stack_of_open_elements->pop(); + return true; + } + + /* + * > Anything else + * > Parse error. Enable foster parenting, process the token using the rules for the + * > "in body" insertion mode, and then disable foster parenting. + * + * @todo Indicate a parse error once it's possible. + */ + anything_else: + $this->bail( 'Foster parenting is not supported.' ); + } + + /** + * Parses next element in the 'in table text' insertion mode. + * + * This internal function performs the 'in table text' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intabletext + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_table_text(): bool { + $this->bail( 'No support for parsing in the ' . WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_TEXT . ' state.' ); + } + + /** + * Parses next element in the 'in caption' insertion mode. + * + * This internal function performs the 'in caption' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-incaption + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_caption(): bool { + $tag_name = $this->get_tag(); + $op_sigil = $this->is_tag_closer() ? '-' : '+'; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > An end tag whose tag name is "caption" + * > A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr" + * > An end tag whose tag name is "table" + * + * These tag handling rules are identical except for the final instruction. + * Handle them in a single block. + */ + case '-CAPTION': + case '+CAPTION': + case '+COL': + case '+COLGROUP': + case '+TBODY': + case '+TD': + case '+TFOOT': + case '+TH': + case '+THEAD': + case '+TR': + case '-TABLE': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'CAPTION' ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->generate_implied_end_tags(); + if ( ! $this->state->stack_of_open_elements->current_node_is( 'CAPTION' ) ) { + // @todo Indicate a parse error once it's possible. + } + + $this->state->stack_of_open_elements->pop_until( 'CAPTION' ); + $this->state->active_formatting_elements->clear_up_to_last_marker(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + + // If this is not a CAPTION end tag, the token should be reprocessed. + if ( '-CAPTION' === $op ) { + return true; + } + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /** + * > An end tag whose tag name is one of: "body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" + */ + case '-BODY': + case '-COL': + case '-COLGROUP': + case '-HTML': + case '-TBODY': + case '-TD': + case '-TFOOT': + case '-TH': + case '-THEAD': + case '-TR': + // Parse error: ignore the token. + return $this->step(); + } + + /** + * > Anything else + * > Process the token using the rules for the "in body" insertion mode. + */ + return $this->step_in_body(); + } + + /** + * Parses next element in the 'in column group' insertion mode. + * + * This internal function performs the 'in column group' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-incolgroup + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_column_group(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), + * > U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + // Insert the character. + $this->insert_html_element( $this->state->current_token ); + return true; + } + + goto in_column_group_anything_else; + break; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A DOCTYPE token + */ + case 'html': + // @todo Indicate a parse error once it's possible. + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + + /* + * > A start tag whose tag name is "col" + */ + case '+COL': + $this->insert_html_element( $this->state->current_token ); + $this->state->stack_of_open_elements->pop(); + return true; + + /* + * > An end tag whose tag name is "colgroup" + */ + case '-COLGROUP': + if ( ! $this->state->stack_of_open_elements->current_node_is( 'COLGROUP' ) ) { + // @todo Indicate a parse error once it's possible. + return $this->step(); + } + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + return true; + + /* + * > An end tag whose tag name is "col" + */ + case '-COL': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "template" + * > An end tag whose tag name is "template" + */ + case '+TEMPLATE': + case '-TEMPLATE': + return $this->step_in_head(); + } + + in_column_group_anything_else: + /* + * > Anything else + */ + if ( ! $this->state->stack_of_open_elements->current_node_is( 'COLGROUP' ) ) { + // @todo Indicate a parse error once it's possible. + return $this->step(); + } + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'in table body' insertion mode. + * + * This internal function performs the 'in table body' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intbody + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_table_body(): bool { + $tag_name = $this->get_tag(); + $op_sigil = $this->is_tag_closer() ? '-' : '+'; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > A start tag whose tag name is "tr" + */ + case '+TR': + $this->state->stack_of_open_elements->clear_to_table_body_context(); + $this->insert_html_element( $this->state->current_token ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW; + return true; + + /* + * > A start tag whose tag name is one of: "th", "td" + */ + case '+TH': + case '+TD': + // @todo Indicate a parse error once it's possible. + $this->state->stack_of_open_elements->clear_to_table_body_context(); + $this->insert_virtual_node( 'TR' ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > An end tag whose tag name is one of: "tbody", "tfoot", "thead" + */ + case '-TBODY': + case '-TFOOT': + case '-THEAD': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $tag_name ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->state->stack_of_open_elements->clear_to_table_body_context(); + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + return true; + + /* + * > A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "tfoot", "thead" + * > An end tag whose tag name is "table" + */ + case '+CAPTION': + case '+COL': + case '+COLGROUP': + case '+TBODY': + case '+TFOOT': + case '+THEAD': + case '-TABLE': + if ( + ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TBODY' ) && + ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'THEAD' ) && + ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TFOOT' ) + ) { + // Parse error: ignore the token. + return $this->step(); + } + $this->state->stack_of_open_elements->clear_to_table_body_context(); + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "td", "th", "tr" + */ + case '-BODY': + case '-CAPTION': + case '-COL': + case '-COLGROUP': + case '-HTML': + case '-TD': + case '-TH': + case '-TR': + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Anything else + * > Process the token using the rules for the "in table" insertion mode. + */ + return $this->step_in_table(); + } + + /** + * Parses next element in the 'in row' insertion mode. + * + * This internal function performs the 'in row' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intr + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_row(): bool { + $tag_name = $this->get_tag(); + $op_sigil = $this->is_tag_closer() ? '-' : '+'; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > A start tag whose tag name is one of: "th", "td" + */ + case '+TH': + case '+TD': + $this->state->stack_of_open_elements->clear_to_table_row_context(); + $this->insert_html_element( $this->state->current_token ); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CELL; + $this->state->active_formatting_elements->insert_marker(); + return true; + + /* + * > An end tag whose tag name is "tr" + */ + case '-TR': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TR' ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->state->stack_of_open_elements->clear_to_table_row_context(); + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY; + return true; + + /* + * > A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr" + * > An end tag whose tag name is "table" + */ + case '+CAPTION': + case '+COL': + case '+COLGROUP': + case '+TBODY': + case '+TFOOT': + case '+THEAD': + case '+TR': + case '-TABLE': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TR' ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->state->stack_of_open_elements->clear_to_table_row_context(); + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > An end tag whose tag name is one of: "tbody", "tfoot", "thead" + */ + case '-TBODY': + case '-TFOOT': + case '-THEAD': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $tag_name ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( 'TR' ) ) { + // Ignore the token. + return $this->step(); + } + + $this->state->stack_of_open_elements->clear_to_table_row_context(); + $this->state->stack_of_open_elements->pop(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "td", "th" + */ + case '-BODY': + case '-CAPTION': + case '-COL': + case '-COLGROUP': + case '-HTML': + case '-TD': + case '-TH': + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > Anything else + * > Process the token using the rules for the "in table" insertion mode. + */ + return $this->step_in_table(); + } + + /** + * Parses next element in the 'in cell' insertion mode. + * + * This internal function performs the 'in cell' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intd + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_cell(): bool { + $tag_name = $this->get_tag(); + $op_sigil = $this->is_tag_closer() ? '-' : '+'; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > An end tag whose tag name is one of: "td", "th" + */ + case '-TD': + case '-TH': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $tag_name ) ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->generate_implied_end_tags(); + + /* + * @todo This needs to check if the current node is an HTML element, meaning that + * when SVG and MathML support is added, this needs to differentiate between an + * HTML element of the given name, such as `
`, and a foreign element of + * the same given name. + */ + if ( ! $this->state->stack_of_open_elements->current_node_is( $tag_name ) ) { + // @todo Indicate a parse error once it's possible. + } + + $this->state->stack_of_open_elements->pop_until( $tag_name ); + $this->state->active_formatting_elements->clear_up_to_last_marker(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW; + return true; + + /* + * > A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "td", + * > "tfoot", "th", "thead", "tr" + */ + case '+CAPTION': + case '+COL': + case '+COLGROUP': + case '+TBODY': + case '+TD': + case '+TFOOT': + case '+TH': + case '+THEAD': + case '+TR': + /* + * > Assert: The stack of open elements has a td or th element in table scope. + * + * Nothing to do here, except to verify in tests that this never appears. + */ + + $this->close_cell(); + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html" + */ + case '-BODY': + case '-CAPTION': + case '-COL': + case '-COLGROUP': + case '-HTML': + // Parse error: ignore the token. + return $this->step(); + + /* + * > An end tag whose tag name is one of: "table", "tbody", "tfoot", "thead", "tr" + */ + case '-TABLE': + case '-TBODY': + case '-TFOOT': + case '-THEAD': + case '-TR': + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $tag_name ) ) { + // Parse error: ignore the token. + return $this->step(); + } + $this->close_cell(); + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /* + * > Anything else + * > Process the token using the rules for the "in body" insertion mode. + */ + return $this->step_in_body(); + } + + /** + * Parses next element in the 'in select' insertion mode. + * + * This internal function performs the 'in select' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inselect + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_select(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > Any other character token + */ + case '#text': + /* + * > A character token that is U+0000 NULL + * + * If a text node only comprises null bytes then it should be + * entirely ignored and should not return to calling code. + */ + if ( parent::TEXT_IS_NULL_SEQUENCE === $this->text_node_classification ) { + // Parse error: ignore the token. + return $this->step(); + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + + /* + * > A start tag whose tag name is "option" + */ + case '+OPTION': + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) { + $this->state->stack_of_open_elements->pop(); + } + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A start tag whose tag name is "optgroup" + * > A start tag whose tag name is "hr" + * + * These rules are identical except for the treatment of the self-closing flag and + * the subsequent pop of the HR void element, all of which is handled elsewhere in the processor. + */ + case '+OPTGROUP': + case '+HR': + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) { + $this->state->stack_of_open_elements->pop(); + } + + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTGROUP' ) ) { + $this->state->stack_of_open_elements->pop(); + } + + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > An end tag whose tag name is "optgroup" + */ + case '-OPTGROUP': + $current_node = $this->state->stack_of_open_elements->current_node(); + if ( $current_node && 'OPTION' === $current_node->node_name ) { + foreach ( $this->state->stack_of_open_elements->walk_up( $current_node ) as $parent ) { + break; + } + if ( $parent && 'OPTGROUP' === $parent->node_name ) { + $this->state->stack_of_open_elements->pop(); + } + } + + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTGROUP' ) ) { + $this->state->stack_of_open_elements->pop(); + return true; + } + + // Parse error: ignore the token. + return $this->step(); + + /* + * > An end tag whose tag name is "option" + */ + case '-OPTION': + if ( $this->state->stack_of_open_elements->current_node_is( 'OPTION' ) ) { + $this->state->stack_of_open_elements->pop(); + return true; + } + + // Parse error: ignore the token. + return $this->step(); + + /* + * > An end tag whose tag name is "select" + * > A start tag whose tag name is "select" + * + * > It just gets treated like an end tag. + */ + case '-SELECT': + case '+SELECT': + if ( ! $this->state->stack_of_open_elements->has_element_in_select_scope( 'SELECT' ) ) { + // Parse error: ignore the token. + return $this->step(); + } + $this->state->stack_of_open_elements->pop_until( 'SELECT' ); + $this->reset_insertion_mode_appropriately(); + return true; + + /* + * > A start tag whose tag name is one of: "input", "keygen", "textarea" + * + * All three of these tags are considered a parse error when found in this insertion mode. + */ + case '+INPUT': + case '+KEYGEN': + case '+TEXTAREA': + if ( ! $this->state->stack_of_open_elements->has_element_in_select_scope( 'SELECT' ) ) { + // Ignore the token. + return $this->step(); + } + $this->state->stack_of_open_elements->pop_until( 'SELECT' ); + $this->reset_insertion_mode_appropriately(); + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > A start tag whose tag name is one of: "script", "template" + * > An end tag whose tag name is "template" + */ + case '+SCRIPT': + case '+TEMPLATE': + case '-TEMPLATE': + return $this->step_in_head(); + } + + /* + * > Anything else + * > Parse error: ignore the token. + */ + return $this->step(); + } + + /** + * Parses next element in the 'in select in table' insertion mode. + * + * This internal function performs the 'in select in table' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inselectintable + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_select_in_table(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( parent::is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > A start tag whose tag name is one of: "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th" + */ + case '+CAPTION': + case '+TABLE': + case '+TBODY': + case '+TFOOT': + case '+THEAD': + case '+TR': + case '+TD': + case '+TH': + // @todo Indicate a parse error once it's possible. + $this->state->stack_of_open_elements->pop_until( 'SELECT' ); + $this->reset_insertion_mode_appropriately(); + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > An end tag whose tag name is one of: "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th" + */ + case '-CAPTION': + case '-TABLE': + case '-TBODY': + case '-TFOOT': + case '-THEAD': + case '-TR': + case '-TD': + case '-TH': + // @todo Indicate a parse error once it's possible. + if ( ! $this->state->stack_of_open_elements->has_element_in_table_scope( $token_name ) ) { + return $this->step(); + } + $this->state->stack_of_open_elements->pop_until( 'SELECT' ); + $this->reset_insertion_mode_appropriately(); + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /* + * > Anything else + */ + return $this->step_in_select(); + } + + /** + * Parses next element in the 'in template' insertion mode. + * + * This internal function performs the 'in template' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-intemplate + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_template(): bool { + $token_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $is_closer = $this->is_tag_closer(); + $op_sigil = '#tag' === $token_type ? ( $is_closer ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$token_name}"; + + switch ( $op ) { + /* + * > A character token + * > A comment token + * > A DOCTYPE token + */ + case '#text': + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + case 'html': + return $this->step_in_body(); + + /* + * > A start tag whose tag name is one of: "base", "basefont", "bgsound", "link", + * > "meta", "noframes", "script", "style", "template", "title" + * > An end tag whose tag name is "template" + */ + case '+BASE': + case '+BASEFONT': + case '+BGSOUND': + case '+LINK': + case '+META': + case '+NOFRAMES': + case '+SCRIPT': + case '+STYLE': + case '+TEMPLATE': + case '+TITLE': + case '-TEMPLATE': + return $this->step_in_head(); + + /* + * > A start tag whose tag name is one of: "caption", "colgroup", "tbody", "tfoot", "thead" + */ + case '+CAPTION': + case '+COLGROUP': + case '+TBODY': + case '+TFOOT': + case '+THEAD': + array_pop( $this->state->stack_of_template_insertion_modes ); + $this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > A start tag whose tag name is "col" + */ + case '+COL': + array_pop( $this->state->stack_of_template_insertion_modes ); + $this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > A start tag whose tag name is "tr" + */ + case '+TR': + array_pop( $this->state->stack_of_template_insertion_modes ); + $this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + + /* + * > A start tag whose tag name is one of: "td", "th" + */ + case '+TD': + case '+TH': + array_pop( $this->state->stack_of_template_insertion_modes ); + $this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /* + * > Any other start tag + */ + if ( ! $is_closer ) { + array_pop( $this->state->stack_of_template_insertion_modes ); + $this->state->stack_of_template_insertion_modes[] = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /* + * > Any other end tag + */ + if ( $is_closer ) { + // Parse error: ignore the token. + return $this->step(); + } + + /* + * > An end-of-file token + */ + if ( ! $this->state->stack_of_open_elements->contains( 'TEMPLATE' ) ) { + // Stop parsing. + return false; + } + + // @todo Indicate a parse error once it's possible. + $this->state->stack_of_open_elements->pop_until( 'TEMPLATE' ); + $this->state->active_formatting_elements->clear_up_to_last_marker(); + array_pop( $this->state->stack_of_template_insertion_modes ); + $this->reset_insertion_mode_appropriately(); + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'after body' insertion mode. + * + * This internal function performs the 'after body' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-afterbody + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_body(): bool { + $tag_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), + * > U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * + * > Process the token using the rules for the "in body" insertion mode. + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + return $this->step_in_body(); + } + goto after_body_anything_else; + break; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->bail( 'Content outside of BODY is unsupported.' ); + break; + + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + + /* + * > An end tag whose tag name is "html" + * + * > If the parser was created as part of the HTML fragment parsing algorithm, + * > this is a parse error; ignore the token. (fragment case) + * > + * > Otherwise, switch the insertion mode to "after after body". + */ + case '-HTML': + if ( isset( $this->context_node ) ) { + return $this->step(); + } + + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_BODY; + return true; + } + + /* + * > Parse error. Switch the insertion mode to "in body" and reprocess the token. + */ + after_body_anything_else: + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'in frameset' insertion mode. + * + * This internal function performs the 'in frameset' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inframeset + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_frameset(): bool { + $tag_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), + * > U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * > + * > Insert the character. + * + * This algorithm effectively strips non-whitespace characters from text and inserts + * them under HTML. This is not supported at this time. + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + return $this->step_in_body(); + } + $this->bail( 'Non-whitespace characters cannot be handled in frameset.' ); + break; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + + /* + * > A start tag whose tag name is "frameset" + */ + case '+FRAMESET': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > An end tag whose tag name is "frameset" + */ + case '-FRAMESET': + /* + * > If the current node is the root html element, then this is a parse error; + * > ignore the token. (fragment case) + */ + if ( $this->state->stack_of_open_elements->current_node_is( 'HTML' ) ) { + return $this->step(); + } + + /* + * > Otherwise, pop the current node from the stack of open elements. + */ + $this->state->stack_of_open_elements->pop(); + + /* + * > If the parser was not created as part of the HTML fragment parsing algorithm + * > (fragment case), and the current node is no longer a frameset element, then + * > switch the insertion mode to "after frameset". + */ + if ( ! isset( $this->context_node ) && ! $this->state->stack_of_open_elements->current_node_is( 'FRAMESET' ) ) { + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_FRAMESET; + } + + return true; + + /* + * > A start tag whose tag name is "frame" + * + * > Insert an HTML element for the token. Immediately pop the + * > current node off the stack of open elements. + * > + * > Acknowledge the token's self-closing flag, if it is set. + */ + case '+FRAME': + $this->insert_html_element( $this->state->current_token ); + $this->state->stack_of_open_elements->pop(); + return true; + + /* + * > A start tag whose tag name is "noframes" + */ + case '+NOFRAMES': + return $this->step_in_head(); + } + + // Parse error: ignore the token. + return $this->step(); + } + + /** + * Parses next element in the 'after frameset' insertion mode. + * + * This internal function performs the 'after frameset' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-afterframeset + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_frameset(): bool { + $tag_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), + * > U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * > + * > Insert the character. + * + * This algorithm effectively strips non-whitespace characters from text and inserts + * them under HTML. This is not supported at this time. + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + return $this->step_in_body(); + } + $this->bail( 'Non-whitespace characters cannot be handled in after frameset' ); + break; + + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_html_element( $this->state->current_token ); + return true; + + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); + + /* + * > A start tag whose tag name is "html" + */ + case '+HTML': + return $this->step_in_body(); + + /* + * > An end tag whose tag name is "html" + */ + case '-HTML': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_FRAMESET; + return true; + + /* + * > A start tag whose tag name is "noframes" + */ + case '+NOFRAMES': + return $this->step_in_head(); + } + + // Parse error: ignore the token. + return $this->step(); + } + + /** + * Parses next element in the 'after after body' insertion mode. + * + * This internal function performs the 'after after body' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-after-after-body-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_after_body(): bool { + $tag_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->bail( 'Content outside of HTML is unsupported.' ); + break; + + /* + * > A DOCTYPE token + * > A start tag whose tag name is "html" + * + * > Process the token using the rules for the "in body" insertion mode. + */ + case 'html': + case '+HTML': + return $this->step_in_body(); + + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), + * > U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * > + * > Process the token using the rules for the "in body" insertion mode. + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + return $this->step_in_body(); + } + goto after_after_body_anything_else; + break; + } + + /* + * > Parse error. Switch the insertion mode to "in body" and reprocess the token. + */ + after_after_body_anything_else: + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } + + /** + * Parses next element in the 'after after frameset' insertion mode. + * + * This internal function performs the 'after after frameset' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#the-after-after-frameset-insertion-mode + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_after_after_frameset(): bool { + $tag_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$tag_name}"; + + switch ( $op ) { + /* + * > A comment token + */ + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->bail( 'Content outside of HTML is unsupported.' ); + break; + + /* + * > A DOCTYPE token + * > A start tag whose tag name is "html" + * + * > Process the token using the rules for the "in body" insertion mode. + */ + case 'html': + case '+HTML': + return $this->step_in_body(); + + /* + * > A character token that is one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), + * > U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE + * > + * > Process the token using the rules for the "in body" insertion mode. + * + * This algorithm effectively strips non-whitespace characters from text and inserts + * them under HTML. This is not supported at this time. + */ + case '#text': + if ( parent::TEXT_IS_WHITESPACE === $this->text_node_classification ) { + return $this->step_in_body(); + } + $this->bail( 'Non-whitespace characters cannot be handled in after after frameset.' ); + break; + + /* + * > A start tag whose tag name is "noframes" + */ + case '+NOFRAMES': + return $this->step_in_head(); + } + + // Parse error: ignore the token. + return $this->step(); + } + + /** + * Parses next element in the 'in foreign content' insertion mode. + * + * This internal function performs the 'in foreign content' insertion mode + * logic for the generalized WP_HTML_Processor::step() function. + * + * @since 6.7.0 Stub implementation. + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @see https://html.spec.whatwg.org/#parsing-main-inforeign + * @see WP_HTML_Processor::step + * + * @return bool Whether an element was found. + */ + private function step_in_foreign_content(): bool { + $tag_name = $this->get_token_name(); + $token_type = $this->get_token_type(); + $op_sigil = '#tag' === $token_type ? ( $this->is_tag_closer() ? '-' : '+' ) : ''; + $op = "{$op_sigil}{$tag_name}"; + + /* + * > A start tag whose name is "font", if the token has any attributes named "color", "face", or "size" + * + * This section drawn out above the switch to more easily incorporate + * the additional rules based on the presence of the attributes. + */ + if ( + '+FONT' === $op && + ( + null !== $this->get_attribute( 'color' ) || + null !== $this->get_attribute( 'face' ) || + null !== $this->get_attribute( 'size' ) + ) + ) { + $op = '+FONT with attributes'; + } - $this->generate_implied_end_tags( $token_name ); + switch ( $op ) { + case '#cdata-section': + case '#text': + /* + * > A character token that is U+0000 NULL + * + * This is handled by `get_modifiable_text()`. + */ - if ( $token_name !== $this->state->stack_of_open_elements->current_node()->node_name ) { - // @todo Indicate a parse error once it's possible. This error does not impact the logic here. + /* + * Whitespace-only text does not affect the frameset-ok flag. + * It is probably inter-element whitespace, but it may also + * contain character references which decode only to whitespace. + */ + if ( parent::TEXT_IS_GENERIC === $this->text_node_classification ) { + $this->state->frameset_ok = false; } - $this->state->stack_of_open_elements->pop_until( $token_name ); + $this->insert_foreign_element( $this->state->current_token, false ); return true; /* - * > An end tag whose tag name is "p" + * > A comment token */ - case '-P': - if ( ! $this->state->stack_of_open_elements->has_p_in_button_scope() ) { - $this->insert_html_element( $this->state->current_token ); - } - - $this->close_a_p_element(); + case '#comment': + case '#funky-comment': + case '#presumptuous-tag': + $this->insert_foreign_element( $this->state->current_token, false ); return true; - // > A start tag whose tag name is "a" - case '+A': - foreach ( $this->state->active_formatting_elements->walk_up() as $item ) { - switch ( $item->node_name ) { - case 'marker': - break; - - case 'A': - $this->run_adoption_agency_algorithm(); - $this->state->active_formatting_elements->remove_node( $item ); - $this->state->stack_of_open_elements->remove_node( $item ); - break; - } - } - - $this->reconstruct_active_formatting_elements(); - $this->insert_html_element( $this->state->current_token ); - $this->state->active_formatting_elements->push( $this->state->current_token ); - return true; + /* + * > A DOCTYPE token + */ + case 'html': + // Parse error: ignore the token. + return $this->step(); /* - * > A start tag whose tag name is one of: "b", "big", "code", "em", "font", "i", - * > "s", "small", "strike", "strong", "tt", "u" + * > A start tag whose tag name is "b", "big", "blockquote", "body", "br", "center", + * > "code", "dd", "div", "dl", "dt", "em", "embed", "h1", "h2", "h3", "h4", "h5", + * > "h6", "head", "hr", "i", "img", "li", "listing", "menu", "meta", "nobr", "ol", + * > "p", "pre", "ruby", "s", "small", "span", "strong", "strike", "sub", "sup", + * > "table", "tt", "u", "ul", "var" + * + * > A start tag whose name is "font", if the token has any attributes named "color", "face", or "size" + * + * > An end tag whose tag name is "br", "p" + * + * Closing BR tags are always reported by the Tag Processor as opening tags. */ case '+B': case '+BIG': + case '+BLOCKQUOTE': + case '+BODY': + case '+BR': + case '+CENTER': case '+CODE': + case '+DD': + case '+DIV': + case '+DL': + case '+DT': case '+EM': - case '+FONT': + case '+EMBED': + case '+H1': + case '+H2': + case '+H3': + case '+H4': + case '+H5': + case '+H6': + case '+HEAD': + case '+HR': case '+I': + case '+IMG': + case '+LI': + case '+LISTING': + case '+MENU': + case '+META': + case '+NOBR': + case '+OL': + case '+P': + case '+PRE': + case '+RUBY': case '+S': case '+SMALL': - case '+STRIKE': + case '+SPAN': case '+STRONG': + case '+STRIKE': + case '+SUB': + case '+SUP': + case '+TABLE': case '+TT': case '+U': - $this->reconstruct_active_formatting_elements(); - $this->insert_html_element( $this->state->current_token ); - $this->state->active_formatting_elements->push( $this->state->current_token ); - return true; - - /* - * > An end tag whose tag name is one of: "a", "b", "big", "code", "em", "font", "i", - * > "nobr", "s", "small", "strike", "strong", "tt", "u" - */ - case '-A': - case '-B': - case '-BIG': - case '-CODE': - case '-EM': - case '-FONT': - case '-I': - case '-S': - case '-SMALL': - case '-STRIKE': - case '-STRONG': - case '-TT': - case '-U': - $this->run_adoption_agency_algorithm(); - return true; - - /* - * > An end tag whose tag name is "br" - * > Parse error. Drop the attributes from the token, and act as described in the next - * > entry; i.e. act as if this was a "br" start tag token with no attributes, rather - * > than the end tag token that it actually is. - */ + case '+UL': + case '+VAR': + case '+FONT with attributes': case '-BR': - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Closing BR tags require unimplemented special handling.' ); - - /* - * > A start tag whose tag name is one of: "area", "br", "embed", "img", "keygen", "wbr" - */ - case '+AREA': - case '+BR': - case '+EMBED': - case '+IMG': - case '+KEYGEN': - case '+WBR': - $this->reconstruct_active_formatting_elements(); - $this->insert_html_element( $this->state->current_token ); - $this->state->frameset_ok = false; - return true; + case '-P': + // @todo Indicate a parse error once it's possible. + foreach ( $this->state->stack_of_open_elements->walk_up() as $current_node ) { + if ( + 'math' === $current_node->integration_node_type || + 'html' === $current_node->integration_node_type || + 'html' === $current_node->namespace + ) { + break; + } - /* - * > A start tag whose tag name is "input" - */ - case '+INPUT': - $this->reconstruct_active_formatting_elements(); - $this->insert_html_element( $this->state->current_token ); - $type_attribute = $this->get_attribute( 'type' ); - /* - * > If the token does not have an attribute with the name "type", or if it does, - * > but that attribute's value is not an ASCII case-insensitive match for the - * > string "hidden", then: set the frameset-ok flag to "not ok". - */ - if ( ! is_string( $type_attribute ) || 'hidden' !== strtolower( $type_attribute ) ) { - $this->state->frameset_ok = false; + $this->state->stack_of_open_elements->pop(); } - return true; + return $this->step( self::REPROCESS_CURRENT_NODE ); + } - /* - * > A start tag whose tag name is "hr" - */ - case '+HR': - if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) { - $this->close_a_p_element(); - } - $this->insert_html_element( $this->state->current_token ); - $this->state->frameset_ok = false; - return true; + /* + * > Any other start tag + */ + if ( ! $this->is_tag_closer() ) { + $this->insert_foreign_element( $this->state->current_token, false ); /* - * > A start tag whose tag name is one of: "param", "source", "track" + * > If the token has its self-closing flag set, then run + * > the appropriate steps from the following list: + * > + * > ↪ the token's tag name is "script", and the new current node is in the SVG namespace + * > Acknowledge the token's self-closing flag, and then act as + * > described in the steps for a "script" end tag below. + * > + * > ↪ Otherwise + * > Pop the current node off the stack of open elements and + * > acknowledge the token's self-closing flag. + * + * Since the rules for SCRIPT below indicate to pop the element off of the stack of + * open elements, which is the same for the Otherwise condition, there's no need to + * separate these checks. The difference comes when a parser operates with the scripting + * flag enabled, and executes the script, which this parser does not support. */ - case '+PARAM': - case '+SOURCE': - case '+TRACK': - $this->insert_html_element( $this->state->current_token ); - return true; + if ( $this->state->current_token->has_self_closing_flag ) { + $this->state->stack_of_open_elements->pop(); + } + return true; } /* - * These tags require special handling in the 'in body' insertion mode - * but that handling hasn't yet been implemented. - * - * As the rules for each tag are implemented, the corresponding tag - * name should be removed from this list. An accompanying test should - * help ensure this list is maintained. - * - * @see Tests_HtmlApi_WpHtmlProcessor::test_step_in_body_fails_on_unsupported_tags - * - * Since this switch structure throws a WP_HTML_Unsupported_Exception, it's - * possible to handle "any other start tag" and "any other end tag" below, - * as that guarantees execution doesn't proceed for the unimplemented tags. - * - * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody + * > An end tag whose name is "script", if the current node is an SVG script element. */ - switch ( $token_name ) { - case 'APPLET': - case 'BASE': - case 'BASEFONT': - case 'BGSOUND': - case 'BODY': - case 'CAPTION': - case 'COL': - case 'COLGROUP': - case 'FORM': - case 'FRAME': - case 'FRAMESET': - case 'HEAD': - case 'HTML': - case 'IFRAME': - case 'LINK': - case 'MARQUEE': - case 'MATH': - case 'META': - case 'NOBR': - case 'NOEMBED': - case 'NOFRAMES': - case 'NOSCRIPT': - case 'OBJECT': - case 'OPTGROUP': - case 'OPTION': - case 'PLAINTEXT': - case 'RB': - case 'RP': - case 'RT': - case 'RTC': - case 'SARCASM': - case 'SCRIPT': - case 'SELECT': - case 'STYLE': - case 'SVG': - case 'TABLE': - case 'TBODY': - case 'TD': - case 'TEMPLATE': - case 'TEXTAREA': - case 'TFOOT': - case 'TH': - case 'THEAD': - case 'TITLE': - case 'TR': - case 'XMP': - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( "Cannot process {$token_name} element." ); + if ( $this->is_tag_closer() && 'SCRIPT' === $this->state->current_token->node_name && 'svg' === $this->state->current_token->namespace ) { + $this->state->stack_of_open_elements->pop(); + return true; } - if ( ! parent::is_tag_closer() ) { - /* - * > Any other start tag - */ - $this->reconstruct_active_formatting_elements(); - $this->insert_html_element( $this->state->current_token ); - return true; - } else { - /* - * > Any other end tag - */ + /* + * > Any other end tag + */ + if ( $this->is_tag_closer() ) { + $node = $this->state->stack_of_open_elements->current_node(); + if ( $tag_name !== $node->node_name ) { + // @todo Indicate a parse error once it's possible. + } + in_foreign_content_end_tag_loop: + if ( $node === $this->state->stack_of_open_elements->at( 1 ) ) { + return true; + } /* - * Find the corresponding tag opener in the stack of open elements, if - * it exists before reaching a special element, which provides a kind - * of boundary in the stack. For example, a `` should not - * close anything beyond its containing `P` or `DIV` element. + * > If node's tag name, converted to ASCII lowercase, is the same as the tag name + * > of the token, pop elements from the stack of open elements until node has + * > been popped from the stack, and then return. */ - foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { - if ( $token_name === $node->node_name ) { - break; + if ( 0 === strcasecmp( $node->node_name, $tag_name ) ) { + foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { + $this->state->stack_of_open_elements->pop(); + if ( $node === $item ) { + return true; + } } + } - if ( self::is_special( $node->node_name ) ) { - // This is a parse error, ignore the token. - return $this->step(); - } + foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) { + $node = $item; + break; } - $this->generate_implied_end_tags( $token_name ); - if ( $node !== $this->state->stack_of_open_elements->current_node() ) { - // @todo Record parse error: this error doesn't impact parsing. + if ( 'html' !== $node->namespace ) { + goto in_foreign_content_end_tag_loop; } - foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { - $this->state->stack_of_open_elements->pop(); - if ( $node === $item ) { - return true; - } + switch ( $this->state->insertion_mode ) { + case WP_HTML_Processor_State::INSERTION_MODE_INITIAL: + return $this->step_initial(); + + case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HTML: + return $this->step_before_html(); + + case WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD: + return $this->step_before_head(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD: + return $this->step_in_head(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD_NOSCRIPT: + return $this->step_in_head_noscript(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD: + return $this->step_after_head(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_BODY: + return $this->step_in_body(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE: + return $this->step_in_table(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_TEXT: + return $this->step_in_table_text(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION: + return $this->step_in_caption(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP: + return $this->step_in_column_group(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY: + return $this->step_in_table_body(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_ROW: + return $this->step_in_row(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_CELL: + return $this->step_in_cell(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT: + return $this->step_in_select(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE: + return $this->step_in_select_in_table(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_TEMPLATE: + return $this->step_in_template(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_BODY: + return $this->step_after_body(); + + case WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET: + return $this->step_in_frameset(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_FRAMESET: + return $this->step_after_frameset(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_BODY: + return $this->step_after_after_body(); + + case WP_HTML_Processor_State::INSERTION_MODE_AFTER_AFTER_FRAMESET: + return $this->step_after_after_frameset(); + + // This should be unreachable but PHP doesn't have total type checking on switch. + default: + $this->bail( "Unaware of the requested parsing mode: '{$this->state->insertion_mode}'." ); } } } @@ -1414,6 +4660,19 @@ private function bookmark_token() { * HTML semantic overrides for Tag Processor */ + /** + * Indicates the namespace of the current token, or "html" if there is none. + * + * @return string One of "html", "math", or "svg". + */ + public function get_namespace(): string { + if ( ! isset( $this->current_element ) ) { + return parent::get_namespace(); + } + + return $this->current_element->token->namespace; + } + /** * Returns the uppercase name of the matched tag. * @@ -1435,12 +4694,12 @@ private function bookmark_token() { * * @return string|null Name of currently matched tag in input HTML, or `null` if none found. */ - public function get_tag() { + public function get_tag(): ?string { if ( null !== $this->last_error ) { return null; } - if ( isset( $this->current_element ) ) { + if ( $this->is_virtual() ) { return $this->current_element->token->node_name; } @@ -1459,6 +4718,27 @@ public function get_tag() { } } + /** + * Indicates if the currently matched tag contains the self-closing flag. + * + * No HTML elements ought to have the self-closing flag and for those, the self-closing + * flag will be ignored. For void elements this is benign because they "self close" + * automatically. For non-void HTML elements though problems will appear if someone + * intends to use a self-closing element in place of that element with an empty body. + * For HTML foreign elements and custom elements the self-closing flag determines if + * they self-close or not. + * + * This function does not determine if a tag is self-closing, + * but only if the self-closing flag is present in the syntax. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @return bool Whether the currently matched tag contains the self-closing flag. + */ + public function has_self_closing_flag(): bool { + return $this->is_virtual() ? false : parent::has_self_closing_flag(); + } + /** * Returns the node name represented by the token. * @@ -1479,12 +4759,10 @@ public function get_tag() { * * @return string|null Name of the matched token. */ - public function get_token_name() { - if ( isset( $this->current_element ) ) { - return $this->current_element->token->node_name; - } - - return parent::get_token_name(); + public function get_token_name(): ?string { + return $this->is_virtual() + ? $this->current_element->token->node_name + : parent::get_token_name(); } /** @@ -1509,10 +4787,17 @@ public function get_token_name() { * * @return string|null What kind of token is matched, or null. */ - public function get_token_type() { - if ( isset( $this->current_element ) ) { - $node_name = $this->current_element->token->node_name; - if ( ctype_upper( $node_name[0] ) ) { + public function get_token_type(): ?string { + if ( $this->is_virtual() ) { + /* + * This logic comes from the Tag Processor. + * + * @todo It would be ideal not to repeat this here, but it's not clearly + * better to allow passing a token name to `get_token_type()`. + */ + $node_name = $this->current_element->token->node_name; + $starting_char = $node_name[0]; + if ( 'A' <= $starting_char && 'Z' >= $starting_char ) { return '#tag'; } @@ -1546,25 +4831,38 @@ public function get_token_type() { * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`. */ public function get_attribute( $name ) { - if ( isset( $this->current_element ) ) { - // Closing tokens cannot contain attributes. - if ( WP_HTML_Stack_Event::POP === $this->current_element->operation ) { - return null; - } - - $node_name = $this->current_element->token->node_name; - - // Only tags can contain attributes. - if ( 'A' > $node_name[0] || 'Z' < $node_name[0] ) { - return null; - } + return $this->is_virtual() ? null : parent::get_attribute( $name ); + } - if ( $this->current_element->token->bookmark_name === (string) $this->bookmark_counter ) { - return parent::get_attribute( $name ); - } - } + /** + * Updates or creates a new attribute on the currently matched tag with the passed value. + * + * For boolean attributes special handling is provided: + * - When `true` is passed as the value, then only the attribute name is added to the tag. + * - When `false` is passed, the attribute gets removed if it existed before. + * + * For string attributes, the value is escaped using the `esc_attr` function. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @param string $name The attribute name to target. + * @param string|bool $value The new attribute value. + * @return bool Whether an attribute value was set. + */ + public function set_attribute( $name, $value ): bool { + return $this->is_virtual() ? false : parent::set_attribute( $name, $value ); + } - return null; + /** + * Remove an attribute from the currently-matched tag. + * + * @since 6.6.0 Subclassed for HTML Processor. + * + * @param string $name The attribute name to remove. + * @return bool Whether an attribute was removed. + */ + public function remove_attribute( $name ): bool { + return $this->is_virtual() ? false : parent::remove_attribute( $name ); } /** @@ -1593,19 +4891,64 @@ public function get_attribute( $name ) { * @param string $prefix Prefix of requested attribute names. * @return array|null List of attribute names, or `null` when no tag opener is matched. */ - public function get_attribute_names_with_prefix( $prefix ) { - if ( isset( $this->current_element ) ) { - if ( WP_HTML_Stack_Event::POP === $this->current_element->operation ) { - return null; - } + public function get_attribute_names_with_prefix( $prefix ): ?array { + return $this->is_virtual() ? null : parent::get_attribute_names_with_prefix( $prefix ); + } - $mark = $this->bookmarks[ $this->current_element->token->bookmark_name ]; - if ( 0 === $mark->length ) { - return null; - } - } + /** + * Adds a new class name to the currently matched tag. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @param string $class_name The class name to add. + * @return bool Whether the class was set to be added. + */ + public function add_class( $class_name ): bool { + return $this->is_virtual() ? false : parent::add_class( $class_name ); + } + + /** + * Removes a class name from the currently matched tag. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @param string $class_name The class name to remove. + * @return bool Whether the class was set to be removed. + */ + public function remove_class( $class_name ): bool { + return $this->is_virtual() ? false : parent::remove_class( $class_name ); + } + + /** + * Returns if a matched tag contains the given ASCII case-insensitive class name. + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. + * @return bool|null Whether the matched tag contains the given class name, or null if not matched. + */ + public function has_class( $wanted_class ): ?bool { + return $this->is_virtual() ? null : parent::has_class( $wanted_class ); + } - return parent::get_attribute_names_with_prefix( $prefix ); + /** + * Generator for a foreach loop to step through each class name for the matched tag. + * + * This generator function is designed to be used inside a "foreach" loop. + * + * Example: + * + * $p = WP_HTML_Processor::create_fragment( "
" ); + * $p->next_tag(); + * foreach ( $p->class_list() as $class_name ) { + * echo "{$class_name} "; + * } + * // Outputs: "free lang-en " + * + * @since 6.6.0 Subclassed for the HTML Processor. + */ + public function class_list() { + return $this->is_virtual() ? null : parent::class_list(); } /** @@ -1628,18 +4971,31 @@ public function get_attribute_names_with_prefix( $prefix ) { * * @return string */ - public function get_modifiable_text() { - if ( isset( $this->current_element ) ) { - if ( WP_HTML_Stack_Event::POP === $this->current_element->operation ) { - return ''; - } + public function get_modifiable_text(): string { + return $this->is_virtual() ? '' : parent::get_modifiable_text(); + } - $mark = $this->bookmarks[ $this->current_element->token->bookmark_name ]; - if ( 0 === $mark->length ) { - return ''; - } - } - return parent::get_modifiable_text(); + /** + * Indicates what kind of comment produced the comment node. + * + * Because there are different kinds of HTML syntax which produce + * comments, the Tag Processor tracks and exposes this as a type + * for the comment. Nominally only regular HTML comments exist as + * they are commonly known, but a number of unrelated syntax errors + * also produce comments. + * + * @see self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT + * @see self::COMMENT_AS_CDATA_LOOKALIKE + * @see self::COMMENT_AS_INVALID_HTML + * @see self::COMMENT_AS_HTML_COMMENT + * @see self::COMMENT_AS_PI_NODE_LOOKALIKE + * + * @since 6.6.0 Subclassed for the HTML Processor. + * + * @return string|null + */ + public function get_comment_type(): ?string { + return $this->is_virtual() ? null : parent::get_comment_type(); } /** @@ -1653,7 +5009,7 @@ public function get_modifiable_text() { * @param string $bookmark_name Name of the bookmark to remove. * @return bool Whether the bookmark already existed before removal. */ - public function release_bookmark( $bookmark_name ) { + public function release_bookmark( $bookmark_name ): bool { return parent::release_bookmark( "_{$bookmark_name}" ); } @@ -1674,7 +5030,7 @@ public function release_bookmark( $bookmark_name ) { * @param string $bookmark_name Jump to the place in the document identified by this bookmark name. * @return bool Whether the internal cursor was successfully moved to the bookmark's location. */ - public function seek( $bookmark_name ) { + public function seek( $bookmark_name ): bool { // Flush any pending updates to the document before beginning. $this->get_updated_html(); @@ -1683,7 +5039,6 @@ public function seek( $bookmark_name ) { ? $this->bookmarks[ $this->state->current_token->bookmark_name ]->start : 0; $bookmark_starts_at = $this->bookmarks[ $actual_bookmark_name ]->start; - $bookmark_length = $this->bookmarks[ $actual_bookmark_name ]->length; $direction = $bookmark_starts_at > $processor_started_at ? 'forward' : 'backward'; /* @@ -1741,6 +5096,12 @@ public function seek( $bookmark_name ) { $this->state->frameset_ok = true; $this->element_queue = array(); $this->current_element = null; + + if ( isset( $this->context_node ) ) { + $this->breadcrumbs = array_slice( $this->breadcrumbs, 0, 2 ); + } else { + $this->breadcrumbs = array(); + } } // When moving forwards, reparse the document until reaching the same location as the original bookmark. @@ -1840,7 +5201,7 @@ public function seek( $bookmark_name ) { * @param string $bookmark_name Identifies this particular bookmark. * @return bool Whether the bookmark was successfully created. */ - public function set_bookmark( $bookmark_name ) { + public function set_bookmark( $bookmark_name ): bool { return parent::set_bookmark( "_{$bookmark_name}" ); } @@ -1852,7 +5213,7 @@ public function set_bookmark( $bookmark_name ) { * @param string $bookmark_name Name to identify a bookmark that potentially exists. * @return bool Whether that bookmark exists. */ - public function has_bookmark( $bookmark_name ) { + public function has_bookmark( $bookmark_name ): bool { return parent::has_bookmark( "_{$bookmark_name}" ); } @@ -1869,7 +5230,7 @@ public function has_bookmark( $bookmark_name ) { * * @see https://html.spec.whatwg.org/#close-a-p-element */ - private function close_a_p_element() { + private function close_a_p_element(): void { $this->generate_implied_end_tags( 'P' ); $this->state->stack_of_open_elements->pop_until( 'P' ); } @@ -1878,23 +5239,31 @@ private function close_a_p_element() { * Closes elements that have implied end tags. * * @since 6.4.0 + * @since 6.7.0 Full spec support. * * @see https://html.spec.whatwg.org/#generate-implied-end-tags * * @param string|null $except_for_this_element Perform as if this element doesn't exist in the stack of open elements. */ - private function generate_implied_end_tags( $except_for_this_element = null ) { + private function generate_implied_end_tags( ?string $except_for_this_element = null ): void { $elements_with_implied_end_tags = array( 'DD', 'DT', 'LI', + 'OPTGROUP', + 'OPTION', 'P', + 'RB', + 'RP', + 'RT', + 'RTC', ); - $current_node = $this->state->stack_of_open_elements->current_node(); + $no_exclusions = ! isset( $except_for_this_element ); + while ( - $current_node && $current_node->node_name !== $except_for_this_element && - in_array( $this->state->stack_of_open_elements->current_node(), $elements_with_implied_end_tags, true ) + ( $no_exclusions || ! $this->state->stack_of_open_elements->current_node_is( $except_for_this_element ) ) && + in_array( $this->state->stack_of_open_elements->current_node()->node_name, $elements_with_implied_end_tags, true ) ) { $this->state->stack_of_open_elements->pop(); } @@ -1907,23 +5276,60 @@ private function generate_implied_end_tags( $except_for_this_element = null ) { * different from generating end tags in the normal sense. * * @since 6.4.0 + * @since 6.7.0 Full spec support. * * @see WP_HTML_Processor::generate_implied_end_tags * @see https://html.spec.whatwg.org/#generate-implied-end-tags */ - private function generate_implied_end_tags_thoroughly() { + private function generate_implied_end_tags_thoroughly(): void { $elements_with_implied_end_tags = array( + 'CAPTION', + 'COLGROUP', 'DD', 'DT', 'LI', + 'OPTGROUP', + 'OPTION', 'P', + 'RB', + 'RP', + 'RT', + 'RTC', + 'TBODY', + 'TD', + 'TFOOT', + 'TH', + 'THEAD', + 'TR', ); - while ( in_array( $this->state->stack_of_open_elements->current_node(), $elements_with_implied_end_tags, true ) ) { + while ( in_array( $this->state->stack_of_open_elements->current_node()->node_name, $elements_with_implied_end_tags, true ) ) { $this->state->stack_of_open_elements->pop(); } } + /** + * Returns the adjusted current node. + * + * > The adjusted current node is the context element if the parser was created as + * > part of the HTML fragment parsing algorithm and the stack of open elements + * > has only one element in it (fragment case); otherwise, the adjusted current + * > node is the current node. + * + * @see https://html.spec.whatwg.org/#adjusted-current-node + * + * @since 6.7.0 + * + * @return WP_HTML_Token|null The adjusted current node. + */ + private function get_adjusted_current_node(): ?WP_HTML_Token { + if ( isset( $this->context_node ) && 1 === $this->state->stack_of_open_elements->count() ) { + return $this->context_node; + } + + return $this->state->stack_of_open_elements->current_node(); + } + /** * Reconstructs the active formatting elements. * @@ -1939,7 +5345,7 @@ private function generate_implied_end_tags_thoroughly() { * * @return bool Whether any formatting elements needed to be reconstructed. */ - private function reconstruct_active_formatting_elements() { + private function reconstruct_active_formatting_elements(): bool { /* * > If there are no entries in the list of active formatting elements, then there is nothing * > to reconstruct; stop this algorithm. @@ -1967,8 +5373,190 @@ private function reconstruct_active_formatting_elements() { return false; } - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Cannot reconstruct active formatting elements when advancing and rewinding is required.' ); + $this->bail( 'Cannot reconstruct active formatting elements when advancing and rewinding is required.' ); + } + + /** + * Runs the reset the insertion mode appropriately algorithm. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#reset-the-insertion-mode-appropriately + */ + private function reset_insertion_mode_appropriately(): void { + // Set the first node. + $first_node = null; + foreach ( $this->state->stack_of_open_elements->walk_down() as $first_node ) { + break; + } + + /* + * > 1. Let _last_ be false. + */ + $last = false; + foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { + /* + * > 2. Let _node_ be the last node in the stack of open elements. + * > 3. _Loop_: If _node_ is the first node in the stack of open elements, then set _last_ + * > to true, and, if the parser was created as part of the HTML fragment parsing + * > algorithm (fragment case), set node to the context element passed to + * > that algorithm. + * > … + */ + if ( $node === $first_node ) { + $last = true; + if ( isset( $this->context_node ) ) { + $node = $this->context_node; + } + } + + switch ( $node->node_name ) { + /* + * > 4. If node is a `select` element, run these substeps: + * > 1. If _last_ is true, jump to the step below labeled done. + * > 2. Let _ancestor_ be _node_. + * > 3. _Loop_: If _ancestor_ is the first node in the stack of open elements, + * > jump to the step below labeled done. + * > 4. Let ancestor be the node before ancestor in the stack of open elements. + * > … + * > 7. Jump back to the step labeled _loop_. + * > 8. _Done_: Switch the insertion mode to "in select" and return. + */ + case 'SELECT': + if ( ! $last ) { + foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $ancestor ) { + switch ( $ancestor->node_name ) { + /* + * > 5. If _ancestor_ is a `template` node, jump to the step below + * > labeled _done_. + */ + case 'TEMPLATE': + break 2; + + /* + * > 6. If _ancestor_ is a `table` node, switch the insertion mode to + * > "in select in table" and return. + */ + case 'TABLE': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT_IN_TABLE; + return; + } + } + } + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_SELECT; + return; + + /* + * > 5. If _node_ is a `td` or `th` element and _last_ is false, then switch the + * > insertion mode to "in cell" and return. + */ + case 'TD': + case 'TH': + if ( ! $last ) { + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CELL; + return; + } + break; + + /* + * > 6. If _node_ is a `tr` element, then switch the insertion mode to "in row" + * > and return. + */ + case 'TR': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW; + return; + + /* + * > 7. If _node_ is a `tbody`, `thead`, or `tfoot` element, then switch the + * > insertion mode to "in table body" and return. + */ + case 'TBODY': + case 'THEAD': + case 'TFOOT': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE_BODY; + return; + + /* + * > 8. If _node_ is a `caption` element, then switch the insertion mode to + * > "in caption" and return. + */ + case 'CAPTION': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_CAPTION; + return; + + /* + * > 9. If _node_ is a `colgroup` element, then switch the insertion mode to + * > "in column group" and return. + */ + case 'COLGROUP': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_COLUMN_GROUP; + return; + + /* + * > 10. If _node_ is a `table` element, then switch the insertion mode to + * > "in table" and return. + */ + case 'TABLE': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_TABLE; + return; + + /* + * > 11. If _node_ is a `template` element, then switch the insertion mode to the + * > current template insertion mode and return. + */ + case 'TEMPLATE': + $this->state->insertion_mode = end( $this->state->stack_of_template_insertion_modes ); + return; + + /* + * > 12. If _node_ is a `head` element and _last_ is false, then switch the + * > insertion mode to "in head" and return. + */ + case 'HEAD': + if ( ! $last ) { + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_HEAD; + return; + } + break; + + /* + * > 13. If _node_ is a `body` element, then switch the insertion mode to "in body" + * > and return. + */ + case 'BODY': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; + return; + + /* + * > 14. If _node_ is a `frameset` element, then switch the insertion mode to + * > "in frameset" and return. (fragment case) + */ + case 'FRAMESET': + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_FRAMESET; + return; + + /* + * > 15. If _node_ is an `html` element, run these substeps: + * > 1. If the head element pointer is null, switch the insertion mode to + * > "before head" and return. (fragment case) + * > 2. Otherwise, the head element pointer is not null, switch the insertion + * > mode to "after head" and return. + */ + case 'HTML': + $this->state->insertion_mode = isset( $this->state->head_element ) + ? WP_HTML_Processor_State::INSERTION_MODE_AFTER_HEAD + : WP_HTML_Processor_State::INSERTION_MODE_BEFORE_HEAD; + return; + } + } + + /* + * > 16. If _last_ is true, then switch the insertion mode to "in body" + * > and return. (fragment case) + * + * This is only reachable if `$last` is true, as per the fragment parsing case. + */ + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_BODY; } /** @@ -1980,7 +5568,7 @@ private function reconstruct_active_formatting_elements() { * * @see https://html.spec.whatwg.org/#adoption-agency-algorithm */ - private function run_adoption_agency_algorithm() { + private function run_adoption_agency_algorithm(): void { $budget = 1000; $subject = $this->get_tag(); $current_node = $this->state->stack_of_open_elements->current_node(); @@ -2021,8 +5609,7 @@ private function run_adoption_agency_algorithm() { // > If there is no such element, then return and instead act as described in the "any other end tag" entry above. if ( null === $formatting_element ) { - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when "any other end tag" is required.' ); + $this->bail( 'Cannot run adoption agency when "any other end tag" is required.' ); } // > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return. @@ -2052,7 +5639,7 @@ private function run_adoption_agency_algorithm() { continue; } - if ( self::is_special( $item->node_name ) ) { + if ( self::is_special( $item ) ) { $furthest_block = $item; break; } @@ -2074,12 +5661,37 @@ private function run_adoption_agency_algorithm() { } } - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Cannot extract common ancestor in adoption agency algorithm.' ); + $this->bail( 'Cannot extract common ancestor in adoption agency algorithm.' ); } - $this->last_error = self::ERROR_UNSUPPORTED; - throw new WP_HTML_Unsupported_Exception( 'Cannot run adoption agency when looping required.' ); + $this->bail( 'Cannot run adoption agency when looping required.' ); + } + + /** + * Runs the "close the cell" algorithm. + * + * > Where the steps above say to close the cell, they mean to run the following algorithm: + * > 1. Generate implied end tags. + * > 2. If the current node is not now a td element or a th element, then this is a parse error. + * > 3. Pop elements from the stack of open elements stack until a td element or a th element has been popped from the stack. + * > 4. Clear the list of active formatting elements up to the last marker. + * > 5. Switch the insertion mode to "in row". + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#close-the-cell + * + * @since 6.7.0 + */ + private function close_cell(): void { + $this->generate_implied_end_tags(); + // @todo Parse error if the current node is a "td" or "th" element. + foreach ( $this->state->stack_of_open_elements->walk_up() as $element ) { + $this->state->stack_of_open_elements->pop(); + if ( 'TD' === $element->node_name || 'TH' === $element->node_name ) { + break; + } + } + $this->state->active_formatting_elements->clear_up_to_last_marker(); + $this->state->insertion_mode = WP_HTML_Processor_State::INSERTION_MODE_IN_ROW; } /** @@ -2091,14 +5703,156 @@ private function run_adoption_agency_algorithm() { * * @param WP_HTML_Token $token Name of bookmark pointing to element in original input HTML. */ - private function insert_html_element( $token ) { + private function insert_html_element( WP_HTML_Token $token ): void { $this->state->stack_of_open_elements->push( $token ); } + /** + * Inserts a foreign element on to the stack of open elements. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#insert-a-foreign-element + * + * @param WP_HTML_Token $token Insert this token. The token's namespace and + * insertion point will be updated correctly. + * @param bool $only_add_to_element_stack Whether to skip the "insert an element at the adjusted + * insertion location" algorithm when adding this element. + */ + private function insert_foreign_element( WP_HTML_Token $token, bool $only_add_to_element_stack ): void { + $adjusted_current_node = $this->get_adjusted_current_node(); + + $token->namespace = $adjusted_current_node ? $adjusted_current_node->namespace : 'html'; + + if ( $this->is_mathml_integration_point() ) { + $token->integration_node_type = 'math'; + } elseif ( $this->is_html_integration_point() ) { + $token->integration_node_type = 'html'; + } + + if ( false === $only_add_to_element_stack ) { + /* + * @todo Implement the "appropriate place for inserting a node" and the + * "insert an element at the adjusted insertion location" algorithms. + * + * These algorithms mostly impacts DOM tree construction and not the HTML API. + * Here, there's no DOM node onto which the element will be appended, so the + * parser will skip this step. + * + * @see https://html.spec.whatwg.org/#insert-an-element-at-the-adjusted-insertion-location + */ + } + + $this->insert_html_element( $token ); + } + + /** + * Inserts a virtual element on the stack of open elements. + * + * @since 6.7.0 + * + * @param string $token_name Name of token to create and insert into the stack of open elements. + * @param string|null $bookmark_name Optional. Name to give bookmark for created virtual node. + * Defaults to auto-creating a bookmark name. + * @return WP_HTML_Token Newly-created virtual token. + */ + private function insert_virtual_node( $token_name, $bookmark_name = null ): WP_HTML_Token { + $here = $this->bookmarks[ $this->state->current_token->bookmark_name ]; + $name = $bookmark_name ?? $this->bookmark_token(); + + $this->bookmarks[ $name ] = new WP_HTML_Span( $here->start, 0 ); + + $token = new WP_HTML_Token( $name, $token_name, false ); + $this->insert_html_element( $token ); + return $token; + } + /* * HTML Specification Helpers */ + /** + * Indicates if the current token is a MathML integration point. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#mathml-text-integration-point + * + * @return bool Whether the current token is a MathML integration point. + */ + private function is_mathml_integration_point(): bool { + $current_token = $this->state->current_token; + if ( ! isset( $current_token ) ) { + return false; + } + + if ( 'math' !== $current_token->namespace || 'M' !== $current_token->node_name[0] ) { + return false; + } + + $tag_name = $current_token->node_name; + + return ( + 'MI' === $tag_name || + 'MO' === $tag_name || + 'MN' === $tag_name || + 'MS' === $tag_name || + 'MTEXT' === $tag_name + ); + } + + /** + * Indicates if the current token is an HTML integration point. + * + * Note that this method must be an instance method with access + * to the current token, since it needs to examine the attributes + * of the currently-matched tag, if it's in the MathML namespace. + * Otherwise it would be required to scan the HTML and ensure that + * no other accounting is overlooked. + * + * @since 6.7.0 + * + * @see https://html.spec.whatwg.org/#html-integration-point + * + * @return bool Whether the current token is an HTML integration point. + */ + private function is_html_integration_point(): bool { + $current_token = $this->state->current_token; + if ( ! isset( $current_token ) ) { + return false; + } + + if ( 'html' === $current_token->namespace ) { + return false; + } + + $tag_name = $current_token->node_name; + + if ( 'svg' === $current_token->namespace ) { + return ( + 'DESC' === $tag_name || + 'FOREIGNOBJECT' === $tag_name || + 'TITLE' === $tag_name + ); + } + + if ( 'math' === $current_token->namespace ) { + if ( 'ANNOTATION-XML' !== $tag_name ) { + return false; + } + + $encoding = $this->get_attribute( 'encoding' ); + + return ( + is_string( $encoding ) && + ( + 0 === strcasecmp( $encoding, 'application/xhtml+xml' ) || + 0 === strcasecmp( $encoding, 'text/html' ) + ) + ); + } + } + /** * Returns whether an element of a given name is in the HTML special category. * @@ -2106,11 +5860,17 @@ private function insert_html_element( $token ) { * * @see https://html.spec.whatwg.org/#special * - * @param string $tag_name Name of element to check. + * @param WP_HTML_Token|string $tag_name Node to check, or only its name if in the HTML namespace. * @return bool Whether the element of the given name is in the special category. */ - public static function is_special( $tag_name ) { - $tag_name = strtoupper( $tag_name ); + public static function is_special( $tag_name ): bool { + if ( is_string( $tag_name ) ) { + $tag_name = strtoupper( $tag_name ); + } else { + $tag_name = 'html' === $tag_name->namespace + ? strtoupper( $tag_name->node_name ) + : "{$tag_name->namespace} {$tag_name->node_name}"; + } return ( 'ADDRESS' === $tag_name || @@ -2198,17 +5958,17 @@ public static function is_special( $tag_name ) { 'XMP' === $tag_name || // MathML. - 'MI' === $tag_name || - 'MO' === $tag_name || - 'MN' === $tag_name || - 'MS' === $tag_name || - 'MTEXT' === $tag_name || - 'ANNOTATION-XML' === $tag_name || + 'math MI' === $tag_name || + 'math MO' === $tag_name || + 'math MN' === $tag_name || + 'math MS' === $tag_name || + 'math MTEXT' === $tag_name || + 'math ANNOTATION-XML' === $tag_name || // SVG. - 'FOREIGNOBJECT' === $tag_name || - 'DESC' === $tag_name || - 'TITLE' === $tag_name + 'svg DESC' === $tag_name || + 'svg FOREIGNOBJECT' === $tag_name || + 'svg TITLE' === $tag_name ); } @@ -2224,7 +5984,7 @@ public static function is_special( $tag_name ) { * @param string $tag_name Name of HTML tag to check. * @return bool Whether the given tag is an HTML Void Element. */ - public static function is_void( $tag_name ) { + public static function is_void( $tag_name ): bool { $tag_name = strtoupper( $tag_name ); return ( @@ -2249,6 +6009,53 @@ public static function is_void( $tag_name ) { ); } + /** + * Gets an encoding from a given string. + * + * This is an algorithm defined in the WHAT-WG specification. + * + * Example: + * + * 'UTF-8' === self::get_encoding( 'utf8' ); + * 'UTF-8' === self::get_encoding( " \tUTF-8 " ); + * null === self::get_encoding( 'UTF-7' ); + * null === self::get_encoding( 'utf8; charset=' ); + * + * @see https://encoding.spec.whatwg.org/#concept-encoding-get + * + * @todo As this parser only supports UTF-8, only the UTF-8 + * encodings are detected. Add more as desired, but the + * parser will bail on non-UTF-8 encodings. + * + * @since 6.7.0 + * + * @param string $label A string which may specify a known encoding. + * @return string|null Known encoding if matched, otherwise null. + */ + protected static function get_encoding( string $label ): ?string { + /* + * > Remove any leading and trailing ASCII whitespace from label. + */ + $label = trim( $label, " \t\f\r\n" ); + + /* + * > If label is an ASCII case-insensitive match for any of the labels listed in the + * > table below, then return the corresponding encoding; otherwise return failure. + */ + switch ( strtolower( $label ) ) { + case 'unicode-1-1-utf-8': + case 'unicode11utf8': + case 'unicode20utf8': + case 'utf-8': + case 'utf8': + case 'x-unicode20utf8': + return 'UTF-8'; + + default: + return null; + } + } + /* * Constants that would pollute the top of the class if they were found there. */ diff --git a/src/wp-includes/html-api/class-wp-html-span.php b/src/wp-includes/html-api/class-wp-html-span.php index b1ab865af3bed..04a1d5258c904 100644 --- a/src/wp-includes/html-api/class-wp-html-span.php +++ b/src/wp-includes/html-api/class-wp-html-span.php @@ -49,7 +49,7 @@ class WP_HTML_Span { * @param int $start Byte offset into document where replacement span begins. * @param int $length Byte length of span. */ - public function __construct( $start, $length ) { + public function __construct( int $start, int $length ) { $this->start = $start; $this->length = $length; } diff --git a/src/wp-includes/html-api/class-wp-html-stack-event.php b/src/wp-includes/html-api/class-wp-html-stack-event.php index 1f58063276992..dcb3c79ef1003 100644 --- a/src/wp-includes/html-api/class-wp-html-stack-event.php +++ b/src/wp-includes/html-api/class-wp-html-stack-event.php @@ -56,14 +56,27 @@ class WP_HTML_Stack_Event { */ public $operation; + /** + * Indicates if the stack element is a real or virtual node. + * + * @since 6.6.0 + * + * @var string + */ + public $provenance; + /** * Constructor function. * - * @param WP_HTML_Token $token Token associated with stack event, always an opening token. - * @param string $operation One of self::PUSH or self::POP. + * @since 6.6.0 + * + * @param WP_HTML_Token $token Token associated with stack event, always an opening token. + * @param string $operation One of self::PUSH or self::POP. + * @param string $provenance "virtual" or "real". */ - public function __construct( $token, $operation ) { - $this->token = $token; - $this->operation = $operation; + public function __construct( WP_HTML_Token $token, string $operation, string $provenance ) { + $this->token = $token; + $this->operation = $operation; + $this->provenance = $provenance; } } diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 5ba8aebc039e4..e4397b2644f9d 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -129,7 +129,7 @@ * $processor = new WP_HTML_Tag_Processor( '
' ); * true === $processor->next_tag( 'DIV' ); * - * #### Special elements + * #### Special self-contained elements * * Some HTML elements are handled in a special way; their start and end tags * act like a void tag. These are special because their contents can't contain @@ -511,6 +511,23 @@ class WP_HTML_Tag_Processor { */ protected $parser_state = self::STATE_READY; + /** + * Indicates whether the parser is inside foreign content, + * e.g. inside an SVG or MathML element. + * + * One of 'html', 'svg', or 'math'. + * + * Several parsing rules change based on whether the parser + * is inside foreign content, including whether CDATA sections + * are allowed and whether a self-closing flag indicates that + * an element has no content. + * + * @since 6.7.0 + * + * @var string + */ + private $parsing_namespace = 'html'; + /** * What kind of syntax token became an HTML comment. * @@ -524,6 +541,20 @@ class WP_HTML_Tag_Processor { */ protected $comment_type = null; + /** + * What kind of text the matched text node represents, if it was subdivided. + * + * @see self::TEXT_IS_NULL_SEQUENCE + * @see self::TEXT_IS_WHITESPACE + * @see self::TEXT_IS_GENERIC + * @see self::subdivide_text_appropriately + * + * @since 6.7.0 + * + * @var string + */ + protected $text_node_classification = self::TEXT_IS_GENERIC; + /** * How many bytes from the original HTML document have been read and parsed. * @@ -614,7 +645,7 @@ class WP_HTML_Tag_Processor { * * @since 6.5.0 * - * @var string + * @var int */ private $text_length; @@ -755,6 +786,20 @@ class WP_HTML_Tag_Processor { */ protected $seek_count = 0; + /** + * Whether the parser should skip over an immediately-following linefeed + * character, as is the case with LISTING, PRE, and TEXTAREA. + * + * > If the next token is a U+000A LINE FEED (LF) character token, then + * > ignore that token and move on to the next one. (Newlines at the start + * > of [these] elements are ignored as an authoring convenience.) + * + * @since 6.7.0 + * + * @var int|null + */ + private $skip_newline_at = null; + /** * Constructor. * @@ -766,6 +811,25 @@ public function __construct( $html ) { $this->html = $html; } + /** + * Switches parsing mode into a new namespace, such as when + * encountering an SVG tag and entering foreign content. + * + * @since 6.7.0 + * + * @param string $new_namespace One of 'html', 'svg', or 'math' indicating into what + * namespace the next tokens will be processed. + * @return bool Whether the namespace was valid and changed. + */ + public function change_parsing_namespace( string $new_namespace ): bool { + if ( ! in_array( $new_namespace, array( 'html', 'math', 'svg' ), true ) ) { + return false; + } + + $this->parsing_namespace = $new_namespace; + return true; + } + /** * Finds the next tag matching the $query. * @@ -784,7 +848,7 @@ public function __construct( $html ) { * } * @return bool Whether a tag was matched. */ - public function next_tag( $query = null ) { + public function next_tag( $query = null ): bool { $this->parse_query( $query ); $already_found = 0; @@ -829,10 +893,11 @@ public function next_tag( $query = null ) { * The Tag Processor currently only supports the tag token. * * @since 6.5.0 + * @since 6.7.0 Recognizes CDATA sections within foreign content. * * @return bool Whether a token was parsed. */ - public function next_token() { + public function next_token(): bool { return $this->base_class_next_token(); } @@ -851,7 +916,7 @@ public function next_token() { * * @return bool Whether a token was parsed. */ - private function base_class_next_token() { + private function base_class_next_token(): bool { $was_at = $this->bytes_already_parsed; $this->after_tag(); @@ -926,20 +991,24 @@ private function base_class_next_token() { $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; /* - * For non-DATA sections which might contain text that looks like HTML tags but - * isn't, scan with the appropriate alternative mode. Looking at the first letter - * of the tag name as a pre-check avoids a string allocation when it's not needed. + * Certain tags require additional processing. The first-letter pre-check + * avoids unnecessary string allocation when comparing the tag names. + * + * - IFRAME + * - LISTING (deprecated) + * - NOEMBED (deprecated) + * - NOFRAMES (deprecated) + * - PRE + * - SCRIPT + * - STYLE + * - TEXTAREA + * - TITLE + * - XMP (deprecated) */ - $t = $this->html[ $this->tag_name_starts_at ]; if ( $this->is_closing_tag || - ! ( - 'i' === $t || 'I' === $t || - 'n' === $t || 'N' === $t || - 's' === $t || 'S' === $t || - 't' === $t || 'T' === $t || - 'x' === $t || 'X' === $t - ) + 'html' !== $this->parsing_namespace || + 1 !== strspn( $this->html, 'iIlLnNpPsStTxX', $this->tag_name_starts_at, 1 ) ) { return true; } @@ -947,6 +1016,26 @@ private function base_class_next_token() { $tag_name = $this->get_tag(); /* + * For LISTING, PRE, and TEXTAREA, the first linefeed of an immediately-following + * text node is ignored as an authoring convenience. + * + * @see static::skip_newline_at + */ + if ( 'LISTING' === $tag_name || 'PRE' === $tag_name ) { + $this->skip_newline_at = $this->bytes_already_parsed; + return true; + } + + /* + * There are certain elements whose children are not DATA but are instead + * RCDATA or RAWTEXT. These cannot contain other elements, and the contents + * are parsed as plaintext, with character references decoded in RCDATA but + * not in RAWTEXT. + * + * These elements are described here as "self-contained" or special atomic + * elements whose end tag is consumed with the opening tag, and they will + * contain modifiable text inside of them. + * * Preserve the opening tag pointers, as these will be overwritten * when finding the closing tag. They will be reset after finding * the closing to tag to point to the opening of the special atomic @@ -959,7 +1048,6 @@ private function base_class_next_token() { $duplicate_attributes = $this->duplicate_attributes; // Find the closing tag if necessary. - $found_closer = false; switch ( $tag_name ) { case 'SCRIPT': $found_closer = $this->skip_script_data(); @@ -1033,7 +1121,7 @@ private function base_class_next_token() { * * @return bool Whether the parse paused at the start of an incomplete token. */ - public function paused_at_incomplete_token() { + public function paused_at_incomplete_token(): bool { return self::STATE_INCOMPLETE_INPUT === $this->parser_state; } @@ -1086,7 +1174,7 @@ public function class_list() { * * @see https://www.w3.org/TR/CSS2/syndata.html#x1 */ - $name = strtolower( substr( $class, $at, $length ) ); + $name = str_replace( "\x00", "\u{FFFD}", strtolower( substr( $class, $at, $length ) ) ); $at += $length; /* @@ -1112,7 +1200,7 @@ public function class_list() { * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. * @return bool|null Whether the matched tag contains the given class name, or null if not matched. */ - public function has_class( $wanted_class ) { + public function has_class( $wanted_class ): ?bool { if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { return null; } @@ -1209,7 +1297,7 @@ public function has_class( $wanted_class ) { * @param string $name Identifies this particular bookmark. * @return bool Whether the bookmark was successfully created. */ - public function set_bookmark( $name ) { + public function set_bookmark( $name ): bool { // It only makes sense to set a bookmark if the parser has paused on a concrete token. if ( self::STATE_COMPLETE === $this->parser_state || @@ -1242,7 +1330,7 @@ public function set_bookmark( $name ) { * @param string $name Name of the bookmark to remove. * @return bool Whether the bookmark already existed before removal. */ - public function release_bookmark( $name ) { + public function release_bookmark( $name ): bool { if ( ! array_key_exists( $name, $this->bookmarks ) ) { return false; } @@ -1262,7 +1350,7 @@ public function release_bookmark( $name ) { * @param string $tag_name The uppercase tag name which will close the RAWTEXT region. * @return bool Whether an end to the RAWTEXT region was found before the end of the document. */ - private function skip_rawtext( $tag_name ) { + private function skip_rawtext( string $tag_name ): bool { /* * These two functions distinguish themselves on whether character references are * decoded, and since functionality to read the inner markup isn't supported, it's @@ -1281,7 +1369,7 @@ private function skip_rawtext( $tag_name ) { * @param string $tag_name The uppercase tag name which will close the RCDATA region. * @return bool Whether an end to the RCDATA region was found before the end of the document. */ - private function skip_rcdata( $tag_name ) { + private function skip_rcdata( string $tag_name ): bool { $html = $this->html; $doc_length = strlen( $html ); $tag_length = strlen( $tag_name ); @@ -1369,7 +1457,7 @@ private function skip_rcdata( $tag_name ) { * * @return bool Whether the script tag was closed before the end of the document. */ - private function skip_script_data() { + private function skip_script_data(): bool { $state = 'unescaped'; $html = $this->html; $doc_length = strlen( $html ); @@ -1394,8 +1482,15 @@ private function skip_script_data() { continue; } - // Everything of interest past here starts with "<". - if ( $at + 1 >= $doc_length || '<' !== $html[ $at++ ] ) { + if ( $at + 1 >= $doc_length ) { + return false; + } + + /* + * Everything of interest past here starts with "<". + * Check this character and advance position regardless. + */ + if ( '<' !== $html[ $at++ ] ) { continue; } @@ -1516,7 +1611,7 @@ private function skip_script_data() { * * @return bool Whether a tag was found before the end of the document. */ - private function parse_next_tag() { + private function parse_next_tag(): bool { $this->after_tag(); $html = $this->html; @@ -1524,21 +1619,10 @@ private function parse_next_tag() { $was_at = $this->bytes_already_parsed; $at = $was_at; - while ( false !== $at && $at < $doc_length ) { + while ( $at < $doc_length ) { $at = strpos( $html, '<', $at ); - - /* - * This does not imply an incomplete parse; it indicates that there - * can be nothing left in the document other than a #text node. - */ if ( false === $at ) { - $this->parser_state = self::STATE_TEXT_NODE; - $this->token_starts_at = $was_at; - $this->token_length = strlen( $html ) - $was_at; - $this->text_starts_at = $was_at; - $this->text_length = $this->token_length; - $this->bytes_already_parsed = strlen( $html ); - return true; + break; } if ( $at > $was_at ) { @@ -1554,19 +1638,9 @@ private function parse_next_tag() { * * @see https://html.spec.whatwg.org/#tag-open-state */ - if ( strlen( $html ) > $at + 1 ) { - $next_character = $html[ $at + 1 ]; - $at_another_node = ( - '!' === $next_character || - '/' === $next_character || - '?' === $next_character || - ( 'A' <= $next_character && $next_character <= 'Z' ) || - ( 'a' <= $next_character && $next_character <= 'z' ) - ); - if ( ! $at_another_node ) { - ++$at; - continue; - } + if ( 1 !== strspn( $html, '!/?abcdefghijklmnopqrstuvwxyzABCEFGHIJKLMNOPQRSTUVWXYZ', $at + 1, 1 ) ) { + ++$at; + continue; } $this->parser_state = self::STATE_TEXT_NODE; @@ -1630,11 +1704,7 @@ private function parse_next_tag() { * `|$)) # - Normative HTML comments. | ]*> # - Closing tags with invalid tag names. + | + ]*> # - Invalid markup declaration nodes. Not all invalid nodes + # are matched so as to avoid breaking legacy behaviors. ) | (<[^>]*(>|$)|>) # Tag-like spans of text. @@ -1114,22 +1117,30 @@ function wp_kses_split2( $content, $allowed_html, $allowed_protocols ) { } /* - * When a closing tag appears with a name that isn't a valid tag name, - * it must be interpreted as an HTML comment. It extends until the - * first `>` character after the initial opening `` + * and then transforms the entire span into an HTML comment. * * Preserve these comments and do not treat them like tags. + * + * @see https://html.spec.whatwg.org/#bogus-comment-state */ - if ( 1 === preg_match( '~^]*>$~', $content ) ) { - $content = substr( $content, 2, -1 ); - $transformed = null; + if ( 1 === preg_match( '~^(?:]*>|]*>)$~', $content ) ) { + /** + * Since the pattern matches `` and also ``, this will + * preserve the type of the cleaned-up token in the output. + */ + $opener = $content[1]; + $content = substr( $content, 2, -1 ); - while ( $transformed !== $content ) { - $transformed = wp_kses( $content, $allowed_html, $allowed_protocols ); - $content = $transformed; - } + do { + $prev = $content; + $content = wp_kses( $content, $allowed_html, $allowed_protocols ); + } while ( $prev !== $content ); - return ""; + // Recombine the modified inner content with the original token structure. + return "<{$opener}{$content}>"; } /* @@ -2515,6 +2526,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) { 'list-style-type', 'object-fit', 'object-position', + 'opacity', 'overflow', 'vertical-align', 'writing-mode', diff --git a/src/wp-includes/l10n/class-wp-translation-file-mo.php b/src/wp-includes/l10n/class-wp-translation-file-mo.php index 3f5e72597ca5f..f50c0ec1cf2d1 100644 --- a/src/wp-includes/l10n/class-wp-translation-file-mo.php +++ b/src/wp-includes/l10n/class-wp-translation-file-mo.php @@ -154,7 +154,7 @@ protected function parse_file(): bool { // Metadata about the MO file is stored in the first translation entry. if ( '' === $original ) { foreach ( explode( "\n", $translation ) as $meta_line ) { - if ( '' === $meta_line ) { + if ( '' === $meta_line || ! str_contains( $meta_line, ':' ) ) { continue; } diff --git a/src/wp-includes/link-template.php b/src/wp-includes/link-template.php index dde12c16168be..4cfc47f83ec05 100644 --- a/src/wp-includes/link-template.php +++ b/src/wp-includes/link-template.php @@ -1092,7 +1092,7 @@ function get_edit_term_link( $term, $taxonomy = '', $object_type = '' ) { } $args = array( - 'taxonomy' => $taxonomy, + 'taxonomy' => $tax->name, 'tag_ID' => $term_id, ); @@ -1595,27 +1595,39 @@ function get_delete_post_link( $post = 0, $deprecated = '', $force_delete = fals * Retrieves the edit comment link. * * @since 2.3.0 + * @since 6.7.0 The $context parameter was added. * * @param int|WP_Comment $comment_id Optional. Comment ID or WP_Comment object. - * @return string|void The edit comment link URL for the given comment. + * @param string $context Optional. Context in which the URL should be used. Either 'display', + * to include HTML entities, or 'url'. Default 'display'. + * @return string|void The edit comment link URL for the given comment, or void if the comment id does not exist or + * the current user is not allowed to edit it. */ -function get_edit_comment_link( $comment_id = 0 ) { +function get_edit_comment_link( $comment_id = 0, $context = 'display' ) { $comment = get_comment( $comment_id ); - if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) { + if ( ! is_object( $comment ) || ! current_user_can( 'edit_comment', $comment->comment_ID ) ) { return; } - $location = admin_url( 'comment.php?action=editcomment&c=' ) . $comment->comment_ID; + if ( 'display' === $context ) { + $action = 'comment.php?action=editcomment&c='; + } else { + $action = 'comment.php?action=editcomment&c='; + } + + $location = admin_url( $action ) . $comment->comment_ID; /** * Filters the comment edit link. * - * @since 2.3.0 + * @since 6.7.0 The $comment_id and $context parameters are now being passed to the filter. * * @param string $location The edit link. + * @param int $comment_id Optional. Unique ID of the comment to generate an edit link. + * @param int $context Optional. Context to include HTML entities in link. Default 'display'. */ - return apply_filters( 'get_edit_comment_link', $location ); + return apply_filters( 'get_edit_comment_link', $location, $comment_id, $context ); } /** @@ -4328,6 +4340,7 @@ function is_avatar_comment_type( $comment_type ) { * Retrieves default data about the avatar. * * @since 4.2.0 + * @since 6.7.0 Gravatar URLs always use HTTPS. * * @param mixed $id_or_email The avatar to retrieve. Accepts a user ID, Gravatar MD5 hash, * user email, WP_User object, WP_Post object, or WP_Comment object. @@ -4358,6 +4371,9 @@ function is_avatar_comment_type( $comment_type ) { * - 'X' (even more mature than above) * Default is the value of the 'avatar_rating' option. * @type string $scheme URL scheme to use. See set_url_scheme() for accepted values. + * For Gravatars this setting is ignored and HTTPS is used to avoid + * unnecessary redirects. The setting is retained for systems using + * the {@see 'pre_get_avatar_data'} filter to customize avatars. * Default null. * @type array $processed_args When the function returns, the value will be the processed/sanitized $args * plus a "found_avatar" guess. Pass as a reference. Default null. @@ -4508,9 +4524,6 @@ function get_avatar_data( $id_or_email, $args = null ) { if ( $email_hash ) { $args['found_avatar'] = true; - $gravatar_server = hexdec( $email_hash[0] ) % 3; - } else { - $gravatar_server = rand( 0, 2 ); } $url_args = array( @@ -4520,15 +4533,17 @@ function get_avatar_data( $id_or_email, $args = null ) { 'r' => $args['rating'], ); - if ( is_ssl() ) { - $url = 'https://secure.gravatar.com/avatar/' . $email_hash; - } else { - $url = sprintf( 'http://%d.gravatar.com/avatar/%s', $gravatar_server, $email_hash ); - } + /* + * Gravatars are always served over HTTPS. + * + * The Gravatar website redirects HTTP requests to HTTPS URLs so always + * use the HTTPS scheme to avoid unnecessary redirects. + */ + $url = 'https://secure.gravatar.com/avatar/' . $email_hash; $url = add_query_arg( rawurlencode_deep( array_filter( $url_args ) ), - set_url_scheme( $url, $args['scheme'] ) + $url ); /** diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 6b743d459aa7b..b0b8209235c3e 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -453,8 +453,6 @@ function wp_is_maintenance_mode() { /** * Gets the time elapsed so far during this PHP script. * - * Uses REQUEST_TIME_FLOAT that appeared in PHP 5.4.0. - * * @since 5.8.0 * * @return float Seconds since the PHP script started. @@ -1681,9 +1679,8 @@ function wp_is_ini_value_changeable( $setting ) { } } - // Bit operator to workaround https://bugs.php.net/bug.php?id=44936 which changes access level to 63 in PHP 5.2.6 - 5.2.17. if ( isset( $ini_all[ $setting ]['access'] ) - && ( INI_ALL === ( $ini_all[ $setting ]['access'] & 7 ) || INI_USER === ( $ini_all[ $setting ]['access'] & 7 ) ) + && ( INI_ALL === $ini_all[ $setting ]['access'] || INI_USER === $ini_all[ $setting ]['access'] ) ) { return true; } diff --git a/src/wp-includes/media-template.php b/src/wp-includes/media-template.php index e0bd68a8f4653..3a9bd723ba78f 100644 --- a/src/wp-includes/media-template.php +++ b/src/wp-includes/media-template.php @@ -605,8 +605,12 @@ function wp_print_media_templates() {
<# if ( data.image && data.image.src && data.image.src !== data.icon ) { #> - <# } else if ( data.sizes && data.sizes.medium ) { #> - + <# } else if ( data.sizes ) { + if ( data.sizes.medium ) { #> + + <# } else { #> + + <# } #> <# } else { #> <# } #> @@ -840,7 +844,7 @@ function wp_print_media_templates() { @@ -867,7 +871,7 @@ function wp_print_media_templates() { @@ -1096,7 +1100,7 @@ function wp_print_media_templates() { @@ -1113,7 +1117,7 @@ function wp_print_media_templates() { @@ -1160,7 +1164,7 @@ function wp_print_media_templates() { @@ -1236,7 +1240,7 @@ function wp_print_media_templates() { @@ -1356,7 +1360,7 @@ function wp_print_media_templates() { - + @@ -1455,7 +1459,7 @@ function wp_print_media_templates() { - + @@ -1542,21 +1546,31 @@ function wp_print_media_templates() { - diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 78a3cb9645966..8a4277a2f57b9 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -1366,13 +1366,17 @@ function wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attac * (which is to say, when they share the domain name of the current request). */ if ( is_ssl() && ! str_starts_with( $image_baseurl, 'https' ) ) { - // Since the `Host:` header might contain a port we should - // compare it against the image URL using the same port. + /* + * Since the `Host:` header might contain a port, it should + * be compared against the image URL using the same port. + */ $parsed = parse_url( $image_baseurl ); - $domain = $parsed['host']; + $domain = isset( $parsed['host'] ) ? $parsed['host'] : ''; + if ( isset( $parsed['port'] ) ) { $domain .= ':' . $parsed['port']; } + if ( $_SERVER['HTTP_HOST'] === $domain ) { $image_baseurl = set_url_scheme( $image_baseurl, 'https' ); } @@ -4062,8 +4066,7 @@ function wp_get_image_editor( $path, $args = array() ) { // Check and set the output mime type mapped to the input type. if ( isset( $args['mime_type'] ) ) { - /** This filter is documented in wp-includes/class-wp-image-editor.php */ - $output_format = apply_filters( 'image_editor_output_format', array(), $path, $args['mime_type'] ); + $output_format = wp_get_image_editor_output_format( $path, $args['mime_type'] ); if ( isset( $output_format[ $args['mime_type'] ] ) ) { $args['output_mime_type'] = $output_format[ $args['mime_type'] ]; } @@ -4222,6 +4225,11 @@ function wp_plupload_default_settings() { $defaults['avif_upload_error'] = true; } + // Check if HEIC images can be edited. + if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) { + $defaults['heic_upload_error'] = true; + } + /** * Filters the Plupload default settings. * @@ -5481,12 +5489,17 @@ function _wp_add_additional_image_sizes() { * Callback to enable showing of the user error when uploading .heic images. * * @since 5.5.0 + * @since 6.7.0 The default behavior is to enable heic uploads as long as the server + * supports the format. The uploads are converted to JPEG's by default. * * @param array[] $plupload_settings The settings for Plupload.js. * @return array[] Modified settings for Plupload.js. */ function wp_show_heic_upload_error( $plupload_settings ) { - $plupload_settings['heic_upload_error'] = true; + // Check if HEIC images can be edited. + if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) { + $plupload_init['heic_upload_error'] = true; + } return $plupload_settings; } @@ -5584,6 +5597,29 @@ function wp_getimagesize( $filename, ?array &$image_info = null ) { } } + // For PHP versions that don't support HEIC images, extract the size info using Imagick when available. + if ( 'image/heic' === wp_get_image_mime( $filename ) ) { + $editor = wp_get_image_editor( $filename ); + if ( is_wp_error( $editor ) ) { + return false; + } + // If the editor for HEICs is Imagick, use it to get the image size. + if ( $editor instanceof WP_Image_Editor_Imagick ) { + $size = $editor->get_size(); + return array( + $size['width'], + $size['height'], + IMAGETYPE_HEIC, + sprintf( + 'width="%d" height="%d"', + $size['width'], + $size['height'] + ), + 'mime' => 'image/heic', + ); + } + } + // The image could not be parsed. return false; } @@ -6067,3 +6103,37 @@ function wp_high_priority_element_flag( $value = null ) { return $high_priority_element; } + +/** + * Determines the output format for the image editor. + * + * @since 6.7.0 + * @access private + * + * @param string $filename Path to the image. + * @param string $mime_type The source image mime type. + * @return string[] An array of mime type mappings. + */ +function wp_get_image_editor_output_format( $filename, $mime_type ) { + /** + * Filters the image editor output format mapping. + * + * Enables filtering the mime type used to save images. By default, + * the mapping array is empty, so the mime type matches the source image. + * + * @see WP_Image_Editor::get_output_format() + * + * @since 5.8.0 + * @since 6.7.0 The default was changed from array() to array( 'image/heic' => 'image/jpeg' ). + * + * @param string[] $output_format { + * An array of mime type mappings. Maps a source mime type to a new + * destination mime type. Default maps uploaded HEIC images to JPEG output. + * + * @type string ...$0 The new mime type. + * } + * @param string $filename Path to the image. + * @param string $mime_type The source image mime type. + */ + return apply_filters( 'image_editor_output_format', array( 'image/heic' => 'image/jpeg' ), $filename, $mime_type ); +} diff --git a/src/wp-includes/meta.php b/src/wp-includes/meta.php index 96ace4e9726ba..32838b135dd34 100644 --- a/src/wp-includes/meta.php +++ b/src/wp-includes/meta.php @@ -568,7 +568,8 @@ function delete_metadata( $meta_type, $object_id, $meta_key, $meta_value = '', $ * The value of the meta field if `$single` is true. * False for an invalid `$object_id` (non-numeric, zero, or negative value), * or if `$meta_type` is not specified. - * An empty string if a valid but non-existing object ID is passed. + * An empty array if a valid but non-existing object ID is passed and `$single` is false. + * An empty string if a valid but non-existing object ID is passed and `$single` is true. */ function get_metadata( $meta_type, $object_id, $meta_key = '', $single = false ) { $value = get_metadata_raw( $meta_type, $object_id, $meta_key, $single ); diff --git a/src/wp-includes/ms-site.php b/src/wp-includes/ms-site.php index 6e197587902a5..71d61cd6da68f 100644 --- a/src/wp-includes/ms-site.php +++ b/src/wp-includes/ms-site.php @@ -1069,7 +1069,8 @@ function delete_site_meta( $site_id, $meta_key, $meta_value = '' ) { * @return mixed An array of values if `$single` is false. * The value of meta data field if `$single` is true. * False for an invalid `$site_id` (non-numeric, zero, or negative value). - * An empty string if a valid but non-existing site ID is passed. + * An empty array if a valid but non-existing site ID is passed and `$single` is false. + * An empty string if a valid but non-existing site ID is passed and `$single` is true. */ function get_site_meta( $site_id, $key = '', $single = false ) { return get_metadata( 'blog', $site_id, $key, $single ); diff --git a/src/wp-includes/nav-menu.php b/src/wp-includes/nav-menu.php index a063835fda341..d808c4e212d39 100644 --- a/src/wp-includes/nav-menu.php +++ b/src/wp-includes/nav-menu.php @@ -491,17 +491,25 @@ function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0, $menu_item $args['menu-item-url'] = ''; $original_title = ''; + if ( 'taxonomy' === $args['menu-item-type'] ) { - $original_parent = get_term_field( 'parent', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' ); - $original_title = get_term_field( 'name', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' ); - } elseif ( 'post_type' === $args['menu-item-type'] ) { + $original_object = get_term( $args['menu-item-object-id'], $args['menu-item-object'] ); + if ( $original_object instanceof WP_Term ) { + $original_parent = get_term_field( 'parent', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' ); + $original_title = get_term_field( 'name', $args['menu-item-object-id'], $args['menu-item-object'], 'raw' ); + } + } elseif ( 'post_type' === $args['menu-item-type'] ) { $original_object = get_post( $args['menu-item-object-id'] ); - $original_parent = (int) $original_object->post_parent; - $original_title = $original_object->post_title; + + if ( $original_object instanceof WP_Post ) { + $original_parent = (int) $original_object->post_parent; + $original_title = $original_object->post_title; + } } elseif ( 'post_type_archive' === $args['menu-item-type'] ) { $original_object = get_post_type_object( $args['menu-item-object'] ); - if ( $original_object ) { + + if ( $original_object instanceof WP_Post_Type ) { $original_title = $original_object->labels->archives; } } diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 4dee6ce1188e9..812fa663e2340 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -375,12 +375,13 @@ function get_options( $options ) { * by the plugin which are generally autoloaded can be set to not autoload when the plugin is inactive. * * @since 6.4.0 + * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $options Associative array of option names and their autoload values to set. The option names are - * expected to not be SQL-escaped. The autoload values accept 'yes'|true to enable or 'no'|false - * to disable. + * expected to not be SQL-escaped. The autoload values should be boolean values. For backward + * compatibility 'yes' and 'no' are also accepted, though using these values is deprecated. * @return array Associative array of all provided $options as keys and boolean values for whether their autoload value * was updated. */ @@ -398,7 +399,12 @@ function wp_set_option_autoload_values( array $options ) { $results = array(); foreach ( $options as $option => $autoload ) { wp_protect_special_option( $option ); // Ensure only valid options can be passed. - if ( 'off' === $autoload || 'no' === $autoload || false === $autoload ) { // Sanitize autoload value and categorize accordingly. + + /* + * Sanitize autoload value and categorize accordingly. + * The values 'yes', 'no', 'on', and 'off' are supported for backward compatibility. + */ + if ( 'off' === $autoload || 'no' === $autoload || false === $autoload ) { $grouped_options['off'][] = $option; } else { $grouped_options['on'][] = $option; @@ -496,12 +502,14 @@ function wp_set_option_autoload_values( array $options ) { * each option at once. * * @since 6.4.0 + * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @see wp_set_option_autoload_values() * - * @param string[] $options List of option names. Expected to not be SQL-escaped. - * @param string|bool $autoload Autoload value to control whether to load the options when WordPress starts up. - * Accepts 'yes'|true to enable or 'no'|false to disable. + * @param string[] $options List of option names. Expected to not be SQL-escaped. + * @param bool $autoload Autoload value to control whether to load the options when WordPress starts up. + * For backward compatibility 'yes' and 'no' are also accepted, though using these values is + * deprecated. * @return array Associative array of all provided $options as keys and boolean values for whether their autoload value * was updated. */ @@ -518,12 +526,14 @@ function wp_set_options_autoload( array $options, $autoload ) { * multiple options at once. * * @since 6.4.0 + * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @see wp_set_option_autoload_values() * - * @param string $option Name of the option. Expected to not be SQL-escaped. - * @param string|bool $autoload Autoload value to control whether to load the option when WordPress starts up. - * Accepts 'yes'|true to enable or 'no'|false to disable. + * @param string $option Name of the option. Expected to not be SQL-escaped. + * @param bool $autoload Autoload value to control whether to load the option when WordPress starts up. + * For backward compatibility 'yes' and 'no' are also accepted, though using these values is + * deprecated. * @return bool True if the autoload value was modified, false otherwise. */ function wp_set_option_autoload( $option, $autoload ) { @@ -803,17 +813,19 @@ function wp_load_core_site_options( $network_id = null ) { * * @since 1.0.0 * @since 4.2.0 The `$autoload` parameter was added. + * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $option Name of the option to update. Expected to not be SQL-escaped. * @param mixed $value Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped. * @param bool|null $autoload Optional. Whether to load the option when WordPress starts up. - * Accepts a boolean, or `null` to stick with the initial value or, if no initial value is set, - * to leave the decision up to default heuristics in WordPress. - * For existing options, - * `$autoload` can only be updated using `update_option()` if `$value` is also changed. - * For backward compatibility 'yes' and 'no' are also accepted. + * Accepts a boolean, or `null` to stick with the initial value or, if no initial value is + * set, to leave the decision up to default heuristics in WordPress. + * For existing options, `$autoload` can only be updated using `update_option()` if `$value` + * is also changed. + * For backward compatibility 'yes' and 'no' are also accepted, though using these values is + * deprecated. * Autoloading too many options can lead to performance problems, especially if the * options are not frequently used. For options which are accessed across several places * in the frontend, it is recommended to autoload them, by using true. @@ -1026,6 +1038,7 @@ function update_option( $option, $value, $autoload = null ) { * * @since 1.0.0 * @since 6.6.0 The $autoload parameter's default value was changed to null. + * @since 6.7.0 The autoload values 'yes' and 'no' are deprecated. * * @global wpdb $wpdb WordPress database abstraction object. * @@ -1034,11 +1047,12 @@ function update_option( $option, $value, $autoload = null ) { * Expected to not be SQL-escaped. * @param string $deprecated Optional. Description. Not used anymore. * @param bool|null $autoload Optional. Whether to load the option when WordPress starts up. - * Accepts a boolean, or `null` to leave the decision up to default heuristics in WordPress. - * For backward compatibility 'yes' and 'no' are also accepted. + * Accepts a boolean, or `null` to leave the decision up to default heuristics in + * WordPress. For backward compatibility 'yes' and 'no' are also accepted, though using + * these values is deprecated. * Autoloading too many options can lead to performance problems, especially if the * options are not frequently used. For options which are accessed across several places - * in the frontend, it is recommended to autoload them, by using 'yes'|true. + * in the frontend, it is recommended to autoload them, by using true. * For options which are accessed only on few specific URLs, it is recommended * to not autoload them, by using false. * Default is null, which means WordPress will determine the autoload value. @@ -1217,6 +1231,15 @@ function delete_option( $option ) { } else { wp_cache_delete( $option, 'options' ); } + + $notoptions = wp_cache_get( 'notoptions', 'options' ); + + if ( ! is_array( $notoptions ) ) { + $notoptions = array(); + } + $notoptions[ $option ] = true; + + wp_cache_set( 'notoptions', $notoptions, 'options' ); } if ( $result ) { @@ -2255,6 +2278,17 @@ function delete_network_option( $network_id, $option ) { 'site_id' => $network_id, ) ); + + if ( $result ) { + $notoptions_key = "$network_id:notoptions"; + $notoptions = wp_cache_get( $notoptions_key, 'site-options' ); + + if ( ! is_array( $notoptions ) ) { + $notoptions = array(); + } + $notoptions[ $option ] = true; + wp_cache_set( $notoptions_key, $notoptions, 'site-options' ); + } } if ( $result ) { diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 5ed63e539cf66..8380a597249ba 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -2224,7 +2224,15 @@ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) /* translators: %s: User login. */ $message = sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n"; $message .= __( 'To set your password, visit the following address:' ) . "\r\n\r\n"; - $message .= network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user->user_login ), 'login' ) . "\r\n\r\n"; + + /* + * Since some user login names end in a period, this could produce ambiguous URLs that + * end in a period. To avoid the ambiguity, ensure that the login is not the last query + * arg in the URL. If moving it to the end, a trailing period will need to be escaped. + * + * @see https://core.trac.wordpress.org/tickets/42957 + */ + $message .= network_site_url( 'wp-login.php?login=' . rawurlencode( $user->user_login ) . "&key=$key&action=rp", 'login' ) . "\r\n\r\n"; $message .= wp_login_url() . "\r\n"; @@ -2460,6 +2468,41 @@ function wp_salt( $scheme = 'auth' ) { } } + /* + * Determine which options to prime. + * + * If the salt keys are undefined, use a duplicate value or the + * default `put your unique phrase here` value the salt will be + * generated via `wp_generate_password()` and stored as a site + * option. These options will be primed to avoid repeated + * database requests for undefined salts. + */ + $options_to_prime = array(); + foreach ( array( 'auth', 'secure_auth', 'logged_in', 'nonce' ) as $key ) { + foreach ( array( 'key', 'salt' ) as $second ) { + $const = strtoupper( "{$key}_{$second}" ); + if ( ! defined( $const ) || true === $duplicated_keys[ constant( $const ) ] ) { + $options_to_prime[] = "{$key}_{$second}"; + } + } + } + + if ( ! empty( $options_to_prime ) ) { + /* + * Also prime `secret_key` used for undefined salting schemes. + * + * If the scheme is unknown, the default value for `secret_key` will be + * used too for the salt. This should rarely happen, so the option is only + * primed if other salts are undefined. + * + * At this point of execution it is known that a database call will be made + * to prime salts, so the `secret_key` option can be primed regardless of the + * constants status. + */ + $options_to_prime[] = 'secret_key'; + wp_prime_site_option_caches( $options_to_prime ); + } + $values = array( 'key' => '', 'salt' => '', @@ -2768,6 +2811,8 @@ function wp_rand( $min = null, $max = null ) { function wp_set_password( $password, $user_id ) { global $wpdb; + $old_user_data = get_userdata( $user_id ); + $hash = wp_hash_password( $password ); $wpdb->update( $wpdb->users, @@ -2784,11 +2829,13 @@ function wp_set_password( $password, $user_id ) { * Fires after the user password is set. * * @since 6.2.0 + * @since 6.7.0 The `$old_user_data` parameter was added. * - * @param string $password The plaintext password just set. - * @param int $user_id The ID of the user whose password was just set. + * @param string $password The plaintext password just set. + * @param int $user_id The ID of the user whose password was just set. + * @param WP_User $old_user_data Object containing user's data prior to update. */ - do_action( 'wp_set_password', $password, $user_id ); + do_action( 'wp_set_password', $password, $user_id, $old_user_data ); } endif; diff --git a/src/wp-includes/post-template.php b/src/wp-includes/post-template.php index 7b12bf6c4c602..4f3bfbef0cd7e 100644 --- a/src/wp-includes/post-template.php +++ b/src/wp-includes/post-template.php @@ -1777,9 +1777,10 @@ function get_the_password_form( $post = 0 ) { /** * Filters the HTML output for the protected post password form. * - * If modifying the password field, please note that the core database schema - * limits the password field to 20 characters regardless of the value of the - * size attribute in the form input. + * If modifying the password field, please note that the WordPress database schema + * limits the password field to 255 characters regardless of the value of the + * `minlength` or `maxlength` attributes or other validation that may be added to + * the input. * * @since 2.7.0 * @since 5.8.0 Added the `$post` parameter. diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 2f7cba613f0ed..df4aa1c32f5b9 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2607,7 +2607,8 @@ function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) { * @return mixed An array of values if `$single` is false. * The value of the meta field if `$single` is true. * False for an invalid `$post_id` (non-numeric, zero, or negative value). - * An empty string if a valid but non-existing post ID is passed. + * An empty array if a valid but non-existing post ID is passed and `$single` is false. + * An empty string if a valid but non-existing post ID is passed and `$single` is true. */ function get_post_meta( $post_id, $key = '', $single = false ) { return get_metadata( 'post', $post_id, $key, $single ); @@ -6829,7 +6830,7 @@ function wp_attachment_is( $type, $post = null ) { switch ( $type ) { case 'image': - $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' ); + $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif', 'heic' ); return in_array( $ext, $image_exts, true ); case 'audio': @@ -8094,10 +8095,11 @@ function get_available_post_mime_types( $type = 'attachment' ) { $mime_types = apply_filters( 'pre_get_available_post_mime_types', null, $type ); if ( ! is_array( $mime_types ) ) { - $mime_types = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_mime_type FROM $wpdb->posts WHERE post_type = %s", $type ) ); + $mime_types = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_mime_type FROM $wpdb->posts WHERE post_type = %s AND post_mime_type != ''", $type ) ); } - return $mime_types; + // Remove nulls from returned $mime_types. + return array_values( array_filter( $mime_types ) ); } /** diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 7aa2534520621..9b023e97ece1b 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -38,26 +38,55 @@ function register_rest_route( $route_namespace, $route, $args = array(), $overri * and namespace indexes. If you really need to register a * non-namespaced route, call `WP_REST_Server::register_route` directly. */ - _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' ); + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: 1: string value of the namespace, 2: string value of the route. */ + __( 'Routes must be namespaced with plugin or theme name and version. Instead there seems to be an empty namespace \'%1$s\' for route \'%2$s\'.' ), + '' . $route_namespace . '', + '' . $route . '' + ), + '4.4.0' + ); return false; } elseif ( empty( $route ) ) { - _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' ); + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: 1: string value of the namespace, 2: string value of the route. */ + __( 'Route must be specified. Instead within the namespace \'%1$s\', there seems to be an empty route \'%2$s\'.' ), + '' . $route_namespace . '', + '' . $route . '' + ), + '4.4.0' + ); return false; } $clean_namespace = trim( $route_namespace, '/' ); if ( $clean_namespace !== $route_namespace ) { - _doing_it_wrong( __FUNCTION__, __( 'Namespace must not start or end with a slash.' ), '5.4.2' ); + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: 1: string value of the namespace, 2: string value of the route. */ + __( 'Namespace must not start or end with a slash. Instead namespace \'%1$s\' for route \'%2$s\' seems to contain a slash.' ), + '' . $route_namespace . '', + '' . $route . '' + ), + '5.4.2' + ); } if ( ! did_action( 'rest_api_init' ) ) { _doing_it_wrong( - 'register_rest_route', + __FUNCTION__, sprintf( - /* translators: %s: rest_api_init */ - __( 'REST API routes must be registered on the %s action.' ), - 'rest_api_init' + /* translators: 1: rest_api_init, 2: string value of the route, 3: string value of the namespace. */ + __( 'REST API routes must be registered on the %1$s action. Instead route \'%2$s\' with namespace \'%3$s\' was not registered on this action.' ), + 'rest_api_init', + '' . $route . '', + '' . $route_namespace . '' ), '5.1.0' ); diff --git a/src/wp-includes/rest-api/class-wp-rest-request.php b/src/wp-includes/rest-api/class-wp-rest-request.php index c671fd3d132bc..6ed6ce667432c 100644 --- a/src/wp-includes/rest-api/class-wp-rest-request.php +++ b/src/wp-includes/rest-api/class-wp-rest-request.php @@ -165,7 +165,7 @@ public function get_headers() { * Canonicalizes the header name. * * Ensures that header names are always treated the same regardless of - * source. Header names are always case insensitive. + * source. Header names are always case-insensitive. * * Note that we treat `-` (dashes) and `_` (underscores) as the same * character, as per header parsing rules in both Apache and nginx. @@ -709,7 +709,7 @@ protected function parse_json_params() { * Parses the request body parameters. * * Parses out URL-encoded bodies for request methods that aren't supported - * natively by PHP. In PHP 5.x, only POST has these parsed automatically. + * natively by PHP. * * @since 4.4.0 */ diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index b85c020b0f112..0eedb0396bcc4 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -1667,7 +1667,7 @@ public function serve_batch_request_v1( WP_REST_Request $batch_request ) { $single_request = new WP_REST_Request( isset( $args['method'] ) ? $args['method'] : 'POST', $parsed_url['path'] ); if ( ! empty( $parsed_url['query'] ) ) { - $query_args = null; // Satisfy linter. + $query_args = array(); wp_parse_str( $parsed_url['query'], $query_args ); $single_request->set_query_params( $query_args ); } diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 97ee830261da9..0c98a729e43f9 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -304,6 +304,17 @@ protected function insert_attachment( $request ) { $attachment->post_mime_type = $type; $attachment->guid = $url; + // If the title was not set, use the original filename. + if ( empty( $attachment->post_title ) && ! empty( $files['file']['name'] ) ) { + // Remove the file extension (after the last `.`) + $tmp_title = substr( $files['file']['name'], 0, strrpos( $files['file']['name'], '.' ) ); + + if ( ! empty( $tmp_title ) ) { + $attachment->post_title = $tmp_title; + } + } + + // Fall back to the original approach. if ( empty( $attachment->post_title ) ) { $attachment->post_title = preg_replace( '/\.[^.]+$/', '', wp_basename( $file ) ); } @@ -328,8 +339,7 @@ protected function insert_attachment( $request ) { * * @since 4.7.0 * - * @param WP_Post $attachment Inserted or updated attachment - * object. + * @param WP_Post $attachment Inserted or updated attachment object. * @param WP_REST_Request $request The request sent to the API. * @param bool $creating True when creating an attachment, false when updating. */ @@ -439,7 +449,7 @@ public function update_item( $request ) { } /** - * Performs post processing on an attachment. + * Performs post-processing on an attachment. * * @since 5.3.0 * @@ -460,7 +470,7 @@ public function post_process_item( $request ) { } /** - * Checks if a given request can perform post processing on an attachment. + * Checks if a given request can perform post-processing on an attachment. * * @since 5.3.0 * @@ -520,7 +530,7 @@ public function edit_media_item( $request ) { ); } - $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' ); + $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' ); $mime_type = get_post_mime_type( $attachment_id ); if ( ! in_array( $mime_type, $supported_types, true ) ) { return new WP_Error( @@ -590,7 +600,7 @@ public function edit_media_item( $request ) { $args = $modifier['args']; switch ( $modifier['type'] ) { case 'rotate': - // Rotation direction: clockwise vs. counter clockwise. + // Rotation direction: clockwise vs. counterclockwise. $rotate = 0 - $args['angle']; if ( 0 !== $rotate ) { @@ -610,12 +620,12 @@ public function edit_media_item( $request ) { case 'crop': $size = $image_editor->get_size(); - $crop_x = round( ( $size['width'] * $args['left'] ) / 100.0 ); - $crop_y = round( ( $size['height'] * $args['top'] ) / 100.0 ); - $width = round( ( $size['width'] * $args['width'] ) / 100.0 ); - $height = round( ( $size['height'] * $args['height'] ) / 100.0 ); + $crop_x = (int) round( ( $size['width'] * $args['left'] ) / 100.0 ); + $crop_y = (int) round( ( $size['height'] * $args['top'] ) / 100.0 ); + $width = (int) round( ( $size['width'] * $args['width'] ) / 100.0 ); + $height = (int) round( ( $size['height'] * $args['height'] ) / 100.0 ); - if ( $size['width'] !== $width && $size['height'] !== $height ) { + if ( $size['width'] !== $width || $size['height'] !== $height ) { $result = $image_editor->crop( $crop_x, $crop_y, $width, $height ); if ( is_wp_error( $result ) ) { @@ -650,7 +660,7 @@ public function edit_media_item( $request ) { $filename = "{$image_name}.{$image_ext}"; - // Create the uploads sub-directory if needed. + // Create the uploads subdirectory if needed. $uploads = wp_upload_dir(); // Make the file name unique in the (new) upload directory. @@ -1196,7 +1206,7 @@ public static function get_filename_from_disposition( $disposition_header ) { continue; } - list( $type, $attr_parts ) = explode( ';', $value, 2 ); + list( , $attr_parts ) = explode( ';', $value, 2 ); $attr_parts = explode( ';', $attr_parts ); $attributes = array(); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php index 36a753416013f..5d31891678cf7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php @@ -78,7 +78,7 @@ public function get_items_permissions_check( $request ) { * @since 6.0.0 * * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $response = array(); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php index 38ac0e1881d2a..263f2a8ef2066 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php @@ -141,7 +141,7 @@ public function get_items( $request ) { $namespace = $request['namespace']; } - foreach ( $block_types as $slug => $obj ) { + foreach ( $block_types as $obj ) { if ( $namespace ) { list ( $block_namespace ) = explode( '/', $obj->name ); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php index 691760812b065..d326782f15c25 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php @@ -50,7 +50,7 @@ public function register_routes() { * * @since 5.9.0 * - * @return WP_Error|true True if the request has access, or WP_Error object. + * @return true|WP_Error True if the request has access, or WP_Error object. */ public function permissions_check() { if ( current_user_can( 'edit_theme_options' ) ) { @@ -70,7 +70,7 @@ public function permissions_check() { * * @since 5.9.0 * - * @return WP_Error|void + * @return void|WP_Error */ public function export() { // Generate the export file. diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index 8ce522c1490e2..e3057e75d86b8 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -231,7 +231,7 @@ public function update_item_permissions_check( $request ) { * * @since 5.9.0 * @since 6.2.0 Added validation of styles.css property. - * @since 6.6.0 Added registration of newly created style variations provided by the user. + * @since 6.6.0 Added registration of block style variations from theme.json sources (theme.json, user theme.json, partials). * * @param WP_REST_Request $request Request object. * @return stdClass|WP_Error Prepared item on success. WP_Error on when the custom CSS is not valid. @@ -265,23 +265,9 @@ protected function prepare_item_for_database( $request ) { $config['styles'] = $existing_config['styles']; } - /* - * If the incoming request is going to create a new variation - * that is not yet registered, we register it here. - * This is because the variations are registered on init, - * but we want this endpoint to return the new variation immediately: - * if we don't register it, it'll be stripped out of the response - * just in this request (subsequent ones will be ok). - * Take the variations defined in styles.blocks.variations from the incoming request - * that are not part of the $exsting_config. - */ - if ( isset( $request['styles']['blocks']['variations'] ) ) { - $existing_variations = isset( $existing_config['styles']['blocks']['variations'] ) ? $existing_config['styles']['blocks']['variations'] : array(); - $new_variations = array_diff_key( $request['styles']['blocks']['variations'], $existing_variations ); - if ( ! empty( $new_variations ) ) { - wp_register_block_style_variations_from_theme_json_data( $new_variations ); - } - } + // Register theme-defined variations e.g. from block style variation partials under `/styles`. + $variations = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); + wp_register_block_style_variations_from_theme_json_partials( $variations ); if ( isset( $request['settings'] ) ) { $config['settings'] = $request['settings']; @@ -341,10 +327,12 @@ public function prepare_item_for_response( $post, $request ) { } if ( rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); $data['title']['rendered'] = get_the_title( $post->ID ); remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); } if ( rest_is_field_included( 'settings', $fields ) ) { @@ -645,8 +633,12 @@ public function get_theme_items( $request ) { } $response = array(); - $variations = WP_Theme_JSON_Resolver::get_style_variations(); + // Register theme-defined variations e.g. from block style variation partials under `/styles`. + $partials = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); + wp_register_block_style_variations_from_theme_json_partials( $partials ); + + $variations = WP_Theme_JSON_Resolver::get_style_variations(); foreach ( $variations as $variation ) { $variation_theme_json = new WP_Theme_JSON( $variation ); $resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $variation_theme_json ); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php index 4119b6530e91e..9fbaa95371b54 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php @@ -510,6 +510,7 @@ public function prepare_item_for_response( $item, $request ) { if ( rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); /** This filter is documented in wp-includes/post-template.php */ $title = apply_filters( 'the_title', $menu_item->title, $menu_item->ID ); @@ -517,6 +518,7 @@ public function prepare_item_for_response( $item, $request ) { $data['title']['rendered'] = $title; remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); } if ( rest_is_field_included( 'status', $fields ) ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php index 47686b915f325..e5bff633a2a87 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php @@ -77,7 +77,7 @@ public function register_routes() { * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ public function get_items_permissions_check( $request ) { if ( ! current_user_can( 'edit_theme_options' ) ) { @@ -97,7 +97,7 @@ public function get_items_permissions_check( $request ) { * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { $data = array(); @@ -140,7 +140,7 @@ public function get_item_permissions_check( $request ) { * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { $registered_menus = get_registered_nav_menus(); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php index 41f37c9f1db8d..3345ea0aa18a7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php @@ -87,13 +87,6 @@ public function get_items_permissions_check( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { - /* - * Include an unmodified `$wp_version`, so the API can craft a response that's tailored to - * it. Some plugins modify the version in a misguided attempt to improve security by - * obscuring the version, which can cause invalid requests. - */ - require ABSPATH . WPINC . '/version.php'; - $valid_query_args = array( 'offset' => true, 'order' => true, @@ -106,7 +99,7 @@ public function get_items( $request ) { $query_args = array_intersect_key( $request->get_params(), $valid_query_args ); $query_args['locale'] = get_user_locale(); - $query_args['wp-version'] = $wp_version; + $query_args['wp-version'] = wp_get_wp_version(); $query_args['pattern-categories'] = isset( $request['category'] ) ? $request['category'] : false; $query_args['pattern-keywords'] = isset( $request['keyword'] ) ? $request['keyword'] : false; diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php index 572950c24cbc5..fab2307a2ea7d 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php @@ -113,7 +113,7 @@ public function get_items( $request ) { $statuses = get_post_stati( array( 'internal' => false ), 'object' ); $statuses['trash'] = get_post_status_object( 'trash' ); - foreach ( $statuses as $slug => $obj ) { + foreach ( $statuses as $obj ) { $ret = $this->check_read_permission( $obj ); if ( ! $ret ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php index 0d3d0e1279146..d766d5c61ddf8 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php @@ -246,6 +246,14 @@ public function prepare_item_for_response( $item, $request ) { $data['rest_namespace'] = $namespace; } + if ( rest_is_field_included( 'template', $fields ) ) { + $data['template'] = $post_type->template ?? array(); + } + + if ( rest_is_field_included( 'template_lock', $fields ) ) { + $data['template_lock'] = ! empty( $post_type->template_lock ) ? $post_type->template_lock : false; + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -407,6 +415,19 @@ public function get_item_schema() { 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), + 'template' => array( + 'type' => array( 'array' ), + 'description' => __( 'The block template associated with the post type.' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'template_lock' => array( + 'type' => array( 'string', 'boolean' ), + 'enum' => array( 'all', 'insert', 'contentOnly', false ), + 'description' => __( 'The template_lock associated with the post type, or false if none.' ), + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), ), ); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index a29f92c56fec7..d6afdef470af2 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -183,7 +183,7 @@ public function get_items_permissions_check( $request ) { * * @param bool $required Whether the post requires a password check. * @param WP_Post $post The post been password checked. - * @return bool Result of password check taking in to account REST API considerations. + * @return bool Result of password check taking into account REST API considerations. */ public function check_password_required( $required, $post ) { if ( ! $required ) { @@ -1844,10 +1844,12 @@ public function prepare_item_for_response( $item, $request ) { } if ( rest_is_field_included( 'title.rendered', $fields ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); $data['title']['rendered'] = get_the_title( $post->ID ); remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); } $has_password_filter = false; @@ -2047,15 +2049,15 @@ public function prepare_item_for_response( $item, $request ) { } /** - * Overwrites the default protected title format. + * Overwrites the default protected and private title format. * - * By default, WordPress will show password protected posts with a title of - * "Protected: %s", as the REST API communicates the protected status of a post - * in a machine readable format, we remove the "Protected: " prefix. + * By default, WordPress will show password protected or private posts with a title of + * "Protected: %s" or "Private: %s", as the REST API communicates the status of a post + * in a machine-readable format, we remove the prefix. * * @since 4.7.0 * - * @return string Protected title format. + * @return string Title format. */ public function protected_title_format() { return '%s'; diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php index 6c8ddc3b48565..0b15f3494c4b5 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php @@ -149,7 +149,7 @@ public function get_items( $request ) { * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has read access for the item, otherwise false or WP_Error object. + * @return bool|WP_Error True if the request has read access for the item, otherwise false or WP_Error object. */ public function get_item_permissions_check( $request ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php index cbf0ee040a9b0..e2063a450de9c 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php @@ -668,8 +668,10 @@ protected function prepare_item_for_database( $request ) { * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { - // Resolve pattern blocks so they don't need to be resolved client-side - // in the editor, improving performance. + /* + * Resolve pattern blocks so they don't need to be resolved client-side + * in the editor, improving performance. + */ $blocks = parse_blocks( $item->content ); $blocks = resolve_pattern_blocks( $blocks ); $item->content = serialize_blocks( $blocks ); @@ -806,11 +808,13 @@ public function prepare_item_for_response( $item, $request ) { */ private static function get_wp_templates_original_source_field( $template_object ) { if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { - // Added by theme. - // Template originally provided by a theme, but customized by a user. - // Templates originally didn't have the 'origin' field so identify - // older customized templates by checking for no origin and a 'theme' - // or 'custom' source. + /* + * Added by theme. + * Template originally provided by a theme, but customized by a user. + * Templates originally didn't have the 'origin' field so identify + * older customized templates by checking for no origin and a 'theme' + * or 'custom' source. + */ if ( $template_object->has_theme_file && ( 'theme' === $template_object->origin || ( empty( $template_object->origin ) && in_array( @@ -831,10 +835,12 @@ private static function get_wp_templates_original_source_field( $template_object return 'plugin'; } - // Added by site. - // Template was created from scratch, but has no author. Author support - // was only added to templates in WordPress 5.9. Fallback to showing the - // site logo and title. + /* + * Added by site. + * Template was created from scratch, but has no author. Author support + * was only added to templates in WordPress 5.9. Fallback to showing the + * site logo and title. + */ if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { return 'site'; } @@ -871,6 +877,9 @@ private static function get_wp_templates_author_text_field( $template_object ) { } return $author->get( 'display_name' ); } + + // Fail-safe to return a string should the original source ever fall through. + return ''; } diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php index 8ffc6ade6eadf..78c9d1ca2b0d8 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php @@ -164,7 +164,7 @@ public function check_read_terms_permission_for_post( $post, $request ) { return true; } - // Otherwise grant access if the post is readable by the logged in user. + // Otherwise grant access if the post is readable by the logged-in user. if ( current_user_can( 'read_post', $post->ID ) ) { return true; } @@ -462,7 +462,7 @@ public function get_item( $request ) { * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has access to create items, false or WP_Error object otherwise. + * @return bool|WP_Error True if the request has access to create items, otherwise false or WP_Error object. */ public function create_item_permissions_check( $request ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php index fe58480fb5554..be12707b3c750 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php @@ -201,7 +201,7 @@ public function get_items( $request ) { $current_theme = wp_get_theme(); $status = $request['status']; - foreach ( $active_themes as $theme_name => $theme ) { + foreach ( $active_themes as $theme ) { $theme_status = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive'; if ( is_array( $status ) && ! in_array( $theme_status, $status, true ) ) { continue; diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php index 7dcc4d79236a1..ab126f981c340 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php @@ -192,7 +192,7 @@ public function parse_url_details( $request ) { * * @since 5.9.0 * - * @return WP_Error|bool True if the request has permission, else WP_Error. + * @return true|WP_Error True if the request has permission, else WP_Error. */ public function permissions_check() { if ( current_user_can( 'edit_posts' ) ) { @@ -347,7 +347,7 @@ private function get_icon( $html, $url ) { * @since 5.9.0 * * @param array $meta_elements { - * A multi-dimensional indexed array on success, else empty array. + * A multidimensional indexed array on success, else empty array. * * @type string[] $0 Meta elements with a content attribute. * @type string[] $1 Content attribute's opening quotation mark. @@ -383,7 +383,7 @@ private function get_description( $meta_elements ) { * @since 5.9.0 * * @param array $meta_elements { - * A multi-dimensional indexed array on success, else empty array. + * A multidimensional indexed array on success, else empty array. * * @type string[] $0 Meta elements with a content attribute. * @type string[] $1 Content attribute's opening quotation mark. @@ -525,7 +525,7 @@ private function get_document_head( $html ) { * * @param string $html The string of HTML to be parsed. * @return array { - * A multi-dimensional indexed array on success, else empty array. + * A multidimensional indexed array on success, else empty array. * * @type string[] $0 Meta elements with a content attribute. * @type string[] $1 Content attribute's opening quotation mark. @@ -588,7 +588,7 @@ private function get_meta_with_content_elements( $html ) { /* * These are the options: - * - i : case insensitive + * - i : case-insensitive * - s : allows newline characters for the . match (needed for multiline elements) * - U means non-greedy matching */ @@ -637,7 +637,7 @@ private function get_metadata_from_meta_element( $meta_elements, $attr, $attr_va /* * These are the options: - * - i : case insensitive + * - i : case-insensitive * - s : allows newline characters for the . match (needed for multiline elements) * - U means non-greedy matching */ diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php index acbd88c4896d1..f2d17186b62d7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php @@ -133,7 +133,7 @@ public function get_items_permissions_check( $request ) { * @since 5.8.0 * * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * @return WP_REST_Response Response object. */ public function get_items( $request ) { $this->retrieve_widgets(); diff --git a/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php b/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php index 7b46905a7aa4f..5f3b55843e23a 100644 --- a/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php +++ b/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php @@ -268,6 +268,7 @@ protected function delete_meta_value( $object_id, $meta_key, $name ) { * Alters the list of values in the database to match the list of provided values. * * @since 4.7.0 + * @since 6.7.0 Stores values into DB even if provided registered default value. * * @param int $object_id Object ID to update. * @param string $meta_key Key for the custom field. @@ -290,7 +291,7 @@ protected function update_multi_meta_value( $object_id, $meta_key, $name, $value ); } - $current_values = get_metadata( $meta_type, $object_id, $meta_key, false ); + $current_values = get_metadata_raw( $meta_type, $object_id, $meta_key, false ); $subtype = get_object_subtype( $meta_type, $object_id ); if ( ! is_array( $current_values ) ) { @@ -367,6 +368,7 @@ function ( $stored_value ) use ( $meta_key, $subtype, $value ) { * Updates a meta value for an object. * * @since 4.7.0 + * @since 6.7.0 Stores values into DB even if provided registered default value. * * @param int $object_id Object ID to update. * @param string $meta_key Key for the custom field. @@ -378,7 +380,7 @@ protected function update_meta_value( $object_id, $meta_key, $name, $value ) { $meta_type = $this->get_meta_type(); // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false. - $old_value = get_metadata( $meta_type, $object_id, $meta_key ); + $old_value = get_metadata_raw( $meta_type, $object_id, $meta_key ); $subtype = get_object_subtype( $meta_type, $object_id ); if ( is_array( $old_value ) && 1 === count( $old_value ) diff --git a/src/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php b/src/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php index 8d815b1491b70..825a87a917a44 100644 --- a/src/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php +++ b/src/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php @@ -61,7 +61,7 @@ public function search_items( WP_REST_Request $request ) { $query_args = apply_filters( 'rest_post_format_search_query', $query_args, $request ); $found_ids = array(); - foreach ( $format_slugs as $index => $format_slug ) { + foreach ( $format_slugs as $format_slug ) { if ( ! empty( $query_args['search'] ) ) { $format_string = get_post_format_string( $format_slug ); $format_slug_match = stripos( $format_slug, $query_args['search'] ) !== false; diff --git a/src/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php b/src/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php index 1d47723b41488..fff921f749e07 100644 --- a/src/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php +++ b/src/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php @@ -132,8 +132,10 @@ public function prepare_item( $id, array $fields ) { if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) { if ( post_type_supports( $post->post_type, 'title' ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); $data[ WP_REST_Search_Controller::PROP_TITLE ] = get_the_title( $post->ID ); remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); } else { $data[ WP_REST_Search_Controller::PROP_TITLE ] = ''; } @@ -183,15 +185,15 @@ public function prepare_item_links( $id ) { } /** - * Overwrites the default protected title format. + * Overwrites the default protected and private title format. * - * By default, WordPress will show password protected posts with a title of - * "Protected: %s". As the REST API communicates the protected status of a post - * in a machine readable format, we remove the "Protected: " prefix. + * By default, WordPress will show password protected or private posts with a title of + * "Protected: %s" or "Private: %s", as the REST API communicates the status of a post + * in a machine-readable format, we remove the prefix. * * @since 5.0.0 * - * @return string Protected title format. + * @return string Title format. */ public function protected_title_format() { return '%s'; diff --git a/src/wp-includes/revision.php b/src/wp-includes/revision.php index 065278b5f8319..2747ea92673d8 100644 --- a/src/wp-includes/revision.php +++ b/src/wp-includes/revision.php @@ -1107,24 +1107,24 @@ function _wp_upgrade_revisions_of_post( $post, $revisions ) { * @param mixed $value Meta value to filter. * @param int $object_id Object ID. * @param string $meta_key Meta key to filter a value for. - * @param bool $single Whether to return a single value. Default false. + * @param bool $single Whether to return a single value. * @return mixed Original meta value if the meta key isn't revisioned, the object doesn't exist, * the post type is a revision or the post ID doesn't match the object ID. * Otherwise, the revisioned meta value is returned for the preview. */ function _wp_preview_meta_filter( $value, $object_id, $meta_key, $single ) { - $post = get_post(); - if ( - empty( $post ) || - $post->ID !== $object_id || - ! in_array( $meta_key, wp_post_revision_meta_keys( $post->post_type ), true ) || - 'revision' === $post->post_type + + if ( empty( $post ) + || $post->ID !== $object_id + || ! in_array( $meta_key, wp_post_revision_meta_keys( $post->post_type ), true ) + || 'revision' === $post->post_type ) { return $value; } $preview = wp_get_post_autosave( $post->ID ); + if ( false === $preview ) { return $value; } diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 715477ad8ecdf..c9eb40d1c5b50 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -54,11 +54,10 @@ function wp_register_tinymce_scripts( $scripts, $force_uncompressed = false ) { script_concat_settings(); - $compressed = $compress_scripts && $concatenate_scripts && isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) - && false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) && ! $force_uncompressed; + $compressed = $compress_scripts && $concatenate_scripts && ! $force_uncompressed; /* - * Load tinymce.js when running from /src, otherwise load wp-tinymce.js.gz (in production) + * Load tinymce.js when running from /src, otherwise load wp-tinymce.js (in production) * or tinymce.min.js (when SCRIPT_DEBUG is true). */ if ( $compressed ) { @@ -97,7 +96,6 @@ function wp_default_packages_vendor( $scripts ) { 'lodash', 'wp-polyfill-fetch', 'wp-polyfill-formdata', - 'wp-polyfill-importmap', 'wp-polyfill-node-contains', 'wp-polyfill-url', 'wp-polyfill-dom-rect', @@ -111,10 +109,10 @@ function wp_default_packages_vendor( $scripts ) { 'react' => '18.3.1', 'react-dom' => '18.3.1', 'react-jsx-runtime' => '18.3.1', - 'regenerator-runtime' => '0.14.0', + 'regenerator-runtime' => '0.14.1', 'moment' => '2.29.4', 'lodash' => '4.17.21', - 'wp-polyfill-fetch' => '3.6.17', + 'wp-polyfill-fetch' => '3.6.20', 'wp-polyfill-formdata' => '4.0.10', 'wp-polyfill-node-contains' => '4.8.0', 'wp-polyfill-url' => '3.6.4', @@ -123,7 +121,6 @@ function wp_default_packages_vendor( $scripts ) { 'wp-polyfill-object-fit' => '2.3.5', 'wp-polyfill-inert' => '3.1.2', 'wp-polyfill' => '3.15.0', - 'wp-polyfill-importmap' => '1.8.2', ); foreach ( $vendor_scripts as $handle => $dependencies ) { @@ -684,7 +681,13 @@ function wp_scripts_get_suffix( $type = '' ) { static $suffixes; if ( null === $suffixes ) { - // Include an unmodified $wp_version. + /* + * Include an unmodified $wp_version. + * + * Note: wp_get_wp_version() is not used here, as this file can be included + * via wp-admin/load-scripts.php or wp-admin/load-styles.php, in which case + * wp-includes/functions.php is not loaded. + */ require ABSPATH . WPINC . '/version.php'; /* @@ -935,7 +938,7 @@ function wp_default_scripts( $scripts ) { $scripts->add( 'jquery-form', "/wp-includes/js/jquery/jquery.form$suffix.js", array( 'jquery' ), '4.3.0', 1 ); // jQuery plugins. - $scripts->add( 'jquery-color', '/wp-includes/js/jquery/jquery.color.min.js', array( 'jquery' ), '2.2.0', 1 ); + $scripts->add( 'jquery-color', '/wp-includes/js/jquery/jquery.color.min.js', array( 'jquery' ), '3.0.0', 1 ); $scripts->add( 'schedule', '/wp-includes/js/jquery/jquery.schedule.js', array( 'jquery' ), '20m', 1 ); $scripts->add( 'jquery-query', '/wp-includes/js/jquery/jquery.query.js', array( 'jquery' ), '2.2.3', 1 ); $scripts->add( 'jquery-serialize-object', '/wp-includes/js/jquery/jquery.serialize-object.js', array( 'jquery' ), '0.2-wp', 1 ); @@ -1033,8 +1036,8 @@ function wp_default_scripts( $scripts ) { $scripts->add( 'json2', "/wp-includes/js/json2$suffix.js", array(), '2015-05-03' ); did_action( 'init' ) && $scripts->add_data( 'json2', 'conditional', 'lt IE 8' ); - $scripts->add( 'underscore', "/wp-includes/js/underscore$dev_suffix.js", array(), '1.13.4', 1 ); - $scripts->add( 'backbone', "/wp-includes/js/backbone$dev_suffix.js", array( 'underscore', 'jquery' ), '1.5.0', 1 ); + $scripts->add( 'underscore', "/wp-includes/js/underscore$dev_suffix.js", array(), '1.13.7', 1 ); + $scripts->add( 'backbone', "/wp-includes/js/backbone$dev_suffix.js", array( 'underscore', 'jquery' ), '1.6.0', 1 ); $scripts->add( 'wp-util', "/wp-includes/js/wp-util$suffix.js", array( 'underscore', 'jquery' ), false, 1 ); did_action( 'init' ) && $scripts->localize( @@ -1392,7 +1395,7 @@ function wp_default_scripts( $scripts ) { $scripts->add( 'admin-tags', "/wp-admin/js/tags$suffix.js", array( 'jquery', 'wp-ajax-response' ), false, 1 ); $scripts->set_translations( 'admin-tags' ); - $scripts->add( 'admin-comments', "/wp-admin/js/edit-comments$suffix.js", array( 'wp-lists', 'quicktags', 'jquery-query' ), false, 1 ); + $scripts->add( 'admin-comments', "/wp-admin/js/edit-comments$suffix.js", array( 'wp-lists', 'quicktags', 'jquery-query', 'wp-a11y' ), false, 1 ); $scripts->set_translations( 'admin-comments' ); did_action( 'init' ) && $scripts->localize( 'admin-comments', @@ -1522,7 +1525,13 @@ function wp_default_scripts( $scripts ) { function wp_default_styles( $styles ) { global $editor_styles; - // Include an unmodified $wp_version. + /* + * Include an unmodified $wp_version. + * + * Note: wp_get_wp_version() is not used here, as this file can be included + * via wp-admin/load-scripts.php or wp-admin/load-styles.php, in which case + * wp-includes/functions.php is not loaded. + */ require ABSPATH . WPINC . '/version.php'; if ( ! defined( 'SCRIPT_DEBUG' ) ) { @@ -2504,6 +2513,20 @@ function wp_enqueue_global_styles() { $stylesheet = wp_get_global_stylesheet(); + if ( $is_block_theme ) { + /* + * Dequeue the Customizer's custom CSS + * and add it before the global styles custom CSS. + */ + remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); + // Get the custom CSS from the Customizer and add it to the global stylesheet. + $custom_css = wp_get_custom_css(); + $stylesheet .= $custom_css; + + // Add the global styles custom CSS at the end. + $stylesheet .= wp_get_global_stylesheet( array( 'custom-css' ) ); + } + if ( empty( $stylesheet ) ) { return; } @@ -2516,27 +2539,6 @@ function wp_enqueue_global_styles() { wp_add_global_styles_for_blocks(); } -/** - * Enqueues the global styles custom css defined via theme.json. - * - * @since 6.2.0 - */ -function wp_enqueue_global_styles_custom_css() { - if ( ! wp_is_block_theme() ) { - return; - } - - // Don't enqueue Customizer's custom CSS separately. - remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); - - $custom_css = wp_get_custom_css(); - $custom_css .= wp_get_global_styles_custom_css(); - - if ( ! empty( $custom_css ) ) { - wp_add_inline_style( 'global-styles', $custom_css ); - } -} - /** * Checks if the editor scripts and styles for all registered block types * should be enqueued on the current screen. @@ -3052,7 +3054,7 @@ static function ( $matches ) use ( $stylesheet_url ) { if ( str_starts_with( $url, 'http:' ) || str_starts_with( $url, 'https:' ) || - str_starts_with( $url, '//' ) || + str_starts_with( $url, '/' ) || str_starts_with( $url, '#' ) || str_starts_with( $url, 'data:' ) ) { diff --git a/src/wp-includes/sodium_compat/autoload.php b/src/wp-includes/sodium_compat/autoload.php index fd12f87b20e98..bfd9e4a034e29 100644 --- a/src/wp-includes/sodium_compat/autoload.php +++ b/src/wp-includes/sodium_compat/autoload.php @@ -54,6 +54,9 @@ function sodiumCompatAutoloader($class) // unless PHP >= 5.3.0 require_once dirname(__FILE__) . '/lib/namespaced.php'; require_once dirname(__FILE__) . '/lib/sodium_compat.php'; + if (!defined('SODIUM_CRYPTO_AEAD_AEGIS128L_KEYBYTES')) { + require_once dirname(__FILE__) . '/lib/php84compat_const.php'; + } } else { require_once dirname(__FILE__) . '/src/PHP52/SplFixedArray.php'; } @@ -71,5 +74,8 @@ function sodiumCompatAutoloader($class) // Older versions of {PHP, ext/sodium} will not define these require_once(dirname(__FILE__) . '/lib/php72compat.php'); } +if (PHP_VERSION_ID < 80400 || !extension_loaded('sodium')) { + require_once dirname(__FILE__) . '/lib/php84compat.php'; +} require_once(dirname(__FILE__) . '/lib/stream-xchacha20.php'); require_once(dirname(__FILE__) . '/lib/ristretto255.php'); diff --git a/src/wp-includes/sodium_compat/lib/php72compat.php b/src/wp-includes/sodium_compat/lib/php72compat.php index e949dbdc68465..b9da5e036505b 100644 --- a/src/wp-includes/sodium_compat/lib/php72compat.php +++ b/src/wp-includes/sodium_compat/lib/php72compat.php @@ -14,14 +14,14 @@ 'BASE64_VARIANT_ORIGINAL_NO_PADDING', 'BASE64_VARIANT_URLSAFE', 'BASE64_VARIANT_URLSAFE_NO_PADDING', - 'CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES', - 'CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES', - 'CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES', - 'CRYPTO_AEAD_CHACHA20POLY1305_ABYTES', 'CRYPTO_AEAD_AES256GCM_KEYBYTES', 'CRYPTO_AEAD_AES256GCM_NSECBYTES', 'CRYPTO_AEAD_AES256GCM_NPUBBYTES', 'CRYPTO_AEAD_AES256GCM_ABYTES', + 'CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES', + 'CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES', + 'CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES', + 'CRYPTO_AEAD_CHACHA20POLY1305_ABYTES', 'CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES', 'CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES', 'CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES', @@ -115,8 +115,12 @@ * @return void * @throws SodiumException */ - function sodium_add(&$string1, $string2) - { + function sodium_add( + #[\SensitiveParameter] + &$string1, + #[\SensitiveParameter] + $string2 + ) { ParagonIE_Sodium_Compat::add($string1, $string2); } } @@ -130,8 +134,12 @@ function sodium_add(&$string1, $string2) * @throws SodiumException * @throws TypeError */ - function sodium_base642bin($string, $variant, $ignore ='') - { + function sodium_base642bin( + #[\SensitiveParameter] + $string, + $variant, + $ignore ='' + ) { return ParagonIE_Sodium_Compat::base642bin($string, $variant, $ignore); } } @@ -144,8 +152,11 @@ function sodium_base642bin($string, $variant, $ignore ='') * @throws SodiumException * @throws TypeError */ - function sodium_bin2base64($string, $variant) - { + function sodium_bin2base64( + #[\SensitiveParameter] + $string, + $variant + ) { return ParagonIE_Sodium_Compat::bin2base64($string, $variant); } } @@ -157,8 +168,10 @@ function sodium_bin2base64($string, $variant) * @throws SodiumException * @throws TypeError */ - function sodium_bin2hex($string) - { + function sodium_bin2hex( + #[\SensitiveParameter] + $string + ) { return ParagonIE_Sodium_Compat::bin2hex($string); } } @@ -171,8 +184,12 @@ function sodium_bin2hex($string) * @throws SodiumException * @throws TypeError */ - function sodium_compare($string1, $string2) - { + function sodium_compare( + #[\SensitiveParameter] + $string1, + #[\SensitiveParameter] + $string2 + ) { return ParagonIE_Sodium_Compat::compare($string1, $string2); } } @@ -185,8 +202,13 @@ function sodium_compare($string1, $string2) * @param string $key * @return string|bool */ - function sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $additional_data, $nonce, $key) - { + function sodium_crypto_aead_aes256gcm_decrypt( + $ciphertext, + $additional_data, + $nonce, + #[\SensitiveParameter] + $key + ) { try { return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_decrypt( $ciphertext, @@ -215,8 +237,14 @@ function sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $additional_data, $no * @throws SodiumException * @throws TypeError */ - function sodium_crypto_aead_aes256gcm_encrypt($message, $additional_data, $nonce, $key) - { + function sodium_crypto_aead_aes256gcm_encrypt( + #[\SensitiveParameter] + $message, + $additional_data, + $nonce, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_encrypt($message, $additional_data, $nonce, $key); } } @@ -239,8 +267,13 @@ function sodium_crypto_aead_aes256gcm_is_available() * @param string $key * @return string|bool */ - function sodium_crypto_aead_chacha20poly1305_decrypt($ciphertext, $additional_data, $nonce, $key) - { + function sodium_crypto_aead_chacha20poly1305_decrypt( + $ciphertext, + $additional_data, + $nonce, + #[\SensitiveParameter] + $key + ) { try { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_decrypt( $ciphertext, @@ -266,8 +299,14 @@ function sodium_crypto_aead_chacha20poly1305_decrypt($ciphertext, $additional_da * @throws SodiumException * @throws TypeError */ - function sodium_crypto_aead_chacha20poly1305_encrypt($message, $additional_data, $nonce, $key) - { + function sodium_crypto_aead_chacha20poly1305_encrypt( + #[\SensitiveParameter] + $message, + $additional_data, + $nonce, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_encrypt( $message, $additional_data, @@ -296,8 +335,13 @@ function sodium_crypto_aead_chacha20poly1305_keygen() * @param string $key * @return string|bool */ - function sodium_crypto_aead_chacha20poly1305_ietf_decrypt($message, $additional_data, $nonce, $key) - { + function sodium_crypto_aead_chacha20poly1305_ietf_decrypt( + $message, + $additional_data, + $nonce, + #[\SensitiveParameter] + $key + ) { try { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_decrypt( $message, @@ -323,8 +367,14 @@ function sodium_crypto_aead_chacha20poly1305_ietf_decrypt($message, $additional_ * @throws SodiumException * @throws TypeError */ - function sodium_crypto_aead_chacha20poly1305_ietf_encrypt($message, $additional_data, $nonce, $key) - { + function sodium_crypto_aead_chacha20poly1305_ietf_encrypt( + #[\SensitiveParameter] + $message, + $additional_data, + $nonce, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_encrypt( $message, $additional_data, @@ -353,8 +403,13 @@ function sodium_crypto_aead_chacha20poly1305_ietf_keygen() * @param string $key * @return string|bool */ - function sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, $additional_data, $nonce, $key) - { + function sodium_crypto_aead_xchacha20poly1305_ietf_decrypt( + $ciphertext, + $additional_data, + $nonce, + #[\SensitiveParameter] + $key + ) { try { return ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_decrypt( $ciphertext, @@ -382,9 +437,11 @@ function sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, $additio * @throws TypeError */ function sodium_crypto_aead_xchacha20poly1305_ietf_encrypt( + #[\SensitiveParameter] $message, $additional_data, $nonce, + #[\SensitiveParameter] $key ) { return ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_encrypt( @@ -416,8 +473,11 @@ function sodium_crypto_aead_xchacha20poly1305_ietf_keygen() * @throws SodiumException * @throws TypeError */ - function sodium_crypto_auth($message, $key) - { + function sodium_crypto_auth( + $message, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_auth($message, $key); } } @@ -442,8 +502,12 @@ function sodium_crypto_auth_keygen() * @throws SodiumException * @throws TypeError */ - function sodium_crypto_auth_verify($mac, $message, $key) - { + function sodium_crypto_auth_verify( + $mac, + $message, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_auth_verify($mac, $message, $key); } } @@ -457,8 +521,13 @@ function sodium_crypto_auth_verify($mac, $message, $key) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_box($message, $nonce, $key_pair) - { + function sodium_crypto_box( + #[\SensitiveParameter] + $message, + $nonce, + #[\SensitiveParameter] + $key_pair + ) { return ParagonIE_Sodium_Compat::crypto_box($message, $nonce, $key_pair); } } @@ -483,8 +552,11 @@ function sodium_crypto_box_keypair() * @throws SodiumException * @throws TypeError */ - function sodium_crypto_box_keypair_from_secretkey_and_publickey($secret_key, $public_key) - { + function sodium_crypto_box_keypair_from_secretkey_and_publickey( + #[\SensitiveParameter] + $secret_key, + $public_key + ) { return ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey($secret_key, $public_key); } } @@ -496,8 +568,12 @@ function sodium_crypto_box_keypair_from_secretkey_and_publickey($secret_key, $pu * @param string $key_pair * @return string|bool */ - function sodium_crypto_box_open($ciphertext, $nonce, $key_pair) - { + function sodium_crypto_box_open( + $ciphertext, + $nonce, + #[\SensitiveParameter] + $key_pair + ) { try { return ParagonIE_Sodium_Compat::crypto_box_open($ciphertext, $nonce, $key_pair); } catch (Error $ex) { @@ -515,8 +591,10 @@ function sodium_crypto_box_open($ciphertext, $nonce, $key_pair) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_box_publickey($key_pair) - { + function sodium_crypto_box_publickey( + #[\SensitiveParameter] + $key_pair + ) { return ParagonIE_Sodium_Compat::crypto_box_publickey($key_pair); } } @@ -528,8 +606,10 @@ function sodium_crypto_box_publickey($key_pair) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_box_publickey_from_secretkey($secret_key) - { + function sodium_crypto_box_publickey_from_secretkey( + #[\SensitiveParameter] + $secret_key + ) { return ParagonIE_Sodium_Compat::crypto_box_publickey_from_secretkey($secret_key); } } @@ -542,8 +622,11 @@ function sodium_crypto_box_publickey_from_secretkey($secret_key) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_box_seal($message, $public_key) - { + function sodium_crypto_box_seal( + #[\SensitiveParameter] + $message, + $public_key + ) { return ParagonIE_Sodium_Compat::crypto_box_seal($message, $public_key); } } @@ -555,8 +638,11 @@ function sodium_crypto_box_seal($message, $public_key) * @return string|bool * @throws SodiumException */ - function sodium_crypto_box_seal_open($message, $key_pair) - { + function sodium_crypto_box_seal_open( + $message, + #[\SensitiveParameter] + $key_pair + ) { try { return ParagonIE_Sodium_Compat::crypto_box_seal_open($message, $key_pair); } catch (SodiumException $ex) { @@ -575,8 +661,10 @@ function sodium_crypto_box_seal_open($message, $key_pair) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_box_secretkey($key_pair) - { + function sodium_crypto_box_secretkey( + #[\SensitiveParameter] + $key_pair + ) { return ParagonIE_Sodium_Compat::crypto_box_secretkey($key_pair); } } @@ -588,8 +676,10 @@ function sodium_crypto_box_secretkey($key_pair) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_box_seed_keypair($seed) - { + function sodium_crypto_box_seed_keypair( + #[\SensitiveParameter] + $seed + ) { return ParagonIE_Sodium_Compat::crypto_box_seed_keypair($seed); } } @@ -603,8 +693,12 @@ function sodium_crypto_box_seed_keypair($seed) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_generichash($message, $key = null, $length = 32) - { + function sodium_crypto_generichash( + $message, + #[\SensitiveParameter] + $key = null, + $length = 32 + ) { return ParagonIE_Sodium_Compat::crypto_generichash($message, $key, $length); } } @@ -631,8 +725,11 @@ function sodium_crypto_generichash_final(&$state, $outputLength = 32) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_generichash_init($key = null, $length = 32) - { + function sodium_crypto_generichash_init( + #[\SensitiveParameter] + $key = null, + $length = 32 + ) { return ParagonIE_Sodium_Compat::crypto_generichash_init($key, $length); } } @@ -656,8 +753,11 @@ function sodium_crypto_generichash_keygen() * @throws SodiumException * @throws TypeError */ - function sodium_crypto_generichash_update(&$state, $message = '') - { + function sodium_crypto_generichash_update( + #[\SensitiveParameter] + &$state, + $message = '' + ) { ParagonIE_Sodium_Compat::crypto_generichash_update($state, $message); } } @@ -682,8 +782,13 @@ function sodium_crypto_kdf_keygen() * @return string * @throws Exception */ - function sodium_crypto_kdf_derive_from_key($subkey_length, $subkey_id, $context, $key) - { + function sodium_crypto_kdf_derive_from_key( + $subkey_length, + $subkey_id, + $context, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_kdf_derive_from_key( $subkey_length, $subkey_id, @@ -703,8 +808,13 @@ function sodium_crypto_kdf_derive_from_key($subkey_length, $subkey_id, $context, * @throws SodiumException * @throws TypeError */ - function sodium_crypto_kx($my_secret, $their_public, $client_public, $server_public) - { + function sodium_crypto_kx( + #[\SensitiveParameter] + $my_secret, + $their_public, + $client_public, + $server_public + ) { return ParagonIE_Sodium_Compat::crypto_kx( $my_secret, $their_public, @@ -719,8 +829,10 @@ function sodium_crypto_kx($my_secret, $their_public, $client_public, $server_pub * @return string * @throws Exception */ - function sodium_crypto_kx_seed_keypair($seed) - { + function sodium_crypto_kx_seed_keypair( + #[\SensitiveParameter] + $seed + ) { return ParagonIE_Sodium_Compat::crypto_kx_seed_keypair($seed); } } @@ -741,8 +853,11 @@ function sodium_crypto_kx_keypair() * @return array{0: string, 1: string} * @throws SodiumException */ - function sodium_crypto_kx_client_session_keys($client_key_pair, $server_key) - { + function sodium_crypto_kx_client_session_keys( + #[\SensitiveParameter] + $client_key_pair, + $server_key + ) { return ParagonIE_Sodium_Compat::crypto_kx_client_session_keys($client_key_pair, $server_key); } } @@ -753,8 +868,11 @@ function sodium_crypto_kx_client_session_keys($client_key_pair, $server_key) * @return array{0: string, 1: string} * @throws SodiumException */ - function sodium_crypto_kx_server_session_keys($server_key_pair, $client_key) - { + function sodium_crypto_kx_server_session_keys( + #[\SensitiveParameter] + $server_key_pair, + $client_key + ) { return ParagonIE_Sodium_Compat::crypto_kx_server_session_keys($server_key_pair, $client_key); } } @@ -764,8 +882,10 @@ function sodium_crypto_kx_server_session_keys($server_key_pair, $client_key) * @return string * @throws Exception */ - function sodium_crypto_kx_secretkey($key_pair) - { + function sodium_crypto_kx_secretkey( + #[\SensitiveParameter] + $key_pair + ) { return ParagonIE_Sodium_Compat::crypto_kx_secretkey($key_pair); } } @@ -775,8 +895,10 @@ function sodium_crypto_kx_secretkey($key_pair) * @return string * @throws Exception */ - function sodium_crypto_kx_publickey($key_pair) - { + function sodium_crypto_kx_publickey( + #[\SensitiveParameter] + $key_pair + ) { return ParagonIE_Sodium_Compat::crypto_kx_publickey($key_pair); } } @@ -793,8 +915,15 @@ function sodium_crypto_kx_publickey($key_pair) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_pwhash($length, $passwd, $salt, $opslimit, $memlimit, $algo = null) - { + function sodium_crypto_pwhash( + $length, + #[\SensitiveParameter] + $passwd, + $salt, + $opslimit, + $memlimit, + $algo = null + ) { return ParagonIE_Sodium_Compat::crypto_pwhash($length, $passwd, $salt, $opslimit, $memlimit, $algo); } } @@ -808,8 +937,12 @@ function sodium_crypto_pwhash($length, $passwd, $salt, $opslimit, $memlimit, $al * @throws SodiumException * @throws TypeError */ - function sodium_crypto_pwhash_str($passwd, $opslimit, $memlimit) - { + function sodium_crypto_pwhash_str( + #[\SensitiveParameter] + $passwd, + $opslimit, + $memlimit + ) { return ParagonIE_Sodium_Compat::crypto_pwhash_str($passwd, $opslimit, $memlimit); } } @@ -823,8 +956,12 @@ function sodium_crypto_pwhash_str($passwd, $opslimit, $memlimit) * * @throws SodiumException */ - function sodium_crypto_pwhash_str_needs_rehash($hash, $opslimit, $memlimit) - { + function sodium_crypto_pwhash_str_needs_rehash( + #[\SensitiveParameter] + $hash, + $opslimit, + $memlimit + ) { return ParagonIE_Sodium_Compat::crypto_pwhash_str_needs_rehash($hash, $opslimit, $memlimit); } } @@ -837,8 +974,12 @@ function sodium_crypto_pwhash_str_needs_rehash($hash, $opslimit, $memlimit) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_pwhash_str_verify($passwd, $hash) - { + function sodium_crypto_pwhash_str_verify( + #[\SensitiveParameter] + $passwd, + #[\SensitiveParameter] + $hash + ) { return ParagonIE_Sodium_Compat::crypto_pwhash_str_verify($passwd, $hash); } } @@ -854,8 +995,14 @@ function sodium_crypto_pwhash_str_verify($passwd, $hash) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_pwhash_scryptsalsa208sha256($length, $passwd, $salt, $opslimit, $memlimit) - { + function sodium_crypto_pwhash_scryptsalsa208sha256( + $length, + #[\SensitiveParameter] + $passwd, + $salt, + $opslimit, + $memlimit + ) { return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256( $length, $passwd, @@ -875,8 +1022,12 @@ function sodium_crypto_pwhash_scryptsalsa208sha256($length, $passwd, $salt, $ops * @throws SodiumException * @throws TypeError */ - function sodium_crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $memlimit) - { + function sodium_crypto_pwhash_scryptsalsa208sha256_str( + #[\SensitiveParameter] + $passwd, + $opslimit, + $memlimit + ) { return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $memlimit); } } @@ -889,8 +1040,12 @@ function sodium_crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $meml * @throws SodiumException * @throws TypeError */ - function sodium_crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash) - { + function sodium_crypto_pwhash_scryptsalsa208sha256_str_verify( + #[\SensitiveParameter] + $passwd, + #[\SensitiveParameter] + $hash + ) { return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash); } } @@ -903,8 +1058,11 @@ function sodium_crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_scalarmult($n, $p) - { + function sodium_crypto_scalarmult( + #[\SensitiveParameter] + $n, + $p + ) { return ParagonIE_Sodium_Compat::crypto_scalarmult($n, $p); } } @@ -916,8 +1074,10 @@ function sodium_crypto_scalarmult($n, $p) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_scalarmult_base($n) - { + function sodium_crypto_scalarmult_base( + #[\SensitiveParameter] + $n + ) { return ParagonIE_Sodium_Compat::crypto_scalarmult_base($n); } } @@ -931,8 +1091,13 @@ function sodium_crypto_scalarmult_base($n) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_secretbox($message, $nonce, $key) - { + function sodium_crypto_secretbox( + #[\SensitiveParameter] + $message, + $nonce, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_secretbox($message, $nonce, $key); } } @@ -955,8 +1120,12 @@ function sodium_crypto_secretbox_keygen() * @param string $key * @return string|bool */ - function sodium_crypto_secretbox_open($ciphertext, $nonce, $key) - { + function sodium_crypto_secretbox_open( + $ciphertext, + $nonce, + #[\SensitiveParameter] + $key + ) { try { return ParagonIE_Sodium_Compat::crypto_secretbox_open($ciphertext, $nonce, $key); } catch (Error $ex) { @@ -972,8 +1141,10 @@ function sodium_crypto_secretbox_open($ciphertext, $nonce, $key) * @return array * @throws SodiumException */ - function sodium_crypto_secretstream_xchacha20poly1305_init_push($key) - { + function sodium_crypto_secretstream_xchacha20poly1305_init_push( + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_init_push($key); } } @@ -987,7 +1158,9 @@ function sodium_crypto_secretstream_xchacha20poly1305_init_push($key) * @throws SodiumException */ function sodium_crypto_secretstream_xchacha20poly1305_push( + #[\SensitiveParameter] &$state, + #[\SensitiveParameter] $message, $additional_data = '', $tag = 0 @@ -1007,8 +1180,11 @@ function sodium_crypto_secretstream_xchacha20poly1305_push( * @return string * @throws Exception */ - function sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $key) - { + function sodium_crypto_secretstream_xchacha20poly1305_init_pull( + $header, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_init_pull($header, $key); } } @@ -1020,8 +1196,12 @@ function sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $key) * @return bool|array{0: string, 1: int} * @throws SodiumException */ - function sodium_crypto_secretstream_xchacha20poly1305_pull(&$state, $ciphertext, $additional_data = '') - { + function sodium_crypto_secretstream_xchacha20poly1305_pull( + #[\SensitiveParameter] + &$state, + $ciphertext, + $additional_data = '' + ) { return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_pull( $state, $ciphertext, @@ -1035,8 +1215,10 @@ function sodium_crypto_secretstream_xchacha20poly1305_pull(&$state, $ciphertext, * @return void * @throws SodiumException */ - function sodium_crypto_secretstream_xchacha20poly1305_rekey(&$state) - { + function sodium_crypto_secretstream_xchacha20poly1305_rekey( + #[\SensitiveParameter] + &$state + ) { ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_rekey($state); } } @@ -1059,8 +1241,11 @@ function sodium_crypto_secretstream_xchacha20poly1305_keygen() * @throws SodiumException * @throws TypeError */ - function sodium_crypto_shorthash($message, $key = '') - { + function sodium_crypto_shorthash( + $message, + #[\SensitiveParameter] + $key = '' + ) { return ParagonIE_Sodium_Compat::crypto_shorthash($message, $key); } } @@ -1084,8 +1269,11 @@ function sodium_crypto_shorthash_keygen() * @throws SodiumException * @throws TypeError */ - function sodium_crypto_sign($message, $secret_key) - { + function sodium_crypto_sign( + $message, + #[\SensitiveParameter] + $secret_key + ) { return ParagonIE_Sodium_Compat::crypto_sign($message, $secret_key); } } @@ -1098,8 +1286,11 @@ function sodium_crypto_sign($message, $secret_key) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_sign_detached($message, $secret_key) - { + function sodium_crypto_sign_detached( + $message, + #[\SensitiveParameter] + $secret_key + ) { return ParagonIE_Sodium_Compat::crypto_sign_detached($message, $secret_key); } } @@ -1112,8 +1303,11 @@ function sodium_crypto_sign_detached($message, $secret_key) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_sign_keypair_from_secretkey_and_publickey($secret_key, $public_key) - { + function sodium_crypto_sign_keypair_from_secretkey_and_publickey( + #[\SensitiveParameter] + $secret_key, + $public_key + ) { return ParagonIE_Sodium_Compat::crypto_sign_keypair_from_secretkey_and_publickey($secret_key, $public_key); } } @@ -1155,8 +1349,10 @@ function sodium_crypto_sign_open($signedMessage, $public_key) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_sign_publickey($key_pair) - { + function sodium_crypto_sign_publickey( + #[\SensitiveParameter] + $key_pair + ) { return ParagonIE_Sodium_Compat::crypto_sign_publickey($key_pair); } } @@ -1168,8 +1364,10 @@ function sodium_crypto_sign_publickey($key_pair) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_sign_publickey_from_secretkey($secret_key) - { + function sodium_crypto_sign_publickey_from_secretkey( + #[\SensitiveParameter] + $secret_key + ) { return ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey($secret_key); } } @@ -1181,8 +1379,10 @@ function sodium_crypto_sign_publickey_from_secretkey($secret_key) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_sign_secretkey($key_pair) - { + function sodium_crypto_sign_secretkey( + #[\SensitiveParameter] + $key_pair + ) { return ParagonIE_Sodium_Compat::crypto_sign_secretkey($key_pair); } } @@ -1194,8 +1394,10 @@ function sodium_crypto_sign_secretkey($key_pair) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_sign_seed_keypair($seed) - { + function sodium_crypto_sign_seed_keypair( + #[\SensitiveParameter] + $seed + ) { return ParagonIE_Sodium_Compat::crypto_sign_seed_keypair($seed); } } @@ -1235,8 +1437,10 @@ function sodium_crypto_sign_ed25519_pk_to_curve25519($public_key) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_sign_ed25519_sk_to_curve25519($secret_key) - { + function sodium_crypto_sign_ed25519_sk_to_curve25519( + #[\SensitiveParameter] + $secret_key + ) { return ParagonIE_Sodium_Compat::crypto_sign_ed25519_sk_to_curve25519($secret_key); } } @@ -1250,8 +1454,12 @@ function sodium_crypto_sign_ed25519_sk_to_curve25519($secret_key) * @throws SodiumException * @throws TypeError */ - function sodium_crypto_stream($length, $nonce, $key) - { + function sodium_crypto_stream( + $length, + $nonce, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_stream($length, $nonce, $key); } } @@ -1276,8 +1484,13 @@ function sodium_crypto_stream_keygen() * @throws SodiumException * @throws TypeError */ - function sodium_crypto_stream_xor($message, $nonce, $key) - { + function sodium_crypto_stream_xor( + #[\SensitiveParameter] + $message, + $nonce, + #[\SensitiveParameter] + $key + ) { return ParagonIE_Sodium_Compat::crypto_stream_xor($message, $nonce, $key); } } @@ -1291,8 +1504,11 @@ function sodium_crypto_stream_xor($message, $nonce, $key) * @throws SodiumException * @throws TypeError */ - function sodium_hex2bin($string, $ignore = '') - { + function sodium_hex2bin( + #[\SensitiveParameter] + $string, + $ignore = '' + ) { return ParagonIE_Sodium_Compat::hex2bin($string, $ignore); } } @@ -1304,8 +1520,10 @@ function sodium_hex2bin($string, $ignore = '') * @throws SodiumException * @throws TypeError */ - function sodium_increment(&$string) - { + function sodium_increment( + #[\SensitiveParameter] + &$string + ) { ParagonIE_Sodium_Compat::increment($string); } } @@ -1348,8 +1566,12 @@ function sodium_version_string() * @throws SodiumException * @throws TypeError */ - function sodium_memcmp($string1, $string2) - { + function sodium_memcmp( + #[\SensitiveParameter] + $string1, + #[\SensitiveParameter] + $string2 + ) { return ParagonIE_Sodium_Compat::memcmp($string1, $string2); } } @@ -1363,8 +1585,10 @@ function sodium_memcmp($string1, $string2) * * @psalm-suppress ReferenceConstraintViolation */ - function sodium_memzero(&$string) - { + function sodium_memzero( + #[\SensitiveParameter] + &$string + ) { ParagonIE_Sodium_Compat::memzero($string); } } @@ -1377,8 +1601,11 @@ function sodium_memzero(&$string) * @throws SodiumException * @throws TypeError */ - function sodium_pad($unpadded, $block_size) - { + function sodium_pad( + #[\SensitiveParameter] + $unpadded, + $block_size + ) { return ParagonIE_Sodium_Compat::pad($unpadded, $block_size, true); } } @@ -1391,8 +1618,11 @@ function sodium_pad($unpadded, $block_size) * @throws SodiumException * @throws TypeError */ - function sodium_unpad($padded, $block_size) - { + function sodium_unpad( + #[\SensitiveParameter] + $padded, + $block_size + ) { return ParagonIE_Sodium_Compat::unpad($padded, $block_size, true); } } diff --git a/src/wp-includes/sodium_compat/lib/php84compat.php b/src/wp-includes/sodium_compat/lib/php84compat.php new file mode 100644 index 0000000000000..ee172a08d0fc7 --- /dev/null +++ b/src/wp-includes/sodium_compat/lib/php84compat.php @@ -0,0 +1,130 @@ + $state */ + protected $state; + public function __construct() + { + $this->state = array_fill(0, 8, ''); + } + + /** + * @internal Only use this for unit tests! + * @return string[] + */ + public function getState() + { + return array_values($this->state); + } + + /** + * @param array $input + * @return self + * @throws SodiumException + * + * @internal Only for unit tests + */ + public static function initForUnitTests(array $input) + { + if (count($input) < 8) { + throw new SodiumException('invalid input'); + } + $state = new self(); + for ($i = 0; $i < 8; ++$i) { + $state->state[$i] = $input[$i]; + } + return $state; + } + + /** + * @param string $key + * @param string $nonce + * @return self + */ + public static function init($key, $nonce) + { + $state = new self(); + + // S0 = key ^ nonce + $state->state[0] = $key ^ $nonce; + // S1 = C1 + $state->state[1] = SODIUM_COMPAT_AEGIS_C1; + // S2 = C0 + $state->state[2] = SODIUM_COMPAT_AEGIS_C0; + // S3 = C1 + $state->state[3] = SODIUM_COMPAT_AEGIS_C1; + // S4 = key ^ nonce + $state->state[4] = $key ^ $nonce; + // S5 = key ^ C0 + $state->state[5] = $key ^ SODIUM_COMPAT_AEGIS_C0; + // S6 = key ^ C1 + $state->state[6] = $key ^ SODIUM_COMPAT_AEGIS_C1; + // S7 = key ^ C0 + $state->state[7] = $key ^ SODIUM_COMPAT_AEGIS_C0; + + // Repeat(10, Update(nonce, key)) + for ($i = 0; $i < 10; ++$i) { + $state->update($nonce, $key); + } + return $state; + } + + /** + * @param string $ai + * @return self + */ + public function absorb($ai) + { + if (ParagonIE_Sodium_Core_Util::strlen($ai) !== 32) { + throw new SodiumException('Input must be two AES blocks in size'); + } + $t0 = ParagonIE_Sodium_Core_Util::substr($ai, 0, 16); + $t1 = ParagonIE_Sodium_Core_Util::substr($ai, 16, 16); + return $this->update($t0, $t1); + } + + + /** + * @param string $ci + * @return string + * @throws SodiumException + */ + public function dec($ci) + { + if (ParagonIE_Sodium_Core_Util::strlen($ci) !== 32) { + throw new SodiumException('Input must be two AES blocks in size'); + } + + // z0 = S6 ^ S1 ^ (S2 & S3) + $z0 = $this->state[6] + ^ $this->state[1] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + // z1 = S2 ^ S5 ^ (S6 & S7) + $z1 = $this->state[2] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); + + // t0, t1 = Split(xi, 128) + $t0 = ParagonIE_Sodium_Core_Util::substr($ci, 0, 16); + $t1 = ParagonIE_Sodium_Core_Util::substr($ci, 16, 16); + + // out0 = t0 ^ z0 + // out1 = t1 ^ z1 + $out0 = $t0 ^ $z0; + $out1 = $t1 ^ $z1; + + // Update(out0, out1) + // xi = out0 || out1 + $this->update($out0, $out1); + return $out0 . $out1; + } + + /** + * @param string $cn + * @return string + */ + public function decPartial($cn) + { + $len = ParagonIE_Sodium_Core_Util::strlen($cn); + + // z0 = S6 ^ S1 ^ (S2 & S3) + $z0 = $this->state[6] + ^ $this->state[1] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + // z1 = S2 ^ S5 ^ (S6 & S7) + $z1 = $this->state[2] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); + + // t0, t1 = Split(ZeroPad(cn, 256), 128) + $cn = str_pad($cn, 32, "\0", STR_PAD_RIGHT); + $t0 = ParagonIE_Sodium_Core_Util::substr($cn, 0, 16); + $t1 = ParagonIE_Sodium_Core_Util::substr($cn, 16, 16); + // out0 = t0 ^ z0 + // out1 = t1 ^ z1 + $out0 = $t0 ^ $z0; + $out1 = $t1 ^ $z1; + + // xn = Truncate(out0 || out1, |cn|) + $xn = ParagonIE_Sodium_Core_Util::substr($out0 . $out1, 0, $len); + + // v0, v1 = Split(ZeroPad(xn, 256), 128) + $padded = str_pad($xn, 32, "\0", STR_PAD_RIGHT); + $v0 = ParagonIE_Sodium_Core_Util::substr($padded, 0, 16); + $v1 = ParagonIE_Sodium_Core_Util::substr($padded, 16, 16); + // Update(v0, v1) + $this->update($v0, $v1); + + // return xn + return $xn; + } + + /** + * @param string $xi + * @return string + * @throws SodiumException + */ + public function enc($xi) + { + if (ParagonIE_Sodium_Core_Util::strlen($xi) !== 32) { + throw new SodiumException('Input must be two AES blocks in size'); + } + + // z0 = S6 ^ S1 ^ (S2 & S3) + $z0 = $this->state[6] + ^ $this->state[1] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + // z1 = S2 ^ S5 ^ (S6 & S7) + $z1 = $this->state[2] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[6], $this->state[7]); + + // t0, t1 = Split(xi, 128) + $t0 = ParagonIE_Sodium_Core_Util::substr($xi, 0, 16); + $t1 = ParagonIE_Sodium_Core_Util::substr($xi, 16, 16); + + // out0 = t0 ^ z0 + // out1 = t1 ^ z1 + $out0 = $t0 ^ $z0; + $out1 = $t1 ^ $z1; + + // Update(t0, t1) + // ci = out0 || out1 + $this->update($t0, $t1); + + // return ci + return $out0 . $out1; + } + + /** + * @param int $ad_len_bits + * @param int $msg_len_bits + * @return string + */ + public function finalize($ad_len_bits, $msg_len_bits) + { + $encoded = ParagonIE_Sodium_Core_Util::store64_le($ad_len_bits) . + ParagonIE_Sodium_Core_Util::store64_le($msg_len_bits); + $t = $this->state[2] ^ $encoded; + for ($i = 0; $i < 7; ++$i) { + $this->update($t, $t); + } + return ($this->state[0] ^ $this->state[1] ^ $this->state[2] ^ $this->state[3]) . + ($this->state[4] ^ $this->state[5] ^ $this->state[6] ^ $this->state[7]); + } + + /** + * @param string $m0 + * @param string $m1 + * @return self + */ + public function update($m0, $m1) + { + /* + S'0 = AESRound(S7, S0 ^ M0) + S'1 = AESRound(S0, S1) + S'2 = AESRound(S1, S2) + S'3 = AESRound(S2, S3) + S'4 = AESRound(S3, S4 ^ M1) + S'5 = AESRound(S4, S5) + S'6 = AESRound(S5, S6) + S'7 = AESRound(S6, S7) + */ + list($s_0, $s_1) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[7], $this->state[0] ^ $m0, + $this->state[0], $this->state[1] + ); + + list($s_2, $s_3) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[1], $this->state[2], + $this->state[2], $this->state[3] + ); + + list($s_4, $s_5) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[3], $this->state[4] ^ $m1, + $this->state[4], $this->state[5] + ); + list($s_6, $s_7) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[5], $this->state[6], + $this->state[6], $this->state[7] + ); + + /* + S0 = S'0 + S1 = S'1 + S2 = S'2 + S3 = S'3 + S4 = S'4 + S5 = S'5 + S6 = S'6 + S7 = S'7 + */ + $this->state[0] = $s_0; + $this->state[1] = $s_1; + $this->state[2] = $s_2; + $this->state[3] = $s_3; + $this->state[4] = $s_4; + $this->state[5] = $s_5; + $this->state[6] = $s_6; + $this->state[7] = $s_7; + return $this; + } +} \ No newline at end of file diff --git a/src/wp-includes/sodium_compat/src/Core/AEGIS/State256.php b/src/wp-includes/sodium_compat/src/Core/AEGIS/State256.php new file mode 100644 index 0000000000000..6f88b828e1bcf --- /dev/null +++ b/src/wp-includes/sodium_compat/src/Core/AEGIS/State256.php @@ -0,0 +1,240 @@ + $state */ + protected $state; + public function __construct() + { + $this->state = array_fill(0, 6, ''); + } + + /** + * @internal Only use this for unit tests! + * @return string[] + */ + public function getState() + { + return array_values($this->state); + } + + /** + * @param array $input + * @return self + * @throws SodiumException + * + * @internal Only for unit tests + */ + public static function initForUnitTests(array $input) + { + if (count($input) < 6) { + throw new SodiumException('invalid input'); + } + $state = new self(); + for ($i = 0; $i < 6; ++$i) { + $state->state[$i] = $input[$i]; + } + return $state; + } + + /** + * @param string $key + * @param string $nonce + * @return self + */ + public static function init($key, $nonce) + { + $state = new self(); + $k0 = ParagonIE_Sodium_Core_Util::substr($key, 0, 16); + $k1 = ParagonIE_Sodium_Core_Util::substr($key, 16, 16); + $n0 = ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16); + $n1 = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 16); + + // S0 = k0 ^ n0 + // S1 = k1 ^ n1 + // S2 = C1 + // S3 = C0 + // S4 = k0 ^ C0 + // S5 = k1 ^ C1 + $k0_n0 = $k0 ^ $n0; + $k1_n1 = $k1 ^ $n1; + $state->state[0] = $k0_n0; + $state->state[1] = $k1_n1; + $state->state[2] = SODIUM_COMPAT_AEGIS_C1; + $state->state[3] = SODIUM_COMPAT_AEGIS_C0; + $state->state[4] = $k0 ^ SODIUM_COMPAT_AEGIS_C0; + $state->state[5] = $k1 ^ SODIUM_COMPAT_AEGIS_C1; + + // Repeat(4, + // Update(k0) + // Update(k1) + // Update(k0 ^ n0) + // Update(k1 ^ n1) + // ) + for ($i = 0; $i < 4; ++$i) { + $state->update($k0); + $state->update($k1); + $state->update($k0 ^ $n0); + $state->update($k1 ^ $n1); + } + return $state; + } + + /** + * @param string $ai + * @return self + * @throws SodiumException + */ + public function absorb($ai) + { + if (ParagonIE_Sodium_Core_Util::strlen($ai) !== 16) { + throw new SodiumException('Input must be an AES block in size'); + } + return $this->update($ai); + } + + /** + * @param string $ci + * @return string + * @throws SodiumException + */ + public function dec($ci) + { + if (ParagonIE_Sodium_Core_Util::strlen($ci) !== 16) { + throw new SodiumException('Input must be an AES block in size'); + } + // z = S1 ^ S4 ^ S5 ^ (S2 & S3) + $z = $this->state[1] + ^ $this->state[4] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + $xi = $ci ^ $z; + $this->update($xi); + return $xi; + } + + /** + * @param string $cn + * @return string + */ + public function decPartial($cn) + { + $len = ParagonIE_Sodium_Core_Util::strlen($cn); + // z = S1 ^ S4 ^ S5 ^ (S2 & S3) + $z = $this->state[1] + ^ $this->state[4] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + + // t = ZeroPad(cn, 128) + $t = str_pad($cn, 16, "\0", STR_PAD_RIGHT); + + // out = t ^ z + $out = $t ^ $z; + + // xn = Truncate(out, |cn|) + $xn = ParagonIE_Sodium_Core_Util::substr($out, 0, $len); + + // v = ZeroPad(xn, 128) + $v = str_pad($xn, 16, "\0", STR_PAD_RIGHT); + // Update(v) + $this->update($v); + + // return xn + return $xn; + } + + /** + * @param string $xi + * @return string + * @throws SodiumException + */ + public function enc($xi) + { + if (ParagonIE_Sodium_Core_Util::strlen($xi) !== 16) { + throw new SodiumException('Input must be an AES block in size'); + } + // z = S1 ^ S4 ^ S5 ^ (S2 & S3) + $z = $this->state[1] + ^ $this->state[4] + ^ $this->state[5] + ^ ParagonIE_Sodium_Core_Util::andStrings($this->state[2], $this->state[3]); + $this->update($xi); + return $xi ^ $z; + } + + /** + * @param int $ad_len_bits + * @param int $msg_len_bits + * @return string + */ + public function finalize($ad_len_bits, $msg_len_bits) + { + $encoded = ParagonIE_Sodium_Core_Util::store64_le($ad_len_bits) . + ParagonIE_Sodium_Core_Util::store64_le($msg_len_bits); + $t = $this->state[3] ^ $encoded; + + for ($i = 0; $i < 7; ++$i) { + $this->update($t); + } + + return ($this->state[0] ^ $this->state[1] ^ $this->state[2]) . + ($this->state[3] ^ $this->state[4] ^ $this->state[5]); + } + + /** + * @param string $m + * @return self + */ + public function update($m) + { + /* + S'0 = AESRound(S5, S0 ^ M) + S'1 = AESRound(S0, S1) + S'2 = AESRound(S1, S2) + S'3 = AESRound(S2, S3) + S'4 = AESRound(S3, S4) + S'5 = AESRound(S4, S5) + */ + list($s_0, $s_1) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[5],$this->state[0] ^ $m, + $this->state[0], $this->state[1] + ); + + list($s_2, $s_3) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[1], $this->state[2], + $this->state[2], $this->state[3] + ); + list($s_4, $s_5) = ParagonIE_Sodium_Core_AES::doubleRound( + $this->state[3], $this->state[4], + $this->state[4], $this->state[5] + ); + + /* + S0 = S'0 + S1 = S'1 + S2 = S'2 + S3 = S'3 + S4 = S'4 + S5 = S'5 + */ + $this->state[0] = $s_0; + $this->state[1] = $s_1; + $this->state[2] = $s_2; + $this->state[3] = $s_3; + $this->state[4] = $s_4; + $this->state[5] = $s_5; + return $this; + } +} diff --git a/src/wp-includes/sodium_compat/src/Core/AEGIS128L.php b/src/wp-includes/sodium_compat/src/Core/AEGIS128L.php new file mode 100644 index 0000000000000..ad1e85d324d3f --- /dev/null +++ b/src/wp-includes/sodium_compat/src/Core/AEGIS128L.php @@ -0,0 +1,119 @@ +> 5; + for ($i = 0; $i < $ad_blocks; ++$i) { + $ai = self::substr($ad, $i << 5, 32); + if (self::strlen($ai) < 32) { + $ai = str_pad($ai, 32, "\0", STR_PAD_RIGHT); + } + $state->absorb($ai); + } + + $msg = ''; + $cn = self::strlen($ct) & 31; + $ct_blocks = self::strlen($ct) >> 5; + for ($i = 0; $i < $ct_blocks; ++$i) { + $msg .= $state->dec(self::substr($ct, $i << 5, 32)); + } + if ($cn) { + $start = $ct_blocks << 5; + $msg .= $state->decPartial(self::substr($ct, $start, $cn)); + } + $expected_tag = $state->finalize( + self::strlen($ad) << 3, + self::strlen($msg) << 3 + ); + if (!self::hashEquals($expected_tag, $tag)) { + try { + // The RFC says to erase msg, so we shall try: + ParagonIE_Sodium_Compat::memzero($msg); + } catch (SodiumException $ex) { + // Do nothing if we cannot memzero + } + throw new SodiumException('verification failed'); + } + return $msg; + } + + /** + * @param string $msg + * @param string $ad + * @param string $key + * @param string $nonce + * @return array + * + * @throws SodiumException + */ + public static function encrypt($msg, $ad, $key, $nonce) + { + $state = self::init($key, $nonce); + // ad_blocks = Split(ZeroPad(ad, 256), 256) + // for ai in ad_blocks: + // Absorb(ai) + $ad_len = self::strlen($ad); + $msg_len = self::strlen($msg); + $ad_blocks = ($ad_len + 31) >> 5; + for ($i = 0; $i < $ad_blocks; ++$i) { + $ai = self::substr($ad, $i << 5, 32); + if (self::strlen($ai) < 32) { + $ai = str_pad($ai, 32, "\0", STR_PAD_RIGHT); + } + $state->absorb($ai); + } + + // msg_blocks = Split(ZeroPad(msg, 256), 256) + // for xi in msg_blocks: + // ct = ct || Enc(xi) + $ct = ''; + $msg_blocks = ($msg_len + 31) >> 5; + for ($i = 0; $i < $msg_blocks; ++$i) { + $xi = self::substr($msg, $i << 5, 32); + if (self::strlen($xi) < 32) { + $xi = str_pad($xi, 32, "\0", STR_PAD_RIGHT); + } + $ct .= $state->enc($xi); + } + // tag = Finalize(|ad|, |msg|) + // ct = Truncate(ct, |msg|) + $tag = $state->finalize( + $ad_len << 3, + $msg_len << 3 + ); + // return ct and tag + return array( + self::substr($ct, 0, $msg_len), + $tag + ); + } + + /** + * @param string $key + * @param string $nonce + * @return ParagonIE_Sodium_Core_AEGIS_State128L + */ + public static function init($key, $nonce) + { + return ParagonIE_Sodium_Core_AEGIS_State128L::init($key, $nonce); + } +} diff --git a/src/wp-includes/sodium_compat/src/Core/AEGIS256.php b/src/wp-includes/sodium_compat/src/Core/AEGIS256.php new file mode 100644 index 0000000000000..605bbcafde9a2 --- /dev/null +++ b/src/wp-includes/sodium_compat/src/Core/AEGIS256.php @@ -0,0 +1,118 @@ +> 4; + // for ai in ad_blocks: + // Absorb(ai) + for ($i = 0; $i < $ad_blocks; ++$i) { + $ai = self::substr($ad, $i << 4, 16); + if (self::strlen($ai) < 16) { + $ai = str_pad($ai, 16, "\0", STR_PAD_RIGHT); + } + $state->absorb($ai); + } + + $msg = ''; + $cn = self::strlen($ct) & 15; + $ct_blocks = self::strlen($ct) >> 4; + // ct_blocks = Split(ZeroPad(ct, 128), 128) + // cn = Tail(ct, |ct| mod 128) + for ($i = 0; $i < $ct_blocks; ++$i) { + $msg .= $state->dec(self::substr($ct, $i << 4, 16)); + } + // if cn is not empty: + // msg = msg || DecPartial(cn) + if ($cn) { + $start = $ct_blocks << 4; + $msg .= $state->decPartial(self::substr($ct, $start, $cn)); + } + $expected_tag = $state->finalize( + self::strlen($ad) << 3, + self::strlen($msg) << 3 + ); + if (!self::hashEquals($expected_tag, $tag)) { + try { + // The RFC says to erase msg, so we shall try: + ParagonIE_Sodium_Compat::memzero($msg); + } catch (SodiumException $ex) { + // Do nothing if we cannot memzero + } + throw new SodiumException('verification failed'); + } + return $msg; + } + + /** + * @param string $msg + * @param string $ad + * @param string $key + * @param string $nonce + * @return array + * @throws SodiumException + */ + public static function encrypt($msg, $ad, $key, $nonce) + { + $state = self::init($key, $nonce); + $ad_len = self::strlen($ad); + $msg_len = self::strlen($msg); + $ad_blocks = ($ad_len + 15) >> 4; + for ($i = 0; $i < $ad_blocks; ++$i) { + $ai = self::substr($ad, $i << 4, 16); + if (self::strlen($ai) < 16) { + $ai = str_pad($ai, 16, "\0", STR_PAD_RIGHT); + } + $state->absorb($ai); + } + + $ct = ''; + $msg_blocks = ($msg_len + 15) >> 4; + for ($i = 0; $i < $msg_blocks; ++$i) { + $xi = self::substr($msg, $i << 4, 16); + if (self::strlen($xi) < 16) { + $xi = str_pad($xi, 16, "\0", STR_PAD_RIGHT); + } + $ct .= $state->enc($xi); + } + $tag = $state->finalize( + $ad_len << 3, + $msg_len << 3 + ); + return array( + self::substr($ct, 0, $msg_len), + $tag + ); + + } + + /** + * @param string $key + * @param string $nonce + * @return ParagonIE_Sodium_Core_AEGIS_State256 + */ + public static function init($key, $nonce) + { + return ParagonIE_Sodium_Core_AEGIS_State256::init($key, $nonce); + } +} diff --git a/src/wp-includes/sodium_compat/src/Core/AES.php b/src/wp-includes/sodium_compat/src/Core/AES.php new file mode 100644 index 0000000000000..d86cff1a5b1f4 --- /dev/null +++ b/src/wp-includes/sodium_compat/src/Core/AES.php @@ -0,0 +1,518 @@ +orthogonalize(); + self::sbox($q); + $q->orthogonalize(); + return $q[0] & self::U32_MAX; + } + + /** + * Calculate the key schedule from a given random key + * + * @param string $key + * @return ParagonIE_Sodium_Core_AES_KeySchedule + * @throws SodiumException + */ + public static function keySchedule($key) + { + $key_len = self::strlen($key); + switch ($key_len) { + case 16: + $num_rounds = 10; + break; + case 24: + $num_rounds = 12; + break; + case 32: + $num_rounds = 14; + break; + default: + throw new SodiumException('Invalid key length: ' . $key_len); + } + $skey = array(); + $comp_skey = array(); + $nk = $key_len >> 2; + $nkf = ($num_rounds + 1) << 2; + $tmp = 0; + + for ($i = 0; $i < $nk; ++$i) { + $tmp = self::load_4(self::substr($key, $i << 2, 4)); + $skey[($i << 1)] = $tmp; + $skey[($i << 1) + 1] = $tmp; + } + + for ($i = $nk, $j = 0, $k = 0; $i < $nkf; ++$i) { + if ($j === 0) { + $tmp = (($tmp & 0xff) << 24) | ($tmp >> 8); + $tmp = (self::subWord($tmp) ^ self::$Rcon[$k]) & self::U32_MAX; + } elseif ($nk > 6 && $j === 4) { + $tmp = self::subWord($tmp); + } + $tmp ^= $skey[($i - $nk) << 1]; + $skey[($i << 1)] = $tmp & self::U32_MAX; + $skey[($i << 1) + 1] = $tmp & self::U32_MAX; + if (++$j === $nk) { + /** @psalm-suppress LoopInvalidation */ + $j = 0; + ++$k; + } + } + for ($i = 0; $i < $nkf; $i += 4) { + $q = ParagonIE_Sodium_Core_AES_Block::fromArray( + array_slice($skey, $i << 1, 8) + ); + $q->orthogonalize(); + // We have to overwrite $skey since we're not using C pointers like BearSSL did + for ($j = 0; $j < 8; ++$j) { + $skey[($i << 1) + $j] = $q[$j]; + } + } + for ($i = 0, $j = 0; $i < $nkf; ++$i, $j += 2) { + $comp_skey[$i] = ($skey[$j] & 0x55555555) + | ($skey[$j + 1] & 0xAAAAAAAA); + } + return new ParagonIE_Sodium_Core_AES_KeySchedule($comp_skey, $num_rounds); + } + + /** + * Mutates $q + * + * @param ParagonIE_Sodium_Core_AES_KeySchedule $skey + * @param ParagonIE_Sodium_Core_AES_Block $q + * @param int $offset + * @return void + */ + public static function addRoundKey( + ParagonIE_Sodium_Core_AES_Block $q, + ParagonIE_Sodium_Core_AES_KeySchedule $skey, + $offset = 0 + ) { + $block = $skey->getRoundKey($offset); + for ($j = 0; $j < 8; ++$j) { + $q[$j] = ($q[$j] ^ $block[$j]) & ParagonIE_Sodium_Core_Util::U32_MAX; + } + } + + /** + * This mainly exists for testing, as we need the round key features for AEGIS. + * + * @param string $message + * @param string $key + * @return string + * @throws SodiumException + */ + public static function decryptBlockECB($message, $key) + { + if (self::strlen($message) !== 16) { + throw new SodiumException('decryptBlockECB() expects a 16 byte message'); + } + $skey = self::keySchedule($key)->expand(); + $q = ParagonIE_Sodium_Core_AES_Block::init(); + $q[0] = self::load_4(self::substr($message, 0, 4)); + $q[2] = self::load_4(self::substr($message, 4, 4)); + $q[4] = self::load_4(self::substr($message, 8, 4)); + $q[6] = self::load_4(self::substr($message, 12, 4)); + + $q->orthogonalize(); + self::bitsliceDecryptBlock($skey, $q); + $q->orthogonalize(); + + return self::store32_le($q[0]) . + self::store32_le($q[2]) . + self::store32_le($q[4]) . + self::store32_le($q[6]); + } + + /** + * This mainly exists for testing, as we need the round key features for AEGIS. + * + * @param string $message + * @param string $key + * @return string + * @throws SodiumException + */ + public static function encryptBlockECB($message, $key) + { + if (self::strlen($message) !== 16) { + throw new SodiumException('encryptBlockECB() expects a 16 byte message'); + } + $comp_skey = self::keySchedule($key); + $skey = $comp_skey->expand(); + $q = ParagonIE_Sodium_Core_AES_Block::init(); + $q[0] = self::load_4(self::substr($message, 0, 4)); + $q[2] = self::load_4(self::substr($message, 4, 4)); + $q[4] = self::load_4(self::substr($message, 8, 4)); + $q[6] = self::load_4(self::substr($message, 12, 4)); + + $q->orthogonalize(); + self::bitsliceEncryptBlock($skey, $q); + $q->orthogonalize(); + + return self::store32_le($q[0]) . + self::store32_le($q[2]) . + self::store32_le($q[4]) . + self::store32_le($q[6]); + } + + /** + * Mutates $q + * + * @param ParagonIE_Sodium_Core_AES_Expanded $skey + * @param ParagonIE_Sodium_Core_AES_Block $q + * @return void + */ + public static function bitsliceEncryptBlock( + ParagonIE_Sodium_Core_AES_Expanded $skey, + ParagonIE_Sodium_Core_AES_Block $q + ) { + self::addRoundKey($q, $skey); + for ($u = 1; $u < $skey->getNumRounds(); ++$u) { + self::sbox($q); + $q->shiftRows(); + $q->mixColumns(); + self::addRoundKey($q, $skey, ($u << 3)); + } + self::sbox($q); + $q->shiftRows(); + self::addRoundKey($q, $skey, ($skey->getNumRounds() << 3)); + } + + /** + * @param string $x + * @param string $y + * @return string + */ + public static function aesRound($x, $y) + { + $q = ParagonIE_Sodium_Core_AES_Block::init(); + $q[0] = self::load_4(self::substr($x, 0, 4)); + $q[2] = self::load_4(self::substr($x, 4, 4)); + $q[4] = self::load_4(self::substr($x, 8, 4)); + $q[6] = self::load_4(self::substr($x, 12, 4)); + + $rk = ParagonIE_Sodium_Core_AES_Block::init(); + $rk[0] = $rk[1] = self::load_4(self::substr($y, 0, 4)); + $rk[2] = $rk[3] = self::load_4(self::substr($y, 4, 4)); + $rk[4] = $rk[5] = self::load_4(self::substr($y, 8, 4)); + $rk[6] = $rk[7] = self::load_4(self::substr($y, 12, 4)); + + $q->orthogonalize(); + self::sbox($q); + $q->shiftRows(); + $q->mixColumns(); + $q->orthogonalize(); + // add round key without key schedule: + for ($i = 0; $i < 8; ++$i) { + $q[$i] ^= $rk[$i]; + } + return self::store32_le($q[0]) . + self::store32_le($q[2]) . + self::store32_le($q[4]) . + self::store32_le($q[6]); + } + + /** + * Process two AES blocks in one shot. + * + * @param string $b0 First AES block + * @param string $rk0 First round key + * @param string $b1 Second AES block + * @param string $rk1 Second round key + * @return string[] + */ + public static function doubleRound($b0, $rk0, $b1, $rk1) + { + $q = ParagonIE_Sodium_Core_AES_Block::init(); + // First block + $q[0] = self::load_4(self::substr($b0, 0, 4)); + $q[2] = self::load_4(self::substr($b0, 4, 4)); + $q[4] = self::load_4(self::substr($b0, 8, 4)); + $q[6] = self::load_4(self::substr($b0, 12, 4)); + // Second block + $q[1] = self::load_4(self::substr($b1, 0, 4)); + $q[3] = self::load_4(self::substr($b1, 4, 4)); + $q[5] = self::load_4(self::substr($b1, 8, 4)); + $q[7] = self::load_4(self::substr($b1, 12, 4));; + + $rk = ParagonIE_Sodium_Core_AES_Block::init(); + // First round key + $rk[0] = self::load_4(self::substr($rk0, 0, 4)); + $rk[2] = self::load_4(self::substr($rk0, 4, 4)); + $rk[4] = self::load_4(self::substr($rk0, 8, 4)); + $rk[6] = self::load_4(self::substr($rk0, 12, 4)); + // Second round key + $rk[1] = self::load_4(self::substr($rk1, 0, 4)); + $rk[3] = self::load_4(self::substr($rk1, 4, 4)); + $rk[5] = self::load_4(self::substr($rk1, 8, 4)); + $rk[7] = self::load_4(self::substr($rk1, 12, 4)); + + $q->orthogonalize(); + self::sbox($q); + $q->shiftRows(); + $q->mixColumns(); + $q->orthogonalize(); + // add round key without key schedule: + for ($i = 0; $i < 8; ++$i) { + $q[$i] ^= $rk[$i]; + } + return array( + self::store32_le($q[0]) . self::store32_le($q[2]) . self::store32_le($q[4]) . self::store32_le($q[6]), + self::store32_le($q[1]) . self::store32_le($q[3]) . self::store32_le($q[5]) . self::store32_le($q[7]), + ); + } + + /** + * @param ParagonIE_Sodium_Core_AES_Expanded $skey + * @param ParagonIE_Sodium_Core_AES_Block $q + * @return void + */ + public static function bitsliceDecryptBlock( + ParagonIE_Sodium_Core_AES_Expanded $skey, + ParagonIE_Sodium_Core_AES_Block $q + ) { + self::addRoundKey($q, $skey, ($skey->getNumRounds() << 3)); + for ($u = $skey->getNumRounds() - 1; $u > 0; --$u) { + $q->inverseShiftRows(); + self::invSbox($q); + self::addRoundKey($q, $skey, ($u << 3)); + $q->inverseMixColumns(); + } + $q->inverseShiftRows(); + self::invSbox($q); + self::addRoundKey($q, $skey, ($u << 3)); + } +} diff --git a/src/wp-includes/sodium_compat/src/Core/AES/Block.php b/src/wp-includes/sodium_compat/src/Core/AES/Block.php new file mode 100644 index 0000000000000..070eb8d329b07 --- /dev/null +++ b/src/wp-includes/sodium_compat/src/Core/AES/Block.php @@ -0,0 +1,343 @@ + + */ + protected $values = array(); + + /** + * @var int + */ + protected $size; + + /** + * @param int $size + */ + public function __construct($size = 8) + { + parent::__construct($size); + $this->size = $size; + $this->values = array_fill(0, $size, 0); + } + + /** + * @return self + */ + public static function init() + { + return new self(8); + } + + /** + * @internal You should not use this directly from another application + * + * @param array $array + * @param bool $save_indexes + * @return self + * + * @psalm-suppress MethodSignatureMismatch + */ + #[ReturnTypeWillChange] + public static function fromArray($array, $save_indexes = null) + { + $count = count($array); + if ($save_indexes) { + $keys = array_keys($array); + } else { + $keys = range(0, $count - 1); + } + $array = array_values($array); + /** @var array $keys */ + + $obj = new ParagonIE_Sodium_Core_AES_Block(); + if ($save_indexes) { + for ($i = 0; $i < $count; ++$i) { + $obj->offsetSet($keys[$i], $array[$i]); + } + } else { + for ($i = 0; $i < $count; ++$i) { + $obj->offsetSet($i, $array[$i]); + } + } + return $obj; + } + + + /** + * @internal You should not use this directly from another application + * + * @param int|null $offset + * @param int $value + * @return void + * + * @psalm-suppress MethodSignatureMismatch + * @psalm-suppress MixedArrayOffset + */ + #[ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if (!is_int($value)) { + throw new InvalidArgumentException('Expected an integer'); + } + if (is_null($offset)) { + $this->values[] = $value; + } else { + $this->values[$offset] = $value; + } + } + + /** + * @internal You should not use this directly from another application + * + * @param int $offset + * @return bool + * + * @psalm-suppress MethodSignatureMismatch + * @psalm-suppress MixedArrayOffset + */ + #[ReturnTypeWillChange] + public function offsetExists($offset) + { + return isset($this->values[$offset]); + } + + /** + * @internal You should not use this directly from another application + * + * @param int $offset + * @return void + * + * @psalm-suppress MethodSignatureMismatch + * @psalm-suppress MixedArrayOffset + */ + #[ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->values[$offset]); + } + + /** + * @internal You should not use this directly from another application + * + * @param int $offset + * @return int + * + * @psalm-suppress MethodSignatureMismatch + * @psalm-suppress MixedArrayOffset + */ + #[ReturnTypeWillChange] + public function offsetGet($offset) + { + if (!isset($this->values[$offset])) { + $this->values[$offset] = 0; + } + return (int) ($this->values[$offset]); + } + + /** + * @internal You should not use this directly from another application + * + * @return array + */ + public function __debugInfo() + { + $out = array(); + foreach ($this->values as $v) { + $out[] = str_pad(dechex($v), 8, '0', STR_PAD_LEFT); + } + return array(implode(', ', $out)); + /* + return array(implode(', ', $this->values)); + */ + } + + /** + * @param int $cl low bit mask + * @param int $ch high bit mask + * @param int $s shift + * @param int $x index 1 + * @param int $y index 2 + * @return self + */ + public function swapN($cl, $ch, $s, $x, $y) + { + static $u32mask = ParagonIE_Sodium_Core_Util::U32_MAX; + $a = $this->values[$x] & $u32mask; + $b = $this->values[$y] & $u32mask; + // (x) = (a & cl) | ((b & cl) << (s)); + $this->values[$x] = ($a & $cl) | ((($b & $cl) << $s) & $u32mask); + // (y) = ((a & ch) >> (s)) | (b & ch); + $this->values[$y] = ((($a & $ch) & $u32mask) >> $s) | ($b & $ch); + return $this; + } + + /** + * @param int $x index 1 + * @param int $y index 2 + * @return self + */ + public function swap2($x, $y) + { + return $this->swapN(0x55555555, 0xAAAAAAAA, 1, $x, $y); + } + + /** + * @param int $x index 1 + * @param int $y index 2 + * @return self + */ + public function swap4($x, $y) + { + return $this->swapN(0x33333333, 0xCCCCCCCC, 2, $x, $y); + } + + /** + * @param int $x index 1 + * @param int $y index 2 + * @return self + */ + public function swap8($x, $y) + { + return $this->swapN(0x0F0F0F0F, 0xF0F0F0F0, 4, $x, $y); + } + + /** + * @return self + */ + public function orthogonalize() + { + return $this + ->swap2(0, 1) + ->swap2(2, 3) + ->swap2(4, 5) + ->swap2(6, 7) + + ->swap4(0, 2) + ->swap4(1, 3) + ->swap4(4, 6) + ->swap4(5, 7) + + ->swap8(0, 4) + ->swap8(1, 5) + ->swap8(2, 6) + ->swap8(3, 7); + } + + /** + * @return self + */ + public function shiftRows() + { + for ($i = 0; $i < 8; ++$i) { + $x = $this->values[$i] & ParagonIE_Sodium_Core_Util::U32_MAX; + $this->values[$i] = ( + ($x & 0x000000FF) + | (($x & 0x0000FC00) >> 2) | (($x & 0x00000300) << 6) + | (($x & 0x00F00000) >> 4) | (($x & 0x000F0000) << 4) + | (($x & 0xC0000000) >> 6) | (($x & 0x3F000000) << 2) + ) & ParagonIE_Sodium_Core_Util::U32_MAX; + } + return $this; + } + + /** + * @param int $x + * @return int + */ + public static function rotr16($x) + { + return (($x << 16) & ParagonIE_Sodium_Core_Util::U32_MAX) | ($x >> 16); + } + + /** + * @return self + */ + public function mixColumns() + { + $q0 = $this->values[0]; + $q1 = $this->values[1]; + $q2 = $this->values[2]; + $q3 = $this->values[3]; + $q4 = $this->values[4]; + $q5 = $this->values[5]; + $q6 = $this->values[6]; + $q7 = $this->values[7]; + $r0 = (($q0 >> 8) | ($q0 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r1 = (($q1 >> 8) | ($q1 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r2 = (($q2 >> 8) | ($q2 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r3 = (($q3 >> 8) | ($q3 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r4 = (($q4 >> 8) | ($q4 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r5 = (($q5 >> 8) | ($q5 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r6 = (($q6 >> 8) | ($q6 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r7 = (($q7 >> 8) | ($q7 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + + $this->values[0] = $q7 ^ $r7 ^ $r0 ^ self::rotr16($q0 ^ $r0); + $this->values[1] = $q0 ^ $r0 ^ $q7 ^ $r7 ^ $r1 ^ self::rotr16($q1 ^ $r1); + $this->values[2] = $q1 ^ $r1 ^ $r2 ^ self::rotr16($q2 ^ $r2); + $this->values[3] = $q2 ^ $r2 ^ $q7 ^ $r7 ^ $r3 ^ self::rotr16($q3 ^ $r3); + $this->values[4] = $q3 ^ $r3 ^ $q7 ^ $r7 ^ $r4 ^ self::rotr16($q4 ^ $r4); + $this->values[5] = $q4 ^ $r4 ^ $r5 ^ self::rotr16($q5 ^ $r5); + $this->values[6] = $q5 ^ $r5 ^ $r6 ^ self::rotr16($q6 ^ $r6); + $this->values[7] = $q6 ^ $r6 ^ $r7 ^ self::rotr16($q7 ^ $r7); + return $this; + } + + /** + * @return self + */ + public function inverseMixColumns() + { + $q0 = $this->values[0]; + $q1 = $this->values[1]; + $q2 = $this->values[2]; + $q3 = $this->values[3]; + $q4 = $this->values[4]; + $q5 = $this->values[5]; + $q6 = $this->values[6]; + $q7 = $this->values[7]; + $r0 = (($q0 >> 8) | ($q0 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r1 = (($q1 >> 8) | ($q1 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r2 = (($q2 >> 8) | ($q2 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r3 = (($q3 >> 8) | ($q3 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r4 = (($q4 >> 8) | ($q4 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r5 = (($q5 >> 8) | ($q5 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r6 = (($q6 >> 8) | ($q6 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $r7 = (($q7 >> 8) | ($q7 << 24)) & ParagonIE_Sodium_Core_Util::U32_MAX; + + $this->values[0] = $q5 ^ $q6 ^ $q7 ^ $r0 ^ $r5 ^ $r7 ^ self::rotr16($q0 ^ $q5 ^ $q6 ^ $r0 ^ $r5); + $this->values[1] = $q0 ^ $q5 ^ $r0 ^ $r1 ^ $r5 ^ $r6 ^ $r7 ^ self::rotr16($q1 ^ $q5 ^ $q7 ^ $r1 ^ $r5 ^ $r6); + $this->values[2] = $q0 ^ $q1 ^ $q6 ^ $r1 ^ $r2 ^ $r6 ^ $r7 ^ self::rotr16($q0 ^ $q2 ^ $q6 ^ $r2 ^ $r6 ^ $r7); + $this->values[3] = $q0 ^ $q1 ^ $q2 ^ $q5 ^ $q6 ^ $r0 ^ $r2 ^ $r3 ^ $r5 ^ self::rotr16($q0 ^ $q1 ^ $q3 ^ $q5 ^ $q6 ^ $q7 ^ $r0 ^ $r3 ^ $r5 ^ $r7); + $this->values[4] = $q1 ^ $q2 ^ $q3 ^ $q5 ^ $r1 ^ $r3 ^ $r4 ^ $r5 ^ $r6 ^ $r7 ^ self::rotr16($q1 ^ $q2 ^ $q4 ^ $q5 ^ $q7 ^ $r1 ^ $r4 ^ $r5 ^ $r6); + $this->values[5] = $q2 ^ $q3 ^ $q4 ^ $q6 ^ $r2 ^ $r4 ^ $r5 ^ $r6 ^ $r7 ^ self::rotr16($q2 ^ $q3 ^ $q5 ^ $q6 ^ $r2 ^ $r5 ^ $r6 ^ $r7); + $this->values[6] = $q3 ^ $q4 ^ $q5 ^ $q7 ^ $r3 ^ $r5 ^ $r6 ^ $r7 ^ self::rotr16($q3 ^ $q4 ^ $q6 ^ $q7 ^ $r3 ^ $r6 ^ $r7); + $this->values[7] = $q4 ^ $q5 ^ $q6 ^ $r4 ^ $r6 ^ $r7 ^ self::rotr16($q4 ^ $q5 ^ $q7 ^ $r4 ^ $r7); + return $this; + } + + /** + * @return self + */ + public function inverseShiftRows() + { + for ($i = 0; $i < 8; ++$i) { + $x = $this->values[$i]; + $this->values[$i] = ParagonIE_Sodium_Core_Util::U32_MAX & ( + ($x & 0x000000FF) + | (($x & 0x00003F00) << 2) | (($x & 0x0000C000) >> 6) + | (($x & 0x000F0000) << 4) | (($x & 0x00F00000) >> 4) + | (($x & 0x03000000) << 6) | (($x & 0xFC000000) >> 2) + ); + } + return $this; + } +} diff --git a/src/wp-includes/sodium_compat/src/Core/AES/Expanded.php b/src/wp-includes/sodium_compat/src/Core/AES/Expanded.php new file mode 100644 index 0000000000000..84a6a47658691 --- /dev/null +++ b/src/wp-includes/sodium_compat/src/Core/AES/Expanded.php @@ -0,0 +1,14 @@ + $skey -- has size 120 */ + protected $skey; + + /** @var bool $expanded */ + protected $expanded = false; + + /** @var int $numRounds */ + private $numRounds; + + /** + * @param array $skey + * @param int $numRounds + */ + public function __construct(array $skey, $numRounds = 10) + { + $this->skey = $skey; + $this->numRounds = $numRounds; + } + + /** + * Get a value at an arbitrary index. Mostly used for unit testing. + * + * @param int $i + * @return int + */ + public function get($i) + { + return $this->skey[$i]; + } + + /** + * @return int + */ + public function getNumRounds() + { + return $this->numRounds; + } + + /** + * @param int $offset + * @return ParagonIE_Sodium_Core_AES_Block + */ + public function getRoundKey($offset) + { + return ParagonIE_Sodium_Core_AES_Block::fromArray( + array_slice($this->skey, $offset, 8) + ); + } + + /** + * Return an expanded key schedule + * + * @return ParagonIE_Sodium_Core_AES_Expanded + */ + public function expand() + { + $exp = new ParagonIE_Sodium_Core_AES_Expanded( + array_fill(0, 120, 0), + $this->numRounds + ); + $n = ($exp->numRounds + 1) << 2; + for ($u = 0, $v = 0; $u < $n; ++$u, $v += 2) { + $x = $y = $this->skey[$u]; + $x &= 0x55555555; + $exp->skey[$v] = ($x | ($x << 1)) & ParagonIE_Sodium_Core_Util::U32_MAX; + $y &= 0xAAAAAAAA; + $exp->skey[$v + 1] = ($y | ($y >> 1)) & ParagonIE_Sodium_Core_Util::U32_MAX; + } + return $exp; + } +} diff --git a/src/wp-includes/sodium_compat/src/Core/Base64/Common.php b/src/wp-includes/sodium_compat/src/Core/Base64/Common.php deleted file mode 100644 index 94b2e8f33ab3f..0000000000000 --- a/src/wp-includes/sodium_compat/src/Core/Base64/Common.php +++ /dev/null @@ -1,213 +0,0 @@ - $chunk */ - $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 3)); - $b0 = $chunk[1]; - $b1 = $chunk[2]; - $b2 = $chunk[3]; - - $dest .= - self::encode6Bits( $b0 >> 2 ) . - self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . - self::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . - self::encode6Bits( $b2 & 63); - } - // The last chunk, which may have padding: - if ($i < $srcLen) { - /** @var array $chunk */ - $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); - $b0 = $chunk[1]; - if ($i + 1 < $srcLen) { - $b1 = $chunk[2]; - $dest .= - self::encode6Bits($b0 >> 2) . - self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . - self::encode6Bits(($b1 << 2) & 63); - if ($pad) { - $dest .= '='; - } - } else { - $dest .= - self::encode6Bits( $b0 >> 2) . - self::encode6Bits(($b0 << 4) & 63); - if ($pad) { - $dest .= '=='; - } - } - } - return $dest; - } - - /** - * decode from base64 into binary - * - * Base64 character set "./[A-Z][a-z][0-9]" - * - * @param string $src - * @param bool $strictPadding - * @return string - * @throws RangeException - * @throws TypeError - * @psalm-suppress RedundantCondition - */ - public static function decode($src, $strictPadding = false) - { - // Remove padding - $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); - if ($srcLen === 0) { - return ''; - } - - if ($strictPadding) { - if (($srcLen & 3) === 0) { - if ($src[$srcLen - 1] === '=') { - $srcLen--; - if ($src[$srcLen - 1] === '=') { - $srcLen--; - } - } - } - if (($srcLen & 3) === 1) { - throw new RangeException( - 'Incorrect padding' - ); - } - if ($src[$srcLen - 1] === '=') { - throw new RangeException( - 'Incorrect padding' - ); - } - } else { - $src = rtrim($src, '='); - $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); - } - - $err = 0; - $dest = ''; - // Main loop (no padding): - for ($i = 0; $i + 4 <= $srcLen; $i += 4) { - /** @var array $chunk */ - $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 4)); - $c0 = self::decode6Bits($chunk[1]); - $c1 = self::decode6Bits($chunk[2]); - $c2 = self::decode6Bits($chunk[3]); - $c3 = self::decode6Bits($chunk[4]); - - $dest .= pack( - 'CCC', - ((($c0 << 2) | ($c1 >> 4)) & 0xff), - ((($c1 << 4) | ($c2 >> 2)) & 0xff), - ((($c2 << 6) | $c3 ) & 0xff) - ); - $err |= ($c0 | $c1 | $c2 | $c3) >> 8; - } - // The last chunk, which may have padding: - if ($i < $srcLen) { - /** @var array $chunk */ - $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); - $c0 = self::decode6Bits($chunk[1]); - - if ($i + 2 < $srcLen) { - $c1 = self::decode6Bits($chunk[2]); - $c2 = self::decode6Bits($chunk[3]); - $dest .= pack( - 'CC', - ((($c0 << 2) | ($c1 >> 4)) & 0xff), - ((($c1 << 4) | ($c2 >> 2)) & 0xff) - ); - $err |= ($c0 | $c1 | $c2) >> 8; - } elseif ($i + 1 < $srcLen) { - $c1 = self::decode6Bits($chunk[2]); - $dest .= pack( - 'C', - ((($c0 << 2) | ($c1 >> 4)) & 0xff) - ); - $err |= ($c0 | $c1) >> 8; - } elseif ($i < $srcLen && $strictPadding) { - $err |= 1; - } - } - /** @var bool $check */ - $check = ($err === 0); - if (!$check) { - throw new RangeException( - 'Base64::decode() only expects characters in the correct base64 alphabet' - ); - } - return $dest; - } - - /** - * Uses bitwise operators instead of table-lookups to turn 6-bit integers - * into 8-bit integers. - * - * Base64 character set: - * [A-Z] [a-z] [0-9] + / - * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f - * - * @param int $src - * @return int - */ - abstract protected static function decode6Bits($src); - - /** - * Uses bitwise operators instead of table-lookups to turn 8-bit integers - * into 6-bit integers. - * - * @param int $src - * @return string - */ - abstract protected static function encode6Bits($src); -} diff --git a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/Cached.php b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/Cached.php index 39bf8977766c9..06774ba4fd13d 100644 --- a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/Cached.php +++ b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/Cached.php @@ -40,26 +40,38 @@ class ParagonIE_Sodium_Core_Curve25519_Ge_Cached * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $T2d */ public function __construct( - ParagonIE_Sodium_Core_Curve25519_Fe $YplusX = null, - ParagonIE_Sodium_Core_Curve25519_Fe $YminusX = null, - ParagonIE_Sodium_Core_Curve25519_Fe $Z = null, - ParagonIE_Sodium_Core_Curve25519_Fe $T2d = null + $YplusX = null, + $YminusX = null, + $Z = null, + $T2d = null ) { if ($YplusX === null) { $YplusX = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($YplusX instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->YplusX = $YplusX; if ($YminusX === null) { $YminusX = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($YminusX instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->YminusX = $YminusX; if ($Z === null) { $Z = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($Z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->Z = $Z; if ($T2d === null) { $T2d = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($T2d instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 4 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->T2d = $T2d; } } diff --git a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P1p1.php b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P1p1.php index a63d6ab232e7c..62d36eb05cb15 100644 --- a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P1p1.php +++ b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P1p1.php @@ -39,26 +39,38 @@ class ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $t */ public function __construct( - ParagonIE_Sodium_Core_Curve25519_Fe $x = null, - ParagonIE_Sodium_Core_Curve25519_Fe $y = null, - ParagonIE_Sodium_Core_Curve25519_Fe $z = null, - ParagonIE_Sodium_Core_Curve25519_Fe $t = null + $x = null, + $y = null, + $z = null, + $t = null ) { if ($x === null) { $x = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($x instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->X = $x; if ($y === null) { $y = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($y instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->Y = $y; if ($z === null) { $z = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->Z = $z; if ($t === null) { $t = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($t instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 4 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->T = $t; } } diff --git a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P2.php b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P2.php index aee4000d14193..029be720c1068 100644 --- a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P2.php +++ b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P2.php @@ -34,21 +34,30 @@ class ParagonIE_Sodium_Core_Curve25519_Ge_P2 * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $z */ public function __construct( - ParagonIE_Sodium_Core_Curve25519_Fe $x = null, - ParagonIE_Sodium_Core_Curve25519_Fe $y = null, - ParagonIE_Sodium_Core_Curve25519_Fe $z = null + $x = null, + $y = null, + $z = null ) { if ($x === null) { $x = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($x instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->X = $x; if ($y === null) { $y = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($y instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->Y = $y; if ($z === null) { $z = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->Z = $z; } } diff --git a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P3.php b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P3.php index 00f5b27a4b288..e5b2fe45992fd 100644 --- a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P3.php +++ b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/P3.php @@ -40,26 +40,38 @@ class ParagonIE_Sodium_Core_Curve25519_Ge_P3 * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $t */ public function __construct( - ParagonIE_Sodium_Core_Curve25519_Fe $x = null, - ParagonIE_Sodium_Core_Curve25519_Fe $y = null, - ParagonIE_Sodium_Core_Curve25519_Fe $z = null, - ParagonIE_Sodium_Core_Curve25519_Fe $t = null + $x = null, + $y = null, + $z = null, + $t = null ) { if ($x === null) { $x = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($x instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->X = $x; if ($y === null) { $y = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($y instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->Y = $y; if ($z === null) { $z = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->Z = $z; if ($t === null) { $t = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($t instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 4 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->T = $t; } } diff --git a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/Precomp.php b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/Precomp.php index 59611c103669e..2503d7a6a86f9 100644 --- a/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/Precomp.php +++ b/src/wp-includes/sodium_compat/src/Core/Curve25519/Ge/Precomp.php @@ -34,21 +34,30 @@ class ParagonIE_Sodium_Core_Curve25519_Ge_Precomp * @param ParagonIE_Sodium_Core_Curve25519_Fe $xy2d */ public function __construct( - ParagonIE_Sodium_Core_Curve25519_Fe $yplusx = null, - ParagonIE_Sodium_Core_Curve25519_Fe $yminusx = null, - ParagonIE_Sodium_Core_Curve25519_Fe $xy2d = null + $yplusx = null, + $yminusx = null, + $xy2d = null ) { if ($yplusx === null) { $yplusx = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($yplusx instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 1 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->yplusx = $yplusx; if ($yminusx === null) { $yminusx = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($yminusx instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 2 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->yminusx = $yminusx; if ($xy2d === null) { $xy2d = new ParagonIE_Sodium_Core_Curve25519_Fe(); } + if (!($xy2d instanceof ParagonIE_Sodium_Core_Curve25519_Fe)) { + throw new TypeError('Argument 3 must be an instance of ParagonIE_Sodium_Core_Curve25519_Fe'); + } $this->xy2d = $xy2d; } } diff --git a/src/wp-includes/sodium_compat/src/Core/Util.php b/src/wp-includes/sodium_compat/src/Core/Util.php index 73e463f298158..e5d96dcdceda8 100644 --- a/src/wp-includes/sodium_compat/src/Core/Util.php +++ b/src/wp-includes/sodium_compat/src/Core/Util.php @@ -9,6 +9,8 @@ */ abstract class ParagonIE_Sodium_Core_Util { + const U32_MAX = 0xFFFFFFFF; + /** * @param int $integer * @param int $size (16, 32, 64) @@ -33,6 +35,28 @@ public static function abs($integer, $size = 0) ); } + /** + * @param string $a + * @param string $b + * @return string + * @throws SodiumException + */ + public static function andStrings($a, $b) + { + /* Type checks: */ + if (!is_string($a)) { + throw new TypeError('Argument 1 must be a string'); + } + if (!is_string($b)) { + throw new TypeError('Argument 2 must be a string'); + } + $len = self::strlen($a); + if (self::strlen($b) !== $len) { + throw new SodiumException('Both strings must be of equal length to combine with bitwise AND'); + } + return $a & $b; + } + /** * Convert a binary string into a hexadecimal string without cache-timing * leaks diff --git a/src/wp-includes/sodium_compat/src/File.php b/src/wp-includes/sodium_compat/src/File.php index e8622c7eba2da..1b02fc4be619e 100644 --- a/src/wp-includes/sodium_compat/src/File.php +++ b/src/wp-includes/sodium_compat/src/File.php @@ -25,8 +25,13 @@ class ParagonIE_Sodium_File extends ParagonIE_Sodium_Core_Util * @throws SodiumException * @throws TypeError */ - public static function box($inputFile, $outputFile, $nonce, $keyPair) - { + public static function box( + $inputFile, + $outputFile, + $nonce, + #[\SensitiveParameter] + $keyPair + ) { /* Type checks: */ if (!is_string($inputFile)) { throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.'); @@ -91,8 +96,13 @@ public static function box($inputFile, $outputFile, $nonce, $keyPair) * @throws SodiumException * @throws TypeError */ - public static function box_open($inputFile, $outputFile, $nonce, $keypair) - { + public static function box_open( + $inputFile, + $outputFile, + $nonce, + #[\SensitiveParameter] + $keypair + ) { /* Type checks: */ if (!is_string($inputFile)) { throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.'); @@ -161,8 +171,12 @@ public static function box_open($inputFile, $outputFile, $nonce, $keypair) * @throws SodiumException * @throws TypeError */ - public static function box_seal($inputFile, $outputFile, $publicKey) - { + public static function box_seal( + $inputFile, + $outputFile, + #[\SensitiveParameter] + $publicKey + ) { /* Type checks: */ if (!is_string($inputFile)) { throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.'); @@ -265,8 +279,12 @@ public static function box_seal($inputFile, $outputFile, $publicKey) * @throws SodiumException * @throws TypeError */ - public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair) - { + public static function box_seal_open( + $inputFile, + $outputFile, + #[\SensitiveParameter] + $ecdhKeypair + ) { /* Type checks: */ if (!is_string($inputFile)) { throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.'); @@ -350,8 +368,12 @@ public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair) * @throws TypeError * @psalm-suppress FailedTypeResolution */ - public static function generichash($filePath, $key = '', $outputLength = 32) - { + public static function generichash( + $filePath, + #[\SensitiveParameter] + $key = '', + $outputLength = 32 + ) { /* Type checks: */ if (!is_string($filePath)) { throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.'); @@ -428,8 +450,13 @@ public static function generichash($filePath, $key = '', $outputLength = 32) * @throws SodiumException * @throws TypeError */ - public static function secretbox($inputFile, $outputFile, $nonce, $key) - { + public static function secretbox( + $inputFile, + $outputFile, + $nonce, + #[\SensitiveParameter] + $key + ) { /* Type checks: */ if (!is_string($inputFile)) { throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given..'); @@ -493,8 +520,13 @@ public static function secretbox($inputFile, $outputFile, $nonce, $key) * @throws SodiumException * @throws TypeError */ - public static function secretbox_open($inputFile, $outputFile, $nonce, $key) - { + public static function secretbox_open( + $inputFile, + $outputFile, + $nonce, + #[\SensitiveParameter] + $key + ) { /* Type checks: */ if (!is_string($inputFile)) { throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.'); @@ -560,8 +592,11 @@ public static function secretbox_open($inputFile, $outputFile, $nonce, $key) * @throws SodiumException * @throws TypeError */ - public static function sign($filePath, $secretKey) - { + public static function sign( + $filePath, + #[\SensitiveParameter] + $secretKey + ) { /* Type checks: */ if (!is_string($filePath)) { throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.'); @@ -656,8 +691,11 @@ public static function sign($filePath, $secretKey) * @throws TypeError * @throws Exception */ - public static function verify($sig, $filePath, $publicKey) - { + public static function verify( + $sig, + $filePath, + $publicKey + ) { /* Type checks: */ if (!is_string($sig)) { throw new TypeError('Argument 1 must be a string, ' . gettype($sig) . ' given.'); diff --git a/src/wp-includes/style-engine/class-wp-style-engine.php b/src/wp-includes/style-engine/class-wp-style-engine.php index 1ba813ed65b14..3012ca3eefd30 100644 --- a/src/wp-includes/style-engine/class-wp-style-engine.php +++ b/src/wp-includes/style-engine/class-wp-style-engine.php @@ -50,31 +50,37 @@ final class WP_Style_Engine { */ const BLOCK_STYLE_DEFINITIONS_METADATA = array( 'background' => array( - 'backgroundImage' => array( + 'backgroundImage' => array( 'property_keys' => array( 'default' => 'background-image', ), 'value_func' => array( self::class, 'get_url_or_value_css_declaration' ), 'path' => array( 'background', 'backgroundImage' ), ), - 'backgroundPosition' => array( + 'backgroundPosition' => array( 'property_keys' => array( 'default' => 'background-position', ), 'path' => array( 'background', 'backgroundPosition' ), ), - 'backgroundRepeat' => array( + 'backgroundRepeat' => array( 'property_keys' => array( 'default' => 'background-repeat', ), 'path' => array( 'background', 'backgroundRepeat' ), ), - 'backgroundSize' => array( + 'backgroundSize' => array( 'property_keys' => array( 'default' => 'background-size', ), 'path' => array( 'background', 'backgroundSize' ), ), + 'backgroundAttachment' => array( + 'property_keys' => array( + 'default' => 'background-attachment', + ), + 'path' => array( 'background', 'backgroundAttachment' ), + ), ), 'color' => array( 'text' => array( diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 693ab1b84d447..bebf55ec79bfa 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1430,7 +1430,8 @@ function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) { * @return mixed An array of values if `$single` is false. * The value of the meta field if `$single` is true. * False for an invalid `$term_id` (non-numeric, zero, or negative value). - * An empty string if a valid but non-existing term ID is passed. + * An empty array if a valid but non-existing term ID is passed and `$single` is false. + * An empty string if a valid but non-existing term ID is passed and `$single` is true. */ function get_term_meta( $term_id, $key = '', $single = false ) { return get_metadata( 'term', $term_id, $key, $single ); @@ -2825,7 +2826,6 @@ function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) { } $tt_ids = array(); - $term_ids = array(); $new_tt_ids = array(); foreach ( (array) $terms as $term ) { @@ -2848,9 +2848,8 @@ function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) { return $term_info; } - $term_ids[] = $term_info['term_id']; - $tt_id = $term_info['term_taxonomy_id']; - $tt_ids[] = $tt_id; + $tt_id = $term_info['term_taxonomy_id']; + $tt_ids[] = $tt_id; if ( $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id = %d", $object_id, $tt_id ) ) ) { continue; diff --git a/src/wp-includes/theme-i18n.json b/src/wp-includes/theme-i18n.json index 8bda329add4ce..1781b9356f4bf 100644 --- a/src/wp-includes/theme-i18n.json +++ b/src/wp-includes/theme-i18n.json @@ -1,5 +1,6 @@ { "title": "Style variation name", + "description": "Style variation description", "settings": { "typography": { "fontSizes": [ @@ -80,15 +81,6 @@ } } }, - "styles": { - "blocks": { - "variations": { - "*": { - "title": "Style variation name" - } - } - } - }, "customTemplates": [ { "title": "Custom template name" diff --git a/src/wp-includes/update.php b/src/wp-includes/update.php index d521913bb93e4..c623b248b9308 100644 --- a/src/wp-includes/update.php +++ b/src/wp-includes/update.php @@ -31,22 +31,20 @@ function wp_version_check( $extra_stats = array(), $force_check = false ) { return; } - // Include an unmodified $wp_version. - require ABSPATH . WPINC . '/version.php'; $php_version = PHP_VERSION; $current = get_site_transient( 'update_core' ); $translations = wp_get_installed_translations( 'core' ); // Invalidate the transient when $wp_version changes. - if ( is_object( $current ) && $wp_version !== $current->version_checked ) { + if ( is_object( $current ) && wp_get_wp_version() !== $current->version_checked ) { $current = false; } if ( ! is_object( $current ) ) { $current = new stdClass(); $current->updates = array(); - $current->version_checked = $wp_version; + $current->version_checked = wp_get_wp_version(); } if ( ! empty( $extra_stats ) ) { @@ -95,7 +93,7 @@ function wp_version_check( $extra_stats = array(), $force_check = false ) { $extensions = get_loaded_extensions(); sort( $extensions, SORT_STRING | SORT_FLAG_CASE ); $query = array( - 'version' => $wp_version, + 'version' => wp_get_wp_version(), 'php' => $php_version, 'locale' => $locale, 'mysql' => $mysql_version, @@ -191,7 +189,7 @@ function wp_version_check( $extra_stats = array(), $force_check = false ) { $options = array( 'timeout' => $doing_cron ? 30 : 3, - 'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url( '/' ), + 'user-agent' => 'WordPress/' . wp_get_wp_version() . '; ' . home_url( '/' ), 'headers' => array( 'wp_install' => $wp_install, 'wp_blog' => home_url( '/' ), @@ -266,7 +264,7 @@ function wp_version_check( $extra_stats = array(), $force_check = false ) { $updates = new stdClass(); $updates->updates = $offers; $updates->last_checked = time(); - $updates->version_checked = $wp_version; + $updates->version_checked = wp_get_wp_version(); if ( isset( $body['translations'] ) ) { $updates->translations = $body['translations']; @@ -315,9 +313,6 @@ function wp_update_plugins( $extra_stats = array() ) { return; } - // Include an unmodified $wp_version. - require ABSPATH . WPINC . '/version.php'; - // If running blog-side, bail unless we've not checked in the last 12 hours. if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; @@ -423,7 +418,7 @@ function wp_update_plugins( $extra_stats = array() ) { 'locale' => wp_json_encode( $locales ), 'all' => wp_json_encode( true ), ), - 'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url( '/' ), + 'user-agent' => 'WordPress/' . wp_get_wp_version() . '; ' . home_url( '/' ), ); if ( $extra_stats ) { @@ -590,9 +585,6 @@ function wp_update_themes( $extra_stats = array() ) { return; } - // Include an unmodified $wp_version. - require ABSPATH . WPINC . '/version.php'; - $installed_themes = wp_get_themes(); $translations = wp_get_installed_translations( 'themes' ); @@ -705,7 +697,7 @@ function wp_update_themes( $extra_stats = array() ) { 'translations' => wp_json_encode( $translations ), 'locale' => wp_json_encode( $locales ), ), - 'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url( '/' ), + 'user-agent' => 'WordPress/' . wp_get_wp_version() . '; ' . home_url( '/' ), ); if ( $extra_stats ) { @@ -989,14 +981,11 @@ function wp_get_update_data() { * @global string $wp_version The WordPress version string. */ function _maybe_update_core() { - // Include an unmodified $wp_version. - require ABSPATH . WPINC . '/version.php'; - $current = get_site_transient( 'update_core' ); if ( isset( $current->last_checked, $current->version_checked ) && 12 * HOUR_IN_SECONDS > ( time() - $current->last_checked ) - && $current->version_checked === $wp_version + && wp_get_wp_version() === $current->version_checked ) { return; } @@ -1110,8 +1099,6 @@ function wp_delete_all_temp_backups() { * @access private * * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. - * - * @return void|WP_Error Void on success, or a WP_Error object on failure. */ function _wp_delete_all_temp_backups() { global $wp_filesystem; @@ -1125,15 +1112,17 @@ function _wp_delete_all_temp_backups() { ob_end_clean(); if ( false === $credentials || ! WP_Filesystem( $credentials ) ) { - return new WP_Error( 'fs_unavailable', __( 'Could not access filesystem.' ) ); + wp_trigger_error( __FUNCTION__, __( 'Could not access filesystem.' ) ); + return; } if ( ! $wp_filesystem->wp_content_dir() ) { - return new WP_Error( - 'fs_no_content_dir', + wp_trigger_error( + __FUNCTION__, /* translators: %s: Directory name. */ sprintf( __( 'Unable to locate WordPress content directory (%s).' ), 'wp-content' ) ); + return; } $temp_backup_dir = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/'; diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index a2949e96bec2d..c234c495dfe3d 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -1185,7 +1185,8 @@ function delete_user_meta( $user_id, $meta_key, $meta_value = '' ) { * @return mixed An array of values if `$single` is false. * The value of meta data field if `$single` is true. * False for an invalid `$user_id` (non-numeric, zero, or negative value). - * An empty string if a valid but non-existing user ID is passed. + * An empty array if a valid but non-existing user ID is passed and `$single` is false. + * An empty string if a valid but non-existing user ID is passed and `$single` is true. */ function get_user_meta( $user_id, $key = '', $single = false ) { return get_metadata( 'user', $user_id, $key, $single ); @@ -1549,12 +1550,10 @@ function setup_userdata( $for_user_id = 0 ) { /** * Creates dropdown HTML content of users. * - * The content can either be displayed, which it is by default or retrieved by - * setting the 'echo' argument. The 'include' and 'exclude' arguments do not - * need to be used; all users will be displayed in that case. Only one can be - * used, either 'include' or 'exclude', but not both. - * - * The available arguments are as follows: + * The content can either be displayed, which it is by default, or retrieved by + * setting the 'echo' argument to false. The 'include' and 'exclude' arguments + * are optional; if they are not specified, all users will be displayed. Only one + * can be used in a single call, either 'include' or 'exclude', but not both. * * @since 2.3.0 * @since 4.5.0 Added the 'display_name_with_login' value for 'show'. @@ -2386,7 +2385,7 @@ function wp_insert_user( $userdata ) { * @type string $user_pass The user's password. * @type string $user_email The user's email. * @type string $user_url The user's url. - * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login + * @type string $user_nicename The user's nice name. Defaults to a URL-safe version of user's login. * @type string $display_name The user's display name. * @type string $user_registered MySQL timestamp describing the moment when the user registered. Defaults to * the current UTC timestamp. @@ -3219,7 +3218,15 @@ function retrieve_password( $user_login = null ) { $message .= sprintf( __( 'Username: %s' ), $user_login ) . "\r\n\r\n"; $message .= __( 'If this was a mistake, ignore this email and nothing will happen.' ) . "\r\n\r\n"; $message .= __( 'To reset your password, visit the following address:' ) . "\r\n\r\n"; - $message .= network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user_login ), 'login' ) . '&wp_lang=' . $locale . "\r\n\r\n"; + + /* + * Since some user login names end in a period, this could produce ambiguous URLs that + * end in a period. To avoid the ambiguity, ensure that the login is not the last query + * arg in the URL. If moving it to the end, a trailing period will need to be escaped. + * + * @see https://core.trac.wordpress.org/tickets/42957 + */ + $message .= network_site_url( 'wp-login.php?login=' . rawurlencode( $user_login ) . "&key=$key&action=rp", 'login' ) . '&wp_lang=' . $locale . "\r\n\r\n"; if ( ! is_user_logged_in() ) { $requester_ip = $_SERVER['REMOTE_ADDR']; @@ -4148,7 +4155,7 @@ function _wp_privacy_send_request_confirmation_notification( $request_id ) { * Data relating to the account action email. * * @type WP_User_Request $request User request object. - * @type string $user_email The email address confirming a request + * @type string $user_email The email address confirming a request. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. @@ -4199,7 +4206,7 @@ function _wp_privacy_send_request_confirmation_notification( $request_id ) { * Data relating to the account action email. * * @type WP_User_Request $request User request object. - * @type string $user_email The email address confirming a request + * @type string $user_email The email address confirming a request. * @type string $description Description of the action being performed * so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. @@ -4239,7 +4246,7 @@ function _wp_privacy_send_request_confirmation_notification( $request_id ) { * Data relating to the account action email. * * @type WP_User_Request $request User request object. - * @type string $user_email The email address confirming a request + * @type string $user_email The email address confirming a request. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. @@ -4270,7 +4277,7 @@ function _wp_privacy_send_request_confirmation_notification( $request_id ) { * Data relating to the account action email. * * @type WP_User_Request $request User request object. - * @type string $user_email The email address confirming a request + * @type string $user_email The email address confirming a request. * @type string $description Description of the action being performed so the user knows what the email is for. * @type string $manage_url The link to click manage privacy requests of this type. * @type string $sitename The site name sending the mail. diff --git a/src/wp-includes/version.php b/src/wp-includes/version.php index 13b17c8bd0e9c..5b77c623a5618 100644 --- a/src/wp-includes/version.php +++ b/src/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.6-beta2-58392-src'; +$wp_version = '6.7-alpha-58576-src'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. diff --git a/src/wp-login.php b/src/wp-login.php index 16faccb703687..45d7794c84ed7 100644 --- a/src/wp-login.php +++ b/src/wp-login.php @@ -1160,11 +1160,11 @@ function wp_login_viewport_meta() {

- +

- +

get_error_code() || 'empty_password' === $errors->get_error_code() ) ? esc_attr( wp_unslash( $_POST['log'] ) ) : ''; + $user_login = ( 'incorrect_password' === $errors->get_error_code() || 'empty_password' === $errors->get_error_code() ) ? wp_unslash( $_POST['log'] ) : ''; } $rememberme = ! empty( $_POST['rememberme'] ); diff --git a/src/wp-settings.php b/src/wp-settings.php index a682c2a50380a..d3dfe5776ee88 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -252,6 +252,7 @@ require ABSPATH . WPINC . '/html-api/html5-named-character-references.php'; require ABSPATH . WPINC . '/html-api/class-wp-html-attribute-token.php'; require ABSPATH . WPINC . '/html-api/class-wp-html-span.php'; +require ABSPATH . WPINC . '/html-api/class-wp-html-doctype-info.php'; require ABSPATH . WPINC . '/html-api/class-wp-html-text-replacement.php'; require ABSPATH . WPINC . '/html-api/class-wp-html-decoder.php'; require ABSPATH . WPINC . '/html-api/class-wp-html-tag-processor.php'; @@ -715,7 +716,7 @@ * Ajax requests should use wp-admin/admin-ajax.php. admin-ajax.php can handle requests for * users not logged in. * - * @link https://codex.wordpress.org/AJAX_in_Plugins + * @link https://developer.wordpress.org/plugins/javascript/ajax * * @since 3.0.0 */ diff --git a/tests/e2e/specs/fatal-error-handler.test.js b/tests/e2e/specs/fatal-error-handler.test.js new file mode 100644 index 0000000000000..1b5522358ebb9 --- /dev/null +++ b/tests/e2e/specs/fatal-error-handler.test.js @@ -0,0 +1,46 @@ +/** + * External dependencies + */ +import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * WordPress dependencies + */ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; + +test.describe( 'Fatal error handler', () => { + const muPlugins = join( + process.cwd(), + process.env.LOCAL_DIR ?? 'src', + 'wp-content/mu-plugins' + ); + const muPluginFile = join( muPlugins, 'fatal-error.php' ); + + test.beforeAll( async () => { + const muPluginCode = ` { + unlinkSync( muPluginFile ); + } ); + + test( 'should display fatal error notice', async ( { admin, page } ) => { + await admin.visitAdminPage( '/' ); + + await expect( + page.getByText( /Fatal error:/ ), + 'should display PHP error message' + ).toBeVisible(); + + await expect( + page.getByText( /There has been a critical error on this website/ ), + 'should display WordPress fatal error handler message' + ).toBeVisible(); + } ); +} ); diff --git a/tests/e2e/specs/install.test.js b/tests/e2e/specs/install.test.js new file mode 100644 index 0000000000000..27bc56575ee32 --- /dev/null +++ b/tests/e2e/specs/install.test.js @@ -0,0 +1,85 @@ +/** + * External dependencies + */ +import { writeFileSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * WordPress dependencies + */ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; + +let wpConfigOriginal; + +test.describe( 'WordPress installation process', () => { + const wpConfig = join( + process.cwd(), + 'wp-config.php', + ); + + + test.beforeEach( async () => { + wpConfigOriginal = readFileSync( wpConfig, 'utf-8' ); + // Changing the table prefix tricks WP into new install mode. + writeFileSync( + wpConfig, + wpConfigOriginal.replace( `$table_prefix = 'wp_';`, `$table_prefix = 'wp_e2e_';` ) + ); + } ); + + test.afterEach( async () => { + writeFileSync( wpConfig, wpConfigOriginal ); + } ); + + test( 'should install WordPress with pre-existing database credentials', async ( { page } ) => { + await page.goto( '/' ); + + await expect( + page, + 'should redirect to the installation page' + ).toHaveURL( /wp-admin\/install\.php$/ ); + + await expect( + page.getByText( /WordPress database error/ ), + 'should not have any database errors' + ).not.toBeVisible(); + + // First page: language selector. Keep default English (US). + await page.getByRole( 'button', { name: 'Continue' } ).click(); + + // Second page: enter site name, username & password. + + await expect( page.getByRole( 'heading', { name: 'Welcome' } ) ).toBeVisible(); + + // This information matches tools/local-env/scripts/install.js. + + await page.getByLabel( 'Site Title' ).fill( 'WordPress Develop' ); + await page.getByLabel( 'Username' ).fill( 'admin' ); + await page.getByLabel( 'Password', { exact: true } ).fill( '' ); + await page.getByLabel( 'Password', { exact: true } ).fill( 'password' ); + await page.getByLabel( /Confirm use of weak password/ ).check() + await page.getByLabel( 'Your Email' ).fill( 'test@test.com' ); + + await page.getByRole( 'button', { name: 'Install WordPress' } ).click(); + + // Installation finished, can now log in. + + await expect( page.getByRole( 'heading', { name: 'Success!' } ) ).toBeVisible(); + + await page.getByRole( 'link', { name: 'Log In' } ).click(); + + await expect( + page, + 'should redirect to the login page' + ).toHaveURL( /wp-login\.php$/ ); + + await page.getByLabel( 'Username or Email Address' ).fill( 'admin' ); + await page.getByLabel( 'Password', { exact: true } ).fill( 'password' ); + + await page.getByRole( 'button', { name: 'Log In' } ).click(); + + await expect( + page.getByRole( 'heading', { name: 'Welcome to WordPress', level: 2 }) + ).toBeVisible(); + } ); +} ); diff --git a/tests/e2e/specs/maintenance-mode.test.js b/tests/e2e/specs/maintenance-mode.test.js new file mode 100644 index 0000000000000..df9d409a07895 --- /dev/null +++ b/tests/e2e/specs/maintenance-mode.test.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { writeFileSync, unlinkSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * WordPress dependencies + */ +import { test, expect } from '@wordpress/e2e-test-utils-playwright'; + +test.describe( 'Maintenance mode', () => { + const documentRoot = join( + process.cwd(), + process.env.LOCAL_DIR ?? 'src', + ); + const maintenanceLockFile = join( documentRoot, '.maintenance' ); + + test.beforeAll( async () => { + writeFileSync( maintenanceLockFile, '' ); // Year 2286. + } ); + + test.afterAll( async () => { + unlinkSync( maintenanceLockFile ); + } ); + + test( 'should display maintenance mode page', async ( { page } ) => { + await page.goto( '/' ); + await expect( + page.getByText( /Briefly unavailable for scheduled maintenance\. Check back in a minute\./ ) + ).toBeVisible(); + } ); +} ); diff --git a/tests/phpunit/data/blocks/notice/variations.php b/tests/phpunit/data/blocks/notice/variations.php new file mode 100644 index 0000000000000..bed66d9544176 --- /dev/null +++ b/tests/phpunit/data/blocks/notice/variations.php @@ -0,0 +1,10 @@ + 'warning', + 'title' => 'warning', + 'description' => 'Shows warning.', + 'keywords' => array( 'warning' ), + ), +); diff --git a/tests/phpunit/data/images/test-image.heic b/tests/phpunit/data/images/test-image.heic new file mode 100644 index 0000000000000..cb104e1d53e42 Binary files /dev/null and b/tests/phpunit/data/images/test-image.heic differ diff --git a/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json index a471d8f326a4a..fee9382dce147 100644 --- a/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json +++ b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json @@ -1,4 +1,55 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 3 + "version": 3, + "styles": { + "variations": { + "outline": { + "color": { + "background": "green", + "text": "white" + } + }, + "block-style-variation-a": { + "color": { + "background": "darkseagreen" + }, + "typography": { + "fontSize": "2em", + "lineHeight": "1.4em" + } + } + }, + "blocks": { + "core/button": { + "variations": { + "outline": { + "color": { + "background": "red" + } + } + } + }, + "core/media-text": { + "variations": { + "block-style-variation-a": { + "color": { + "background": "blue" + }, + "typography": { + "fontSize": "1.5em" + } + } + } + }, + "core/heading": { + "variations": { + "block-style-variation-b": { + "typography": { + "fontSize": "3em" + } + } + } + } + } + } } diff --git a/tests/phpunit/includes/testcase-rest-post-type-controller.php b/tests/phpunit/includes/testcase-rest-post-type-controller.php index 9b1d366315bc4..75326284e6e5d 100644 --- a/tests/phpunit/includes/testcase-rest-post-type-controller.php +++ b/tests/phpunit/includes/testcase-rest-post-type-controller.php @@ -107,8 +107,10 @@ protected function check_post_data( $post, $data, $context, $links ) { // Check filtered values. if ( post_type_supports( $post->post_type, 'title' ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); $this->assertSame( get_the_title( $post->ID ), $data['title']['rendered'] ); remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); if ( 'edit' === $context ) { $this->assertSame( $post->post_title, $data['title']['raw'] ); } else { diff --git a/tests/phpunit/tests/admin/includesPlugin.php b/tests/phpunit/tests/admin/includesPlugin.php index 0d29b81d028e0..e95697810d43f 100644 --- a/tests/phpunit/tests/admin/includesPlugin.php +++ b/tests/phpunit/tests/admin/includesPlugin.php @@ -96,7 +96,7 @@ public function test_submenu_position( $position, $expected_position ) { wp_set_current_user( $current_user ); // Clean up the temporary user. - wp_delete_user( $admin_user ); + self::delete_user( $admin_user ); // Verify the menu was inserted at the expected position. $this->assertSame( 'custom-position', $submenu[ $parent ][ $expected_position ][2] ); @@ -204,7 +204,7 @@ public function test_submenu_helpers_position( $position, $expected_position ) { } // Clean up the temporary user. - wp_delete_user( $admin_user ); + self::delete_user( $admin_user ); foreach ( $actual_positions as $test => $actual_position ) { // Verify the menu was inserted at the expected position. @@ -295,7 +295,7 @@ public function test_position_when_parent_slug_child_slug_are_the_same() { // Clean up the temporary user. wp_set_current_user( $current_user ); - wp_delete_user( $admin_user ); + self::delete_user( $admin_user ); // Verify the menu was inserted at the expected position. $this->assertSame( 'main_slug', $submenu['main_slug'][0][2] ); @@ -326,7 +326,7 @@ public function test_passing_string_as_position_fires_doing_it_wrong_submenu() { // Clean up the temporary user. wp_set_current_user( $current_user ); - wp_delete_user( $admin_user ); + self::delete_user( $admin_user ); // Verify the menu was inserted at the expected position. $this->assertSame( 'submenu_page_1', $submenu['main_slug'][1][2] ); @@ -355,7 +355,7 @@ public function test_passing_float_as_position_does_not_override_int() { // Clean up the temporary user. wp_set_current_user( $current_user ); - wp_delete_user( $admin_user ); + self::delete_user( $admin_user ); // Verify the menus were inserted. $this->assertSame( 'main_slug_1', $menu[1][2] ); diff --git a/tests/phpunit/tests/admin/includesSchema.php b/tests/phpunit/tests/admin/includesSchema.php index 77b3e06a53ed6..9f2d19f0c16aa 100644 --- a/tests/phpunit/tests/admin/includesSchema.php +++ b/tests/phpunit/tests/admin/includesSchema.php @@ -123,7 +123,7 @@ public function data_populate_options() { 'rss_use_excerpt' => '0', 'mailserver_url' => 'mail.example.com', 'mailserver_login' => 'login@example.com', - 'mailserver_pass' => 'password', + 'mailserver_pass' => '', ), ), array( @@ -137,7 +137,7 @@ public function data_populate_options() { 'rss_use_excerpt' => '1', 'mailserver_url' => 'mail.example.com', 'mailserver_login' => 'login@example.com', - 'mailserver_pass' => 'password', + 'mailserver_pass' => '', ), ), array( @@ -151,7 +151,7 @@ public function data_populate_options() { 'rss_use_excerpt' => '0', 'mailserver_url' => 'mail.example.com', 'mailserver_login' => 'login@example.com', - 'mailserver_pass' => 'password', + 'mailserver_pass' => '', ), ), array( diff --git a/tests/phpunit/tests/admin/wpSiteHealth.php b/tests/phpunit/tests/admin/wpSiteHealth.php index 52afdb49d38f6..4f3ac99cf2489 100644 --- a/tests/phpunit/tests/admin/wpSiteHealth.php +++ b/tests/phpunit/tests/admin/wpSiteHealth.php @@ -38,9 +38,6 @@ public function set_up() { * @covers ::__construct() */ public function test_mysql_recommended_version_matches_readme_html() { - // This test is designed to only run on trunk. - $this->skipOnAutomatedBranches(); - $reflection = new ReflectionClass( $this->instance ); $reflection_property = $reflection->getProperty( 'mysql_recommended_version' ); $reflection_property->setAccessible( true ); @@ -57,9 +54,6 @@ public function test_mysql_recommended_version_matches_readme_html() { * @covers ::__construct() */ public function test_mariadb_recommended_version_matches_readme_html() { - // This test is designed to only run on trunk. - $this->skipOnAutomatedBranches(); - $reflection = new ReflectionClass( $this->instance ); $reflection_property = $reflection->getProperty( 'mariadb_recommended_version' ); $reflection_property->setAccessible( true ); diff --git a/tests/phpunit/tests/auth.php b/tests/phpunit/tests/auth.php index a28051dc09e2d..5c196b499fcce 100644 --- a/tests/phpunit/tests/auth.php +++ b/tests/phpunit/tests/auth.php @@ -115,16 +115,22 @@ public function test_password_trimming() { * Tests hooking into wp_set_password(). * * @ticket 57436 + * @ticket 61541 * * @covers ::wp_set_password */ public function test_wp_set_password_action() { $action = new MockAction(); - add_action( 'wp_set_password', array( $action, 'action' ) ); - wp_set_password( 'A simple password', self::$user_id ); + $previous_user_pass = get_user_by( 'id', $this->user->ID )->user_pass; + + add_action( 'wp_set_password', array( $action, 'action' ), 10, 3 ); + wp_set_password( 'A simple password', $this->user->ID ); $this->assertSame( 1, $action->get_call_count() ); + + // Check that the old data passed through the hook is correct. + $this->assertSame( $previous_user_pass, $action->get_args()[0][2]->user_pass ); } /** diff --git a/tests/phpunit/tests/avatar.php b/tests/phpunit/tests/avatar.php index 32ff9ac648f8d..97f24841e372b 100644 --- a/tests/phpunit/tests/avatar.php +++ b/tests/phpunit/tests/avatar.php @@ -11,7 +11,7 @@ class Tests_Avatar extends WP_UnitTestCase { */ public function test_get_avatar_url_gravatar_url() { $url = get_avatar_url( 1 ); - $this->assertSame( preg_match( '|^http?://[0-9]+.gravatar.com/avatar/[0-9a-f]{32}\?|', $url ), 1 ); + $this->assertSame( preg_match( '|^https?://secure.gravatar.com/avatar/[0-9a-f]{32}\?|', $url ), 1 ); } /** @@ -56,19 +56,29 @@ public function test_get_avatar_url_rating() { } /** + * Ensures the get_avatar_url always returns an HTTPS scheme for gravatars. + * * @ticket 21195 + * @ticket 37454 + * + * @covers ::get_avatar_url */ public function test_get_avatar_url_scheme() { $url = get_avatar_url( 1 ); - $this->assertSame( preg_match( '|^http://|', $url ), 1 ); + $this->assertSame( preg_match( '|^https://|', $url ), 1, 'Avatars should default to the HTTPS scheme' ); $args = array( 'scheme' => 'https' ); $url = get_avatar_url( 1, $args ); - $this->assertSame( preg_match( '|^https://|', $url ), 1 ); + $this->assertSame( preg_match( '|^https://|', $url ), 1, 'Requesting the HTTPS scheme should be respected' ); + + $args = array( 'scheme' => 'http' ); + $url = get_avatar_url( 1, $args ); + $this->assertSame( preg_match( '|^https://|', $url ), 1, 'Requesting the HTTP scheme should return an HTTPS URL to avoid redirects' ); $args = array( 'scheme' => 'lolcat' ); $url = get_avatar_url( 1, $args ); - $this->assertSame( preg_match( '|^lolcat://|', $url ), 0 ); + $this->assertSame( preg_match( '|^lolcat://|', $url ), 0, 'Unrecognized schemes should be ignored' ); + $this->assertSame( preg_match( '|^https://|', $url ), 1, 'Unrecognized schemes should return an HTTPS URL' ); } /** @@ -257,7 +267,7 @@ public function test_get_avatar_data_should_return_gravatar_url_when_input_avata $actual_data = get_avatar_data( $comment ); $this->assertTrue( is_avatar_comment_type( $comment_type ) ); - $this->assertMatchesRegularExpression( '|^http?://[0-9]+.gravatar.com/avatar/[0-9a-f]{32}\?|', $actual_data['url'] ); + $this->assertMatchesRegularExpression( '|^https?://secure.gravatar.com/avatar/[0-9a-f]{32}\?|', $actual_data['url'] ); } /** diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php index fc5b91a9d702a..e4aa415e9af96 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php @@ -380,6 +380,8 @@ public function test_merging_uses_context_from_multiple_sources() { ); $new_uses_context = $block_registry->get_registered( 'core/paragraph' )->uses_context; + unregister_block_bindings_source( 'test/source-one' ); + unregister_block_bindings_source( 'test/source-two' ); // Checks that the resulting `uses_context` contains the values from both sources. $this->assertContains( 'commonContext', $new_uses_context ); $this->assertContains( 'sourceOneContext', $new_uses_context ); diff --git a/tests/phpunit/tests/block-supports/block-style-variations.php b/tests/phpunit/tests/block-supports/block-style-variations.php index 6b50b87fa2abc..fe43f5893c812 100644 --- a/tests/phpunit/tests/block-supports/block-style-variations.php +++ b/tests/phpunit/tests/block-supports/block-style-variations.php @@ -62,17 +62,11 @@ public function filter_set_theme_root() { * * @ticket 61312 * @ticket 61440 + * @ticket 61451 */ public function test_add_registered_block_styles_to_theme_data() { switch_theme( 'block-theme' ); - /* - * Trigger block style registration that occurs on `init` action. - * do_action( 'init' ) could be used here however this direct call - * means only the updates being tested are performed. - */ - wp_register_block_style_variations_from_theme(); - $variation_styles_data = array( 'color' => array( 'background' => 'darkslateblue', @@ -127,14 +121,6 @@ public function test_add_registered_block_styles_to_theme_data() { $group_styles = $theme_json['styles']['blocks']['core/group'] ?? array(); $expected = array( 'variations' => array( - // @ticket 61440 - 'WithSlug' => array( - 'color' => array( - 'background' => 'aliceblue', - 'text' => 'midnightblue', - ), - ), - 'my-variation' => $variation_styles_data, /* * The following block style variations are registered @@ -153,12 +139,98 @@ public function test_add_registered_block_styles_to_theme_data() { 'text' => 'lightblue', ), ), + + /* + * Manually registered variations. + * @ticket 61440 + */ + 'WithSlug' => array( + 'color' => array( + 'background' => 'aliceblue', + 'text' => 'midnightblue', + ), + ), + 'my-variation' => $variation_styles_data, ), ); unregister_block_style( 'core/group', 'my-variation' ); unregister_block_style( 'core/group', 'WithSlug' ); - $this->assertSameSetsWithIndex( $expected, $group_styles ); + $this->assertSameSetsWithIndex( $expected, $group_styles, 'Variation data does not match' ); + } + + /** + * Tests that block style variations resolve any `ref` values when generating styles. + * + * @ticket 61589 + */ + public function test_block_style_variation_ref_values() { + switch_theme( 'block-theme' ); + + $variation_data = array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.does-not-exist', + ), + 'background' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-a.color.text', + ), + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-a.color.background', + ), + 'background' => array( + 'ref' => '', + ), + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-b.color.text', + ), + 'background' => array( + 'ref' => null, + ), + ), + ':hover' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-b.color.background', + ), + ), + ), + ), + ), + ); + + $theme_json = WP_Theme_JSON_Resolver::get_theme_data()->get_raw_data(); + + wp_resolve_block_style_variation_ref_values( $variation_data, $theme_json ); + + $expected = array( + 'color' => array( 'background' => 'plum' ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( 'text' => 'indigo' ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( 'text' => 'lightblue' ), + ':hover' => array( + 'color' => array( 'text' => 'midnightblue' ), + ), + ), + ), + ); + + $this->assertSameSetsWithIndex( $expected, $variation_data, 'Variation data with resolved ref values does not match' ); } } diff --git a/tests/phpunit/tests/block-supports/typography.php b/tests/phpunit/tests/block-supports/typography.php index 477796475ccff..59c47516caa2d 100644 --- a/tests/phpunit/tests/block-supports/typography.php +++ b/tests/phpunit/tests/block-supports/typography.php @@ -295,6 +295,7 @@ public function test_should_generate_classname_for_font_family() { * @ticket 57065 * @ticket 58523 * @ticket 61118 + * @ticket 61932 * * @covers ::wp_get_typography_font_size_value * @@ -359,7 +360,11 @@ public function data_generate_font_size_preset_fixtures() { 'font_size_preset' => array( 'size' => null, ), - 'settings' => null, + 'settings' => array( + 'typography' => array( + 'fluid' => true, + ), + ), 'expected_output' => null, ), @@ -429,8 +434,7 @@ public function data_generate_font_size_preset_fixtures() { ), 'returns already clamped value' => array( 'font_size_preset' => array( - 'size' => 'clamp(21px, 1.313rem + ((1vw - 7.68px) * 2.524), 42px)', - 'fluid' => false, + 'size' => 'clamp(21px, 1.313rem + ((1vw - 7.68px) * 2.524), 42px)', ), 'settings' => array( 'typography' => array( @@ -442,8 +446,7 @@ public function data_generate_font_size_preset_fixtures() { 'returns value with unsupported unit' => array( 'font_size_preset' => array( - 'size' => '1000%', - 'fluid' => false, + 'size' => '1000%', ), 'settings' => array( 'typography' => array( @@ -773,6 +776,33 @@ public function data_generate_font_size_preset_fixtures() { ), 'expected_output' => 'clamp(100px, 6.25rem + ((1vw - 3.2px) * 7.813), 200px)', ), + + // Individual preset settings override global settings. + 'should convert individual preset size to fluid if fluid is disabled in global settings' => array( + 'font_size' => array( + 'size' => '17px', + 'fluid' => true, + ), + 'settings' => array( + 'typography' => array(), + ), + 'expected_output' => 'clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.234), 17px)', + ), + 'should use individual preset settings if fluid is disabled in global settings' => array( + 'font_size' => array( + 'size' => '17px', + 'fluid' => array( + 'min' => '16px', + 'max' => '26px', + ), + ), + 'settings' => array( + 'typography' => array( + 'fluid' => false, + ), + ), + 'expected_output' => 'clamp(16px, 1rem + ((1vw - 3.2px) * 0.781), 26px)', + ), ); } diff --git a/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationInstanceName.php b/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationInstanceName.php index 1e37d48e8ed1d..10475587287a8 100644 --- a/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationInstanceName.php +++ b/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationInstanceName.php @@ -13,6 +13,8 @@ class Tests_Block_Supports_WpCreateBlockStyleVariationInstanceName extends WP_Un * @ticket 61312 * * @covers ::wp_create_block_style_variation_instance_name + * + * @expectedDeprecated wp_create_block_style_variation_instance_name */ public function test_block_style_variation_instance_name_generation() { $block = array( 'name' => 'test/block' ); diff --git a/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php b/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php index 3fa7da28908a3..4de405eec883d 100644 --- a/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php @@ -69,6 +69,8 @@ public function filter_set_theme_root() { * @ticket 59357 * @ticket 60175 * @ticket 61123 + * @ticket 61720 + * @ticket 61858 * * @covers ::wp_render_background_support * @@ -139,20 +141,21 @@ public function data_background_block_support() { 'expected_wrapper' => '
Content
', 'wrapper' => '
Content
', ), - 'background image style with contain, position, and repeat is applied' => array( + 'background image style with contain, position, attachment, and repeat is applied' => array( 'theme_name' => 'block-theme-child-with-fluid-typography', 'block_name' => 'test/background-rules-are-output', 'background_settings' => array( 'backgroundImage' => true, ), 'background_style' => array( - 'backgroundImage' => array( + 'backgroundImage' => array( 'url' => 'https://example.com/image.jpg', ), - 'backgroundRepeat' => 'no-repeat', - 'backgroundSize' => 'contain', + 'backgroundRepeat' => 'no-repeat', + 'backgroundSize' => 'contain', + 'backgroundAttachment' => 'fixed', ), - 'expected_wrapper' => '
Content
', + 'expected_wrapper' => '
Content
', 'wrapper' => '
Content
', ), 'background image style is appended if a style attribute already exists' => array( diff --git a/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromFile.php b/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromFile.php index 3aedcf0c2c7b8..673e35af5b097 100644 --- a/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromFile.php +++ b/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromFile.php @@ -7,6 +7,20 @@ * @covers ::_build_block_template_result_from_file */ class Tests_Block_Templates_BuildBlockTemplateResultFromFile extends WP_Block_Templates_UnitTestCase { + /** + * Tear down each test method. + * + * @since 6.7.0 + */ + public function tear_down() { + $registry = WP_Block_Type_Registry::get_instance(); + + if ( $registry->is_registered( 'tests/my-block' ) ) { + $registry->unregister( 'tests/my-block' ); + } + + parent::tear_down(); + } /** * @ticket 54335 @@ -178,4 +192,78 @@ public function test_should_ignore_post_types_property_when_building_template_pa $this->assertEmpty( $template->post_types ); } + + /** + * @ticket 60506 + */ + public function test_should_inject_hooked_block_into_template_part() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/paragraph' => 'after', + ), + ) + ); + + $template_part = _build_block_template_result_from_file( + array( + 'slug' => 'header', + 'postTypes' => array( 'post' ), + 'path' => DIR_TESTDATA . '/templates/template.html', + ), + 'wp_template_part' + ); + $this->assertStringEndsWith( '', $template_part->content ); + } + + /** + * @ticket 60506 + * @ticket 60854 + */ + public function test_should_injected_hooked_block_into_template_part_first_child() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/template-part' => 'first_child', + ), + ) + ); + + $template_part = _build_block_template_result_from_file( + array( + 'slug' => 'header', + 'postTypes' => array( 'post' ), + 'path' => DIR_TESTDATA . '/templates/template.html', + ), + 'wp_template_part' + ); + $this->assertStringStartsWith( '', $template_part->content ); + } + + /** + * @ticket 60506 + * @ticket 60854 + */ + public function test_should_injected_hooked_block_into_template_part_last_child() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/template-part' => 'last_child', + ), + ) + ); + + $template_part = _build_block_template_result_from_file( + array( + 'slug' => 'header', + 'postTypes' => array( 'post' ), + 'path' => DIR_TESTDATA . '/templates/template.html', + ), + 'wp_template_part' + ); + $this->assertStringEndsWith( '', $template_part->content ); + } } diff --git a/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromPost.php b/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromPost.php index 586e9beded17b..5dbf91d911f07 100644 --- a/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromPost.php +++ b/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromPost.php @@ -111,6 +111,50 @@ public function test_should_inject_hooked_block_into_template_part() { $this->assertStringEndsWith( '', $template_part->content ); } + /** + * @ticket 59646 + * @ticket 60506 + * @ticket 60854 + */ + public function test_should_injected_hooked_block_into_template_part_first_child() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/template-part' => 'first_child', + ), + ) + ); + + $template_part = _build_block_template_result_from_post( + self::$template_part_post, + 'wp_template_part' + ); + $this->assertStringStartsWith( '', $template_part->content ); + } + + /** + * @ticket 59646 + * @ticket 60506 + * @ticket 60854 + */ + public function test_should_injected_hooked_block_into_template_part_last_child() { + register_block_type( + 'tests/my-block', + array( + 'block_hooks' => array( + 'core/template-part' => 'last_child', + ), + ) + ); + + $template_part = _build_block_template_result_from_post( + self::$template_part_post, + 'wp_template_part' + ); + $this->assertStringEndsWith( '', $template_part->content ); + } + /** * @ticket 59646 * @ticket 60506 diff --git a/tests/phpunit/tests/block-templates/injectIgnoredHookedBlocksMetadataAttributes.php b/tests/phpunit/tests/block-templates/injectIgnoredHookedBlocksMetadataAttributes.php index bc753504bbce0..e9c03e00d9daf 100644 --- a/tests/phpunit/tests/block-templates/injectIgnoredHookedBlocksMetadataAttributes.php +++ b/tests/phpunit/tests/block-templates/injectIgnoredHookedBlocksMetadataAttributes.php @@ -17,6 +17,7 @@ public function tear_down() { if ( WP_Block_Type_Registry::get_instance()->is_registered( 'tests/hooked-block' ) ) { unregister_block_type( 'tests/hooked-block' ); } + delete_post_meta( self::$template_part_post->ID, '_wp_ignored_hooked_blocks' ); parent::tear_down(); } @@ -38,14 +39,35 @@ public function test_hooked_block_types_filter_with_newly_created_template() { inject_ignored_hooked_blocks_metadata_attributes( $changes ); - $args = $action->get_args(); - $anchor_block_type = end( $args )[2]; - $context = end( $args )[3]; + $args = $action->get_args(); + $relative_positions = array_column( $args, 1 ); + $anchor_block_types = array_column( $args, 2 ); + $contexts = array_column( $args, 3 ); - $this->assertSame( 'tests/anchor-block', $anchor_block_type ); + $this->assertSame( + array( + 'before', + 'after', + ), + $relative_positions, + 'The relative positions passed to the hooked_block_types filter are incorrect.' + ); - $this->assertInstanceOf( 'WP_Block_Template', $context ); + $this->assertSame( + array( + 'tests/anchor-block', + 'tests/anchor-block', + ), + $anchor_block_types, + 'The anchor block types passed to the hooked_block_types filter are incorrect.' + ); + $context = $contexts[0]; + $this->assertSame( + array_fill( 0, count( $contexts ), $context ), + $contexts, + 'The context passed to the hooked_block_types filter should be the same for all calls.' + ); $this->assertSame( $changes->post_type, $context->type, @@ -69,6 +91,7 @@ public function test_hooked_block_types_filter_with_newly_created_template() { /** * @ticket 60754 + * @ticket 60854 */ public function test_hooked_block_types_filter_with_newly_created_template_part() { $action = new MockAction(); @@ -85,14 +108,48 @@ public function test_hooked_block_types_filter_with_newly_created_template_part( inject_ignored_hooked_blocks_metadata_attributes( $changes ); - $args = $action->get_args(); - $anchor_block_type = end( $args )[2]; - $context = end( $args )[3]; + $args = $action->get_args(); + $relative_positions = array_column( $args, 1 ); + $anchor_block_types = array_column( $args, 2 ); + $contexts = array_column( $args, 3 ); - $this->assertSame( 'tests/anchor-block', $anchor_block_type ); + $this->assertSame( + array( + 'before', + 'after', + 'first_child', + 'before', + 'after', + 'last_child', + ), + $relative_positions, + 'The relative positions passed to the hooked_block_types filter are incorrect.' + ); - $this->assertInstanceOf( 'WP_Block_Template', $context ); + $this->assertSame( + array( + 'core/template-part', + 'core/template-part', + 'core/template-part', + 'tests/anchor-block', + 'tests/anchor-block', + 'core/template-part', + ), + $anchor_block_types, + 'The anchor block types passed to the hooked_block_types filter are incorrect.' + ); + $context = $contexts[0]; + $this->assertSame( + array_fill( 0, count( $contexts ), $context ), + $contexts, + 'The context passed to the hooked_block_types filter should be the same for all calls.' + ); + $this->assertInstanceOf( + 'WP_Block_Template', + $context, + 'The context passed to the hooked_block_types filter is not an instance of WP_Block_Template.' + ); $this->assertSame( $changes->post_type, $context->type, @@ -140,14 +197,35 @@ public function test_hooked_block_types_filter_with_existing_template_file() { inject_ignored_hooked_blocks_metadata_attributes( $changes ); - $args = $action->get_args(); - $anchor_block_type = end( $args )[2]; - $context = end( $args )[3]; + $args = $action->get_args(); + $relative_positions = array_column( $args, 1 ); + $anchor_block_types = array_column( $args, 2 ); + $contexts = array_column( $args, 3 ); - $this->assertSame( 'tests/anchor-block', $anchor_block_type ); + $this->assertSame( + array( + 'before', + 'after', + ), + $relative_positions, + 'The relative positions passed to the hooked_block_types filter are incorrect.' + ); - $this->assertInstanceOf( 'WP_Block_Template', $context ); + $this->assertSame( + array( + 'tests/anchor-block', + 'tests/anchor-block', + ), + $anchor_block_types, + 'The anchor block types passed to the hooked_block_types filter are incorrect.' + ); + $context = $contexts[0]; + $this->assertSame( + array_fill( 0, count( $contexts ), $context ), + $contexts, + 'The context passed to the hooked_block_types filter should be the same for all calls.' + ); $this->assertSame( $changes->post_name, $context->slug, @@ -181,6 +259,7 @@ public function test_hooked_block_types_filter_with_existing_template_file() { /** * @ticket 60754 + * @ticket 60854 */ public function test_hooked_block_types_filter_with_existing_template_part_file() { $action = new MockAction(); @@ -201,14 +280,48 @@ public function test_hooked_block_types_filter_with_existing_template_part_file( inject_ignored_hooked_blocks_metadata_attributes( $changes ); - $args = $action->get_args(); - $anchor_block_type = end( $args )[2]; - $context = end( $args )[3]; + $args = $action->get_args(); + $relative_positions = array_column( $args, 1 ); + $anchor_block_types = array_column( $args, 2 ); + $contexts = array_column( $args, 3 ); - $this->assertSame( 'tests/anchor-block', $anchor_block_type ); + $this->assertSame( + array( + 'before', + 'after', + 'first_child', + 'before', + 'after', + 'last_child', + ), + $relative_positions, + 'The relative positions passed to the hooked_block_types filter are incorrect.' + ); - $this->assertInstanceOf( 'WP_Block_Template', $context ); + $this->assertSame( + array( + 'core/template-part', + 'core/template-part', + 'core/template-part', + 'tests/anchor-block', + 'tests/anchor-block', + 'core/template-part', + ), + $anchor_block_types, + 'The anchor block types passed to the hooked_block_types filter are incorrect.' + ); + $context = $contexts[0]; + $this->assertSame( + array_fill( 0, count( $contexts ), $context ), + $contexts, + 'The context passed to the hooked_block_types filter should be the same for all calls.' + ); + $this->assertInstanceOf( + 'WP_Block_Template', + $context, + 'The context passed to the hooked_block_types filter is not an instance of WP_Block_Template.' + ); $this->assertSame( $changes->post_name, $context->slug, @@ -259,14 +372,35 @@ public function test_hooked_block_types_filter_with_existing_template_post() { inject_ignored_hooked_blocks_metadata_attributes( $changes ); - $args = $action->get_args(); - $anchor_block_type = end( $args )[2]; - $context = end( $args )[3]; + $args = $action->get_args(); + $relative_positions = array_column( $args, 1 ); + $anchor_block_types = array_column( $args, 2 ); + $contexts = array_column( $args, 3 ); - $this->assertSame( 'tests/anchor-block', $anchor_block_type ); + $this->assertSame( + array( + 'before', + 'after', + ), + $relative_positions, + 'The relative positions passed to the hooked_block_types filter are incorrect.' + ); - $this->assertInstanceOf( 'WP_Block_Template', $context ); + $this->assertSame( + array( + 'tests/anchor-block', + 'tests/anchor-block', + ), + $anchor_block_types, + 'The anchor block types passed to the hooked_block_types filter are incorrect.' + ); + $context = $contexts[0]; + $this->assertSame( + array_fill( 0, count( $contexts ), $context ), + $contexts, + 'The context passed to the hooked_block_types filter should be the same for all calls.' + ); $this->assertSame( $changes->post_name, $context->slug, @@ -302,6 +436,7 @@ public function test_hooked_block_types_filter_with_existing_template_post() { /** * @ticket 60754 + * @ticket 60854 */ public function test_hooked_block_types_filter_with_existing_template_part_post() { $action = new MockAction(); @@ -318,14 +453,48 @@ public function test_hooked_block_types_filter_with_existing_template_part_post( inject_ignored_hooked_blocks_metadata_attributes( $changes ); - $args = $action->get_args(); - $anchor_block_type = end( $args )[2]; - $context = end( $args )[3]; + $args = $action->get_args(); + $relative_positions = array_column( $args, 1 ); + $anchor_block_types = array_column( $args, 2 ); + $contexts = array_column( $args, 3 ); - $this->assertSame( 'tests/anchor-block', $anchor_block_type ); + $this->assertSame( + array( + 'before', + 'after', + 'first_child', + 'before', + 'after', + 'last_child', + ), + $relative_positions, + 'The relative positions passed to the hooked_block_types filter are incorrect.' + ); - $this->assertInstanceOf( 'WP_Block_Template', $context ); + $this->assertSame( + array( + 'core/template-part', + 'core/template-part', + 'core/template-part', + 'tests/anchor-block', + 'tests/anchor-block', + 'core/template-part', + ), + $anchor_block_types, + 'The anchor block types passed to the hooked_block_types filter are incorrect.' + ); + $context = $contexts[0]; + $this->assertSame( + array_fill( 0, count( $contexts ), $context ), + $contexts, + 'The context passed to the hooked_block_types filter should be the same for all calls.' + ); + $this->assertInstanceOf( + 'WP_Block_Template', + $context, + 'The context passed to the hooked_block_types filter is not an instance of WP_Block_Template.' + ); $this->assertSame( $changes->post_name, $context->slug, @@ -419,4 +588,92 @@ public function test_inject_ignored_hooked_blocks_metadata_attributes_into_templ 'The hooked block was not injected into the anchor block\'s ignoredHookedBlocks metadata.' ); } + + /** + * @ticket 60854 + */ + public function test_inject_ignored_hooked_blocks_metadata_attributes_into_template_part_postmeta() { + register_block_type( + 'tests/hooked-block', + array( + 'block_hooks' => array( + 'core/template-part' => 'last_child', + ), + ) + ); + + $id = self::TEST_THEME . '//' . 'my_template_part'; + $template = get_block_template( $id, 'wp_template_part' ); + + $changes = new stdClass(); + $changes->ID = $template->wp_id; + $changes->post_content = 'Hello'; + + $post = inject_ignored_hooked_blocks_metadata_attributes( $changes ); + $this->assertSame( + array( 'tests/hooked-block' ), + json_decode( $post->meta_input['_wp_ignored_hooked_blocks'], true ), + 'The hooked block was not injected into the wp_template_part\'s _wp_ignored_hooked_blocks postmeta.' + ); + $this->assertSame( + $changes->post_content, + $post->post_content, + 'The template part\'s post content was modified.' + ); + } + + /** + * @ticket 61550 + */ + public function test_inject_ignored_hooked_blocks_metadata_attributes_into_template_with_no_changes_to_post_content() { + register_block_type( + 'tests/hooked-block', + array( + 'block_hooks' => array( + 'core/heading' => 'after', + ), + ) + ); + + $id = self::TEST_THEME . '//' . 'my_template'; + $template = get_block_template( $id, 'wp_template' ); + + $changes = new stdClass(); + $changes->ID = $template->wp_id; + + // Note that we're not setting `$changes->post_content`! + + $post = inject_ignored_hooked_blocks_metadata_attributes( $changes ); + $this->assertFalse( + isset( $post->post_content ), + "post_content shouldn't have been set." + ); + } + + /** + * @ticket 61550 + */ + public function test_inject_ignored_hooked_blocks_metadata_attributes_into_template_part_with_no_changes_to_post_content() { + register_block_type( + 'tests/hooked-block', + array( + 'block_hooks' => array( + 'core/heading' => 'after', + ), + ) + ); + + $id = self::TEST_THEME . '//' . 'my_template_part'; + $template = get_block_template( $id, 'wp_template_part' ); + + $changes = new stdClass(); + $changes->ID = $template->wp_id; + // Note that we're not setting `$changes->post_content`! + + $post = inject_ignored_hooked_blocks_metadata_attributes( $changes ); + $this->assertFalse( + isset( $post->post_content ), + "post_content shouldn't have been set." + ); + } } diff --git a/tests/phpunit/tests/blocks/editor.php b/tests/phpunit/tests/blocks/editor.php index 0682839605da9..b10dbb93dae01 100644 --- a/tests/phpunit/tests/blocks/editor.php +++ b/tests/phpunit/tests/blocks/editor.php @@ -720,4 +720,38 @@ public function data_block_editor_rest_api_preload_adds_missing_leading_slash() ), ); } + + /** + * @ticket 61641 + */ + public function test_get_block_editor_settings_block_bindings_sources() { + $block_editor_context = new WP_Block_Editor_Context(); + register_block_bindings_source( + 'test/source-one', + array( + 'label' => 'Source One', + 'get_value_callback' => function () {}, + 'uses_context' => array( 'postId' ), + ) + ); + register_block_bindings_source( + 'test/source-two', + array( + 'label' => 'Source Two', + 'get_value_callback' => function () {}, + ) + ); + $settings = get_block_editor_settings( array(), $block_editor_context ); + $exposed_sources = $settings['blockBindingsSources']; + unregister_block_bindings_source( 'test/source-one' ); + unregister_block_bindings_source( 'test/source-two' ); + // It is expected to have 4 sources: the 2 registered sources in the test, and the 2 core sources. + $this->assertCount( 4, $exposed_sources ); + $source_one = $exposed_sources['test/source-one']; + $this->assertSame( 'Source One', $source_one['label'] ); + $this->assertSameSets( array( 'postId' ), $source_one['usesContext'] ); + $source_two = $exposed_sources['test/source-two']; + $this->assertSame( 'Source Two', $source_two['label'] ); + $this->assertArrayNotHasKey( 'usesContext', $source_two ); + } } diff --git a/tests/phpunit/tests/blocks/register.php b/tests/phpunit/tests/blocks/register.php index 1dbc688bb16cf..7e0c391e1f226 100644 --- a/tests/phpunit/tests/blocks/register.php +++ b/tests/phpunit/tests/blocks/register.php @@ -957,6 +957,37 @@ public function data_register_block_registers_with_args_override_returns_false_w ); } + /** + * Tests registering a block with variations from a PHP file. + * + * @ticket 61280 + * + * @covers ::register_block_type_from_metadata + */ + public function test_register_block_type_from_metadata_with_variations_php_file() { + $filter_metadata_registration = static function ( $metadata ) { + $metadata['variations'] = 'file:./variations.php'; + return $metadata; + }; + + add_filter( 'block_type_metadata', $filter_metadata_registration, 10, 2 ); + $result = register_block_type_from_metadata( + DIR_TESTDATA . '/blocks/notice' + ); + remove_filter( 'block_type_metadata', $filter_metadata_registration ); + + $this->assertInstanceOf( 'WP_Block_Type', $result, 'The block was not registered' ); + + $this->assertIsCallable( $result->variation_callback, 'The variation callback hasn\'t been set' ); + $expected_variations = require DIR_TESTDATA . '/blocks/notice/variations.php'; + $this->assertSame( + $expected_variations, + call_user_func( $result->variation_callback ), + 'The variation callback hasn\'t been set correctly' + ); + $this->assertSame( $expected_variations, $result->variations, 'The block variations are incorrect' ); + } + /** * Tests that the function returns the registered block when the `block.json` * is found in the fixtures directory. diff --git a/tests/phpunit/tests/blocks/updateIgnoredHookedBlocksPostMeta.php b/tests/phpunit/tests/blocks/updateIgnoredHookedBlocksPostMeta.php index 6f60243630002..7b0a830dd52dd 100644 --- a/tests/phpunit/tests/blocks/updateIgnoredHookedBlocksPostMeta.php +++ b/tests/phpunit/tests/blocks/updateIgnoredHookedBlocksPostMeta.php @@ -75,7 +75,7 @@ public function test_update_ignored_hooked_blocks_postmeta_preserves_entities() ); $this->assertSame( array( 'tests/my-block' ), - json_decode( get_post_meta( self::$navigation_post->ID, '_wp_ignored_hooked_blocks', true ), true ), + json_decode( $post->meta_input['_wp_ignored_hooked_blocks'], true ), 'Block was not added to ignored hooked blocks metadata.' ); } diff --git a/tests/phpunit/tests/comment/getCommentAuthor.php b/tests/phpunit/tests/comment/getCommentAuthor.php index 31d5a57bdbe7e..61e8c213b54b9 100644 --- a/tests/phpunit/tests/comment/getCommentAuthor.php +++ b/tests/phpunit/tests/comment/getCommentAuthor.php @@ -22,7 +22,7 @@ public static function set_up_before_class() { public function get_comment_author_filter( $comment_author, $comment_id, $comment ) { $this->assertSame( $comment_id, self::$comment->comment_ID, 'Comment IDs do not match.' ); - $this->assertTrue( is_string( $comment_id ), '$comment_id parameter is not a string.' ); + $this->assertIsString( $comment_id, '$comment_id parameter is not a string.' ); return $comment_author; } @@ -41,7 +41,7 @@ public function test_comment_author_passes_correct_comment_id_for_int() { public function get_comment_author_filter_non_existent_id( $comment_author, $comment_id, $comment ) { $this->assertSame( $comment_id, (string) self::$non_existent_comment_id, 'Comment IDs do not match.' ); - $this->assertTrue( is_string( $comment_id ), '$comment_id parameter is not a string.' ); + $this->assertIsString( $comment_id, '$comment_id parameter is not a string.' ); return $comment_author; } @@ -56,4 +56,60 @@ public function test_comment_author_passes_correct_comment_id_for_non_existent_c get_comment_author( self::$non_existent_comment_id ); // Non-existent comment ID. } + + /** + * @ticket 61681 + * + * @dataProvider data_should_return_author_when_given_object_without_comment_id + * + * @param stdClass $comment_props Comment properties test data. + * @param string $expected The expected result. + * @param array $user_data Optional. User data for creating an author. Default empty array. + */ + public function test_should_return_author_when_given_object_without_comment_id( $comment_props, $expected, $user_data = array() ) { + if ( ! empty( $comment_props->user_id ) ) { + $user = self::factory()->user->create_and_get( $user_data ); + $comment_props->user_id = $user->ID; + } + + $comment = new WP_Comment( $comment_props ); + + $this->assertSame( $expected, get_comment_author( $comment ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_should_return_author_when_given_object_without_comment_id() { + return array( + 'with no author' => array( + 'comment_props' => new stdClass(), + 'expected' => 'Anonymous', + ), + 'with author name' => array( + 'comment_props' => (object) array( + 'comment_author' => 'tester1', + ), + 'expected' => 'tester1', + ), + 'with author name, empty ID' => array( + 'comment_props' => (object) array( + 'comment_author' => 'tester2', + 'comment_ID' => '', + ), + 'expected' => 'tester2', + ), + 'with author ID' => array( + 'comment_props' => (object) array( + 'user_id' => 1, // Populates in the test with an actual user ID. + ), + 'expected' => 'Tester3', + 'user_data' => array( + 'display_name' => 'Tester3', + ), + ), + ); + } } diff --git a/tests/phpunit/tests/comment/getCommentAuthorLink.php b/tests/phpunit/tests/comment/getCommentAuthorLink.php new file mode 100644 index 0000000000000..c3d1033d837b5 --- /dev/null +++ b/tests/phpunit/tests/comment/getCommentAuthorLink.php @@ -0,0 +1,116 @@ +comment->create_and_get( + array( + 'comment_post_ID' => 0, + ) + ); + } + + public function get_comment_author_link_filter( $comment_author_link, $comment_author, $comment_id ) { + $this->assertSame( $comment_id, self::$comment->comment_ID, 'Comment IDs do not match.' ); + $this->assertIsString( $comment_id, '$comment_id parameter is not a string.' ); + + return $comment_author_link; + } + + public function test_comment_author_link_passes_correct_comment_id_for_comment_object() { + add_filter( 'get_comment_author_link', array( $this, 'get_comment_author_link_filter' ), 99, 3 ); + + get_comment_author_link( self::$comment ); + } + + public function test_comment_author_link_passes_correct_comment_id_for_int() { + add_filter( 'get_comment_author_link', array( $this, 'get_comment_author_link_filter' ), 99, 3 ); + + get_comment_author_link( (int) self::$comment->comment_ID ); + } + + public function get_comment_author_link_filter_non_existent_id( $comment_author_link, $comment_author, $comment_id ) { + $this->assertSame( $comment_id, (string) self::$non_existent_comment_id, 'Comment IDs do not match.' ); + $this->assertIsString( $comment_id, '$comment_id parameter is not a string.' ); + + return $comment_author_link; + } + + /** + * @ticket 60475 + */ + public function test_comment_author_link_passes_correct_comment_id_for_non_existent_comment() { + add_filter( 'get_comment_author_link', array( $this, 'get_comment_author_link_filter_non_existent_id' ), 99, 3 ); + + self::$non_existent_comment_id = self::$comment->comment_ID + 1; + + get_comment_author_link( self::$non_existent_comment_id ); // Non-existent comment ID. + } + + /** + * @ticket 61681 + * @ticket 61715 + * + * @dataProvider data_should_return_author_when_given_object_without_comment_id + * + * @param stdClass $comment_props Comment properties test data. + * @param string $expected The expected result. + * @param array $user_data Optional. User data for creating an author. Default empty array. + */ + public function test_should_return_author_when_given_object_without_comment_id( $comment_props, $expected, $user_data = array() ) { + if ( ! empty( $comment_props->user_id ) ) { + $user = self::factory()->user->create_and_get( $user_data ); + $comment_props->user_id = $user->ID; + } + + $comment = new WP_Comment( $comment_props ); + + $this->assertSame( $expected, get_comment_author_link( $comment ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_should_return_author_when_given_object_without_comment_id() { + return array( + 'with no author' => array( + 'comment_props' => new stdClass(), + 'expected' => 'Anonymous', + ), + 'with author name' => array( + 'comment_props' => (object) array( + 'comment_author' => 'tester1', + ), + 'expected' => 'tester1', + ), + 'with author name, empty ID' => array( + 'comment_props' => (object) array( + 'comment_author' => 'tester2', + 'comment_ID' => '', + ), + 'expected' => 'tester2', + ), + 'with author ID' => array( + 'comment_props' => (object) array( + 'user_id' => 1, // Populates in the test with an actual user ID. + ), + 'expected' => 'Tester3', + 'user_data' => array( + 'display_name' => 'Tester3', + ), + ), + ); + } +} diff --git a/tests/phpunit/tests/date/currentTime.php b/tests/phpunit/tests/date/currentTime.php index b308f4918ff5b..a41ea258dbe37 100644 --- a/tests/phpunit/tests/date/currentTime.php +++ b/tests/phpunit/tests/date/currentTime.php @@ -91,9 +91,14 @@ public function test_should_work_with_changed_timezone() { /** * @ticket 40653 + * @ticket 57998 + * + * @dataProvider data_timezones + * + * @param string $timezone The timezone to test. */ - public function test_should_return_wp_timestamp() { - update_option( 'timezone_string', 'Europe/Helsinki' ); + public function test_should_return_wp_timestamp( $timezone ) { + update_option( 'timezone_string', $timezone ); $timestamp = time(); $datetime = new DateTime( '@' . $timestamp ); @@ -101,24 +106,29 @@ public function test_should_return_wp_timestamp() { $wp_timestamp = $timestamp + $datetime->getOffset(); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.RequestedUTC - $this->assertEqualsWithDelta( $timestamp, current_time( 'timestamp', true ), 2, 'The dates should be equal' ); + $this->assertEqualsWithDelta( $timestamp, current_time( 'timestamp', true ), 2, 'When passing "timestamp", the date should be equal to time()' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.RequestedUTC - $this->assertEqualsWithDelta( $timestamp, current_time( 'U', true ), 2, 'The dates should be equal' ); + $this->assertEqualsWithDelta( $timestamp, current_time( 'U', true ), 2, 'When passing "U", the date should be equal to time()' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested - $this->assertEqualsWithDelta( $wp_timestamp, current_time( 'timestamp' ), 2, 'The dates should be equal' ); + $this->assertEqualsWithDelta( $wp_timestamp, current_time( 'timestamp' ), 2, 'When passing "timestamp", the date should be equal to calculated timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested - $this->assertEqualsWithDelta( $wp_timestamp, current_time( 'U' ), 2, 'The dates should be equal' ); + $this->assertEqualsWithDelta( $wp_timestamp, current_time( 'U' ), 2, 'When passing "U", the date should be equal to calculated timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested - $this->assertIsInt( current_time( 'timestamp' ) ); + $this->assertIsInt( current_time( 'timestamp' ), 'The returned timestamp should be an integer' ); } /** * @ticket 40653 + * @ticket 57998 + * + * @dataProvider data_timezones + * + * @param string $timezone The timezone to test. */ - public function test_should_return_correct_local_time() { - update_option( 'timezone_string', 'Europe/Helsinki' ); + public function test_should_return_correct_local_time( $timezone ) { + update_option( 'timezone_string', $timezone ); $timestamp = time(); $datetime_local = new DateTime( '@' . $timestamp ); @@ -127,7 +137,20 @@ public function test_should_return_correct_local_time() { $datetime_utc->setTimezone( new DateTimeZone( 'UTC' ) ); $this->assertEqualsWithDelta( strtotime( $datetime_local->format( DATE_W3C ) ), strtotime( current_time( DATE_W3C ) ), 2, 'The dates should be equal' ); - $this->assertEqualsWithDelta( strtotime( $datetime_utc->format( DATE_W3C ) ), strtotime( current_time( DATE_W3C, true ) ), 2, 'The dates should be equal' ); + $this->assertEqualsWithDelta( strtotime( $datetime_utc->format( DATE_W3C ) ), strtotime( current_time( DATE_W3C, true ) ), 2, 'When passing "timestamp", the dates should be equal' ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_timezones() { + return array( + array( 'Europe/Helsinki' ), + array( 'Indian/Antananarivo' ), + array( 'Australia/Adelaide' ), + ); } /** @@ -158,4 +181,88 @@ public function test_should_work_with_deprecated_timezone() { $this->assertSame( gmdate( $format ), $current_time_gmt, 'The dates should be equal [3]' ); $this->assertSame( $datetime->format( $format ), $current_time, 'The dates should be equal [4]' ); } + + /** + * Ensures an empty offset does not cause a type error. + * + * @ticket 57998 + */ + public function test_empty_offset_does_not_cause_a_type_error() { + // Ensure `wp_timezone_override_offset()` doesn't override offset. + update_option( 'timezone_string', '' ); + update_option( 'gmt_offset', '' ); + + $expected = time(); + + // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested + $this->assertEqualsWithDelta( $expected, current_time( 'timestamp' ), 2, 'The timestamps should be equal' ); + } + + /** + * Ensures the offset applied in current_time() is correct. + * + * @ticket 57998 + * + * @dataProvider data_partial_hour_timezones_with_timestamp + * + * @param float $partial_hour Partial hour GMT offset to test. + */ + public function test_partial_hour_timezones_with_timestamp( $partial_hour ) { + // Ensure `wp_timezone_override_offset()` doesn't override offset. + update_option( 'timezone_string', '' ); + update_option( 'gmt_offset', $partial_hour ); + + $expected = time() + (int) ( $partial_hour * HOUR_IN_SECONDS ); + + // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested + $this->assertEqualsWithDelta( $expected, current_time( 'timestamp' ), 2, 'The timestamps should be equal' ); + } + + /** + * Tests the tests. + * + * Ensures the offsets match the stated timezones in the data provider. + * + * @ticket 57998 + * + * @dataProvider data_partial_hour_timezones_with_timestamp + * + * @param float $partial_hour Partial hour GMT offset to test. + * @param string $timezone_string Timezone string to test. + */ + public function test_partial_hour_timezones_match_datetime_offset( $partial_hour, $timezone_string ) { + $timezone = new DateTimeZone( $timezone_string ); + $datetime = new DateTime( 'now', $timezone ); + $dst_offset = (int) $datetime->format( 'I' ); + + // Timezone offset in hours. + $offset = $timezone->getOffset( $datetime ) / HOUR_IN_SECONDS; + + /* + * Adjust for daylight saving time. + * + * DST adds an hour to the offset, the partial hour offset + * is set the the standard time offset so this removes the + * DST offset to avoid false negatives. + */ + $offset -= $dst_offset; + + $this->assertSame( $partial_hour, $offset, 'The offset should match to timezone.' ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_partial_hour_timezones_with_timestamp() { + return array( + '+12:45' => array( 12.75, 'Pacific/Chatham' ), // New Zealand, Chatham Islands. + '+9:30' => array( 9.5, 'Australia/Darwin' ), // Australian Northern Territory. + '+05:30' => array( 5.5, 'Asia/Kolkata' ), // India and Sri Lanka. + '+05:45' => array( 5.75, 'Asia/Kathmandu' ), // Nepal. + '-03:30' => array( -3.50, 'Canada/Newfoundland' ), // Canada, Newfoundland. + '-09:30' => array( -9.50, 'Pacific/Marquesas' ), // French Polynesia, Marquesas Islands. + ); + } } diff --git a/tests/phpunit/tests/dependencies.php b/tests/phpunit/tests/dependencies.php index 39a2860fc365e..dfcd253239583 100644 --- a/tests/phpunit/tests/dependencies.php +++ b/tests/phpunit/tests/dependencies.php @@ -148,4 +148,94 @@ public function test_enqueue_before_register() { $this->assertContains( 'one', $dep->queue ); } + + /** + * Data provider for test_get_etag. + * + * @return array[] + */ + public function data_provider_get_etag() { + return array( + 'should accept one dependency' => array( + 'load' => array( + 'abcd' => '1.0.2', + ), + 'hash_source_string' => 'WP:6.7;abcd:1.0.2;', + 'expected' => 'W/"8145d7e3c41d5a9cc2bccba4afa861fc"', + ), + 'should accept empty array of dependencies' => array( + 'load' => array(), + 'hash_source_string' => 'WP:6.7;', + 'expected' => 'W/"7ee896c19250a3d174f11469a4ad0b1e"', + ), + ); + } + + /** + * Tests get_etag method for WP_Scripts. + * + * @ticket 58433 + * @ticket 61485 + * + * @covers WP_Dependencies::get_etag + * + * @dataProvider data_provider_get_etag + * + * @param array $load List of scripts to load. + * @param string $hash_source_string Hash source string. + * @param string $expected Expected etag. + */ + public function test_get_etag_scripts( $load, $hash_source_string, $expected ) { + global $wp_version; + // Modify global to avoid tests needing to change with each new version of WordPress. + $original_wp_version = $wp_version; + $wp_version = '6.7'; + $instance = wp_scripts(); + + foreach ( $load as $handle => $ver ) { + // The src should not be empty. + wp_enqueue_script( $handle, 'https://example.org', array(), $ver ); + } + + $result = $instance->get_etag( array_keys( $load ) ); + + // Restore global prior to making assertions. + $wp_version = $original_wp_version; + + $this->assertSame( $expected, $result, "Expected MD hash: $expected for $hash_source_string, but got: $result." ); + } + + /** + * Tests get_etag method for WP_Styles. + * + * @ticket 58433 + * @ticket 61485 + * + * @covers WP_Dependencies::get_etag + * + * @dataProvider data_provider_get_etag + * + * @param array $load List of styles to load. + * @param string $hash_source_string Hash source string. + * @param string $expected Expected etag. + */ + public function test_get_etag_styles( $load, $hash_source_string, $expected ) { + global $wp_version; + // Modify global to avoid tests needing to change with each new version of WordPress. + $original_wp_version = $wp_version; + $wp_version = '6.7'; + $instance = wp_scripts(); + + foreach ( $load as $handle => $ver ) { + // The src should not be empty. + wp_enqueue_style( $handle, 'https://example.cdn', array(), $ver ); + } + + $result = $instance->get_etag( array_keys( $load ) ); + + // Restore global prior to making assertions. + $wp_version = $original_wp_version; + + $this->assertSame( $expected, $result, "Expected MD hash: $expected for $hash_source_string, but got: $result." ); + } } diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index 203066998dd42..485dc9f34580e 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -3117,9 +3117,9 @@ public function test_wp_scripts_move_to_footer( $set_up, $expected_header, $expe */ public function test_wp_get_script_polyfill() { global $wp_scripts; - $script_name = 'wp-polyfill-importmap'; - $test_script = 'HTMLScriptElement.supports && HTMLScriptElement.supports("importmap")'; - $script_url = 'https://example.com/wp-polyfill-importmap.js'; + $script_name = 'tmp-polyfill-foo'; + $test_script = 'HTMLScriptElement.supports && HTMLScriptElement.supports("foo")'; + $script_url = 'https://example.com/polyfill-foo.js'; wp_register_script( $script_name, $script_url ); $polyfill = wp_get_script_polyfill( @@ -3133,7 +3133,7 @@ public function test_wp_get_script_polyfill() { $expected = '( ' . $test_script . ' ) || document.write( \'", "\u{FFFD} is U+FFFD" ), + 'esc(SCRIPT) + NULL byte' => array( "", " is U+FFFD" ), + 'STYLE + NULL byte' => array( "", "\u{FFFD} is U+FFFD" ), + 'XMP + NULL byte' => array( "\x00 is U+FFFD", "\u{FFFD} is U+FFFD" ), + 'CDATA-like + NULL byte' => array( "", "just a \u{FFFD}comment" ), + 'Funky comment + NULL byte' => array( "", "%just a \u{FFFD}comment" ), + ); + } + /** * Ensures that normative Elements are properly parsed. * @@ -435,6 +512,67 @@ public function test_basic_assertion_abruptly_closed_cdata_section() { ); } + /** + * Ensures that basic CDATA sections inside foreign content are detected. + * + * @ticket 61576 + */ + public function test_basic_cdata_in_foreign_content() { + $processor = new WP_HTML_Tag_Processor( 'this is >&gt; real CDATA' ); + $processor->next_token(); + + // Artificially change namespace; this should be done in the HTML Processor. + $processor->change_parsing_namespace( 'svg' ); + $processor->next_token(); + + $this->assertSame( + '#cdata-section', + $processor->get_token_name(), + "Should have found a CDATA section but found {$processor->get_token_name()} instead." + ); + + $this->assertNull( + $processor->get_tag(), + 'Should not have been able to query tag name on non-element token.' + ); + + $this->assertNull( + $processor->get_attribute( 'type' ), + 'Should not have been able to query attributes on non-element token.' + ); + + $this->assertSame( + 'this is >> real CDATA', + $processor->get_modifiable_text(), + 'Found incorrect modifiable text.' + ); + } + + /** + * Ensures that empty CDATA sections inside foreign content are detected. + * + * @ticket 61576 + */ + public function test_empty_cdata_in_foreign_content() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + // Artificially change namespace; this should be done in the HTML Processor. + $processor->change_parsing_namespace( 'svg' ); + $processor->next_token(); + + $this->assertSame( + '#cdata-section', + $processor->get_token_name(), + "Should have found a CDATA section but found {$processor->get_token_name()} instead." + ); + + $this->assertEmpty( + $processor->get_modifiable_text(), + 'Found non-empty modifiable text.' + ); + } + /** * Ensures that normative Processing Instruction nodes are properly parsed. * diff --git a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php index fad1000dd763c..393fd2cda06db 100644 --- a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php @@ -601,6 +601,31 @@ public function test_next_tag_should_return_false_for_a_non_existing_tag() { $this->assertFalse( $processor->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); } + /** + * @ticket 61545 + */ + public function test_next_tag_should_not_match_on_substrings_of_a_requested_tag() { + $processor = new WP_HTML_Tag_Processor( '

' ); + + $this->assertTrue( + $processor->next_tag( 'PICTURE' ), + 'Failed to find a tag when requested: check test setup.' + ); + + $this->assertSame( + 'PICTURE', + $processor->get_tag(), + 'Should have skipped past substring tag matches, directly finding the PICTURE element.' + ); + + $processor = new WP_HTML_Tag_Processor( '

' ); + + $this->assertFalse( + $processor->next_tag( 'PICTURE' ), + "Should not have found any PICTURE element, but found '{$processor->get_token_name()}' instead." + ); + } + /** * @ticket 59209 * @@ -1376,12 +1401,12 @@ public function test_remove_class_removes_a_single_class_from_the_class_attribut $processor->remove_class( 'main' ); $this->assertSame( - '

Text
', + '
Text
', $processor->get_updated_html(), 'Updated HTML does not reflect class name removed from existing class attribute via remove_class()' ); $this->assertSame( - ' with-border', + 'with-border', $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect class name removed from existing class attribute via remove_class()" ); @@ -1466,12 +1491,12 @@ public function test_add_class_when_there_is_a_class_attribute_with_excessive_wh $processor->add_class( 'foo-class' ); $this->assertSame( - '
Text
', + '
Text
', $processor->get_updated_html(), 'Updated HTML does not reflect existing excessive whitespace after adding class name via add_class()' ); $this->assertSame( - ' main with-border foo-class', + 'main with-border foo-class', $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect existing excessive whitespace after adding class name via add_class()" ); @@ -1490,12 +1515,12 @@ public function test_remove_class_preserves_whitespaces_when_there_is_a_class_at $processor->remove_class( 'with-border' ); $this->assertSame( - '
Text
', + '
Text
', $processor->get_updated_html(), 'Updated HTML does not reflect existing excessive whitespace after removing class name via remove_class()' ); $this->assertSame( - ' main', + 'main', $processor->get_attribute( 'class' ), "get_attribute( 'class' ) does not reflect existing excessive whitespace after removing class name via removing_class()" ); @@ -1696,8 +1721,8 @@ public function test_advanced_use_case() { $expected_output = <<
-
-
+
+
@@ -2211,6 +2236,35 @@ public function test_class_list_visits_unique_class_names_only_once() { $this->assertSame( array( 'one' ), $found_classes, 'Visited multiple copies of the same class name when it should have skipped the duplicates.' ); } + /** + * Ensures that null bytes are replaced with the replacement character (U+FFFD) in class_list. + * + * @ticket 61531 + * + * @covers WP_HTML_Tag_Processor::class_list + */ + public function test_class_list_null_bytes_replaced() { + $processor = new WP_HTML_Tag_Processor( "
" ); + $processor->next_tag(); + + $found_classes = iterator_to_array( $processor->class_list() ); + + $this->assertSame( array( 'a', "\u{FFFD}", "b\u{FFFD}", "\u{FFFD}c\u{FFFD}" ), $found_classes ); + } + + /** + * Ensures that the tag processor matches class names with null bytes correctly. + * + * @ticket 61531 + * + * @covers WP_HTML_Tag_Processor::has_class + */ + public function test_has_class_null_byte_class_name() { + $processor = new WP_HTML_Tag_Processor( "
" ); + $processor->next_tag(); + $this->assertTrue( $processor->has_class( 'null-byte-�-there' ) ); + } + /** * @ticket 59209 * @@ -2875,4 +2929,59 @@ public function insert_after( $new_html ) { 'Should have properly applied the update from in front of the cursor.' ); } + + /** + * Test an infinite loop bugfix in incomplete script tag parsing. + * + * @small + * + * @ticket 61810 + */ + public function test_script_tag_processing_no_infinite_loop_final_dash() { + $processor = new WP_HTML_Tag_Processor( '' ); + $processor->next_token(); + + $this->assertSame( + 'SCRIPT', + $processor->get_token_name(), + "Should have found text node but found '{$processor->get_token_name()}' instead: check test setup." + ); + + $this->assertSame( + '', + $processor->get_modifiable_text(), + 'Should have found initial test text: check test setup.' + ); + + $processor->set_modifiable_text( $after ); + $this->assertSame( + $after, + $processor->get_modifiable_text(), + 'Should have found enqueued updated text.' + ); + + $processor->get_updated_html(); + $this->assertSame( + $after, + $processor->get_modifiable_text(), + 'Should have found updated text.' + ); + } + + /** + * Ensures that updates to modifiable text that are shorter than the + * original text do not cause the parser to lose its orientation. + * + * @ticket 61617 + */ + public function test_setting_shorter_modifiable_text() { + $processor = new WP_HTML_Tag_Processor( '
' ); + + // Find the test node in the middle. + while ( 'TEXTAREA' !== $processor->get_token_name() && $processor->next_token() ) { + continue; + } + + $this->assertSame( + 'TEXTAREA', + $processor->get_token_name(), + 'Failed to find the test TEXTAREA node; check the test setup.' + ); + + $processor->set_modifiable_text( 'short' ); + $processor->get_updated_html(); + $this->assertSame( + 'short', + $processor->get_modifiable_text(), + 'Should have updated modifiable text to something shorter than the original.' + ); + + $this->assertTrue( + $processor->next_token(), + 'Should have advanced to the last token in the input.' + ); + + $this->assertSame( + 'DIV', + $processor->get_token_name(), + 'Should have recognized the final DIV in the input.' + ); + + $this->assertSame( + 'not a ', + $processor->get_attribute( 'id' ), + 'Should have read in the id from the last DIV as "not a "' + ); + } + + /** + * Ensures that reads to modifiable text after setting it reads the updated + * enqueued values, and not the original value. + * + * @ticket 61617 + */ + public function test_modifiable_text_reads_updates_after_setting() { + $processor = new WP_HTML_Tag_Processor( 'This is text' ); + + $processor->next_token(); + $this->assertSame( + '#text', + $processor->get_token_name(), + 'Failed to find first text node: check test setup.' + ); + + $update = 'This is new text'; + $processor->set_modifiable_text( $update ); + $this->assertSame( + $update, + $processor->get_modifiable_text(), + 'Failed to read updated enqueued value of text node.' + ); + + $processor->next_token(); + $this->assertSame( + '#comment', + $processor->get_token_name(), + 'Failed to advance to comment: check test setup.' + ); + + $this->assertSame( + ' this is not ', + $processor->get_modifiable_text(), + 'Failed to read modifiable text for next token; did it read the old enqueued value from the previous token?' + ); + } + + /** + * Ensures that when ignoring a newline after LISTING and PRE tags, that this + * happens appropriately after seeking. + */ + public function test_get_modifiable_text_ignores_newlines_after_seeking() { + $processor = new WP_HTML_Tag_Processor( + <<\nhere +\ngone +
reset last known ignore-point
+
\nhere
+HTML + ); + + $processor->next_tag( 'SPAN' ); + $processor->next_token(); + $processor->set_bookmark( 'span' ); + + $this->assertSame( + "\nhere", + $processor->get_modifiable_text(), + 'Should not have removed the leading newline from the first SPAN.' + ); + + $processor->next_tag( 'LISTING' ); + $processor->next_token(); + $processor->set_bookmark( 'listing' ); + + $this->assertSame( + 'gone', + $processor->get_modifiable_text(), + 'Should have stripped the leading newline from the LISTING element on first traversal.' + ); + + $processor->next_tag( 'DIV' ); + $processor->next_token(); + $processor->set_bookmark( 'div' ); + + $this->assertSame( + "\nhere", + $processor->get_modifiable_text(), + 'Should not have removed the leading newline from the last DIV.' + ); + + $processor->seek( 'span' ); + $this->assertSame( + "\nhere", + $processor->get_modifiable_text(), + 'Should not have removed the leading newline from the first SPAN on its second traversal.' + ); + + $processor->seek( 'listing' ); + if ( "\ngone" === $processor->get_modifiable_text() ) { + $this->markTestSkipped( "There's no support currently for handling the leading newline after seeking." ); + } + + $this->assertSame( + 'gone', + $processor->get_modifiable_text(), + 'Should have remembered to remote leading newline from LISTING element after seeking around it.' + ); + + $processor->seek( 'div' ); + $this->assertSame( + "\nhere", + $processor->get_modifiable_text(), + 'Should not have removed the leading newline from the last DIV on its second traversal.' + ); + } + + /** + * Ensures that modifiable text updates are not applied where they aren't supported. + * + * @ticket 61617 + * + * @dataProvider data_tokens_not_supporting_modifiable_text_updates + * + * @param string $html Contains HTML with a token not supporting modifiable text updates. + * @param int $advance_n_tokens Count of times to run `next_token()` before reaching target node. + */ + public function test_rejects_updates_on_unsupported_match_locations( string $html, int $advance_n_tokens ) { + $processor = new WP_HTML_Tag_Processor( $html ); + while ( --$advance_n_tokens >= 0 ) { + $processor->next_token(); + } + + $this->assertFalse( + $processor->set_modifiable_text( 'Bazinga!' ), + 'Should have prevented modifying the text at the target node.' + ); + + $this->assertSame( + $html, + $processor->get_updated_html(), + 'Should not have modified the input document in any way.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_tokens_not_supporting_modifiable_text_updates() { + return array( + 'Before parsing' => array( 'nothing to see here', 0 ), + 'After parsing' => array( 'nothing here either', 2 ), + 'Incomplete document' => array( ' array( 'Text', 1, 'Blubber', 'Blubber' ), + 'Text node (middle)' => array( 'Bold move', 2, 'yo', 'yo' ), + 'Text node (end)' => array( 'of a dog', 2, 'of a cat', 'of a cat' ), + 'Encoded text node' => array( '
birds and dogs
', 2, ' & ', '
<birds> & <dogs>
' ), + 'SCRIPT tag' => array( 'beforeafter', 2, 'const img = " &
";', 'beforeafter' ), + 'STYLE tag' => array( '', 1, 'p::before { content: " & "; }', '' ), + 'TEXTAREA tag' => array( 'ab', 2, "so it ", "ab" ), + 'TEXTAREA (escape)' => array( 'ab', 2, 'but it does for ', 'ab' ), + 'TEXTAREA (escape+attrs)' => array( 'ab', 2, 'but it does for ', 'ab' ), + 'TITLE tag' => array( 'ahas no need to escapeb', 2, "so it ", "aso it <doesn't>b" ), + 'TITLE (escape)' => array( 'ahas no need to escapeb', 2, 'but it does for ', 'abut it does for </title>b' ), + 'TITLE (escape+attrs)' => array( 'ahas no need to escapeb', 2, 'but it does for ', 'abut it does for </title not an="attribute">b' ), + ); + } + + /** + * Ensures that updates with potentially-compromising values aren't accepted. + * + * For example, a modifiable text update should be allowed which would break + * the structure of the containing element, such as in a script or comment. + * + * @ticket 61617 + * + * @dataProvider data_unallowed_modifiable_text_updates + * + * @param string $html_with_nonempty_modifiable_text Will be used to find the test element. + * @param string $invalid_update Update containing possibly-compromising text. + */ + public function test_rejects_updates_with_unallowed_substrings( string $html_with_nonempty_modifiable_text, string $invalid_update ) { + $processor = new WP_HTML_Tag_Processor( $html_with_nonempty_modifiable_text ); + + while ( '' === $processor->get_modifiable_text() && $processor->next_token() ) { + continue; + } + + $original_text = $processor->get_modifiable_text(); + $this->assertNotEmpty( $original_text, 'Should have found non-empty text: check test setup.' ); + + $this->assertFalse( + $processor->set_modifiable_text( $invalid_update ), + 'Should have reject possibly-compromising modifiable text update.' + ); + + // Flush updates. + $processor->get_updated_html(); + + $this->assertSame( + $original_text, + $processor->get_modifiable_text(), + 'Should have preserved the original modifiable text before the rejected update.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_unallowed_modifiable_text_updates() { + return array( + 'Comment with -->' => array( '', 'Comments end in -->' ), + 'Comment with --!>' => array( '', 'Invalid but legitimate comments end in --!>' ), + 'SCRIPT with ' => array( '', 'Just a ' ), + 'SCRIPT with ' => array( '', 'beforeafter' ), + ); + } +} diff --git a/tests/phpunit/tests/http/http.php b/tests/phpunit/tests/http/http.php index a2fd75b95d1cc..533c584f04d53 100644 --- a/tests/phpunit/tests/http/http.php +++ b/tests/phpunit/tests/http/http.php @@ -114,7 +114,7 @@ public function data_wp_parse_url() { ), ), - // < PHP 5.4.7: Schemeless URL. + // Schemeless URL. array( '//example.com/path/', array( @@ -138,7 +138,7 @@ public function data_wp_parse_url() { ), ), - // < PHP 5.4.7: Scheme separator in the URL. + // Scheme separator in the URL. array( 'http://example.com/http://example.net/', array( @@ -149,7 +149,7 @@ public function data_wp_parse_url() { ), array( '/path/http://example.net/', array( 'path' => '/path/http://example.net/' ) ), - // < PHP 5.4.7: IPv6 literals in schemeless URLs are handled incorrectly. + // IPv6 literals in schemeless URLs. array( '//[::FFFF::127.0.0.1]/', array( @@ -238,7 +238,7 @@ public function data_wp_parse_url_with_component() { array( self::FULL_TEST_URL, PHP_URL_QUERY, 'arg1=value1&arg2=value2' ), array( self::FULL_TEST_URL, PHP_URL_FRAGMENT, 'anchor' ), - // < PHP 5.4.7: Schemeless URL. + // Schemeless URL. array( '//example.com/path/', PHP_URL_HOST, 'example.com' ), array( '//example.com/path/', PHP_URL_PATH, '/path/' ), array( '//example.com/', PHP_URL_HOST, 'example.com' ), @@ -246,13 +246,13 @@ public function data_wp_parse_url_with_component() { array( 'http://example.com//path/', PHP_URL_HOST, 'example.com' ), array( 'http://example.com//path/', PHP_URL_PATH, '//path/' ), - // < PHP 5.4.7: Scheme separator in the URL. + // Scheme separator in the URL. array( 'http://example.com/http://example.net/', PHP_URL_HOST, 'example.com' ), array( 'http://example.com/http://example.net/', PHP_URL_PATH, '/http://example.net/' ), array( '/path/http://example.net/', PHP_URL_HOST, null ), array( '/path/http://example.net/', PHP_URL_PATH, '/path/http://example.net/' ), - // < PHP 5.4.7: IPv6 literals in schemeless URLs are handled incorrectly. + // IPv6 literals in schemeless URLs. array( '//[::FFFF::127.0.0.1]/', PHP_URL_HOST, '[::FFFF::127.0.0.1]' ), array( '//[::FFFF::127.0.0.1]/', PHP_URL_PATH, '/' ), diff --git a/tests/phpunit/tests/image/functions.php b/tests/phpunit/tests/image/functions.php index e70d19c0327ba..e895ec9dc8f7d 100644 --- a/tests/phpunit/tests/image/functions.php +++ b/tests/phpunit/tests/image/functions.php @@ -237,6 +237,7 @@ public function data_file_is_displayable_image_negative() { 'test-image.jp2', 'test-image.psd', 'test-image-zip.tiff', + 'test-image.heic', ); return $this->text_array_to_dataprovider( $files ); diff --git a/tests/phpunit/tests/image/resize.php b/tests/phpunit/tests/image/resize.php index e82dd3d2e6b2a..f4fe8632a0580 100644 --- a/tests/phpunit/tests/image/resize.php +++ b/tests/phpunit/tests/image/resize.php @@ -114,6 +114,32 @@ public function test_resize_avif() { $this->assertSame( IMAGETYPE_AVIF, $type ); } + /** + * Test resizing HEIC image. + * + * @ticket 53645 + */ + public function test_resize_heic() { + $file = DIR_TESTDATA . '/images/test-image.heic'; + $editor = wp_get_image_editor( $file ); + + // Check if the editor supports the HEIC mime type. + if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/heic' ) ) { + $this->markTestSkipped( 'No HEIC support in the editor engine on this system.' ); + } + + $image = $this->resize_helper( $file, 25, 25 ); + + list( $w, $h, $type ) = wp_getimagesize( $image ); + + unlink( $image ); + + $this->assertSame( 'test-image-25x25.jpg', wp_basename( $image ) ); + $this->assertSame( 25, $w ); + $this->assertSame( 25, $h ); + $this->assertSame( IMAGETYPE_JPEG, $type ); + } + public function test_resize_larger() { // image_resize() should refuse to make an image larger. $image = $this->resize_helper( DIR_TESTDATA . '/images/test-image.jpg', 100, 100 ); @@ -235,6 +261,6 @@ protected function resize_helper( $file, $width, $height, $crop = false ) { return $saved; } - return $dest_file; + return $saved['path']; } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php index b9cf16952be91..d7bf77bb66470 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-class.php @@ -58,7 +58,7 @@ private function process_directives( $html ) { public function test_wp_class_sets_class_name() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); } /** @@ -76,7 +76,7 @@ public function test_wp_class_sets_multiple_class_names() { data-wp-class--other-class="myPlugin::state.true" >Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class other-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class other-class', $p->get_attribute( 'class' ) ); } /** @@ -94,7 +94,7 @@ public function test_wp_class_handles_multiple_class_names_with_different_values data-wp-class--other-class="myPlugin::state.false" >Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); } /** @@ -116,7 +116,7 @@ class="other-class" public function test_wp_class_sets_class_name_when_class_attribute_exists() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-class some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'other-class some-class', $p->get_attribute( 'class' ) ); } /** @@ -143,7 +143,7 @@ public function test_wp_class_doesnt_add_class_attribute_on_false() { public function test_wp_class_doesnt_add_class_name_on_false() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'other-class', $p->get_attribute( 'class' ) ); } /** @@ -157,7 +157,7 @@ public function test_wp_class_doesnt_add_class_name_on_false() { public function test_wp_class_keeps_class_name_when_class_name_exists() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); } /** @@ -171,7 +171,7 @@ public function test_wp_class_keeps_class_name_when_class_name_exists() { public function test_wp_class_keeps_class_name_when_class_name_exists_and_is_not_the_only_one() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-class some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'other-class some-class', $p->get_attribute( 'class' ) ); } /** @@ -199,7 +199,7 @@ public function test_wp_class_removes_class_attribute_when_class_name_exists_and public function test_wp_class_removes_class_name_when_class_name_exists_and_is_not_the_only_one() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'other-class', $p->get_attribute( 'class' ) ); } /** @@ -227,7 +227,7 @@ public function test_wp_class_doesnt_remove_empty_class_attribute() { public function test_wp_class_doesnt_change_class_attribute_with_empty_directive_suffix() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'other-class', $p->get_attribute( 'class' ) ); } /** @@ -242,7 +242,7 @@ public function test_wp_class_doesnt_change_class_attribute_with_empty_directive public function test_wp_class_doesnt_change_class_attribute_with_empty_value() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'other-class', $p->get_attribute( 'class' ) ); } /** @@ -257,7 +257,7 @@ public function test_wp_class_doesnt_change_class_attribute_with_empty_value() { public function test_wp_class_doesnt_change_class_attribute_without_value() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'other-class', $p->get_attribute( 'class' ) ); } /** @@ -271,7 +271,7 @@ public function test_wp_class_doesnt_change_class_attribute_without_value() { public function test_wp_class_works_with_multiple_directives() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); } /** @@ -286,17 +286,17 @@ public function test_wp_class_sets_class_name_on_truthy_values() { $this->interactivity->state( 'myPlugin', array( 'text' => 'some text' ) ); $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); $this->interactivity->state( 'myPlugin', array( 'array' => array( 1, 2 ) ) ); $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); $this->interactivity->state( 'myPlugin', array( 'number' => 1 ) ); $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); } /** diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php index 93e2528126215..442c62c84e52f 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-context.php @@ -56,7 +56,7 @@ public function test_wp_context_directive_sets_a_context_in_a_custom_namespace()
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -78,7 +78,7 @@ class="test"
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -99,9 +99,9 @@ public function test_wp_context_directive_merges_context_in_the_same_custom_name
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id-2', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-2', $p->get_attribute( 'id' ) ); } /** @@ -121,7 +121,7 @@ public function test_wp_context_directive_overwrites_context_in_the_same_custom_
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-2', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-2', $p->get_attribute( 'id' ) ); } /** @@ -142,9 +142,9 @@ public function test_wp_context_directive_replaces_old_context_after_closing_tag
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-2', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-2', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); } /** @@ -165,9 +165,9 @@ public function test_wp_context_directive_merges_context_in_different_custom_nam
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id-2', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-2', $p->get_attribute( 'id' ) ); } /** @@ -206,9 +206,9 @@ public function test_wp_context_directive_doesnt_overwrite_context_on_malformed_
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); } /** @@ -247,9 +247,9 @@ public function test_wp_context_directive_doesnt_overwrite_context_on_empty_cont
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); } /** @@ -288,9 +288,9 @@ public function test_wp_context_directive_doesnt_overwrite_context_on_context_wi
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); } /** @@ -307,7 +307,7 @@ public function test_wp_context_works_with_multiple_directives() {
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -346,7 +346,7 @@ public function test_wp_context_directive_works_with_default_namespace() {
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -368,7 +368,7 @@ public function test_wp_context_directive_overrides_default_namespace() {
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -389,7 +389,7 @@ public function test_wp_context_directive_overrides_default_namespace_with_same_ '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -416,11 +416,11 @@ public function test_wp_context_directive_works_with_nested_default_namespaces() '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'other-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); $this->assertNull( $p->get_attribute( 'id' ) ); } @@ -445,7 +445,7 @@ class="test" '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -469,9 +469,9 @@ public function test_wp_context_directive_merges_context_in_the_same_default_nam '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id-2', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-2', $p->get_attribute( 'id' ) ); } /** @@ -494,7 +494,7 @@ public function test_wp_context_directive_overwrites_context_in_the_same_default '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-2', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-2', $p->get_attribute( 'id' ) ); } /** @@ -518,8 +518,8 @@ public function test_wp_context_directive_replaces_old_context_after_closing_tag '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id-2', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-2', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id-1', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id-1', $p->get_attribute( 'id' ) ); } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php index 4628ec702a67b..0446fa461df14 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-each.php @@ -42,7 +42,7 @@ public function test_wp_each_doesnt_do_anything_on_non_template_tags() { '; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $original, $new ); + $this->assertSame( $original, $new ); } /** @@ -68,7 +68,7 @@ public function test_wp_each_doesnt_do_anything_on_associative_arrays() { '; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $original, $new ); + $this->assertSame( $original, $new ); } /** @@ -92,7 +92,7 @@ public function test_wp_each_simple_tags() { '2' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -116,7 +116,7 @@ public function test_wp_each_empty_array() { '' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -145,7 +145,7 @@ public function test_wp_each_merges_context_correctly() { '
New text
' . ''; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -173,7 +173,7 @@ public function test_wp_each_gets_arrays_from_context() { '
Text
' . ''; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -201,7 +201,7 @@ public function test_wp_each_default_namespace() { '
Text
' . ''; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -229,7 +229,7 @@ public function test_wp_each_multiple_tags_per_item() { '2' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -257,7 +257,7 @@ public function test_wp_each_void_tags() { '' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -286,7 +286,7 @@ public function test_wp_each_void_and_non_void_tags() { '2' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -318,7 +318,7 @@ public function test_wp_each_nested_tags() { '' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -361,7 +361,7 @@ public function test_wp_each_nested_item_properties() { 'two' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -385,7 +385,7 @@ public function test_wp_each_different_item_names() { '2' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -410,7 +410,7 @@ public function test_wp_each_different_item_names_transforms_camelcase() { '2' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -426,14 +426,14 @@ public function test_wp_each_doesnt_work_with_top_level_text() { 'id: ' . ''; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $original, $new ); + $this->assertSame( $original, $new ); $original = '' . ''; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $original, $new ); + $this->assertSame( $original, $new ); // But it should work fine with spaces and linebreaks. $original = ' @@ -444,9 +444,9 @@ public function test_wp_each_doesnt_work_with_top_level_text() { $p = new WP_HTML_Tag_Processor( $new ); $p->next_tag( array( 'class_name' => 'test' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( '1', $p->get_attribute( 'id' ) ); + $this->assertSame( '1', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( '2', $p->get_attribute( 'id' ) ); + $this->assertSame( '2', $p->get_attribute( 'id' ) ); } /** @@ -487,7 +487,7 @@ public function test_wp_each_nested_template_tags() { '4' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -533,7 +533,7 @@ public function test_wp_each_directly_nested_template_tags() { '4' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -571,7 +571,7 @@ public function test_wp_each_nested_template_tags_using_previous_item_as_list() '4' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -590,7 +590,7 @@ public function test_wp_each_unbalanced_tags() { '' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $original, $new ); + $this->assertSame( $original, $new ); } /** @@ -614,7 +614,7 @@ public function test_wp_each_unbalanced_tags_in_nested_template_tags() { '' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $original, $new ); + $this->assertSame( $original, $new ); } /** @@ -639,23 +639,23 @@ public function test_wp_each_doesnt_process_if_not_array() { $this->interactivity->state( 'myPlugin', array( 'list' => null ) ); $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); $this->interactivity->state( 'myPlugin', array( 'list' => 'Text' ) ); $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); $this->interactivity->state( 'myPlugin', array( 'list' => 100 ) ); $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); $this->interactivity->state( 'myPlugin', array( 'list' => false ) ); $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); $this->interactivity->state( 'myPlugin', array( 'list' => true ) ); $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } /** @@ -682,6 +682,6 @@ public function test_wp_each_doesnt_process_with_manual_server_directive_process '2' . '
Text
'; $new = $this->interactivity->process_directives( $original ); - $this->assertEquals( $expected, $new ); + $this->assertSame( $expected, $new ); } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-interactive.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-interactive.php index 65f4e4258b4e3..c1f3b9c8d2ae2 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-interactive.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-interactive.php @@ -58,7 +58,7 @@ public function test_wp_interactive_sets_a_default_namespace_with_object() { '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -76,7 +76,7 @@ public function test_wp_interactive_sets_a_default_namespace_with_string() { '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -97,9 +97,9 @@ public function test_wp_interactive_replaces_the_previous_default_namespace() { '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'other-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'other-id', $p->get_attribute( 'id' ) ); } /** @@ -121,9 +121,9 @@ public function test_wp_interactive_json_without_namespace_doesnt_replace_the_pr '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -144,9 +144,9 @@ public function test_wp_interactive_with_empty_value_doesnt_replace_the_previous '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -167,9 +167,9 @@ public function test_wp_interactive_with_invalid_value_doesnt_replace_the_previo '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -190,9 +190,9 @@ public function test_wp_interactive_without_value_doesnt_replace_the_previous_de '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -209,7 +209,7 @@ public function test_wp_interactive_works_with_multiple_directives() { '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -227,7 +227,7 @@ public function test_wp_interactive_namespace_can_be_override_by_custom_one() { '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'other-id', $p->get_attribute( 'id' ) ); } /** @@ -248,9 +248,9 @@ public function test_wp_interactive_set_is_unset_on_closing_tag() { '; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'other-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'other-id', $p->get_attribute( 'id' ) ); $html = '
@@ -261,8 +261,8 @@ public function test_wp_interactive_set_is_unset_on_closing_tag() {
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'other-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'other-id', $p->get_attribute( 'id' ) ); $p->next_tag( array( 'class_name' => 'test' ) ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-router-region.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-router-region.php index 88a03e483db59..8d8cdb6228d2e 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-router-region.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-router-region.php @@ -88,9 +88,9 @@ public function test_wp_router_region_missing() { $html = '
Nothing here
'; $new_html = $this->interactivity->process_directives( $html ); $footer = $this->render_wp_footer(); - $this->assertEquals( $html, $new_html ); - $this->assertEquals( '', $footer ); - $this->assertEquals( '', get_echo( 'wp_print_styles' ) ); + $this->assertSame( $html, $new_html ); + $this->assertSame( '', $footer ); + $this->assertSame( '', get_echo( 'wp_print_styles' ) ); } /** @@ -108,14 +108,14 @@ public function test_wp_router_region_adds_loading_bar_aria_live_region_only_onc
Another interactive region
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( $html, $new_html ); + $this->assertSame( $html, $new_html ); // Check that the style is loaded, but only once. $styles = get_echo( 'wp_print_styles' ); $query = array( 'tag_name' => 'style' ); $p = new WP_HTML_Tag_Processor( $styles ); $this->assertTrue( $p->next_tag( $query ) ); - $this->assertEquals( 'wp-interactivity-router-animations-inline-css', $p->get_attribute( 'id' ) ); + $this->assertSame( 'wp-interactivity-router-animations-inline-css', $p->get_attribute( 'id' ) ); $this->assertStringContainsString( '.wp-interactivity-router-loading-bar', $styles ); $this->assertFalse( $p->next_tag( $query ) ); diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php index 3b645b0dd4df3..b69f0c79c392f 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-style.php @@ -61,47 +61,47 @@ private function merge_style_property( $style_attribute_value, $style_property_n public function test_merge_style_property_sets_properties() { // Adds property on empty style attribute. $result = $this->merge_style_property( '', 'color', 'green' ); - $this->assertEquals( 'color:green;', $result ); + $this->assertSame( 'color:green;', $result ); // Changes style property when there is an existing property. $result = $this->merge_style_property( 'color:red;', 'color', 'green' ); - $this->assertEquals( 'color:green;', $result ); + $this->assertSame( 'color:green;', $result ); // Adds a new property when the existing one does not match. $result = $this->merge_style_property( 'color:red;', 'background', 'blue' ); - $this->assertEquals( 'color:red;background:blue;', $result ); + $this->assertSame( 'color:red;background:blue;', $result ); // Handles multiple existing properties. $result = $this->merge_style_property( 'color:red;margin:5px;', 'color', 'green' ); - $this->assertEquals( 'margin:5px;color:green;', $result ); + $this->assertSame( 'margin:5px;color:green;', $result ); // Adds a new property when multiple existing properties do not match. $result = $this->merge_style_property( 'color:red;margin:5px;', 'padding', '10px' ); - $this->assertEquals( 'color:red;margin:5px;padding:10px;', $result ); + $this->assertSame( 'color:red;margin:5px;padding:10px;', $result ); // Removes whitespaces in all properties. $result = $this->merge_style_property( ' color : red; margin : 5px; ', 'padding', ' 10px ' ); - $this->assertEquals( 'color:red;margin:5px;padding:10px;', $result ); + $this->assertSame( 'color:red;margin:5px;padding:10px;', $result ); // Updates a property when it's not the first one in the value. $result = $this->merge_style_property( 'color:red;margin:5px;', 'margin', '15px' ); - $this->assertEquals( 'color:red;margin:15px;', $result ); + $this->assertSame( 'color:red;margin:15px;', $result ); // Adds missing trailing semicolon. $result = $this->merge_style_property( 'color:red;margin:5px', 'padding', '10px' ); - $this->assertEquals( 'color:red;margin:5px;padding:10px;', $result ); + $this->assertSame( 'color:red;margin:5px;padding:10px;', $result ); // Doesn't add double semicolons. $result = $this->merge_style_property( 'color:red;margin:5px;', 'padding', '10px;' ); - $this->assertEquals( 'color:red;margin:5px;padding:10px;', $result ); + $this->assertSame( 'color:red;margin:5px;padding:10px;', $result ); // Handles empty properties in the input. $result = $this->merge_style_property( 'color:red;;margin:5px;;', 'padding', '10px' ); - $this->assertEquals( 'color:red;margin:5px;padding:10px;', $result ); + $this->assertSame( 'color:red;margin:5px;padding:10px;', $result ); // Moves the modified property to the end. $result = $this->merge_style_property( 'border-style: dashed; border: 3px solid red;', 'border-style', 'inset' ); - $this->assertEquals( 'border:3px solid red;border-style:inset;', $result ); + $this->assertSame( 'border:3px solid red;border-style:inset;', $result ); } /** @@ -115,35 +115,35 @@ public function test_merge_style_property_sets_properties() { public function test_merge_style_property_with_falsy_values() { // Removes a property with an empty string. $result = $this->merge_style_property( 'color:red;margin:5px;', 'color', '' ); - $this->assertEquals( 'margin:5px;', $result ); + $this->assertSame( 'margin:5px;', $result ); // Removes a property with null. $result = $this->merge_style_property( 'color:red;margin:5px;', 'color', null ); - $this->assertEquals( 'margin:5px;', $result ); + $this->assertSame( 'margin:5px;', $result ); // Removes a property with false. $result = $this->merge_style_property( 'color:red;margin:5px;', 'color', false ); - $this->assertEquals( 'margin:5px;', $result ); + $this->assertSame( 'margin:5px;', $result ); // Removes a property with 0. $result = $this->merge_style_property( 'color:red;margin:5px;', 'color', 0 ); - $this->assertEquals( 'margin:5px;', $result ); + $this->assertSame( 'margin:5px;', $result ); // It doesn't add a new property with an empty string. $result = $this->merge_style_property( 'color:red;', 'padding', '' ); - $this->assertEquals( 'color:red;', $result ); + $this->assertSame( 'color:red;', $result ); // It doesn't add a new property with null. $result = $this->merge_style_property( 'color:red;', 'padding', null ); - $this->assertEquals( 'color:red;', $result ); + $this->assertSame( 'color:red;', $result ); // It doesn't add a new property with false. $result = $this->merge_style_property( 'color:red;', 'padding', false ); - $this->assertEquals( 'color:red;', $result ); + $this->assertSame( 'color:red;', $result ); // It doesn't add a new property with 0. $result = $this->merge_style_property( 'color:red;', 'padding', 0 ); - $this->assertEquals( 'color:red;', $result ); + $this->assertSame( 'color:red;', $result ); } /** @@ -170,7 +170,7 @@ private function process_directives( $html ) { public function test_wp_style_sets_style_attribute() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'color:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'color:green;', $p->get_attribute( 'style' ) ); } /** @@ -188,7 +188,7 @@ public function test_wp_style_sets_multiple_style_properties() { data-wp-style--background="myPlugin::state.green" >Text'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'color:green;background:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'color:green;background:green;', $p->get_attribute( 'style' ) ); } /** @@ -206,7 +206,7 @@ public function test_wp_style_sets_multiple_style_properties_with_different_valu data-wp-style--background="myPlugin::state.false" >Text'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'color:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'color:green;', $p->get_attribute( 'style' ) ); $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'color:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'color:green;', $p->get_attribute( 'style' ) ); } /** @@ -229,7 +229,7 @@ public function test_wp_style_sets_multiple_style_properties_with_different_valu public function test_wp_style_sets_style_property_when_style_attribute_exists() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'padding:10px;color:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'padding:10px;color:green;', $p->get_attribute( 'style' ) ); } /** @@ -243,7 +243,7 @@ public function test_wp_style_sets_style_property_when_style_attribute_exists() public function test_wp_style_overwrites_style_property_when_style_property_exists() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'color:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'color:green;', $p->get_attribute( 'style' ) ); } /** @@ -271,7 +271,7 @@ public function test_wp_style_doesnt_add_style_attribute_on_false() { public function test_wp_style_doesnt_add_style_property_on_false() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'padding:10px;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'padding:10px;', $p->get_attribute( 'style' ) ); } /** @@ -285,7 +285,7 @@ public function test_wp_style_doesnt_add_style_property_on_false() { public function test_wp_style_keeps_style_property_when_style_property_exists() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'color:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'color:green;', $p->get_attribute( 'style' ) ); } /** @@ -299,7 +299,7 @@ public function test_wp_style_keeps_style_property_when_style_property_exists() public function test_wp_style_keeps_style_property_when_style_property_exists_and_is_not_the_only_one() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'padding:10px;color:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'padding:10px;color:green;', $p->get_attribute( 'style' ) ); } /** @@ -327,7 +327,7 @@ public function test_wp_style_removes_style_attribute_when_style_property_exists public function test_wp_style_removes_style_property_when_style_property_exists_and_is_not_the_only_one() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'padding:10px;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'padding:10px;', $p->get_attribute( 'style' ) ); } /** @@ -355,7 +355,7 @@ public function test_wp_style_doesnt_remove_empty_style_attribute() { public function test_wp_style_doesnt_change_style_attribute_with_empty_directive_suffix() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'padding:10px;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'padding:10px;', $p->get_attribute( 'style' ) ); } /** @@ -370,7 +370,7 @@ public function test_wp_style_doesnt_change_style_attribute_with_empty_directive public function test_wp_style_doesnt_change_style_attribute_with_empty_value() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'padding:10px;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'padding:10px;', $p->get_attribute( 'style' ) ); } /** @@ -385,7 +385,7 @@ public function test_wp_style_doesnt_change_style_attribute_with_empty_value() { public function test_wp_style_doesnt_change_style_attribute_without_value() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'padding:10px;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'padding:10px;', $p->get_attribute( 'style' ) ); } /** @@ -399,7 +399,7 @@ public function test_wp_style_doesnt_change_style_attribute_without_value() { public function test_wp_style_works_with_multiple_directives() { $html = '
Text
'; list($p) = $this->process_directives( $html ); - $this->assertEquals( 'color:green;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'color:green;', $p->get_attribute( 'style' ) ); } /** diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php index d031698707960..dc19b1117de39 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-text.php @@ -39,7 +39,7 @@ public function set_up() { public function test_wp_text_sets_inner_content() { $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
Updated
', $new_html ); + $this->assertSame( '
Updated
', $new_html ); } /** @@ -53,7 +53,7 @@ public function test_wp_text_sets_inner_content_numbers() { $this->interactivity->state( 'myPlugin', array( 'number' => 100 ) ); $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
100
', $new_html ); + $this->assertSame( '
100
', $new_html ); } /** @@ -77,23 +77,23 @@ public function test_wp_text_removes_inner_content_on_types_that_are_not_strings ); $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
', $new_html ); + $this->assertSame( '
', $new_html ); $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
', $new_html ); + $this->assertSame( '
', $new_html ); $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
', $new_html ); + $this->assertSame( '
', $new_html ); $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
', $new_html ); + $this->assertSame( '
', $new_html ); $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
', $new_html ); + $this->assertSame( '
', $new_html ); } /** @@ -107,7 +107,7 @@ public function test_wp_text_removes_inner_content_on_types_that_are_not_strings public function test_wp_text_sets_inner_content_with_nested_tags() { $html = '
Text
Another text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
Updated
', $new_html ); + $this->assertSame( '
Updated
', $new_html ); } /** @@ -121,7 +121,7 @@ public function test_wp_text_sets_inner_content_with_nested_tags() { public function test_wp_text_sets_inner_content_even_with_unbalanced_but_different_tags_inside_content() { $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
Updated
', $new_html ); + $this->assertSame( '
Updated
', $new_html ); } /** @@ -137,7 +137,7 @@ public function test_wp_text_sets_inner_content_even_with_unbalanced_but_differe public function test_wp_text_fails_with_unbalanced_and_same_tags_inside_content() { $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
Text
', $new_html ); + $this->assertSame( '
Text
', $new_html ); } /** @@ -152,6 +152,6 @@ public function test_wp_text_cant_set_inner_html_in_the_content() { $this->interactivity->state( 'myPlugin', array( 'text' => 'Updated' ) ); $html = '
Text
'; $new_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( '
<span>Updated</span>
', $new_html ); + $this->assertSame( '
<span>Updated</span>
', $new_html ); } } diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php index 25c8c2dc3abce..e9349190ebdb2 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php @@ -69,8 +69,8 @@ private function set_internal_context_stack( ...$stack ) { * @covers ::config */ public function test_state_and_config_should_be_empty() { - $this->assertEquals( array(), $this->interactivity->state( 'myPlugin' ) ); - $this->assertEquals( array(), $this->interactivity->config( 'myPlugin' ) ); + $this->assertSame( array(), $this->interactivity->state( 'myPlugin' ) ); + $this->assertSame( array(), $this->interactivity->config( 'myPlugin' ) ); } /** @@ -89,9 +89,9 @@ public function test_state_and_config_can_be_changed() { 'nested' => array( 'c' => 3 ), ); $result = $this->interactivity->state( 'myPlugin', $state ); - $this->assertEquals( $state, $result ); + $this->assertSame( $state, $result ); $result = $this->interactivity->config( 'myPlugin', $state ); - $this->assertEquals( $state, $result ); + $this->assertSame( $state, $result ); } /** @@ -106,14 +106,14 @@ public function test_state_and_config_can_be_merged() { $this->interactivity->state( 'myPlugin', array( 'a' => 1 ) ); $this->interactivity->state( 'myPlugin', array( 'b' => 2 ) ); $this->interactivity->state( 'otherPlugin', array( 'c' => 3 ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => 1, 'b' => 2, ), $this->interactivity->state( 'myPlugin' ) ); - $this->assertEquals( + $this->assertSame( array( 'c' => 3 ), $this->interactivity->state( 'otherPlugin' ) ); @@ -121,14 +121,14 @@ public function test_state_and_config_can_be_merged() { $this->interactivity->config( 'myPlugin', array( 'a' => 1 ) ); $this->interactivity->config( 'myPlugin', array( 'b' => 2 ) ); $this->interactivity->config( 'otherPlugin', array( 'c' => 3 ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => 1, 'b' => 2, ), $this->interactivity->config( 'myPlugin' ) ); - $this->assertEquals( + $this->assertSame( array( 'c' => 3 ), $this->interactivity->config( 'otherPlugin' ) ); } @@ -145,14 +145,14 @@ public function test_state_and_config_can_be_merged() { public function test_state_and_config_existing_props_can_be_overwritten() { $this->interactivity->state( 'myPlugin', array( 'a' => 1 ) ); $this->interactivity->state( 'myPlugin', array( 'a' => 2 ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => 2 ), $this->interactivity->state( 'myPlugin' ) ); $this->interactivity->config( 'myPlugin', array( 'a' => 1 ) ); $this->interactivity->config( 'myPlugin', array( 'a' => 2 ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => 2 ), $this->interactivity->config( 'myPlugin' ) ); @@ -170,287 +170,185 @@ public function test_state_and_config_existing_props_can_be_overwritten() { public function test_state_and_config_existing_indexed_arrays_are_replaced() { $this->interactivity->state( 'myPlugin', array( 'a' => array( 1, 2 ) ) ); $this->interactivity->state( 'myPlugin', array( 'a' => array( 3, 4 ) ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => array( 3, 4 ) ), $this->interactivity->state( 'myPlugin' ) ); $this->interactivity->config( 'myPlugin', array( 'a' => array( 1, 2 ) ) ); $this->interactivity->config( 'myPlugin', array( 'a' => array( 3, 4 ) ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => array( 3, 4 ) ), $this->interactivity->config( 'myPlugin' ) ); } /** - * Invokes the private `print_client_interactivity` method of - * WP_Interactivity_API class. - * - * @return array|null The content of the JSON object printed on the client-side or null if nothings was printed. - */ - private function print_client_interactivity_data() { - $interactivity_data_markup = get_echo( array( $this->interactivity, 'print_client_interactivity_data' ) ); - preg_match( '/ - -SCRIPT_TAG; + $filter = $this->get_script_data_filter_result( + function () { + $this->interactivity->state( 'pluginWithEmptyState_prune', array() ); + $this->interactivity->state( 'pluginWithState_include', array( 'value' => 'excellent' ) ); + } + ); - $this->assertSame( $expected, $printed_script ); + $this->assertSame( array( 'state' => array( 'pluginWithState_include' => array( 'value' => 'excellent' ) ) ), $filter->get_args()[0][0] ); } /** * Tests that data consisting of only empty state objects is not printed. * * @ticket 60761 - * - * @covers ::print_client_interactivity_data + * @ticket 61512 */ public function test_state_not_printed_when_only_empty_arrays() { - $this->interactivity->state( 'pluginWithEmptyState_prune', array() ); - $printed_script = get_echo( array( $this->interactivity, 'print_client_interactivity_data' ) ); - $this->assertSame( '', $printed_script ); + $filter = $this->get_script_data_filter_result( + function () { + $this->interactivity->state( 'pluginWithEmptyState_prune', array() ); + } + ); + + $this->assertSame( array(), $filter->get_args()[0][0] ); } /** * Tests that nested empty state objects are printed correctly. * * @ticket 60761 - * - * @covers ::print_client_interactivity_data + * @ticket 61512 */ public function test_state_printed_correctly_with_nested_empty_array() { - $this->interactivity->state( 'myPlugin', array( 'emptyArray' => array() ) ); - $printed_script = get_echo( array( $this->interactivity, 'print_client_interactivity_data' ) ); - $expected = <<<'SCRIPT_TAG' - - -SCRIPT_TAG; + $filter = $this->get_script_data_filter_result( + function () { + $this->interactivity->state( 'myPlugin', array( 'emptyArray' => array() ) ); + } + ); - $this->assertSame( $expected, $printed_script ); + $this->assertSame( array( 'state' => array( 'myPlugin' => array( 'emptyArray' => array() ) ) ), $filter->get_args()[0][0] ); } /** * Tests that empty config objects are pruned from printed data. * * @ticket 60761 - * - * @covers ::print_client_interactivity_data + * @ticket 61512 */ public function test_config_not_printed_when_empty_array() { - $this->interactivity->config( 'pluginWithEmptyConfig_prune', array() ); - $this->interactivity->config( 'pluginWithConfig_include', array( 'value' => 'excellent' ) ); - $printed_script = get_echo( array( $this->interactivity, 'print_client_interactivity_data' ) ); - $expected = <<<'SCRIPT_TAG' - - -SCRIPT_TAG; + $filter = $this->get_script_data_filter_result( + function () { + $this->interactivity->config( 'pluginWithEmptyConfig_prune', array() ); + $this->interactivity->config( 'pluginWithConfig_include', array( 'value' => 'excellent' ) ); + } + ); - $this->assertSame( $expected, $printed_script ); + $this->assertSame( array( 'config' => array( 'pluginWithConfig_include' => array( 'value' => 'excellent' ) ) ), $filter->get_args()[0][0] ); } /** * Tests that data consisting of only empty config objects is not printed. * * @ticket 60761 - * - * @covers ::print_client_interactivity_data + * @ticket 61512 */ public function test_config_not_printed_when_only_empty_arrays() { - $this->interactivity->config( 'pluginWithEmptyConfig_prune', array() ); - $printed_script = get_echo( array( $this->interactivity, 'print_client_interactivity_data' ) ); - $this->assertSame( '', $printed_script ); + $filter = $this->get_script_data_filter_result( + function () { + $this->interactivity->config( 'pluginWithEmptyConfig_prune', array() ); + } + ); + + $this->assertSame( array(), $filter->get_args()[0][0] ); } /** * Tests that nested empty config objects are printed correctly. * * @ticket 60761 - * - * @covers ::print_client_interactivity_data + * @ticket 61512 */ public function test_config_printed_correctly_with_nested_empty_array() { - $this->interactivity->config( 'myPlugin', array( 'emptyArray' => array() ) ); - $printed_script = get_echo( array( $this->interactivity, 'print_client_interactivity_data' ) ); - $expected = <<<'SCRIPT_TAG' - - -SCRIPT_TAG; - - $this->assertSame( $expected, $printed_script ); - } - - /** - * Tests that special characters in the initial state and configuration are - * properly escaped. - * - * @ticket 60356 - * @ticket 61170 - * - * @covers ::state - * @covers ::config - * @covers ::print_client_interactivity_data - */ - public function test_state_and_config_escape_special_characters() { - $this->interactivity->state( - 'myPlugin', - array( - 'ampersand' => '&', - 'less-than sign' => '<', - 'greater-than sign' => '>', - 'solidus' => '/', - 'line separator' => "\u{2028}", - 'paragraph separator' => "\u{2029}", - 'flag of england' => "\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}", - 'malicious script closer' => '', - 'entity-encoded malicious script closer' => '</script>', - ) + $filter = $this->get_script_data_filter_result( + function () { + $this->interactivity->config( 'myPlugin', array( 'emptyArray' => array() ) ); + } ); - $this->interactivity->config( 'myPlugin', array( 'chars' => '&<>/' ) ); - - $interactivity_data_markup = get_echo( array( $this->interactivity, 'print_client_interactivity_data' ) ); - preg_match( '~~s', $interactivity_data_markup, $interactivity_data_string ); - $expected = <<<"JSON" -{"config":{"myPlugin":{"chars":"&\\u003C\\u003E/"}},"state":{"myPlugin":{"ampersand":"&","less-than sign":"\\u003C","greater-than sign":"\\u003E","solidus":"/","line separator":"\u{2028}","paragraph separator":"\u{2029}","flag of england":"\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}","malicious script closer":"\\u003C/script\\u003E","entity-encoded malicious script closer":"</script>"}}} -JSON; - $this->assertEquals( $expected, $interactivity_data_string[1] ); - } - - /** - * Tests that special characters in the initial state and configuration are - * properly escaped when the blog_charset is not UTF-8 (unicode compatible). - * - * This this test, unicode and line terminators should be escaped to their - * JSON unicode sequences. - * - * @ticket 61170 - * - * @covers ::state - * @covers ::config - * @covers ::print_client_interactivity_data - */ - public function test_state_and_config_escape_special_characters_non_utf8() { - add_filter( 'pre_option_blog_charset', array( $this, 'charset_iso_8859_1' ) ); - $this->interactivity->state( - 'myPlugin', - array( - 'ampersand' => '&', - 'less-than sign' => '<', - 'greater-than sign' => '>', - 'solidus' => '/', - 'line separator' => "\u{2028}", - 'paragraph separator' => "\u{2029}", - 'flag of england' => "\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}", - 'malicious script closer' => '', - 'entity-encoded malicious script closer' => '</script>', - ) - ); - $this->interactivity->config( 'myPlugin', array( 'chars' => '&<>/' ) ); - - $interactivity_data_markup = get_echo( array( $this->interactivity, 'print_client_interactivity_data' ) ); - preg_match( '~~s', $interactivity_data_markup, $interactivity_data_string ); - - $expected = <<<"JSON" -{"config":{"myPlugin":{"chars":"&\\u003C\\u003E/"}},"state":{"myPlugin":{"ampersand":"&","less-than sign":"\\u003C","greater-than sign":"\\u003E","solidus":"/","line separator":"\\u2028","paragraph separator":"\\u2029","flag of england":"\\ud83c\\udff4\\udb40\\udc67\\udb40\\udc62\\udb40\\udc65\\udb40\\udc6e\\udb40\\udc67\\udb40\\udc7f","malicious script closer":"\\u003C/script\\u003E","entity-encoded malicious script closer":"</script>"}}} -JSON; - $this->assertEquals( $expected, $interactivity_data_string[1] ); + $this->assertSame( array( 'config' => array( 'myPlugin' => array( 'emptyArray' => array() ) ) ), $filter->get_args()[0][0] ); } /** @@ -467,7 +365,7 @@ public function test_state_without_namespace() { $this->interactivity->state( 'myPlugin', array( 'a' => 1 ) ); $this->interactivity->state( 'otherPlugin', array( 'b' => 2 ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => 1 ), $this->interactivity->state() ); @@ -488,7 +386,7 @@ public function test_state_with_data_and_invalid_namespace() { $this->interactivity->state( 'myPlugin', array( 'a' => 1 ) ); $this->interactivity->state( 'otherPlugin', array( 'b' => 2 ) ); - $this->assertEquals( + $this->assertSame( array(), $this->interactivity->state( null, array( 'newProp' => 'value' ) ) ); @@ -508,7 +406,7 @@ public function test_state_with_empty_string_as_namespace() { $this->interactivity->state( 'myPlugin', array( 'a' => 1 ) ); $this->interactivity->state( 'otherPlugin', array( 'b' => 2 ) ); - $this->assertEquals( + $this->assertSame( array(), $this->interactivity->state( '' ) ); @@ -524,7 +422,7 @@ public function test_state_with_empty_string_as_namespace() { * @expectedIncorrectUsage WP_Interactivity_API::state */ public function test_state_without_namespace_outside_directive_processing() { - $this->assertEquals( + $this->assertSame( array(), $this->interactivity->state() ); @@ -550,11 +448,11 @@ public function test_get_context_with_namespace() { ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => 1 ), $this->interactivity->get_context( 'myPlugin' ) ); - $this->assertEquals( + $this->assertSame( array( 'b' => 2 ), $this->interactivity->get_context( 'otherPlugin' ) ); @@ -580,7 +478,7 @@ public function test_get_context_without_namespace() { ) ); - $this->assertEquals( + $this->assertSame( array( 'a' => 1 ), $this->interactivity->get_context() ); @@ -598,7 +496,7 @@ public function test_get_context_with_empty_context_stack() { $this->set_internal_namespace_stack( 'myPlugin' ); $this->set_internal_context_stack(); - $this->assertEquals( + $this->assertSame( array(), $this->interactivity->get_context( 'myPlugin' ) ); @@ -623,7 +521,7 @@ public function test_get_context_with_undefined_namespace() { ) ); - $this->assertEquals( + $this->assertSame( array(), $this->interactivity->get_context( 'otherPlugin' ) ); @@ -648,7 +546,7 @@ public function test_get_context_with_empty_namespace() { ) ); - $this->assertEquals( + $this->assertSame( array(), $this->interactivity->get_context( '' ) ); @@ -666,7 +564,7 @@ public function test_get_context_with_empty_namespace() { */ public function test_get_context_outside_of_directive_processing() { $context = $this->interactivity->get_context(); - $this->assertEquals( array(), $context ); + $this->assertSame( array(), $context ); } /** @@ -681,55 +579,55 @@ public function test_extract_directive_value() { $extract_directive_value->setAccessible( true ); $result = $extract_directive_value->invoke( $this->interactivity, 'state.foo', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', 'state.foo' ), $result ); + $this->assertSame( array( 'myPlugin', 'state.foo' ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::state.foo', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', 'state.foo' ), $result ); + $this->assertSame( array( 'otherPlugin', 'state.foo' ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, '{ "isOpen": false }', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', array( 'isOpen' => false ) ), $result ); + $this->assertSame( array( 'myPlugin', array( 'isOpen' => false ) ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::{ "isOpen": false }', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', array( 'isOpen' => false ) ), $result ); + $this->assertSame( array( 'otherPlugin', array( 'isOpen' => false ) ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'true', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', true ), $result ); + $this->assertSame( array( 'myPlugin', true ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'false', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', false ), $result ); + $this->assertSame( array( 'myPlugin', false ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'null', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', null ), $result ); + $this->assertSame( array( 'myPlugin', null ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, '100', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', 100 ), $result ); + $this->assertSame( array( 'myPlugin', 100 ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, '1.2', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', 1.2 ), $result ); + $this->assertSame( array( 'myPlugin', 1.2 ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, '1.2.3', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', '1.2.3' ), $result ); + $this->assertSame( array( 'myPlugin', '1.2.3' ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::true', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', true ), $result ); + $this->assertSame( array( 'otherPlugin', true ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::false', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', false ), $result ); + $this->assertSame( array( 'otherPlugin', false ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::null', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', null ), $result ); + $this->assertSame( array( 'otherPlugin', null ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::100', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', 100 ), $result ); + $this->assertSame( array( 'otherPlugin', 100 ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::1.2', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', 1.2 ), $result ); + $this->assertSame( array( 'otherPlugin', 1.2 ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::1.2.3', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', '1.2.3' ), $result ); + $this->assertSame( array( 'otherPlugin', '1.2.3' ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, 'otherPlugin::[{"o":4}, null, 3e6]', 'myPlugin' ); - $this->assertEquals( array( 'otherPlugin', array( array( 'o' => 4 ), null, 3000000.0 ) ), $result ); + $this->assertSame( array( 'otherPlugin', array( array( 'o' => 4 ), null, 3000000.0 ) ), $result ); } /** @@ -744,26 +642,26 @@ public function test_extract_directive_value_empty_values() { $extract_directive_value->setAccessible( true ); $result = $extract_directive_value->invoke( $this->interactivity, '', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', null ), $result ); + $this->assertSame( array( 'myPlugin', null ), $result ); // This is a boolean attribute. $result = $extract_directive_value->invoke( $this->interactivity, true, 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', null ), $result ); + $this->assertSame( array( 'myPlugin', null ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, false, 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', null ), $result ); + $this->assertSame( array( 'myPlugin', null ), $result ); $result = $extract_directive_value->invoke( $this->interactivity, null, 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', null ), $result ); + $this->assertSame( array( 'myPlugin', null ), $result ); // A string ending in `::` without any extra characters is not considered a // namespace. $result = $extract_directive_value->invoke( $this->interactivity, 'myPlugin::', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', 'myPlugin::' ), $result ); + $this->assertSame( array( 'myPlugin', 'myPlugin::' ), $result ); // A namespace with invalid characters is not considered a valid namespace. $result = $extract_directive_value->invoke( $this->interactivity, '$myPlugin::state.foo', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', '$myPlugin::state.foo' ), $result ); + $this->assertSame( array( 'myPlugin', '$myPlugin::state.foo' ), $result ); } /** @@ -779,11 +677,11 @@ public function test_extract_directive_value_invalid_json() { // Invalid JSON due to missing quotes. Returns the original value. $result = $extract_directive_value->invoke( $this->interactivity, '{ isOpen: false }', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', '{ isOpen: false }' ), $result ); + $this->assertSame( array( 'myPlugin', '{ isOpen: false }' ), $result ); // Null string. Returns null. $result = $extract_directive_value->invoke( $this->interactivity, 'null', 'myPlugin' ); - $this->assertEquals( array( 'myPlugin', null ), $result ); + $this->assertSame( array( 'myPlugin', null ), $result ); } /** @@ -799,13 +697,13 @@ public function test_extract_prefix_and_suffix() { $extract_prefix_and_suffix->setAccessible( true ); $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-interactive' ); - $this->assertEquals( array( 'data-wp-interactive' ), $result ); + $this->assertSame( array( 'data-wp-interactive' ), $result ); $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-bind--src' ); - $this->assertEquals( array( 'data-wp-bind', 'src' ), $result ); + $this->assertSame( array( 'data-wp-bind', 'src' ), $result ); $result = $extract_prefix_and_suffix->invoke( $this->interactivity, 'data-wp-foo--and--bar' ); - $this->assertEquals( array( 'data-wp-foo', 'and--bar' ), $result ); + $this->assertSame( array( 'data-wp-foo', 'and--bar' ), $result ); } /** @@ -819,11 +717,11 @@ public function test_extract_prefix_and_suffix() { public function test_process_directives_do_nothing_without_directives() { $html = '
Inner content here
'; $processed_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( $html, $processed_html ); + $this->assertSame( $html, $processed_html ); $html = '
ContentMore Content
'; $processed_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( $html, $processed_html ); + $this->assertSame( $html, $processed_html ); } /** @@ -840,7 +738,7 @@ public function test_process_directives_changes_html_with_balanced_tags() { $processed_html = $this->interactivity->process_directives( $html ); $p = new WP_HTML_Tag_Processor( $processed_html ); $p->next_tag(); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -853,7 +751,7 @@ public function test_process_directives_changes_html_with_balanced_tags() { public function test_process_directives_doesnt_fail_with_unknown_directives() { $html = '
Text
'; $processed_html = $this->interactivity->process_directives( $html ); - $this->assertEquals( $html, $processed_html ); + $this->assertSame( $html, $processed_html ); } /** @@ -876,9 +774,9 @@ public function test_process_directives_process_the_directives_in_the_correct_or $processed_html = $this->interactivity->process_directives( $html ); $p = new WP_HTML_Tag_Processor( $processed_html ); $p->next_tag(); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); - $this->assertEquals( 'some-class', $p->get_attribute( 'class' ) ); - $this->assertEquals( 'display:none;', $p->get_attribute( 'style' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-class', $p->get_attribute( 'class' ) ); + $this->assertSame( 'display:none;', $p->get_attribute( 'style' ) ); $this->assertStringContainsString( 'Updated', $p->get_updated_html() ); $this->assertStringNotContainsString( 'Text', $p->get_updated_html() ); } @@ -952,7 +850,7 @@ public function test_process_directives_changes_html_if_contains_svgs() { $processed_html = $this->interactivity->process_directives( $html ); $p = new WP_HTML_Tag_Processor( $processed_html ); $p->next_tag( 'div' ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -1017,7 +915,7 @@ public function test_process_directives_change_html_if_contains_math() { $p->next_tag( 'math' ); $this->assertNull( $p->get_attribute( 'id' ) ); $p->next_tag( 'div' ); - $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) ); + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** @@ -1121,16 +1019,16 @@ public function offsetUnset( $offset ): void {} $this->set_internal_namespace_stack( 'myPlugin' ); $result = $this->evaluate( 'state.key' ); - $this->assertEquals( 'myPlugin-state', $result ); + $this->assertSame( 'myPlugin-state', $result ); $result = $this->evaluate( 'context.key' ); - $this->assertEquals( 'myPlugin-context', $result ); + $this->assertSame( 'myPlugin-context', $result ); $result = $this->evaluate( 'otherPlugin::state.key' ); - $this->assertEquals( 'otherPlugin-state', $result ); + $this->assertSame( 'otherPlugin-state', $result ); $result = $this->evaluate( 'otherPlugin::context.key' ); - $this->assertEquals( 'otherPlugin-context', $result ); + $this->assertSame( 'otherPlugin-context', $result ); $result = $this->evaluate( 'state.obj.prop' ); $this->assertSame( 'object property', $result ); @@ -1241,16 +1139,16 @@ public function test_evaluate_nested_value() { $this->set_internal_namespace_stack( 'myPlugin' ); $result = $this->evaluate( 'state.nested.key' ); - $this->assertEquals( 'myPlugin-state-nested', $result ); + $this->assertSame( 'myPlugin-state-nested', $result ); $result = $this->evaluate( 'context.nested.key' ); - $this->assertEquals( 'myPlugin-context-nested', $result ); + $this->assertSame( 'myPlugin-context-nested', $result ); $result = $this->evaluate( 'otherPlugin::state.nested.key' ); - $this->assertEquals( 'otherPlugin-state-nested', $result ); + $this->assertSame( 'otherPlugin-state-nested', $result ); $result = $this->evaluate( 'otherPlugin::context.nested.key' ); - $this->assertEquals( 'otherPlugin-context-nested', $result ); + $this->assertSame( 'otherPlugin-context-nested', $result ); } /** @@ -1399,7 +1297,6 @@ public function test_evaluate_derived_state_defined_in_different_namespace() { $this->assertSame( "Derived state: otherPlugin-state\nDerived context: otherPlugin-context", $result ); } - /** * Tests the `evaluate` method for derived state functions that throw. * @@ -1424,6 +1321,29 @@ public function test_evaluate_derived_state_that_throws() { $this->assertNull( $result ); } + /** + * Tests the `evaluate` method for derived state intermediate values. + * + * @ticket 61741 + * + * @covers ::evaluate + */ + public function test_evaluate_derived_state_intermediate() { + $this->interactivity->state( + 'myPlugin', + array( + 'derivedState' => function () { + return array( 'property' => 'value' ); + }, + ) + ); + $this->set_internal_context_stack(); + $this->set_internal_namespace_stack( 'myPlugin' ); + + $result = $this->evaluate( 'state.derivedState.property' ); + $this->assertSame( 'value', $result ); + } + /** * Tests the `kebab_to_camel_case` method. * diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php index 5812c061666c8..d674f5e54ce06 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php @@ -24,14 +24,14 @@ public function test_get_content_between_balanced_template_tags_standard_tags() $content = ''; $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); - $this->assertEquals( 'Text', $p->get_content_between_balanced_template_tags() ); + $this->assertSame( 'Text', $p->get_content_between_balanced_template_tags() ); $content = ''; $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); - $this->assertEquals( 'Text', $p->get_content_between_balanced_template_tags() ); + $this->assertSame( 'Text', $p->get_content_between_balanced_template_tags() ); $p->next_tag(); - $this->assertEquals( 'More text', $p->get_content_between_balanced_template_tags() ); + $this->assertSame( 'More text', $p->get_content_between_balanced_template_tags() ); } /** @@ -46,7 +46,7 @@ public function test_get_content_between_balanced_template_tags_empty_tag() { $content = ''; $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); - $this->assertEquals( '', $p->get_content_between_balanced_template_tags() ); + $this->assertSame( '', $p->get_content_between_balanced_template_tags() ); } /** @@ -81,12 +81,12 @@ public function test_get_content_between_balanced_template_tags_nested_tags() { $content = ''; $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); - $this->assertEquals( 'ContentMore Content', $p->get_content_between_balanced_template_tags() ); + $this->assertSame( 'ContentMore Content', $p->get_content_between_balanced_template_tags() ); $content = ''; $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); - $this->assertEquals( '', $p->get_content_between_balanced_template_tags() ); + $this->assertSame( '', $p->get_content_between_balanced_template_tags() ); } /** @@ -131,7 +131,7 @@ public function test_get_content_between_balanced_template_tags_with_unbalanced_ $content = ''; $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); - $this->assertEquals( 'Missing opening span', $p->get_content_between_balanced_template_tags() ); + $this->assertSame( 'Missing opening span', $p->get_content_between_balanced_template_tags() ); } /** @@ -163,10 +163,10 @@ public function test_get_content_between_balanced_template_tags_positions_cursor $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); $p->get_content_between_balanced_template_tags(); - $this->assertEquals( 'TEMPLATE', $p->get_tag() ); + $this->assertSame( 'TEMPLATE', $p->get_tag() ); $this->assertTrue( $p->is_tag_closer() ); $p->next_tag(); - $this->assertEquals( 'DIV', $p->get_tag() ); + $this->assertSame( 'DIV', $p->get_tag() ); } /** @@ -479,13 +479,13 @@ public function test_append_content_after_template_tag_closer_simple_tags() { $this->assertTrue( $result ); $this->assertEquals( $content_1 . $content_2, $p ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); - $this->assertEquals( 'content-2', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-2', $p->get_attribute( 'class' ) ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); $result = $p->append_content_after_template_tag_closer( $content_3 ); $this->assertTrue( $result ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); $this->assertEquals( $content_1 . $content_2 . $content_3, $p ); - $this->assertEquals( 'content-3', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-3', $p->get_attribute( 'class' ) ); } /** @@ -509,7 +509,7 @@ public function test_append_content_after_template_tag_closer_in_the_middle_of_t $this->assertTrue( $result ); $this->assertEquals( $content_1 . $content_2 . $content_3, $p ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); - $this->assertEquals( 'content-3', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-3', $p->get_attribute( 'class' ) ); $p = new WP_Interactivity_API_Directives_Processor( $content_1 . $content_3 ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); @@ -518,7 +518,7 @@ public function test_append_content_after_template_tag_closer_in_the_middle_of_t $this->assertTrue( $result ); $this->assertEquals( $content_1 . $content_4 . $content_3, $p ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); - $this->assertEquals( 'content-4', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-4', $p->get_attribute( 'class' ) ); } /** @@ -558,16 +558,16 @@ public function test_append_content_after_template_tag_closer_multiple_calls_in_ $this->assertTrue( $result ); $this->assertEquals( $content_1 . $content_2, $p ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); - $this->assertEquals( 'content-2', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-2', $p->get_attribute( 'class' ) ); // Rewinds to the first template. $p->seek( 'first template' ); $p->release_bookmark( 'first template' ); - $this->assertEquals( 'content-1', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-1', $p->get_attribute( 'class' ) ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); $result = $p->append_content_after_template_tag_closer( $content_3 ); $this->assertEquals( $content_1 . $content_3 . $content_2, $p ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); - $this->assertEquals( 'content-3', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-3', $p->get_attribute( 'class' ) ); } /** @@ -615,9 +615,9 @@ public function test_append_content_after_template_tag_closer_with_existing_tags $this->assertTrue( $result ); $this->assertEquals( $content_1 . $content_2, $p ); $p->next_tag(); - $this->assertEquals( 'content-2-template-1', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-2-template-1', $p->get_attribute( 'class' ) ); $p->next_tag(); - $this->assertEquals( 'content-2-template-2', $p->get_attribute( 'class' ) ); + $this->assertSame( 'content-2-template-2', $p->get_attribute( 'class' ) ); $p->next_tag( array( 'tag_closers' => 'visit' ) ); $result = $p->append_content_after_template_tag_closer( $content_3 ); $this->assertTrue( $result ); @@ -640,7 +640,7 @@ public function test_append_content_after_template_tag_closer_empty() { $result = $p->append_content_after_template_tag_closer( '' ); $this->assertFalse( $result ); $this->assertEquals( $content, $p ); - $this->assertEquals( 'TEMPLATE', $p->get_tag() ); // It didn't move. + $this->assertSame( 'TEMPLATE', $p->get_tag() ); // It didn't move. $this->assertTrue( $p->is_tag_closer() ); // It didn't move. } @@ -694,7 +694,7 @@ public function test_next_balanced_tag_closer_tag_standard_tags() { $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); $this->assertTrue( $p->next_balanced_tag_closer_tag() ); - $this->assertEquals( 'DIV', $p->get_tag() ); + $this->assertSame( 'DIV', $p->get_tag() ); $this->assertTrue( $p->is_tag_closer() ); } @@ -731,14 +731,14 @@ public function test_next_balanced_tag_closer_tag_nested_tags() { $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); $this->assertTrue( $p->next_balanced_tag_closer_tag() ); - $this->assertEquals( 'DIV', $p->get_tag() ); + $this->assertSame( 'DIV', $p->get_tag() ); $this->assertTrue( $p->is_tag_closer() ); $content = '
Nested content
'; $p = new WP_Interactivity_API_Directives_Processor( $content ); $p->next_tag(); $this->assertTrue( $p->next_balanced_tag_closer_tag() ); - $this->assertEquals( 'DIV', $p->get_tag() ); + $this->assertSame( 'DIV', $p->get_tag() ); $this->assertTrue( $p->is_tag_closer() ); $this->assertFalse( $p->next_tag() ); // No more content. } @@ -793,7 +793,7 @@ public function test_skip_to_tag_closer() { $p->next_tag(); $this->assertTrue( $p->skip_to_tag_closer() ); $this->assertTrue( $p->is_tag_closer() ); - $this->assertEquals( 'DIV', $p->get_tag() ); + $this->assertSame( 'DIV', $p->get_tag() ); } /** diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPIFunctions.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPIFunctions.php index 259b8a32b744b..7c933109154a4 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPIFunctions.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPIFunctions.php @@ -76,7 +76,7 @@ public function test_process_directives_of_single_interactive_block() { $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'interactive/block-1' ) ); - $this->assertEquals( '1', $p->get_attribute( 'value' ) ); + $this->assertSame( '1', $p->get_attribute( 'value' ) ); } /** @@ -97,13 +97,13 @@ public function test_process_directives_of_multiple_interactive_blocks_in_parall $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'interactive/block-1' ) ); - $this->assertEquals( '1', $p->get_attribute( 'value' ) ); + $this->assertSame( '1', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'interactive/block-2' ) ); - $this->assertEquals( '2', $p->get_attribute( 'value' ) ); + $this->assertSame( '2', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'non-interactive/block-3' ) ); $this->assertNull( $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'interactive/block-4' ) ); - $this->assertEquals( '4', $p->get_attribute( 'value' ) ); + $this->assertSame( '4', $p->get_attribute( 'value' ) ); } /** @@ -122,7 +122,7 @@ public function test_process_directives_of_interactive_block_inside_non_interact $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'interactive/block-2' ) ); - $this->assertEquals( '2', $p->get_attribute( 'value' ) ); + $this->assertSame( '2', $p->get_attribute( 'value' ) ); } /** @@ -143,9 +143,9 @@ public function test_process_directives_of_multiple_interactive_blocks_inside_no $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'interactive/block-2' ) ); - $this->assertEquals( '2', $p->get_attribute( 'value' ) ); + $this->assertSame( '2', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'interactive/block-3' ) ); - $this->assertEquals( '3', $p->get_attribute( 'value' ) ); + $this->assertSame( '3', $p->get_attribute( 'value' ) ); } /** @@ -168,9 +168,9 @@ public function test_process_directives_of_interactive_block_inside_multiple_non $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'interactive/block-2' ) ); - $this->assertEquals( '2', $p->get_attribute( 'value' ) ); + $this->assertSame( '2', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'interactive/block-4' ) ); - $this->assertEquals( '4', $p->get_attribute( 'value' ) ); + $this->assertSame( '4', $p->get_attribute( 'value' ) ); } /** @@ -190,7 +190,7 @@ public function test_process_directives_of_interactive_block_containing_non_inte $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'interactive/block-1' ) ); - $this->assertEquals( '1', $p->get_attribute( 'value' ) ); + $this->assertSame( '1', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'non-interactive/block-2' ) ); $this->assertNull( $p->get_attribute( 'value' ) ); } @@ -212,9 +212,9 @@ public function test_process_directives_of_interactive_block_containing_non_inte $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'interactive/block-1' ) ); - $this->assertEquals( '1', $p->get_attribute( 'value' ) ); + $this->assertSame( '1', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'non-interactive/block-2' ) ); - $this->assertEquals( '1', $p->get_attribute( 'value' ) ); + $this->assertSame( '1', $p->get_attribute( 'value' ) ); } /** @@ -238,13 +238,13 @@ public function test_process_directives_of_interactive_block_containing_nested_i $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'interactive/block-1' ) ); - $this->assertEquals( '1', $p->get_attribute( 'value' ) ); + $this->assertSame( '1', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'interactive/block-2' ) ); - $this->assertEquals( '2', $p->get_attribute( 'value' ) ); + $this->assertSame( '2', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'non-interactive/block-3' ) ); - $this->assertEquals( '2', $p->get_attribute( 'value' ) ); + $this->assertSame( '2', $p->get_attribute( 'value' ) ); $p->next_tag( array( 'class_name' => 'non-interactive/block-4' ) ); - $this->assertEquals( '1', $p->get_attribute( 'value' ) ); + $this->assertSame( '1', $p->get_attribute( 'value' ) ); } /** @@ -288,7 +288,7 @@ public function test_process_directives_only_process_the_root_interactive_blocks $html = '
'; $this->data_wp_test_processor_count = 0; wp_interactivity_process_directives( $html ); - $this->assertEquals( 1, $this->data_wp_test_processor_count ); + $this->assertSame( 1, $this->data_wp_test_processor_count ); register_block_type( 'test/custom-directive-block', @@ -309,7 +309,7 @@ public function test_process_directives_only_process_the_root_interactive_blocks $this->data_wp_test_processor_count = 0; do_blocks( $post_content ); unregister_block_type( 'test/custom-directive-block' ); - $this->assertEquals( 2, $this->data_wp_test_processor_count ); + $this->assertSame( 2, $this->data_wp_test_processor_count ); $directive_processors->setValue( null, $old_directive_processors ); } @@ -332,18 +332,20 @@ public function test_process_directives_when_block_is_filtered() { ), ) ); - function test_render_block_data( $parsed_block ) { + + $test_render_block_data = static function ( $parsed_block ) { $parsed_block['testKey'] = true; return $parsed_block; - } - add_filter( 'render_block_data', 'test_render_block_data' ); + }; + + add_filter( 'render_block_data', $test_render_block_data ); $post_content = ''; $processed_content = do_blocks( $post_content ); $processor = new WP_HTML_Tag_Processor( $processed_content ); $processor->next_tag( array( 'data-wp-interactive' => 'nameSpace' ) ); - remove_filter( 'render_block_data', 'test_render_block_data' ); + remove_filter( 'render_block_data', $test_render_block_data ); unregister_block_type( 'test/custom-directive-block' ); - $this->assertEquals( 'test', $processor->get_attribute( 'value' ) ); + $this->assertSame( 'test', $processor->get_attribute( 'value' ) ); } /** @@ -352,27 +354,43 @@ function test_render_block_data( $parsed_block ) { * * @ticket 60356 * - * @covers wp_interactivity_data_wp_context + * @covers wp_interactivity_data_wp_context + * @dataProvider data_wp_interactivity_data_wp_context_with_different_arrays + * + * @param array $context Context to encode. + * @param string $expected Expected function output. */ - public function test_wp_interactivity_data_wp_context_with_different_arrays() { - $this->assertEquals( 'data-wp-context=\'{}\'', wp_interactivity_data_wp_context( array() ) ); - $this->assertEquals( - 'data-wp-context=\'{"a":1,"b":"2","c":true}\'', - wp_interactivity_data_wp_context( - array( + public function test_wp_interactivity_data_wp_context_with_different_arrays( $context, $expected ) { + $this->assertSame( $expected, wp_interactivity_data_wp_context( $context ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_wp_interactivity_data_wp_context_with_different_arrays() { + return array( + 'empty array' => array( + 'context' => array(), + 'expected' => 'data-wp-context=\'{}\'', + ), + 'associative array with mixed values' => array( + 'context' => array( 'a' => 1, 'b' => '2', 'c' => true, - ) - ) - ); - $this->assertEquals( - 'data-wp-context=\'{"a":[1,2]}\'', - wp_interactivity_data_wp_context( array( 'a' => array( 1, 2 ) ) ) - ); - $this->assertEquals( - 'data-wp-context=\'[1,2]\'', - wp_interactivity_data_wp_context( array( 1, 2 ) ) + ), + 'expected' => 'data-wp-context=\'{"a":1,"b":"2","c":true}\'', + ), + 'associative array with nested array as value' => array( + 'context' => array( 'a' => array( 1, 2 ) ), + 'expected' => 'data-wp-context=\'{"a":[1,2]}\'', + ), + 'array without keys, integer values' => array( + 'context' => array( 1, 2 ), + 'expected' => 'data-wp-context=\'[1,2]\'', + ), ); } @@ -382,28 +400,48 @@ public function test_wp_interactivity_data_wp_context_with_different_arrays() { * * @ticket 60356 * - * @covers wp_interactivity_data_wp_context + * @covers wp_interactivity_data_wp_context + * @dataProvider data_wp_interactivity_data_wp_context_with_different_arrays_and_a_namespace + * + * @param array $context Context to encode. + * @param string $store Store namespace. + * @param string $expected Expected function output. */ - public function test_wp_interactivity_data_wp_context_with_different_arrays_and_a_namespace() { - $this->assertEquals( 'data-wp-context=\'myPlugin::{}\'', wp_interactivity_data_wp_context( array(), 'myPlugin' ) ); - $this->assertEquals( - 'data-wp-context=\'myPlugin::{"a":1,"b":"2","c":true}\'', - wp_interactivity_data_wp_context( - array( + public function test_wp_interactivity_data_wp_context_with_different_arrays_and_a_namespace( $context, $store, $expected ) { + $this->assertSame( $expected, wp_interactivity_data_wp_context( $context, $store ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_wp_interactivity_data_wp_context_with_different_arrays_and_a_namespace() { + return array( + 'empty array' => array( + 'context' => array(), + 'store' => 'myPlugin', + 'expected' => 'data-wp-context=\'myPlugin::{}\'', + ), + 'associative array with mixed values' => array( + 'context' => array( 'a' => 1, 'b' => '2', 'c' => true, ), - 'myPlugin' - ) - ); - $this->assertEquals( - 'data-wp-context=\'myPlugin::{"a":[1,2]}\'', - wp_interactivity_data_wp_context( array( 'a' => array( 1, 2 ) ), 'myPlugin' ) - ); - $this->assertEquals( - 'data-wp-context=\'myPlugin::[1,2]\'', - wp_interactivity_data_wp_context( array( 1, 2 ), 'myPlugin' ) + 'store' => 'myPlugin', + 'expected' => 'data-wp-context=\'myPlugin::{"a":1,"b":"2","c":true}\'', + ), + 'associative array with nested array as value' => array( + 'context' => array( 'a' => array( 1, 2 ) ), + 'store' => 'myPlugin', + 'expected' => 'data-wp-context=\'myPlugin::{"a":[1,2]}\'', + ), + 'array without keys, integer values' => array( + 'context' => array( 1, 2 ), + 'store' => 'myPlugin', + 'expected' => 'data-wp-context=\'myPlugin::[1,2]\'', + ), ); } @@ -415,13 +453,40 @@ public function test_wp_interactivity_data_wp_context_with_different_arrays_and_ * * @ticket 60356 * - * @covers wp_interactivity_data_wp_context + * @covers wp_interactivity_data_wp_context + * @dataProvider data_wp_interactivity_data_wp_context_with_json_flags + * + * @param array $context Context to encode. + * @param string $expected Expected function output. */ - public function test_wp_interactivity_data_wp_context_with_json_flags() { - $this->assertEquals( 'data-wp-context=\'{"tag":"\u003Cfoo\u003E"}\'', wp_interactivity_data_wp_context( array( 'tag' => '' ) ) ); - $this->assertEquals( 'data-wp-context=\'{"apos":"\u0027bar\u0027"}\'', wp_interactivity_data_wp_context( array( 'apos' => "'bar'" ) ) ); - $this->assertEquals( 'data-wp-context=\'{"quot":"\u0022baz\u0022"}\'', wp_interactivity_data_wp_context( array( 'quot' => '"baz"' ) ) ); - $this->assertEquals( 'data-wp-context=\'{"amp":"T\u0026T"}\'', wp_interactivity_data_wp_context( array( 'amp' => 'T&T' ) ) ); + public function test_wp_interactivity_data_wp_context_with_json_flags( $context, $expected ) { + $this->assertSame( $expected, wp_interactivity_data_wp_context( $context ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_wp_interactivity_data_wp_context_with_json_flags() { + return array( + 'value contains <> brackets' => array( + 'context' => array( 'tag' => '' ), + 'expected' => 'data-wp-context=\'{"tag":"\u003Cfoo\u003E"}\'', + ), + 'value contains single quote chars' => array( + 'context' => array( 'apos' => "'bar'" ), + 'expected' => 'data-wp-context=\'{"apos":"\u0027bar\u0027"}\'', + ), + 'value contains double quote chars' => array( + 'context' => array( 'quot' => '"baz"' ), + 'expected' => 'data-wp-context=\'{"quot":"\u0022baz\u0022"}\'', + ), + 'value contains & ampersand' => array( + 'context' => array( 'amp' => 'T&T' ), + 'expected' => 'data-wp-context=\'{"amp":"T\u0026T"}\'', + ), + ); } /** @@ -448,7 +513,7 @@ public function test_process_directives_in_tags_that_dont_visit_closer_tag() { $processor = new WP_HTML_Tag_Processor( $processed_content ); $processor->next_tag( array( 'class_name' => 'test' ) ); unregister_block_type( 'test/custom-directive-block' ); - $this->assertEquals( '1', $processor->get_attribute( 'src' ) ); + $this->assertSame( '1', $processor->get_attribute( 'src' ) ); } /** @@ -488,8 +553,8 @@ public function test_process_context_directive_in_void_tags() { ); $second_input_value = $processor->get_attribute( 'value' ); unregister_block_type( 'test/custom-directive-block' ); - $this->assertEquals( 'inner', $first_input_value ); - $this->assertEquals( 'outer', $second_input_value ); + $this->assertSame( 'inner', $first_input_value ); + $this->assertSame( 'outer', $second_input_value ); } /** diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php index 36bf2baf123d3..b5cca81047172 100644 --- a/tests/phpunit/tests/kses.php +++ b/tests/phpunit/tests/kses.php @@ -1353,6 +1353,11 @@ public function data_safecss_filter_attr() { 'css' => 'background-repeat: no-repeat', 'expected' => 'background-repeat: no-repeat', ), + // `opacity` introduced in 6.7. + array( + 'css' => 'opacity: 10', + 'expected' => 'opacity: 10', + ), ); } @@ -1936,11 +1941,13 @@ public function filter_wp_kses_object_added_in_html_filter( $tags, $context ) { * * @ticket 61009 * + * @dataProvider data_html_containing_various_kinds_of_html_comments + * * @param string $html_comment HTML containing a comment; must not be a valid comment * but must be syntax which a browser interprets as a comment. * @param string $expected_output How `wp_kses()` ought to transform the comment. */ - public function wp_kses_preserves_html_comments( $html_comment, $expected_output ) { + public function test_wp_kses_preserves_html_comments( $html_comment, $expected_output ) { $this->assertSame( $expected_output, wp_kses( $html_comment, array() ), @@ -1957,6 +1964,7 @@ public static function data_html_containing_various_kinds_of_html_comments() { return array( 'Normative HTML comment' => array( 'beforeafter', 'beforeafter' ), 'Closing tag with invalid tag name' => array( 'beforeafter', 'beforeafter' ), + 'Incorrectly opened comment (Markup declaration)' => array( 'beforeafter', 'beforeafter' ), ); } diff --git a/tests/phpunit/tests/link/getDashboardUrl.php b/tests/phpunit/tests/link/getDashboardUrl.php index dafaa7b62e96a..2864db235ffb9 100644 --- a/tests/phpunit/tests/link/getDashboardUrl.php +++ b/tests/phpunit/tests/link/getDashboardUrl.php @@ -12,11 +12,7 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { } public static function wpTearDownAfterClass() { - if ( is_multisite() ) { - wpmu_delete_user( self::$user_id ); - } else { - wp_delete_user( self::$user_id ); - } + self::delete_user( self::$user_id ); } /** diff --git a/tests/phpunit/tests/link/getEditCommentLink.php b/tests/phpunit/tests/link/getEditCommentLink.php new file mode 100644 index 0000000000000..1d574d40286d1 --- /dev/null +++ b/tests/phpunit/tests/link/getEditCommentLink.php @@ -0,0 +1,130 @@ +comment->create( array( 'comment_content' => 'Test comment' ) ); + + self::$user_ids = array( + 'admin' => $factory->user->create( array( 'role' => 'administrator' ) ), + 'subscriber' => $factory->user->create( array( 'role' => 'subscriber' ) ), + ); + } + + public static function wpTearDownAfterClass() { + // Delete the test comment. + wp_delete_comment( self::$comment_id, true ); + + // Delete the test users. + foreach ( self::$user_ids as $user_id ) { + self::delete_user( $user_id ); + } + } + + public function set_up() { + parent::set_up(); + wp_set_current_user( self::$user_ids['admin'] ); + } + + /** + * Tests that get_edit_comment_link() returns the correct URL by default. + */ + public function test_get_edit_comment_link_default() { + $comment_id = self::$comment_id; + $expected_url = admin_url( 'comment.php?action=editcomment&c=' . $comment_id ); + $actual_url = get_edit_comment_link( $comment_id ); + + $this->assertSame( $expected_url, $actual_url ); + } + + /** + * Tests that get_edit_comment_link() returns the correct URL with a context of 'display'. + * + * The expected result should include HTML entities. + * + * @ticket 61727 + */ + public function test_get_edit_comment_link_display_context() { + $comment_id = self::$comment_id; + $expected_url = admin_url( 'comment.php?action=editcomment&c=' . $comment_id ); + $actual_url = get_edit_comment_link( $comment_id, 'display' ); + + $this->assertSame( $expected_url, $actual_url ); + } + + /** + * Tests that get_edit_comment_link() returns the correct URL with a context of 'url'. + * + * The expected result should not include HTML entities. + * + * @ticket 61727 + */ + public function test_get_edit_comment_link_url_context() { + $comment_id = self::$comment_id; + $expected_url = admin_url( 'comment.php?action=editcomment&c=' . $comment_id ); + $actual_url = get_edit_comment_link( $comment_id, 'url' ); + + $this->assertSame( $expected_url, $actual_url ); + } + + /** + * Tests that get_edit_comment_link() returns nothing if the comment ID is invalid. + * + * @ticket 61727 + */ + public function test_get_edit_comment_link_invalid_comment() { + $comment_id = 12345; + $actual_url_display = get_edit_comment_link( $comment_id, 'display' ); + $actual_url = get_edit_comment_link( $comment_id, 'url' ); + + $this->assertNull( $actual_url_display ); + $this->assertNull( $actual_url ); + } + + /** + * Tests that get_edit_comment_link() returns nothing if the current user cannot edit it. + */ + public function test_get_edit_comment_link_user_cannot_edit() { + wp_set_current_user( self::$user_ids['subscriber'] ); + $comment_id = self::$comment_id; + $actual_url_display = get_edit_comment_link( $comment_id, 'display' ); + $actual_url = get_edit_comment_link( $comment_id, 'url' ); + + $this->assertNull( $actual_url_display ); + $this->assertNull( $actual_url ); + } + + /** + * Tests that the 'get_edit_comment_link' filter works as expected, including the additional parameters. + * + * @ticket 61727 + */ + public function test_get_edit_comment_link_filter() { + $comment_id = self::$comment_id; + $expected_url_display = admin_url( 'comment-test.php?context=display' ); + $expected_url = admin_url( 'comment-test.php?context=url' ); + + add_filter( + 'get_edit_comment_link', + function ( $location, $comment_id, $context ) { + return admin_url( 'comment-test.php?context=' . $context ); + }, + 10, + 3 + ); + + $actual_url_display = get_edit_comment_link( $comment_id, 'display' ); + $actual_url = get_edit_comment_link( $comment_id, 'url' ); + + // Assert the final URLs are as expected + $this->assertSame( $expected_url_display, $actual_url_display ); + $this->assertSame( $expected_url, $actual_url ); + } +} diff --git a/tests/phpunit/tests/link/getEditTermLink.php b/tests/phpunit/tests/link/getEditTermLink.php index d0303f1b1a403..e86cd78449f3d 100644 --- a/tests/phpunit/tests/link/getEditTermLink.php +++ b/tests/phpunit/tests/link/getEditTermLink.php @@ -236,4 +236,17 @@ public function data_get_edit_term_link() { ), ); } + + /** + * Checks that `get_edit_term_link()` produces the correct URL when called without taxonomy. + * + * @ticket 61726 + */ + public function test_get_edit_term_link_without_taxonomy() { + $term = $this->get_term( 'wptests_tax', true ); + + $actual = get_edit_term_link( $term ); + $expected = sprintf( admin_url( 'term.php?taxonomy=wptests_tax&tag_ID=%d&post_type=post' ), $term ); + $this->assertSame( $expected, $actual ); + } } diff --git a/tests/phpunit/tests/load/wpIsIniValueChangeable.php b/tests/phpunit/tests/load/wpIsIniValueChangeable.php index 0f7779dc7dde6..db8dce74e7a63 100644 --- a/tests/phpunit/tests/load/wpIsIniValueChangeable.php +++ b/tests/phpunit/tests/load/wpIsIniValueChangeable.php @@ -41,7 +41,7 @@ public function data_wp_is_ini_value_changeable() { array( 'upload_tmp_dir', false ), // PHP_INI_SYSTEM. ); - if ( PHP_VERSION_ID > 70000 && extension_loaded( 'Tidy' ) ) { + if ( extension_loaded( 'Tidy' ) ) { $array[] = array( 'tidy.clean_output', true ); // PHP_INI_USER. } diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index 034830195786e..12d037961255e 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -1833,6 +1833,31 @@ public function test_wp_calculate_image_srcset_with_absolute_path_in_meta() { } } + /** + * @ticket 61690 + * @requires function imagejpeg + */ + public function test_wp_calculate_image_srcset_with_relative_content_url() { + $_SERVER['HTTPS'] = 'on'; + + add_filter( + 'upload_dir', + static function ( $upload_dir ) { + $upload_dir['baseurl'] = '/wp-content/uploads'; + return $upload_dir; + } + ); + + $image_url = wp_get_attachment_image_url( self::$large_id, 'medium' ); + $image_meta = wp_get_attachment_metadata( self::$large_id ); + + $size_array = array( 300, 225 ); + + $srcset = wp_calculate_image_srcset( $size_array, $image_url, $image_meta ); + + $this->assertStringStartsWith( '/wp-content/uploads', $srcset ); + } + /** * @ticket 33641 */ diff --git a/tests/phpunit/tests/meta.php b/tests/phpunit/tests/meta.php index ae94c35508036..857414190a2c3 100644 --- a/tests/phpunit/tests/meta.php +++ b/tests/phpunit/tests/meta.php @@ -419,6 +419,34 @@ public function test_get_metadata_with_empty_key_nested_array_value() { $this->assertSame( array( $value ), $found['foo'] ); } + /** + * @dataProvider data_get_metadata_with_non_existent_object_id + */ + public function test_get_metadata_with_non_existent_object_id( $expected, $args ) { + $this->assertSame( $expected, get_metadata( 'user', ...$args ) ); + } + + public function data_get_metadata_with_non_existent_object_id() { + return array( + 'should return empty array for default `$meta_key` and `$single` values' => array( + 'expected' => array(), + 'args' => array( PHP_INT_MAX ), + ), + 'should return empty array for default `$single` value' => array( + 'expected' => array(), + 'args' => array( PHP_INT_MAX, 'meta_key' ), + ), + 'should return empty array when `$single` is `false`' => array( + 'expected' => array(), + 'args' => array( PHP_INT_MAX, 'meta_key', false ), + ), + 'should return empty string when `$single` is `true`' => array( + 'expected' => '', + 'args' => array( PHP_INT_MAX, 'meta_key', true ), + ), + ); + } + /** Helpers */ public function updated_meta( $meta_id ) { diff --git a/tests/phpunit/tests/option/networkOption.php b/tests/phpunit/tests/option/networkOption.php index bfce484a8d38a..a4247d4926cec 100644 --- a/tests/phpunit/tests/option/networkOption.php +++ b/tests/phpunit/tests/option/networkOption.php @@ -56,6 +56,36 @@ public function test_delete_network_option_on_only_one_network() { $this->assertSame( $value, get_network_option( $id, $option, false ) ); } + /** + * Tests that calling delete_network_option() updates nooptions when option deleted. + * + * @ticket 61484 + * @ticket 61730 + * + * @covers ::delete_network_option + */ + public function test_check_delete_network_option_updates_notoptions() { + add_network_option( 1, 'foo', 'value1' ); + + delete_network_option( 1, 'foo' ); + $cache_key = is_multisite() ? '1:notoptions' : 'notoptions'; + $cache_group = is_multisite() ? 'site-options' : 'options'; + $notoptions = wp_cache_get( $cache_key, $cache_group ); + $this->assertIsArray( $notoptions, 'The notoptions cache is expected to be an array.' ); + $this->assertTrue( $notoptions['foo'], 'The deleted options is expected to be in notoptions.' ); + + if ( ! is_multisite() ) { + $network_notoptions = wp_cache_get( '1:notoptions', 'site-options' ); + $this->assertTrue( empty( $network_notoptions['foo'] ), 'The deleted option is not expected to be in network notoptions on a non-multisite.' ); + } + + $before = get_num_queries(); + get_network_option( 1, 'foo' ); + $queries = get_num_queries() - $before; + + $this->assertSame( 0, $queries, 'get_network_option should not make any database queries.' ); + } + /** * @ticket 22846 * @group ms-excluded @@ -228,4 +258,158 @@ public function test_update_network_option_array_with_object() { // Check that no new database queries were performed. $this->assertSame( $num_queries_pre_update, get_num_queries() ); } + + /** + * Tests that calling update_network_option() clears the notoptions cache. + * + * @ticket 61484 + * + * @covers ::update_network_option + */ + public function test_update_network_option_clears_the_notoptions_cache() { + $option_name = 'ticket_61484_option_to_be_created'; + $cache_key = is_multisite() ? '1:notoptions' : 'notoptions'; + $cache_group = is_multisite() ? 'site-options' : 'options'; + $notoptions = wp_cache_get( $cache_key, $cache_group ); + if ( ! is_array( $notoptions ) ) { + $notoptions = array(); + } + $notoptions[ $option_name ] = true; + wp_cache_set( $cache_key, $notoptions, $cache_group ); + $this->assertArrayHasKey( $option_name, wp_cache_get( $cache_key, $cache_group ), 'The "foobar" option should be in the notoptions cache.' ); + + update_network_option( 1, $option_name, 'baz' ); + + $updated_notoptions = wp_cache_get( $cache_key, $cache_group ); + $this->assertArrayNotHasKey( $option_name, $updated_notoptions, 'The "foobar" option should not be in the notoptions cache after updating it.' ); + } + + /** + * Tests that calling add_network_option() clears the notoptions cache. + * + * @ticket 61484 + * + * @covers ::add_network_option + */ + public function test_add_network_option_clears_the_notoptions_cache() { + $option_name = 'ticket_61484_option_to_be_created'; + $cache_key = is_multisite() ? '1:notoptions' : 'notoptions'; + $cache_group = is_multisite() ? 'site-options' : 'options'; + $notoptions = wp_cache_get( $cache_key, $cache_group ); + if ( ! is_array( $notoptions ) ) { + $notoptions = array(); + } + $notoptions[ $option_name ] = true; + wp_cache_set( $cache_key, $notoptions, $cache_group ); + $this->assertArrayHasKey( $option_name, wp_cache_get( $cache_key, $cache_group ), 'The "foobar" option should be in the notoptions cache.' ); + + add_network_option( 1, $option_name, 'baz' ); + + $updated_notoptions = wp_cache_get( $cache_key, $cache_group ); + $this->assertArrayNotHasKey( $option_name, $updated_notoptions, 'The "foobar" option should not be in the notoptions cache after updating it.' ); + } + + /** + * Test adding a previously known notoption returns the correct value. + * + * @ticket 61730 + * + * @covers ::add_network_option + * @covers ::delete_network_option + */ + public function test_adding_previous_notoption_returns_correct_value() { + $option_name = 'ticket_61730_option_to_be_created'; + + add_network_option( 1, $option_name, 'baz' ); + delete_network_option( 1, $option_name ); + + $this->assertFalse( get_network_option( 1, $option_name ), 'The option should not be found.' ); + + add_network_option( 1, $option_name, 'foo' ); + $this->assertSame( 'foo', get_network_option( 1, $option_name ), 'The option should return the newly set value.' ); + } + + /** + * Test `get_network_option()` does not use network notoptions cache for single sites. + * + * @ticket 61730 + * + * @group ms-excluded + * + * @covers ::get_network_option + */ + public function test_get_network_option_does_not_use_network_notoptions_cache_for_single_sites() { + get_network_option( 1, 'ticket_61730_notoption' ); + + $network_notoptions_cache = wp_cache_get( '1:notoptions', 'site-options' ); + $single_site_notoptions_cache = wp_cache_get( 'notoptions', 'options' ); + + $this->assertEmpty( $network_notoptions_cache, 'Network notoptions cache should not be set for single site installs.' ); + $this->assertIsArray( $single_site_notoptions_cache, 'Single site notoptions cache should be set.' ); + $this->assertArrayHasKey( 'ticket_61730_notoption', $single_site_notoptions_cache, 'The option should be in the notoptions cache.' ); + } + + /** + * Test `delete_network_option()` does not use network notoptions cache for single sites. + * + * @ticket 61730 + * @ticket 61484 + * + * @group ms-excluded + * + * @covers ::delete_network_option + */ + public function test_delete_network_option_does_not_use_network_notoptions_cache_for_single_sites() { + add_network_option( 1, 'ticket_61730_notoption', 'value' ); + delete_network_option( 1, 'ticket_61730_notoption' ); + + $network_notoptions_cache = wp_cache_get( '1:notoptions', 'site-options' ); + $single_site_notoptions_cache = wp_cache_get( 'notoptions', 'options' ); + + $this->assertEmpty( $network_notoptions_cache, 'Network notoptions cache should not be set for single site installs.' ); + $this->assertIsArray( $single_site_notoptions_cache, 'Single site notoptions cache should be set.' ); + $this->assertArrayHasKey( 'ticket_61730_notoption', $single_site_notoptions_cache, 'The option should be in the notoptions cache.' ); + } + + /** + * Test `get_network_option()` does not use single site notoptions cache for networks. + * + * @ticket 61730 + * + * @group ms-required + * + * @covers ::get_network_option + */ + public function test_get_network_option_does_not_use_single_site_notoptions_cache_for_networks() { + get_network_option( 1, 'ticket_61730_notoption' ); + + $network_notoptions_cache = wp_cache_get( '1:notoptions', 'site-options' ); + $single_site_notoptions_cache = wp_cache_get( 'notoptions', 'options' ); + + $this->assertEmpty( $single_site_notoptions_cache, 'Single site notoptions cache should not be set for multisite installs.' ); + $this->assertIsArray( $network_notoptions_cache, 'Multisite notoptions cache should be set.' ); + $this->assertArrayHasKey( 'ticket_61730_notoption', $network_notoptions_cache, 'The option should be in the notoptions cache.' ); + } + + /** + * Test `delete_network_option()` does not use single site notoptions cache for networks. + * + * @ticket 61730 + * @ticket 61484 + * + * @group ms-required + * + * @covers ::delete_network_option + */ + public function test_delete_network_option_does_not_use_single_site_notoptions_cache_for_networks() { + add_network_option( 1, 'ticket_61730_notoption', 'value' ); + delete_network_option( 1, 'ticket_61730_notoption' ); + + $network_notoptions_cache = wp_cache_get( '1:notoptions', 'site-options' ); + $single_site_notoptions_cache = wp_cache_get( 'notoptions', 'options' ); + + $this->assertEmpty( $single_site_notoptions_cache, 'Single site notoptions cache should not be set for multisite installs.' ); + $this->assertIsArray( $network_notoptions_cache, 'Multisite notoptions cache should be set.' ); + $this->assertArrayHasKey( 'ticket_61730_notoption', $network_notoptions_cache, 'The option should be in the notoptions cache.' ); + } } diff --git a/tests/phpunit/tests/option/option.php b/tests/phpunit/tests/option/option.php index db9f35dd69d38..36a40d9a2f495 100644 --- a/tests/phpunit/tests/option/option.php +++ b/tests/phpunit/tests/option/option.php @@ -143,7 +143,7 @@ public function test_get_option_notoptions_set_cache() { * @covers ::get_option */ public function test_get_option_notoptions_do_not_load_cache() { - add_option( 'foo', 'bar', '', 'no' ); + add_option( 'foo', 'bar', '', false ); wp_cache_delete( 'notoptions', 'options' ); $before = get_num_queries(); @@ -360,12 +360,14 @@ public function test_option_autoloading( $name, $autoload_value, $expected ) { public function data_option_autoloading() { return array( // Supported values. - array( 'autoload_yes', 'yes', 'on' ), array( 'autoload_true', true, 'on' ), - array( 'autoload_no', 'no', 'off' ), array( 'autoload_false', false, 'off' ), array( 'autoload_null', null, 'auto' ), + // Values supported for backward compatibility. + array( 'autoload_yes', 'yes', 'on' ), + array( 'autoload_no', 'no', 'off' ), + // Technically unsupported values. array( 'autoload_string', 'foo', 'auto' ), array( 'autoload_int', 123456, 'auto' ), @@ -457,8 +459,8 @@ public function test_update_option_autoloading_small_option_auto() { * @covers ::update_option */ public function test_update_option_with_autoload_change_no_to_yes() { - add_option( 'foo', 'value1', '', 'no' ); - update_option( 'foo', 'value2', 'yes' ); + add_option( 'foo', 'value1', '', false ); + update_option( 'foo', 'value2', true ); delete_option( 'foo' ); $this->assertFalse( get_option( 'foo' ) ); } @@ -473,9 +475,77 @@ public function test_update_option_with_autoload_change_no_to_yes() { * @covers ::update_option */ public function test_update_option_with_autoload_change_yes_to_no() { - add_option( 'foo', 'value1', '', 'yes' ); - update_option( 'foo', 'value2', 'no' ); + add_option( 'foo', 'value1', '', true ); + update_option( 'foo', 'value2', false ); delete_option( 'foo' ); $this->assertFalse( get_option( 'foo' ) ); } + + /** + * Tests that calling delete_option() updates notoptions when option deleted. + * + * @ticket 61484 + * + * @covers ::delete_option + */ + public function test_check_delete_option_updates_notoptions() { + add_option( 'foo', 'value1' ); + + delete_option( 'foo' ); + $notoptions = wp_cache_get( 'notoptions', 'options' ); + $this->assertIsArray( $notoptions, 'The notoptions cache is expected to be an array.' ); + $this->assertTrue( $notoptions['foo'], 'The deleted options is expected to be in notoptions.' ); + + $before = get_num_queries(); + get_option( 'foo' ); + $queries = get_num_queries() - $before; + + $this->assertSame( 0, $queries, 'get_option should not make any database queries.' ); + } + + /** + * Tests that calling update_option() clears the notoptions cache. + * + * @ticket 61484 + * + * @covers ::update_option + */ + public function test_update_option_clears_the_notoptions_cache() { + $option_name = 'ticket_61484_option_to_be_created'; + $notoptions = wp_cache_get( 'notoptions', 'options' ); + if ( ! is_array( $notoptions ) ) { + $notoptions = array(); + } + $notoptions[ $option_name ] = true; + wp_cache_set( 'notoptions', $notoptions, 'options' ); + $this->assertArrayHasKey( $option_name, wp_cache_get( 'notoptions', 'options' ), 'The "foobar" option should be in the notoptions cache.' ); + + update_option( $option_name, 'baz' ); + + $updated_notoptions = wp_cache_get( 'notoptions', 'options' ); + $this->assertArrayNotHasKey( $option_name, $updated_notoptions, 'The "foobar" option should not be in the notoptions cache after updating it.' ); + } + + /** + * Tests that calling add_option() clears the notoptions cache. + * + * @ticket 61484 + * + * @covers ::add_option + */ + public function test_add_option_clears_the_notoptions_cache() { + $option_name = 'ticket_61484_option_to_be_created'; + $notoptions = wp_cache_get( 'notoptions', 'options' ); + if ( ! is_array( $notoptions ) ) { + $notoptions = array(); + } + $notoptions[ $option_name ] = true; + wp_cache_set( 'notoptions', $notoptions, 'options' ); + $this->assertArrayHasKey( $option_name, wp_cache_get( 'notoptions', 'options' ), 'The "foobar" option should be in the notoptions cache.' ); + + add_option( $option_name, 'baz' ); + + $updated_notoptions = wp_cache_get( 'notoptions', 'options' ); + $this->assertArrayNotHasKey( $option_name, $updated_notoptions, 'The "foobar" option should not be in the notoptions cache after adding it.' ); + } } diff --git a/tests/phpunit/tests/option/registration.php b/tests/phpunit/tests/option/registration.php index 9b0e418c91c57..850376498e91a 100644 --- a/tests/phpunit/tests/option/registration.php +++ b/tests/phpunit/tests/option/registration.php @@ -151,14 +151,13 @@ public function test_unregister_setting_removes_default() { } /** - * The test passes if a Notice | Warning | Error is not raised. Thus. the absence of a Notice | Warning | Error - * is an indicator the fix in the ticket resolves the issue. + * Ensures that unregister_setting() does not throw a notice or warning for unknown settings. * * @ticket 57674 * * @covers ::unregister_setting */ - public function test_unregister_invalid_setting_does_not_raise_php_notice_warning_or_error() { + public function test_unregister_invalid_setting_does_not_throw_notice_or_warning() { $setting = uniqid(); unregister_setting( $setting, $setting ); $this->assertFalse( has_filter( 'default_option_' . $setting, 'filter_default_option' ) ); diff --git a/tests/phpunit/tests/option/updateOption.php b/tests/phpunit/tests/option/updateOption.php index 1be8aa0cf8c5a..c33f91ef73ebb 100644 --- a/tests/phpunit/tests/option/updateOption.php +++ b/tests/phpunit/tests/option/updateOption.php @@ -52,7 +52,7 @@ public function test_should_set_autoload_yes_for_nonexistent_option_when_autoloa */ public function test_should_set_autoload_yes_for_nonexistent_option_when_autoload_param_is_yes() { $this->flush_cache(); - update_option( 'test_update_option_default', 'value', 'yes' ); + update_option( 'test_update_option_default', 'value', true ); $this->flush_cache(); // Populate the alloptions cache, which includes autoload=yes options. @@ -75,7 +75,7 @@ public function test_should_set_autoload_yes_for_nonexistent_option_when_autoloa */ public function test_should_set_autoload_no_for_nonexistent_option_when_autoload_param_is_no() { $this->flush_cache(); - update_option( 'test_update_option_default', 'value', 'no' ); + update_option( 'test_update_option_default', 'value', false ); $this->flush_cache(); // Populate the alloptions cache, which does not include autoload=no options. @@ -122,7 +122,7 @@ public function test_should_set_autoload_no_for_nonexistent_option_when_autoload * @covers ::get_option */ public function test_autoload_should_be_updated_for_existing_option_when_value_is_changed() { - add_option( 'foo', 'bar', '', 'no' ); + add_option( 'foo', 'bar', '', false ); $updated = update_option( 'foo', 'bar2', true ); $this->assertTrue( $updated ); @@ -146,7 +146,7 @@ public function test_autoload_should_be_updated_for_existing_option_when_value_i * @covers ::get_option */ public function test_autoload_should_not_be_updated_for_existing_option_when_value_is_unchanged() { - add_option( 'foo', 'bar', '', 'yes' ); + add_option( 'foo', 'bar', '', true ); $updated = update_option( 'foo', 'bar', false ); $this->assertFalse( $updated ); @@ -171,7 +171,7 @@ public function test_autoload_should_not_be_updated_for_existing_option_when_val * @covers ::get_option */ public function test_autoload_should_not_be_updated_for_existing_option_when_value_is_changed_but_no_value_of_autoload_is_provided() { - add_option( 'foo', 'bar', '', 'yes' ); + add_option( 'foo', 'bar', '', true ); // Don't pass a value for `$autoload`. $updated = update_option( 'foo', 'bar2' ); diff --git a/tests/phpunit/tests/option/wpLoadAlloptions.php b/tests/phpunit/tests/option/wpLoadAlloptions.php index 34b8ebac9012d..166979a3772d7 100644 --- a/tests/phpunit/tests/option/wpLoadAlloptions.php +++ b/tests/phpunit/tests/option/wpLoadAlloptions.php @@ -26,7 +26,7 @@ public function test_if_alloptions_is_cached() { */ public function test_default_and_yes() { add_option( 'foo', 'bar' ); - add_option( 'bar', 'foo', '', 'yes' ); + add_option( 'bar', 'foo', '', true ); $alloptions = wp_load_alloptions(); $this->assertArrayHasKey( 'foo', $alloptions ); $this->assertArrayHasKey( 'bar', $alloptions ); @@ -39,7 +39,7 @@ public function test_default_and_yes() { */ public function test_default_and_no() { add_option( 'foo', 'bar' ); - add_option( 'bar', 'foo', '', 'no' ); + add_option( 'bar', 'foo', '', false ); $alloptions = wp_load_alloptions(); $this->assertArrayHasKey( 'foo', $alloptions ); $this->assertArrayNotHasKey( 'bar', $alloptions ); diff --git a/tests/phpunit/tests/option/wpSetOptionAutoload.php b/tests/phpunit/tests/option/wpSetOptionAutoload.php index 226ad4b155d1f..b6eeb4026374e 100644 --- a/tests/phpunit/tests/option/wpSetOptionAutoload.php +++ b/tests/phpunit/tests/option/wpSetOptionAutoload.php @@ -11,6 +11,8 @@ class Tests_Option_WpSetOptionAutoload extends WP_UnitTestCase { /** * Tests that setting an option's autoload value to 'yes' works as expected. * + * The values 'yes' and 'no' are only supported for backward compatibility. + * * @ticket 58964 */ public function test_wp_set_option_autoload_yes() { @@ -30,6 +32,8 @@ public function test_wp_set_option_autoload_yes() { /** * Tests that setting an option's autoload value to 'no' works as expected. * + * The values 'yes' and 'no' are only supported for backward compatibility. + * * @ticket 58964 */ public function test_wp_set_option_autoload_no() { @@ -56,9 +60,9 @@ public function test_wp_set_option_autoload_same() { $option = 'test_option'; $value = 'value'; - add_option( $option, $value, '', 'yes' ); + add_option( $option, $value, '', true ); - $this->assertFalse( wp_set_option_autoload( $option, 'yes' ), 'Function did unexpectedly succeed' ); + $this->assertFalse( wp_set_option_autoload( $option, true ), 'Function did unexpectedly succeed' ); $this->assertSame( 'on', $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ), 'Option autoload value unexpectedly updated in database' ); } @@ -72,7 +76,7 @@ public function test_wp_set_option_autoload_missing() { $option = 'test_option'; - $this->assertFalse( wp_set_option_autoload( $option, 'yes' ), 'Function did unexpectedly succeed' ); + $this->assertFalse( wp_set_option_autoload( $option, true ), 'Function did unexpectedly succeed' ); $this->assertNull( $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ), 'Missing option autoload value was set in database' ); $this->assertArrayNotHasKey( $option, wp_cache_get( 'alloptions', 'options' ), 'Missing option found in alloptions cache' ); $this->assertFalse( wp_cache_get( $option, 'options' ), 'Missing option found in individual cache' ); diff --git a/tests/phpunit/tests/option/wpSetOptionAutoloadValues.php b/tests/phpunit/tests/option/wpSetOptionAutoloadValues.php index 7f7e9a15b8d28..7214beedfe8e9 100644 --- a/tests/phpunit/tests/option/wpSetOptionAutoloadValues.php +++ b/tests/phpunit/tests/option/wpSetOptionAutoloadValues.php @@ -11,6 +11,8 @@ class Tests_Option_WpSetOptionAutoloadValues extends WP_UnitTestCase { /** * Tests setting options' autoload to 'yes' where for some options this is already the case. * + * The values 'yes' and 'no' are only supported for backward compatibility. + * * @ticket 58964 */ public function test_wp_set_option_autoload_values_all_yes_partial_update() { @@ -20,8 +22,8 @@ public function test_wp_set_option_autoload_values_all_yes_partial_update() { 'test_option1' => 'yes', 'test_option2' => 'yes', ); - add_option( 'test_option1', 'value1', '', 'yes' ); - add_option( 'test_option2', 'value2', '', 'no' ); + add_option( 'test_option1', 'value1', '', true ); + add_option( 'test_option2', 'value2', '', false ); $expected = array( 'test_option1' => false, 'test_option2' => true, @@ -42,6 +44,8 @@ public function test_wp_set_option_autoload_values_all_yes_partial_update() { * * In this case, the 'alloptions' cache should not be cleared, but only its options set to 'no' should be deleted. * + * The values 'yes' and 'no' are only supported for backward compatibility. + * * @ticket 58964 */ public function test_wp_set_option_autoload_values_all_no_partial_update() { @@ -51,8 +55,8 @@ public function test_wp_set_option_autoload_values_all_no_partial_update() { 'test_option1' => 'no', 'test_option2' => 'no', ); - add_option( 'test_option1', 'value1', '', 'yes' ); - add_option( 'test_option2', 'value2', '', 'no' ); + add_option( 'test_option1', 'value1', '', true ); + add_option( 'test_option2', 'value2', '', false ); $expected = array( 'test_option1' => true, 'test_option2' => false, @@ -70,6 +74,8 @@ public function test_wp_set_option_autoload_values_all_no_partial_update() { /** * Tests setting options' autoload to 'yes' where for all of them this is already the case. * + * The values 'yes' and 'no' are only supported for backward compatibility. + * * @ticket 58964 */ public function test_wp_set_option_autoload_values_all_yes_no_update() { @@ -79,8 +85,8 @@ public function test_wp_set_option_autoload_values_all_yes_no_update() { 'test_option1' => 'yes', 'test_option2' => 'yes', ); - add_option( 'test_option1', 'value1', '', 'yes' ); - add_option( 'test_option2', 'value2', '', 'yes' ); + add_option( 'test_option1', 'value1', '', true ); + add_option( 'test_option2', 'value2', '', true ); $expected = array( 'test_option1' => false, 'test_option2' => false, @@ -96,7 +102,7 @@ public function test_wp_set_option_autoload_values_all_yes_no_update() { } /** - * Tests setting options' autoload to either 'yes' or 'no' where for some options this is already the case. + * Tests setting options' autoload to either true or false where for some options this is already the case. * * The test also covers one option that is entirely missing. * @@ -106,14 +112,14 @@ public function test_wp_set_option_autoload_values_mixed_partial_update() { global $wpdb; $options = array( - 'test_option1' => 'yes', - 'test_option2' => 'no', - 'test_option3' => 'yes', - 'missing_opt' => 'yes', + 'test_option1' => true, + 'test_option2' => false, + 'test_option3' => true, + 'missing_opt' => true, ); - add_option( 'test_option1', 'value1', '', 'no' ); - add_option( 'test_option2', 'value2', '', 'yes' ); - add_option( 'test_option3', 'value3', '', 'yes' ); + add_option( 'test_option1', 'value1', '', false ); + add_option( 'test_option2', 'value2', '', true ); + add_option( 'test_option3', 'value3', '', true ); $expected = array( 'test_option1' => true, 'test_option2' => true, @@ -132,7 +138,7 @@ public function test_wp_set_option_autoload_values_mixed_partial_update() { } /** - * Tests setting options' autoload to either 'yes' or 'no' while only the 'no' options actually need to be updated. + * Tests setting options' autoload to either true or false while only the false options actually need to be updated. * * In this case, the 'alloptions' cache should not be cleared, but only its options set to 'no' should be deleted. * @@ -142,13 +148,13 @@ public function test_wp_set_option_autoload_values_mixed_only_update_no() { global $wpdb; $options = array( - 'test_option1' => 'yes', - 'test_option2' => 'no', - 'test_option3' => 'yes', + 'test_option1' => true, + 'test_option2' => false, + 'test_option3' => true, ); - add_option( 'test_option1', 'value1', '', 'yes' ); - add_option( 'test_option2', 'value2', '', 'yes' ); - add_option( 'test_option3', 'value3', '', 'yes' ); + add_option( 'test_option1', 'value1', '', true ); + add_option( 'test_option2', 'value2', '', true ); + add_option( 'test_option3', 'value3', '', true ); $expected = array( 'test_option1' => false, 'test_option2' => true, @@ -160,7 +166,7 @@ public function test_wp_set_option_autoload_values_mixed_only_update_no() { $this->assertSame( $num_queries + 2, get_num_queries(), 'Function made unexpected amount of database queries' ); $this->assertSameSets( array( 'on', 'off', 'on' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); foreach ( $options as $option => $autoload ) { - if ( 'no' === $autoload ) { + if ( false === $autoload ) { $this->assertArrayNotHasKey( $option, wp_cache_get( 'alloptions', 'options' ), sprintf( 'Option %s not deleted from alloptions cache', $option ) ); } else { $this->assertArrayHasKey( $option, wp_cache_get( 'alloptions', 'options' ), sprintf( 'Option %s unexpectedly deleted from alloptions cache', $option ) ); @@ -177,11 +183,11 @@ public function test_wp_set_option_autoload_values_with_sql_query_failure() { global $wpdb; $options = array( - 'test_option1' => 'yes', - 'test_option2' => 'yes', + 'test_option1' => true, + 'test_option2' => true, ); - add_option( 'test_option1', 'value1', '', 'no' ); - add_option( 'test_option2', 'value2', '', 'no' ); + add_option( 'test_option1', 'value1', '', false ); + add_option( 'test_option2', 'value2', '', false ); // Force UPDATE queries to fail, leading to no autoload values being updated. add_filter( @@ -203,7 +209,7 @@ static function ( $query ) { } /** - * Tests setting options' autoload with boolean values. + * Tests setting options' autoload with now encouraged boolean values. * * @ticket 58964 */ diff --git a/tests/phpunit/tests/option/wpSetOptionsAutoload.php b/tests/phpunit/tests/option/wpSetOptionsAutoload.php index 68a1191df18d4..eb8358f533527 100644 --- a/tests/phpunit/tests/option/wpSetOptionsAutoload.php +++ b/tests/phpunit/tests/option/wpSetOptionsAutoload.php @@ -11,6 +11,8 @@ class Tests_Option_WpSetOptionsAutoload extends WP_UnitTestCase { /** * Tests that setting options' autoload value to 'yes' works as expected. * + * The values 'yes' and 'no' are only supported for backward compatibility. + * * @ticket 58964 */ public function test_wp_set_options_autoload_yes() { @@ -23,7 +25,7 @@ public function test_wp_set_options_autoload_yes() { $expected = array(); foreach ( $options as $option => $value ) { - add_option( $option, $value, '', 'no' ); + add_option( $option, $value, '', false ); $expected[ $option ] = true; } @@ -40,6 +42,8 @@ public function test_wp_set_options_autoload_yes() { /** * Tests that setting options' autoload value to 'no' works as expected. * + * The values 'yes' and 'no' are only supported for backward compatibility. + * * @ticket 58964 */ public function test_wp_set_options_autoload_no() { @@ -52,7 +56,7 @@ public function test_wp_set_options_autoload_no() { $expected = array(); foreach ( $options as $option => $value ) { - add_option( $option, $value, '', 'yes' ); + add_option( $option, $value, '', true ); $expected[ $option ] = true; } @@ -80,12 +84,12 @@ public function test_wp_set_options_autoload_same() { $expected = array(); foreach ( $options as $option => $value ) { - add_option( $option, $value, '', 'yes' ); + add_option( $option, $value, '', true ); $expected[ $option ] = false; } $num_queries = get_num_queries(); - $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), 'yes' ), 'Function did unexpectedly succeed' ); + $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), true ), 'Function did unexpectedly succeed' ); $this->assertSame( $num_queries + 1, get_num_queries(), 'Function attempted to update options autoload value in database' ); $this->assertSame( array( 'on', 'on' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Options autoload value unexpectedly updated in database' ); } @@ -108,7 +112,7 @@ public function test_wp_set_options_autoload_missing() { $expected[ $option ] = false; } - $this->assertSame( $expected, wp_set_options_autoload( $options, 'yes' ), 'Function did unexpectedly succeed' ); + $this->assertSame( $expected, wp_set_options_autoload( $options, true ), 'Function did unexpectedly succeed' ); $this->assertSame( array(), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Missing options autoload value was set in database' ); } @@ -125,14 +129,14 @@ public function test_wp_set_options_autoload_mixed() { 'test_option2' => 'value2', ); - add_option( 'test_option1', $options['test_option1'], '', 'yes' ); - add_option( 'test_option2', $options['test_option2'], '', 'no' ); + add_option( 'test_option1', $options['test_option1'], '', true ); + add_option( 'test_option2', $options['test_option2'], '', false ); $expected = array( 'test_option1' => false, 'test_option2' => true, ); - $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), 'yes' ), 'Function produced unexpected result' ); + $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), true ), 'Function produced unexpected result' ); $this->assertSame( array( 'on', 'on' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); foreach ( $options as $option => $value ) { $this->assertFalse( wp_cache_get( $option, 'options' ), sprintf( 'Option %s not deleted from individual cache', $option ) ); diff --git a/tests/phpunit/tests/post/getAvailablePostMimeTypes.php b/tests/phpunit/tests/post/getAvailablePostMimeTypes.php new file mode 100644 index 0000000000000..cf6f46b920324 --- /dev/null +++ b/tests/phpunit/tests/post/getAvailablePostMimeTypes.php @@ -0,0 +1,58 @@ +remove_added_uploads(); + remove_filter( 'pre_get_available_post_mime_types', array( $this, 'filter_add_null_to_post_mime_types' ) ); + parent::tear_down(); + } + + public function test_should_return_expected_post_mime_types() { + // Upload a JPEG image. + $filename = DIR_TESTDATA . '/images/test-image.jpg'; + $contents = file_get_contents( $filename ); + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $this->assertEmpty( $upload['error'], 'Uploading a JPEG file should not result in an error.' ); + $this->_make_attachment( $upload ); + + // Upload a PDF file. + $filename = DIR_TESTDATA . '/images/test-alpha.pdf'; + $contents = file_get_contents( $filename ); + $upload = wp_upload_bits( wp_basename( $filename ), null, $contents ); + $this->assertEmpty( $upload['error'], 'Uploading a PDF file should not result in an error.' ); + $this->_make_attachment( $upload ); + + $mime_types = get_available_post_mime_types(); + + $this->assertSame( array( 'image/jpeg', 'application/pdf' ), $mime_types, 'The MIME types returned should match the uploaded file MIME types.' ); + } + + public function test_should_remove_null() { + // Add filter to inject null into the mime types array. + add_filter( 'pre_get_available_post_mime_types', array( $this, 'filter_add_null_to_post_mime_types' ) ); + + $mime_types = get_available_post_mime_types(); + $this->assertEqualsCanonicalizing( array( 'image/jpeg', 'image/png' ), $mime_types ); + } + + /** + * Filter to inject null into the mime types array. + * + * @param string $type Post type. + * @return array + */ + public function filter_add_null_to_post_mime_types( $type ) { + return array( 'image/jpeg', null, 'image/png' ); + } +} diff --git a/tests/phpunit/tests/post/nav-menu.php b/tests/phpunit/tests/post/nav-menu.php index 7a320f9f8d628..d4ece1ff1776c 100644 --- a/tests/phpunit/tests/post/nav-menu.php +++ b/tests/phpunit/tests/post/nav-menu.php @@ -1208,6 +1208,35 @@ public function test_wp_update_nav_menu_item_with_special_characters_in_category $this->assertEmpty( $category_item->post_title ); } + /** + * Tests `wp_update_nav_menu_item()` with a non-existing taxonomy. + * + * When inserting a term from a non-existing taxonomy as a nav item, + * the `post_title` property should be empty, and the function + * should not throw a fatal error for `wp_specialchars_decode()`. + * + * @ticket 61799 + */ + public function test_wp_update_nav_menu_item_with_invalid_taxonomy() { + register_taxonomy( 'invalid', 'post' ); + $term = self::factory()->term->create_and_get( array( 'taxonomy' => 'invalid' ) ); + unregister_taxonomy( 'invalid' ); + + $menu_item_id = wp_update_nav_menu_item( + $this->menu_id, + 0, + array( + 'menu-item-type' => 'taxonomy', + 'menu-item-object' => 'invalid', + 'menu-item-object-id' => $term->term_id, + 'menu-item-status' => 'publish', + ) + ); + + $menu_item = get_post( $menu_item_id ); + $this->assertEmpty( $menu_item->post_title ); + } + /** * Test passed post_date/post_date_gmt. * diff --git a/tests/phpunit/tests/post/wpListPages.php b/tests/phpunit/tests/post/wpListPages.php index 38ba729cee20b..f059591219b1a 100644 --- a/tests/phpunit/tests/post/wpListPages.php +++ b/tests/phpunit/tests/post/wpListPages.php @@ -160,6 +160,42 @@ public function test_wp_list_pages_depth() { $this->assertSameIgnoreEOL( $expected, wp_list_pages( $args ) ); } + /** + * @ticket 61749 + */ + public function test_wp_list_pages_depth_equals_zero() { + $expected = ''; + + // Execute wp_list_pages() with a string to force calling wp_parse_args(). + ob_start(); + wp_list_pages( 'depth=0' ); + $output = ob_get_clean(); + + // If depth equals 0, all levels should be displayed. + $this->assertSameIgnoreEOL( $expected, $output ); + } + public function test_wp_list_pages_show_date() { $args = array( 'echo' => false, diff --git a/tests/phpunit/tests/privacy/wpPrivacySendErasureFulfillmentNotification.php b/tests/phpunit/tests/privacy/wpPrivacySendErasureFulfillmentNotification.php index 401528a158da4..bb6195912e233 100644 --- a/tests/phpunit/tests/privacy/wpPrivacySendErasureFulfillmentNotification.php +++ b/tests/phpunit/tests/privacy/wpPrivacySendErasureFulfillmentNotification.php @@ -98,6 +98,58 @@ public function tear_down() { parent::tear_down(); } + /** + * The function should not send an email when the request ID does not exist. + * + * @ticket 44234 + */ + public function test_should_not_send_email_when_not_a_valid_request_id() { + _wp_privacy_send_erasure_fulfillment_notification( 1234567890 ); + + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertEmpty( $mailer->mock_sent ); + } + + /** + * The function should not send an email when the ID passed does not correspond to a user request. + * + * @ticket 44234 + */ + public function test_should_not_send_email_when_not_a_user_request() { + $post_id = self::factory()->post->create( + array( + 'post_type' => 'post', // Should be 'user_request'. + ) + ); + + _wp_privacy_send_erasure_fulfillment_notification( $post_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertEmpty( $mailer->mock_sent ); + } + + /** + * The function should not send an email when the request is not completed. + * + * @ticket 44234 + */ + public function test_should_not_send_email_when_request_not_completed() { + wp_update_post( + array( + 'ID' => self::$request_id, + 'post_status' => 'request-confirmed', // Should be 'request-completed'. + ) + ); + + _wp_privacy_send_erasure_fulfillment_notification( self::$request_id ); + + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertEmpty( $mailer->mock_sent ); + $this->assertFalse( metadata_exists( 'post', self::$request_id, '_wp_user_notified' ) ); + } + /** * The function should send an email when a valid request ID is passed. * @@ -282,58 +334,6 @@ public function modify_email_headers( $headers ) { return $headers; } - /** - * The function should not send an email when the request ID does not exist. - * - * @ticket 44234 - */ - public function test_should_not_send_email_when_passed_invalid_request_id() { - _wp_privacy_send_erasure_fulfillment_notification( 1234567890 ); - - $mailer = tests_retrieve_phpmailer_instance(); - - $this->assertEmpty( $mailer->mock_sent ); - } - - /** - * The function should not send an email when the ID passed does not correspond to a user request. - * - * @ticket 44234 - */ - public function test_should_not_send_email_when_not_user_request() { - $post_id = self::factory()->post->create( - array( - 'post_type' => 'post', // Should be 'user_request'. - ) - ); - - _wp_privacy_send_erasure_fulfillment_notification( $post_id ); - $mailer = tests_retrieve_phpmailer_instance(); - - $this->assertEmpty( $mailer->mock_sent ); - } - - /** - * The function should not send an email when the request is not completed. - * - * @ticket 44234 - */ - public function test_should_not_send_email_when_request_not_completed() { - wp_update_post( - array( - 'ID' => self::$request_id, - 'post_status' => 'request-confirmed', // Should be 'request-completed'. - ) - ); - - _wp_privacy_send_erasure_fulfillment_notification( self::$request_id ); - - $mailer = tests_retrieve_phpmailer_instance(); - - $this->assertEmpty( $mailer->mock_sent ); - $this->assertFalse( metadata_exists( 'post', self::$request_id, '_wp_user_notified' ) ); - } - /** * The function should respect the user locale settings when the site uses the default locale. * diff --git a/tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php b/tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php index a0f4c96d298b2..37e2e4c282bd5 100644 --- a/tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php +++ b/tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php @@ -46,27 +46,6 @@ class Tests_Privacy_wpPrivacySendPersonalDataExportEmail extends WP_UnitTestCase */ protected static $admin_user; - /** - * Reset the mocked phpmailer instance before each test method. - * - * @since 4.9.6 - */ - public function set_up() { - parent::set_up(); - reset_phpmailer_instance(); - } - - /** - * Reset the mocked phpmailer instance after each test method. - * - * @since 4.9.6 - */ - public function tear_down() { - reset_phpmailer_instance(); - restore_previous_locale(); - parent::tear_down(); - } - /** * Create user request fixtures shared by test methods. * @@ -95,31 +74,32 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { } /** - * The function should send an export link to the requester when the user request is confirmed. + * Reset the mocked phpmailer instance before each test method. + * + * @since 4.9.6 */ - public function test_function_should_send_export_link_to_requester() { - $exports_url = wp_privacy_exports_url(); - $export_file_name = 'wp-personal-data-file-Wv0RfMnGIkl4CFEDEEkSeIdfLmaUrLsl.zip'; - $export_file_url = $exports_url . $export_file_name; - update_post_meta( self::$request_id, '_export_file_name', $export_file_name ); - - $email_sent = wp_privacy_send_personal_data_export_email( self::$request_id ); - $mailer = tests_retrieve_phpmailer_instance(); + public function set_up() { + parent::set_up(); + reset_phpmailer_instance(); + } - $this->assertSame( 'request-confirmed', get_post_status( self::$request_id ) ); - $this->assertSame( self::$requester_email, $mailer->get_recipient( 'to' )->address ); - $this->assertStringContainsString( 'Personal Data Export', $mailer->get_sent()->subject ); - $this->assertStringContainsString( $export_file_url, $mailer->get_sent()->body ); - $this->assertStringContainsString( 'please download it', $mailer->get_sent()->body ); - $this->assertTrue( $email_sent ); + /** + * Reset the mocked phpmailer instance after each test method. + * + * @since 4.9.6 + */ + public function tear_down() { + reset_phpmailer_instance(); + restore_previous_locale(); + parent::tear_down(); } /** - * The function should error when the request ID is invalid. + * The function should error when the request ID does not exist. * * @since 4.9.6 */ - public function test_function_should_error_when_request_id_invalid() { + public function test_should_return_wp_error_when_not_a_valid_request_id() { $request_id = 0; $email_sent = wp_privacy_send_personal_data_export_email( $request_id ); $this->assertWPError( $email_sent ); @@ -131,12 +111,30 @@ public function test_function_should_error_when_request_id_invalid() { $this->assertSame( 'invalid_request', $email_sent->get_error_code() ); } + /** + * The function should error when the ID passed does not correspond to a user request. + * + * @since 6.7.0 + * @ticket 46560 + */ + public function test_should_return_wp_error_when_not_a_user_request() { + $post_id = self::factory()->post->create( + array( + 'post_type' => 'post', // Should be 'user_request'. + ) + ); + + $email_sent = wp_privacy_send_personal_data_export_email( $post_id ); + $this->assertWPError( $email_sent ); + $this->assertSame( 'invalid_request', $email_sent->get_error_code() ); + } + /** * The function should error when the email was not sent. * * @since 4.9.6 */ - public function test_return_wp_error_when_send_fails() { + public function test_should_return_wp_error_when_sending_fails() { add_filter( 'wp_mail_from', '__return_empty_string' ); // Cause `wp_mail()` to return false. $email_sent = wp_privacy_send_personal_data_export_email( self::$request_id ); @@ -144,6 +142,26 @@ public function test_return_wp_error_when_send_fails() { $this->assertSame( 'privacy_email_error', $email_sent->get_error_code() ); } + /** + * The function should send an export link to the requester when the user request is confirmed. + */ + public function test_should_send_export_link_to_requester() { + $exports_url = wp_privacy_exports_url(); + $export_file_name = 'wp-personal-data-file-Wv0RfMnGIkl4CFEDEEkSeIdfLmaUrLsl.zip'; + $export_file_url = $exports_url . $export_file_name; + update_post_meta( self::$request_id, '_export_file_name', $export_file_name ); + + $email_sent = wp_privacy_send_personal_data_export_email( self::$request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertSame( 'request-confirmed', get_post_status( self::$request_id ) ); + $this->assertSame( self::$requester_email, $mailer->get_recipient( 'to' )->address ); + $this->assertStringContainsString( 'Personal Data Export', $mailer->get_sent()->subject ); + $this->assertStringContainsString( $export_file_url, $mailer->get_sent()->body ); + $this->assertStringContainsString( 'please download it', $mailer->get_sent()->body ); + $this->assertTrue( $email_sent ); + } + /** * The export expiration should be filterable. * diff --git a/tests/phpunit/tests/privacy/wpPrivacySendRequestConfirmationNotification.php b/tests/phpunit/tests/privacy/wpPrivacySendRequestConfirmationNotification.php index 951039cb086d1..59d56c0642f35 100644 --- a/tests/phpunit/tests/privacy/wpPrivacySendRequestConfirmationNotification.php +++ b/tests/phpunit/tests/privacy/wpPrivacySendRequestConfirmationNotification.php @@ -32,11 +32,11 @@ public function tear_down() { } /** - * The function should not send emails when the request ID does not exist. + * The function should not send an email when the request ID does not exist. * * @ticket 43967 */ - public function test_function_should_not_send_email_when_not_a_valid_request_id() { + public function test_should_not_send_email_when_not_a_valid_request_id() { _wp_privacy_send_request_confirmation_notification( 1234567890 ); $mailer = tests_retrieve_phpmailer_instance(); @@ -44,14 +44,14 @@ public function test_function_should_not_send_email_when_not_a_valid_request_id( } /** - * The function should not send emails when the ID passed is not a WP_User_Request. + * The function should not send an email when the ID passed does not correspond to a user request. * * @ticket 43967 */ - public function test_function_should_not_send_email_when_not_a_wp_user_request() { + public function test_should_not_send_email_when_not_a_user_request() { $post_id = self::factory()->post->create( array( - 'post_type' => 'post', + 'post_type' => 'post', // Should be 'user_request'. ) ); @@ -66,7 +66,7 @@ public function test_function_should_not_send_email_when_not_a_wp_user_request() * * @ticket 43967 */ - public function test_function_should_send_email_to_site_admin_when_user_request_confirmed() { + public function test_should_send_email_to_site_admin_when_user_request_confirmed() { $email = 'export.request.from.unregistered.user@example.com'; $request_id = wp_create_user_request( $email, 'export_personal_data' ); @@ -89,7 +89,7 @@ public function test_function_should_send_email_to_site_admin_when_user_request_ * * @ticket 43967 */ - public function test_function_should_only_send_email_to_site_admin_when_user_request_is_confirmed() { + public function test_should_only_send_email_to_site_admin_when_user_request_is_confirmed() { $email = 'export.request.from.unregistered.user@example.com'; $request_id = wp_create_user_request( $email, 'export_personal_data' ); @@ -109,7 +109,7 @@ public function test_function_should_only_send_email_to_site_admin_when_user_req * * @ticket 43967 */ - public function test_function_should_only_send_email_once_to_admin_when_user_request_is_confirmed() { + public function test_should_only_send_email_once_to_admin_when_user_request_is_confirmed() { $email = 'export.request.from.unregistered.user@example.com'; $request_id = wp_create_user_request( $email, 'export_personal_data' ); diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index 745cfc03e209f..737c32b8e1ab1 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -2014,6 +2014,60 @@ public function test_rest_insert_attachment_hooks_fire_once_on_create() { $this->assertSame( 1, self::$rest_after_insert_attachment_count ); } + /** + * Tests that the naming behavior of REST media uploads matches core media uploads. + * + * In particular, filenames with spaces should maintain the spaces rather than + * replacing them with hyphens. + * + * @ticket 57957 + * + * @covers WP_REST_Attachments_Controller::insert_attachment + * @dataProvider rest_upload_filename_spaces + */ + public function test_rest_upload_filename_spaces( $filename, $expected ) { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); + $request->set_header( 'Content-Type', 'image/jpeg' ); + $request->set_body( file_get_contents( self::$test_file ) ); + $request->set_file_params( + array( + 'file' => array( + 'file' => file_get_contents( self::$test_file2 ), + 'name' => $filename, + 'size' => filesize( self::$test_file2 ), + 'tmp_name' => self::$test_file2, + ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( 201, $response->get_status(), 'The file was not uploaded.' ); + $this->assertSame( $expected, $data['title']['raw'], 'An incorrect filename was returned.' ); + } + + /** + * Data provider for text_rest_upload_filename_spaces. + * + * @return array + */ + public function rest_upload_filename_spaces() { + return array( + 'filename with spaces' => array( + 'Filename With Spaces.jpg', + 'Filename With Spaces', + ), + 'filename.with.periods' => array( + 'Filename.With.Periods.jpg', + 'Filename.With.Periods', + ), + 'filename-with-dashes' => array( + 'Filename-With-Dashes.jpg', + 'Filename-With-Dashes', + ), + ); + } + /** * Ensure the `rest_after_insert_attachment` and `rest_insert_attachment` hooks only fire * once when attachments are updated. @@ -2322,7 +2376,44 @@ public function test_edit_image_crop() { $this->assertCount( 1, WP_Image_Editor_Mock::$spy['crop'] ); $this->assertSame( - array( 320.0, 48.0, 64.0, 24.0 ), + array( 320, 48, 64, 24 ), + WP_Image_Editor_Mock::$spy['crop'][0] + ); + } + + /** + * @ticket 61514 + * @requires function imagejpeg + */ + public function test_edit_image_crop_one_axis() { + wp_set_current_user( self::$superadmin_id ); + $attachment = self::factory()->attachment->create_upload_object( self::$test_file ); + + $this->setup_mock_editor(); + WP_Image_Editor_Mock::$size_return = array( + 'width' => 640, + 'height' => 480, + ); + + WP_Image_Editor_Mock::$edit_return['crop'] = new WP_Error(); + + $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment}/edit" ); + $request->set_body_params( + array( + 'x' => 50, + 'y' => 0, + 'width' => 10, + 'height' => 100, + 'src' => wp_get_attachment_image_url( $attachment, 'full' ), + + ) + ); + $response = rest_do_request( $request ); + $this->assertErrorResponse( 'rest_image_crop_failed', $response, 500 ); + + $this->assertCount( 1, WP_Image_Editor_Mock::$spy['crop'] ); + $this->assertSame( + array( 320, 0, 64, 480 ), WP_Image_Editor_Mock::$spy['crop'][0] ); } diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 8e80aa2dfaf33..d4d0aef8e10d2 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -609,6 +609,7 @@ public function test_update_item_invalid_styles_css() { * * @covers WP_REST_Global_Styles_Controller_Gutenberg::update_item * @ticket 61312 + * @ticket 61451 */ public function test_update_item_with_custom_block_style_variations() { wp_set_current_user( self::$admin_id ); @@ -616,6 +617,18 @@ public function test_update_item_with_custom_block_style_variations() { grant_super_admin( self::$admin_id ); } + /* + * For variations to be resolved they have to have been registered + * via either a theme.json partial or through the WP_Block_Styles_Registry. + */ + register_block_style( + 'core/group', + array( + 'name' => 'fromThemeStyleVariation', + 'label' => 'From Theme Style Variation', + ) + ); + $group_variations = array( 'fromThemeStyleVariation' => array( 'color' => array( @@ -629,16 +642,16 @@ public function test_update_item_with_custom_block_style_variations() { $request->set_body_params( array( 'styles' => array( - 'blocks' => array( - 'variations' => array( - 'fromThemeStyleVariation' => array( - 'blockTypes' => array( 'core/group', 'core/columns' ), - 'color' => array( - 'background' => '#000000', - 'text' => '#ffffff', - ), + 'variations' => array( + 'fromThemeStyleVariation' => array( + 'blockTypes' => array( 'core/group', 'core/columns' ), + 'color' => array( + 'background' => '#000000', + 'text' => '#ffffff', ), ), + ), + 'blocks' => array( 'core/group' => array( 'variations' => $group_variations, ), diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php index 6b03aa3af1f42..b01ada9be28ff 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php @@ -272,12 +272,12 @@ protected function check_get_revision_response( $response_revision_item, $revisi // Global styles. $config = ( new WP_Theme_JSON( json_decode( $revision_expected_item->post_content, true ), 'custom' ) )->get_raw_data(); - $this->assertEquals( + $this->assertSame( $config['settings'], $response_revision_item['settings'], 'Check that the revision settings exist in the response.' ); - $this->assertEquals( + $this->assertSame( $config['styles'], $response_revision_item['styles'], 'Check that the revision styles match the updated styles.' @@ -371,7 +371,7 @@ public function test_get_items_eligible_roles() { $data = $response->get_data(); $this->assertCount( $this->total_revisions + 1, $data, 'Check that extra revision exist' ); - $this->assertEquals( self::$second_admin_id, $data[0]['author'], 'Check that second author id returns expected value.' ); + $this->assertSame( self::$second_admin_id, $data[0]['author'], 'Check that second author id returns expected value.' ); } /** diff --git a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php index 1418db19dbce2..2546076b2ae1d 100644 --- a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php +++ b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php @@ -3095,6 +3095,464 @@ public function test_default_is_added_to_schema() { $this->assertSame( 'Goodnight Moon', $schema['default'] ); } + /** + * Ensures that REST API calls with post meta containing the default value for the + * registered meta field stores the default value into the database. + * + * When the default value isn't persisted in the database, a read of the post meta + * at some point in the future might return a different value if the code setting the + * default changed. This ensures that once a value is intentionally saved into the + * database that it will remain durably in future reads. + * + * @ticket 55600 + * + * @dataProvider data_scalar_default_values + * + * @param string $type Scalar type of default value: one of `boolean`, `integer`, `number`, or `string`. + * @param mixed $default_value Appropriate default value for given type. + * @param mixed $alternative_value Ignored in this test. + */ + public function test_scalar_singular_default_is_saved_to_db( $type, $default_value, $alternative_value ) { + $this->grant_write_permission(); + + $meta_key_single = "with_{$type}_default"; + + register_post_meta( + 'post', + $meta_key_single, + array( + 'type' => $type, + 'single' => true, + 'show_in_rest' => true, + 'default' => $default_value, + ) + ); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_single => $default_value, + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + $this->assertSame( + array( (string) $default_value ), + get_metadata_raw( 'post', self::$post_id, $meta_key_single, false ), + 'Should have stored a single meta value with string-cast version of default value.' + ); + } + + /** + * Ensures that REST API calls with multi post meta values (containing the default) + * for the registered meta field stores the default value into the database. + * + * When the default value isn't persisted in the database, a read of the post meta + * at some point in the future might return a different value if the code setting the + * default changed. This ensures that once a value is intentionally saved into the + * database that it will remain durably in future reads. + * + * Further, the total count of stored values may be wrong if the default value + * is culled from the results of a "multi" read. + * + * @ticket 55600 + * + * @dataProvider data_scalar_default_values + * + * @param string $type Scalar type of default value: one of `boolean`, `integer`, `number`, or `string`. + * @param mixed $default_value Appropriate default value for given type. + * @param mixed $alternative_value Appropriate value for given type that doesn't match the default value. + */ + public function test_scalar_multi_default_is_saved_to_db( $type, $default_value, $alternative_value ) { + $this->grant_write_permission(); + + $meta_key_multiple = "with_multi_{$type}_default"; + + // Register non-singular post meta for type. + register_post_meta( + 'post', + $meta_key_multiple, + array( + 'type' => $type, + 'single' => false, + 'show_in_rest' => true, + 'default' => $default_value, + ) + ); + + // Write the default value as the sole value. + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_multiple => array( $default_value ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + $this->assertSame( + array( (string) $default_value ), + get_metadata_raw( 'post', self::$post_id, $meta_key_multiple, false ), + 'Should have stored a single meta value with string-cast version of default value.' + ); + + // Write multiple values, including the default, to ensure it remains. + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_multiple => array( + $default_value, + $alternative_value, + ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + $this->assertSame( + array( (string) $default_value, (string) $alternative_value ), + get_metadata_raw( 'post', self::$post_id, $meta_key_multiple, false ), + 'Should have stored both the default and non-default string-cast values.' + ); + } + + /** + * Ensures that REST API calls with post meta containing an object as the default + * value for the registered meta field stores the default value into the database. + * + * When the default value isn't persisted in the database, a read of the post meta + * at some point in the future might return a different value if the code setting the + * default changed. This ensures that once a value is intentionally saved into the + * database that it will remain durably in future reads. + * + * @ticket 55600 + * + * @dataProvider data_scalar_default_values + * + * @param string $type Scalar type of default value: one of `boolean`, `integer`, `number`, or `string`. + * @param mixed $default_value Appropriate default value for given type. + * @param mixed $alternative_value Ignored in this test. + */ + public function test_object_singular_default_is_saved_to_db( $type, $default_value, $alternative_value ) { + $this->grant_write_permission(); + + $meta_key_single = "with_{$type}_default"; + + // Register singular post meta for type. + register_post_meta( + 'post', + $meta_key_single, + array( + 'type' => 'object', + 'single' => true, + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => array( + $type => array( 'type' => $type ), + ), + ), + ), + 'default' => (object) array( $type => $default_value ), + ) + ); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_single => (object) array( $type => $default_value ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + // Objects stored into the database are read back as arrays. + $this->assertSame( + array( array( $type => $default_value ) ), + get_metadata_raw( 'post', self::$post_id, $meta_key_single, false ), + 'Should have stored a single meta value with an object representing the default value.' + ); + } + + /** + * Ensures that REST API calls with multi post meta values (containing an object as + * the default) for the registered meta field stores the default value into the database. + * + * When the default value isn't persisted in the database, a read of the post meta + * at some point in the future might return a different value if the code setting the + * default changed. This ensures that once a value is intentionally saved into the + * database that it will remain durably in future reads. + * + * Further, the total count of stored values may be wrong if the default value + * is culled from the results of a "multi" read. + * + * @ticket 55600 + * + * @dataProvider data_scalar_default_values + * + * @param string $type Scalar type of default value: one of `boolean`, `integer`, `number`, or `string`. + * @param mixed $default_value Appropriate default value for given type. + * @param mixed $alternative_value Appropriate value for given type that doesn't match the default value. + */ + public function test_object_multi_default_is_saved_to_db( $type, $default_value, $alternative_value ) { + $this->grant_write_permission(); + + $meta_key_multiple = "with_multi_{$type}_default"; + + // Register non-singular post meta for type. + register_post_meta( + 'post', + $meta_key_multiple, + array( + 'type' => 'object', + 'single' => false, + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => array( + $type => array( 'type' => $type ), + ), + ), + ), + 'default' => (object) array( $type => $default_value ), + ) + ); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_multiple => array( (object) array( $type => $default_value ) ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + // Objects stored into the database are read back as arrays. + $this->assertSame( + array( array( $type => $default_value ) ), + get_metadata_raw( 'post', self::$post_id, $meta_key_multiple, false ), + 'Should have stored a single meta value with an object representing the default value.' + ); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_multiple => array( + (object) array( $type => $default_value ), + (object) array( $type => $alternative_value ), + ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + // Objects stored into the database are read back as arrays. + $this->assertSame( + array( array( $type => $default_value ), array( $type => $alternative_value ) ), + get_metadata_raw( 'post', self::$post_id, $meta_key_multiple, false ), + 'Should have stored a single meta value with an object representing the default value.' + ); + } + + /** + * Ensures that REST API calls with post meta containing a list array as the default + * value for the registered meta field stores the default value into the database. + * + * When the default value isn't persisted in the database, a read of the post meta + * at some point in the future might return a different value if the code setting the + * default changed. This ensures that once a value is intentionally saved into the + * database that it will remain durably in future reads. + * + * @ticket 55600 + * + * @dataProvider data_scalar_default_values + * + * @param string $type Scalar type of default value: one of `boolean`, `integer`, `number`, or `string`. + * @param mixed $default_value Appropriate default value for given type. + * @param mixed $alternative_value Ignored in this test. + */ + public function test_array_singular_default_is_saved_to_db( $type, $default_value, $alternative_value ) { + $this->grant_write_permission(); + + $meta_key_single = "with_{$type}_default"; + + // Register singular post meta for type. + register_post_meta( + 'post', + $meta_key_single, + array( + 'type' => 'array', + 'single' => true, + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'array', + 'items' => array( + 'type' => $type, + ), + ), + ), + 'default' => $default_value, + ) + ); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_single => array( $default_value ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + $this->assertSame( + array( array( $default_value ) ), + get_metadata_raw( 'post', self::$post_id, $meta_key_single, false ), + 'Should have stored a single meta value with an array containing only the default value.' + ); + } + + /** + * Ensures that REST API calls with multi post meta values (containing a list array as + * the default) for the registered meta field stores the default value into the database. + * + * When the default value isn't persisted in the database, a read of the post meta + * at some point in the future might return a different value if the code setting the + * default changed. This ensures that once a value is intentionally saved into the + * database that it will remain durably in future reads. + * + * Further, the total count of stored values may be wrong if the default value + * is culled from the results of a "multi" read. + * + * @ticket 55600 + * + * @dataProvider data_scalar_default_values + * + * @param string $type Scalar type of default value: one of `boolean`, `integer`, `number`, or `string`. + * @param mixed $default_value Appropriate default value for given type. + * @param mixed $alternative_value Appropriate value for given type that doesn't match the default value. + */ + public function test_array_multi_default_is_saved_to_db( $type, $default_value, $alternative_value ) { + $this->grant_write_permission(); + + $meta_key_multiple = "with_multi_{$type}_default"; + + // Register non-singular post meta for type. + register_post_meta( + 'post', + $meta_key_multiple, + array( + 'type' => 'array', + 'single' => false, + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'array', + 'items' => array( + 'type' => $type, + ), + ), + ), + 'default' => $default_value, + ) + ); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_multiple => array( array( $default_value ) ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + $this->assertSame( + array( array( $default_value ) ), + get_metadata_raw( 'post', self::$post_id, $meta_key_multiple, false ), + 'Should have stored a single meta value with an object representing the default value.' + ); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + $meta_key_multiple => array( + array( $default_value ), + array( $alternative_value ), + ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( + 200, + $response->get_status(), + "API call should have returned successfully but didn't: check test setup." + ); + + $this->assertSame( + array( array( $default_value ), array( $alternative_value ) ), + get_metadata_raw( 'post', self::$post_id, $meta_key_multiple, false ), + 'Should have stored a single meta value with an object representing the default value.' + ); + } + /** * @ticket 48823 */ @@ -3163,7 +3621,6 @@ public function error_delete_query( $query ) { return $query; } - /** * Test that single post meta is revisioned when saving to the posts REST API endpoint. * @@ -3201,7 +3658,6 @@ public function test_revisioned_single_post_meta_with_posts_endpoint() { $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) ); $revision_id = array_shift( $revisions )->ID; - // @todo Ensure the revisions endpoint returns the correct meta values // Check that the revisions endpoint returns the correct meta value. $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d/revisions/%d', $post_id, $revision_id ) ); $response = rest_get_server()->dispatch( $request ); @@ -3516,4 +3972,21 @@ public function data_revisioned_single_post_meta_with_posts_endpoint_page_and_cp ), ); } + + /** + * Data provider. + * + * Provides example default values of scalar types; + * in contrast to arrays, objects, etc... + * + * @return array[] + */ + public static function data_scalar_default_values() { + return array( + 'boolean default' => array( 'boolean', true, false ), + 'integer default' => array( 'integer', 42, 43 ), + 'number default' => array( 'number', 42.99, 43.99 ), + 'string default' => array( 'string', 'string', 'string2' ), + ); + } } diff --git a/tests/phpunit/tests/rest-api/rest-post-types-controller.php b/tests/phpunit/tests/rest-api/rest-post-types-controller.php index c902aefbd375d..8967a507063a1 100644 --- a/tests/phpunit/tests/rest-api/rest-post-types-controller.php +++ b/tests/phpunit/tests/rest-api/rest-post-types-controller.php @@ -77,6 +77,27 @@ public function test_get_item_cpt() { $this->check_post_type_object_response( 'view', $response, 'cpt' ); } + /** + * @ticket 61477 + */ + public function test_get_item_template_cpt() { + register_post_type( + 'cpt_template', + array( + 'show_in_rest' => true, + 'rest_base' => 'cpt_template', + 'rest_namespace' => 'wordpress/v1', + 'template' => array( + array( 'core/paragraph', array( 'placeholder' => 'Content' ) ), + ), + 'template_lock' => 'all', + ) + ); + $request = new WP_REST_Request( 'GET', '/wp/v2/types/cpt_template' ); + $response = rest_get_server()->dispatch( $request ); + $this->check_post_type_object_response( 'view', $response, 'cpt_template' ); + } + public function test_get_item_page() { $request = new WP_REST_Request( 'GET', '/wp/v2/types/page' ); $response = rest_get_server()->dispatch( $request ); @@ -165,7 +186,7 @@ public function test_get_item_schema() { $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertCount( 14, $properties, 'Schema should have 14 properties' ); + $this->assertCount( 16, $properties, 'Schema should have 16 properties' ); $this->assertArrayHasKey( 'capabilities', $properties, '`capabilities` should be included in the schema' ); $this->assertArrayHasKey( 'description', $properties, '`description` should be included in the schema' ); $this->assertArrayHasKey( 'hierarchical', $properties, '`hierarchical` should be included in the schema' ); @@ -180,6 +201,8 @@ public function test_get_item_schema() { $this->assertArrayHasKey( 'rest_namespace', $properties, '`rest_namespace` should be included in the schema' ); $this->assertArrayHasKey( 'visibility', $properties, '`visibility` should be included in the schema' ); $this->assertArrayHasKey( 'icon', $properties, '`icon` should be included in the schema' ); + $this->assertArrayHasKey( 'template', $properties, '`template` should be included in the schema' ); + $this->assertArrayHasKey( 'template_lock', $properties, '`template_lock` should be included in the schema' ); } public function test_get_additional_field_registration() { @@ -230,6 +253,8 @@ protected function check_post_type_obj( $context, $post_type_obj, $data, $links $this->assertSame( $post_type_obj->rest_base, $data['rest_base'] ); $this->assertSame( $post_type_obj->rest_namespace, $data['rest_namespace'] ); $this->assertSame( $post_type_obj->has_archive, $data['has_archive'] ); + $this->assertSame( $post_type_obj->template ?? array(), $data['template'] ); + $this->assertSame( ! empty( $post_type_obj->template_lock ) ? $post_type_obj->template_lock : false, $data['template_lock'] ); $links = test_rest_expand_compact_links( $links ); $this->assertSame( rest_url( 'wp/v2/types' ), $links['collection'][0]['href'] ); diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index 3c5b8a1966b7f..4d5c269357a68 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -729,9 +729,9 @@ public function test_build_wp_api_client_fixtures() { 'TagModel.meta.test_multi' => array(), 'TagModel.meta.test_tag_meta' => '', 'UsersCollection.0.link' => 'http://example.org/?author=1', - 'UsersCollection.0.avatar_urls.24' => 'http://0.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=24&d=mm&r=g', - 'UsersCollection.0.avatar_urls.48' => 'http://0.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=48&d=mm&r=g', - 'UsersCollection.0.avatar_urls.96' => 'http://0.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=96&d=mm&r=g', + 'UsersCollection.0.avatar_urls.24' => 'https://secure.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=24&d=mm&r=g', + 'UsersCollection.0.avatar_urls.48' => 'https://secure.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=48&d=mm&r=g', + 'UsersCollection.0.avatar_urls.96' => 'https://secure.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=96&d=mm&r=g', 'UsersCollection.0._links.self.0.href' => 'http://example.org/index.php?rest_route=/wp/v2/users/1', 'UsersCollection.0._links.collection.0.href' => 'http://example.org/index.php?rest_route=/wp/v2/users', 'UsersCollection.1.id' => 2, diff --git a/tests/phpunit/tests/rest-api/rest-sidebars-controller.php b/tests/phpunit/tests/rest-api/rest-sidebars-controller.php index 0bddf12df92b9..67a49770dbc86 100644 --- a/tests/phpunit/tests/rest-api/rest-sidebars-controller.php +++ b/tests/phpunit/tests/rest-api/rest-sidebars-controller.php @@ -43,8 +43,8 @@ public static function wpSetUpBeforeClass( $factory ) { } public static function wpTearDownAfterClass() { - wp_delete_user( self::$admin_id ); - wp_delete_user( self::$author_id ); + self::delete_user( self::$admin_id ); + self::delete_user( self::$author_id ); } public function set_up() { diff --git a/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php b/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php index 53d52350b6b1c..5508123deac19 100644 --- a/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php +++ b/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php @@ -862,8 +862,10 @@ protected function check_menu_item_data( $post, $data, $context, $links ) { // Check filtered values. if ( post_type_supports( self::POST_TYPE, 'title' ) ) { add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); $this->assertSame( $post->title, $data['title']['rendered'] ); remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); + remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); if ( 'edit' === $context ) { $this->assertSame( $post->title, $data['title']['raw'] ); } else { diff --git a/tests/phpunit/tests/script-modules/wpScriptModules.php b/tests/phpunit/tests/script-modules/wpScriptModules.php index 21a98ac5ba518..85f9599f0dac3 100644 --- a/tests/phpunit/tests/script-modules/wpScriptModules.php +++ b/tests/phpunit/tests/script-modules/wpScriptModules.php @@ -122,8 +122,8 @@ public function test_wp_dequeue_script_module() { $enqueued_script_modules = $this->get_enqueued_script_modules(); $this->assertCount( 1, $enqueued_script_modules ); - $this->assertFalse( isset( $enqueued_script_modules['foo'] ) ); - $this->assertTrue( isset( $enqueued_script_modules['bar'] ) ); + $this->assertArrayNotHasKey( 'foo', $enqueued_script_modules ); + $this->assertArrayHasKey( 'bar', $enqueued_script_modules ); } @@ -149,8 +149,8 @@ public function test_wp_deregister_script_module() { $enqueued_script_modules = $this->get_enqueued_script_modules(); $this->assertCount( 1, $enqueued_script_modules ); - $this->assertFalse( isset( $enqueued_script_modules['foo'] ) ); - $this->assertTrue( isset( $enqueued_script_modules['bar'] ) ); + $this->assertArrayNotHasKey( 'foo', $enqueued_script_modules ); + $this->assertArrayHasKey( 'bar', $enqueued_script_modules ); } /** @@ -168,7 +168,7 @@ public function test_wp_deregister_unexistent_script_module() { $enqueued_script_modules = $this->get_enqueued_script_modules(); $this->assertCount( 0, $enqueued_script_modules ); - $this->assertFalse( isset( $enqueued_script_modules['unexistent'] ) ); + $this->assertArrayNotHasKey( 'unexistent', $enqueued_script_modules ); } /** @@ -190,13 +190,13 @@ public function test_wp_deregister_already_deregistered_script_module() { $enqueued_script_modules = $this->get_enqueued_script_modules(); $this->assertCount( 0, $enqueued_script_modules ); - $this->assertFalse( isset( $enqueued_script_modules['foo'] ) ); + $this->assertArrayNotHasKey( 'foo', $enqueued_script_modules ); $this->script_modules->deregister( 'foo' ); // Dequeued. $enqueued_script_modules = $this->get_enqueued_script_modules(); $this->assertCount( 0, $enqueued_script_modules ); - $this->assertFalse( isset( $enqueued_script_modules['foo'] ) ); + $this->assertArrayNotHasKey( 'foo', $enqueued_script_modules ); } /** @@ -218,7 +218,7 @@ public function test_wp_enqueue_script_module_works_before_register() { $this->assertCount( 1, $enqueued_script_modules ); $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] ); - $this->assertFalse( isset( $enqueued_script_modules['bar'] ) ); + $this->assertArrayNotHasKey( 'bar', $enqueued_script_modules ); } /** @@ -242,8 +242,8 @@ public function test_wp_dequeue_script_module_works_before_register() { $enqueued_script_modules = $this->get_enqueued_script_modules(); $this->assertCount( 1, $enqueued_script_modules ); - $this->assertFalse( isset( $enqueued_script_modules['foo'] ) ); - $this->assertTrue( isset( $enqueued_script_modules['bar'] ) ); + $this->assertArrayNotHasKey( 'foo', $enqueued_script_modules ); + $this->assertArrayHasKey( 'bar', $enqueued_script_modules ); } /** @@ -266,7 +266,7 @@ public function test_wp_import_map_dependencies() { $this->assertCount( 1, $import_map ); $this->assertStringStartsWith( '/dep.js', $import_map['dep'] ); - $this->assertFalse( isset( $import_map['no-dep'] ) ); + $this->assertArrayNotHasKey( 'no-dep', $import_map ); } /** @@ -340,7 +340,7 @@ public function test_wp_import_map_recursive_dependencies() { $this->assertStringStartsWith( '/dynamic-dep.js', $import_map['dynamic-dep'] ); $this->assertStringStartsWith( '/nested-static-dep.js', $import_map['nested-static-dep'] ); $this->assertStringStartsWith( '/nested-dynamic-dep.js', $import_map['nested-dynamic-dep'] ); - $this->assertFalse( isset( $import_map['no-dep'] ) ); + $this->assertArrayNotHasKey( 'no-dep', $import_map ); } /** @@ -409,9 +409,9 @@ public function test_wp_enqueue_preloaded_static_dependencies() { $this->assertCount( 2, $preloaded_script_modules ); $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep'] ); $this->assertStringStartsWith( '/nested-static-dep.js', $preloaded_script_modules['nested-static-dep'] ); - $this->assertFalse( isset( $preloaded_script_modules['no-dep'] ) ); - $this->assertFalse( isset( $preloaded_script_modules['dynamic-dep'] ) ); - $this->assertFalse( isset( $preloaded_script_modules['nested-dynamic-dep'] ) ); + $this->assertArrayNotHasKey( 'dynamic-dep', $preloaded_script_modules ); + $this->assertArrayNotHasKey( 'nested-dynamic-dep', $preloaded_script_modules ); + $this->assertArrayNotHasKey( 'no-dep', $preloaded_script_modules ); } /** @@ -445,9 +445,9 @@ public function test_wp_dont_preload_static_dependencies_of_dynamic_dependencies $this->assertCount( 1, $preloaded_script_modules ); $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep'] ); - $this->assertFalse( isset( $preloaded_script_modules['dynamic-dep'] ) ); - $this->assertFalse( isset( $preloaded_script_modules['nested-static-dep'] ) ); - $this->assertFalse( isset( $preloaded_script_modules['no-dep'] ) ); + $this->assertArrayNotHasKey( 'dynamic-dep', $preloaded_script_modules ); + $this->assertArrayNotHasKey( 'nested-dynamic-dep', $preloaded_script_modules ); + $this->assertArrayNotHasKey( 'no-dep', $preloaded_script_modules ); } /** @@ -476,8 +476,8 @@ public function test_wp_preloaded_dependencies_filter_enqueued_script_modules() $preloaded_script_modules = $this->get_preloaded_script_modules(); $this->assertCount( 1, $preloaded_script_modules ); - $this->assertTrue( isset( $preloaded_script_modules['dep'] ) ); - $this->assertFalse( isset( $preloaded_script_modules['enqueued-dep'] ) ); + $this->assertArrayHasKey( 'dep', $preloaded_script_modules ); + $this->assertArrayNotHasKey( 'enqueued-dep', $preloaded_script_modules ); } /** @@ -507,8 +507,8 @@ public function test_wp_enqueued_script_modules_with_dependants_add_import_map() $import_map = $this->get_import_map(); $this->assertCount( 2, $import_map ); - $this->assertTrue( isset( $import_map['dep'] ) ); - $this->assertTrue( isset( $import_map['enqueued-dep'] ) ); + $this->assertArrayHasKey( 'dep', $import_map ); + $this->assertArrayHasKey( 'enqueued-dep', $import_map ); } /** @@ -531,7 +531,7 @@ public function test_get_src() { ); $result = $get_src->invoke( $this->script_modules, 'module_with_version' ); - $this->assertEquals( 'http://example.com/module.js?ver=1.0', $result ); + $this->assertSame( 'http://example.com/module.js?ver=1.0', $result ); $this->script_modules->register( 'module_without_version', @@ -541,7 +541,7 @@ public function test_get_src() { ); $result = $get_src->invoke( $this->script_modules, 'module_without_version' ); - $this->assertEquals( 'http://example.com/module.js', $result ); + $this->assertSame( 'http://example.com/module.js', $result ); $this->script_modules->register( 'module_with_wp_version', @@ -551,7 +551,7 @@ public function test_get_src() { ); $result = $get_src->invoke( $this->script_modules, 'module_with_wp_version' ); - $this->assertEquals( 'http://example.com/module.js?ver=' . get_bloginfo( 'version' ), $result ); + $this->assertSame( 'http://example.com/module.js?ver=' . get_bloginfo( 'version' ), $result ); $this->script_modules->register( 'module_with_existing_query_string', @@ -561,7 +561,7 @@ public function test_get_src() { ); $result = $get_src->invoke( $this->script_modules, 'module_with_existing_query_string' ); - $this->assertEquals( 'http://example.com/module.js?foo=bar&ver=1.0', $result ); + $this->assertSame( 'http://example.com/module.js?foo=bar&ver=1.0', $result ); // Filter the version to include the ID in the final URL, to test the filter, this should affect the tests below. add_filter( @@ -574,10 +574,10 @@ function ( $src, $id ) { ); $result = $get_src->invoke( $this->script_modules, 'module_without_version' ); - $this->assertEquals( 'http://example.com/module.js?script_module_id=module_without_version', $result ); + $this->assertSame( 'http://example.com/module.js?script_module_id=module_without_version', $result ); $result = $get_src->invoke( $this->script_modules, 'module_with_existing_query_string' ); - $this->assertEquals( 'http://example.com/module.js?foo=bar&ver=1.0&script_module_id=module_with_existing_query_string', $result ); + $this->assertSame( 'http://example.com/module.js?foo=bar&ver=1.0&script_module_id=module_with_existing_query_string', $result ); } /** @@ -606,13 +606,13 @@ public function test_version_is_propagated_correctly() { $this->script_modules->enqueue( 'foo' ); $enqueued_script_modules = $this->get_enqueued_script_modules(); - $this->assertEquals( '/foo.js?ver=1.0', $enqueued_script_modules['foo'] ); + $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo'] ); $import_map = $this->get_import_map(); - $this->assertEquals( '/dep.js?ver=2.0', $import_map['dep'] ); + $this->assertSame( '/dep.js?ver=2.0', $import_map['dep'] ); $preloaded_script_modules = $this->get_preloaded_script_modules(); - $this->assertEquals( '/dep.js?ver=2.0', $preloaded_script_modules['dep'] ); + $this->assertSame( '/dep.js?ver=2.0', $preloaded_script_modules['dep'] ); } /** @@ -630,7 +630,7 @@ public function test_wp_enqueue_script_module_doesnt_register_without_a_valid_sr $enqueued_script_modules = $this->get_enqueued_script_modules(); $this->assertCount( 0, $enqueued_script_modules ); - $this->assertFalse( isset( $enqueued_script_modules['foo'] ) ); + $this->assertArrayNotHasKey( 'foo', $enqueued_script_modules ); } /** @@ -666,7 +666,7 @@ public function test_wp_enqueue_script_module_registers_with_valid_src_the_secon $enqueued_script_modules = $this->get_enqueued_script_modules(); $this->assertCount( 0, $enqueued_script_modules ); - $this->assertFalse( isset( $enqueued_script_modules['foo'] ) ); + $this->assertArrayNotHasKey( 'foo', $enqueued_script_modules ); $this->script_modules->enqueue( 'foo', '/foo.js' ); // Valid src. @@ -695,41 +695,215 @@ public function test_wp_enqueue_script_module_registers_all_params() { $import_map = $this->get_import_map(); $this->assertCount( 1, $enqueued_script_modules ); - $this->assertEquals( '/foo.js?ver=1.0', $enqueued_script_modules['foo'] ); + $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo'] ); $this->assertCount( 1, $import_map ); $this->assertStringStartsWith( '/dep.js', $import_map['dep'] ); } /** - * @ticket 60348 + * @ticket 61510 + */ + public function test_print_script_module_data_prints_enqueued_module_data() { + $this->script_modules->enqueue( '@test/module', '/example.js' ); + add_action( + 'script_module_data_@test/module', + function ( $data ) { + $data['foo'] = 'bar'; + return $data; + } + ); + + $actual = get_echo( array( $this->script_modules, 'print_script_module_data' ) ); + + $expected = << +{"foo":"bar"} + + +HTML; + $this->assertSame( $expected, $actual ); + } + + /** + * @ticket 61510 + */ + public function test_print_script_module_data_prints_dependency_module_data() { + $this->script_modules->register( '@test/dependency', '/dependency.js' ); + $this->script_modules->enqueue( '@test/module', '/example.js', array( '@test/dependency' ) ); + add_action( + 'script_module_data_@test/dependency', + function ( $data ) { + $data['foo'] = 'bar'; + return $data; + } + ); + + $actual = get_echo( array( $this->script_modules, 'print_script_module_data' ) ); + + $expected = << +{"foo":"bar"} + + +HTML; + $this->assertSame( $expected, $actual ); + } + + /** + * @ticket 61510 + */ + public function test_print_script_module_data_does_not_print_nondependency_module_data() { + $this->script_modules->register( '@test/other', '/dependency.js' ); + $this->script_modules->enqueue( '@test/module', '/example.js' ); + add_action( + 'script_module_data_@test/other', + function ( $data ) { + $data['foo'] = 'bar'; + return $data; + } + ); + + $actual = get_echo( array( $this->script_modules, 'print_script_module_data' ) ); + + $this->assertSame( '', $actual ); + } + + /** + * @ticket 61510 + */ + public function test_print_script_module_data_does_not_print_empty_data() { + $this->script_modules->enqueue( '@test/module', '/example.js' ); + add_action( + 'script_module_data_@test/module', + function ( $data ) { + return $data; + } + ); + + $actual = get_echo( array( $this->script_modules, 'print_script_module_data' ) ); + + $this->assertSame( '', $actual ); + } + + /** + * @ticket 61510 * - * @covers ::print_import_map_polyfill() + * @dataProvider data_special_chars_script_encoding + * @param string $input Raw input string. + * @param string $expected Expected output string. + * @param string $charset Blog charset option. */ - public function test_wp_print_import_map_has_no_polyfill_when_no_modules_registered() { - $import_map_polyfill = get_echo( array( $this->script_modules, 'print_import_map' ) ); + public function test_print_script_module_data_encoding( $input, $expected, $charset ) { + add_filter( + 'pre_option_blog_charset', + function () use ( $charset ) { + return $charset; + } + ); - $this->assertEquals( '', $import_map_polyfill ); + $this->script_modules->enqueue( '@test/module', '/example.js' ); + add_action( + 'script_module_data_@test/module', + function ( $data ) use ( $input ) { + $data[''] = $input; + return $data; + } + ); + + $actual = get_echo( array( $this->script_modules, 'print_script_module_data' ) ); + + $expected = << +{"":"{$expected}"} + + +HTML; + + $this->assertSame( $expected, $actual ); } /** - * @ticket 60348 + * Data provider. * - * @covers ::print_import_map_polyfill() + * @return array */ - public function test_wp_print_import_map_has_polyfill_when_modules_registered() { - $script_name = 'wp-polyfill-importmap'; - wp_register_script( $script_name, '/wp-polyfill-importmap.js' ); + public static function data_special_chars_script_encoding(): array { + return array( + // UTF-8 + 'Solidus' => array( '/', '/', 'UTF-8' ), + 'Double quote' => array( '"', '\\"', 'UTF-8' ), + 'Single quote' => array( '\'', '\'', 'UTF-8' ), + 'Less than' => array( '<', '\u003C', 'UTF-8' ), + 'Greater than' => array( '>', '\u003E', 'UTF-8' ), + 'Ampersand' => array( '&', '&', 'UTF-8' ), + 'Newline' => array( "\n", "\\n", 'UTF-8' ), + 'Tab' => array( "\t", "\\t", 'UTF-8' ), + 'Form feed' => array( "\f", "\\f", 'UTF-8' ), + 'Carriage return' => array( "\r", "\\r", 'UTF-8' ), + 'Line separator' => array( "\u{2028}", "\u{2028}", 'UTF-8' ), + 'Paragraph separator' => array( "\u{2029}", "\u{2029}", 'UTF-8' ), + + /* + * The following is the Flag of England emoji + * PHP: "\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}" + */ + 'Flag of england' => array( '🏴󠁧󠁢󠁥󠁮󠁧󠁿', '🏴󠁧󠁢󠁥󠁮󠁧󠁿', 'UTF-8' ), + 'Malicious script closer' => array( '', '\u003C/script\u003E', 'UTF-8' ), + 'Entity-encoded malicious script closer' => array( '</script>', '</script>', 'UTF-8' ), + + // Non UTF-8 + 'Solidus' => array( '/', '/', 'iso-8859-1' ), + 'Less than' => array( '<', '\u003C', 'iso-8859-1' ), + 'Greater than' => array( '>', '\u003E', 'iso-8859-1' ), + 'Ampersand' => array( '&', '&', 'iso-8859-1' ), + 'Newline' => array( "\n", "\\n", 'iso-8859-1' ), + 'Tab' => array( "\t", "\\t", 'iso-8859-1' ), + 'Form feed' => array( "\f", "\\f", 'iso-8859-1' ), + 'Carriage return' => array( "\r", "\\r", 'iso-8859-1' ), + 'Line separator' => array( "\u{2028}", "\u2028", 'iso-8859-1' ), + 'Paragraph separator' => array( "\u{2029}", "\u2029", 'iso-8859-1' ), + /* + * The following is the Flag of England emoji + * PHP: "\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}" + */ + 'Flag of england' => array( '🏴󠁧󠁢󠁥󠁮󠁧󠁿', "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f", 'iso-8859-1' ), + 'Malicious script closer' => array( '', '\u003C/script\u003E', 'iso-8859-1' ), + 'Entity-encoded malicious script closer' => array( '</script>', '</script>', 'iso-8859-1' ), - $this->script_modules->enqueue( 'foo', '/foo.js', array( 'dep' ), '1.0' ); - $this->script_modules->register( 'dep', '/dep.js' ); - $import_map_polyfill = get_echo( array( $this->script_modules, 'print_import_map' ) ); + ); + } + + /** + * @ticket 61510 + * + * @dataProvider data_invalid_script_module_data + * @param mixed $data Data to return in filter. + */ + public function test_print_script_module_data_does_not_print_invalid_data( $data ) { + $this->script_modules->enqueue( '@test/module', '/example.js' ); + add_action( + 'script_module_data_@test/module', + function ( $_ ) use ( $data ) { + return $data; + } + ); - wp_deregister_script( $script_name ); + $actual = get_echo( array( $this->script_modules, 'print_script_module_data' ) ); - $p = new WP_HTML_Tag_Processor( $import_map_polyfill ); - $p->next_tag( array( 'tag' => 'SCRIPT' ) ); - $id = $p->get_attribute( 'id' ); + $this->assertSame( '', $actual ); + } - $this->assertEquals( 'wp-load-polyfill-importmap', $id ); + /** + * Data provider. + * + * @return array + */ + public static function data_invalid_script_module_data(): array { + return array( + 'null' => array( null ), + 'stdClass' => array( new stdClass() ), + 'number 1' => array( 1 ), + 'string' => array( 'string' ), + ); } } diff --git a/tests/phpunit/tests/style-engine/styleEngine.php b/tests/phpunit/tests/style-engine/styleEngine.php index 9092ce5b6df03..686865c6803ab 100644 --- a/tests/phpunit/tests/style-engine/styleEngine.php +++ b/tests/phpunit/tests/style-engine/styleEngine.php @@ -28,6 +28,7 @@ public function tear_down() { * @ticket 58549 * @ticket 58590 * @ticket 60175 + * @ticket 61720 * * @covers ::wp_style_engine_get_styles * @@ -539,22 +540,24 @@ public function data_wp_style_engine_get_styles() { 'inline_background_image_url_with_background_size' => array( 'block_styles' => array( 'background' => array( - 'backgroundImage' => array( + 'backgroundImage' => array( 'url' => 'https://example.com/image.jpg', ), - 'backgroundPosition' => 'center', - 'backgroundRepeat' => 'no-repeat', - 'backgroundSize' => 'cover', + 'backgroundPosition' => 'center', + 'backgroundRepeat' => 'no-repeat', + 'backgroundSize' => 'cover', + 'backgroundAttachment' => 'fixed', ), ), 'options' => array(), 'expected_output' => array( - 'css' => "background-image:url('https://example.com/image.jpg');background-position:center;background-repeat:no-repeat;background-size:cover;", + 'css' => "background-image:url('https://example.com/image.jpg');background-position:center;background-repeat:no-repeat;background-size:cover;background-attachment:fixed;", 'declarations' => array( - 'background-image' => "url('https://example.com/image.jpg')", - 'background-position' => 'center', - 'background-repeat' => 'no-repeat', - 'background-size' => 'cover', + 'background-image' => "url('https://example.com/image.jpg')", + 'background-position' => 'center', + 'background-repeat' => 'no-repeat', + 'background-size' => 'cover', + 'background-attachment' => 'fixed', ), ), ), diff --git a/tests/phpunit/tests/term/cache.php b/tests/phpunit/tests/term/cache.php index 0651551f941e9..f299bf9bed643 100644 --- a/tests/phpunit/tests/term/cache.php +++ b/tests/phpunit/tests/term/cache.php @@ -116,9 +116,6 @@ public function test_get_term_should_update_term_cache_when_passed_an_object() { $num_queries = get_num_queries(); - // get_term() will only be update the cache if the 'filter' prop is unset. - unset( $term_object->filter ); - $term_object_2 = get_term( $term_object, 'wptests_tax' ); // No new queries should have fired. diff --git a/tests/phpunit/tests/term/getTerm.php b/tests/phpunit/tests/term/getTerm.php index ed6acab691fc5..a72ebca40ad40 100644 --- a/tests/phpunit/tests/term/getTerm.php +++ b/tests/phpunit/tests/term/getTerm.php @@ -98,7 +98,6 @@ public function test_passing_term_object_should_skip_database_query_when_filter_ $num_queries = get_num_queries(); - unset( $term->filter ); $term_a = get_term( $term, 'wptests_tax' ); $this->assertSame( $num_queries, get_num_queries() ); diff --git a/tests/phpunit/tests/term/wpInsertTerm.php b/tests/phpunit/tests/term/wpInsertTerm.php index 798db9233bfc8..0bf95b9a10279 100644 --- a/tests/phpunit/tests/term/wpInsertTerm.php +++ b/tests/phpunit/tests/term/wpInsertTerm.php @@ -186,7 +186,6 @@ public function test_wp_insert_term_slug_0() { public function test_wp_insert_term_duplicate_name() { $term = self::factory()->tag->create_and_get( array( 'name' => 'Bozo' ) ); $this->assertNotWPError( $term ); - $this->assertEmpty( $term->errors ); // Test existing term name with unique slug. $term1 = self::factory()->tag->create( diff --git a/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php b/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php index fb547db50b763..7d34e8d2a6b33 100644 --- a/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php +++ b/tests/phpunit/tests/theme/wpAddGlobalStylesForBlocks.php @@ -75,87 +75,6 @@ public function test_third_party_blocks_inline_styles_get_registered_to_global_s ); } - /** - * Ensure that the block cache is set for global styles. - * - * @ticket 59595 - */ - public function test_styles_for_blocks_cache_is_set() { - $this->set_up_third_party_block(); - - wp_register_style( 'global-styles', false, array(), true, true ); - - $cache_key = $this->get_wp_styles_for_blocks_cache_key(); - $styles_for_blocks_before = get_site_transient( $cache_key ); - $this->assertFalse( $styles_for_blocks_before ); - - wp_add_global_styles_for_blocks(); - - $styles_for_blocks_after = get_site_transient( $cache_key ); - $this->assertNotEmpty( $styles_for_blocks_after ); - } - - /** - * Confirm that the block cache is skipped when in dev mode for themes. - * - * @ticket 59595 - */ - public function test_styles_for_blocks_skips_cache_in_dev_mode() { - global $_wp_tests_development_mode; - - $orig_dev_mode = $_wp_tests_development_mode; - - // Setting development mode to theme should skip the cache. - $_wp_tests_development_mode = 'theme'; - - wp_register_style( 'global-styles', false, array(), true, true ); - - // Initial register of global styles. - wp_add_global_styles_for_blocks(); - - $cache_key = $this->get_wp_styles_for_blocks_cache_key(); - $styles_for_blocks_initial = get_site_transient( $cache_key ); - - // Cleanup. - $_wp_tests_development_mode = $orig_dev_mode; - - $this->assertFalse( $styles_for_blocks_initial ); - } - - /** - * Confirm that the block cache is updated if the block meta has changed. - * - * @ticket 59595 - */ - public function test_styles_for_blocks_cache_is_skipped() { - wp_register_style( 'global-styles', false, array(), true, true ); - - // Initial register of global styles. - wp_add_global_styles_for_blocks(); - - $cache_key = $this->get_wp_styles_for_blocks_cache_key(); - $styles_for_blocks_initial = get_site_transient( $cache_key ); - $this->assertNotEmpty( $styles_for_blocks_initial, 'Initial cache was not set.' ); - - $this->set_up_third_party_block(); - - /* - * Call register of global styles again to ensure the cache is updated. - * In normal conditions, this function is only called once per request. - */ - wp_add_global_styles_for_blocks(); - - $cache_key = $this->get_wp_styles_for_blocks_cache_key(); - $styles_for_blocks_updated = get_site_transient( $cache_key ); - $this->assertNotEmpty( $styles_for_blocks_updated, 'Updated cache was not set.' ); - - $this->assertNotEquals( - $styles_for_blocks_initial, - $styles_for_blocks_updated, - 'Block style cache was not updated.' - ); - } - /** * @ticket 56915 * @ticket 61165 @@ -334,25 +253,4 @@ private function get_global_styles() { $actual = wp_styles()->get_data( 'global-styles', 'after' ); return is_array( $actual ) ? $actual : array(); } - - /** - * Get cache key for `wp_styles_for_blocks`. - * - * @return string The cache key. - */ - private function get_wp_styles_for_blocks_cache_key() { - $tree = WP_Theme_JSON_Resolver::get_merged_data(); - $block_nodes = $tree->get_styles_block_nodes(); - // md5 is a costly operation, so we hashing global settings and block_node in a single call. - $hash = md5( - wp_json_encode( - array( - 'global_setting' => wp_get_global_settings(), - 'block_nodes' => $block_nodes, - ) - ) - ); - - return "wp_styles_for_blocks:$hash"; - } } diff --git a/tests/phpunit/tests/theme/wpGetBlockCssSelector.php b/tests/phpunit/tests/theme/wpGetBlockCssSelector.php index 670007dbb0103..d52dff978998c 100644 --- a/tests/phpunit/tests/theme/wpGetBlockCssSelector.php +++ b/tests/phpunit/tests/theme/wpGetBlockCssSelector.php @@ -47,7 +47,7 @@ public function test_get_root_selector_via_selectors_api() { ); $selector = wp_get_block_css_selector( $block_type ); - $this->assertEquals( '.wp-custom-block-class', $selector ); + $this->assertSame( '.wp-custom-block-class', $selector ); } /** @@ -61,7 +61,7 @@ public function test_get_root_selector_via_experimental_property() { ); $selector = wp_get_block_css_selector( $block_type ); - $this->assertEquals( '.experimental-selector', $selector ); + $this->assertSame( '.experimental-selector', $selector ); } /** @@ -75,7 +75,7 @@ public function test_default_root_selector_generation_for_core_block() { ); $selector = wp_get_block_css_selector( $block_type ); - $this->assertEquals( '.wp-block-without-selectors-or-supports', $selector ); + $this->assertSame( '.wp-block-without-selectors-or-supports', $selector ); } /** @@ -89,7 +89,7 @@ public function test_default_root_selector_generation() { ); $selector = wp_get_block_css_selector( $block_type ); - $this->assertEquals( '.wp-block-test-without-selectors-or-supports', $selector ); + $this->assertSame( '.wp-block-test-without-selectors-or-supports', $selector ); } /** @@ -103,7 +103,7 @@ public function test_get_feature_selector_via_selectors_api() { ); $selector = wp_get_block_css_selector( $block_type, 'typography' ); - $this->assertEquals( '.typography', $selector ); + $this->assertSame( '.typography', $selector ); } /** @@ -117,7 +117,7 @@ public function test_get_feature_selector_via_selectors_api_shorthand_property() ); $selector = wp_get_block_css_selector( $block_type, 'typography' ); - $this->assertEquals( '.typography', $selector ); + $this->assertSame( '.typography', $selector ); } /** @@ -131,7 +131,7 @@ public function test_no_feature_level_selector_via_selectors_api() { ); $selector = wp_get_block_css_selector( $block_type, 'typography' ); - $this->assertEquals( null, $selector ); + $this->assertSame( null, $selector ); } /** @@ -145,7 +145,7 @@ public function test_fallback_feature_level_selector_via_selectors_api_to_genera ); $selector = wp_get_block_css_selector( $block_type, 'typography', true ); - $this->assertEquals( '.wp-block-test-fallback-feature-selector', $selector ); + $this->assertSame( '.wp-block-test-fallback-feature-selector', $selector ); } /** @@ -159,7 +159,7 @@ public function test_fallback_feature_level_selector_via_selectors_api() { ); $selector = wp_get_block_css_selector( $block_type, 'typography', true ); - $this->assertEquals( '.fallback-root-selector', $selector ); + $this->assertSame( '.fallback-root-selector', $selector ); } /** @@ -177,7 +177,7 @@ public function test_get_feature_selector_via_experimental_property() { ); $selector = wp_get_block_css_selector( $block_type, 'typography' ); - $this->assertEquals( '.wp-block-test-experimental-feature-selector .experimental-typography', $selector ); + $this->assertSame( '.wp-block-test-experimental-feature-selector .experimental-typography', $selector ); } /** @@ -191,7 +191,7 @@ public function test_fallback_feature_selector_via_experimental_property() { ); $selector = wp_get_block_css_selector( $block_type, 'typography', true ); - $this->assertEquals( '.wp-block-test-fallback-feature-selector', $selector ); + $this->assertSame( '.wp-block-test-fallback-feature-selector', $selector ); } /** @@ -205,7 +205,7 @@ public function test_no_feature_selector_via_experimental_property() { ); $selector = wp_get_block_css_selector( $block_type, 'typography' ); - $this->assertEquals( null, $selector ); + $this->assertSame( null, $selector ); } /** @@ -227,7 +227,7 @@ public function test_get_subfeature_selector_via_selectors_api() { array( 'typography', 'textDecoration' ) ); - $this->assertEquals( '.root .typography .text-decoration', $selector ); + $this->assertSame( '.root .typography .text-decoration', $selector ); } /** @@ -248,7 +248,7 @@ public function test_fallback_subfeature_selector_via_selectors_api() { true ); - $this->assertEquals( '.root .typography', $selector ); + $this->assertSame( '.root .typography', $selector ); } /** @@ -262,7 +262,7 @@ public function test_no_subfeature_level_selector_via_selectors_api() { ); $selector = wp_get_block_css_selector( $block_type, array( 'typography', 'fontSize' ) ); - $this->assertEquals( null, $selector ); + $this->assertSame( null, $selector ); } /** @@ -280,7 +280,7 @@ public function test_fallback_subfeature_selector_via_experimental_property() { array( 'typography', 'fontSize' ), true ); - $this->assertEquals( '.wp-block-test-fallback-subfeature-selector', $selector ); + $this->assertSame( '.wp-block-test-fallback-subfeature-selector', $selector ); } /** @@ -297,7 +297,7 @@ public function test_no_subfeature_selector_via_experimental_property() { $block_type, array( 'typography', 'fontSize' ) ); - $this->assertEquals( null, $selector ); + $this->assertSame( null, $selector ); } /** @@ -311,10 +311,10 @@ public function test_empty_target_returns_null() { ); $selector = wp_get_block_css_selector( $block_type, array() ); - $this->assertEquals( null, $selector ); + $this->assertSame( null, $selector ); $selector = wp_get_block_css_selector( $block_type, '' ); - $this->assertEquals( null, $selector ); + $this->assertSame( null, $selector ); } /** @@ -328,10 +328,10 @@ public function test_string_targets_for_features() { ); $selector = wp_get_block_css_selector( $block_type, 'typography' ); - $this->assertEquals( '.found', $selector ); + $this->assertSame( '.found', $selector ); $selector = wp_get_block_css_selector( $block_type, array( 'typography' ) ); - $this->assertEquals( '.found', $selector ); + $this->assertSame( '.found', $selector ); } /** @@ -347,9 +347,9 @@ public function test_string_targets_for_subfeatures() { ); $selector = wp_get_block_css_selector( $block_type, 'typography.fontSize' ); - $this->assertEquals( '.found', $selector ); + $this->assertSame( '.found', $selector ); $selector = wp_get_block_css_selector( $block_type, array( 'typography', 'fontSize' ) ); - $this->assertEquals( '.found', $selector ); + $this->assertSame( '.found', $selector ); } } diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 6a0597be82508..2de327dd7c47e 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -395,6 +395,8 @@ public function test_get_settings_appearance_false_does_not_opt_in() { * @ticket 60365 * @ticket 60936 * @ticket 61165 + * @ticket 61630 + * @ticket 61704 */ public function test_get_stylesheet() { $theme_json = new WP_Theme_JSON( @@ -565,7 +567,7 @@ public function test_get_stylesheet() { ); $variables = ':root{--wp--preset--color--grey: grey;--wp--preset--gradient--custom-gradient: linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%);--wp--preset--font-size--small: 14px;--wp--preset--font-size--big: 41px;--wp--preset--font-family--arial: Arial, serif;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}'; - $styles = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:root :where(body){color: var(--wp--preset--color--grey);}:root :where(a:where(:not(.wp-element-button))){background-color: #333;color: #111;}:root :where(.wp-element-button, .wp-block-button__link){box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.66);}:root :where(.wp-block-cover){min-height: unset;aspect-ratio: 16/9;}:root :where(.wp-block-group){background: var(--wp--preset--gradient--custom-gradient);border-radius: 10px;min-height: 50vh;padding: 24px;}:root :where(.wp-block-group a:where(:not(.wp-element-button))){color: #111;}:root :where(.wp-block-heading){color: #123456;}:root :where(.wp-block-heading a:where(:not(.wp-element-button))){background-color: #333;color: #111;font-size: 60px;}:root :where(.wp-block-media-text){text-align: center;}:root :where(.wp-block-post-date){color: #123456;}:root :where(.wp-block-post-date a:where(:not(.wp-element-button))){background-color: #777;color: #555;}:root :where(.wp-block-post-excerpt){column-count: 2;}:root :where(.wp-block-image){margin-bottom: 30px;}:root :where(.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder){border-top-left-radius: 10px;border-bottom-right-radius: 1em;}:root :where(.wp-block-image img, .wp-block-image .components-placeholder){filter: var(--wp--preset--duotone--custom-duotone);}'; + $styles = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}:root :where(.wp-element-button, .wp-block-button__link){box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.66);}:root :where(.wp-block-cover){min-height: unset;aspect-ratio: 16/9;}:root :where(.wp-block-group){background: var(--wp--preset--gradient--custom-gradient);border-radius: 10px;min-height: 50vh;padding: 24px;}:root :where(.wp-block-group a:where(:not(.wp-element-button))){color: #111;}:root :where(.wp-block-heading){color: #123456;}:root :where(.wp-block-heading a:where(:not(.wp-element-button))){background-color: #333;color: #111;font-size: 60px;}:root :where(.wp-block-media-text){text-align: center;}:root :where(.wp-block-post-date){color: #123456;}:root :where(.wp-block-post-date a:where(:not(.wp-element-button))){background-color: #777;color: #555;}:root :where(.wp-block-post-excerpt){column-count: 2;}:root :where(.wp-block-image){margin-bottom: 30px;}:root :where(.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder){border-top-left-radius: 10px;border-bottom-right-radius: 1em;}:root :where(.wp-block-image img, .wp-block-image .components-placeholder){filter: var(--wp--preset--duotone--custom-duotone);}'; $presets = '.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}.has-custom-gradient-gradient-background{background: var(--wp--preset--gradient--custom-gradient) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-big-font-size{font-size: var(--wp--preset--font-size--big) !important;}.has-arial-font-family{font-family: var(--wp--preset--font-family--arial) !important;}'; $all = $variables . $styles . $presets; @@ -682,6 +684,7 @@ public function test_get_stylesheet_skips_disabled_protected_properties() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61829 */ public function test_get_stylesheet_renders_enabled_protected_properties() { $theme_json = new WP_Theme_JSON( @@ -700,7 +703,7 @@ public function test_get_stylesheet_renders_enabled_protected_properties() { ) ); - $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: 1em;margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: 1em;margin-block-end: 0;}.is-layout-flex {gap: 1em;}.is-layout-grid {gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}'; + $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1em;}:root :where(.is-layout-grid){gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}'; $this->assertSame( $expected, $theme_json->get_stylesheet() ); $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -835,6 +838,7 @@ public function test_get_stylesheet_generates_proper_classes_and_css_vars_from_s * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61630 */ public function test_get_styles_for_block_handles_whitelisted_element_pseudo_selectors() { $theme_json = new WP_Theme_JSON( @@ -882,7 +886,7 @@ public function test_get_styles_for_block_handles_whitelisted_element_pseudo_sel 'selector' => 'a:where(:not(.wp-element-button)):focus', ); - $link_style = ':root :where(a:where(:not(.wp-element-button))){background-color: red;color: green;}'; + $link_style = 'a:where(:not(.wp-element-button)){background-color: red;color: green;}'; $hover_style = ':root :where(a:where(:not(.wp-element-button)):hover){background-color: green;color: red;font-size: 10em;text-transform: uppercase;}'; $focus_style = ':root :where(a:where(:not(.wp-element-button)):focus){background-color: black;color: yellow;}'; @@ -938,6 +942,7 @@ public function test_get_stylesheet_handles_only_pseudo_selector_rules_for_given * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61630 */ public function test_get_stylesheet_ignores_pseudo_selectors_on_non_whitelisted_elements() { $theme_json = new WP_Theme_JSON( @@ -968,7 +973,7 @@ public function test_get_stylesheet_ignores_pseudo_selectors_on_non_whitelisted_ ) ); - $expected = ':root :where(h4){background-color: red;color: green;}'; + $expected = 'h4{background-color: red;color: green;}'; $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); } @@ -978,6 +983,7 @@ public function test_get_stylesheet_ignores_pseudo_selectors_on_non_whitelisted_ * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61630 */ public function test_get_stylesheet_ignores_non_whitelisted_pseudo_selectors() { $theme_json = new WP_Theme_JSON( @@ -1008,7 +1014,7 @@ public function test_get_stylesheet_ignores_non_whitelisted_pseudo_selectors() { ) ); - $expected = ':root :where(a:where(:not(.wp-element-button))){background-color: red;color: green;}:root :where(a:where(:not(.wp-element-button)):hover){background-color: green;color: red;}'; + $expected = 'a:where(:not(.wp-element-button)){background-color: red;color: green;}:root :where(a:where(:not(.wp-element-button)):hover){background-color: green;color: red;}'; $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); $this->assertStringNotContainsString( 'a:levitate{', $theme_json->get_stylesheet( array( 'styles' ) ) ); @@ -1022,6 +1028,7 @@ public function test_get_stylesheet_ignores_non_whitelisted_pseudo_selectors() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61630 */ public function test_get_stylesheet_handles_priority_of_elements_vs_block_elements_pseudo_selectors() { $theme_json = new WP_Theme_JSON( @@ -1060,7 +1067,7 @@ public function test_get_stylesheet_handles_priority_of_elements_vs_block_elemen ) ); - $expected = ':root :where(a:where(:not(.wp-element-button))){background-color: red;color: green;}:root :where(a:where(:not(.wp-element-button)):hover){background-color: green;color: red;}:root :where(.wp-block-group a:where(:not(.wp-element-button)):hover){background-color: black;color: yellow;}'; + $expected = 'a:where(:not(.wp-element-button)){background-color: red;color: green;}:root :where(a:where(:not(.wp-element-button)):hover){background-color: green;color: red;}:root :where(.wp-block-group a:where(:not(.wp-element-button)):hover){background-color: black;color: yellow;}'; $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); } @@ -1071,6 +1078,7 @@ public function test_get_stylesheet_handles_priority_of_elements_vs_block_elemen * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61829 */ public function test_get_stylesheet_generates_layout_styles() { $theme_json = new WP_Theme_JSON( @@ -1096,7 +1104,7 @@ public function test_get_stylesheet_generates_layout_styles() { // Results also include root site blocks styles. $this->assertSame( - ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: 1em;margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: 1em;margin-block-end: 0;}.is-layout-flex {gap: 1em;}.is-layout-grid {gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}', + ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1em;}:root :where(.is-layout-grid){gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}', $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -1107,6 +1115,7 @@ public function test_get_stylesheet_generates_layout_styles() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61829 */ public function test_get_stylesheet_generates_layout_styles_with_spacing_presets() { $theme_json = new WP_Theme_JSON( @@ -1132,7 +1141,7 @@ public function test_get_stylesheet_generates_layout_styles_with_spacing_presets // Results also include root site blocks styles. $this->assertSame( - ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: var(--wp--preset--spacing--60); }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}.is-layout-flex {gap: var(--wp--preset--spacing--60);}.is-layout-grid {gap: var(--wp--preset--spacing--60);}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}', + ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: var(--wp--preset--spacing--60); margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: var(--wp--preset--spacing--60); }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: var(--wp--preset--spacing--60);margin-block-end: 0;}:root :where(.is-layout-flex){gap: var(--wp--preset--spacing--60);}:root :where(.is-layout-grid){gap: var(--wp--preset--spacing--60);}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}', $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -1233,6 +1242,7 @@ public function test_get_stylesheet_skips_layout_styles() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61829 */ public function test_get_stylesheet_generates_valid_block_gap_values_and_skips_null_or_false_values() { $theme_json = new WP_Theme_JSON( @@ -1279,7 +1289,7 @@ public function test_get_stylesheet_generates_valid_block_gap_values_and_skips_n ); $this->assertSame( - ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1rem; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1rem; }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: 1rem;margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: 1rem;margin-block-end: 0;}.is-layout-flex {gap: 1rem;}.is-layout-grid {gap: 1rem;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:root :where(.wp-block-post-content){color: gray;}.wp-block-social-links-is-layout-flow > :first-child{margin-block-start: 0;}.wp-block-social-links-is-layout-flow > :last-child{margin-block-end: 0;}.wp-block-social-links-is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-social-links-is-layout-constrained > :first-child{margin-block-start: 0;}.wp-block-social-links-is-layout-constrained > :last-child{margin-block-end: 0;}.wp-block-social-links-is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-social-links-is-layout-flex {gap: 0;}.wp-block-social-links-is-layout-grid {gap: 0;}.wp-block-buttons-is-layout-flow > :first-child{margin-block-start: 0;}.wp-block-buttons-is-layout-flow > :last-child{margin-block-end: 0;}.wp-block-buttons-is-layout-flow > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-buttons-is-layout-constrained > :first-child{margin-block-start: 0;}.wp-block-buttons-is-layout-constrained > :last-child{margin-block-end: 0;}.wp-block-buttons-is-layout-constrained > *{margin-block-start: 0;margin-block-end: 0;}.wp-block-buttons-is-layout-flex {gap: 0;}.wp-block-buttons-is-layout-grid {gap: 0;}', + ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1rem; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1rem; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1rem;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1rem;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1rem;}:root :where(.is-layout-grid){gap: 1rem;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:root :where(.wp-block-post-content){color: gray;}:root :where(.wp-block-social-links-is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.wp-block-social-links-is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.wp-block-social-links-is-layout-flow) > *{margin-block-start: 0;margin-block-end: 0;}:root :where(.wp-block-social-links-is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.wp-block-social-links-is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.wp-block-social-links-is-layout-constrained) > *{margin-block-start: 0;margin-block-end: 0;}:root :where(.wp-block-social-links-is-layout-flex){gap: 0;}:root :where(.wp-block-social-links-is-layout-grid){gap: 0;}:root :where(.wp-block-buttons-is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.wp-block-buttons-is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-flow) > *{margin-block-start: 0;margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-constrained) > *{margin-block-start: 0;margin-block-end: 0;}:root :where(.wp-block-buttons-is-layout-flex){gap: 0;}:root :where(.wp-block-buttons-is-layout-grid){gap: 0;}', $theme_json->get_stylesheet() ); } @@ -1359,6 +1369,8 @@ public function test_get_stylesheet_custom_root_selector() { * * @ticket 61118 * @ticket 61165 + * @ticket 61630 + * @ticket 61704 */ public function test_get_stylesheet_generates_fluid_typography_values() { register_block_type( @@ -1413,7 +1425,7 @@ public function test_get_stylesheet_generates_fluid_typography_values() { unregister_block_type( 'test/clamp-me' ); $this->assertSame( - ':root{--wp--preset--font-size--pickles: clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.156), 16px);--wp--preset--font-size--toast: clamp(14.642px, 0.915rem + ((1vw - 3.2px) * 0.575), 22px);}:root :where(body){font-size: clamp(0.875em, 0.875rem + ((1vw - 0.2em) * 0.156), 1em);}:root :where(h1){font-size: clamp(50.171px, 3.136rem + ((1vw - 3.2px) * 3.893), 100px);}:root :where(.wp-block-test-clamp-me){font-size: clamp(27.894px, 1.743rem + ((1vw - 3.2px) * 1.571), 48px);}.has-pickles-font-size{font-size: var(--wp--preset--font-size--pickles) !important;}.has-toast-font-size{font-size: var(--wp--preset--font-size--toast) !important;}', + ':root{--wp--preset--font-size--pickles: clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.156), 16px);--wp--preset--font-size--toast: clamp(14.642px, 0.915rem + ((1vw - 3.2px) * 0.575), 22px);}body{font-size: clamp(0.875em, 0.875rem + ((1vw - 0.2em) * 0.156), 1em);}h1{font-size: clamp(50.171px, 3.136rem + ((1vw - 3.2px) * 3.893), 100px);}:root :where(.wp-block-test-clamp-me){font-size: clamp(27.894px, 1.743rem + ((1vw - 3.2px) * 1.571), 48px);}.has-pickles-font-size{font-size: var(--wp--preset--font-size--pickles) !important;}.has-toast-font-size{font-size: var(--wp--preset--font-size--toast) !important;}', $theme_json->get_stylesheet( array( 'styles', 'variables', 'presets' ), null, array( 'skip_root_layout_styles' => true ) ) ); } @@ -2322,6 +2334,115 @@ public function test_merge_incoming_data_presets_use_default_names() { $this->assertSameSetsWithIndex( $expected, $actual ); } + /** + * @ticket 61858 + */ + public function test_merge_incoming_background_styles() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/quote.png', + ), + 'backgroundSize' => 'cover', + ), + 'blocks' => array( + 'core/group' => array( + 'background' => array( + 'backgroundImage' => array( + 'ref' => 'styles.blocks.core/verse.background.backgroundImage', + ), + 'backgroundAttachment' => 'fixed', + ), + ), + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/quote.png', + ), + 'backgroundAttachment' => array( + 'ref' => 'styles.blocks.core/group.background.backgroundAttachment', + ), + ), + ), + ), + ), + ) + ); + + $update_background_image_styles = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'background' => array( + 'backgroundSize' => 'contain', + ), + 'blocks' => array( + 'core/group' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/group.png', + ), + ), + ), + 'core/quote' => array( + 'background' => array( + 'backgroundAttachment' => 'fixed', + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'ref' => 'styles.blocks.core/group.background.backgroundImage', + ), + ), + ), + ), + ), + ); + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/quote.png', + ), + 'backgroundSize' => 'contain', + ), + 'blocks' => array( + 'core/group' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/group.png', + ), + 'backgroundAttachment' => 'fixed', + ), + ), + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/quote.png', + ), + 'backgroundAttachment' => 'fixed', + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'ref' => 'styles.blocks.core/group.background.backgroundImage', + ), + ), + ), + ), + ), + ); + $theme_json->merge( new WP_Theme_JSON( $update_background_image_styles ) ); + $actual = $theme_json->get_raw_data(); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + /** * @ticket 54336 */ @@ -3542,6 +3663,7 @@ public function test_get_element_class_name_invalid() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61704 */ public function test_get_property_value_valid() { $theme_json = new WP_Theme_JSON( @@ -3564,7 +3686,7 @@ public function test_get_property_value_valid() { ) ); - $expected = ':root :where(body){background-color: #ffffff;color: #000000;}:root :where(.wp-element-button, .wp-block-button__link){background-color: #000000;color: #ffffff;}'; + $expected = 'body{background-color: #ffffff;color: #000000;}:root :where(.wp-element-button, .wp-block-button__link){background-color: #000000;color: #ffffff;}'; $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); } @@ -3624,6 +3746,7 @@ public function data_get_property_value_should_return_string_for_invalid_paths_o * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61704 * @expectedIncorrectUsage get_property_value */ public function test_get_property_value_loop() { @@ -3647,7 +3770,7 @@ public function test_get_property_value_loop() { ) ); - $expected = ':root :where(body){background-color: #ffffff;}:root :where(.wp-element-button, .wp-block-button__link){color: #ffffff;}'; + $expected = 'body{background-color: #ffffff;}:root :where(.wp-element-button, .wp-block-button__link){color: #ffffff;}'; $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); } @@ -3660,6 +3783,7 @@ public function test_get_property_value_loop() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61704 * @expectedIncorrectUsage get_property_value */ public function test_get_property_value_recursion() { @@ -3683,7 +3807,7 @@ public function test_get_property_value_recursion() { ) ); - $expected = ':root :where(body){background-color: #ffffff;color: #ffffff;}:root :where(.wp-element-button, .wp-block-button__link){color: #ffffff;}'; + $expected = 'body{background-color: #ffffff;color: #ffffff;}:root :where(.wp-element-button, .wp-block-button__link){color: #ffffff;}'; $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); } @@ -3695,6 +3819,7 @@ public function test_get_property_value_recursion() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61704 * @expectedIncorrectUsage get_property_value */ public function test_get_property_value_self() { @@ -3710,7 +3835,7 @@ public function test_get_property_value_self() { ) ); - $expected = ':root :where(body){background-color: #ffffff;}'; + $expected = 'body{background-color: #ffffff;}'; $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); } @@ -3720,6 +3845,7 @@ public function test_get_property_value_self() { * @ticket 60936 * @ticket 61304 * @ticket 61165 + * @ticket 61704 */ public function test_get_styles_for_block_with_padding_aware_alignments() { $theme_json = new WP_Theme_JSON( @@ -3746,7 +3872,7 @@ public function test_get_styles_for_block_with_padding_aware_alignments() { 'selector' => 'body', ); - $expected = ':where(body) { margin: 0; }.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(.has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) { padding-right: 0; padding-left: 0; }.has-global-padding :where(.has-global-padding:not(.wp-block-block, .alignfull, .alignwide)) > .alignfull { margin-left: 0; margin-right: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:root :where(body){--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}'; + $expected = ':where(body) { margin: 0; }.wp-site-blocks { padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom); }.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}body{--wp--style--root--padding-top: 10px;--wp--style--root--padding-right: 12px;--wp--style--root--padding-bottom: 10px;--wp--style--root--padding-left: 12px;}'; $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON::ROOT_BLOCK_SELECTOR, $metadata ); $style_rules = $theme_json->get_styles_for_block( $metadata ); $this->assertSame( $expected, $root_rules . $style_rules ); @@ -3757,6 +3883,7 @@ public function test_get_styles_for_block_with_padding_aware_alignments() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61704 */ public function test_get_styles_for_block_without_padding_aware_alignments() { $theme_json = new WP_Theme_JSON( @@ -3780,7 +3907,7 @@ public function test_get_styles_for_block_without_padding_aware_alignments() { 'selector' => 'body', ); - $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:root :where(body){padding-top: 10px;padding-right: 12px;padding-bottom: 10px;padding-left: 12px;}'; + $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}body{padding-top: 10px;padding-right: 12px;padding-bottom: 10px;padding-left: 12px;}'; $root_rules = $theme_json->get_root_layout_rules( WP_Theme_JSON::ROOT_BLOCK_SELECTOR, $metadata ); $style_rules = $theme_json->get_styles_for_block( $metadata ); $this->assertSame( $expected, $root_rules . $style_rules ); @@ -3819,6 +3946,7 @@ public function test_get_styles_with_content_width() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61829 */ public function test_get_styles_with_appearance_tools() { $theme_json = new WP_Theme_JSON( @@ -3835,7 +3963,7 @@ public function test_get_styles_with_appearance_tools() { 'selector' => 'body', ); - $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: ; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: ; }.is-layout-flow > :first-child{margin-block-start: 0;}.is-layout-flow > :last-child{margin-block-end: 0;}.is-layout-flow > *{margin-block-start: 1;margin-block-end: 0;}.is-layout-constrained > :first-child{margin-block-start: 0;}.is-layout-constrained > :last-child{margin-block-end: 0;}.is-layout-constrained > *{margin-block-start: 1;margin-block-end: 0;}.is-layout-flex {gap: 1;}.is-layout-grid {gap: 1;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}'; + $expected = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: ; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: ; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1;}:root :where(.is-layout-grid){gap: 1;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}'; $this->assertSame( $expected, $theme_json->get_root_layout_rules( WP_Theme_JSON::ROOT_BLOCK_SELECTOR, $metadata ) ); } @@ -3930,6 +4058,92 @@ public function test_sanitize_for_unregistered_style_variations() { $this->assertSameSetsWithIndex( $expected, $sanitized_theme_json, 'Sanitized theme.json styles does not match' ); } + /** + * @ticket 61451 + */ + public function test_unwraps_block_style_variations() { + register_block_style( + array( 'core/paragraph', 'core/group' ), + array( + 'name' => 'myVariation', + 'label' => 'My variation', + ) + ); + + $input = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'variations' => array( + 'myVariation' => array( + 'color' => array( + 'background' => 'topLevel', + 'gradient' => 'topLevel', + ), + 'typography' => array( + 'fontFamily' => 'topLevel', + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'variations' => array( + 'myVariation' => array( + 'color' => array( + 'background' => 'blockLevel', + 'text' => 'blockLevel', + ), + 'outline' => array( + 'offset' => 'blockLevel', + ), + ), + ), + ), + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'variations' => array( + 'myVariation' => array( + 'color' => array( + 'background' => 'blockLevel', + 'gradient' => 'topLevel', + 'text' => 'blockLevel', + ), + 'typography' => array( + 'fontFamily' => 'topLevel', + ), + 'outline' => array( + 'offset' => 'blockLevel', + ), + ), + ), + ), + 'core/group' => array( + 'variations' => array( + 'myVariation' => array( + 'color' => array( + 'background' => 'topLevel', + 'gradient' => 'topLevel', + ), + 'typography' => array( + 'fontFamily' => 'topLevel', + ), + ), + ), + ), + ), + ), + ); + $this->assertSameSetsWithIndex( $expected, $input->get_raw_data(), 'Unwrapped block style variations do not match' ); + } + /** * @ticket 57583 * @@ -4860,6 +5074,7 @@ public function test_shadow_preset_styles() { * @ticket 58550 * @ticket 60936 * @ticket 61165 + * @ticket 61630 */ public function test_get_shadow_styles_for_blocks() { $theme_json = new WP_Theme_JSON( @@ -4894,16 +5109,21 @@ public function test_get_shadow_styles_for_blocks() { ); $variable_styles = ':root{--wp--preset--shadow--natural: 5px 5px 0 0 black;}'; - $element_styles = ':root :where(a:where(:not(.wp-element-button))){box-shadow: var(--wp--preset--shadow--natural);}:root :where(.wp-element-button, .wp-block-button__link){box-shadow: var(--wp--preset--shadow--natural);}:root :where(p){box-shadow: var(--wp--preset--shadow--natural);}'; + $element_styles = 'a:where(:not(.wp-element-button)){box-shadow: var(--wp--preset--shadow--natural);}:root :where(.wp-element-button, .wp-block-button__link){box-shadow: var(--wp--preset--shadow--natural);}:root :where(p){box-shadow: var(--wp--preset--shadow--natural);}'; $expected_styles = $variable_styles . $element_styles; $this->assertSame( $expected_styles, $theme_json->get_stylesheet( array( 'styles', 'presets', 'variables' ), null, array( 'skip_root_layout_styles' => true ) ) ); } /** - * Tests that theme background image styles are correctly generated. + * Tests that theme background image styles are correctly generated, + * and that default background size of "cover" isn't + * applied (it's only applied to blocks). * * @ticket 61123 * @ticket 61165 + * @ticket 61720 + * @ticket 61704 + * @ticket 61858 */ public function test_get_top_level_background_image_styles() { $theme_json = new WP_Theme_JSON( @@ -4911,12 +5131,12 @@ public function test_get_top_level_background_image_styles() { 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'styles' => array( 'background' => array( - 'backgroundImage' => array( + 'backgroundImage' => array( 'url' => 'http://example.org/image.png', ), - 'backgroundSize' => 'contain', - 'backgroundRepeat' => 'no-repeat', - 'backgroundPosition' => 'center center', + 'backgroundRepeat' => 'no-repeat', + 'backgroundPosition' => 'center center', + 'backgroundAttachment' => 'fixed', ), ), ) @@ -4927,37 +5147,193 @@ public function test_get_top_level_background_image_styles() { 'selector' => 'body', ); - $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:root :where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}"; - $this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_stylesheet()" with top-level background styles type does not match expectations' ); + $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}body{background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-attachment: fixed;}"; + $this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_stylesheet()" with top-level background styles type do not match expectations' ); $theme_json = new WP_Theme_JSON( array( 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'styles' => array( 'background' => array( - 'backgroundImage' => "url('http://example.org/image.png')", - 'backgroundSize' => 'contain', - 'backgroundRepeat' => 'no-repeat', - 'backgroundPosition' => 'center center', + 'backgroundImage' => "url('http://example.org/image.png')", + 'backgroundSize' => 'contain', + 'backgroundRepeat' => 'no-repeat', + 'backgroundPosition' => 'center center', + 'backgroundAttachment' => 'fixed', ), ), ) ); - $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:root :where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}"; - $this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_stylesheet()" with top-level background image as string type does not match expectations' ); + $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}body{background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;background-attachment: fixed;}"; + $this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_stylesheet()" with top-level background image as string type do not match expectations' ); } /** - * @ticket 57536 - * @ticket 61165 + * Block-level global background image styles. + * + * @ticket 61588 + * @ticket 61720 + * @ticket 61858 */ - public function test_get_custom_css_handles_global_custom_css() { + public function test_get_block_background_image_styles() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'background' => array( + 'backgroundImage' => "url('http://example.org/group.png')", + 'backgroundRepeat' => 'no-repeat', + 'backgroundPosition' => 'center center', + 'backgroundAttachment' => 'fixed', + ), + ), + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/quote.png', + 'id' => 321, + ), + 'backgroundSize' => 'contain', + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/verse.png', + 'id' => 123, + ), + ), + ), + ), + ), + ) + ); + + $group_node = array( + 'name' => 'core/group', + 'path' => array( 'styles', 'blocks', 'core/group' ), + 'selector' => '.wp-block-group', + 'selectors' => array( + 'root' => '.wp-block-group', + ), + ); + + $group_styles = ":root :where(.wp-block-group){background-image: url('http://example.org/group.png');background-position: center center;background-repeat: no-repeat;background-attachment: fixed;}"; + $this->assertSame( $group_styles, $theme_json->get_styles_for_block( $group_node ), 'Styles returned from "::get_styles_for_block()" with core/group background styles as string type do not match expectations.' ); + + $quote_node = array( + 'name' => 'core/quote', + 'path' => array( 'styles', 'blocks', 'core/quote' ), + 'selector' => '.wp-block-quote', + 'selectors' => array( + 'root' => '.wp-block-quote', + ), + ); + + $quote_styles = ":root :where(.wp-block-quote){background-image: url('http://example.org/quote.png');background-position: 50% 50%;background-size: contain;}"; + $this->assertSame( $quote_styles, $theme_json->get_styles_for_block( $quote_node ), 'Styles returned from "::get_styles_for_block()" with core/quote default background styles do not match expectations.' ); + + $verse_node = array( + 'name' => 'core/verse', + 'path' => array( 'styles', 'blocks', 'core/verse' ), + 'selector' => '.wp-block-verse', + 'selectors' => array( + 'root' => '.wp-block-verse', + ), + ); + + $verse_styles = ":root :where(.wp-block-verse){background-image: url('http://example.org/verse.png');background-size: cover;}"; + $this->assertSame( $verse_styles, $theme_json->get_styles_for_block( $verse_node ), 'Styles returned from "::get_styles_for_block()" with default core/verse background styles as string type do not match expectations.' ); + } + + /** + * Testing background dynamic properties in theme.json. + * + * @ticket 61858 + */ + public function test_get_resolved_background_image_styles() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/top.png', + ), + 'backgroundSize' => 'contain', + 'backgroundRepeat' => 'repeat', + 'backgroundPosition' => '10% 20%', + 'backgroundAttachment' => 'scroll', + ), + 'blocks' => array( + 'core/group' => array( + 'background' => array( + 'backgroundImage' => array( + 'id' => 123, + 'url' => 'http://example.org/group.png', + ), + ), + ), + 'core/post-content' => array( + 'background' => array( + 'backgroundImage' => array( + 'ref' => 'styles.background.backgroundImage', + ), + 'backgroundSize' => array( + 'ref' => 'styles.background.backgroundSize', + ), + 'backgroundRepeat' => array( + 'ref' => 'styles.background.backgroundRepeat', + ), + 'backgroundPosition' => array( + 'ref' => 'styles.background.backgroundPosition', + ), + 'backgroundAttachment' => array( + 'ref' => 'styles.background.backgroundAttachment', + ), + ), + ), + ), + ), + ) + ); + + $expected = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}body{background-image: url('http://example.org/top.png');background-position: 10% 20%;background-repeat: repeat;background-size: contain;background-attachment: scroll;}:root :where(.wp-block-group){background-image: url('http://example.org/group.png');background-size: cover;}:root :where(.wp-block-post-content){background-image: url('http://example.org/top.png');background-position: 10% 20%;background-repeat: repeat;background-size: contain;background-attachment: scroll;}"; + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); + } + + /** + * Tests that base custom CSS is generated correctly. + * + * @ticket 61395 + */ + public function test_get_stylesheet_handles_base_custom_css() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'css' => 'body {color:purple;}', + ), + ) + ); + + $custom_css = 'body {color:purple;}'; + $this->assertSame( $custom_css, $theme_json->get_stylesheet( array( 'custom-css' ) ) ); + } + + /** + * Tests that block custom CSS is generated correctly. + * + * @ticket 61395 + */ + public function test_get_styles_for_block_handles_block_custom_css() { $theme_json = new WP_Theme_JSON( array( 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'styles' => array( - 'css' => 'body {color:purple;}', 'blocks' => array( 'core/paragraph' => array( 'css' => 'color:red;', @@ -4967,8 +5343,17 @@ public function test_get_custom_css_handles_global_custom_css() { ) ); - $custom_css = 'body {color:purple;}:root :where(p){color:red;}'; - $this->assertSame( $custom_css, $theme_json->get_custom_css() ); + $paragraph_node = array( + 'name' => 'core/paragraph', + 'path' => array( 'styles', 'blocks', 'core/paragraph' ), + 'selector' => 'p', + 'selectors' => array( + 'root' => 'p', + ), + ); + + $custom_css = ':root :where(p){color:red;}'; + $this->assertSame( $custom_css, $theme_json->get_styles_for_block( $paragraph_node ) ); } /** @@ -5046,6 +5431,7 @@ public function data_custom_css_for_user_caps() { /** * @ticket 61165 + * @ticket 61769 * * @dataProvider data_process_blocks_custom_css * @@ -5073,6 +5459,13 @@ public function test_process_blocks_custom_css( $input, $expected ) { public function data_process_blocks_custom_css() { return array( // Simple CSS without any nested selectors. + 'empty css' => array( + 'input' => array( + 'selector' => '.foo', + 'css' => '', + ), + 'expected' => '', + ), 'no nested selectors' => array( 'input' => array( 'selector' => '.foo', @@ -5088,13 +5481,20 @@ public function data_process_blocks_custom_css() { ), 'expected' => ':root :where(.foo){color: red; margin: auto;}:root :where(.foo.one){color: blue;}:root :where(.foo .two){color: green;}', ), + 'no root styles' => array( + 'input' => array( + 'selector' => '.foo', + 'css' => '&::before{color: red;}', + ), + 'expected' => ':root :where(.foo)::before{color: red;}', + ), // CSS with pseudo elements. 'with pseudo elements' => array( 'input' => array( 'selector' => '.foo', 'css' => 'color: red; margin: auto; &::before{color: blue;} & ::before{color: green;} &.one::before{color: yellow;} & .two::before{color: purple;}', ), - 'expected' => ':root :where(.foo){color: red; margin: auto;}:root :where(.foo::before){color: blue;}:root :where(.foo ::before){color: green;}:root :where(.foo.one::before){color: yellow;}:root :where(.foo .two::before){color: purple;}', + 'expected' => ':root :where(.foo){color: red; margin: auto;}:root :where(.foo)::before{color: blue;}:root :where(.foo) ::before{color: green;}:root :where(.foo.one)::before{color: yellow;}:root :where(.foo .two)::before{color: purple;}', ), // CSS with multiple root selectors. 'with multiple root selectors' => array( @@ -5102,7 +5502,7 @@ public function data_process_blocks_custom_css() { 'selector' => '.foo, .bar', 'css' => 'color: red; margin: auto; &.one{color: blue;} & .two{color: green;} &::before{color: yellow;} & ::before{color: purple;} &.three::before{color: orange;} & .four::before{color: skyblue;}', ), - 'expected' => ':root :where(.foo, .bar){color: red; margin: auto;}:root :where(.foo.one, .bar.one){color: blue;}:root :where(.foo .two, .bar .two){color: green;}:root :where(.foo::before, .bar::before){color: yellow;}:root :where(.foo ::before, .bar ::before){color: purple;}:root :where(.foo.three::before, .bar.three::before){color: orange;}:root :where(.foo .four::before, .bar .four::before){color: skyblue;}', + 'expected' => ':root :where(.foo, .bar){color: red; margin: auto;}:root :where(.foo.one, .bar.one){color: blue;}:root :where(.foo .two, .bar .two){color: green;}:root :where(.foo, .bar)::before{color: yellow;}:root :where(.foo, .bar) ::before{color: purple;}:root :where(.foo.three, .bar.three)::before{color: orange;}:root :where(.foo .four, .bar .four)::before{color: skyblue;}', ), ); } @@ -5155,20 +5555,20 @@ public function test_internal_syntax_is_converted_to_css_variables() { ); $styles = $result->get_raw_data()['styles']; - $this->assertEquals( 'var(--wp--preset--color--primary)', $styles['color']['background'], 'Top level: Assert the originally correct values are still correct.' ); - $this->assertEquals( 'var(--wp--preset--color--secondary)', $styles['color']['text'], 'Top level: Assert the originally correct values are still correct.' ); + $this->assertSame( 'var(--wp--preset--color--primary)', $styles['color']['background'], 'Top level: Assert the originally correct values are still correct.' ); + $this->assertSame( 'var(--wp--preset--color--secondary)', $styles['color']['text'], 'Top level: Assert the originally correct values are still correct.' ); - $this->assertEquals( 'var(--wp--preset--color--pri)', $styles['elements']['link']['color']['background'], 'Element top level: Assert the originally correct values are still correct.' ); - $this->assertEquals( 'var(--wp--preset--color--sec)', $styles['elements']['link']['color']['text'], 'Element top level: Assert the originally correct values are still correct.' ); + $this->assertSame( 'var(--wp--preset--color--pri)', $styles['elements']['link']['color']['background'], 'Element top level: Assert the originally correct values are still correct.' ); + $this->assertSame( 'var(--wp--preset--color--sec)', $styles['elements']['link']['color']['text'], 'Element top level: Assert the originally correct values are still correct.' ); - $this->assertEquals( 'var(--wp--preset--font-size--small)', $styles['blocks']['core/post-terms']['typography']['fontSize'], 'Top block level: Assert the originally correct values are still correct.' ); - $this->assertEquals( 'var(--wp--preset--color--secondary)', $styles['blocks']['core/post-terms']['color']['background'], 'Top block level: Assert the internal variables are convert to CSS custom variables.' ); + $this->assertSame( 'var(--wp--preset--font-size--small)', $styles['blocks']['core/post-terms']['typography']['fontSize'], 'Top block level: Assert the originally correct values are still correct.' ); + $this->assertSame( 'var(--wp--preset--color--secondary)', $styles['blocks']['core/post-terms']['color']['background'], 'Top block level: Assert the internal variables are convert to CSS custom variables.' ); - $this->assertEquals( 'var(--wp--preset--color--p)', $styles['blocks']['core/navigation']['elements']['link']['color']['background'], 'Elements block level: Assert the originally correct values are still correct.' ); - $this->assertEquals( 'var(--wp--preset--color--s)', $styles['blocks']['core/navigation']['elements']['link']['color']['text'], 'Elements block level: Assert the originally correct values are still correct.' ); + $this->assertSame( 'var(--wp--preset--color--p)', $styles['blocks']['core/navigation']['elements']['link']['color']['background'], 'Elements block level: Assert the originally correct values are still correct.' ); + $this->assertSame( 'var(--wp--preset--color--s)', $styles['blocks']['core/navigation']['elements']['link']['color']['text'], 'Elements block level: Assert the originally correct values are still correct.' ); - $this->assertEquals( 'var(--wp--preset--font-size--s)', $styles['blocks']['core/quote']['variations']['plain']['typography']['fontSize'], 'Style variations: Assert the originally correct values are still correct.' ); - $this->assertEquals( 'var(--wp--preset--color--s)', $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Style variations: Assert the internal variables are convert to CSS custom variables.' ); + $this->assertSame( 'var(--wp--preset--font-size--s)', $styles['blocks']['core/quote']['variations']['plain']['typography']['fontSize'], 'Style variations: Assert the originally correct values are still correct.' ); + $this->assertSame( 'var(--wp--preset--color--s)', $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Style variations: Assert the internal variables are convert to CSS custom variables.' ); } /** @@ -5315,42 +5715,42 @@ public function test_resolve_variables() { $styles = $theme_json::resolve_variables( $theme_json )->get_raw_data()['styles']; - $this->assertEquals( $primary_color, $styles['color']['background'], 'Top level: Assert values are converted' ); - $this->assertEquals( $raw_color_value, $styles['color']['text'], 'Top level: Assert raw values stay intact' ); + $this->assertSame( $primary_color, $styles['color']['background'], 'Top level: Assert values are converted' ); + $this->assertSame( $raw_color_value, $styles['color']['text'], 'Top level: Assert raw values stay intact' ); - $this->assertEquals( $contrast_color, $styles['elements']['button']['color']['text'], 'Elements: color' ); - $this->assertEquals( $small_font, $styles['elements']['button']['typography']['fontSize'], 'Elements: font-size' ); + $this->assertSame( $contrast_color, $styles['elements']['button']['color']['text'], 'Elements: color' ); + $this->assertSame( $small_font, $styles['elements']['button']['typography']['fontSize'], 'Elements: font-size' ); - $this->assertEquals( $large_font, $styles['blocks']['core/quote']['typography']['fontSize'], 'Blocks: font-size' ); - $this->assertEquals( $primary_color, $styles['blocks']['core/quote']['color']['background'], 'Blocks: color' ); - $this->assertEquals( $raw_color_value, $styles['blocks']['core/post-terms']['color']['background'], 'Blocks: Raw color value stays intact' ); - $this->assertEquals( $small_font, $styles['blocks']['core/post-terms']['typography']['fontSize'], 'Block core/post-terms: font-size' ); - $this->assertEquals( + $this->assertSame( $large_font, $styles['blocks']['core/quote']['typography']['fontSize'], 'Blocks: font-size' ); + $this->assertSame( $primary_color, $styles['blocks']['core/quote']['color']['background'], 'Blocks: color' ); + $this->assertSame( $raw_color_value, $styles['blocks']['core/post-terms']['color']['background'], 'Blocks: Raw color value stays intact' ); + $this->assertSame( $small_font, $styles['blocks']['core/post-terms']['typography']['fontSize'], 'Block core/post-terms: font-size' ); + $this->assertSame( "linear-gradient(90deg, $primary_color 0%, $secondary_color 35%, var(--wp--undefined--color--secondary) 100%)", $styles['blocks']['core/more']['color']['background'], 'Blocks: multiple colors and undefined color' ); - $this->assertEquals( 'var(--undefined--font-size--small)', $styles['blocks']['core/more']['typography']['fontSize'], 'Blocks: undefined font-size ' ); - $this->assertEquals( "calc($small_font + 20px)", $styles['blocks']['core/comment-content']['typography']['fontSize'], 'Blocks: font-size in random place' ); - $this->assertEquals( $primary_color, $styles['blocks']['core/comment-content']['color']['text'], 'Blocks: text color with fallback' ); - $this->assertEquals( $primary_color, $styles['blocks']['core/comment-content']['color']['background'], 'Blocks: background color with var as fallback' ); - $this->assertEquals( $primary_color, $styles['blocks']['core/navigation']['elements']['link']['color']['background'], 'Block element: background color' ); - $this->assertEquals( $secondary_color, $styles['blocks']['core/navigation']['elements']['link']['color']['text'], 'Block element: text color' ); - $this->assertEquals( $large_font, $styles['blocks']['core/navigation']['elements']['link']['typography']['fontSize'], 'Block element: font-size' ); + $this->assertSame( 'var(--undefined--font-size--small)', $styles['blocks']['core/more']['typography']['fontSize'], 'Blocks: undefined font-size ' ); + $this->assertSame( "calc($small_font + 20px)", $styles['blocks']['core/comment-content']['typography']['fontSize'], 'Blocks: font-size in random place' ); + $this->assertSame( $primary_color, $styles['blocks']['core/comment-content']['color']['text'], 'Blocks: text color with fallback' ); + $this->assertSame( $primary_color, $styles['blocks']['core/comment-content']['color']['background'], 'Blocks: background color with var as fallback' ); + $this->assertSame( $primary_color, $styles['blocks']['core/navigation']['elements']['link']['color']['background'], 'Block element: background color' ); + $this->assertSame( $secondary_color, $styles['blocks']['core/navigation']['elements']['link']['color']['text'], 'Block element: text color' ); + $this->assertSame( $large_font, $styles['blocks']['core/navigation']['elements']['link']['typography']['fontSize'], 'Block element: font-size' ); - $this->assertEquals( + $this->assertSame( "var(--undefined--color--primary, $small_font)", $styles['blocks']['core/comments']['color']['text'], 'Blocks: text color with undefined var and fallback' ); - $this->assertEquals( + $this->assertSame( $primary_color, $styles['blocks']['core/comments']['color']['background'], 'Blocks: background color with variable and undefined fallback' ); - $this->assertEquals( $small_font, $styles['blocks']['core/quote']['variations']['plain']['typography']['fontSize'], 'Block variations: font-size' ); - $this->assertEquals( $secondary_color, $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Block variations: color' ); + $this->assertSame( $small_font, $styles['blocks']['core/quote']['variations']['plain']['typography']['fontSize'], 'Block variations: font-size' ); + $this->assertSame( $secondary_color, $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Block variations: color' ); /* * As with wp_get_global_styles(), WP_Theme_JSON::resolve_variables may be called with merged data from * WP_Theme_JSON_Resolver. WP_Theme_JSON_Resolver::get_block_data() sets blockGap for supported blocks to `null` if the value is not defined. @@ -5359,7 +5759,7 @@ public function test_resolve_variables() { $styles['blocks']['core/post-template']['spacing']['blockGap'], 'Blocks: Post Template spacing.blockGap should be null' ); - $this->assertEquals( + $this->assertSame( $spacing, $styles['blocks']['core/columns']['spacing']['blockGap'], 'Blocks: Columns spacing.blockGap should match' @@ -5385,7 +5785,7 @@ public function test_get_block_style_variation_selector( $selector, $expected ) $actual = $func->invoke( null, 'custom', $selector ); - $this->assertEquals( $expected, $actual ); + $this->assertSame( $expected, $actual ); } /** @@ -5498,6 +5898,87 @@ public function test_scope_style_node_selectors() { ), ); - $this->assertEquals( $expected, $actual ); + $this->assertSame( $expected, $actual ); + } + + /** + * Block style variations styles aren't generated by default. This test covers + * the `get_block_nodes` does not include variations by default, preventing + * the inclusion of their styles. + * + * @ticket 61443 + */ + public function test_opt_out_of_block_style_variations_by_default() { + $theme_json = new ReflectionClass( 'WP_Theme_JSON' ); + + $func = $theme_json->getMethod( 'get_block_nodes' ); + $func->setAccessible( true ); + + $theme_json = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'variations' => array( + 'outline' => array( + 'color' => array( + 'background' => 'red', + ), + ), + ), + ), + ), + ), + ); + $selectors = array(); + + $block_nodes = $func->invoke( null, $theme_json, $selectors ); + $button_variations = $block_nodes[0]['variations'] ?? array(); + + $this->assertSame( array(), $button_variations ); + } + + /** + * Block style variations styles aren't generated by default. This test ensures + * variations are included by `get_block_nodes` when requested. + * + * @ticket 61443 + */ + public function test_opt_in_to_block_style_variations() { + $theme_json = new ReflectionClass( 'WP_Theme_JSON' ); + + $func = $theme_json->getMethod( 'get_block_nodes' ); + $func->setAccessible( true ); + + $theme_json = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'variations' => array( + 'outline' => array( + 'color' => array( + 'background' => 'red', + ), + ), + ), + ), + ), + ), + ); + $selectors = array(); + $options = array( 'include_block_style_variations' => true ); + + $block_nodes = $func->invoke( null, $theme_json, $selectors, $options ); + $button_variations = $block_nodes[0]['variations'] ?? array(); + + $expected = array( + array( + 'path' => array( 'styles', 'blocks', 'core/button', 'variations', 'outline' ), + 'selector' => '.wp-block-button.is-style-outline .wp-block-button__link', + ), + ); + + $this->assertSame( $expected, $button_variations ); } } diff --git a/tests/phpunit/tests/theme/wpThemeJsonResolver.php b/tests/phpunit/tests/theme/wpThemeJsonResolver.php index 0cb102b7accf4..4fb3784f1a41b 100644 --- a/tests/phpunit/tests/theme/wpThemeJsonResolver.php +++ b/tests/phpunit/tests/theme/wpThemeJsonResolver.php @@ -1257,6 +1257,7 @@ public function test_shadow_default_presets_value_for_block_and_classic_themes() * * @covers WP_Theme_JSON_Resolver::resolve_theme_file_uris * @ticket 61273 + * @ticket 61588 */ public function test_resolve_theme_file_uris() { $theme_json = new WP_Theme_JSON( @@ -1268,6 +1269,22 @@ public function test_resolve_theme_file_uris() { 'url' => 'file:./assets/image.png', ), ), + 'blocks' => array( + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./assets/quote.png', + ), + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./assets/verse.png', + ), + ), + ), + ), ), ) ); @@ -1280,6 +1297,22 @@ public function test_resolve_theme_file_uris() { 'url' => 'https://example.org/wp-content/themes/example-theme/assets/image.png', ), ), + 'blocks' => array( + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'https://example.org/wp-content/themes/example-theme/assets/quote.png', + ), + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'https://example.org/wp-content/themes/example-theme/assets/verse.png', + ), + ), + ), + ), ), ); @@ -1293,6 +1326,7 @@ public function test_resolve_theme_file_uris() { * * @covers WP_Theme_JSON_Resolver::get_resolved_theme_uris * @ticket 61273 + * @ticket 61588 */ public function test_get_resolved_theme_uris() { $theme_json = new WP_Theme_JSON( @@ -1304,6 +1338,22 @@ public function test_get_resolved_theme_uris() { 'url' => 'file:./assets/image.png', ), ), + 'blocks' => array( + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./assets/quote.jpg', + ), + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./assets/verse.gif', + ), + ), + ), + ), ), ) ); @@ -1315,10 +1365,113 @@ public function test_get_resolved_theme_uris() { 'target' => 'styles.background.backgroundImage.url', 'type' => 'image/png', ), + array( + 'name' => 'file:./assets/quote.jpg', + 'href' => 'https://example.org/wp-content/themes/example-theme/assets/quote.jpg', + 'target' => 'styles.blocks.core/quote.background.backgroundImage.url', + 'type' => 'image/jpeg', + ), + array( + 'name' => 'file:./assets/verse.gif', + 'href' => 'https://example.org/wp-content/themes/example-theme/assets/verse.gif', + 'target' => 'styles.blocks.core/verse.background.backgroundImage.url', + 'type' => 'image/gif', + ), ); $actual = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json ); $this->assertSame( $expected_data, $actual ); } + + /** + * Tests that block style variations data gets merged in the following + * priority order, from highest priority to lowest. + * + * - `styles.blocks.blockType.variations` from theme.json + * - `styles.variations` from theme.json + * - variations from block style variation files under `/styles` + * - variations from `WP_Block_Styles_Registry` + * + * @ticket 61451 + */ + public function test_block_style_variation_merge_order() { + switch_theme( 'block-theme-child-with-block-style-variations' ); + + /* + * Register style for a block that isn't included in the block style variation's partial + * theme.json's blockTypes. The name must match though so we can ensure the partial's + * styles do not get applied to this block. + */ + register_block_style( + 'core/heading', + array( + 'name' => 'block-style-variation-b', + 'label' => 'Heading only variation', + ) + ); + + // Register variation for a block that will be partially overridden at all levels. + register_block_style( + 'core/media-text', + array( + 'name' => 'block-style-variation-a', + 'label' => 'Block Style Variation A', + 'style_data' => array( + 'color' => array( + 'background' => 'pink', + 'gradient' => 'var(--custom)', + ), + ), + ) + ); + + $data = WP_Theme_JSON_Resolver::get_theme_data()->get_raw_data(); + $block_styles = $data['styles']['blocks'] ?? array(); + $actual = array_intersect_key( + $block_styles, + array_flip( array( 'core/button', 'core/media-text', 'core/heading' ) ) + ); + $expected = array( + 'core/button' => array( + 'variations' => array( + 'outline' => array( + 'color' => array( + 'background' => 'red', + 'text' => 'white', + ), + ), + ), + ), + 'core/media-text' => array( + 'variations' => array( + 'block-style-variation-a' => array( + 'color' => array( + 'background' => 'blue', + 'gradient' => 'var(--custom)', + 'text' => 'aliceblue', + ), + 'typography' => array( + 'fontSize' => '1.5em', + 'lineHeight' => '1.4em', + ), + ), + ), + ), + 'core/heading' => array( + 'variations' => array( + 'block-style-variation-b' => array( + 'typography' => array( + 'fontSize' => '3em', + ), + ), + ), + ), + ); + + unregister_block_style( 'core/heading', 'block-style-variation-b' ); + unregister_block_style( 'core/media-text', 'block-style-variation-a' ); + + $this->assertSameSetsWithIndex( $expected, $actual, 'Merged variation styles do not match.' ); + } } diff --git a/tests/phpunit/tests/user/queryCache.php b/tests/phpunit/tests/user/queryCache.php index 985c6d7da9f6d..0e708a55c4de8 100644 --- a/tests/phpunit/tests/user/queryCache.php +++ b/tests/phpunit/tests/user/queryCache.php @@ -388,7 +388,7 @@ public function test_query_cache_delete_user() { $this->assertSameSets( $expected, $found, 'Find author in returned values' ); - wp_delete_user( $user_id ); + self::delete_user( $user_id ); $q2 = new WP_User_Query( array( diff --git a/tests/phpunit/tests/xmlrpc/wp/getUser.php b/tests/phpunit/tests/xmlrpc/wp/getUser.php index dfeb1c8e2e226..391ec7157cee9 100644 --- a/tests/phpunit/tests/xmlrpc/wp/getUser.php +++ b/tests/phpunit/tests/xmlrpc/wp/getUser.php @@ -98,7 +98,7 @@ public function test_valid_user() { $this->assertSame( $user_data['user_login'], $result['username'] ); $this->assertContains( $user_data['role'], $result['roles'] ); - wp_delete_user( $user_id ); + self::delete_user( $user_id ); } public function test_no_fields() { diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 017a18ecaeb93..7e97f19b18588 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -13014,6 +13014,8 @@ mockedApiResponse.TypesCollection = { ], "rest_base": "posts", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13044,6 +13046,8 @@ mockedApiResponse.TypesCollection = { "taxonomies": [], "rest_base": "pages", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13074,6 +13078,8 @@ mockedApiResponse.TypesCollection = { "taxonomies": [], "rest_base": "media", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13106,6 +13112,8 @@ mockedApiResponse.TypesCollection = { ], "rest_base": "menu-items", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13138,6 +13146,8 @@ mockedApiResponse.TypesCollection = { ], "rest_base": "blocks", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13168,6 +13178,8 @@ mockedApiResponse.TypesCollection = { "taxonomies": [], "rest_base": "templates", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13198,6 +13210,8 @@ mockedApiResponse.TypesCollection = { "taxonomies": [], "rest_base": "template-parts", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13228,6 +13242,8 @@ mockedApiResponse.TypesCollection = { "taxonomies": [], "rest_base": "global-styles", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13258,6 +13274,8 @@ mockedApiResponse.TypesCollection = { "taxonomies": [], "rest_base": "navigation", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13288,6 +13306,8 @@ mockedApiResponse.TypesCollection = { "taxonomies": [], "rest_base": "font-families", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13318,6 +13338,8 @@ mockedApiResponse.TypesCollection = { "taxonomies": [], "rest_base": "font-families/(?P[\\d]+)/font-faces", "rest_namespace": "wp/v2", + "template": [], + "template_lock": false, "_links": { "collection": [ { @@ -13352,7 +13374,9 @@ mockedApiResponse.TypeModel = { "post_tag" ], "rest_base": "posts", - "rest_namespace": "wp/v2" + "rest_namespace": "wp/v2", + "template": [], + "template_lock": false }; mockedApiResponse.StatusesCollection = { @@ -13724,9 +13748,9 @@ mockedApiResponse.UsersCollection = [ "link": "http://example.org/?author=1", "slug": "admin", "avatar_urls": { - "24": "http://0.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=24&d=mm&r=g", - "48": "http://0.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=48&d=mm&r=g", - "96": "http://0.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=96&d=mm&r=g" + "24": "https://secure.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=24&d=mm&r=g", + "48": "https://secure.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=48&d=mm&r=g", + "96": "https://secure.gravatar.com/avatar/96614ec98aa0c0d2ee75796dced6df54?s=96&d=mm&r=g" }, "meta": { "meta_key": "meta_value" @@ -13752,9 +13776,9 @@ mockedApiResponse.UsersCollection = [ "link": "http://example.org/?author=2", "slug": "restapiclientfixtureuser", "avatar_urls": { - "24": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=24&d=mm&r=g", - "48": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=48&d=mm&r=g", - "96": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=96&d=mm&r=g" + "24": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=24&d=mm&r=g", + "48": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=48&d=mm&r=g", + "96": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=96&d=mm&r=g" }, "meta": { "meta_key": "" @@ -13782,9 +13806,9 @@ mockedApiResponse.UserModel = { "link": "http://example.org/?author=2", "slug": "restapiclientfixtureuser", "avatar_urls": { - "24": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=24&d=mm&r=g", - "48": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=48&d=mm&r=g", - "96": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=96&d=mm&r=g" + "24": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=24&d=mm&r=g", + "48": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=48&d=mm&r=g", + "96": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=96&d=mm&r=g" }, "meta": { "meta_key": "" @@ -13799,9 +13823,9 @@ mockedApiResponse.me = { "link": "http://example.org/?author=2", "slug": "restapiclientfixtureuser", "avatar_urls": { - "24": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=24&d=mm&r=g", - "48": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=48&d=mm&r=g", - "96": "http://2.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=96&d=mm&r=g" + "24": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=24&d=mm&r=g", + "48": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=48&d=mm&r=g", + "96": "https://secure.gravatar.com/avatar/57cbd982c963c7eb2294e2eee1b4448e?s=96&d=mm&r=g" }, "meta": { "meta_key": "" @@ -13825,9 +13849,9 @@ mockedApiResponse.CommentsCollection = [ "status": "approved", "type": "comment", "author_avatar_urls": { - "24": "http://2.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=24&d=mm&r=g", - "48": "http://2.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=48&d=mm&r=g", - "96": "http://2.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=96&d=mm&r=g" + "24": "https://secure.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=24&d=mm&r=g", + "48": "https://secure.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=48&d=mm&r=g", + "96": "https://secure.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=96&d=mm&r=g" }, "meta": { "meta_key": "meta_value" @@ -13870,9 +13894,9 @@ mockedApiResponse.CommentModel = { "status": "approved", "type": "comment", "author_avatar_urls": { - "24": "http://2.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=24&d=mm&r=g", - "48": "http://2.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=48&d=mm&r=g", - "96": "http://2.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=96&d=mm&r=g" + "24": "https://secure.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=24&d=mm&r=g", + "48": "https://secure.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=48&d=mm&r=g", + "96": "https://secure.gravatar.com/avatar/bd7c2b505bcf39cc71cfee564c614956?s=96&d=mm&r=g" }, "meta": { "meta_key": "meta_value" diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js index 17913885eb187..f431d43b682ee 100644 --- a/tools/webpack/packages.js +++ b/tools/webpack/packages.js @@ -93,9 +93,10 @@ module.exports = function ( 'wp-polyfill-object-fit.js': 'objectFitPolyfill/src/objectFitPolyfill.js', 'wp-polyfill-inert.js': 'wicg-inert/dist/inert.js', - 'wp-polyfill-importmap.js': 'es-module-shims/dist/es-module-shims.wasm.js', 'moment.js': 'moment/moment.js', 'regenerator-runtime.js': 'regenerator-runtime/runtime.js', + 'react.js': 'react/umd/react.development.js', + 'react-dom.js': 'react-dom/umd/react-dom.development.js', }; const minifiedVendors = { @@ -109,6 +110,8 @@ module.exports = function ( 'objectFitPolyfill/dist/objectFitPolyfill.min.js', 'wp-polyfill-inert.min.js': 'wicg-inert/dist/inert.min.js', 'moment.min.js': 'moment/min/moment.min.js', + 'react.min.js': 'react/umd/react.production.min.js', + 'react-dom.min.js': 'react-dom/umd/react-dom.production.min.js', }; const minifyVendors = { @@ -118,7 +121,6 @@ module.exports = function ( 'polyfill-library/polyfills/__dist/Node.prototype.contains/raw.js', 'wp-polyfill-dom-rect.min.js': 'polyfill-library/polyfills/__dist/DOMRect/raw.js', - 'wp-polyfill-importmap.min.js': 'es-module-shims/dist/es-module-shims.wasm.js', }; const phpFiles = { diff --git a/tools/webpack/shared.js b/tools/webpack/shared.js index ec12959351151..9cfd335176492 100644 --- a/tools/webpack/shared.js +++ b/tools/webpack/shared.js @@ -41,15 +41,6 @@ const getBaseConfig = ( env ) => { watch: env.watch, plugins: [ new DefinePlugin( { - /* - * These variables are part of https://github.com/WordPress/gutenberg/pull/61486 - * They're expected to be released in an upcoming version of Gutenberg. - * - * Defining this before the packages are released is harmless. - * - * @todo Remove the non-globalThis defines here when packages have been upgraded to the globalThis versions. - */ - // Inject the `IS_GUTENBERG_PLUGIN` global, used for feature flagging. 'globalThis.IS_GUTENBERG_PLUGIN': JSON.stringify( false ), // Inject the `IS_WORDPRESS_CORE` global, used for feature flagging. @@ -58,13 +49,6 @@ const getBaseConfig = ( env ) => { 'globalThis.SCRIPT_DEBUG': JSON.stringify( mode === 'development' ), - - // Inject the `IS_GUTENBERG_PLUGIN` global, used for feature flagging. - 'process.env.IS_GUTENBERG_PLUGIN': JSON.stringify( false ), - // Inject the `IS_WORDPRESS_CORE` global, used for feature flagging. - 'process.env.IS_WORDPRESS_CORE': JSON.stringify( true ), - // Inject the `SCRIPT_DEBUG` global, used for dev versions of JavaScript. - SCRIPT_DEBUG: JSON.stringify( mode === 'development' ), } ), ], }; diff --git a/tools/webpack/vendors.js b/tools/webpack/vendors.js index 4fc5bded7535c..1c1f0a187a176 100644 --- a/tools/webpack/vendors.js +++ b/tools/webpack/vendors.js @@ -4,8 +4,6 @@ const { join } = require( 'path' ); const importedVendors = { - react: { import: 'react', global: 'React' }, - 'react-dom': { import: 'react-dom', global: 'ReactDOM' }, 'react-jsx-runtime': { import: 'react/jsx-runtime', global: 'ReactJSXRuntime', @@ -21,7 +19,7 @@ module.exports = function ( : mode === 'production' ? 'build' : 'src'; - buildTarget = buildTarget + '/wp-includes/js/dist/vendor/'; + buildTarget = buildTarget + '/wp-includes/js/dist/vendor/'; return [ ...Object.entries( importedVendors ).flatMap( ( [ name, config ] ) => { return [ 'production', 'development' ].map( ( currentMode ) => { @@ -45,12 +43,9 @@ module.exports = function ( }, }, - externals: - name === 'react' - ? {} - : { - react: 'React', - }, + externals: { + react: 'React', + }, }; } ); } ),